diff options
Diffstat (limited to 'sublab_project/matekarte')
-rw-r--r-- | sublab_project/matekarte/__init__.py | 0 | ||||
-rw-r--r-- | sublab_project/matekarte/models.py | 3 | ||||
-rw-r--r-- | sublab_project/matekarte/osmux.py | 186 | ||||
-rw-r--r-- | sublab_project/matekarte/tasks.py | 49 | ||||
-rw-r--r-- | sublab_project/matekarte/templates/matekarte/matekarte.html | 191 | ||||
-rw-r--r-- | sublab_project/matekarte/tests.py | 16 | ||||
-rw-r--r-- | sublab_project/matekarte/urls.py | 6 | ||||
-rw-r--r-- | sublab_project/matekarte/views.py | 1 |
8 files changed, 452 insertions, 0 deletions
diff --git a/sublab_project/matekarte/__init__.py b/sublab_project/matekarte/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/sublab_project/matekarte/__init__.py diff --git a/sublab_project/matekarte/models.py b/sublab_project/matekarte/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/sublab_project/matekarte/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. 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_) diff --git a/sublab_project/matekarte/tasks.py b/sublab_project/matekarte/tasks.py new file mode 100644 index 0000000..bc1018b --- /dev/null +++ b/sublab_project/matekarte/tasks.py @@ -0,0 +1,49 @@ +"""Celery tasks. +""" +from celery.task import PeriodicTask +from celery.schedules import crontab +from django.conf import settings +import os + +from osmux import osmux + +class OSMFetcher(object): + """ + A mixin to get and store OpenStreetmap Data + """ + + @property + def bbox(self): + raise NotImplementedError + + @property + def store_name(self): + raise NotImplementedError + + @property + def tag(self): + raise NotImplementedError + + value = '*' + + def run(self, **kwargs): + + fn = os.path.join(settings.MEDIA_ROOT, self.store_name) + fn_new = os.path.join(settings.MEDIA_ROOT, 'new-%s' % self.store_name) + + with open(fn_new, 'wb') as output: + output.write(osmux( + self.tag, + self.bbox[0], + self.bbox[1], + self.bbox[2], + self.bbox[3], + self.value)) + os.rename(fn_new, fn) + + +#class MateFetcher(OSMFetcher, PeriodicTask): +# run_every = crontab(minute=0, hour=3) +# bbox = (55.26, 46.52, 15.26, 5.22) +# store_name = 'mate.xml' +# tag = 'drink:club-mate' diff --git a/sublab_project/matekarte/templates/matekarte/matekarte.html b/sublab_project/matekarte/templates/matekarte/matekarte.html new file mode 100644 index 0000000..7172b46 --- /dev/null +++ b/sublab_project/matekarte/templates/matekarte/matekarte.html @@ -0,0 +1,191 @@ +{% extends 'base.html' %} + +{% block title %}Mate{% endblock %} + +{% block content %} +<h2>Mate</h2> +<p> + Diese Karte markiert alle Orte in Leipzig (bzw. sogar ganz Deutschland: + einfach herauszoomen) an denen Club Mate zu haben ist und die bei + <a href="http://www.openstreetmap.org">OpenStreetMap</a> mit dem Tag + <i>drink:club-mate=*</i> versehen wurden. Ein paar mehr Details findet + Ihr <a href="http://bitfehler.net/mate">hier</a>. Die Daten werden jede + Nacht aktualisiert. +</p> +<div id="spacer" style="margin-left: 20px;"> +<div id="mapdiv"></div> +</div> +<p style="font-size: 10px;">Javascript required. Click an icon for details. Map data +<a style="font-size: 10px;" href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a> 2011 +<a style="font-size: 10px;" href="http://www.openstreetmap.org">OpenStreetMap</a> contributors.</p> + +<script type="text/javascript" src="http://www.openlayers.org/api/OpenLayers.js"></script> +<script defer="defer" type="text/javascript"> +<!-- + +var obj, map, pois, styleMap, selectControl, selectedFeature, loadingPopup; +var numDots = 3; + +function resizeMap() { + document.getElementById("mapdiv").style.width = "100px" + document.getElementById("mapdiv").style.height = "100px"; + var c = document.getElementsByClassName("content")[0]; + var w = c.offsetWidth; + var h = c.offsetHeight; + w = Math.round(w * 0.90); + document.getElementById("mapdiv").style.width = w + "px" + document.getElementById("mapdiv").style.height = "500px"; +} + +function onPopupClose(evt) { + // 'this' is the popup. + selectControl.unselect(this.feature); +} + +// Pretty-print an amenity tag (and others actually) +function amenityPp(amenity) { + var words = amenity.split("_"); + var result = ""; + for (w in words) { + if (result != "") { + result += " "; + } + result += words[w].charAt(0).toUpperCase() + words[w].slice(1); + } + return result; +} + +function popupText(feature) { + var title = ""; + if ("name" in feature.attributes) { + title = feature.attributes["name"]; + } + else { + title = "*no name*"; + } + if ("website" in feature.attributes) { + title = '<a href="' + feature.attributes["website"] + '" rel="external">' + title + '</a>'; + } + + var desc = ""; + if ("amenity" in feature.attributes) { + desc = '<p style="font-size: smaller">' + amenityPp(feature.attributes["amenity"]) + '</p>'; + } + else if ("leisure" in feature.attributes) { + desc = '<p style="font-size: smaller">' + amenityPp(feature.attributes["leisure"]) + '</p>'; + } + else if ("shop" in feature.attributes) { + desc = '<p style="font-size: smaller">' + amenityPp(feature.attributes["shop"]) + '</p>'; + } + + var addr = ""; + if ("addr:street" in feature.attributes) { + addr += feature.attributes["addr:street"] + if ("addr:housenumber" in feature.attributes) { + addr += " " + feature.attributes["addr:housenumber"]; + } + addr += "</br>"; + } + if ("addr:city" in feature.attributes) { + if ("addr:postcode" in feature.attributes) { + addr += feature.attributes["addr:postcode"] + " "; + } + addr += feature.attributes["addr:city"]; + } + + var open = ""; + if ("opening_hours" in feature.attributes) { + open += '<p style="font-size: smaller">Open: ' + feature.attributes["opening_hours"] + '</p>'; + } + + return "<h2>" + title + "</h2>" + desc + "<p>" + addr + "</p>" + open; +} + +function onFeatureSelect(feature) { + var lst = ""; + for (var prop in feature.attributes) { + lst += prop + " "; + } + popup = new OpenLayers.Popup.FramedCloud( + "featurePopup", + feature.geometry.getBounds().getCenterLonLat(), + new OpenLayers.Size(100,100), + popupText(feature), + null, true, onPopupClose + ); + feature.popup = popup; + popup.feature = feature; + map.addPopup(popup); +} +function onFeatureUnselect(feature) { + if (feature.popup) { + popup.feature = null; + map.removePopup(feature.popup); + feature.popup.destroy(); + feature.popup = null; + } +} + +function dataCallback() { + map.removePopup(loadingPopup); +} + +resizeMap(); +window.onresize = resizeMap; + +map = new OpenLayers.Map("mapdiv"); +layer = new OpenLayers.Layer.OSM(); +layer.attribution = 0; + + +var styleMap = new OpenLayers.Style({ + externalGraphic: '/static/img/mate_icon.png', + graphicWidth: 32, + graphicHeight: 32, + graphicXOffset: 0, + graphicYOffset: -32 +}); + +var pois = new OpenLayers.Layer.Vector("Marker", { + styleMap: styleMap, + eventListeners: {"featuresadded": dataCallback }, + strategies: [new OpenLayers.Strategy.Fixed], + projection:map.displayProjection, + protocol: new OpenLayers.Protocol.HTTP({ + url: "{{ MEDIA_URL }}mate.xml", + format: new OpenLayers.Format.OSM, + }) +}); + +map.addLayer(layer); +map.addLayer(pois); + +//Set start centrepoint and zoom +var lonLat = new OpenLayers.LonLat(12.3903, 51.342).transform( + new OpenLayers.Projection("EPSG:4326"), // transform from WGS 1984 + map.getProjectionObject() // to Spherical Mercator Projection +); +map.setCenter(lonLat, 12); + +var lonLatPopup = new OpenLayers.LonLat(12.34, 51.36).transform( + new OpenLayers.Projection("EPSG:4326"), // transform from WGS 1984 + map.getProjectionObject() // to Spherical Mercator Projection +); + +loadingPopup = new OpenLayers.Popup("loadingPopup", + lonLatPopup, + new OpenLayers.Size(300,200), + '<p style="font-weight: bold; text-align: center; margin-top: 60px">Loading OpenStreetMap data.<br>Please wait.<br><br><span id="dots">...</span></p>', false +); +loadingPopup.opacity = 0.9; +map.addPopup(loadingPopup); + +selectControl = new OpenLayers.Control.SelectFeature(pois, { + onSelect: onFeatureSelect, + onUnselect: onFeatureUnselect +}); +map.addControl(selectControl); +selectControl.activate(); +--> +</script> +{% endblock content %} diff --git a/sublab_project/matekarte/tests.py b/sublab_project/matekarte/tests.py new file mode 100644 index 0000000..501deb7 --- /dev/null +++ b/sublab_project/matekarte/tests.py @@ -0,0 +1,16 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +from django.test import TestCase + + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) diff --git a/sublab_project/matekarte/urls.py b/sublab_project/matekarte/urls.py new file mode 100644 index 0000000..f15aaa7 --- /dev/null +++ b/sublab_project/matekarte/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls.defaults import patterns, url +from django.views.generic.simple import direct_to_template + +urlpatterns = patterns('', + url(r'^$', direct_to_template, {'template': 'matekarte/matekarte.html' }), +) diff --git a/sublab_project/matekarte/views.py b/sublab_project/matekarte/views.py new file mode 100644 index 0000000..60f00ef --- /dev/null +++ b/sublab_project/matekarte/views.py @@ -0,0 +1 @@ +# Create your views here. |