diff options
Diffstat (limited to 'sublab_project/matekarte/osmux.py')
-rw-r--r-- | sublab_project/matekarte/osmux.py | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/sublab_project/matekarte/osmux.py b/sublab_project/matekarte/osmux.py new file mode 100644 index 0000000..69cefe5 --- /dev/null +++ b/sublab_project/matekarte/osmux.py @@ -0,0 +1,186 @@ +''' + +OSMux - A query multiplexer for the Overpass OpenStreetMap-API + +Version 2011-10-22 + +(c) 2011 - Conrad Hoffmann <ch@bitfehler.net> + +This software is public domain and comes without warranty of any kind. + +This script splits queries for large areas into several smaller bounding boxes, sends them to them +to the server and merges the results into a single file (the result for the original query). + +Additionally, ways are returned as nodes, so they can be displayed in map overlays. Actually, for +a matching way the script returns the first node of the way - but with all the tags that were +originally attached to the way. + +Basic usage instructions available via the --help switch. + +See http://code.bitfehler.net/osmux for more details. + +''' + +from __future__ import print_function + +from os import close +from os import remove +from sys import stderr +from time import sleep +from tempfile import mkstemp +from argparse import ArgumentParser +import xml.etree.ElementTree as etree + +try: + from urllib.request import urlretrieve +except ImportError: + from urllib import urlretrieve + +QUERY_TEMPLATE = 'http://www.overpass-api.de/api/xapi?%s[bbox=%f,%f,%f,%f][%s=%s]' + +def log(msg): + ''' Print a message to the console, unless the 'quiet' flag was set. ''' + + if 'params' not in globals(): + return + + if not params.quiet: + print(msg) + +def extract(xmlfile, tagname): + ''' Extract nodes from a query result. + + Regular nodes are just added to the result. For ways, the ways tags are copied to the first + node of the way and this first node is added to the result. + + ''' + tree = etree.parse(xmlfile) + root = tree.getroot() + result = {} + nodes = {} + + for child in root: + if child.tag == 'node': + # Save all nodes in this temp structure, if we are parsing ways we need them later on. + nodes[child.attrib['id']] = child + # If the node also contains the tag we are looking for, add it to the result. + for tag in child: + if tag.attrib['k'] == tagname: + result[child.attrib['id']] = child + elif child.tag == 'way': + # Create a new custom node that contains all of the ways tags and add it to result. If + # a way is returned, it must contain the tag we are looking for, so no need to check. + node = nodes[child[0].attrib['ref']] + for sub in child: + if sub.tag == 'tag': + etree.SubElement(node, 'tag', sub.attrib) + result[node.attrib['id']] = node + elif child.tag == 'remark': + # This usually means the query timed out or ran out of memory. + print('Potential error: %s' % child.text, file=stderr) + + return result + +def osmux(tag, north, south, east, west, value='*', pause=1, step=1.0): + (handle, tmpfile) = mkstemp(suffix='.xml', prefix='osm') + close(handle) + log("Using temporary file %s, step %f and pause %d." % (tmpfile, step, pause)) + + result = {} + + cur_lat = south + cur_lon = west + + while cur_lat < north: + + # While we are in this loop, there are more squares to fetch. + + # We know which square to go for, get its bbox: + bbox_lon = [cur_lon, min(cur_lon + step, east)] + bbox_lat = [cur_lat, min(cur_lat + step, north)] + + nquery = QUERY_TEMPLATE % ('node', bbox_lon[0], bbox_lat[0], bbox_lon[1], bbox_lat[1], tag, value) + wquery = QUERY_TEMPLATE % ('way', bbox_lon[0], bbox_lat[0], bbox_lon[1], bbox_lat[1], tag, value) + + log("Fetching %s" % nquery) + urlretrieve(nquery, tmpfile) + result.update(extract(tmpfile, tag)) + + log("Fetching %s" % wquery) + urlretrieve(wquery, tmpfile) + result.update(extract(tmpfile, tag)) + + # Calculate the next sub-bbox: + if cur_lon + step < east: + # Proceed with next square in current "row": + cur_lon += step + else: + # Reset longitude, continue with next "row", if any: + cur_lon = west + cur_lat += step + + # Wait a moment - might help the server. + sleep(pause) + + + log("Removing temporary data file...") + remove(tmpfile) + + log("Generating data...") + buffer_ = (('''<?xml version='1.0' encoding='UTF-8'?> +<osm version="0.6" generator="OSMux (http://code.bitfehler.net/osmux"> + <bound box="%f,%f,%f,%f" origin="Osmosis SNAPSHOT-rexported"/> +''' % (west, south, east, north)).encode('utf-8')) + for nid in result: + node = result[nid] + buffer_ += etree.tostring(node, encoding='utf-8') + buffer_ += '</osm>'.encode('utf-8') + return buffer_ + +if __name__ == '__main__': + # Parse command line arguments. + parser = ArgumentParser(description='Generate an OpenLayers OSM file using the Overpass API.', + epilog='See http://code.bitfehler.net for more details.') + parser.add_argument('-q', '--quiet', dest='quiet', action='store_true', help='suppress progress messages') + parser.add_argument('-S', '--step', dest='step', metavar='D', default=1.0, type=float, help='split into steps of D degrees') + parser.add_argument('-p', '--pause', dest='pause', metavar='P', default=1, type=int, help='pause for P seconds between requests to reduce server load (default: 1, must be >= 0)') + parser.add_argument('-t', '--tag', dest='tag', metavar='<tag>', type=str, required=True, help='return only elements with this tag') + parser.add_argument('-v', '--value', dest='value', metavar='<value>', default='*', type=str, help='return only elements with this value (omit for \'*\')') + parser.add_argument('-s', '--south', dest='south', metavar='S', type=float, required=True, help='latitude of the southern border of the bounding box') + parser.add_argument('-w', '--west', dest='west', metavar='W', type=float, required=True, help='longitude of the western border of the bounding box') + parser.add_argument('-n', '--north', dest='north', metavar='N', type=float, required=True, help='latitude of the northern border of the bounding box') + parser.add_argument('-e', '--east', dest='east', metavar='E', type=float, required=True, help='longitude of the eastern border of the bounding box') + parser.add_argument('outfile', metavar='<output file>', type=str, help='the output file for the merged OSM data') + params = parser.parse_args() + + # Sanity checks for the arguments. + if params.pause < 0: + print('Value for --pause must be >= 0. Exiting.', file=stderr) + exit(1) + if params.step <= 0: + print('Value for --pause must be > 0. Exiting.', file=stderr) + exit(1) + try: + open(params.outfile, mode='ab') + except IOError as e: + (errno, strerror) = e.args + print('Failed to open output file for writing: %s. Exiting.' % strerror, file=stderr) + exit(1) + if min(params.south, params.north) != params.south: + print('Value for --south must be less then --north. Use negative numbers for southern hemisphere. Exiting.', file=stderr) + exit(1) + if min(params.east, params.west) != params.west: + print('Value for --west must be less then --east. Use e.g. --east 187.5 if you need to cover the 180th meridian. Exiting.', file=stderr) + exit(1) + + buffer_ = osmux(params.tag, + params.north, + params.south, + params.east, + params.west, + value=params.value, + pause=params.pause, + step=params.step) + + with open(params.outfile, mode='wb') as outfile: + outfile.write(buffer_) |