Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/route/route2OD.py
169674 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 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
return options
65
66
67
def main(options):
68
random.seed(options.seed)
69
if not options.edgeod:
70
edgeFromTaz = defaultdict(list)
71
edgeToTaz = defaultdict(list)
72
numTaz = 0
73
for tazfile in options.tazfiles:
74
for taz in sumolib.xml.parse(tazfile, 'taz'):
75
numTaz += 1
76
if taz.edges:
77
for edge in taz.edges.split():
78
edgeFromTaz[edge].append(taz.id)
79
edgeToTaz[edge].append(taz.id)
80
if taz.tazSource:
81
for source in taz.tazSource:
82
edgeFromTaz[source.id].append(taz.id)
83
if taz.tazSink:
84
for sink in taz.tazSink:
85
edgeToTaz[sink.id].append(taz.id)
86
87
ambiguousSource = []
88
ambiguousSink = []
89
for edge, tazs in edgeFromTaz.items():
90
if len(tazs) > 1:
91
ambiguousSource.append(edge)
92
for edge, tazs in edgeToTaz.items():
93
if len(tazs) > 1:
94
ambiguousSink.append(edge)
95
96
print("read %s TAZ" % numTaz)
97
98
if len(ambiguousSource) > 0:
99
print("edges %s (total %s) are sources for more than one TAZ" %
100
(ambiguousSource[:5], len(ambiguousSource)))
101
if len(ambiguousSink) > 0:
102
print("edges %s (total %s) are sinks for more than one TAZ" %
103
(ambiguousSink[:5], len(ambiguousSink)))
104
105
class nl: # nonlocal integral variables
106
numFromNotFound = 0
107
numToNotFound = 0
108
numVehicles = 0
109
end = 0
110
111
# begin -> od -> count
112
intervals = defaultdict(lambda: defaultdict(lambda: 0))
113
intervals_edge = defaultdict(lambda: defaultdict(lambda: 0)) # for options.edgeod
114
115
def addVehicle(vehID, fromEdge, toEdge, time, count=1, isTaz=False):
116
nl.numVehicles += count
117
if options.interval is None:
118
intervalBegin = 0
119
else:
120
intervalBegin = int(time / options.interval) * options.interval
121
122
if options.edgeod and not isTaz:
123
intervals_edge[intervalBegin][(fromEdge, toEdge)] += count
124
else:
125
fromTaz = None
126
toTaz = None
127
if isTaz:
128
fromTaz = fromEdge
129
toTaz = toEdge
130
else:
131
if fromEdge in edgeFromTaz:
132
fromTaz = random.choice(edgeFromTaz[fromEdge])
133
if toEdge in edgeToTaz:
134
toTaz = random.choice(edgeToTaz[toEdge])
135
136
if fromTaz and toTaz:
137
intervals[intervalBegin][(fromTaz, toTaz)] += count
138
else:
139
if fromTaz is None:
140
nl.numFromNotFound += 1
141
if nl.numFromNotFound < 5:
142
if isTaz:
143
print("No fromTaz found for vehicle '%s' " % (vehID))
144
else:
145
print("No fromTaz found for edge '%s' of vehicle '%s' " % (fromEdge, vehID))
146
if toTaz is None:
147
nl.numToNotFound += 1
148
if nl.numToNotFound < 5:
149
if isTaz:
150
print("No toTaz found for vehicle '%s' " % (vehID))
151
else:
152
print("No toTaz found for edge '%s' of vehicle '%s' " % (toEdge, vehID))
153
154
nl.end = max(nl.end, time)
155
156
for vehicle in sumolib.xml.parse(options.routefile, ['vehicle']):
157
if vehicle.route and isinstance(vehicle.route, list):
158
edges = vehicle.route[0].edges.split()
159
addVehicle(vehicle.id, edges[0], edges[-1], parseTime(vehicle.depart))
160
else:
161
print("No edges found for vehicle '%s'" % vehicle.id)
162
163
for trip in sumolib.xml.parse(options.routefile, ['trip']):
164
if trip.attr_from and trip.to:
165
addVehicle(trip.id, trip.attr_from, trip.to, parseTime(trip.depart))
166
elif trip.fromTaz and trip.toTaz:
167
if options.edgeod:
168
print("No OD-edge information! Only TAZ-based OD counts with a given district file can be calculated.")
169
sys.exit(1)
170
else:
171
addVehicle(trip.id, trip.fromTaz, trip.toTaz, parseTime(trip.depart), 1, True)
172
173
for flow in sumolib.xml.parse(options.routefile, ['flow']):
174
count = None
175
if flow.number:
176
count = int(flow.number)
177
else:
178
time = parseTime(flow.end) - parseTime(flow.begin)
179
if flow.probability:
180
count = time * float(flow.probability)
181
elif flow.vehsPerHour:
182
count = time * float(flow.vehsPerHour) / 3600
183
elif flow.period:
184
count = time / float(flow.period)
185
if count is None:
186
print("Could not determine count for flow '%s'" % (flow.id))
187
count = 1
188
if flow.attr_from and flow.to:
189
addVehicle(flow.id, flow.attr_from, flow.to, parseTime(flow.begin), count)
190
elif flow.route and isinstance(flow.route, list):
191
edges = flow.route[0].edges.split()
192
addVehicle(flow.id, edges[0], edges[-1], parseTime(flow.begin), count)
193
elif flow.fromTaz and flow.toTaz:
194
if options.edgeod:
195
print("No OD-edge information! A district file is needed for TAZ-based OD counts.")
196
sys.exit(1)
197
else:
198
addVehicle(flow.id, flow.fromTaz, flow.toTaz, parseTime(flow.begin), count, True)
199
200
else:
201
print("No edges found for flow '%s'" % flow.id)
202
203
print("read %s vehicles" % nl.numVehicles)
204
if not options.edgeod:
205
if nl.numFromNotFound > 0 or nl.numToNotFound > 0:
206
print("No fromTaz found for %s edges and no toTaz found for %s edges" % (
207
nl.numFromNotFound, nl.numToNotFound))
208
209
if nl.numVehicles > 0:
210
numOD = 0
211
numVehicles = 0
212
distinctOD = set()
213
if options.edgeod:
214
edgeOD = set()
215
with open(options.outfile, 'w') as outf:
216
sumolib.writeXMLHeader(outf, "$Id$", "data", "datamode_file.xsd", options=options) # noqa
217
for begin, edgeRelations in intervals_edge.items():
218
if options.interval is not None:
219
end = begin + options.interval
220
else:
221
end = nl.end + 1
222
outf.write(4 * ' ' + '<interval id="%s" begin="%s" end="%s">\n' % (
223
options.intervalID, begin, end))
224
for od in sorted(edgeRelations.keys()):
225
numOD += 1
226
numVehicles += edgeRelations[od]
227
edgeOD.add(od)
228
outf.write(8 * ' ' + '<edgeRelation from="%s" to="%s" count="%s"/>\n' % (
229
od[0], od[1], edgeRelations[od]))
230
outf.write(4 * ' ' + '</interval>\n')
231
outf.write('</data>\n')
232
233
print("Wrote %s OD-pairs (%s edgeOD) in %s intervals (%s vehicles total)" % (
234
numOD, len(edgeOD), len(intervals_edge), numVehicles))
235
else:
236
with open(options.outfile, 'w') as outf:
237
sumolib.writeXMLHeader(outf, "$Id$", "data", "datamode_file.xsd", options=options) # noqa
238
for begin, tazRelations in intervals.items():
239
if options.interval is not None:
240
end = begin + options.interval
241
else:
242
end = nl.end + 1
243
outf.write(4 * ' ' + '<interval id="%s" begin="%s" end="%s">\n' % (
244
options.intervalID, begin, end))
245
for od in sorted(tazRelations.keys()):
246
numOD += 1
247
numVehicles += tazRelations[od]
248
distinctOD.add(od)
249
outf.write(8 * ' ' + '<tazRelation from="%s" to="%s" count="%s"/>\n' % (
250
od[0], od[1], tazRelations[od]))
251
outf.write(4 * ' ' + '</interval>\n')
252
outf.write('</data>\n')
253
254
print("Wrote %s OD-pairs (%s distinct) in %s intervals (%s vehicles total)" % (
255
numOD, len(distinctOD), len(intervals), numVehicles))
256
257
258
if __name__ == "__main__":
259
if not main(get_options()):
260
sys.exit(1)
261
262