author | Tero Marttila <terom@fixme.fi> |
Mon, 25 Jan 2010 02:49:05 +0200 | |
changeset 72 | c3f8502cfd97 |
parent 47 | 201257cbd887 |
child 89 | 02e5b9b08881 |
permissions | -rw-r--r-- |
30 | 1 |
""" |
2 |
Our WSGI web interface, which can serve the JS UI and any .png tiles via HTTP. |
|
3 |
""" |
|
4 |
||
5 |
from werkzeug import Request, Response, responder |
|
6 |
from werkzeug import exceptions |
|
42
a5bca7b0cd8a
get DATA_ROOT from os.environ, fix use of prefix for dir view
Tero Marttila <terom@fixme.fi>
parents:
40
diff
changeset
|
7 |
import os.path, os |
30 | 8 |
|
9 |
import pypngtile as pt |
|
10 |
||
42
a5bca7b0cd8a
get DATA_ROOT from os.environ, fix use of prefix for dir view
Tero Marttila <terom@fixme.fi>
parents:
40
diff
changeset
|
11 |
DATA_ROOT = os.environ.get("PNGTILE_DATA_PATH") or os.path.abspath('data/') |
30 | 12 |
|
13 |
IMAGE_CACHE = {} |
|
14 |
||
15 |
TILE_WIDTH = 256 |
|
16 |
TILE_HEIGHT = 256 |
|
17 |
||
43 | 18 |
# max. output resolution to allow |
19 |
MAX_PIXELS = 1920 * 1200 |
|
20 |
||
31
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
21 |
def dir_view (req, name, path) : |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
22 |
prefix = os.path.dirname(req.script_root).rstrip('/') |
42
a5bca7b0cd8a
get DATA_ROOT from os.environ, fix use of prefix for dir view
Tero Marttila <terom@fixme.fi>
parents:
40
diff
changeset
|
23 |
script_prefix = req.script_root |
31
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
24 |
name = name.rstrip('/') |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
25 |
|
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
26 |
|
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
27 |
return """\ |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
28 |
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
29 |
<head> |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
30 |
<title>Index of %(dir)s</title> |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
31 |
<link rel="Stylesheet" type="text/css" href="%(prefix)s/static/style.css"> |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
32 |
</head> |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
33 |
<body> |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
34 |
<h1>Index of %(dir)s</h1> |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
35 |
|
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
36 |
<ul> |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
37 |
%(listing)s |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
38 |
</ul> |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
39 |
</body> |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
40 |
</html>""" % dict( |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
41 |
prefix = prefix, |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
42 |
dir = name, |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
43 |
|
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
44 |
listing = "\n".join( |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
45 |
"""<li><a href="%(url)s">%(name)s</a></li>""" % dict( |
42
a5bca7b0cd8a
get DATA_ROOT from os.environ, fix use of prefix for dir view
Tero Marttila <terom@fixme.fi>
parents:
40
diff
changeset
|
46 |
url = '/'.join((script_prefix, name, item)), |
31
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
47 |
name = item, |
42
a5bca7b0cd8a
get DATA_ROOT from os.environ, fix use of prefix for dir view
Tero Marttila <terom@fixme.fi>
parents:
40
diff
changeset
|
48 |
) for item in ['..'] + [i for i in os.listdir(path) if i.endswith('.png') or os.path.isdir(os.path.join(path, i))] |
31
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
49 |
), |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
50 |
) |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
51 |
|
30 | 52 |
def image_view (req, image_path, image) : |
53 |
image_name = os.path.basename(image_path) |
|
54 |
||
43 | 55 |
img_width, img_height = image.info() |
56 |
||
30 | 57 |
return """\ |
58 |
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> |
|
59 |
<head> |
|
60 |
<title>%(title)s</title> |
|
61 |
<script src="%(prefix)s/static/prototype.js" type="text/javascript"></script> |
|
62 |
<script src="%(prefix)s/static/dragdrop.js" type="text/javascript"></script> |
|
63 |
<script src="%(prefix)s/static/builder.js" type="text/javascript"></script> |
|
64 |
<script src="%(prefix)s/static/tiles2.js" type="text/javascript"></script> |
|
65 |
<link rel="Stylesheet" type="text/css" href="%(prefix)s/static/style.css"> |
|
66 |
</head> |
|
67 |
<body> |
|
68 |
<div id="wrapper"> |
|
69 |
<div id="viewport" style="width: 100%%; height: 100%%"> |
|
39 | 70 |
<div class="overlay"> |
71 |
<input type="button" id="btn-zoom-in" value="Zoom In" /> |
|
72 |
<input type="button" id="btn-zoom-out" value="Zoom Out" /> |
|
40 | 73 |
<a class="link" id="lnk-image" href="#"></a> |
39 | 74 |
</div> |
75 |
||
30 | 76 |
<div class="substrate"></div> |
47
201257cbd887
add a simple Loading... background
Tero Marttila <terom@fixme.fi>
parents:
44
diff
changeset
|
77 |
|
201257cbd887
add a simple Loading... background
Tero Marttila <terom@fixme.fi>
parents:
44
diff
changeset
|
78 |
<div class="background"> |
201257cbd887
add a simple Loading... background
Tero Marttila <terom@fixme.fi>
parents:
44
diff
changeset
|
79 |
Loading... |
201257cbd887
add a simple Loading... background
Tero Marttila <terom@fixme.fi>
parents:
44
diff
changeset
|
80 |
</div> |
30 | 81 |
</div> |
82 |
</div> |
|
83 |
||
84 |
<script type="text/javascript"> |
|
43 | 85 |
var tile_source = new Source("%(tile_url)s", %(tile_width)d, %(tile_height)d, -4, 0, %(img_width)d, %(img_height)d); |
30 | 86 |
var main = new Viewport(tile_source, "viewport"); |
87 |
</script> |
|
88 |
</body> |
|
89 |
</html>""" % dict( |
|
90 |
title = image_name, |
|
91 |
prefix = os.path.dirname(req.script_root).rstrip('/'), |
|
92 |
tile_url = req.url, |
|
93 |
||
94 |
tile_width = TILE_WIDTH, |
|
95 |
tile_height = TILE_HEIGHT, |
|
43 | 96 |
|
97 |
img_width = img_width, |
|
98 |
img_height = img_height, |
|
30 | 99 |
) |
100 |
||
34 | 101 |
def scale_by_zoom (val, zoom) : |
102 |
if zoom > 0 : |
|
103 |
return val << zoom |
|
104 |
||
105 |
elif zoom > 0 : |
|
106 |
return val >> -zoom |
|
107 |
||
108 |
else : |
|
109 |
return val |
|
110 |
||
40 | 111 |
def render_tile (image, x, y, zoom, width=TILE_WIDTH, height=TILE_HEIGHT) : |
34 | 112 |
return image.tile_mem( |
40 | 113 |
width, height, |
34 | 114 |
scale_by_zoom(x, -zoom), scale_by_zoom(y, -zoom), |
115 |
zoom |
|
116 |
) |
|
30 | 117 |
|
40 | 118 |
def render_image (image, cx, cy, zoom, width, height) : |
119 |
x = scale_by_zoom(cx - width / 2, -zoom) |
|
120 |
y = scale_by_zoom(cy - height / 2, -zoom) |
|
121 |
||
43 | 122 |
# safely limit |
123 |
if width * height > MAX_PIXELS : |
|
124 |
raise exceptions.Forbidden("Image too large: %d * %d > %d" % (width, height, MAX_PIXELS)) |
|
125 |
||
40 | 126 |
return image.tile_mem( |
127 |
width, height, |
|
128 |
x, y, |
|
129 |
zoom |
|
43 | 130 |
) |
40 | 131 |
|
30 | 132 |
def handle_main (req) : |
133 |
# path to image |
|
31
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
134 |
image_name = req.path.lstrip('/') |
30 | 135 |
|
136 |
# build absolute path |
|
31
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
137 |
image_path = os.path.abspath(os.path.join(DATA_ROOT, image_name)) |
42
a5bca7b0cd8a
get DATA_ROOT from os.environ, fix use of prefix for dir view
Tero Marttila <terom@fixme.fi>
parents:
40
diff
changeset
|
138 |
|
30 | 139 |
|
140 |
# ensure the path points inside the data root |
|
141 |
if not image_path.startswith(DATA_ROOT) : |
|
31
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
142 |
raise exceptions.NotFound(image_name) |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
143 |
|
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
144 |
|
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
145 |
if os.path.isdir(image_path) : |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
146 |
return Response(dir_view(req, image_name, image_path), content_type="text/html") |
37
a6fc2c58e25b
return 404 for non-existant files
Tero Marttila <terom@fixme.fi>
parents:
34
diff
changeset
|
147 |
|
a6fc2c58e25b
return 404 for non-existant files
Tero Marttila <terom@fixme.fi>
parents:
34
diff
changeset
|
148 |
elif not os.path.exists(image_path) : |
a6fc2c58e25b
return 404 for non-existant files
Tero Marttila <terom@fixme.fi>
parents:
34
diff
changeset
|
149 |
raise exceptions.NotFound(image_name) |
31
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
150 |
|
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
151 |
elif not image_name or not image_name.endswith('.png') : |
37
a6fc2c58e25b
return 404 for non-existant files
Tero Marttila <terom@fixme.fi>
parents:
34
diff
changeset
|
152 |
raise exceptions.BadRequest("Not a PNG file") |
30 | 153 |
|
31
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
154 |
|
30 | 155 |
# get Image object |
156 |
if image_path in IMAGE_CACHE : |
|
157 |
# get from cache |
|
158 |
image = IMAGE_CACHE[image_path] |
|
159 |
||
160 |
else : |
|
161 |
# ensure exists |
|
162 |
if not os.path.exists(image_path) : |
|
31
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
163 |
raise exceptions.NotFound(image_name) |
30 | 164 |
|
165 |
# cache |
|
166 |
image = IMAGE_CACHE[image_path] = pt.Image(image_path) |
|
31
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
167 |
|
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
168 |
if image.status() == pt.CACHE_NONE : |
7eec7486a0af
dir index view, report if image not cached
Tero Marttila <terom@fixme.fi>
parents:
30
diff
changeset
|
169 |
raise exceptions.InternalServerError("Image not cached: " + image_name) |
30 | 170 |
|
171 |
# what view? |
|
172 |
if not req.args : |
|
173 |
# viewport |
|
174 |
return Response(image_view(req, image_path, image), content_type="text/html") |
|
175 |
||
40 | 176 |
elif 'w' in req.args and 'h' in req.args and 'cx' in req.args and 'cy' in req.args : |
177 |
# specific image |
|
178 |
width = int(req.args['w']) |
|
179 |
height = int(req.args['h']) |
|
180 |
cx = int(req.args['cx']) |
|
181 |
cy = int(req.args['cy']) |
|
182 |
zoom = int(req.args.get('zl', "0")) |
|
183 |
||
184 |
# yay full render |
|
185 |
return Response(render_image(image, cx, cy, zoom, width, height), content_type="image/png") |
|
186 |
||
30 | 187 |
elif 'x' in req.args and 'y' in req.args : |
188 |
# tile |
|
189 |
x = int(req.args['x']) |
|
190 |
y = int(req.args['y']) |
|
40 | 191 |
zoom = int(req.args.get('zl', "0")) |
30 | 192 |
|
193 |
# yay render |
|
34 | 194 |
return Response(render_tile(image, x, y, zoom), content_type="image/png") |
30 | 195 |
|
196 |
else : |
|
197 |
raise exceptions.BadRequest("Unknown args") |
|
198 |
||
199 |
||
200 |
@responder |
|
201 |
def application (env, start_response) : |
|
202 |
req = Request(env, start_response) |
|
203 |
||
204 |
try : |
|
205 |
return handle_main(req) |
|
206 |
||
207 |
except exceptions.HTTPException, e : |
|
208 |
return e |
|
209 |