Add supported media type list to feature formats

This commit is contained in:
Tim Schaub
2021-09-04 11:06:52 -06:00
parent ac4e472353
commit c8067bebbb
9 changed files with 104 additions and 5 deletions
-1
View File
@@ -10,7 +10,6 @@ const map = new Map({
new VectorTileLayer({ new VectorTileLayer({
source: new OGCVectorTile({ source: new OGCVectorTile({
url: 'https://maps.ecere.com/ogcapi/collections/NaturalEarth:cultural:ne_10m_admin_0_countries/tiles/WebMercatorQuad', 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(), format: new MVT(),
}), }),
}), }),
+6
View File
@@ -76,6 +76,12 @@ class FeatureFormat {
* @type {import("../proj/Projection.js").default|undefined} * @type {import("../proj/Projection.js").default|undefined}
*/ */
this.defaultFeatureProjection = undefined; this.defaultFeatureProjection = undefined;
/**
* A list media types supported by the format in descending order of preference.
* @type {Array<string>}
*/
this.supportedMediaTypes = null;
} }
/** /**
+2
View File
@@ -132,6 +132,8 @@ class GMLBase extends XMLFeature {
'featureMember': makeArrayPusher(this.readFeaturesInternal), 'featureMember': makeArrayPusher(this.readFeaturesInternal),
'featureMembers': makeReplacer(this.readFeaturesInternal), 'featureMembers': makeReplacer(this.readFeaturesInternal),
}; };
this.supportedMediaTypes = ['application/gml+xml'];
} }
/** /**
+5
View File
@@ -82,6 +82,11 @@ class GeoJSON extends JSONFeature {
* @private * @private
*/ */
this.extractGeometryName_ = options.extractGeometryName; this.extractGeometryName_ = options.extractGeometryName;
this.supportedMediaTypes = [
'application/geo+json',
'application/vnd.geo+json',
];
} }
/** /**
+2
View File
@@ -489,6 +489,8 @@ class KML extends XMLFeature {
this.iconUrlFunction_ = options.iconUrlFunction this.iconUrlFunction_ = options.iconUrlFunction
? options.iconUrlFunction ? options.iconUrlFunction
: defaultIconUrlFunction; : defaultIconUrlFunction;
this.supportedMediaTypes = ['application/vnd.google-earth.kml+xml'];
} }
/** /**
+5
View File
@@ -89,6 +89,11 @@ class MVT extends FeatureFormat {
* @type {string} * @type {string}
*/ */
this.idProperty_ = options.idProperty; this.idProperty_ = options.idProperty;
this.supportedMediaTypes = [
'application/vnd.mapbox-vector-tile',
'application/x-protobuf',
];
} }
/** /**
+6 -2
View File
@@ -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. * (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 {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, * @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 * the source will try to find a link with rel="item" that uses a vector type supported by the configured format.
* must be parseable by the configured format.
* @property {import("./Source.js").AttributionLike} [attributions] Attributions. * @property {import("./Source.js").AttributionLike} [attributions] Attributions.
* @property {boolean} [attributionsCollapsible=true] Attributions are collapsible. * @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. * @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. * 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) * 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. * 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 { class OGCVectorTile extends VectorTile {
/** /**
@@ -65,6 +68,7 @@ class OGCVectorTile extends VectorTile {
url: options.url, url: options.url,
projection: this.getProjection(), projection: this.getProjection(),
mediaType: options.mediaType, mediaType: options.mediaType,
supportedMediaTypes: options.format.supportedMediaTypes,
context: options.context || null, context: options.context || null,
}; };
+27 -2
View File
@@ -93,6 +93,7 @@ const knownVectorMediaTypes = {
* @typedef {Object} SourceInfo * @typedef {Object} SourceInfo
* @property {string} url The tile set URL. * @property {string} url The tile set URL.
* @property {string} mediaType The preferred tile media type. * @property {string} mediaType The preferred tile media type.
* @property {Array<string>} [supportedMediaTypes] The supported media types.
* @property {import("../proj/Projection.js").default} projection The source projection. * @property {import("../proj/Projection.js").default} projection The source projection.
* @property {Object} [context] Optional context for constructing the URL. * @property {Object} [context] Optional context for constructing the URL.
*/ */
@@ -134,13 +135,26 @@ export function getMapTileUrlTemplate(links, mediaType) {
/** /**
* @param {Array<Link>} links Tileset links. * @param {Array<Link>} links Tileset links.
* @param {string} [mediaType] The preferred media type. * @param {string} [mediaType] The preferred media type.
* @param {Array<string>} [supportedMediaTypes] The media types supported by the parser.
* @return {string} The tile URL template. * @return {string} The tile URL template.
*/ */
export function getVectorTileUrlTemplate(links, mediaType) { export function getVectorTileUrlTemplate(
links,
mediaType,
supportedMediaTypes
) {
let tileUrlTemplate; let tileUrlTemplate;
let fallbackUrlTemplate; let fallbackUrlTemplate;
/**
* Lookup of URL by media type.
* @type {Object<string, string>}
*/
const hrefLookup = {};
for (let i = 0; i < links.length; ++i) { for (let i = 0; i < links.length; ++i) {
const link = links[i]; const link = links[i];
hrefLookup[link.type] = link.href;
if (link.rel === 'item') { if (link.rel === 'item') {
if (link.type === mediaType) { if (link.type === mediaType) {
tileUrlTemplate = link.href; 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 (!tileUrlTemplate) {
if (fallbackUrlTemplate) { if (fallbackUrlTemplate) {
tileUrlTemplate = fallbackUrlTemplate; tileUrlTemplate = fallbackUrlTemplate;
@@ -333,7 +357,8 @@ function parseTileSetMetadata(sourceInfo, tileSet) {
} else if (tileSet.dataType === 'vector') { } else if (tileSet.dataType === 'vector') {
tileUrlTemplate = getVectorTileUrlTemplate( tileUrlTemplate = getVectorTileUrlTemplate(
tileSet.links, tileSet.links,
sourceInfo.mediaType sourceInfo.mediaType,
sourceInfo.supportedMediaTypes
); );
} else { } else {
throw new Error('Expected tileset data type to be "map" or "vector"'); throw new Error('Expected tileset data type to be "map" or "vector"');
+51
View File
@@ -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' '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()', () => { 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', () => { it('throws if it cannot find preferred media type or a known fallback', () => {
function call() { function call() {
getVectorTileUrlTemplate([], 'application/vnd.mapbox-vector-tile'); getVectorTileUrlTemplate([], 'application/vnd.mapbox-vector-tile');