pngtile/image.py
author Tero Marttila <terom@qmsk.net>
Sat, 04 Oct 2014 15:13:56 +0300
changeset 176 ad32cf3a25fa
parent 172 73380dd6a816
permissions -rw-r--r--
pngtile.store: change list() to yield name, type tuples rather than appending / to dirs
"""
    Image handling.
"""


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

import pngtile.tile
import pngtile.application
import pypngtile

import json
import os, os.path

class ImageApplication (pngtile.application.PNGTileApplication):

    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, tiles_server=None, **opts):
        """
            http://.../ path to tileserver root
        """

        super(ImageApplication, self).__init__(**opts)
                
        self.tiles_server = tiles_server

    def tiles_url (self, name, **args):
        return werkzeug.urls.Href(self.tiles_server)(name, **args)

    def render_html (self, title, body, stylesheets=None, scripts=None, end=()):
        if stylesheets is None:
            stylesheets = self.STYLESHEETS

        if scripts is None:
            scripts = self.SCRIPTS

        return html.html(lang='en', *[
            html.head(
                html.title(title),
                *[
                    html.link(rel='stylesheet', href=href) for href in stylesheets
                ]
            ),
            html.body(
                *(body + tuple(
                    html.script(src=src) for src in scripts
                ) + end)
            ),
        ])


    def render_dir_breadcrumb (self, name):
        href = werkzeug.urls.Href('/')
        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=href(*path), *[part]))

    def render_dir_item (self, name, type):
        if type:
            return html.a(href=name, *[name])
        else:
            dir = name + '/'
            return html.a(href=dir, *[dir])

    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', *[
                                self.render_dir_item(name, type) for name, type in items
                            ]) for name, type 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(
            tiles_url       = self.tiles_url(name),
            tiles_mtime     = image_info['cache_mtime'],

            tile_url        = '{tiles_url}?t={tiles_mtime}&x={x}&y={y}&zoom={z}',
            tile_size       = pngtile.tile.TILE_SIZE,
            tile_zoom       = pngtile.tile.MAX_ZOOM,
            
            # do NOT include a mtime in these urls.. we should always revalidate them
            image_url       = '{tiles_url}?w={w}&h={h}&x={x}&y={y}&zoom={z}',
            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(self.list(request.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(self.tiles_url(name,
                w       = request.args['w'],
                h       = request.args['h'],
                x       = request.args['cx'],
                y       = request.args['cy'],
                zoom    = request.args['zl'],
            ))

        image, name = self.open(request.path)

        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, type = self.lookup(request.path)
        
        # determine handler
        if not type:
            return self.handle_dir(request, name, path)
        
        else:
            return self.handle_image(request, name, path)

    @Request.application
    def __call__ (self, request):
        """
            WSGI entry point.
        """

        try:
            return self.handle(request)

        except exceptions.HTTPException as error:
            return error