Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/import/gtfs/gtfs2pt.py
169679 views
1
#!/usr/bin/env python3
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2010-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 gtfs2pt.py
15
# @author Jakob Erdmann
16
# @author Michael Behrisch
17
# @author Robert Hilbrich
18
# @date 2018-08-28
19
20
"""
21
Maps GTFS data to a given network, generating routes, stops and vehicles
22
"""
23
24
from __future__ import print_function
25
from __future__ import absolute_import
26
from __future__ import division
27
import os
28
import sys
29
import glob
30
import subprocess
31
import collections
32
import zipfile
33
import rtree
34
import pandas as pd
35
pd.options.mode.chained_assignment = None # default='warn'
36
37
sys.path += [os.path.join(os.environ["SUMO_HOME"], "tools"),
38
os.path.join(os.environ['SUMO_HOME'], 'tools', 'route')]
39
import route2poly # noqa
40
import sumolib # noqa
41
from sumolib.miscutils import humanReadableTime # noqa
42
import tracemapper # noqa
43
44
import gtfs2fcd # noqa
45
import gtfs2osm # noqa
46
47
48
def get_options(args=None):
49
ap = gtfs2fcd.add_options()
50
# ----------------------- general options ---------------------------------
51
ap.add_argument("-n", "--network", category="input", required=True, type=ap.net_file,
52
help="sumo network to use")
53
ap.add_argument("--route-output", category="output", type=ap.route_file,
54
help="file to write the generated public transport vehicles to")
55
ap.add_argument("--additional-output", category="output", type=ap.additional_file,
56
help="file to write the generated public transport stops and routes to")
57
ap.add_argument("--duration", default=10, category="input",
58
type=int, help="minimum time to wait on a stop")
59
ap.add_argument("--bus-parking", action="store_true", default=False, category="processing", dest="busParking",
60
help="set parking to true for bus mode")
61
ap.add_argument("--bus-stop-length", default=13, category="input", type=float,
62
help="length for a bus stop")
63
ap.add_argument("--train-stop-length", default=110, category="input", type=float,
64
help="length for a train stop")
65
ap.add_argument("--tram-stop-length", default=60, category="input", type=float,
66
help="length for a tram stop")
67
ap.add_argument("--center-stops", action="store_true", default=False, category="processing",
68
help="use stop position as center not as front")
69
ap.add_argument("--skip-access", action="store_true", default=False, category="processing",
70
help="do not create access links")
71
ap.add_argument("--sort", action="store_true", default=False, category="processing",
72
help="sorting the output-file")
73
ap.add_argument("--stops", category="input", type=ap.file_list,
74
help="files with candidate stops (selected by proxmity)")
75
ap.add_argument("--patched-stops", category="input", dest="patchedStops", type=ap.file,
76
help="file with replacement stops (based on stop ids)")
77
ap.add_argument("--radius", default=150, category="input", type=float,
78
help="maximum matching radius for candidate edges and stops")
79
80
# ----------------------- fcd options -------------------------------------
81
ap.add_argument("--network-split", category="input",
82
help="directory to write generated networks to")
83
ap.add_argument("--network-split-vclass", action="store_true", default=False, category="processing",
84
help="use the allowed vclass instead of the edge type to split the network")
85
ap.add_argument("--warn-unmapped", action="store_true", default=False, category="processing",
86
help="warn about unmapped routes")
87
ap.add_argument("--mapperlib", default="lib/fcd-process-chain-2.2.2.jar", category="input",
88
help="mapping library to use")
89
ap.add_argument("--map-output", category="output",
90
help="directory to write the generated mapping files to")
91
ap.add_argument("--map-output-config", default="conf/output_configuration_template.xml", category="output",
92
type=ap.file, help="output configuration template for the mapper library")
93
ap.add_argument("--map-input-config", default="conf/input_configuration_template.xml", category="input",
94
type=ap.file, help="input configuration template for the mapper library")
95
ap.add_argument("--map-parameter", default="conf/parameters_template.xml", category="input", type=ap.file,
96
help="parameter template for the mapper library")
97
ap.add_argument("--poly-output", category="output", type=ap.file,
98
help="file to write the generated polygon files to")
99
ap.add_argument("--fill-gaps", default=5000, type=float, category="input",
100
help="maximum distance between stops")
101
ap.add_argument("--skip-fcd", action="store_true", default=False, category="processing",
102
help="skip generating fcd data")
103
ap.add_argument("--skip-map", action="store_true", default=False, category="processing",
104
help="skip network mapping")
105
106
# ----------------------- osm options -------------------------------------
107
ap.add_argument("--osm-routes", category="input", type=ap.route_file, help="osm routes file")
108
ap.add_argument("--warning-output", category="output", type=ap.file,
109
help="file to write the unmapped elements from gtfs")
110
ap.add_argument("--dua-repair-output", category="output", type=ap.file,
111
help="file to write the osm routes with errors")
112
ap.add_argument("--repair", help="repair osm routes", action='store_true', category="processing")
113
ap.add_argument("--min-stops", default=1, type=int, category="input",
114
help="minimum number of stops a public transport line must have to be imported")
115
116
options = ap.parse_args(args)
117
118
options = gtfs2fcd.check_options(options)
119
120
if options.additional_output is None:
121
options.additional_output = options.region + "_pt_stops.add.xml"
122
if options.route_output is None:
123
options.route_output = options.region + "_pt_vehicles.add.xml"
124
if options.warning_output is None:
125
options.warning_output = options.region + "_missing.xml"
126
if options.dua_repair_output is None:
127
options.dua_repair_output = options.region + "_repair_errors.txt"
128
if options.map_output is None:
129
options.map_output = os.path.join('output', options.region)
130
if options.network_split is None:
131
options.network_split = os.path.join('resources', options.region)
132
133
return options
134
135
136
def splitNet(options):
137
netcCall = [sumolib.checkBinary("netconvert"), "--no-internal-links", "--numerical-ids", "--no-turnarounds",
138
"--no-warnings",
139
"--offset.disable-normalization", "--output.original-names", "--aggregate-warnings", "1",
140
"--junctions.corner-detail", "0", "--dlr-navteq.precision", "0", "--geometry.avoid-overlap", "false"]
141
doNavteqOut = os.path.exists(options.mapperlib)
142
if not os.path.exists(options.network_split):
143
os.makedirs(options.network_split)
144
numIdNet = os.path.join(options.network_split, "numerical.net.xml")
145
if os.path.exists(numIdNet) and os.path.getmtime(numIdNet) > os.path.getmtime(options.network):
146
print("Reusing old", numIdNet)
147
else:
148
subprocess.call(netcCall + ["-s", options.network, "-o", numIdNet,
149
"--discard-params", "origId,origFrom,origTo"])
150
edgeMap = {}
151
invEdgeMap = {}
152
seenTypes = set()
153
for e in sumolib.net.readNet(numIdNet).getEdges():
154
origId = e.getLanes()[0].getParam("origId", e.getID())
155
edgeMap[e.getID()] = origId
156
invEdgeMap[origId] = e.getID()
157
seenTypes.add(e.getType())
158
typedNets = {}
159
for inp in sorted(glob.glob(os.path.join(options.fcd, "*.fcd.xml"))):
160
mode = os.path.basename(inp)[:-8]
161
if not options.modes or mode in options.modes.split(","):
162
netPrefix = os.path.join(options.network_split, mode)
163
if options.network_split_vclass:
164
vclass = gtfs2osm.OSM2SUMO_MODES.get(mode)
165
edgeFilter = ["--keep-edges.by-vclass", vclass] if vclass else None
166
else:
167
edgeFilter = ["--keep-edges.by-type", mode] if mode in seenTypes else None
168
if "rail" in mode or mode == "subway":
169
if "railway." + mode in seenTypes:
170
edgeFilter = ["--keep-edges.by-type", "railway." + mode]
171
elif mode == "train":
172
if "railway.rail" in seenTypes or "railway.light_rail" in seenTypes:
173
edgeFilter = ["--keep-edges.by-type", "railway.rail,railway.light_rail"]
174
elif mode in ("tram", "bus"):
175
edgeFilter = ["--keep-edges.by-vclass", mode]
176
if edgeFilter:
177
if (os.path.exists(netPrefix + ".net.xml") and
178
os.path.getmtime(netPrefix + ".net.xml") > os.path.getmtime(numIdNet)):
179
print("Reusing old", netPrefix + ".net.xml")
180
else:
181
if subprocess.call(netcCall + ["-s", numIdNet, "-o", netPrefix + ".net.xml"] + edgeFilter):
182
print("Error generating %s.net.xml, maybe it does not contain infrastructure for '%s'." %
183
(netPrefix, mode))
184
continue
185
if doNavteqOut:
186
subprocess.call(netcCall + ["-s", netPrefix + ".net.xml", "-o", "NUL", "--dismiss-vclasses"
187
"--no-internal-links", # traceMap ignores internal links
188
"--dlr-navteq-output", netPrefix])
189
typedNets[mode] = (inp, netPrefix)
190
return edgeMap, invEdgeMap, typedNets
191
192
193
def mapFCD(options, typedNets):
194
for o in glob.glob(os.path.join(options.map_output, "*.dat")):
195
os.remove(o)
196
outConf = os.path.join(os.path.dirname(options.map_output_config), "output_configuration.xml")
197
with open(options.map_output_config) as inp, open(outConf, "w") as outp:
198
outp.write(inp.read() % {"output": options.map_output})
199
for mode, (gpsdat, netPrefix) in typedNets.items():
200
conf = os.path.join(os.path.dirname(options.map_input_config), "input_configuration_%s.xml") % mode
201
with open(options.map_input_config) as inp, open(conf, "w") as outp:
202
outp.write(inp.read() % {"input": gpsdat, "net_prefix": netPrefix})
203
param = os.path.join(os.path.dirname(options.map_parameter), "parameters_%s.xml") % mode
204
with open(options.map_parameter) as inp, open(param, "w") as outp:
205
outp.write(inp.read() % {"radius": 100 if mode in ("bus", "tram") else 1000})
206
call = "java -mx16000m -jar %s %s %s %s" % (options.mapperlib, conf, outConf, param)
207
if options.verbose:
208
print(call)
209
sys.stdout.flush()
210
subprocess.call(call, shell=True)
211
212
213
def traceMap(options, veh2mode, typedNets, fixedStops, stopLookup, invEdgeMap, radius=150):
214
routes = collections.OrderedDict()
215
for mode in sorted(typedNets.keys()):
216
vclass = gtfs2osm.OSM2SUMO_MODES.get(mode)
217
if options.verbose:
218
print("mapping", mode)
219
net = sumolib.net.readNet(os.path.join(options.network_split, mode + ".net.xml"))
220
mode_edges = set([e.getID() for e in net.getEdges()])
221
netBox = net.getBBoxXY()
222
numTraces = 0
223
numRoutes = 0
224
cacheHits = 0
225
filePath = os.path.join(options.fcd, mode + ".fcd.xml")
226
if not os.path.exists(filePath):
227
return []
228
traces = tracemapper.readFCD(filePath, net, True)
229
traceCache = {}
230
for tid, trace in traces:
231
trace = tuple(trace)
232
numTraces += 1
233
minX, minY, maxX, maxY = sumolib.geomhelper.addToBoundingBox(trace)
234
if (minX < netBox[1][0] + radius and minY < netBox[1][1] + radius and
235
maxX > netBox[0][0] - radius and maxY > netBox[0][1] - radius):
236
vias = {}
237
if stopLookup.hasCandidates():
238
for idx, xy in enumerate(trace):
239
candidates = stopLookup.getCandidates(xy, options.radius)
240
if candidates:
241
all_edges = [invEdgeMap[sumolib._laneID2edgeID(stop.lane)] for stop in candidates]
242
vias[idx] = [e for e in all_edges if e in mode_edges]
243
for idx in range(len(trace)):
244
fixed = fixedStops.get("%s.%s" % (tid, idx))
245
if fixed:
246
vias[idx] = [invEdgeMap[sumolib._laneID2edgeID(fixed.lane)]]
247
if trace in traceCache:
248
mappedRoute = traceCache[trace]
249
cacheHits += 1
250
else:
251
mappedRoute = sumolib.route.mapTrace(trace, net, radius, verbose=options.verbose,
252
fillGaps=options.fill_gaps, gapPenalty=5000.,
253
vClass=vclass, vias=vias,
254
fastest=True,
255
reversalPenalty=1000.)
256
traceCache[trace] = mappedRoute
257
258
if mappedRoute:
259
numRoutes += 1
260
routes[tid] = [e.getID() for e in mappedRoute]
261
veh2mode[tid] = mode
262
if options.verbose:
263
print("mapped %s traces to %s routes (%s cacheHits)" % (
264
numTraces, numRoutes, cacheHits))
265
return routes
266
267
268
def generate_polygons(net, routes, outfile):
269
colorgen = sumolib.miscutils.Colorgen(('random', 1, 1))
270
271
class PolyOptions:
272
internal = False
273
spread = 0.2
274
blur = 0
275
geo = True
276
layer = 100
277
with open(outfile, 'w') as outf:
278
outf.write('<polygons>\n')
279
for vehID, edges in routes.items():
280
route2poly.generate_poly(PolyOptions, net, vehID, colorgen(), edges, outf)
281
outf.write('</polygons>\n')
282
283
284
def map_stops(options, net, routes, rout, edgeMap, fixedStops, stopLookup):
285
stops = collections.defaultdict(list)
286
stopEnds = collections.defaultdict(list)
287
rid = None
288
for inp in sorted(glob.glob(os.path.join(options.fcd, "*.fcd.xml"))):
289
mode = os.path.basename(inp)[:-8]
290
vclass = gtfs2osm.OSM2SUMO_MODES.get(mode)
291
typedNetFile = os.path.join(options.network_split, mode + ".net.xml")
292
if not os.path.exists(typedNetFile):
293
print("Warning! No net", typedNetFile, file=sys.stderr)
294
continue
295
if options.verbose:
296
print("Reading", typedNetFile)
297
typedNet = sumolib.net.readNet(typedNetFile)
298
seen = set()
299
fixed = {}
300
for veh in sumolib.xml.parse_fast(inp, "vehicle", ("id", "x", "y", "until", "name",
301
"fareZone", "fareSymbol", "startFare")):
302
stopName = veh.attr_name
303
addAttrs = ' friendlyPos="true" name="%s"' % stopName
304
params = ""
305
if veh.fareZone:
306
params = "".join([' <param key="%s" value="%s"/>\n' %
307
p for p in (('fareZone', veh.fareZone), ('fareSymbol', veh.fareSymbol),
308
('startFare', veh.startFare))])
309
if rid != veh.id:
310
lastIndex = 0
311
lastPos = -1
312
rid = veh.id
313
stopIndex = 0
314
else:
315
stopIndex += 1
316
if rid not in routes:
317
if options.warn_unmapped and rid not in seen:
318
print("Warning! Not mapped", rid, file=sys.stderr)
319
seen.add(rid)
320
continue
321
if rid not in fixed:
322
routeFixed = [routes[rid][0]]
323
for routeEdgeID in routes[rid][1:]:
324
path, _ = typedNet.getShortestPath(typedNet.getEdge(routeFixed[-1]),
325
typedNet.getEdge(routeEdgeID),
326
vClass=vclass)
327
if path is None or len(path) > options.fill_gaps + 2:
328
error = "no path found" if path is None else "path too long (%s)" % len(path)
329
print("Warning! Disconnected route '%s' between '%s' and '%s', %s. Keeping longer part." %
330
(rid, edgeMap.get(routeFixed[-1]), edgeMap.get(routeEdgeID), error), file=sys.stderr)
331
if len(routeFixed) > len(routes[rid]) // 2:
332
break
333
routeFixed = [routeEdgeID]
334
else:
335
if len(path) > 2:
336
print("Warning! Fixed connection", rid, len(path), file=sys.stderr)
337
routeFixed += [e.getID() for e in path[1:]]
338
if rid not in routes:
339
continue
340
routes[rid] = routeFixed
341
fixed[rid] = [edgeMap[e] for e in routeFixed]
342
route = fixed[rid]
343
if mode == "bus":
344
stopLength = options.bus_stop_length
345
elif mode == "tram":
346
stopLength = options.tram_stop_length
347
else:
348
stopLength = options.train_stop_length
349
stop = "%s.%s" % (rid, stopIndex)
350
if stop in fixedStops:
351
s = fixedStops[stop]
352
laneID, start, end = s.lane, float(s.startPos), float(s.endPos)
353
else:
354
result = None
355
if stopLookup.hasCandidates():
356
xy = net.convertLonLat2XY(float(veh.x), float(veh.y))
357
candidates = stopLookup.getCandidates(xy, options.radius)
358
if candidates:
359
on_route = [s for s in candidates if sumolib._laneID2edgeID(s.lane) in route[lastIndex:]]
360
if on_route:
361
bestDist = 1e3 * options.radius
362
for stopObj in on_route:
363
lane = net.getLane(stopObj.lane)
364
if not lane.allows(vclass):
365
for lane2 in lane.getEdge().getLanes():
366
if lane2.allows(vclass):
367
print("Warning! Fixed lane of loaded stop", stopObj.id, file=sys.stderr)
368
lane = lane2
369
if not lane.allows(vclass):
370
print("Warning! Unable to fix lane of loaded stop", stopObj.id, file=sys.stderr)
371
continue
372
373
endPos = float(stopObj.endPos)
374
if (stopIndex > 0
375
and sumolib._laneID2edgeID(stopObj.lane) == route[lastIndex]
376
and lane.interpretOffset(endPos) < lane.interpretOffset(lastPos)):
377
continue
378
dist = sumolib.geomhelper.distance(stopObj.center_xy, xy)
379
if dist < bestDist:
380
bestDist = dist
381
result = (lane.getID(), float(stopObj.startPos), endPos)
382
if result is None:
383
result = gtfs2osm.getBestLane(net, veh.x, veh.y, 200, stopLength, options.center_stops,
384
route[lastIndex:], gtfs2osm.OSM2SUMO_MODES[mode], lastPos)
385
if result is None:
386
if options.warn_unmapped:
387
print("Warning! No stop for %s." % str(veh), file=sys.stderr)
388
continue
389
laneID, start, end = result
390
edgeID = laneID.rsplit("_", 1)[0]
391
lastIndex = route.index(edgeID, lastIndex)
392
lastPos = end
393
keep = True
394
for otherStop, otherStart, otherEnd in stopEnds[laneID]:
395
if (otherEnd > start and otherEnd <= end) or (end > otherStart and end <= otherEnd):
396
keep = False
397
stop = otherStop
398
break
399
if keep:
400
stopEnds[laneID].append((stop, start, end))
401
access = None if options.skip_access else gtfs2osm.getAccess(net, veh.x, veh.y, 100, laneID)
402
if not access and not params:
403
addAttrs += "/"
404
typ = "busStop" if mode == "bus" else "trainStop"
405
rout.write(u' <%s id="%s" lane="%s" startPos="%.2f" endPos="%.2f"%s>\n%s' %
406
(typ, stop, laneID, start, end, addAttrs, params))
407
if access or params:
408
for a in sorted(access):
409
rout.write(a)
410
rout.write(u' </%s>\n' % typ)
411
stops[rid].append((stop, int(veh.until), stopName))
412
return stops
413
414
415
def filter_trips(options, routes, stops, outf, begin, end):
416
ft = humanReadableTime if options.hrtime else lambda x: x
417
numDays = int(end) // 86400
418
if end % 86400 != 0:
419
numDays += 1
420
if options.sort:
421
vehs = collections.defaultdict(lambda: "")
422
for inp in sorted(glob.glob(os.path.join(options.fcd, "*.rou.xml"))):
423
for veh in sumolib.xml.parse_fast_structured(inp, "vehicle", ("id", "route", "type", "depart", "line"),
424
{"param": ["key", "value"]}):
425
if len(routes.get(veh.route, [])) > 0 and len(stops.get(veh.route, [])) > 1:
426
until = stops[veh.route][0][1]
427
for d in range(numDays):
428
depart = max(0, d * 86400 + int(veh.depart) + until - options.duration)
429
if begin <= depart < end:
430
if d != 0 and veh.id.endswith(".trimmed"):
431
# only add trimmed trips the first day
432
continue
433
line = (u' <vehicle id="%s.%s" route="%s" type="%s" depart="%s" line="%s">\n' %
434
(veh.id, d, veh.route, veh.type, ft(depart), veh.line))
435
for p in veh.param:
436
line += u' <param key="%s" value="%s"/>\n' % p
437
line += u' </vehicle>\n'
438
if options.sort:
439
vehs[depart] += line
440
else:
441
outf.write(line)
442
if options.sort:
443
for _, vehs in sorted(vehs.items()):
444
outf.write(vehs)
445
446
447
class StopLookup:
448
def __init__(self, fnames, net):
449
self._candidates = []
450
self._net = net
451
self._rtree = rtree.index.Index()
452
if fnames:
453
for fname in fnames.split(','):
454
self._candidates += list(sumolib.xml.parse(fname, ("busStop", "trainStop")))
455
for ri, stop in enumerate(self._candidates):
456
lane = net.getLane(stop.lane)
457
middle = (lane.interpretOffset(float(stop.startPos)) +
458
lane.interpretOffset(float(stop.endPos))) / 2
459
x, y = sumolib.geomhelper.positionAtShapeOffset(lane.getShape(), middle)
460
bbox = (x - 1, y - 1, x + 1, y + 1)
461
self._rtree.add(ri, bbox)
462
stop.center_xy = (x, y)
463
464
def hasCandidates(self):
465
return len(self._candidates) > 0
466
467
def getCandidates(self, xy, r=150):
468
if self._candidates:
469
stops = []
470
x, y = xy
471
for i in self._rtree.intersection((x - r, y - r, x + r, y + r)):
472
stops.append(self._candidates[i])
473
return stops
474
else:
475
return []
476
477
478
def main(options):
479
if options.verbose:
480
print('Loading net')
481
net = sumolib.net.readNet(options.network)
482
483
if not options.bbox:
484
bboxXY = net.getBBoxXY()
485
options.bbox = net.convertXY2LonLat(*bboxXY[0]) + net.convertXY2LonLat(*bboxXY[1])
486
else:
487
options.bbox = [float(coord) for coord in options.bbox.split(",")]
488
fixedStops = {}
489
stopLookup = StopLookup(options.stops, net)
490
if options.patchedStops:
491
for stop in sumolib.xml.parse(options.patchedStops, ("busStop", "trainStop")):
492
fixedStops[stop.id] = stop
493
if options.osm_routes:
494
# Import PT from GTFS and OSM routes
495
gtfsZip = zipfile.ZipFile(sumolib.openz(options.gtfs, mode="rb", tryGZip=False, printErrors=True))
496
routes, trips_on_day, shapes, stops, stop_times = gtfs2osm.import_gtfs(options, gtfsZip)
497
gtfsZip.fp.close()
498
if options.mergedCSVOutput:
499
full_data_merged = gtfs2fcd.get_merged_data(options)
500
full_data_merged.sort_values(by=['trip_id', 'stop_sequence'], inplace=True)
501
full_data_merged.to_csv(options.mergedCSVOutput, sep=";", index=False)
502
503
if routes.empty or trips_on_day.empty:
504
return
505
if shapes is None:
506
print('Warning: GTFS shapes file not found! Continuing mapping without shapes.', file=sys.stderr)
507
(gtfs_data, trip_list,
508
filtered_stops,
509
shapes, shapes_dict) = gtfs2osm.filter_gtfs(options, routes,
510
trips_on_day, shapes,
511
stops, stop_times)
512
513
osm_routes = gtfs2osm.import_osm(options, net)
514
515
(mapped_routes, mapped_stops,
516
missing_stops, missing_lines) = gtfs2osm.map_gtfs_osm(options, net, osm_routes, gtfs_data, shapes,
517
shapes_dict, filtered_stops)
518
519
gtfs2osm.write_gtfs_osm_outputs(options, mapped_routes, mapped_stops,
520
missing_stops, missing_lines,
521
gtfs_data, trip_list, shapes_dict, net)
522
if not options.osm_routes:
523
veh2mode = {}
524
# Import PT from GTFS
525
if not options.skip_fcd:
526
if not os.path.exists(options.mapperlib):
527
options.gpsdat = None
528
if not gtfs2fcd.main(options):
529
print("Warning! GTFS data did not contain any trips with stops within the given bounding box area.",
530
file=sys.stderr)
531
return
532
edgeMap, invEdgeMap, typedNets = splitNet(options)
533
if os.path.exists(options.mapperlib):
534
if not options.skip_map:
535
mapFCD(options, typedNets)
536
routes = collections.OrderedDict()
537
for o in glob.glob(os.path.join(options.map_output, "*.dat")):
538
for line in open(o):
539
time, edge, speed, coverage, id, minute_of_week = line.split('\t')[:6]
540
routes.setdefault(id, []).append(edge)
541
else:
542
if not gtfs2fcd.dataAvailable(options):
543
print("Warning! No infrastructure for the given modes %s." % options.modes, file=sys.stderr)
544
return
545
if options.mapperlib != "tracemapper":
546
print("Warning! No mapping library found, falling back to tracemapper.", file=sys.stderr)
547
routes = traceMap(options, veh2mode, typedNets, fixedStops, stopLookup, invEdgeMap, options.radius)
548
549
if options.poly_output:
550
generate_polygons(net, routes, options.poly_output)
551
with sumolib.openz(options.additional_output, mode='w') as aout:
552
sumolib.xml.writeHeader(aout, os.path.basename(__file__), "additional", options=options)
553
stops = map_stops(options, net, routes, aout, edgeMap, fixedStops, stopLookup)
554
aout.write(u'</additional>\n')
555
with sumolib.openz(options.route_output, mode='w') as rout:
556
ft = humanReadableTime if options.hrtime else lambda x: x
557
sumolib.xml.writeHeader(rout, os.path.basename(__file__), "routes", options=options)
558
for vehID, edges in routes.items():
559
parking = ' parking="true"' if (options.busParking and veh2mode.get(vehID) == "bus") else ""
560
if edges:
561
rout.write(u' <route id="%s" edges="%s">\n' % (vehID, " ".join([edgeMap[e] for e in edges])))
562
offset = None
563
for stop in stops[vehID]:
564
if offset is None:
565
offset = stop[1]
566
rout.write(u' <stop busStop="%s" duration="%s" until="%s"%s/> <!-- %s -->\n' %
567
(stop[0], ft(options.duration), ft(stop[1] - offset), parking, stop[2]))
568
rout.write(u' </route>\n')
569
else:
570
print("Warning! Empty route for %s." % vehID, file=sys.stderr)
571
filter_trips(options, routes, stops, rout, options.begin, options.end)
572
rout.write(u'</routes>\n')
573
574
575
if __name__ == "__main__":
576
main(get_options())
577
578