livetiles is a simple live renderer for mapnik tiles

it has been tested using the mapnik style provided in
this directory as well as the mapnik-german style from
the parent directory



git-svn-id: http://svn.openstreetmap.org/applications/rendering/mapnik@28366 b9d5c4c9-76e1-0310-9c85-f3177eceb1e4
This commit is contained in:
giggls
2012-04-29 15:58:01 +00:00
parent d15f561cb8
commit d7fc8fde78
5 changed files with 334 additions and 0 deletions

29
livetiles/README Normal file
View File

@ -0,0 +1,29 @@
Simple live tile generator for style development sandboxes
(c) 2012 Sven Geggus <sven-osm@geggus.net>
livetiles.wsgi will render live mapnik tiles on request which is
handy for maintenance of mapnik styles and completely unsuitable for
a production tileserver.
In addition to rendering tiles livetiles.wsgi can also generate
slippymaps.
To enable livetiles.wsgi in apache mod_wsgi just use something like
this in your apache configuration:
WSGIScriptAlias /livetiles /path/to/livetiles.wsgi
Also copy livetiles.conf.sample to livetiles.conf and adjust the
sandbox_dir option
The sandbox should just contain a couple of directories with
different mapnik styles or symbolic links to mapnik style
directories.
a sample tile URL will then look like this:
http://<yourserver>/livetiles//<name-of-styledir>/<z>/<x>/<y>.png
A sample URL to a livetiles slippymap will look like this:
http://<yourserver>/livetiles/ (multi style map)
http://<yourserver>/livetiles/<name-of-styledir>/ (single style map)

View File

@ -0,0 +1,9 @@
[global]
# name of mapnik style
stylename=osm.xml
# directory containing available mapnik styles
sandbox_dir=/path/to/mapnik/style/sandboxes
# full path to map_template.html
map_template=/path/to/map_template.html

149
livetiles/livetiles.wsgi Normal file
View File

@ -0,0 +1,149 @@
#!/usr/bin/python
#
# Simple live tile generator for style development sandboxes
#
# (c) 2012 Sven Geggus <sven-osm@geggus.net>
#
# Do not use for for production tileservers,
# use mod_tile + Tirex or renderd instead
#
# Released under the terms of the
# GNU Affero General Public License (AGPL)
# http://www.gnu.org/licenses/agpl-3.0.html
# Version 3.0 or later
#
import math,sys,re,os,ConfigParser
try:
import mapnik2 as mapnik
except:
import mapnik
def TileToMeters(tx, ty, zoom):
initialResolution = 20037508.342789244 * 2.0 / 256.0
originShift = 20037508.342789244
tileSize = 256.0
zoom2 = (2.0**zoom)
res = initialResolution / zoom2
mx = (res*tileSize*(tx+1))-originShift
my = (res*tileSize*(zoom2-ty))-originShift
return mx, my
def TileToBBox(x,y,z):
x1,y1=TileToMeters(x-1,y+1,z)
x2,y2=TileToMeters(x,y,z)
return x1,y1,x2,y2
# generate error page if invalid URL has been called
def InvalidURL(start_response,msg,status):
html="<html><body>%s</body></html>" % msg
response_headers = [('Content-type', 'text/html'),('Content-Length', str(len(html)))]
start_response(status, response_headers)
return html
# generate slyypy map for all styles or a single style
def genSlippyMap(start_response,stylelist = []):
status = '200 OK'
if stylelist == []:
dirList=os.listdir(sandbox_dir)
stylelist=[]
for s in dirList:
if os.path.isdir(sandbox_dir+'/'+s):
stylelist.append(s)
templ = open(map_template,'r')
tdata = templ.read()
templ.close()
p = re.compile('%MAPSTYLES%')
# take advantage of the fact, that python lists
# and javascript lists use the same string representation
tdata = p.sub(str(stylelist),tdata)
response_headers = [('Content-type', 'text/html'),('Content-Length', str(len(tdata)))]
start_response(status, response_headers)
return tdata
# check if sandbox of given name does exist
def checkSandbox(name):
mapfile=sandbox_dir+"/"+name+"/"+stylename
if not os.path.exists(mapfile):
return True
return False
def application(env, start_response):
global sandbox_dir
global stylename
global map_template
status = '200 OK'
pathinfo=env['PATH_INFO']
# read configuration file
config = ConfigParser.ConfigParser()
cfgfile=os.path.dirname(env['SCRIPT_FILENAME'])+"/livetiles.conf"
config.read(cfgfile)
stylename = config.get("global","stylename")
sandbox_dir = config.get("global","sandbox_dir")
map_template = config.get("global","map_template")
# mod-wsgi gives control to our script only for /name and /name/...
# thus any length <2 should show the overview page
if len(pathinfo) < 2:
return genSlippyMap(start_response)
# check for valid tile URLs
# whitelist for sandbox names: allow characters, numbers, - and _ only
# two type of valid URLs:
# /stylename/ for slipplymap
# /stylename/z/x/y.png for tyle
m = re.match(r"^/+([a-zA-Z0-9_\-]+)/*(.*)$", pathinfo)
if m is None:
msg="Invalid URL: %s<br />should be /&lt;sandbox-name&gt;/&lt;z&gt;/&lt;x&gt;/&lt;y&gt;.png" % pathinfo
return InvalidURL(start_response,msg,'404 Invalid URL')
sandbox=m.group(1)
# check for mapnik style file in requested sandbox
if checkSandbox(sandbox):
msg="ERROR: sandbox &gt;%s&lt; does not exist!!!" % sandbox
return InvalidURL(start_response,msg,'404 Invalid sandbox')
# the remaining possibilities are SlippyMap or Tile-URL
if m.group(2) == '':
return genSlippyMap(start_response,[m.group(1)])
m = re.match(r"^([0-9]+)/+([0-9]+)/+([0-9]+).png$", m.group(2))
if m is None:
msg="Invalid URL: %s<br />should be /&lt;sandbox-name&gt;/&lt;z&gt;/&lt;x&gt;/&lt;y&gt;.png" % pathinfo
return InvalidURL(start_response,msg,'404 Invalid URL')
z=int(m.group(1))
x=int(m.group(2))
y=int(m.group(3))
# check for mapnik style file in requested sandbox
mapfile=sandbox_dir+"/"+sandbox+"/"+stylename
if not os.path.exists(mapfile):
msg="ERROR: sandbox &gt;%s&lt; does not exist!!!" % sandbox
return InvalidURL(start_response,msg,'404 Invalid sandbox')
# we have a valid Tile-URL request so just render the tile now
m = mapnik.Map(256, 256)
mapnik.load_map(m, mapfile)
bba=TileToBBox(x,y,z)
bbox=mapnik.Box2d(bba[0],bba[1],bba[2],bba[3])
m.zoom_to_box(bbox)
im = mapnik.Image(256, 256)
mapnik.render(m, im)
output = im.tostring('png')
response_headers = [('Content-type', 'image/png'),
('Content-Length', str(len(output)))]
start_response(status, response_headers)
return [output]

