User:Bettercom/code/mtilegenerator
Jump to navigation
Jump to search
Please do not use former releases (without VERSION string), they caused a memory leakage in create_tiles().
mtilegenerator.py
#!/usr/bin/env python
# -*- coding: latin-1 -*-
import os, sys
from optparse import OptionParser
from math import pi, cos, sin, log, exp, atan
from subprocess import call
from mapnik import *
VERSION = '0.1.1'
usage = "usage: %prog [options] arg1 arg2"
p = OptionParser(usage=usage)
p.add_option('-m', '--minzoom', dest='minzoom', default=8, type='int', metavar='Z0',
help='MinZoom at which the renderer should start (default: %default)')
p.add_option('-n', '--maxzoom', dest='maxzoom', default=8, type='int', metavar='Zn',
help='MaxZoom at which the renderer should stop (default: %default)')
p.add_option('-t', '--tile', dest='is_tile', default=False, action="store_true",
help='If set, startx is the Dir of Tile and starty the name of the tilefile')
p.add_option('-a', '--startx', dest='startx', type='float', metavar='X0',
help="Where to start rendering: X (depends on tile-option!)")
p.add_option('-b', '--starty', dest='starty', type='float', metavar='Y0',
help="Where to start rendering: Y (depends on tile-option!)")
p.add_option('-x', '--endx', dest='endx', type='float', default=0.0, metavar='Xn',
help="Where to end rendering: X (depends on tile-option!)")
p.add_option('-y', '--endy', dest='endy', type='float', default=0.0, metavar='Yn',
help="Where to end rendering: Y (depends on tile-option!)")
p.add_option('-o', '--overwrite', dest='force', default=False, action="store_true",
help='When set existing tiles will be overwritten, otherwise the renderer will continue without re-rendering')
p.add_option('-f', '--file', dest='file',
help='Read list of Tiles to [re-] render from file. This implies option --tile.')
DEG_TO_RAD = pi / 180
RAD_TO_DEG = 180 / pi
PRJOPTS = ' '.join(('+proj=merc',
'+a=6378137',
'+b=6378137'
'+lat_ts=0.0'
'+lon_0=0.0'
'+x_0=0.0',
'+y_0=0',
'+k=1.0',
'+units=m',
'+nadgrids=@null',
'+no_defs +over'))
MAPFILE = os.environ['MAPNIK_MAP_FILE']
TILEDIR = os.environ['MAPNIK_TILE_DIR']
# The unconverted (larger 32bpp) Water- and Land-Tiles for comparing
WATER = file('%s/WATER0.png' % TILEDIR).read()
LAND = file('%s/LAND0.png' % TILEDIR).read()
# The converted (smaller 1bpp) Water- and Land-Tile we use for symlinks:
# XXX: Would be better if we could send a 302-redirect instead because
# client-browser would then cache the URIs of blank tiles
WLINK, LLINK = '../../WATER.png', '../../LAND.png'
# 2009-07-04: We must instantiate these mapnik-classes here on module-
# level and never in a multible called function (It's
# faster and otherwise we suffer a mem-leak :-( )
prj = Projection(PRJOPTS)
m = Map(2 * 256, 2 * 256)
load_map(m, MAPFILE)
def minmax (a,b,c):
a = max(a,b)
a = min(a,c)
return a
class GoogleProjection:
def __init__(self,levels=18):
self.Bc = []
self.Cc = []
self.zc = []
self.Ac = []
c = 256
for d in range(0,levels):
e = c/2;
self.Bc.append(c/360.0)
self.Cc.append(c/(2 * pi))
self.zc.append((e,e))
self.Ac.append(c)
c *= 2
def fromLLtoPixel(self,ll,zoom):
d = self.zc[zoom]
e = round(d[0] + ll[0] * self.Bc[zoom])
f = minmax(sin(DEG_TO_RAD * ll[1]),-0.9999,0.9999)
g = round(d[1] + 0.5*log((1+f)/(1-f))*-self.Cc[zoom])
return (e,g)
def fromPixelToLL(self,px,zoom):
e = self.zc[zoom]
f = (px[0] - e[0])/self.Bc[zoom]
g = (px[1] - e[1])/-self.Cc[zoom]
h = RAD_TO_DEG * ( 2 * atan(exp(g)) - 0.5 * pi)
return (f,h)
def get_tlist(z=9, maxZoom=13, x=0, y=0):
"""Create a list with tuples of tiles to generate"""
l = [(z, x, y)]
for _z in range(z + 1, maxZoom + 1):
_t = 2 ** (_z - z)
_s = (x * _t, x * _t + _t, y * _t, y * _t + _t)
l.extend([(_z, _x, _y) for _x in range(_s[0], _s[1])
for _y in range(_s[2], _s[3])])
return l
def get_tileuri(_t):
"""
Checks for existing dirs, creates them if necessary and returns
the full file-name for the tile _t => (z, x, y).
"""
if not os.path.isdir('%s%s' % (TILEDIR, _t[0])):
os.mkdir('%s%s' % (TILEDIR, _t[0]))
if not os.path.isdir('%s%s/%s' % (TILEDIR, _t[0], _t[1])):
os.mkdir('%s%s/%s' % (TILEDIR, _t[0], _t[1]))
return '%s%s/%s/%s.png' % (TILEDIR, _t[0], _t[1], _t[2])
def _symlink_tile(_t_uri, _t_type):
try:
os.unlink(_t_uri)
except:
pass
if _t_type == 'WATER':
os.symlink(WLINK, _t_uri)
return '(symlink to %s) ' % WLINK
elif _t_type == 'LAND':
os.symlink(LLINK, _t_uri)
return '(symlink to %s) ' % LLINK
return ''
def create_tiles(x=0, y=0, minZoom=8, maxZoom=10, force=False):
"""
Render Tile z/x/y and all childs up to zoomlevel maxZoom.
If force is True existing tiles will be overwritten
"""
gprj = GoogleProjection(maxZoom+1)
tlist = get_tlist(minZoom, maxZoom, x, y)
sys.stderr.write('%s tiles to generate, last one is %s\n' % (len(tlist), tlist[-1]))
while tlist:
tile = tlist.pop(0)
tile_uri = get_tileuri(tile)
if not force and os.path.isfile(tile_uri):
sys.stderr.write('%s exists, overwriting not forced, loop\n' % tile_uri)
continue
elif force and os.path.isfile(tile_uri):
os.unlink(tile_uri)
p0 = gprj.fromPixelToLL((tile[1] * 256.0, (tile[2] + 1) * 256.0), tile[0])
p1 = gprj.fromPixelToLL(((tile[1] + 1) * 256.0, tile[2] * 256.0), tile[0])
# render a new tile and store it on filesystem
c0 = prj.forward(Coord(p0[0], p0[1]))
c1 = prj.forward(Coord(p1[0], p1[1]))
bbox = Envelope(c0.x, c0.y, c1.x, c1.y)
bbox.width(bbox.width() * 2)
bbox.height(bbox.height() * 2)
m.zoom_to_box(bbox)
im = Image(512, 512)
render(m, im)
view = im.view(128, 128, 256, 256) # x,y,width,height
f = view.tostring('png')
if f == WATER:
_sym = _symlink_tile(tile_uri, 'WATER')
elif f == LAND:
_sym = _symlink_tile(tile_uri, 'LAND')
else:
view.save(tile_uri, 'png')
command = "convert -quiet -colors 255 %s %s" % (tile_uri, tile_uri)
r = call(command, shell=True)
_sym = ''
sys.stderr.write('%s %screated, %s childs remain\n' % (tile_uri, _sym, len(tlist)))
def render_tiles(bbox, minZoom=1, maxZoom=12, force=False):
sys.stderr.write("render_tiles(%s [%s-%s])\n" %
(bbox, minZoom, maxZoom))
# Create a list of x/y- tiles at min-Zoom-Level and
# call create_tiles for each
gprj = GoogleProjection()
_tl = gprj.fromLLtoPixel((bbox[0], bbox[3]), minZoom)
_br = gprj.fromLLtoPixel((bbox[2], bbox[1]), minZoom)
x0, y0 = int(_tl[0] / 256.0), int(_tl[1] / 256.0)
x1, y1 = int(_br[0] / 256.0), int(_br[1] / 256.0)
l = [(x, y) for x in range(x0, x1 + 1) for y in range(y0, y1 + 1)
if x < 2 ** minZoom and y < 2 ** minZoom]
while l:
t = l.pop(0)
sys.stderr.write('Create tiles (and childs) for %s (%s remain)\n' %
(t, len(l)))
create_tiles(t[0], t[1], minZoom, maxZoom, force)
def _main(opts):
if ((opts.endx and opts.endx < opts.startx) or
(opts.endy and opts.endy < opts.starty)):
sys.stderr.write('Error: EndX and/or EndY smaller than StartX/StartY\n')
sys.exit(1)
if opts.file:
try:
f = file(opts.file)
l = [l[:-1] for l in f]
f.close()
sys.stderr.write('%s tiles have to be [re-] rendered\n' % len(l))
except Exception, ex:
sys.stderr.write('Error %s reading file %s\n' % (ex, opts.file))
sys.exit()
while l:
try:
z, x, y = l.pop(0).split('/')
z, x, y = int(z), int(x), int(y)
create_tiles(x, y, z, z, opts.force)
sys.stderr.write('%s tiles remain for [re-] rendering\n' % len(l))
except Exception, ex:
sys.stderr.write('Error %s with line %s\n' % (ex, (z,x,y)))
elif opts.is_tile and opts.endx and opts.endy:
l = [(x, y) for x in range(int(opts.startx), int(opts.endx) + 1)
for y in range(int(opts.starty), int(opts.endy) + 1)]
while l:
t = l.pop(0)
sys.stderr.write('Create tiles (and childs) for %s (%s parents remain) on zoomlevel %s\n' %
(t, len(l), opts.minzoom))
create_tiles(t[0], t[1], opts.minzoom, opts.maxzoom, opts.force)
elif opts.is_tile:
print ('Call create_tiles(%i, %i, %i, %i, %s)' %
(int(opts.startx), int(opts.starty),
int(opts.minzoom), int(opts.maxzoom),
bool(opts.force)))
create_tiles(int(opts.startx), int(opts.starty),
int(opts.minzoom), int(opts.maxzoom),
bool(opts.force))
else:
print ('Call render_tiles(%s, %i, %i, %s)' %
((float(opts.startx), float(opts.starty),
float(opts.endx), float(opts.endy)),
int(opts.minzoom), int(opts.maxzoom),
bool(opts.force)))
render_tiles((float(opts.startx), float(opts.starty),
float(opts.endx), float(opts.endy)),
int(opts.minzoom), int(opts.maxzoom),
bool(opts.force))
if __name__ == "__main__":
opts = p.parse_args()[0]
_main(opts)
print "mtilegenerator finished"
Fixed version - I hope --Bettercom 18:46, 4 July 2009 (UTC) Another bugfix --Bettercom 07:17, 9 July 2009 (UTC)