diff --git a/GeoHealthCheck/config_main.py b/GeoHealthCheck/config_main.py index 44b58ecf..36262b6d 100644 --- a/GeoHealthCheck/config_main.py +++ b/GeoHealthCheck/config_main.py @@ -104,6 +104,7 @@ # Probes 'GeoHealthCheck.plugins.probe.owsgetcaps', 'GeoHealthCheck.plugins.probe.wms', + 'GeoHealthCheck.plugins.probe.wmts', 'GeoHealthCheck.plugins.probe.wfs', 'GeoHealthCheck.plugins.probe.tms', 'GeoHealthCheck.plugins.probe.http', diff --git a/GeoHealthCheck/plugins/probe/owsgetcaps.py b/GeoHealthCheck/plugins/probe/owsgetcaps.py index 01fc3be6..afb6da56 100644 --- a/GeoHealthCheck/plugins/probe/owsgetcaps.py +++ b/GeoHealthCheck/plugins/probe/owsgetcaps.py @@ -158,6 +158,29 @@ def __init__(self): }) """Param defs""" + # This is to catch errors because url is only accessible through REST + # owslib will always do KVP request and this way the GetCap url will be: + # .../1.0.0/WMTSCapabilities.xml?service=WMTS&version=1.0.0& + # request=GetCapabilities + # This new request will return a valid WebMapTileService object. + def before_request(self): + self.original_url = self._resource.url + + try: + response = Probe.perform_get_request(self, self._resource.url) + except Exception: + self._resource.url = self._resource.url + \ + '/1.0.0/WMTSCapabilities.xml' + return + + if (response.status_code != 200 and + ' 0: + # We have a failed layer: add to result message + for result in results_failed: + msg = 'layer %s: %s' % (layer, result.message) + result.message = msg + + results_failed_total += results_failed + self.result.results_failed = [] + + self.result.results = [] + + self.result.results_failed = results_failed_total + + def actual_request(self): + """ Perform actual request to service""" + + # Actualize request query string or POST body + # by substitution in template. + url_base = self._resource.url + + # Remove capabilities string from url before sending request. + rest_url_end = '/1.0.0/WMTSCapabilities.xml' + if url_base.endswith(rest_url_end): + url_base = url_base[0:-len(rest_url_end)] + + if '?' in url_base: + url_base = url_base.split('?')[0] + + request_string = None + if self.REQUEST_TEMPLATE: + request_string = self.REQUEST_TEMPLATE + if '?' in url_base and self.REQUEST_TEMPLATE[0] == '?': + self.REQUEST_TEMPLATE = '&' + self.REQUEST_TEMPLATE[1:] + + if self._parameters: + request_parms = Plugin.copy(self.parameters_copy) + param_defs = self.get_param_defs() + + # Expand string list array to comma separated string + for param in request_parms: + if param_defs[param]['type'] == 'stringlist': + request_parms[param] = ','.join(request_parms[param]) + + request_string = self.REQUEST_TEMPLATE.format(**request_parms) + + complete_url = url_base + request_string + + self.log('Requesting: %s url=%s' % (self.REQUEST_METHOD, complete_url)) + + try: + if self.REQUEST_METHOD == 'GET': + # Default is plain URL, e.g. for WWW:LINK + url = url_base + if request_string: + # Query String: mainly OWS:* resources + url = "%s%s" % (url, request_string) + + self.response = Probe.perform_get_request(self, url) + + elif self.REQUEST_METHOD == 'POST': + self.response = Probe.perform_post_request(self, + url_base, + request_string) + except requests.exceptions.RequestException as e: + msg = "Request Err: %s %s" % (e.__class__.__name__, str(e)) + self.result.set(False, msg) + + if self.response: + self.log('response: status=%d' % self.response.status_code) + + if self.response.status_code / 100 in [4, 5]: + self.log('Error response: %s' % (str(self.response.text))) + + def calculate_center_tile(self, center_coord, tilematrix, crs): + """ + Determine center tile row and column indexes based on + topleft coordinate, scale, center coordinate and tilewidth/height + """ + scale = tilematrix.scaledenominator + topleftcorner = list(tilematrix.topleftcorner) + center_coord = list(center_coord) + + first_axis = crs.axis_info[0].direction + unit = crs.axis_info[0].unit_name + + # Adjust for coordinate systems that have reversed lat/lon coordinates + if first_axis == 'north': + center_coord.reverse() + topleftcorner.reverse() + + # Formula for metre to degree conversion factor from: + # https://stackoverflow.com/questions/639695/ + conv = { + 'metre': [1], + 'degree': [1 / (111320 * + math.cos(math.pi * center_coord[0] / 180)), + 1 / 111320], + 'foot': [coordinate_system.UNIT_FT['conversion_factor']], + 'US survey foot': [ + coordinate_system.UNIT_US_FT['conversion_factor']] + } + + # Calculate tile size + tilewidth = 0.00028 * scale * tilematrix.tilewidth * conv[unit][0] + tileheight = 0.00028 * scale * tilematrix.tileheight * conv[unit][-1] + + # Calculate tile index of center tile in the right projection + tilecol = int((center_coord[0] - topleftcorner[0]) / tilewidth) + tilerow = int((topleftcorner[1] - center_coord[1]) / tileheight) + + return tilecol, tilerow + + +class WmtsGetTileAll(WmtsGetTile): + """ + Get WMTS GetTile for all layers. + """ + + NAME = 'WMTS GetTile for all layers.' + DESCRIPTION = """ + WMTS GetTile for all layers. + """ + + PARAM_DEFS = Plugin.merge(WmtsGetTile.PARAM_DEFS, {}) + """Param defs""" + + def __init__(self): + WmtsGetTile.__init__(self) + self.wmts = None + self.layers = None + + # Overridden: expand param-ranges from WMTS metadata + def expand_params(self, resource): + # Use WMTS Capabilities doc to get metadata for + # PARAM_DEFS ranges/defaults + try: + self.PARAM_DEFS['kvprest']['range'] = self.test_kvp_rest() + + self.PARAM_DEFS['layers'] = { + 'type': 'stringlist', + 'description': 'All WMTS layers', + 'value': ['All layers'] + } + + wmts = self.get_metadata_cached(resource, version='1.0.0') + + layers = wmts.contents + self.PARAM_DEFS['layers']['range'] = list(layers.keys()) + + for layer in layers: + layer_object = layers[layer] + break + + bbox84 = layer_object.boundingBoxWGS84 + center_coord_84 = [(bbox84[0] + bbox84[2]) / 2, + (bbox84[1] + bbox84[3]) / 2] + + self.PARAM_DEFS['latitude_4326']['default'] = center_coord_84[1] + self.PARAM_DEFS['longitude_4326']['default'] = center_coord_84[0] + + except Exception as err: + raise err + + def before_request(self): + """ Before request to service, overridden from base class""" + + # Get capabilities doc to get all layers + try: + self.wmts = self.get_metadata_cached(self._resource, + version='1.0.0') + + except Exception as err: + self.result.set(False, str(err)) + + self.REQUEST_TEMPLATE = self.REQUEST_TEMPLATE[ + self._parameters['kvprest']] + + self.layers = self.wmts.contents diff --git a/GeoHealthCheck/probe.py b/GeoHealthCheck/probe.py index 766df319..86d92489 100644 --- a/GeoHealthCheck/probe.py +++ b/GeoHealthCheck/probe.py @@ -266,7 +266,7 @@ def perform_request(self): self.REQUEST_TEMPLATE = '&' + self.REQUEST_TEMPLATE[1:] if self._parameters: - request_parms = self._parameters + request_parms = Plugin.copy(self._parameters) param_defs = self.get_param_defs() # Expand string list array to comma separated string @@ -287,6 +287,7 @@ def perform_request(self): url = "%s%s" % (url, request_string) self.response = self.perform_get_request(url) + elif self.REQUEST_METHOD == 'POST': self.response = self.perform_post_request( url_base, request_string) diff --git a/tests/data/fixtures.json b/tests/data/fixtures.json index e995728c..ce161bc1 100644 --- a/tests/data/fixtures.json +++ b/tests/data/fixtures.json @@ -68,6 +68,18 @@ "tiling" ] }, + "PDOK WMTS": { + "owner": "admin", + "resource_type": "OGC:WMTS", + "active": true, + "title": "Dutch PDOK Luchtfoto WMTS", + "url": "https://service.pdok.nl/brt/achtergrondkaart/wmts/v2_0", + "tags": [ + "ows", + "tiling", + "pdok" + ] + }, "WOUDC LINK": { "owner": "admin", "resource_type": "WWW:LINK", @@ -197,6 +209,51 @@ "version": "1.0.0" } }, + "WOUDC WMTS - GetTileREST": { + "resource": "WOUDC WMTS", + "probe_class": "GeoHealthCheck.plugins.probe.wmts.WmtsGetTile", + "parameters": { + "layers": ["natura2000"], + "tilematrixset": "sample", + "tilematrix": "all", + "latitude_4326": "52", + "longitude_4326": "5", + "format": "sample", + "exceptions": "application/vnd.ogc.se_xml", + "style": "default", + "kvprest": "REST" + } + }, + "WOUDC WMTS - GetTileKVP": { + "resource": "WOUDC WMTS", + "probe_class": "GeoHealthCheck.plugins.probe.wmts.WmtsGetTile", + "parameters": { + "layers": ["natura2000"], + "tilematrixset": "sample", + "tilematrix": "all", + "latitude_4326": "52", + "longitude_4326": "5", + "format": "sample", + "exceptions": "application/vnd.ogc.se_xml", + "style": "default", + "kvprest": "KVP" + } + }, + "PDOK WMTS - GetTileAllKVP": { + "resource": "PDOK WMTS", + "probe_class": "GeoHealthCheck.plugins.probe.wmts.WmtsGetTileAll", + "parameters": { + "layers": ["All layers"], + "tilematrixset": "sample", + "tilematrix": "sample", + "latitude_4326": "52", + "longitude_4326": "5", + "format": "sample", + "exceptions": "application/vnd.ogc.se_xml", + "style": "default", + "kvprest": "KVP" + } + }, "WOUDC LINK - PING": { "resource": "WOUDC LINK", "probe_class": "GeoHealthCheck.plugins.probe.http.HttpGet", @@ -341,6 +398,36 @@ "probe_vars": "OPENGEOGROEP TMS - TopTile", "check_class": "GeoHealthCheck.plugins.check.checks.HttpHasImageContentType", "parameters": {} + }, + "WOUDC WMTS - GetTileREST - No Exception": { + "probe_vars": "WOUDC WMTS - GetTileREST", + "check_class": "GeoHealthCheck.plugins.check.checks.NotContainsOwsException", + "parameters": {"strings": ["ExceptionReport>", "ServiceException>"]} + }, + "WOUDC WMTS - GetTileREST - Content Type": { + "probe_vars": "WOUDC WMTS - GetTileREST", + "check_class": "GeoHealthCheck.plugins.check.checks.HttpHasImageContentType", + "parameters": {} + }, + "WOUDC WMTS - GetTileKVP - No Exception": { + "probe_vars": "WOUDC WMTS - GetTileKVP", + "check_class": "GeoHealthCheck.plugins.check.checks.NotContainsOwsException", + "parameters": {"strings": ["ExceptionReport>", "ServiceException>"]} + }, + "WOUDC WMTS - GetTileKVP - Content Type": { + "probe_vars": "WOUDC WMTS - GetTileKVP", + "check_class": "GeoHealthCheck.plugins.check.checks.HttpHasImageContentType", + "parameters": {} + }, + "PDOK WMTS - GetTileAllKVP - No Exception": { + "probe_vars": "PDOK WMTS - GetTileAllKVP", + "check_class": "GeoHealthCheck.plugins.check.checks.NotContainsOwsException", + "parameters": {"strings": ["ExceptionReport>", "ServiceException>"]} + }, + "PDOK WMTS - GetTileAllKVP - Content Type": { + "probe_vars": "PDOK WMTS - GetTileAllKVP", + "check_class": "GeoHealthCheck.plugins.check.checks.HttpHasImageContentType", + "parameters": {} } } } diff --git a/tests/test_resources.py b/tests/test_resources.py index ab9df17e..1b3a8a16 100644 --- a/tests/test_resources.py +++ b/tests/test_resources.py @@ -60,7 +60,7 @@ def tearDown(self): def testResourcesPresent(self): resources = Resource.query.all() - self.assertEqual(len(resources), 9) + self.assertEqual(len(resources), 10) def testRunResoures(self): # Do the whole healthcheck for all Resources for now