pngtile/image.py
author Tero Marttila <terom@qmsk.net>
Fri, 03 Oct 2014 22:54:05 +0300
changeset 157 0d8674e64221
parent 154 e9eef49f4d57
child 164 e1e0c8099c8b
permissions -rw-r--r--
setup.py: use rpath to find lib/libpngtile.so
"""
    Image handling.
"""


from werkzeug import Request, Response, exceptions
from werkzeug.utils import html, redirect

import pngtile.tile
from pngtile.application import url, BaseApplication
import pypngtile

import json
import os, os.path

def dir_list (root):
    """
        Yield a series of directory items to show for the given dir
    """

    for name in os.listdir(root):
        path = os.path.join(root, name)

        # skip dotfiles
        if name.startswith('.'):
            continue
        
        # show dirs
        if os.path.isdir(path):
            if not os.access(path, os.R_OK):
                # skip inaccessible dirs
                continue

            yield name + '/'

        # examine ext
        if '.' in name:
            name_base, name_type = name.rsplit('.', 1)
        else:
            name_base = name
            name_type = None

        # show .png files with a .cache file
        if name_type in ImageApplication.IMAGE_TYPES and os.path.exists(os.path.join(root, name_base + '.cache')):
            yield name

class ImageApplication (BaseApplication):

    STYLESHEETS = (
        'https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css',
        'https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css',
        'http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css',
        '/static/pngtile/map.css',
    )

    SCRIPTS = (
        'https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js',
        'https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js',
        'http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js',
        '/static/pngtile/map.js',
    )

    def __init__ (self, tile_server=None, **opts):
        """
            http://.../ path to tileserver root
        """

        BaseApplication.__init__(self, **opts)
                
        self.tile_server = tile_server

    def render_dir_breadcrumb (self, name):
        path = []

        yield html.li(html.a(href='/', *[u"Index"]))
        
        if name:
            for part in name.split('/'):
                path.append(part)

                yield html.li(html.a(href=url('/', *path), *[part]))

    def render_dir (self, request, name, items):
        """
            request:    werkzeug.Request
            name:       /.../... url to dir
            items:      [...] items in dir
        """
        
        if name:
            title = name
        else:
            title = "Index"

        return self.render_html(
            title       = name,
            body        = (
                html.div(class_='container', *[
                    html.h1(title),
                    html.div(*[
                        html.ol(class_='breadcrumb', *self.render_dir_breadcrumb(name)),
                    ]),
                    html.div(class_='list', *[
                        html.ul(class_='list-group', *[html.li(class_='list-group-item', *[
                                html.a(href=item, *[item])
                            ]) for item in items]
                        ),
                    ]),
                ]),
            ),
        )

    def render_image (self, request, image, name):
        """
            request:    werkzeug.Request
            image:      pypngtile.Image
            name:       request path for .../.../*.png image
        """

        image_info = image.info()

        map_config = dict(
            tile_url        = '{server}/{name}?x={x}&y={y}&zoom={z}',
            tile_server     = self.tile_server.rstrip('/'),
            tile_name       = name,

            tile_size       = pngtile.tile.TILE_SIZE,
            tile_zoom       = pngtile.tile.MAX_ZOOM,
            
            image_url       = '{server}/{name}?w={w}&h={h}&x={x}&y={y}&zoom={z}',
            image_server    = self.tile_server.rstrip('/'),
            image_name      = name,
            image_width     = image_info['img_width'],
            image_height    = image_info['img_height'],
        )

        return self.render_html(
            title       = name,
            body        = (
                html.div(id='wrapper', *[
                    html.div(id='map')
                ]),
            ),
            end         = (
                html.script("""$(function() {{ map_init({map_config}); }});""".format(map_config=json.dumps(map_config))),
            ),
        )

    def handle_dir (self, request, name, path):
        """
            Generate response for directory listing.
        """

        if not request.path.endswith('/'):
            # we generate HTML with relative links
            return redirect(request.path + '/')

        items = sorted(dir_list(path))

        html = self.render_dir(request, name, items)

        return Response(html, content_type="text/html")

    def handle_image (self, request, name, path):
        """
            Generate Response for image request.
        """

        # backwards-compat redirect from frontend -> tile-server
        if all(attr in request.args for attr in ('cx', 'cy', 'w', 'h', 'zl')):
            return redirect(url(self.tile_server, name,
                w       = request.args['w'],
                h       = request.args['h'],
                x       = request.args['cx'],
                y       = request.args['cy'],
                zoom    = request.args['zl'],
            ))

        try:
            image, name = self.get_image(request.path)
        except pypngtile.Error as error:
            raise exceptions.BadRequest(str(error))

        html = self.render_image(request, image, name)

        return Response(html, content_type="text/html")

    def handle (self, request):
        """
            Handle request for an image
        """

        name, path = self.lookup_path(request.path)
        
        # determine type
        if '/' in name:
            _, name_base = name.rsplit('/', 1)
        else:
            name_base = name

        if '.' in name_base:
            name_base, name_type = name_base.rsplit('.', 1)
        else:
            name_type = None

        # determine handler
        if os.path.isdir(path):
            return self.handle_dir(request, name, path)
        
        elif name_type and name_type in self.IMAGE_TYPES:
            return self.handle_image(request, name, path)

        else:
            raise exceptions.NotFound(path)