Esempio n. 1
0
def _calculate_cost(gene: TileGeneration, layer_name: str,
                    options: Namespace) -> Tuple[float, timedelta, float, int]:
    nb_metatiles = {}
    nb_tiles = {}
    config = gene.get_config(options.config)
    layer = config.config["layers"][layer_name]

    meta = layer["meta"]
    if options.cost_algo == "area":
        tile_size = config.config["grids"][layer["grid"]]["tile_size"]
        for zoom, resolution in enumerate(
                config.config["grids"][layer["grid"]]["resolutions"]):
            if "min_resolution_seed" in layer and resolution < layer[
                    "min_resolution_seed"]:
                continue

            print(f"Calculate zoom {zoom}.")

            px_buffer = layer["px_buffer"] + layer["meta_buffer"] if meta else 0
            m_buffer = px_buffer * resolution
            if meta:
                size = tile_size * layer["meta_size"] * resolution
                meta_buffer = size * 0.7 + m_buffer
                meta_geom = gene.get_geoms(config, layer_name)[zoom].buffer(
                    meta_buffer, 1)
                nb_metatiles[zoom] = int(round(meta_geom.area / size**2))
            size = tile_size * resolution
            tile_buffer = size * 0.7 + m_buffer
            geom = gene.get_geoms(config,
                                  layer_name)[zoom].buffer(tile_buffer, 1)
            nb_tiles[zoom] = int(round(geom.area / size**2))

    elif options.cost_algo == "count":
        gene.init_tilecoords(config, layer_name)
        gene.add_geom_filter()

        if meta:

            def count_metatile(tile: Tile) -> Tile:
                if tile:
                    if tile.tilecoord.z in nb_metatiles:
                        nb_metatiles[tile.tilecoord.z] += 1
                    else:
                        nb_metatiles[tile.tilecoord.z] = 1
                return tile

            gene.imap(count_metatile)

            class MetaTileSplitter(TileStore):
                """Convert the metatile flow to tile flow."""
                @staticmethod
                def get(tiles: Iterable[Tile]) -> Iterator[Tile]:
                    for metatile in tiles:
                        for tilecoord in metatile.tilecoord:
                            yield Tile(tilecoord)

            gene.add_metatile_splitter(MetaTileSplitter())

            # Only keep tiles that intersect geometry
            gene.add_geom_filter()

        def count_tile(tile: Tile) -> Tile:
            if tile:
                if tile.tilecoord.z in nb_tiles:
                    nb_tiles[tile.tilecoord.z] += 1
                else:
                    print(f"Calculate zoom {tile.tilecoord.z}.")
                    nb_tiles[tile.tilecoord.z] = 1
            return tile

        gene.imap(count_tile)

        run = Run(gene, gene.functions_metatiles)
        assert gene.tilestream
        for tile in gene.tilestream:
            tile.metadata["layer"] = layer_name
            run(tile)

    times = {}
    print()
    for z, nb_metatile in nb_metatiles.items():
        print(f"{nb_metatile} meta tiles in zoom {z}.")
        times[z] = layer["cost"]["metatile_generation_time"] * nb_metatile

    price: float = 0
    all_size: float = 0
    all_time: float = 0
    all_tiles = 0
    for z, nb_tile in nb_tiles.items():
        print()
        print(f"{nb_tile} tiles in zoom {z}.")
        all_tiles += nb_tile
        if meta:
            time = times[z] + layer["cost"]["tile_generation_time"] * nb_tile
        else:
            time = layer["cost"]["tileonly_generation_time"] * nb_tile
        size = layer["cost"]["tile_size"] * nb_tile
        all_size += size

        all_time += time
        td = timedelta(milliseconds=time)
        print(f"Time to generate: {duration_format(td)} [d h:mm:ss]")
        c = gene.get_main_config(
        ).config["cost"]["s3"]["put"] * nb_tile / 1000.0
        price += c
        print(f"S3 PUT: {c:0.2f} [$]")

        if "sqs" in gene.get_main_config().config:
            if meta:
                nb_sqs = nb_metatiles[z] * 3
            else:
                nb_sqs = nb_tile * 3
            c = nb_sqs * gene.get_main_config(
            ).config["cost"]["sqs"]["request"] / 1000000.0
            price += c
            print(f"SQS usage: {c:0.2f} [$]")

    print()
    td = timedelta(milliseconds=all_time)
    print(f"Number of tiles: {all_tiles}")
    print(f"Generation time: {duration_format(td)} [d h:mm:ss]")
    print(f"Generation cost: {price:0.2f} [$]")

    return all_size, td, price, all_tiles
