ogcopenapis 0.0.12 → 1.0.0
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.
- package/LICENSE +20 -20
- package/README.md +310 -72
- package/lib/OgcOpenApiCrsTools.d.ts +10 -9
- package/lib/OgcOpenApiCrsTools.d.ts.map +1 -0
- package/lib/OgcOpenApiCrsTools.js +40 -39
- package/lib/OgcOpenApiCrsTools.js.map +1 -0
- package/lib/OgcOpenApiFeatureStore.d.ts +94 -93
- package/lib/OgcOpenApiFeatureStore.d.ts.map +1 -0
- package/lib/OgcOpenApiFeatureStore.js +200 -244
- package/lib/OgcOpenApiFeatureStore.js.map +1 -0
- package/lib/OgcOpenApiFetchTools.d.ts +37 -30
- package/lib/OgcOpenApiFetchTools.d.ts.map +1 -0
- package/lib/OgcOpenApiFetchTools.js +68 -50
- package/lib/OgcOpenApiFetchTools.js.map +1 -0
- package/lib/OgcOpenApiGetCapabilities.d.ts +209 -112
- package/lib/OgcOpenApiGetCapabilities.d.ts.map +1 -0
- package/lib/OgcOpenApiGetCapabilities.js +424 -277
- package/lib/OgcOpenApiGetCapabilities.js.map +1 -0
- package/lib/OgcOpenApiMapsModel.d.ts +41 -36
- package/lib/OgcOpenApiMapsModel.d.ts.map +1 -0
- package/lib/OgcOpenApiMapsModel.js +88 -75
- package/lib/OgcOpenApiMapsModel.js.map +1 -0
- package/lib/OgcOpenApiMvtTools.d.ts +12 -0
- package/lib/OgcOpenApiMvtTools.d.ts.map +1 -0
- package/lib/OgcOpenApiMvtTools.js +100 -0
- package/lib/OgcOpenApiMvtTools.js.map +1 -0
- package/lib/OgcOpenApiStyleTools.d.ts +51 -0
- package/lib/OgcOpenApiStyleTools.d.ts.map +1 -0
- package/lib/OgcOpenApiStyleTools.js +155 -0
- package/lib/OgcOpenApiStyleTools.js.map +1 -0
- package/lib/OgcOpenApiTileMatrixTools.d.ts +41 -0
- package/lib/OgcOpenApiTileMatrixTools.d.ts.map +1 -0
- package/lib/OgcOpenApiTileMatrixTools.js +88 -0
- package/lib/OgcOpenApiTileMatrixTools.js.map +1 -0
- package/lib/OgcOpenApiTilesModel.d.ts +56 -31
- package/lib/OgcOpenApiTilesModel.d.ts.map +1 -0
- package/lib/OgcOpenApiTilesModel.js +167 -128
- package/lib/OgcOpenApiTilesModel.js.map +1 -0
- package/lib/OgcOpenApiVectorTilesModel.d.ts +39 -0
- package/lib/OgcOpenApiVectorTilesModel.d.ts.map +1 -0
- package/lib/OgcOpenApiVectorTilesModel.js +122 -0
- package/lib/OgcOpenApiVectorTilesModel.js.map +1 -0
- package/lib/OgcOpenApiWmtsTools.d.ts +30 -0
- package/lib/OgcOpenApiWmtsTools.d.ts.map +1 -0
- package/lib/OgcOpenApiWmtsTools.js +89 -0
- package/lib/OgcOpenApiWmtsTools.js.map +1 -0
- package/package.json +56 -27
- package/src/OgcOpenApiCrsTools.ts +40 -0
- package/src/OgcOpenApiFeatureStore.ts +296 -0
- package/src/OgcOpenApiFetchTools.ts +84 -0
- package/src/OgcOpenApiGetCapabilities.ts +641 -0
- package/src/OgcOpenApiMapsModel.ts +128 -0
- package/src/OgcOpenApiMvtTools.ts +104 -0
- package/src/OgcOpenApiStyleTools.ts +187 -0
- package/src/OgcOpenApiTileMatrixTools.ts +111 -0
- package/src/OgcOpenApiTilesModel.ts +214 -0
- package/src/OgcOpenApiVectorTilesModel.ts +141 -0
- package/src/OgcOpenApiWmtsTools.ts +114 -0
- package/lib/OgcOpenApiGetCollectionTiles.d.ts +0 -7
- package/lib/OgcOpenApiGetCollectionTiles.js +0 -7
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
import {type FetchServerOptions, OgcOpenApiFetchTools} from "./OgcOpenApiFetchTools.js";
|
|
2
|
+
const SCALE_DENOMINATOR_TOLERANCE = 1e-8;
|
|
3
|
+
|
|
4
|
+
type RequestHeaders = {[headerName: string]: string;} | null
|
|
5
|
+
export interface LinkType {
|
|
6
|
+
rel: string;
|
|
7
|
+
type: string;
|
|
8
|
+
title: string;
|
|
9
|
+
href: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface TileMatrix {
|
|
13
|
+
id: string,
|
|
14
|
+
scaleDenominator: number;
|
|
15
|
+
cellSize: number;
|
|
16
|
+
cornerOfOrigin: string;
|
|
17
|
+
pointOfOrigin: number[];
|
|
18
|
+
matrixWidth: number;
|
|
19
|
+
matrixHeight: number;
|
|
20
|
+
tileWidth: number;
|
|
21
|
+
tileHeight: number;
|
|
22
|
+
variableMatrixWidths: {
|
|
23
|
+
coalesce: number;
|
|
24
|
+
minTileRow: number;
|
|
25
|
+
maxTileRow: number;
|
|
26
|
+
}[]
|
|
27
|
+
}
|
|
28
|
+
export interface TileSetData {
|
|
29
|
+
id: string;
|
|
30
|
+
title: string;
|
|
31
|
+
uri: string;
|
|
32
|
+
crs: string;
|
|
33
|
+
orderedAxes: string[];
|
|
34
|
+
tileMatrices: TileMatrix[];
|
|
35
|
+
boundingBox: {
|
|
36
|
+
lowerLeft: number[],
|
|
37
|
+
upperRight: number[],
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TileSetLayer {
|
|
42
|
+
id: string;
|
|
43
|
+
title?: string;
|
|
44
|
+
dataType?: string;
|
|
45
|
+
geometryDimension?: number;
|
|
46
|
+
minTileMatrix?: string;
|
|
47
|
+
maxTileMatrix?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// A tileset of a collection, as listed in its tilesets (/collections/{collectionId}/tiles)
|
|
51
|
+
export interface CollectionTileSetInfo {
|
|
52
|
+
// The URI of the tile matrix set, which identifies it whatever id the server gives it
|
|
53
|
+
tileMatrixSetURI?: string;
|
|
54
|
+
tileMatrixSetId?: string;
|
|
55
|
+
dataType?: string;
|
|
56
|
+
crs?: string;
|
|
57
|
+
// The tileset itself, its self link
|
|
58
|
+
href: string;
|
|
59
|
+
// The definition of the tile matrix set, when the server links it
|
|
60
|
+
tilingSchemeHref?: string;
|
|
61
|
+
// The definition of the tile matrix set, when the tileset includes it
|
|
62
|
+
tileMatrixSet?: TileSetData;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface CollectionTileSet {
|
|
66
|
+
dataType?: string;
|
|
67
|
+
links: LinkType[];
|
|
68
|
+
layers: TileSetLayer[];
|
|
69
|
+
tileMatrixSetLimits?: TileMatrixLimits[];
|
|
70
|
+
// Where the data is, when the server tells: in the CRS of boundingBox.crs, or of the tile matrix set
|
|
71
|
+
boundingBox?: {lowerLeft: number[], upperRight: number[], crs?: string};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface TileMatrixLimits {
|
|
75
|
+
tileMatrix: string;
|
|
76
|
+
minTileRow: number;
|
|
77
|
+
maxTileRow: number;
|
|
78
|
+
minTileCol: number;
|
|
79
|
+
maxTileCol: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface TileSetMeta {
|
|
83
|
+
id: string;
|
|
84
|
+
title: string;
|
|
85
|
+
links: LinkType[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface OgcOpenApiCapabilitiesObject {
|
|
89
|
+
name: string;
|
|
90
|
+
collections: OgcOpenApiCapabilitiesCollection[];
|
|
91
|
+
version: string;
|
|
92
|
+
service: string;
|
|
93
|
+
info: any;
|
|
94
|
+
crs?: any[];
|
|
95
|
+
baseUrl: string;
|
|
96
|
+
// The landing page link to the tile matrix sets (rel tiling-schemes)
|
|
97
|
+
tileMatrixSetsUrl?: string;
|
|
98
|
+
// The links of the landing page, to the tilesets and styles of the dataset among others
|
|
99
|
+
links?: LinkType[];
|
|
100
|
+
serverOptions: FetchServerOptions;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface OgcOpenApiCapabilitiesCollection {
|
|
104
|
+
id: string;
|
|
105
|
+
name: string;
|
|
106
|
+
title: string;
|
|
107
|
+
defaultReference: string;
|
|
108
|
+
outputFormats: string[];
|
|
109
|
+
description: string;
|
|
110
|
+
keywords: string[];
|
|
111
|
+
links: OgcOpenApiCapabilitiesCollectionServiceLinkType[];
|
|
112
|
+
extent: any;
|
|
113
|
+
crs?: any;
|
|
114
|
+
storageCrs?: any;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface OgcOpenApiCapabilitiesCollectionServiceLinkType {
|
|
118
|
+
rel: string;
|
|
119
|
+
type: string;
|
|
120
|
+
title: string;
|
|
121
|
+
href: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export enum CollectionLinkType {
|
|
125
|
+
Items = 'items',
|
|
126
|
+
Styles = 'styles',
|
|
127
|
+
Map = 'map',
|
|
128
|
+
Tiles = 'tiles',
|
|
129
|
+
VectorTiles = 'vectorTiles',
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const CollectionLinkTypeAlternatives = {
|
|
133
|
+
[CollectionLinkType.Items]: ["items", "item"],
|
|
134
|
+
[CollectionLinkType.Styles]: ["http://www.opengis.net/def/rel/ogc/1.0/styles", "styles"],
|
|
135
|
+
[CollectionLinkType.Map]: ["http://www.opengis.net/def/rel/ogc/1.0/map", "map"],
|
|
136
|
+
[CollectionLinkType.Tiles]: ["http://www.opengis.net/def/rel/ogc/1.0/tilesets-map", "tilesets-map"],
|
|
137
|
+
[CollectionLinkType.VectorTiles]: ["http://www.opengis.net/def/rel/ogc/1.0/tilesets-vector", "tilesets-vector"],
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// OGC API link relations are given in full or, by older servers, short: tiling-scheme or http://www.opengis.net/def/rel/ogc/1.0/tiling-scheme
|
|
141
|
+
const OGC_REL = "http://www.opengis.net/def/rel/ogc/1.0/";
|
|
142
|
+
const isRel = (link: LinkType, rel: string) => link.rel === OGC_REL + rel || link.rel === rel;
|
|
143
|
+
|
|
144
|
+
const CollectionLinkTypeDefaultFormat = {
|
|
145
|
+
[CollectionLinkType.Map]: "image/png",
|
|
146
|
+
[CollectionLinkType.Tiles]: "image/png",
|
|
147
|
+
[CollectionLinkType.VectorTiles]: "application/vnd.mapbox-vector-tile",
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
interface FetchLinkContentOptions {
|
|
151
|
+
hostUrl: string;
|
|
152
|
+
credentials?: boolean;
|
|
153
|
+
requestHeaders?: {
|
|
154
|
+
[headerName: string]: string;
|
|
155
|
+
} | null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type ProxifyFunction = (url: string, fetchOptions: RequestInit) => {
|
|
159
|
+
url: string;
|
|
160
|
+
fetchOptions: RequestInit;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface OgcOpenApiGetCapabilitiesFromURL {
|
|
164
|
+
filterCollectionsByLinkType?: CollectionLinkType
|
|
165
|
+
credentials?: boolean;
|
|
166
|
+
requestHeaders?: {
|
|
167
|
+
[headerName: string]: string;
|
|
168
|
+
} | null;
|
|
169
|
+
proxify?: ProxifyFunction
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export class OgcOpenApiGetCapabilities {
|
|
173
|
+
|
|
174
|
+
public static fromURL(inputRequest: string, fromUrlOptions?: OgcOpenApiGetCapabilitiesFromURL) {
|
|
175
|
+
const options: OgcOpenApiGetCapabilitiesFromURL = fromUrlOptions ? fromUrlOptions : {};
|
|
176
|
+
return new Promise<OgcOpenApiCapabilitiesObject>((resolve, reject) => {
|
|
177
|
+
const baseUrl = OgcOpenApiFetchTools.cleanUrl(inputRequest);
|
|
178
|
+
const requestUrl = OgcOpenApiFetchTools.cleanUrl(inputRequest);
|
|
179
|
+
const serverOptions = OgcOpenApiFetchTools.createFetchOptions({originalUrl: requestUrl, proxify: options?.proxify});
|
|
180
|
+
|
|
181
|
+
OgcOpenApiFetchTools.fetch(requestUrl, {
|
|
182
|
+
method: 'GET',
|
|
183
|
+
credentials: options?.credentials ? "same-origin" : "omit",
|
|
184
|
+
headers: options? options.requestHeaders: {},
|
|
185
|
+
}, serverOptions).then(
|
|
186
|
+
(response) => {
|
|
187
|
+
if (response.status === 200) {
|
|
188
|
+
response.json().then((jsonObject) => {
|
|
189
|
+
if (!jsonObject.links) {
|
|
190
|
+
reject("Invalid format: (property 'links' is missing)")
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
let linkToData = jsonObject.links.find(
|
|
194
|
+
(link: OgcOpenApiCapabilitiesCollectionServiceLinkType) =>
|
|
195
|
+
link.rel === 'data' && link.type === 'application/json'
|
|
196
|
+
);
|
|
197
|
+
if (!linkToData) {
|
|
198
|
+
linkToData = jsonObject.links.find((link) => link.rel === 'data' );
|
|
199
|
+
}
|
|
200
|
+
const linkToApi = jsonObject.links.find(
|
|
201
|
+
(link: OgcOpenApiCapabilitiesCollectionServiceLinkType) => {
|
|
202
|
+
return (
|
|
203
|
+
(link.rel === 'service-desc' || link.rel === 'service') &&
|
|
204
|
+
link.type.indexOf('openapi+json') > -1
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
const tilingSchemes = jsonObject.links.filter((link) => isRel(link, 'tiling-schemes'));
|
|
209
|
+
const linkToTileMatrixSets = tilingSchemes.find((link) => link.type === 'application/json') || tilingSchemes.find((link) => !link.type);
|
|
210
|
+
// The links of the landing page are relative to it, the links of the collections to /collections
|
|
211
|
+
const resolveLink = (link: OgcOpenApiCapabilitiesCollectionServiceLinkType, documentUrl: string) =>
|
|
212
|
+
({...link, href: OgcOpenApiFetchTools.resolveUrl(link.href, documentUrl, serverOptions)});
|
|
213
|
+
const promiseArray = [];
|
|
214
|
+
if (linkToData) {
|
|
215
|
+
const collectionsLink = resolveLink(linkToData, requestUrl);
|
|
216
|
+
const collectionsPromise = OgcOpenApiGetCapabilities.fetchLinkContentAsJSON(
|
|
217
|
+
collectionsLink,
|
|
218
|
+
{credentials: options.credentials, requestHeaders: options.requestHeaders},
|
|
219
|
+
serverOptions
|
|
220
|
+
);
|
|
221
|
+
promiseArray.push(collectionsPromise);
|
|
222
|
+
if (linkToApi) {
|
|
223
|
+
const apiPromise = OgcOpenApiGetCapabilities.fetchLinkContentAsJSON(
|
|
224
|
+
resolveLink(linkToApi, requestUrl),
|
|
225
|
+
{credentials: options.credentials, requestHeaders: options.requestHeaders},
|
|
226
|
+
serverOptions
|
|
227
|
+
);
|
|
228
|
+
promiseArray.push(apiPromise);
|
|
229
|
+
}
|
|
230
|
+
Promise.all(promiseArray).then((responses: any) => {
|
|
231
|
+
const responseDataLink = responses[0];
|
|
232
|
+
const responseOpenApi =
|
|
233
|
+
responses.length >= 1 ? responses[1] : undefined;
|
|
234
|
+
const crsArray = typeof responseDataLink.crs !== "undefined" ? { crs: responseDataLink.crs } : {};
|
|
235
|
+
const foundCollections = responseDataLink.collections.filter( c =>
|
|
236
|
+
options && options.filterCollectionsByLinkType ?
|
|
237
|
+
OgcOpenApiGetCapabilities.filterCollectionLinks(c.links,options.filterCollectionsByLinkType).length > 0 : true
|
|
238
|
+
)
|
|
239
|
+
.map(
|
|
240
|
+
(collection: any) => {
|
|
241
|
+
const name =
|
|
242
|
+
typeof collection.id !== 'undefined'
|
|
243
|
+
? collection.id
|
|
244
|
+
: collection.name;
|
|
245
|
+
const collectionCrsObject = {} as any;
|
|
246
|
+
if (typeof collection.crs !== "undefined") collectionCrsObject.crs = collection.crs;
|
|
247
|
+
if (typeof collection.storageCrs !== "undefined") collectionCrsObject.storageCrs = collection.storageCrs;
|
|
248
|
+
const layer: OgcOpenApiCapabilitiesCollection = {
|
|
249
|
+
description: collection.description,
|
|
250
|
+
id: name,
|
|
251
|
+
name,
|
|
252
|
+
title: collection.title,
|
|
253
|
+
keywords: collection.keywords
|
|
254
|
+
? collection.keywords
|
|
255
|
+
: [],
|
|
256
|
+
defaultReference: 'CRS:84',
|
|
257
|
+
links: collection.links,
|
|
258
|
+
outputFormats: options.filterCollectionsByLinkType ?
|
|
259
|
+
this.filterCollectionLinks(collection.links, options.filterCollectionsByLinkType).map(link => this.detectTypeFormat(link))
|
|
260
|
+
:
|
|
261
|
+
collection.links.map(link => this.detectTypeFormat(link))
|
|
262
|
+
,
|
|
263
|
+
extent: collection.extent,
|
|
264
|
+
...collectionCrsObject
|
|
265
|
+
};
|
|
266
|
+
return layer;
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
// console.log(featureTypes);
|
|
270
|
+
const o: OgcOpenApiCapabilitiesObject = {
|
|
271
|
+
name: '',
|
|
272
|
+
collections: foundCollections.map(col=>({...col, crs: col.crs ? col.crs : [], links: col.links.map(l => resolveLink(l, collectionsLink.href))})),
|
|
273
|
+
version: responseOpenApi ? responseOpenApi.openapi : '',
|
|
274
|
+
service: '',
|
|
275
|
+
info: responseOpenApi ? responseOpenApi.info : {},
|
|
276
|
+
...crsArray,
|
|
277
|
+
serverOptions: serverOptions,
|
|
278
|
+
links: jsonObject.links.map(l => resolveLink(l, requestUrl)),
|
|
279
|
+
baseUrl,
|
|
280
|
+
tileMatrixSetsUrl: linkToTileMatrixSets ? resolveLink(linkToTileMatrixSets, requestUrl).href : undefined
|
|
281
|
+
};
|
|
282
|
+
resolve(o);
|
|
283
|
+
});
|
|
284
|
+
} else {
|
|
285
|
+
reject("Invalid format: (property 'links' contains no links with rel=data");
|
|
286
|
+
}
|
|
287
|
+
}, ()=>{
|
|
288
|
+
reject("Not JSON")
|
|
289
|
+
});
|
|
290
|
+
} else {
|
|
291
|
+
reject(response.status);
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
(err) => {
|
|
295
|
+
reject(err);
|
|
296
|
+
}
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private static fetchLinkContentAsJSON(link: OgcOpenApiCapabilitiesCollectionServiceLinkType, options: {credentials: boolean, requestHeaders: RequestHeaders}, serverOptions?: FetchServerOptions) {
|
|
302
|
+
return new Promise((resolve) => {
|
|
303
|
+
OgcOpenApiFetchTools.fetch(link.href, {method: "GET", credentials: options.credentials ? "same-origin":"omit", headers: options.requestHeaders}, serverOptions).then(
|
|
304
|
+
(result) => {
|
|
305
|
+
if (result.status === 200) {
|
|
306
|
+
result.json().then((json) => resolve(json));
|
|
307
|
+
} else {
|
|
308
|
+
resolve(undefined);
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
() => {
|
|
312
|
+
resolve(undefined);
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
static filterCollectionLinks(
|
|
319
|
+
links: OgcOpenApiCapabilitiesCollectionServiceLinkType[],
|
|
320
|
+
linkType: CollectionLinkType
|
|
321
|
+
) {
|
|
322
|
+
switch (linkType) {
|
|
323
|
+
case CollectionLinkType.Items:
|
|
324
|
+
case CollectionLinkType.Map:
|
|
325
|
+
case CollectionLinkType.Styles:
|
|
326
|
+
case CollectionLinkType.Tiles:
|
|
327
|
+
case CollectionLinkType.VectorTiles:
|
|
328
|
+
return links.filter((link: any) => CollectionLinkTypeAlternatives[linkType].some(e=>e===link.rel));
|
|
329
|
+
default:
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
public static getDataLink(
|
|
335
|
+
currentLayer: OgcOpenApiCapabilitiesCollection,
|
|
336
|
+
preferedFormat: string
|
|
337
|
+
) {
|
|
338
|
+
const dataLink = OgcOpenApiGetCapabilities.filterCollectionLinks(
|
|
339
|
+
currentLayer.links,
|
|
340
|
+
CollectionLinkType.Items
|
|
341
|
+
).find((link) => link.type === preferedFormat);
|
|
342
|
+
return dataLink ? dataLink.href : '';
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private static detectTypeFormat(link: OgcOpenApiCapabilitiesCollectionServiceLinkType) {
|
|
346
|
+
if (link.type) return link.type;
|
|
347
|
+
for (const key in CollectionLinkTypeAlternatives) {
|
|
348
|
+
if (CollectionLinkTypeAlternatives.hasOwnProperty(key) && CollectionLinkTypeAlternatives[key].some(e=>e===link.rel)) {
|
|
349
|
+
if (CollectionLinkTypeDefaultFormat[key]) return CollectionLinkTypeDefaultFormat[key];
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return "application/json"
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* The tile matrix sets of the server, with their definitions. A definition that cannot be read is left out, so one
|
|
358
|
+
* broken definition does not hide the others. Rejects only when none can be read.
|
|
359
|
+
*/
|
|
360
|
+
public static async fetchTileSetsInFull(capabilities: OgcOpenApiCapabilitiesObject | null, options?:{credentials?:boolean, requestHeaders?: RequestHeaders}) {
|
|
361
|
+
const tileSetMetaArray = await this.fetchTileSets(capabilities, options);
|
|
362
|
+
const settled = await Promise.all(tileSetMetaArray.map(tml => this.fetchTileset(capabilities, tml, options).then(
|
|
363
|
+
tileSet => ({tileSet, error: undefined}),
|
|
364
|
+
error => ({tileSet: undefined as TileSetData, error})
|
|
365
|
+
)));
|
|
366
|
+
const tileSets = settled.filter(s => s.tileSet).map(s => s.tileSet);
|
|
367
|
+
const failed = settled.find(s => !s.tileSet);
|
|
368
|
+
if (tileSets.length === 0 && failed) throw failed.error;
|
|
369
|
+
return tileSets;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
public static getQuadTreeCompatibleLevelOffset(tileMatrixSet: TileSetData) {
|
|
373
|
+
const levels = tileMatrixSet.tileMatrices;
|
|
374
|
+
const reference = levels[levels.length - 1];
|
|
375
|
+
|
|
376
|
+
for (let level = levels.length - 2; level >= 0; level--) {
|
|
377
|
+
const current = levels[level];
|
|
378
|
+
|
|
379
|
+
// tslint:disable-next-line:no-bitwise
|
|
380
|
+
const multiplier = 1 << (levels.length - level - 1);
|
|
381
|
+
|
|
382
|
+
const errorMargin = current.scaleDenominator * SCALE_DENOMINATOR_TOLERANCE;
|
|
383
|
+
if (Math.abs(current.scaleDenominator - reference.scaleDenominator * multiplier) > errorMargin) {
|
|
384
|
+
return level + 1;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (current.pointOfOrigin[0] !== reference.pointOfOrigin[0]) {
|
|
388
|
+
return level + 1;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (current.tileWidth !== reference.tileWidth || current.tileHeight !== reference.tileHeight) {
|
|
392
|
+
return level + 1;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (typeof current.variableMatrixWidths!=="undefined") {
|
|
396
|
+
return level + 1;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (current.matrixWidth !== (reference.matrixWidth / multiplier) || current.matrixHeight !== (reference.matrixHeight / multiplier)) {
|
|
400
|
+
return level + 1;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return 0;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private static async fetchTileSets(capabilities: OgcOpenApiCapabilitiesObject | null, options?:{credentials?:boolean, requestHeaders?: RequestHeaders}): Promise<TileSetMeta[]> {
|
|
407
|
+
if (capabilities === null) throw {error: true, message: "No capabilities"};
|
|
408
|
+
const url = capabilities.tileMatrixSetsUrl ? capabilities.tileMatrixSetsUrl : capabilities.baseUrl + "tileMatrixSets";
|
|
409
|
+
const data = await OgcOpenApiGetCapabilities.fetchJson(capabilities, url, options);
|
|
410
|
+
if (!data.tileMatrixSets) throw {error: true, message: "tileMatrixSets not defined"};
|
|
411
|
+
const resolve = (href: string) => OgcOpenApiFetchTools.resolveUrl(href, url, capabilities.serverOptions);
|
|
412
|
+
return data.tileMatrixSets.map(tml => ({
|
|
413
|
+
...tml,
|
|
414
|
+
href: resolve(tml.href),
|
|
415
|
+
links: (tml.links ? tml.links : []).map(l => ({...l, href: resolve(l.href)}))
|
|
416
|
+
}));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// GETs a JSON document. Servers answer JSON for the Accept header, or for the f parameter that their links carry.
|
|
420
|
+
private static async fetchJson(capabilities: OgcOpenApiCapabilitiesObject, href: string, options?: {credentials?: boolean, requestHeaders?: RequestHeaders}) {
|
|
421
|
+
let result: Response;
|
|
422
|
+
try {
|
|
423
|
+
result = await OgcOpenApiFetchTools.fetch(href, {
|
|
424
|
+
method: "GET",
|
|
425
|
+
credentials: options?.credentials ? "same-origin" : "omit",
|
|
426
|
+
headers: {Accept: "application/json", ...(options?.requestHeaders ? options.requestHeaders : {})}
|
|
427
|
+
}, capabilities.serverOptions);
|
|
428
|
+
} catch (err) {
|
|
429
|
+
throw {error: true, message: err.message};
|
|
430
|
+
}
|
|
431
|
+
if (result.status !== 200) throw {error: true, message: "Invalid response: " + result.status};
|
|
432
|
+
try {
|
|
433
|
+
return await result.json();
|
|
434
|
+
} catch (err) {
|
|
435
|
+
throw {error: true, message: "Not JSON data"};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// The link that identifies the tile matrix set of a tileset, relative to the document that has it
|
|
440
|
+
private static getTilingSchemeHref(capabilities: OgcOpenApiCapabilitiesObject, links: LinkType[], documentUrl: string) {
|
|
441
|
+
const tilingScheme = links.find(l => isRel(l, "tiling-scheme"));
|
|
442
|
+
return tilingScheme ? OgcOpenApiFetchTools.resolveUrl(tilingScheme.href, documentUrl, capabilities.serverOptions) : undefined;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
public static getTilesLink (
|
|
446
|
+
capabilities: OgcOpenApiCapabilitiesObject | null,
|
|
447
|
+
currentCollection: OgcOpenApiCapabilitiesCollection,
|
|
448
|
+
tileMatrixId: string,
|
|
449
|
+
options?: {credentials?: boolean, requestHeaders?: RequestHeaders},
|
|
450
|
+
linkType: CollectionLinkType = CollectionLinkType.Tiles
|
|
451
|
+
) {
|
|
452
|
+
return this.fetchCollectionTileSet(capabilities, currentCollection, tileMatrixId, options, linkType).then(tileSet => tileSet.links);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// The tilesets link of a collection, the JSON one when the server types its links
|
|
456
|
+
private static getTileSetsHref(collection: OgcOpenApiCapabilitiesCollection, linkType: CollectionLinkType) {
|
|
457
|
+
const dataLinks = OgcOpenApiGetCapabilities.filterCollectionLinks(collection.links, linkType);
|
|
458
|
+
const link = dataLinks.find((link) => link.type === "application/json");
|
|
459
|
+
return link ? link.href : dataLinks.length > 0 ? dataLinks[0].href : '';
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// The tilesets link of the landing page: the tilesets of the dataset, with all its collections in each tile
|
|
463
|
+
private static getDatasetTileSetsHref(capabilities: OgcOpenApiCapabilitiesObject, linkType: CollectionLinkType) {
|
|
464
|
+
const dataLinks = OgcOpenApiGetCapabilities.filterCollectionLinks(capabilities.links ? capabilities.links : [], linkType);
|
|
465
|
+
const link = dataLinks.find((link) => link.type === "application/json") || dataLinks.find((link) => !link.type) || dataLinks[0];
|
|
466
|
+
return link ? link.href : '';
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Whether the server tiles the dataset as a whole, all its collections in each tile, for linkType Tiles or VectorTiles
|
|
471
|
+
*/
|
|
472
|
+
public static hasDatasetTileSets(capabilities: OgcOpenApiCapabilitiesObject | null, linkType: CollectionLinkType) {
|
|
473
|
+
return capabilities !== null && OgcOpenApiGetCapabilities.getDatasetTileSetsHref(capabilities, linkType) !== '';
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Lists the tilesets of a collection: the tile matrix sets the collection is tiled in, which can be fewer than the
|
|
478
|
+
* tile matrix sets of the server.
|
|
479
|
+
*/
|
|
480
|
+
public static async fetchCollectionTileSets(
|
|
481
|
+
capabilities: OgcOpenApiCapabilitiesObject | null,
|
|
482
|
+
collection: OgcOpenApiCapabilitiesCollection,
|
|
483
|
+
linkType: CollectionLinkType = CollectionLinkType.Tiles,
|
|
484
|
+
options?: {credentials?: boolean, requestHeaders?: RequestHeaders}
|
|
485
|
+
): Promise<CollectionTileSetInfo[]> {
|
|
486
|
+
if (capabilities === null) throw {error: true, message: "No capabilities"};
|
|
487
|
+
const href = OgcOpenApiGetCapabilities.getTileSetsHref(collection, linkType);
|
|
488
|
+
if (href === '') throw {error: true, message: "Collection " + collection.id + " has no " + linkType + " link"};
|
|
489
|
+
return OgcOpenApiGetCapabilities.fetchTileSetList(capabilities, href, options);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Lists the tilesets of the dataset (/tiles of the landing page): each tile has the data of all its collections.
|
|
494
|
+
* Some servers tile only the dataset (PDOK).
|
|
495
|
+
*/
|
|
496
|
+
public static async fetchDatasetTileSets(
|
|
497
|
+
capabilities: OgcOpenApiCapabilitiesObject | null,
|
|
498
|
+
linkType: CollectionLinkType = CollectionLinkType.Tiles,
|
|
499
|
+
options?: {credentials?: boolean, requestHeaders?: RequestHeaders}
|
|
500
|
+
): Promise<CollectionTileSetInfo[]> {
|
|
501
|
+
if (capabilities === null) throw {error: true, message: "No capabilities"};
|
|
502
|
+
const href = OgcOpenApiGetCapabilities.getDatasetTileSetsHref(capabilities, linkType);
|
|
503
|
+
if (href === '') throw {error: true, message: "The dataset has no " + linkType + " link"};
|
|
504
|
+
return OgcOpenApiGetCapabilities.fetchTileSetList(capabilities, href, options);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
private static async fetchTileSetList(capabilities: OgcOpenApiCapabilitiesObject, href: string, options?: {credentials?: boolean, requestHeaders?: RequestHeaders}) {
|
|
508
|
+
const data = await OgcOpenApiGetCapabilities.fetchJson(capabilities, href, options);
|
|
509
|
+
if (!Array.isArray(data.tilesets)) throw {error: true, message: "Response has no tilesets"};
|
|
510
|
+
const tileSets: CollectionTileSetInfo[] = [];
|
|
511
|
+
for (const tileSet of data.tilesets) {
|
|
512
|
+
const links: LinkType[] = tileSet.links ? tileSet.links : [];
|
|
513
|
+
const selfLinks = links.filter(l => l.rel === "self");
|
|
514
|
+
const self = selfLinks.find(l => l.type === "application/json") || selfLinks[0];
|
|
515
|
+
if (!self) continue;
|
|
516
|
+
tileSets.push({
|
|
517
|
+
tileMatrixSetURI: tileSet.tileMatrixSetURI,
|
|
518
|
+
tileMatrixSetId: tileSet.tileMatrixSetId,
|
|
519
|
+
dataType: tileSet.dataType,
|
|
520
|
+
crs: typeof tileSet.crs === "string" ? tileSet.crs : undefined,
|
|
521
|
+
href: OgcOpenApiFetchTools.resolveUrl(self.href, href, capabilities.serverOptions),
|
|
522
|
+
tilingSchemeHref: OgcOpenApiGetCapabilities.getTilingSchemeHref(capabilities, links, href),
|
|
523
|
+
tileMatrixSet: OgcOpenApiGetCapabilities.getInlineTileMatrixSet(tileSet),
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
return tileSets;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* The tile matrix set of a tileset among the tile matrix sets of the server (fetchTileSetsInFull): by URI, which
|
|
531
|
+
* servers give with their own ids (CubeWerx: WebMercatorQuad is smerc), then by id.
|
|
532
|
+
*/
|
|
533
|
+
public static matchTileMatrixSet(tileSet: CollectionTileSetInfo, tileMatrixSets: TileSetData[]): TileSetData | undefined {
|
|
534
|
+
const lastSegment = (url: string) => url.split("?")[0].replace(/\/$/, "").split("/").pop();
|
|
535
|
+
const byUri = tileSet.tileMatrixSetURI ? tileMatrixSets.find(t => OgcOpenApiGetCapabilities.sameUri(t.uri, tileSet.tileMatrixSetURI)) : undefined;
|
|
536
|
+
if (byUri) return byUri;
|
|
537
|
+
const ids = [tileSet.tileMatrixSetId,
|
|
538
|
+
tileSet.tileMatrixSetURI ? lastSegment(tileSet.tileMatrixSetURI) : undefined,
|
|
539
|
+
tileSet.tilingSchemeHref ? lastSegment(tileSet.tilingSchemeHref) : undefined].filter(id => !!id);
|
|
540
|
+
return tileMatrixSets.find(t => ids.indexOf(t.id) > -1);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Both forms name the same resource: http or https, the case of the scheme and host, a trailing slash
|
|
544
|
+
private static sameUri(a: string | undefined, b: string | undefined) {
|
|
545
|
+
const normalize = (uri: string) => uri.trim().replace(/\/$/, "")
|
|
546
|
+
.replace(/^https?:\/\/[^/]*/i, prefix => prefix.toLowerCase().replace(/^https:/, "http:"));
|
|
547
|
+
return !!a && !!b && normalize(a) === normalize(b);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* The tile matrix set of a tileset: matched among the tile matrix sets of the server, or otherwise fetched from the
|
|
552
|
+
* tiling-scheme link of the tileset. A tileset in a list of tilesets is a summary that need not identify its tile
|
|
553
|
+
* matrix set, then the tileset itself is read (CubeWerx).
|
|
554
|
+
*/
|
|
555
|
+
public static async fetchTileMatrixSetOfTileSet(
|
|
556
|
+
capabilities: OgcOpenApiCapabilitiesObject | null,
|
|
557
|
+
tileSet: CollectionTileSetInfo,
|
|
558
|
+
tileMatrixSets: TileSetData[],
|
|
559
|
+
options?: {credentials?: boolean, requestHeaders?: RequestHeaders}
|
|
560
|
+
): Promise<TileSetData> {
|
|
561
|
+
if (capabilities === null) throw {error: true, message: "No capabilities"};
|
|
562
|
+
if (tileSet.tileMatrixSet) return tileSet.tileMatrixSet;
|
|
563
|
+
let identified = tileSet;
|
|
564
|
+
if (!tileSet.tileMatrixSetURI && !tileSet.tileMatrixSetId && !tileSet.tilingSchemeHref) {
|
|
565
|
+
identified = {...tileSet, ...await OgcOpenApiGetCapabilities.fetchTileSetIdentity(capabilities, tileSet.href, options)};
|
|
566
|
+
if (identified.tileMatrixSet) return identified.tileMatrixSet;
|
|
567
|
+
}
|
|
568
|
+
const match = OgcOpenApiGetCapabilities.matchTileMatrixSet(identified, tileMatrixSets);
|
|
569
|
+
if (match) return match;
|
|
570
|
+
if (!identified.tilingSchemeHref) {
|
|
571
|
+
throw {error: true, message: "No tile matrix set for " + (identified.tileMatrixSetURI || identified.href)};
|
|
572
|
+
}
|
|
573
|
+
return OgcOpenApiGetCapabilities.fetchTileset(capabilities, {id: identified.tileMatrixSetId, title: "", links: [{rel: "self", type: "application/json", title: "", href: identified.tilingSchemeHref}]}, options);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// What identifies the tile matrix set in a tileset: its URI, its id or its tiling-scheme link
|
|
577
|
+
private static async fetchTileSetIdentity(capabilities: OgcOpenApiCapabilitiesObject, href: string, options?: {credentials?: boolean, requestHeaders?: RequestHeaders}) {
|
|
578
|
+
const data = await OgcOpenApiGetCapabilities.fetchJson(capabilities, href, options);
|
|
579
|
+
return {
|
|
580
|
+
tileMatrixSetURI: data.tileMatrixSetURI as string | undefined,
|
|
581
|
+
tileMatrixSetId: data.tileMatrixSetId as string | undefined,
|
|
582
|
+
tilingSchemeHref: OgcOpenApiGetCapabilities.getTilingSchemeHref(capabilities, data.links ? data.links : [], href),
|
|
583
|
+
tileMatrixSet: OgcOpenApiGetCapabilities.getInlineTileMatrixSet(data),
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// A tileset can include the definition of its tile matrix set instead of linking it
|
|
588
|
+
private static getInlineTileMatrixSet(tileSet: any): TileSetData | undefined {
|
|
589
|
+
const tileMatrixSet = tileSet ? tileSet.tileMatrixSet : undefined;
|
|
590
|
+
return tileMatrixSet && typeof tileMatrixSet.id !== "undefined" && Array.isArray(tileMatrixSet.tileMatrices) && tileMatrixSet.crs ?
|
|
591
|
+
tileMatrixSet : undefined;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Fetches the tileset of a collection for one tile matrix set: the tile URL templates (rel item) and, for vector
|
|
596
|
+
* tiles, the layers contained in each tile.
|
|
597
|
+
*/
|
|
598
|
+
public static async fetchCollectionTileSet (
|
|
599
|
+
capabilities: OgcOpenApiCapabilitiesObject | null,
|
|
600
|
+
// null for a tileset of the dataset
|
|
601
|
+
currentCollection: OgcOpenApiCapabilitiesCollection | null,
|
|
602
|
+
// The id of the tile matrix set, or a tileset from fetchCollectionTileSets or fetchDatasetTileSets
|
|
603
|
+
tileMatrix: string | CollectionTileSetInfo,
|
|
604
|
+
options?: {credentials?: boolean, requestHeaders?: RequestHeaders},
|
|
605
|
+
linkType: CollectionLinkType = CollectionLinkType.Tiles
|
|
606
|
+
): Promise<CollectionTileSet> {
|
|
607
|
+
if (capabilities === null) throw {error: true, message: "No capabilities"};
|
|
608
|
+
const tileSetsHref = currentCollection ?
|
|
609
|
+
OgcOpenApiGetCapabilities.getTileSetsHref(currentCollection, linkType) :
|
|
610
|
+
OgcOpenApiGetCapabilities.getDatasetTileSetsHref(capabilities, linkType);
|
|
611
|
+
const tileInfoUrl = typeof tileMatrix === "string" ? OgcOpenApiFetchTools.cleanUrl(tileSetsHref) + tileMatrix : tileMatrix.href;
|
|
612
|
+
// pygeoapi answers 500 without f=json or an Accept header
|
|
613
|
+
const data = await OgcOpenApiGetCapabilities.fetchJson(capabilities, tileInfoUrl, options);
|
|
614
|
+
const links = data.links ? data.links.filter(l => l.rel === "item" || l.rel === "items").map(a => ({...a, href: OgcOpenApiFetchTools.resolveUrl(a.href, tileInfoUrl, capabilities.serverOptions)})) : [];
|
|
615
|
+
if (links.length === 0) throw {error: true, message: "Invalid"};
|
|
616
|
+
return {
|
|
617
|
+
dataType: data.dataType,
|
|
618
|
+
links,
|
|
619
|
+
layers: data.layers ? data.layers : [],
|
|
620
|
+
tileMatrixSetLimits: data.tileMatrixSetLimits,
|
|
621
|
+
boundingBox: data.boundingBox && Array.isArray(data.boundingBox.lowerLeft) && Array.isArray(data.boundingBox.upperRight) ? data.boundingBox : undefined,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Fetches the definition of a tile matrix set, by its JSON link as the server gives it: with the query that tells
|
|
627
|
+
* the server to answer JSON (f=json), if it has one.
|
|
628
|
+
*/
|
|
629
|
+
public static async fetchTileset(
|
|
630
|
+
capabilities: OgcOpenApiCapabilitiesObject | null,
|
|
631
|
+
tileset: TileSetMeta,
|
|
632
|
+
options?: {credentials?: boolean, requestHeaders?: RequestHeaders}
|
|
633
|
+
): Promise<TileSetData> {
|
|
634
|
+
if (capabilities === null) throw {error: true, message: "No capabilities"};
|
|
635
|
+
const jsonLink = tileset.links.find(l => l.type === "application/json") || tileset.links.find(l => !l.type) || tileset.links[0];
|
|
636
|
+
if (!jsonLink) throw {error: true, message: "Tile matrix set " + tileset.id + " has no link"};
|
|
637
|
+
const data: TileSetData = await OgcOpenApiGetCapabilities.fetchJson(capabilities, jsonLink.href, options);
|
|
638
|
+
if (typeof data.id === "undefined" || !data.tileMatrices || !data.crs) throw {error: true, message: "TileSet has invalid data"};
|
|
639
|
+
return data;
|
|
640
|
+
}
|
|
641
|
+
}
|