Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/randomTrips.py
169659 views
1
#!/usr/bin/env python
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2010-2025 German Aerospace Center (DLR) and others.
4
# This program and the accompanying materials are made available under the
5
# terms of the Eclipse Public License 2.0 which is available at
6
# https://www.eclipse.org/legal/epl-2.0/
7
# This Source Code may also be made available under the following Secondary
8
# Licenses when the conditions for such availability set forth in the Eclipse
9
# Public License 2.0 are satisfied: GNU General Public License, version 2
10
# or later which is available at
11
# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13
14
# @file randomTrips.py
15
# @author Daniel Krajzewicz
16
# @author Jakob Erdmann
17
# @author Michael Behrisch
18
# @date 2010-03-06
19
20
21
from __future__ import print_function
22
from __future__ import absolute_import
23
import os
24
import sys
25
import random
26
import bisect
27
import subprocess
28
from collections import defaultdict
29
import math
30
31
if 'SUMO_HOME' in os.environ:
32
sys.path.append(os.path.join(os.environ['SUMO_HOME'], 'tools'))
33
import sumolib # noqa
34
from sumolib.miscutils import euclidean, parseTime, intIfPossible, openz # noqa
35
from sumolib.geomhelper import naviDegree, minAngleDegreeDiff # noqa
36
from sumolib.net.lane import is_vehicle_class # noqa
37
38
DUAROUTER = sumolib.checkBinary('duarouter')
39
MAROUTER = sumolib.checkBinary('marouter')
40
41
SOURCE_SUFFIX = ".src.xml"
42
DEST_SUFFIX = ".dst.xml"
43
VIA_SUFFIX = ".via.xml"
44
45
MAXIMIZE_FACTOR = "max"
46
47
48
def get_options(args=None):
49
op = sumolib.options.ArgumentParser(description="Generate trips between random locations",
50
allowed_programs=['duarouter', 'marouter'])
51
# input
52
op.add_argument("-n", "--net-file", category="input", dest="netfile", required=True, type=op.net_file,
53
help="define the net file (mandatory)")
54
op.add_argument("-a", "--additional-files", category="input", dest="additional", type=op.additional_file,
55
help="define additional files to be loaded by the router")
56
op.add_argument("--weights-prefix", category="input", dest="weightsprefix", type=op.file,
57
help="loads probabilities for being source, destination and via-edge from the files named " +
58
"'prefix'.src.xml, 'prefix'.dst.xml and 'prefix'.via.xml")
59
op.add_argument("--edge-type-file", category="input", dest="typeFactorFile",
60
help="Load a file that defines probability factors for specific edge types (each line with 'TYPE FLOAT')") # noqa
61
# output
62
op.add_argument("-o", "--output-trip-file", category="output", dest="tripfile", type=op.route_file,
63
default="trips.trips.xml",
64
help="define the output trip filename")
65
op.add_argument("-r", "--route-file", category="output", dest="routefile", type=op.route_file,
66
help="generates route file with duarouter")
67
op.add_argument("--vtype-output", category="output", dest="vtypeout", type=op.file,
68
help="Store generated vehicle types in a separate file")
69
op.add_argument("--weights-output-prefix", category="output", dest="weights_outprefix", type=op.file,
70
help="generates weights files for visualisation")
71
op.add_argument("--error-log", category="output", dest="errorlog", type=op.file,
72
help="record routing errors")
73
# persons
74
op.add_argument("--pedestrians", category="persons", action="store_true", default=False,
75
help="create a person file with pedestrian trips instead of vehicle trips")
76
op.add_argument("--personrides", category="persons",
77
help="create a person file with rides using STR as lines attribute")
78
op.add_argument("--persontrips", category="persons", action="store_true", default=False,
79
help="create a person file with person trips instead of vehicle trips")
80
op.add_argument("--persontrip.transfer.car-walk", category="persons", dest="carWalkMode",
81
help="Where are mode changes from car to walking allowed " +
82
"(possible values: 'ptStops', 'allJunctions' and combinations)")
83
op.add_argument("--persontrip.walkfactor", category="persons", dest="walkfactor", metavar="FLOAT", type=float,
84
help="Use FLOAT as a factor on pedestrian maximum speed during intermodal routing")
85
op.add_argument("--persontrip.walk-opposite-factor", category="persons", dest="walkoppositefactor",
86
metavar="FLOAT", type=float,
87
help="Use FLOAT as a factor on pedestrian maximum speed against vehicle traffic direction")
88
op.add_argument("--from-stops", category="persons", dest="fromStops",
89
help="Create trips that start at stopping places of the indicated type(s). i.e. 'busStop'")
90
op.add_argument("--to-stops", category="persons", dest="toStops",
91
help="Create trips that end at stopping places of the indicated type(s). i.e. 'busStop'")
92
# attributes
93
op.add_argument("--prefix", category="attributes", dest="tripprefix", default="",
94
help="prefix for the trip ids")
95
op.add_argument("-t", "--trip-attributes", category="attributes", dest="tripattrs", default="",
96
help="additional trip attributes. When generating pedestrians, attributes for " +
97
"'person' and 'walk' are supported.")
98
op.add_argument("--fringe-start-attributes", category="attributes", dest="fringeattrs", default="",
99
help="additional trip attributes when starting on a fringe.")
100
op.add_argument("--vehicle-class",
101
help="The vehicle class assigned to the generated trips (adds a standard vType definition " +
102
"to the output file).")
103
op.add_argument("--random-departpos", category="attributes", dest="randomDepartPos", action="store_true",
104
default=False, help="Randomly choose a position on the starting edge of the trip")
105
op.add_argument("--random-arrivalpos", category="attributes", dest="randomArrivalPos", action="store_true",
106
default=False, help="Randomly choose a position on the ending edge of the trip")
107
op.add_argument("--junction-taz", category="attributes", dest="junctionTaz", action="store_true", default=False,
108
help="Write trips with fromJunction and toJunction")
109
# weights
110
op.add_argument("-l", "--length", category="weights", action="store_true", default=False,
111
help="weight edge probability by length")
112
op.add_argument("-L", "--lanes", category="weights", action="store_true", default=False,
113
help="weight edge probability by number of lanes")
114
op.add_argument("--edge-param", category="weights", dest="edgeParam",
115
help="use the given edge parameter as factor for edge")
116
op.add_argument("--speed-exponent", category="weights", dest="speed_exponent", metavar="FLOAT", type=float,
117
default=0.0, help="weight edge probability by speed^'FLOAT' (default 0)")
118
op.add_argument("--fringe-speed-exponent", category="weights", dest="fringe_speed_exponent", metavar="FLOAT",
119
help="weight fringe edge probability by speed^'FLOAT' (default: speed exponent)")
120
op.add_argument("--angle", category="weights", dest="angle", default=90.0, type=float,
121
help="weight edge probability by angle [0-360] relative to the network center")
122
op.add_argument("--angle-factor", category="weights", dest="angle_weight", default=1.0, type=float,
123
help="maximum weight factor for angle")
124
op.add_argument("--random-factor", category="weights", dest="randomFactor", default=1.0, type=float,
125
help="edge weights are dynamically disturbed by a random factor drawn uniformly from [1,FLOAT]")
126
op.add_argument("--fringe-factor", category="weights", dest="fringe_factor", default="1.0",
127
help="multiply weight of fringe edges by 'FLOAT' (default 1)" +
128
" or set value 'max' to force all traffic to start/end at the fringe.")
129
op.add_argument("--fringe-threshold", category="weights", dest="fringe_threshold", default=0.0, type=float,
130
help="only consider edges with speed above 'FLOAT' as fringe edges (default 0)")
131
op.add_argument("--allow-fringe", category="weights", dest="allow_fringe", action="store_true", default=False,
132
help="Allow departing on edges that leave the network and arriving on edges " +
133
"that enter the network (via turnarounds or as 1-edge trips")
134
op.add_argument("--allow-fringe.min-length", category="weights", dest="allow_fringe_min_length", type=float,
135
help="Allow departing on edges that leave the network and arriving on edges " +
136
"that enter the network, if they have at least the given length")
137
op.add_argument("--fringe-junctions", category="weights", action="store_true", dest="fringeJunctions",
138
default=False, help="Determine fringe edges based on junction attribute 'fringe'")
139
op.add_argument("--edge-permission", "--vclass", category="weights",
140
help="only from and to edges which permit the given vehicle class")
141
op.add_argument("--via-edge-types", category="weights", dest="viaEdgeTypes",
142
help="Set list of edge types that cannot be used for departure or arrival " +
143
"(unless being on the fringe)")
144
op.add_argument("--allow-roundabouts", category="weights", dest="allowRoundabouts", action="store_true",
145
default=False, help="Permit trips that start or end inside a roundabout")
146
# processing
147
op.add_argument("-s", "--seed", default=42, type=int,
148
help="random seed")
149
op.add_argument("--random", action="store_true", default=False,
150
help="use a random seed to initialize the random number generator")
151
op.add_argument("--min-distance", dest="min_distance", metavar="FLOAT", default=0.0,
152
type=float, help="require start and end edges for each trip to be at least 'FLOAT' m apart")
153
op.add_argument("--min-distance.fringe", dest="min_dist_fringe", metavar="FLOAT", type=float,
154
help="require start and end edges for each fringe to fringe trip to be at least 'FLOAT' m apart")
155
op.add_argument("--max-distance", dest="max_distance", metavar="FLOAT", type=float,
156
help="require start and end edges for each trip to be at most 'FLOAT' m " +
157
"apart (default 0 which disables any checks)")
158
op.add_argument("-i", "--intermediate", default=0, type=int,
159
help="generates the given number of intermediate way points")
160
op.add_argument("--jtrrouter", action="store_true", default=False,
161
help="Create flows without destination as input for jtrrouter")
162
op.add_argument("--maxtries", default=100, type=int,
163
help="number of attemps for finding a trip which meets the distance constraints")
164
op.add_argument("--remove-loops", dest="remove_loops", action="store_true", default=False,
165
help="Remove loops at route start and end")
166
op.add_argument("--random-routing-factor", dest="randomRoutingFactor", default=1, type=float,
167
help="Edge weights for routing are dynamically disturbed "
168
"by a random factor drawn uniformly from [1,FLOAT)")
169
op.add_argument("--marouter", default=False, action="store_true",
170
help="Compute routes with marouter instead of duarouter")
171
op.add_argument("--validate", action="store_true",
172
help="Whether to produce trip output that is already checked for connectivity")
173
op.add_argument("--no-validate", dest="validate", action="store_false")
174
op.set_defaults(validate=True)
175
op.add_argument("--min-success-rate", dest="minSuccessRate", default=0.1, type=float,
176
help="Minimum ratio of valid trips to retry sampling if some trips are invalid")
177
op.add_argument("-v", "--verbose", action="store_true", default=False,
178
help="tell me what you are doing")
179
# flow
180
op.add_argument("-b", "--begin", category="flow", default=0, type=op.time,
181
help="begin time")
182
op.add_argument("-e", "--end", category="flow", default=3600, type=op.time,
183
help="end time (default 3600)")
184
group = op.add_mutually_exclusive_group()
185
group.add_argument("-p", "--period", nargs="+", metavar="FLOAT", category="flow",
186
action=sumolib.options.SplitAction,
187
help="Generate vehicles with equidistant departure times and period=FLOAT (default 1.0). " +
188
"If option --binomial is used, the expected arrival rate is set to 1/period.")
189
group.add_argument("--insertion-rate", dest="insertionRate", nargs="+", metavar="FLOAT", category="flow",
190
action=sumolib.options.SplitAction,
191
help="How much vehicles arrive in the simulation per hour (alternative to the period option).")
192
group.add_argument("--insertion-density", dest="insertionDensity", nargs="+", metavar="FLOAT", category="flow",
193
action=sumolib.options.SplitAction,
194
help="How much vehicles arrive in the simulation per hour per kilometer of road " +
195
"(alternative to the period option).")
196
op.add_argument("--flows", category="flow", default=0, type=int,
197
help="generates INT flows that together output vehicles with the specified period")
198
op.add_argument("--poisson", default=False, action="store_true",
199
help="Flows will use poisson distributed departures")
200
op.add_argument("--random-depart", category="flow", action="store_true", dest="randomDepart", default=False,
201
help="Distribute departures randomly between begin and end")
202
op.add_argument("--binomial", category="flow", metavar="N", type=int,
203
help="If this is set, the number of departures per second will be drawn from a binomial " +
204
"distribution with n=N and p=PERIOD/N where PERIOD is the argument given to --period")
205
206
options = op.parse_args(args=args)
207
if options.edge_permission and not is_vehicle_class(options.edge_permission):
208
raise ValueError("The string '%s' doesn't correspond to a legit vehicle class." % options.edge_permission)
209
210
if options.persontrips or options.personrides:
211
options.pedestrians = True
212
213
if options.edge_permission is None:
214
if options.vehicle_class:
215
options.edge_permission = options.vehicle_class
216
else:
217
options.edge_permission = 'pedestrian' if options.pedestrians else 'passenger'
218
if options.validate and options.routefile is None:
219
options.routefile = "routes.rou.xml"
220
221
if options.period is None and options.insertionRate is None and options.insertionDensity is None:
222
options.period = [1.]
223
224
options.net = sumolib.net.readNet(options.netfile)
225
if options.insertionDensity:
226
# Compute length of the network
227
length = 0. # In meters
228
for edge in options.net.getEdges():
229
if edge.allows(options.edge_permission):
230
length += edge.getLaneNumber() * edge.getLength()
231
if length == 0:
232
raise ValueError("No valid edges for computing insertion-density")
233
234
options.insertionRate = [density * (length / 1000.0) for density in options.insertionDensity]
235
236
if options.insertionRate:
237
options.period = [3600.0 / rate if rate != 0.0 else 0.0 for rate in options.insertionRate]
238
239
if options.period:
240
if any([period < 0 for period in options.period]):
241
raise ValueError("Period / insertionRate must be non-negative.")
242
options.period = list(map(intIfPossible, options.period))
243
if options.binomial:
244
for p in options.period:
245
if p != 0.0 and 1.0 / p / options.binomial >= 1:
246
print("Warning: Option --binomial %s is too low for insertion period %s." % (options.binomial, p)
247
+ " Insertions will not be randomized.", file=sys.stderr)
248
249
if options.jtrrouter and options.flows <= 0:
250
raise ValueError("Option --jtrrouter must be used with option --flows.")
251
252
if options.vehicle_class:
253
if not is_vehicle_class(options.vehicle_class):
254
raise ValueError("The string '%s' doesn't correspond to a legit vehicle class." % options.vehicle_class)
255
256
if options.tripprefix:
257
options.vtypeID = "%s_%s" % (options.tripprefix, options.vehicle_class)
258
else:
259
options.vtypeID = options.vehicle_class
260
261
if 'type=' in options.tripattrs:
262
raise ValueError("Trip-attribute 'type' cannot be used together with option --vehicle-class.")
263
264
if options.randomDepartPos:
265
if 'departPos' in options.tripattrs:
266
raise ValueError("Trip-attribute 'departPos' cannot be used together with option --random-departpos.")
267
268
if options.randomArrivalPos:
269
if 'arrivalPos' in options.tripattrs:
270
raise ValueError("Trip-attribute 'arrivalPos' cannot be used together with option --random-arrivalpos.")
271
272
if options.weightsprefix:
273
weight_files = [options.weightsprefix + s for s in (SOURCE_SUFFIX, DEST_SUFFIX, VIA_SUFFIX)]
274
if not any([os.path.isfile(w) for w in weight_files]):
275
raise ValueError("None of the weight files '%s' exists." % "', '".join(weight_files))
276
277
if options.randomFactor < 1:
278
raise ValueError("Option --random-factor requires a value >= 1.")
279
280
if options.fromStops or options.toStops:
281
options.edgeFromStops, options.edgeToStops = loadStops(options)
282
283
if options.viaEdgeTypes:
284
options.viaEdgeTypes = options.viaEdgeTypes.split(',')
285
if options.fringe_speed_exponent is None:
286
options.fringe_speed_exponent = options.speed_exponent
287
288
if options.fringe_factor.lower() == MAXIMIZE_FACTOR:
289
options.fringe_factor = MAXIMIZE_FACTOR
290
else:
291
try:
292
options.fringe_factor = float(options.fringe_factor)
293
if options.fringe_factor < 0:
294
raise ValueError("--fringe-factor argument may not be negative.")
295
except ValueError:
296
raise ValueError("--fringe-factor argument must be a float or 'max'.")
297
298
options.typeFactors = defaultdict(lambda: 1.0)
299
if options.typeFactorFile:
300
with openz(options.typeFactorFile) as tff:
301
for line in tff:
302
typeID, factor = line.split()
303
options.typeFactors[typeID] = float(factor)
304
305
return options
306
307
308
class InvalidGenerator(Exception):
309
pass
310
311
312
def loadStops(options):
313
edgeFromStops = defaultdict(list) # edge -> [(stopType1, stopID1), ...]
314
edgeToStops = defaultdict(list) # edge -> [(stopType1, stopID1), ...]
315
if options.additional is None:
316
print("Error: Option %s requires option --additional-files for loading infrastructure elements" %
317
("--from-stops" if options.fromStops else "--to-stops"), file=sys.stderr)
318
sys.exit(1)
319
stopTypes = []
320
if options.fromStops:
321
options.fromStops = options.fromStops.split(',')
322
stopTypes += options.fromStops
323
else:
324
options.fromStops = []
325
if options.toStops:
326
options.toStops = options.toStops.split(',')
327
stopTypes += options.toStops
328
else:
329
options.toStops = []
330
stopTypes = list(set(stopTypes))
331
typeCounts = defaultdict(lambda: 0)
332
for additional in options.additional.split(','):
333
for stop in sumolib.xml.parse(additional, stopTypes):
334
edgeID = stop.lane.rsplit('_', 1)[0]
335
if stop.name in options.fromStops:
336
edgeFromStops[edgeID].append((stop.name, stop.id))
337
if stop.name in options.toStops:
338
edgeToStops[edgeID].append((stop.name, stop.id))
339
typeCounts[stop.name] += 1
340
341
if options.fromStops:
342
available = sum([typeCounts[t] for t in options.fromStops])
343
if available == 0:
344
print("No stops of type%s '%s' were found in additional-files %s" % (
345
('' if len(options.fromStops) == 1 else 's'),
346
options.fromStops[0], options.additional), file=sys.stderr)
347
sys.exit(1)
348
if options.toStops:
349
available = sum([typeCounts[t] for t in options.toStops])
350
if available == 0:
351
print("No stops of type%s '%s' were found in additional-files %s" % (
352
('' if len(options.toStops) == 1 else 's'),
353
options.toStops[0], options.additional), file=sys.stderr)
354
sys.exit(1)
355
return edgeFromStops, edgeToStops
356
357
358
# assigns a weight to each edge using weight_fun and then draws from a discrete
359
# distribution with these weights
360
361
362
class RandomEdgeGenerator:
363
364
def __init__(self, net, weight_fun):
365
self.net = net
366
self.weight_fun = weight_fun
367
self.cumulative_weights = []
368
self.total_weight = 0
369
for edge in self.net._edges:
370
# print edge.getID(), weight_fun(edge)
371
self.total_weight += weight_fun(edge)
372
self.cumulative_weights.append(self.total_weight)
373
if self.total_weight == 0:
374
raise InvalidGenerator()
375
376
def get(self):
377
r = random.random() * self.total_weight
378
index = bisect.bisect(self.cumulative_weights, r)
379
return self.net._edges[index]
380
381
def write_weights(self, fname, interval_id, begin, end):
382
# normalize to [0,100]
383
normalizer = 100.0 / max(1, max(map(self.weight_fun, self.net._edges)))
384
weights = [(self.weight_fun(e) * normalizer, e.getID()) for e in self.net.getEdges()]
385
weights.sort(reverse=True)
386
total = sum([w for w, e in weights])
387
with openz(fname, 'w+') as f:
388
f.write('<edgedata>\n')
389
f.write(' <interval id="%s" begin="%s" end="%s" totalWeight="%0.2f">\n' % (
390
interval_id, begin, end, total))
391
for weight, edgeID in weights:
392
f.write(' <edge id="%s" value="%0.2f"/>\n' %
393
(edgeID, weight))
394
f.write(' </interval>\n')
395
f.write('</edgedata>\n')
396
397
398
class RandomTripGenerator:
399
400
def __init__(self, source_generator, sink_generator, via_generator, intermediate, pedestrians):
401
self.source_generator = source_generator
402
self.sink_generator = sink_generator
403
self.via_generator = via_generator
404
self.intermediate = intermediate
405
self.pedestrians = pedestrians
406
407
def get_trip(self, min_distance, max_distance, maxtries=100, junctionTaz=False, min_dist_fringe=None):
408
for min_dist in [min_distance, min_dist_fringe]:
409
if min_dist is None:
410
break
411
for _ in range(maxtries):
412
source_edge = self.source_generator.get()
413
intermediate = [self.via_generator.get() for __ in range(self.intermediate)]
414
sink_edge = self.sink_generator.get()
415
is_fringe2fringe = source_edge.is_fringe() and sink_edge.is_fringe() and not intermediate
416
if min_dist == min_dist_fringe and not is_fringe2fringe:
417
continue
418
if self.pedestrians:
419
destCoord = sink_edge.getFromNode().getCoord()
420
else:
421
destCoord = sink_edge.getToNode().getCoord()
422
coords = ([source_edge.getFromNode().getCoord()] +
423
[e.getFromNode().getCoord() for e in intermediate] +
424
[destCoord])
425
distance = sum([euclidean(p, q)
426
for p, q in zip(coords[:-1], coords[1:])])
427
if (distance >= min_dist
428
and (not junctionTaz or source_edge.getFromNode() != sink_edge.getToNode())
429
and (max_distance is None or distance < max_distance)):
430
return source_edge, sink_edge, intermediate
431
raise Exception("Warning: no trip found after %s tries" % maxtries)
432
433
434
class CachedTripGenerator:
435
436
def __init__(self, cache):
437
self._cache = cache
438
self._nCalled = 0
439
440
def get_trip(self, min_distance, max_distance, maxtries=100, junctionTaz=False, min_dist_fringe=None):
441
result = self._cache[self._nCalled % len(self._cache)]
442
self._nCalled += 1
443
return result
444
445
446
def get_prob_fun(options, fringe_bonus, fringe_forbidden, max_length):
447
# fringe_bonus None generates intermediate way points
448
randomProbs = defaultdict(lambda: 1)
449
if options.randomFactor != 1:
450
for edge in options.net.getEdges():
451
randomProbs[edge.getID()] = random.uniform(1, options.randomFactor)
452
453
roundabouts = set()
454
if not options.allowRoundabouts:
455
for roundabout in options.net.getRoundabouts():
456
roundabouts.update(roundabout.getEdges())
457
458
stopDict = None
459
if options.fromStops and fringe_bonus == "_incoming":
460
stopDict = options.edgeFromStops
461
elif options.toStops and fringe_bonus == "_outgoing":
462
stopDict = options.edgeToStops
463
464
def edge_probability(edge):
465
bonus_connections = None if fringe_bonus is None else getattr(edge, fringe_bonus)
466
forbidden_connections = None if fringe_forbidden is None else getattr(edge, fringe_forbidden)
467
if options.edge_permission and not edge.allows(options.edge_permission) and not stopDict:
468
return 0 # not allowed
469
if fringe_bonus is None and edge.is_fringe() and not options.pedestrians:
470
return 0 # not suitable as intermediate way point
471
if (fringe_forbidden is not None and
472
edge.is_fringe(forbidden_connections) and
473
not options.pedestrians and
474
(options.allow_fringe_min_length is None or edge.getLength() < options.allow_fringe_min_length)):
475
return 0 # the wrong kind of fringe
476
if (fringe_bonus is not None and options.viaEdgeTypes is not None and
477
not edge.is_fringe(bonus_connections, checkJunctions=options.fringeJunctions) and
478
edge.getType() in options.viaEdgeTypes):
479
return 0 # the wrong type of edge (only allows depart and arrival on the fringe)
480
if fringe_bonus is not None and edge.getID() in roundabouts:
481
return 0 # traffic typically does not start/end inside a roundabout
482
prob = randomProbs[edge.getID()]
483
if stopDict:
484
prob *= len(stopDict[edge.getID()])
485
if options.length:
486
if (options.fringe_factor != 1.0 and fringe_bonus is not None and
487
edge.is_fringe(bonus_connections, checkJunctions=options.fringeJunctions)):
488
# short fringe edges should not suffer a penalty
489
prob *= max_length
490
else:
491
prob *= edge.getLength()
492
if options.lanes:
493
prob *= edge.getLaneNumber()
494
if edge.is_fringe(bonus_connections, checkJunctions=options.fringeJunctions):
495
prob *= (edge.getSpeed() ** options.fringe_speed_exponent)
496
else:
497
prob *= (edge.getSpeed() ** options.speed_exponent)
498
if options.fringe_factor != 1.0 and fringe_bonus is not None:
499
isFringe = (edge.getSpeed() > options.fringe_threshold and
500
edge.is_fringe(bonus_connections, checkJunctions=options.fringeJunctions))
501
if isFringe and options.fringe_factor != MAXIMIZE_FACTOR:
502
prob *= options.fringe_factor
503
elif not isFringe and options.fringe_factor == MAXIMIZE_FACTOR:
504
prob = 0
505
if options.edgeParam is not None:
506
prob *= float(edge.getParam(options.edgeParam, 1.0))
507
if options.angle_weight != 1.0 and fringe_bonus is not None:
508
xmin, ymin, xmax, ymax = edge.getBoundingBox()
509
ex, ey = ((xmin + xmax) / 2, (ymin + ymax) / 2)
510
nx, ny = options.angle_center
511
edgeAngle = naviDegree(math.atan2(ey - ny, ex - nx))
512
angleDiff = minAngleDegreeDiff(options.angle, edgeAngle)
513
# print("e=%s nc=%s ec=%s ea=%s a=%s ad=%s" % (
514
# edge.getID(), options.angle_center, (ex,ey), edgeAngle,
515
# options.angle, angleDiff))
516
# relDist = 2 * euclidean((ex, ey), options.angle_center) / max(xmax - xmin, ymax - ymin)
517
# prob *= (relDist * (options.angle_weight - 1) + 1)
518
if fringe_bonus == "_incoming":
519
# source edge
520
prob *= (angleDiff * (options.angle_weight - 1) + 1)
521
else:
522
prob *= ((180 - angleDiff) * (options.angle_weight - 1) + 1)
523
prob *= options.typeFactors[edge.getType()]
524
525
return prob
526
return edge_probability
527
528
529
class LoadedProps:
530
531
def __init__(self, fname):
532
self.weights = defaultdict(lambda: 0)
533
for edge in sumolib.xml.parse_fast(fname, 'edge', ['id', 'value']):
534
self.weights[edge.id] = float(edge.value)
535
536
def __call__(self, edge):
537
return self.weights[edge.getID()]
538
539
540
def buildTripGenerator(net, options):
541
try:
542
max_length = 0
543
for edge in net.getEdges():
544
if not edge.is_fringe():
545
max_length = max(max_length, edge.getLength())
546
forbidden_source_fringe = None if options.allow_fringe else "_outgoing"
547
forbidden_sink_fringe = None if options.allow_fringe else "_incoming"
548
source_generator = RandomEdgeGenerator(
549
net, get_prob_fun(options, "_incoming", forbidden_source_fringe, max_length))
550
sink_generator = RandomEdgeGenerator(
551
net, get_prob_fun(options, "_outgoing", forbidden_sink_fringe, max_length))
552
if options.weightsprefix:
553
if os.path.isfile(options.weightsprefix + SOURCE_SUFFIX):
554
source_generator = RandomEdgeGenerator(
555
net, LoadedProps(options.weightsprefix + SOURCE_SUFFIX))
556
if os.path.isfile(options.weightsprefix + DEST_SUFFIX):
557
sink_generator = RandomEdgeGenerator(
558
net, LoadedProps(options.weightsprefix + DEST_SUFFIX))
559
except InvalidGenerator:
560
print("Error: no valid edges for generating source or destination. Try using option --allow-fringe",
561
file=sys.stderr)
562
return None
563
564
try:
565
via_generator = RandomEdgeGenerator(
566
net, get_prob_fun(options, None, None, 1))
567
if options.weightsprefix and os.path.isfile(options.weightsprefix + VIA_SUFFIX):
568
via_generator = RandomEdgeGenerator(
569
net, LoadedProps(options.weightsprefix + VIA_SUFFIX))
570
except InvalidGenerator:
571
if options.intermediate > 0:
572
print("Error: no valid edges for generating intermediate points", file=sys.stderr)
573
return None
574
else:
575
via_generator = None
576
577
return RandomTripGenerator(
578
source_generator, sink_generator, via_generator, options.intermediate, options.pedestrians)
579
580
581
def is_walk_attribute(attr):
582
for cand in ['arrivalPos', 'speed=', 'duration=', 'busStop=']:
583
if cand in attr:
584
return True
585
return False
586
587
588
def is_persontrip_attribute(attr):
589
for cand in ['vTypes', 'modes']:
590
if cand in attr:
591
return True
592
return False
593
594
595
def is_person_attribute(attr):
596
for cand in ['departPos', 'type']:
597
if cand in attr:
598
return True
599
return False
600
601
602
def is_vehicle_attribute(attr):
603
# speedFactor could be used in vType and vehicle but we need it in the vType
604
# to allow for the multi-parameter version
605
for cand in ['depart', 'arrival', 'line', 'personNumber', 'containerNumber', 'type']:
606
if cand in attr:
607
return True
608
return False
609
610
611
def split_trip_attributes(tripattrs, pedestrians, hasType, verbose):
612
# handle attribute values with a space
613
# assume that no attribute value includes an '=' sign
614
allattrs = []
615
for a in tripattrs.split():
616
if "=" in a:
617
allattrs.append(a)
618
else:
619
if len(allattrs) == 0:
620
print("Warning: invalid trip-attribute '%s'" % a)
621
else:
622
allattrs[-1] += ' ' + a
623
624
# figure out which of the tripattrs belong to the <person> or <vehicle>,
625
# which belong to the <vType> and which belong to the <walk> or <persontrip>
626
vehicleattrs = []
627
personattrs = []
628
vtypeattrs = []
629
otherattrs = []
630
for a in allattrs:
631
if pedestrians:
632
if is_walk_attribute(a) or is_persontrip_attribute(a):
633
otherattrs.append(a)
634
elif is_person_attribute(a):
635
personattrs.append(a)
636
else:
637
vtypeattrs.append(a)
638
else:
639
if is_vehicle_attribute(a):
640
vehicleattrs.append(a)
641
else:
642
vtypeattrs.append(a)
643
644
if not hasType:
645
if pedestrians:
646
personattrs += vtypeattrs
647
else:
648
vehicleattrs += vtypeattrs
649
vtypeattrs = []
650
651
return (prependSpace(' '.join(vtypeattrs)),
652
prependSpace(' '.join(vehicleattrs)),
653
prependSpace(' '.join(personattrs)),
654
prependSpace(' '.join(otherattrs)))
655
656
657
def prependSpace(s):
658
if len(s) == 0 or s[0] == " ":
659
return s
660
else:
661
return " " + s
662
663
664
def samplePosition(edge):
665
return random.uniform(0.0, edge.getLength())
666
667
668
def getElement(options):
669
if options.pedestrians:
670
if options.flows > 0:
671
return "personFlow"
672
else:
673
return "person"
674
else:
675
if options.flows > 0:
676
return "flow"
677
else:
678
return "trip"
679
680
681
def main(options):
682
if all([period == 0 for period in options.period]):
683
print("Warning: All intervals are empty.", file=sys.stderr)
684
return False
685
686
if not options.random:
687
random.seed(options.seed)
688
689
if options.min_distance > options.net.getBBoxDiameter() * (options.intermediate + 1):
690
options.intermediate = int(math.ceil(options.min_distance / options.net.getBBoxDiameter())) - 1
691
print(("Warning: Using %s intermediate waypoints to achieve a minimum trip length of %s in a network "
692
"with diameter %.2f.") % (options.intermediate, options.min_distance, options.net.getBBoxDiameter()),
693
file=sys.stderr)
694
695
if options.angle_weight != 1:
696
xmin, ymin, xmax, ymax = options.net.getBoundary()
697
options.angle_center = (xmin + xmax) / 2, (ymin + ymax) / 2
698
699
trip_generator = buildTripGenerator(options.net, options)
700
701
if trip_generator and options.weights_outprefix:
702
idPrefix = ""
703
if options.tripprefix:
704
idPrefix = options.tripprefix + "."
705
trip_generator.source_generator.write_weights(
706
options.weights_outprefix + SOURCE_SUFFIX,
707
idPrefix + "src", options.begin, options.end)
708
trip_generator.sink_generator.write_weights(
709
options.weights_outprefix + DEST_SUFFIX,
710
idPrefix + "dst", options.begin, options.end)
711
if trip_generator.via_generator:
712
trip_generator.via_generator.write_weights(
713
options.weights_outprefix + VIA_SUFFIX,
714
idPrefix + "via", options.begin, options.end)
715
716
createTrips(options, trip_generator)
717
718
# return wether trips could be generated as requested
719
return trip_generator is not None
720
721
722
def createTrips(options, trip_generator, rerunFactor=None, skipValidation=False):
723
idx = 0
724
725
vtypeattrs, tripattrs, personattrs, otherattrs = split_trip_attributes(
726
options.tripattrs, options.pedestrians, options.vehicle_class, options.verbose)
727
728
vias = {}
729
generatedTrips = [] # (label, origin, destination, intermediate)
730
validatedTrips = [] # (origin, destination, intermediate)
731
732
time_delta = (parseTime(options.end) - parseTime(options.begin)) / len(options.period)
733
times = [parseTime(options.begin) + i * time_delta for i in range(len(options.period) + 1)]
734
times = list(map(intIfPossible, times))
735
736
def generate_origin_destination(trip_generator, options):
737
source_edge, sink_edge, intermediate = trip_generator.get_trip(
738
options.min_distance, options.max_distance, options.maxtries,
739
options.junctionTaz, options.min_dist_fringe)
740
return source_edge, sink_edge, intermediate
741
742
def generate_attributes(idx, departureTime, arrivalTime, origin, destination, intermediate, options):
743
label = "%s%s" % (options.tripprefix, idx)
744
if options.pedestrians:
745
combined_attrs = ""
746
else:
747
combined_attrs = tripattrs
748
arrivalPos = ""
749
if options.randomDepartPos:
750
randomPosition = samplePosition(origin)
751
combined_attrs += ' departPos="%.2f"' % randomPosition
752
if options.randomArrivalPos:
753
randomPosition = samplePosition(destination)
754
arrivalPos = ' arrivalPos="%.2f"' % randomPosition
755
if not options.pedestrians:
756
combined_attrs += arrivalPos
757
if options.fringeattrs and origin.is_fringe(
758
origin._incoming, checkJunctions=options.fringeJunctions):
759
combined_attrs += " " + options.fringeattrs
760
if options.junctionTaz:
761
attrFrom = ' fromJunction="%s"' % origin.getFromNode().getID()
762
attrTo = ' toJunction="%s"' % destination.getToNode().getID()
763
else:
764
attrFrom = ' from="%s"' % origin.getID()
765
attrTo = ' to="%s"' % destination.getID()
766
if options.fromStops:
767
attrFrom = ' %s="%s"' % random.choice(options.edgeFromStops[origin.getID()])
768
if options.toStops:
769
attrTo = ' %s="%s"' % random.choice(options.edgeToStops[destination.getID()])
770
via = ""
771
if intermediate:
772
via = ' via="%s" ' % ' '.join(
773
[e.getID() for e in intermediate])
774
if options.validate:
775
vias[label] = via
776
return label, combined_attrs, attrFrom, attrTo, via, arrivalPos
777
778
def generate_one_plan(combined_attrs, attrFrom, attrTo, arrivalPos, intermediate, options):
779
element = "walk"
780
attrs = otherattrs + arrivalPos
781
if options.fromStops:
782
fouttrips.write(' <stop%s duration="0"/>\n' % attrFrom)
783
attrFrom = ''
784
if options.persontrips:
785
element = "personTrip"
786
elif options.personrides:
787
element = "ride"
788
attrs = ' lines="%s%s"' % (options.personrides, otherattrs)
789
if intermediate:
790
fouttrips.write(' <%s%s to="%s"%s/>\n' % (element, attrFrom, intermediate[0].getID(), attrs))
791
for edge in intermediate[1:]:
792
fouttrips.write(' <%s to="%s"%s/>\n' % (element, edge.getID(), attrs))
793
fouttrips.write(' <%s%s%s/>\n' % (element, attrTo, attrs))
794
else:
795
fouttrips.write(' <%s%s%s%s/>\n' % (element, attrFrom, attrTo, attrs))
796
797
def generate_one_person(label, combined_attrs, attrFrom, attrTo, arrivalPos, departureTime, intermediate, options):
798
fouttrips.write(
799
' <person id="%s" depart="%.2f"%s%s>\n' % (label, departureTime, personattrs, combined_attrs))
800
generate_one_plan(combined_attrs, attrFrom, attrTo, arrivalPos, intermediate, options)
801
fouttrips.write(' </person>\n')
802
803
def generate_one_flow(label, combined_attrs, departureTime, arrivalTime, period, options, timeIdx):
804
if len(options.period) > 1:
805
label = label + "#%s" % timeIdx
806
if options.binomial:
807
for j in range(options.binomial):
808
fouttrips.write((' <flow id="%s#%s" begin="%s" end="%s" probability="%.2f"%s/>\n') % (
809
label, j, departureTime, arrivalTime, 1.0 / period / options.binomial,
810
combined_attrs))
811
elif options.poisson:
812
fouttrips.write((' <flow id="%s" begin="%s" end="%s" period="exp(%s)"%s/>\n') % (
813
label, departureTime, arrivalTime, 1 / (period * options.flows), combined_attrs))
814
else:
815
fouttrips.write((' <flow id="%s" begin="%s" end="%s" period="%s"%s/>\n') % (
816
label, departureTime, arrivalTime, intIfPossible(period * options.flows), combined_attrs))
817
818
def generate_one_personflow(label, combined_attrs, attrFrom, attrTo, arrivalPos,
819
departureTime, arrivalTime, period, options, timeIdx):
820
if len(options.period) > 1:
821
label = label + "#%s" % timeIdx
822
if options.binomial:
823
for j in range(options.binomial):
824
fouttrips.write((' <personFlow id="%s#%s" begin="%s" end="%s" probability="%.2f"%s>\n') % (
825
label, j, departureTime, arrivalTime, 1.0 / period / options.binomial,
826
combined_attrs))
827
generate_one_plan(combined_attrs, attrFrom, attrTo, arrivalPos, intermediate, options)
828
fouttrips.write(' </personFlow>\n')
829
else:
830
if options.poisson:
831
fouttrips.write((' <personFlow id="%s" begin="%s" end="%s" period="exp(%s)"%s>\n') % (
832
label, departureTime, arrivalTime, 1 / (period * options.flows), combined_attrs))
833
else:
834
fouttrips.write((' <personFlow id="%s" begin="%s" end="%s" period="%s"%s>\n') % (
835
label, departureTime, arrivalTime, intIfPossible(period * options.flows), combined_attrs))
836
generate_one_plan(combined_attrs, attrFrom, attrTo, arrivalPos, intermediate, options)
837
fouttrips.write(' </personFlow>\n')
838
839
def generate_one_trip(label, combined_attrs, departureTime):
840
fouttrips.write(' <trip id="%s" depart="%.2f"%s/>\n' % (
841
label, departureTime, combined_attrs))
842
843
def generate_one(idx, departureTime, arrivalTime, period, origin, destination, intermediate, timeIdx=None):
844
try:
845
label, combined_attrs, attrFrom, attrTo, via, arrivalPos = generate_attributes(
846
idx, departureTime, arrivalTime, origin, destination, intermediate, options)
847
generatedTrips.append((label, origin, destination, intermediate))
848
849
if options.pedestrians:
850
if options.flows > 0:
851
generate_one_personflow(label, combined_attrs, attrFrom, attrTo, arrivalPos,
852
departureTime, arrivalTime, period, options, timeIdx)
853
else:
854
generate_one_person(label, combined_attrs, attrFrom, attrTo, arrivalPos,
855
departureTime, intermediate, options)
856
else:
857
if options.jtrrouter:
858
attrTo = ''
859
860
combined_attrs = attrFrom + attrTo + via + combined_attrs
861
862
if options.flows > 0:
863
generate_one_flow(label, combined_attrs, departureTime, arrivalTime, period, options, timeIdx)
864
else:
865
generate_one_trip(label, combined_attrs, departureTime)
866
867
except Exception as exc:
868
print(exc, file=sys.stderr)
869
870
return idx + 1
871
872
with openz(options.tripfile, 'w') as fouttrips:
873
sumolib.writeXMLHeader(fouttrips, "$Id$", "routes", options=options)
874
if options.vehicle_class:
875
vTypeDef = ' <vType id="%s" vClass="%s"%s/>\n' % (
876
options.vtypeID, options.vehicle_class, vtypeattrs)
877
if options.vtypeout:
878
# ensure that trip output does not contain types, file may be
879
# overwritten by later call to duarouter
880
if options.additional is None:
881
options.additional = options.vtypeout
882
elif options.vtypeout not in options.additional:
883
options.additional = options.additional + "," + options.vtypeout
884
with openz(options.vtypeout, 'w') as fouttype:
885
sumolib.writeXMLHeader(fouttype, "$Id$", "additional", options=options)
886
fouttype.write(vTypeDef)
887
fouttype.write("</additional>\n")
888
else:
889
fouttrips.write(vTypeDef)
890
tripattrs += ' type="%s"' % options.vtypeID
891
personattrs += ' type="%s"' % options.vtypeID
892
893
if trip_generator:
894
if options.flows == 0:
895
for i in range(len(times)-1):
896
time = departureTime = parseTime(times[i])
897
arrivalTime = parseTime(times[i+1])
898
period = options.period[i]
899
if rerunFactor is not None:
900
period /= rerunFactor
901
if period == 0.0:
902
continue
903
if options.binomial is None:
904
departures = []
905
if options.randomDepart:
906
subsecond = math.fmod(period, 1)
907
while time < arrivalTime:
908
rTime = random.randrange(int(departureTime), int(arrivalTime))
909
time += period
910
if subsecond != 0:
911
# allow all multiples of subsecond to appear
912
rSubSecond = math.fmod(
913
subsecond * random.randrange(int(departureTime), int(arrivalTime)), 1)
914
rTime = min(arrivalTime, rTime + rSubSecond)
915
departures.append(rTime)
916
departures.sort()
917
else:
918
# generate with constant spacing
919
while departureTime < arrivalTime:
920
departures.append(departureTime)
921
departureTime += period
922
923
for time in departures:
924
try:
925
origin, destination, intermediate = generate_origin_destination(trip_generator, options)
926
idx = generate_one(idx, time, arrivalTime, period, origin, destination, intermediate)
927
except Exception as exc:
928
print(exc, file=sys.stderr)
929
else:
930
time = departureTime
931
while time < arrivalTime:
932
# draw n times from a Bernoulli distribution
933
# for an average arrival rate of 1 / period
934
prob = 1.0 / period / options.binomial
935
for _ in range(options.binomial):
936
if random.random() < prob:
937
try:
938
origin, destination, intermediate = generate_origin_destination(
939
trip_generator, options)
940
idx = generate_one(idx, time, arrivalTime, period,
941
origin, destination, intermediate)
942
except Exception as exc:
943
print(exc, file=sys.stderr)
944
time += 1.0
945
else:
946
try:
947
origins_destinations = [generate_origin_destination(
948
trip_generator, options) for _ in range(options.flows)]
949
for i in range(len(times)-1):
950
for j in range(options.flows):
951
departureTime = times[i]
952
arrivalTime = times[i+1]
953
period = options.period[i]
954
if rerunFactor is not None:
955
period /= rerunFactor
956
if period == 0.0:
957
continue
958
origin, destination, intermediate = origins_destinations[j]
959
generate_one(j, departureTime, arrivalTime, period, origin, destination, intermediate, i)
960
except Exception as exc:
961
print(exc, file=sys.stderr)
962
963
fouttrips.write("</routes>\n")
964
965
# call duarouter for routes or validated trips
966
args = ['-n', options.netfile, '-r', options.tripfile, '--ignore-errors',
967
'--begin', str(options.begin), '--end', str(options.end),
968
'--no-warnings',
969
'--no-step-log']
970
if options.additional is not None:
971
args += ['--additional-files', options.additional]
972
if options.remove_loops:
973
args += ['--remove-loops']
974
if options.vtypeout is not None:
975
args += ['--vtype-output', options.vtypeout]
976
if options.junctionTaz:
977
args += ['--junction-taz']
978
if options.verbose:
979
args += ['-v']
980
if options.errorlog:
981
args += ['--error-log', options.errorlog]
982
983
duargs = [DUAROUTER, '--alternatives-output', 'NUL'] + args
984
maargs = [MAROUTER] + args
985
986
if options.carWalkMode is not None:
987
duargs += ['--persontrip.transfer.car-walk', options.carWalkMode]
988
if options.walkfactor is not None:
989
duargs += ['--persontrip.walkfactor', str(options.walkfactor)]
990
if options.walkoppositefactor is not None:
991
duargs += ['--persontrip.walk-opposite-factor', str(options.walkoppositefactor)]
992
if options.randomRoutingFactor != 1:
993
duargs += ['--weights.random-factor', str(options.randomRoutingFactor)]
994
995
options_to_forward = sumolib.options.get_prefixed_options(options)
996
for router, routerargs in [('duarouter', duargs), ('marouter', maargs)]:
997
if router in options_to_forward:
998
for option in options_to_forward[router]:
999
if not option[0].startswith('--'):
1000
option[0] = '--' + option[0]
1001
if option[0] not in routerargs:
1002
routerargs += option
1003
else:
1004
raise ValueError("The argument '%s' has already been passed without the %s prefix." % (
1005
option[0], router))
1006
1007
redirect = None if options.verbose else subprocess.DEVNULL
1008
1009
if options.routefile and rerunFactor is None:
1010
args2 = (maargs if options.marouter else duargs)[:]
1011
args2 += ['-o', options.routefile]
1012
if options.verbose:
1013
print("calling", " ".join(args2))
1014
sys.stdout.flush()
1015
subprocess.call(args2, stdout=redirect)
1016
sys.stdout.flush()
1017
sumolib.xml.insertOptionsHeader(options.routefile, options)
1018
1019
if options.validate and not skipValidation:
1020
# write to temporary file because the input is read incrementally
1021
tmpTrips = options.tripfile + ".tmp"
1022
args2 = duargs + ['-o', tmpTrips, '--write-trips']
1023
if options.junctionTaz:
1024
args2 += ['--write-trips.junctions']
1025
if options.verbose:
1026
print("calling", " ".join(args2))
1027
sys.stdout.flush()
1028
subprocess.call(args2, stdout=redirect)
1029
sys.stdout.flush()
1030
1031
validLabels = set([t.id for t in sumolib.xml.parse_fast(tmpTrips, getElement(options), ['id'])])
1032
validatedTrips = [(o, d, i) for (label, o, d, i) in generatedTrips if label in validLabels]
1033
replaceWithTmp = False
1034
1035
if rerunFactor is None:
1036
nRequested = idx
1037
nValid = len(validLabels)
1038
if nRequested > 0 and nValid < nRequested:
1039
successRate = nValid / nRequested
1040
if successRate < options.minSuccessRate:
1041
print("Warning: Only %s out of %s requested %ss passed validation. "
1042
"Set option --error-log for more details on the failure. "
1043
"Set option --min-success-rate to find more valid trips." %
1044
(nValid, nRequested, getElement(options)), file=sys.stderr)
1045
replaceWithTmp = True
1046
else:
1047
os.remove(tmpTrips)
1048
if options.verbose:
1049
print("Only %s out of %s requested %ss passed validation. Sampling again to find more." % (
1050
nValid, nRequested, getElement(options)))
1051
1052
# 1. call the current trip_generator again to generate more valid origin-destination pairs
1053
validatedTrips2 = createTrips(options, trip_generator, 1.2 / successRate - 1)
1054
# 2. reconfigure trip_generator to only output valid pairs
1055
trip_generator2 = CachedTripGenerator(validatedTrips + validatedTrips2)
1056
# 3. call trip_generator again to output the desired number of trips
1057
return createTrips(options, trip_generator2, skipValidation=True)
1058
1059
if replaceWithTmp:
1060
os.remove(options.tripfile)
1061
os.rename(tmpTrips, options.tripfile)
1062
else:
1063
os.remove(tmpTrips)
1064
1065
return validatedTrips
1066
1067
1068
if __name__ == "__main__":
1069
try:
1070
if not main(get_options()):
1071
print("Error: Trips couldn't be generated as requested. ", file=sys.stderr)
1072
sys.exit(1)
1073
except ValueError as e:
1074
print("Error:", e, file=sys.stderr)
1075
sys.exit(1)
1076
1077