Skip to content

Commit c46c208

Browse files
committed
Adding in tiled OSM data insert and server.
1 parent 30a3288 commit c46c208

File tree

2 files changed

+340
-0
lines changed

2 files changed

+340
-0
lines changed

insert_tiled_osm_data.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"""This program parses an OSM XML file and inserts the data in a
2+
MongoDB database"""
3+
4+
import sys
5+
import os
6+
import time
7+
import pymongo
8+
from datetime import datetime
9+
from xml.sax import make_parser
10+
from xml.sax.handler import ContentHandler
11+
from pymongo import Connection
12+
13+
from globalmaptiles import GlobalMercator
14+
15+
class OsmHandler(ContentHandler):
16+
"""Base class for parsing OSM XML data"""
17+
def __init__(self, client):
18+
self.proj = GlobalMercator()
19+
self.nodeRecords = []
20+
self.wayRecords = []
21+
self.relationRecords = []
22+
self.record = {}
23+
self.nodeLocations = {}
24+
self.client = client
25+
26+
self.stats = {'nodes': 0, 'ways': 0, 'relations': 0}
27+
self.lastStatString = ""
28+
self.statsCount = 0
29+
30+
def writeStatsToScreen(self):
31+
for char in self.lastStatString:
32+
sys.stdout.write('\b')
33+
self.lastStatString = "%d nodes, %d ways, %d relations" % (self.stats['nodes'],
34+
self.stats['ways'],
35+
self.stats['relations'])
36+
sys.stdout.write(self.lastStatString)
37+
38+
def fillDefault(self, attrs):
39+
"""Fill in default record values"""
40+
self.record['_id'] = int(attrs['id'])
41+
self.record['ts'] = self.isoToTimestamp(attrs['timestamp'])
42+
self.record['tg'] = []
43+
if attrs.has_key('user'):
44+
self.record['u'] = attrs['user']
45+
if attrs.has_key('uid'):
46+
self.record['uid'] = int(attrs['uid'])
47+
if attrs.has_key('version'):
48+
self.record['v'] = int(attrs['version'])
49+
if attrs.has_key('changeset'):
50+
self.record['c'] = int(attrs['changeset'])
51+
52+
def isoToTimestamp(self, isotime):
53+
"""Parse a date and return a time tuple"""
54+
t = datetime.strptime(isotime, "%Y-%m-%dT%H:%M:%SZ")
55+
return time.mktime(t.timetuple())
56+
57+
def quadKey(self, lat, lon, zoom):
58+
(mx, my) = self.proj.LatLonToMeters(lat, lon)
59+
(tx, ty) = self.proj.MetersToTile(mx, my, zoom)
60+
return self.proj.QuadTree(tx, ty, zoom)
61+
62+
def startElement(self, name, attrs):
63+
"""Parse the XML element at the start"""
64+
if name == 'node':
65+
self.fillDefault(attrs)
66+
self.record['loc'] = {'lat': float(attrs['lat']),
67+
'lon': float(attrs['lon'])}
68+
self.record['qk'] = self.quadKey(float(attrs['lat']), float(attrs['lon']), 17)
69+
self.nodeLocations[self.record['_id']] = self.record['qk']
70+
elif name == 'changeset':
71+
self.fillDefault(attrs)
72+
elif name == 'tag':
73+
k = attrs['k']
74+
v = attrs['v']
75+
# MongoDB doesn't let us have dots in the key names.
76+
#k = k.replace('.', ',,')
77+
self.record['tg'].append((k, v))
78+
elif name == 'way':
79+
self.fillDefault(attrs)
80+
self.record['n'] = []
81+
self.record['loc'] = []
82+
elif name == 'relation':
83+
self.fillDefault(attrs)
84+
self.record['m'] = []
85+
elif name == 'nd':
86+
ref = int(attrs['ref'])
87+
self.record['n'].append(ref)
88+
refLoc = self.nodeLocations[ref]
89+
if refLoc not in self.record['loc']:
90+
self.record['loc'].append(refLoc)
91+
elif name == 'member':
92+
ref = int(attrs['ref'])
93+
member = {'type': attrs['type'],
94+
'ref': ref,
95+
'role': attrs['role']}
96+
self.record['m'].append(member)
97+
98+
if attrs['type'] == 'way':
99+
ways2relations = self.client.osm.ways.find_one({ '_id' : ref})
100+
if ways2relations:
101+
if 'relations' not in ways2relations:
102+
ways2relations['relations'] = []
103+
ways2relations['relations'].append(self.record['_id'])
104+
self.client.osm.ways.save(ways2relations)
105+
elif attrs['type'] == 'node':
106+
nodes2relations = self.client.osm.nodes.find_one({ '_id' : ref})
107+
if nodes2relations:
108+
if 'relations' not in nodes2relations:
109+
nodes2relations['relations'] = []
110+
nodes2relations['relations'].append(self.record['_id'])
111+
self.client.osm.nodes.save(nodes2relations)
112+
113+
def endElement(self, name):
114+
"""Finish parsing an element
115+
(only really used with nodes, ways and relations)"""
116+
if name == 'node':
117+
self.nodeRecords.append(self.record)
118+
if len(self.nodeRecords) > 1500:
119+
self.client.osm.nodes.insert(self.nodeRecords)
120+
self.nodeRecords = []
121+
self.writeStatsToScreen()
122+
self.record = {}
123+
self.stats['nodes'] = self.stats['nodes'] + 1
124+
elif name == 'way':
125+
# Clean up any existing nodes
126+
if len(self.nodeRecords) > 0:
127+
self.client.osm.nodes.insert(self.nodeRecords)
128+
self.nodeRecords = []
129+
130+
self.wayRecords.append(self.record)
131+
if len(self.wayRecords) > 100:
132+
self.client.osm.ways.insert(self.wayRecords)
133+
self.wayRecords = []
134+
self.writeStatsToScreen()
135+
self.record = {}
136+
self.stats['ways'] = self.stats['ways'] + 1
137+
elif name == 'relation':
138+
self.client.osm.relations.save(self.record)
139+
self.record = {}
140+
self.statsCount = self.statsCount + 1
141+
if self.statsCount > 10:
142+
self.writeStatsToScreen()
143+
self.statsCount = 0
144+
self.stats['relations'] = self.stats['relations'] + 1
145+
146+
if __name__ == "__main__":
147+
filename = sys.argv[1]
148+
149+
if not os.path.exists(filename):
150+
print "Path %s doesn't exist." % (filename)
151+
sys.exit(-1)
152+
153+
client = Connection()
154+
parser = make_parser()
155+
handler = OsmHandler(client)
156+
parser.setContentHandler(handler)
157+
parser.parse(open(filename))
158+
client.disconnect()
159+
160+
print

