From 3949d16c9955cb0d2edd7c639395764393d09921 Mon Sep 17 00:00:00 2001 From: Olivia Date: Thu, 1 Feb 2024 23:02:27 +0100 Subject: [PATCH] Add WMTS endpoint implementation --- __mocks__/ol/proj.js | 5 + __mocks__/ol/proj/proj4.js | 1 + __mocks__/ol/tilegrid/WMTS.js | 1 + fixtures/wmts/arcgis.xml | 413 +++++ ...capabilities_epsg4326_with_boundingbox.xml | 1476 +++++++++++++++++ fixtures/wmts/capabilities_wgs84.xml | 172 ++ .../capabilities_wgs84_with_boundingbox.xml | 705 ++++++++ .../capabilities_with_tilematrixsetlink.xml | 287 ++++ fixtures/wmts/capabilities_wrapx.xml | 1109 +++++++++++++ fixtures/wmts/ign.xml | 501 ++++++ fixtures/wmts/ogcsample.xml | 414 +++++ src/wmts/capabilities.spec.ts | 740 +++++++++ src/wmts/capabilities.ts | 235 +++ src/wmts/endpoint.spec.ts | 442 +++++ src/wmts/endpoint.ts | 188 +++ src/wmts/model.ts | 103 ++ src/wmts/ol-tilegrid.spec.ts | 205 +++ src/wmts/ol-tilegrid.ts | 41 + src/wmts/url.spec.ts | 40 + src/wmts/url.ts | 36 + src/worker/index.ts | 15 + src/worker/worker.ts | 12 + 22 files changed, 7141 insertions(+) create mode 100644 __mocks__/ol/proj.js create mode 100644 __mocks__/ol/proj/proj4.js create mode 100644 __mocks__/ol/tilegrid/WMTS.js create mode 100644 fixtures/wmts/arcgis.xml create mode 100644 fixtures/wmts/capabilities_epsg4326_with_boundingbox.xml create mode 100644 fixtures/wmts/capabilities_wgs84.xml create mode 100644 fixtures/wmts/capabilities_wgs84_with_boundingbox.xml create mode 100644 fixtures/wmts/capabilities_with_tilematrixsetlink.xml create mode 100644 fixtures/wmts/capabilities_wrapx.xml create mode 100644 fixtures/wmts/ign.xml create mode 100644 fixtures/wmts/ogcsample.xml create mode 100644 src/wmts/capabilities.spec.ts create mode 100644 src/wmts/capabilities.ts create mode 100644 src/wmts/endpoint.spec.ts create mode 100644 src/wmts/endpoint.ts create mode 100644 src/wmts/model.ts create mode 100644 src/wmts/ol-tilegrid.spec.ts create mode 100644 src/wmts/ol-tilegrid.ts create mode 100644 src/wmts/url.spec.ts create mode 100644 src/wmts/url.ts diff --git a/__mocks__/ol/proj.js b/__mocks__/ol/proj.js new file mode 100644 index 0000000..e1dab48 --- /dev/null +++ b/__mocks__/ol/proj.js @@ -0,0 +1,5 @@ +module.exports = { + get: jest.fn(() => ({ + code: '3857', + })), +}; diff --git a/__mocks__/ol/proj/proj4.js b/__mocks__/ol/proj/proj4.js new file mode 100644 index 0000000..7337781 --- /dev/null +++ b/__mocks__/ol/proj/proj4.js @@ -0,0 +1 @@ +module.exports = { fromEPSGCode: jest.fn(), register: jest.fn() }; diff --git a/__mocks__/ol/tilegrid/WMTS.js b/__mocks__/ol/tilegrid/WMTS.js new file mode 100644 index 0000000..1d70a63 --- /dev/null +++ b/__mocks__/ol/tilegrid/WMTS.js @@ -0,0 +1 @@ +module.exports = { createFromCapabilitiesMatrixSet: jest.fn(() => {}) }; diff --git a/fixtures/wmts/arcgis.xml b/fixtures/wmts/arcgis.xml new file mode 100644 index 0000000..7b2dbe5 --- /dev/null +++ b/fixtures/wmts/arcgis.xml @@ -0,0 +1,413 @@ + + + + + Demographics_USA_Population_Density + OGC WMTS + 1.0.0 + + + + + + + + + + RESTful + + + + + + + + KVP + + + + + + + + + + + + + RESTful + + + + + + + KVP + + + + + + + + + + + Demographics_USA_Population_Density + Demographics_USA_Population_Density + + -1.98402303899E7 2144435.3407000005 + -7452840.4651999995 1.1536810662600003E7 + + + -178.2278219969978 18.910787002877576 + -66.95000499993604 71.38957425051252 + + + image/png + + default028mm + + + + GoogleMapsCompatible + + + + + + + TileMatrix using 0.28mm + The tile matrix set that has scale values calculated based on the dpi + defined by OGC specification (dpi assumes 0.28mm as the physical distance of a + pixel). + + default028mm + urn:ogc:def:crs:EPSG::3857 + + 0 + 5.590822640285016E8 + -2.0037508342787E7 2.0037508342787E7 + 256 + 256 + 1 + 1 + + + 1 + 2.7954113201425034E8 + -2.0037508342787E7 2.0037508342787E7 + 256 + 256 + 1 + 1 + + + 2 + 1.3977056600712562E8 + -2.0037508342787E7 2.0037508342787E7 + 256 + 256 + 2 + 2 + + + 3 + 6.988528300356235E7 + -2.0037508342787E7 2.0037508342787E7 + 256 + 256 + 3 + 4 + + + 4 + 3.494264150178117E7 + -2.0037508342787E7 2.0037508342787E7 + 256 + 256 + 6 + 8 + + + 5 + 1.7471320750890587E7 + -2.0037508342787E7 2.0037508342787E7 + 256 + 256 + 11 + 15 + + + 6 + 8735660.375445293 + -2.0037508342787E7 2.0037508342787E7 + 256 + 256 + 21 + 29 + + + 7 + 4367830.187722647 + -2.0037508342787E7 2.0037508342787E7 + 256 + 256 + 41 + 58 + + + 8 + 2183915.0938617955 + -2.0037508342787E7 2.0037508342787E7 + 256 + 256 + 81 + 115 + + + 9 + 1091957.5469304253 + -2.0037508342787E7 2.0037508342787E7 + 256 + 256 + 161 + 229 + + + 10 + 545978.7734656851 + -2.0037508342787E7 2.0037508342787E7 + 256 + 256 + 322 + 458 + + + 11 + 272989.38673237007 + -2.0037508342787E7 2.0037508342787E7 + 256 + 256 + 644 + 915 + + + 12 + 136494.69336618503 + -2.0037508342787E7 2.0037508342787E7 + 256 + 256 + 1287 + 1829 + + + 13 + 68247.34668309252 + -2.0037508342787E7 2.0037508342787E7 + 256 + 256 + 2573 + 3658 + + + + GoogleMapsCompatible + the wellknown 'GoogleMapsCompatible' tile matrix set defined by OGC WMTS + specification + + GoogleMapsCompatible + urn:ogc:def:crs:EPSG:6.18.3:3857 + urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible + + 0 + 559082264.0287178 + -20037508.34278925 20037508.34278925 + 256 + 256 + 1 + 1 + + + 1 + 279541132.0143589 + -20037508.34278925 20037508.34278925 + 256 + 256 + 2 + 2 + + + 2 + 139770566.0071794 + -20037508.34278925 20037508.34278925 + 256 + 256 + 4 + 4 + + + 3 + 69885283.00358972 + -20037508.34278925 20037508.34278925 + 256 + 256 + 8 + 8 + + + 4 + 34942641.50179486 + -20037508.34278925 20037508.34278925 + 256 + 256 + 16 + 16 + + + 5 + 17471320.75089743 + -20037508.34278925 20037508.34278925 + 256 + 256 + 32 + 32 + + + 6 + 8735660.375448715 + -20037508.34278925 20037508.34278925 + 256 + 256 + 64 + 64 + + + 7 + 4367830.187724357 + -20037508.34278925 20037508.34278925 + 256 + 256 + 128 + 128 + + + 8 + 2183915.093862179 + -20037508.34278925 20037508.34278925 + 256 + 256 + 256 + 256 + + + 9 + 1091957.546931089 + -20037508.34278925 20037508.34278925 + 256 + 256 + 512 + 512 + + + 10 + 545978.7734655447 + -20037508.34278925 20037508.34278925 + 256 + 256 + 1024 + 1024 + + + 11 + 272989.3867327723 + -20037508.34278925 20037508.34278925 + 256 + 256 + 2048 + 2048 + + + 12 + 136494.6933663862 + -20037508.34278925 20037508.34278925 + 256 + 256 + 4096 + 4096 + + + 13 + 68247.34668319309 + -20037508.34278925 20037508.34278925 + 256 + 256 + 8192 + 8192 + + + 14 + 34123.67334159654 + -20037508.34278925 20037508.34278925 + 256 + 256 + 16384 + 16384 + + + 15 + 17061.83667079827 + -20037508.34278925 20037508.34278925 + 256 + 256 + 32768 + 32768 + + + 16 + 8530.918335399136 + -20037508.34278925 20037508.34278925 + 256 + 256 + 65536 + 65536 + + + 17 + 4265.459167699568 + -20037508.34278925 20037508.34278925 + 256 + 256 + 131072 + 131072 + + + 18 + 2132.729583849784 + -20037508.34278925 20037508.34278925 + 256 + 256 + 262144 + 262144 + + + + + diff --git a/fixtures/wmts/capabilities_epsg4326_with_boundingbox.xml b/fixtures/wmts/capabilities_epsg4326_with_boundingbox.xml new file mode 100644 index 0000000..d266173 --- /dev/null +++ b/fixtures/wmts/capabilities_epsg4326_with_boundingbox.xml @@ -0,0 +1,1476 @@ + + + + EOX::Maps + EOX::Maps are background and overlay maps made of Open Data and designed and provided by EOX. + + + maps + + OGC WMTS + 1.0.0 + Proper attribution is required for any usage. The attribution shall follow the example of + the demo map at https://maps.eox.at in the lower right corner including the respective links e.g. "Terrain { + Data © OpenStreetMap contributers and others, Rendering © EOX }" with links to + http://www.openstreetmap.org/copyright, https://maps.eox.at/#data, and https://eox.at. Additional + restrictions may apply for individual layers as indicated in the respective abstract. + + + + EOX + + + Stephan Meissl + + + +43 664 9688701 + + + EOX IT Services GmbH + Vienna + 1090 + Austria + stephan.meissl@eox.at + + + + + + + + + + + + KVP + + + + + + + + + + + + + KVP + + + + + + + + + + + + + KVP + + + + + + + + + TBD + application/vnd.iso.19139+xml + + + + eng + + + + eng + + + + + + Blue marble background layer by EOX - 4326 + <a href="https://maps.eox.at">Blue Marble</a> { &copy; <a + href="http://nasa.gov">NASA</a> } + + + -180.000000 -90.000000 + 180.000000 90.000000 + + bluemarble + + image/jpeg + + WGS84 + + + + + Sentinel-2 cloudless layer for 2021 by EOX - 3857 + <a xmlns:dct="http://purl.org/dc/terms/" href="https://s2maps.eu" property="dct:title">Sentinel-2 + cloudless - https://s2maps.eu</a> by <a xmlns:cc="http://creativecommons.org/ns#" + href="https://eox.at" property="cc:attributionName" rel="cc:attributionURL">EOX IT Services GmbH</a> + (Contains modified Copernicus Sentinel data 2021) released under <a rel="license" + href="https://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons + Attribution-NonCommercial-ShareAlike 4.0 International License</a>. For commercial usage please + see <a href="https://cloudless.eox.at">https://cloudless.eox.at</a> + + s2cloudless-2021_3857 + + image/jpeg + + g + + + GoogleMapsCompatible + + + + + Black coastline overlay layer by EOX - 4326 + <a href="https://maps.eox.at">Black coastline overlay</a> { Rendering &copy; + <a href="https://eox.at">EOX</a> } + + + -180.000000 -90.000000 + 180.000000 90.000000 + + coastline_black + + image/png + + WGS84 + + + + + Sentinel-2 cloudless layer for 2017 by EOX - 3857 + <a xmlns:dct="http://purl.org/dc/terms/" href="https://s2maps.eu" property="dct:title">Sentinel-2 + cloudless - https://s2maps.eu</a> by <a xmlns:cc="http://creativecommons.org/ns#" + href="https://eox.at" property="cc:attributionName" rel="cc:attributionURL">EOX IT Services GmbH</a> + (Contains modified Copernicus Sentinel data 2017) released under <a rel="license" + href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International + License</a>. + + s2cloudless-2017_3857 + + image/jpeg + + g + + + GoogleMapsCompatible + + + + + Streets overlay layer by EOX - 3857 + <a href="https://maps.eox.at">Streets overlay</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Rendering &copy; + <a href="https://eox.at">EOX</a> and <a href="https://github.com/mapserver/basemaps">MapServer</a> + } + + streets_3857 + + image/png + + g + + + GoogleMapsCompatible + + + + + Bright overlay layer by EOX - 4326 + <a href="https://maps.eox.at">Overlay bright</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Rendering &copy; + <a href="https://eox.at">EOX</a> and <a href="https://github.com/mapserver/basemaps">MapServer</a> + } + + + -180.000000 -90.000000 + 180.000000 90.000000 + + overlay_bright + + image/png + + WGS84 + + + + + Sentinel-2 cloudless layer for 2019 by EOX - 3857 + <a xmlns:dct="http://purl.org/dc/terms/" href="https://s2maps.eu" property="dct:title">Sentinel-2 + cloudless - https://s2maps.eu</a> by <a xmlns:cc="http://creativecommons.org/ns#" + href="https://eox.at" property="cc:attributionName" rel="cc:attributionURL">EOX IT Services GmbH</a> + (Contains modified Copernicus Sentinel data 2019) released under <a rel="license" + href="https://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons + Attribution-NonCommercial-ShareAlike 4.0 International License</a>. For commercial usage please + see <a href="https://cloudless.eox.at">https://cloudless.eox.at</a> + + s2cloudless-2019_3857 + + image/jpeg + + g + + + GoogleMapsCompatible + + + + + Overlay layer by EOX - 4326 + <a href="https://maps.eox.at">Overlay</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Rendering &copy; + <a href="https://eox.at">EOX</a> and <a href="https://github.com/mapserver/basemaps">MapServer</a> + } + + + -180.000000 -90.000000 + 180.000000 90.000000 + + overlay + + image/png + + WGS84 + + + + + Black marble background layer by EOX - 3857 + <a href="https://maps.eox.at">Black Marble</a> { &copy; <a + href="http://nasa.gov">NASA</a> } + + blackmarble_3857 + + image/jpeg + + g + + + + + Hydrography overlay layer by EOX - 3857 + <a href="https://maps.eox.at">Hydrography overlay</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Rendering &copy; + <a href="https://eox.at">EOX</a> and <a href="https://github.com/mapserver/basemaps">MapServer</a> + } + + hydrography_3857 + + image/png + + g + + + GoogleMapsCompatible + + + + + Sentinel-2 cloudless layer for 2017 by EOX - 4326 + <a xmlns:dct="http://purl.org/dc/terms/" href="https://s2maps.eu" property="dct:title">Sentinel-2 + cloudless - https://s2maps.eu</a> by <a xmlns:cc="http://creativecommons.org/ns#" + href="https://eox.at" property="cc:attributionName" rel="cc:attributionURL">EOX IT Services GmbH</a> + (Contains modified Copernicus Sentinel data 2017) released under <a rel="license" + href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International + License</a>. + + + -180.000000 -90.000000 + 180.000000 90.000000 + + s2cloudless-2017 + + image/jpeg + + WGS84 + + + + + Streets overlay layer by EOX - 4326 + <a href="https://maps.eox.at">Streets overlay</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Rendering &copy; + <a href="https://eox.at">EOX</a> and <a href="https://github.com/mapserver/basemaps">MapServer</a> + } + + + -180.000000 -90.000000 + 180.000000 90.000000 + + streets + + image/png + + WGS84 + + + + + Sentinel-2 cloudless layer for 2018 by EOX - 4326 + <a xmlns:dct="http://purl.org/dc/terms/" href="https://s2maps.eu" property="dct:title">Sentinel-2 + cloudless - https://s2maps.eu</a> by <a xmlns:cc="http://creativecommons.org/ns#" + href="https://eox.at" property="cc:attributionName" rel="cc:attributionURL">EOX IT Services GmbH</a> + (Contains modified Copernicus Sentinel data 2017 &amp; 2018) released under <a rel="license" + href="https://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons + Attribution-NonCommercial-ShareAlike 4.0 International License</a>. For commercial usage please + see <a href="https://cloudless.eox.at">https://cloudless.eox.at</a> + + + -180.000000 -90.000000 + 180.000000 90.000000 + + s2cloudless-2018 + + image/jpeg + + WGS84 + + + + + Sentinel-2 cloudless layer for 2019 by EOX - 4326 + <a xmlns:dct="http://purl.org/dc/terms/" href="https://s2maps.eu" property="dct:title">Sentinel-2 + cloudless - https://s2maps.eu</a> by <a xmlns:cc="http://creativecommons.org/ns#" + href="https://eox.at" property="cc:attributionName" rel="cc:attributionURL">EOX IT Services GmbH</a> + (Contains modified Copernicus Sentinel data 2019) released under <a rel="license" + href="https://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons + Attribution-NonCommercial-ShareAlike 4.0 International License</a>. For commercial usage please + see <a href="https://cloudless.eox.at">https://cloudless.eox.at</a> + + + -180.000000 -90.000000 + 180.000000 90.000000 + + s2cloudless-2019 + + image/jpeg + + WGS84 + + + + + OpenStreetMap background layer by EOX - 4326 + <a href="https://maps.eox.at">OpenStreetMap</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Rendering &copy; + <a href="https://eox.at">EOX</a> and <a href="https://github.com/mapserver/basemaps">MapServer</a> + } + + + -180.000000 -90.000000 + 180.000000 90.000000 + + osm + + image/jpeg + + WGS84 + + + + + Black marble background layer by EOX - 4326 + <a href="https://maps.eox.at">Black Marble</a> { &copy; <a + href="http://nasa.gov">NASA</a> } + + + -180.000000 -90.000000 + 180.000000 90.000000 + + blackmarble + + image/jpeg + + WGS84 + + + + + Terrain Light background layer by EOX - 3857 + <a href="https://maps.eox.at">Terrain Light</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors and <a + href="https://maps.eox.at/#data">others</a>, Rendering &copy; <a href="https://eox.at">EOX</a> + } + + terrain-light_3857 + + image/jpeg + + g + + + GoogleMapsCompatible + + + + + Blue marble background layer by EOX - 3857 + <a href="https://maps.eox.at">Blue Marble</a> { &copy; <a + href="http://nasa.gov">NASA</a> } + + bluemarble_3857 + + image/jpeg + + g + + + + + Overlay layer by EOX - 3857 + <a href="https://maps.eox.at">Overlay</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Rendering &copy; + <a href="https://eox.at">EOX</a> and <a href="https://github.com/mapserver/basemaps">MapServer</a> + } + + overlay_base_3857 + + image/png + + g + + + GoogleMapsCompatible + + + + + Sentinel-2 cloudless layer for 2016 by EOX - 4326 + <a xmlns:dct="http://purl.org/dc/terms/" href="https://s2maps.eu" property="dct:title">Sentinel-2 + cloudless - https://s2maps.eu</a> by <a xmlns:cc="http://creativecommons.org/ns#" + href="https://eox.at" property="cc:attributionName" rel="cc:attributionURL">EOX IT Services GmbH</a> + (Contains modified Copernicus Sentinel data 2016 &amp; 2017) released under <a rel="license" + href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International + License</a>. + + + -180.000000 -90.000000 + 180.000000 90.000000 + + s2cloudless + + image/jpeg + + WGS84 + + + + + Terrain background layer by EOX - 4326 + <a href="https://maps.eox.at">Terrain</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors and <a + href="https://maps.eox.at/#data">others</a>, Rendering &copy; <a href="https://eox.at">EOX</a> + } + + + -180.000000 -90.000000 + 180.000000 90.000000 + + terrain + + image/jpeg + + WGS84 + + + + + Bright overlay layer by EOX - 3857 + <a href="https://maps.eox.at">Overlay bright</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Rendering &copy; + <a href="https://eox.at">EOX</a> and <a href="https://github.com/mapserver/basemaps">MapServer</a> + } + + overlay_bright_3857 + + image/png + + g + + + GoogleMapsCompatible + + + + + Coastline overlay layer by EOX - 3857 + <a href="https://maps.eox.at">Coastline overlay</a> { Rendering &copy; <a + href="https://eox.at">EOX</a> } + + coastline_3857 + + image/png + + g + + + + + Sentinel-2 cloudless layer for 2020 by EOX - 3857 + <a xmlns:dct="http://purl.org/dc/terms/" href="https://s2maps.eu" property="dct:title">Sentinel-2 + cloudless - https://s2maps.eu</a> by <a xmlns:cc="http://creativecommons.org/ns#" + href="https://eox.at" property="cc:attributionName" rel="cc:attributionURL">EOX IT Services GmbH</a> + (Contains modified Copernicus Sentinel data 2020) released under <a rel="license" + href="https://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons + Attribution-NonCommercial-ShareAlike 4.0 International License</a>. For commercial usage please + see <a href="https://cloudless.eox.at">https://cloudless.eox.at</a> + + s2cloudless-2020_3857 + + image/jpeg + + g + + + GoogleMapsCompatible + + + + + Magnetic Graticules overlay layer by EOX - 4326 + <a href="https://maps.eox.at">Magnetic Graticules</a> { Rendering &copy; <a + href="https://eox.at">EOX</a> } + + + -180.000000 -90.000000 + 180.000000 90.000000 + + magnetic_graticules + + image/png + + WGS84 + + + + + Terrain Light background layer by EOX - 4326 + <a href="https://maps.eox.at">Terrain Light</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors and <a + href="https://maps.eox.at/#data">others</a>, Rendering &copy; <a href="https://eox.at">EOX</a> + } + + + -180.000000 -90.000000 + 180.000000 90.000000 + + terrain-light + + image/jpeg + + WGS84 + + + + + Bright overlay layer by EOX - 4326 + <a href="https://maps.eox.at">Overlay bright</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Rendering &copy; + <a href="https://eox.at">EOX</a> and <a href="https://github.com/mapserver/basemaps">MapServer</a> + } + + + -180.000000 -90.000000 + 180.000000 90.000000 + + overlay_base_bright + + image/png + + WGS84 + + + + + Overlay layer by EOX - 4326 + <a href="https://maps.eox.at">Overlay</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Rendering &copy; + <a href="https://eox.at">EOX</a> and <a href="https://github.com/mapserver/basemaps">MapServer</a> + } + + + -180.000000 -90.000000 + 180.000000 90.000000 + + overlay_base + + image/png + + WGS84 + + + + + Sentinel-2 cloudless layer for 2018 by EOX - 3857 + <a xmlns:dct="http://purl.org/dc/terms/" href="https://s2maps.eu" property="dct:title">Sentinel-2 + cloudless - https://s2maps.eu</a> by <a xmlns:cc="http://creativecommons.org/ns#" + href="https://eox.at" property="cc:attributionName" rel="cc:attributionURL">EOX IT Services GmbH</a> + (Contains modified Copernicus Sentinel data 2017 &amp; 2018) released under <a rel="license" + href="https://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons + Attribution-NonCommercial-ShareAlike 4.0 International License</a>. For commercial usage please + see <a href="https://cloudless.eox.at">https://cloudless.eox.at</a> + + s2cloudless-2018_3857 + + image/jpeg + + g + + + GoogleMapsCompatible + + + + + Coastline overlay layer by EOX - 4326 + <a href="https://maps.eox.at">Coastline overlay</a> { Rendering &copy; <a + href="https://eox.at">EOX</a> } + + + -180.000000 -90.000000 + 180.000000 90.000000 + + coastline + + image/png + + WGS84 + + + + + Sentinel-2 cloudless layer for 2020 by EOX - 4326 + <a xmlns:dct="http://purl.org/dc/terms/" href="https://s2maps.eu" property="dct:title">Sentinel-2 + cloudless - https://s2maps.eu</a> by <a xmlns:cc="http://creativecommons.org/ns#" + href="https://eox.at" property="cc:attributionName" rel="cc:attributionURL">EOX IT Services GmbH</a> + (Contains modified Copernicus Sentinel data 2020) released under <a rel="license" + href="https://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons + Attribution-NonCommercial-ShareAlike 4.0 International License</a>. For commercial usage please + see <a href="https://cloudless.eox.at">https://cloudless.eox.at</a> + + + -180.000000 -90.000000 + 180.000000 90.000000 + + s2cloudless-2020 + + image/jpeg + + WGS84 + + + + + Sentinel-2 cloudless layer for 2021 by EOX - 4326 + <a xmlns:dct="http://purl.org/dc/terms/" href="https://s2maps.eu" property="dct:title">Sentinel-2 + cloudless - https://s2maps.eu</a> by <a xmlns:cc="http://creativecommons.org/ns#" + href="https://eox.at" property="cc:attributionName" rel="cc:attributionURL">EOX IT Services GmbH</a> + (Contains modified Copernicus Sentinel data 2021) released under <a rel="license" + href="https://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons + Attribution-NonCommercial-ShareAlike 4.0 International License</a>. For commercial usage please + see <a href="https://cloudless.eox.at">https://cloudless.eox.at</a> + + + -180.000000 -90.000000 + 180.000000 90.000000 + + s2cloudless-2021 + + image/jpeg + + WGS84 + + + + + OpenStreetMap background layer by EOX - 3857 + <a href="https://maps.eox.at">OpenStreetMap</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Rendering &copy; + <a href="https://eox.at">EOX</a> and <a href="https://github.com/mapserver/basemaps">MapServer</a> + } + + osm_3857 + + image/jpeg + + g + + + GoogleMapsCompatible + + + + + Sentinel-2 cloudless layer for 2016 by EOX - 3857 + <a xmlns:dct="http://purl.org/dc/terms/" href="https://s2maps.eu" property="dct:title">Sentinel-2 + cloudless - https://s2maps.eu</a> by <a xmlns:cc="http://creativecommons.org/ns#" + href="https://eox.at" property="cc:attributionName" rel="cc:attributionURL">EOX IT Services GmbH</a> + (Contains modified Copernicus Sentinel data 2016 &amp; 2017) released under <a rel="license" + href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International + License</a>. + + s2cloudless_3857 + + image/jpeg + + g + + + GoogleMapsCompatible + + + + + Terrain background layer by EOX - 3857 + <a href="https://maps.eox.at">Terrain</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors and <a + href="https://maps.eox.at/#data">others</a>, Rendering &copy; <a href="https://eox.at">EOX</a> + } + + terrain_3857 + + image/jpeg + + g + + + + + Bright overlay layer by EOX - 3857 + <a href="https://maps.eox.at">Overlay bright</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Rendering &copy; + <a href="https://eox.at">EOX</a> and <a href="https://github.com/mapserver/basemaps">MapServer</a> + } + + overlay_base_bright_3857 + + image/png + + g + + + GoogleMapsCompatible + + + + + Hydrography overlay layer by EOX - 4326 + <a href="https://maps.eox.at">Hydrography overlay</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Rendering &copy; + <a href="https://eox.at">EOX</a> and <a href="https://github.com/mapserver/basemaps">MapServer</a> + } + + + -180.000000 -90.000000 + 180.000000 90.000000 + + hydrography + + image/png + + WGS84 + + + + + Graticules overlay layer by EOX - 4326 + <a href="https://maps.eox.at">Graticules</a> { Rendering &copy; <a + href="https://eox.at">EOX</a> } + + + -180.000000 -90.000000 + 180.000000 90.000000 + + graticules + + image/png + + WGS84 + + + + + Overlay layer by EOX - 3857 + <a href="https://maps.eox.at">Overlay</a> { Data &copy; <a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Rendering &copy; + <a href="https://eox.at">EOX</a> and <a href="https://github.com/mapserver/basemaps">MapServer</a> + } + + overlay_3857 + + image/png + + g + + + GoogleMapsCompatible + + + + + WGS84 + + -90.000000 -180.000000 + 90.000000 180.000000 + + urn:ogc:def:crs:EPSG:6.3:4326 + urn:ogc:def:wkss:OGC:1.0:GoogleCRS84Quad + + 0 + 279541132.01435887813568115234 + 90.000000 -180.000000 + 256 + 256 + 2 + 1 + + + 1 + 139770566.00717943906784057617 + 90.000000 -180.000000 + 256 + 256 + 4 + 2 + + + 2 + 69885283.00358971953392028809 + 90.000000 -180.000000 + 256 + 256 + 8 + 4 + + + 3 + 34942641.50179485976696014404 + 90.000000 -180.000000 + 256 + 256 + 16 + 8 + + + 4 + 17471320.75089742988348007202 + 90.000000 -180.000000 + 256 + 256 + 32 + 16 + + + 5 + 8735660.37544871494174003601 + 90.000000 -180.000000 + 256 + 256 + 64 + 32 + + + 6 + 4367830.18772435747087001801 + 90.000000 -180.000000 + 256 + 256 + 128 + 64 + + + 7 + 2183915.09386217873543500900 + 90.000000 -180.000000 + 256 + 256 + 256 + 128 + + + 8 + 1091957.54693108936771750450 + 90.000000 -180.000000 + 256 + 256 + 512 + 256 + + + 9 + 545978.77346554468385875225 + 90.000000 -180.000000 + 256 + 256 + 1024 + 512 + + + 10 + 272989.38673277234192937613 + 90.000000 -180.000000 + 256 + 256 + 2048 + 1024 + + + 11 + 136494.69336638617096468806 + 90.000000 -180.000000 + 256 + 256 + 4096 + 2048 + + + 12 + 68247.34668319308548234403 + 90.000000 -180.000000 + 256 + 256 + 8192 + 4096 + + + 13 + 34123.67334159654274117202 + 90.000000 -180.000000 + 256 + 256 + 16384 + 8192 + + + 14 + 17061.83667079825318069197 + 90.000000 -180.000000 + 256 + 256 + 32768 + 16384 + + + 15 + 8530.91833539912659034599 + 90.000000 -180.000000 + 256 + 256 + 65536 + 32768 + + + 16 + 4265.45916769956329517299 + 90.000000 -180.000000 + 256 + 256 + 131072 + 65536 + + + 17 + 2132.72958384978574031265 + 90.000000 -180.000000 + 256 + 256 + 262144 + 131072 + + + + GoogleMapsCompatible + + -20037508.342789 -20037508.342789 + 20037508.342789 20037508.342789 + + urn:ogc:def:crs:EPSG:6.3:3857 + urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible + + 0 + 559082264.02871787548065185547 + -20037508.342789 20037508.342789 + 256 + 256 + 1 + 1 + + + 1 + 279541132.01435887813568115234 + -20037508.342789 20037508.342789 + 256 + 256 + 2 + 2 + + + 2 + 139770566.00717940926551818848 + -20037508.342789 20037508.342789 + 256 + 256 + 4 + 4 + + + 3 + 69885283.00358971953392028809 + -20037508.342789 20037508.342789 + 256 + 256 + 8 + 8 + + + 4 + 34942641.50179485976696014404 + -20037508.342789 20037508.342789 + 256 + 256 + 16 + 16 + + + 5 + 17471320.75089742988348007202 + -20037508.342789 20037508.342789 + 256 + 256 + 32 + 32 + + + 6 + 8735660.37544871494174003601 + -20037508.342789 20037508.342789 + 256 + 256 + 64 + 64 + + + 7 + 4367830.18772435747087001801 + -20037508.342789 20037508.342789 + 256 + 256 + 128 + 128 + + + 8 + 2183915.09386217873543500900 + -20037508.342789 20037508.342789 + 256 + 256 + 256 + 256 + + + 9 + 1091957.54693108866922557354 + -20037508.342789 20037508.342789 + 256 + 256 + 512 + 512 + + + 10 + 545978.77346554468385875225 + -20037508.342789 20037508.342789 + 256 + 256 + 1024 + 1024 + + + 11 + 272989.38673277228372171521 + -20037508.342789 20037508.342789 + 256 + 256 + 2048 + 2048 + + + 12 + 136494.69336638617096468806 + -20037508.342789 20037508.342789 + 256 + 256 + 4096 + 4096 + + + 13 + 68247.34668319307093042880 + -20037508.342789 20037508.342789 + 256 + 256 + 8192 + 8192 + + + 14 + 34123.67334159654274117202 + -20037508.342789 20037508.342789 + 256 + 256 + 16384 + 16384 + + + 15 + 17061.83667079827137058601 + -20037508.342789 20037508.342789 + 256 + 256 + 32768 + 32768 + + + 16 + 8530.91833539913568529300 + -20037508.342789 20037508.342789 + 256 + 256 + 65536 + 65536 + + + 17 + 4265.45916769956784264650 + -20037508.342789 20037508.342789 + 256 + 256 + 131072 + 131072 + + + 18 + 2132.72958384978392132325 + -20037508.342789 20037508.342789 + 256 + 256 + 262144 + 262144 + + + + g + + -20037508.342789 -20037508.342789 + 20037508.342789 20037508.342789 + + urn:ogc:def:crs:EPSG:6.3:900913 + urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible + + 0 + 559082264.02871787548065185547 + -20037508.342789 20037508.342789 + 256 + 256 + 1 + 1 + + + 1 + 279541132.01435887813568115234 + -20037508.342789 20037508.342789 + 256 + 256 + 2 + 2 + + + 2 + 139770566.00717940926551818848 + -20037508.342789 20037508.342789 + 256 + 256 + 4 + 4 + + + 3 + 69885283.00358971953392028809 + -20037508.342789 20037508.342789 + 256 + 256 + 8 + 8 + + + 4 + 34942641.50179485976696014404 + -20037508.342789 20037508.342789 + 256 + 256 + 16 + 16 + + + 5 + 17471320.75089742988348007202 + -20037508.342789 20037508.342789 + 256 + 256 + 32 + 32 + + + 6 + 8735660.37544871494174003601 + -20037508.342789 20037508.342789 + 256 + 256 + 64 + 64 + + + 7 + 4367830.18772435747087001801 + -20037508.342789 20037508.342789 + 256 + 256 + 128 + 128 + + + 8 + 2183915.09386217873543500900 + -20037508.342789 20037508.342789 + 256 + 256 + 256 + 256 + + + 9 + 1091957.54693108866922557354 + -20037508.342789 20037508.342789 + 256 + 256 + 512 + 512 + + + 10 + 545978.77346554468385875225 + -20037508.342789 20037508.342789 + 256 + 256 + 1024 + 1024 + + + 11 + 272989.38673277228372171521 + -20037508.342789 20037508.342789 + 256 + 256 + 2048 + 2048 + + + 12 + 136494.69336638617096468806 + -20037508.342789 20037508.342789 + 256 + 256 + 4096 + 4096 + + + 13 + 68247.34668319307093042880 + -20037508.342789 20037508.342789 + 256 + 256 + 8192 + 8192 + + + 14 + 34123.67334159654274117202 + -20037508.342789 20037508.342789 + 256 + 256 + 16384 + 16384 + + + 15 + 17061.83667079827137058601 + -20037508.342789 20037508.342789 + 256 + 256 + 32768 + 32768 + + + 16 + 8530.91833539913568529300 + -20037508.342789 20037508.342789 + 256 + 256 + 65536 + 65536 + + + 17 + 4265.45916769956784264650 + -20037508.342789 20037508.342789 + 256 + 256 + 131072 + 131072 + + + 18 + 2132.72958384978392132325 + -20037508.342789 20037508.342789 + 256 + 256 + 262144 + 262144 + + + + diff --git a/fixtures/wmts/capabilities_wgs84.xml b/fixtures/wmts/capabilities_wgs84.xml new file mode 100644 index 0000000..c9e0c1d --- /dev/null +++ b/fixtures/wmts/capabilities_wgs84.xml @@ -0,0 +1,172 @@ + + + + Sample WMTS + OGC WMTS + 1.0.0 + None + none + + + + + + + + + + + + + + + + + + Baselayer + Baselayer + baselayer + + -180.0 -90.0 + 180.0 90.0 + + + -90.0 -180.0 + 90.0 180.0 + + + image/png + + inspire_quad + + + + + InspireCRS84Quad + inspire_quad + urn:ogc:def:crs:EPSG::4326 + + 0 + 279541132.014357 + 90.0 -180.0 + 256 + 256 + 2 + 1 + + + 1 + 1.3977056600717938E8 + 90.0 -180.0 + 256 + 256 + 4 + 2 + + + 2 + 6.988528300358969E7 + 90.0 -180.0 + 256 + 256 + 8 + 4 + + + 3 + 3.4942641501794845E7 + 90.0 -180.0 + 256 + 256 + 16 + 8 + + + 4 + 1.7471320750897422E7 + 90.0 -180.0 + 256 + 256 + 32 + 16 + + + 5 + 8735660.375448711 + 90.0 -180.0 + 256 + 256 + 64 + 32 + + + 6 + 4367830.187724356 + 90.0 -180.0 + 256 + 256 + 128 + 64 + + + 7 + 2183915.093862178 + 90.0 -180.0 + 256 + 256 + 256 + 128 + + + 8 + 1091957.546931089 + 90.0 -180.0 + 256 + 256 + 512 + 256 + + + 9 + 545978.7734655445 + 90.0 -180.0 + 256 + 256 + 1024 + 512 + + + 10 + 272989.3867327722 + 90.0 -180.0 + 256 + 256 + 2048 + 1024 + + + 11 + 136494.6933663861 + 90.0 -180.0 + 256 + 256 + 4096 + 2048 + + + 12 + 68247.34668319306 + 90.0 -180.0 + 256 + 256 + 8192 + 4096 + + + + + diff --git a/fixtures/wmts/capabilities_wgs84_with_boundingbox.xml b/fixtures/wmts/capabilities_wgs84_with_boundingbox.xml new file mode 100644 index 0000000..45693a6 --- /dev/null +++ b/fixtures/wmts/capabilities_wgs84_with_boundingbox.xml @@ -0,0 +1,705 @@ + + + basemap.at + basemap.at ist eine Rasterkarte in Form eines vorgenerierten Kachel-Caches, in der Web Mercator + Auxiliary Sphere und damit kompatibel zu den gängigen weltweiten Basiskarten wie beispielsweise jenen von + OpenStreetMap, Google Maps und Bing Maps. + + OGC WMTS + 1.0.0 + none + https://www.basemap.at + + + City of Vienna + + + + + Vienna + Austria + viennagis@post.wien.gv.at + + + + + + + + + + + + RESTful + + + + + + + RESTful + + + + + + + RESTful + + + + + + + RESTful + + + + + + + RESTful + + + + + + + + + + + + + RESTful + + + + + + + RESTful + + + + + + + RESTful + + + + + + + RESTful + + + + + + + RESTful + + + + + + + + + + Geoland Basemap + Basemap von Österreich in Farbe + + 8.782379 46.358770 + 17.5 49.037872 + + geolandbasemap + + image/png + + google3857 + + + + + + + + + Geoland Basemap Overlay + Basemap von Österreich nur Beschriftung als transparenter Layer + + 8.782379 46.358770 + 17.5 49.037872 + + bmapoverlay + + image/png + + google3857 + + + + + + + + + Geoland Basemap Grau + Basemap von Österreich in Grau + + 8.782379 46.358770 + 17.5 49.037872 + + bmapgrau + + image/png + + google3857 + + + + + + + + + Basemap High DPI + Basemap mit 512x512px Kacheln für Unterstützung von User Endgeräten mit high dpi Displays + (iPad retina, smartphones mit HD Auflösung) + + + 8.782379 46.358770 + 17.5 49.037872 + + bmaphidpi + + image/jpeg + + google3857 + + + + + + + + + Geoland Basemap Orthofoto + Basemap als farbiges Orthofoto + + 8.782379 46.358770 + 17.5 49.037872 + + bmaporthofoto30cm + + image/jpeg + + google3857 + + + + + + + + + Geoland Basemap Gelände + Basemap Geländedarstellung von Österreich in Grau + + 8.782379 46.358770 + 17.5 49.037872 + + bmapgelaende + + image/jpeg + + google3857_0-17 + + + + + + + + + Geoland Basemap Oberfläche + Basemap Oberflächendarstellung von Österreich in Grau + + 8.782379 46.358770 + 17.5 49.037872 + + bmapoberflaeche + + image/jpeg + + google3857_0-17 + + + + + + + + + google3857 + + 977650 5838030 + 1913530 6281290 + + urn:ogc:def:crs:EPSG:6.18.3:3857 + urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible + + 0 + 559082264.029 + -20037508.3428 20037508.3428 + 256 + 256 + 1 + 1 + + + 1 + 279541132.015 + -20037508.3428 20037508.3428 + 256 + 256 + 2 + 2 + + + 2 + 139770566.007 + -20037508.3428 20037508.3428 + 256 + 256 + 4 + 4 + + + 3 + 69885283.0036 + -20037508.3428 20037508.3428 + 256 + 256 + 8 + 8 + + + 4 + 34942641.5018 + -20037508.3428 20037508.3428 + 256 + 256 + 16 + 16 + + + 5 + 17471320.7509 + -20037508.3428 20037508.3428 + 256 + 256 + 32 + 32 + + + 6 + 8735660.37545 + -20037508.3428 20037508.3428 + 256 + 256 + 64 + 64 + + + 7 + 4367830.18773 + -20037508.3428 20037508.3428 + 256 + 256 + 128 + 128 + + + 8 + 2183915.09386 + -20037508.3428 20037508.3428 + 256 + 256 + 256 + 256 + + + 9 + 1091957.54693 + -20037508.3428 20037508.3428 + 256 + 256 + 512 + 512 + + + 10 + 545978.773466 + -20037508.3428 20037508.3428 + 256 + 256 + 1024 + 1024 + + + 11 + 272989.386733 + -20037508.3428 20037508.3428 + 256 + 256 + 2048 + 2048 + + + 12 + 136494.693366 + -20037508.3428 20037508.3428 + 256 + 256 + 4096 + 4096 + + + 13 + 68247.3466832 + -20037508.3428 20037508.3428 + 256 + 256 + 8192 + 8192 + + + 14 + 34123.6733416 + -20037508.3428 20037508.3428 + 256 + 256 + 16384 + 16384 + + + 15 + 17061.8366708 + -20037508.3428 20037508.3428 + 256 + 256 + 32768 + 32768 + + + 16 + 8530.91833540 + -20037508.3428 20037508.3428 + 256 + 256 + 65536 + 65536 + + + 17 + 4265.45916770 + -20037508.3428 20037508.3428 + 256 + 256 + 131072 + 131072 + + + 18 + 2132.72958385 + -20037508.3428 20037508.3428 + 256 + 256 + 262144 + 262144 + + + 19 + 1066.36479193 + -20037508.3428 20037508.3428 + 256 + 256 + 524288 + 524288 + + + 20 + 533.18239597 + -20037508.3428 20037508.3428 + 256 + 256 + 1048576 + 1048576 + + + + google3857_0-17 + + 977650 5838030 + 1913530 6281290 + + urn:ogc:def:crs:EPSG:6.18.3:3857 + urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible + + 0 + 559082264.029 + -20037508.3428 20037508.3428 + 256 + 256 + 1 + 1 + + + 1 + 279541132.015 + -20037508.3428 20037508.3428 + 256 + 256 + 2 + 2 + + + 2 + 139770566.007 + -20037508.3428 20037508.3428 + 256 + 256 + 4 + 4 + + + 3 + 69885283.0036 + -20037508.3428 20037508.3428 + 256 + 256 + 8 + 8 + + + 4 + 34942641.5018 + -20037508.3428 20037508.3428 + 256 + 256 + 16 + 16 + + + 5 + 17471320.7509 + -20037508.3428 20037508.3428 + 256 + 256 + 32 + 32 + + + 6 + 8735660.37545 + -20037508.3428 20037508.3428 + 256 + 256 + 64 + 64 + + + 7 + 4367830.18773 + -20037508.3428 20037508.3428 + 256 + 256 + 128 + 128 + + + 8 + 2183915.09386 + -20037508.3428 20037508.3428 + 256 + 256 + 256 + 256 + + + 9 + 1091957.54693 + -20037508.3428 20037508.3428 + 256 + 256 + 512 + 512 + + + 10 + 545978.773466 + -20037508.3428 20037508.3428 + 256 + 256 + 1024 + 1024 + + + 11 + 272989.386733 + -20037508.3428 20037508.3428 + 256 + 256 + 2048 + 2048 + + + 12 + 136494.693366 + -20037508.3428 20037508.3428 + 256 + 256 + 4096 + 4096 + + + 13 + 68247.3466832 + -20037508.3428 20037508.3428 + 256 + 256 + 8192 + 8192 + + + 14 + 34123.6733416 + -20037508.3428 20037508.3428 + 256 + 256 + 16384 + 16384 + + + 15 + 17061.8366708 + -20037508.3428 20037508.3428 + 256 + 256 + 32768 + 32768 + + + 16 + 8530.91833540 + -20037508.3428 20037508.3428 + 256 + 256 + 65536 + 65536 + + + 17 + 4265.45916770 + -20037508.3428 20037508.3428 + 256 + 256 + 131072 + 131072 + + + + + diff --git a/fixtures/wmts/capabilities_with_tilematrixsetlink.xml b/fixtures/wmts/capabilities_with_tilematrixsetlink.xml new file mode 100644 index 0000000..3dfb454 --- /dev/null +++ b/fixtures/wmts/capabilities_with_tilematrixsetlink.xml @@ -0,0 +1,287 @@ + + + + Sample WMTS + OGC WMTS + 1.0.0 + None + none + + + + + + + + + + + + + + + + + + Baselayer + Baselayer + baselayer + + -180.0 -90.0 + 180.0 90.0 + + + -90.0 -180.0 + 90.0 180.0 + + + image/png + + inspire_quad + + + + + Mean depth full coverage with land coverage + + mean_atlas_land + + -36.0 15.0 + 43.0 90.0 + + + 14.999942759061003 -36.0 + 90.0 42.999938986416 + + + image/png + + inspire_quad + + + 0 + 0 + 0 + 0 + 1 + + + 1 + 0 + 0 + 1 + 2 + + + 2 + 0 + 1 + 3 + 4 + + + 3 + 0 + 3 + 6 + 9 + + + 4 + 0 + 6 + 12 + 19 + + + 5 + 0 + 13 + 25 + 39 + + + 6 + 0 + 26 + 51 + 79 + + + 7 + 0 + 53 + 102 + 158 + + + 8 + 0 + 106 + 204 + 317 + + + 9 + 0 + 213 + 409 + 634 + + + 10 + 0 + 426 + 819 + 1268 + + + 11 + 0 + 853 + 1638 + 2537 + + + 12 + 0 + 1706 + 3276 + 5074 + + + + + + + InspireCRS84Quad + inspire_quad + urn:ogc:def:crs:EPSG::4326 + + 0 + 279541132.014357 + 90.0 -180.0 + 256 + 256 + 2 + 1 + + + 1 + 1.3977056600717938E8 + 90.0 -180.0 + 256 + 256 + 4 + 2 + + + 2 + 6.988528300358969E7 + 90.0 -180.0 + 256 + 256 + 8 + 4 + + + 3 + 3.4942641501794845E7 + 90.0 -180.0 + 256 + 256 + 16 + 8 + + + 4 + 1.7471320750897422E7 + 90.0 -180.0 + 256 + 256 + 32 + 16 + + + 5 + 8735660.375448711 + 90.0 -180.0 + 256 + 256 + 64 + 32 + + + 6 + 4367830.187724356 + 90.0 -180.0 + 256 + 256 + 128 + 64 + + + 7 + 2183915.093862178 + 90.0 -180.0 + 256 + 256 + 256 + 128 + + + 8 + 1091957.546931089 + 90.0 -180.0 + 256 + 256 + 512 + 256 + + + 9 + 545978.7734655445 + 90.0 -180.0 + 256 + 256 + 1024 + 512 + + + 10 + 272989.3867327722 + 90.0 -180.0 + 256 + 256 + 2048 + 1024 + + + 11 + 136494.6933663861 + 90.0 -180.0 + 256 + 256 + 4096 + 2048 + + + 12 + 68247.34668319306 + 90.0 -180.0 + 256 + 256 + 8192 + 4096 + + + + + diff --git a/fixtures/wmts/capabilities_wrapx.xml b/fixtures/wmts/capabilities_wrapx.xml new file mode 100644 index 0000000..a410e03 --- /dev/null +++ b/fixtures/wmts/capabilities_wrapx.xml @@ -0,0 +1,1109 @@ + + + + OGC WMTS + 1.0.0 + + + + + + + + + + + + + + KVP + + + + + + + + + + + + + KVP + + + + + + + + + + + + + KVP + + + + + + + + + + no-bb + no-bb + + image/png; mode=8bit + + wrap + + + wrap:0 + 0 + 0 + 0 + 0 + + + wrap:1 + 0 + 1 + 0 + 1 + + + wrap:2 + 0 + 3 + 0 + 3 + + + wrap:3 + 0 + 7 + 0 + 7 + + + wrap:4 + 0 + 15 + 0 + 15 + + + wrap:5 + 0 + 31 + 0 + 31 + + + wrap:6 + 0 + 63 + 0 + 63 + + + wrap:7 + 0 + 127 + 0 + 127 + + + wrap:8 + 0 + 255 + 0 + 255 + + + wrap:9 + 0 + 511 + 0 + 511 + + + wrap:10 + 0 + 1023 + 0 + 1023 + + + wrap:11 + 0 + 2047 + 0 + 2047 + + + wrap:12 + 0 + 4095 + 0 + 4095 + + + wrap:13 + 0 + 8191 + 0 + 8191 + + + wrap:14 + 0 + 16383 + 0 + 16383 + + + wrap:15 + 0 + 32767 + 0 + 32767 + + + wrap:16 + 0 + 65535 + 0 + 65535 + + + wrap:17 + 0 + 131071 + 0 + 131071 + + + wrap:18 + 0 + 262143 + 0 + 262143 + + + wrap:19 + 0 + 524287 + 0 + 524287 + + + wrap:20 + 0 + 1048575 + 0 + 1048575 + + + + + + + only-wgs84-bb + only-wgs84-bb + + -180.0 -90.0 + 180.0 90.0 + + + image/png; mode=8bit + + wrap + + + wrap:0 + 0 + 0 + 0 + 0 + + + wrap:1 + 0 + 1 + 0 + 1 + + + wrap:2 + 0 + 3 + 0 + 3 + + + wrap:3 + 0 + 7 + 0 + 7 + + + wrap:4 + 0 + 15 + 0 + 15 + + + wrap:5 + 0 + 31 + 0 + 31 + + + wrap:6 + 0 + 63 + 0 + 63 + + + wrap:7 + 0 + 127 + 0 + 127 + + + wrap:8 + 0 + 255 + 0 + 255 + + + wrap:9 + 0 + 511 + 0 + 511 + + + wrap:10 + 0 + 1023 + 0 + 1023 + + + wrap:11 + 0 + 2047 + 0 + 2047 + + + wrap:12 + 0 + 4095 + 0 + 4095 + + + wrap:13 + 0 + 8191 + 0 + 8191 + + + wrap:14 + 0 + 16383 + 0 + 16383 + + + wrap:15 + 0 + 32767 + 0 + 32767 + + + wrap:16 + 0 + 65535 + 0 + 65535 + + + wrap:17 + 0 + 131071 + 0 + 131071 + + + wrap:18 + 0 + 262143 + 0 + 262143 + + + wrap:19 + 0 + 524287 + 0 + 524287 + + + wrap:20 + 0 + 1048575 + 0 + 1048575 + + + + + + + no-wrap-wgs84-bb + no-wrap-wgs84-bb + + -179.9 -90.0 + 180.0 90.0 + + + image/png; mode=8bit + + wrap + + + wrap:0 + 0 + 0 + 0 + 0 + + + wrap:1 + 0 + 1 + 0 + 1 + + + wrap:2 + 0 + 3 + 0 + 3 + + + wrap:3 + 0 + 7 + 0 + 7 + + + wrap:4 + 0 + 15 + 0 + 15 + + + wrap:5 + 0 + 31 + 0 + 31 + + + wrap:6 + 0 + 63 + 0 + 63 + + + wrap:7 + 0 + 127 + 0 + 127 + + + wrap:8 + 0 + 255 + 0 + 255 + + + wrap:9 + 0 + 511 + 0 + 511 + + + wrap:10 + 0 + 1023 + 0 + 1023 + + + wrap:11 + 0 + 2047 + 0 + 2047 + + + wrap:12 + 0 + 4095 + 0 + 4095 + + + wrap:13 + 0 + 8191 + 0 + 8191 + + + wrap:14 + 0 + 16383 + 0 + 16383 + + + wrap:15 + 0 + 32767 + 0 + 32767 + + + wrap:16 + 0 + 65535 + 0 + 65535 + + + wrap:17 + 0 + 131071 + 0 + 131071 + + + wrap:18 + 0 + 262143 + 0 + 262143 + + + wrap:19 + 0 + 524287 + 0 + 524287 + + + wrap:20 + 0 + 1048575 + 0 + 1048575 + + + + + + + no-wrap-tm + no-wrap-tm + + image/png; mode=8bit + + no-wrap + + + no-wrap:0 + 0 + 0 + 0 + 0 + + + no-wrap:1 + 0 + 1 + 0 + 1 + + + no-wrap:2 + 0 + 3 + 0 + 3 + + + no-wrap:3 + 0 + 7 + 0 + 7 + + + no-wrap:4 + 0 + 15 + 0 + 15 + + + no-wrap:5 + 0 + 31 + 0 + 31 + + + no-wrap:6 + 0 + 63 + 0 + 63 + + + no-wrap:7 + 0 + 127 + 0 + 127 + + + no-wrap:8 + 0 + 255 + 0 + 255 + + + no-wrap:9 + 0 + 511 + 0 + 511 + + + no-wrap:10 + 0 + 1023 + 0 + 1023 + + + no-wrap:11 + 0 + 2047 + 0 + 2047 + + + no-wrap:12 + 0 + 4095 + 0 + 4095 + + + no-wrap:13 + 0 + 8191 + 0 + 8191 + + + no-wrap:14 + 0 + 16383 + 0 + 16383 + + + no-wrap:15 + 0 + 32767 + 0 + 32767 + + + no-wrap:16 + 0 + 65535 + 0 + 65535 + + + no-wrap:17 + 0 + 131071 + 0 + 131071 + + + no-wrap:18 + 0 + 262143 + 0 + 262143 + + + no-wrap:19 + 0 + 524287 + 0 + 524287 + + + no-wrap:20 + 0 + 1048575 + 0 + 1048575 + + + + + + + wrap + + -20037508.342789 -20037508.342789 + 20037508.342789 20037508.342789 + + urn:ogc:def:crs:EPSG:6.3:3857 + + 0 + 559082264.02871787548065185547 + -20037508.342789 20037508.342789 + 256 + 256 + 1 + 1 + + + 1 + 279541132.01435893774032592773 + -20037508.342789 20037508.342789 + 256 + 256 + 2 + 2 + + + 2 + 139770566.00717946887016296387 + -20037508.342789 20037508.342789 + 256 + 256 + 4 + 4 + + + 3 + 69885283.00358973443508148193 + -20037508.342789 20037508.342789 + 256 + 256 + 8 + 8 + + + 4 + 34942641.50179486721754074097 + -20037508.342789 20037508.342789 + 256 + 256 + 16 + 16 + + + 5 + 17471320.75089743360877037048 + -20037508.342789 20037508.342789 + 256 + 256 + 32 + 32 + + + 6 + 8735660.37544871680438518524 + -20037508.342789 20037508.342789 + 256 + 256 + 64 + 64 + + + 7 + 4367830.18772435840219259262 + -20037508.342789 20037508.342789 + 256 + 256 + 128 + 128 + + + 8 + 2183915.09386217920109629631 + -20037508.342789 20037508.342789 + 256 + 256 + 256 + 256 + + + 9 + 1091957.54693108960054814816 + -20037508.342789 20037508.342789 + 256 + 256 + 512 + 512 + + + 10 + 545978.77346554480027407408 + -20037508.342789 20037508.342789 + 256 + 256 + 1024 + 1024 + + + 11 + 272989.38673277240013703704 + -20037508.342789 20037508.342789 + 256 + 256 + 2048 + 2048 + + + 12 + 136494.69336638620006851852 + -20037508.342789 20037508.342789 + 256 + 256 + 4096 + 4096 + + + 13 + 68247.34668319310003425926 + -20037508.342789 20037508.342789 + 256 + 256 + 8192 + 8192 + + + 14 + 34123.67334159655001712963 + -20037508.342789 20037508.342789 + 256 + 256 + 16384 + 16384 + + + 15 + 17061.83667079827500856481 + -20037508.342789 20037508.342789 + 256 + 256 + 32768 + 32768 + + + 16 + 8530.91833539913750428241 + -20037508.342789 20037508.342789 + 256 + 256 + 65536 + 65536 + + + 17 + 4265.45916769956875214120 + -20037508.342789 20037508.342789 + 256 + 256 + 131072 + 131072 + + + 18 + 2132.72958384978437607060 + -20037508.342789 20037508.342789 + 256 + 256 + 262144 + 262144 + + + 19 + 1066.36479192489218803530 + -20037508.342789 20037508.342789 + 256 + 256 + 524288 + 524288 + + + 20 + 533.18239596244609401765 + -20037508.342789 20037508.342789 + 256 + 256 + 1048576 + 1048576 + + + + no-wrap + + -19037508.342789 -20037508.342789 + 20037508.342789 20037508.342789 + + urn:ogc:def:crs:EPSG:6.3:3857 + + 0 + 559082264.02871787548065185547 + -20037508.342789 20037508.342789 + 256 + 256 + 1 + 1 + + + 1 + 279541132.01435893774032592773 + -20037508.342789 20037508.342789 + 256 + 256 + 2 + 2 + + + 2 + 139770566.00717946887016296387 + -20037508.342789 20037508.342789 + 256 + 256 + 4 + 4 + + + 3 + 69885283.00358973443508148193 + -20037508.342789 20037508.342789 + 256 + 256 + 8 + 8 + + + 4 + 34942641.50179486721754074097 + -20037508.342789 20037508.342789 + 256 + 256 + 16 + 16 + + + 5 + 17471320.75089743360877037048 + -20037508.342789 20037508.342789 + 256 + 256 + 32 + 32 + + + 6 + 8735660.37544871680438518524 + -20037508.342789 20037508.342789 + 256 + 256 + 64 + 64 + + + 7 + 4367830.18772435840219259262 + -20037508.342789 20037508.342789 + 256 + 256 + 128 + 128 + + + 8 + 2183915.09386217920109629631 + -20037508.342789 20037508.342789 + 256 + 256 + 256 + 256 + + + 9 + 1091957.54693108960054814816 + -20037508.342789 20037508.342789 + 256 + 256 + 512 + 512 + + + 10 + 545978.77346554480027407408 + -20037508.342789 20037508.342789 + 256 + 256 + 1024 + 1024 + + + 11 + 272989.38673277240013703704 + -20037508.342789 20037508.342789 + 256 + 256 + 2048 + 2048 + + + 12 + 136494.69336638620006851852 + -20037508.342789 20037508.342789 + 256 + 256 + 4096 + 4096 + + + 13 + 68247.34668319310003425926 + -20037508.342789 20037508.342789 + 256 + 256 + 8192 + 8192 + + + 14 + 34123.67334159655001712963 + -20037508.342789 20037508.342789 + 256 + 256 + 16384 + 16384 + + + 15 + 17061.83667079827500856481 + -20037508.342789 20037508.342789 + 256 + 256 + 32768 + 32768 + + + 16 + 8530.91833539913750428241 + -20037508.342789 20037508.342789 + 256 + 256 + 65536 + 65536 + + + 17 + 4265.45916769956875214120 + -20037508.342789 20037508.342789 + 256 + 256 + 131072 + 131072 + + + 18 + 2132.72958384978437607060 + -20037508.342789 20037508.342789 + 256 + 256 + 262144 + 262144 + + + 19 + 1066.36479192489218803530 + -20037508.342789 20037508.342789 + 256 + 256 + 524288 + 524288 + + + 20 + 533.18239596244609401765 + -20037508.342789 20037508.342789 + 256 + 256 + 1048576 + 1048576 + + + + diff --git a/fixtures/wmts/ign.xml b/fixtures/wmts/ign.xml new file mode 100644 index 0000000..6cd8d54 --- /dev/null +++ b/fixtures/wmts/ign.xml @@ -0,0 +1,501 @@ + + + + Service de visualisation WMTS + + Ce service permet la visualisation de couches de données raster IGN au travers d'un flux WMTS + + + Unités administratives + Limites administratives + Surfaces bâties + Réseaux de transport + Routes + Réseaux ferroviaires + Aérodromes + Réseau hydrographique + Parcelles cadastrales + Bâtiments + Services d'utilité publique et services publics + Réseaux de transport + Hydrographie + Photographies aériennes + Cartes + Cartes historiques + Altitude + + OGC WMTS + 1.0.0 + licences + + Conditions Générales d'Utilisation disponibles ici : + http://professionnels.ign.fr/doc/Conditions_d_utilisation_des_licences_et_des_services_en_ligne.pdf + + + + IGN + + + Géoportail SAV + custodian + + + + + + + 73 avenue de Paris + Saint Mandé + + 94160 + France + geop_services@geoportail.fr + + + + + + + + + + + + KVP + + + + + + + + + + + + + KVP + + + + + + + + + + Photographies aériennes + Photographies aériennes + + Photographies + + + -180 -86 + 180 84 + + ORTHOIMAGERY.ORTHOPHOTOS + + image/jpeg + + PM + + + 0 + 0 + 1 + 0 + 1 + + + 1 + 0 + 2 + 0 + 2 + + + 10 + 31 + 1024 + 0 + 1024 + + + 11 + 62 + 2048 + 0 + 2048 + + + 12 + 125 + 4096 + 0 + 4096 + + + 13 + 2739 + 4628 + 41 + 7917 + + + 14 + 5478 + 9256 + 82 + 15835 + + + 15 + 10956 + 18513 + 165 + 31670 + + + 16 + 21912 + 37026 + 330 + 63341 + + + 17 + 43825 + 74052 + 660 + 126683 + + + 18 + 87651 + 148105 + 1320 + 253366 + + + 19 + 175302 + 294060 + 170159 + 343473 + + + 2 + 0 + 4 + 0 + 4 + + + 3 + 0 + 8 + 0 + 8 + + + 4 + 0 + 16 + 0 + 16 + + + 5 + 0 + 32 + 0 + 32 + + + 6 + 1 + 64 + 0 + 64 + + + 7 + 3 + 128 + 0 + 128 + + + 8 + 7 + 256 + 0 + 256 + + + 9 + 15 + 512 + 0 + 512 + + + + + Prefixed + + + Prefixed:0 + 0 + 1 + 0 + 1 + + + Prefixed:1 + 0 + 2 + 0 + 2 + + + + + + PM + EPSG:3857 + + 0 + 559082264.0287178958533332 + -20037508 20037508 + 256 + 256 + 1 + 1 + + + 1 + 279541132.0143588959472254 + -20037508 20037508 + 256 + 256 + 2 + 2 + + + 10 + 545978.7734655447186469 + -20037508 20037508 + 256 + 256 + 1024 + 1024 + + + 11 + 272989.3867327723085907 + -20037508 20037508 + 256 + 256 + 2048 + 2048 + + + 12 + 136494.6933663861796617 + -20037508 20037508 + 256 + 256 + 4096 + 4096 + + + 13 + 68247.3466831930771477 + -20037508 20037508 + 256 + 256 + 8192 + 8192 + + + 14 + 34123.6733415965449154 + -20037508 20037508 + 256 + 256 + 16384 + 16384 + + + 15 + 17061.8366707982724577 + -20037508 20037508 + 256 + 256 + 32768 + 32768 + + + 16 + 8530.9183353991362289 + -20037508 20037508 + 256 + 256 + 65536 + 65536 + + + 17 + 4265.4591676995681144 + -20037508 20037508 + 256 + 256 + 131072 + 131072 + + + 18 + 2132.7295838497840572 + -20037508 20037508 + 256 + 256 + 262144 + 262144 + + + 19 + 1066.3647919248918304 + -20037508 20037508 + 256 + 256 + 524288 + 524288 + + + 2 + 139770566.0071793960087234 + -20037508 20037508 + 256 + 256 + 4 + 4 + + + 20 + 533.1823959624461134 + -20037508 20037508 + 256 + 256 + 1048576 + 1048576 + + + 21 + 266.5911979812228585 + -20037508 20037508 + 256 + 256 + 2097152 + 2097152 + + + 3 + 69885283.0035897239868063 + -20037508 20037508 + 256 + 256 + 8 + 8 + + + 4 + 34942641.5017948619934032 + -20037508 20037508 + 256 + 256 + 16 + 16 + + + 5 + 17471320.7508974309967016 + -20037508 20037508 + 256 + 256 + 32 + 32 + + + 6 + 8735660.3754487154983508 + -20037508 20037508 + 256 + 256 + 64 + 64 + + + 7 + 4367830.1877243577491754 + -20037508 20037508 + 256 + 256 + 128 + 128 + + + 8 + 2183915.0938621788745877 + -20037508 20037508 + 256 + 256 + 256 + 256 + + + 9 + 1091957.5469310886252288 + -20037508 20037508 + 256 + 256 + 512 + 512 + + + + Prefixed + EPSG:3857 + + 0 + 559082264.0287178958533332 + -20037508 20037508 + 256 + 256 + 1 + 1 + + + 1 + 279541132.0143588959472254 + -20037508 20037508 + 256 + 256 + 2 + 2 + + + + diff --git a/fixtures/wmts/ogcsample.xml b/fixtures/wmts/ogcsample.xml new file mode 100644 index 0000000..524afd8 --- /dev/null +++ b/fixtures/wmts/ogcsample.xml @@ -0,0 +1,414 @@ + + + + Web Map Tile Service + Service that contrains the map + access interface to some TileMatrixSets + + + tile + tile matrix set + map + + OGC WMTS + 1.0.0 + none + none + + + MiraMon + + + Joan Maso Pau + Senior Software Engineer + + + +34 93 581 1312 + +34 93 581 4151 + + + Fac Ciencies UAB + Bellaterra + Barcelona + 08193 + Spain + joan.maso@uab.cat + + + + + + + + + + + + KVP + SOAP + + + + + + + + + + + + + KVP + + + + + + + + + + Blue Marble Next Generation + Blue Marble Next Generation NASA Product + + -180 -90 + 180 90 + + BlueMarbleNextGeneration + + + image/jpeg + image/gif + + BigWorldPixel + + + google3857 + + + google3857subset + + + + + Time + 20110805 + 20110805 + 20081024 + + + OtherDimension + abcd + + + + + google3857 + + 1799448.394855 6124949.747770 + 1848250.442089 6162571.828177 + + urn:ogc:def:crs:EPSG:6.18:3:3857 + urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible + + 0 + 559082264.029 + -20037508.3428 20037508.3428 + 256 + 256 + 1 + 1 + + + 1 + 279541132.015 + -20037508.3428 20037508.3428 + 256 + 256 + 2 + 2 + + + 2 + 139770566.007 + -20037508.3428 20037508.3428 + 256 + 256 + 4 + 4 + + + 3 + 69885283.0036 + -20037508.3428 20037508.3428 + 256 + 256 + 8 + 8 + + + 4 + 34942641.5018 + -20037508.3428 20037508.3428 + 256 + 256 + 16 + 16 + + + 5 + 17471320.7509 + -20037508.3428 20037508.3428 + 256 + 256 + 32 + 32 + + + 6 + 8735660.37545 + -20037508.3428 20037508.3428 + 256 + 256 + 64 + 64 + + + 7 + 4367830.18773 + -20037508.3428 20037508.3428 + 256 + 256 + 128 + 128 + + + 8 + 2183915.09386 + -20037508.3428 20037508.3428 + 256 + 256 + 256 + 256 + + + 9 + 1091957.54693 + -20037508.3428 20037508.3428 + 256 + 256 + 512 + 512 + + + 10 + 545978.773466 + -20037508.3428 20037508.3428 + 256 + 256 + 1024 + 1024 + + + 11 + 272989.386733 + -20037508.3428 20037508.3428 + 256 + 256 + 2048 + 2048 + + + 12 + 136494.693366 + -20037508.3428 20037508.3428 + 256 + 256 + 4096 + 4096 + + + 13 + 68247.3466832 + -20037508.3428 20037508.3428 + 256 + 256 + 8192 + 8192 + + + 14 + 34123.6733416 + -20037508.3428 20037508.3428 + 256 + 256 + 16384 + 16384 + + + 15 + 17061.8366708 + -20037508.3428 20037508.3428 + 256 + 256 + 32768 + 32768 + + + 16 + 8530.91833540 + -20037508.3428 20037508.3428 + 256 + 256 + 65536 + 65536 + + + 17 + 4265.45916770 + -20037508.3428 20037508.3428 + 256 + 256 + 131072 + 131072 + + + 18 + 2132.72958385 + -20037508.3428 20037508.3428 + 256 + 256 + 262144 + 262144 + + + 19 + 1066.36479193 + -20037508.3428 20037508.3428 + 256 + 256 + 524288 + 524288 + + + + BigWorldPixel + urn:ogc:def:crs:OGC:1.3:CRS84 + urn:ogc:def:wkss:OGC:1.0:GlobalCRS84Pixel + + 10000m + 33130800.83133142 + -180 90 + 640 + 480 + 7 + 5 + + + 20000m + 66261601.66266284 + -180 90 + 640 + 480 + 4 + 3 + + + 40000m + 132523203.3253257 + -180 90 + 640 + 480 + 2 + 2 + + + 60000m + 198784804.9879885 + -180 90 + 640 + 480 + 1 + 1 + + + 120000m + 397569609.9759771 + -180 90 + 640 + 480 + 1 + 1 + + + 240000m + 795139219.9519541 + -180 90 + 640 + 480 + 1 + 1 + + + + BigWorld + urn:ogc:def:crs:OGC:1.3:CRS84 + + 1e6 + 1e6 + -180 84 + 256 + 256 + 60000 + 50000 + + + 2.5e6 + 2.5e6 + -180 84 + 256 + 256 + 9000 + 7000 + + + + + google3857subset + urn:ogc:def:crs:EPSG:6.18:3:3857 + + 18 + 2132.72958385 + -10000000 10000000 + 256 + 256 + 1 + 1 + + + 18 + 1066.36479193 + -10000000 10000000 + 256 + 256 + 2 + 2 + + + + + diff --git a/src/wmts/capabilities.spec.ts b/src/wmts/capabilities.spec.ts new file mode 100644 index 0000000..e94c1ea --- /dev/null +++ b/src/wmts/capabilities.spec.ts @@ -0,0 +1,740 @@ +import { + readInfoFromCapabilities, + readLayersFromCapabilities, + readMatrixSetsFromCapabilities, +} from './capabilities'; +import { parseXmlString } from '../shared/xml-utils'; +// @ts-ignore +import ogcsample from '../../fixtures/wmts/ogcsample.xml'; +// @ts-ignore +import arcgis from '../../fixtures/wmts/arcgis.xml'; +// @ts-ignore +import ign from '../../fixtures/wmts/ign.xml'; + +describe('WMTS Capabilities', () => { + describe('readInfoFromCapabilities', () => { + describe('ogcsample.xml', () => { + it('parses the service info', () => { + const doc = parseXmlString(ogcsample); + expect(readInfoFromCapabilities(doc)).toEqual({ + abstract: + 'Service that contrains the map\n access interface to some TileMatrixSets\n ', + constraints: 'none', + fees: 'none', + keywords: ['tile', 'tile matrix set', 'map'], + name: 'OGC WMTS', + title: 'Web Map Tile Service', + getTileUrls: { + kvp: 'http://www.maps.bob/cgi-bin/MiraMon5_0.cgi?', + }, + }); + }); + }); + describe('arcgis.xml', () => { + it('parses the service info', () => { + const doc = parseXmlString(arcgis); + expect(readInfoFromCapabilities(doc)).toEqual({ + abstract: '', + constraints: '', + fees: '', + keywords: [], + name: 'OGC WMTS', + title: 'Demographics_USA_Population_Density', + getTileUrls: { + rest: 'https://services.arcgisonline.com/arcgis/rest/services/Demographics/USA_Population_Density/MapServer/WMTS/tile/1.0.0/', + kvp: 'https://services.arcgisonline.com/arcgis/rest/services/Demographics/USA_Population_Density/MapServer/WMTS?', + }, + }); + }); + }); + describe('ign.xml', () => { + it('parses the service info', () => { + const doc = parseXmlString(ign); + expect(readInfoFromCapabilities(doc)).toEqual({ + abstract: + "\n Ce service permet la visualisation de couches de données raster IGN au travers d'un flux WMTS\n ", + constraints: + "\n Conditions Générales d'Utilisation disponibles ici :\n http://professionnels.ign.fr/doc/Conditions_d_utilisation_des_licences_et_des_services_en_ligne.pdf\n ", + fees: 'licences', + keywords: [ + 'Unités administratives', + 'Limites administratives', + 'Surfaces bâties', + 'Réseaux de transport', + 'Routes', + 'Réseaux ferroviaires', + 'Aérodromes', + 'Réseau hydrographique', + 'Parcelles cadastrales', + 'Bâtiments', + "Services d'utilité publique et services publics", + 'Réseaux de transport', + 'Hydrographie', + 'Photographies aériennes', + 'Cartes', + 'Cartes historiques', + 'Altitude', + ], + name: 'OGC WMTS', + title: 'Service de visualisation WMTS', + getTileUrls: { + kvp: 'http://wxs.ign.fr/geoportail/wmts?', + }, + }); + }); + }); + }); + + describe('readMatrixSetsFromCapabilities', () => { + describe('ogcsample.xml', () => { + it('parses the matrix sets', () => { + const doc = parseXmlString(ogcsample); + expect(readMatrixSetsFromCapabilities(doc)).toEqual([ + { + boundingBox: [ + 1799448.394855, 6124949.74777, 1848250.442089, 6162571.828177, + ], + crs: 'urn:ogc:def:crs:EPSG:6.18:3:3857', + identifier: 'google3857', + tileMatrices: [ + { + identifier: '0', + matrixHeight: 1, + matrixWidth: 1, + scaleDenominator: 559082264.029, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '1', + matrixHeight: 2, + matrixWidth: 2, + scaleDenominator: 279541132.015, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '2', + matrixHeight: 4, + matrixWidth: 4, + scaleDenominator: 139770566.007, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '3', + matrixHeight: 8, + matrixWidth: 8, + scaleDenominator: 69885283.0036, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '4', + matrixHeight: 16, + matrixWidth: 16, + scaleDenominator: 34942641.5018, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '5', + matrixHeight: 32, + matrixWidth: 32, + scaleDenominator: 17471320.7509, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '6', + matrixHeight: 64, + matrixWidth: 64, + scaleDenominator: 8735660.37545, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '7', + matrixHeight: 128, + matrixWidth: 128, + scaleDenominator: 4367830.18773, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '8', + matrixHeight: 256, + matrixWidth: 256, + scaleDenominator: 2183915.09386, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '9', + matrixHeight: 512, + matrixWidth: 512, + scaleDenominator: 1091957.54693, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '10', + matrixHeight: 1024, + matrixWidth: 1024, + scaleDenominator: 545978.773466, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '11', + matrixHeight: 2048, + matrixWidth: 2048, + scaleDenominator: 272989.386733, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '12', + matrixHeight: 4096, + matrixWidth: 4096, + scaleDenominator: 136494.693366, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '13', + matrixHeight: 8192, + matrixWidth: 8192, + scaleDenominator: 68247.3466832, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '14', + matrixHeight: 16384, + matrixWidth: 16384, + scaleDenominator: 34123.6733416, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '15', + matrixHeight: 32768, + matrixWidth: 32768, + scaleDenominator: 17061.8366708, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '16', + matrixHeight: 65536, + matrixWidth: 65536, + scaleDenominator: 8530.9183354, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '17', + matrixHeight: 131072, + matrixWidth: 131072, + scaleDenominator: 4265.4591677, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '18', + matrixHeight: 262144, + matrixWidth: 262144, + scaleDenominator: 2132.72958385, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '19', + matrixHeight: 524288, + matrixWidth: 524288, + scaleDenominator: 1066.36479193, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + ], + wellKnownScaleSet: 'urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible', + }, + { + crs: 'urn:ogc:def:crs:OGC:1.3:CRS84', + identifier: 'BigWorldPixel', + tileMatrices: [ + { + identifier: '10000m', + matrixHeight: 5, + matrixWidth: 7, + scaleDenominator: 33130800.83133142, + tileHeight: 480, + tileWidth: 640, + topLeft: [-180, 90], + }, + { + identifier: '20000m', + matrixHeight: 3, + matrixWidth: 4, + scaleDenominator: 66261601.66266284, + tileHeight: 480, + tileWidth: 640, + topLeft: [-180, 90], + }, + { + identifier: '40000m', + matrixHeight: 2, + matrixWidth: 2, + scaleDenominator: 132523203.3253257, + tileHeight: 480, + tileWidth: 640, + topLeft: [-180, 90], + }, + { + identifier: '60000m', + matrixHeight: 1, + matrixWidth: 1, + scaleDenominator: 198784804.9879885, + tileHeight: 480, + tileWidth: 640, + topLeft: [-180, 90], + }, + { + identifier: '120000m', + matrixHeight: 1, + matrixWidth: 1, + scaleDenominator: 397569609.9759771, + tileHeight: 480, + tileWidth: 640, + topLeft: [-180, 90], + }, + { + identifier: '240000m', + matrixHeight: 1, + matrixWidth: 1, + scaleDenominator: 795139219.9519541, + tileHeight: 480, + tileWidth: 640, + topLeft: [-180, 90], + }, + ], + wellKnownScaleSet: 'urn:ogc:def:wkss:OGC:1.0:GlobalCRS84Pixel', + }, + { + crs: 'urn:ogc:def:crs:OGC:1.3:CRS84', + identifier: 'BigWorld', + tileMatrices: [ + { + identifier: '1e6', + matrixHeight: 50000, + matrixWidth: 60000, + scaleDenominator: 1000000, + tileHeight: 256, + tileWidth: 256, + topLeft: [-180, 84], + }, + { + identifier: '2.5e6', + matrixHeight: 7000, + matrixWidth: 9000, + scaleDenominator: 2500000, + tileHeight: 256, + tileWidth: 256, + topLeft: [-180, 84], + }, + ], + }, + { + crs: 'urn:ogc:def:crs:EPSG:6.18:3:3857', + identifier: 'google3857subset', + tileMatrices: [ + { + identifier: '18', + matrixHeight: 1, + matrixWidth: 1, + scaleDenominator: 2132.72958385, + tileHeight: 256, + tileWidth: 256, + topLeft: [-10000000, 10000000], + }, + { + identifier: '18', + matrixHeight: 2, + matrixWidth: 2, + scaleDenominator: 1066.36479193, + tileHeight: 256, + tileWidth: 256, + topLeft: [-10000000, 10000000], + }, + ], + }, + ]); + }); + }); + describe('arcgis.xml', () => { + it('parses the matrix sets', () => { + const doc = parseXmlString(arcgis); + expect(readMatrixSetsFromCapabilities(doc)).toEqual([ + { + crs: 'urn:ogc:def:crs:EPSG::3857', + identifier: 'default028mm', + tileMatrices: expect.arrayContaining([ + { + identifier: '0', + matrixHeight: 1, + matrixWidth: 1, + scaleDenominator: 559082264.0285016, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.342787, 20037508.342787], + }, + { + identifier: '1', + matrixHeight: 1, + matrixWidth: 1, + scaleDenominator: 279541132.01425034, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.342787, 20037508.342787], + }, + { + identifier: '2', + matrixHeight: 2, + matrixWidth: 2, + scaleDenominator: 139770566.00712562, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.342787, 20037508.342787], + }, + ]), + }, + { + crs: 'urn:ogc:def:crs:EPSG:6.18.3:3857', + identifier: 'GoogleMapsCompatible', + wellKnownScaleSet: 'urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible', + tileMatrices: expect.arrayContaining([ + { + identifier: '0', + matrixHeight: 1, + matrixWidth: 1, + scaleDenominator: 559082264.0287178, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.34278925, 20037508.34278925], + }, + { + identifier: '1', + matrixHeight: 2, + matrixWidth: 2, + scaleDenominator: 279541132.0143589, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.34278925, 20037508.34278925], + }, + { + identifier: '2', + matrixHeight: 4, + matrixWidth: 4, + scaleDenominator: 139770566.0071794, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.34278925, 20037508.34278925], + }, + ]), + }, + ]); + }); + }); + describe('ign.xml', () => { + it('parses the matrix sets', () => { + const doc = parseXmlString(ign); + expect(readMatrixSetsFromCapabilities(doc)).toEqual([ + { + crs: 'EPSG:3857', + identifier: 'PM', + tileMatrices: expect.arrayContaining([ + { + identifier: '0', + matrixHeight: 1, + matrixWidth: 1, + scaleDenominator: 559082264.0287179, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508, 20037508], + }, + { + identifier: '1', + matrixHeight: 2, + matrixWidth: 2, + scaleDenominator: 279541132.0143589, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508, 20037508], + }, + { + identifier: '2', + matrixHeight: 4, + matrixWidth: 4, + scaleDenominator: 139770566.0071794, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508, 20037508], + }, + ]), + }, + { + crs: 'EPSG:3857', + identifier: 'Prefixed', + tileMatrices: [ + { + identifier: '0', + matrixHeight: 1, + matrixWidth: 1, + scaleDenominator: 559082264.0287179, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508, 20037508], + }, + { + identifier: '1', + matrixHeight: 2, + matrixWidth: 2, + scaleDenominator: 279541132.0143589, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508, 20037508], + }, + ], + }, + ]); + }); + }); + }); + + describe('readLayersFromCapabilities', () => { + describe('ogcsample.xml', () => { + it('parses the layers', () => { + const doc = parseXmlString(ogcsample); + expect(readLayersFromCapabilities(doc)).toEqual([ + { + name: 'BlueMarbleNextGeneration', + title: 'Blue Marble Next Generation', + abstract: 'Blue Marble Next Generation NASA Product', + latLonBoundingBox: [-180, -90, 180, 90], + styles: [ + { + name: 'DarkBlue', + title: 'Dark Blue', + legendUrl: + 'http://www.miramon.uab.es/wmts/Coastlines/coastlines_darkBlue.png', + }, + { + name: 'thickAndRed', + title: 'Thick And Red', + }, + ], + defaultStyle: 'DarkBlue', + matrixSets: [ + { + identifier: 'BigWorldPixel', + crs: 'urn:ogc:def:crs:EPSG:6.18:3:3857', + limits: [], + }, + { + identifier: 'google3857', + crs: 'urn:ogc:def:crs:EPSG:6.18:3:3857', + limits: [], + }, + { + identifier: 'google3857subset', + crs: 'urn:ogc:def:crs:EPSG:6.18:3:3857', + limits: [], + }, + ], + resourceUrls: [ + { + encoding: 'REST', + format: 'image/png', + url: 'http://www.example.com/wmts/coastlines/{TileMatrix}/{TileRow}/{TileCol}.png', + }, + { + encoding: 'KVP', + format: 'image/jpeg', + url: 'http://www.maps.bob/cgi-bin/MiraMon5_0.cgi?', + }, + { + encoding: 'KVP', + format: 'image/gif', + url: 'http://www.maps.bob/cgi-bin/MiraMon5_0.cgi?', + }, + ], + dimensions: [ + { + defaultValue: '20110805', + identifier: 'Time', + values: [], + }, + { + defaultValue: 'abcd', + identifier: 'OtherDimension', + values: [], + }, + ], + }, + ]); + }); + }); + describe('arcgis.xml', () => { + it('parses the layers', () => { + const doc = parseXmlString(arcgis); + expect(readLayersFromCapabilities(doc)).toEqual([ + { + abstract: '', + defaultStyle: 'default', + dimensions: [], + latLonBoundingBox: [ + -178.2278219969978, 18.910787002877576, -66.95000499993604, + 71.38957425051252, + ], + matrixSets: [ + { + identifier: 'default028mm', + crs: 'urn:ogc:def:crs:EPSG::3857', + limits: [], + }, + { + identifier: 'GoogleMapsCompatible', + crs: 'urn:ogc:def:crs:EPSG::3857', + limits: [], + }, + ], + name: 'Demographics_USA_Population_Density', + styles: [ + { + name: 'default', + title: 'Default Style', + }, + ], + title: 'Demographics_USA_Population_Density', + resourceUrls: [ + { + encoding: 'REST', + format: 'image/png', + url: 'https://services.arcgisonline.com/arcgis/rest/services/Demographics/USA_Population_Density/MapServer/WMTS/tile/1.0.0/Demographics_USA_Population_Density/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png', + }, + { + encoding: 'REST', + format: 'image/jpeg', + url: 'https://services.arcgisonline.com/arcgis/rest/services/Demographics/USA_Population_Density/MapServer/WMTS/tile/1.0.0/Demographics_USA_Population_Density/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg', + }, + { + encoding: 'KVP', + format: 'image/png', + url: 'https://services.arcgisonline.com/arcgis/rest/services/Demographics/USA_Population_Density/MapServer/WMTS?', + }, + ], + }, + ]); + }); + }); + describe('ign.xml', () => { + it('parses the layers', () => { + const doc = parseXmlString(ign); + expect(readLayersFromCapabilities(doc)).toEqual([ + { + abstract: 'Photographies aériennes', + defaultStyle: 'normal', + dimensions: [], + latLonBoundingBox: [-180, -86, 180, 84], + matrixSets: [ + { + identifier: 'PM', + crs: 'EPSG:3857', + limits: expect.arrayContaining([ + { + maxTileCol: 1, + maxTileRow: 1, + minTileCol: 0, + minTileRow: 0, + tileMatrix: '0', + }, + { + maxTileCol: 2, + maxTileRow: 2, + minTileCol: 0, + minTileRow: 0, + tileMatrix: '1', + }, + { + maxTileCol: 4, + maxTileRow: 4, + minTileCol: 0, + minTileRow: 0, + tileMatrix: '2', + }, + ]), + }, + { + identifier: 'Prefixed', + crs: 'EPSG:3857', + limits: [ + { + maxTileCol: 1, + maxTileRow: 1, + minTileCol: 0, + minTileRow: 0, + tileMatrix: 'Prefixed:0', + }, + { + maxTileCol: 2, + maxTileRow: 2, + minTileCol: 0, + minTileRow: 0, + tileMatrix: 'Prefixed:1', + }, + ], + }, + ], + name: 'ORTHOIMAGERY.ORTHOPHOTOS', + resourceUrls: [ + { + encoding: 'KVP', + format: 'image/jpeg', + url: 'http://wxs.ign.fr/geoportail/wmts?', + }, + ], + styles: [ + { + legendUrl: 'http://www.geoportail.gouv.fr/depot/LEGEND.jpg', + name: 'normal', + title: 'Données Brutes', + }, + ], + title: 'Photographies aériennes', + }, + ]); + }); + }); + }); +}); diff --git a/src/wmts/capabilities.ts b/src/wmts/capabilities.ts new file mode 100644 index 0000000..94d4d56 --- /dev/null +++ b/src/wmts/capabilities.ts @@ -0,0 +1,235 @@ +import { XmlDocument, XmlElement } from '@rgrove/parse-xml'; +import { BoundingBox } from '../shared/models'; +import { + findChildElement, + findChildrenElement, + getElementAttribute, + getElementText, + getRootElement, +} from '../shared/xml-utils'; +import { + LayerResourceUrl, + LayerStyle, + MatrixSetLink, + TileMatrix, + WmtsEndpointInfo, + WmtsLayer, + WmtsMatrixSet, +} from './model'; + +function parseBBox(xmlElement: XmlElement): BoundingBox { + const result = ['LowerCorner', 'UpperCorner'] + .map((elName) => findChildElement(xmlElement, elName)) + .map((cornerEl) => getElementText(cornerEl).split(' ')) + .reduce((prev, curr) => [...prev, ...curr]) + .map(parseFloat) as BoundingBox; + if (result.some(Number.isNaN)) return null; + return result; +} + +export function readInfoFromCapabilities( + capabilitiesDoc: XmlDocument +): WmtsEndpointInfo { + const rootEl = getRootElement(capabilitiesDoc); + const service = findChildElement(rootEl, 'ServiceIdentification'); + const keywords = findChildrenElement( + findChildElement(service, 'Keywords'), + 'Keyword' + ).map(getElementText); + const metadata = findChildElement(rootEl, 'OperationsMetadata'); + const getTileOperation = findChildrenElement(metadata, 'Operation').find( + (el) => getElementAttribute(el, 'name') == 'GetTile' + ); + const getTileUrls = findChildrenElement(getTileOperation, 'Get', true).reduce( + (prev, curr) => { + const encodingType = getElementText( + findChildElement(curr, 'Value', true) + ); + const url = getElementAttribute(curr, 'xlink:href'); + if (encodingType.toLowerCase() === 'restful') + return { ...prev, rest: url }; + return { ...prev, kvp: url }; + }, + {} + ); + + return { + title: getElementText(findChildElement(service, 'Title')), + name: getElementText(findChildElement(service, 'ServiceType')), + abstract: getElementText(findChildElement(service, 'Abstract')), + fees: getElementText(findChildElement(service, 'Fees')), + constraints: getElementText(findChildElement(service, 'AccessConstraints')), + keywords, + getTileUrls, + }; +} + +export function readMatrixSetsFromCapabilities( + capabilitiesDoc: XmlDocument +): WmtsMatrixSet[] { + function parseMatrixSet(element: XmlElement): TileMatrix { + const topLeft = getElementText(findChildElement(element, 'TopLeftCorner')) + .split(' ') + .map(parseFloat) as [number, number]; + return { + identifier: getElementText(findChildElement(element, 'Identifier')), + tileWidth: parseInt( + getElementText(findChildElement(element, 'TileWidth')) + ), + tileHeight: parseInt( + getElementText(findChildElement(element, 'TileHeight')) + ), + matrixWidth: parseInt( + getElementText(findChildElement(element, 'MatrixWidth')) + ), + matrixHeight: parseInt( + getElementText(findChildElement(element, 'MatrixHeight')) + ), + scaleDenominator: parseFloat( + getElementText(findChildElement(element, 'ScaleDenominator')) + ), + topLeft, + }; + } + const contents = findChildElement( + getRootElement(capabilitiesDoc), + 'Contents' + ); + const matrixSets = findChildrenElement(contents, 'TileMatrixSet'); + return matrixSets.map((element) => { + const wellKnownScaleSet = getElementText( + findChildElement(element, 'WellKnownScaleSet') + ); + const boundingBox = parseBBox(findChildElement(element, 'BoundingBox')); + return { + identifier: getElementText(findChildElement(element, 'Identifier')), + crs: getElementText(findChildElement(element, 'SupportedCRS')), + tileMatrices: findChildrenElement(element, 'TileMatrix').map( + parseMatrixSet + ), + ...(boundingBox && { boundingBox }), + ...(wellKnownScaleSet && { wellKnownScaleSet }), + }; + }); +} + +export function readLayersFromCapabilities( + capabilitiesDoc: XmlDocument +): WmtsLayer[] { + const rootEl = getRootElement(capabilitiesDoc); + const contentsEl = findChildElement(rootEl, 'Contents'); + function parseMatrixSetLink(element: XmlElement): MatrixSetLink { + const fullMatrixSet = findChildrenElement(contentsEl, 'TileMatrixSet').find( + (el) => getElementText(findChildElement(el, 'Identifier')) + ); + return { + identifier: getElementText(findChildElement(element, 'TileMatrixSet')), + crs: getElementText(findChildElement(fullMatrixSet, 'SupportedCRS')), + limits: findChildrenElement(element, 'TileMatrixLimits', true).map( + (element) => ({ + tileMatrix: getElementText(findChildElement(element, 'TileMatrix')), + minTileRow: parseInt( + getElementText(findChildElement(element, 'MinTileRow')) + ), + minTileCol: parseInt( + getElementText(findChildElement(element, 'MinTileCol')) + ), + maxTileRow: parseInt( + getElementText(findChildElement(element, 'MaxTileRow')) + ), + maxTileCol: parseInt( + getElementText(findChildElement(element, 'MaxTileCol')) + ), + }) + ), + }; + } + const getTileOperation = findChildrenElement( + findChildElement(rootEl, 'OperationsMetadata'), + 'Operation' + ).find((el) => getElementAttribute(el, 'name') == 'GetTile'); + const getKvpElt = findChildrenElement(getTileOperation, 'Get', true).filter( + (elt) => { + const encodingType = getElementText(findChildElement(elt, 'Value', true)); + return encodingType.toLowerCase() === 'kvp'; + } + )[0]; + const getKvpUrl = getKvpElt + ? getElementAttribute(getKvpElt, 'xlink:href') + : ''; + const contents = findChildElement(rootEl, 'Contents'); + const layers = findChildrenElement(contents, 'Layer'); + return layers.map((element) => { + const latLonBoundingBox = parseBBox( + findChildElement(element, 'WGS84BoundingBox') + ); + let defaultStyle = ''; + const styles = findChildrenElement(element, 'Style').map((element) => { + const legendUrl = getElementAttribute( + findChildElement(element, 'LegendURL'), + 'xlink:href' + ); + const style: LayerStyle = { + title: getElementText(findChildElement(element, 'Title')), + name: getElementText(findChildElement(element, 'Identifier')), + ...(legendUrl && { legendUrl }), + }; + if (getElementAttribute(element, 'isDefault') === 'true') { + defaultStyle = style.name; + } + return style; + }); + const outputFormats = findChildrenElement(element, 'Format').map( + getElementText + ); + const resourceUrls: LayerResourceUrl[] = findChildrenElement( + element, + 'ResourceURL' + ) + .filter( + (element) => getElementAttribute(element, 'resourceType') === 'tile' + ) + .map((element) => { + const format = getElementAttribute(element, 'format'); + const url = getElementAttribute(element, 'template'); + return { format, url, encoding: 'REST' as const }; + }); + if (getKvpUrl) { + resourceUrls.push( + ...outputFormats.map((format) => ({ + encoding: 'KVP' as const, + url: getKvpUrl, + format, + })) + ); + } + const matrixSets = findChildrenElement(element, 'TileMatrixSetLink').map( + parseMatrixSetLink + ); + const dimensions = findChildrenElement(element, 'Dimension').map( + (element) => { + const identifier = getElementText( + findChildElement(element, 'Identifier') + ); + const defaultValue = getElementText( + findChildElement(element, 'Default') + ); + const values = findChildrenElement(element, 'Values').map( + getElementText + ); + return { identifier, defaultValue, values }; + } + ); + return { + name: getElementText(findChildElement(element, 'Identifier')), + title: getElementText(findChildElement(element, 'Title')), + abstract: getElementText(findChildElement(element, 'Abstract')), + styles, + resourceUrls, + matrixSets, + defaultStyle, + ...(latLonBoundingBox && { latLonBoundingBox }), + ...(dimensions && { dimensions }), + } as WmtsLayer; + }); +} diff --git a/src/wmts/endpoint.spec.ts b/src/wmts/endpoint.spec.ts new file mode 100644 index 0000000..ea9e8f1 --- /dev/null +++ b/src/wmts/endpoint.spec.ts @@ -0,0 +1,442 @@ +import WmtsEndpoint from './endpoint'; +import { useCache } from '../shared/cache'; +// @ts-ignore +import ogcsample from '../../fixtures/wmts/ogcsample.xml'; +// @ts-ignore +import arcgis from '../../fixtures/wmts/arcgis.xml'; +// @ts-ignore +import ign from '../../fixtures/wmts/ign.xml'; +import { buildOpenLayersTileGrid } from './ol-tilegrid'; + +jest.mock('../shared/cache', () => ({ + useCache: jest.fn((factory) => factory()), +})); + +jest.mock('./ol-tilegrid', () => ({ + buildOpenLayersTileGrid: jest.fn(() => ({ tileGrid: true })), +})); + +const global = window as any; + +describe('WmtsEndpoint', () => { + let endpoint: WmtsEndpoint; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('OGC WMTS', () => { + beforeEach(() => { + global.fetchResponseFactory = () => ogcsample; + endpoint = new WmtsEndpoint('https://my.test.service/ogc/wmts?bb=c'); + }); + + it('makes a getcapabilities request', async () => { + await endpoint.isReady(); + expect(global.fetch).toHaveBeenCalledWith( + 'https://my.test.service/ogc/wmts?bb=c&SERVICE=WMTS&REQUEST=GetCapabilities', + { method: 'GET' } + ); + }); + + describe('caching', () => { + beforeEach(async () => { + await endpoint.isReady(); + }); + it('uses cache once', () => { + expect(useCache).toHaveBeenCalledTimes(1); + }); + it('stores the parsed capabilities in cache', async () => { + await expect( + (useCache as any).mock.results[0].value + ).resolves.toMatchObject({ + info: { + title: 'Web Map Tile Service', + }, + }); + }); + }); + + describe('#isReady', () => { + it('resolves with the endpoint object', async () => { + await expect(endpoint.isReady()).resolves.toEqual(endpoint); + }); + }); + + describe('#getLayers', () => { + it('returns a list of layers', async () => { + await endpoint.isReady(); + expect(endpoint.getLayers()).toEqual([ + expect.objectContaining({ + name: 'BlueMarbleNextGeneration', + title: 'Blue Marble Next Generation', + }), + ]); + }); + }); + + describe('#getLayerByName', () => { + it('returns a layer', async () => { + await endpoint.isReady(); + expect( + endpoint.getLayerByName('BlueMarbleNextGeneration') + ).toMatchObject({ + abstract: 'Blue Marble Next Generation NASA Product', + name: 'BlueMarbleNextGeneration', + defaultStyle: 'DarkBlue', + dimensions: [ + { + defaultValue: '20110805', + identifier: 'Time', + values: [], + }, + { + defaultValue: 'abcd', + identifier: 'OtherDimension', + values: [], + }, + ], + latLonBoundingBox: [-180, -90, 180, 90], + }); + }); + }); + + describe('#getLayerResourceUrl', () => { + it('returns a layer resource url without type hint', async () => { + await endpoint.isReady(); + expect( + endpoint.getLayerResourceUrl('BlueMarbleNextGeneration') + ).toEqual({ + encoding: 'REST', + format: 'image/png', + url: 'http://www.example.com/wmts/coastlines/{TileMatrix}/{TileRow}/{TileCol}.png', + }); + }); + it('returns a layer resource url with type hint', async () => { + await endpoint.isReady(); + expect( + endpoint.getLayerResourceUrl('BlueMarbleNextGeneration', 'image/jpeg') + ).toEqual({ + encoding: 'KVP', + format: 'image/jpeg', + url: 'http://www.maps.bob/cgi-bin/MiraMon5_0.cgi?', + }); + }); + }); + + describe('#getDefaultDimensions', () => { + it('returns a specific matrix set', async () => { + await endpoint.isReady(); + expect( + endpoint.getDefaultDimensions('BlueMarbleNextGeneration') + ).toEqual({ OtherDimension: 'abcd', Time: '20110805' }); + }); + }); + + describe('#getOpenLayersTileGrid', () => { + it('returns a tile grid using the first matrix set', async () => { + await endpoint.isReady(); + const tileGrid = await endpoint.getOpenLayersTileGrid( + 'BlueMarbleNextGeneration' + ); + expect(buildOpenLayersTileGrid).toHaveBeenCalledWith( + { + crs: 'urn:ogc:def:crs:OGC:1.3:CRS84', + identifier: 'BigWorldPixel', + tileMatrices: expect.arrayContaining([ + { + identifier: '10000m', + matrixHeight: 5, + matrixWidth: 7, + scaleDenominator: 33130800.83133142, + tileHeight: 480, + tileWidth: 640, + topLeft: [-180, 90], + }, + ]), + wellKnownScaleSet: 'urn:ogc:def:wkss:OGC:1.0:GlobalCRS84Pixel', + }, + [] + ); + expect(tileGrid).toEqual({ tileGrid: true }); + }); + it('returns a tile grid using a specific matrix set', async () => { + await endpoint.isReady(); + await endpoint.getOpenLayersTileGrid( + 'BlueMarbleNextGeneration', + 'google3857' + ); + expect(buildOpenLayersTileGrid).toHaveBeenCalledWith( + { + boundingBox: [ + 1799448.394855, 6124949.74777, 1848250.442089, 6162571.828177, + ], + crs: 'urn:ogc:def:crs:EPSG:6.18:3:3857', + identifier: 'google3857', + tileMatrices: expect.arrayContaining([ + { + identifier: '0', + matrixHeight: 1, + matrixWidth: 1, + scaleDenominator: 559082264.029, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + ]), + wellKnownScaleSet: 'urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible', + }, + [] + ); + }); + }); + + describe('#getMatrixSets', () => { + it('returns a list of matrix sets', async () => { + await endpoint.isReady(); + expect(endpoint.getMatrixSets()).toEqual([ + expect.objectContaining({ + identifier: 'google3857', + }), + expect.objectContaining({ + identifier: 'BigWorldPixel', + }), + expect.objectContaining({ + identifier: 'BigWorld', + }), + expect.objectContaining({ + identifier: 'google3857subset', + }), + ]); + }); + }); + + describe('#getMatrixSetByIdentifier', () => { + it('returns a specific matrix set', async () => { + await endpoint.isReady(); + // TODO + }); + }); + + describe('#getServiceInfo', () => { + it('returns service info', async () => { + await endpoint.isReady(); + expect(endpoint.getServiceInfo()).toEqual({ + abstract: + 'Service that contrains the map\n access interface to some TileMatrixSets\n ', + constraints: 'none', + fees: 'none', + keywords: ['tile', 'tile matrix set', 'map'], + name: 'OGC WMTS', + title: 'Web Map Tile Service', + getTileUrls: { + kvp: 'http://www.maps.bob/cgi-bin/MiraMon5_0.cgi?', + }, + }); + }); + }); + + describe('#getTileUrl', () => { + it('returns a full tile url (KVP)', async () => { + await endpoint.isReady(); + expect( + endpoint.getTileUrl( + 'BlueMarbleNextGeneration', + 'DarkBlue', + 'BigWorldPixel', + '3', + 2, + 1, + 'image/gif' + ) + ).toEqual( + 'http://www.maps.bob/cgi-bin/MiraMon5_0.cgi?layer=BlueMarbleNextGeneration&style=DarkBlue&tilematrixset=BigWorldPixel&Service=WMTS&Request=GetTile&Format=image%2Fgif&TileMatrix=3&TileCol=1&TileRow=2' + ); + }); + it('returns a full tile url (REST)', async () => { + await endpoint.isReady(); + expect( + endpoint.getTileUrl( + 'BlueMarbleNextGeneration', + 'thickAndRed', + 'google3857subset', + '18', + 2, + 3 + ) + ).toBe('http://www.example.com/wmts/coastlines/18/2/3.png'); + }); + }); + }); + + describe('ArcGIS WMTS', () => { + beforeEach(() => { + global.fetchResponseFactory = () => arcgis; + endpoint = new WmtsEndpoint('https://my.test.service/ogc/wmts?bb=c'); + }); + + describe('#getLayers', () => { + it('returns a list of layers', async () => { + await endpoint.isReady(); + expect(endpoint.getLayers()).toEqual([ + expect.objectContaining({ + name: 'Demographics_USA_Population_Density', + title: 'Demographics_USA_Population_Density', + }), + ]); + }); + }); + + describe('#getLayerByName', () => { + it('returns a layer', async () => { + await endpoint.isReady(); + expect( + endpoint.getLayerByName('Demographics_USA_Population_Density') + ).toMatchObject({ + abstract: '', + name: 'Demographics_USA_Population_Density', + defaultStyle: 'default', + dimensions: [], + latLonBoundingBox: [ + -178.2278219969978, 18.910787002877576, -66.95000499993604, + 71.38957425051252, + ], + }); + }); + }); + + describe('#getMatrixSets', () => { + it('returns a list of matrix sets', async () => { + await endpoint.isReady(); + expect(endpoint.getMatrixSets()).toEqual([ + expect.objectContaining({ + identifier: 'default028mm', + }), + expect.objectContaining({ + identifier: 'GoogleMapsCompatible', + }), + ]); + }); + }); + + describe('#getServiceInfo', () => { + it('returns service info', async () => { + await endpoint.isReady(); + expect(endpoint.getServiceInfo()).toEqual({ + abstract: '', + constraints: '', + fees: '', + getTileUrls: { + kvp: 'https://services.arcgisonline.com/arcgis/rest/services/Demographics/USA_Population_Density/MapServer/WMTS?', + rest: 'https://services.arcgisonline.com/arcgis/rest/services/Demographics/USA_Population_Density/MapServer/WMTS/tile/1.0.0/', + }, + keywords: [], + name: 'OGC WMTS', + title: 'Demographics_USA_Population_Density', + }); + }); + }); + + describe('#getTileUrl', () => { + it('returns a full tile url (REST)', async () => { + await endpoint.isReady(); + expect( + endpoint.getTileUrl( + 'Demographics_USA_Population_Density', + 'default', + 'default028mm', + '3', + 2, + 1 + ) + ).toEqual( + 'https://services.arcgisonline.com/arcgis/rest/services/Demographics/USA_Population_Density/MapServer/WMTS/tile/1.0.0/Demographics_USA_Population_Density/default/default028mm/3/2/1.png' + ); + }); + it('returns a full tile url with type hint (REST)', async () => { + await endpoint.isReady(); + expect( + endpoint.getTileUrl( + 'Demographics_USA_Population_Density', + 'default', + 'default028mm', + '3', + 2, + 1, + 'image/jpeg' + ) + ).toBe( + 'https://services.arcgisonline.com/arcgis/rest/services/Demographics/USA_Population_Density/MapServer/WMTS/tile/1.0.0/Demographics_USA_Population_Density/default/default028mm/3/2/1.jpeg' + ); + }); + }); + }); + + describe('IGN WMTS', () => { + beforeEach(() => { + global.fetchResponseFactory = () => ign; + endpoint = new WmtsEndpoint('https://my.test.service/ogc/wmts?bb=c'); + }); + + describe('#getOpenLayersTileGrid', () => { + it('returns a tile grid including tile limits', async () => { + await endpoint.isReady(); + await endpoint.getOpenLayersTileGrid('ORTHOIMAGERY.ORTHOPHOTOS'); + expect(buildOpenLayersTileGrid).toHaveBeenCalledWith( + { + crs: 'EPSG:3857', + identifier: 'PM', + tileMatrices: expect.arrayContaining([ + { + identifier: '0', + matrixHeight: 1, + matrixWidth: 1, + scaleDenominator: 559082264.0287179, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508, 20037508], + }, + ]), + }, + expect.arrayContaining([ + { + maxTileCol: 1, + maxTileRow: 1, + minTileCol: 0, + minTileRow: 0, + tileMatrix: '0', + }, + { + maxTileCol: 2, + maxTileRow: 2, + minTileCol: 0, + minTileRow: 0, + tileMatrix: '1', + }, + { + maxTileCol: 1024, + maxTileRow: 1024, + minTileCol: 0, + minTileRow: 31, + tileMatrix: '10', + }, + { + maxTileCol: 2048, + maxTileRow: 2048, + minTileCol: 0, + minTileRow: 62, + tileMatrix: '11', + }, + { + maxTileCol: 4096, + maxTileRow: 4096, + minTileCol: 0, + minTileRow: 125, + tileMatrix: '12', + }, + ]) + ); + }); + }); + }); +}); diff --git a/src/wmts/endpoint.ts b/src/wmts/endpoint.ts new file mode 100644 index 0000000..bf7ca3e --- /dev/null +++ b/src/wmts/endpoint.ts @@ -0,0 +1,188 @@ +import { MimeType } from '../shared/models'; +import { setQueryParams } from '../shared/http-utils'; +import { useCache } from '../shared/cache'; +import { parseWmtsCapabilities } from '../worker'; +import { + LayerDimensionValue, + LayerResourceUrl, + WmtsEndpointInfo, + WmtsLayer, + WmtsMatrixSet, +} from './model'; +import { generateGetTileUrl } from './url'; +import type WMTSTileGrid from 'ol/tilegrid/WMTS'; + +/** + * Represents a WMTS endpoint advertising several layers. + */ +export default class WmtsEndpoint { + private _capabilitiesPromise: Promise; + private _info: WmtsEndpointInfo = null; + private _layers: WmtsLayer[] = null; + private _matrixSets: WmtsMatrixSet[] = null; + + /** + * @param url WMTS endpoint url; can contain any query parameters, these will be used to + * initialize the endpoint + */ + constructor(url: string) { + const capabilitiesUrl = setQueryParams(url, { + SERVICE: 'WMTS', + REQUEST: 'GetCapabilities', + }); + + /** + * This fetches the capabilities doc and parses its contents + */ + this._capabilitiesPromise = useCache( + () => parseWmtsCapabilities(capabilitiesUrl), + 'WMTS', + 'CAPABILITIES', + capabilitiesUrl + ).then(({ info, layers, matrixSets }) => { + this._info = info; + this._layers = layers; + this._matrixSets = matrixSets; + }); + } + + /** + * @throws {EndpointError} + */ + isReady() { + return this._capabilitiesPromise.then(() => this); + } + + getServiceInfo() { + return this._info; + } + + getLayers() { + return this._layers; + } + + getMatrixSets() { + return this._matrixSets; + } + + /** + * Returns a matrix set by identifier + * @param identifier Matrix set identifier + * @return return null if matrix set was not found + */ + getMatrixSetByIdentifier(identifier: string): WmtsMatrixSet { + if (!this._matrixSets) return null; + return ( + this._matrixSets.find( + (matrixSet) => matrixSet.identifier === identifier + ) ?? null + ); + } + + /** + * Returns a complete layer based on its name + * Note: the first matching layer will be returned + * @param name Layer name property + * @return return null if layer was not found + */ + getLayerByName(name: string): WmtsLayer { + if (!this._layers) return null; + return this._layers.find((layer) => layer.name === name) ?? null; + } + + getLayerResourceUrl( + layerName: string, + formatHint?: MimeType + ): LayerResourceUrl { + if (!this._layers) return null; + const layer = this.getLayerByName(layerName); + let resourceUrlIndex = 0; + if (formatHint) { + resourceUrlIndex = + layer.resourceUrls.findIndex( + (resourceUrl) => resourceUrl.format === formatHint + ) || 0; + } + const resourceUrl = layer.resourceUrls[resourceUrlIndex]; + if (formatHint && resourceUrl.format !== formatHint) { + console.warn( + `[ogc-client] Requested '${formatHint}' format for the WMTS layer but it is not available in REST encoding, falling back to '${resourceUrl.format}'` + ); + } + return resourceUrl; + } + + /** + * Generates a URL for a specific tile of a specific layer + */ + getTileUrl( + layerName: string, + styleName: string, + matrixSetName: string, + tileMatrix: string, + tileRow: number, + tileCol: number, + outputFormat?: MimeType + ): string { + if (!this._layers) return null; + const resourceUrl = this.getLayerResourceUrl(layerName, outputFormat); + return generateGetTileUrl( + resourceUrl.url, + resourceUrl.encoding, + layerName, + styleName, + matrixSetName, + tileMatrix, + tileRow, + tileCol, + resourceUrl.format + ); + } + + /** + * Return an object with all defined dimensions for the layer, as well as their default values. + * @param layerName + */ + getDefaultDimensions(layerName: string): Record { + if (!this._layers) return null; + const layer = this.getLayerByName(layerName); + if (!layer.dimensions) return {}; + return layer.dimensions.reduce( + (prev, curr) => ({ ...prev, [curr.identifier]: curr.defaultValue }), + {} + ); + } + + tileGridModule: Promise; + + /** + * Creates a WMTSTileGrid instance from the 'ol' package, for a given layer. Optionally, a matrix set + * can be provided; + * @param layerName + * @param matrixSetIdentifier + */ + getOpenLayersTileGrid( + layerName: string, + matrixSetIdentifier?: string + ): Promise { + if (!this._layers) return null; + if (!this.tileGridModule) { + this.tileGridModule = import('./ol-tilegrid').catch((e) => { + console.warn( + `[ogc-client] Cannot use getOpenLayersTileGrid, the 'ol' package is probably not available.\n`, + e + ); + return null; + }); + } + const layer = this.getLayerByName(layerName); + const matrixSetLink = + layer.matrixSets.find( + (matrixSet) => matrixSet.identifier === matrixSetIdentifier + ) ?? layer.matrixSets[0]; + const matrixSet = this.getMatrixSetByIdentifier(matrixSetLink.identifier); + return this.tileGridModule.then(({ buildOpenLayersTileGrid }) => + buildOpenLayersTileGrid(matrixSet, matrixSetLink.limits) + ); + } +} diff --git a/src/wmts/model.ts b/src/wmts/model.ts new file mode 100644 index 0000000..443f5cc --- /dev/null +++ b/src/wmts/model.ts @@ -0,0 +1,103 @@ +import { + BoundingBox, + CrsCode, + GenericEndpointInfo, + MimeType, +} from '../shared/models'; + +export interface WmtsEndpointInfo extends GenericEndpointInfo { + getTileUrls: { + kvp?: string; + rest?: string; + }; +} + +export interface TileMatrix { + identifier: string; + scaleDenominator: number; + resolution?: number; // FOR OL??? or computeResolution? + /** + * coordinates of the top left origin of the tile matrix + */ + topLeft: [number, number]; + /** + * width in pixels + */ + tileWidth: number; + /** + * height in pixels + */ + tileHeight: number; + /** + * horizontal tile count + */ + matrixWidth: number; + /** + * vertical tile count + */ + matrixHeight: number; +} + +export interface WmtsMatrixSet { + identifier: string; + wellKnownScaleSet?: string; // from fixed list? + crs: CrsCode; + boundingBox?: BoundingBox; + tileMatrices: TileMatrix[]; +} + +export interface LayerStyle { + name: string; + title: string; + legendUrl?: string; +} + +export interface MatrixSetLink { + identifier: string; + crs: string; + limits: MatrixSetLimit[]; +} + +export interface MatrixSetLimit { + tileMatrix: string; + minTileRow: number; + maxTileRow: number; + minTileCol: number; + maxTileCol: number; +} + +export interface LayerResourceUrl { + url: string; + encoding: WmtsRequestEncoding; + format: MimeType; +} + +export type LayerDimensionValue = string; + +export interface LayerDimension { + identifier: string; + defaultValue: LayerDimensionValue; + values: LayerDimensionValue[]; +} + +export interface WmtsLayer { + name: string; + resourceUrls: LayerResourceUrl[]; + styles: LayerStyle[]; + defaultStyle: string; + matrixSets: MatrixSetLink[]; + latLonBoundingBox?: BoundingBox; + dimensions?: LayerDimension[]; +} + +export interface WmtsTileGrid { + minZoom: number; + /** + * for these arrays the index is the zoom value, so items with an index lower than minZoom are undefined + */ + origins: [number, number][]; + sizes: [number, number][]; + tileSizes: [number, number][]; +} + +export type WmtsRequestEncoding = 'KVP' | 'REST'; diff --git a/src/wmts/ol-tilegrid.spec.ts b/src/wmts/ol-tilegrid.spec.ts new file mode 100644 index 0000000..d0335c2 --- /dev/null +++ b/src/wmts/ol-tilegrid.spec.ts @@ -0,0 +1,205 @@ +import { createFromCapabilitiesMatrixSet } from 'ol/tilegrid/WMTS'; +import { buildOpenLayersTileGrid } from './ol-tilegrid'; + +describe('buildOpenLayersTileGrid', () => { + it('calls the OL function accordingly', () => { + buildOpenLayersTileGrid( + { + crs: 'EPSG:3857', + identifier: 'PM', + tileMatrices: [ + { + identifier: '0', + matrixHeight: 1, + matrixWidth: 1, + scaleDenominator: 559082264.029, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '1', + matrixHeight: 2, + matrixWidth: 2, + scaleDenominator: 279541132.015, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '2', + matrixHeight: 4, + matrixWidth: 4, + scaleDenominator: 139770566.007, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '3', + matrixHeight: 8, + matrixWidth: 8, + scaleDenominator: 69885283.0036, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '4', + matrixHeight: 16, + matrixWidth: 16, + scaleDenominator: 34942641.5018, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '5', + matrixHeight: 32, + matrixWidth: 32, + scaleDenominator: 17471320.7509, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + { + identifier: '6', + matrixHeight: 64, + matrixWidth: 64, + scaleDenominator: 8735660.37545, + tileHeight: 256, + tileWidth: 256, + topLeft: [-20037508.3428, 20037508.3428], + }, + ], + }, + [ + { + maxTileCol: 1, + maxTileRow: 1, + minTileCol: 0, + minTileRow: 0, + tileMatrix: '0', + }, + { + maxTileCol: 2, + maxTileRow: 2, + minTileCol: 0, + minTileRow: 0, + tileMatrix: '1', + }, + { + maxTileCol: 1024, + maxTileRow: 1024, + minTileCol: 0, + minTileRow: 31, + tileMatrix: '10', + }, + { + maxTileCol: 2048, + maxTileRow: 2048, + minTileCol: 0, + minTileRow: 62, + tileMatrix: '11', + }, + { + maxTileCol: 4096, + maxTileRow: 4096, + minTileCol: 0, + minTileRow: 125, + tileMatrix: '12', + }, + ] + ); + expect(createFromCapabilitiesMatrixSet).toHaveBeenCalledWith( + { + SupportedCRS: { + code: '3857', + }, + TileMatrix: [ + { + Identifier: '0', + MatrixHeight: 1, + MatrixWidth: 1, + ScaleDenominator: 559082264.029, + TileHeight: 256, + TileWidth: 256, + TopLeftCorner: [-20037508.3428, 20037508.3428], + }, + { + Identifier: '1', + MatrixHeight: 2, + MatrixWidth: 2, + ScaleDenominator: 279541132.015, + TileHeight: 256, + TileWidth: 256, + TopLeftCorner: [-20037508.3428, 20037508.3428], + }, + { + Identifier: '2', + MatrixHeight: 4, + MatrixWidth: 4, + ScaleDenominator: 139770566.007, + TileHeight: 256, + TileWidth: 256, + TopLeftCorner: [-20037508.3428, 20037508.3428], + }, + { + Identifier: '3', + MatrixHeight: 8, + MatrixWidth: 8, + ScaleDenominator: 69885283.0036, + TileHeight: 256, + TileWidth: 256, + TopLeftCorner: [-20037508.3428, 20037508.3428], + }, + { + Identifier: '4', + MatrixHeight: 16, + MatrixWidth: 16, + ScaleDenominator: 34942641.5018, + TileHeight: 256, + TileWidth: 256, + TopLeftCorner: [-20037508.3428, 20037508.3428], + }, + { + Identifier: '5', + MatrixHeight: 32, + MatrixWidth: 32, + ScaleDenominator: 17471320.7509, + TileHeight: 256, + TileWidth: 256, + TopLeftCorner: [-20037508.3428, 20037508.3428], + }, + { + Identifier: '6', + MatrixHeight: 64, + MatrixWidth: 64, + ScaleDenominator: 8735660.37545, + TileHeight: 256, + TileWidth: 256, + TopLeftCorner: [-20037508.3428, 20037508.3428], + }, + ], + }, + null, + [ + { + TileMatrix: '0', + }, + { + TileMatrix: '1', + }, + { + TileMatrix: '10', + }, + { + TileMatrix: '11', + }, + { + TileMatrix: '12', + }, + ] + ); + }); +}); diff --git a/src/wmts/ol-tilegrid.ts b/src/wmts/ol-tilegrid.ts new file mode 100644 index 0000000..0a36463 --- /dev/null +++ b/src/wmts/ol-tilegrid.ts @@ -0,0 +1,41 @@ +import { MatrixSetLimit, WmtsMatrixSet } from './model'; +import WMTSTileGrid, { + createFromCapabilitiesMatrixSet, +} from 'ol/tilegrid/WMTS'; +import { get as getProjection } from 'ol/proj'; +import { fromEPSGCode, register } from 'ol/proj/proj4'; +import proj4 from 'proj4'; + +register(proj4); + +export async function buildOpenLayersTileGrid( + matrixSet: WmtsMatrixSet, + limits: MatrixSetLimit[] +): Promise { + // if the matrix set crs is not known, load it + let projection = getProjection(matrixSet.crs); + if (!projection) { + projection = await fromEPSGCode(matrixSet.crs); + } + if (!projection) { + throw new Error( + `[ogc-client] could not create OpenLayers tile grid, the following projection is unknown: ${matrixSet.crs}` + ); + } + const matrixSetInfo = { + SupportedCRS: projection, + TileMatrix: matrixSet.tileMatrices.map((tileMatrix) => ({ + Identifier: tileMatrix.identifier, + ScaleDenominator: tileMatrix.scaleDenominator, + TopLeftCorner: tileMatrix.topLeft, + TileWidth: tileMatrix.tileWidth, + TileHeight: tileMatrix.tileHeight, + MatrixWidth: tileMatrix.matrixWidth, + MatrixHeight: tileMatrix.matrixHeight, + })), + }; + const matrixSetLimits = limits.map((limit) => ({ + TileMatrix: limit.tileMatrix, + })); + return createFromCapabilitiesMatrixSet(matrixSetInfo, null, matrixSetLimits); +} diff --git a/src/wmts/url.spec.ts b/src/wmts/url.spec.ts new file mode 100644 index 0000000..7238a74 --- /dev/null +++ b/src/wmts/url.spec.ts @@ -0,0 +1,40 @@ +import { generateGetTileUrl } from './url'; + +describe('URL utils', () => { + describe('generateGetTileUrl', () => { + it('generates URL with KVP encoding', () => { + const url = generateGetTileUrl( + 'http://my.service.org/wmts', + 'KVP', + 'myLayer', + 'myStyle', + 'webMercator', + 'zoom:3', + 4, + 5, + 'image/png' + ); + expect(url).toBe( + 'http://my.service.org/wmts?layer=myLayer&style=myStyle&tilematrixset=webMercator&Service=WMTS&Request=GetTile&Format=image%2Fpng&TileMatrix=zoom%3A3&TileCol=5&TileRow=4' + ); + }); + }); + describe('generateGetTileUrl', () => { + it('generates URL with KVP encoding', () => { + const url = generateGetTileUrl( + 'http://my.service.org/wmts/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png', + 'REST', + 'myLayer', + 'myStyle', + 'webMercator', + 'zoom:3', + 4, + 5, + 'image/png' + ); + expect(url).toBe( + 'http://my.service.org/wmts/myStyle/webMercator/zoom:3/4/5.png' + ); + }); + }); +}); diff --git a/src/wmts/url.ts b/src/wmts/url.ts new file mode 100644 index 0000000..e93d3b4 --- /dev/null +++ b/src/wmts/url.ts @@ -0,0 +1,36 @@ +import { WmtsRequestEncoding } from './model'; +import { MimeType } from '../shared/models'; +import { setQueryParams } from '../shared/http-utils'; + +export function generateGetTileUrl( + baseUrl: string, + requestEncoding: WmtsRequestEncoding, + layerName: string, + styleName: string, + matrixSetName: string, + tileMatrix: string, + tileRow: number, + tileCol: number, + outputFormat: MimeType +): string { + const context = { + layer: layerName, + style: styleName, + tilematrixset: matrixSetName, + Service: 'WMTS', + Request: 'GetTile', + Format: outputFormat, + TileMatrix: tileMatrix, + TileCol: tileCol.toString(), + TileRow: tileRow.toString(), + }; + if (requestEncoding === 'REST') { + let url = baseUrl; + for (const key in context) { + url = url.replace(new RegExp(`{${key}}`, 'ig'), context[key]); + } + return url; + } else { + return setQueryParams(baseUrl, context); + } +} diff --git a/src/worker/index.ts b/src/worker/index.ts index 1baa444..0bbe618 100644 --- a/src/worker/index.ts +++ b/src/worker/index.ts @@ -8,6 +8,7 @@ import { import { GenericEndpointInfo } from '../shared/models'; import { WmsLayerFull, WmsVersion } from '../wms/endpoint'; import { setFetchOptionsUpdateCallback } from '../shared/http-utils'; +import { WmtsEndpointInfo, WmtsLayer, WmtsMatrixSet } from '../wmts/model'; let fallbackWithoutWorker = false; @@ -81,6 +82,20 @@ export function queryWfsFeatureTypeDetails( }); } +/** + * Parses the capabilities document and return all relevant information + * @param capabilitiesUrl This url should point to the capabilities document + */ +export function parseWmtsCapabilities(capabilitiesUrl: string): Promise<{ + info: WmtsEndpointInfo; + layers: WmtsLayer[]; + matrixSets: WmtsMatrixSet[]; +}> { + return sendTaskRequest('parseWmtsCapabilities', getWorkerInstance(), { + url: capabilitiesUrl, + }); +} + setFetchOptionsUpdateCallback((options) => { const worker = getWorkerInstance(); if (!worker) return; diff --git a/src/worker/worker.ts b/src/worker/worker.ts index 628f890..e0e24d1 100644 --- a/src/worker/worker.ts +++ b/src/worker/worker.ts @@ -2,6 +2,7 @@ import { addTaskHandler } from './utils'; import { queryXmlDocument, setFetchOptions } from '../shared/http-utils'; import * as wmsCapabilities from '../wms/capabilities'; import * as wfsCapabilities from '../wfs/capabilities'; +import * as wmtsCapabilities from '../wmts/capabilities'; import { computeFeaturePropsDetails, parseFeatureProps, @@ -62,3 +63,14 @@ addTaskHandler( return Promise.resolve({}); } ); + +addTaskHandler( + 'parseWmtsCapabilities', + globalThis, + ({ url }: { url: string }) => + queryXmlDocument(url).then((xmlDoc) => ({ + info: wmtsCapabilities.readInfoFromCapabilities(xmlDoc), + layers: wmtsCapabilities.readLayersFromCapabilities(xmlDoc), + matrixSets: wmtsCapabilities.readMatrixSetsFromCapabilities(xmlDoc), + })) +);