terom@135: """ terom@135: Raw tile handling. terom@135: """ terom@135: terom@135: from werkzeug import Request, Response, exceptions terom@165: from werkzeug.utils import redirect terom@167: import werkzeug.urls terom@135: terom@167: import pngtile.application terom@135: import pypngtile terom@135: terom@172: import datetime terom@172: terom@135: ## Coordinates terom@135: # width of a tile terom@140: TILE_SIZE = 256 terom@140: terom@140: # maximum zoom out terom@140: MAX_ZOOM = 4 terom@135: terom@135: # max. output resolution to allow terom@135: MAX_PIXELS = 1920 * 1200 terom@135: terom@135: def scale (val, zoom): terom@135: """ terom@135: Scale dimension by zoom factor terom@135: terom@135: zl > 0 -> bigger terom@135: zl < 0 -> smaller terom@135: """ terom@135: terom@135: if zoom > 0: terom@135: return val << zoom terom@135: terom@135: elif zoom < 0: terom@135: return val >> -zoom terom@135: terom@135: else: terom@135: return val terom@135: terom@135: def scale_center (val, dim, zoom): terom@135: """ terom@135: Scale value about center by zoom. terom@135: """ terom@135: terom@175: return scale(val - dim / 2, zoom) terom@135: terom@167: class TileApplication (pngtile.application.PNGTileApplication): terom@173: # age in seconds for caching an unknown-mtime image for revalidates terom@173: MIN_AGE = 10 # 10 seconds terom@173: terom@172: # age in seconds for caching a known-mtime image terom@172: MAX_AGE = 7 * 24 * 60 * 60 # 1 week terom@172: terom@165: def __init__ (self, image_server, **opts): terom@165: """ terom@165: image_server: http://.../ url to image-server frontend terom@165: """ terom@165: terom@167: super(TileApplication, self).__init__(**opts) terom@165: terom@165: self.image_server = image_server terom@135: terom@167: def image_url (self, name): terom@167: return werkzeug.urls.Href(self.image_server)(path) terom@167: terom@135: def render_region (self, request, image): terom@135: """ terom@135: Handle request for an image region terom@135: """ terom@135: terom@135: width = int(request.args['w']) terom@135: height = int(request.args['h']) terom@135: x = int(request.args['x']) terom@135: y = int(request.args['y']) terom@135: zoom = int(request.args.get('zoom', "0")) terom@135: terom@140: # safety limit terom@135: if width * height > MAX_PIXELS: terom@135: raise exceptions.BadRequest("Image size: %d * %d > %d" % (width, height, MAX_PIXELS)) terom@135: terom@140: if zoom > MAX_ZOOM: terom@140: raise exceptions.BadRequest("Image zoom: %d > %d" % (zoom, MAX_ZOOM)) terom@140: terom@144: x = scale_center(x, width, zoom) terom@144: y = scale_center(y, height, zoom) terom@135: terom@135: try: terom@135: return image.tile_mem(width, height, x, y, zoom) terom@135: terom@135: except pypngtile.Error as error: terom@167: raise exceptions.InternalServerError(str(error)) terom@135: terom@135: def render_tile (self, request, image): terom@135: """ terom@135: Handle request for image tile terom@135: """ terom@135: terom@140: width = TILE_SIZE terom@140: height = TILE_SIZE terom@140: x = int(request.args['x']) terom@140: y = int(request.args['y']) terom@135: zoom = int(request.args.get('zoom', "0")) terom@140: terom@140: if zoom > MAX_ZOOM: terom@140: raise exceptions.BadRequest("Image zoom: %d > %d" % (zoom, MAX_ZOOM)) terom@135: terom@140: x = scale(x * width, zoom) terom@140: y = scale(y * height, zoom) terom@135: terom@135: try: terom@135: return image.tile_mem(width, height, x, y, zoom) terom@135: terom@135: except pypngtile.Error as error: terom@167: raise exceptions.InternalServerError(str(error)) terom@135: terom@165: def handle_dir (self, request, name, path): terom@135: """ terom@165: Redirect to the image frontend for a non-tile request. terom@135: """ terom@165: terom@165: if not name.endswith('/'): terom@165: # avoid an additional redirect terom@165: name += '/' terom@165: terom@167: return redirect(self.image_url(name)) terom@165: terom@165: def handle_image (self, request, name, path): terom@165: """ terom@165: Redirect to the image frontend for a non-tile request. terom@165: """ terom@165: terom@167: return redirect(self.image_url(name)) terom@165: terom@165: def handle (self, request): terom@165: """ terom@165: Handle request for an image terom@165: """ terom@167: terom@167: name, path, type = self.lookup(request.path) terom@165: terom@165: # determine handler terom@165: if not type: terom@165: return self.handle_dir(request, name, path) terom@165: terom@165: elif not request.args: terom@165: return self.handle_image(request, name, path) terom@165: terom@165: elif 'w' in request.args and 'h' in request.args and 'x' in request.args and 'y' in request.args: terom@171: render_func = self.render_region terom@135: terom@140: elif 'x' in request.args and 'y' in request.args: terom@171: render_func = self.render_tile terom@135: terom@135: else: terom@135: raise exceptions.BadRequest("Unknown args") terom@171: terom@171: # handle image terom@171: image, name = self.open(request.path) terom@135: terom@171: # http caching terom@171: mtime = image.cache_mtime() terom@171: terom@172: if 't' in request.args: terom@172: try: terom@172: ttime = datetime.datetime.utcfromtimestamp(int(request.args['t'])) terom@172: except ValueError: terom@172: ttime = None terom@172: else: terom@172: ttime = None terom@172: terom@171: if request.if_modified_since and mtime == request.if_modified_since: terom@174: response = Response(status=304) terom@174: else: terom@174: # render terom@174: png = render_func(request, image) terom@171: terom@174: response = Response(png, content_type='image/png') terom@174: terom@174: # cache out terom@171: response.last_modified = mtime terom@171: terom@172: if not ttime: terom@172: # cached item may change while url stays the same terom@173: response.headers['Cache-Control'] = 'max-age={min_age:d}'.format(min_age=self.MIN_AGE) terom@172: terom@172: elif ttime == mtime: terom@172: # url will change if content changes terom@172: response.headers['Cache-Control'] = 'max-age={max_age:d}'.format(max_age=self.MAX_AGE) terom@172: terom@172: else: terom@172: # XXX: mismatch wtf terom@172: response.headers['Cache-Control'] = 'must-revalidate' terom@172: terom@171: return response terom@171: terom@171: