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