# HG changeset patch # User Tero Marttila # Date 1410704394 -10800 # Node ID e99dd75afa15b1e9d2d562b1a7146619014525c2 # Parent 08a0056f6175c505a60f9df359a253498a15a465 pngtile.tile: separate Application dedicated to PNG serving diff -r 08a0056f6175 -r e99dd75afa15 bin/tile-server --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/tile-server Sun Sep 14 17:19:54 2014 +0300 @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +""" + Development server for pngtile.tile serving. +""" + +import argparse +import pngtile.tile +import werkzeug.serving + +def main (): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--listen', metavar='ADDR', default='0.0.0.0', + help="Listen on address") + parser.add_argument('--port', metavar='PORT', type=int, default=8080, + help="Listen on port") + + parser.add_argument('--reload', action='store_true', + help="Reload") + parser.add_argument('--debugger', action='store_true', + help="Debugger") + + parser.add_argument('image_root', metavar='PATH', + help="Path to images") + + args = parser.parse_args() + + application = pngtile.tile.Application( + image_root = args.image_root, + ) + + werkzeug.serving.run_simple(args.listen, args.port, application, + use_reloader = args.reload, + use_debugger = args.debugger, + ) + +if __name__ == '__main__': + main() diff -r 08a0056f6175 -r e99dd75afa15 bin/tile.wsgi --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/tile.wsgi Sun Sep 14 17:19:54 2014 +0300 @@ -0,0 +1,5 @@ +import pngtile.tile + +application = pngtile.tile.Application( + image_root = './var', +) diff -r 08a0056f6175 -r e99dd75afa15 pngtile/tile.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pngtile/tile.py Sun Sep 14 17:19:54 2014 +0300 @@ -0,0 +1,176 @@ +""" + Raw tile handling. +""" + +import os.path + +from werkzeug import Request, Response, exceptions + +import pypngtile + +## Coordinates +# width of a tile +TILE_WIDTH = 256 +TILE_HEIGHT = 256 + +# max. output resolution to allow +MAX_PIXELS = 1920 * 1200 + +def scale (val, zoom): + """ + Scale dimension by zoom factor + + zl > 0 -> bigger + zl < 0 -> smaller + """ + + if zoom > 0: + return val << zoom + + elif zoom < 0: + return val >> -zoom + + else: + return val + +def scale_center (val, dim, zoom): + """ + Scale value about center by zoom. + """ + + return scale(val - dim / 2, zoom) + +class Application (object): + def __init__ (self, image_root): + if not os.path.isdir(image_root) : + raise Exception("Given image_root does not exist: {image_root}".format(image_root=image_root)) + + self.image_root = os.path.abspath(image_root) + + self.image_cache = { } + + def lookup_image (self, url): + """ + Lookup image by request path. + + Returns image_name, image_path. + """ + + if not os.path.isdir(self.image_root): + raise exceptions.InternalServerError("Server image_root has gone missing") + + # path to image + name = url.lstrip('/') + + # build absolute path + path = os.path.abspath(os.path.join(self.image_root, name)) + + # ensure the path points inside the data root + if not path.startswith(self.image_root): + raise exceptions.NotFound(name) + + return name, path + + def get_image (self, url): + """ + Return Image object. + """ + + name, path = self.lookup_image(url) + + # get Image object + image = self.image_cache.get(path) + + if not image: + # open + image = pypngtile.Image(path) + + # check + if image.status() not in (pypngtile.CACHE_FRESH, pypngtile.CACHE_STALE): + raise exceptions.InternalServerError("Image cache not available: {name}".format(name=name)) + + # load + image.open() + + # cache + self.image_cache[path] = image + + return image + + def render_region (self, request, image): + """ + Handle request for an image region + """ + + width = int(request.args['w']) + height = int(request.args['h']) + x = int(request.args['x']) + y = int(request.args['y']) + zoom = int(request.args.get('zoom', "0")) + + # safely limit + if width * height > MAX_PIXELS: + raise exceptions.BadRequest("Image size: %d * %d > %d" % (width, height, MAX_PIXELS)) + + x = scale(x, zoom) + y = scale(y, zoom) + + try: + return image.tile_mem(width, height, x, y, zoom) + + except pypngtile.Error as error: + raise exceptions.BadRequest(str(error)) + + def render_tile (self, request, image): + """ + Handle request for image tile + """ + + width = TILE_WIDTH + height = TILE_HEIGHT + row = int(request.args['row']) + col = int(request.args['col']) + zoom = int(request.args.get('zoom', "0")) + + x = scale(row * width, zoom) + y = scale(col * height, zoom) + + try: + return image.tile_mem(width, height, x, y, zoom) + + except pypngtile.Error as error: + raise exceptions.BadRequest(str(error)) + + def handle (self, request): + """ + Handle request for an image + """ + + try: + image = self.get_image(request.path) + except pypngtile.Error as error: + raise exceptions.BadRequest(str(error)) + + if 'w' in request.args and 'h' in request.args and 'x' in request.args and 'y' in request.args: + png = self.render_region(request, image) + + elif 'row' in request.args and 'col' in request.args: + png = self.render_tile(request, image) + + else: + raise exceptions.BadRequest("Unknown args") + + return Response(png, content_type='image/png') + + @Request.application + def __call__ (self, request): + """ + WSGI entry point. + """ + + try: + return self.handle(request) + + except exceptions.HTTPException as error: + return error +