summaryrefslogtreecommitdiff
path: root/scripts/osmux.py
diff options
context:
space:
mode:
authorConrad Hoffmann <ch@bitfehler.net>2011-10-23 21:48:58 +0200
committerConrad Hoffmann <ch@bitfehler.net>2011-10-23 21:48:58 +0200
commit9cb93d4a0797dae55cbe823d6c09c027d730815e (patch)
treeaffc33ddd5d49b62ba90152fce56fdf99d92a26f /scripts/osmux.py
parentf142f502526b74dcf1c15f692e4210a09748a612 (diff)
Add script to retrieve Club Mate data from OSM
Diffstat (limited to 'scripts/osmux.py')
-rw-r--r--scripts/osmux.py166
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>')
+