103
livetiles/map_template.html Normal file
View File

@ -0,0 +1,103 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
<HEAD>
<meta http-equiv="content-type" content="text/html; charset=latin9"/>
<TITLE>Mapnik map with live rendered tiles</TITLE>
<style type="text/css">
html,body {
background-color: #ffffff;
height: 100%;
width: 100%;
margin: 0; padding: 0;
font-family: Helvetica,Arial,sans-serif;
overflow: hidden;
color: #000000;
}
div#map {
height: 100%;
width: 100%;
background-color:white;
}
div#permalink {
position:absolute;
top: 25px;
left: 80px;
height: 30px;
width: 400px;
z-index:4;
}
</style>
<script type="text/javascript" src="http://openlayers.org/api/OpenLayers.js"></script>
<script type="text/javascript">
//<![CDATA[
var map;
function drawmap() {
OpenLayers.Lang.setCode('de');
var lon = 11.19815;
var lat = 49.25059;
var zoom = 6;
var proj4326 = new OpenLayers.Projection("EPSG:4326");
var projmerc = new OpenLayers.Projection("EPSG:900913");
map = new OpenLayers.Map('map', {
projection: new OpenLayers.Projection("EPSG:900913"),
displayProjection: new OpenLayers.Projection("EPSG:4326"),
controls: [
new OpenLayers.Control.Navigation(),
new OpenLayers.Control.PanZoomBar(),
new OpenLayers.Control.Permalink(),
new OpenLayers.Control.Attribution()],
maxExtent: new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34),
numZoomLevels: 19,
maxResolution: 156543,
units: 'meters'
});
// inserted by livetiles.wsgi
var mapstyles = %MAPSTYLES%;
if (window.location.pathname[window.location.pathname.length-1] != "/") {
baseurl=window.location.pathname+"/";
} else {
baseurl=window.location.pathname;
};
if (mapstyles.length >1) {
for (var i in mapstyles) {
map.addLayer(new OpenLayers.Layer.XYZ(mapstyles[i],baseurl+mapstyles[i]+'/${z}/${x}/${y}.png'));
};
ls=new OpenLayers.Control.LayerSwitcher();
map.addControl(ls);
ls.maximizeControl();
} else {
map.addLayer(new
OpenLayers.Layer.XYZ(mapstyles[0],baseurl+'${z}/${x}/${y}.png'));
}
if (!map.getCenter()) {
var lonlat = new OpenLayers.LonLat(lon, lat);
lonlat.transform(proj4326, projmerc);
map.setCenter(lonlat, zoom);
};
};
//]]>
</script>
</head>
<body onload=drawmap();>
<div id="map"></div>
</BODY>
</HTML>

44
livetiles/render_single_tile.py Executable file
View File

@ -0,0 +1,44 @@
#!/usr/bin/python
#
# render a single tile using mapnik
import math
import sys
try:
import mapnik2 as mapnik
except:
import mapnik
def TileToMeters(tx, ty, zoom):
initialResolution = 20037508.342789244 * 2.0 / 256.0
originShift = 20037508.342789244
tileSize = 256.0
zoom2 = (2.0**zoom)
res = initialResolution / zoom2
mx = (res*tileSize*(tx+1))-originShift
my = (res*tileSize*(zoom2-ty))-originShift
return mx, my
def TileToBBox(x,y,z):
x1,y1=TileToMeters(x-1,y+1,z)
x2,y2=TileToMeters(x,y,z)
return x1,y1,x2,y2
if __name__ == "__main__":
if len(sys.argv) != 5:
sys.stderr.write("usage: render_single_tile.py <stylefile> z x y\n")
sys.exit(1)
mapfile = sys.argv[1]
z=int(sys.argv[2])
x=int(sys.argv[3])
y=int(sys.argv[4])
m = mapnik.Map(256, 256)
mapnik.load_map(m, mapfile)
bba=TileToBBox(x,y,z)
bbox=mapnik.Box2d(bba[0],bba[1],bba[2],bba[3])
m.zoom_to_box(bbox)
im = mapnik.Image(256, 256)
mapnik.render(m, im)
sys.stdout.write(im.tostring('png'));