Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/route/routecheck.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 routecheck.py
15
# @author Michael Behrisch
16
# @author Jakob Erdmann
17
# @author Yun-Pang Floetteroed
18
# @date 2007-03-09
19
20
"""
21
This script does simple checks for the routes on a given network.
22
Warnings will be issued if there is an unknown edge in the route,
23
if the route is disconnected,
24
or if the route definition does not use the edges attribute.
25
If one specifies -f or --fix all route files will be fixed
26
(if possible). At the moment this means adding an intermediate edge
27
if just one link is missing in a disconnected route, or adding an edges
28
attribute if it is missing.
29
All changes are documented within the output file which has the same name
30
as the input file with an additional .fixed suffix.
31
If the route file uses the old format (<route>edge1 edge2</route>)
32
this is changed without adding comments. The same is true for camelCase
33
changes of attributes.
34
"""
35
from __future__ import print_function
36
from __future__ import absolute_import
37
from collections import defaultdict
38
from xml.sax import saxutils, make_parser, handler
39
import os
40
import sys
41
if 'SUMO_HOME' in os.environ:
42
sys.path.append(os.path.join(os.environ['SUMO_HOME'], 'tools'))
43
from sumolib.options import ArgumentParser # noqa
44
try:
45
from StringIO import StringIO
46
except ImportError:
47
from io import StringIO
48
49
camelCase = {"departlane": "departLane",
50
"departpos": "departPos",
51
"departspeed": "departSpeed",
52
"arrivallane": "arrivalLane",
53
"arrivalpos": "arrivalPos",
54
"arrivalspeed": "arrivalSpeed",
55
"maxspeed": "maxSpeed",
56
"bus_stop": "busStop",
57
"vclass": "vClass",
58
"fromtaz": "fromTaz",
59
"totaz": "toTaz",
60
"no": "number",
61
"vtype": "vType",
62
"vtypeDistribution": "vTypeDistribution",
63
"tripdef": "trip"}
64
65
deletedKeys = defaultdict(list)
66
deletedKeys["route"] = ["edges", "multi_ref"]
67
68
69
class NetReader(handler.ContentHandler):
70
71
def __init__(self):
72
self._nb = {}
73
74
def startElement(self, name, attrs):
75
if name == 'edge' and ('function' not in attrs or attrs['function'] != 'internal'):
76
self._nb[attrs['id']] = set()
77
elif name == 'connection':
78
if attrs['from'] in self._nb and attrs['to'] in self._nb:
79
self._nb[attrs['from']].add(attrs['to'])
80
81
def hasEdge(self, edge):
82
return edge in self._nb
83
84
def isNeighbor(self, orig, dest):
85
return dest in self._nb[orig]
86
87
def getIntermediateEdge(self, orig, dest):
88
for inter in self._nb[orig]:
89
if dest in self._nb[inter]:
90
return inter
91
return ''
92
93
94
class RouteReader(handler.ContentHandler):
95
96
def __init__(self, net, outFileName):
97
self._vID = ''
98
self._routeID = ''
99
self._routeString = ''
100
self._addedString = ''
101
self._net = net
102
if outFileName:
103
self._out = open(outFileName, 'w')
104
else:
105
self._out = None
106
self._fileOut = None
107
self._isRouteValid = True
108
self._changed = False
109
110
def startDocument(self):
111
if self._out:
112
print('<?xml version="1.0"?>', file=self._out)
113
114
def endDocument(self):
115
if self._out:
116
self._out.close()
117
if not self._changed:
118
os.remove(self._out.name)
119
120
def condOutputRedirect(self):
121
if self._out and not self._fileOut:
122
self._fileOut = self._out
123
self._out = StringIO()
124
125
def endOutputRedirect(self):
126
if self._fileOut:
127
if not self._isRouteValid:
128
self._changed = True
129
self._fileOut.write("<!-- ")
130
self._fileOut.write(self._out.getvalue())
131
if not self._isRouteValid:
132
self._fileOut.write(" -->")
133
if self._addedString != '':
134
self._fileOut.write(
135
"<!-- added edges: " + self._addedString + "-->")
136
self._addedString = ''
137
self._out.close()
138
self._out = self._fileOut
139
self._fileOut = None
140
141
def startElement(self, name, attrs):
142
if name == 'vehicle' and 'route' not in attrs:
143
self.condOutputRedirect()
144
self._vID = attrs['id']
145
if name == 'route':
146
self.condOutputRedirect()
147
if 'id' in attrs:
148
self._routeID = attrs['id']
149
else:
150
self._routeID = "for vehicle " + self._vID
151
self._routeString = ''
152
if 'edges' in attrs:
153
self._routeString = attrs['edges']
154
else:
155
self._changed = True
156
print("Warning: No edges attribute in route " + self._routeID)
157
elif self._routeID:
158
print(
159
"Warning: This script does not handle nested '%s' elements properly." % name)
160
if self._out:
161
if name in camelCase:
162
name = camelCase[name]
163
self._changed = True
164
self._out.write('<' + name)
165
if options.fix_length and 'length' in attrs and 'minGap' not in attrs:
166
length = float(attrs["length"])
167
minGap = 2.5
168
if 'guiOffset' in attrs:
169
minGap = float(attrs["guiOffset"])
170
attrs = dict(attrs)
171
attrs["length"] = str(length - minGap)
172
attrs["minGap"] = str(minGap)
173
self._changed = True
174
for key, value in sorted(attrs.items()):
175
if key in camelCase:
176
key = camelCase[key]
177
self._changed = True
178
if key not in deletedKeys[name]:
179
self._out.write(' %s="%s"' % (key, saxutils.escape(value)))
180
if name != 'route':
181
if name in ["ride", "stop", "walk"]:
182
self._out.write('/')
183
self._out.write('>')
184
185
def endElement(self, name):
186
if name in ["ride", "stop", "walk"]:
187
return
188
if name == 'route':
189
self._isRouteValid = self.testRoute()
190
if self._out:
191
self._out.write(' edges="%s"/>' % self._routeString)
192
self._routeID = ''
193
self._routeString = ''
194
if self._vID == '':
195
self.endOutputRedirect()
196
elif name == 'vehicle' and self._vID != '':
197
self._vID = ''
198
if self._out:
199
self._out.write('</vehicle>')
200
self.endOutputRedirect()
201
elif self._out:
202
self._out.write('</%s>' % camelCase.get(name, name))
203
204
def characters(self, content):
205
if self._routeID != '':
206
self._routeString += content
207
elif self._out:
208
self._out.write(saxutils.escape(content))
209
210
def testRoute(self):
211
if self._routeID != '':
212
returnValue = True
213
edgeList = self._routeString.split()
214
if len(edgeList) == 0:
215
print("Warning: Route %s is empty" % self._routeID)
216
return False
217
if net is None:
218
return True
219
doConnectivityTest = True
220
cleanedEdgeList = []
221
for v in edgeList:
222
if self._net.hasEdge(v):
223
cleanedEdgeList.append(v)
224
else:
225
print(
226
"Warning: Unknown edge " + v + " in route " + self._routeID)
227
returnValue = False
228
while doConnectivityTest:
229
doConnectivityTest = False
230
for i, v in enumerate(cleanedEdgeList):
231
if i < len(cleanedEdgeList) - 1 and not self._net.isNeighbor(v, cleanedEdgeList[i + 1]):
232
print("Warning: Route " + self._routeID +
233
" disconnected between " + v + " and " + cleanedEdgeList[i + 1])
234
interEdge = self._net.getIntermediateEdge(
235
v, cleanedEdgeList[i + 1])
236
if interEdge != '':
237
cleanedEdgeList.insert(i + 1, interEdge)
238
self._changed = True
239
self._addedString += interEdge + " "
240
self._routeString = ' '.join(cleanedEdgeList)
241
doConnectivityTest = True
242
break
243
returnValue = False
244
return returnValue
245
return False
246
247
def ignorableWhitespace(self, content):
248
if self._out:
249
self._out.write(content)
250
251
def processingInstruction(self, target, data):
252
if self._out:
253
self._out.write('<?%s %s?>' % (target, data))
254
255
256
ap = ArgumentParser()
257
ap.add_argument("-v", "--verbose", action="store_true",
258
default=False, help="tell me what you are doing")
259
ap.add_argument("-f", "--fix", action="store_true",
260
default=False, help="fix errors into '.fixed' file")
261
ap.add_argument("-l", "--fix-length", action="store_true",
262
default=False, help="fix vehicle type's length and guiOffset attributes")
263
ap.add_argument("-i", "--inplace", action="store_true",
264
default=False, help="replace original files")
265
ap.add_argument("-n", "--net", category="input", type=ap.net_file, help="network to check connectivity")
266
ap.add_argument("routes", category="input", type=ap.file, nargs="+", help="route files to check")
267
options = ap.parse_args()
268
parser = make_parser()
269
net = None
270
if options.net:
271
net = NetReader()
272
parser.setContentHandler(net)
273
parser.parse(options.net)
274
parser.setContentHandler(RouteReader(net, ''))
275
276
if options.fix_length:
277
deletedKeys["vType"] += ['guiOffset']
278
279
for f in options.routes:
280
ffix = f + '.fixed'
281
if options.fix:
282
if options.verbose:
283
print("fixing " + f)
284
parser.setContentHandler(RouteReader(net, ffix))
285
else:
286
if options.verbose:
287
print("checking " + f)
288
parser.parse(f)
289
if options.fix and os.path.exists(ffix) and options.inplace:
290
os.rename(ffix, f)
291
292