#!/usr/bin/env python
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.org/sumo
# Copyright (C) 2010-2021 German Aerospace Center (DLR) and others.
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0/
# This Source Code may also be made available under the following Secondary
# Licenses when the conditions for such availability set forth in the Eclipse
# Public License 2.0 are satisfied: GNU General Public License, version 2
# or later which is available at
# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
# @file generateContinuousRerouters.py
# @author Jakob Erdmann
# @date 2019-11-23
"""
This script generates rerouters ahead of every intersection with routes to each of
the downstream intersections.
"""
from __future__ import print_function
from __future__ import absolute_import
import os
import sys
import optparse
from collections import defaultdict
if 'SUMO_HOME' in os.environ:
sys.path.append(os.path.join(os.environ['SUMO_HOME'], 'tools'))
import sumolib # noqa
from sumolib.miscutils import euclidean # noqa
from sumolib.geomhelper import naviDegree, minAngleDegreeDiff # noqa
def get_options(args=None):
parser = optparse.OptionParser()
parser.add_option("-n", "--net-file", dest="netfile",
help="define the net file (mandatory)")
parser.add_option("-o", "--output-file", dest="outfile", default="rerouters.xml",
help="define the output rerouter filename")
parser.add_option("-T", "--turn-defaults", dest="turnDefaults", default="30,50,20",
help="Use STR[] as default turn probabilities [right,straight,left[,turn]]")
parser.add_option("-l", "--long-routes", action="store_true", dest="longRoutes", default=False,
help="place rerouters further upstream (after the previous decision point) to increase " +
"overlap of routes when rerouting and thereby improve anticipation of intersections")
parser.add_option("-b", "--begin", default=0, help="begin time")
parser.add_option("-e", "--end", default=3600, help="end time (default 3600)")
(options, args) = parser.parse_args(args=args)
if not options.netfile:
parser.print_help()
sys.exit(1)
options.turnDefaults = list(map(float, options.turnDefaults.split(',')))
if len(options.turnDefaults) not in [3, 4]:
sys.exit("turn-defaults must be defined as 3 or 4 numbers")
if len(options.turnDefaults) == 3:
options.turnDefaults.append(0) # turn with 0 probability
return options
def getEdgesToIntersection(edge):
result = [edge]
seen = set()
seen.add(edge)
while len(edge.getOutgoing().keys()) == 1:
edge = list(edge.getOutgoing().keys())[0]
if edge in seen:
break
else:
seen.add(edge)
result.append(edge)
return result
def getTurnIndex(fromEdge, toEdge):
cons = fromEdge.getOutgoing()[toEdge]
con = cons[0]
dir = con.getDirection()
if dir == con.LINKDIR_RIGHT or dir == con.LINKDIR_PARTRIGHT:
return 0
elif dir == con.LINKDIR_STRAIGHT:
return 1
elif dir == con.LINKDIR_LEFT or dir == con.LINKDIR_PARTLEFT:
return 2
else:
return 3
def getNumAlternatives(edge, routes):
numAlternatives = 0
for edges in routes:
if edges[0] in edge.getOutgoing().keys():
numAlternatives += 1
return numAlternatives
def getNumSiblings(edge):
"""return number of outgoing edges at the fromNode of this edge that can be
reached from a common predecessor of the give nedge"""
siblings = set()
for cons in edge.getIncoming().values():
for con in cons:
for outCons in con.getFrom().getOutgoing().values():
for outCon in outCons:
siblings.add(outCon.getTo())
return len(siblings)
def main(options):
net = sumolib.net.readNet(options.netfile)
incomingRoutes = defaultdict(set) # edge : set(route0, route1, ...)
if options.longRoutes:
# build dictionary of routes leading from an intersection to each edge
for junction in net.getNodes():
isEntry = len(junction.getIncoming()) == 0
if len(junction.getOutgoing()) > 1 or isEntry:
for edge in junction.getOutgoing():
if isEntry or getNumSiblings(edge) > 1:
edges = getEdgesToIntersection(edge)
edgeIDs = tuple([e.getID() for e in edges])
incomingRoutes[edges[-1]].add(edgeIDs)
with open(options.outfile, 'w') as outf:
sumolib.xml.writeHeader(outf, root="additional")
for junction in net.getNodes():
if len(junction.getOutgoing()) > 1:
routes = []
for edge in junction.getOutgoing():
routes.append(getEdgesToIntersection(edge))
for edge in junction.getIncoming():
if options.longRoutes:
# overlapping routes: start behind an intersection and
# route across the next intersection to the entry of the
# 2nd intersetion (more rerouters and overlapping routes)
if getNumAlternatives(edge, routes) > 1:
for incomingRoute in sorted(incomingRoutes[edge]):
assert(incomingRoute[-1] == edge.getID())
firstEdgeID = incomingRoute[0]
routeIDs = []
for edges in routes:
if edges[0] in edge.getOutgoing().keys():
routeID = "%s_%s_%s" % (firstEdgeID, edge.getID(), edges[0].getID())
prob = options.turnDefaults[getTurnIndex(edge, edges[0])]
edgeIDs = list(incomingRoute) + [e.getID() for e in edges]
outf.write(' \n' % (routeID, ' '.join(edgeIDs)))
routeIDs.append((routeID, prob))
outf.write(' \n' %
(firstEdgeID, edge.getID(), firstEdgeID))
outf.write(' \n' % (options.begin, options.end))
for routeID, prob in routeIDs:
outf.write(' \n' %
(routeID, prob))
outf.write(' \n')
outf.write(' \n')
else:
# minimal routes: start ahead of an intersection and
# continue up to the entry of the next intersection
routeIDs = []
for edges in routes:
if edges[0] in edge.getOutgoing().keys():
routeID = "%s_%s" % (edge.getID(), edges[0].getID())
prob = options.turnDefaults[getTurnIndex(edge, edges[0])]
edgeIDs = [e.getID() for e in [edge] + edges]
outf.write(' \n' % (routeID, ' '.join(edgeIDs)))
routeIDs.append((routeID, prob))
if len(routeIDs) > 1:
outf.write(' \n' % (edge.getID(), edge.getID()))
outf.write(' \n' % (options.begin, options.end))
for routeID, prob in routeIDs:
outf.write(' \n' %
(routeID, prob))
outf.write(' \n')
outf.write(' \n')
outf.write('\n')
if __name__ == "__main__":
main(get_options())