Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/output/fcdDiff.py
169674 views
1
#!/usr/bin/env python
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2012-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 fcdDiff.py
15
# @author Jakob Erdmann
16
# @date 2022-12-06
17
18
"""
19
Compare two fcd output files with regard to spatial difference
20
"""
21
22
from __future__ import absolute_import
23
from __future__ import print_function
24
25
import os
26
import sys
27
from math import sqrt
28
29
import pandas as pd
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, humanReadableTime # noqa
35
from sumolib.statistics import Statistics # noqa
36
from sumolib.xml import parse_fast_nested # noqa
37
38
pd.options.display.width = 0 # auto-detect terminal width
39
40
GROUPSTATS = {
41
'mean': lambda s: s.avg(),
42
'median': lambda s: s.median(),
43
'min': lambda s: s.min,
44
'max': lambda s: s.max,
45
}
46
47
48
def get_options(args=None):
49
parser = sumolib.options.ArgumentParser(description="Compare route-file stop timing with stop-output")
50
parser.add_argument("old", help="the first fcd file")
51
parser.add_argument("new", help="the second fcd file")
52
parser.add_argument("-o", "--xml-output", dest="output",
53
help="xml output file")
54
parser.add_option("--csv-output", dest="csv_output", help="write diff as csv", metavar="FILE")
55
parser.add_option("--filter-ids", dest="filterIDs", help="only include data points from the given list of ids")
56
parser.add_option("--exclude-ids", dest="excludeIDs", help="skip data points from the given list of ids")
57
parser.add_argument("-H", "--human-readable-time", dest="hrTime", action="store_true", default=False,
58
help="Write time values as hour:minute:second or day:hour:minute:second rathern than seconds")
59
parser.add_argument("-t", "--tripid", dest="tripId", action="store_true", default=False,
60
help="use attr tripId for matching instead of vehicle id")
61
parser.add_argument("-i", "--histogram", dest="histogram", type=float,
62
help="histogram bin size")
63
parser.add_argument("--grouped", action="store_true", default=False, help="provide statistics grouped by id")
64
parser.add_argument("-v", "--verbose", action="store_true",
65
default=False, help="tell me what you are doing")
66
67
options = parser.parse_args(args=args)
68
if options.old is None or options.new is None:
69
parser.print_help()
70
sys.exit()
71
72
if options.filterIDs is not None:
73
options.filterIDs = set(options.filterIDs.split(','))
74
if options.excludeIDs is not None:
75
options.excludeIDs = set(options.excludeIDs.split(','))
76
77
return options
78
79
80
ATTR_CONVERSIONS = {
81
'time': parseTime,
82
}
83
84
85
def key(row):
86
return "%s_%s" % (row[0], humanReadableTime(row['t']))
87
88
89
def getDataFrame(options, fname, attrs, columns, tripId):
90
data = []
91
if tripId:
92
orderedAttrs = attrs[2:] + [attrs[0]]
93
else:
94
orderedAttrs = [attrs[0]] + attrs[2:]
95
96
for ts, v in parse_fast_nested(fname, 'timestep', ['time'], 'vehicle', orderedAttrs):
97
vID = getattr(v, attrs[0])
98
if options.excludeIDs is not None and vID in options.excludeIDs:
99
continue
100
if options.filterIDs is None or vID in options.filterIDs:
101
data.append([vID, parseTime(ts.time)] + [float(getattr(v, a)) for a in attrs[2:]])
102
return pd.DataFrame.from_records(data, columns=columns)
103
104
105
def main(options):
106
idAttr = 'tripId' if options.tripId else 'id'
107
attrs = [
108
idAttr,
109
't', # time
110
'x',
111
'y',
112
]
113
cols2 = [
114
idAttr,
115
't', # time
116
'x2',
117
'y2',
118
]
119
120
df1 = getDataFrame(options, options.old, attrs, attrs, options.tripId)
121
print("read", options.old)
122
df2 = getDataFrame(options, options.new, attrs, cols2, options.tripId)
123
print("new", options.old)
124
125
# merge on common columns id, time
126
df = pd.merge(df1, df2, on=attrs[:2], how="inner")
127
# outer merge to count missing entries
128
dfOuter = pd.merge(df1, df2, on=attrs[:2], how="outer")
129
130
if options.verbose:
131
print("Found %s matches" % len(df))
132
print("Found %s records" % len(dfOuter))
133
print("missing in old: %s" % dfOuter['x'].isna().sum())
134
print("missing in new: %s" % dfOuter['x2'].isna().sum())
135
136
useHist = options.histogram is not None
137
s = Statistics("euclidian_error", histogram=useHist, scale=options.histogram)
138
idStats = {}
139
140
def euclidian_error(r):
141
e = sqrt((r['x'] - r['x2']) ** 2 + (r['y'] - r['y2']) ** 2)
142
s.add(e, key(r))
143
if options.grouped:
144
i = r[idAttr]
145
if i not in idStats:
146
idStats[i] = Statistics(i, histogram=useHist, scale=options.histogram)
147
idStats[i].add(e, key(r))
148
return e
149
150
df['e'] = df.apply(euclidian_error, axis=1)
151
152
idStatList = list(idStats.values())
153
idStatList.sort(key=lambda x: x.count())
154
155
for x in idStatList:
156
print(x)
157
print(s)
158
# print(dfOuter)
159
160
if options.output:
161
with open(options.output, "w") as outf:
162
outf.write('<fcd-diff>\n')
163
lastTime = None
164
writtenTime = None
165
for index, row in df.iterrows():
166
time = row['t']
167
if time != lastTime:
168
writtenTime = humanReadableTime(time) if options.hrTime else time
169
if lastTime is not None:
170
outf.write(' </timestep>\n')
171
outf.write(' <timestep time="%s">\n' % writtenTime)
172
outf.write(' <vehicle %s/>\n' % ' '.join(['%s="%s"' % (k, v) for k, v in row.items()]))
173
lastTime = time
174
outf.write(' </timestep>\n')
175
outf.write('</fcd-diff>\n')
176
177
178
if __name__ == "__main__":
179
main(get_options())
180
181