#!/usr/bin/env python # (c) 2009 Simon Budig import sys, os, gzip, math, xml.sax import cairo class MapDrawing (object): def __init__ (self, size, bbox): self.bbox = bbox self.w = size self.h = size self.surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, self.w, self.h) self.cr = cr = cairo.Context (self.surface) cr.set_source_rgba (0.0, 0.0, 0.0, 0.0) cr.paint () cr.translate (self.w/2.0, self.h/2.0) cr.scale (min (self.w/2., self.h/2.), min (self.w/2., self.h/2.)) cr.scale (min (2. / (bbox[1] - bbox[0]), 2. / (bbox[3] - bbox[2])), min (2. / (bbox[1] - bbox[0]), 2. / (bbox[3] - bbox[2]))) cr.translate (- (bbox[0] + bbox[1]) / 2, - (bbox[2] + bbox[3]) / 2) self.pxl = min ([abs (i) for i in cr.device_to_user_distance (1.0, 1.0)]) cr.set_font_size (10 * self.pxl) cr.set_line_width (min (0.00001, self.pxl * 2)) cr.set_line_join (cairo.LINE_JOIN_ROUND) cr.set_line_cap (cairo.LINE_CAP_BUTT) def save (self, outfile): self.surface.flush () m = max (self.surface.get_data ()) if ord (m) == 0: return False self.cr.set_operator (cairo.OPERATOR_DEST_OVER) self.cr.set_source_rgba (0.0, 0.0, 0.0, 0.3) self.cr.paint () self.surface.flush () self.surface.write_to_png (outfile) return True def draw_ways (self, color, wayids, ways, nodes, show_users = False): cr = self.cr cr.set_source_rgb (*color) for w in wayids: way = ways [w] if len (way.noderefs) > 1: cr.new_sub_path () cr.move_to (nodes[way.noderefs[0]].lon, nodes[way.noderefs[0]].lat) for n in way.noderefs[1:]: cr.line_to (nodes[n].lon, nodes[n].lat) if way.noderefs[0] == way.noderefs[-1]: cr.close_path () cr.stroke () if show_users: cr.set_source_rgb (0, 0, 0) for w in wayids: way = ways [w] if len (way.noderefs) > 1: n = len (way.noderefs) - 1 n0 = nodes[way.noderefs[n/2]] n1 = nodes[way.noderefs[n/2+1]] cr.save () cr.translate ((n0.lon + n1.lon)/2, (n0.lat + n1.lat)/2) cr.rotate (math.atan2 (n1.lat - n0.lat, n1.lon - n0.lon)) width = cr.text_extents (way.properties["user"])[2] cr.translate (-width/2, -5*self.pxl) cr.move_to (0, 0) cr.show_text (way.properties["user"]) cr.restore () def draw_nodes (self, color, nodeids, nodes, show_users = False): cr = self.cr cr.set_source_rgb (*color) for n in nodeids: node = nodes [n] cr.new_sub_path () cr.arc (node.lon, node.lat, self.pxl * 3, 0, math.pi * 2) cr.fill () if show_users: cr.set_source_rgb (0, 0, 0) for n in nodeids: node = nodes [n] cr.move_to (node.lon + self.pxl * 4, node.lat + self.pxl * 10) cr.show_text (node.properties["user"]) class OSMNode (object): def __init__ (self, properties, tags): self.properties = dict (properties) self.tags = dict (tags) self.lon = float (self.properties["lon"]) self.lat = float (self.properties["lat"]) * math.pi / 180.0 # Mercator self.lon = (180.0 + self.lon) / 360.0 self.lat = (1.0 - math.log (math.tan (self.lat) + (1 / math.cos (self.lat))) / math.pi) / 2.0 class OSMWay (object): def __init__ (self, properties, tags, noderefs): self.properties = dict (properties) self.tags = dict (tags) self.noderefs = noderefs[:] class OSMData (object): def __init__ (self, infilename): self.nodes = {} self.ways = {} pars = xml.sax.make_parser () pars.setContentHandler (OSMHandler (self.nodes, self.ways)) pars.parse (gzip.GzipFile (infilename)) self.nodeids = set (self.nodes.keys ()) self.wayids = set (self.ways.keys ()) class OSMHandler (xml.sax.handler.ContentHandler): def __init__ (self, nodestorage, waystorage): self.tagnesting = [] self.char_data = "" self.cur_node_attrs = {} self.cur_tags = {} self.nodes = nodestorage self.ways = waystorage # these functions do some dispatching for the different tags # via introspection. def startElement (self, name, attrs): self.tagnesting.append (name) self.char_data = "" handler = OSMHandler.__dict__.get ("handle_start_" + name, None) if handler: handler (self, attrs) def characters (self, chars): self.char_data += chars def endElement (self, name): # handle the concatenated char data handler = OSMHandler.__dict__.get ("handle_chars_" + self.tagnesting[-1]) if handler: handler (self, self.char_data) self.tagnesting.pop () handler = OSMHandler.__dict__.get ("handle_end_" + name, None) if handler: handler (self) # actual handlers. Must have the name "handle_start_" def handle_start_node (self, attrs): self.cur_attrs = attrs self.cur_tags = {} self.cur_id = int (attrs["id"]) def handle_end_node (self): self.nodes [self.cur_id] = OSMNode (self.cur_attrs, self.cur_tags) def handle_start_way (self, attrs): self.cur_attrs = attrs self.cur_tags = {} self.cur_noderefs = [] self.cur_id = int (attrs["id"]) def handle_end_way (self): self.ways [self.cur_id] = OSMWay (self.cur_attrs, self.cur_tags, self.cur_noderefs) def handle_start_nd (self, attrs): self.cur_noderefs.append (int (attrs["ref"])) def handle_start_tag (self, attrs): self.cur_tags[attrs["k"]] = attrs["v"] def get_tile_origin (lat, lon, zoom): lat_rad = lat * math.pi / 180.0 n = 2.0 ** zoom xtile = int((lon + 180.0) / 360.0 * n) ytile = int((1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n) return (xtile, ytile) def render_tiles (olddata, newdata, commonwayids, delwayids, changedwayids, retaggedwayids, addwayids, delnodeids, changednodeids, retaggednodeids, addnodeids, basedir, x, y, startzoom, endzoom, tilesize=256): renderqueue = [ (x, y, startzoom) ] while renderqueue: x, y, zoom = renderqueue[0] renderqueue = renderqueue[1:] scale = 1./2**zoom bbox = [x * scale, (x + 1) * scale, y * scale, (y + 1) * scale] drawing = MapDrawing (tilesize, bbox) #drawing.draw_ways ((.4, .4, .4), commonwayids, newdata.ways, newdata.nodes, zoom >= 17) drawing.draw_ways ((1, 0, 0), delwayids, olddata.ways, olddata.nodes, zoom >= 17) drawing.draw_ways ((1, 0.7, 0), changedwayids, newdata.ways, newdata.nodes, zoom >= 17) drawing.draw_ways ((0.3, 0.2, 1), retaggedwayids - changedwayids, newdata.ways, newdata.nodes, zoom >= 17) drawing.draw_ways ((0, 1, 0), addwayids, newdata.ways, newdata.nodes, zoom >= 17) drawing.draw_nodes ((1, 0, 0), delnodeids, olddata.nodes, zoom >= 17) drawing.draw_nodes ((1, 0.7, 0), changednodeids, newdata.nodes, zoom >= 17) drawing.draw_nodes ((0.3, 0.2, 1), retaggednodeids - changednodeids, newdata.nodes, zoom >= 17) drawing.draw_nodes ((0, 1, 0), addnodeids, newdata.nodes, zoom >= 17) dirname = "%s/%d/%d" % (basedir, zoom, x) if not os.path.isdir (dirname): os.makedirs (dirname) if drawing.save ("%s/%d.png" % (dirname, y)): if zoom < endzoom: renderqueue += [ (x*2, y*2, zoom+1), (x*2+1, y*2, zoom+1), (x*2, y*2+1, zoom+1), (x*2+1, y*2+1, zoom+1) ] if __name__ == '__main__': if len (sys.argv) < 8: print >> sys.stderr, "usage: %s " % sys.argv[0] sys.exit (1) basedir = sys.argv[1] startzoom = int (sys.argv[2]) x = int (sys.argv[3]) y = int (sys.argv[4]) endzoom = int (sys.argv[5]) olddata = OSMData (sys.argv[6]) newdata = OSMData (sys.argv[7]) tilesize = 256 delnodeids = olddata.nodeids - newdata.nodeids addnodeids = newdata.nodeids - olddata.nodeids changednodeids = set ([id for id in (olddata.nodeids & newdata.nodeids) if olddata.nodes[id].properties["lat"] != newdata.nodes[id].properties["lat"] or olddata.nodes[id].properties["lon"] != newdata.nodes[id].properties["lon"]]) retaggednodeids = set ([id for id in (olddata.nodeids & newdata.nodeids) if olddata.nodes[id].tags != newdata.nodes[id].tags]) commonnodeids = newdata.nodeids & olddata.nodeids - changednodeids delwayids = olddata.wayids - newdata.wayids addwayids = newdata.wayids - olddata.wayids changedwayids = set ([id for id in (olddata.wayids & newdata.wayids) if olddata.ways[id].noderefs != newdata.ways[id].noderefs]) retaggedwayids = set ([id for id in (olddata.wayids & newdata.wayids) if olddata.ways[id].tags != newdata.ways[id].tags]) commonwayids = newdata.wayids & olddata.wayids - changedwayids render_tiles (olddata, newdata, commonwayids, delwayids, changedwayids, retaggedwayids, addwayids, delnodeids, changednodeids, retaggednodeids, addnodeids, basedir, x, y, startzoom, endzoom)