Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/detector/flowFromRoutes.py
169674 views
1
#!/usr/bin/env python
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2007-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 flowFromRoutes.py
15
# @author Daniel Krajzewicz
16
# @author Jakob Erdmann
17
# @author Michael Behrisch
18
# @author Mirko Barthauer
19
# @date 2007-06-28
20
21
from __future__ import absolute_import
22
from __future__ import print_function
23
import math
24
import sys
25
import os
26
27
from xml.sax import make_parser, handler
28
29
SUMO_HOME = os.environ.get('SUMO_HOME',
30
os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..'))
31
sys.path.append(os.path.join(SUMO_HOME, 'tools'))
32
33
from sumolib.miscutils import uMin, uMax # noqa
34
from sumolib.options import ArgumentParser # noqa
35
import detector # noqa
36
from detector import relError # noqa
37
38
39
def make_geh(interval_minutes):
40
def geh(m, c):
41
# GEH expects hourly flow
42
m = m * 60 / interval_minutes
43
c = c * 60 / interval_minutes
44
"""Error function for hourly traffic flow measures after Geoffrey E. Havers"""
45
if m + c == 0:
46
return 0
47
else:
48
return math.sqrt(2 * (m - c) * (m - c) / float(m + c))
49
return geh
50
51
52
SEP = ";"
53
54
55
def print_record(*args, **kwargs):
56
comment = '#' + SEP if kwargs.get('comment', False) else ''
57
print(comment + SEP.join([repr(a) if type(a) is float else str(a) for a in args]))
58
59
60
class LaneMap:
61
62
def get(self, key, default):
63
return key[0:-2]
64
65
66
class DetectorRouteEmitterReader(handler.ContentHandler):
67
68
def __init__(self, detFile):
69
self._routes = {}
70
self._detReader = detector.DetectorReader(detFile, LaneMap())
71
self._edgeFlow = {}
72
self._parser = make_parser()
73
self._parser.setContentHandler(self)
74
self._begin = None
75
self._end = None
76
self.minTime = uMax
77
self.maxTime = uMin
78
79
def reset(self, start, end):
80
self._routes = {}
81
self._edgeFlow = {}
82
self._begin = 60 * start
83
self._end = 60 * end
84
85
def addRouteFlow(self, route, flow):
86
for edge in self._routes[route]:
87
if edge not in self._edgeFlow:
88
self._edgeFlow[edge] = 0
89
self._edgeFlow[edge] += flow
90
91
def startElement(self, name, attrs):
92
if name == 'route':
93
if 'id' in attrs:
94
self._routes[attrs['id']] = attrs['edges'].split()
95
if name == 'vehicle':
96
if self._begin is None or float(attrs['depart']) >= self._begin:
97
self.addRouteFlow(attrs['route'], 1)
98
self.minTime = min(self.minTime, float(attrs['depart']))
99
self.maxTime = max(self.maxTime, float(attrs['depart']))
100
if name == 'flow':
101
if 'route' in attrs:
102
if self._begin is None or float(attrs['begin']) >= self._begin and float(attrs['end']) <= self._end:
103
self.addRouteFlow(attrs['route'], float(attrs['number']))
104
self.minTime = min(self.minTime, float(attrs['begin']))
105
self.maxTime = max(self.maxTime, float(attrs['end']))
106
if name == 'routeDistribution':
107
if 'routes' in attrs:
108
routes = attrs['routes'].split()
109
nums = attrs['probabilities'].split()
110
for r, n in zip(routes, nums):
111
self.addRouteFlow(r, float(n))
112
113
def readDetFlows(self, flowFile, flowCol):
114
if self._begin is None:
115
return self._detReader.readFlows(flowFile, flow=flowCol)
116
else:
117
return self._detReader.readFlows(flowFile, flow=options.flowcol, time="Time",
118
timeVal=self._begin / 60, timeMax=self._end / 60)
119
120
def getDataIntervalMinutes(self, fallback=60):
121
if self.maxTime == uMin:
122
return fallback
123
interval = (self.maxTime - self.minTime) / 60
124
if interval == 0:
125
interval = fallback
126
return interval
127
128
def clearFlows(self):
129
self._detReader.clearFlows()
130
131
def calcStatistics(self, interval, geh_threshold):
132
rSum = 0.
133
dSum = 0.
134
sumAbsDev = 0.
135
sumSquaredDev = 0.
136
sumSquaredPercent = 0.
137
sumGEH = 0.
138
nGEHthresh = 0.
139
n = 0
140
geh = make_geh(interval)
141
for edge, detData in self._detReader._edge2DetData.items():
142
rFlow = self._edgeFlow.get(edge, 0)
143
for group in detData:
144
if group.isValid:
145
dFlow = group.totalFlow
146
if dFlow > 0 or options.respectzero:
147
rSum += rFlow
148
dSum += dFlow
149
dev = float(abs(rFlow - dFlow))
150
sumAbsDev += dev
151
sumSquaredDev += dev * dev
152
if dFlow > 0:
153
sumSquaredPercent += dev * dev / dFlow / dFlow
154
sumGEH += geh(rFlow, dFlow)
155
if geh(rFlow, dFlow) < geh_threshold:
156
nGEHthresh += 1
157
n += 1
158
if self._begin is not None:
159
print_record('interval', self._begin, comment=True)
160
print_record('avgRouteFlow', 'avgDetFlow', 'avgDev', 'RMSE', 'RMSPE', 'GEH', 'GEH%', comment=True)
161
if n == 0:
162
# avoid division by zero
163
n = -1
164
print_record(
165
rSum / n,
166
dSum / n,
167
sumAbsDev / n,
168
math.sqrt(sumSquaredDev / n),
169
math.sqrt(sumSquaredPercent / n),
170
sumGEH / n,
171
100. * nGEHthresh / n,
172
comment=True)
173
174
def printFlows(self, includeDets, useGEH, interval):
175
edgeIDCol = ["edge"] if options.edgenames else []
176
timeCol = ["Time"] if options.writetime else []
177
measureCol = "ratio"
178
measure = relError
179
if useGEH:
180
measureCol = "GEH"
181
measure = make_geh(interval)
182
183
if includeDets:
184
cols = ['Detector'] + edgeIDCol + timeCol + ['RouteFlow', 'DetFlow', measureCol]
185
else:
186
cols = ['Detector'] + edgeIDCol + timeCol + ['qPkw']
187
print_record(*cols)
188
output = []
189
time = self._begin if self._begin is not None else 0
190
for edge, detData in self._detReader._edge2DetData.items():
191
detString = []
192
dFlow = []
193
for group in detData:
194
if group.isValid:
195
detString.append(group.getName(
196
options.longnames, options.firstname))
197
dFlow.append(group.totalFlow)
198
rFlow = len(detString) * [self._edgeFlow.get(edge, 0)]
199
edges = len(detString) * [edge]
200
if includeDets:
201
output.extend(zip(detString, edges, rFlow, dFlow))
202
else:
203
output.extend(zip(detString, edges, rFlow))
204
if includeDets:
205
for group, edge, rflow, dflow in sorted(output):
206
if dflow > 0 or options.respectzero:
207
edgeCol = [edge] if options.edgenames else []
208
timeCol = [time] if options.writetime else []
209
cols = [group] + edgeCol + timeCol + [rflow, dflow, measure(rflow, dflow)]
210
print_record(*cols)
211
else:
212
for group, edge, flow in sorted(output):
213
edgeCol = [edge] if options.edgenames else []
214
timeCol = [time] if options.writetime else []
215
cols = [group] + edgeCol + timeCol + [flow]
216
print_record(*cols)
217
218
219
parser = ArgumentParser()
220
parser.add_argument("-d", "--detector-file", dest="detfile", category="input", type=ArgumentParser.additional_file,
221
help="read detectors from FILE (mandatory)", metavar="FILE")
222
parser.add_argument("-r", "--routes", dest="routefile", category="input", type=ArgumentParser.route_file,
223
help="read routes from FILE (mandatory)", metavar="FILE")
224
parser.add_argument("-e", "--emitters", dest="emitfile", category="input", type=ArgumentParser.file,
225
help="read emitters from FILE (mandatory)", metavar="FILE")
226
parser.add_argument("-f", "--detector-flow-file", dest="flowfile", category="input", type=ArgumentParser.file,
227
help="read detector flows to compare to from FILE", metavar="FILE")
228
parser.add_argument("--flow-column", dest="flowcol", default="qPKW", type=str,
229
help="which column contains flows", metavar="STRING")
230
parser.add_argument("-z", "--respect-zero", action="store_true", dest="respectzero",
231
default=False, help="respect detectors without data (or with permanent zero) with zero flow")
232
parser.add_argument("-D", "--dfrouter-style", action="store_true", dest="dfrstyle",
233
default=False, help="emitter files in dfrouter style (explicit routes)")
234
parser.add_argument("-i", "--interval", type=ArgumentParser.time, help="aggregation interval in minutes")
235
parser.add_argument("--long-names", action="store_true", dest="longnames",
236
default=False, help="do not use abbreviated names for detector groups")
237
parser.add_argument("--first-name", action="store_true", dest="firstname",
238
default=False, help="use first id in group as representative")
239
parser.add_argument("--edge-names", action="store_true", dest="edgenames",
240
default=False, help="include detector group edge name in output")
241
parser.add_argument("-b", "--begin", type=ArgumentParser.time, default=0, help="begin time in minutes")
242
parser.add_argument("--end", type=ArgumentParser.time, default=None, help="end time in minutes")
243
parser.add_argument("--geh", action="store_true", dest="geh",
244
default=False, help="compare flows using GEH measure")
245
parser.add_argument("--geh-threshold", type=float, default=5, dest="geh_threshold",
246
help="report percentage of detectors below threshold")
247
parser.add_argument("--write-time", action="store_true", dest="writetime",
248
default=False, help="write time in output")
249
parser.add_argument("-v", "--verbose", action="store_true", dest="verbose",
250
default=False, help="tell me what you are doing")
251
options = parser.parse_args()
252
if not options.detfile or not options.routefile or not options.emitfile:
253
parser.print_help()
254
sys.exit()
255
parser = make_parser()
256
if options.verbose:
257
print("Reading detectors")
258
reader = DetectorRouteEmitterReader(options.detfile)
259
parser.setContentHandler(reader)
260
if options.interval:
261
haveFlows = True
262
start = options.begin # minutes
263
while ((options.end is None and haveFlows) or
264
(options.end is not None and start < options.end)):
265
end = start + options.interval
266
if options.end is not None:
267
end = min(end, options.end)
268
reader.reset(start, end)
269
if options.verbose:
270
print("Reading routes")
271
parser.parse(options.routefile)
272
if options.verbose:
273
print("Reading emitters")
274
parser.parse(options.emitfile)
275
if options.flowfile:
276
if options.verbose:
277
print("Reading flows")
278
haveFlows = reader.readDetFlows(options.flowfile, options.flowcol)
279
if haveFlows:
280
reader.printFlows(bool(options.flowfile), options.geh, options.interval)
281
if options.flowfile:
282
reader.calcStatistics(options.interval, options.geh_threshold)
283
reader.clearFlows()
284
start += options.interval
285
else:
286
if options.verbose:
287
print("Reading routes")
288
parser.parse(options.routefile)
289
if options.verbose:
290
print("Reading emitters")
291
parser.parse(options.emitfile)
292
if options.flowfile:
293
if options.verbose:
294
print("Reading flows")
295
reader.readDetFlows(options.flowfile, options.flowcol)
296
fallbackInterval = 60
297
if options.begin and options.end:
298
fallbackInterval = options.end - options.begin
299
dataInterval = reader.getDataIntervalMinutes(fallbackInterval)
300
reader.printFlows(bool(options.flowfile), options.geh, dataInterval)
301
if options.flowfile:
302
reader.calcStatistics(dataInterval, options.geh_threshold)
303
304