pngtile/image.py
author Tero Marttila <terom@qmsk.net>
Sat, 04 Oct 2014 01:04:45 +0300
changeset 169 b69ffcd1ecc7
parent 168 260aa4a05e82
child 172 73380dd6a816
permissions -rw-r--r--
bin/tile-server --threaded
"""
    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 (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(
            tiles_url       = self.tiles_url(name),

            tile_url        = '{tiles_url}?x={x}&y={y}&zoom={z}',
            tile_size       = pngtile.tile.TILE_SIZE,
            tile_zoom       = pngtile.tile.MAX_ZOOM,
            
            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