Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/route/route2poly.py
169674 views
1
#!/usr/bin/env python
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2012-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 route2poly.py
15
# @author Jakob Erdmann
16
# @author Michael Behrisch
17
# @date 2012-11-15
18
19
"""
20
From a sumo network and a route file, this script generates a polygon (polyline) for every route
21
which can be loaded with sumo-gui for visualization
22
"""
23
from __future__ import absolute_import
24
import sys
25
import os
26
import itertools
27
import random
28
from collections import defaultdict
29
sys.path.append(os.path.join(os.environ['SUMO_HOME'], 'tools'))
30
from sumolib.output import parse # noqa
31
from sumolib.net import readNet # noqa
32
from sumolib.miscutils import Colorgen # noqa
33
from sumolib import geomhelper # noqa
34
from sumolib.options import ArgumentParser # noqa
35
36
37
def parse_args(args):
38
ap = ArgumentParser(description="generates a polygon for every loaded route")
39
ap.add_option("net", category="input", type=ap.net_file,
40
help="input net file")
41
ap.add_option("routefiles", nargs="+", category="input", type=ap.file_list,
42
help="input route files")
43
ap.add_option("-o", "--outfile", category="output", type=ap.file,
44
help="name of output file")
45
ap.add_option("-u", "--hue", default="random",
46
help="hue for polygons (float from [0,1] or 'random')")
47
ap.add_option("-s", "--saturation", default=1,
48
help="saturation for polygons (float from [0,1] or 'random')")
49
ap.add_option("-b", "--brightness", default=1,
50
help="brightness for polygons (float from [0,1] or 'random')")
51
ap.add_option("-l", "--layer", default=100,
52
help="layer for generated polygons")
53
ap.add_option("--geo", action="store_true", default=False,
54
help="write polygons with geo-coordinates")
55
ap.add_option("--internal", action="store_true", default=False,
56
help="include internal edges in generated shapes")
57
ap.add_option("--spread", type=float,
58
help="spread polygons laterally to avoid overlap")
59
ap.add_option("--blur", type=float, default=0,
60
help="maximum random disturbance to route geometry")
61
ap.add_option("--scale-width", type=float, dest="scaleWidth",
62
help=("group similar routes and scale width by group size or route probablity," +
63
"multiplied with the given factor (in m)"))
64
ap.add_option("--filter-count", type=float, dest="filterCount",
65
help="only include routes that occur at least INT times")
66
ap.add_option("--standalone", action="store_true", default=False,
67
help="Parse stand-alone routes that are not define as child-element of a vehicle")
68
ap.add_option("--filter-output.file", dest="filterOutputFile",
69
help="only write output for edges in the given selection file")
70
ap.add_option("--seed", type=int, help="random seed")
71
options = ap.parse_args(args=args)
72
if options.seed:
73
random.seed(options.seed)
74
options.colorgen = Colorgen((options.hue, options.saturation, options.brightness))
75
if options.outfile is None:
76
options.outfile = options.routefiles[0] + ".poly.xml"
77
78
return options
79
80
81
def randomize_pos(pos, blur):
82
return tuple([val + random.uniform(-blur, blur) for val in pos])
83
84
85
MISSING_EDGES = set()
86
SPREAD = defaultdict(set)
87
SPREAD_MAX = [0]
88
89
90
def getSpread(lanes, positive=False):
91
"""find the smallest spread value that is available for all lanes"""
92
cands = []
93
if not positive:
94
cands.append(0)
95
for i in range(1, SPREAD_MAX[0] + 2):
96
cands.append(i)
97
if not positive:
98
cands.append(-i)
99
100
for i in cands:
101
if all([i not in SPREAD[lane] for lane in lanes]):
102
SPREAD_MAX[0] = max(SPREAD_MAX[0], i)
103
for lane in lanes:
104
SPREAD[lane].add(i)
105
return i
106
else:
107
pass
108
# print(i, [l.getID() for l in lanes])
109
assert False
110
111
112
def hasBidi(lanes):
113
for lane in lanes:
114
if lane.getEdge().getBidi():
115
return True
116
return False
117
118
119
def generate_poly(options, net, id, color, edges, outf, type="route", lineWidth=None, params=None):
120
if params is None:
121
params = {}
122
lanes = []
123
spread = 0
124
for e in edges:
125
if net.hasEdge(e):
126
lanes.append(net.getEdge(e).getLane(0))
127
else:
128
if e not in MISSING_EDGES:
129
sys.stderr.write("Warning: unknown edge '%s'\n" % e)
130
MISSING_EDGES.add(e)
131
if not lanes:
132
return
133
if options.internal and len(lanes) > 1:
134
lanes2 = []
135
preferedCon = -1
136
for i, lane in enumerate(lanes):
137
edge = lane.getEdge()
138
if i == 0:
139
cons = edge.getConnections(lanes[i + 1].getEdge())
140
if cons:
141
lanes2.append(cons[preferedCon].getFromLane())
142
else:
143
lanes2.append(lane)
144
else:
145
cons = lanes[i - 1].getEdge().getConnections(edge)
146
if cons:
147
viaID = cons[preferedCon].getViaLaneID()
148
if viaID:
149
via = net.getLane(viaID)
150
lanes2.append(via)
151
cons2 = via.getEdge().getConnections(edge)
152
if cons2:
153
viaID2 = cons2[preferedCon].getViaLaneID()
154
if viaID2:
155
via2 = net.getLane(viaID2)
156
lanes2.append(via2)
157
lanes2.append(cons[preferedCon].getToLane())
158
else:
159
lanes2.append(lane)
160
lanes = lanes2
161
162
shape = list(itertools.chain(*list(lane.getShape() for lane in lanes)))
163
if options.spread:
164
spread = getSpread(lanes, hasBidi(lanes))
165
if spread:
166
shape = geomhelper.move2side(shape, options.spread * spread)
167
params["spread"] = str(spread)
168
if options.blur > 0:
169
shape = [randomize_pos(pos, options.blur) for pos in shape]
170
171
geoFlag = ""
172
lineWidth = '' if lineWidth is None else ' lineWidth="%s"' % lineWidth
173
if options.geo:
174
shape = [net.convertXY2LonLat(*pos) for pos in shape]
175
geoFlag = ' geo="true"'
176
shapeString = ' '.join('%s,%s' % (x, y) for x, y in shape)
177
close = '/'
178
if params:
179
close = ''
180
outf.write('<poly id="%s" color="%s" layer="%s" type="%s" shape="%s"%s%s%s>\n' % (
181
id, color, options.layer, type, shapeString, geoFlag, lineWidth, close))
182
if params:
183
for key, value in params.items():
184
outf.write(' <param key="%s" value="%s"/>\n' % (key, value))
185
outf.write('</poly>\n')
186
187
188
def filterEdges(edges, keep):
189
if keep is None:
190
return edges
191
else:
192
return [e for e in edges if e in keep]
193
194
195
def parseRoutes(options):
196
known_ids = set()
197
198
def unique_id(cand, index=0):
199
cand2 = cand
200
if index > 0:
201
cand2 = "%s#%s" % (cand, index)
202
if cand2 in known_ids:
203
return unique_id(cand, index + 1)
204
else:
205
known_ids.add(cand2)
206
return cand2
207
208
keep = None
209
if options.filterOutputFile is not None:
210
keep = set()
211
with open(options.filterOutputFile) as filterOutput:
212
for line in filterOutput:
213
if line.startswith('edge:'):
214
keep.add(line.replace('edge:', '').strip())
215
216
for routefile in options.routefiles:
217
print("parsing %s" % routefile)
218
if options.standalone:
219
for route in parse(routefile, 'route'):
220
# print("found veh", vehicle.id)
221
yield unique_id(route.id), filterEdges(route.edges.split(), keep), route.probability
222
else:
223
for vehicle in parse(routefile, 'vehicle'):
224
# print("found veh", vehicle.id)
225
yield unique_id(vehicle.id), filterEdges(vehicle.route[0].edges.split(), keep), None
226
227
228
def main(args):
229
options = parse_args(args)
230
net = readNet(options.net, withInternal=options.internal)
231
232
with open(options.outfile, 'w') as outf:
233
outf.write('<polygons>\n')
234
if options.scaleWidth is None:
235
for route_id, edges, _ in parseRoutes(options):
236
generate_poly(options, net, route_id, options.colorgen(), edges, outf)
237
else:
238
count = {}
239
for route_id, edges, rCount in parseRoutes(options):
240
edges = tuple(edges)
241
rCount = 1 if rCount is None else float(rCount)
242
if edges in count:
243
count[edges][0] += rCount
244
else:
245
count[edges] = [rCount, route_id]
246
247
countItems = [(-n, route_id, edges) for edges, (n, route_id) in count.items()]
248
for nNeg, route_id, edges in sorted(countItems):
249
n = -nNeg
250
if options.filterCount and n < options.filterCount:
251
continue
252
width = options.scaleWidth * n
253
params = {'count': str(n)}
254
generate_poly(options, net, route_id, options.colorgen(), edges, outf, lineWidth=width, params=params)
255
256
outf.write('</polygons>\n')
257
258
259
if __name__ == "__main__":
260
main(sys.argv[1:])
261
262