basic caching behaviour, not use yet
authorTero Marttila <terom@fixme.fi>
Tue, 26 Jan 2010 01:26:05 +0200
changeset 103 1a6a6957197d
parent 102 a4d1e046ed3e
child 104 b5ae988c78b8
basic caching behaviour, not use yet
bin/dev-server
pngtile/handlers.py
pngtile/render.py
pngtile/wsgi.py
--- a/bin/dev-server	Mon Jan 25 22:11:10 2010 +0200
+++ b/bin/dev-server	Tue Jan 26 01:26:05 2010 +0200
@@ -6,18 +6,24 @@
 
 import pngtile.wsgi
 
-# dispatch on URL
-app = werkzeug.DispatcherMiddleware(pngtile.wsgi.application, {
-    '/static':      werkzeug.SharedDataMiddleware(NotFound(), {
-        '/':            'static',
-    }),
-})
+def main (host='0.0.0.0', port=8000) :
+    # original app
+    app = pngtile.wsgi.WSGIApplication()
 
-def main (host='0.0.0.0', port=8000) :
+    # dispatch on URL
+    # XXX: just replace with SharedDataMiddleware..
+    app = werkzeug.DispatcherMiddleware(app, {
+        '/static':      werkzeug.SharedDataMiddleware(NotFound(), {
+            '/':            'static',
+        }),
+    })
+    
+    # http server
     httpd = wsgiref.simple_server.make_server(host, port, app)
 
     print "Listening on %s:%d" % (host, port)
-
+    
+    # go
     httpd.serve_forever()
 
 if __name__ == '__main__' :
--- a/pngtile/handlers.py	Mon Jan 25 22:11:10 2010 +0200
+++ b/pngtile/handlers.py	Tue Jan 26 01:26:05 2010 +0200
@@ -11,7 +11,6 @@
 # only open each image once
 IMAGE_CACHE = {}
 
