Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/net/abstractRail.py
169673 views
1
#!/usr/bin/env python
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2007-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 abstractRail.py
15
# @author Jakob Erdmann
16
# @author Mirko Barthauer
17
# @date 2023-02-22
18
19
"""
20
Convert a geodetical rail network in a abstract (schematic) rail network.
21
If the network is segmented (stationDistricts.py), the resulting network will be
22
a hybrid of multiple schematic pieces being oriented in a roughly geodetical manner
23
"""
24
from __future__ import absolute_import
25
from __future__ import print_function
26
import os
27
import sys
28
import subprocess
29
import numpy as np
30
import math
31
import time
32
import scipy.optimize as opt
33
from collections import defaultdict
34
SUMO_HOME = os.environ.get('SUMO_HOME',
35
os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..'))
36
sys.path.append(os.path.join(SUMO_HOME, 'tools'))
37
import sumolib # noqa
38
from sumolib.options import ArgumentParser # noqa
39
from sumolib.miscutils import Colorgen # noqa
40
import sumolib.geomhelper as gh # noqa
41
42
try:
43
sys.stdout.reconfigure(encoding='utf-8')
44
except: # noqa
45
pass
46
47
INTERSECT_RANGE = 1e5
48
COMPRESSION_WEIGHT = 0.01
49
STRAIGHT_WEIGHT = 2
50
OTHER_WEIGHT = 1
51
52
NETCONVERT = sumolib.checkBinary('netconvert')
53
STATION_DISTRICTS = os.path.join(SUMO_HOME, 'tools', 'district', 'stationDistricts.py')
54
55
56
def get_options():
57
ap = ArgumentParser()
58
ap.add_argument("-v", "--verbose", action="store_true", default=False,
59
help="tell me what you are doing")
60
ap.add_argument("-n", "--net-file", category="input", dest="netfile", required=True, type=ap.net_file,
61
help="the network to read lane and edge permissions")
62
ap.add_argument("-s", "--stop-file", category="input", dest="stopfile", required=True, type=ap.additional_file,
63
help="the additional file with stops")
64
ap.add_argument("-a", "--region-file", category="input", dest="regionfile", type=ap.additional_file,
65
help="Load network regions from additional file (as taz elements)")
66
ap.add_argument("-o", "--output-prefix", category="output", dest="prefix", required=True, type=ap.file,
67
help="output prefix for patch files")
68
ap.add_argument("--split", action="store_true", default=False,
69
help="automatically create a region file from the loaded stops,"
70
+ " automatically split the network if needed")
71
ap.add_argument("--filter-regions", dest="filterRegions",
72
help="filter regions by name or id")
73
ap.add_argument("--main-stops", dest="mainStops",
74
help="determine main direction from stops names or ids")
75
ap.add_argument("--keep-all", action="store_true", dest="keepAll", default=False,
76
help="keep original regions outside the filtered regions")
77
ap.add_argument("--horizontal", action="store_true", dest="horizontal", default=False,
78
help="output shapes roughly aligned along the horizontal")
79
ap.add_argument("--track-offset", type=float, default=20, dest="trackOffset",
80
help="default distance between parallel tracks")
81
ap.add_argument("--track-length", type=float, default=20, dest="trackLength",
82
help="maximum length of track pieces")
83
ap.add_argument("--time-limit", type=float, dest="timeLimit",
84
help="time limit per region")
85
ap.add_argument("--max-iter", type=int, dest="maxIter",
86
help="maximum number of solver iterations per region")
87
ap.add_argument("--skip-large", type=int, dest="skipLarge",
88
help="skip regions require more than the given number of constraints")
89
ap.add_argument("--skip-yopt", action="store_true", dest="skipYOpt", default=False,
90
help="do not optimize the track offsets")
91
ap.add_argument("--skip-building", action="store_true", dest="skipBuilding", default=False,
92
help="do not call netconvert with the patch files")
93
ap.add_argument("--extra-verbose", action="store_true", default=False, dest="verbose2",
94
help="tell me more about what you are doing")
95
options = ap.parse_args()
96
97
if options.regionfile and options.split:
98
ap.print_help()
99
ap.exit("Error! Only one of the options --split or --region-file may be given")
100
101
if options.mainStops:
102
options.mainStops = set(options.mainStops.split(','))
103
104
options.output_nodes = options.prefix + ".nod.xml"
105
options.output_edges = options.prefix + ".edg.xml"
106
options.output_net = options.prefix + ".net.xml"
107
108
options.filterRegions = set(options.filterRegions.split(",")) if options.filterRegions else set()
109
110
return options
111
112
113
def loadRegions(options, net):
114
regions = dict()
115
if options.regionfile:
116
for taz in sumolib.xml.parse(options.regionfile, 'taz'):
117
name = taz.attr_name
118
if (options.filterRegions
119
and name not in options.filterRegions
120
and taz.id not in options.filterRegions):
121
continue
122
edgeIDs = set()
123
if taz.edges is not None:
124
edgeIDs.update(taz.edges.split())
125
else:
126
edgeIDs.update([tazSink.id for tazSink in taz.tazSink])
127
edgeIDs.update([tazSource.id for tazSource in taz.tazSource])
128
regions[name] = [net.getEdge(e) for e in edgeIDs if net.hasEdge(e)]
129
else:
130
regions['ALL'] = list(net.getEdges())
131
return regions
132
133
134
def filterBidi(edges):
135
return [e for e in edges if e.getBidi() is None or e.getID() < e.getBidi().getID()]
136
137
138
def initShapes(edges, nodeCoords, edgeShapes):
139
nodes = getNodes(edges)
140
141
for node in nodes:
142
nodeCoords[node.getID()] = node.getCoord()
143
144
for edge in edges:
145
edgeShapes[edge.getID()] = edge.getShape(True)
146
147
148
def getStops(stopfile, net):
149
stops = dict() # edgeID -> (stopID, stopName, start, end)
150
for stop in sumolib.xml.parse(stopfile, ['busStop', 'trainStop']):
151
edgeID = stop.lane.rsplit('_', 1)[0]
152
if not net.hasEdge(edgeID):
153
sys.stderr.write("Warning: Unknown stop edge '%s'" % edgeID)
154
continue
155
edge = net.getEdge(edgeID)
156
startPos = float(stop.startPos)
157
endPos = float(stop.endPos)
158
if startPos < 0:
159
startPos += edge.getLength()
160
if startPos < 0:
161
startPos = 0
162
if startPos >= edge.getLength():
163
startPos = edge.getLength()
164
if endPos < 0:
165
endPos += edge.getLength()
166
if endPos < 0:
167
endPos = 0
168
if endPos >= edge.getLength():
169
endPos = edge.getLength()
170
171
expectedLength = min(50, edge.getLength())
172
if endPos - startPos < expectedLength:
173
mid = (endPos + startPos) / 2
174
startPos = max(0, mid - expectedLength)
175
endPos = min(edge.getLength(), mid + expectedLength)
176
stops[edgeID] = (stop.id, stop.getAttributeSecure("attr_name", stop.id),
177
startPos, endPos)
178
return stops
179
180
181
def findMainline(options, name, net, edges, stops):
182
"""use platforms to determine mainline orientation"""
183
knownEdges = set([e.getID() for e in edges])
184
185
angles = []
186
for edgeID, (stopID, stopName, startPos, endPos) in stops.items():
187
if options.mainStops and stopID not in options.mainStops and stopName not in options.mainStops:
188
continue
189
if edgeID not in knownEdges:
190
continue
191
edge = net.getEdge(edgeID)
192
begCoord = gh.positionAtShapeOffset(edge.getShape(), startPos)
193
endCoord = gh.positionAtShapeOffset(edge.getShape(), endPos)
194
assert (startPos != endPos)
195
assert (begCoord != endCoord)
196
angle = gh.angleTo2D(begCoord, endCoord)
197
if 180 < gh.naviDegree(angle) <= 360:
198
angle -= math.pi
199
begCoord, endCoord = endCoord, begCoord
200
angles.append((angle, (begCoord, endCoord)))
201
202
if not angles:
203
print("Warning: No stops loaded for region '%s'. Using median edge angle instead" % name, file=sys.stderr)
204
for edge in edges:
205
begCoord = edge.getShape()[0]
206
endCoord = edge.getShape()[-1]
207
angles.append((gh.angleTo2D(begCoord, endCoord), (begCoord, endCoord)))
208
209
angles.sort()
210
mainLine = angles[int(len(angles) / 2)][1]
211
212
if options.verbose2:
213
print("mainLine=", shapeStr(mainLine))
214
215
return mainLine
216
217
218
def getNodes(edges):
219
nodes = set()
220
for edge in edges:
221
nodes.add(edge.getFromNode())
222
nodes.add(edge.getToNode())
223
return nodes
224
225
226
def rotateByMainLine(mainLine, edges, nodeCoords, edgeShapes, reverse,
227
horizontal=False, multiRegions=False):
228
center = mainLine[0]
229
angle = gh.angleTo2D(mainLine[0], mainLine[1])
230
nodes = getNodes(edges)
231
232
if reverse:
233
def transform(coord):
234
if not horizontal:
235
coord = gh.rotateAround2D(coord, angle, (0, 0))
236
if multiRegions or not horizontal:
237
coord = gh.add(coord, center)
238
return coord
239
else:
240
def transform(coord):
241
coord = gh.sub(coord, center)
242
coord = gh.rotateAround2D(coord, -angle, (0, 0))
243
return coord
244
245
for node in nodes:
246
coord = nodeCoords[node.getID()]
247
nodeCoords[node.getID()] = transform(coord)
248
249
for edge in edges:
250
shape = edgeShapes[edge.getID()]
251
edgeShapes[edge.getID()] = [transform(coord) for coord in shape]
252
253
254
def getEdgeOrdering(edgeIDs, ordering, useOutgoing):
255
result = []
256
for vN in ordering:
257
if isinstance(vN, sumolib.net.edge.Edge):
258
result.append(vN.getID())
259
else:
260
edges = vN.getOutgoing() if useOutgoing else vN.getIncoming()
261
# filter bidi
262
edges = [e.getID() for e in edges if e.getID() in edgeIDs]
263
if len(edges) == 1:
264
result.append(edges[0])
265
else:
266
result.append(None)
267
return result
268
269
270
def differentOrderings(edgeIDs, o1, o2):
271
"""
272
check whether two orderings are different
273
"""
274
if o2 is None or len(o1) != len(o2):
275
return True
276
277
# convert node to edge (two possibilities for each ordering)
278
o1a = getEdgeOrdering(edgeIDs, o1, True)
279
o1b = getEdgeOrdering(edgeIDs, o1, False)
280
o2a = getEdgeOrdering(edgeIDs, o2, True)
281
o2b = getEdgeOrdering(edgeIDs, o2, False)
282
283
for o1x in [o1a, o1b]:
284
for o2x in [o2a, o2b]:
285
if o1x == o2x:
286
return False
287
return True
288
289
290
def computeTrackOrdering(options, mainLine, edges, nodeCoords, edgeShapes, region):
291
"""
292
precondition: network is rotated so that the mainLine is on the horizontal
293
for each x-value we imagine a vertical line and find all the edges that intersect
294
this gives a list of track orderings
295
Then we try to assign integers for each edge that are consistent with this ordering
296
"""
297
298
# step 1: find ordering constraints
299
orderings = []
300
nodes = getNodes(edges)
301
edgeIDs = set([e.getID() for e in edges])
302
xyNodes = [(nodeCoords[n.getID()], n) for n in nodes]
303
xyNodes.sort(key=lambda x: x[0][0])
304
305
prevOrdering = None
306
sameOrdering = []
307
for (x, y), node in xyNodes:
308
node._newX = x
309
vertical = [(x, -INTERSECT_RANGE), (x, INTERSECT_RANGE)]
310
ordering = []
311
for edge in edges:
312
if edge.getFromNode() == node or edge.getToNode() == node:
313
continue
314
shape = edgeShapes[edge.getID()]
315
intersects = gh.intersectsAtLengths2D(vertical, shape)
316
intersects = [i - INTERSECT_RANGE for i in intersects]
317
# intersects now holds y-values
318
if len(intersects) == 1:
319
ordering.append((intersects[0], edge))
320
elif len(intersects) > 1:
321
sys.stderr.write(("Cannot compute track ordering for edge '%s'" +
322
" because it runs orthogonal to the main line (intersects: %s)\n") % (
323
edge.getID(), intersects))
324
# print("vertical=%s %s=%s" % (shapeStr(vertical), edge.getID(), shapeStr(shape)))
325
326
if ordering:
327
# also append the actual node before sorting
328
ordering.append((y, node))
329
ordering.sort(key=lambda x: x[0])
330
ordering = [vn for y, vn in ordering]
331
332
if differentOrderings(edgeIDs, ordering, prevOrdering):
333
orderings.append((node.getID(), ordering))
334
prevOrdering = ordering
335
if options.verbose2:
336
print(x, list(map(lambda vn: vn.getID(), ordering)))
337
else:
338
sameOrdering.append((prevOrdering, ordering))
339
if options.verbose2:
340
print("sameOrdering:", prevOrdering, ordering)
341
342
# step 3:
343
nodeYValues = optimizeTrackOrder(options, edges, nodes, orderings, nodeCoords, region)
344
345
# step 4: apply yValues to virtual nodes that were skipped
346
if nodeYValues:
347
for prevOrdering, ordering in sameOrdering:
348
for n1, n2 in zip(prevOrdering, ordering):
349
nodeYValues[n2] = nodeYValues[n1]
350
351
if options.verbose2:
352
for k, v in nodeYValues.items():
353
print(k.getID(), v)
354
return nodeYValues
355
356
357
def optimizeTrackOrder(options, edges, nodes, orderings, nodeCoords, region):
358
constrainedEdges = set()
359
for _, ordering in orderings:
360
for vNode in ordering:
361
if isinstance(vNode, sumolib.net.edge.Edge):
362
constrainedEdges.add(vNode)
363
364
# every node and every edge is assigned a single y-values
365
generalizedNodes = list(nodes) + list(constrainedEdges)
366
generalizedNodes.sort(key=lambda n: n.getID())
367
nodeIndex = dict((n, i) for i, n in enumerate(generalizedNodes))
368
369
# collect ordering constraints for nodes and virtual nodes
370
yOrderConstraints = []
371
for nodeID, ordering in orderings:
372
for vNode, vNode2 in zip(ordering[:-1], ordering[1:]):
373
yOrderConstraints.append((nodeIndex[vNode], nodeIndex[vNode2]))
374
375
# collect constraints for keeping edges parallel
376
yPrios = []
377
ySimilarConstraints = []
378
for edge in edges:
379
angle = gh.angleTo2D(nodeCoords[edge.getFromNode().getID()], nodeCoords[edge.getToNode().getID()])
380
straight = min(abs(angle), abs(angle - math.pi)) < np.deg2rad(10)
381
if edge in constrainedEdges:
382
numConstraints = 2
383
ySimilarConstraints.append((nodeIndex[edge.getFromNode()], nodeIndex[edge]))
384
ySimilarConstraints.append((nodeIndex[edge.getToNode()], nodeIndex[edge]))
385
else:
386
numConstraints = 1
387
ySimilarConstraints.append((nodeIndex[edge.getFromNode()], nodeIndex[edge.getToNode()]))
388
yPrios += [2 if straight else 1] * numConstraints
389
390
k = len(generalizedNodes)
391
m = len(ySimilarConstraints)
392
m2 = m * 2
393
# n: number of variables
394
n = k + m
395
# q: number of equations:
396
# 2 per ySame constraint to minimize the absolute difference and one per elementary ordering constraint
397
q = m2 + len(yOrderConstraints)
398
399
# we use m slack variables for the differences between y-values (one per edge)
400
# x2 = [x, s]
401
402
b_ub = [0] * m2 + [-options.trackOffset] * len(yOrderConstraints)
403
A_ub = np.zeros((q, n))
404
405
row = 0
406
# encode inequalities for slack variables (minimize differences)
407
for slackI, (fromI, toI) in enumerate(ySimilarConstraints):
408
slackI += k
409
A_ub[row][fromI] = 1
410
A_ub[row][toI] = -1
411
A_ub[row][slackI] = -1
412
row += 1
413
A_ub[row][fromI] = -1
414
A_ub[row][toI] = 1
415
A_ub[row][slackI] = -1
416
row += 1
417
418
# encode inequalities for ordering
419
for index1, index2 in yOrderConstraints:
420
A_ub[row][index1] = 1
421
A_ub[row][index2] = -1
422
row += 1
423
424
# minimization objective (only minimize slack variables)
425
c = [COMPRESSION_WEIGHT] * k + yPrios
426
427
if options.verbose2:
428
print("k=%s" % k)
429
print("m=%s" % m)
430
print("q=%s" % q)
431
print("A_ub (%s) %s" % (A_ub.shape, A_ub))
432
print("b_ub (%s) %s" % (len(b_ub), b_ub))
433
print("c (%s) %s" % (len(c), c))
434
435
if options.skipLarge and q > options.skipLarge:
436
sys.stderr.write("Skipping optimization with %s inequalities\n" % q)
437
return dict()
438
439
linProgOpts = {}
440
started = time.time()
441
if options.verbose:
442
print("Starting optimization with %s inequalities" % q)
443
444
if options.verbose2:
445
linProgOpts["disp"] = True
446
447
if options.timeLimit:
448
linProgOpts["time_limit"] = options.timeLimit
449
450
if options.maxIter:
451
linProgOpts["maxiter"] = options.maxIter
452
453
res = opt.linprog(c, A_ub=A_ub, b_ub=b_ub, options=linProgOpts)
454
455
if not res.success:
456
sys.stderr.write("Optimization failed for region %s\n" % region)
457
return dict()
458
459
if options.verbose:
460
if options.verbose:
461
score = np.dot(res.x, c)
462
runtime = time.time() - started
463
print("Optimization succeeded after %ss (score=%s)" % (runtime, score))
464
if options.verbose2:
465
print(res.x)
466
yValues = res.x[:k] # cut of slack variables
467
# print(yValues)
468
469
nodeYValues = dict([(vNode, yValues[i]) for vNode, i in nodeIndex.items()])
470
return nodeYValues
471
472
473
def patchShapes(options, edges, nodeCoords, edgeShapes, nodeYValues):
474
nodes = getNodes(edges)
475
476
# patch node y-values
477
edgeYValues = defaultdict(list)
478
for node in nodes:
479
coord = nodeCoords[node.getID()]
480
nodeCoords[node.getID()] = (coord[0], nodeYValues[node])
481
482
# compute inner edge shape points (0 or 2 per edge)
483
for vNode, y in nodeYValues.items():
484
if vNode not in nodes:
485
fromID = vNode.getFromNode().getID()
486
toID = vNode.getToNode().getID()
487
x1, y1 = nodeCoords[fromID]
488
x2, y2 = nodeCoords[toID]
489
xDelta = x2 - x1
490
length = abs(xDelta)
491
yDelta1 = abs(y1 - y)
492
yDelta2 = abs(y2 - y)
493
xOffset1 = min(yDelta1, length / 2)
494
xOffset2 = min(yDelta2, length / 2)
495
sign = 1 if x2 > x1 else -1
496
edgeYValues[vNode.getID()].append((x1 + xOffset1 * sign, y))
497
edgeYValues[vNode.getID()].append((x2 - xOffset2 * sign, y))
498
499
# set edge shapes
500
for edge in edges:
501
edgeID = edge.getID()
502
fromCoord = nodeCoords[edge.getFromNode().getID()]
503
toCoord = nodeCoords[edge.getToNode().getID()]
504
shape = [fromCoord, toCoord]
505
if edgeID in edgeYValues:
506
for coord in edgeYValues[edgeID]:
507
shape.append(coord)
508
shape.sort()
509
if fromCoord[0] > toCoord[0]:
510
shape = list(reversed(shape))
511
edgeShapes[edgeID] = shape
512
513
514
def patchXValues(options, edges, nodeCoords, edgeShapes):
515
nodes = getNodes(edges)
516
xValues = set()
517
for node in nodes:
518
xValues.add(nodeCoords[node.getID()][0])
519
for edge in edges:
520
xValues.update([c[0] for c in edgeShapes[edge.getID()]])
521
522
xValues = sorted(list(xValues))
523
xValues2 = list(xValues)
524
baseRank = getIndexOfClosestToZero(xValues)
525
for i in range(len(xValues2) - 1):
526
dist = xValues2[i + 1] - xValues2[i]
527
if dist > options.trackLength:
528
for j in range(i + 1):
529
xValues2[j] += dist - options.trackLength
530
# shift the central point back to 0
531
shift = xValues2[baseRank]
532
xValues2 = [x - shift for x in xValues2]
533
xMap = dict(zip(xValues, xValues2))
534
535
for node in nodes:
536
coord = nodeCoords[node.getID()]
537
nodeCoords[node.getID()] = (xMap[coord[0]], coord[1])
538
for edge in edges:
539
shape = edgeShapes[edge.getID()]
540
edgeShapes[edge.getID()] = [(xMap[c[0]], c[1]) for c in shape]
541
542
543
def getIndexOfClosestToZero(values):
544
result = None
545
bestValue = 1e100
546
for i, v in enumerate(values):
547
if abs(v) < bestValue:
548
result = i
549
bestValue = abs(v)
550
return result
551
552
553
def cleanShapes(options, net, nodeCoords, edgeShapes, stops):
554
"""Ensure consistent edge shape in case the same edge was part of multiple regions"""
555
556
for edgeID, shape in edgeShapes.items():
557
edge = net.getEdge(edgeID)
558
fromID = edge.getFromNode().getID()
559
toID = edge.getToNode().getID()
560
if ((shape[0] != nodeCoords[fromID] or shape[-1] != nodeCoords[toID])
561
and options.horizontal and edgeID in stops and len(shape) == 2):
562
shape.insert(0, shape[0])
563
shape.append(shape[-1])
564
shape[0] = nodeCoords[fromID]
565
shape[-1] = nodeCoords[toID]
566
567
568
def shapeStr(shape):
569
return ' '.join(["%.2f,%.2f" % coord for coord in shape])
570
571
572
def splitNet(options):
573
# 1. create region file from stops
574
options.regionfile = options.prefix + ".taz.xml"
575
splitfile = options.prefix + ".split.edg.xml"
576
oldNet = options.netfile
577
oldStops = options.stopfile
578
579
if options.verbose:
580
print("Creating region file '%s'" % options.regionfile)
581
subprocess.call([sys.executable, STATION_DISTRICTS,
582
'-n', options.netfile,
583
'-s', options.stopfile,
584
'-o', options.regionfile,
585
'--split-output', splitfile])
586
587
# 2. optionally split the network if regions have shared edges
588
numSplits = len(list(sumolib.xml.parse(splitfile, 'edge')))
589
if numSplits > 0:
590
if options.verbose:
591
print("Splitting %s edges to ensure distinct regions" % numSplits)
592
593
# rebuilt the network and stop file
594
if options.netfile[-11:] == ".net.xml.gz":
595
options.netfile = options.netfile[:-11] + ".split.net.xml.gz"
596
else:
597
options.netfile = options.netfile[:-8] + ".split.net.xml"
598
if options.stopfile[-8:] == ".add.xml":
599
options.stopfile = options.stopfile[:-8] + ".split.add.xml"
600
else:
601
options.stopfile = options.stopfile[:-4] + ".split.xml"
602
603
subprocess.call([NETCONVERT,
604
'-e', splitfile,
605
'-s', oldNet,
606
'-o', options.netfile,
607
'--ptstop-files', oldStops,
608
'--ptstop-output', options.stopfile])
609
610
if options.verbose:
611
print("Re-creating region file '%s' after splitting network" % options.regionfile)
612
subprocess.call([sys.executable, STATION_DISTRICTS,
613
'-n', options.netfile,
614
'-s', options.stopfile,
615
'-o', options.regionfile])
616
617
618
def main(options):
619
if options.split:
620
splitNet(options)
621
622
if options.verbose:
623
print("Reading net")
624
net = sumolib.net.readNet(options.netfile)
625
stops = getStops(options.stopfile, net)
626
627
regions = loadRegions(options, net)
628
multiRegions = len(regions) > 1
629
nodeCoords = dict()
630
edgeShapes = dict()
631
632
for name, allEdges in regions.items():
633
edges = filterBidi(allEdges)
634
if options.verbose:
635
print("Processing region '%s' with %s edges" % (name, len(edges)))
636
initShapes(edges, nodeCoords, edgeShapes)
637
mainLine = findMainline(options, name, net, allEdges, stops)
638
rotateByMainLine(mainLine, edges, nodeCoords, edgeShapes, False)
639
if not options.skipYOpt:
640
nodeYValues = computeTrackOrdering(options, mainLine, edges, nodeCoords, edgeShapes, name)
641
if nodeYValues:
642
patchShapes(options, edges, nodeCoords, edgeShapes, nodeYValues)
643
if options.trackLength:
644
patchXValues(options, edges, nodeCoords, edgeShapes)
645
rotateByMainLine(mainLine, edges, nodeCoords, edgeShapes, True, options.horizontal, multiRegions)
646
647
if len(regions) > 1:
648
cleanShapes(options, net, nodeCoords, edgeShapes, stops)
649
650
with open(options.output_nodes, 'w') as outf_nod:
651
sumolib.writeXMLHeader(outf_nod, "$Id$", "nodes", options=options)
652
for nodeID in sorted(nodeCoords.keys()):
653
coord = nodeCoords[nodeID]
654
outf_nod.write(' <node id="%s" x="%.2f" y="%.2f"/>\n' % (
655
nodeID, coord[0], coord[1]))
656
outf_nod.write("</nodes>\n")
657
658
with open(options.output_edges, 'w') as outf_edg:
659
sumolib.writeXMLHeader(outf_edg, "$Id$", "edges", schemaPath="edgediff_file.xsd", options=options)
660
for edgeID in sorted(edgeShapes.keys()):
661
shape = edgeShapes[edgeID]
662
edge = net.getEdge(edgeID)
663
outf_edg.write(' <edge id="%s" shape="%s" length="%.2f"/>\n' % (
664
edgeID, shapeStr(shape), edge.getLength()))
665
if edge.getBidi():
666
outf_edg.write(' <edge id="%s" shape="%s" length="%.2f"/>\n' % (
667
edge.getBidi().getID(), shapeStr(reversed(shape)), edge.getLength()))
668
# remove the rest of the network
669
if options.filterRegions and not options.keepAll:
670
for edge in net.getEdges():
671
if edge.getID() not in edgeShapes and edge.getBidi() not in edgeShapes:
672
outf_edg.write(' <delete id="%s"/>\n' % (edge.getID()))
673
outf_edg.write("</edges>\n")
674
675
if not options.skipBuilding:
676
if options.verbose:
677
print("Building new net")
678
sys.stderr.flush()
679
subprocess.call([NETCONVERT,
680
'-s', options.netfile,
681
'-n', options.output_nodes,
682
'-e', options.output_edges,
683
'-o', options.output_net,
684
], stdout=subprocess.DEVNULL)
685
686
687
if __name__ == "__main__":
688
main(get_options())
689
690