ogcopenapis 1.0.1 → 1.0.2
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/CHANGELOG.md +12 -1
- package/README.md +152 -81
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,16 @@ Each pull request adds its changes under Unreleased. A release renames Unrelease
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [1.0.2] - 2026-10-27
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- The README examples take every value from the capabilities: the formats of the links, the CRS of a map (the
|
|
15
|
+
reference of the map when the collection offers it, otherwise CRS84), the tileset in the reference of the map, the
|
|
16
|
+
styles for its tile matrix set, and the extents. The examples are functions that run as written; two of them failed on
|
|
17
|
+
servers whose links have no type or whose tilesets list no URI.
|
|
18
|
+
- Demo: the CRS of a map and the encoding of features come from the capabilities, and a tileset bounding box without a
|
|
19
|
+
CRS is not taken as CRS84.
|
|
20
|
+
|
|
11
21
|
## [1.0.1] - 2026-10-27
|
|
12
22
|
|
|
13
23
|
### Fixed
|
|
@@ -58,6 +68,7 @@ Each pull request adds its changes under Unreleased. A release renames Unrelease
|
|
|
58
68
|
|
|
59
69
|
See the [git history](https://github.com/felipecarrillo100/ogcopenapis/commits/main).
|
|
60
70
|
|
|
61
|
-
[Unreleased]: https://github.com/felipecarrillo100/ogcopenapis/compare/v1.0.
|
|
71
|
+
[Unreleased]: https://github.com/felipecarrillo100/ogcopenapis/compare/v1.0.2...HEAD
|
|
72
|
+
[1.0.2]: https://github.com/felipecarrillo100/ogcopenapis/compare/v1.0.1...v1.0.2
|
|
62
73
|
[1.0.1]: https://github.com/felipecarrillo100/ogcopenapis/compare/v1.0.0...v1.0.1
|
|
63
74
|
[1.0.0]: https://github.com/felipecarrillo100/ogcopenapis/releases/tag/v1.0.0
|
package/README.md
CHANGED
|
@@ -100,12 +100,15 @@ The package is native ES modules. Import each component by its name, `ogcopenapi
|
|
|
100
100
|
instance. The paths of version 0.0.12, `ogcopenapis/lib/OgcOpenApiFeatureStore`, still work.
|
|
101
101
|
|
|
102
102
|
### Capabilities
|
|
103
|
-
`fromURL` reads the landing page of a server and its collections, optionally only those with links of one type
|
|
103
|
+
`fromURL` reads the landing page of a server and its collections, optionally only those with links of one type. Everything
|
|
104
|
+
the examples below need comes from these capabilities: links and their formats, extents, the CRSs a collection offers,
|
|
105
|
+
tilesets, tile matrix sets and styles. Nothing is configured by hand.
|
|
104
106
|
```typescript
|
|
105
107
|
import {CollectionLinkType, OgcOpenApiGetCapabilities} from "ogcopenapis/OgcOpenApiGetCapabilities";
|
|
106
108
|
|
|
107
|
-
const capabilities = await OgcOpenApiGetCapabilities.fromURL(
|
|
108
|
-
|
|
109
|
+
const capabilities = await OgcOpenApiGetCapabilities.fromURL(serverUrl, {filterCollectionsByLinkType: CollectionLinkType.Items});
|
|
110
|
+
// The collections, as the server lists them: id, title, extent, crs and links
|
|
111
|
+
const collection = capabilities.collections.find(c => c.id === collectionId);
|
|
109
112
|
```
|
|
110
113
|
Links that are relative are resolved against the document that has them.
|
|
111
114
|
|
|
@@ -116,6 +119,19 @@ const proxify = (url: string, fetchOptions: RequestInit) => ({url: "https://prox
|
|
|
116
119
|
const capabilities = await OgcOpenApiGetCapabilities.fromURL(serverUrl, {proxify});
|
|
117
120
|
```
|
|
118
121
|
|
|
122
|
+
The examples below share these imports and constants:
|
|
123
|
+
```typescript
|
|
124
|
+
import type {Map} from "@luciad/ria/view/Map.js";
|
|
125
|
+
import {getReference} from "@luciad/ria/reference/ReferenceProvider.js";
|
|
126
|
+
import {
|
|
127
|
+
CollectionLinkType, OgcOpenApiCapabilitiesCollection, OgcOpenApiCapabilitiesObject, OgcOpenApiGetCapabilities,
|
|
128
|
+
CollectionTileSet, CollectionTileSetInfo, TileSetData
|
|
129
|
+
} from "ogcopenapis/OgcOpenApiGetCapabilities";
|
|
130
|
+
import {OgcOpenApiCrsTools} from "ogcopenapis/OgcOpenApiCrsTools";
|
|
131
|
+
|
|
132
|
+
const CRS84 = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
|
|
133
|
+
```
|
|
134
|
+
|
|
119
135
|
### Features
|
|
120
136
|
OgcOpenApiFeatureStore implements a LuciadRIA store, to use with FeatureModel and FeatureLayer:
|
|
121
137
|
```typescript
|
|
@@ -123,20 +139,27 @@ import {FeatureModel} from "@luciad/ria/model/feature/FeatureModel.js";
|
|
|
123
139
|
import {FeatureLayer} from "@luciad/ria/view/feature/FeatureLayer.js";
|
|
124
140
|
import {LoadSpatially} from "@luciad/ria/view/feature/loadingstrategy/LoadSpatially.js";
|
|
125
141
|
import {GeoJsonCodec} from "@luciad/ria/model/codec/GeoJsonCodec.js";
|
|
126
|
-
import {getReference} from "@luciad/ria/reference/ReferenceProvider.js";
|
|
127
142
|
import {OgcOpenApiFeatureStore} from "ogcopenapis/OgcOpenApiFeatureStore";
|
|
128
143
|
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
function createFeatureLayer(collection: OgcOpenApiCapabilitiesCollection) {
|
|
145
|
+
const items = OgcOpenApiGetCapabilities.filterCollectionLinks(collection.links, CollectionLinkType.Items);
|
|
146
|
+
// GeoJSON, the encoding that GeoJsonCodec reads
|
|
147
|
+
const link = items.find(l => l.type === "application/geo+json") || items.find(l => l.type === "application/json");
|
|
148
|
+
if (!link) throw new Error("Collection " + collection.id + " has no GeoJSON items");
|
|
149
|
+
// CRS84: OGC API Features requires every server to support it
|
|
150
|
+
const crs = CRS84;
|
|
151
|
+
const store = new OgcOpenApiFeatureStore({
|
|
152
|
+
dataUrl: link.href,
|
|
153
|
+
outputFormat: link.type,
|
|
154
|
+
// The ids of the server: get(id) requests items/{id} with them
|
|
155
|
+
codec: new GeoJsonCodec({generateIDs: false}),
|
|
156
|
+
crs,
|
|
157
|
+
extent: collection.extent,
|
|
158
|
+
useCrs84Bounds: true,
|
|
159
|
+
});
|
|
160
|
+
const model = new FeatureModel(store, {reference: getReference(OgcOpenApiCrsTools.getReferenceName(crs))});
|
|
161
|
+
return new FeatureLayer(model, {label: collection.title || collection.id, loadingStrategy: new LoadSpatially()});
|
|
162
|
+
}
|
|
140
163
|
```
|
|
141
164
|
The store requests the features in view, a query does not follow the `next` links of a response yet (no paging).
|
|
142
165
|
|
|
@@ -144,99 +167,147 @@ Keep the ids of the server: `generateIDs: false`. Each feature of an OGC API Fea
|
|
|
144
167
|
the store requests `items/{id}` with it, and LuciadRIA updates only the features that changed when the ids stay the same
|
|
145
168
|
between queries. `generateIDs: true` replaces them with ids of its own on every query.
|
|
146
169
|
|
|
147
|
-
### Maps
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
OgcOpenApiMapsModel requests the map as tiles. The map link carries the `f` value the server accepts, such as `f=png`:
|
|
170
|
+
### Maps
|
|
171
|
+
OgcOpenApiMapsModel extends WMSTileSetModel, use it with a RasterTileSetLayer. It requests the map as tiles, in the
|
|
172
|
+
reference of the map when the collection offers it, otherwise in CRS84, which every server supports, and LuciadRIA
|
|
173
|
+
reprojects it:
|
|
152
174
|
```typescript
|
|
153
175
|
import {RasterTileSetLayer} from "@luciad/ria/view/tileset/RasterTileSetLayer.js";
|
|
154
176
|
import {OgcOpenApiMapsModel} from "ogcopenapis/OgcOpenApiMapsModel";
|
|
155
177
|
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
178
|
+
function createMapLayer(map: Map, collection: OgcOpenApiCapabilitiesCollection) {
|
|
179
|
+
const mapLinks = OgcOpenApiGetCapabilities.filterCollectionLinks(collection.links, CollectionLinkType.Map);
|
|
180
|
+
// PNG, with transparency, when the server types its links. The link carries the f value the server accepts
|
|
181
|
+
const link = mapLinks.find(l => l.type === "image/png") || mapLinks[0];
|
|
182
|
+
if (!link) throw new Error("Collection " + collection.id + " has no map");
|
|
183
|
+
const crs = (collection.crs || []).find(c => OgcOpenApiCrsTools.getReferenceName(c) === map.reference.identifier) || CRS84;
|
|
184
|
+
const model = new OgcOpenApiMapsModel({crs, baseURL: link.href, collection: collection.id});
|
|
185
|
+
return new RasterTileSetLayer(model, {label: collection.title || collection.id});
|
|
186
|
+
}
|
|
163
187
|
```
|
|
164
188
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
189
|
+
### Tilesets
|
|
190
|
+
Map tiles and vector tiles start from the tilesets: of a collection, or of the dataset (`collection` null), all its
|
|
191
|
+
collections in each tile. Each tileset is in one tile matrix set. Servers give tile matrix sets their own ids (CubeWerx
|
|
192
|
+
calls WebMercatorQuad `smerc`), `fetchTileMatrixSetOfTileSet` matches them by URI.
|
|
193
|
+
```typescript
|
|
194
|
+
import {OgcOpenApiTileMatrixTools} from "ogcopenapis/OgcOpenApiTileMatrixTools";
|
|
195
|
+
|
|
196
|
+
interface UsableTileSet {
|
|
197
|
+
tileSet: CollectionTileSetInfo;
|
|
198
|
+
tileMatrix: TileSetData;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// The tilesets that LuciadRIA can use, each with its tile matrix set. Left out: tile matrix sets in a reference that is
|
|
202
|
+
// not registered (see References below), and for vector tiles those that LuciadRIA cannot address as a quad-tree
|
|
203
|
+
async function fetchUsableTileSets(capabilities: OgcOpenApiCapabilitiesObject, collection: OgcOpenApiCapabilitiesCollection | null, linkType: CollectionLinkType) {
|
|
204
|
+
// fetchTileSetsInFull leaves out definitions that cannot be read, it rejects only when none can
|
|
205
|
+
const tileMatrixSets = await OgcOpenApiGetCapabilities.fetchTileSetsInFull(capabilities);
|
|
206
|
+
const tileSets = collection ?
|
|
207
|
+
await OgcOpenApiGetCapabilities.fetchCollectionTileSets(capabilities, collection, linkType) :
|
|
208
|
+
await OgcOpenApiGetCapabilities.fetchDatasetTileSets(capabilities, linkType);
|
|
209
|
+
const usable = await Promise.all(tileSets.map(async (tileSet): Promise<UsableTileSet | null> => {
|
|
210
|
+
try {
|
|
211
|
+
const tileMatrix = await OgcOpenApiGetCapabilities.fetchTileMatrixSetOfTileSet(capabilities, tileSet, tileMatrixSets);
|
|
212
|
+
const grid = OgcOpenApiTileMatrixTools.createTileGrid(tileMatrix);
|
|
213
|
+
return linkType === CollectionLinkType.VectorTiles && grid.levelCount <= 1 ? null : {tileSet, tileMatrix};
|
|
214
|
+
} catch (e) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
}));
|
|
218
|
+
return usable.filter(t => t !== null);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// The tileset in the reference of the map, which needs no reprojection, otherwise the first one
|
|
222
|
+
function pickTileSet(map: Map, tileSets: UsableTileSet[]) {
|
|
223
|
+
if (tileSets.length === 0) throw new Error("No tileset in a tile matrix set that LuciadRIA can use");
|
|
224
|
+
return tileSets.find(t => OgcOpenApiCrsTools.getReferenceName(t.tileMatrix.crs) === map.reference.identifier) || tileSets[0];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Where the data is, in CRS84 as [minX, minY, maxX, maxY]: the extent of the collection, or for the dataset the
|
|
228
|
+
// bounding box of the tileset, when the server gives them in CRS84
|
|
229
|
+
function getExtent(collection: OgcOpenApiCapabilitiesCollection | null, tileSet: CollectionTileSet): number[] | undefined {
|
|
230
|
+
if (collection) {
|
|
231
|
+
const spatial = collection.extent && collection.extent.spatial;
|
|
232
|
+
return spatial && spatial.bbox && (!spatial.crs || /CRS84$/.test(spatial.crs)) ? spatial.bbox[0] : undefined;
|
|
233
|
+
}
|
|
234
|
+
const box = tileSet.boundingBox;
|
|
235
|
+
return box && box.crs && /CRS84$/.test(box.crs) ? [...box.lowerLeft, ...box.upperRight] : undefined;
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Map tiles
|
|
240
|
+
OgcOpenApiTilesModel extends WMTSTileSetModel, use it with a RasterTileSetLayer. Create it with `createFromTileSet`: it
|
|
241
|
+
describes the tileset to LuciadRIA's WMTS support, which handles tile matrix sets that are quad-trees and tile matrix sets
|
|
242
|
+
that are not (such as GlobalCRS84Scale), and it does not request tiles outside the `tileMatrixSetLimits`:
|
|
168
243
|
```typescript
|
|
169
244
|
import {OgcOpenApiTilesModel} from "ogcopenapis/OgcOpenApiTilesModel";
|
|
170
245
|
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
246
|
+
async function createTileLayer(map: Map, capabilities: OgcOpenApiCapabilitiesObject, collection: OgcOpenApiCapabilitiesCollection | null) {
|
|
247
|
+
const {tileSet: tileSetInfo, tileMatrix} = pickTileSet(map, await fetchUsableTileSets(capabilities, collection, CollectionLinkType.Tiles));
|
|
248
|
+
const tileSet = await OgcOpenApiGetCapabilities.fetchCollectionTileSet(capabilities, collection, tileSetInfo, {}, CollectionLinkType.Tiles);
|
|
249
|
+
// PNG, with transparency, when the server types its links
|
|
250
|
+
const link = tileSet.links.find(l => l.type === "image/png") || tileSet.links[0];
|
|
251
|
+
const model = await OgcOpenApiTilesModel.createFromTileSet({
|
|
252
|
+
tileMatrix,
|
|
253
|
+
baseURL: link.href,
|
|
254
|
+
collection: collection ? collection.id : "dataset",
|
|
255
|
+
format: link.type && link.type.indexOf("image/") === 0 ? link.type : undefined,
|
|
256
|
+
tileMatrixSetLimits: tileSet.tileMatrixSetLimits,
|
|
257
|
+
extent: getExtent(collection, tileSet),
|
|
258
|
+
});
|
|
259
|
+
return new RasterTileSetLayer(model, {label: collection ? collection.title || collection.id : "All collections"});
|
|
260
|
+
}
|
|
180
261
|
```
|
|
181
262
|
The constructor `new OgcOpenApiTilesModel(options)` still works, but is deprecated: it supports quad-tree tile matrix sets only.
|
|
182
|
-
Look at the LuciadRIA documentation if you need further information on using RasterTileSetLayer.
|
|
183
263
|
|
|
184
264
|
### Vector tiles
|
|
185
|
-
|
|
186
|
-
|
|
265
|
+
OgcOpenApiVectorTilesModel extends LuciadRIA's MapboxVectorTileSetModel, use it with a MapboxVectorTileSetLayer and a
|
|
266
|
+
MapboxStyle. The style is one of the server (OGC API Styles) for the tile matrix set in use, or a style generated from
|
|
267
|
+
the layers in the tiles when the server has none:
|
|
187
268
|
```typescript
|
|
188
269
|
import {MapboxVectorTileSetLayer} from "@luciad/ria/view/mapbox/MapboxVectorTileSetLayer.js";
|
|
189
270
|
import {MapboxStyle} from "@luciad/ria/view/mapbox/MapboxStyle.js";
|
|
190
|
-
import {CollectionLinkType, OgcOpenApiGetCapabilities} from "ogcopenapis/OgcOpenApiGetCapabilities";
|
|
191
271
|
import {OgcOpenApiVectorTilesModel} from "ogcopenapis/OgcOpenApiVectorTilesModel";
|
|
192
272
|
import {OgcOpenApiStyleTools} from "ogcopenapis/OgcOpenApiStyleTools";
|
|
193
273
|
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const layers = tileSet.layers.length > 0 ? tileSet.layers : await model.discoverLayers({extent: collection
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
map.layerTree.addChild(new MapboxVectorTileSetLayer(model, {label: collection.title, style: MapboxStyle.createFromObject(styleJson)}));
|
|
274
|
+
async function createVectorTileLayer(map: Map, capabilities: OgcOpenApiCapabilitiesObject, collection: OgcOpenApiCapabilitiesCollection | null) {
|
|
275
|
+
const tileSets = await fetchUsableTileSets(capabilities, collection, CollectionLinkType.VectorTiles);
|
|
276
|
+
const {tileSet: tileSetInfo, tileMatrix} = pickTileSet(map, tileSets);
|
|
277
|
+
const tileSet = await OgcOpenApiGetCapabilities.fetchCollectionTileSet(capabilities, collection, tileSetInfo, {}, CollectionLinkType.VectorTiles);
|
|
278
|
+
// Mapbox Vector Tiles, the vector tile encoding LuciadRIA reads
|
|
279
|
+
const link = tileSet.links.find(l => l.type === "application/vnd.mapbox-vector-tile") || tileSet.links.find(l => !l.type);
|
|
280
|
+
if (!link) throw new Error("The tileset has no Mapbox Vector Tiles");
|
|
281
|
+
const label = collection ? collection.title || collection.id : "All collections";
|
|
282
|
+
const model = new OgcOpenApiVectorTilesModel({
|
|
283
|
+
tileMatrix,
|
|
284
|
+
baseURL: link.href,
|
|
285
|
+
collection: collection ? collection.id : "dataset",
|
|
286
|
+
tileMatrixSetLimits: tileSet.tileMatrixSetLimits,
|
|
287
|
+
});
|
|
288
|
+
// The styles of the collection, or of the dataset, for this tile matrix set. An application can let the user pick one
|
|
289
|
+
const styles = OgcOpenApiStyleTools.getStylesForTileMatrixSet(
|
|
290
|
+
await OgcOpenApiStyleTools.fetchStyles(capabilities, collection), tileMatrix.id, tileSets.map(t => t.tileMatrix.id));
|
|
291
|
+
let styleJson;
|
|
292
|
+
if (styles.length > 0) {
|
|
293
|
+
styleJson = await OgcOpenApiStyleTools.fetchMapboxStyle(capabilities, OgcOpenApiStyleTools.pickStylesheet(styles[0], tileMatrix.id));
|
|
294
|
+
} else {
|
|
295
|
+
// The layers in the tiles: from the tileset, or read from a few tiles where the data is
|
|
296
|
+
const layers = tileSet.layers.length > 0 ? tileSet.layers : await model.discoverLayers({extent: getExtent(collection, tileSet)});
|
|
297
|
+
styleJson = OgcOpenApiStyleTools.createDefaultStyle(layers, label);
|
|
298
|
+
}
|
|
299
|
+
return new MapboxVectorTileSetLayer(model, {label, style: MapboxStyle.createFromObject(styleJson)});
|
|
300
|
+
}
|
|
222
301
|
```
|
|
223
302
|
|
|
224
303
|
### Tiles of the dataset
|
|
225
304
|
Some servers tile the dataset as a whole, with all its collections in each tile, and some only the dataset (PDOK).
|
|
226
|
-
The tilesets and styles of the dataset are linked from the landing page:
|
|
227
|
-
|
|
305
|
+
The tilesets and styles of the dataset are linked from the landing page. The functions above take `null` for them:
|
|
228
306
|
```typescript
|
|
229
307
|
if (OgcOpenApiGetCapabilities.hasDatasetTileSets(capabilities, CollectionLinkType.VectorTiles)) {
|
|
230
|
-
|
|
231
|
-
const tileMatrix = await OgcOpenApiGetCapabilities.fetchTileMatrixSetOfTileSet(capabilities, datasetTileSets[0], tileMatrixSets);
|
|
232
|
-
// null instead of a collection: the tileset of the dataset
|
|
233
|
-
const tileSet = await OgcOpenApiGetCapabilities.fetchCollectionTileSet(capabilities, null, datasetTileSets[0], {}, CollectionLinkType.VectorTiles);
|
|
234
|
-
// The styles of the dataset, only those for the tile matrix set in use
|
|
235
|
-
const datasetStyles = OgcOpenApiStyleTools.getStylesForTileMatrixSet(
|
|
236
|
-
await OgcOpenApiStyleTools.fetchStyles(capabilities, null), tileMatrix.id, [tileMatrix.id /* and the ids of the other tilesets */]);
|
|
308
|
+
map.layerTree.addChild(await createVectorTileLayer(map, capabilities, null));
|
|
237
309
|
}
|
|
238
310
|
```
|
|
239
|
-
A tileset of the dataset can have a `boundingBox`, where its data is. A collection has its `extent` instead.
|
|
240
311
|
|
|
241
312
|
### Styles
|
|
242
313
|
* LuciadRIA reads the Mapbox GL encoding of OGC API Styles only, not SLD or other encodings.
|