From 396176f3e6a4ae7960b0d9a96db8ee96c27fd07c Mon Sep 17 00:00:00 2001 From: Simon Seyock Date: Mon, 8 Jul 2024 17:10:20 +0200 Subject: [PATCH 1/4] feat: include WMS legend url in WMTS capabilities --- doc/configuration.rst | 2 +- mapproxy/config/loader.py | 1 + mapproxy/service/templates/wmts100capabilities.xml | 8 +++++++- mapproxy/service/tile.py | 3 ++- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/doc/configuration.rst b/doc/configuration.rst index d7afacd68..87f3ff505 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -304,7 +304,7 @@ Please read :ref:`scale vs. resolution ` for some notes on `sc ``legendurl`` """"""""""""" -Configure a URL to an image that should be returned as the legend for this layer. Local URLs (``file://``) are also supported. MapProxy ignores the legends from the sources of this layer if you configure a ``legendurl`` here. +Configure a URL to an image that should be returned as the legend for this layer. Local URLs (``file://``) are also supported. MapProxy ignores the legends from the sources of this layer if you configure a ``legendurl`` here. If WMS and WMTS are enabled the address to the WMS `GetLegendGraphic` endpoint will be included in the WMTS capabilities as the legend url. .. _layer_metadata: diff --git a/mapproxy/config/loader.py b/mapproxy/config/loader.py index a76f50e00..52a43d529 100644 --- a/mapproxy/config/loader.py +++ b/mapproxy/config/loader.py @@ -1957,6 +1957,7 @@ def tile_layers(self, grid_name_as_path=False): md=md, tile_manager=cache_source, dimensions=dimensions, + legend='legendurl' in self.conf and 'wms' in self.context.services.conf ) ) diff --git a/mapproxy/service/templates/wmts100capabilities.xml b/mapproxy/service/templates/wmts100capabilities.xml index f85a1aaa0..2f65f2057 100644 --- a/mapproxy/service/templates/wmts100capabilities.xml +++ b/mapproxy/service/templates/wmts100capabilities.xml @@ -11,7 +11,7 @@ {{keyword}} {{endfor}} {{endfor}} - + {{endif}} OGC WMTS 1.0.0 @@ -97,6 +97,12 @@ {{layer.name}} image/{{layer.format}} {{if layer.queryable}} diff --git a/mapproxy/service/tile.py b/mapproxy/service/tile.py index 1c1b623d6..f154411b0 100644 --- a/mapproxy/service/tile.py +++ b/mapproxy/service/tile.py @@ -204,7 +204,7 @@ def _render_root_resource_template(self, service): class TileLayer(object): - def __init__(self, name, title, md, tile_manager, info_sources=[], dimensions=None): + def __init__(self, name, title, md, tile_manager, info_sources=[], dimensions=None, legend=False): """ :param md: the layer metadata :param tile_manager: the layer tile manager @@ -220,6 +220,7 @@ def __init__(self, name, title, md, tile_manager, info_sources=[], dimensions=No self._empty_tile = None self._mixed_format = True if self.md.get('format', False) == 'mixed' else False self.empty_response_as_png = True + self.legend = legend @property def bbox(self): From ff36f704b5fb83305c18da2b3c68d5bdd57e6f6c Mon Sep 17 00:00:00 2001 From: Simon Seyock Date: Tue, 9 Jul 2024 12:19:07 +0200 Subject: [PATCH 2/4] fix: use a version string for the wmts legend_url --- mapproxy/config/loader.py | 8 +++++++- mapproxy/service/templates/wmts100capabilities.xml | 4 ++-- mapproxy/service/tile.py | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/mapproxy/config/loader.py b/mapproxy/config/loader.py index 52a43d529..5e68d4f63 100644 --- a/mapproxy/config/loader.py +++ b/mapproxy/config/loader.py @@ -1950,6 +1950,12 @@ def tile_layers(self, grid_name_as_path=False): md['format'] = self.context.caches[cache_name].image_opts().format md['cache_name'] = cache_name md['extent'] = extent + legend_version = None + if 'legendurl' in self.conf: + wms_conf = self.context.services.conf.get('wms', None) + # GetLegendGraphic with legendurl does not seem to work in non 1.3.0 versions + if wms_conf is not None and '1.3.0' in wms_conf.get('versions', ['1.3.0']): + legend_version = '1.3.0' tile_layers.append( TileLayer( self.conf['name'], self.conf['title'], @@ -1957,7 +1963,7 @@ def tile_layers(self, grid_name_as_path=False): md=md, tile_manager=cache_source, dimensions=dimensions, - legend='legendurl' in self.conf and 'wms' in self.context.services.conf + legend_version=legend_version ) ) diff --git a/mapproxy/service/templates/wmts100capabilities.xml b/mapproxy/service/templates/wmts100capabilities.xml index 2f65f2057..94a393a94 100644 --- a/mapproxy/service/templates/wmts100capabilities.xml +++ b/mapproxy/service/templates/wmts100capabilities.xml @@ -97,10 +97,10 @@ {{layer.name}} diff --git a/mapproxy/service/tile.py b/mapproxy/service/tile.py index f154411b0..2149dbce2 100644 --- a/mapproxy/service/tile.py +++ b/mapproxy/service/tile.py @@ -204,7 +204,7 @@ def _render_root_resource_template(self, service): class TileLayer(object): - def __init__(self, name, title, md, tile_manager, info_sources=[], dimensions=None, legend=False): + def __init__(self, name, title, md, tile_manager, info_sources=[], dimensions=None, legend_version=None): """ :param md: the layer metadata :param tile_manager: the layer tile manager @@ -220,7 +220,7 @@ def __init__(self, name, title, md, tile_manager, info_sources=[], dimensions=No self._empty_tile = None self._mixed_format = True if self.md.get('format', False) == 'mixed' else False self.empty_response_as_png = True - self.legend = legend + self.legend_version = legend_version @property def bbox(self): From 99644d30b9920ebdbb6efb10f4a12cc75ff71e4f Mon Sep 17 00:00:00 2001 From: Simon Seyock Date: Tue, 9 Jul 2024 14:48:34 +0200 Subject: [PATCH 3/4] test: add test for legendurl in wmts capabilities --- .../test/system/fixture/legendgraphic.yaml | 14 +++++++++++ mapproxy/test/system/test_legendgraphic.py | 23 +++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/mapproxy/test/system/fixture/legendgraphic.yaml b/mapproxy/test/system/fixture/legendgraphic.yaml index cb859a933..6c42cab4b 100644 --- a/mapproxy/test/system/fixture/legendgraphic.yaml +++ b/mapproxy/test/system/fixture/legendgraphic.yaml @@ -27,6 +27,7 @@ services: email: info@example.org access_constraints: Here be dragons. + wmts: layers: - name: wms_legend @@ -45,6 +46,13 @@ layers: title: Layer with a static LegendURL legendurl: http://localhost:42423/staticlegend_layer.png sources: [legendurl_static_2] + - name: wmts_layer_legendurl + title: WMTS Layer with a static LegendURL + legendurl: http://localhost:42423/staticlegend_layer.png + sources: [tile_cache] + - name: wmts_layer_no_legendurl + title: WMTS Layer without a static LegendURL + sources: [tile_cache] sources: legend_cache: @@ -91,3 +99,9 @@ sources: url: http://localhost:42423/service layers: foo +caches: + tile_cache: + cache: + type: file + grids: [GLOBAL_WEBMERCATOR] + sources: [] diff --git a/mapproxy/test/system/test_legendgraphic.py b/mapproxy/test/system/test_legendgraphic.py index c923c241d..b667d1bdf 100644 --- a/mapproxy/test/system/test_legendgraphic.py +++ b/mapproxy/test/system/test_legendgraphic.py @@ -28,12 +28,14 @@ WMS111LegendGraphicRequest, WMS130LegendGraphicRequest, ) +from mapproxy.request.wmts import WMTS100CapabilitiesRequest from mapproxy.test.image import is_png, tmp_image from mapproxy.test.helper import validate_with_dtd, validate_with_xsd from mapproxy.test.http import mock_httpd from mapproxy.test.system import SysTest from mapproxy.test.system.test_wms import is_111_capa, assert_xpath_wms130, ns130 +from mapproxy.test.system.test_wmts import assert_xpath_wmts, ns_wmts @pytest.fixture(scope="module") @@ -64,6 +66,10 @@ def setup_method(self): url="/service?", param=dict(format="image/png", layer="wms_legend", sld_version="1.1.0"), ) + self.common_wmts_cap_req = WMTS100CapabilitiesRequest( + url="/service?", + param=dict(service="WMTS", version="1.0.0", request="GetCapabilities"), + ) # test_00, test_01, test_02 need to run first in order to run the other tests properly def test_00_get_legendgraphic_multiple_sources_111(self, app): @@ -145,8 +151,8 @@ def test_capabilities_111(self, app): xml.xpath("//Layer/Style/LegendURL/@height"), ) assert legend_sizes == ( - ["256", "256", "256", "256"], - ["512", "768", "256", "256"], + ["256", "256", "256", "256", "256"], + ["512", "768", "256", "256", "256"], ) layer_urls = xml.xpath( "//Layer/Style/LegendURL/OnlineResource/@xlink:href", namespaces=ns130 @@ -306,3 +312,16 @@ def test_get_legendgraphic_json_single_source_multiple_sub_layers(self, app): json_data = json.loads(json_str) assert json_data['Legend'][0]['layerName'] == 'foo' assert json_data['Legend'][1]['layerName'] == 'bar' + + def test_wmts_legend_url(self, app): + resp = app.get(self.common_wmts_cap_req) + assert resp.content_type == "application/xml" + xml = resp.lxml + assert_xpath_wmts( + xml, + '//wmts:Layer[1]/wmts:Style/wmts:LegendURL/@xlink:href', + 'http://localhost/service?service=WMS&request=GetLegendGraphic&version=1.3.0&format=image%2Fpng' + '&layer=wmts_layer_legendurl' + ) + assert xml.xpath('count(//wmts:Layer)', namespaces=ns_wmts) == 2 + assert xml.xpath('count(//wmts:LegendURL)', namespaces=ns_wmts) == 1 From 546202fb34a2dac432aa4ed416857e2c452c6fca Mon Sep 17 00:00:00 2001 From: Simon Seyock Date: Wed, 17 Jul 2024 13:39:56 +0200 Subject: [PATCH 4/4] fix: always use wms url for legend graphic --- mapproxy/config/loader.py | 10 ++++++---- .../service/templates/wmts100capabilities.xml | 2 +- mapproxy/service/wmts.py | 11 ++++++++--- mapproxy/test/system/fixture/legendgraphic.yaml | 2 ++ mapproxy/test/system/test_legendgraphic.py | 17 +++++++++++++++++ 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/mapproxy/config/loader.py b/mapproxy/config/loader.py index 5e68d4f63..ff14931e9 100644 --- a/mapproxy/config/loader.py +++ b/mapproxy/config/loader.py @@ -31,6 +31,7 @@ import warnings from copy import deepcopy, copy from functools import partial +from packaging.version import Version import logging from urllib.parse import urlparse @@ -1952,10 +1953,11 @@ def tile_layers(self, grid_name_as_path=False): md['extent'] = extent legend_version = None if 'legendurl' in self.conf: - wms_conf = self.context.services.conf.get('wms', None) - # GetLegendGraphic with legendurl does not seem to work in non 1.3.0 versions - if wms_conf is not None and '1.3.0' in wms_conf.get('versions', ['1.3.0']): - legend_version = '1.3.0' + wms_conf = self.context.services.conf.get('wms') + if wms_conf is not None: + versions = wms_conf.get('versions', ['1.3.0']) + versions.sort(key=Version) + legend_version = versions[-1] tile_layers.append( TileLayer( self.conf['name'], self.conf['title'], diff --git a/mapproxy/service/templates/wmts100capabilities.xml b/mapproxy/service/templates/wmts100capabilities.xml index 94a393a94..09f8cf5c6 100644 --- a/mapproxy/service/templates/wmts100capabilities.xml +++ b/mapproxy/service/templates/wmts100capabilities.xml @@ -100,7 +100,7 @@ {{if layer.legend_version}} {{endif}} diff --git a/mapproxy/service/wmts.py b/mapproxy/service/wmts.py index 60a0d6cc5..0795fe35c 100644 --- a/mapproxy/service/wmts.py +++ b/mapproxy/service/wmts.py @@ -18,6 +18,7 @@ """ from __future__ import print_function +import re from functools import partial from mapproxy.request.wmts import ( @@ -273,11 +274,13 @@ def render(self, _map_request): return self._render_template(_map_request.capabilities_template) def template_context(self): - return dict(service=bunch(default='', **self.service), + service = bunch(default='', **self.service) + return dict(service=service, restful=False, layers=self.layers, info_formats=self.info_formats, - tile_matrix_sets=self.matrix_sets) + tile_matrix_sets=self.matrix_sets, + wms_service_url=service.url) def _render_template(self, template): template = get_template(template) @@ -294,7 +297,8 @@ def __init__(self, server_md, layers, matrix_sets, url_converter, fi_url_convert self.fi_url_converter = fi_url_converter def template_context(self): - return dict(service=bunch(default='', **self.service), + service = bunch(default='', **self.service) + return dict(service=service, restful=True, layers=self.layers, info_formats=self.info_formats, @@ -306,6 +310,7 @@ def template_context(self): dimension_keys=dict((k.lower(), k) for k in self.url_converter.dimensions), format_resource_template=format_resource_template, format_info_resource_template=format_info_resource_template, + wms_service_url=re.sub(r'wmts$', 'service', service.url) ) diff --git a/mapproxy/test/system/fixture/legendgraphic.yaml b/mapproxy/test/system/fixture/legendgraphic.yaml index 6c42cab4b..6430b4cbc 100644 --- a/mapproxy/test/system/fixture/legendgraphic.yaml +++ b/mapproxy/test/system/fixture/legendgraphic.yaml @@ -28,6 +28,8 @@ services: access_constraints: Here be dragons. wmts: + restful: true + kvp: true layers: - name: wms_legend diff --git a/mapproxy/test/system/test_legendgraphic.py b/mapproxy/test/system/test_legendgraphic.py index b667d1bdf..7682c0621 100644 --- a/mapproxy/test/system/test_legendgraphic.py +++ b/mapproxy/test/system/test_legendgraphic.py @@ -21,6 +21,7 @@ import json from mapproxy.compat.image import Image +from mapproxy.request.base import BaseRequest from mapproxy.request.wms import ( WMS111MapRequest, WMS111CapabilitiesRequest, @@ -70,6 +71,9 @@ def setup_method(self): url="/service?", param=dict(service="WMTS", version="1.0.0", request="GetCapabilities"), ) + self.common_wmts_rest_cap_req = BaseRequest( + url="/wmts/1.0.0/WMTSCapabilities.xml" + ) # test_00, test_01, test_02 need to run first in order to run the other tests properly def test_00_get_legendgraphic_multiple_sources_111(self, app): @@ -325,3 +329,16 @@ def test_wmts_legend_url(self, app): ) assert xml.xpath('count(//wmts:Layer)', namespaces=ns_wmts) == 2 assert xml.xpath('count(//wmts:LegendURL)', namespaces=ns_wmts) == 1 + + def test_wmts_rest_legend_url(self, app): + resp = app.get(self.common_wmts_rest_cap_req) + assert resp.content_type == "application/xml" + xml = resp.lxml + assert_xpath_wmts( + xml, + '//wmts:Layer[1]/wmts:Style/wmts:LegendURL/@xlink:href', + 'http://localhost/service?service=WMS&request=GetLegendGraphic&version=1.3.0&format=image%2Fpng' + '&layer=wmts_layer_legendurl' + ) + assert xml.xpath('count(//wmts:Layer)', namespaces=ns_wmts) == 2 + assert xml.xpath('count(//wmts:LegendURL)', namespaces=ns_wmts) == 1