File

src/app/simulation/simulation.service.ts

Description

Applies the traffic flow propagation algorithm.

Index

Properties
Methods

Constructor

constructor(store: Store, network: NetworkService, demand: DemandService)
Parameters :
Name Type Optional
store Store<fromSimulation.SimulationState> No
network NetworkService No
demand DemandService No

Methods

Private endLtm
endLtm()

The algorithm ends when all the vehicles are started and there is no more traffic volume on the links.

Returns : boolean
Private getAvgSpeed
getAvgSpeed()

The sum of the densities for the velocity square divided by the sum of the flows.

Returns : number
Private getCounts
getCounts()
Returns : Counts
Public getStatistics
getStatistics()

Extracts the network statistics.

Returns : any
Private getTotalAvgSpeed
getTotalAvgSpeed()
Returns : number
Public init
init()

Initializes the simulation.

Returns : Observable<any>
Private initEdges
initEdges()
Returns : void
Private initOdNodes
initOdNodes()
Returns : void
Private initPaths
initPaths()

Initializes existing paths.

Returns : void
Private initPathsDemand
initPathsDemand(odMatrix: number[], startingTimes: number[])

Initializes the demand for paths as the rate of the total demand.

Parameters :
Name Type Optional Description
odMatrix number[] No

The O/D matrix

startingTimes number[] No

The starting times of O/D pairs

Returns : void
Private initTimeInterval
initTimeInterval()

Initializes the time interval as the smallest connection travel time.

Returns : void
Private ltm
ltm()

Link Transmission Model algorithm.

Returns : void
Private numericalSimulation
numericalSimulation()
Public propagateFlows
propagateFlows()

Performs a flows propagation cycle.

Returns : void
Public reset
reset()
Returns : void
Public resetFlows
resetFlows()

Resets the initial values.

Returns : void
Private takeFirstStep
takeFirstStep(node: LtmNode)

Calculates sending and receiving flows.

Parameters :
Name Type Optional Description
node LtmNode No

The node on the paths

Returns : void
Private takeSecondStep
takeSecondStep(node: LtmNode)

Determines the transition flows from incoming links to outgoing links.

Parameters :
Name Type Optional Description
node LtmNode No

The node on the paths

Returns : void
Private takeThirdStep
takeThirdStep(node: LtmNode)

Updates the cumulative vehicles number.

Parameters :
Name Type Optional Description
node LtmNode No

The node on the paths

Returns : void
Private updateStatistics
updateStatistics()
Returns : void
Private updateTimePeriods
updateTimePeriods()
Returns : void

Properties

Private avgSpeeds
avgSpeeds: number[]
Type : number[]
Default value : []

The network average speed for each step.

Private graph
graph: LtmGraph
Type : LtmGraph

Ltm graph instance.

Private paths
paths: any[]
Type : any[]
Default value : []

Existing paths for each:

  • [path][origin][link]
  • [path][link][link]
  • [path][link][destination]
Private pathsDemand
pathsDemand: number[]
Type : number[]
Default value : []

Demand of each path.

Private pathsStartingTimes
pathsStartingTimes: number[]
Type : number[]
Default value : []

Starting time of each path.

Private timeInterval
timeInterval: number
Type : number

The time period is divided into time intervals.

Private timePeriods
timePeriods: number[]
Type : number[]
Default value : []

The cumulated time period.

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';

import { Store } from '@ngrx/store';

import { NetworkService } from '../network/network.service';
import { DemandService } from '../demand/demand.service';
import * as fromSimulation from './models/reducers';
import { SimulationActionTypes } from './models/actions/simulation.actions';
import { Tag } from '../network/graph';
import { LtmGraph, LtmEdge, LtmNode } from './ltm-graph';
import { NumericalSimulation, Counts } from './models/simulation-state';
import { Statistics } from './statistics';
import { round } from '../utils';
import { uiConfig } from '../ui/ui-config';

/**
 * Applies the traffic flow propagation algorithm.
 */
@Injectable() export class SimulationService {

    /**
     * Ltm graph instance.
     */
    private graph: LtmGraph;

    /**
     * The cumulated time period.
     */
    private timePeriods: number[] = [];

    /**
     * The time period is divided into time intervals.
     */
    private timeInterval: number;

