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.
Files changed (3) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/README.md +152 -81
  3. 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.1...HEAD
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("https://demo.pygeoapi.io/master/", {filterCollectionsByLinkType: CollectionLinkType.Items});
108
- const collection = capabilities.collections.find(c => c.id === "lakes");
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
- const items = OgcOpenApiGetCapabilities.filterCollectionLinks(collection.links, CollectionLinkType.Items);
130
- const store = new OgcOpenApiFeatureStore({
131
- dataUrl: items.find(l => l.type === "application/geo+json").href,
132
- outputFormat: "application/geo+json",
133
- codec: new GeoJsonCodec({generateIDs: false}),
134
- crs: "http://www.opengis.net/def/crs/OGC/1.3/CRS84",
135
- extent: collection.extent,
136
- useCrs84Bounds: true,
137
- });
138
- const model = new FeatureModel(store, {reference: getReference("CRS:84")});
139
- map.layerTree.addChild(new FeatureLayer(model, {label: collection.title, loadingStrategy: new LoadSpatially()}));
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 and map tiles
148
- For maps and map tiles use OgcOpenApiMapsModel and OgcOpenApiTilesModel. OgcOpenApiMapsModel extends WMSTileSetModel
149
- and OgcOpenApiTilesModel extends WMTSTileSetModel, so you use them in combination with RasterTileSetLayer.
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
- const mapLinks = OgcOpenApiGetCapabilities.filterCollectionLinks(collection.links, CollectionLinkType.Map);
157
- const mapsModel = new OgcOpenApiMapsModel({
158
- crs: "http://www.opengis.net/def/crs/EPSG/0/3857",
159
- baseURL: (mapLinks.find(l => l.type === "image/png") || mapLinks[0]).href,
160
- collection: collection.id,
161
- });
162
- map.layerTree.addChild(new RasterTileSetLayer(mapsModel, {label: collection.title}));
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
- Create OgcOpenApiTilesModel with `createFromTileSet`. It describes the tileset to LuciadRIA's WMTS support, which handles
166
- tile matrix sets that are quad-trees and tile matrix sets that are not (such as GlobalCRS84Scale), and it does not request
167
- tiles outside the `tileMatrixSetLimits`. The tilesets and tile matrix sets are found as for vector tiles, below:
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
- const tileSet = await OgcOpenApiGetCapabilities.fetchCollectionTileSet(capabilities, collection, tileSetInfo, {}, CollectionLinkType.Tiles);
172
- const model = await OgcOpenApiTilesModel.createFromTileSet({
173
- tileMatrix,
174
- baseURL: tileSet.links.find(l => l.type === "image/png").href,
175
- collection: collection.id,
176
- tileMatrixSetLimits: tileSet.tileMatrixSetLimits,
177
- extent: collection.extent.spatial.bbox[0],
178
- });
179
- map.layerTree.addChild(new RasterTileSetLayer(model, {label: collection.title}));
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
- For vector tiles use OgcOpenApiVectorTilesModel. It extends LuciadRIA's MapboxVectorTileSetModel, so you use it in combination with
186
- MapboxVectorTileSetLayer and a MapboxStyle:
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
- const capabilities = await OgcOpenApiGetCapabilities.fromURL("https://demo.ldproxy.net/daraa/", {filterCollectionsByLinkType: CollectionLinkType.VectorTiles});
195
- const collection = capabilities.collections.find(c => c.id === "TransportationGroundCrv");
196
-
197
- // The tilesets of the collection: the tile matrix sets it is tiled in, which can be fewer than the ones of the server.
198
- // Servers give tile matrix sets their own ids (CubeWerx calls WebMercatorQuad smerc), they are matched by URI.
199
- // fetchTileSetsInFull leaves out definitions that cannot be read, it rejects only when none can
200
- const tileMatrixSets = await OgcOpenApiGetCapabilities.fetchTileSetsInFull(capabilities);
201
- const tileSets = await OgcOpenApiGetCapabilities.fetchCollectionTileSets(capabilities, collection, CollectionLinkType.VectorTiles);
202
- const webMercator = tileSets.find(t => /WebMercatorQuad\/?$/.test(t.tileMatrixSetURI));
203
- const tileMatrix = await OgcOpenApiGetCapabilities.fetchTileMatrixSetOfTileSet(capabilities, webMercator, tileMatrixSets);
204
- const tileSet = await OgcOpenApiGetCapabilities.fetchCollectionTileSet(capabilities, collection, webMercator, {}, CollectionLinkType.VectorTiles);
205
-
206
- const model = new OgcOpenApiVectorTilesModel({
207
- tileMatrix,
208
- baseURL: tileSet.links.find(l => l.type === "application/vnd.mapbox-vector-tile").href,
209
- collection: collection.id,
210
- tileMatrixSetLimits: tileSet.tileMatrixSetLimits,
211
- });
212
-
213
- // A style of the server (OGC API Styles), or a default style when the server has none.
214
- // The default style needs the layers in the tiles: from the tileset, or read from a few tiles when the tileset does not list them
215
- const styles = await OgcOpenApiStyleTools.fetchStyles(capabilities, collection);
216
- const layers = tileSet.layers.length > 0 ? tileSet.layers : await model.discoverLayers({extent: collection.extent.spatial.bbox[0]});
217
- const styleJson = styles.length > 0 ?
218
- await OgcOpenApiStyleTools.fetchMapboxStyle(capabilities, OgcOpenApiStyleTools.pickStylesheet(styles[0], tileMatrix.id)) :
219
- OgcOpenApiStyleTools.createDefaultStyle(layers);
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
- const datasetTileSets = await OgcOpenApiGetCapabilities.fetchDatasetTileSets(capabilities, CollectionLinkType.VectorTiles);
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ogcopenapis",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "OGC API Features, Maps, Tiles and Styles for LuciadRIA",
5
5
  "type": "module",
6
6
  "exports": {