Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/countEdgeUsage.py
169659 views
1
#!/usr/bin/env python
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2008-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 countEdgeUsage.py
15
# @author Jakob Erdmann
16
# @author Mirko Barthauer
17
# @date 2015-08-05
18
19
from __future__ import print_function
20
import sys
21
from collections import defaultdict
22
import sumolib
23
from sumolib.output import parse_fast, parse
24
from sumolib.miscutils import Statistics, parseTime, getFlowNumber
25
26
END_UNLIMITED = 1e100
27
28
29
def parse_args():
30
DEFAULT_ELEMENTS = ['trip', 'route', 'walk']
31
# when departure time must be known
32
DEFAULT_ELEMENTS2 = ['vehicle', 'trip', 'flow']
33
34
op = sumolib.options.ArgumentParser(description="count edge usage by vehicles")
35
op.add_argument("-o", "--output-file", dest="outfile", category="output", type=op.file,
36
help="name of output file")
37
op.add_argument("--subpart", category="processing",
38
help="Restrict counts to routes that contain the given consecutive edge sequence")
39
op.add_argument("--subpart-file", dest="subpart_file", category="processing", type=op.additional_file,
40
help="Restrict counts to routes that contain one of the consecutive edge sequences " +
41
"in the given input file (one sequence per line)")
42
op.add_argument("--subpart.via", action="store_true", default=False, category="processing", dest="subpartVia",
43
help="Use subpart as via-edges (permit gaps)")
44
op.add_argument("-i", "--intermediate", action="store_true", default=False, category="processing",
45
help="count all edges of a route")
46
op.add_argument("--taz", action="store_true", default=False, category="processing",
47
help="use fromTaz and toTaz instead of from and to")
48
op.add_argument("--elements", default=','.join(DEFAULT_ELEMENTS), category="processing",
49
help="include edges for the given elements in output")
50
op.add_argument("-b", "--begin", default=0, category="time",
51
help="collect departures after begin time")
52
op.add_argument("-e", "--end", category="time",
53
help="collect departures up to end time (default unlimited)")
54
op.add_argument("--period", category="time",
55
help="create data intervals of the given period duration")
56
op.add_argument("-m", "--min-count", category="processing", default=0, type=int,
57
help="include only values above the minimum")
58
op.add_argument("-n", "--net-file", category="processing", type=op.net_file,
59
help="parse net for geo locations of the edges")
60
op.add_argument("-p", "--poi-file", category="processing", type=op.additional_file,
61
help="write geo POIs")
62
op.add_argument("-v", "--verbose", action="store_true", default=False,
63
help="tell me what you are doing")
64
op.add_argument("routefiles", nargs="+", category="input", type=op.route_file,
65
help="Set one or more input route files")
66
67
options = op.parse_args()
68
if options.outfile is None:
69
options.outfile = options.routefiles[0] + ".departsAndArrivals.xml"
70
if options.net_file and not options.poi_file:
71
options.poi_file = options.net_file + "_count.poi.xml"
72
73
options.subparts = []
74
if options.subpart is not None:
75
options.subparts.append(options.subpart.strip("'").split(','))
76
if options.subpart_file is not None:
77
with open(options.subpart_file) as subparts:
78
for line in subparts:
79
options.subparts.append(line.strip().split(','))
80
if options.taz:
81
for subpart in options.subparts:
82
if len(subpart) > 2:
83
sys.stderr.write("At most two elements can be in a subpart when using --taz (found %s)\n" % subpart)
84
sys.exit(1)
85
86
options.elements = options.elements.split(',')
87
88
options.begin = parseTime(options.begin)
89
if options.end is not None:
90
options.end = parseTime(options.end)
91
if options.period is not None:
92
options.period = parseTime(options.period)
93
94
options.elements2 = []
95
if options.begin != 0 or options.end is not None or options.period or options.taz or 'flow' in options.elements:
96
if options.elements == DEFAULT_ELEMENTS:
97
options.elements2 = DEFAULT_ELEMENTS2
98
else:
99
for elem in options.elements:
100
if elem not in DEFAULT_ELEMENTS2:
101
sys.stderr.write("Element '%s' does not supply departure time. Use one of %s instead.\n" %
102
(elem, DEFAULT_ELEMENTS2))
103
else:
104
options.elements2.append(elem)
105
options.elements = []
106
107
if options.end is None:
108
options.end = END_UNLIMITED
109
110
return options
111
112
113
def hasVias(edges, vias):
114
if not vias:
115
return True
116
elif len(edges) < len(vias):
117
return False
118
for i, e in enumerate(edges):
119
if e == vias[0] and hasVias(edges[i + 1:], vias[1:]):
120
return True
121
return False
122
123
124
def hasSubpart(edges, subparts, isVia):
125
if not subparts:
126
return True
127
for subpart in subparts:
128
if isVia:
129
if hasVias(edges, subpart):
130
return True
131
else:
132
for i in range(len(edges)):
133
if edges[i:i + len(subpart)] == subpart:
134
return True
135
return False
136
137
138
def getEdges(elem, taz, routeDict):
139
edges = []
140
src = None
141
dst = None
142
if elem.edges:
143
edges = elem.edges.split()
144
if elem.route:
145
if not isinstance(elem.route, list):
146
# named route
147
edges = routeDict.get(elem.route, [])
148
if not edges:
149
print("Unknown route id '%s' for %s '%s'" % (elem.route, elem.name, elem.id))
150
else:
151
edges = elem.route[0].edges.split()
152
if edges:
153
src = edges[0]
154
dst = edges[-1]
155
try:
156
if taz:
157
src = elem.fromTaz
158
dst = elem.toTaz
159
elif not edges:
160
src = elem.attr_from
161
dst = elem.to
162
except AttributeError:
163
pass
164
return src, dst, edges
165
166
167
def writeInterval(outf, options, departCounts, arrivalCounts, intermediateCounts, begin=0, end="1000000", prefix=""):
168
departStats = Statistics(prefix + "departEdges")
169
arrivalStats = Statistics(prefix + "arrivalEdges")
170
intermediateStats = Statistics(prefix + "intermediateEdges")
171
for e in sorted(departCounts.keys()):
172
departStats.add(departCounts[e], e)
173
for e in sorted(arrivalCounts.keys()):
174
arrivalStats.add(arrivalCounts[e], e)
175
if options.verbose:
176
print("Loaded %s routes" % sum(departCounts.values()))
177
178
print(departStats)
179
print(arrivalStats)
180
if options.intermediate:
181
for e in sorted(intermediateCounts.keys()):
182
intermediateStats.add(intermediateCounts[e], e)
183
print(intermediateStats)
184
185
outf.write(' <interval begin="%s" end="%s" id="routeStats">\n' % (begin, end))
186
allEdges = set(departCounts.keys())
187
allEdges.update(arrivalCounts.keys())
188
if options.intermediate:
189
allEdges.update(intermediateCounts.keys())
190
for e in sorted(allEdges):
191
intermediate = ' intermediate="%s"' % intermediateCounts[e] if options.intermediate else ''
192
if (departCounts[e] > options.min_count or arrivalCounts[e] > options.min_count or
193
intermediateCounts[e] > 0):
194
outf.write(' <edge id="%s" departed="%s" arrived="%s" delta="%s"%s/>\n' %
195
(e, departCounts[e], arrivalCounts[e], arrivalCounts[e] - departCounts[e], intermediate))
196
outf.write(" </interval>\n")
197
departCounts.clear()
198
arrivalCounts.clear()
199
intermediateCounts.clear()
200
201
202
def parseSimple(outf, options):
203
"""parse elements without checking time (uses fast parser)"""
204
departCounts = defaultdict(lambda: 0)
205
arrivalCounts = defaultdict(lambda: 0)
206
intermediateCounts = defaultdict(lambda: 0)
207
208
for element in options.elements:
209
for routefile in options.routefiles:
210
if element == 'route':
211
for route in parse_fast(routefile, element, ['id']):
212
print(("Warning: Cannot handle named routes in file '%s'." +
213
" Use option --elements vehicle,flow instead") % routefile,
214
file=sys.stderr)
215
break
216
for route in parse_fast(routefile, element, ['edges']):
217
edges = route.edges.split()
218
if not hasSubpart(edges, options.subparts, options.subpartVia):
219
continue
220
departCounts[edges[0]] += 1
221
arrivalCounts[edges[-1]] += 1
222
if options.intermediate:
223
for e in edges:
224
intermediateCounts[e] += 1
225
226
# warn about potentially missing edges
227
fromAttr, toAttr = ('fromTaz', 'toTaz') if options.taz else ('from', 'to')
228
if 'trip' in options.elements:
229
for routefile in options.routefiles:
230
for trip in parse_fast(routefile, 'trip', ['id', fromAttr, toAttr]):
231
if not hasSubpart([trip[1], trip[2]], options.subparts, options.subpartVia):
232
continue
233
departCounts[trip[1]] += 1
234
arrivalCounts[trip[2]] += 1
235
if 'walk' in options.elements:
236
for routefile in options.routefiles:
237
for walk in parse_fast(routefile, 'walk', ['from', 'to']):
238
if not hasSubpart([walk[1], walk[2]], options.subparts, options.subpartVia):
239
continue
240
departCounts[walk.attr_from] += 1
241
arrivalCounts[walk.to] += 1
242
243
if options.net_file:
244
net = sumolib.net.readNet(options.net_file)
245
with open(options.poi_file, "w") as pois:
246
sumolib.xml.writeHeader(pois, root="additional")
247
allEdges = set(departCounts.keys())
248
allEdges.update(arrivalCounts.keys())
249
for e in sorted(allEdges):
250
if departCounts[e] > options.min_count or arrivalCounts[e] > options.min_count:
251
lon, lat = net.convertXY2LonLat(*net.getEdge(e).getShape()[0])
252
pois.write(' <poi id="%s" lon="%.6f" lat="%.6f">\n' % (e, lon, lat))
253
pois.write(' <param key="departed" value="%s"/>\n' % departCounts[e])
254
pois.write(' <param key="arrived" value="%s"/>\n </poi>\n' % arrivalCounts[e])
255
pois.write("</additional>\n")
256
writeInterval(outf, options, departCounts, arrivalCounts, intermediateCounts)
257
258
259
def parseTimed(outf, options):
260
departCounts = defaultdict(lambda: 0)
261
arrivalCounts = defaultdict(lambda: 0)
262
intermediateCounts = defaultdict(lambda: 0)
263
lastDepart = 0
264
period = options.period if options.period else options.end
265
begin = options.begin
266
periodEnd = options.period if options.period else options.end
267
routeDict = {} # routeID -> edges
268
269
# parse named routes
270
for routefile in options.routefiles:
271
for elem in parse(routefile, 'route'):
272
if elem.id:
273
src, dst, edges = getEdges(elem, False, None)
274
routeDict[elem.id] = edges
275
276
# parse the elements
277
for routefile in options.routefiles:
278
for elem in parse(routefile, options.elements2):
279
if elem.hasAttribute("fromJunction"):
280
print("Warning: Cannot handle fromJunction/toJunction attributes in file '%s', thus skipping the %s element." % (routefile, elem.name), # noqa
281
file=sys.stderr)
282
continue
283
depart = elem.depart if elem.depart is not None else elem.begin
284
if depart != "triggered":
285
depart = parseTime(depart)
286
lastDepart = depart
287
if depart < lastDepart:
288
sys.stderr.write("Unsorted departure %s for %s '%s'" % (
289
depart, elem.tag, elem.id))
290
lastDepart = depart
291
if depart < begin:
292
continue
293
if depart >= periodEnd or depart >= options.end:
294
description = "%s-%s " % (begin, periodEnd)
295
writeInterval(outf, options, departCounts, arrivalCounts,
296
intermediateCounts, begin, periodEnd, description)
297
periodEnd += period
298
begin += period
299
if depart >= options.end:
300
continue
301
number = getFlowNumber(elem) if elem.name == 'flow' else 1
302
src, dst, edges = getEdges(elem, options.taz, routeDict)
303
filterBy = [src, dst] if options.taz or not edges else edges
304
if not hasSubpart(filterBy, options.subparts, options.subpartVia):
305
continue
306
departCounts[src] += number
307
arrivalCounts[dst] += number
308
if options.intermediate:
309
for e in edges:
310
intermediateCounts[e] += number
311
312
description = "%s-%s " % (begin, periodEnd) if periodEnd != END_UNLIMITED else ""
313
if len(departCounts) > 0:
314
writeInterval(outf, options, departCounts, arrivalCounts, intermediateCounts, begin, lastDepart, description)
315
316
317
def main():
318
options = parse_args()
319
outf = open(options.outfile, 'w')
320
outf.write("<edgedata>\n")
321
322
if options.elements2:
323
parseTimed(outf, options)
324
else:
325
parseSimple(outf, options)
326
327
outf.write("</edgedata>\n")
328
outf.close()
329
330
331
if __name__ == "__main__":
332
main()
333
334