mirror of
https://github.com/openstreetmap/mapnik-stylesheets.git
synced 2025-07-24 09:57:57 +00:00
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:
29
livetiles/README
Normal file
29
livetiles/README
Normal 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)
|
9
livetiles/livetiles.conf.sample
Normal file
9
livetiles/livetiles.conf.sample
Normal 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
149
livetiles/livetiles.wsgi
Normal 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 /<sandbox-name>/<z>/<x>/<y>.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 >%s< 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 /<sandbox-name>/<z>/<x>/<y>.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 >%s< 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
103
livetiles/map_template.html
Normal 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
44
livetiles/render_single_tile.py
Executable 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'));
|
||||
|
Reference in New Issue
Block a user