tile_server.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import pymongo
2+
from pymongo import Connection
3+
from xml.sax.saxutils import escape
4+
import re
5+
6+
from globalmaptiles import GlobalMercator
7+
8+
class OsmApi:
9+
def __init__(self):
10+
self.client = Connection()
11+
self.proj = GlobalMercator()
12+
13+
def getTile(self, zoom, x, y):
14+
(x, y) = self.proj.GoogleTile(x,y,zoom)
15+
quadkey = self.proj.QuadTree(x,y,zoom)
16+
print "Querying for %s." % (quadkey,)
17+
(minlat, minlon, maxlat, maxlon) = self.proj.TileLatLonBounds(x,y,zoom)
18+
19+
# Nodes in the tile
20+
nodes = {}
21+
cursor = self.client.osm.nodes.find({'qk': {'$regex': "^%s" % (quadkey,)} })
22+
for row in cursor:
23+
nodes[row['_id']] = row
24+
25+
# Ways with nodes in the tile
26+
ways = {}
27+
cursor = self.client.osm.ways.find({'loc': {'$regex': "^%s" % (quadkey,)} })
28+
for row in cursor:
29+
ways[row['_id']] = row
30+
31+
# Nodes on ways that extend beyond the bounding box
32+
otherNids = set()
33+
for way in ways.values():
34+
for nid in way['nodes']:
35+
otherNids.add(nid)
36+
cursor = self.client.osm.nodes.find({'_id': {'$in': list(otherNids)} })
37+
for row in cursor:
38+
nodes[row['_id']] = row
39+
40+
# Relations that contain any of the above as members
41+
relations = {}
42+
43+
# Sort the results by id
44+
nodes = sorted(nodes.iteritems())
45+
ways = sorted(ways.iteritems())
46+
relations = sorted(relations.iteritems())
47+
48+
doc = {'bounds': {'minlat': minlat,
49+
'minlon': minlon,
50+
'maxlat': maxlat,
51+
'maxlon': maxlon},
52+
'nodes': nodes,
53+
'ways': ways,
54+
'relations': relations}
55+
56+
return doc
57+
58+
class OsmXmlOutput:
59+
def addNotNullAttr(self, mappable, mappableElement, name, outName=None):
60+
if not outName:
61+
outName = name
62+
if name in mappable:
63+
mappableElement.setAttribute(escape(outName), escape(unicode(mappable[name])))
64+
65+
def defaultAttrs(self, mappableElement, mappable):
66+
self.addNotNullAttr(mappable, mappableElement, "_id", "id")
67+
self.addNotNullAttr(mappable, mappableElement, "v", "version")
68+
self.addNotNullAttr(mappable, mappableElement, "user")
69+
70+
def tagNodes(self, doc, mappableElement, mappable):
71+
for mappable in mappable['tags']:
72+
k,v = mappable
73+
tagElement = doc.createElement("tag")
74+
tagElement.setAttribute("k", k)
75+
tagElement.setAttribute("v", v)
76+
mappableElement.appendChild(tagElement)
77+
78+
def iter(self, data):
79+
from xml.dom.minidom import Document
80+
doc = Document()
81+
82+
yield '<osm generator="%s" version="%s">\n' % ("tiled mongosm 0.1", "0.6")
83+
84+
if 'bounds' in data:
85+
yield '<bounds minlat="%s" minlon="%s" maxlat="%s" maxlon="%s"/>\n' % (
86+
str(data['bounds']['minlat']),
87+
str(data['bounds']['minlon']),
88+
str(data['bounds']['maxlat']),
89+
str(data['bounds']['maxlon']))
90+
91+
if 'nodes' in data:
92+
for (id, node) in data['nodes']:
93+
nodeElem = doc.createElement("node")
94+
nodeElem.setAttribute("lat", str(node['loc']['lat']))
95+
nodeElem.setAttribute("lon", str(node['loc']['lon']))
96+
self.defaultAttrs(nodeElem, node)
97+
self.tagNodes(doc, nodeElem, node)
98+
yield "%s\n" % (nodeElem.toxml('UTF-8'),)
99+
100+
if 'ways' in data:
101+
for (id, way) in data['ways']:
102+
wayElem = doc.createElement("way")
103+
self.defaultAttrs(wayElem, way)
104+
self.tagNodes(doc, wayElem, way)
105+
for ref in way['nodes']:
106+
refElement = doc.createElement("nd")
107+
refElement.setAttribute("ref", str(ref))
108+
wayElem.appendChild(refElement)
109+
yield "%s\n" % (wayElem.toxml('UTF-8'),)
110+
111+
if 'relations' in data:
112+
for (id, relation) in data['relations']:
113+
relationElem = doc.createElement("relation")
114+
self.defaultAttrs(relationElem, relation)
115+
self.tagNodes(doc, relationElem, relation)
116+
for member in relation['members']:
117+
memberElem = doc.createElement("member")
118+
memberElem.setAttribute("type", member['type'])
119+
memberElem.setAttribute("ref", str(member['ref']))
120+
memberElem.setAttribute("role", member['role'])
121+
relationElem.appendChild(memberElem)
122+
yield "%s\n" % (relationElem.toxml('UTF-8'),)
123+
124+
yield '</osm>\n'
125+
126+
import time, sys
127+
import os
128+
import urlparse
129+
from werkzeug.wrappers import Request, Response
130+
from werkzeug.routing import Map, Rule
131+
from werkzeug.exceptions import HTTPException, NotFound
132+
133+
class Mongosm(object):
134+
135+
def tileRequest(self, request, zoom, x, y):
136+
#(minlon, minlat, maxlon, maxlat) = request.args['bbox'].split(',')
137+
#bbox = [[float(minlat), float(minlon)],[float(maxlat), float(maxlon)]]
138+
139+
api = OsmApi()
140+
data = api.getTile(int(zoom), int(x), int(y))
141+
142+
outputter = OsmXmlOutput()
143+
144+
return Response(outputter.iter(data), content_type='text/xml', direct_passthrough=True)
145+
146+
def capabilitiesRequest(self, request):
147+
return Response("""
148+
<osm version="0.6" generator="mongosm 0.1">
149+
<api>
150+
<version minimum="0.6" maximum="0.6"/>
151+
<area maximum="0.5"/>
152+
</api>
153+
</osm>""")
154+
155+
def __init__(self):
156+
self.url_map = Map([
157+
Rule('/tiles/0.6/<zoom>/<x>/<y>', endpoint='tileRequest'),
158+
Rule('/api/capabilities', endpoint='capabilitiesRequest'),
159+
])
160+
161+
def dispatch_request(self, request):
162+
adapter = self.url_map.bind_to_environ(request.environ)
163+
try:
164+
endpoint, values = adapter.match()
165+
return getattr(self, endpoint)(request, **values)
166+
except HTTPException, e:
167+
return e
168+
169+
def wsgi_app(self, environ, start_response):
170+
request = Request(environ)
171+
response = self.dispatch_request(request)
172+
return response(environ, start_response)
173+
174+
def __call__(self, environ, start_response):
175+
return self.wsgi_app(environ, start_response)
176+
177+
if __name__ == '__main__':
178+
from werkzeug.serving import run_simple
179+
app = Mongosm()
180+
run_simple('0.0.0.0', 5000, app, use_debugger=True, use_reloader=True)

0 commit comments

Comments
 (0)