From 06943668166ae8245683db740e0bbd0fa68b5b49 Mon Sep 17 00:00:00 2001 From: Olivia Date: Sat, 13 Jan 2024 14:40:51 +0100 Subject: [PATCH] add url utils --- fixtures/wmts/arcgis.xml | 3 + src/wmts/capabilities.spec.ts | 16 +- src/wmts/capabilities.ts | 81 ++------ src/wmts/endpoint.spec.ts | 353 ++++++++++++++++++++++++++++------ src/wmts/endpoint.ts | 111 ++++++++++- src/wmts/model.ts | 89 +++++++++ src/wmts/tilegrid.ts | 8 +- src/wmts/url.spec.ts | 41 +++- src/wmts/url.ts | 42 +++- src/worker/index.ts | 4 +- 10 files changed, 584 insertions(+), 164 deletions(-) create mode 100644 src/wmts/model.ts diff --git a/fixtures/wmts/arcgis.xml b/fixtures/wmts/arcgis.xml index f81b9d8..7b2dbe5 100644 --- a/fixtures/wmts/arcgis.xml +++ b/fixtures/wmts/arcgis.xml @@ -86,6 +86,9 @@ + diff --git a/src/wmts/capabilities.spec.ts b/src/wmts/capabilities.spec.ts index 6891fa1..e7f7337 100644 --- a/src/wmts/capabilities.spec.ts +++ b/src/wmts/capabilities.spec.ts @@ -570,9 +570,10 @@ describe('WMTS Capabilities', () => { matrixSetLimits: [], }, ], - urlTemplates: [ - 'http://www.example.com/wmts/coastlines/{TileMatrix}/{TileRow}/{TileCol}.png', - ], + urlTemplates: { + 'image/png': + 'http://www.example.com/wmts/coastlines/{TileMatrix}/{TileRow}/{TileCol}.png', + }, dimensions: ['Time'], }, ]); @@ -609,9 +610,10 @@ describe('WMTS Capabilities', () => { }, ], title: 'Demographics_USA_Population_Density', - urlTemplates: [ - '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', - ], + urlTemplates: { + 'image/png': + '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', + }, }, ]); }); @@ -682,7 +684,7 @@ describe('WMTS Capabilities', () => { }, ], title: 'Photographies aƩriennes', - urlTemplates: [], + urlTemplates: {}, }, ]); }); diff --git a/src/wmts/capabilities.ts b/src/wmts/capabilities.ts index c778e9a..9377ad7 100644 --- a/src/wmts/capabilities.ts +++ b/src/wmts/capabilities.ts @@ -1,10 +1,5 @@ import { XmlDocument, XmlElement } from '@rgrove/parse-xml'; -import { - BoundingBox, - CrsCode, - GenericEndpointInfo, - MimeType, -} from '../shared/models'; +import { BoundingBox } from '../shared/models'; import { findChildElement, findChildrenElement, @@ -12,8 +7,16 @@ import { getElementText, getRootElement, } from '../shared/xml-utils'; +import { + LayerStyle, + MatrixSetLink, + TileMatrix, + WmtsEndpointInfo, + WmtsLayer, + WmtsMatrixSet, +} from './model'; -function parseBBox(xmlElement): BoundingBox { +function parseBBox(xmlElement: XmlElement): BoundingBox { const result = ['LowerCorner', 'UpperCorner'] .map((elName) => findChildElement(xmlElement, elName)) .map((cornerEl) => getElementText(cornerEl).split(' ')) @@ -23,13 +26,6 @@ function parseBBox(xmlElement): BoundingBox { return result; } -export interface WmtsEndpointInfo extends GenericEndpointInfo { - getTileUrls: { - kvp?: string; - rest?: string; - }; -} - export function readInfoFromCapabilities( capabilitiesDoc: XmlDocument ): WmtsEndpointInfo { @@ -67,29 +63,10 @@ export function readInfoFromCapabilities( }; } -interface TileMatrix { - identifier: string; - scaleDenominator: number; - resolution?: number; // FOR OL??? or computeResolution? - topLeft: [number, number]; - tileWidth: number; - tileHeight: number; - matrixWidth: number; - matrixHeight: number; -} - -export interface WmtsMatrixSet { - identifier: string; - wellKnownScaleSet?: string; // from fixed list? - crs: CrsCode; - boundingBox: BoundingBox; - tileMatrices: TileMatrix[]; -} - export function readMatrixSetsFromCapabilities( capabilitiesDoc: XmlDocument ): WmtsMatrixSet[] { - function parseMatrixSet(element): TileMatrix { + function parseMatrixSet(element: XmlElement): TileMatrix { const topLeft = getElementText(findChildElement(element, 'TopLeftCorner')) .split(' ') .map(parseFloat) as [number, number]; @@ -135,36 +112,6 @@ export function readMatrixSetsFromCapabilities( }); } -interface LayerStyle { - name: string; - title: string; - legendUrl?: string; -} - -interface MatrixSetLink { - matrixSetId: string; - matrixSetLimits: MatrixSetLimit[]; -} - -interface MatrixSetLimit { - tileMatrix: string; - minTileRow: number; - maxTileRow: number; - minTileCol: number; - maxTileCol: number; -} - -export interface WmtsLayer { - name: string; - urlTemplates: string[]; - styles: LayerStyle[]; - defaultStyle: string; - matrixSets: MatrixSetLink[]; - outputFormats: MimeType[]; - latLonBoundingBox?: BoundingBox; - dimensions?: string[]; -} - export function readLayersFromCapabilities( capabilitiesDoc: XmlDocument ): WmtsLayer[] { @@ -224,7 +171,11 @@ export function readLayersFromCapabilities( .filter( (element) => getElementAttribute(element, 'resourceType') === 'tile' ) - .map((element) => getElementAttribute(element, 'template')); + .reduce((prev, element) => { + const mimeType = getElementAttribute(element, 'format'); + const urlTemplate = getElementAttribute(element, 'template'); + return { ...prev, [mimeType]: urlTemplate }; + }, {}); const matrixSets = findChildrenElement(element, 'TileMatrixSetLink').map( parseMatrixSetLink ); diff --git a/src/wmts/endpoint.spec.ts b/src/wmts/endpoint.spec.ts index 239a68a..cf44488 100644 --- a/src/wmts/endpoint.spec.ts +++ b/src/wmts/endpoint.spec.ts @@ -1,7 +1,9 @@ -// @ts-ignore -import ogcsample from '../../fixtures/wmts/ogcsample.xml'; 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'; jest.mock('../shared/cache', () => ({ useCache: jest.fn((factory) => factory()), @@ -14,85 +16,308 @@ describe('WmtsEndpoint', () => { beforeEach(() => { jest.clearAllMocks(); - 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('OGC WMTS', () => { + beforeEach(() => { + global.fetchResponseFactory = () => ogcsample; + endpoint = new WmtsEndpoint('https://my.test.service/ogc/wmts?bb=c'); + }); - describe('caching', () => { - beforeEach(async () => { + 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' } + ); }); - it('uses cache once', () => { - expect(useCache).toHaveBeenCalledTimes(1); + + 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', + }, + }); + }); }); - 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('#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('#getLayers', () => { - it('returns a list of layers', async () => { - await endpoint.isReady(); - expect(endpoint.getLayers()).toEqual([ - expect.objectContaining({ + describe('#getRequestEncoding', () => { + it('returns KVP encoding', async () => { + await endpoint.isReady(); + expect(endpoint.getRequestEncoding()).toEqual('KVP'); + }); + }); + + describe('#getLayerByName', () => { + it('returns a layer', async () => { + await endpoint.isReady(); + expect( + endpoint.getLayerByName('BlueMarbleNextGeneration') + ).toMatchObject({ + abstract: 'Blue Marble Next Generation NASA Product', name: 'BlueMarbleNextGeneration', - title: 'Blue Marble Next Generation', - }), - ]); + defaultStyle: 'DarkBlue', + dimensions: ['Time'], + latLonBoundingBox: [-180, -90, 180, 90], + }); + }); }); - }); - 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('#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('#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('#getCompatibleOutputFormat', () => { + beforeEach(async () => { + await endpoint.isReady(); + }); + it('returns hinted format if supported', () => { + expect( + endpoint.getCompatibleOutputFormat( + 'BlueMarbleNextGeneration', + 'image/gif' + ) + ).toEqual('image/gif'); + }); + it('returns the first supported format if hinted format is not supported', () => { + expect( + endpoint.getCompatibleOutputFormat( + 'BlueMarbleNextGeneration', + 'image/bmp' + ) + ).toEqual('image/jpeg'); + }); + it('returns the first supported format if no hint', () => { + expect( + endpoint.getCompatibleOutputFormat('BlueMarbleNextGeneration') + ).toEqual('image/jpeg'); + }); + }); + + describe('#getLayerBaseTileUrl', () => { + it('returns the layer base GetTile url', async () => { + await endpoint.isReady(); + expect( + endpoint.getLayerBaseTileUrl('BlueMarbleNextGeneration', 'image/gif') + ).toEqual('http://www.maps.bob/cgi-bin/MiraMon5_0.cgi?'); + }); + }); + + describe('#getTileUrl', () => { + it('returns a complete GetTile url', 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' + ); + }); }); }); - 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', + 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('#getRequestEncoding', () => { + it('returns REST encoding', async () => { + await endpoint.isReady(); + expect(endpoint.getRequestEncoding()).toEqual('REST'); + }); + }); + + 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('#getCompatibleOutputFormat', () => { + beforeEach(async () => { + await endpoint.isReady(); + }); + it('returns hinted format if supported', () => { + expect( + endpoint.getCompatibleOutputFormat( + 'Demographics_USA_Population_Density', + 'image/jpeg' + ) + ).toEqual('image/jpeg'); + }); + it('returns the first supported format if hinted format is not supported', () => { + expect( + endpoint.getCompatibleOutputFormat( + 'Demographics_USA_Population_Density', + 'image/bmp' + ) + ).toEqual('image/png'); + }); + it('returns the first supported format if no hint', () => { + expect( + endpoint.getCompatibleOutputFormat( + 'Demographics_USA_Population_Density' + ) + ).toEqual('image/png'); + }); + }); + + describe('#getLayerBaseTileUrl', () => { + it('returns the layer base GetTile url', async () => { + await endpoint.isReady(); + expect( + endpoint.getLayerBaseTileUrl( + 'Demographics_USA_Population_Density', + 'image/jpeg' + ) + ).toEqual( + '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' + ); + }); + }); + + describe('#getTileUrl', () => { + it('returns a complete GetTile url', async () => { + await endpoint.isReady(); + expect( + endpoint.getTileUrl( + 'Demographics_USA_Population_Density', + 'default', + 'default028mm', + '3', + 2, + 1, + 'image/jpeg' + ) + ).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.jpeg' + ); }); }); }); diff --git a/src/wmts/endpoint.ts b/src/wmts/endpoint.ts index db40f57..6bfb849 100644 --- a/src/wmts/endpoint.ts +++ b/src/wmts/endpoint.ts @@ -1,15 +1,23 @@ -import { GenericEndpointInfo, MimeType } from '../shared/models'; +import { MimeType } from '../shared/models'; import { setQueryParams } from '../shared/http-utils'; import { useCache } from '../shared/cache'; -import { WmtsLayer, WmtsMatrixSet } from './capabilities'; import { parseWmtsCapabilities } from '../worker'; +import { buildTileGrid } from './tilegrid'; +import { + WmtsEndpointInfo, + WmtsLayer, + WmtsMatrixSet, + WmtsRequestEncoding, + WmtsTileGrid, +} from './model'; +import { generateGetTileUrl } from './url'; /** * Represents a WMTS endpoint advertising several layers. */ export default class WmtsEndpoint { private _capabilitiesPromise: Promise; - private _info: GenericEndpointInfo = null; + private _info: WmtsEndpointInfo = null; private _layers: WmtsLayer[] = null; private _matrixSets: WmtsMatrixSet[] = null; @@ -57,16 +65,105 @@ export default class WmtsEndpoint { return this._matrixSets; } + getRequestEncoding(): WmtsRequestEncoding { + if (!this._info) return null; + return 'rest' in this._info.getTileUrls ? 'REST' : 'KVP'; + } + + /** + * 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; + } + + /** + * Will return a format available for a layer; a hint can be provided but only supported formats will be returned + * @param layerName + * @param formatHint + */ + getCompatibleOutputFormat(layerName: string, formatHint?: MimeType) { + if (!this._layers) return null; + const layer = this.getLayerByName(layerName); + const formats = + this.getRequestEncoding() === 'REST' + ? Object.keys(layer.urlTemplates) + : layer.outputFormats; + if (formatHint && formats.includes(formatHint)) { + return formatHint; + } + return formats[0]; + } + /** - * Generates a URL template containing the {TileMatrix}, {TileCol} and {TileRow} tokens + * Generates either a URL template potentially containing the {Style}, {TileMatrixSet}, + * {TileMatrix}, {TileCol} and {TileRow} tokens (REST encoding), + * or a base URL to which query parameters will be added (KVP encoding) */ - generateGetTileTemplateUrl( - urlTemplate: string, + getLayerBaseTileUrl(layerName: string, outputFormat?: MimeType): string { + if (!this._layers) return null; + const layer = this.getLayerByName(layerName); + if (this.getRequestEncoding() === 'REST') { + let format = this.getCompatibleOutputFormat(layerName, outputFormat); + if (outputFormat && format !== outputFormat) { + console.warn( + `[ogc-client] Requested '${outputFormat}' format for the WMTS layer but it is not available in REST encoding, falling back to '${format}'` + ); + } + return layer.urlTemplates[format]; + } else { + return this.getServiceInfo().getTileUrls.kvp; + } + } + + /** + * Generates a URL for a specific tile of a specific layer + */ + getTileUrl( layerName: string, - tileMatrixSet: string, styleName: string, + matrixSetName: string, + tileMatrix: string, + tileRow: number, + tileCol: number, outputFormat?: MimeType ): string { if (!this._layers) return null; + const baseUrl = this.getLayerBaseTileUrl(layerName, outputFormat); + const format = this.getCompatibleOutputFormat(layerName, outputFormat); + return generateGetTileUrl( + baseUrl, + this.getRequestEncoding(), + layerName, + styleName, + matrixSetName, + tileMatrix, + tileRow, + tileCol, + format + ); } + + getTileGrid(layerName: string): WmtsTileGrid { + if (!this._layers) return null; + const layer = this.getLayerByName(layerName); + return buildTileGrid(layer, this.getMatrixSets()); + } + + /** + * Needed for an OL layer + * - urls: string[] + * - matrix set: string + * - format: string + * - projection: string + * - request encoding: string + * - tile grid: extent, minimum zoom, origins (one [x,y] per zoom level), + * - style: string + * - dimensions: object with dimension names -> values + * - global: boolean, true if the matrix set covers the whole world + */ } diff --git a/src/wmts/model.ts b/src/wmts/model.ts new file mode 100644 index 0000000..b3c6a7a --- /dev/null +++ b/src/wmts/model.ts @@ -0,0 +1,89 @@ +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 { + matrixSetId: string; + matrixSetLimits: MatrixSetLimit[]; +} + +export interface MatrixSetLimit { + tileMatrix: string; + minTileRow: number; + maxTileRow: number; + minTileCol: number; + maxTileCol: number; +} + +export interface WmtsLayer { + name: string; + urlTemplates: Record; // key is the image format + styles: LayerStyle[]; + defaultStyle: string; + matrixSets: MatrixSetLink[]; + outputFormats: MimeType[]; + latLonBoundingBox?: BoundingBox; + dimensions?: string[]; +} + +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/tilegrid.ts b/src/wmts/tilegrid.ts index 0b189ae..ae1b460 100644 --- a/src/wmts/tilegrid.ts +++ b/src/wmts/tilegrid.ts @@ -1,10 +1,4 @@ -import { WmtsLayer, WmtsMatrixSet } from './capabilities'; - -export interface WmtsTileGrid { - origins: [number, number][]; - sizes: [number, number][]; - tileSizes: [number, number][]; -} +import { WmtsLayer, WmtsMatrixSet, WmtsTileGrid } from './model'; export function buildTileGrid( layer: WmtsLayer, diff --git a/src/wmts/url.spec.ts b/src/wmts/url.spec.ts index 6c9402a..7238a74 100644 --- a/src/wmts/url.spec.ts +++ b/src/wmts/url.spec.ts @@ -1,5 +1,40 @@ -describe('url utils', () => { - describe('generateGetTileTemplateUrl', () => { - describe('with KVP and REST options', () => {}); +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 index ec077b1..e93d3b4 100644 --- a/src/wmts/url.ts +++ b/src/wmts/url.ts @@ -1,12 +1,36 @@ -import { WmtsEndpointInfo, WmtsLayer } from './capabilities'; +import { WmtsRequestEncoding } from './model'; +import { MimeType } from '../shared/models'; +import { setQueryParams } from '../shared/http-utils'; -/** - * Generates a URL template containing the {TileMatrix}, {TileCol} and {TileRow} tokens - */ -export function generateGetTileTemplateUrl( - endpointInfo: WmtsEndpointInfo, - layer: WmtsLayer, - matrixSetName: string +export function generateGetTileUrl( + baseUrl: string, + requestEncoding: WmtsRequestEncoding, + layerName: string, + styleName: string, + matrixSetName: string, + tileMatrix: string, + tileRow: number, + tileCol: number, + outputFormat: MimeType ): string { - return layer.urlTemplates[0]; + 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 7852953..0bbe618 100644 --- a/src/worker/index.ts +++ b/src/worker/index.ts @@ -7,8 +7,8 @@ import { } from '../wfs/endpoint'; import { GenericEndpointInfo } from '../shared/models'; import { WmsLayerFull, WmsVersion } from '../wms/endpoint'; -import { WmtsLayer, WmtsMatrixSet } from '../wmts/capabilities'; import { setFetchOptionsUpdateCallback } from '../shared/http-utils'; +import { WmtsEndpointInfo, WmtsLayer, WmtsMatrixSet } from '../wmts/model'; let fallbackWithoutWorker = false; @@ -87,7 +87,7 @@ export function queryWfsFeatureTypeDetails( * @param capabilitiesUrl This url should point to the capabilities document */ export function parseWmtsCapabilities(capabilitiesUrl: string): Promise<{ - info: GenericEndpointInfo; + info: WmtsEndpointInfo; layers: WmtsLayer[]; matrixSets: WmtsMatrixSet[]; }> {