Changeable vector backends.

- added: vtile_backend
 - fixed: converter now generalizes only intresting ways
 - first steps to detach renderer
This commit is contained in:
Komяpa 2010-05-02 00:18:15 +03:00
parent 5b6c4537dc
commit 5b4174e9db
5 changed files with 223 additions and 86 deletions

4
.hgignore Normal file
View file

@ -0,0 +1,4 @@
src/data/
src/tiles/
.*~
.*.pyc

View file

@ -23,15 +23,11 @@ import string
import threading
import time
import Queue
from twms import projections
# Ucomment one of the following lines depending on current mode:
from debug import debug, Timer
#from production import debug, Timer
from vtiles_backend import QuadTileBackend as DataBackend
@ -56,7 +52,7 @@ class Renderer(threading.Thread):
break
#debug (" got request:", request)
t = Timer("Rendering screen")
res = RasterTile(request.size[0], request.size[1], request.zoomlevel, request.data_projection)
res = RasterTile(request.size[0], request.size[1], request.zoomlevel, request.data_backend)
res.update_surface(request.center_lonlat, request.zoom, self.tc, request.style)
t.stop()
comm[1].put(res)
@ -68,11 +64,12 @@ class Navigator:
self.comm = comm
self.center_coord = (27.6749791, 53.8621394)
self.width, self.height = 800, 480
self.zoomlevel = 16
self.zoomlevel = 15
self.data_projection = "EPSG:4326"
self.zoom = self.width/0.02;
self.request_d = (0,0)
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.data = DataBackend()
self.dx = 0
self.dy = 0
self.drag_x = 0
@ -125,10 +122,10 @@ class Navigator:
if self.drag:
self.dx = event.x - self.drag_x
self.dy = event.y - self.drag_y
#if((abs(self.dx) > 150 or abs(self.dy) > 150) and self.f):
# self.redraw()
if((abs(self.dx) > 150 or abs(self.dy) > 150) and self.f):
self.redraw()
# self.request_d = (self.dx, self.dy)
# self.f = False
self.f = False
widget.queue_draw()
def delete_ev(self, widget, event):
gtk.main_quit()
@ -162,10 +159,10 @@ class Navigator:
self.zoomlevel += 1
debug("Zoom in")
elif event.direction == gtk.gdk.SCROLL_DOWN:
self.zoom /= 2
self.zoomlevel -= 1
debug("Zoom out")
if self.zoomlevel >= 0: ## negative zooms are nonsense
self.zoom /= 2
self.zoomlevel -= 1
debug("Zoom out")
self.redraw()
# widget.queue_draw()
def redraw(self):
@ -174,7 +171,7 @@ class Navigator:
"""
com = MessageContainer()
com.center_lonlat = self.center_coord
com.data_projection = self.data_projection
com.data_backend = self.data
com.zoomlevel = self.zoomlevel
com.zoom = self.zoom
com.size = (self.width + self.border*2, self.height + self.border*2)
@ -191,7 +188,7 @@ class Navigator:
self.height = widget.allocation.height
self.rastertile = None
if self.rastertile is None:
self.rastertile = RasterTile(self.width + self.border*2, self.height + self.border*2, self.zoomlevel, self.data_projection)
self.rastertile = RasterTile(self.width + self.border*2, self.height + self.border*2, self.zoomlevel, self.data)
self.rastertile.update_surface(self.center_coord, self.zoom, self.tilecache, self.style, None)
nrt = None
while(not self.comm[1].empty()):
@ -244,33 +241,10 @@ def poly(cr, c):
cr.line_to(c[k], c[k + 1])
cr.fill()
def ways(t):
# return [y for x in t.itervalues() for y in x.itervalues()]
r = {}
for i in t.values():
r.update(i)
return r.values()
def load_tile(k,lock = None):
#debug("loading tile: ", k)
try:
f = open(key_to_filename(k))
except IOError:
# debug ( "Failed open: %s" % key_to_filename(k) )
return {}
t = {}
for line in f:
a = line.split(" ")
w = Way(a[0], int(a[1]), int(a[2]), map(lambda x: float(x), a[3:]))
t[w.id] = w
f.close()
if lock is not None:
lock.acquire()
lock.release()
return t
class RasterTile:
def __init__(self, width, height, zoom, data_projection):
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)
@ -279,7 +253,7 @@ class RasterTile:
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)):
@ -293,23 +267,13 @@ class RasterTile:
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)]
#debug((latmin, lonmin, latmax, lonmax))
debug(( a, b, c, d))
###########################################3
#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))
#ww = ways(tilecache)
#debug("ways: %s" % len(ww))
ww = self.data.get_vectors((lonmin,latmin,lonmax,latmax),self.zoomlevel).values()
if lock is not None:
lock.acquire()
lock.release()
@ -339,16 +303,7 @@ class RasterTile:
elif w.type == "P":
poly(cr, w.cs)
class Way:
def __init__(self, type, id, style, coords):
self.type = type
self.id = id
self.coords = coords
self.style = style
self.cs = None
def key_to_filename((z,x,y)):
return "tiles/z%s/%s/x%s/%s/y%s.vtile"%(z, x/1024, x, y/1024, y)
if __name__ == "__main__":

View file

@ -26,7 +26,7 @@ try:
except ImportError:
pass
MAXZOOM = 18
MAXZOOM = 16
proj = "EPSG:4326"
style = {}
@ -106,26 +106,6 @@ def main ():
elif elem.tag == "way":
mzoom = 1
way_simplified = {}
for zoom in xrange(MAXZOOM,-1,-1): ######## generalize a bit
# TODO: Douglas-Peucker
prev_point = curway[0]
way = [prev_point]
for point in curway:
if pix_distance(point, prev_point, zoom) > 2.:
way.append(point)
else:
DROPPED_POINTS += 1
prev_point = point
if len(way) == 1:
mzoom = zoom
#print zoom
break
if len(way) > 1:
way_simplified[zoom] = way
#print way
waytype, waynum = 0, 0
for objtype, tagset in style.iteritems():
@ -140,6 +120,25 @@ def main ():
waynum = tid
if waytype is not 0:
way_simplified = {MAXZOOM: curway}
for zoom in xrange(MAXZOOM+1,-1,-1): ######## generalize a bit
# TODO: Douglas-Peucker
prev_point = curway[0]
way = [prev_point]
for point in curway:
if pix_distance(point, prev_point, zoom) > 2.:
way.append(point)
else:
DROPPED_POINTS += 1
prev_point = point
if len(way) == 1:
mzoom = zoom
#print zoom
break
if len(way) > 1:
way_simplified[zoom] = way
#print way
for tile in tilelist_by_geometry(curway, mzoom+1):
z, x, y = tile
path = "../tiles/z%s/%s/x%s/%s/"%(z, x/1024, x, y/1024)

89
src/render.py Normal file
View file

@ -0,0 +1,89 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of kothic, the realtime map renderer.
# kothic is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# kothic is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with kothic. If not, see <http://www.gnu.org/licenses/>.
import cairo
class Renderer:
def __init__(self, width, height, zoom, data_projection):
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_projection = data_projection
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):
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)
a,d,c,b = [int(x) for x in projections.tile_by_bbox((lonmin, latmin, lonmax, latmax),self.zoomlevel, self.data_projection)]
#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])
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]));
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)

90
src/vtiles_backend.py Normal file
View file

@ -0,0 +1,90 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of kothic, the realtime map renderer.
# kothic is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# kothic is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with kothic. If not, see <http://www.gnu.org/licenses/>.
from debug import debug
from twms import projections
class Way:
def __init__(self, type, style, coords):
self.type = type
self.coords = coords
self.style = style
self.cs = None
class QuadTileBackend:
"""
A class that gives out vector data on demand.
"""
def __init__(self,max_zoom = 16,proj = "EPSG:4326", path = "tiles", lang = "ru"):
debug("Bakend created")
self.max_zoom = max_zoom # no better tiles available
self.path = path # path to tile files
self.lang = lang # map language to use
self.tiles = {} # loaded vector tiles go here
self.data_projection = proj # which projection used to cut map in tiles
self.keep_tiles = 90 # a number of tiles to cache in memory
self.tile_load_log = [] # used when selecting which tile to unload
def filename(self, (z,x,y)):
return "%s/z%s/%s/x%s/%s/y%s.vtile"%(self.path, z, x/1024, x, y/1024, y)
def load_tile(self, k):
debug("loading tile: %s"% (k,))
try:
f = open(self.filename(k))
except IOError:
debug ( "Failed open: %s" % self.filename(k) )
return {}
t = {}
for line in f:
a = line.split(" ")
w = Way(a[0], int(a[2]), [float(x) for x in a[3:]])
t[int(a[1])] = w
f.close()
return t
def collect_garbage(self):
"""
Cleans up some RAM by removing least accessed tiles.
"""
if len(self.tiles) > self.keep_tiles:
debug("Now %s tiles cached, trying to kill %s"%(len(self.tiles),len(self.tiles)-self.keep_tiles))
for tile in self.tile_load_log[0:len(self.tiles)-self.keep_tiles]:
try:
del self.tiles[tile]
self.tile_load_log.remove(tile)
debug ("killed tile: %s" % (tile,))
except KeyError, ValueError:
debug ("tile killed not by us: %s" % (tile,))
def get_vectors (self, bbox, zoom):
zoom = min(zoom, self.max_zoom) ## If requested zoom is better than the best, take the best
zoom = max(zoom, 0) ## Negative zooms are nonsense
a,d,c,b = [int(x) for x in projections.tile_by_bbox(bbox,zoom, self.data_projection)]
resp = {}
for tile in set([(zoom,i,j) for i in range(a, c+1) for j in range(b, d+1)]):
if tile not in self.tiles:
self.tiles[tile] = self.load_tile(tile)
try:
self.tile_load_log.remove(tile)
except ValueError:
pass
self.tile_load_log.append(tile)
resp.update(self.tiles[tile])
self.collect_garbage()
return resp