Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/osmGet.py
193717 views
1
#!/usr/bin/env python
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2009-2026 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 osmGet.py
15
# @author Daniel Krajzewicz
16
# @author Jakob Erdmann
17
# @author Michael Behrisch
18
# @date 2009-08-01
19
20
from __future__ import absolute_import
21
from __future__ import print_function
22
import sys
23
import os
24
import gzip
25
import ssl
26
import json
27
import collections
28
import time
29
30
try:
31
from urllib2 import urlopen, Request
32
HTTPError = URLError = Exception
33
except ImportError:
34
# python3
35
from urllib.request import urlopen, Request
36
from urllib.error import HTTPError, URLError
37
try:
38
import certifi
39
HAVE_CERTIFI = True
40
except ImportError:
41
HAVE_CERTIFI = False
42
43
import sumolib
44
45
THIS_DIR = os.path.abspath(os.path.dirname(__file__))
46
TYPEMAP_DIR = os.path.join(THIS_DIR, "..", "data", "typemap")
47
48
49
def readCompressed(options, urls, context, query, roadTypesJSON, getShapes, filename):
50
# generate query string for each road-type category
51
queryStringNode = []
52
53
commonQueryStringNode = """
54
<query type="nwr">
55
%s
56
%s
57
</query>"""
58
59
for category in roadTypesJSON:
60
if len(roadTypesJSON[category]) > 0:
61
typeList = "|".join(roadTypesJSON[category])
62
regvQueryString = '<has-kv k="%s" modv="" regv="%s"/>' % (category, typeList)
63
queryStringNode.append(commonQueryStringNode % (regvQueryString, query))
64
65
if getShapes and roadTypesJSON:
66
keyValueDict = collections.defaultdict(set)
67
for typemap in ["osmPolyconvert.typ.xml", "osmPolyconvertRail.typ.xml"]:
68
with open(os.path.join(TYPEMAP_DIR, typemap), 'r') as osmPolyconvert:
69
for polygon in sumolib.xml.parse(osmPolyconvert, 'polygonType'):
70
keyValue = polygon.id.split('.') + ["."]
71
keyValueDict[keyValue[0]].add(keyValue[1])
72
73
for category, value in keyValueDict.items():
74
if category in roadTypesJSON:
75
continue
76
if '.' in value:
77
regvQueryString = '<has-kv k="%s"/>' % category
78
else:
79
typeList = "|".join(value)
80
regvQueryString = '<has-kv k="%s" modv="" regv="%s"/>' % (category, typeList)
81
queryStringNode.append(commonQueryStringNode % (regvQueryString, query))
82
83
if queryStringNode:
84
# include all nodes that are relevant for public transport
85
regvQueryString = '<has-kv k="public_transport" modv="" regv="."/>'
86
queryStringNode.append(commonQueryStringNode % (regvQueryString, query))
87
88
if queryStringNode and getShapes:
89
unionQueryString = """
90
<union into="waysBB">
91
%s
92
</union>
93
<union into="nodesBB">
94
%s
95
<item from="waysBB"/>
96
<recurse type="way-node"/>
97
</union>
98
<union into="waysBB2">
99
<item from="waysBB"/>
100
<recurse type="way-node"/>
101
<recurse type="node-relation"/>
102
<recurse type="way-relation"/>
103
</union>
104
<union into="waysBB3">
105
<item from="waysBB2"/>
106
<recurse type="relation-way"/>
107
<recurse type="way-node"/>
108
</union>
109
<query type="node">
110
<item from="nodesBB"/>
111
<item from="waysBB3"/>
112
</query>
113
<query type="way" into="waysBB4">
114
<item from="waysBB3"/>
115
</query>
116
<query type="relation" into="relsBB4">
117
<item from="waysBB3"/>
118
</query>
119
<union>
120
<item/>
121
<item from="waysBB4"/>
122
<item from="relsBB4"/>
123
</union>
124
""" % ("\n".join(queryStringNode), query)
125
126
elif queryStringNode:
127
unionQueryString = """
128
<union>
129
%s
130
</union>
131
<union>
132
<item/>
133
<recurse type="way-node"/>
134
<recurse type="node-relation"/>
135
<recurse type="way-relation"/>
136
</union>""" % "\n".join(queryStringNode)
137
138
else:
139
unionQueryString = """
140
<union>
141
%s
142
<recurse type="node-relation" into="rels"/>
143
<recurse type="node-way"/>
144
<recurse type="way-relation"/>
145
</union>
146
<union>
147
<item/>
148
<recurse type="way-node"/>
149
</union>""" % query
150
151
finalQuery = """<osm-script timeout="240" element-limit="1073741824">
152
%s
153
<print mode="body"/>
154
</osm-script>""" % unionQueryString
155
156
if options.query_output:
157
with open(options.query_output, "w") as outf:
158
outf.write(finalQuery)
159
160
headers = {"Accept-Encoding": "gzip", "Content-Type": "application/xml"}
161
for idx in range(options.retries + 1):
162
url = urls[idx % len(urls)]
163
try:
164
req = Request(url, data=finalQuery.encode(), headers=headers)
165
with urlopen(req, context=context) as response:
166
if options.verbose:
167
print(response.status, response.reason)
168
if response.getheader('Content-Encoding') == 'gzip':
169
lines = gzip.decompress(response.read())
170
else:
171
lines = response.read()
172
declClose = lines.find(b'>') + 1
173
lines = (lines[:declClose]
174
+ b"\n"
175
+ sumolib.xml.buildHeader(options=options).encode()
176
+ lines[declClose:])
177
with open(filename, "wb") as out:
178
if filename.endswith(".gz"):
179
out.write(gzip.compress(lines))
180
else:
181
out.write(lines)
182
break
183
except HTTPError as e:
184
if idx < options.retries:
185
if e.code == 504:
186
if options.verbose:
187
print("Download from %s failed, retrying." % url)
188
time.sleep(options.retry_delay)
189
continue
190
urls = [u for u in urls if u != url]
191
if urls:
192
print("Removing %s from list of valid servers (%s %s)." % (url, e.code, e.msg))
193
continue
194
raise
195
except (ValueError, URLError):
196
urls = [u for u in urls if u != url]
197
if urls:
198
print("Removing %s from list of valid servers." % url)
199
continue
200
raise
201
202
203
def get_options(args):
204
optParser = sumolib.options.ArgumentParser(description="Get network from OpenStreetMap")
205
optParser.add_argument("-p", "--prefix", category="output", default="osm", help="for output file")
206
optParser.add_argument("-b", "--bbox", category="input",
207
help="bounding box to retrieve in geo coordinates west,south,east,north")
208
optParser.add_argument("-t", "--tiles", type=int,
209
default=1, help="number of tiles the output gets split into")
210
optParser.add_argument("-d", "--output-dir", category="output",
211
help="optional output directory (must already exist)")
212
optParser.add_argument("-a", "--area", type=int, help="area id to retrieve")
213
optParser.add_argument("-x", "--polygon", category="processing",
214
help="calculate bounding box from polygon data in file")
215
optParser.add_argument("-u", "--url", default="hpi,hpi,oapi",
216
help="Download from the given Overpass server(s)")
217
optParser.add_argument("-w", "--wikidata", action="store_true",
218
default=False, help="get the corresponding wikidata")
219
optParser.add_argument("-r", "--road-types", dest="roadTypes",
220
help="only delivers osm data to the specified road-types")
221
optParser.add_argument("-s", "--shapes", action="store_true", default=False,
222
help="determines if polygon data (buildings, areas, etc.) is downloaded")
223
optParser.add_argument("-z", "--gzip", category="output", action="store_true",
224
default=False, help="save gzipped output")
225
optParser.add_argument("-q", "--query-output", category="output",
226
help="write query to the given FILE")
227
optParser.add_argument("--config-output", category="output",
228
help="write configuration to the given FILE and not as comment into the output file")
229
optParser.add_argument("--retries", type=int, default=5,
230
help="How many attempts to download if a 504 is encountered")
231
optParser.add_argument("--retry-delay", type=int, default=3, help="Delay between download attempts")
232
optParser.add_argument("-v", "--verbose", action="store_true",
233
default=False, help="tell me what you are doing")
234
options = optParser.parse_args(args=args)
235
if not options.bbox and not options.area and not options.polygon:
236
optParser.error("At least one of 'bbox' and 'area' and 'polygon' has to be set.")
237
if options.bbox:
238
west, south, east, north = [float(v) for v in options.bbox.split(',')]
239
if south > north or west > east or south < -90 or north > 90 or west < -180 or east > 180:
240
optParser.error("Invalid geocoordinates in bbox.")
241
return options
242
243
244
def get(args=None):
245
options = get_options(args)
246
if options.polygon:
247
west = 1e400
248
south = 1e400
249
east = -1e400
250
north = -1e400
251
for area in sumolib.output.parse_fast(options.polygon, 'poly', ['shape']):
252
for coord in area.shape.split():
253
point = tuple(map(float, coord.split(',')))
254
west = min(point[0], west)
255
south = min(point[1], south)
256
east = max(point[0], east)
257
north = max(point[1], north)
258
if options.bbox:
259
west, south, east, north = [float(v) for v in options.bbox.split(',')]
260
261
if options.output_dir:
262
options.prefix = os.path.join(options.output_dir, options.prefix)
263
264
shortcut = {"oapi": "https://overpass-api.de/api/interpreter", "hpi": "https://osm.hpi.de/overpass/api/interpreter"}
265
urls = [shortcut.get(u, u) for u in options.url.split(",")]
266
context = ssl.create_default_context(cafile=certifi.where() if HAVE_CERTIFI else None)
267
context.minimum_version = ssl.TLSVersion.TLSv1_2
268
269
roadTypesJSON = json.loads(options.roadTypes.replace("\'", "\"").lower()) if options.roadTypes else {}
270
271
suffix = ".osm.xml.gz" if options.gzip else ".osm.xml"
272
if options.area:
273
if options.area < 3600000000:
274
options.area += 3600000000
275
readCompressed(options, urls, context, '<area-query ref="%s"/>' %
276
options.area, roadTypesJSON, options.shapes, options.prefix + "_city" + suffix)
277
if options.bbox or options.polygon:
278
if options.tiles == 1:
279
readCompressed(options, urls, context, '<bbox-query n="%s" s="%s" w="%s" e="%s"/>' %
280
(north, south, west, east), roadTypesJSON,
281
options.shapes,
282
options.prefix + "_bbox" + suffix)
283
else:
284
num = options.tiles
285
b = west
286
for i in range(num):
287
if options.verbose:
288
print("Getting tile %d of %d." % (i + 1, num))
289
e = b + (east - west) / float(num)
290
readCompressed(options, urls, context, '<bbox-query n="%s" s="%s" w="%s" e="%s"/>' % (
291
north, south, b, e), roadTypesJSON, options.shapes,
292
"%s%s_%s%s" % (options.prefix, i, num, suffix))
293
b = e
294
295
# extract the wiki data according to the wikidata-value in the extracted osm file
296
if options.wikidata:
297
filename = options.prefix + '.wikidata.xml.gz'
298
osmFile = options.prefix + "_bbox" + suffix
299
codeSet = set()
300
# deal with invalid characters
301
bad_chars = [';', ':', '!', "*", ')', '(', '-', '_', '%', '&', '/', '=', '?', '$', '//', '\\', '#', '<', '>']
302
for line in sumolib.openz(osmFile):
303
subSet = set()
304
if 'wikidata' in line and line.split('"')[3][0] == 'Q':
305
basicData = line.split('"')[3]
306
for i in bad_chars:
307
basicData = basicData.replace(i, ' ')
308
elems = basicData.split(' ')
309
for e in elems:
310
if e and e[0] == 'Q':
311
subSet.add(e)
312
codeSet.update(subSet)
313
314
# make and save query results iteratively
315
codeList = list(codeSet)
316
interval = 50 # the maximal number of query items
317
outf = gzip.open(filename, "wb")
318
for i in range(0, len(codeSet), interval):
319
j = i + interval
320
if j > len(codeSet):
321
j = len(codeSet)
322
subList = codeList[i:j]
323
content = urlopen("https://www.wikidata.org/w/api.php?action=wbgetentities&ids=%s&format=json" %
324
("|".join(subList))).read()
325
if options.verbose:
326
print(type(content))
327
outf.write(content + b"\n")
328
outf.close()
329
330
331
if __name__ == "__main__":
332
try:
333
get()
334
except ssl.CertificateError:
335
print("Error with SSL certificate, try 'pip install -U certifi'.", file=sys.stderr)
336
337