Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/osmGet.py
169659 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 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 base64
26
import ssl
27
import json
28
import collections
29
30
try:
31
import httplib
32
import urlparse
33
from urllib2 import urlopen
34
except ImportError:
35
# python3
36
import http.client as httplib
37
import urllib.parse as urlparse
38
from urllib.request import urlopen
39
try:
40
import certifi
41
HAVE_CERTIFI = True
42
except ImportError:
43
HAVE_CERTIFI = False
44
45
import sumolib
46
47
THIS_DIR = os.path.abspath(os.path.dirname(__file__))
48
TYPEMAP_DIR = os.path.join(THIS_DIR, "..", "data", "typemap")
49
50
51
def readCompressed(options, conn, urlpath, query, roadTypesJSON, getShapes, filename):
52
# generate query string for each road-type category
53
queryStringNode = []
54
55
commonQueryStringNode = """
56
<query type="nwr">
57
%s
58
%s
59
</query>"""
60
61
for category in roadTypesJSON:
62
if len(roadTypesJSON[category]) > 0:
63
typeList = "|".join(roadTypesJSON[category])
64
regvQueryString = '<has-kv k="%s" modv="" regv="%s"/>' % (category, typeList)
65
queryStringNode.append(commonQueryStringNode % (regvQueryString, query))
66
67
if getShapes and roadTypesJSON:
68
keyValueDict = collections.defaultdict(set)
69
for typemap in ["osmPolyconvert.typ.xml", "osmPolyconvertRail.typ.xml"]:
70
with open(os.path.join(TYPEMAP_DIR, typemap), 'r') as osmPolyconvert:
71
for polygon in sumolib.xml.parse(osmPolyconvert, 'polygonType'):
72
keyValue = polygon.id.split('.') + ["."]
73
keyValueDict[keyValue[0]].add(keyValue[1])
74
75
for category, value in keyValueDict.items():
76
if category in roadTypesJSON:
77
continue
78
if '.' in value:
79
regvQueryString = '<has-kv k="%s"/>' % category
80
else:
81
typeList = "|".join(value)
82
regvQueryString = '<has-kv k="%s" modv="" regv="%s"/>' % (category, typeList)
83
queryStringNode.append(commonQueryStringNode % (regvQueryString, query))
84
85
if queryStringNode:
86
# include all nodes that are relevant for public transport
87
regvQueryString = '<has-kv k="public_transport" modv="" regv="."/>'
88
queryStringNode.append(commonQueryStringNode % (regvQueryString, query))
89
90
if queryStringNode and getShapes:
91
unionQueryString = """
92
<union into="nodesBB">
93
%s
94
</union>
95
<union into="waysBB">
96
%s
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
""" % (query, "\n".join(queryStringNode))
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 = """
152
<osm-script timeout="240" element-limit="1073741824">
153
%s
154
<print mode="body"/>
155
</osm-script>""" % unionQueryString
156
157
if options.query_output:
158
with open(options.query_output, "w") as outf:
159
outf.write(finalQuery)
160
161
conn.request("POST", "/" + urlpath, finalQuery, headers={'Accept-Encoding': 'gzip'})
162
163
response = conn.getresponse()
164
if options.verbose:
165
print(response.status, response.reason)
166
if response.status == 200:
167
with open(filename, "wb") as out:
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
if filename.endswith(".gz"):
178
out.write(gzip.compress(lines))
179
else:
180
out.write(lines)
181
182
183
def get_options(args):
184
optParser = sumolib.options.ArgumentParser(description="Get network from OpenStreetMap")
185
optParser.add_argument("-p", "--prefix", category="output", default="osm", help="for output file")
186
optParser.add_argument("-b", "--bbox", category="input",
187
help="bounding box to retrieve in geo coordinates west,south,east,north")
188
optParser.add_argument("-t", "--tiles", type=int,
189
default=1, help="number of tiles the output gets split into")
190
optParser.add_argument("-d", "--output-dir", category="output",
191
help="optional output directory (must already exist)")
192
optParser.add_argument("-a", "--area", type=int, help="area id to retrieve")
193
optParser.add_argument("-x", "--polygon", category="processing",
194
help="calculate bounding box from polygon data in file")
195
optParser.add_argument("-u", "--url", default="www.overpass-api.de/api/interpreter",
196
help="Download from the given OpenStreetMap server")
197
# alternatives: overpass.kumi.systems/api/interpreter, sumo.dlr.de/osm/api/interpreter
198
optParser.add_argument("-w", "--wikidata", action="store_true",
199
default=False, help="get the corresponding wikidata")
200
optParser.add_argument("-r", "--road-types", dest="roadTypes",
201
help="only delivers osm data to the specified road-types")
202
optParser.add_argument("-s", "--shapes", action="store_true", default=False,
203
help="determines if polygon data (buildings, areas , etc.) is downloaded")
204
optParser.add_argument("-z", "--gzip", category="output", action="store_true",
205
default=False, help="save gzipped output")
206
optParser.add_argument("-q", "--query-output", category="output",
207
help="write query to the given FILE")
208
optParser.add_argument("-v", "--verbose", action="store_true",
209
default=False, help="tell me what you are doing")
210
options = optParser.parse_args(args=args)
211
if not options.bbox and not options.area and not options.polygon:
212
optParser.error("At least one of 'bbox' and 'area' and 'polygon' has to be set.")
213
if options.bbox:
214
west, south, east, north = [float(v) for v in options.bbox.split(',')]
215
if south > north or west > east or south < -90 or north > 90 or west < -180 or east > 180:
216
optParser.error("Invalid geocoordinates in bbox.")
217
return options
218
219
220
def get(args=None):
221
options = get_options(args)
222
if options.polygon:
223
west = 1e400
224
south = 1e400
225
east = -1e400
226
north = -1e400
227
for area in sumolib.output.parse_fast(options.polygon, 'poly', ['shape']):
228
for coord in area.shape.split():
229
point = tuple(map(float, coord.split(',')))
230
west = min(point[0], west)
231
south = min(point[1], south)
232
east = max(point[0], east)
233
north = max(point[1], north)
234
if options.bbox:
235
west, south, east, north = [float(v) for v in options.bbox.split(',')]
236
237
if options.output_dir:
238
options.prefix = os.path.join(options.output_dir, options.prefix)
239
240
if "http" in options.url:
241
url = urlparse.urlparse(options.url)
242
else:
243
url = urlparse.urlparse("https://" + options.url)
244
if os.environ.get("https_proxy") is not None:
245
headers = {}
246
proxy_url = urlparse.urlparse(os.environ.get("https_proxy"))
247
if proxy_url.username and proxy_url.password:
248
auth = '%s:%s' % (proxy_url.username, proxy_url.password)
249
headers['Proxy-Authorization'] = 'Basic ' + base64.b64encode(auth)
250
conn = httplib.HTTPSConnection(proxy_url.hostname, proxy_url.port)
251
conn.set_tunnel(url.hostname, 443, headers)
252
else:
253
if url.scheme == "https":
254
context = ssl.create_default_context(cafile=certifi.where() if HAVE_CERTIFI else None)
255
context.minimum_version = ssl.TLSVersion.TLSv1_2
256
conn = httplib.HTTPSConnection(url.hostname, url.port, context=context)
257
else:
258
conn = httplib.HTTPConnection(url.hostname, url.port)
259
260
roadTypesJSON = json.loads(options.roadTypes.replace("\'", "\"").lower()) if options.roadTypes else {}
261
262
suffix = ".osm.xml.gz" if options.gzip else ".osm.xml"
263
if options.area:
264
if options.area < 3600000000:
265
options.area += 3600000000
266
readCompressed(options, conn, url.path, '<area-query ref="%s"/>' %
267
options.area, roadTypesJSON, options.shapes, options.prefix + "_city" + suffix)
268
if options.bbox or options.polygon:
269
if options.tiles == 1:
270
readCompressed(options, conn, url.path, '<bbox-query n="%s" s="%s" w="%s" e="%s"/>' %
271
(north, south, west, east), roadTypesJSON,
272
options.shapes,
273
options.prefix + "_bbox" + suffix)
274
else:
275
num = options.tiles
276
b = west
277
for i in range(num):
278
if options.verbose:
279
print("Getting tile %d of %d." % (i + 1, num))
280
e = b + (east - west) / float(num)
281
readCompressed(options, conn, url.path, '<bbox-query n="%s" s="%s" w="%s" e="%s"/>' % (
282
north, south, b, e), roadTypesJSON, options.shapes,
283
"%s%s_%s%s" % (options.prefix, i, num, suffix))
284
b = e
285
286
conn.close()
287
# extract the wiki data according to the wikidata-value in the extracted osm file
288
if options.wikidata:
289
filename = options.prefix + '.wikidata.xml.gz'
290
osmFile = options.prefix + "_bbox" + suffix
291
codeSet = set()
292
# deal with invalid characters
293
bad_chars = [';', ':', '!', "*", ')', '(', '-', '_', '%', '&', '/', '=', '?', '$', '//', '\\', '#', '<', '>']
294
for line in sumolib.openz(osmFile):
295
subSet = set()
296
if 'wikidata' in line and line.split('"')[3][0] == 'Q':
297
basicData = line.split('"')[3]
298
for i in bad_chars:
299
basicData = basicData.replace(i, ' ')
300
elems = basicData.split(' ')
301
for e in elems:
302
if e and e[0] == 'Q':
303
subSet.add(e)
304
codeSet.update(subSet)
305
306
# make and save query results iteratively
307
codeList = list(codeSet)
308
interval = 50 # the maximal number of query items
309
outf = gzip.open(filename, "wb")
310
for i in range(0, len(codeSet), interval):
311
j = i + interval
312
if j > len(codeSet):
313
j = len(codeSet)
314
subList = codeList[i:j]
315
content = urlopen("https://www.wikidata.org/w/api.php?action=wbgetentities&ids=%s&format=json" %
316
("|".join(subList))).read()
317
if options.verbose:
318
print(type(content))
319
outf.write(content + b"\n")
320
outf.close()
321
322
323
if __name__ == "__main__":
324
try:
325
get()
326
except ssl.CertificateError:
327
print("Error with SSL certificate, try 'pip install -U certifi'.", file=sys.stderr)
328
329