Esempio n. 2
0
class Server:
    def __init__(self, config_file):
        self.filters = {}
        self.max_zoom_seed = {}

        logger.info("Config file: '{}'".format(config_file))
        self.tilegeneration = TileGeneration(config_file)

        self.expires_hours = self.tilegeneration.config['apache']['expires']
        self.static_allow_extension = self.tilegeneration.config['server']['static_allow_extension'] \
            if 'static_allow_extension' in self.tilegeneration.config['server'] \
            else ['jpeg', 'png', 'xml', 'js', 'html', 'css']

        self.cache = self.tilegeneration.caches[
            self.tilegeneration.config['server']['cache'] if 'cache' in
            self.tilegeneration.config['server'] else self.tilegeneration.
            config['generation']['default_cache']]

        if self.cache['type'] == 's3':  # pragma: no cover
            client = tilecloud.store.s3.get_client(self.cache.get('host'))
            bucket = self.cache['bucket']

            def _get(self, path, **kwargs):
                key_name = os.path.join('{folder}'.format(**self.cache), path)
                try:
                    response = client.get_object(Bucket=bucket, Key=key_name)
                    return response['Body'].read(), response.get('ContentType')
                except Exception:
                    client = tilecloud.store.s3.get_client(
                        self.cache.get('host'))
                    response = client.get_object(Bucket=bucket, Key=key_name)
                    return response['Body'].read(), response.get('ContentType')
        else:
            folder = self.cache['folder'] or ''

            def _get(self, path, **kwargs):
                if path.split(
                        '.'
                )[-1] not in self.static_allow_extension:  # pragma: no cover
                    return self.error(403, "Extension not allowed",
                                      **kwargs), None
                p = os.path.join(folder, path)
                if not os.path.isfile(p):  # pragma: no cover
                    return self.error(404, path + " not found", **kwargs), None
                with open(p, 'rb') as file:
                    data = file.read()
                mime = mimetypes.guess_type(p)
                return data, mime[0]

        # get capabilities or other static files
        self._get = types.MethodType(_get, self)

        mapcache_base = self.tilegeneration.config['server']['mapcache_base'] if \
            'mapcache_base' in self.tilegeneration.config['server'] else \
            'http://localhost/'
        self.mapcache_baseurl = mapcache_base + self.tilegeneration.config[
            'mapcache']['location'] + '/wmts'
        self.mapcache_header = self.tilegeneration.config['server']['mapcache_headers'] if \
            'mapcache_headers' in self.tilegeneration.config['server'] else {}

        geoms_redirect = bool(self.tilegeneration.config['server']['geoms_redirect']) if \
            'geoms_redirect' in self.tilegeneration.config['server'] else False

        self.layers = self.tilegeneration.config['server']['layers'] if \
            'layers' in self.tilegeneration.config['server'] else \
            self.tilegeneration.layers.keys()
        self.stores = {}
        for layer_name in self.layers:
            layer = self.tilegeneration.layers[layer_name]

            # build geoms redirect
            if geoms_redirect:
                self.filters[
                    layer_name] = self.tilegeneration.get_geoms_filter(
                        layer=layer,
                        grid=layer['grid_ref'],
                        geoms=self.tilegeneration.get_geoms(
                            layer,
                            extent=layer['bbox']
                            if 'bbox' in layer else layer['grid_ref']['bbox'],
                        ),
                    )

            if 'min_resolution_seed' in layer:
                max_zoom_seed = -1
                for zoom, resolution in enumerate(
                        layer['grid_ref']['resolutions']):
                    if resolution > layer['min_resolution_seed']:
                        max_zoom_seed = zoom
                self.max_zoom_seed[layer_name] = max_zoom_seed
            else:
                self.max_zoom_seed[layer_name] = 999999

            # build stores
            store_defs = [{
                'ref': [layer_name],
                'dimensions': {},
            }]
            for dimension in layer['dimensions']:
                new_store_defs = []
                for store_def in store_defs:
                    for value in dimension['values']:
                        dimensions = {}
                        dimensions.update(store_def['dimensions'])
                        dimensions[dimension['name']] = value
                        new_store_defs.append({
                            'ref': store_def['ref'] + [value],
                            'dimensions': dimensions,
                        })
                store_defs = new_store_defs
            for store_def in store_defs:
                self.stores['/'.join(store_def['ref'])] = \
                    self.tilegeneration.get_store(self.cache, layer, read_only=True)

    def __call__(self, environ, start_response):
        params = {}
        for key, value in parse_qs(environ['QUERY_STRING'], True).items():
            params[key.upper()] = value[0]

        path = None if len(params) > 0 else environ['PATH_INFO'][1:].split('/')

        return self.serve(path, params, start_response=start_response)

    def serve(self, path, params, **kwargs):
        dimensions = []
        metadata = {}

        if path:
            if len(path) >= 1 and path[0] == 'static':
                body, mime = self._get('/'.join(path[1:]), **kwargs)
                if mime is not None:
                    return self.response(
                        body, {
                            'Content-Type':
                            mime,
                            'Expires':
                            (datetime.datetime.utcnow() + datetime.timedelta(
                                hours=self.expires_hours)).isoformat(),
                            'Cache-Control':
                            "max-age={}".format((3600 * self.expires_hours)),
                            'Access-Control-Allow-Origin':
                            '*',
                            'Access-Control-Allow-Methods':
                            'GET',
                        }, **kwargs)
                else:  # pragma: no cover
                    return body
            elif len(path) >= 1 and path[0] != 'wmts':  # pragma: no cover
                return self.error(
                    404,
                    "Type '{}' don't exists, allows values: 'wmts' or 'static'"
                    .format(path[0]), **kwargs)
            path = path[1:]  # remove type

            if len(path) == 2 and path[0] == '1.0.0' and path[1].lower(
            ) == 'wmtscapabilities.xml':
                params['SERVICE'] = 'WMTS'
                params['VERSION'] = '1.0.0'
                params['REQUEST'] = 'GetCapabilities'
            elif len(path) < 7:
                return self.error(400, "Not enough path", **kwargs)
            else:
                params['SERVICE'] = 'WMTS'
                params['VERSION'] = path[0]

                params['LAYER'] = path[1]
                params['STYLE'] = path[2]

                if params['LAYER'] in self.layers:
                    layer = self.tilegeneration.layers[params['LAYER']]
                else:
                    return self.error(
                        400, "Wrong Layer '{}'".format(params['LAYER']),
                        **kwargs)

                index = 3
                dimensions = path[index:index + len(layer['dimensions'])]
                for dimension in layer['dimensions']:
                    metadata["dimension_" + dimension['name']] = path[index]
                    params[dimension['name'].upper()] = path[index]
                    index += 1

                last = path[-1].split('.')
                if len(path) < index + 4:  # pragma: no cover
                    return self.error(400, "Not enough path", **kwargs)
                params['TILEMATRIXSET'] = path[index]
                params['TILEMATRIX'] = path[index + 1]
                params['TILEROW'] = path[index + 2]
                if len(path) == index + 4:
                    params['REQUEST'] = 'GetTile'
                    params['TILECOL'] = last[0]
                    if last[1] != layer['extension']:  # pragma: no cover
                        return self.error(
                            400, "Wrong extension '{}'".format(last[1]),
                            **kwargs)
                elif len(path) == index + 6:
                    params['REQUEST'] = 'GetFeatureInfo'
                    params['TILECOL'] = path[index + 3]
                    params['I'] = path[index + 4]
                    params['J'] = last[0]
                    params['INFO_FORMAT'] = layer.get(
                        'info_formats', ['application/vnd.ogc.gml'])[0]
                else:  # pragma: no cover
                    return self.error(400, "Wrong path length", **kwargs)

                params['FORMAT'] = layer['mime_type']
        else:
            if \
                    'SERVICE' not in params or \
                    'REQUEST' not in params or \
                    'VERSION' not in params:
                return self.error(400,
                                  "Not all required parameters are present",
                                  **kwargs)

        if params['SERVICE'] != 'WMTS':
            return self.error(400,
                              "Wrong Service '{}'".format(params['SERVICE']),
                              **kwargs)
        if params['VERSION'] != '1.0.0':
            return self.error(400,
                              "Wrong Version '{}'".format(params['VERSION']),
                              **kwargs)

        if params['REQUEST'] == 'GetCapabilities':
            wmtscapabilities_file = self.cache['wmtscapabilities_file']
            body, mime = self._get(wmtscapabilities_file, **kwargs)
            if mime is not None:
                return self.response(
                    body,
                    headers={
                        'Content-Type':
                        "application/xml",
                        'Expires':
                        (datetime.datetime.utcnow() + datetime.timedelta(
                            hours=self.expires_hours)).isoformat(),
                        'Cache-Control':
                        "max-age={}".format((3600 * self.expires_hours)),
                        'Access-Control-Allow-Origin':
                        '*',
                        'Access-Control-Allow-Methods':
                        'GET',
                    },
                    **kwargs)
            else:  # pragma: no cover
                return body

        if \
                'FORMAT' not in params or \
                'LAYER' not in params or \
                'TILEMATRIXSET' not in params or \
                'TILEMATRIX' not in params or \
                'TILEROW' not in params or \
                'TILECOL' not in params:  # pragma: no cover
            return self.error(400, "Not all required parameters are present",
                              **kwargs)

        if not path:
            if params['LAYER'] in self.layers:
                layer = self.tilegeneration.layers[params['LAYER']]
            else:
                return self.error(400,
                                  "Wrong Layer '{}'".format(params['LAYER']),
                                  **kwargs)

            for dimension in layer['dimensions']:
                value = params[dimension['name'].upper()] \
                    if dimension['name'].upper() in params \
                    else dimension['default']
                dimensions.append(value)
                metadata["dimension_" + dimension['name']] = value

        if params['STYLE'] != layer['wmts_style']:
            return self.error(400, "Wrong Style '{}'".format(params['STYLE']),
                              **kwargs)
        if params['TILEMATRIXSET'] != layer['grid']:
            return self.error(
                400,
                "Wrong TileMatrixSet '{}'".format(params['TILEMATRIXSET']),
                **kwargs)

        tile = Tile(
            TileCoord(
                # TODO fix for matrix_identifier = resolution
                int(params['TILEMATRIX']),
                int(params['TILECOL']),
                int(params['TILEROW']),
            ),
            metadata=metadata)

        if params['REQUEST'] == 'GetFeatureInfo':
            if \
                    'I' not in params or \
                    'J' not in params or \
                    'INFO_FORMAT' not in params:  # pragma: no cover
                return self.error(400,
                                  "Not all required parameters are present",
                                  **kwargs)
            if 'query_layers' in layer:
                return self.forward(layer['url'] + '?' + urlencode(
                    {
                        'SERVICE': 'WMS',
                        'VERSION': layer['version'],
                        'REQUEST': 'GetFeatureInfo',
                        'LAYERS': layer['layers'],
                        'QUERY_LAYERS': layer['query_layers'],
                        'STYLES': params['STYLE'],
                        'FORMAT': params['FORMAT'],
                        'INFO_FORMAT': params['INFO_FORMAT'],
                        'WIDTH': layer['grid_ref']['tile_size'],
                        'HEIGHT': layer['grid_ref']['tile_size'],
                        'SRS': layer['grid_ref']['srs'],
                        'BBOX': layer['grid_ref']['obj'].extent(
                            tile.tilecoord),
                        'X': params['I'],
                        'Y': params['J'],
                    }),
                                    no_cache=True,
                                    **kwargs)
            else:  # pragma: no cover
                return self.error(
                    400, "Layer '{}' not queryable".format(layer['name']),
                    **kwargs)

        if params['REQUEST'] != 'GetTile':
            return self.error(400,
                              "Wrong Request '{}'".format(params['REQUEST']),
                              **kwargs)

        if params['FORMAT'] != layer['mime_type']:
            return self.error(400,
                              "Wrong Format '{}'".format(params['FORMAT']),
                              **kwargs)

        if tile.tilecoord.z > self.max_zoom_seed[
                layer['name']]:  # pragma: no cover
            return self.forward(self.mapcache_baseurl + '?' +
                                urlencode(params),
                                headers=self.mapcache_header,
                                **kwargs)

        if layer['name'] in self.filters:
            layer_filter = self.filters[layer['name']]
            meta_size = layer['meta_size']
            meta_tilecoord = TileCoord(
                # TODO fix for matrix_identifier = resolution
                tile.tilecoord.z,
                tile.tilecoord.x / meta_size * meta_size,
                tile.tilecoord.y / meta_size * meta_size,
                meta_size,
            ) if meta_size != 1 else tile.tilecoord
            if not layer_filter.filter_tilecoord(
                    meta_tilecoord):  # pragma: no cover
                return self.forward(self.mapcache_baseurl + '?' +
                                    urlencode(params),
                                    headers=self.mapcache_header,
                                    **kwargs)

        store_ref = '/'.join([params['LAYER']] + list(dimensions))
        if store_ref in self.stores:  # pragma: no cover
            store = self.stores[store_ref]
        else:  # pragma: no cover
            return self.error(
                400, "No store found for layer '{}' and dimensions {}".format(
                    layer['name'], ', '.join(dimensions)), **kwargs)

        tile = store.get_one(tile)
        if tile:
            if tile.error:
                return self.error(500, tile.error, **kwargs)

            return self.response(
                tile.data,
                headers={
                    'Content-Type':
                    tile.content_type,
                    'Expires':
                    (datetime.datetime.utcnow() +
                     datetime.timedelta(hours=self.expires_hours)).isoformat(),
                    'Cache-Control':
                    "max-age={}".format((3600 * self.expires_hours)),
                    'Access-Control-Allow-Origin':
                    '*',
                    'Access-Control-Allow-Methods':
                    'GET',
                },
                **kwargs)
        else:
            return self.error(204, **kwargs)

    def forward(self, url, headers=None, no_cache=False, **kwargs):
        if headers is None:
            headers = {}
        if no_cache:
            headers['Cache-Control'] = 'no-cache'
            headers['Pragma'] = 'no-cache'

        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            response_headers = response.headers.copy()
            if no_cache:
                response_headers['Cache-Control'] = 'no-cache, no-store'
                response_headers['Pragma'] = 'no-cache'
            else:  # pragma: no cover
                response_headers['Expires'] = (
                    datetime.datetime.utcnow() +
                    datetime.timedelta(hours=self.expires_hours)).isoformat()
                response_headers['Cache-Control'] = "max-age={}".format(
                    (3600 * self.expires_hours))
                response_headers['Access-Control-Allow-Origin'] = '*'
                response_headers['Access-Control-Allow-Methods'] = 'GET'
            return self.response(response.content,
                                 headers=response_headers,
                                 **kwargs)
        else:  # pragma: no cover
            message = "The URL '{}' return '{} {}', content:\n{}".format(
                url, response.status_code, response.reason, response.text)
            logger.warning(message)
            return self.error(502, message=message, **kwargs)

    HTTP_MESSAGES = {
        204: '204 No Content',
        400: '400 Bad Request',
        403: '403 Forbidden',
        404: '404 Not Found',
        502: '502 Bad Gateway',
    }

    def error(self, code, message='', **kwargs):
        kwargs['start_response'](self.HTTP_MESSAGES[code], [])
        return [message]

    @staticmethod
    def response(data, headers=None, **kwargs):
        if headers is None:  # pragma: no cover
            headers = {}
        headers['Content-Length'] = str(len(data))
        kwargs['start_response']('200 OK', headers.items())
        return [data]
