From c15f861e9058932424a857a2fda7555881b51f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kom=D1=8Fpa?= Date: Sun, 9 May 2010 12:48:37 +0300 Subject: [PATCH] Renderer moved to render.py --- src/kothic.py | 185 +--------------------------------------- src/render.py | 191 +++++++++++++++++++++++++++++++++--------- src/style.py | 9 +- src/vtiles_backend.py | 3 +- 4 files changed, 162 insertions(+), 226 deletions(-) diff --git a/src/kothic.py b/src/kothic.py index c5551f1..9ef730a 100644 --- a/src/kothic.py +++ b/src/kothic.py @@ -29,6 +29,7 @@ import Queue from debug import debug, Timer from vtiles_backend import QuadTileBackend as DataBackend from style import Styling +from render import RasterTile @@ -199,7 +200,6 @@ class Navigator: cr.set_source_surface(self.rastertile.surface, self.dx-self.width + self.rastertile.offset_x, self.dy - self.height + self.rastertile.offset_y) cr.paint() self.comm[3].release() -# cr. def main(self): self.window.show_all() @@ -213,189 +213,6 @@ class MessageContainer: pass -def line(cr, c): - cr.move_to(c[0], c[1]) - for k in range(2, len(c), 2): - cr.line_to(c[k], c[k + 1]) - cr.stroke() - -def poly(cr, c): - cr.move_to(c[0], c[1]) - for k in range(2, len(c), 2): - cr.line_to(c[k], c[k + 1]) - cr.fill() - - - -class RasterTile: - def __init__(self, width, height, zoom, data_backend): - self.w = width - self.h = height - self.surface = cairo.ImageSurface(cairo.FORMAT_RGB24, self.w, self.h) - self.offset_x = 0 - self.offset_y = 0 - self.center_coord = None - self.zoomlevel = zoom - self.zoom = None - self.data = data_backend - def screen2lonlat(self, x, y): - return (x - self.w/2)/(math.cos(self.center_coord[1]*math.pi/180)*self.zoom) + self.center_coord[0], -(y - self.h/2)/self.zoom + self.center_coord[1] - def lonlat2screen(self, (lon, lat)): - return (lon - self.center_coord[0])*self.lcc*self.zoom + self.w/2, -(lat - self.center_coord[1])*self.zoom + self.h/2 - def update_surface(self, lonlat, zoom, tilecache, style, lock = None): - rendertimer = Timer("Rendering image") - timer = Timer("Gettimg data") - self.zoom = zoom - self.center_coord = lonlat - cr = cairo.Context(self.surface) - cr.rectangle(0, 0, self.w, self.h) - cr.set_source_rgb(0.7, 0.7, 0.7) - cr.fill() - lonmin, latmin = self.screen2lonlat(0, self.h) - lonmax, latmax = self.screen2lonlat(self.w, 0) - datatimer = Timer("Asking backend and styling") - ww = [ (x, style.get_style("way", x.tags)) for x in self.data.get_vectors((lonmin,latmin,lonmax,latmax),self.zoomlevel).values()] - datatimer.stop() - ww1 = [] - for way in ww: - if way[1]: - ww1.append(way) - debug( "%s objects on screen (%s in dataset)"%(len(ww1),len(ww)) ) - ww = ww1 - - if lock is not None: - lock.acquire() - lock.release() - self.lcc = math.cos(self.center_coord[1]*math.pi/180) - - - #debug(objs_by_layers) - #ww = [x[0] for x in ww] - - lcc = math.cos(self.center_coord[1]*math.pi/180) - for w in ww: - cs = [] - for k in range(0, len(w[0].coords), 2): - x, y = self.lonlat2screen((w[0].coords[k], w[0].coords[k+1])); - cs.append(x) - cs.append(y) - w[0].cs = cs - - ww.sort(key=lambda x: x[1]["layer"]) - layers = list(set([int(x[1]["layer"]/100.) for x in ww])) - layers.sort() - objs_by_layers = {} - for layer in layers: - objs_by_layers[layer] = [] - for obj in ww: - # debug(obj) - objs_by_layers[int(obj[1]["layer"]/100.)].append(obj) - #debug ((obj[1]["layer"], obj[0].tags)) - del ww - timer.stop() - timer = Timer("Rasterizing image") - linecaps = {"butt":0, "round":1, "square":2} - linejoin = {"miter":0, "round":1, "bevel":2} - - text_rendered_at = set([(-100,-100)]) - #cr.set_antialias(2) - for layer in layers: - data = objs_by_layers[layer] - # - fill polygons - for obj in data: - if "fill-color" in obj[1]: ## TODO: fill-image - color = obj[1]["fill-color"] - cr.set_source_rgba(color[0], color[1], color[2], obj[1].get("fill-opacity", 1)) - - if not "extrude" in obj[1]: - poly(cr, obj[0].cs) - else: - line(cr, obj[0].cs) - if "extrude" in obj[1]: - hgt = obj[1]["extrude"] - c = obj[0].cs - excoords = [c[0], c[1]-hgt] - #pp = (c[0],c[1]) - cr.set_line_width (1) - for k in range(2, len(c), 2): - excoords.append(c[k]) - excoords.append(c[k + 1]-hgt) - - line(cr, [c[k],c[k+1],c[k],c[k+1]-hgt],) - poly(cr,excoords) - #line(cr, obj[0].cs) - - - - # - draw casings on layer - for obj in data: - ### Extras: casing-linecap, casing-linejoin - if "casing-width" in obj[1] or "casing-color" in obj[1]: - cr.set_dash(obj[1].get("casing-dashes",obj[1].get("dashes", []))) - cr.set_line_join(linejoin.get(obj[1].get("casing-linejoin",obj[1].get("linejoin", "round")),1)) - color = obj[1].get("casing-color", (0,0,0)) - cr.set_source_rgba(color[0], color[1], color[2], obj[1].get("casing-opacity", 1)) - ## TODO: good combining of transparent lines and casing - ## Probable solution: render casing, render way as mask and put casing with mask chopped out onto image - - - cr.set_line_width (obj[1].get("casing-width", obj[1].get("width",0)+1 )) - cr.set_line_cap(linecaps.get(obj[1].get("casing-linecap", obj[1].get("linecap", "butt")),0)) - line(cr, obj[0].cs) - # - draw line centers - for obj in data: - if "width" in obj[1] or "color" in obj[1]: - cr.set_dash(obj[1].get("dashes", [])) - cr.set_line_join(linejoin.get(obj[1].get("linejoin", "round"),1)) - color = obj[1].get("color", (0,0,0)) - cr.set_source_rgba(color[0], color[1], color[2], obj[1].get("opacity", 1)) - ## TODO: better overlapping of transparent lines. - ## Probable solution: render them (while they're of the same opacity and layer) on a temporary canvas that's merged into main later - cr.set_line_width (obj[1].get("width", 1)) - cr.set_line_cap(linecaps.get(obj[1].get("linecap", "butt"),0)) - line(cr, obj[0].cs) - # - render text labels - texttimer = Timer("Text rendering") - cr.set_line_join(1) # setting linejoin to "round" to get less artifacts on halo render - for obj in data: - if "text" in obj[1]: - - text = obj[1]["text"] - - cr.set_line_width (obj[1].get("width", 1)) - cr.set_font_size(obj[1].get("font-size", 9)) - if obj[1].get("text-position", "center") == "center": - where = self.lonlat2screen(obj[0].center) - for t in text_rendered_at: - if ((t[0]-where[0])**2+(t[1]-where[1])**2)**(0.5) < 15: - break - else: - text_rendered_at.add(where) - #debug ("drawing text: %s at %s"%(text, where)) - if "text-halo-color" in obj[1] or "text-halo-radius" in obj[1]: - cr.new_path() - cr.move_to(where[0], where[1]) - cr.set_line_width (obj[1].get("text-halo-radius", 1)) - color = obj[1].get("text-halo-color", (1.,1.,1.)) - cr.set_source_rgb(color[0], color[1], color[2]) - cr.text_path(text) - cr.stroke() - cr.new_path() - cr.move_to(where[0], where[1]) - cr.set_line_width (obj[1].get("text-halo-radius", 1)) - color = obj[1].get("text-color", (0.,0.,0.)) - cr.set_source_rgb(color[0], color[1], color[2]) - cr.text_path(text) - cr.fill() - else: - pass - texttimer.stop() - - timer.stop() - rendertimer.stop() - - - if __name__ == "__main__": gtk.gdk.threads_init() diff --git a/src/render.py b/src/render.py index f725416..4d033ff 100644 --- a/src/render.py +++ b/src/render.py @@ -15,11 +15,29 @@ # You should have received a copy of the GNU General Public License # along with kothic. If not, see . +from debug import debug, Timer import cairo +import math -class Renderer: - def __init__(self, width, height, zoom, data_projection): +def line(cr, c): + cr.move_to(c[0], c[1]) + for k in range(2, len(c), 2): + cr.line_to(c[k], c[k + 1]) + cr.stroke() + +def poly(cr, c): + cr.move_to(c[0], c[1]) + for k in range(2, len(c), 2): + cr.line_to(c[k], c[k + 1]) + cr.fill() + + + + + +class RasterTile: + def __init__(self, width, height, zoom, data_backend): self.w = width self.h = height self.surface = cairo.ImageSurface(cairo.FORMAT_RGB24, self.w, self.h) @@ -28,12 +46,14 @@ class Renderer: self.center_coord = None self.zoomlevel = zoom self.zoom = None - self.data_projection = data_projection + self.data = data_backend def screen2lonlat(self, x, y): return (x - self.w/2)/(math.cos(self.center_coord[1]*math.pi/180)*self.zoom) + self.center_coord[0], -(y - self.h/2)/self.zoom + self.center_coord[1] def lonlat2screen(self, (lon, lat)): return (lon - self.center_coord[0])*self.lcc*self.zoom + self.w/2, -(lat - self.center_coord[1])*self.zoom + self.h/2 def update_surface(self, lonlat, zoom, tilecache, style, lock = None): + rendertimer = Timer("Rendering image") + timer = Timer("Gettimg data") self.zoom = zoom self.center_coord = lonlat cr = cairo.Context(self.surface) @@ -42,48 +62,143 @@ class Renderer: cr.fill() lonmin, latmin = self.screen2lonlat(0, self.h) lonmax, latmax = self.screen2lonlat(self.w, 0) - a,d,c,b = [int(x) for x in projections.tile_by_bbox((lonmin, latmin, lonmax, latmax),self.zoomlevel, self.data_projection)] + datatimer = Timer("Asking backend and styling") + ww = [ (x, style.get_style("way", x.tags)) for x in self.data.get_vectors((lonmin,latmin,lonmax,latmax),self.zoomlevel).values()] + datatimer.stop() + ww1 = [] + for way in ww: + if way[1]: + ww1.append(way) + debug( "%s objects on screen (%s in dataset)"%(len(ww1),len(ww)) ) + ww = ww1 - #debug((latmin, lonmin, latmax, lonmax)) - debug(( a, b, c, d)) -#FIXME: add time - active_tile = set([(self.zoomlevel,i,j) for i in range(a, c+1) for j in range(b, d+1)]) - debug("Active tiles in memory: %s" % len(active_tile)) - for k in tilecache.keys(): - if k not in active_tile: - del tilecache[k] - debug("del tile: %s" % (k,)) - for k in active_tile: - if k not in tilecache: - tilecache[k] = load_tile(k) - #FIXME add time2 - ww = ways(tilecache) - debug("ways: %s" % len(ww)) if lock is not None: lock.acquire() lock.release() self.lcc = math.cos(self.center_coord[1]*math.pi/180) - ww.sort(key=lambda x: style[x.style][3]) + + + #debug(objs_by_layers) + #ww = [x[0] for x in ww] + lcc = math.cos(self.center_coord[1]*math.pi/180) for w in ww: cs = [] - for k in range(0, len(w.coords), 2): - x, y = self.lonlat2screen((w.coords[k], w.coords[k+1])); + for k in range(0, len(w[0].coords), 2): + x, y = self.lonlat2screen((w[0].coords[k], w[0].coords[k+1])); cs.append(x) cs.append(y) - w.cs = cs - for passn in range(1, 4): - debug("pass %s" % passn) - for w in ww: - stn = w.style - #if lock is not None: - #lock.acquire() - #lock.release() - if stn < len(style) and style[stn] is not None and style[stn][passn-1] is not None: - st = style[w.style][passn-1] - cr.set_line_width(st[0]) - cr.set_source_rgb(st[1][0], st[1][1], st[1][2]) - if w.type == "L": - line(cr, w.cs) - elif w.type == "P": - poly(cr, w.cs) \ No newline at end of file + w[0].cs = cs + + ww.sort(key=lambda x: x[1]["layer"]) + layers = list(set([int(x[1]["layer"]/100.) for x in ww])) + layers.sort() + objs_by_layers = {} + for layer in layers: + objs_by_layers[layer] = [] + for obj in ww: + # debug(obj) + objs_by_layers[int(obj[1]["layer"]/100.)].append(obj) + #debug ((obj[1]["layer"], obj[0].tags)) + del ww + timer.stop() + timer = Timer("Rasterizing image") + linecaps = {"butt":0, "round":1, "square":2} + linejoin = {"miter":0, "round":1, "bevel":2} + + text_rendered_at = set([(-100,-100)]) + #cr.set_antialias(2) + for layer in layers: + data = objs_by_layers[layer] + # - fill polygons + for obj in data: + if "fill-color" in obj[1]: ## TODO: fill-image + color = obj[1]["fill-color"] + cr.set_source_rgba(color[0], color[1], color[2], obj[1].get("fill-opacity", 1)) + + if not "extrude" in obj[1]: + poly(cr, obj[0].cs) + else: + line(cr, obj[0].cs) + if "extrude" in obj[1]: + hgt = obj[1]["extrude"] + c = obj[0].cs + excoords = [c[0], c[1]-hgt] + #pp = (c[0],c[1]) + cr.set_line_width (1) + for k in range(2, len(c), 2): + excoords.append(c[k]) + excoords.append(c[k + 1]-hgt) + + line(cr, [c[k],c[k+1],c[k],c[k+1]-hgt],) + poly(cr,excoords) + #line(cr, obj[0].cs) + + + + # - draw casings on layer + for obj in data: + ### Extras: casing-linecap, casing-linejoin + if "casing-width" in obj[1] or "casing-color" in obj[1]: + cr.set_dash(obj[1].get("casing-dashes",obj[1].get("dashes", []))) + cr.set_line_join(linejoin.get(obj[1].get("casing-linejoin",obj[1].get("linejoin", "round")),1)) + color = obj[1].get("casing-color", (0,0,0)) + cr.set_source_rgba(color[0], color[1], color[2], obj[1].get("casing-opacity", 1)) + ## TODO: good combining of transparent lines and casing + ## Probable solution: render casing, render way as mask and put casing with mask chopped out onto image + + + cr.set_line_width (obj[1].get("casing-width", obj[1].get("width",0)+1 )) + cr.set_line_cap(linecaps.get(obj[1].get("casing-linecap", obj[1].get("linecap", "butt")),0)) + line(cr, obj[0].cs) + # - draw line centers + for obj in data: + if "width" in obj[1] or "color" in obj[1]: + cr.set_dash(obj[1].get("dashes", [])) + cr.set_line_join(linejoin.get(obj[1].get("linejoin", "round"),1)) + color = obj[1].get("color", (0,0,0)) + cr.set_source_rgba(color[0], color[1], color[2], obj[1].get("opacity", 1)) + ## TODO: better overlapping of transparent lines. + ## Probable solution: render them (while they're of the same opacity and layer) on a temporary canvas that's merged into main later + cr.set_line_width (obj[1].get("width", 1)) + cr.set_line_cap(linecaps.get(obj[1].get("linecap", "butt"),0)) + line(cr, obj[0].cs) + # - render text labels + texttimer = Timer("Text rendering") + cr.set_line_join(1) # setting linejoin to "round" to get less artifacts on halo render + for obj in data: + if "text" in obj[1]: + + text = obj[1]["text"] + + cr.set_line_width (obj[1].get("width", 1)) + cr.set_font_size(obj[1].get("font-size", 9)) + if obj[1].get("text-position", "center") == "center": + where = self.lonlat2screen(obj[0].center) + for t in text_rendered_at: + if ((t[0]-where[0])**2+(t[1]-where[1])**2)**(0.5) < 15: + break + else: + text_rendered_at.add(where) + #debug ("drawing text: %s at %s"%(text, where)) + if "text-halo-color" in obj[1] or "text-halo-radius" in obj[1]: + cr.new_path() + cr.move_to(where[0], where[1]) + cr.set_line_width (obj[1].get("text-halo-radius", 1)) + color = obj[1].get("text-halo-color", (1.,1.,1.)) + cr.set_source_rgb(color[0], color[1], color[2]) + cr.text_path(text) + cr.stroke() + cr.new_path() + cr.move_to(where[0], where[1]) + cr.set_line_width (obj[1].get("text-halo-radius", 1)) + color = obj[1].get("text-color", (0.,0.,0.)) + cr.set_source_rgb(color[0], color[1], color[2]) + cr.text_path(text) + cr.fill() + else: ### render text along line + pass + texttimer.stop() + + timer.stop() + rendertimer.stop() \ No newline at end of file diff --git a/src/style.py b/src/style.py index de628c2..563366f 100644 --- a/src/style.py +++ b/src/style.py @@ -32,9 +32,12 @@ class Styling(): self.Selectors["node"] = [] self.Selectors["relation"] = [] if not stylefile: + self.Selectors["way"].append(StyleSelector( ( [ ( ("building",),(None) ) ] ),{"fill-color": "#f00"} )) + + if stylefile=="zzzz": ### using "builtin" styling self.Selectors["way"].append(StyleSelector( ( [ ( ("area",),("yes") ) ] ),{"fill-color": "#ff0000"} )) - self.Selectors["way"].append(StyleSelector( ( [ ( ("highway",),(None) ) ] ),{"width":1,"color":"#ff0000"} )) + self.Selectors["way"].append(StyleSelector( ( [ ( ("highway",),(None) ) ] ),{"width":1,"color":"#ff0000","text": "name", "text-position":"line"} )) self.Selectors["way"].append(StyleSelector( ( [ ( ("barrier",),(None) ) ] ),{"casing-width":1,} )) self.Selectors["way"].append(StyleSelector( ( [ ( ("highway",),("residential", "tertiary", "living_street")) ] ),{"width": 3, "color":"#ffffff", "casing-width": 5, "z-index":10} )) @@ -50,10 +53,10 @@ class Styling(): self.Selectors["way"].append(StyleSelector( ( [ ( ("waterway","natural"),("riverbank", "water") ) ] ),{"fill-color": "#002"} )) self.Selectors["way"].append(StyleSelector( ( [ ( ("waterway","natural"),("river", "stream") ) ] ),{"color": "#002"} )) self.Selectors["way"].append(StyleSelector( ( [ ( ("landuse","natural"),("grass",) ) ] ),{"fill-color": "#050",} )) - self.Selectors["way"].append(StyleSelector( ( [ ( ("highway",),("footway","pedestrian","path" )) ] ),{"width":2.5, "color":"#655", "z-index":3} )) + self.Selectors["way"].append(StyleSelector( ( [ ( ("highway",),("footway","pedestrian","path" )) ] ),{"width":2.5, "color":"#655", "dashes": [3,1],"z-index":3} )) self.Selectors["way"].append(StyleSelector( ( [ ( ("bridge",),("yes") ) ] ),{"casing-width":10} )) self.Selectors["way"].append(StyleSelector( ( [ ( ("power",),("line",)) ] ),{"width": 1, "color":"#ccc",} )) - self.Selectors["way"].append(StyleSelector( ( [ ( ("building",),(None) ) ] ),{"fill-color": "#522","z-index": 1, "text": "addr:housenumber","text-halo-radius":2} )) + self.Selectors["way"].append(StyleSelector( ( [ ( ("building",),(None) ) ] ),{"fill-color": "#522","z-index": 1, "text": "addr:housenumber","text-halo-radius":2,"extrude":10,"z-index":100} )) self.stylefile = stylefile self.useful_keys = set(["layer"]) diff --git a/src/vtiles_backend.py b/src/vtiles_backend.py index 9dd0029..03799ec 100644 --- a/src/vtiles_backend.py +++ b/src/vtiles_backend.py @@ -34,7 +34,7 @@ class Way: # left for the better times: #self.center = reduce(lambda x, y: (x[0]+y[0],x[1]+y[1]), coords) self.center = (self.center[0]/len(self.coords)*2,self.center[1]/len(self.coords)*2) - debug(self.center) + #debug(self.center) class QuadTileBackend: """ @@ -64,6 +64,7 @@ class QuadTileBackend: return {} t = {} for line in f: + #debug(line) a = line.split(" ") w = Way(a[0], [float(x) for x in a[2:]]) t[int(a[1])] = w