#!/usr/bin/env python3 # -*- coding: utf-8 -*- ''' OSMux - A query multiplexer for the Overpass OpenStreetMap-API Version 2011-10-22 (c) 2011 - Conrad Hoffmann 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='', type=str, required=True, help='return only elements with this tag') parser.add_argument('-v', '--value', dest='value', metavar='', 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='', 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='a', 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(''' ''' % (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('')