Esempio n. 3
0
class Server:

    def __init__(self, config_file):
        self.filters = {}
        self.max_zoom_seed = {}

        logger.info("Config file: '%s'" % config_file)
        self.tilegeneration = TileGeneration(config_file)

        self.expires_hours = self.tilegeneration.config['apache']['expires']
        self.static_allow_extension = self.tilegeneration.config['server']['static_allow_extension'] \
            if 'static_allow_extension' in self.tilegeneration.config['server'] \
            else ['jpeg', 'png', 'xml', 'js', 'html', 'css']

        self.cache = self.tilegeneration.caches[
            self.tilegeneration.config['server']['cache'] if
            'cache' in self.tilegeneration.config['server'] else
            self.tilegeneration.config['generation']['default_cache']
        ]

        if self.cache['type'] == 's3':  # pragma: no cover
            s3bucket = S3Connection().bucket(self.cache['bucket'])

            def _get(self, path, **kwargs):
                global s3bucket
                try:
                    s3key = s3bucket.key(os.path.join('%(folder)s' % self.cache, path))
                    responce = s3key.get()
                    return responce.body, responce.headers['Content-Type']
                except:
                    s3bucket = S3Connection().bucket(self.cache['bucket'])
                    s3key = s3bucket.key(os.path.join('%(folder)s' % self.cache, path))
                    responce = s3key.get()
                    return responce.body, responce.headers['Content-Type']
        else:
            folder = self.cache['folder'] or ''

            def _get(self, path, **kwargs):
                if path.split('.')[-1] not in self.static_allow_extension:  # pragma: no cover
                    return self.error(403, "Extension not allowed", **kwargs), None
                p = os.path.join(folder, path)
                if not os.path.isfile(p):  # pragma: no cover
                    return self.error(404, path + " not found", **kwargs), None
                with open(p, 'rb') as file:
                    data = file.read()
                mime = mimetypes.guess_type(p)
                return data, mime[0]
        # get capabilities or other static files
        self._get = types.MethodType(_get, self)

        mapcache_base = self.tilegeneration.config['server']['mapcache_base'] if \
            'mapcache_base' in self.tilegeneration.config['server'] else \
            'http://localhost/'
        self.mapcache_baseurl = mapcache_base + self.tilegeneration.config['mapcache']['location'] + '/wmts'
        self.mapcache_header = self.tilegeneration.config['server']['mapcache_headers'] if \
            'mapcache_headers' in self.tilegeneration.config['server'] else {}

        geoms_redirect = bool(self.tilegeneration.config['server']['geoms_redirect']) if \
            'geoms_redirect' in self.tilegeneration.config['server'] else False

        self.layers = self.tilegeneration.config['server']['layers'] if \
            'layers' in self.tilegeneration.config['server'] else \
            self.tilegeneration.layers.keys()
        self.stores = {}
        for layer_name in self.layers:
            layer = self.tilegeneration.layers[layer_name]

            # build geoms redirect
            if geoms_redirect:
                self.filters[layer_name] = self.tilegeneration.get_geoms_filter(
                    layer=layer,
                    grid=layer['grid_ref'],
                    geoms=self.tilegeneration.get_geoms(
                        layer,
                        extent=layer['bbox'] if 'bbox' in layer else layer['grid_ref']['bbox'],
                    ),
                )

            if 'min_resolution_seed' in layer:
                max_zoom_seed = -1
                for zoom, resolution in enumerate(layer['grid_ref']['resolutions']):
                    if resolution > layer['min_resolution_seed']:
                        max_zoom_seed = zoom
                self.max_zoom_seed[layer_name] = max_zoom_seed
            else:
                self.max_zoom_seed[layer_name] = 999999

            # build stores
            store_defs = [{
                'ref': [layer_name],
                'dimensions': {},
            }]
            for dimension in layer['dimensions']:
                new_store_defs = []
                for store_def in store_defs:
                    for value in dimension['values']:
                        dimensions = {}
                        dimensions.update(store_def['dimensions'])
                        dimensions[dimension['name']] = value
                        new_store_defs.append({
                            'ref': store_def['ref'] + [value],
                            'dimensions': dimensions,
                        })
                store_defs = new_store_defs
            for store_def in store_defs:
                self.stores['/'.join(store_def['ref'])] = \
                    self.tilegeneration.get_store(self.cache, layer, store_def['dimensions'], read_only=True)

    def __call__(self, environ, start_response):
        params = {}
        for key, value in parse_qs(environ['QUERY_STRING'], True).items():
            params[key.upper()] = value[0]

        path = None if len(params) > 0 else environ['PATH_INFO'][1:].split('/')

        return self.serve(path, params, start_response=start_response)

    def serve(self, path, params, **kwargs):
        dimensions = []

        if path is not None:
            if len(path) >= 1 and path[0] == 'static':
                body, mime = self._get('/'.join(path[1:]), **kwargs)
                if mime is not None:
                    return self.responce(body, {
                        'Content-Type': mime,
                        'Expires': (
                            datetime.datetime.utcnow() +
                            datetime.timedelta(hours=self.expires_hours)
                        ).isoformat(),
                        'Cache-Control': "max-age=%i" % (3600 * self.expires_hours),
                        'Access-Control-Allow-Origin': '*',
                        'Access-Control-Allow-Methods': 'GET',
                    }, **kwargs)
                else:  # pragma: no cover
                    return body
            elif len(path) >= 1 and path[0] != 'wmts':  # pragma: no cover
                return self.error(
                    404,
                    "Type '%s' don't exists, allows values: 'wmts' or 'static'" % path[0],
                    **kwargs
                )
            path = path[1:]  # remove type

            if len(path) == 2 and path[0] == '1.0.0' and path[1].lower() == 'wmtscapabilities.xml':
                params['SERVICE'] = 'WMTS'
                params['VERSION'] = '1.0.0'
                params['REQUEST'] = 'GetCapabilities'
            elif len(path) < 7:
                return self.error(400, "Not enough path", **kwargs)
            else:
                params['SERVICE'] = 'WMTS'
                params['VERSION'] = path[0]

                params['LAYER'] = path[1]
                params['STYLE'] = path[2]

                if params['LAYER'] in self.layers:
                    layer = self.tilegeneration.layers[params['LAYER']]
                else:
                    return self.error(400, "Wrong Layer '%s'" % params['LAYER'], **kwargs)

                index = 3
                dimensions = path[index:index + len(layer['dimensions'])]
                for dimension in layer['dimensions']:
                    params[dimension['name'].upper()] = path[index]
                    index += 1

                last = path[-1].split('.')
                if len(path) < index + 4:  # pragma: no cover
                    return self.error(400, "Not enough path", **kwargs)
                params['TILEMATRIXSET'] = path[index]
                params['TILEMATRIX'] = path[index + 1]
                params['TILEROW'] = path[index + 2]
                if len(path) == index + 4:
                    params['REQUEST'] = 'GetTile'
                    params['TILECOL'] = last[0]
                    if last[1] != layer['extension']:  # pragma: no cover
                        return self.error(400, "Wrong extention '%s'" % last[1], **kwargs)
                elif len(path) == index + 6:
                    params['REQUEST'] = 'GetFeatureInfo'
                    params['TILECOL'] = path[index + 3]
                    params['I'] = path[index + 4]
                    params['J'] = last[0]
                    params['INFO_FORMAT'] = layer.get('info_formats', ['application/vnd.ogc.gml'])[0]
                else:  # pragma: no cover
                    return self.error(400, "Wrong path length", **kwargs)

                params['FORMAT'] = layer['mime_type']
        else:
            if \
                    'SERVICE' not in params or \
                    'REQUEST' not in params or \
                    'VERSION' not in params:
                return self.error(400, "Not all required parameters are present", **kwargs)

        if params['SERVICE'] != 'WMTS':
            return self.error(400, "Wrong Service '%s'" % params['SERVICE'], **kwargs)
        if params['VERSION'] != '1.0.0':
            return self.error(400, "Wrong Version '%s'" % params['VERSION'], **kwargs)

        if params['REQUEST'] == 'GetCapabilities':
            wmtscapabilities_file = self.cache['wmtscapabilities_file']
            body, mime = self._get(wmtscapabilities_file, **kwargs)
            if mime is not None:
                return self.responce(body, headers={
                    'Content-Type': "application/xml",
                    'Expires': (
                        datetime.datetime.utcnow() +
                        datetime.timedelta(hours=self.expires_hours)
                    ).isoformat(),
                    'Cache-Control': "max-age=%i" % (3600 * self.expires_hours),
                    'Access-Control-Allow-Origin': '*',
                    'Access-Control-Allow-Methods': 'GET',
                }, **kwargs)
            else:  # pragma: no cover
                return body

        if \
                'FORMAT' not in params or \
                'LAYER' not in params or \
                'TILEMATRIXSET' not in params or \
                'TILEMATRIX' not in params or \
                'TILEROW' not in params or \
                'TILECOL' not in params:  # pragma: no cover
            return self.error(400, "Not all required parameters are present", **kwargs)

        if path is None:
            if params['LAYER'] in self.layers:
                layer = self.tilegeneration.layers[params['LAYER']]
            else:
                return self.error(400, "Wrong Layer '%s'" % params['LAYER'], **kwargs)

            for dimension in layer['dimensions']:
                dimensions.append(
                    params[dimension['name'].upper()]
                    if dimension['name'].lower() in params
                    else dimension['default']
                )

        if params['STYLE'] != layer['wmts_style']:
            return self.error(400, "Wrong Style '%s'" % params['STYLE'], **kwargs)
        if params['TILEMATRIXSET'] != layer['grid']:
            return self.error(400, "Wrong TileMatrixSet '%s'" % params['TILEMATRIXSET'], **kwargs)

        tile = Tile(TileCoord(
            # TODO fix for matrix_identifier = resolution
            int(params['TILEMATRIX']),
            int(params['TILECOL']),
            int(params['TILEROW']),
        ))

        if params['REQUEST'] == 'GetFeatureInfo':
            if \
                    'I' not in params or \
                    'J' not in params or \
                    'INFO_FORMAT' not in params:  # pragma: no cover
                return self.error(400, "Not all required parameters are present", **kwargs)
            if 'query_layers' in layer:
                return self.forward(
                    layer['url'] + '?' + urlencode({
                        'SERVICE': 'WMS',
                        'VERSION': '1.1.1',
                        'REQUEST': 'GetFeatureInfo',
                        'LAYERS': layer['layers'],
                        'QUERY_LAYERS': layer['query_layers'],
                        'STYLES': params['STYLE'],
                        'FORMAT': params['FORMAT'],
                        'INFO_FORMAT': params['INFO_FORMAT'],
                        'WIDTH': layer['grid_ref']['tile_size'],
                        'HEIGHT': layer['grid_ref']['tile_size'],
                        'SRS': layer['grid_ref']['srs'],
                        'BBOX': layer['grid_ref']['obj'].extent(tile.tilecoord),
                        'X': params['I'],
                        'Y': params['J'],
                    }), no_cache=True, **kwargs
                )
            else:  # pragma: no cover
                return self.error(400, "Layer '%s' not queryable" % layer['name'], **kwargs)

        if params['REQUEST'] != 'GetTile':
            return self.error(400, "Wrong Request '%s'" % params['REQUEST'], **kwargs)

        if params['FORMAT'] != layer['mime_type']:
            return self.error(400, "Wrong Format '%s'" % params['FORMAT'], **kwargs)

        if tile.tilecoord.z > self.max_zoom_seed[layer['name']]:  # pragma: no cover
            return self.forward(
                self.mapcache_baseurl + '?' + urlencode(params),
                headers=self.mapcache_header,
                **kwargs
            )

        if layer['name'] in self.filters:
            layer_filter = self.filters[layer['name']]
            meta_size = layer['meta_size']
            meta_tilecoord = TileCoord(
                # TODO fix for matrix_identifier = resolution
                tile.tilecoord.z,
                tile.tilecoord.x / meta_size * meta_size,
                tile.tilecoord.y / meta_size * meta_size,
                meta_size,
            ) if meta_size != 1 else tile.tilecoord
            if not layer_filter.filter_tilecoord(meta_tilecoord):  # pragma: no cover
                return self.forward(
                    self.mapcache_baseurl + '?' + urlencode(params),
                    headers=self.mapcache_header,
                    **kwargs
                )

        store_ref = '/'.join([params['LAYER']] + dimensions)
        if store_ref in self.stores:  # pragma: no cover
            store = self.stores[store_ref]
        else:  # pragma: no cover
            return self.error(
                400,
                "No store found for layer '%s' and dimensions %s" % (
                    layer['name'], ', '.join(dimensions)
                ),
                **kwargs
            )

        tile = store.get_one(tile)
        if tile:
            return self.responce(tile.data, headers={
                'Content-Type': tile.content_type,
                'Expires': (
                    datetime.datetime.utcnow() +
                    datetime.timedelta(hours=self.expires_hours)
                ).isoformat(),
                'Cache-Control': "max-age=%i" % (3600 * self.expires_hours),
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Methods': 'GET',
            }, **kwargs)
        else:
            return self.error(204, **kwargs)

    def forward(self, url, headers={}, no_cache=False, **kwargs):
        if no_cache:
            headers['Cache-Control'] = 'no-cache'
            headers['Pragma'] = 'no-cache'

        responce = requests.get(url, headers=headers)
        if responce.status_code == 200:
            responce_headers = responce.headers.copy()
            if no_cache:
                responce_headers['Cache-Control'] = 'no-cache, no-store'
                responce_headers['Pragma'] = 'no-cache'
            else:  # pragma: no cover
                responce_headers['Expires'] = (
                    datetime.datetime.utcnow() +
                    datetime.timedelta(hours=self.expires_hours)
                ).isoformat()
                responce_headers['Cache-Control'] = "max-age=%i" % (3600 * self.expires_hours)
                responce_headers['Access-Control-Allow-Origin'] = '*'
                responce_headers['Access-Control-Allow-Methods'] = 'GET'
            responce_headers
            return self.responce(responce.content, headers=responce_headers, **kwargs)
        else:  # pragma: no cover
            message = "The URL '%s' return '%i %s', content:\n%s" % (
                url, responce.status_code, responce.reason, responce.text,
            )
            logger.warning(message)
            return self.error(502, message=message, **kwargs)

    HTTP_MESSAGES = {
        204: '204 No Content',
        400: '400 Bad Request',
        403: '403 Forbidden',
        404: '404 Not Found',
        502: '502 Bad Gateway',
    }

    def error(self, code, message='', start_response=None):
        start_response(self.HTTP_MESSAGES[code], [])
        return [message]

    def responce(self, data, headers={}, start_response=None):
        headers['Content-Length'] = str(len(data))
        start_response('200 OK', headers.items())
        return [data]