""" Generate parking area rerouters from the parking area definition. """
from __future__ import print_function
from __future__ import absolute_import
import collections
import functools
import multiprocessing
import sys
import xml.etree.ElementTree
import numpy
import sumolib
if not hasattr(functools, "lru_cache"):
def lru_cache_dummy(maxsize):
class Cache_info:
hits = -1
misses = -1
def deco(fun):
fun.cache_info = lambda: Cache_info()
return fun
return deco
functools.lru_cache = lru_cache_dummy
def get_options(cmd_args=None):
""" Argument Parser. """
parser = sumolib.options.ArgumentParser(
prog='generateParkingAreaRerouters.py', usage='%(prog)s [options]',
description='Generate parking area rerouters from the parking area definition.')
parser.add_argument(
'-a', '--parking-areas', type=parser.additional_file, category="input", dest='paFiles', required=True,
help='SUMO parkingArea definition.')
parser.add_argument(
'-n', '--sumo-net', type=parser.net_file, category="input", dest='sumo_net_definition', required=True,
help='SUMO network definition.')
parser.add_argument(
'-b', '--begin', type=float, dest='begin', default=0.0,
help='Rerouter interval begin')
parser.add_argument(
'-e', '--end', type=float, dest='end', default=86400.0,
help='Rerouter interval end')
parser.add_argument(
'--max-number-alternatives', type=int, category="processing", dest='num_alternatives', default=10,
help='Rerouter: max number of alternatives.')
parser.add_argument(
'--max-distance-alternatives', type=float, category="processing", dest='dist_alternatives', default=500.0,
help='Rerouter: max distance for the alternatives.')
parser.add_argument(
'--min-capacity-visibility-true', type=int, category="processing", dest='capacity_threshold', default=25,
help='Rerouter: parking capacity for the visibility threshold.')
parser.add_argument(
'--max-distance-visibility-true', type=float, category="processing", dest='dist_threshold', default=250.0,
help='Rerouter: parking distance for the visibility threshold.')
parser.add_argument(
'--opposite-visible', action="store_true", category="processing", dest='opposite_visible',
default=False, help="ParkingArea on the opposite side of the road is always visible")
parser.add_argument(
'--prefer-visible', action="store_true", category="processing", dest='prefer_visible',
default=False, help="ParkingAreas which are visible are preferentially")
parser.add_argument(
'--min-capacity', type=int, category="processing", dest='min_capacity', default=1,
help='Do no reroute to parkingAreas with less than min-capacity')
parser.add_argument(
'--distribute', dest='distribute', category="processing",
help='Distribute alternatives by distance according to the given weights. 3,1 '
+ 'means that 75 percent of the alternatives are below the median distance of all'
+ 'alternatives in range and 25 percent are above the median distance')
parser.add_argument(
'--visible-ids', dest='visible_ids', category="processing", default="",
help='set list of parkingArea ids as always visible')
parser.add_argument(
'--processes', type=int, category="processing", dest='processes', default=1,
help='Number of processes spawned to compute the distance between parking areas.')
parser.add_argument(
'-o', '--output', type=parser.additional_file, category="output", dest='output', required=True,
help='Name for the output file.')
parser.add_argument(
'--tqdm', dest='with_tqdm', category="processing", action='store_true',
help='Enable TQDM feature.')
parser.set_defaults(with_tqdm=False)
options = parser.parse_args(cmd_args)
if options.distribute is not None:
dists = options.distribute.split(',')
for x in dists:
try:
x = float(x)
except ValueError:
print("Value '%s' in option --distribute must be numeric" % x,
file=sys.stderr)
sys.exit()
options.visible_ids = set(options.visible_ids.split(','))
return options
def initRTree(all_parkings):
try:
import rtree
except ImportError:
sys.stdout.write("Warning: Module 'rtree' not available. Using slow brute-force search for alternative parkingAreas\n")
return None
result = None
result = rtree.index.Index()
result.interleaved = True
for index, parking in enumerate(all_parkings.values()):
x, y = parking['pos']
r = 1
bbox = (x - r, y - r, x + r, y + r)
result.add(index, bbox)
return result
class ReroutersGeneration(object):
""" Generate parking area rerouters from the parking area definition. """
def __init__(self, options):
self._opt = options
self._parking_areas = dict()
self._sumo_rerouters = dict()
print('Loading SUMO network: {}'.format(options.sumo_net_definition))
self._sumo_net = sumolib.net.readNet(options.sumo_net_definition, withInternal=True)
for pafile in options.paFiles.split(','):
print('Loading parking file: {}'.format(pafile))
self._load_parking_areas_from_file(pafile)
self._generate_rerouters()
self._save_rerouters()
def _load_parking_areas_from_file(self, filename):
""" Load parkingArea from XML file. """
xml_tree = xml.etree.ElementTree.parse(filename).getroot()
sequence = None
if self._opt.with_tqdm:
from tqdm import tqdm
sequence = tqdm(xml_tree)
else:
sequence = xml_tree
for child in sequence:
if child.tag != "parkingArea":
continue
self._parking_areas[child.attrib['id']] = child.attrib
laneID = child.attrib['lane']
lane = self._sumo_net.getLane(laneID)
endPos = lane.getLength()
if 'endPos' in child.attrib:
endPos = float(child.attrib['endPos'])
if endPos < 0:
endPos = lane.getLength()
else:
child.attrib['endPos'] = endPos
if 'startPos' not in child.attrib:
child.attrib['startPos'] = 0
pa = self._parking_areas[child.attrib['id']]
pa['edge'] = lane.getEdge().getID()
pa['pos'] = sumolib.geomhelper.positionAtShapeOffset(lane.getShape(), endPos)
pa['capacity'] = (int(child.get('roadsideCapacity', 1 if len(child.findall('space')) == 0 else 0)) +
len(child.findall('space')))
def _generate_rerouters(self):
""" Compute the rerouters for each parking lot for SUMO. """
print('Computing distances and sorting parking alternatives.')
with multiprocessing.Pool(processes=self._opt.processes) as pool:
list_parameters = list()
splits = numpy.array_split(list(self._parking_areas.keys()), self._opt.processes)
for parkings in splits:
parameters = {
'selection': parkings,
'all_parking_areas': self._parking_areas,
'net_file': self._opt.sumo_net_definition,
'with_tqdm': self._opt.with_tqdm,
'num_alternatives': self._opt.num_alternatives,
'dist_alternatives': self._opt.dist_alternatives,
'dist_threshold': self._opt.dist_threshold,
'capacity_threshold': self._opt.capacity_threshold,
'min_capacity': self._opt.min_capacity,
'opposite_visible': self._opt.opposite_visible,
'prefer_visible': self._opt.prefer_visible,
'distribute': self._opt.distribute,
'visible_ids': self._opt.visible_ids,
}
list_parameters.append(parameters)
for res in pool.imap_unordered(generate_rerouters_process, list_parameters):
for key, value in res.items():
self._sumo_rerouters[key] = value
print('Computed {} rerouters.'.format(len(self._sumo_rerouters.keys())))
_REROUTER = """
<rerouter id="{rid}" edges="{edges}">
<interval begin="{begin}" end="{end}">
<!-- in order of distance --> {parkings}
</interval>
</rerouter>
"""
_RR_PARKING = """
<parkingAreaReroute id="{pid}" visible="{visible}"/> <!-- dist: {dist:.1f} -->"""
def _save_rerouters(self):
""" Save the parking lots into a SUMO XML additional file
with threshold visibility set to True. """
print("Creation of {}".format(self._opt.output))
with open(self._opt.output, 'w') as outfile:
sumolib.writeXMLHeader(outfile, "$Id$", "additional", options=self._opt)
ordered_rerouters = sorted(self._sumo_rerouters.keys())
for rerouter_id in ordered_rerouters:
rerouter = self._sumo_rerouters[rerouter_id]
opposite = None
if self._opt.opposite_visible:
rrEdge = self._sumo_net.getEdge(rerouter['edge'])
for e in rrEdge.getToNode().getOutgoing():
if e.getToNode() == rrEdge.getFromNode():
opposite = e
break
alternatives = ''
for alt, dist in rerouter['rerouters']:
altEdge = self._sumo_net.getEdge(self._parking_areas[alt]['edge'])
if altEdge == opposite:
opposite = None
_visibility = isVisible(rerouter_id, alt, dist,
self._sumo_net,
self._parking_areas,
self._opt.dist_threshold,
self._opt.capacity_threshold,
self._opt.opposite_visible,
self._opt.visible_ids)
_visibility = str(_visibility).lower()
alternatives += self._RR_PARKING.format(pid=alt, visible=_visibility, dist=dist)
edges = [rerouter['edge']]
if opposite is not None:
edges.append(opposite.getID())
outfile.write(self._REROUTER.format(
rid=rerouter['rid'], edges=' '.join(edges), begin=self._opt.begin, end=self._opt.end,
parkings=alternatives))
outfile.write("</additional>\n")
print("{} created.".format(self._opt.output))
def isVisible(pID, altID, dist, net, parking_areas, dist_threshold,
capacity_threshold, opposite_visible, visible_ids):
if altID == pID:
return True
if (int(parking_areas[altID].get('roadsideCapacity', 0)) >= capacity_threshold):
return True
if dist <= dist_threshold:
return True
if opposite_visible:
rrEdge = net.getEdge(parking_areas[pID]['edge'])
altEdge = net.getEdge(parking_areas[altID]['edge'])
if rrEdge.getFromNode() == altEdge.getToNode() and rrEdge.getToNode() == altEdge.getFromNode():
return True
if altID in visible_ids:
return True
return False
def generate_rerouters_process(parameters):
""" Compute the rerouters for the given parking areas."""
sumo_net = sumolib.net.readNet(parameters['net_file'], withInternal=True)
rtree = initRTree(parameters['all_parking_areas'])
ret_rerouters = dict()
@functools.lru_cache(maxsize=None)
def _cached_get_shortest_path(from_edge, to_edge, fromPos, toPos):
""" Calls and caches sumolib: net.getShortestPath. """
return sumo_net.getShortestPath(from_edge, to_edge, fromPos=fromPos, toPos=toPos)
distances = collections.defaultdict(dict)
routes = collections.defaultdict(dict)
sequence = None
if parameters['with_tqdm']:
from tqdm import tqdm
sequence = tqdm(parameters['selection'])
else:
sequence = parameters['selection']
distWeights = None
distWeightSum = None
distThresholds = None
if parameters['distribute'] is not None:
distWeights = list(map(float, parameters['distribute'].split(',')))
distWeightSum = sum(distWeights)
for parking_id in sequence:
parking_a = parameters['all_parking_areas'][parking_id]
from_edge = sumo_net.getEdge(parking_a['edge'])
fromPos = float(parking_a['endPos'])
candidates = parameters['all_parking_areas'].values()
if rtree is not None:
allParkings = list(candidates)
candidates = []
x, y = parking_a['pos']
r = parameters['dist_alternatives']
for i in rtree.intersection((x - r, y - r, x + r, y + r)):
candidates.append(allParkings[i])
for parking_b in candidates:
if parking_a['id'] == parking_b['id']:
continue
toPos = float(parking_b['startPos'])
route, cost = _cached_get_shortest_path(from_edge,
sumo_net.getEdge(parking_b['edge']),
fromPos, toPos)
if route:
distances[parking_a['id']][parking_b['id']] = cost
routes[parking_a['id']][parking_b['id']] = route
cache_info = _cached_get_shortest_path.cache_info()
total = float(cache_info.hits + cache_info.misses)
perc = cache_info.hits * 100.0
if total:
perc /= float(cache_info.hits + cache_info.misses)
print('Cache: hits {}, misses {}, used {}%.'.format(
cache_info.hits, cache_info.misses, perc))
sequence = None
if parameters['with_tqdm']:
from tqdm import tqdm
sequence = tqdm(distances.items())
else:
sequence = distances.items()
for pid, dists in sequence:
list_of_dist = [tuple(reversed(kv)) for kv in dists.items() if kv[1] is not None]
list_of_dist = sorted(list_of_dist)
temp_rerouters = [(pid, 0.0)]
numAlternatives = min(len(list_of_dist), parameters['num_alternatives'])
used = set()
if parameters['prefer_visible']:
for distance, parking in list_of_dist:
if parameters['all_parking_areas'][parking].get('capacity') < parameters['min_capacity']:
continue
if len(temp_rerouters) > parameters['num_alternatives']:
break
if isVisible(pid, parking, distance, sumo_net,
parameters['all_parking_areas'],
parameters['dist_threshold'],
parameters['capacity_threshold'],
parameters['opposite_visible'],
parameters['visible_ids']):
temp_rerouters.append((parking, distance))
used.add(parking)
dist = None
distThresholds = None
if distWeights is not None:
dist = [int(x / distWeightSum * numAlternatives) for x in distWeights]
distThresholdIndex = [int(i * len(list_of_dist) / len(dist)) for i in range(len(dist))]
distThresholds = [list_of_dist[i][0] for i in distThresholdIndex]
distIndex = 0
found = 0
required = dist[distIndex] if dist else None
for distance, parking in list_of_dist:
route = routes[pid][parking]
if parking in used:
found += 1
continue
if parameters['all_parking_areas'][parking].get('capacity') < parameters['min_capacity']:
continue
if dist is not None and found >= required:
if distIndex + 1 < len(dist):
distIndex += 1
required += dist[distIndex]
if distThresholds is not None and distance < distThresholds[distIndex]:
continue
if len(temp_rerouters) > parameters['num_alternatives']:
break
if distance > parameters['dist_alternatives']:
break
found += 1
temp_rerouters.append((parking, distance))
if not list_of_dist:
print('Parking {} has 0 neighbours!'.format(pid))
ret_rerouters[pid] = {
'rid': pid,
'edge': parameters['all_parking_areas'][pid]['edge'],
'rerouters': temp_rerouters,
}
return ret_rerouters
def main(cmd_args):
""" Generate parking area rerouters from the parking area definition. """
args = get_options(cmd_args)
ReroutersGeneration(args)
print('Done.')
if __name__ == "__main__":
multiprocessing.set_start_method('spawn')
main(sys.argv[1:])