Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/route/route2OD.py
193724 views
1
#!/usr/bin/env python
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2010-2026 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 route2OD.py
15
# @author Jakob Erdmann
16
# @date 2021-09-09
17
18
# Given a route file
19
# - a taz-based OD xml-file and/or
20
# - a edge-based OD xml-file (optional)
21
# will be generated.
22
#
23
24
from __future__ import print_function
25
from __future__ import absolute_import
26
import os
27
import sys
28
import random
29
from collections import defaultdict
30
31
if 'SUMO_HOME' in os.environ:
32
sys.path.append(os.path.join(os.environ['SUMO_HOME'], 'tools'))
33
import sumolib # noqa
34
from sumolib.miscutils import parseTime # noqa
35
36
37
def get_options(args=None):
38
optParser = sumolib.options.ArgumentParser(description="Add fromTaz and toTaz to a route file")
39
optParser.add_argument("-r", "--route-file", dest="routefile",
40
help="define the input route file (mandatory)")
41
optParser.add_argument("-a", "--taz-files", dest="tazfiles",
42
help="define the files to load TAZ (districts); "
43
"it is mandatory when generating taz-based OD file")
44
optParser.add_argument("-o", "--output-file", dest="outfile",
45
help="define the output filename (mandatory)")
46
optParser.add_argument("-i", "--interval",
47
help="define the output aggregation interval")
48
optParser.add_argument("--id", default="DEFAULT_VEHTYPE", dest="intervalID",
49
help="define the output aggregation interval")
50
optParser.add_argument("-s", "--seed", type=int, default=42, help="random seed")
51
optParser.add_argument("--edge-relations", action="store_true", dest="edgeod", default=False,
52
help="generate edgeRelations instead of tazRelations")
53
54
options = optParser.parse_args(args=args)
55
if not options.routefile or not options.outfile or (not options.edgeod and not options.tazfiles):
56
optParser.print_help()
57
sys.exit(1)
58
59
if options.interval is not None:
60
options.interval = parseTime(options.interval)
61
62
if not options.edgeod:
63
options.tazfiles = options.tazfiles.split()
64
if len(options.tazfiles) == 1:
65
options.tazfiles = options.tazfiles[0].split(',')
66
67
return options
68
69
70
def main(options):
71
random.seed(options.seed)
72
if not options.edgeod:
73
edgeFromTaz = defaultdict(list)
74
edgeToTaz = defaultdict(list)
75
numTaz = 0
76
for tazfile in options.tazfiles:
77
for taz in sumolib.xml.parse(tazfile, 'taz'):
78
numTaz += 1
79
if taz.edges:
80
for edge in taz.edges.split():
81
edgeFromTaz[edge].append(taz.id)
82
edgeToTaz[edge].append(taz.id)
83
if taz.tazSource:
84
for source in taz.tazSource:
85
edgeFromTaz[source.id].append(taz.id)
86
if taz.tazSink:
87
for sink in taz.tazSink:
88
edgeToTaz[sink.id].append(taz.id)
89
90
ambiguousSource = []
91
ambiguousSink = []
92
for edge, tazs in edgeFromTaz.items():
93
if len(tazs) > 1:
94
ambiguousSource.append(edge)
95
for edge, tazs in edgeToTaz.items():
96
if len(tazs) > 1:
97
ambiguousSink.append(edge)
98
99
print("read %s TAZ" % numTaz)
100
101
if len(ambiguousSource) > 0:
102
print("edges %s (total %s) are sources for more than one TAZ" %
103
(ambiguousSource[:5], len(ambiguousSource)))
104
if len(ambiguousSink) > 0:
105
print("edges %s (total %s) are sinks for more than one TAZ" %
106
(ambiguousSink[:5], len(ambiguousSink)))
107
108
class nl: # nonlocal integral variables
109
numFromNotFound = 0
110
numToNotFound = 0
111
numVehicles = 0
112
end = 0
113
114
# begin -> od -> count
115
intervals = defaultdict(lambda: defaultdict(lambda: 0))
116
intervals_edge = defaultdict(lambda: defaultdict(lambda: 0)) # for options.edgeod
117
118
def addVehicle(vehID, fromEdge, toEdge, time, count=1, isTaz=False):
119
nl.numVehicles += count
120
if options.interval is None:
121
intervalBegin = 0
122
else:
123
intervalBegin = int(time / options.interval) * options.interval
124
125
if options.edgeod and not isTaz:
126
intervals_edge[intervalBegin][(fromEdge, toEdge)] += count
127
else:
128
fromTaz = None
129
toTaz = None
130
if isTaz:
131
fromTaz = fromEdge
132
toTaz = toEdge
133
else:
134
if fromEdge in edgeFromTaz:
135
fromTaz = random.choice(edgeFromTaz[fromEdge])
136
if toEdge in edgeToTaz:
137
toTaz = random.choice(edgeToTaz[toEdge])
138
139
if fromTaz and toTaz:
140
intervals[intervalBegin][(fromTaz, toTaz)] += count
141
else:
142
if fromTaz is None:
143
nl.numFromNotFound += 1
144
if nl.numFromNotFound < 5:
145
if isTaz:
146
print("No fromTaz found for vehicle '%s' " % (vehID))
147
else:
148
print("No fromTaz found for edge '%s' of vehicle '%s' " % (fromEdge, vehID))
149
if toTaz is None:
150
nl.numToNotFound += 1
151
if nl.numToNotFound < 5:
152
if isTaz:
153
print("No toTaz found for vehicle '%s' " % (vehID))
154
else:
155
print("No toTaz found for edge '%s' of vehicle '%s' " % (toEdge, vehID))
156
157
nl.end = max(nl.end, time)
158
159
for vehicle in sumolib.xml.parse(options.routefile, ['vehicle']):
160
if vehicle.route and isinstance(vehicle.route, list):
161
edges = vehicle.route[0].edges.split()
162
addVehicle(vehicle.id, edges[0], edges[-1], parseTime(vehicle.depart))
163
else:
164
print("No edges found for vehicle '%s'" % vehicle.id)
165
166
for trip in sumolib.xml.parse(options.routefile, ['trip']):
167
if trip.attr_from and trip.to:
168
addVehicle(trip.id, trip.attr_from, trip.to, parseTime(trip.depart))
169
elif trip.fromTaz and trip.toTaz:
170
if options.edgeod:
171
print("No OD-edge information! Only TAZ-based OD counts with a given district file can be calculated.")
172
sys.exit(1)
173
else:
174
addVehicle(trip.id, trip.fromTaz, trip.toTaz, parseTime(trip.depart), 1, True)
175
176
for flow in sumolib.xml.parse(options.routefile, ['flow']):
177
count = None
178
if flow.number:
179
count = int(flow.number)
180
else:
181
time = parseTime(flow.end) - parseTime(flow.begin)
182
if flow.probability:
183
count = time * float(flow.probability)
184
elif flow.vehsPerHour:
185
count = time * float(flow.vehsPerHour) / 3600
186
elif flow.period:
187
count = time / float(flow.period)
188
if count is None:
189
print("Could not determine count for flow '%s'" % (flow.id))
190
count = 1
191
if flow.attr_from and flow.to:
192
addVehicle(flow.id, flow.attr_from, flow.to, parseTime(flow.begin), count)
193
elif flow.route and isinstance(flow.route, list):
194
edges = flow.route[0].edges.split()
195
addVehicle(flow.id, edges[0], edges[-1], parseTime(flow.begin), count)
196
elif flow.fromTaz and flow.toTaz:
197
if options.edgeod:
198
print("No OD-edge information! A district file is needed for TAZ-based OD counts.")
199
sys.exit(1)
200
else:
201
addVehicle(flow.id, flow.fromTaz, flow.toTaz, parseTime(flow.begin), count, True)
202
203
else:
204
print("No edges found for flow '%s'" % flow.id)
205
206
print("read %s vehicles" % nl.numVehicles)
207
if not options.edgeod:
208
if nl.numFromNotFound > 0 or nl.numToNotFound > 0:
209
print("No fromTaz found for %s edges and no toTaz found for %s edges" % (
210
nl.numFromNotFound, nl.numToNotFound))
211
212
if nl.numVehicles > 0:
213
numOD = 0
214
numVehicles = 0
215
distinctOD = set()
216
if options.edgeod:
217
edgeOD = set()
218
with open(options.outfile, 'w') as outf:
219
sumolib.writeXMLHeader(outf, "$Id$", "data", "datamode_file.xsd", options=options) # noqa
220
for begin, edgeRelations in intervals_edge.items():
221
if options.interval is not None:
222
end = begin + options.interval
223
else:
224
end = nl.end + 1
225
outf.write(4 * ' ' + '<interval id="%s" begin="%s" end="%s">\n' % (
226
options.intervalID, begin, end))
227
for od in sorted(edgeRelations.keys()):
228
numOD += 1
229
numVehicles += edgeRelations[od]
230
edgeOD.add(od)
231
outf.write(8 * ' ' + '<edgeRelation from="%s" to="%s" count="%s"/>\n' % (
232
od[0], od[1], edgeRelations[od]))
233
outf.write(4 * ' ' + '</interval>\n')
234
outf.write('</data>\n')
235
236
print("Wrote %s OD-pairs (%s edgeOD) in %s intervals (%s vehicles total)" % (
237
numOD, len(edgeOD), len(intervals_edge), numVehicles))
238
else:
239
with open(options.outfile, 'w') as outf:
240
sumolib.writeXMLHeader(outf, "$Id$", "data", "datamode_file.xsd", options=options) # noqa
241
for begin, tazRelations in intervals.items():
242
if options.interval is not None:
243
end = begin + options.interval
244
else:
245
end = nl.end + 1
246
outf.write(4 * ' ' + '<interval id="%s" begin="%s" end="%s">\n' % (
247
options.intervalID, begin, end))
248
for od in sorted(tazRelations.keys()):
249
numOD += 1
250
numVehicles += tazRelations[od]
251
distinctOD.add(od)
252
outf.write(8 * ' ' + '<tazRelation from="%s" to="%s" count="%s"/>\n' % (
253
od[0], od[1], tazRelations[od]))
254
outf.write(4 * ' ' + '</interval>\n')
255
outf.write('</data>\n')
256
257
print("Wrote %s OD-pairs (%s distinct) in %s intervals (%s vehicles total)" % (
258
numOD, len(distinctOD), len(intervals), numVehicles))
259
260
261
if __name__ == "__main__":
262
if not main(get_options()):
263
sys.exit(1)
264
265