Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/route/cutRoutes.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 cutRoutes.py
15
# @author Jakob Erdmann
16
# @author Michael Behrisch
17
# @author Leonhard Luecken
18
# @date 2017-04-11
19
20
"""
21
Cut down routes from a large scenario to a sub-scenario optionally using exitTimes
22
Output can be a route file or a tripfile.
23
"""
24
from __future__ import absolute_import
25
from __future__ import print_function
26
27
import os
28
import sys
29
import copy
30
import itertools
31
import io
32
33
from collections import defaultdict
34
import sort_routes
35
36
if 'SUMO_HOME' in os.environ:
37
tools = os.path.join(os.environ['SUMO_HOME'], 'tools')
38
sys.path.append(os.path.join(tools))
39
from sumolib.xml import parse, writeHeader # noqa
40
from sumolib.net import readNet # noqa
41
from sumolib.miscutils import parseTime # noqa
42
import sumolib # noqa
43
else:
44
sys.exit("please declare environment variable 'SUMO_HOME'")
45
46
47
class Statistics:
48
def __init__(self):
49
self.num_vehicles = 0
50
self.num_persons = 0
51
self.num_flows = 0
52
self.num_returned = 0
53
self.missingEdgeOccurrences = defaultdict(lambda: 0)
54
# routes which enter the sub-scenario multiple times
55
self.multiAffectedRoutes = 0
56
self.teleportFactorSum = 0.0
57
self.too_short = 0
58
self.broken = 0
59
60
def total(self):
61
return self.num_vehicles + self.num_persons + self.num_flows
62
63
64
def get_options(args=None):
65
USAGE = """Usage %(prog)s [options] <new_net.xml> <routes> [<routes2> ...]
66
If the given routes contain exit times these will be used to compute new
67
departure times. If the option --orig-net is given departure times will be
68
extrapolated based on edge-lengths and maximum speeds multiplied with --speed-factor"""
69
optParser = sumolib.options.ArgumentParser(usage=USAGE)
70
optParser.add_option("-v", "--verbose", action="store_true",
71
default=False, help="Give more output")
72
optParser.add_option("--trips-output", category='output', help="output trip file")
73
optParser.add_option("--pt-input", category='input', help="read public transport flows from file")
74
optParser.add_option("--pt-output", category='output', help="write reduced public transport flows to file")
75
optParser.add_option("--min-length", type=int,
76
default=0, help="minimum route length in the subnetwork (in #edges)")
77
optParser.add_option("--min-air-dist", type=float,
78
default=0., help="minimum route length in the subnetwork (in meters)")
79
optParser.add_option("-o", "--routes-output", category='output', help="output route file")
80
optParser.add_option("--stops-output", category='output', help="output filtered stop file")
81
optParser.add_option("-a", "--additional-input", category='input',
82
help="additional file (for bus stop locations)")
83
optParser.add_option("--speed-factor", type=float, default=1.0,
84
help="Factor for modifying maximum edge speeds when extrapolating new departure times " +
85
"(default 1.0)")
86
optParser.add_option("--default.stop-duration", type=float, default=0.0, dest="defaultStopDuration",
87
help="default duration for stops in stand-alone routes")
88
optParser.add_option("--default.departLane", default="best", dest="defaultDepartLane",
89
help="default departure lane for cut routes")
90
optParser.add_option("--default.departSpeed", default="max", dest="defaultDepartSpeed",
91
help="default departure speed for cut routes")
92
optParser.add_option("--orig-net", help="complete network for retrieving edge lengths")
93
optParser.add_option("-b", "--big", action="store_true", default=False,
94
help="Perform out-of-memory sort using module sort_routes (slower but more memory efficient)")
95
optParser.add_option("-d", "--disconnected-action", default='discard',
96
choices=['discard', 'keep', "keep.walk"], # XXX 'split', 'longest'
97
help="How to deal with routes that are disconnected in the subnetwork. If 'keep' is chosen " +
98
"a disconnected route generates several routes in the subnetwork corresponding to " +
99
"its parts.")
100
optParser.add_option("-e", "--heterogeneous", action="store_true", default=False,
101
help="this option has no effect and only exists for backward compatibility")
102
optParser.add_option("--missing-edges", type=int, metavar="N",
103
default=0, help="print N most missing edges")
104
optParser.add_option("--discard-exit-times", action="store_true",
105
default=False, help="do not use exit times")
106
optParser.add_argument("network", category='input', help="Provide an input network")
107
optParser.add_argument("routeFiles", nargs="*", category='input',
108
help="If the given routes contain exit times, "
109
"these will be used to compute new departure times")
110
# optParser.add_option("--orig-weights",
111
# help="weight file for the original network for extrapolating new departure times")
112
options = optParser.parse_args(args=args)
113
114
if options.heterogeneous:
115
print("Warning, the heterogeneous option is now enabled by default. Please do not use it any longer.")
116
if options.trips_output is not None and options.routes_output is not None:
117
sys.exit("Only one of the options --trips-output or --routes-output can be given")
118
else:
119
if options.trips_output:
120
options.output = options.trips_output
121
options.trips = True
122
options.routes = False
123
else:
124
options.output = options.routes_output
125
options.routes = True
126
options.trips = False
127
return options
128
129
130
def hasMinLength(fromIndex, toIndex, edges, orig_net, options):
131
if toIndex - fromIndex + 1 < options.min_length:
132
return False
133
if options.min_air_dist > 0:
134
fromPos = orig_net.getEdge(edges[fromIndex]).getFromNode().getCoord()
135
toPos = orig_net.getEdge(edges[toIndex]).getToNode().getCoord()
136
if sumolib.miscutils.euclidean(fromPos, toPos) < options.min_air_dist:
137
return False
138
return True
139
140
141
def _cutEdgeList(areaEdges, oldDepart, exitTimes, edges, orig_net, options, stats, disconnected_action,
142
startIdx=None, endIdx=None):
143
startIdx = 0 if startIdx is None else int(startIdx)
144
endIdx = len(edges) - 1 if endIdx is None else int(endIdx)
145
firstIndex = getFirstIndex(areaEdges, edges[startIdx:endIdx + 1])
146
if firstIndex is None or firstIndex > endIdx:
147
return [] # route does not touch the area
148
lastIndex = endIdx - getFirstIndex(areaEdges, reversed(edges[:endIdx + 1]))
149
if lastIndex < startIdx:
150
return [] # route does not touch the area
151
firstIndex += startIdx
152
# check for connectivity
153
route_parts = [(firstIndex + i, firstIndex + j)
154
for i, j in missingEdges(areaEdges, edges[firstIndex:(lastIndex + 1)],
155
stats.missingEdgeOccurrences)]
156
if len(route_parts) > 1:
157
stats.multiAffectedRoutes += 1
158
if disconnected_action == 'discard':
159
return None
160
# loop over different route parts
161
result = []
162
for fromIndex, toIndex in route_parts:
163
if not hasMinLength(fromIndex, toIndex, edges, orig_net, options):
164
stats.too_short += 1
165
continue
166
# compute new departure
167
if exitTimes is not None:
168
departTimes = [oldDepart] + exitTimes.split()[:-1]
169
teleportFactor = len(departTimes) / float(len(edges))
170
stats.teleportFactorSum += teleportFactor
171
# assume teleports were spread evenly across the vehicles route
172
newDepart = parseTime(departTimes[int(fromIndex * teleportFactor)])
173
if (exitTimes is None) or (newDepart == -1):
174
if orig_net is not None:
175
# extrapolate new departure using default speed
176
newDepart = parseTime(oldDepart)
177
for e in edges[startIdx:fromIndex]:
178
if orig_net.hasEdge(e):
179
edge = orig_net.getEdge(e)
180
newDepart += edge.getLength() / (edge.getSpeed() * options.speed_factor)
181
else:
182
stats.broken += 1
183
return None
184
else:
185
newDepart = parseTime(oldDepart)
186
result.append((newDepart, edges[fromIndex:toIndex + 1]))
187
stats.num_returned += 1
188
return result
189
190
191
def cut_routes(aEdges, orig_net, options, busStopEdges=None, ptRoutes=None, oldPTRoutes=None, collectPT=False):
192
areaEdges = set(aEdges)
193
stats = Statistics()
194
standaloneRoutes = {} # routeID -> routeObject
195
standaloneRoutesDepart = {} # routeID -> time or 'discard' or None
196
vehicleTypes = {}
197
if options.additional_input:
198
for addFile in options.additional_input.split(","):
199
parse_standalone_routes(addFile, standaloneRoutes, vehicleTypes)
200
for routeFile in options.routeFiles:
201
parse_standalone_routes(routeFile, standaloneRoutes, vehicleTypes)
202
for _, t in sorted(vehicleTypes.items()):
203
yield -1, t
204
205
for routeFile in options.routeFiles:
206
print("Parsing routes from %s" % routeFile)
207
for moving in parse(routeFile, (u'vehicle', u'person', u'flow'),
208
{u"walk": (u"edges", u"busStop", u"trainStop")}):
209
if options.verbose and stats.total() > 0 and stats.total() % 100000 == 0:
210
print("%s items read" % stats.total())
211
old_route = None
212
if moving.name == 'person':
213
stats.num_persons += 1
214
oldDepart = moving.depart
215
newDepart = None
216
remaining = set()
217
newPlan = []
218
isDiscoBefore = True
219
isDiscoAfter = False
220
params = []
221
for planItem in moving.getChildList():
222
if planItem.name == "param":
223
params.append(planItem)
224
continue
225
if planItem.name == "walk":
226
disco = "keep" if options.disconnected_action == "keep.walk" else options.disconnected_action
227
routeParts = _cutEdgeList(areaEdges, oldDepart, None,
228
planItem.edges.split(), orig_net, options, stats, disco)
229
if routeParts is None:
230
# the walk itself is disconnected and the disconnected_action says not to keep the person
231
newPlan = []
232
break
233
walkEdges = []
234
for depart, edges in routeParts:
235
if newDepart is None:
236
newDepart = depart
237
walkEdges += edges
238
if walkEdges:
239
if walkEdges[-1] != planItem.edges.split()[-1]:
240
planItem.busStop = None
241
planItem.trainStop = None
242
isDiscoAfter = True
243
if walkEdges[0] != planItem.edges.split()[0]:
244
isDiscoBefore = True
245
remaining.update(walkEdges)
246
planItem.edges = " ".join(walkEdges)
247
if planItem.busStop and busStopEdges.get(planItem.busStop) not in areaEdges:
248
planItem.busStop = None
249
isDiscoAfter = True
250
if planItem.trainStop and busStopEdges.get(planItem.trainStop) not in areaEdges:
251
planItem.trainStop = None
252
isDiscoAfter = True
253
else:
254
planItem = None
255
elif planItem.name == "ride":
256
# "busStop" / "trainStop" overrides "to"
257
toEdge = busStopEdges.get(planItem.busStop) if planItem.busStop else planItem.to
258
if planItem.trainStop:
259
toEdge = busStopEdges.get(planItem.trainStop)
260
try:
261
if toEdge not in areaEdges:
262
if planItem.lines in ptRoutes:
263
ptRoute = ptRoutes[planItem.lines]
264
oldPTRoute = oldPTRoutes[planItem.lines]
265
# test whether ride ends before new network
266
if oldPTRoute.index(toEdge) < oldPTRoute.index(ptRoute[0]):
267
planItem = None
268
else:
269
planItem.busStop = None
270
planItem.trainStop = None
271
planItem.setAttribute("to", ptRoute[-1])
272
isDiscoAfter = True
273
else:
274
planItem = None
275
if planItem is not None:
276
if planItem.attr_from and planItem.attr_from not in areaEdges:
277
if planItem.lines in ptRoutes:
278
ptRoute = ptRoutes[planItem.lines]
279
oldPTRoute = oldPTRoutes[planItem.lines]
280
# test whether ride starts after new network
281
if oldPTRoute.index(planItem.attr_from) > oldPTRoute.index(ptRoute[-1]):
282
planItem = None
283
else:
284
planItem.setAttribute("from", ptRoute[0])
285
if planItem.intended:
286
planItem.lines = planItem.intended
287
isDiscoBefore = True
288
else:
289
planItem = None
290
elif planItem.attr_from is None and len(newPlan) == 0:
291
if planItem.lines in ptRoutes:
292
planItem.setAttribute("from", ptRoutes[planItem.lines][0])
293
else:
294
planItem = None
295
except ValueError as e:
296
print("Error handling ride in '%s'" % moving.id, e)
297
planItem = None
298
if planItem is not None and newDepart is None and planItem.depart is not None:
299
newDepart = parseTime(planItem.depart)
300
if planItem is None:
301
isDiscoAfter = True
302
else:
303
newPlan.append(planItem)
304
if len(newPlan) > 1 and isDiscoBefore and options.disconnected_action == "discard":
305
newPlan = []
306
break
307
isDiscoBefore = isDiscoAfter
308
isDiscoAfter = False
309
if not newPlan:
310
continue
311
moving.setChildList(params + newPlan)
312
cut_stops(moving, busStopEdges, remaining)
313
if newDepart is None:
314
newDepart = parseTime(moving.depart)
315
if newPlan[0].name == "ride" and newPlan[0].lines == newPlan[0].intended:
316
moving.depart = "triggered"
317
else:
318
moving.depart = "%.2f" % newDepart
319
yield newDepart, moving
320
else:
321
if moving.name == 'vehicle':
322
stats.num_vehicles += 1
323
oldDepart = parseTime(moving.depart)
324
else:
325
stats.num_flows += 1
326
oldDepart = parseTime(moving.begin)
327
if moving.routeDistribution is not None:
328
old_route = moving.addChild("route", {"edges": moving.routeDistribution[0].route[-1].edges})
329
moving.removeChild(moving.routeDistribution[0])
330
routeRef = None
331
elif isinstance(moving.route, list):
332
old_route = moving.route[0]
333
routeRef = None
334
else:
335
newDepart = standaloneRoutesDepart.get(moving.route)
336
if newDepart == 'discard':
337
# route was already checked and discarded
338
continue
339
elif newDepart is not None:
340
# route was already treated
341
if moving.name == 'vehicle':
342
newDepart += oldDepart
343
moving.depart = "%.2f" % newDepart
344
else:
345
if moving.end:
346
moving.end = "%.2f" % (newDepart + parseTime(moving.end))
347
newDepart += oldDepart
348
moving.begin = "%.2f" % newDepart
349
if collectPT and moving.line and moving.line not in oldPTRoutes:
350
oldPTRoutes[moving.line] = standaloneRoutes[moving.route].edges.split()
351
cut_stops(moving, busStopEdges, set(standaloneRoutes[moving.route].edges.split()))
352
moving.departEdge = None # the cut already removed the unused edges
353
moving.arrivalEdge = None # the cut already removed the unused edges
354
yield newDepart, moving
355
continue
356
else:
357
old_route = routeRef = standaloneRoutes[moving.route]
358
if options.discard_exit_times:
359
old_route.exitTimes = None
360
if collectPT and moving.line and moving.line not in oldPTRoutes:
361
oldPTRoutes[moving.line] = old_route.edges.split()
362
routeParts = _cutEdgeList(areaEdges, oldDepart, old_route.exitTimes,
363
old_route.edges.split(), orig_net, options,
364
stats, options.disconnected_action, moving.departEdge, moving.arrivalEdge)
365
if options.verbose and routeParts and old_route.exitTimes is None and orig_net is None:
366
print("Could not reconstruct new departure time for %s '%s'. Using old departure time." %
367
(moving.name, moving.id))
368
old_route.exitTimes = None
369
if routeRef and not routeParts:
370
standaloneRoutesDepart[moving.route] = 'discard'
371
for ix_part, (newDepart, remaining) in enumerate(routeParts or []):
372
departShift = cut_stops(moving, busStopEdges, remaining)
373
if routeRef:
374
departShift = cut_stops(routeRef, busStopEdges, remaining,
375
newDepart - oldDepart, options.defaultStopDuration, True)
376
standaloneRoutesDepart[moving.route] = departShift
377
newDepart = oldDepart + departShift
378
routeRef.edges = " ".join(remaining)
379
yield -1, routeRef
380
else:
381
if ix_part > 0 or old_route.edges.split()[0] != remaining[0]:
382
moving.setAttribute("departLane", options.defaultDepartLane)
383
moving.setAttribute("departSpeed", options.defaultDepartSpeed)
384
newDepart = max(newDepart, departShift)
385
old_route.edges = " ".join(remaining)
386
if moving.name == 'vehicle':
387
moving.depart = "%.2f" % newDepart
388
else:
389
moving.begin = "%.2f" % newDepart
390
if moving.end:
391
moving.end = "%.2f" % (newDepart - oldDepart + parseTime(moving.end))
392
moving.departEdge = None # the cut already removed the unused edges
393
moving.arrivalEdge = None # the cut already removed the unused edges
394
if len(routeParts) > 1:
395
# return copies of the vehicle for each route part
396
yield_mov = copy.deepcopy(moving)
397
yield_mov.id = moving.id + "_part" + str(ix_part)
398
yield newDepart, yield_mov
399
else:
400
yield newDepart, moving
401
402
if stats.teleportFactorSum > 0:
403
teleports = " (avg teleportFactor %s)" % (
404
1 - stats.teleportFactorSum / stats.num_returned)
405
else:
406
teleports = ""
407
408
print("Parsed %s vehicles, %s persons, %s flows and kept %s routes%s" %
409
(stats.num_vehicles, stats.num_persons, stats.num_flows, stats.num_returned, teleports))
410
if stats.too_short > 0:
411
msg = "Discarded %s routes because they have less than %s edges" % (stats.too_short, options.min_length)
412
if options.min_air_dist > 0:
413
msg += " or the air-line distance between start and end is less than %s" % options.min_air_dist
414
print(msg)
415
print("Number of disconnected routes: %s, broken routes: %s." % (stats.multiAffectedRoutes, stats.broken))
416
if options.missing_edges > 0:
417
print("Most frequent missing edges:")
418
counts = sorted([(v, k) for k, v in stats.missingEdgeOccurrences.items()], reverse=True)
419
for count, edge in itertools.islice(counts, options.missing_edges):
420
print(count, edge)
421
422
423
def cut_stops(vehicle, busStopEdges, remaining, departShift=0, defaultDuration=0, isStandalone=False):
424
if vehicle.stop:
425
skippedStopDuration = 0
426
haveStop = False
427
for stop in list(vehicle.stop):
428
until = None if stop.until is None else parseTime(stop.until)
429
if stop.busStop:
430
if not busStopEdges:
431
print("No bus stop locations parsed, skipping bus stop '%s'." % stop.busStop)
432
elif stop.busStop not in busStopEdges:
433
print("Skipping bus stop '%s', which could not be located." % stop.busStop)
434
elif busStopEdges[stop.busStop] in remaining:
435
if departShift > 0 and until is not None and isStandalone:
436
stop.until = max(0, until - departShift)
437
haveStop = True
438
continue
439
elif stop.duration is not None:
440
skippedStopDuration += parseTime(stop.duration)
441
else:
442
skippedStopDuration += defaultDuration
443
elif stop.lane[:-2] in remaining:
444
haveStop = True
445
continue
446
if until is not None and not haveStop:
447
if departShift < until:
448
departShift = until
449
vehicle.removeChild(stop)
450
return departShift
451
452
453
def getFirstIndex(areaEdges, edges):
454
for i, edge in enumerate(edges):
455
if edge in areaEdges:
456
return i
457
return None
458
459
460
def missingEdges(areaEdges, edges, missingEdgeOccurrences):
461
'''
462
Returns a list of intervals corresponding to the overlapping parts of the route with the area
463
'''
464
# store present edge-intervals
465
route_intervals = []
466
start = 0
467
lastEdgePresent = False # assert: first edge is always in areaEdges
468
for j, edge in enumerate(edges):
469
if edge not in areaEdges:
470
if lastEdgePresent:
471
# this is the end of a present interval
472
route_intervals.append((start, j - 1))
473
# print("edge '%s' not in area."%edge)
474
lastEdgePresent = False
475
missingEdgeOccurrences[edge] += 1
476
else:
477
if not lastEdgePresent:
478
# this is a start of a present interval
479
start = j
480
lastEdgePresent = True
481
if lastEdgePresent:
482
# print("edges = %s"%str(edges))
483
route_intervals.append((start, len(edges) - 1))
484
return route_intervals
485
486
487
def write_trip(file, vehicle):
488
edges = vehicle.route[0].edges.split()
489
file.write(u' <trip depart="%s" id="%s" from="%s" to="%s" type="%s"' %
490
(vehicle.depart, vehicle.id, edges[0], edges[-1], vehicle.type))
491
if vehicle.departLane:
492
file.write(u' departLane="%s"' % vehicle.departLane)
493
if vehicle.departSpeed:
494
file.write(u' departSpeed="%s"' % vehicle.departSpeed)
495
if vehicle.stop:
496
file.write(u'>\n')
497
for stop in vehicle.stop:
498
file.write(stop.toXML(u' '))
499
file.write(u'</trip>\n')
500
else:
501
file.write(u'/>\n')
502
503
504
def write_route(file, vehicle):
505
file.write(vehicle.toXML(u' '))
506
507
508
def parse_standalone_routes(file, into, typesMap):
509
for element in parse(file, ('vType', 'route')):
510
if element.id is not None:
511
if element.name == 'vType':
512
typesMap[element.id] = element
513
else:
514
into[element.id] = element
515
516
517
def main(options):
518
if options.verbose:
519
print("Reading reduced network from", options.network)
520
net = readNet(options.network)
521
edges = set([e.getID() for e in net.getEdges()])
522
if options.orig_net is not None:
523
if options.verbose:
524
print("Reading original network from", options.orig_net)
525
orig_net = readNet(options.orig_net)
526
else:
527
orig_net = None
528
print("Valid area contains %s edges" % len(edges))
529
530
if options.trips:
531
writer = write_trip
532
else:
533
writer = write_route
534
535
busStopEdges = {}
536
if options.stops_output:
537
busStops = io.open(options.stops_output, 'w', encoding="utf8")
538
writeHeader(busStops, os.path.basename(__file__), 'additional', options=options)
539
if options.additional_input:
540
num_busstops = 0
541
kept_busstops = 0
542
num_taz = 0
543
kept_taz = 0
544
for addFile in options.additional_input.split(","):
545
for busStop in parse(addFile, ('busStop', 'trainStop')):
546
num_busstops += 1
547
edge = busStop.lane[:-2]
548
busStopEdges[busStop.id] = edge
549
if options.stops_output and edge in edges:
550
kept_busstops += 1
551
if busStop.access:
552
busStop.access = [acc for acc in busStop.access if acc.lane[:-2] in edges]
553
busStops.write(busStop.toXML(u' '))
554
for taz in parse(addFile, 'taz'):
555
num_taz += 1
556
taz_edges = [e for e in taz.edges.split() if e in edges]
557
if taz_edges:
558
taz.edges = " ".join(taz_edges)
559
if options.stops_output:
560
kept_taz += 1
561
busStops.write(taz.toXML(u' '))
562
if num_busstops > 0 and num_taz > 0:
563
print("Kept %s of %s busStops and %s of %s tazs" % (
564
kept_busstops, num_busstops, kept_taz, num_taz))
565
elif num_busstops > 0:
566
print("Kept %s of %s busStops" % (kept_busstops, num_busstops))
567
elif num_taz > 0:
568
print("Kept %s of %s tazs" % (kept_taz, num_taz))
569
570
if options.stops_output:
571
busStops.write(u'</additional>\n')
572
busStops.close()
573
574
def write_to_file(vehicles, f):
575
writeHeader(f, os.path.basename(__file__), 'routes', options=options)
576
numRefs = defaultdict(int)
577
for _, v in vehicles:
578
if options.trips and v.name == "vehicle":
579
numRefs["trip"] += 1
580
else:
581
numRefs[v.name] += 1
582
if v.name == "vType":
583
f.write(v.toXML(u' '))
584
else:
585
writer(f, v)
586
f.write(u'</routes>\n')
587
if numRefs:
588
print("Wrote", ", ".join(["%s %ss" % (k[1], k[0]) for k in sorted(numRefs.items())]))
589
else:
590
print("Wrote nothing")
591
592
ptRoutes = {}
593
oldPTRoutes = {}
594
if options.pt_input:
595
allRouteFiles = options.routeFiles
596
options.routeFiles = options.pt_input.split(",")
597
ptExternalRoutes = {}
598
with io.open(options.pt_output if options.pt_output else options.pt_input + ".cut", 'w', encoding="utf8") as f:
599
writeHeader(f, os.path.basename(__file__), 'routes', options=options)
600
for _, v in cut_routes(edges, orig_net, options, busStopEdges, None, oldPTRoutes, True):
601
f.write(v.toXML(u' '))
602
if v.name == "route":
603
ptExternalRoutes[v.id] = v.edges.split()
604
elif isinstance(v.route, list):
605
ptRoutes[v.line] = v.route[0].edges.split()
606
elif v.route is not None:
607
ptRoutes[v.line] = ptExternalRoutes[v.route]
608
f.write(u'</routes>\n')
609
options.routeFiles = allRouteFiles
610
611
if options.output:
612
if options.big:
613
# write output unsorted
614
tmpname = options.output + ".unsorted"
615
with io.open(tmpname, 'w', encoding="utf8") as f:
616
write_to_file(cut_routes(edges, orig_net, options, busStopEdges, ptRoutes, oldPTRoutes), f)
617
# sort out of memory
618
sort_routes.main([tmpname, '--big', '--outfile', options.output] +
619
(['--verbose'] if options.verbose else []))
620
else:
621
routes = list(cut_routes(edges, orig_net, options, busStopEdges, ptRoutes, oldPTRoutes))
622
routes.sort(key=lambda v: v[0])
623
with io.open(options.output, 'w', encoding="utf8") as f:
624
write_to_file(routes, f)
625
626
627
if __name__ == "__main__":
628
main(get_options())
629
630