    /**
     * Existing paths for each:
     * - [path][origin][link]
     * - [path][link][link]
     * - [path][link][destination]
     */
    private paths: any[] = [];

    /**
     * Demand of each path.
     */
    private pathsDemand: number[] = [];

    /**
     * Starting time of each path.
     */
    private pathsStartingTimes: number[] = [];

    /**
     * The network average speed for each step.
     */
    private avgSpeeds: number[] = [];

    constructor(
        private store: Store<fromSimulation.SimulationState>,
        private network: NetworkService,
        private demand: DemandService
    ) { }

    public reset(): void {
        this.graph = null;
        this.timePeriods = [];
        this.timeInterval = 0;
        this.paths = [];
        this.pathsDemand = [];
        this.pathsStartingTimes = [];
        this.avgSpeeds = [];
        // Simulation state.
        this.store.dispatch({
            type: SimulationActionTypes.Reset
        });
    }

    /**
     * Initializes the simulation.
     */
    public init(): Observable<any> {
        // Gets graph from network.
        const graph = this.network.getGraph();
        // Gets O/D matrix from demand.
        const demand = this.demand.getOdMatrix();
        // Gets starting times from demand.
        const startingTimes = this.demand.getStartingTimes();
        // Instances LTM graph from graph.
        this.graph = new LtmGraph(graph);
        // Sets the time period.
        this.timePeriods[0] = 0;
        // Initializes time interval.
        this.initTimeInterval();
        // Initializes existing paths.
        this.initPaths();
        // Initializes paths demand.
        this.initPathsDemand(demand, startingTimes);
        // Initializes O/D nodes expected flows.
        this.initOdNodes();
        // Initializes edges upstream and downstream.
        this.initEdges();
        // Updates simulation state.
        this.store.dispatch({
            type: SimulationActionTypes.PeriodsChanged,
            payload: { timeInterval: this.timeInterval, timePeriods: this.timePeriods }
        });
        return of(null);
    }

    /**
     * Performs a flows propagation cycle.
     */
    public propagateFlows(): void {
        // Performs a LTM algorithm cycle.
        this.ltm();
        // Updates statistics.
        this.updateStatistics();
        // Updates time period.
        this.updateTimePeriods();
        // Updates simulation state.
        this.store.dispatch({
            type: SimulationActionTypes.PeriodsChanged,
            payload: { timeInterval: this.timeInterval, timePeriods: this.timePeriods }
        });
        this.store.dispatch({
            type: SimulationActionTypes.SimulationChanged,
            payload: { simulation: { data: this.numericalSimulation(), counts: this.getCounts(), avgSpeed: this.getAvgSpeed() } }
        });
        // Checks if the simulation is finished.
        if (this.endLtm()) {
            // Updates simulation state.
            this.store.dispatch({
                type: SimulationActionTypes.SimulationEnded,
                payload: true
            });
        }
    }

    /**
     * Resets the initial values.
     */
    public resetFlows(): void {
        // Resets.
        this.graph.reset();
        this.timePeriods = [];
        this.avgSpeeds = [];
        // Reinitializes.
        this.timePeriods[0] = 0;
        this.initOdNodes();
        this.initEdges();
        // Updates simulation state.
        this.store.dispatch({
            type: SimulationActionTypes.PeriodsChanged,
            payload: { timeInterval: this.timeInterval, timePeriods: this.timePeriods }
        });
        this.store.dispatch({
            type: SimulationActionTypes.SimulationChanged,
            payload: { simulation: { data: [], counts: {}, avgSpeed: null } }
        });
    }

    /**
     * Extracts the network statistics.
     */
    public getStatistics(): any {
        const edges = this.graph.getEdges().filter((edge: LtmEdge) =>
            edge.distance > uiConfig.minDistance && edge.duration > uiConfig.minDuration
        );

        return {
            totalTime: this.timePeriods[this.timePeriods.length - 1],
            totalAvgSpeed: this.getTotalAvgSpeed(),
            heavyTrafficLabels: Statistics.getHeavyTrafficLabels(edges),
            moderateTrafficLabels: Statistics.getModerateTrafficLabels(edges),
            heavyTrafficData: Statistics.getHeavyTrafficData(edges, this.timeInterval),
            moderateTrafficData: Statistics.getModerateyTrafficData(edges, this.timeInterval),
            busiestEdgeLabel: Statistics.getBusiestEdgeLabel(edges),
            busiestEdgeData: Statistics.getBusiestEdgeData(edges),
            busiestEdgeCapacity: Statistics.getBusiestEdgeCapacity(edges),
            busiestEdgeDelay: Statistics.getBusiestEdgeDelay(edges, this.timePeriods),
            periods: this.timePeriods
        };
    }

