UNPKG

3d-tiles-renderer

Version:

https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification

295 lines (222 loc) 7.96 kB
import { TiledImageSource } from './TiledImageSource.js'; import { ProjectionScheme } from '../utils/ProjectionScheme.js'; /** * @typedef {Object} WMTSTileMatrix * @property {string} identifier - TileMatrix identifier (e.g., 'Level0', 'EPSG:3857:0'). * @property {number} matrixWidth - Number of tile columns at this level. * @property {number} matrixHeight - Number of tile rows at this level. * @property {number} [tileWidth] - Tile width in pixels (defaults to tileDimension). * @property {number} [tileHeight] - Tile height in pixels (defaults to tileDimension). * @property {number[]} tileBounds - Tile grid bounds in radians `[west, south, east, north]`. * Required because the actual coverage depends on TopLeftCorner and ScaleDenominator * from the capabilities XML and cannot be computed from grid dimensions alone. */ /** * @typedef {Object} WMTSImageSourceOptions * @property {string} url - WMTS service URL. For KVP mode, this is the base endpoint. * For RESTful mode, include template variables: `{TileMatrixSet}`, `{TileMatrix}`, * `{TileRow}`, `{TileCol}`, `{Style}`. * @property {string} layer - WMTS layer identifier. * @property {string} tileMatrixSet - TileMatrixSet identifier (e.g., 'GoogleMapsCompatible', 'EPSG:3857'). * @property {string} [style='default'] - Style identifier. * @property {string} [format='image/jpeg'] - Output image format (e.g., 'image/png', 'image/jpeg'). * @property {Object<string, string>|null} [dimensions=null] - WMTS dimension values * (e.g., `{ TIME: '2024-01-01' }`). Used in both KVP and RESTful modes. * @property {string[]|null} [tileMatrixLabels=null] - Custom TileMatrix identifiers per level * (Tier 2). If provided, these labels replace numeric level indices in requests. * @property {WMTSTileMatrix[]|null} [tileMatrices=null] - Explicit per-level tile matrix definitions * (Tier 3). When provided, `levels` and `tileMatrixLabels` are ignored. * @property {string|null} [projection=null] - Projection identifier ('EPSG:3857' or 'EPSG:4326'). * Defaults to 'EPSG:3857' if not specified. * @property {number} [levels=20] - Number of zoom levels (Tier 1 & 2). Ignored if `tileMatrices` is provided. * @property {number} [tileDimension=256] - Default tile width and height in pixels. * @property {number[]|null} [contentBoundingBox=null] - Content bounding box in radians * `[west, south, east, north]`. If null, uses full projection bounds. * @ignore */ /** * WMTS (Web Map Tile Service) image source. * * All configuration is via literal values -- no capabilities dependency. * * Three tiers of configuration complexity: * * Tier 1 - Standard grid (~80% of services): * Just `url`, `layer`, `tileMatrixSet`, `projection`, and optionally `levels`. * Assumes standard power-of-two doubling grid. * * Tier 2 - Standard grid with custom labels (~15%): * Adds `tileMatrixLabels` (string[]) to map zoom levels to non-numeric * TileMatrix identifiers (e.g., 'EPSG:3857:0', 'EPSG:3857:1', ...). * * Tier 3 - Non-standard grid (~5%): * Uses explicit `tileMatrices` array with per-level definitions including * `identifier`, `matrixWidth`, `matrixHeight`, and optional `tileWidth`/`tileHeight`. * When provided, `levels` and `tileMatrixLabels` are ignored. * * Supports both KVP and RESTful request modes with automatic detection. * If the URL contains template variables, RESTful mode is used; * otherwise KVP query parameters are appended. * * Note: `contentBoundingBox` is specified in radians `[west, south, east, north]`. * * @extends TiledImageSource * @ignore */ export class WMTSImageSource extends TiledImageSource { /** * @param {WMTSImageSourceOptions} options - Configuration options. */ constructor( options = {} ) { const { layer = null, tileMatrixSet = 'default', style = 'default', url = null, format = 'image/jpeg', dimensions = null, tileMatrixLabels = null, tileMatrices = null, projection = null, levels = 20, tileDimension = 256, contentBoundingBox = null, ...rest } = options; super( rest ); this.layer = layer; this.tileMatrixSet = tileMatrixSet; this.style = style; this.url = url; this.format = format; this.dimensions = dimensions; this.tileMatrixLabels = tileMatrixLabels; this.tileMatrices = tileMatrices; this.projection = projection; this.levels = levels; this.tileDimension = tileDimension; this.contentBoundingBox = contentBoundingBox; this._useKvp = false; } /** * Detects whether the URL uses KVP or RESTful mode. * If the URL contains no template variables, it is considered a KVP endpoint. */ _detectRequestMode( url ) { return ! /\{/.test( url ); } init() { const { tiling, tileDimension, levels, dimensions, contentBoundingBox, tileMatrices, style, tileMatrixSet, } = this; let { url } = this; // Determine projection const projectionScheme = this.projection || 'EPSG:3857'; // Setup tiling tiling.flipY = true; tiling.setProjection( new ProjectionScheme( projectionScheme ) ); if ( contentBoundingBox !== null ) { tiling.setContentBounds( contentBoundingBox[ 0 ], contentBoundingBox[ 1 ], contentBoundingBox[ 2 ], contentBoundingBox[ 3 ], ); } else { tiling.setContentBounds( ...tiling.projection.getBounds() ); } // Tiered initialization if ( Array.isArray( tileMatrices ) ) { // Tier 3: Explicit per-level tile matrix definitions. tileMatrices.forEach( ( tm, i ) => { const tw = tm.tileWidth || tileDimension; const th = tm.tileHeight || tileDimension; tiling.setLevel( i, { tilePixelWidth: tw, tilePixelHeight: th, tileCountX: tm.matrixWidth, tileCountY: tm.matrixHeight, tileBounds: tm.tileBounds || tm.bounds, } ); } ); } else { // Tier 1 & 2: Standard power-of-two doubling grid tiling.generateLevels( levels, tiling.projection.tileCountX, tiling.projection.tileCountY, { tilePixelWidth: tileDimension, tilePixelHeight: tileDimension, }, ); } // Detect request mode this._useKvp = this._detectRequestMode( url ); if ( ! this._useKvp ) { // RESTful: pre-fill static template values url = url .replace( /{\s*TileMatrixSet\s*}/gi, tileMatrixSet ) .replace( /{\s*Style\s*}/gi, style ); if ( dimensions ) { for ( const key in dimensions ) { url = url.replace( new RegExp( `{\\s*${ key }\\s*}`, 'gi' ), dimensions[ key ] ); } } } this.url = url; return Promise.resolve(); } getUrl( x, y, level ) { const { tileMatrices, tileMatrixLabels } = this; // Determine the TileMatrix identifier for this level (tier priority: 3 > 2 > 1) let tileMatrix; if ( tileMatrices !== null && tileMatrices.length > 0 ) { tileMatrix = tileMatrices[ level ].identifier; } else if ( tileMatrixLabels ) { tileMatrix = tileMatrixLabels[ level ]; } else { tileMatrix = level.toString(); } if ( this._useKvp ) { return this._buildKvpUrl( x, y, tileMatrix ); } return this._buildRestfulUrl( x, y, tileMatrix ); } _buildRestfulUrl( x, y, tileMatrix ) { return this.url .replace( /{\s*TileMatrix\s*}/gi, tileMatrix ) .replace( /{\s*TileCol\s*}/gi, x ) .replace( /{\s*TileRow\s*}/gi, y ); } _buildKvpUrl( x, y, tileMatrix ) { const { dimensions, format } = this; const baseUrl = this.url; const params = new URLSearchParams( { SERVICE: 'WMTS', VERSION: '1.0.0', REQUEST: 'GetTile', LAYER: this.layer, STYLE: this.style, TILEMATRIXSET: this.tileMatrixSet, TILEMATRIX: tileMatrix, TILEROW: y, TILECOL: x, FORMAT: format, } ); if ( dimensions ) { for ( const key in dimensions ) { params.set( key, dimensions[ key ] ); } } const separator = baseUrl.includes( '?' ) ? '&' : '?'; return baseUrl + separator + params.toString(); } }