ogcopenapis 0.0.12 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/LICENSE +20 -20
  3. package/README.md +305 -72
  4. package/lib/OgcOpenApiCrsTools.d.ts +10 -9
  5. package/lib/OgcOpenApiCrsTools.d.ts.map +1 -0
  6. package/lib/OgcOpenApiCrsTools.js +40 -39
  7. package/lib/OgcOpenApiCrsTools.js.map +1 -0
  8. package/lib/OgcOpenApiFeatureStore.d.ts +94 -93
  9. package/lib/OgcOpenApiFeatureStore.d.ts.map +1 -0
  10. package/lib/OgcOpenApiFeatureStore.js +200 -244
  11. package/lib/OgcOpenApiFeatureStore.js.map +1 -0
  12. package/lib/OgcOpenApiFetchTools.d.ts +37 -30
  13. package/lib/OgcOpenApiFetchTools.d.ts.map +1 -0
  14. package/lib/OgcOpenApiFetchTools.js +68 -50
  15. package/lib/OgcOpenApiFetchTools.js.map +1 -0
  16. package/lib/OgcOpenApiGetCapabilities.d.ts +209 -112
  17. package/lib/OgcOpenApiGetCapabilities.d.ts.map +1 -0
  18. package/lib/OgcOpenApiGetCapabilities.js +424 -277
  19. package/lib/OgcOpenApiGetCapabilities.js.map +1 -0
  20. package/lib/OgcOpenApiMapsModel.d.ts +41 -36
  21. package/lib/OgcOpenApiMapsModel.d.ts.map +1 -0
  22. package/lib/OgcOpenApiMapsModel.js +88 -75
  23. package/lib/OgcOpenApiMapsModel.js.map +1 -0
  24. package/lib/OgcOpenApiMvtTools.d.ts +12 -0
  25. package/lib/OgcOpenApiMvtTools.d.ts.map +1 -0
  26. package/lib/OgcOpenApiMvtTools.js +100 -0
  27. package/lib/OgcOpenApiMvtTools.js.map +1 -0
  28. package/lib/OgcOpenApiStyleTools.d.ts +51 -0
  29. package/lib/OgcOpenApiStyleTools.d.ts.map +1 -0
  30. package/lib/OgcOpenApiStyleTools.js +155 -0
  31. package/lib/OgcOpenApiStyleTools.js.map +1 -0
  32. package/lib/OgcOpenApiTileMatrixTools.d.ts +41 -0
  33. package/lib/OgcOpenApiTileMatrixTools.d.ts.map +1 -0
  34. package/lib/OgcOpenApiTileMatrixTools.js +88 -0
  35. package/lib/OgcOpenApiTileMatrixTools.js.map +1 -0
  36. package/lib/OgcOpenApiTilesModel.d.ts +56 -31
  37. package/lib/OgcOpenApiTilesModel.d.ts.map +1 -0
  38. package/lib/OgcOpenApiTilesModel.js +167 -128
  39. package/lib/OgcOpenApiTilesModel.js.map +1 -0
  40. package/lib/OgcOpenApiVectorTilesModel.d.ts +39 -0
  41. package/lib/OgcOpenApiVectorTilesModel.d.ts.map +1 -0
  42. package/lib/OgcOpenApiVectorTilesModel.js +122 -0
  43. package/lib/OgcOpenApiVectorTilesModel.js.map +1 -0
  44. package/lib/OgcOpenApiWmtsTools.d.ts +30 -0
  45. package/lib/OgcOpenApiWmtsTools.d.ts.map +1 -0
  46. package/lib/OgcOpenApiWmtsTools.js +89 -0
  47. package/lib/OgcOpenApiWmtsTools.js.map +1 -0
  48. package/package.json +57 -27
  49. package/src/OgcOpenApiCrsTools.ts +40 -0
  50. package/src/OgcOpenApiFeatureStore.ts +296 -0
  51. package/src/OgcOpenApiFetchTools.ts +84 -0
  52. package/src/OgcOpenApiGetCapabilities.ts +641 -0
  53. package/src/OgcOpenApiMapsModel.ts +128 -0
  54. package/src/OgcOpenApiMvtTools.ts +104 -0
  55. package/src/OgcOpenApiStyleTools.ts +187 -0
  56. package/src/OgcOpenApiTileMatrixTools.ts +111 -0
  57. package/src/OgcOpenApiTilesModel.ts +214 -0
  58. package/src/OgcOpenApiVectorTilesModel.ts +141 -0
  59. package/src/OgcOpenApiWmtsTools.ts +114 -0
  60. package/lib/OgcOpenApiGetCollectionTiles.d.ts +0 -7
  61. package/lib/OgcOpenApiGetCollectionTiles.js +0 -7