    /**
     * Link Transmission Model algorithm.
     */
    private ltm(): void {
        const nodes = this.graph.getNodes();
        // For each node on the paths.
        for (const node of nodes) {
            this.takeFirstStep(node);
            this.takeSecondStep(node);
            this.takeThirdStep(node);
        }
    }

    /**
     * Calculates sending and receiving flows.
     * @param node The node on the paths
     */
    private takeFirstStep(node: LtmNode): void {
        // Sending flows.
        for (const edge of node.incomingEdges) {
            edge.calcSendingFlows(this.timePeriods, this.timeInterval, this.paths);
        }
        // Receiving flows.
        for (const edge of node.outgoingEdges) {
            edge.calcReceivingFlow(this.timePeriods, this.timeInterval);
        }
    }

    /**
     * Determines the transition flows from incoming links to outgoing links.
     * @param node The node on the paths
     */
    private takeSecondStep(node: LtmNode): void {
        node.calcTransitionFlows(this.timePeriods, this.timeInterval, this.paths);
    }

    /**
     * Updates the cumulative vehicles number.
     * @param node The node on the paths
     */
    private takeThirdStep(node: LtmNode): void {
        node.updateCumulativeFlows(this.paths);
    }

    /**
     * The algorithm ends when all the vehicles are started
     * and there is no more traffic volume on the links.
     */
    private endLtm(): boolean {
        const originNodes = this.graph.getOriginNodes();
        const edges = this.graph.getEdges();
        const started = originNodes.filter((node: LtmNode) =>
            node.origin.sendingFlow == node.origin.expectedInflow
        ).length == originNodes.length;
        const busy = edges.filter(
            (edge: LtmEdge) =>
                edge.trafficVolume > 0
        ).length > 0;
        return started && !busy;
    }

    /**
     * Initializes the time interval as the smallest connection travel time.
     */
    private initTimeInterval(): void {
        const edges = this.graph.getEdges();
        const edge = edges.reduce((prev: LtmEdge, curr: LtmEdge) => prev.duration < curr.duration ? prev : curr);
        if (edge.duration > uiConfig.maxTimeInterval) {
            this.timeInterval = uiConfig.maxTimeInterval;
        } else {
            this.timeInterval = edge.duration;
        }
    }

    /**
     * Initializes existing paths.
     */
    private initPaths(): void {
        const shortestPaths = this.graph.getShortestPaths();

        let i = 0;
        for (let z = 0; z < shortestPaths.length; z++) {
            for (let n = 0; n < shortestPaths[z].length; n++) {
                this.paths[i] = {};
                for (let m = 0; m < shortestPaths[z][n].length; m++) {
                    const edge = shortestPaths[z][n][m];
                    if (m == 0) {
                        this.paths[i][edge.origin.label] = edge.label;
                    }
                    if (m < shortestPaths[z][n].length - 1) {
                        this.paths[i][edge.label] = shortestPaths[z][n][m + 1].label;
                    }
                    if (m == shortestPaths[z][n].length - 1) {
                        this.paths[i][edge.label] = edge.destination.label;
                    }
                }
                i++;
            }
        }
    }

    /**
     * Initializes the demand for paths as the rate of the total demand.
     * @param odMatrix The O/D matrix
     * @param startingTimes The starting times of O/D pairs
     */
    private initPathsDemand(odMatrix: number[], startingTimes: number[]): void {
        const assignmentMatrix = this.graph.getAssignmentMatrix();

        let i = 0;
        for (let z = 0; z < assignmentMatrix.length; z++) {
            if (odMatrix[z] != null) {
                const pos = i;

                let sum = 0;
                for (let n = 0; n < assignmentMatrix[z].length; n++) {
                    const p = assignmentMatrix[z][n].find(value => value > 0) || 0;
                    this.pathsDemand[i] = round(p * odMatrix[z]);
                    this.pathsStartingTimes[i] = startingTimes[z];

                    sum += this.pathsDemand[i];
                    i++;
                }
                if (odMatrix[z] - sum > 0) { this.pathsDemand[pos] = odMatrix[z] - sum; }
            }
        }
    }

