#!/usr/bin/env python # Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo # Copyright (C) 2010-2023 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 randomTrips.py # @author Daniel Krajzewicz # @author Jakob Erdmann # @author Michael Behrisch # @date 2010-03-06 from __future__ import print_function from __future__ import absolute_import import os import sys import random import bisect import subprocess from collections import defaultdict import math 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, parseTime, intIfPossible # noqa from sumolib.geomhelper import naviDegree, minAngleDegreeDiff # noqa from sumolib.net.lane import is_vehicle_class # noqa DUAROUTER = sumolib.checkBinary('duarouter') SOURCE_SUFFIX = ".src.xml" DEST_SUFFIX = ".dst.xml" VIA_SUFFIX = ".via.xml" MAXIMIZE_FACTOR = "max" def get_options(args=None): op = sumolib.options.ArgumentParser(description="Generate trips between random locations", allowed_programs=['duarouter']) # input op.add_argument("-n", "--net-file", category="input", dest="netfile", required=True, type=op.net_file, help="define the net file (mandatory)") op.add_argument("-a", "--additional-files", category="input", dest="additional", type=op.additional_file, help="define additional files to be loaded by the router") op.add_argument("--weights-prefix", category="input", dest="weightsprefix", type=op.file, help="loads probabilities for being source, destination and via-edge from the files named " + "'prefix'.src.xml, 'prefix'.dst.xml and 'prefix'.via.xml") # output op.add_argument("-o", "--output-trip-file", category="output", dest="tripfile", type=op.route_file, default="trips.trips.xml", help="define the output trip filename") op.add_argument("-r", "--route-file", category="output", dest="routefile", type=op.route_file, help="generates route file with duarouter") op.add_argument("--vtype-output", category="output", dest="vtypeout", type=op.file, help="Store generated vehicle types in a separate file") op.add_argument("--weights-output-prefix", category="output", dest="weights_outprefix", type=op.file, help="generates weights files for visualisation") # persons op.add_argument("--pedestrians", category="persons", action="store_true", default=False, help="create a person file with pedestrian trips instead of vehicle trips") op.add_argument("--personrides", category="persons", help="create a person file with rides using STR as lines attribute") op.add_argument("--persontrips", category="persons", action="store_true", default=False, help="create a person file with person trips instead of vehicle trips") op.add_argument("--persontrip.transfer.car-walk", category="persons", dest="carWalkMode", help="Where are mode changes from car to walking allowed " + "(possible values: 'ptStops', 'allJunctions' and combinations)") op.add_argument("--persontrip.walkfactor", category="persons", dest="walkfactor", metavar="FLOAT", type=float, help="Use FLOAT as a factor on pedestrian maximum speed during intermodal routing") op.add_argument("--persontrip.walk-opposite-factor", category="persons", dest="walkoppositefactor", metavar="FLOAT", type=float, help="Use FLOAT as a factor on pedestrian maximum speed against vehicle traffic direction") op.add_argument("--from-stops", category="persons", dest="fromStops", help="Create trips that start at stopping places of the indicated type(s). i.e. 'busStop'") op.add_argument("--to-stops", category="persons", dest="toStops", help="Create trips that end at stopping places of the indicated type(s). i.e. 'busStop'") # attributes op.add_argument("--prefix", category="attributes", dest="tripprefix", default="", help="prefix for the trip ids") op.add_argument("-t", "--trip-attributes", category="attributes", dest="tripattrs", default="", help="additional trip attributes. When generating pedestrians, attributes for " + "'person' and 'walk' are supported.") op.add_argument("--fringe-start-attributes", category="attributes", dest="fringeattrs", default="", help="additional trip attributes when starting on a fringe.") op.add_argument("--vehicle-class", help="The vehicle class assigned to the generated trips (adds a standard vType definition " + "to the output file).") op.add_argument("--random-departpos", category="attributes", dest="randomDepartPos", action="store_true", default=False, help="Randomly choose a position on the starting edge of the trip") op.add_argument("--random-arrivalpos", category="attributes", dest="randomArrivalPos", action="store_true", default=False, help="Randomly choose a position on the ending edge of the trip") op.add_argument("--junction-taz", category="attributes", dest="junctionTaz", action="store_true", default=False, help="Write trips with fromJunction and toJunction") # weights op.add_argument("-l", "--length", category="weights", action="store_true", default=False, help="weight edge probability by length") op.add_argument("-L", "--lanes", category="weights", action="store_true", default=False, help="weight edge probability by number of lanes") op.add_argument("--edge-param", category="weights", dest="edgeParam", help="use the given edge parameter as factor for edge") op.add_argument("--speed-exponent", category="weights", dest="speed_exponent", metavar="FLOAT", type=float, default=0.0, help="weight edge probability by speed^'FLOAT' (default 0)") op.add_argument("--fringe-speed-exponent", category="weights", dest="fringe_speed_exponent", metavar="FLOAT", help="weight fringe edge probability by speed^'FLOAT' (default: speed exponent)") op.add_argument("--angle", category="weights", dest="angle", default=90.0, type=float, help="weight edge probability by angle [0-360] relative to the network center") op.add_argument("--angle-factor", category="weights", dest="angle_weight", default=1.0, type=float, help="maximum weight factor for angle") op.add_argument("--random-factor", category="weights", dest="randomFactor", default=1.0, type=float, help="edge weights are dynamically disturbed by a random factor drawn uniformly from [1,FLOAT]") op.add_argument("--fringe-factor", category="weights", dest="fringe_factor", default="1.0", help="multiply weight of fringe edges by 'FLOAT' (default 1)" + " or set value 'max' to force all traffic to start/end at the fringe.") op.add_argument("--fringe-threshold", category="weights", dest="fringe_threshold", default=0.0, type=float, help="only consider edges with speed above 'FLOAT' as fringe edges (default 0)") op.add_argument("--allow-fringe", category="weights", dest="allow_fringe", action="store_true", default=False, help="Allow departing on edges that leave the network and arriving on edges " + "that enter the network (via turnarounds or as 1-edge trips") op.add_argument("--allow-fringe.min-length", category="weights", dest="allow_fringe_min_length", type=float, help="Allow departing on edges that leave the network and arriving on edges " + "that enter the network, if they have at least the given length") op.add_argument("--fringe-junctions", category="weights", action="store_true", dest="fringeJunctions", default=False, help="Determine fringe edges based on junction attribute 'fringe'") op.add_argument("--vclass", "--edge-permission", category="weights", default="passenger", help="only from and to edges which permit the given vehicle class") op.add_argument("--via-edge-types", category="weights", dest="viaEdgeTypes", help="Set list of edge types that cannot be used for departure or arrival " + "(unless being on the fringe)") op.add_argument("--allow-roundabouts", category="weights", dest="allowRoundabouts", action="store_true", default=False, help="Permit trips that start or end inside a roundabout") # processing op.add_argument("-s", "--seed", default=42, type=int, help="random seed") op.add_argument("--random", action="store_true", default=False, help="use a random seed to initialize the random number generator") op.add_argument("--min-distance", dest="min_distance", metavar="FLOAT", default=0.0, type=float, help="require start and end edges for each trip to be at least 'FLOAT' m apart") op.add_argument("--min-distance.fringe", dest="min_dist_fringe", metavar="FLOAT", type=float, help="require start and end edges for each fringe to fringe trip to be at least 'FLOAT' m apart") op.add_argument("--max-distance", dest="max_distance", metavar="FLOAT", type=float, help="require start and end edges for each trip to be at most 'FLOAT' m " + "apart (default 0 which disables any checks)") op.add_argument("-i", "--intermediate", default=0, type=int, help="generates the given number of intermediate way points") op.add_argument("--jtrrouter", action="store_true", default=False, help="Create flows without destination as input for jtrrouter") op.add_argument("--maxtries", default=100, type=int, help="number of attemps for finding a trip which meets the distance constraints") op.add_argument("--remove-loops", dest="remove_loops", action="store_true", default=False, help="Remove loops at route start and end") op.add_argument("--random-routing-factor", dest="randomRoutingFactor", default=1, type=float, help="Edge weights for routing are dynamically disturbed " "by a random factor drawn uniformly from [1,FLOAT)") op.add_argument("--validate", default=False, action="store_true", help="Whether to produce trip output that is already checked for connectivity") op.add_argument("-v", "--verbose", action="store_true", default=False, help="tell me what you are doing") # flow op.add_argument("-b", "--begin", category="flow", default=0, type=op.time, help="begin time") op.add_argument("-e", "--end", category="flow", default=3600, type=op.time, help="end time (default 3600)") group = op.add_mutually_exclusive_group() group.add_argument("-p", "--period", nargs="+", metavar="FLOAT", category="flow", action=sumolib.options.SplitAction, help="Generate vehicles with equidistant departure times and period=FLOAT (default 1.0). " + "If option --binomial is used, the expected arrival rate is set to 1/period.") group.add_argument("--insertion-rate", dest="insertionRate", nargs="+", metavar="FLOAT", category="flow", action=sumolib.options.SplitAction, help="How much vehicles arrive in the simulation per hour (alternative to the period option).") group.add_argument("--insertion-density", dest="insertionDensity", nargs="+", metavar="FLOAT", category="flow", action=sumolib.options.SplitAction, help="How much vehicles arrive in the simulation per hour per kilometer of road " + "(alternative to the period option).") op.add_argument("--flows", category="flow", default=0, type=int, help="generates INT flows that together output vehicles with the specified period") op.add_argument("--random-depart", category="flow", action="store_true", dest="randomDepart", default=False, help="Distribute departures randomly between begin and end") op.add_argument("--binomial", category="flow", metavar="N", type=int, help="If this is set, the number of departures per second will be drawn from a binomial " + "distribution with n=N and p=PERIOD/N where PERIOD is the argument given to --period") options = op.parse_args(args=args) if options.vclass and not is_vehicle_class(options.vclass): raise ValueError("The string '%s' doesn't correspond to a legit vehicle class." % options.vclass) if options.persontrips or options.personrides: options.pedestrians = True if options.pedestrians: options.vclass = 'pedestrian' if options.flows > 0: raise ValueError("Person flows are not supported yet.") if options.validate and options.routefile is None: options.routefile = "routes.rou.xml" if options.period is None and options.insertionRate is None and options.insertionDensity is None: options.period = [1.] options.net = sumolib.net.readNet(options.netfile) if options.insertionDensity: # Compute length of the network length = 0. # In meters for edge in options.net.getEdges(): if edge.allows(options.vclass): length += edge.getLaneNumber() * edge.getLength() options.insertionRate = [density * (length / 1000.0) for density in options.insertionDensity] if options.insertionRate: options.period = [3600.0 / rate if rate != 0.0 else 0.0 for rate in options.insertionRate] if options.period: if any([period < 0 for period in options.period]): raise ValueError("Period / insertionRate must be non-negative.") options.period = list(map(intIfPossible, options.period)) if options.binomial: for p in options.period: if p != 0.0 and 1.0 / p / options.binomial >= 1: print("Warning: Option --binomial %s is too low for insertion period %s." % (options.binomial, p) + " Insertions will not be randomized.", file=sys.stderr) if options.jtrrouter and options.flows <= 0: raise ValueError("Option --jtrrouter must be used with option --flows.") if options.vehicle_class: if not is_vehicle_class(options.vehicle_class): raise ValueError("The string '%s' doesn't correspond to a legit vehicle class." % options.vehicle_class) if options.tripprefix: options.vtypeID = "%s_%s" % (options.tripprefix, options.vehicle_class) else: options.vtypeID = options.vehicle_class if 'type=' in options.tripattrs: raise ValueError("Trip-attribute 'type' cannot be used together with option --vehicle-class.") if options.randomDepartPos: if 'departPos' in options.tripattrs: raise ValueError("Trip-attribute 'departPos' cannot be used together with option --random-departpos.") if options.randomArrivalPos: if 'arrivalPos' in options.tripattrs: raise ValueError("Trip-attribute 'arrivalPos' cannot be used together with option --random-arrivalpos.") if options.weightsprefix: weight_files = [options.weightsprefix + s for s in (SOURCE_SUFFIX, DEST_SUFFIX, VIA_SUFFIX)] if not any([os.path.isfile(w) for w in weight_files]): raise ValueError("None of the weight files '%s' exists." % "', '".join(weight_files)) if options.randomFactor < 1: raise ValueError("Option --random-factor requires a value >= 1.") if options.fromStops or options.toStops: options.edgeFromStops, options.edgeToStops = loadStops(options) if options.viaEdgeTypes: options.viaEdgeTypes = options.viaEdgeTypes.split(',') if options.fringe_speed_exponent is None: options.fringe_speed_exponent = options.speed_exponent if options.fringe_factor.lower() == MAXIMIZE_FACTOR: options.fringe_factor = MAXIMIZE_FACTOR else: try: options.fringe_factor = float(options.fringe_factor) if options.fringe_factor < 0: raise ValueError("--fringe-factor argument may not be negative.") except ValueError: raise ValueError("--fringe-factor argument must be a float or 'max'.") return options class InvalidGenerator(Exception): pass def loadStops(options): edgeFromStops = defaultdict(list) # edge -> [(stopType1, stopID1), ...] edgeToStops = defaultdict(list) # edge -> [(stopType1, stopID1), ...] if options.additional is None: print("Error: Option %s requires option --additional-files for loading infrastructure elements" % ("--from-stops" if options.fromStops else "--to-stops"), file=sys.stderr) sys.exit(1) stopTypes = [] if options.fromStops: options.fromStops = options.fromStops.split(',') stopTypes += options.fromStops else: options.fromStops = [] if options.toStops: options.toStops = options.toStops.split(',') stopTypes += options.toStops else: options.toStops = [] stopTypes = list(set(stopTypes)) typeCounts = defaultdict(lambda: 0) for additional in options.additional.split(','): for stop in sumolib.xml.parse(additional, stopTypes): edgeID = stop.lane.rsplit('_', 1)[0] if stop.name in options.fromStops: edgeFromStops[edgeID].append((stop.name, stop.id)) if stop.name in options.toStops: edgeToStops[edgeID].append((stop.name, stop.id)) typeCounts[stop.name] += 1 if options.fromStops: available = sum([typeCounts[t] for t in options.fromStops]) if available == 0: print("No stops of type%s '%s' were found in additional-files %s" % ( ('' if len(options.fromStops) == 1 else 's'), options.fromStops[0], options.additional), file=sys.stderr) sys.exit(1) if options.toStops: available = sum([typeCounts[t] for t in options.toStops]) if available == 0: print("No stops of type%s '%s' were found in additional-files %s" % ( ('' if len(options.toStops) == 1 else 's'), options.toStops[0], options.additional), file=sys.stderr) sys.exit(1) return edgeFromStops, edgeToStops # assigns a weight to each edge using weight_fun and then draws from a discrete # distribution with these weights class RandomEdgeGenerator: def __init__(self, net, weight_fun): self.net = net self.weight_fun = weight_fun self.cumulative_weights = [] self.total_weight = 0 for edge in self.net._edges: # print edge.getID(), weight_fun(edge) self.total_weight += weight_fun(edge) self.cumulative_weights.append(self.total_weight) if self.total_weight == 0: raise InvalidGenerator() def get(self): r = random.random() * self.total_weight index = bisect.bisect(self.cumulative_weights, r) return self.net._edges[index] def write_weights(self, fname, interval_id, begin, end): # normalize to [0,100] normalizer = 100.0 / max(1, max(map(self.weight_fun, self.net._edges))) weights = [(self.weight_fun(e) * normalizer, e.getID()) for e in self.net.getEdges()] weights.sort(reverse=True) with open(fname, 'w+') as f: f.write('\n') f.write(' \n' % ( interval_id, begin, end)) for weight, edgeID in weights: f.write(' \n' % (edgeID, weight)) f.write(' \n') f.write('\n') class RandomTripGenerator: def __init__(self, source_generator, sink_generator, via_generator, intermediate, pedestrians): self.source_generator = source_generator self.sink_generator = sink_generator self.via_generator = via_generator self.intermediate = intermediate self.pedestrians = pedestrians def get_trip(self, min_distance, max_distance, maxtries=100, junctionTaz=False, min_dist_fringe=None): for min_dist in [min_distance, min_dist_fringe]: if min_dist is None: break for _ in range(maxtries): source_edge = self.source_generator.get() intermediate = [self.via_generator.get() for __ in range(self.intermediate)] sink_edge = self.sink_generator.get() is_fringe2fringe = source_edge.is_fringe() and sink_edge.is_fringe() and not intermediate if min_dist == min_dist_fringe and not is_fringe2fringe: continue if self.pedestrians: destCoord = sink_edge.getFromNode().getCoord() else: destCoord = sink_edge.getToNode().getCoord() coords = ([source_edge.getFromNode().getCoord()] + [e.getFromNode().getCoord() for e in intermediate] + [destCoord]) distance = sum([euclidean(p, q) for p, q in zip(coords[:-1], coords[1:])]) if (distance >= min_dist and (not junctionTaz or source_edge.getFromNode() != sink_edge.getToNode()) and (max_distance is None or distance < max_distance)): return source_edge, sink_edge, intermediate raise Exception("Warning: no trip found after %s tries" % maxtries) def get_prob_fun(options, fringe_bonus, fringe_forbidden, max_length): # fringe_bonus None generates intermediate way points randomProbs = defaultdict(lambda: 1) if options.randomFactor != 1: for edge in options.net.getEdges(): randomProbs[edge.getID()] = random.uniform(1, options.randomFactor) roundabouts = set() if not options.allowRoundabouts: for roundabout in options.net.getRoundabouts(): roundabouts.update(roundabout.getEdges()) stopDict = None if options.fromStops and fringe_bonus == "_incoming": stopDict = options.edgeFromStops elif options.toStops and fringe_bonus == "_outgoing": stopDict = options.edgeToStops def edge_probability(edge): bonus_connections = None if fringe_bonus is None else getattr(edge, fringe_bonus) forbidden_connections = None if fringe_forbidden is None else getattr(edge, fringe_forbidden) if options.vclass and not edge.allows(options.vclass) and not stopDict: return 0 # not allowed if fringe_bonus is None and edge.is_fringe() and not options.pedestrians: return 0 # not suitable as intermediate way point if (fringe_forbidden is not None and edge.is_fringe(forbidden_connections) and not options.pedestrians and (options.allow_fringe_min_length is None or edge.getLength() < options.allow_fringe_min_length)): return 0 # the wrong kind of fringe if (fringe_bonus is not None and options.viaEdgeTypes is not None and not edge.is_fringe(bonus_connections, checkJunctions=options.fringeJunctions) and edge.getType() in options.viaEdgeTypes): return 0 # the wrong type of edge (only allows depart and arrival on the fringe) if fringe_bonus is not None and edge.getID() in roundabouts: return 0 # traffic typically does not start/end inside a roundabout prob = randomProbs[edge.getID()] if stopDict: prob *= len(stopDict[edge.getID()]) if options.length: if (options.fringe_factor != 1.0 and fringe_bonus is not None and edge.is_fringe(bonus_connections, checkJunctions=options.fringeJunctions)): # short fringe edges should not suffer a penalty prob *= max_length else: prob *= edge.getLength() if options.lanes: prob *= edge.getLaneNumber() if edge.is_fringe(bonus_connections, checkJunctions=options.fringeJunctions): prob *= (edge.getSpeed() ** options.fringe_speed_exponent) else: prob *= (edge.getSpeed() ** options.speed_exponent) if options.fringe_factor != 1.0 and fringe_bonus is not None: isFringe = (edge.getSpeed() > options.fringe_threshold and edge.is_fringe(bonus_connections, checkJunctions=options.fringeJunctions)) if isFringe and options.fringe_factor != MAXIMIZE_FACTOR: prob *= options.fringe_factor elif not isFringe and options.fringe_factor == MAXIMIZE_FACTOR: prob = 0 if options.edgeParam is not None: prob *= float(edge.getParam(options.edgeParam, 1.0)) if options.angle_weight != 1.0 and fringe_bonus is not None: xmin, ymin, xmax, ymax = edge.getBoundingBox() ex, ey = ((xmin + xmax) / 2, (ymin + ymax) / 2) nx, ny = options.angle_center edgeAngle = naviDegree(math.atan2(ey - ny, ex - nx)) angleDiff = minAngleDegreeDiff(options.angle, edgeAngle) # print("e=%s nc=%s ec=%s ea=%s a=%s ad=%s" % ( # edge.getID(), options.angle_center, (ex,ey), edgeAngle, # options.angle, angleDiff)) # relDist = 2 * euclidean((ex, ey), options.angle_center) / max(xmax - xmin, ymax - ymin) # prob *= (relDist * (options.angle_weight - 1) + 1) if fringe_bonus == "_incoming": # source edge prob *= (angleDiff * (options.angle_weight - 1) + 1) else: prob *= ((180 - angleDiff) * (options.angle_weight - 1) + 1) return prob return edge_probability class LoadedProps: def __init__(self, fname): self.weights = defaultdict(lambda: 0) for edge in sumolib.output.parse_fast(fname, 'edge', ['id', 'value']): self.weights[edge.id] = float(edge.value) def __call__(self, edge): return self.weights[edge.getID()] def buildTripGenerator(net, options): try: max_length = 0 for edge in net.getEdges(): if not edge.is_fringe(): max_length = max(max_length, edge.getLength()) forbidden_source_fringe = None if options.allow_fringe else "_outgoing" forbidden_sink_fringe = None if options.allow_fringe else "_incoming" source_generator = RandomEdgeGenerator( net, get_prob_fun(options, "_incoming", forbidden_source_fringe, max_length)) sink_generator = RandomEdgeGenerator( net, get_prob_fun(options, "_outgoing", forbidden_sink_fringe, max_length)) if options.weightsprefix: if os.path.isfile(options.weightsprefix + SOURCE_SUFFIX): source_generator = RandomEdgeGenerator( net, LoadedProps(options.weightsprefix + SOURCE_SUFFIX)) if os.path.isfile(options.weightsprefix + DEST_SUFFIX): sink_generator = RandomEdgeGenerator( net, LoadedProps(options.weightsprefix + DEST_SUFFIX)) except InvalidGenerator: print("Error: no valid edges for generating source or destination. Try using option --allow-fringe", file=sys.stderr) return None try: via_generator = RandomEdgeGenerator( net, get_prob_fun(options, None, None, 1)) if options.weightsprefix and os.path.isfile(options.weightsprefix + VIA_SUFFIX): via_generator = RandomEdgeGenerator( net, LoadedProps(options.weightsprefix + VIA_SUFFIX)) except InvalidGenerator: if options.intermediate > 0: print("Error: no valid edges for generating intermediate points", file=sys.stderr) return None else: via_generator = None return RandomTripGenerator( source_generator, sink_generator, via_generator, options.intermediate, options.pedestrians) def is_walk_attribute(attr): for cand in ['arrivalPos', 'speed=', 'duration=', 'busStop=']: if cand in attr: return True return False def is_persontrip_attribute(attr): for cand in ['vTypes', 'modes']: if cand in attr: return True return False def is_person_attribute(attr): for cand in ['departPos', 'type']: if cand in attr: return True return False def is_vehicle_attribute(attr): # speedFactor could be used in vType and vehicle but we need it in the vType # to allow for the multi-parameter version for cand in ['depart', 'arrival', 'line', 'personNumber', 'containerNumber', 'type']: if cand in attr: return True return False def split_trip_attributes(tripattrs, pedestrians, hasType, verbose): # handle attribute values with a space # assume that no attribute value includes an '=' sign allattrs = [] for a in tripattrs.split(): if "=" in a: allattrs.append(a) else: if len(allattrs) == 0: print("Warning: invalid trip-attribute '%s'" % a) else: allattrs[-1] += ' ' + a # figure out which of the tripattrs belong to the or , # which belong to the and which belong to the or vehicleattrs = [] personattrs = [] vtypeattrs = [] otherattrs = [] for a in allattrs: if pedestrians: if is_walk_attribute(a) or is_persontrip_attribute(a): otherattrs.append(a) elif is_person_attribute(a): personattrs.append(a) else: vtypeattrs.append(a) else: if is_vehicle_attribute(a): vehicleattrs.append(a) else: vtypeattrs.append(a) if not hasType: if pedestrians: personattrs += vtypeattrs else: vehicleattrs += vtypeattrs vtypeattrs = [] return (prependSpace(' '.join(vtypeattrs)), prependSpace(' '.join(vehicleattrs)), prependSpace(' '.join(personattrs)), prependSpace(' '.join(otherattrs))) def prependSpace(s): if len(s) == 0 or s[0] == " ": return s else: return " " + s def samplePosition(edge): return random.uniform(0.0, edge.getLength()) def main(options): if all([period == 0 for period in options.period]): print("Warning: All intervals are empty.", file=sys.stderr) return False if not options.random: random.seed(options.seed) if options.min_distance > options.net.getBBoxDiameter() * (options.intermediate + 1): options.intermediate = int(math.ceil(options.min_distance / options.net.getBBoxDiameter())) - 1 print(("Warning: Using %s intermediate waypoints to achieve a minimum trip length of %s in a network " "with diameter %.2f.") % (options.intermediate, options.min_distance, options.net.getBBoxDiameter()), file=sys.stderr) if options.angle_weight != 1: xmin, ymin, xmax, ymax = options.net.getBoundary() options.angle_center = (xmin + xmax) / 2, (ymin + ymax) / 2 trip_generator = buildTripGenerator(options.net, options) idx = 0 vtypeattrs, options.tripattrs, personattrs, otherattrs = split_trip_attributes( options.tripattrs, options.pedestrians, options.vehicle_class, options.verbose) vias = {} time_delta = (parseTime(options.end) - parseTime(options.begin)) / len(options.period) times = [parseTime(options.begin) + i * time_delta for i in range(len(options.period) + 1)] times = list(map(intIfPossible, times)) def generate_origin_destination(trip_generator, options): source_edge, sink_edge, intermediate = trip_generator.get_trip( options.min_distance, options.max_distance, options.maxtries, options.junctionTaz, options.min_dist_fringe) return source_edge, sink_edge, intermediate def generate_attributes(idx, departureTime, arrivalTime, origin, destination, intermediate, options): label = "%s%s" % (options.tripprefix, idx) combined_attrs = options.tripattrs if options.randomDepartPos: randomPosition = samplePosition(origin) combined_attrs += ' departPos="%.2f"' % randomPosition if options.randomArrivalPos: randomPosition = samplePosition(destination) combined_attrs += ' arrivalPos="%.2f"' % randomPosition if options.fringeattrs and origin.is_fringe( origin._incoming, checkJunctions=options.fringeJunctions): combined_attrs += " " + options.fringeattrs if options.junctionTaz: attrFrom = ' fromJunction="%s"' % origin.getFromNode().getID() attrTo = ' toJunction="%s"' % destination.getToNode().getID() else: attrFrom = ' from="%s"' % origin.getID() attrTo = ' to="%s"' % destination.getID() if options.fromStops: attrFrom = ' %s="%s"' % random.choice(options.edgeFromStops[origin.getID()]) if options.toStops: attrTo = ' %s="%s"' % random.choice(options.edgeToStops[destination.getID()]) via = "" if intermediate: via = ' via="%s" ' % ' '.join( [e.getID() for e in intermediate]) if options.validate: vias[label] = via return label, combined_attrs, attrFrom, attrTo, via def generate_one_person(label, combined_attrs, attrFrom, attrTo, departureTime, intermediate, options): fouttrips.write( ' \n' % (label, departureTime, personattrs)) element = "walk" attrs = otherattrs if options.fromStops: fouttrips.write(' \n' % attrFrom) attrFrom = '' if options.persontrips: element = "personTrip" elif options.personrides: element = "ride" attrs = ' lines="%s%s"' % (options.personrides, otherattrs) if intermediate: fouttrips.write(' <%s%s to="%s"%s/>\n' % (element, attrFrom, intermediate[0].getID(), attrs)) for edge in intermediate[1:]: fouttrips.write(' <%s to="%s"%s/>\n' % (element, edge.getID(), attrs)) fouttrips.write(' <%s%s%s/>\n' % (element, attrTo, attrs)) else: fouttrips.write(' <%s%s%s%s/>\n' % (element, attrFrom, attrTo, attrs)) fouttrips.write(' \n') def generate_one_flow(label, combined_attrs, departureTime, arrivalTime, period, options, timeIdx): if len(options.period) > 1: label = label + "#%s" % timeIdx if options.binomial: for j in range(options.binomial): fouttrips.write((' \n') % ( label, j, departureTime, arrivalTime, 1.0 / period / options.binomial, combined_attrs)) else: fouttrips.write((' \n') % ( label, departureTime, arrivalTime, intIfPossible(period * options.flows), combined_attrs)) def generate_one_trip(label, combined_attrs, departureTime): fouttrips.write(' \n' % ( label, departureTime, combined_attrs)) def generate_one(idx, departureTime, arrivalTime, period, origin, destination, intermediate, timeIdx=None): try: label, combined_attrs, attrFrom, attrTo, via = generate_attributes( idx, departureTime, arrivalTime, origin, destination, intermediate, options) if options.pedestrians: generate_one_person(label, combined_attrs, attrFrom, attrTo, departureTime, intermediate, options) else: if options.jtrrouter: attrTo = '' combined_attrs = attrFrom + attrTo + via + combined_attrs if options.flows > 0: generate_one_flow(label, combined_attrs, departureTime, arrivalTime, period, options, timeIdx) else: generate_one_trip(label, combined_attrs, departureTime) except Exception as exc: if options.verbose: print(exc, file=sys.stderr) return idx + 1 with open(options.tripfile, 'w') as fouttrips: sumolib.writeXMLHeader(fouttrips, "$Id$", "routes", options=options) if options.vehicle_class: vTypeDef = ' \n' % ( options.vtypeID, options.vehicle_class, vtypeattrs) if options.vtypeout: # ensure that trip output does not contain types, file may be # overwritten by later call to duarouter if options.additional is None: options.additional = options.vtypeout else: options.additional = options.additional + "," + options.vtypeout with open(options.vtypeout, 'w') as fouttype: sumolib.writeXMLHeader(fouttype, "$Id$", "additional", options=options) fouttype.write(vTypeDef) fouttype.write("\n") else: fouttrips.write(vTypeDef) options.tripattrs += ' type="%s"' % options.vtypeID personattrs += ' type="%s"' % options.vtypeID if trip_generator: if options.flows == 0: for i in range(len(times)-1): time = departureTime = parseTime(times[i]) arrivalTime = parseTime(times[i+1]) period = options.period[i] if period == 0.0: continue if options.binomial is None: departures = [] if options.randomDepart: subsecond = math.fmod(period, 1) while time < arrivalTime: rTime = random.randrange(int(departureTime), int(arrivalTime)) time += period if subsecond != 0: # allow all multiples of subsecond to appear rSubSecond = math.fmod( subsecond * random.randrange(int(departureTime), int(arrivalTime)), 1) rTime = min(arrivalTime, rTime + rSubSecond) departures.append(rTime) departures.sort() else: while departureTime < arrivalTime: departures.append(departureTime) departureTime += period for time in departures: # generate with constant spacing try: origin, destination, intermediate = generate_origin_destination(trip_generator, options) idx = generate_one(idx, time, arrivalTime, period, origin, destination, intermediate) except Exception as exc: print(exc, file=sys.stderr) else: time = departureTime while time < arrivalTime: # draw n times from a Bernoulli distribution # for an average arrival rate of 1 / period prob = 1.0 / period / options.binomial for _ in range(options.binomial): if random.random() < prob: try: origin, destination, intermediate = generate_origin_destination( trip_generator, options) idx = generate_one(idx, time, arrivalTime, period, origin, destination, intermediate) except Exception as exc: if options.verbose: print(exc, file=sys.stderr) time += 1.0 else: try: origins_destinations = [generate_origin_destination( trip_generator, options) for _ in range(options.flows)] for i in range(len(times)-1): for j in range(options.flows): departureTime = times[i] arrivalTime = times[i+1] period = options.period[i] if period == 0.0: continue origin, destination, intermediate = origins_destinations[j] generate_one(j, departureTime, arrivalTime, period, origin, destination, intermediate, i) except Exception as exc: if options.verbose: print(exc, file=sys.stderr) fouttrips.write("\n") # call duarouter for routes or validated trips args = [DUAROUTER, '-n', options.netfile, '-r', options.tripfile, '--ignore-errors', '--begin', str(options.begin), '--end', str(options.end), '--alternatives-output', 'NUL', '--no-step-log'] if options.additional is not None: args += ['--additional-files', options.additional] if options.carWalkMode is not None: args += ['--persontrip.transfer.car-walk', options.carWalkMode] if options.walkfactor is not None: args += ['--persontrip.walkfactor', str(options.walkfactor)] if options.walkoppositefactor is not None: args += ['--persontrip.walk-opposite-factor', str(options.walkoppositefactor)] if options.remove_loops: args += ['--remove-loops'] if options.randomRoutingFactor != 1: args += ['--weights.random-factor', str(options.randomRoutingFactor)] if options.vtypeout is not None: args += ['--vtype-output', options.vtypeout] if options.junctionTaz: args += ['--junction-taz'] if not options.verbose: args += ['--no-warnings'] else: args += ['-v'] options_to_forward = sumolib.options.get_prefixed_options(options) if 'duarouter' in options_to_forward: for option in options_to_forward['duarouter']: option[0] = '--' + option[0] if option[0] not in args: args += option else: raise ValueError("The argument '%s' has already been passed without the duarouter prefix." % option[0]) if options.routefile: args2 = args + ['-o', options.routefile] if options.verbose: print("calling", " ".join(args2)) sys.stdout.flush() subprocess.call(args2) sys.stdout.flush() sumolib.xml.insertOptionsHeader(options.routefile, options) if options.validate: # write to temporary file because the input is read incrementally tmpTrips = options.tripfile + ".tmp" args2 = args + ['-o', tmpTrips, '--write-trips'] if options.junctionTaz: args2 += ['--write-trips.junctions'] if options.verbose: print("calling", " ".join(args2)) sys.stdout.flush() subprocess.call(args2) sys.stdout.flush() os.remove(options.tripfile) # on windows, rename does not overwrite os.rename(tmpTrips, options.tripfile) sumolib.xml.insertOptionsHeader(options.tripfile, options) if trip_generator and options.weights_outprefix: idPrefix = "" if options.tripprefix: idPrefix = options.tripprefix + "." trip_generator.source_generator.write_weights( options.weights_outprefix + SOURCE_SUFFIX, idPrefix + "src", options.begin, options.end) trip_generator.sink_generator.write_weights( options.weights_outprefix + DEST_SUFFIX, idPrefix + "dst", options.begin, options.end) if trip_generator.via_generator: trip_generator.via_generator.write_weights( options.weights_outprefix + VIA_SUFFIX, idPrefix + "via", options.begin, options.end) # return wether trips could be generated as requested return trip_generator is not None if __name__ == "__main__": try: if not main(get_options()): print("Error: Trips couldn't be generated as requested. " "Try the --verbose option to output more details on the failure.", file=sys.stderr) sys.exit(1) except ValueError as e: print("Error:", e, file=sys.stderr) sys.exit(1)