@@ -0,0 +1,214 @@
1
+ import {WMTSTileSetModel, type WMTSTileSetModelConstructorOptions} from "@luciad/ria/model/tileset/WMTSTileSetModel.js";
2
+ import {WMTSRequestEncoding} from "@luciad/ria/model/tileset/WMTSRequestEncoding.js";
3
+ import type {QuadTreeRasterTileSetStructure, TileMatrix, TileMatrixSet2DRasterTileSetStructure} from "@luciad/ria/model/tileset/RasterTileSetModel.js";
4
+ import {RasterDataType} from "@luciad/ria/model/tileset/RasterDataType.js";
5
+ import {RasterSamplingMode} from "@luciad/ria/model/tileset/RasterSamplingMode.js";
6
+ import type {TileCoordinate} from "@luciad/ria/model/tileset/TileCoordinate.js";
7
+ import type {ProxifyFunction, TileMatrixLimits, TileSetData} from "./OgcOpenApiGetCapabilities.js";
8
+ import {type OgcOpenApiTileGrid, OgcOpenApiTileMatrixTools} from "./OgcOpenApiTileMatrixTools.js";
9
+ import {OgcOpenApiWmtsTools} from "./OgcOpenApiWmtsTools.js";
10
+
11
+
12
+ export interface OgcOpenApiTilesModelOptions {
13
+ tileMatrix: TileSetData;
14
+ baseURL: string;
15
+ collection: string;
16
+ dataType?: RasterDataType;
17
+ samplingMode?: RasterSamplingMode;
18
+ credentials?: boolean;
19
+ requestHeaders?: { [p: string]: string };
20
+ transparent?: boolean;
21
+ bgcolor?: string;
22
+
23
+ proxify?: ProxifyFunction
24
+ }
25
+
26
+ export interface OgcOpenApiTilesModelCreateOptions extends OgcOpenApiTilesModelOptions {
27
+ // From OgcOpenApiGetCapabilities.fetchCollectionTileSet, tiles outside these limits are not requested
28
+ tileMatrixSetLimits?: TileMatrixLimits[];
29
+ // The data extent in CRS:84, [minX, minY, maxX, maxY], such as the extent of the collection
30
+ extent?: number[];
31
+ // The media type of the tiles, image/png by default
32
+ format?: string;
33
+ }
34
+
35
+ // The tile structure that LuciadRIA's WMTS support derived from the tile matrix set
36
+ interface WmtsTileStructure {
37
+ structure: QuadTreeRasterTileSetStructure | TileMatrixSet2DRasterTileSetStructure;
38
+ // The tile matrix of each level, with its OGC id and number of rows
39
+ tileMatrices: { id: string, matrixHeight: number }[];
40
+ }
41
+
42
+ export class OgcOpenApiTilesModel extends WMTSTileSetModel {
43
+ private transparent: boolean;
44
+ private bgcolor: string | undefined;
45
+ private proxy: ProxifyFunction;
46
+ private tileMatrixSetLimits: TileMatrixLimits[] | undefined;
47
+ // Not tileMatrices: WMTSTileSetModel keeps the tile matrix ids in that property
48
+ private ogcTileMatrices: { id: string, matrixHeight: number }[];
49
+ // Only for the deprecated constructor, which addresses the tiles itself with the OGC API template
50
+ private tileGrid: OgcOpenApiTileGrid | null;
51
+ private ogcTemplate: string;
52
+
53
+ /**
54
+ * Creates the model with LuciadRIA's WMTS support, which handles tile matrix sets that are quad-trees and
55
+ * tile matrix sets that are not, such as GlobalCRS84Scale.
56
+ */
57
+ static async createFromTileSet(options: OgcOpenApiTilesModelCreateOptions): Promise<OgcOpenApiTilesModel> {
58
+ const tileSet = options.tileMatrix;
59
+ // WMTS 1.0 has no other corner of origin than the top left
60
+ if (tileSet.tileMatrices.some(m => typeof m.cornerOfOrigin === "string" && m.cornerOfOrigin.toUpperCase() === "BOTTOMLEFT")) {
61
+ return new OgcOpenApiTilesModel(options);
62
+ }
63
+ const format = options.format ? options.format : "image/png";
64
+ const capabilities = await OgcOpenApiWmtsTools.createCapabilities({
65
+ layer: options.collection, tileMatrix: tileSet, template: options.baseURL, format,
66
+ tileMatrixSetLimits: options.tileMatrixSetLimits, extent: options.extent,
67
+ });
68
+ const wmts = WMTSTileSetModel.createFromCapabilities(capabilities, {layer: options.collection}, {format});
69
+ return new OgcOpenApiTilesModel(options, OgcOpenApiTilesModel.getTileStructure(wmts, tileSet));
70
+ }
71
+
72
+ /**
73
+ * @deprecated Supports quad-tree tile matrix sets only, use createFromTileSet
74
+ */
75
+ constructor(options: OgcOpenApiTilesModelCreateOptions, wmtsStructure?: WmtsTileStructure) {
76
+ const tileGrid = wmtsStructure ? null : OgcOpenApiTileMatrixTools.createTileGrid(options.tileMatrix);
77
+ const tileMatrices = wmtsStructure ? wmtsStructure.tileMatrices :
78
+ tileGrid.tileMatrices.map(m => ({id: m.id, matrixHeight: m.matrixHeight}));
79
+ const structure = wmtsStructure ? wmtsStructure.structure : {
80
+ reference: tileGrid.reference,
81
+ bounds: tileGrid.bounds,
82
+ levelCount: tileGrid.levelCount,
83
+ level0Columns: tileGrid.level0Columns,
84
+ level0Rows: tileGrid.level0Rows,
85
+ tileWidth: tileGrid.tileWidth,
86
+ tileHeight: tileGrid.tileHeight,
87
+ };
88
+ const template = OgcOpenApiWmtsTools.toWmtsTemplate(options.baseURL);
89
+ const o: WMTSTileSetModelConstructorOptions = {
90
+ baseURL: template,
91
+ url: template,
92
+ layer: options.collection,
93
+ tileMatrixSet: options.tileMatrix.id,
94
+ tileMatrices: tileMatrices.map(m => m.id),
95
+ format: options.format ? options.format : "image/png",
96
+ requestEncoding: WMTSRequestEncoding.REST,
97
+ structure,
98
+ // Tiles can be missing inside the limits, do not stop refining at them
99
+ isSparseTileSet: !!options.tileMatrixSetLimits,
100
+ dataType: options.dataType,
101
+ samplingMode: options.samplingMode,
102
+ credentials: options.credentials,
103
+ requestHeaders: options.requestHeaders ? options.requestHeaders : {},
104
+ }
105
+ super(o);
106
+ this.tileGrid = tileGrid;
107
+ this.ogcTemplate = options.baseURL;
108
+ this.ogcTileMatrices = tileMatrices;
109
+ this.tileMatrixSetLimits = options.tileMatrixSetLimits;
110
+ this.transparent = typeof options.transparent !== "undefined" ? options.transparent : true;
111
+ this.bgcolor = options.bgcolor;
112
+
113
+ this.proxy = options.proxify;
114
+
115
+ this.modelDescriptor = {
116
+ source: options.baseURL,
117
+ name: options.collection,
118
+ description: "OGC Open API Tiles",
119
+ type: this.dataType
120
+ };
121
+ }
122
+
123
+ getTileURL(baseURL: string, tile: TileCoordinate): string | null {
124
+ // return this.debugAsImage(tile)
125
+ if (!this.hasData(tile)) return null;
126
+ const targetUrl = this.tileGrid ?
127
+ OgcOpenApiTileMatrixTools.resolveTileTemplate(this.ogcTemplate, this.tileGrid, tile) :
128
+ super.getTileURL(baseURL, tile);
129
+ if (targetUrl === null) return null;
130
+ let tileUrl = OgcOpenApiTileMatrixTools.appendQueryParameters(targetUrl, {
131
+ transparent: this.transparent,
132
+ bgcolor: this.transparent ? undefined : this.bgcolor
133
+ });
134
+ if (typeof this.proxy === "function") {
135
+ const proxyfied = this.proxy(tileUrl, {});
136
+ tileUrl = proxyfied.url;
137
+ }
138
+ return tileUrl
139
+ }
140
+
141
+ // Whether the tile is inside the tileMatrixSetLimits, all tiles are when there are no limits
142
+ private hasData(tile: TileCoordinate) {
143
+ if (!this.tileMatrixSetLimits) return true;
144
+ const tileMatrix = this.ogcTileMatrices[tile.level];
145
+ if (!tileMatrix) return false;
146
+ const limits = this.tileMatrixSetLimits.find(l => l.tileMatrix === tileMatrix.id);
147
+ const tileRow = tileMatrix.matrixHeight - 1 - tile.y;
148
+ return !!limits &&
149
+ tileRow >= limits.minTileRow && tileRow <= limits.maxTileRow &&
150
+ tile.x >= limits.minTileCol && tile.x <= limits.maxTileCol;
151
+ }
152
+
153
+ // Reads the levels of the model that LuciadRIA's WMTS support created, as a quad-tree structure when they are one
154
+ private static getTileStructure(wmts: WMTSTileSetModel, tileSet: TileSetData): WmtsTileStructure {
155
+ const levels: TileMatrix[] = [];
156
+ for (let level = 0; level < wmts.levelCount; level++) {
157
+ levels.push({
158
+ bounds: wmts.getBounds(level),
159
+ tileColumnCount: wmts.getTileColumnCount(level),
160
+ tileRowCount: wmts.getTileRowCount(level),
161
+ tileWidth: wmts.getTileWidth(level),
162
+ tileHeight: wmts.getTileHeight(level),
163
+ });
164
+ }
165
+ // The OGC tile matrix of each level, in order and with the same number of columns and rows
166
+ const tileMatrices: { id: string, matrixHeight: number }[] = [];
167
+ let next = 0;
168
+ for (const level of levels) {
169
+ while (next < tileSet.tileMatrices.length &&
170
+ (tileSet.tileMatrices[next].matrixWidth !== level.tileColumnCount || tileSet.tileMatrices[next].matrixHeight !== level.tileRowCount)) next++;
171
+ if (next >= tileSet.tileMatrices.length) throw new Error("The tile matrices of " + tileSet.id + " do not match the WMTS levels");
172
+ tileMatrices.push({id: tileSet.tileMatrices[next].id, matrixHeight: tileSet.tileMatrices[next].matrixHeight});
173
+ next++;
174
+ }
175
+ const first = levels[0];
176
+ const isQuadTree = levels.every((level, l) =>
177
+ level.tileColumnCount === first.tileColumnCount * Math.pow(2, l) &&
178
+ level.tileRowCount === first.tileRowCount * Math.pow(2, l) &&
179
+ level.tileWidth === first.tileWidth && level.tileHeight === first.tileHeight &&
180
+ level.bounds.equals(first.bounds));
181
+ // A quad-tree structure also works on a Canvas map, a structure per level only on a WebGL map
182
+ const structure = isQuadTree ? {
183
+ reference: wmts.reference,
184
+ bounds: first.bounds,
185
+ levelCount: levels.length,
186
+ level0Columns: first.tileColumnCount,
187
+ level0Rows: first.tileRowCount,
188
+ tileWidth: first.tileWidth,
189
+ tileHeight: first.tileHeight,
190
+ } : {
191
+ reference: wmts.reference,
192
+ bounds: first.bounds,
193
+ tileMatrix: levels,
194
+ };
195
+ return {structure, tileMatrices};
196
+ }
197
+
198
+ debugAsImage(tileCorrected: TileCoordinate) {
199
+ const canvas = document.createElement('canvas');
200
+ if (canvas) {
201
+ canvas.width = this.getTileWidth(tileCorrected.level) as number;
202
+ canvas.height = this.getTileHeight(tileCorrected.level) as number;
203
+ const ctx = canvas.getContext("2d");
204
+ if (ctx) {
205
+ ctx.font = "30px Arial";
206
+ ctx.fillText(`x:${tileCorrected.x}, y:${tileCorrected.y}, z:${tileCorrected.level}`, 10, canvas.height/2);
207
+ }
208
+ const dataURL = canvas.toDataURL();
209
+ return dataURL;
210
+ }
211
+ return null;
212
+ }
213
+
214
+ }
@@ -0,0 +1,141 @@
1
+ import {MapboxVectorTileSetModel} from "@luciad/ria/model/tileset/mapbox/MapboxVectorTileSetModel.js";
2
+ import type {TileCoordinate} from "@luciad/ria/model/tileset/TileCoordinate.js";
3
+ import {Bounds} from "@luciad/ria/shape/Bounds.js";
4
+ import {getReference} from "@luciad/ria/reference/ReferenceProvider.js";
5
+ import {createPoint} from "@luciad/ria/shape/ShapeFactory.js";
6
+ import {createTransformation} from "@luciad/ria/transformation/TransformationFactory.js";
7
+ import type {ProxifyFunction, TileMatrixLimits, TileSetData, TileSetLayer} from "./OgcOpenApiGetCapabilities.js";
8
+ import {OgcOpenApiMvtTools} from "./OgcOpenApiMvtTools.js";
9
+ import {type FetchServerOptions, OgcOpenApiFetchTools} from "./OgcOpenApiFetchTools.js";
10
+ import {type OgcOpenApiTileGrid, OgcOpenApiTileMatrixTools} from "./OgcOpenApiTileMatrixTools.js";
11
+
12
+ export interface OgcOpenApiVectorTilesModelOptions {
13
+ tileMatrix: TileSetData;
14
+ baseURL: string;
15
+ collection: string;
16
+ credentials?: boolean;
17
+ requestHeaders?: { [p: string]: string };
18
+ // From OgcOpenApiGetCapabilities.fetchCollectionTileSet, tiles outside these limits are not requested
19
+ tileMatrixSetLimits?: TileMatrixLimits[];
20
+ // The data extent, defaults to the extent of the tile matrix set
21
+ bounds?: Bounds;
22
+ styleUrl?: string;
23
+
24
+ proxify?: ProxifyFunction
25
+ }
26
+
27
+ export class OgcOpenApiVectorTilesModel extends MapboxVectorTileSetModel {
28
+ private tileGrid: OgcOpenApiTileGrid;
29
+ private tileTemplate: string;
30
+ private serverOptions: FetchServerOptions;
31
+ private tileMatrixSetLimits: TileMatrixLimits[] | undefined;
32
+
33
+ constructor(options: OgcOpenApiVectorTilesModelOptions) {
34
+ const tileGrid = OgcOpenApiTileMatrixTools.createTileGrid(options.tileMatrix);
35
+ const maxLevel = OgcOpenApiVectorTilesModel.getMaxLevel(tileGrid, options.tileMatrixSetLimits);
36
+ super({
37
+ baseURL: options.baseURL,
38
+ structure: {
39
+ reference: tileGrid.reference,
40
+ bounds: tileGrid.bounds,
41
+ level0Columns: tileGrid.level0Columns,
42
+ level0Rows: tileGrid.level0Rows,
43
+ },
44
+ // LuciadRIA 2026.1.1 paints nothing when minLevel > 0, levels without data are skipped in getVectorTile
45
+ minLevel: 0,
46
+ maxLevel,
47
+ bounds: options.bounds,
48
+ name: options.collection,
49
+ styleUrl: options.styleUrl ? options.styleUrl : null,
50
+ credentials: options.credentials,
51
+ requestHeaders: options.requestHeaders ? options.requestHeaders : {},
52
+ });
53
+ this.tileGrid = tileGrid;
54
+ this.tileTemplate = options.baseURL;
55
+ this.serverOptions = OgcOpenApiFetchTools.createFetchOptions({originalUrl: options.baseURL, proxify: options.proxify});
56
+ this.tileMatrixSetLimits = options.tileMatrixSetLimits;
57
+ }
58
+
59
+ getVectorTile(tile: TileCoordinate, signal?: AbortSignal): Promise<ArrayBuffer | null> {
60
+ if (!this.hasData(tile)) return Promise.resolve(null);
61
+ const targetUrl = OgcOpenApiTileMatrixTools.resolveTileTemplate(this.tileTemplate, this.tileGrid, tile);
62
+ if (targetUrl === null) return Promise.resolve(null);
63
+ const tileUrl = OgcOpenApiTileMatrixTools.appendQueryParameters(targetUrl, this.requestParameters ? this.requestParameters : {});
64
+ return OgcOpenApiFetchTools.fetch(tileUrl, {
65
+ method: "GET",
66
+ credentials: this.credentials ? "same-origin" : "omit",
67
+ headers: this.requestHeaders ? this.requestHeaders : {},
68
+ signal
69
+ }, this.serverOptions).then(response => {
70
+ // Servers answer a tile without data with 204, 404 or an empty 200
71
+ if (response.status === 204 || response.status === 404) return null;
72
+ if (response.status !== 200) throw new Error("Invalid response: " + response.status + " for " + tileUrl);
73
+ return response.arrayBuffer().then(content => content.byteLength > 0 ? content : null);
74
+ });
75
+ }
76
+
77
+ /**
78
+ * The layers in the tiles, read from a few sample tiles. For tilesets whose metadata does not list their layers,
79
+ * the layer names in the tiles cannot be guessed: some servers name a layer after the collection, others not.
80
+ * extent, in CRS:84 as [minX, minY, maxX, maxY], is where the data is, such as the extent of the collection.
81
+ * Resolves the layers of the first sample tile that has any, or an empty array.
82
+ */
83
+ async discoverLayers(options: { extent?: number[], maxRequests?: number } = {}): Promise<TileSetLayer[]> {
84
+ const maxRequests = typeof options.maxRequests === "number" ? options.maxRequests : 6;
85
+ for (const tile of this.getSampleTiles(options.extent).slice(0, maxRequests)) {
86
+ const content = await this.getVectorTile(tile).catch(() => null);
87
+ if (!content) continue;
88
+ const layers = await OgcOpenApiMvtTools.getLayers(content).catch(() => []);
89
+ if (layers.length > 0) return layers;
90
+ }
91
+ return [];
92
+ }
93
+
94
+ // Tiles likely to have data: the middle tile of the limits per level, or the tile at the center of the extent
95
+ private getSampleTiles(extent?: number[]): TileCoordinate[] {
96
+ if (this.tileMatrixSetLimits) {
97
+ return this.tileMatrixSetLimits
98
+ .map(l => OgcOpenApiTileMatrixTools.getLuciadTile(this.tileGrid, l.tileMatrix,
99
+ Math.floor((l.minTileRow + l.maxTileRow) / 2), Math.floor((l.minTileCol + l.maxTileCol) / 2)))
100
+ .filter(tile => tile !== null)
101
+ .sort((a, b) => a.level - b.level);
102
+ }
103
+ if (!extent || extent.length < 4) {
104
+ const tiles: TileCoordinate[] = [];
105
+ for (let x = 0; x < this.tileGrid.level0Columns; x++) {
106
+ for (let y = 0; y < this.tileGrid.level0Rows; y++) tiles.push({level: 0, x, y});
107
+ }
108
+ return tiles;
109
+ }
110
+ const crs84 = getReference("CRS:84");
111
+ const center = createTransformation(crs84, this.tileGrid.reference)
112
+ .transform(createPoint(crs84, [(extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2]));
113
+ const bounds = this.tileGrid.bounds;
114
+ return [0, 2, 4, 6, 8, 10, 12].filter(level => level < this.tileGrid.levelCount).map(level => {
115
+ const columns = this.tileGrid.level0Columns * Math.pow(2, level);
116
+ const rows = this.tileGrid.level0Rows * Math.pow(2, level);
117
+ const x = Math.floor((center.x - bounds.x) / (bounds.width / columns));
118
+ const y = Math.floor((center.y - bounds.y) / (bounds.height / rows));
119
+ return {level, x: Math.min(Math.max(x, 0), columns - 1), y: Math.min(Math.max(y, 0), rows - 1)};
120
+ });
121
+ }
122
+
123
+ // Whether the tile is inside the tileMatrixSetLimits, all tiles are when there are no limits
124
+ private hasData(tile: TileCoordinate) {
125
+ if (!this.tileMatrixSetLimits) return true;
126
+ const ogcTile = OgcOpenApiTileMatrixTools.getOgcTile(this.tileGrid, tile);
127
+ if (ogcTile === null) return false;
128
+ const limits = this.tileMatrixSetLimits.find(l => l.tileMatrix === ogcTile.tileMatrix);
129
+ return !!limits &&
130
+ ogcTile.tileRow >= limits.minTileRow && ogcTile.tileRow <= limits.maxTileRow &&
131
+ ogcTile.tileCol >= limits.minTileCol && ogcTile.tileCol <= limits.maxTileCol;
132
+ }
133
+
134
+ // The deepest level in the tileMatrixSetLimits, LuciadRIA over-zooms beyond it
135
+ private static getMaxLevel(tileGrid: OgcOpenApiTileGrid, tileMatrixSetLimits?: TileMatrixLimits[]) {
136
+ const levels = tileMatrixSetLimits ?
137
+ tileMatrixSetLimits.map(l => tileGrid.tileMatrices.findIndex(m => m.id === l.tileMatrix)).filter(level => level > -1) :
138
+ [];
139
+ return levels.length === 0 ? tileGrid.levelCount - 1 : Math.max(...levels);
140
+ }
141
+ }
@@ -0,0 +1,114 @@
1
+ import {WMTSCapabilities} from "@luciad/ria/model/capabilities/WMTSCapabilities.js";
2
+ import {WMTSRequestEncoding} from "@luciad/ria/model/tileset/WMTSRequestEncoding.js";
3
+ import {CoordinateType} from "@luciad/ria/reference/CoordinateType.js";
4
+ import {getReference} from "@luciad/ria/reference/ReferenceProvider.js";
5
+ import {OgcOpenApiCrsTools} from "./OgcOpenApiCrsTools.js";
6
+ import type {TileMatrixLimits, TileSetData} from "./OgcOpenApiGetCapabilities.js";
7
+
8
+ /**
9
+ * Describes an OGC API tileset as a WMTS 1.0 layer, so that LuciadRIA's WMTS support reads its tile matrix set:
10
+ * quad-trees and tile matrix sets that are not quad-trees, with their tile matrix limits.
11
+ */
12
+ export interface OgcOpenApiWmtsLayer {
13
+ layer: string;
14
+ tileMatrix: TileSetData;
15
+ // The OGC API tile URL template, with {tileMatrix}, {tileRow} and {tileCol}
16
+ template: string;
17
+ format: string;
18
+ tileMatrixSetLimits?: TileMatrixLimits[];
19
+ // The data extent in CRS:84, [minX, minY, maxX, maxY]
20
+ extent?: number[];
21
+ }
22
+
23
+ // Meters per degree at the equator, to derive a scale denominator from the cell size of a geographic tile matrix set
24
+ const METERS_PER_DEGREE = 2 * Math.PI * 6378137 / 360;
25
+ // The standardized rendering pixel size of WMTS, 0.28 mm
26
+ const PIXEL_SIZE = 0.00028;
27
+
28
+ class OgcOpenApiWmtsTools {
29
+
30
+ /**
31
+ * WMTS uses {TileMatrix}, {TileRow} and {TileCol} where OGC API Tiles uses {tileMatrix}, {tileRow} and {tileCol}
32
+ */
33
+ public static toWmtsTemplate(template: string) {
34
+ return template.replace("{tileMatrix}", "{TileMatrix}").replace("{tileRow}", "{TileRow}").replace("{tileCol}", "{TileCol}");
35
+ }
36
+
37
+ // http://www.opengis.net/def/crs/EPSG/0/3857 becomes urn:ogc:def:crs:EPSG::3857
38
+ public static toCrsUrn(crs: string) {
39
+ const match = /^https?:\/\/www\.opengis\.net\/def\/crs\/([^/]+)\/([^/]+)\/([^/]+)$/.exec(crs.replace(/[<>]/g, "").trim());
40
+ if (!match) return crs;
41
+ const [, authority, version, code] = match;
42
+ return "urn:ogc:def:crs:" + authority + ":" + (version === "0" ? "" : version) + ":" + code;
43
+ }
44
+
45
+ public static toCapabilitiesXml(description: OgcOpenApiWmtsLayer): string {
46
+ const tileSet = description.tileMatrix;
47
+ const isGeographic = getReference(OgcOpenApiCrsTools.getReferenceName(tileSet.crs)).coordinateType === CoordinateType.GEODETIC;
48
+ const matrices = tileSet.tileMatrices.map(m => {
49
+ const scaleDenominator = typeof m.scaleDenominator === "number" ? m.scaleDenominator :
50
+ m.cellSize * (isGeographic ? METERS_PER_DEGREE : 1) / PIXEL_SIZE;
51
+ return "<TileMatrix>" +
52
+ OgcOpenApiWmtsTools.element("ows:Identifier", m.id) +
53
+ OgcOpenApiWmtsTools.element("ScaleDenominator", scaleDenominator) +
54
+ // Both pointOfOrigin and TopLeftCorner are in the axis order of the CRS
55
+ OgcOpenApiWmtsTools.element("TopLeftCorner", m.pointOfOrigin[0] + " " + m.pointOfOrigin[1]) +
56
+ OgcOpenApiWmtsTools.element("TileWidth", m.tileWidth) +
57
+ OgcOpenApiWmtsTools.element("TileHeight", m.tileHeight) +
58
+ OgcOpenApiWmtsTools.element("MatrixWidth", m.matrixWidth) +
59
+ OgcOpenApiWmtsTools.element("MatrixHeight", m.matrixHeight) +
60
+ "</TileMatrix>";
61
+ }).join("");
62
+ const limits = description.tileMatrixSetLimits && description.tileMatrixSetLimits.length > 0 ?
63
+ "<TileMatrixSetLimits>" + description.tileMatrixSetLimits.map(l => "<TileMatrixLimits>" +
64
+ OgcOpenApiWmtsTools.element("TileMatrix", l.tileMatrix) +
65
+ OgcOpenApiWmtsTools.element("MinTileRow", l.minTileRow) +
66
+ OgcOpenApiWmtsTools.element("MaxTileRow", l.maxTileRow) +
67
+ OgcOpenApiWmtsTools.element("MinTileCol", l.minTileCol) +
68
+ OgcOpenApiWmtsTools.element("MaxTileCol", l.maxTileCol) +
69
+ "</TileMatrixLimits>").join("") + "</TileMatrixSetLimits>" : "";
70
+ const extent = description.extent && description.extent.length >= 4 ?
71
+ "<ows:WGS84BoundingBox>" +
72
+ OgcOpenApiWmtsTools.element("ows:LowerCorner", description.extent[0] + " " + description.extent[1]) +
73
+ OgcOpenApiWmtsTools.element("ows:UpperCorner", description.extent[2] + " " + description.extent[3]) +
74
+ "</ows:WGS84BoundingBox>" : "";
75
+ return '<?xml version="1.0" encoding="UTF-8"?>' +
76
+ '<Capabilities xmlns="http://www.opengis.net/wmts/1.0" xmlns:ows="http://www.opengis.net/ows/1.1" version="1.0.0">' +
77
+ "<ows:ServiceIdentification><ows:Title>OGC API Tiles</ows:Title><ows:ServiceType>OGC WMTS</ows:ServiceType>" +
78
+ "<ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion></ows:ServiceIdentification>" +
79
+ "<Contents><Layer>" +
80
+ OgcOpenApiWmtsTools.element("ows:Title", description.layer) +
81
+ OgcOpenApiWmtsTools.element("ows:Identifier", description.layer) +
82
+ extent +
83
+ '<Style isDefault="true"><ows:Identifier>default</ows:Identifier></Style>' +
84
+ OgcOpenApiWmtsTools.element("Format", description.format) +
85
+ "<TileMatrixSetLink>" + OgcOpenApiWmtsTools.element("TileMatrixSet", tileSet.id) + limits + "</TileMatrixSetLink>" +
86
+ '<ResourceURL format="' + OgcOpenApiWmtsTools.escape(description.format) + '" resourceType="tile" template="' +
87
+ OgcOpenApiWmtsTools.escape(OgcOpenApiWmtsTools.toWmtsTemplate(description.template)) + '"/>' +
88
+ "</Layer><TileMatrixSet>" +
89
+ OgcOpenApiWmtsTools.element("ows:Identifier", tileSet.id) +
90
+ OgcOpenApiWmtsTools.element("ows:SupportedCRS", OgcOpenApiWmtsTools.toCrsUrn(tileSet.crs)) +
91
+ matrices +
92
+ "</TileMatrixSet></Contents></Capabilities>";
93
+ }
94
+
95
+ /**
96
+ * LuciadRIA's WMTSCapabilities for the tileset. The document is passed as a data URL, so it is not requested.
97
+ */
98
+ public static createCapabilities(description: OgcOpenApiWmtsLayer): Promise<WMTSCapabilities> {
99
+ const url = "data:application/xml;charset=utf-8," + encodeURIComponent(OgcOpenApiWmtsTools.toCapabilitiesXml(description));
100
+ return WMTSCapabilities.fromURL(url, {preferredRequestEncoding: WMTSRequestEncoding.REST});
101
+ }
102
+
103
+ private static element(name: string, value: string | number) {
104
+ return "<" + name + ">" + OgcOpenApiWmtsTools.escape(String(value)) + "</" + name + ">";
105
+ }
106
+
107
+ private static escape(value: string) {
108
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
109
+ }
110
+ }
111
+
112
+ export {
113
+ OgcOpenApiWmtsTools
114
+ }
@@ -1,7 +0,0 @@
1
- import { OgcOpenApiCapabilitiesCollection, OgcOpenApiCapabilitiesObject } from "./OgcOpenApiGetCapabilities";
2
- declare class OgcOpenApiGetCollectionTiles {
3
- private collection;
4
- private capabilities;
5
- constructor(collection: OgcOpenApiCapabilitiesCollection, capabilities: OgcOpenApiCapabilitiesObject);
6
- }
7
- export { OgcOpenApiGetCollectionTiles };
@@ -1,7 +0,0 @@
1
- class OgcOpenApiGetCollectionTiles {
2
- constructor(collection, capabilities) {
3
- this.collection = collection;
4
- this.capabilities = capabilities;
5
- }
6
- }
7
- export { OgcOpenApiGetCollectionTiles };