    private initOdNodes(): void {
        const shortestPaths = this.graph.getShortestPaths();

        let i = 0;
        for (let z = 0; z < shortestPaths.length; z++) {
            for (let n = 0; n < shortestPaths[z].length; n++) {
                const origin = shortestPaths[z][n][0].origin;
                const destination = shortestPaths[z][n][shortestPaths[z][n].length - 1].destination;
                if (!origin.origin) {
                    origin.origin = {
                        sendingFlow: 0,
                        expectedInflows: [],
                        expectedInflow: 0,
                        startingTimes: []
                    };
                }
                origin.origin.expectedInflows[i] = this.pathsDemand[i];
                origin.origin.expectedInflow += this.pathsDemand[i];
                origin.origin.startingTimes[i] = this.pathsStartingTimes[i];
                if (!destination.destination) {
                    destination.destination = {
                        receivingFlow: 0,
                        expectedOutFlow: 0
                    };
                }
                destination.destination.expectedOutFlow += this.pathsDemand[i];
                i++;
            }
        }
    }

    private initEdges(): void {
        const edges = this.graph.getEdges();
        for (const edge of edges) {
            for (let i = 0; i < this.paths.length; i++) {
                edge.upstreams[i] = [];
                edge.upstreams[i][0] = 0;
                edge.upstream[0] = 0;
                edge.downstreams[i] = [];
                edge.downstreams[i][0] = 0;
                edge.downstream[0] = 0;
            }
        }
    }

    private updateStatistics(): void {
        const edges = this.graph.getEdges();
        for (const edge of edges) {
            edge.updateTrafficVolume();
            edge.updateTrafficCounts();
            edge.updateMoes(this.timeInterval);
        }
    }

    private updateTimePeriods(): void {
        this.timePeriods.push(this.timePeriods[this.timePeriods.length - 1] + this.timeInterval);
    }

    private numericalSimulation(): NumericalSimulation[] {
        const data: NumericalSimulation[] = [];

        const edges = this.graph.getEdges();
        for (const edge of edges) {
            const wayTag = edge.tags.find((tag: Tag) => tag.key == 'name');
            const wayName = wayTag ? wayTag.value : '';
            data.push(
                {
                    edge: edge.label,
                    wayName: wayName,
                    trafficVolume: edge.trafficVolume,
                    trafficCount: edge.trafficCount,
                    delay: edge.delay,
                    stops: edge.stops
                }
            );
        }
        return data;
    }

    private getCounts(): Counts {
        let startedVehicles = 0;
        let arrivedVehicles = 0;

        const nodes = this.graph.getNodes();
        for (const node of nodes) {
            if (node.origin) {
                startedVehicles += node.origin.sendingFlow;
            }
            if (node.destination) {
                arrivedVehicles += node.destination.receivingFlow;
            }
        }
        return {
            startedVehicles: startedVehicles,
            arrivedVehicles: arrivedVehicles
        };
    }

    /**
     * The sum of the densities for the velocity square divided by the sum of the flows.
     */
    private getAvgSpeed(): number {
        const edges = this.graph.getEdges();

        let sum = 0;
        let flow = 0;
        for (const edge of edges) {
            sum += edge.velocity > 0 ?
                (edge.getKjam() * (edge.freeFlowVelocity - edge.velocity) / edge.freeFlowVelocity) * Math.pow(edge.velocity, 2)
                : 0;
            flow += edge.velocity > 0 ?
                (edge.getKjam() * (edge.freeFlowVelocity - edge.velocity) / edge.freeFlowVelocity) * edge.velocity
                : 0;
        }
        const speed = flow > 0 ? round(sum / flow, 2) : 0;
        this.avgSpeeds.push(speed);
        return speed;
    }

    private getTotalAvgSpeed(): number {
        const sum = this.avgSpeeds.reduce((a, b) => a + b, 0);
        return round(sum / this.avgSpeeds.length, 2);
    }

}

result-matching ""

    No results matching ""