diff --git a/tools/ptlines2flows.py b/tools/ptlines2flows.py
index c58573c40a8..da0490b03d4 100755
--- a/tools/ptlines2flows.py
+++ b/tools/ptlines2flows.py
@@ -23,10 +23,12 @@
import subprocess
import collections
import random
+import math
import sumolib
from sumolib.xml import quoteattr
from sumolib.options import ArgumentParser
+from sumolib import geomhelper
def get_options(args=None):
@@ -51,12 +53,16 @@ def get_options(args=None):
"if none is specified in the ptlines file"))
ap.add_option("-b", "--begin", type=ap.time, default=0, help="start time")
ap.add_option("-e", "--end", type=ap.time, default=3600, help="end time")
- ap.add_option("-j", "--jump-duration", type=ap.time, default=300,
- help="The time to add for each missing (when joining opposite lines)")
+ ap.add_option("-j", "--jump-duration", type=ap.time, default=180, dest="jumpDuration",
+ help="The time to add for each missing stop (when joining opposite lines)")
ap.add_option("-T", "--turnaround-duration", type=ap.time, default=300,
help="The extra stopping time at terminal stops")
- ap.add_option("--no-join", default=False, action="store_true", dest='noJoin',
- help="Do not join opposite lines at the terminals")
+ ap.add_option("--join", default=False, action="store_true",
+ help="Join opposite lines at the terminals")
+ ap.add_option("--join-threshold", default=100, type=float, dest='joinThreshold',
+ help="maximum distance for terminal stops when joining lines")
+ ap.add_option("--multistart", default=False, action="store_true",
+ help="Insert multiple vehicles per line at different offsets along the route to avoid warmup")
ap.add_option("--min-stops", type=int, default=2, help="only import lines with at least this number of stops")
ap.add_option("-f", "--flow-attributes", dest="flowattrs", default="", help="additional flow attributes")
ap.add_option("--use-osm-routes", default=False, action="store_true", dest='osmRoutes', help="use osm routes")
@@ -111,8 +117,8 @@ def __init__(self, ref, name, completeness, missingBefore, missingAfter,
self.ref = ref
self.name = name
self.completeness = completeness
- self.missingBeore = missingBefore
- self.missingAfter = missingAfter
+ self.missingBefore = int(missingBefore) if missingBefore is not None else 0
+ self.missingAfter = int(missingAfter) if missingAfter is not None else 0
self.fromEdge = fromEdge
self.toEdge = toEdge
self.period = period
@@ -122,6 +128,10 @@ def __init__(self, ref, name, completeness, missingBefore, missingAfter,
self.depart = depart
self.stop_ids = stop_ids
self.vias = vias
+ # stop indices that need special handling
+ self.jumps = {} # stopIndex -> duration
+ self.terminalIndices = []
+ self.repeat = None
def writeTypes(fout, prefix, options):
@@ -173,7 +183,6 @@ def createTrips(options):
tripList = [] # ids
trpMap = {} # ids->PTLine
- linePairs = collections.defaultdict(list)
departTimes = [options.begin for line in sumolib.output.parse_fast(options.ptlines, 'ptLine', ['id'])]
if options.randomBegin:
@@ -284,9 +293,8 @@ def createTrips(options):
fromEdge = getStopEdge(stopsLanes, stop_ids[0])
toEdge = getStopEdge(stopsLanes, stop_ids[-1])
- linePairs[lineRefOrig].append(lineRef)
- missingBefore = 0 if line.completeness == 1 else line.getAttributeSecure(line.missingBefore)
- missingAfter = 0 if line.completeness == 1 else line.getAttributeSecure(line.missingAfter)
+ missingBefore = 0 if line.completeness == 1 else line.missingBefore
+ missingAfter = 0 if line.completeness == 1 else line.missingAfter
typeID = options.vtypeprefix + line.type
trpMap[tripID] = PTLine(lineRef, line.attr_name, line.completeness,
missingBefore, missingAfter,
@@ -302,6 +310,8 @@ def createTrips(options):
numLines += 1
numStops += len(stop_ids)
+ if options.join:
+ joinTrips(options, tripList, trpMap)
writeTrips(options, tripList, trpMap)
if options.verbose:
print("Imported %s lines with %s stops and skipped %s lines" % (numLines, numStops, numSkipped))
@@ -311,6 +321,79 @@ def createTrips(options):
return trpMap, stopNames
+def joinTrips(options, tripList, trpMap):
+ # join opposite pairs of trips
+ linePairs = collections.defaultdict(list)
+ for tripID, ptl in trpMap.items():
+ linePairs[ptl.refOrig].append(tripID)
+
+ for refOrig, tripIDs in linePairs.items():
+ if len(tripIDs) > 2:
+ sys.stderr.write("Warning: Cannot join line '%s' with more %s trips" % (refOrig, len(tripIDs)))
+ elif len(tripIDs) == 2:
+ net = getNet(options)
+ ptl1 = trpMap[tripIDs[0]]
+ ptl2 = trpMap[tripIDs[1]]
+ join1 = distCheck(options, refOrig, ptl1.toEdge, ptl2.fromEdge)
+ join2 = distCheck(options, refOrig, ptl1.fromEdge, ptl2.toEdge)
+ if not join1 and not join2:
+ continue
+ ptl1.completeness = str(0.5 * (float(ptl1.completeness) + float(ptl2.completeness)))
+ ptl2.completeness = ptl1.completeness
+ ptl1.ref = ptl1.refOrig
+ ptl2.ref = ptl2.refOrig
+
+ from1 = net.getEdge(ptl1.fromEdge)
+ from2 = net.getEdge(ptl2.fromEdge)
+ to1 = net.getEdge(ptl1.toEdge)
+ to2 = net.getEdge(ptl2.toEdge)
+ missingPart1 = ptl1.missingAfter + ptl2.missingBefore
+ missingPart2 = ptl2.missingAfter + ptl1.missingBefore
+
+ if join1:
+ # append ptl1 after ptl2
+ tripList.remove(tripIDs[1])
+ if missingPart1 != 0 or to1.getBidi() != from2.getID():
+ ptl1.jumps[len(ptl1.stop_ids) - 1] = missingPart1 * options.jumpDuration
+ ptl1.terminalIndices.append(len(ptl1.stop_ids) - 1)
+ ptl1.stop_ids += ptl2.stop_ids
+ ptl1.vias += ptl2.vias
+ ptl1.toEdge = ptl2.toEdge
+
+ if join2:
+ # line forms a full circle so that vehicles can stay in the simulation continously.
+ # We have to compute the appropriate number of vehicles and then adapt the end time
+ if missingPart2 != 0 or to2.getBidi() != from1.getID():
+ ptl1.jumps[len(ptl1.stop_ids) - 1] = missingPart2 * options.jumpDuration
+ ptl1.terminalIndices.append(len(ptl1.stop_ids) - 1)
+
+
+ elif join2:
+ # only append ptl1 after ptl2
+ tripList.remove(tripIDs[0])
+ if missingPart2 != 0 or to2.getBidi() != from1.getID():
+ ptl2.jumps[len(ptl2.stop_ids) - 1] = missingPart2 * options.jumpDuration
+ ptl2.terminalIndices.append(len(ptl2.stop_ids) - 1)
+ ptl2.stop_ids += ptl1.stop_ids
+ ptl2.vias += ptl1.vias
+ ptl2.toEdge = ptl1.toEdge
+
+
+def distCheck(options, refOrig, eID1, eID2):
+ net = getNet(options)
+ shape1 = net.getEdge(eID1).getShape(True)
+ shape2 = net.getEdge(eID2).getShape(True)
+ minDist = options.joinThreshold + 1
+ for p in shape1:
+ minDist = min(minDist, geomhelper.distancePointToPolygon(p, shape2))
+ if minDist > options.joinThreshold:
+ sys.stderr.write("Warning: Cannot join line '%s' at edges '%s' and '%s' with distance %s" % (
+ refOrig, eID1, eID2, minDist))
+ return False
+ else:
+ return True
+
+
def writeTrips(options, tripList, trpMap):
with codecs.open(options.trips, 'w', encoding="UTF8") as fouttrips:
sumolib.writeXMLHeader(
@@ -324,9 +407,13 @@ def writeTrips(options, tripList, trpMap):
(' \n') % (
tripID, ptl.typeID, ptl.depart, ptl.fromEdge, ptl.toEdge, ptl.vias))
- for stop in ptl.stop_ids:
+ for i, stop in enumerate(ptl.stop_ids):
dur = options.stopduration + options.stopdurationSlack
- fouttrips.write(' \n' % (stop, dur))
+ if i in ptl.jumps:
+ jump = ' jump="%s"' % ptl.jumps[i]
+ else:
+ jump = ""
+ fouttrips.write(' \n' % (stop, dur, jump))
fouttrips.write(' \n')
fouttrips.write("\n")
@@ -369,6 +456,9 @@ def createRoutes(options, trpMap, stopNames):
if not options.novtypes:
writeTypes(foutflows, options.vtypeprefix, None)
collections.defaultdict(int)
+ routeDurations = {}
+ routeSize = {}
+ flow_duration = options.end - options.begin
for vehicle in sumolib.output.parse(options.routes, 'vehicle'):
id = vehicle.id
ptline = trpMap[id]
@@ -378,6 +468,7 @@ def createRoutes(options, trpMap, stopNames):
edges = vehicle.route[0].edges
else:
edges = vehicle.routeDistribution[0].route[1].edges
+ routeSize[flowID] = len(edges.split())
except BaseException:
if options.ignoreErrors:
sys.stderr.write("Warning: Could not parse edges for vehicle '%s'\n" % id)
@@ -389,38 +480,71 @@ def createRoutes(options, trpMap, stopNames):
parking = ' parking="true"' if vehicle.type == "bus" and options.busparking else ''
stops = vehicle.stop
color = ' color="%s"' % ptline.color if ptline.color is not None else ""
- foutflows.write(' \n' % (flowID, color, edges))
+ repeat = ""
+ if len(ptline.terminalIndices) == 2 and stops:
+ lastBusStop = stops[-1].busStop
+ lastUntil = stopsUntil.get((id, lastBusStop))
+ if lastUntil is not None:
+ numRepeats = math.ceil(flow_duration / (lastUntil[-1] - actualDepart[id]))
+ if numRepeats > 1:
+ repeat = ' repeat="%s"' % numRepeats
+
+ foutflows.write(' \n' % (flowID, color, edges, repeat))
if vehicle.stop is not None:
for stop in stops:
if (id, stop.busStop) in stopsUntil:
until = stopsUntil[(id, stop.busStop)]
stopname = ' ' % stopNames[stop.busStop] if stop.busStop in stopNames else ''
untilZeroBased = until[0] - actualDepart[id]
+ if stop.jump is not None:
+ jump = ' jump="%s"' % stop.jump
+ else:
+ jump = ""
if len(until) > 1:
stopsUntil[(id, stop.busStop)] = until[1:]
foutflows.write(
- ' %s\n' % (
- stop.busStop, options.stopduration, ft(untilZeroBased), parking, stopname))
+ ' %s\n' % (
+ stop.busStop, options.stopduration, ft(untilZeroBased), parking, jump, stopname))
+ routeDurations[flowID] = untilZeroBased
else:
sys.stderr.write("Warning: Missing stop '%s' for flow '%s'\n" % (stop.busStop, id))
else:
sys.stderr.write("Warning: No stops for flow '%s'\n" % id)
foutflows.write(' \n')
- flow_duration = options.end - options.begin
for vehID, flowID, lineRef, type, begin in flows:
ptline = trpMap[vehID]
- foutflows.write(' \n' % (
- flowID, type, flowID, ft(begin), ft(begin + flow_duration),
- int(float(ptline.period)), ptline.ref, options.flowattrs))
- if ptline.name is not None:
- foutflows.write(' \n' % quoteattr(ptline.name))
- if ptline.completeness is not None:
- foutflows.write(' \n' % quoteattr(ptline.completeness))
- foutflows.write(' \n')
+ number = None
+ if flowID in routeDurations:
+ number = max(1, int(routeDurations[flowID] / float(ptline.period)))
+ if options.multistart and len(ptline.terminalIndices) == 2:
+ # vehicles stay in a continuous loop. We create a fixed number of vehicles with repeating routes
+ for i in range(number):
+ departEdge = int(i * routeSize[flowID] / float(number)) # this is a hack since edges could have very different lengths
+ foutflows.write(' \n' % (
+ flowID, i, type, flowID, ft(begin), departEdge, ptline.ref, options.flowattrs))
+ writeParams(foutflows, ptline)
+ foutflows.write(' \n')
+ foutflows.write('\n')
+
+ else:
+ end = ' end="%s"' % ft(begin + flow_duration)
+ if len(ptline.terminalIndices) == 2 and number is not None:
+ end = ' number="%s"' % number
+ foutflows.write(' \n' % (
+ flowID, type, flowID, ft(begin), end,
+ int(float(ptline.period)), ptline.ref, options.flowattrs))
+ writeParams(foutflows, ptline)
+ foutflows.write(' \n')
foutflows.write('\n')
print("done.")
+def writeParams(foutflows, ptline):
+ if ptline.name is not None:
+ foutflows.write(' \n' % quoteattr(ptline.name))
+ if ptline.completeness is not None:
+ foutflows.write(' \n' % quoteattr(ptline.completeness))
+
def main(options):
if options.seed: