Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/route/scaleTimeLine.py
169673 views
1
#!/usr/bin/env python
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2010-2025 German Aerospace Center (DLR) and others.
4
# This program and the accompanying materials are made available under the
5
# terms of the Eclipse Public License 2.0 which is available at
6
# https://www.eclipse.org/legal/epl-2.0/
7
# This Source Code may also be made available under the following Secondary
8
# Licenses when the conditions for such availability set forth in the Eclipse
9
# Public License 2.0 are satisfied: GNU General Public License, version 2
10
# or later which is available at
11
# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13
14
# @file scaleTimeLine.py
15
# @author Yun-Pang Floetteroed
16
# @author Jakob Erdmann
17
# @date 2022-04-19
18
19
"""
20
- write a new route file with fewer or more vehicles/flows depending on
21
the given percentages and the sorted route/trip file(s)
22
23
- if more than one route file are given, all outputs will be stored in
24
one output file. It may need to sort this file by departure time.
25
"""
26
from __future__ import print_function
27
from __future__ import absolute_import
28
import os
29
import sys
30
import copy
31
import random
32
33
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
34
from sumolib.miscutils import parseTime, getFlowNumber, intIfPossible # noqa
35
import sumolib # noqa
36
37
38
def get_options(args=None):
39
ap = sumolib.options.ArgumentParser()
40
ap.add_argument("-r", "--route-files", dest="routefiles", category="input", type=ap.file_list,
41
required=True, help="define the route file separated by comma (mandatory)")
42
ap.add_argument("-o", "--output-file", dest="outfile", category="output", type=ap.file,
43
help="define the output filename")
44
ap.add_argument("--timeline-list", dest="timelinelist", type=str,
45
# TGw2_PKW from https://sumo.dlr.de/docs/Demand/Importing_O/D_Matrices.html#daily_time_lines
46
# multiplied by 10 (suitable for using with peak-hour-traffic and tools/route/route_1htoday.py
47
default="3600,8,5,4,3,4,12,45,74,66,52,50,50,52,53,56,67,84,86,74,50,39,30,21,16",
48
help="Define the interval duration and then the scaled percentage for each interval; "
49
"e.g. 200 percent of the current demand")
50
ap.add_argument("--timeline-pair", dest="timelinepair", type=str,
51
help="Define the timeline pairs (duration, scacled percentage)")
52
ap.add_argument("--random", action="store_true", dest="random", category="random",
53
default=False, help="use a random seed to initialize the random number generator")
54
ap.add_argument("-s", "--seed", type=int, dest="seed", category="random", default=42, help="random seed")
55
ap.add_argument("-v", "--verbose", dest="verbose", action="store_true",
56
default=False, help="tell me what you are doing")
57
options = ap.parse_args(args=args)
58
59
if options.timelinepair:
60
pairs = [x.split(',') for x in options.timelinepair.split(';')]
61
options.timelinelist = []
62
for d, s in pairs:
63
options.timelinelist.append([float(d), float(s)])
64
else:
65
duration = float(options.timelinelist.split(",")[0])
66
options.timelinelist = [[duration, float(i)] for i in options.timelinelist.split(",")[1:]]
67
68
options.routefiles = options.routefiles.split(',')
69
if not options.outfile:
70
options.outfile = options.routefiles[0][:-4] + "_scaled.rou.xml"
71
return options
72
73
74
def getScaledObjList(periodMap, periodList, currIndex, candidatsList, idMap):
75
scale = periodMap[periodList[currIndex]]/100.
76
sampleSize = int((scale - 1.) * len(candidatsList))
77
selectedList = random.choices(candidatsList, k=abs(sampleSize))
78
selectedList = [copy.deepcopy(o) for o in selectedList]
79
if scale >= 1.:
80
selectedList, idMap = changeIDs(selectedList, idMap)
81
totalList = candidatsList + selectedList
82
else:
83
idList = [i.id for i in selectedList]
84
totalList = [i for i in candidatsList if i.id not in idList]
85
86
totalList = sorted(totalList, key=lambda simobject: simobject.depart)
87
88
return totalList, idMap
89
90
91
def changeIDs(selectedList, idMap):
92
selectedList = sorted(selectedList, key=lambda simobject: simobject.id)
93
for obj in selectedList:
94
# get last cloneId
95
if idMap[obj.id]:
96
cloneIdx = idMap[obj.id] + 1
97
else:
98
cloneIdx = 1
99
idMap[obj.id] = cloneIdx
100
obj.id = obj.id + '#' + str(cloneIdx) # e.g. '123' --> '123#1' --> '123#1#1'
101
102
return selectedList, idMap
103
104
105
def getScale(depart, periodList, periodMap):
106
scale = 1.
107
for i, p in enumerate(periodList):
108
if i == 0 and depart < p:
109
scale = periodMap[p] / 100.
110
elif depart < p and depart >= periodList[i - 1]:
111
scale = periodMap[p] / 100.
112
113
return scale
114
115
116
def writeObjs(totalList, outf):
117
for elem in totalList:
118
outf.write(elem.toXML(' '*4))
119
120
121
def scaleRoutes(options, outf):
122
periodMap = {}
123
accPeriod = 0
124
periodList = []
125
idMap = {}
126
for duration, scale in options.timelinelist:
127
accPeriod += duration
128
periodList.append(accPeriod)
129
periodMap[accPeriod] = scale
130
131
# get all ids
132
for routefile in options.routefiles:
133
for elem in sumolib.xml.parse(routefile, ['vehicle', 'trip', 'flow', 'person', 'personFlow', 'vType']):
134
idMap[elem.id] = None
135
136
# scale the number of objs for each pre-defined interval
137
for routefile in options.routefiles:
138
lastDepart = 0
139
currIndex = 0
140
candidatsList = []
141
periodBegin = 0
142
periodEnd = periodList[currIndex]
143
for elem in sumolib.xml.parse(routefile, ['vehicle', 'trip', 'flow', 'person', 'personFlow', 'vType']):
144
if elem.name == 'vType':
145
outf.write(elem.toXML(' ' * 4))
146
elif elem.name in ['flow', 'personFlow']:
147
begin = parseTime(elem.begin)
148
if begin < lastDepart:
149
sys.stderr.write("Unsorted departure %s for %s '%s'" % (
150
begin, elem.tag, elem.id))
151
lastDepart = begin
152
scale = getScale(begin, periodList, periodMap)
153
if elem.hasAttribute("number"):
154
elem.number = str(int(getFlowNumber(elem) * scale))
155
elif elem.hasAttribute("period"):
156
if "exp" in elem.period:
157
rate = float(elem.period[4:-2])
158
elem.period = 'exp(%s)' % rate * scale
159
else:
160
elem.period = float(elem.period) / scale
161
outf.write(elem.toXML(' ' * 4))
162
else:
163
depart = parseTime(elem.depart)
164
elem.depart = intIfPossible(depart)
165
if depart < lastDepart:
166
sys.stderr.write("Unsorted departure %s for %s '%s'" % (
167
depart, elem.tag, elem.id))
168
lastDepart = depart
169
if depart >= periodBegin and depart < periodEnd:
170
candidatsList.append(elem)
171
else:
172
# write old period and start new period
173
if candidatsList:
174
totalList, idMap = getScaledObjList(periodMap, periodList, currIndex, candidatsList, idMap)
175
writeObjs(totalList, outf)
176
candidatsList.clear()
177
while currIndex + 1 < len(periodList):
178
currIndex += 1
179
periodBegin = periodEnd
180
periodEnd = periodList[currIndex]
181
if depart >= periodBegin and depart < periodEnd:
182
candidatsList.append(elem)
183
break
184
if candidatsList:
185
totalList, idMap = getScaledObjList(periodMap, periodList, currIndex, candidatsList, idMap)
186
writeObjs(totalList, outf)
187
188
189
def main(options):
190
if not options.random:
191
random.seed(options.seed)
192
with open(options.outfile, 'w') as outf:
193
sumolib.writeXMLHeader(outf, "$Id$", "routes", options=options) # noqa
194
scaleRoutes(options, outf)
195
outf.write('</routes>\n')
196
outf.close()
197
198
199
if __name__ == "__main__":
200
main(get_options(sys.argv[1:]))
201
202