diff options
-rw-r--r-- | scripts/osmux.py | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/scripts/osmux.py b/scripts/osmux.py new file mode 100644 index 0000000..88e43ae --- /dev/null +++ b/scripts/osmux.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +''' + +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 os import close +from os import remove +from sys import stderr +from time import sleep +from tempfile import mkstemp +from argparse import ArgumentParser +from urllib.request import urlretrieve +import xml.etree.ElementTree as etree + +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 not params.quiet: + print(msg) + +def extract(xmlfile): + ''' 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'] == params.tag: + 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 + +# 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='w', encoding='utf-8') +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) + + +(handle, tmpfile) = mkstemp(suffix='.xml', prefix='osm') +close(handle) +log("Using temporary file %s, step %f and pause %d." % (tmpfile, params.step, params.pause)) + +result = {} + +cur_lat = params.south +cur_lon = params.west + +while cur_lat < params.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 + params.step, params.east)] + bbox_lat = [cur_lat, min(cur_lat + params.step, params.north)] + + nquery = QUERY_TEMPLATE % ('node', bbox_lon[0], bbox_lat[0], bbox_lon[1], bbox_lat[1], params.tag, params.value) + wquery = QUERY_TEMPLATE % ('way', bbox_lon[0], bbox_lat[0], bbox_lon[1], bbox_lat[1], params.tag, params.value) + + log("Fetching %s" % nquery) + urlretrieve(nquery, tmpfile) + result.update(extract(tmpfile)) + + log("Fetching %s" % wquery) + urlretrieve(wquery, tmpfile) + result.update(extract(tmpfile)) + + # Calculate the next sub-bbox: + if cur_lon + params.step < params.east: + # Proceed with next square in current "row": + cur_lon += params.step + else: + # Reset longitude, continue with next "row", if any: + cur_lon = params.west + cur_lat += params.step + + # Wait a moment - might help the server. + sleep(params.pause) + + +log("Removing temporary data file...") +remove(tmpfile) + +log("Generating data...") +with open(params.outfile, mode='w', encoding='utf-8') as out: + out.write('''<?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"/> +''' % (params.west, params.south, params.east, params.north)) + for nid in result: + node = result[nid] + out.write(etree.tostring(node, encoding='utf-8').decode('utf-8')) + out.write('</osm>') + |