Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/net/net2geojson.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 net2geojson.py
15
# @author Jakob Erdmann
16
# @date 2020-05-05
17
18
"""
19
This script converts a sumo network to GeoJSON and optionally includes edgeData
20
"""
21
from __future__ import absolute_import, print_function
22
23
import json
24
import os
25
import sys
26
from collections import defaultdict
27
28
if 'SUMO_HOME' in os.environ:
29
sys.path.append(os.path.join(os.environ['SUMO_HOME'], 'tools'))
30
import sumolib # noqa
31
import sumolib.geomhelper as gh
32
33
34
def parse_args():
35
op = sumolib.options.ArgumentParser(description="net to geojson",
36
usage="Usage: " + sys.argv[0] + " -n <net> <options>")
37
# input
38
op.add_argument("-n", "--net-file", category="input", dest="netFile", required=True, type=op.net_file,
39
help="The .net.xml file to convert")
40
op.add_argument("-d", "--edgedata-file", category="input", dest="edgeData", type=op.edgedata_file,
41
help="Optional edgeData to include in the output")
42
op.add_argument("-p", "--ptline-file", category="input", dest="ptlines", type=op.file,
43
help="Optional ptline information to include in the output")
44
# output
45
op.add_argument("-o", "--output-file", dest="outFile", category="output", required=True, type=op.file,
46
help="The geojson output file name")
47
# processing
48
op.add_argument("-l", "--lanes", action="store_true", default=False,
49
help="Export lane geometries")
50
op.add_argument("-e", "--edges", action="store_true", default=False, help="Export edge geometries")
51
op.add_argument("--junctions", action="store_true", default=False,
52
help="Export junction geometries")
53
op.add_argument("-i", "--internal", action="store_true", default=False,
54
help="Export internal geometries")
55
op.add_argument("-j", "--junction-coordinates", dest="junctionCoords", action="store_true", default=False,
56
help="Append junction coordinates to edge shapes")
57
op.add_argument("-b", "--boundary", dest="boundary", action="store_true", default=False,
58
help="Export boundary shapes instead of center-lines")
59
op.add_argument("-t", "--traffic-lights", action="store_true", default=False, dest="tls",
60
help="Export traffic light geometries")
61
op.add_argument("--edgedata-timeline", action="store_true", default=False, dest="edgedataTimeline",
62
help="Exports all time intervals (by default only the first is exported)")
63
op.add_argument("-x", "--extra-attributes", action="store_true", default=False, dest="extraAttributes",
64
help="Exports extra attributes from edge and lane "
65
"(such as max speed, number of lanes and allowed vehicles)")
66
67
options = op.parse_args()
68
if not options.edges and not options.lanes:
69
options.edges = True
70
71
return options
72
73
74
def shape2json(net, geometry, isBoundary):
75
lonLatGeometry = [net.convertXY2LonLat(x, y) for x, y in geometry]
76
coords = [[round(x, 6), round(y, 6)] for x, y in lonLatGeometry]
77
if isBoundary:
78
coords = [coords]
79
return {
80
"type": "Polygon" if isBoundary else "LineString",
81
"coordinates": coords
82
}
83
84
85
def addFeature(options, features, addLanes):
86
geomType = 'lane' if addLanes else 'edge'
87
for id, geometry, width in net.getGeometries(addLanes, options.junctionCoords):
88
feature = {"type": "Feature"}
89
feature["properties"] = {
90
"element": geomType,
91
"id": id,
92
}
93
edgeID = net.getLane(id).getEdge().getID() if addLanes else id
94
if edgeID in edgeData:
95
if options.edgedataTimeline:
96
feature["properties"]["edgeData"] = edgeData[edgeID]
97
else:
98
feature["properties"].update(edgeData[edgeID][0])
99
100
if edgeID in ptLines:
101
for ptType, lines in ptLines[edgeID].items():
102
feature["properties"][ptType] = " ".join(sorted(lines))
103
104
if not addLanes or not options.edges:
105
feature["properties"]["name"] = net.getEdge(edgeID).getName()
106
if options.extraAttributes:
107
feature["properties"]["maxSpeed"] = net.getEdge(edgeID).getSpeed()
108
if geomType == 'lane':
109
feature["properties"]["allow"] = ','.join(sorted(net.getLane(id).getPermissions()))
110
feature["properties"]["index"] = net.getLane(id).getIndex()
111
outgoingLanes = [lane.getID() for lane in net.getLane(id).getOutgoingLanes()]
112
feature["properties"]["outgoingLanes"] = ','.join(sorted(outgoingLanes))
113
directions = [conn.getDirection() for conn in net.getLane(id).getOutgoing()]
114
feature["properties"]["directions"] = ','.join(sorted(set(directions)))
115
else:
116
feature["properties"]["numLanes"] = net.getEdge(edgeID).getLaneNumber()
117
permissions_union = set()
118
for lane in net.getEdge(edgeID).getLanes():
119
permissions_union.update(lane.getPermissions())
120
feature["properties"]["allow"] = ",".join(sorted(permissions_union))
121
feature["properties"]["fromNode"] = net.getEdge(edgeID).getFromNode().getID()
122
feature["properties"]["toNode"] = net.getEdge(edgeID).getToNode().getID()
123
if options.boundary:
124
geometry = gh.line2boundary(geometry, width)
125
feature["geometry"] = shape2json(net, geometry, options.boundary)
126
features.append(feature)
127
128
129
if __name__ == "__main__":
130
options = parse_args()
131
net = sumolib.net.readNet(options.netFile, withInternal=options.internal)
132
if not net.hasGeoProj():
133
sys.stderr.write("Network does not provide geo projection\n")
134
sys.exit(1)
135
136
edgeData = defaultdict(dict)
137
if options.edgeData:
138
for i, interval in enumerate(sumolib.xml.parse(options.edgeData, "interval", heterogeneous=True)):
139
for edge in interval.edge:
140
data = dict(edge.getAttributes())
141
data["begin"] = interval.begin
142
data["end"] = interval.end
143
del data["id"]
144
edgeData[edge.id][i] = data
145
if not options.edgedataTimeline:
146
break
147
148
ptLines = defaultdict(lambda: defaultdict(set))
149
if options.ptlines:
150
for ptline in sumolib.xml.parse(options.ptlines, "ptLine", heterogeneous=True):
151
if ptline.route:
152
for edge in ptline.route[0].edges.split():
153
ptLines[edge][ptline.type].add(ptline.line)
154
155
features = []
156
157
if options.edges:
158
addFeature(options, features, False)
159
if options.lanes:
160
addFeature(options, features, True)
161
162
if options.junctions:
163
for junction in net.getNodes():
164
feature = {"type": "Feature"}
165
feature["properties"] = {
166
"element": 'junction',
167
"id": junction.getID(),
168
}
169
feature["geometry"] = shape2json(net, junction.getShape(), options.boundary)
170
features.append(feature)
171
172
if options.tls:
173
for edge in net.getEdges():
174
for lane in edge.getLanes():
175
nCons = len(lane.getOutgoing())
176
for i, con in enumerate(lane.getOutgoing()):
177
if con.getTLSID() != "":
178
feature = {"type": "Feature"}
179
feature["properties"] = {
180
"element": 'tls_connection',
181
"id": "%s_%s" % (con.getJunction().getID(), con.getJunctionIndex()),
182
"tls": con.getTLSID(),
183
"tlIndex": con.getTLLinkIndex(),
184
}
185
barLength = lane.getWidth() / nCons
186
offset = i * barLength - lane.getWidth() * 0.5
187
prev, end = lane.getShape()[-2:]
188
geometry = [gh.add(end, gh.sideOffset(prev, end, offset)),
189
gh.add(end, gh.sideOffset(prev, end, offset + barLength))]
190
if options.boundary:
191
geometry = gh.line2boundary(geometry, 0.2)
192
feature["geometry"] = shape2json(net, geometry, options.boundary)
193
features.append(feature)
194
195
geojson = {}
196
geojson["type"] = "FeatureCollection"
197
geojson["features"] = features
198
with sumolib.openz(options.outFile, 'w') as outf:
199
json.dump(geojson, outf, sort_keys=True, indent=4, separators=(',', ': '))
200
print(file=outf)
201
202