@open-pioneer/ogc-features 1.3.0-dev.20260408130821 → 1.3.0-dev.20260422120518

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @open-pioneer/ogc-features
2
2
 
3
- ## 1.3.0-dev.20260408130821
3
+ ## 1.3.0-dev.20260422120518
4
4
 
5
5
  ### Minor Changes
6
6
 
@@ -13,6 +13,7 @@
13
13
  ### Patch Changes
14
14
 
15
15
  - a96d004: VectorSource: trigger change events on error if no features were added. This should resolve an error where the map was permanently loading if the capabilities failed to load.
16
+ - eb120a2: OgcFeaturesVectorSource: use promise based `loader` function (see <https://openlayers.org/en/latest/apidoc/module-ol_source_Vector-VectorSource.html> and the [PR](https://github.com/openlayers/openlayers/pull/17403) that introduced the API).
16
17
 
17
18
  ## 1.2.0
18
19
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@open-pioneer/ogc-features",
4
- "version": "1.3.0-dev.20260408130821",
4
+ "version": "1.3.0-dev.20260422120518",
5
5
  "description": "This package provides utilities to work with OGC API Features services.",
6
6
  "keywords": [
7
7
  "open-pioneer-trails"
@@ -14,9 +14,9 @@
14
14
  "directory": "src/packages/ogc-features"
15
15
  },
16
16
  "dependencies": {
17
- "@open-pioneer/core": "^4.5.0",
18
- "@open-pioneer/http": "^4.5.0",
19
- "ol": "^10.8.0",
17
+ "@open-pioneer/core": "4.6.0-dev.20260420101935",
18
+ "@open-pioneer/http": "4.6.0-dev.20260420101935",
19
+ "ol": "^10.9.0",
20
20
  "uuid": "^13.0.0"
21
21
  },
22
22
  "exports": {
@@ -36,12 +36,8 @@ class OgcFeaturesVectorSource extends VectorSource {
36
36
  format,
37
37
  strategy: bbox,
38
38
  attributions: options.attributions,
39
- // NOTE: it is IMPORTANT that every individual parameter is named here.
40
- // OpenLayers tests the length of the signature, `...args` to forward these parameters
41
- // will _not_ work!
42
- // See also https://github.com/openlayers/openlayers/blob/da23cb2025bec601439fdc69076cf34af7582cb9/src/ol/source/Vector.js#L1041-L1042
43
- loader: (e, r, p, s, f) => {
44
- this.#load(e, r, p, s, f);
39
+ loader: (e, r, p) => {
40
+ return this.#load(e, r, p);
45
41
  },
46
42
  ...options.additionalOptions
47
43
  });
@@ -51,17 +47,15 @@ class OgcFeaturesVectorSource extends VectorSource {
51
47
  this.#collectionUrl = `${options.baseUrl.replace(/\/+$/, "")}/collections/${options.collectionId}`;
52
48
  this.#itemsUrl = `${this.#collectionUrl}/items`;
53
49
  }
54
- async #load(extent, _resolution, projection, success, failure) {
50
+ async #load(extent, _resolution, projection) {
55
51
  try {
56
52
  const features = await this.#loadImpl(extent, projection);
57
- success?.(features);
58
- this.changed();
53
+ return features;
59
54
  } catch (e) {
60
55
  if (!isAbortError(e)) {
61
56
  LOG.error("Failed to load features from ogc service", e);
62
57
  }
63
- failure?.();
64
- this.changed();
58
+ throw e;
65
59
  }
66
60
  }
67
61
  async #loadImpl(extent, projection) {
@@ -1 +1 @@
1
- {"version":3,"file":"OgcFeaturesVectorSource.js","sources":["OgcFeaturesVectorSource.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { createAbortError, createLogger, isAbortError, throwAbortError } from \"@open-pioneer/core\";\nimport { HttpService } from \"@open-pioneer/http\";\nimport { Extent } from \"ol/extent\";\nimport Feature from \"ol/Feature\";\nimport GeoJSON from \"ol/format/GeoJSON\";\nimport { bbox } from \"ol/loadingstrategy\";\nimport { Projection } from \"ol/proj\";\nimport VectorSource from \"ol/source/Vector\";\nimport { sourceId } from \"open-pioneer:source-info\";\nimport { OgcFeatureVectorSourceOptions } from \"../api\";\nimport { CollectionMetadata, findMatchingCrs, getCollectionMetadata } from \"./Metadata\";\nimport { NextStrategy } from \"./NextStrategy\";\nimport { OffsetStrategy, supportsOffsetStrategy } from \"./OffsetStrategy\";\nimport { createCollectionRequestUrl } from \"./requestUtils\";\n\nconst LOG = createLogger(sourceId);\n\nconst CRS_OGC_CRS84 = \"http://www.opengis.net/def/crs/OGC/1.3/CRS84\";\n\ntype SuccessCallback = (features: Feature[]) => void;\ntype FailureCallback = () => void;\n\nexport class OgcFeaturesVectorSource extends VectorSource {\n #featureFormat: GeoJSON;\n #httpService: HttpService;\n\n #options: OgcFeatureVectorSourceOptions;\n #itemsUrl: string;\n #collectionUrl: string;\n\n #metadataPromise: Promise<CollectionMetadata> | undefined;\n #loadingStrategyPromise: Promise<\"offset\" | \"next\"> | undefined;\n\n // Cancels pending feature load operations.\n #featuresAbortController: AbortController | undefined;\n\n // Maps a map CRS to the corresponding request CRS that should be used for requests to the OGC API Features service.\n #mapCrsToRequestCrs: Record<string, string> = {\n // Special case: When map is in EPSG:4326, which defines lat/long-order for coordinate values,\n // make sure features are requested in CRS84, which has long/lat-order.\n // Reason for this special case is that OpenLayers always expects coordinates to be in long/lat-order,\n // even when the CRS definition specifies lat/long-order (as is the case for EPSG:4326).\n // CRS84 is mandated to be supported by all OGC API Features services, so we can safely assume that the service will support it.\n [\"4326\"]: CRS_OGC_CRS84,\n [\"EPSG:4326\"]: CRS_OGC_CRS84\n };\n\n constructor(options: OgcFeatureVectorSourceOptions, httpService: HttpService) {\n const format = new GeoJSON();\n super({\n format,\n strategy: bbox,\n attributions: options.attributions,\n\n // NOTE: it is IMPORTANT that every individual parameter is named here.\n // OpenLayers tests the length of the signature, `...args` to forward these parameters\n // will _not_ work!\n // See also https://github.com/openlayers/openlayers/blob/da23cb2025bec601439fdc69076cf34af7582cb9/src/ol/source/Vector.js#L1041-L1042\n loader: (e, r, p, s, f) => {\n this.#load(e, r, p, s, f);\n },\n ...options.additionalOptions\n });\n this.#featureFormat = format;\n this.#httpService = httpService;\n this.#options = options;\n this.#collectionUrl = `${options.baseUrl.replace(/\\/+$/, \"\")}/collections/${options.collectionId}`;\n this.#itemsUrl = `${this.#collectionUrl}/items`;\n }\n\n async #load(\n extent: Extent,\n _resolution: number,\n projection: Projection,\n success: SuccessCallback | undefined,\n failure: FailureCallback | undefined\n ) {\n try {\n const features = await this.#loadImpl(extent, projection);\n success?.(features);\n this.changed(); // Always trigger changed event to unstuck loading state\n } catch (e) {\n if (!isAbortError(e)) {\n LOG.error(\"Failed to load features from ogc service\", e);\n }\n failure?.();\n\n // Always trigger changed event to unstuck loading state\n // See https://github.com/openlayers/openlayers/issues/17335\n this.changed();\n }\n }\n\n async #loadImpl(extent: Extent, projection: Projection): Promise<Feature[]> {\n const [collectionMetadata, strategy] = await Promise.all([\n this.#loadCollectionMetadata(),\n this.#getLoadingStrategy()\n ]);\n\n // An extent-change should cancel open requests for older extents, because otherwise,\n // old and expensive requests could block new requests for a new extent\n // => no features are drawn on the current map for a long time.\n //TODO: More context\n this.#featuresAbortController?.abort(createAbortError());\n const abortController = (this.#featuresAbortController = new AbortController());\n const requestCrs = this.#getRequestCrs(collectionMetadata, projection);\n const fullUrl = this.#getRequestUrl(extent, requestCrs);\n const sharedOptions = {\n fullUrl,\n featureFormat: this.#featureFormat,\n limit: this.#options.limit,\n httpService: this.#httpService,\n signal: abortController.signal,\n onFeaturesLoaded: (features: Feature[]) => {\n LOG.debug(`Adding ${features.length} features`);\n this.addFeatures(features);\n }\n };\n let strategyImpl;\n switch (strategy) {\n case \"next\": {\n strategyImpl = new NextStrategy(sharedOptions);\n break;\n }\n case \"offset\":\n strategyImpl = new OffsetStrategy({\n ...sharedOptions,\n concurrency: this.#options.maxConcurrentRequests\n });\n break;\n }\n\n const features = await strategyImpl.load();\n LOG.debug(\"Finished loading features for extent:\", extent);\n return features;\n }\n\n // Fetches collection metadata from the service (once).\n async #loadCollectionMetadata() {\n const run = async () => {\n let metadata;\n try {\n metadata = await getCollectionMetadata(this.#collectionUrl, this.#httpService);\n } catch (e) {\n LOG.error(\n `Failed to retrieve collection metadata for collection '${this.#collectionUrl}'`,\n e\n );\n throwAbortError(); // Report error up the stack but only log error once\n }\n\n try {\n if (this.getAttributions() == null && metadata.attribution) {\n this.setAttributions(metadata.attribution);\n }\n } catch (e) {\n LOG.error(\"Failed to apply attributions\", e);\n throwAbortError(); // Report error up the stack but only log error once\n }\n return metadata;\n };\n\n const promise = (this.#metadataPromise ??= run());\n return await promise;\n }\n\n // Runs feature detection on the service (once).\n async #getLoadingStrategy() {\n const run = async () => {\n let supportsOffset;\n try {\n supportsOffset = await supportsOffsetStrategy(this.#itemsUrl, this.#httpService);\n } catch (e) {\n LOG.error(\n `Failed to retrieve collection information for collection '${this.#collectionUrl}'`,\n e\n );\n throwAbortError(); // Report error up the stack but only log error once\n }\n\n const options = this.#options;\n let strategy = options?.strategy || (supportsOffset ? \"offset\" : \"next\");\n if (strategy === \"offset\" && !supportsOffset) {\n strategy = \"next\";\n }\n return strategy;\n };\n\n const promise = (this.#loadingStrategyPromise ??= run());\n return await promise;\n }\n\n // Computes the appropriate request crs for the current configuration.\n #getRequestCrs(collectionMetadata: CollectionMetadata | undefined, projection: Projection) {\n const mapCrs = projection.getCode();\n\n const requestCrs = this.#options.crs ?? this.#mapCrsToRequestCrs[mapCrs];\n if (requestCrs) {\n return requestCrs;\n }\n\n const matchingMapCrs = findMatchingCrs(mapCrs, collectionMetadata?.crs);\n if (matchingMapCrs) {\n this.#mapCrsToRequestCrs[mapCrs] = matchingMapCrs;\n return matchingMapCrs;\n } else {\n LOG.error(`Map CRS '${mapCrs}' not supported by collection '${this.#collectionUrl}'.`);\n throwAbortError();\n }\n }\n\n #getRequestUrl(extent: Extent, requestCrs: string) {\n let requestUrl = createCollectionRequestUrl(this.#itemsUrl, extent, requestCrs);\n const rewriteUrl = this.#options.rewriteUrl;\n if (rewriteUrl) {\n requestUrl = rewriteUrl(requestUrl) ?? requestUrl;\n }\n return requestUrl;\n }\n}\n"],"names":["features"],"mappings":"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"}
1
+ {"version":3,"file":"OgcFeaturesVectorSource.js","sources":["OgcFeaturesVectorSource.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { createAbortError, createLogger, isAbortError, throwAbortError } from \"@open-pioneer/core\";\nimport { HttpService } from \"@open-pioneer/http\";\nimport { Extent } from \"ol/extent\";\nimport Feature from \"ol/Feature\";\nimport GeoJSON from \"ol/format/GeoJSON\";\nimport { bbox } from \"ol/loadingstrategy\";\nimport { Projection } from \"ol/proj\";\nimport VectorSource from \"ol/source/Vector\";\nimport { sourceId } from \"open-pioneer:source-info\";\nimport { OgcFeatureVectorSourceOptions } from \"../api\";\nimport { CollectionMetadata, findMatchingCrs, getCollectionMetadata } from \"./Metadata\";\nimport { NextStrategy } from \"./NextStrategy\";\nimport { OffsetStrategy, supportsOffsetStrategy } from \"./OffsetStrategy\";\nimport { createCollectionRequestUrl } from \"./requestUtils\";\n\nconst LOG = createLogger(sourceId);\n\nconst CRS_OGC_CRS84 = \"http://www.opengis.net/def/crs/OGC/1.3/CRS84\";\n\nexport class OgcFeaturesVectorSource extends VectorSource {\n #featureFormat: GeoJSON;\n #httpService: HttpService;\n\n #options: OgcFeatureVectorSourceOptions;\n #itemsUrl: string;\n #collectionUrl: string;\n\n #metadataPromise: Promise<CollectionMetadata> | undefined;\n #loadingStrategyPromise: Promise<\"offset\" | \"next\"> | undefined;\n\n // Cancels pending feature load operations.\n #featuresAbortController: AbortController | undefined;\n\n // Maps a map CRS to the corresponding request CRS that should be used for requests to the OGC API Features service.\n #mapCrsToRequestCrs: Record<string, string> = {\n // Special case: When map is in EPSG:4326, which defines lat/long-order for coordinate values,\n // make sure features are requested in CRS84, which has long/lat-order.\n // Reason for this special case is that OpenLayers always expects coordinates to be in long/lat-order,\n // even when the CRS definition specifies lat/long-order (as is the case for EPSG:4326).\n // CRS84 is mandated to be supported by all OGC API Features services, so we can safely assume that the service will support it.\n [\"4326\"]: CRS_OGC_CRS84,\n [\"EPSG:4326\"]: CRS_OGC_CRS84\n };\n\n constructor(options: OgcFeatureVectorSourceOptions, httpService: HttpService) {\n const format = new GeoJSON();\n super({\n format,\n strategy: bbox,\n attributions: options.attributions,\n loader: (e, r, p) => {\n return this.#load(e, r, p);\n },\n ...options.additionalOptions\n });\n this.#featureFormat = format;\n this.#httpService = httpService;\n this.#options = options;\n this.#collectionUrl = `${options.baseUrl.replace(/\\/+$/, \"\")}/collections/${options.collectionId}`;\n this.#itemsUrl = `${this.#collectionUrl}/items`;\n }\n\n async #load(extent: Extent, _resolution: number, projection: Projection) {\n try {\n const features = await this.#loadImpl(extent, projection);\n return features;\n } catch (e) {\n if (!isAbortError(e)) {\n LOG.error(\"Failed to load features from ogc service\", e);\n }\n throw e;\n }\n }\n\n async #loadImpl(extent: Extent, projection: Projection): Promise<Feature[]> {\n const [collectionMetadata, strategy] = await Promise.all([\n this.#loadCollectionMetadata(),\n this.#getLoadingStrategy()\n ]);\n\n // An extent-change should cancel open requests for older extents, because otherwise,\n // old and expensive requests could block new requests for a new extent\n // => no features are drawn on the current map for a long time.\n //TODO: More context\n this.#featuresAbortController?.abort(createAbortError());\n const abortController = (this.#featuresAbortController = new AbortController());\n const requestCrs = this.#getRequestCrs(collectionMetadata, projection);\n const fullUrl = this.#getRequestUrl(extent, requestCrs);\n const sharedOptions = {\n fullUrl,\n featureFormat: this.#featureFormat,\n limit: this.#options.limit,\n httpService: this.#httpService,\n signal: abortController.signal,\n onFeaturesLoaded: (features: Feature[]) => {\n LOG.debug(`Adding ${features.length} features`);\n this.addFeatures(features);\n }\n };\n let strategyImpl;\n switch (strategy) {\n case \"next\": {\n strategyImpl = new NextStrategy(sharedOptions);\n break;\n }\n case \"offset\":\n strategyImpl = new OffsetStrategy({\n ...sharedOptions,\n concurrency: this.#options.maxConcurrentRequests\n });\n break;\n }\n\n const features = await strategyImpl.load();\n LOG.debug(\"Finished loading features for extent:\", extent);\n return features;\n }\n\n // Fetches collection metadata from the service (once).\n async #loadCollectionMetadata() {\n const run = async () => {\n let metadata;\n try {\n metadata = await getCollectionMetadata(this.#collectionUrl, this.#httpService);\n } catch (e) {\n LOG.error(\n `Failed to retrieve collection metadata for collection '${this.#collectionUrl}'`,\n e\n );\n throwAbortError(); // Report error up the stack but only log error once\n }\n\n try {\n if (this.getAttributions() == null && metadata.attribution) {\n this.setAttributions(metadata.attribution);\n }\n } catch (e) {\n LOG.error(\"Failed to apply attributions\", e);\n throwAbortError(); // Report error up the stack but only log error once\n }\n return metadata;\n };\n\n const promise = (this.#metadataPromise ??= run());\n return await promise;\n }\n\n // Runs feature detection on the service (once).\n async #getLoadingStrategy() {\n const run = async () => {\n let supportsOffset;\n try {\n supportsOffset = await supportsOffsetStrategy(this.#itemsUrl, this.#httpService);\n } catch (e) {\n LOG.error(\n `Failed to retrieve collection information for collection '${this.#collectionUrl}'`,\n e\n );\n throwAbortError(); // Report error up the stack but only log error once\n }\n\n const options = this.#options;\n let strategy = options?.strategy || (supportsOffset ? \"offset\" : \"next\");\n if (strategy === \"offset\" && !supportsOffset) {\n strategy = \"next\";\n }\n return strategy;\n };\n\n const promise = (this.#loadingStrategyPromise ??= run());\n return await promise;\n }\n\n // Computes the appropriate request crs for the current configuration.\n #getRequestCrs(collectionMetadata: CollectionMetadata | undefined, projection: Projection) {\n const mapCrs = projection.getCode();\n\n const requestCrs = this.#options.crs ?? this.#mapCrsToRequestCrs[mapCrs];\n if (requestCrs) {\n return requestCrs;\n }\n\n const matchingMapCrs = findMatchingCrs(mapCrs, collectionMetadata?.crs);\n if (matchingMapCrs) {\n this.#mapCrsToRequestCrs[mapCrs] = matchingMapCrs;\n return matchingMapCrs;\n } else {\n LOG.error(`Map CRS '${mapCrs}' not supported by collection '${this.#collectionUrl}'.`);\n throwAbortError();\n }\n }\n\n #getRequestUrl(extent: Extent, requestCrs: string) {\n let requestUrl = createCollectionRequestUrl(this.#itemsUrl, extent, requestCrs);\n const rewriteUrl = this.#options.rewriteUrl;\n if (rewriteUrl) {\n requestUrl = rewriteUrl(requestUrl) ?? requestUrl;\n }\n return requestUrl;\n }\n}\n"],"names":["features"],"mappings":"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"}