From c8067bebbb8b283e737188767b42ab8aba4814ab Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Sat, 4 Sep 2021 11:06:52 -0600 Subject: [PATCH] Add supported media type list to feature formats --- examples/ogc-vector-tiles.js | 1 - src/ol/format/Feature.js | 6 +++ src/ol/format/GMLBase.js | 2 + src/ol/format/GeoJSON.js | 5 +++ src/ol/format/KML.js | 2 + src/ol/format/MVT.js | 5 +++ src/ol/source/OGCVectorTile.js | 8 +++- src/ol/source/ogcTileUtil.js | 29 +++++++++++++- test/node/ol/source/ogcTileUtil.test.js | 51 +++++++++++++++++++++++++ 9 files changed, 104 insertions(+), 5 deletions(-) diff --git a/examples/ogc-vector-tiles.js b/examples/ogc-vector-tiles.js index 408af880f3..a52d31a39a 100644 --- a/examples/ogc-vector-tiles.js +++ b/examples/ogc-vector-tiles.js @@ -10,7 +10,6 @@ const map = new Map({ new VectorTileLayer({ source: new OGCVectorTile({ url: 'https://maps.ecere.com/ogcapi/collections/NaturalEarth:cultural:ne_10m_admin_0_countries/tiles/WebMercatorQuad', - mediaType: 'application/vnd.mapbox-vector-tile', format: new MVT(), }), }), diff --git a/src/ol/format/Feature.js b/src/ol/format/Feature.js index 9f629c776a..a53bce0b04 100644 --- a/src/ol/format/Feature.js +++ b/src/ol/format/Feature.js @@ -76,6 +76,12 @@ class FeatureFormat { * @type {import("../proj/Projection.js").default|undefined} */ this.defaultFeatureProjection = undefined; + + /** + * A list media types supported by the format in descending order of preference. + * @type {Array} + */ + this.supportedMediaTypes = null; } /** diff --git a/src/ol/format/GMLBase.js b/src/ol/format/GMLBase.js index d67249db04..859f642338 100644 --- a/src/ol/format/GMLBase.js +++ b/src/ol/format/GMLBase.js @@ -132,6 +132,8 @@ class GMLBase extends XMLFeature { 'featureMember': makeArrayPusher(this.readFeaturesInternal), 'featureMembers': makeReplacer(this.readFeaturesInternal), }; + + this.supportedMediaTypes = ['application/gml+xml']; } /** diff --git a/src/ol/format/GeoJSON.js b/src/ol/format/GeoJSON.js index 4de6ee2885..a4cb0c7bb3 100644 --- a/src/ol/format/GeoJSON.js +++ b/src/ol/format/GeoJSON.js @@ -82,6 +82,11 @@ class GeoJSON extends JSONFeature { * @private */ this.extractGeometryName_ = options.extractGeometryName; + + this.supportedMediaTypes = [ + 'application/geo+json', + 'application/vnd.geo+json', + ]; } /** diff --git a/src/ol/format/KML.js b/src/ol/format/KML.js index 8e86545c71..27c00fd9ee 100644 --- a/src/ol/format/KML.js +++ b/src/ol/format/KML.js @@ -489,6 +489,8 @@ class KML extends XMLFeature { this.iconUrlFunction_ = options.iconUrlFunction ? options.iconUrlFunction : defaultIconUrlFunction; + + this.supportedMediaTypes = ['application/vnd.google-earth.kml+xml']; } /** diff --git a/src/ol/format/MVT.js b/src/ol/format/MVT.js index 5fa9305c36..1298b47f7a 100644 --- a/src/ol/format/MVT.js +++ b/src/ol/format/MVT.js @@ -89,6 +89,11 @@ class MVT extends FeatureFormat { * @type {string} */ this.idProperty_ = options.idProperty; + + this.supportedMediaTypes = [ + 'application/vnd.mapbox-vector-tile', + 'application/x-protobuf', + ]; } /** diff --git a/src/ol/source/OGCVectorTile.js b/src/ol/source/OGCVectorTile.js index b6e75cb8d5..da5d5c451f 100644 --- a/src/ol/source/OGCVectorTile.js +++ b/src/ol/source/OGCVectorTile.js @@ -13,8 +13,7 @@ import {getTileSetInfo} from './ogcTileUtil.js'; * (zoom level), `{tileRow}`, and `{tileCol}` variables in the URL will always be provided by the source. * @property {import("../format/Feature.js").default} format Feature parser for tiles. * @property {string} [mediaType] The content type for the tiles (e.g. "application/vnd.mapbox-vector-tile"). If not provided, - * the source will try to find a link with rel="item" that uses a supported vector type. The chosen media type - * must be parseable by the configured format. + * the source will try to find a link with rel="item" that uses a vector type supported by the configured format. * @property {import("./Source.js").AttributionLike} [attributions] Attributions. * @property {boolean} [attributionsCollapsible=true] Attributions are collapsible. * @property {number} [cacheSize] Initial tile cache size. Will auto-grow to hold at least twice the number of tiles in the viewport. @@ -41,6 +40,10 @@ import {getTileSetInfo} from './ogcTileUtil.js'; * Layer source for map tiles from an [OGC API - Tiles](https://ogcapi.ogc.org/tiles/) service that provides "vector" type tiles. * The service must conform to at least the core (http://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/core) * and tileset (http://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/tileset) conformance classes. + * + * Vector tile sets may come in a variety of formats (e.g. GeoJSON, MVT). The `format` option is used to determine + * which of the advertised media types is used. If you need to force the use of a particular media type, you can + * provide the `mediaType` option. */ class OGCVectorTile extends VectorTile { /** @@ -65,6 +68,7 @@ class OGCVectorTile extends VectorTile { url: options.url, projection: this.getProjection(), mediaType: options.mediaType, + supportedMediaTypes: options.format.supportedMediaTypes, context: options.context || null, }; diff --git a/src/ol/source/ogcTileUtil.js b/src/ol/source/ogcTileUtil.js index 6ad4ace43a..7fe318489a 100644 --- a/src/ol/source/ogcTileUtil.js +++ b/src/ol/source/ogcTileUtil.js @@ -93,6 +93,7 @@ const knownVectorMediaTypes = { * @typedef {Object} SourceInfo * @property {string} url The tile set URL. * @property {string} mediaType The preferred tile media type. + * @property {Array} [supportedMediaTypes] The supported media types. * @property {import("../proj/Projection.js").default} projection The source projection. * @property {Object} [context] Optional context for constructing the URL. */ @@ -134,13 +135,26 @@ export function getMapTileUrlTemplate(links, mediaType) { /** * @param {Array} links Tileset links. * @param {string} [mediaType] The preferred media type. + * @param {Array} [supportedMediaTypes] The media types supported by the parser. * @return {string} The tile URL template. */ -export function getVectorTileUrlTemplate(links, mediaType) { +export function getVectorTileUrlTemplate( + links, + mediaType, + supportedMediaTypes +) { let tileUrlTemplate; let fallbackUrlTemplate; + + /** + * Lookup of URL by media type. + * @type {Object} + */ + const hrefLookup = {}; + for (let i = 0; i < links.length; ++i) { const link = links[i]; + hrefLookup[link.type] = link.href; if (link.rel === 'item') { if (link.type === mediaType) { tileUrlTemplate = link.href; @@ -152,6 +166,16 @@ export function getVectorTileUrlTemplate(links, mediaType) { } } + if (!tileUrlTemplate && supportedMediaTypes) { + for (let i = 0; i < supportedMediaTypes.length; ++i) { + const supportedMediaType = supportedMediaTypes[i]; + if (hrefLookup[supportedMediaType]) { + tileUrlTemplate = hrefLookup[supportedMediaType]; + break; + } + } + } + if (!tileUrlTemplate) { if (fallbackUrlTemplate) { tileUrlTemplate = fallbackUrlTemplate; @@ -333,7 +357,8 @@ function parseTileSetMetadata(sourceInfo, tileSet) { } else if (tileSet.dataType === 'vector') { tileUrlTemplate = getVectorTileUrlTemplate( tileSet.links, - sourceInfo.mediaType + sourceInfo.mediaType, + sourceInfo.supportedMediaTypes ); } else { throw new Error('Expected tileset data type to be "map" or "vector"'); diff --git a/test/node/ol/source/ogcTileUtil.test.js b/test/node/ol/source/ogcTileUtil.test.js index ed4cee5eaa..b0219fb3a7 100644 --- a/test/node/ol/source/ogcTileUtil.test.js +++ b/test/node/ol/source/ogcTileUtil.test.js @@ -143,6 +143,48 @@ describe('ol/source/ogcTileUtil.js', () => { 'https://maps.ecere.com/ogcapi/collections/NaturalEarth:cultural:ne_10m_admin_0_countries/tiles/WebMercatorQuad/3/1/2.mvt' ); }); + + it('uses supported media types if available', async () => { + baseUrl = 'https://maps.ecere.com/'; + const sourceInfo = { + url: 'https://maps.ecere.com/ogcapi/collections/ne_10m_admin_0_countries/tiles/WebMercatorQuad', + supportedMediaTypes: [ + 'bogus-media-type', + 'application/vnd.mapbox-vector-tile', + 'application/geo+json', // should not be used + ], + }; + const tileInfo = await getTileSetInfo(sourceInfo); + expect(tileInfo).to.be.an(Object); + expect(tileInfo.urlTemplate).to.be( + '/ogcapi/collections/NaturalEarth:cultural:ne_10m_admin_0_countries/tiles/WebMercatorQuad/{tileMatrix}/{tileRow}/{tileCol}.mvt' + ); + expect(tileInfo.urlFunction).to.be.a(Function); + expect(tileInfo.urlFunction([3, 2, 1])).to.be( + 'https://maps.ecere.com/ogcapi/collections/NaturalEarth:cultural:ne_10m_admin_0_countries/tiles/WebMercatorQuad/3/1/2.mvt' + ); + }); + + it('treats supported media types in descending order of priority', async () => { + baseUrl = 'https://maps.ecere.com/'; + const sourceInfo = { + url: 'https://maps.ecere.com/ogcapi/collections/ne_10m_admin_0_countries/tiles/WebMercatorQuad', + supportedMediaTypes: [ + 'bogus-media-type', + 'application/geo+json', // should be preferred + 'application/vnd.mapbox-vector-tile', + ], + }; + const tileInfo = await getTileSetInfo(sourceInfo); + expect(tileInfo).to.be.an(Object); + expect(tileInfo.urlTemplate).to.be( + '/ogcapi/collections/NaturalEarth:cultural:ne_10m_admin_0_countries/tiles/WebMercatorQuad/{tileMatrix}/{tileRow}/{tileCol}.json' + ); + expect(tileInfo.urlFunction).to.be.a(Function); + expect(tileInfo.urlFunction([3, 2, 1])).to.be( + 'https://maps.ecere.com/ogcapi/collections/NaturalEarth:cultural:ne_10m_admin_0_countries/tiles/WebMercatorQuad/3/1/2.json' + ); + }); }); describe('getVectorTileUrlTemplate()', () => { @@ -173,6 +215,15 @@ describe('ol/source/ogcTileUtil.js', () => { ); }); + it('uses supported media types is preferred media type is not given', () => { + const urlTemplate = getVectorTileUrlTemplate(links, undefined, [ + 'application/vnd.mapbox-vector-tile', + ]); + expect(urlTemplate).to.be( + '/ogcapi/collections/NaturalEarth:cultural:ne_10m_admin_0_countries/tiles/WebMercatorQuad/{tileMatrix}/{tileRow}/{tileCol}.mvt' + ); + }); + it('throws if it cannot find preferred media type or a known fallback', () => { function call() { getVectorTileUrlTemplate([], 'application/vnd.mapbox-vector-tile');