1# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.org/sumo
2# Copyright (C) 2013-2019 German Aerospace Center (DLR) and others.
3# This program and the accompanying materials
4# are made available under the terms of the Eclipse Public License v2.0
5# which accompanies this distribution, and is available at
6# http://www.eclipse.org/legal/epl-v20.html
7# SPDX-License-Identifier: EPL-2.0
8
9# @file    demand.py
10# @author  Daniel Krajzewicz
11# @date    2013-10-10
12# @version $Id$
13
14from __future__ import absolute_import
15from __future__ import print_function
16import random
17import sumolib
18import os
19import subprocess
20import math
21import tempfile
22
23
24PIVOT__PEAK = 10000
25
26
27class LinearChange:
28
29    def __init__(self, beginFlow, endFlow, beginTime, endTime):
30        self.beginFlow = beginFlow / 3600.
31        self.endFlow = endFlow / 3600.
32        self.beginTime = beginTime
33        self.endTime = endTime
34
35    def depart(self, t):
36        return random.random() < (
37            self.beginFlow + (self.endFlow - self.beginFlow) / (self.endTime - self.beginTime) * (t - self.beginTime))
38
39
40class WaveComposition:
41
42    def __init__(self, offset, curves):
43        self.offset = offset
44        self.curves = curves
45
46    def depart(self, t):
47        v = self.offset
48        for c in self.curves:
49            dt = t - c[3]
50            v = v + \
51                c[0] * math.sin(2 * math.pi * dt * c[2]) + \
52                c[1] * math.cos(2 * math.pi * dt * c[2])
53        v = v / 3600.
54        return random.random() < v
55
56
57class Vehicle:
58
59    def __init__(self, id, depart, fromEdge, toEdge, vType, via=None):
60        self.id = id
61        self.depart = depart
62        self.fromEdge = fromEdge
63        self.toEdge = toEdge
64        self.vType = vType
65        self._via = via
66
67
68class Stream:
69
70    def __init__(self, sid, validFrom, validUntil, numberModel,
71                 departEdgeModel, arrivalEdgeModel, vTypeModel, via=None):
72        self.sid = sid
73        self._numberModel = numberModel
74        self._departEdgeModel = departEdgeModel
75        self._arrivalEdgeModel = arrivalEdgeModel
76        self._vTypeModel = vTypeModel
77        self._validFrom = validFrom
78        self._validUntil = validUntil
79        self._via = via
80
81    def getVehicleDepartures(self, b, e, sampleFactor=None, seenRatio=None):
82        if self._validFrom is not None and self._validUntil is not None and (
83                e < self._validFrom or b > self._validUntil):
84            return []
85        ret = []
86        for i in range(b, e):
87            if self._validFrom is not None and self._validUntil is not None and (
88                    i < self._validFrom or i > self._validUntil):
89                continue
90            depart = i
91            if sampleFactor is not None:
92                off = i % (sampleFactor * 24)
93                if not off < sampleFactor:
94                    continue
95                depart = sampleFactor * int(i / (24 * sampleFactor)) + off
96            if isinstance(self._numberModel, int) or isinstance(self._numberModel, float):
97                if random.random() < float(self._numberModel) / 3600.:
98                    ret.append(depart)
99            elif self._numberModel.depart(i):
100                ret.append(depart)
101        return ret
102
103    def getFrom(self, what, i, number):
104        if isinstance(what, str):
105            return what
106        if isinstance(what, int):
107            return what
108        if isinstance(what, float):
109            return what
110        if isinstance(what, list):
111            return what[i % len(what)]
112        if isinstance(what, dict):
113            r = random.random()
114            s = 0
115            for k in what:
116                s = s + what[k]
117                if s > r:
118                    return k
119            return None
120        return what.get()
121
122    def toVehicles(self, b, e, offset=0, sampleFactor=None, seenRatio=None):
123        vehicles = []
124        departures = self.getVehicleDepartures(b, e, sampleFactor, seenRatio)
125        number = len(departures)
126        for i, d in enumerate(departures):
127            fromEdge = self.getFrom(self._departEdgeModel, i, number)
128            toEdge = self.getFrom(self._arrivalEdgeModel, i, number)
129            vType = self.getFrom(self._vTypeModel, i, number)
130            sid = self.sid
131            if sid is None:
132                sid = fromEdge + "_to_" + toEdge + "_" + str(i)
133            vehicles.append(
134                Vehicle(sid + "#" + str(i + offset), int(d), fromEdge, toEdge, vType, self._via))
135        return vehicles
136
137
138class Demand:
139
140    def __init__(self):
141        self.streams = []
142
143    def addStream(self, s):
144        self.streams.append(s)
145
146    def build(self, b, e, netName="net.net.xml", routesName="input_routes.rou.xml", sampleFactor=None):
147        vehicles = []
148        for s in self.streams:
149            vehicles.extend(s.toVehicles(b, e, len(vehicles), sampleFactor))
150        fdo = tempfile.NamedTemporaryFile(mode="w", delete=False)
151        fdo.write("<routes>\n")
152        for v in sorted(vehicles, key=lambda veh: veh.depart):
153            via = ""
154            if v._via is not None:
155                via = ' via="%s"' % v._via
156            if v.vType == "pedestrian":
157                fdo.write('    <person id="%s" depart="%s" type="pedestrian"><walk from="%s" to="%s"/></person>\n' %
158                          (v.id, v.depart, v.fromEdge, v.toEdge))
159            else:
160                fdo.write('    <trip id="%s" depart="%s" from="%s" to="%s" type="%s" %s/>\n' %
161                          (v.id, v.depart, v.fromEdge, v.toEdge, v.vType, via))
162        fdo.write("</routes>")
163        fdo.close()
164        duarouter = sumolib.checkBinary("duarouter")
165        print("netName > %s" % netName)
166        print("routesName > %s" % routesName)
167        # aeh, implicitly setting --no-warnings is not nice, is it?; and the
168        # need to dump generated vtypes to a temporary file as well
169        subprocess.call([duarouter, "-v", "-n", netName, "-t", fdo.name, "-o", routesName,
170                         "--no-warnings", "--additional-files", "vtypes.add.xml", "--vtype-output", "tmp.add.xml"])
171        os.remove(fdo.name)
172