-
 ### Parse request data
 def get_path (req_path) :
     """
@@ -84,7 +83,7 @@
     return Response(render.img_html(prefix, name, image), content_type="text/html")
 
 
-def handle_img_region (req, image) :
+def handle_img_region (req, image, cache) :
     """
         Handle request for an image region
     """
@@ -98,14 +97,14 @@
     
     try :
         # yay full render
-        return Response(render.img_png_region(image, cx, cy, zoom, width, height), content_type="image/png")
+        return Response(render.img_png_region(image, cx, cy, zoom, width, height, cache), content_type="image/png")
 
     except ValueError, ex :
         # too large
         raise exceptions.Forbidden(str(ex))
 
 
-def handle_img_tile (req, image) :
+def handle_img_tile (req, image, cache) :
     """
         Handle request for image tile
     """
@@ -114,12 +113,14 @@
     x = int(req.args['x'])
     y = int(req.args['y'])
     zoom = int(req.args.get('zl', "0"))
+
+    # cache?
         
     # yay render
-    return Response(render.img_png_tile(image, x, y, zoom), content_type="image/png")
+    return Response(render.img_png_tile(image, x, y, zoom, cache), content_type="image/png")
 
 ## Dispatch req to handle_img_*
-def handle_img (req, name, path) :
+def handle_img (req, name, path, cache) :
     """
         Handle request for an image
     """
@@ -132,10 +133,10 @@
         return handle_img_viewport(req, image, name)
 
     elif 'w' in req.args and 'h' in req.args and 'cx' in req.args and 'cy' in req.args :
-        return handle_img_region(req, image)
+        return handle_img_region(req, image, cache)
 
     elif 'x' in req.args and 'y' in req.args :
-        return handle_img_tile(req, image)
+        return handle_img_tile(req, image, cache)
 
     else :
         raise exceptions.BadRequest("Unknown args")
@@ -143,7 +144,7 @@
 
 
 ## Dispatch request to handle_*
-def handle_req (req) :
+def handle_req (req, cache) :
     """
         Main request handler
     """
@@ -166,6 +167,6 @@
     
     else :
         # image
-        return handle_img(req, name, path)
+        return handle_img(req, name, path, cache)
 
 
--- a/pngtile/render.py	Mon Jan 25 22:11:10 2010 +0200
+++ b/pngtile/render.py	Tue Jan 26 01:26:05 2010 +0200
@@ -52,20 +52,6 @@
         if ext == '.png' and os.path.exists(base + '.cache') :
             yield item
 
-def scale_by_zoom (val, zoom) :
-    """
-        Scale coordinates by zoom factor
-    """
-
-    if zoom > 0 :
-        return val << zoom
-
-    elif zoom > 0 :
-        return val >> -zoom
-
-    else :
-        return val
-
 
 ### Render HTML data
 def dir_html (prefix, name, path) :
@@ -157,19 +143,90 @@
         img_height      = img_height,
     )
 
+
+# threshold to cache images on - only images with a source data region *larger* than this are cached
+CACHE_THRESHOLD = 512 * 512
+
+def scale_by_zoom (val, zoom) :
+    """
+        Scale dimension by zoom factor
+    """
+
+    if zoom > 0 :
+        return val << zoom
+
+    elif zoom > 0 :
+        return val >> -zoom
+
+    else :
+        return val
+
+### Image caching
+def check_cache_threshold (width, height, zl) :
+    """
+        Checks if a tile with the given dimensions should be cached
+    """
+
+    return (scale_by_zoom(width, -zl) * scale_by_zoom(height, -zl)) > CACHE_THRESHOLD
+
+def render_raw (image, width, height, x, y, zl) :
+    """
+        Render and return tile
+    """
+
+    return image.tile_mem(
+            width, height,
+            x, y, zl
+    )
+
+def render_cache (cache, image, width, height, x, y, zl) :
+    """
+        Perform a cached render of the given tile
+    """
+    
+    if cache :
+        # cache key
+        # XXX: need a better id for the image..
+        key = "tl_%d:%d_%d:%d:%d_%s" % (x, y, width, height, zl, id(image))
+        
+        # lookup
+        data = cache.get(key)
+
+    else :
+        # oops, no cache
+        data = None
+
+    if not data :
+        # cache miss, render
+        data = render_raw(image, width, height, x, y, zl)
+        
+        if cache :
+            # store
+            cache.add(key, data)
+    
+    # ok
+    return data
+
 ### Render PNG Data
-def img_png_tile (image, x, y, zoom) :
+def img_png_tile (image, x, y, zoom, cache) :
     """
         Render given tile, returning PNG data
     """
 
-    return image.tile_mem(
-        TILE_WIDTH, TILE_HEIGHT,
-        scale_by_zoom(x, -zoom), scale_by_zoom(y, -zoom), 
-        zoom
-    )
+    # remap coordinates by zoom
+    x = scale_by_zoom(x, -zoom)
+    y = scale_by_zoom(y, -zoom)
 
-def img_png_region (image, cx, cy, zoom, width, height) :
+    # do we want to cache this?
+    if check_cache_threshold(TILE_WIDTH, TILE_HEIGHT, zoom) :
+        # go via the cache
+        return render_cache(cache, image, TILE_WIDTH, TILE_HEIGHT, x, y, zoom)
+
+    else :
+        # just go raw
+        return render_raw(image, TILE_WIDTH, TILE_HEIGHT, x, y, zoom)
+
+def img_png_region (image, cx, cy, zoom, width, height, cache) :
     """
         Render arbitrary tile, returning PNG data
     """
@@ -180,10 +237,7 @@
     # safely limit
     if width * height > MAX_PIXELS :
         raise ValueError("Image size: %d * %d > %d" % (width, height, MAX_PIXELS))
+    
+    # these images are always cached
+    return render_cache(cache, image, width, height, x, y, zoom)
 
-    return image.tile_mem(
-        width, height,
-        x, y,
-        zoom
-    )
-
--- a/pngtile/wsgi.py	Mon Jan 25 22:11:10 2010 +0200
+++ b/pngtile/wsgi.py	Tue Jan 26 01:26:05 2010 +0200
@@ -8,19 +8,31 @@
 from pngtile import handlers
 
 
-@responder
-def application (env, start_response) :
+class WSGIApplication (object) :
     """
-        Main WSGI entry point.
-
-        This is wrapped with werkzeug, so we can return a Response object
+        Simple WSGI application invoking the werkzeug handlers
     """
 
-    req = Request(env, start_response)
-    
-    try :
-        return handlers.handle_req(req)
+    def __init__ (self, cache=None) :
+        """
+            Use given cache if any
+        """
 
-    except exceptions.HTTPException, e :
-        return e
+        self.cache = cache
 
+    @responder
+    def __call__ (self, env, start_response) :
+        """
+            Main WSGI entry point.
+
+            This is wrapped with werkzeug, so we can return a Response object
+        """
+
+        req = Request(env, start_response)
+        
+        try :
+            return handlers.handle_req(req, self.cache)
+
+        except exceptions.HTTPException, e :
+            return e
+