summaryrefslogtreecommitdiff
path: root/sublab_project/matekarte/osmux.py
blob: 69cefe51441f6331df5ec80d1d675f64a1be055d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
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_)