Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/route/sort_routes.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 sort_routes.py
15
# @author Jakob Erdmann
16
# @author Michael Behrisch
17
# @author Pieter Loof
18
# @date 2011-07-14
19
20
from __future__ import absolute_import
21
from __future__ import print_function
22
import os
23
import sys
24
from xml.dom import pulldom
25
from xml.sax import handler
26
from xml.sax import make_parser
27
28
sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
29
from sumolib.options import ArgumentParser # noqa
30
from sumolib.miscutils import parseTime # noqa
31
import sumolib # noqa
32
33
DEPART_ATTRS = {'vehicle': 'depart', 'trip': 'depart', 'flow': 'begin', 'person': 'depart'}
34
35
36
def get_options(args=None):
37
ap = ArgumentParser()
38
ap.add_argument("-o", "--outfile", category="output", type=ap.file, help="name of output file")
39
ap.add_argument("-b", "--big", action="store_true", default=False,
40
help="Use alternative sorting strategy for large files (slower but more memory efficient)")
41
ap.add_argument("-v", "--verbose", action="store_true", default=False,
42
help="Tell me what you are doing")
43
ap.add_argument("routefile", category="input", type=ap.file, help="route file whose routes should be sorted")
44
options = ap.parse_args(args=args)
45
if options.outfile is None:
46
options.outfile = options.routefile + ".sorted"
47
return options
48
49
50
def sort_departs(routefile, outfile, verbose):
51
if isinstance(routefile, str):
52
stream = open(routefile, 'rb')
53
else:
54
stream = routefile
55
stream.seek(0)
56
routes_doc = pulldom.parse(stream)
57
vehicles = []
58
root = None
59
for event, parsenode in routes_doc:
60
if event == pulldom.START_ELEMENT:
61
if root is None:
62
root = parsenode.localName
63
sumolib.writeXMLHeader(outfile, root=root, includeXMLDeclaration=False)
64
continue
65
routes_doc.expandNode(parsenode)
66
departAttr = DEPART_ATTRS.get(parsenode.localName)
67
if departAttr is not None:
68
startString = parsenode.getAttribute(departAttr)
69
if startString == "triggered":
70
start = -1 # before everything else
71
else:
72
start = parseTime(startString)
73
vehicles.append(
74
(start, parsenode.toprettyxml(indent="", newl="")))
75
else:
76
# copy to output
77
outfile.write(
78
" " * 4 + parsenode.toprettyxml(indent="", newl="") + "\n")
79
80
if verbose:
81
print('read %s elements.' % len(vehicles))
82
vehicles.sort(key=lambda v: v[0])
83
for _, vehiclexml in vehicles:
84
outfile.write(" " * 4 + vehiclexml + "\n")
85
outfile.write("</%s>\n" % root)
86
if verbose:
87
print('wrote %s elements.' % len(vehicles))
88
if isinstance(routefile, str):
89
stream.close()
90
91
92
class RouteHandler(handler.ContentHandler):
93
94
def __init__(self, elements_with_depart):
95
self.elements_with_depart = elements_with_depart
96
self._depart = None
97
98
def setDocumentLocator(self, locator):
99
self.locator = locator
100
101
def startElement(self, name, attrs):
102
if name in DEPART_ATTRS.keys():
103
startString = attrs[DEPART_ATTRS[name]]
104
if startString == "triggered":
105
self._depart = -1
106
else:
107
self._depart = parseTime(startString)
108
self._start_line = self.locator.getLineNumber()
109
if name == "ride" and self._depart == -1 and "depart" in attrs:
110
# this is at least the attempt to put triggered persons behind their vehicle
111
# it probably works only for vehroute output
112
self._depart = parseTime(attrs["depart"])
113
114
def endElement(self, name):
115
if name in DEPART_ATTRS.keys():
116
end_line = self.locator.getLineNumber()
117
self.elements_with_depart.append(
118
(self._depart, self._start_line, end_line))
119
self._depart = None
120
121
122
def create_line_index(file, verbose):
123
if verbose:
124
print("Building line offset index for %s" % file)
125
result = []
126
offset = 0
127
with open(file, 'rb') as f: # need to read binary here for correct offsets
128
for line in f:
129
result.append(offset)
130
offset += len(line)
131
return result
132
133
134
def get_element_lines(routefilename, verbose):
135
# [(depart, line_index_where_element_starts, line_index_where_element_ends), ...]
136
if verbose:
137
print("Parsing %s for line indices and departs" % routefilename)
138
result = []
139
parser = make_parser()
140
parser.setContentHandler(RouteHandler(result))
141
parser.parse(open(routefilename))
142
if verbose:
143
print(" found %s items" % len(result))
144
return result
145
146
147
def copy_elements(routefilename, outfilename, element_lines, line_offsets, verbose):
148
if verbose:
149
print("Copying elements from %s to %s sorted by departure" % (
150
routefilename, outfilename))
151
# don't read binary here for line end conversion
152
with open(routefilename) as routefile, open(outfilename, 'w') as outfile:
153
# copy header
154
for line in routefile:
155
outfile.write(line)
156
# find start of the route file but ignore option in header comment
157
if '<routes' in line and 'value=' not in line:
158
break
159
for _, start, end in element_lines:
160
# convert from 1-based to 0-based indices
161
routefile.seek(line_offsets[start - 1])
162
for __ in range(end - start + 1):
163
outfile.write(routefile.readline())
164
outfile.write('</routes>')
165
166
167
def main(args=None):
168
options = get_options(args=args)
169
if options.big:
170
line_offsets = create_line_index(options.routefile, options.verbose)
171
element_lines = sorted(get_element_lines(options.routefile, options.verbose))
172
copy_elements(options.routefile, options.outfile, element_lines, line_offsets, options.verbose)
173
else:
174
with open(options.routefile) as routefile, open(options.outfile, 'w') as outfile:
175
# copy header
176
for line in routefile:
177
if line.find('<routes') == 0 or line.find('<additional') == 0:
178
break
179
outfile.write(line)
180
sort_departs(routefile, outfile, options.verbose)
181
182
183
if __name__ == "__main__":
184
main()
185
186