Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/net/remap_additionals.py
169673 views
1
#!/usr/bin/env python
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2009-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 remap_additionals.py
15
# @author Jakob Erdmann
16
# @date 2025-02-19
17
18
from __future__ import print_function
19
from __future__ import absolute_import
20
import os
21
import sys
22
from math import fabs, degrees
23
24
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
25
import sumolib # noqa
26
from sumolib.geomhelper import distance # noqa
27
from sumolib.xml import parse # noqa
28
from sumolib.net import lane2edge # noqa
29
import sumolib.geomhelper as gh # noqa
30
31
32
def get_options(args=None):
33
ap = sumolib.options.ArgumentParser(description="Remap infrastructure from one network to another")
34
ap.add_argument("--orig-net", dest="origNet", required=True, category="input", type=ap.net_file,
35
help="SUMO network for loading infrastructure", metavar="FILE")
36
ap.add_argument("--target-net", dest="targetNet", required=True, category="input", type=ap.net_file,
37
help="SUMO network for writing infrastructure", metavar="FILE")
38
ap.add_argument("-a", "--additional-file", dest="additional", required=True, category="input",
39
type=ap.additional_file,
40
help="File for reading infrastructure", metavar="FILE")
41
ap.add_argument("-o", "--output-file", dest="output", required=True, category="output", type=ap.additional_file,
42
help="File for writing infrastructure", metavar="FILE")
43
ap.add_argument("--radius", type=float, default=20,
44
help="radius for finding candidate edges")
45
ap.add_argument("--shapecut", type=float, default=40,
46
help="Shorten polygon and edge shapes to FLOAT to increase robustness of angular comparison")
47
ap.add_argument("--angle-tolerance", type=float, default=20, dest="atol",
48
help="Match polygons and edges if their angle differs by no more than DEGRESS")
49
ap.add_argument("-v", "--verbose", action="store_true", dest="verbose",
50
default=False, help="tell me what you are doing")
51
options = ap.parse_args()
52
return options
53
54
55
def make_consecutive(net, laneIDs):
56
lanes = [net.getLane(laneID) for laneID in laneIDs]
57
lanes2 = []
58
for i, lane in enumerate(lanes[:-1]):
59
lanes2.append(lane)
60
lane2 = lanes[i + 1]
61
nTo = lane.getEdge().getToNode()
62
nFrom = lane2.getEdge().getFromNode()
63
if nTo != nFrom:
64
path, cost = net.getShortestPath(lane.getEdge(), lane2.getEdge())
65
if path:
66
for edge in path[1:-1]:
67
index = min(edge.getLaneNumber() - 1, lane.getIndex())
68
lanes2.append(edge.getLanes()[index])
69
lanes2.append(lanes[-1])
70
71
return [lane.getID() for lane in lanes2]
72
73
74
def remap_lanes(options, obj, laneIDs, pos=None):
75
laneIDs_positions2 = [remap_lane(options, obj, laneID, pos) for laneID in laneIDs.split()]
76
laneIDs2 = [la for la, pos in laneIDs_positions2]
77
if obj.name in ["e2Detector", "laneAreaDetector"]:
78
laneIDs2 = make_consecutive(options.net2, laneIDs2)
79
pos2 = laneIDs_positions2[0][1]
80
81
return ' '.join(laneIDs2), pos2
82
83
84
def remap_edges(options, obj, edgeIDs, pos=None):
85
edgeIDs_positions2 = [remap_edge(options, obj, edgeID, pos) for edgeID in edgeIDs.split()]
86
edgeIDs2 = [e for e, pos in edgeIDs_positions2]
87
pos2 = edgeIDs_positions2[0][1]
88
return ' '.join(edgeIDs2), pos2
89
90
91
def remap_lane(options, obj, laneID, pos=None):
92
lane = options.net.getLane(laneID)
93
lane2 = None
94
edge = lane.getEdge()
95
edge2, pos2 = remap_edge(options, obj, edge.getID(), pos)
96
if edge2:
97
cands = [c for c in edge.getLanes() if c.getPermissions() == lane.getPermissions()]
98
candIndex = cands.index(lane)
99
100
cands2 = [c for c in edge2.getLanes() if c.getPermissions() == lane.getPermissions()]
101
if not cands2 and lane.allows("passenger"):
102
cands2 = [c for c in edge2.getLanes() if c.allows("passenger")]
103
if not cands2 and lane.allows("bus"):
104
cands2 = [c for c in edge2.getLanes() if c.allows("bus")]
105
if not cands2 and lane.allows("taxi"):
106
cands2 = [c for c in edge2.getLanes() if c.allows("taxi")]
107
if not cands2 and lane.allows("bicycle"):
108
cands2 = [c for c in edge2.getLanes() if c.allows("bicycle")]
109
if not cands2:
110
cands2 = edge2.getLanes()
111
lane2 = cands2[min(candIndex, len(cands2) - 1)].getID()
112
return lane2, pos2
113
114
115
def remap_edge(options, obj, edgeID, pos=None):
116
edge = options.net.getEdge(edgeID)
117
permissions = set(edge.getPermissions())
118
shape = edge.getShape()
119
shapelen = gh.polyLength(shape)
120
relpos = pos / edge.getLength() if pos else 0.5
121
if relpos < 0:
122
relpos += 1
123
x, y = gh.positionAtShapeOffset(shape, shapelen * relpos)
124
x2, y2 = options.remap_xy((x, y))
125
126
edges = options.net2.getNeighboringEdges(x2, y2, options.radius)
127
if not edges:
128
print("No edges near %.2f,%.2f (origEdge %s)" % (x, y, edgeID), file=sys.stderr)
129
return None, None
130
131
scut = options.shapecut
132
if shapelen < scut:
133
origAngle = gh.angleTo2D(shape[0], shape[-1])
134
else:
135
origAngle = gh.angleTo2D(gh.positionAtShapeOffset(shape, shapelen * relpos - scut / 2),
136
gh.positionAtShapeOffset(shape, shapelen * relpos + scut / 2))
137
cands = []
138
for e, d in edges:
139
if e.getLength() < scut:
140
angle = gh.angleTo2D(e.getFromNode().getCoord(), e.getToNode().getCoord())
141
else:
142
eShape = e.getShape()
143
offset = gh.polygonOffsetWithMinimumDistanceToPoint((x2, y2), eShape)
144
offset1 = max(0, offset - scut / 2)
145
offset2 = min(gh.polyLength(eShape), offset + scut / 2)
146
angle = gh.angleTo2D(gh.positionAtShapeOffset(eShape, offset1),
147
gh.positionAtShapeOffset(eShape, offset2))
148
if degrees(fabs(origAngle - angle)) < options.atol:
149
cands.append(e)
150
edges = cands
151
cands2 = [e for e in edges if permissions.issubset(e.getPermissions())]
152
if not cands2:
153
# relax permission requirements a minimum
154
for svc in ["passenger", "bus", "bicycle" "tram", "rail", "pedestrian"]:
155
if svc in permissions:
156
permissions = set([svc])
157
break
158
cands2 = [e for e in edges if permissions.issubset(e.getPermissions())]
159
if not cands2:
160
print("No edges that allow '%s' found near %.2f,%.2f (origEdge %s)" % (
161
" ".join(permissions), x2, y2, edgeID), file=sys.stderr)
162
return None, None
163
edges = cands2
164
165
if not edges:
166
print("No edges with angle %.2f found near %.2f,%.2f (origEdge %s)" % (
167
degrees(origAngle), x2, y2, edgeID), file=sys.stderr)
168
return None, None
169
170
shape2 = list(map(options.remap_xy, shape))
171
edge2 = None
172
bestDist = 1e10
173
for e in edges:
174
if shapelen > e.getLength():
175
maxDist = max([gh.distancePointToPolygon(xy, shape2) for xy in e.getShape()])
176
else:
177
maxDist = max([gh.distancePointToPolygon(xy, e.getShape()) for xy in shape2])
178
if maxDist < bestDist:
179
bestDist = maxDist
180
edge2 = e
181
182
if pos is not None:
183
edge2shapelen = gh.polyLength(edge2.getShape())
184
pos2 = gh.polygonOffsetWithMinimumDistanceToPoint((x2, y2), edge2.getShape())
185
pos2 = pos2 / edge2shapelen * edge2.getLength()
186
187
return edge2, pos2
188
189
190
POSITIONS = ['startPos', 'endPos', 'pos']
191
192
193
def getPosAttrs(obj):
194
result = []
195
for attr in POSITIONS:
196
if obj.hasAttribute(attr):
197
result.append((attr, float(getattr(obj, attr))))
198
return result
199
200
201
IDS = {
202
'edge': remap_edge,
203
'lane': remap_lane,
204
'edges': remap_edges,
205
'lanes': remap_lanes,
206
}
207
208
209
def remap(options, obj, level=1):
210
success = True
211
for attr, mapper in IDS.items():
212
if obj.hasAttribute(attr):
213
posAttrs = getPosAttrs(obj)
214
if len(posAttrs) == 0:
215
pos = None
216
else:
217
pos = posAttrs[0][1]
218
obj.setAttribute("friendlyPos", True)
219
id2, pos2 = mapper(options, obj, getattr(obj, attr), pos)
220
if id2:
221
obj.setAttribute(attr, id2)
222
for posAttr, posOrig in posAttrs:
223
obj.setAttribute(posAttr, pos2 + posOrig - pos)
224
else:
225
print("Could not map %s on %s '%s'" % (
226
obj.name, attr, getattr(obj, attr)),
227
file=sys.stderr)
228
if level == 1:
229
success = False
230
else:
231
obj.setCommented()
232
for child in obj.getChildList():
233
success &= remap(options, child, level + 1)
234
patchSpecialCases(options, obj, level)
235
return success
236
237
238
def patchSpecialCases(options, obj, level):
239
if level == 1:
240
accessEdges = set()
241
for child in obj.getChildList():
242
if child.name == "access" and not child.isCommented():
243
edge = lane2edge(child.lane)
244
if edge in accessEdges:
245
print("Disabling duplicate access on edge %s" % edge, file=sys.stderr)
246
child.setCommented()
247
else:
248
accessEdges.add(edge)
249
lane = options.net2.getLane(child.lane)
250
if not lane.allows("pedestrian"):
251
found = False
252
for cand in lane.getEdge().getLanes():
253
if cand.allows("pedestrian"):
254
child.lane = cand.getID()
255
found = True
256
break
257
if not found:
258
print("Disabling access on non-pedestrian lane %s" % child.lane, file=sys.stderr)
259
child.setCommented()
260
261
262
def main(options):
263
if options.verbose:
264
print("Reading orig-net '%s'" % options.origNet)
265
options.net = sumolib.net.readNet(options.origNet)
266
if options.verbose:
267
print("Reading target-net '%s'" % options.targetNet)
268
options.net2 = sumolib.net.readNet(options.targetNet)
269
270
if options.net.hasGeoProj() and options.net2.hasGeoProj():
271
def remap_xy(xy):
272
lon, lat = options.net.convertXY2LonLat(*xy)
273
return options.net2.convertLonLat2XY(lon, lat)
274
options.remap_xy = remap_xy
275
else:
276
options.remap_xy = lambda x: x
277
278
with open(options.output, 'w') as fout:
279
sumolib.writeXMLHeader(fout, "$Id$", "additional", options=options)
280
for obj in parse(options.additional):
281
if not remap(options, obj):
282
obj.setCommented()
283
fout.write(obj.toXML(initialIndent=" " * 4))
284
fout.write("</additional>\n")
285
286
287
if __name__ == "__main__":
288
main(get_options())
289
290