"""
Compare two fcd output files with regard to spatial difference
"""
from __future__ import absolute_import
from __future__ import print_function
import os
import sys
from math import sqrt
import pandas as pd
if 'SUMO_HOME' in os.environ:
sys.path.append(os.path.join(os.environ['SUMO_HOME'], 'tools'))
import sumolib
from sumolib.miscutils import parseTime, humanReadableTime
from sumolib.statistics import Statistics
from sumolib.xml import parse_fast_nested
pd.options.display.width = 0
GROUPSTATS = {
'mean': lambda s: s.avg(),
'median': lambda s: s.median(),
'min': lambda s: s.min,
'max': lambda s: s.max,
}
def get_options(args=None):
parser = sumolib.options.ArgumentParser(description="Compare route-file stop timing with stop-output")
parser.add_argument("old", help="the first fcd file")
parser.add_argument("new", help="the second fcd file")
parser.add_argument("-o", "--xml-output", dest="output",
help="xml output file")
parser.add_option("--csv-output", dest="csv_output", help="write diff as csv", metavar="FILE")
parser.add_option("--filter-ids", dest="filterIDs", help="only include data points from the given list of ids")
parser.add_option("--exclude-ids", dest="excludeIDs", help="skip data points from the given list of ids")
parser.add_argument("-H", "--human-readable-time", dest="hrTime", action="store_true", default=False,
help="Write time values as hour:minute:second or day:hour:minute:second rathern than seconds")
parser.add_argument("-t", "--tripid", dest="tripId", action="store_true", default=False,
help="use attr tripId for matching instead of vehicle id")
parser.add_argument("-i", "--histogram", dest="histogram", type=float,
help="histogram bin size")
parser.add_argument("--grouped", action="store_true", default=False, help="provide statistics grouped by id")
parser.add_argument("-v", "--verbose", action="store_true",
default=False, help="tell me what you are doing")
options = parser.parse_args(args=args)
if options.old is None or options.new is None:
parser.print_help()
sys.exit()
if options.filterIDs is not None:
options.filterIDs = set(options.filterIDs.split(','))
if options.excludeIDs is not None:
options.excludeIDs = set(options.excludeIDs.split(','))
return options
ATTR_CONVERSIONS = {
'time': parseTime,
}
def key(row):
return "%s_%s" % (row[0], humanReadableTime(row['t']))
def getDataFrame(options, fname, attrs, columns, tripId):
data = []
if tripId:
orderedAttrs = attrs[2:] + [attrs[0]]
else:
orderedAttrs = [attrs[0]] + attrs[2:]
for ts, v in parse_fast_nested(fname, 'timestep', ['time'], 'vehicle', orderedAttrs):
vID = getattr(v, attrs[0])
if options.excludeIDs is not None and vID in options.excludeIDs:
continue
if options.filterIDs is None or vID in options.filterIDs:
data.append([vID, parseTime(ts.time)] + [float(getattr(v, a)) for a in attrs[2:]])
return pd.DataFrame.from_records(data, columns=columns)
def main(options):
idAttr = 'tripId' if options.tripId else 'id'
attrs = [
idAttr,
't',
'x',
'y',
]
cols2 = [
idAttr,
't',
'x2',
'y2',
]
df1 = getDataFrame(options, options.old, attrs, attrs, options.tripId)
print("read", options.old)
df2 = getDataFrame(options, options.new, attrs, cols2, options.tripId)
print("new", options.old)
df = pd.merge(df1, df2, on=attrs[:2], how="inner")
dfOuter = pd.merge(df1, df2, on=attrs[:2], how="outer")
if options.verbose:
print("Found %s matches" % len(df))
print("Found %s records" % len(dfOuter))
print("missing in old: %s" % dfOuter['x'].isna().sum())
print("missing in new: %s" % dfOuter['x2'].isna().sum())
useHist = options.histogram is not None
s = Statistics("euclidian_error", histogram=useHist, scale=options.histogram)
idStats = {}
def euclidian_error(r):
e = sqrt((r['x'] - r['x2']) ** 2 + (r['y'] - r['y2']) ** 2)
s.add(e, key(r))
if options.grouped:
i = r[idAttr]
if i not in idStats:
idStats[i] = Statistics(i, histogram=useHist, scale=options.histogram)
idStats[i].add(e, key(r))
return e
df['e'] = df.apply(euclidian_error, axis=1)
idStatList = list(idStats.values())
idStatList.sort(key=lambda x: x.count())
for x in idStatList:
print(x)
print(s)
if options.output:
with open(options.output, "w") as outf:
outf.write('<fcd-diff>\n')
lastTime = None
writtenTime = None
for index, row in df.iterrows():
time = row['t']
if time != lastTime:
writtenTime = humanReadableTime(time) if options.hrTime else time
if lastTime is not None:
outf.write(' </timestep>\n')
outf.write(' <timestep time="%s">\n' % writtenTime)
outf.write(' <vehicle %s/>\n' % ' '.join(['%s="%s"' % (k, v) for k, v in row.items()]))
lastTime = time
outf.write(' </timestep>\n')
outf.write('</fcd-diff>\n')
if __name__ == "__main__":
main(get_options())