Skip to content

Commit

Permalink
first version of #5320
Browse files Browse the repository at this point in the history
  • Loading branch information
namdre committed Mar 19, 2024
1 parent 6adfbf1 commit 235325c
Showing 1 changed file with 148 additions and 24 deletions.
172 changes: 148 additions & 24 deletions tools/ptlines2flows.py
Expand Up @@ -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):
Expand All @@ -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")
Expand Down Expand Up @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand All @@ -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))
Expand All @@ -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(
Expand All @@ -324,9 +407,13 @@ def writeTrips(options, tripList, trpMap):
(' <trip id="%s" type="%s" depart="%s" departLane="best" from="%s" ' +
'to="%s"%s>\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(' <stop busStop="%s" duration="%s"/>\n' % (stop, dur))
if i in ptl.jumps:
jump = ' jump="%s"' % ptl.jumps[i]
else:
jump = ""
fouttrips.write(' <stop busStop="%s" duration="%s"%s/>\n' % (stop, dur, jump))
fouttrips.write(' </trip>\n')
fouttrips.write("</routes>\n")

Expand Down Expand Up @@ -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]
Expand All @@ -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)
Expand All @@ -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(' <route id="%s"%s edges="%s" >\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(' <route id="%s"%s edges="%s"%s >\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 = ' <!-- %s -->' % 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(
' <stop busStop="%s" duration="%s" until="%s"%s/>%s\n' % (
stop.busStop, options.stopduration, ft(untilZeroBased), parking, stopname))
' <stop busStop="%s" duration="%s" until="%s"%s%s/>%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(' </route>\n')
flow_duration = options.end - options.begin
for vehID, flowID, lineRef, type, begin in flows:
ptline = trpMap[vehID]
foutflows.write(' <flow id="%s" type="%s" route="%s" begin="%s" end="%s" period="%s" line="%s" %s>\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(' <param key="name" value=%s/>\n' % quoteattr(ptline.name))
if ptline.completeness is not None:
foutflows.write(' <param key="completeness" value=%s/>\n' % quoteattr(ptline.completeness))
foutflows.write(' </flow>\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(' <vehicle id="%s.%s" type="%s" route="%s" depart="%s" departEdge="%s" line="%s" %s>\n' % (
flowID, i, type, flowID, ft(begin), departEdge, ptline.ref, options.flowattrs))
writeParams(foutflows, ptline)
foutflows.write(' </vehicle>\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(' <flow id="%s" type="%s" route="%s" begin="%s"%s period="%s" line="%s" %s>\n' % (
flowID, type, flowID, ft(begin), end,
int(float(ptline.period)), ptline.ref, options.flowattrs))
writeParams(foutflows, ptline)
foutflows.write(' </flow>\n')
foutflows.write('</routes>\n')

print("done.")

def writeParams(foutflows, ptline):
if ptline.name is not None:
foutflows.write(' <param key="name" value=%s/>\n' % quoteattr(ptline.name))
if ptline.completeness is not None:
foutflows.write(' <param key="completeness" value=%s/>\n' % quoteattr(ptline.completeness))


def main(options):
if options.seed:
Expand Down

0 comments on commit 235325c

Please sign in to comment.