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
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]
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]