@open-pioneer/ogc-features 1.3.0-dev.20260211111005 → 1.3.0-dev.20260225083007

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,11 +1,18 @@
1
1
  # @open-pioneer/ogc-features
2
2
 
3
- ## 1.3.0-dev.20260211111005
3
+ ## 1.3.0-dev.20260225083007
4
4
 
5
5
  ### Minor Changes
6
6
 
7
+ - 91cf8f1: Derive request CRS from map CRS.
8
+ The `crs` property of the layer configuration for OGC API Features layers is now _optional_.
9
+ - 6f73670: Changes the behavior when no 'attributions' properties is set for OGC API Features layers. In that case, the 'attribution' value provided by the collection metadata is used, if available.
7
10
  - d54ccfd: Update to Chakra UI 3.32.0
8
11
 
12
+ ### Patch Changes
13
+
14
+ - 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.
15
+
9
16
  ## 1.2.0
10
17
 
11
18
  ### Patch Changes
package/Metadata.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { HttpService } from "@open-pioneer/http";
2
+ /**
3
+ * Requests metadata for an OGC API Features service collection.
4
+ *
5
+ * @param baseUrl Base URL of the OGC API Features service (e.g. `https://example.com/ogcapi/v1`).
6
+ * @param collectionId ID of the collection to retrieve the metadata for.
7
+ * @param httpService Instance to perform the HTTP request.
8
+ * @returns
9
+ */
10
+ export declare function getCollectionMetadata(baseUrl: string, collectionId: string, httpService: HttpService): Promise<CollectionMetadata>;
11
+ /**
12
+ * Metadata of a collection retrieved from the OGC API Features service as provided by the `/collections/{collectionId}` endpoint.
13
+ */
14
+ export interface CollectionMetadata {
15
+ id: string;
16
+ crs: string[] | undefined;
17
+ attribution?: string;
18
+ }
19
+ /**
20
+ * Determines the appropriate coordinate reference system (CRS) identifier to use for requests to the OGC API Features service, based on the map's current CRS, the collection's supported CRSs, and any explicitly configured CRS in the options.
21
+ *
22
+ * @param mapCrs the CRS used by the map that is requesting to load the features.
23
+ * @param supportedCrses list of supported CRS identifiers.
24
+ * @param configuredCrs an explicitly configured CRS identifier to use for requests, if any.
25
+ * @returns the CRS to use for feature requests based on the given information.
26
+ */
27
+ export declare function getRequestCrs(mapCrs: string, supportedCrses: string[] | undefined, configuredCrs: string | undefined): string;
28
+ /**
29
+ * Checks if a given coordinate reference system (CRS) identifier matches any of the available CRS URIs.
30
+ * The function especially supports matching a simple EPSG code like "EPSG:4326" with its corresponding CRS URI (e.g. "http://www.opengis.net/def/crs/EPSG/0/4326").
31
+ *
32
+ *
33
+ * @param testCrs a CRS identifier to test, e.g. "EPSG:4326" or "http://www.opengis.net/def/crs/EPSG/0/4326"
34
+ * @param availableCrsUris list of CRS URIs to check against, expected to be in the form of "http://www.opengis.net/def/crs/{authority}/{version}/{code}".
35
+ *
36
+ * @returns the matching CRS URI if a match is found, otherwise `undefined`.
37
+ */
38
+ export declare function findMatchingCrs(testCrs: string, availableCrsUris: string[] | undefined): string | undefined;
package/Metadata.js ADDED
@@ -0,0 +1,47 @@
1
+ import { createLogger } from '@open-pioneer/core';
2
+ import { sourceId } from './_virtual/source-info.js';
3
+
4
+ const LOG = createLogger(sourceId);
5
+ async function getCollectionMetadata(baseUrl, collectionId, httpService) {
6
+ const collectionMetadataUrl = `${baseUrl}/collections/${collectionId}`;
7
+ const response = await httpService.fetch(collectionMetadataUrl, {
8
+ headers: {
9
+ Accept: "application/json"
10
+ }
11
+ });
12
+ if (!response.ok) {
13
+ throw new Error(
14
+ `Failed to fetch collection metadata for collection '${collectionId}' (status code ${response.status})`
15
+ );
16
+ }
17
+ return await response.json();
18
+ }
19
+ function getRequestCrs(mapCrs, supportedCrses, configuredCrs) {
20
+ if (configuredCrs) {
21
+ return configuredCrs;
22
+ }
23
+ const DEFAULT_CRS = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
24
+ const matchingMapCrs = findMatchingCrs(mapCrs, supportedCrses);
25
+ if (!matchingMapCrs) {
26
+ LOG.warn(
27
+ `Map CRS '${mapCrs}' not supported. Falling back to default CRS '${DEFAULT_CRS}'.`
28
+ );
29
+ return DEFAULT_CRS;
30
+ }
31
+ return matchingMapCrs;
32
+ }
33
+ function findMatchingCrs(testCrs, availableCrsUris) {
34
+ if (!availableCrsUris) {
35
+ return void 0;
36
+ }
37
+ if (testCrs.startsWith("EPSG:")) {
38
+ const testCode = testCrs.split(":")[1];
39
+ const testCrsUri = `http://www.opengis.net/def/crs/EPSG/0/${testCode}`;
40
+ return availableCrsUris.find((crsUri) => crsUri === testCrsUri);
41
+ } else {
42
+ return availableCrsUris.find((crsUri) => crsUri === testCrs);
43
+ }
44
+ }
45
+
46
+ export { getCollectionMetadata, getRequestCrs };
47
+ //# sourceMappingURL=Metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Metadata.js","sources":["Metadata.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\n\nimport { createLogger } from \"@open-pioneer/core\";\nimport { HttpService } from \"@open-pioneer/http\";\nimport { sourceId } from \"open-pioneer:source-info\";\n\n/**\n * @module\n *\n * Provides metadata-related utilities for working with OGC API Features services, such as fetching collection metadata.\n *\n *\n */\n\nconst LOG = createLogger(sourceId);\n\n/**\n * Requests metadata for an OGC API Features service collection.\n *\n * @param baseUrl Base URL of the OGC API Features service (e.g. `https://example.com/ogcapi/v1`).\n * @param collectionId ID of the collection to retrieve the metadata for.\n * @param httpService Instance to perform the HTTP request.\n * @returns\n */\nexport async function getCollectionMetadata(\n baseUrl: string,\n collectionId: string,\n httpService: HttpService\n): Promise<CollectionMetadata> {\n const collectionMetadataUrl = `${baseUrl}/collections/${collectionId}`;\n const response = await httpService.fetch(collectionMetadataUrl, {\n headers: {\n Accept: \"application/json\"\n }\n });\n if (!response.ok) {\n throw new Error(\n `Failed to fetch collection metadata for collection '${collectionId}' (status code ${response.status})`\n );\n }\n // Note: Currently no validation\n return await response.json();\n}\n\n/**\n * Metadata of a collection retrieved from the OGC API Features service as provided by the `/collections/{collectionId}` endpoint.\n */\nexport interface CollectionMetadata {\n id: string;\n crs: string[] | undefined;\n attribution?: string;\n}\n\n/**\n * Determines the appropriate coordinate reference system (CRS) identifier to use for requests to the OGC API Features service, based on the map's current CRS, the collection's supported CRSs, and any explicitly configured CRS in the options.\n *\n * @param mapCrs the CRS used by the map that is requesting to load the features.\n * @param supportedCrses list of supported CRS identifiers.\n * @param configuredCrs an explicitly configured CRS identifier to use for requests, if any.\n * @returns the CRS to use for feature requests based on the given information.\n */\nexport function getRequestCrs(\n mapCrs: string,\n supportedCrses: string[] | undefined,\n configuredCrs: string | undefined\n): string {\n if (configuredCrs) {\n return configuredCrs;\n }\n\n const DEFAULT_CRS = \"http://www.opengis.net/def/crs/OGC/1.3/CRS84\";\n const matchingMapCrs = findMatchingCrs(mapCrs, supportedCrses);\n if (!matchingMapCrs) {\n LOG.warn(\n `Map CRS '${mapCrs}' not supported. Falling back to default CRS '${DEFAULT_CRS}'.`\n );\n return DEFAULT_CRS;\n }\n\n return matchingMapCrs;\n}\n\n/**\n * Checks if a given coordinate reference system (CRS) identifier matches any of the available CRS URIs.\n * The function especially supports matching a simple EPSG code like \"EPSG:4326\" with its corresponding CRS URI (e.g. \"http://www.opengis.net/def/crs/EPSG/0/4326\").\n *\n *\n * @param testCrs a CRS identifier to test, e.g. \"EPSG:4326\" or \"http://www.opengis.net/def/crs/EPSG/0/4326\"\n * @param availableCrsUris list of CRS URIs to check against, expected to be in the form of \"http://www.opengis.net/def/crs/{authority}/{version}/{code}\".\n *\n * @returns the matching CRS URI if a match is found, otherwise `undefined`.\n */\n\nexport function findMatchingCrs(\n testCrs: string,\n availableCrsUris: string[] | undefined\n): string | undefined {\n if (!availableCrsUris) {\n return undefined;\n }\n\n if (testCrs.startsWith(\"EPSG:\")) {\n const testCode = testCrs.split(\":\")[1];\n const testCrsUri = `http://www.opengis.net/def/crs/EPSG/0/${testCode}`;\n return availableCrsUris.find((crsUri) => crsUri === testCrsUri);\n } else {\n return availableCrsUris.find((crsUri) => crsUri === testCrs);\n }\n}\n"],"names":[],"mappings":";;;AAeA,MAAM,GAAA,GAAM,aAAa,QAAQ,CAAA;AAUjC,eAAsB,qBAAA,CAClB,OAAA,EACA,YAAA,EACA,WAAA,EAC2B;AAC3B,EAAA,MAAM,qBAAA,GAAwB,CAAA,EAAG,OAAO,CAAA,aAAA,EAAgB,YAAY,CAAA,CAAA;AACpE,EAAA,MAAM,QAAA,GAAW,MAAM,WAAA,CAAY,KAAA,CAAM,qBAAA,EAAuB;AAAA,IAC5D,OAAA,EAAS;AAAA,MACL,MAAA,EAAQ;AAAA;AACZ,GACH,CAAA;AACD,EAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,IAAA,MAAM,IAAI,KAAA;AAAA,MACN,CAAA,oDAAA,EAAuD,YAAY,CAAA,eAAA,EAAkB,QAAA,CAAS,MAAM,CAAA,CAAA;AAAA,KACxG;AAAA,EACJ;AAEA,EAAA,OAAO,MAAM,SAAS,IAAA,EAAK;AAC/B;AAmBO,SAAS,aAAA,CACZ,MAAA,EACA,cAAA,EACA,aAAA,EACM;AACN,EAAA,IAAI,aAAA,EAAe;AACf,IAAA,OAAO,aAAA;AAAA,EACX;AAEA,EAAA,MAAM,WAAA,GAAc,8CAAA;AACpB,EAAA,MAAM,cAAA,GAAiB,eAAA,CAAgB,MAAA,EAAQ,cAAc,CAAA;AAC7D,EAAA,IAAI,CAAC,cAAA,EAAgB;AACjB,IAAA,GAAA,CAAI,IAAA;AAAA,MACA,CAAA,SAAA,EAAY,MAAM,CAAA,8CAAA,EAAiD,WAAW,CAAA,EAAA;AAAA,KAClF;AACA,IAAA,OAAO,WAAA;AAAA,EACX;AAEA,EAAA,OAAO,cAAA;AACX;AAaO,SAAS,eAAA,CACZ,SACA,gBAAA,EACkB;AAClB,EAAA,IAAI,CAAC,gBAAA,EAAkB;AACnB,IAAA,OAAO,MAAA;AAAA,EACX;AAEA,EAAA,IAAI,OAAA,CAAQ,UAAA,CAAW,OAAO,CAAA,EAAG;AAC7B,IAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,KAAA,CAAM,GAAG,EAAE,CAAC,CAAA;AACrC,IAAA,MAAM,UAAA,GAAa,yCAAyC,QAAQ,CAAA,CAAA;AACpE,IAAA,OAAO,gBAAA,CAAiB,IAAA,CAAK,CAAC,MAAA,KAAW,WAAW,UAAU,CAAA;AAAA,EAClE,CAAA,MAAO;AACH,IAAA,OAAO,gBAAA,CAAiB,IAAA,CAAK,CAAC,MAAA,KAAW,WAAW,OAAO,CAAA;AAAA,EAC/D;AACJ;;;;"}
package/README.md CHANGED
@@ -34,7 +34,6 @@ const vectorLayer = new VectorLayer({
34
34
  source: vectorSourceFactory.createVectorSource({
35
35
  baseUrl: "https://ogc-api.nrw.de/inspire-us-kindergarten/v1",
36
36
  collectionId: "governmentalservice",
37
- crs: "http://www.opengis.net/def/crs/EPSG/0/25832",
38
37
  attributions:
39
38
  "<a href='https://www.govdata.de/dl-de/by-2-0'>Datenlizenz Deutschland - Namensnennung - Version 2.0</a>",
40
39
 
@@ -72,7 +71,6 @@ Example:
72
71
  vectorSourceFactory.createVectorSource({
73
72
  baseUrl: "https://ogc-api.nrw.de/inspire-us-kindergarten/v1",
74
73
  collectionId: "governmentalservice",
75
- crs: "http://www.opengis.net/def/crs/EPSG/0/25832",
76
74
 
77
75
  strategy: "offset",
78
76
  limit: 2500,
@@ -94,7 +92,6 @@ Example:
94
92
  vectorSourceFactory.createVectorSource({
95
93
  baseUrl: "https://ogc-api.nrw.de/inspire-us-kindergarten/v1",
96
94
  collectionId: "governmentalservice",
97
- crs: "http://www.opengis.net/def/crs/EPSG/0/25832",
98
95
 
99
96
  rewriteUrl(url) {
100
97
  url.searchParams.set("property", "value");
@@ -1,4 +1,6 @@
1
- const sourceId = "@open-pioneer/ogc-features/createVectorSource";
1
+ const sourceId$1 = "@open-pioneer/ogc-features/createVectorSource";
2
2
 
3
- export { sourceId };
3
+ const sourceId = "@open-pioneer/ogc-features/Metadata";
4
+
5
+ export { sourceId, sourceId$1 };
4
6
  //# sourceMappingURL=source-info.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"source-info.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
1
+ {"version":3,"file":"source-info.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
package/api.d.ts CHANGED
@@ -23,8 +23,11 @@ export interface OgcFeatureVectorSourceOptions {
23
23
  baseUrl: string;
24
24
  /** The collection-ID */
25
25
  collectionId: string;
26
- /** the URL to the EPSG-Code, e.g. http://www.opengis.net/def/crs/EPSG/0/25832 */
27
- crs: string;
26
+ /**
27
+ * The URL to the EPSG-Code, e.g. http://www.opengis.net/def/crs/EPSG/0/25832
28
+ * If not provided, the vector source will attempt to use the map's CRS if supported by the collection, or fall back to CRS84 otherwise.
29
+ */
30
+ crs?: string;
28
31
  /**
29
32
  * The maximum number of features to fetch within a single request.
30
33
  * Corresponds to the `limit` parameter in the URL.
@@ -38,7 +41,13 @@ export interface OgcFeatureVectorSourceOptions {
38
41
  limit?: number;
39
42
  /** The maximum number of concurrent requests. Defaults to `6`. */
40
43
  maxConcurrentRequests?: number;
41
- /** Optional attribution for the layer (e.g. copyright hints). */
44
+ /**
45
+ * Optional attribution for the vector source (e.g. copyright hints).
46
+ *
47
+ * If set, this property is passed to the map as attribution information for this vector source.
48
+ * If the property is not set or `undefined`, the vector source will check if the collection metadata contains a non-standard `attribution` property and use it as attribution.
49
+ * Setting the this value to the empty string will explicitly disable attributions for this vector source, even if the collection metadata contains an `attribution` property.
50
+ */
42
51
  attributions?: AttributionLike | undefined;
43
52
  /** Optional additional options for the VectorSource. */
44
53
  additionalOptions?: Options<Feature<Geometry>>;
@@ -5,6 +5,7 @@ import VectorSource from "ol/source/Vector";
5
5
  import { OgcFeatureVectorSourceOptions } from "./api";
6
6
  import { CollectionInfos, getCollectionInfos } from "./OffsetStrategy";
7
7
  import { FeatureResponse, queryFeatures } from "./requestUtils";
8
+ import { getCollectionMetadata } from "./Metadata";
8
9
  /**
9
10
  * This function creates an OpenLayers VectorSource for OGC API Features services to be used inside
10
11
  * an OpenLayers VectorLayer.
@@ -22,6 +23,7 @@ export interface InternalOptions {
22
23
  queryFeaturesParam?: QueryFeaturesFunc | undefined;
23
24
  addFeaturesParam?: AddFeaturesFunc | undefined;
24
25
  getCollectionInfosParam?: GetCollectionInfosFunc | undefined;
26
+ getCollectionMetadataParam?: GetCollectionMetadataFunc | undefined;
25
27
  }
26
28
  /**
27
29
  * @internal
@@ -35,6 +37,8 @@ type QueryFeaturesFunc = typeof queryFeatures;
35
37
  /** @internal **/
36
38
  type GetCollectionInfosFunc = typeof getCollectionInfos;
37
39
  /** @internal **/
40
+ type GetCollectionMetadataFunc = typeof getCollectionMetadata;
41
+ /** @internal **/
38
42
  type AddFeaturesFunc = (features: FeatureLike[]) => void;
39
43
  /** @internal **/
40
44
  export interface LoadFeatureOptions {
@@ -2,13 +2,14 @@ import { createLogger, isAbortError } from '@open-pioneer/core';
2
2
  import GeoJSON from 'ol/format/GeoJSON.js';
3
3
  import { bbox } from 'ol/loadingstrategy.js';
4
4
  import VectorSource from 'ol/source/Vector.js';
5
- import { sourceId } from './_virtual/source-info.js';
5
+ import { sourceId$1 as sourceId } from './_virtual/source-info.js';
6
6
  import { getCollectionInfos, loadAllFeaturesWithOffset } from './OffsetStrategy.js';
7
7
  import { createCollectionRequestUrl, queryFeatures } from './requestUtils.js';
8
+ import { getCollectionMetadata, getRequestCrs } from './Metadata.js';
8
9
 
9
10
  const LOG = createLogger(sourceId);
10
11
  const DEFAULT_LIMIT = 5e3;
11
- const DEFAULT_CONCURRENTY = 6;
12
+ const DEFAULT_CONCURRENCY = 6;
12
13
  function createVectorSource(options, httpService) {
13
14
  return _createVectorSource(options, { httpService });
14
15
  }
@@ -23,13 +24,47 @@ function _createVectorSource(options, internals) {
23
24
  });
24
25
  const queryFeaturesFunc = internals.queryFeaturesParam ?? queryFeatures;
25
26
  const getCollectionInfosFunc = internals.getCollectionInfosParam ?? getCollectionInfos;
27
+ const getCollectionMetadataFunc = internals.getCollectionMetadataParam ?? getCollectionMetadata;
26
28
  const addFeaturesFunc = internals.addFeaturesParam || function(features) {
27
29
  LOG.debug(`Adding ${features.length} features`);
28
30
  vectorSrc.addFeatures(features);
29
31
  };
30
32
  let abortController;
31
33
  let collectionInfosPromise;
32
- const loaderFunction = async (extent, _, __, success, failure) => {
34
+ let collectionMetadataPromise;
35
+ const mapCrsToRequestCrs = {};
36
+ const loaderFunction = async (extent, _, projection, successImpl, failureImpl) => {
37
+ const success = (features) => {
38
+ successImpl?.(features);
39
+ vectorSrc.changed();
40
+ };
41
+ const failure = () => {
42
+ failureImpl?.();
43
+ vectorSrc.changed();
44
+ };
45
+ collectionMetadataPromise ??= getCollectionMetadataFunc(
46
+ options.baseUrl,
47
+ options.collectionId,
48
+ httpService
49
+ );
50
+ let collectionMetadata;
51
+ try {
52
+ collectionMetadata = await collectionMetadataPromise;
53
+ } catch (e) {
54
+ LOG.error("Failed to retrieve collection metadata", e);
55
+ failure();
56
+ collectionMetadataPromise = void 0;
57
+ return;
58
+ }
59
+ const mapCrs = projection.getCode();
60
+ const requestCrs = mapCrsToRequestCrs[mapCrs] ??= getRequestCrs(
61
+ mapCrs,
62
+ collectionMetadata?.crs,
63
+ options.crs
64
+ );
65
+ if (vectorSrc.getAttributions() == null && collectionMetadata.attribution) {
66
+ vectorSrc.setAttributions(collectionMetadata.attribution);
67
+ }
33
68
  collectionInfosPromise ??= getCollectionInfosFunc(collectionItemsURL, httpService);
34
69
  let collectionInfos;
35
70
  try {
@@ -45,7 +80,7 @@ function _createVectorSource(options, internals) {
45
80
  const fullURL = createCollectionRequestUrl(
46
81
  collectionItemsURL,
47
82
  extent,
48
- options.crs,
83
+ requestCrs,
49
84
  options.rewriteUrl
50
85
  );
51
86
  let strategy = options?.strategy || (collectionInfos?.supportsOffsetStrategy ? "offset" : "next");
@@ -60,7 +95,7 @@ function _createVectorSource(options, internals) {
60
95
  queryFeatures: queryFeaturesFunc,
61
96
  addFeatures: addFeaturesFunc,
62
97
  limit: options.limit ?? DEFAULT_LIMIT,
63
- maxConcurrentRequests: options.maxConcurrentRequests ?? DEFAULT_CONCURRENTY,
98
+ maxConcurrentRequests: options.maxConcurrentRequests ?? DEFAULT_CONCURRENCY,
64
99
  signal: abortController.signal,
65
100
  collectionInfos
66
101
  });
@@ -72,8 +107,8 @@ function _createVectorSource(options, internals) {
72
107
  } else {
73
108
  LOG.debug("Query-Feature-Request aborted", e);
74
109
  vectorSrc.removeLoadedExtent(extent);
75
- failure?.();
76
110
  }
111
+ failure?.();
77
112
  }
78
113
  };
79
114
  vectorSrc.setLoader(loaderFunction);
@@ -1 +1 @@
1
- {"version":3,"file":"createVectorSource.js","sources":["createVectorSource.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { createLogger, isAbortError } from \"@open-pioneer/core\";\nimport { HttpService } from \"@open-pioneer/http\";\nimport { FeatureLike } from \"ol/Feature\";\nimport { FeatureLoader } from \"ol/featureloader\";\nimport FeatureFormat from \"ol/format/Feature\";\nimport GeoJSON from \"ol/format/GeoJSON\";\nimport { bbox } from \"ol/loadingstrategy\";\nimport VectorSource from \"ol/source/Vector\";\nimport { sourceId } from \"open-pioneer:source-info\";\nimport { OgcFeatureVectorSourceOptions } from \"./api\";\nimport { CollectionInfos, getCollectionInfos, loadAllFeaturesWithOffset } from \"./OffsetStrategy\";\nimport { FeatureResponse, createCollectionRequestUrl, queryFeatures } from \"./requestUtils\";\n\nconst LOG = createLogger(sourceId);\nconst DEFAULT_LIMIT = 5000;\nconst DEFAULT_CONCURRENTY = 6;\n\n/**\n * This function creates an OpenLayers VectorSource for OGC API Features services to be used inside\n * an OpenLayers VectorLayer.\n *\n * @param options Options for the vector source.\n * @param httpService Reference to httpService for fetching the features from the service.\n */\nexport function createVectorSource(\n options: OgcFeatureVectorSourceOptions,\n httpService: HttpService\n): VectorSource {\n return _createVectorSource(options, { httpService });\n}\n\n/**\n * @internal\n * Exported for tests\n */\nexport interface InternalOptions {\n httpService: HttpService;\n queryFeaturesParam?: QueryFeaturesFunc | undefined;\n addFeaturesParam?: AddFeaturesFunc | undefined;\n getCollectionInfosParam?: GetCollectionInfosFunc | undefined;\n}\n\n/**\n * @internal\n * Creates the actual vector source.\n * Exported for testing.\n * Exposes `queryFeatures`, `addFeatures` and `getCollectionInfos` for easier testing.\n */\nexport function _createVectorSource(\n options: OgcFeatureVectorSourceOptions,\n internals: InternalOptions\n): VectorSource {\n const httpService = internals.httpService;\n const collectionItemsURL = `${options.baseUrl}/collections/${options.collectionId}/items?`;\n const vectorSrc = new VectorSource({\n format: new GeoJSON(),\n strategy: bbox,\n attributions: options.attributions,\n ...options.additionalOptions\n });\n\n const queryFeaturesFunc = internals.queryFeaturesParam ?? queryFeatures;\n const getCollectionInfosFunc = internals.getCollectionInfosParam ?? getCollectionInfos;\n const addFeaturesFunc =\n internals.addFeaturesParam ||\n function (features: FeatureLike[]) {\n LOG.debug(`Adding ${features.length} features`);\n\n // Type mismatch FeatureLike <--> Feature<Geometry>\n // MIGHT be incorrect! We will see.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n vectorSrc.addFeatures(features as any);\n };\n\n // Abort controller for the currently pending request(s).\n // Used to cancel outdated requests.\n let abortController: AbortController;\n let collectionInfosPromise: Promise<CollectionInfos | undefined> | undefined;\n\n const loaderFunction: FeatureLoader = async (\n extent,\n _,\n __,\n success,\n failure\n ): Promise<void> => {\n collectionInfosPromise ??= getCollectionInfosFunc(collectionItemsURL, httpService);\n let collectionInfos;\n try {\n collectionInfos = await collectionInfosPromise;\n } catch (e) {\n LOG.error(\"Failed to retrieve collection information\", e);\n failure?.();\n collectionInfosPromise = undefined;\n return;\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 abortController?.abort(\"Extent changed\");\n abortController = new AbortController();\n\n const fullURL = createCollectionRequestUrl(\n collectionItemsURL,\n extent,\n options.crs,\n options.rewriteUrl\n );\n\n let strategy =\n options?.strategy || (collectionInfos?.supportsOffsetStrategy ? \"offset\" : \"next\");\n\n if (strategy === \"offset\" && !collectionInfos?.supportsOffsetStrategy) {\n strategy = \"next\";\n }\n\n try {\n const features = await loadAllFeatures(strategy, {\n fullURL: fullURL.toString(),\n httpService: httpService,\n featureFormat: vectorSrc.getFormat(),\n queryFeatures: queryFeaturesFunc,\n addFeatures: addFeaturesFunc,\n limit: options.limit ?? DEFAULT_LIMIT,\n maxConcurrentRequests: options.maxConcurrentRequests ?? DEFAULT_CONCURRENTY,\n signal: abortController.signal,\n collectionInfos: collectionInfos\n });\n // Type mismatch FeatureLike <--> Feature<Geometry>\n // MIGHT be incorrect! We will see.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n success?.(features as any);\n LOG.debug(\"Finished loading features for extent:\", extent);\n } catch (e) {\n if (!isAbortError(e)) {\n LOG.error(\"Failed to load features\", e);\n } else {\n LOG.debug(\"Query-Feature-Request aborted\", e);\n vectorSrc.removeLoadedExtent(extent);\n failure?.();\n }\n }\n };\n vectorSrc.setLoader(loaderFunction);\n return vectorSrc;\n}\n\n/** @internal **/\ntype QueryFeaturesFunc = typeof queryFeatures;\n/** @internal **/\ntype GetCollectionInfosFunc = typeof getCollectionInfos;\n/** @internal **/\ntype AddFeaturesFunc = (features: FeatureLike[]) => void;\n\n/** @internal **/\nexport interface LoadFeatureOptions {\n fullURL: string;\n httpService: HttpService;\n featureFormat: FeatureFormat | null;\n queryFeatures: QueryFeaturesFunc;\n addFeatures: AddFeaturesFunc;\n limit: number;\n maxConcurrentRequests: number;\n signal?: AbortSignal;\n collectionInfos?: CollectionInfos;\n}\n\n/**\n * @internal\n * Fetches _all_ features according to the given strategy.\n */\nfunction loadAllFeatures(\n strategy: \"next\" | \"offset\",\n options: LoadFeatureOptions\n): Promise<FeatureLike[]> {\n switch (strategy) {\n case \"next\":\n return loadAllFeaturesNextStrategy(options);\n case \"offset\":\n return loadAllFeaturesWithOffset(options);\n }\n}\n\n/**\n * @internal\n * Fetches features by following the `next` links in the server's response.\n */\nexport async function loadAllFeaturesNextStrategy(\n options: Omit<LoadFeatureOptions, \"offsetRequestProps\" | \"collectionInfos\">\n): Promise<FeatureLike[]> {\n const limit = options.limit;\n\n let url = new URL(options.fullURL);\n url.searchParams.set(\"limit\", limit.toString());\n let allFeatures: FeatureLike[] = [];\n do {\n const featureResp = await loadPages(\n [url.toString()],\n options.featureFormat,\n options.httpService,\n options.signal,\n options.addFeatures,\n options.queryFeatures\n );\n\n allFeatures = allFeatures.concat(featureResp.features);\n if (!featureResp.nextURL) {\n break;\n }\n\n url = new URL(featureResp.nextURL);\n // eslint-disable-next-line no-constant-condition\n } while (1);\n return allFeatures;\n}\n\nexport async function loadFeatures(\n requestUrl: string,\n featureFormat: FeatureFormat,\n httpService: HttpService,\n signal: AbortSignal | undefined,\n addFeaturesFunc: AddFeaturesFunc,\n queryFeaturesFunc: QueryFeaturesFunc = queryFeatures\n): Promise<FeatureResponse> {\n const featureResponse = await queryFeaturesFunc(requestUrl, featureFormat, httpService, signal);\n const features = featureResponse.features as FeatureLike[];\n addFeaturesFunc(features);\n return featureResponse;\n}\n\n/**\n * Loads features from multiple urls in parallel.\n * The URLs should represent pages of the same result set.\n * The `nextURL` of the last page (if any) is returned from this function.\n *\n * @internal\n */\nexport async function loadPages(\n allUrls: string[],\n featureFormat: FeatureFormat | null,\n httpService: HttpService,\n signal: AbortSignal | undefined,\n addFeaturesFunc: AddFeaturesFunc,\n queryFeaturesFunc: QueryFeaturesFunc = queryFeatures\n): Promise<FeatureResponse> {\n const allFeatureResponse: FeatureResponse = {\n nextURL: undefined,\n numberMatched: undefined,\n features: []\n };\n const allRequestPromises = allUrls.map(async (singleUrl, index): Promise<void> => {\n const isLast = index === allUrls.length - 1;\n\n const featureResponse = await queryFeaturesFunc(\n singleUrl,\n featureFormat,\n httpService,\n signal\n );\n addFeaturesFunc(featureResponse.features as FeatureLike[]);\n\n LOG.debug(\n `NextURL for index = ${index} (isLast = ${isLast}): ${\n featureResponse.nextURL || \"No Next URL\"\n }`\n );\n allFeatureResponse.features.push(...featureResponse.features);\n if (isLast) {\n allFeatureResponse.numberMatched = featureResponse.numberMatched;\n allFeatureResponse.nextURL = featureResponse.nextURL;\n }\n });\n await Promise.all(allRequestPromises);\n return allFeatureResponse;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"createVectorSource.js","sources":["createVectorSource.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { createLogger, isAbortError } from \"@open-pioneer/core\";\nimport { HttpService } from \"@open-pioneer/http\";\nimport { FeatureLike } from \"ol/Feature\";\nimport { FeatureLoader } from \"ol/featureloader\";\nimport FeatureFormat from \"ol/format/Feature\";\nimport GeoJSON from \"ol/format/GeoJSON\";\nimport { bbox } from \"ol/loadingstrategy\";\nimport VectorSource from \"ol/source/Vector\";\nimport { sourceId } from \"open-pioneer:source-info\";\nimport { OgcFeatureVectorSourceOptions } from \"./api\";\nimport { CollectionInfos, getCollectionInfos, loadAllFeaturesWithOffset } from \"./OffsetStrategy\";\nimport { FeatureResponse, createCollectionRequestUrl, queryFeatures } from \"./requestUtils\";\nimport { CollectionMetadata, getCollectionMetadata, getRequestCrs } from \"./Metadata\";\n\nconst LOG = createLogger(sourceId);\nconst DEFAULT_LIMIT = 5000;\nconst DEFAULT_CONCURRENCY = 6;\n\n/**\n * This function creates an OpenLayers VectorSource for OGC API Features services to be used inside\n * an OpenLayers VectorLayer.\n *\n * @param options Options for the vector source.\n * @param httpService Reference to httpService for fetching the features from the service.\n */\nexport function createVectorSource(\n options: OgcFeatureVectorSourceOptions,\n httpService: HttpService\n): VectorSource {\n return _createVectorSource(options, { httpService });\n}\n\n/**\n * @internal\n * Exported for tests\n */\nexport interface InternalOptions {\n httpService: HttpService;\n queryFeaturesParam?: QueryFeaturesFunc | undefined;\n addFeaturesParam?: AddFeaturesFunc | undefined;\n getCollectionInfosParam?: GetCollectionInfosFunc | undefined;\n getCollectionMetadataParam?: GetCollectionMetadataFunc | undefined;\n}\n\n/**\n * @internal\n * Creates the actual vector source.\n * Exported for testing.\n * Exposes `queryFeatures`, `addFeatures` and `getCollectionInfos` for easier testing.\n */\nexport function _createVectorSource(\n options: OgcFeatureVectorSourceOptions,\n internals: InternalOptions\n): VectorSource {\n const httpService = internals.httpService;\n const collectionItemsURL = `${options.baseUrl}/collections/${options.collectionId}/items?`;\n const vectorSrc = new VectorSource({\n format: new GeoJSON(),\n strategy: bbox,\n attributions: options.attributions,\n ...options.additionalOptions\n });\n\n const queryFeaturesFunc = internals.queryFeaturesParam ?? queryFeatures;\n const getCollectionInfosFunc = internals.getCollectionInfosParam ?? getCollectionInfos;\n const getCollectionMetadataFunc = internals.getCollectionMetadataParam ?? getCollectionMetadata;\n const addFeaturesFunc =\n internals.addFeaturesParam ||\n function (features: FeatureLike[]) {\n LOG.debug(`Adding ${features.length} features`);\n\n // Type mismatch FeatureLike <--> Feature<Geometry>\n // MIGHT be incorrect! We will see.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n vectorSrc.addFeatures(features as any);\n };\n\n // Abort controller for the currently pending request(s).\n // Used to cancel outdated requests.\n let abortController: AbortController;\n let collectionInfosPromise: Promise<CollectionInfos | undefined> | undefined;\n\n let collectionMetadataPromise: Promise<CollectionMetadata> | 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 const mapCrsToRequestCrs: Record<string, string> = {};\n\n const loaderFunction: FeatureLoader = async (\n extent,\n _,\n projection,\n successImpl,\n failureImpl\n ): Promise<void> => {\n const success = (features: FeatureLike[]) => {\n successImpl?.(features);\n vectorSrc.changed();\n };\n\n const failure = () => {\n failureImpl?.();\n vectorSrc.changed(); // Always trigger changed event to unstuck loading state\n };\n\n collectionMetadataPromise ??= getCollectionMetadataFunc(\n options.baseUrl,\n options.collectionId,\n httpService\n );\n\n let collectionMetadata;\n try {\n collectionMetadata = await collectionMetadataPromise;\n } catch (e) {\n LOG.error(\"Failed to retrieve collection metadata\", e);\n failure();\n collectionMetadataPromise = undefined;\n return;\n }\n\n const mapCrs = projection.getCode();\n const requestCrs = (mapCrsToRequestCrs[mapCrs] ??= getRequestCrs(\n mapCrs,\n collectionMetadata?.crs,\n options.crs\n ));\n if (vectorSrc.getAttributions() == null && collectionMetadata.attribution) {\n vectorSrc.setAttributions(collectionMetadata.attribution);\n }\n\n collectionInfosPromise ??= getCollectionInfosFunc(collectionItemsURL, httpService);\n let collectionInfos;\n try {\n collectionInfos = await collectionInfosPromise;\n } catch (e) {\n LOG.error(\"Failed to retrieve collection information\", e);\n failure?.();\n collectionInfosPromise = undefined;\n return;\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 abortController?.abort(\"Extent changed\");\n abortController = new AbortController();\n\n const fullURL = createCollectionRequestUrl(\n collectionItemsURL,\n extent,\n requestCrs,\n options.rewriteUrl\n );\n\n let strategy =\n options?.strategy || (collectionInfos?.supportsOffsetStrategy ? \"offset\" : \"next\");\n\n if (strategy === \"offset\" && !collectionInfos?.supportsOffsetStrategy) {\n strategy = \"next\";\n }\n\n try {\n const features = await loadAllFeatures(strategy, {\n fullURL: fullURL.toString(),\n httpService: httpService,\n featureFormat: vectorSrc.getFormat(),\n queryFeatures: queryFeaturesFunc,\n addFeatures: addFeaturesFunc,\n limit: options.limit ?? DEFAULT_LIMIT,\n maxConcurrentRequests: options.maxConcurrentRequests ?? DEFAULT_CONCURRENCY,\n signal: abortController.signal,\n collectionInfos: collectionInfos\n });\n // Type mismatch FeatureLike <--> Feature<Geometry>\n // MIGHT be incorrect! We will see.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n success?.(features as any);\n LOG.debug(\"Finished loading features for extent:\", extent);\n } catch (e) {\n if (!isAbortError(e)) {\n LOG.error(\"Failed to load features\", e);\n } else {\n LOG.debug(\"Query-Feature-Request aborted\", e);\n vectorSrc.removeLoadedExtent(extent);\n }\n failure?.();\n }\n };\n vectorSrc.setLoader(loaderFunction);\n return vectorSrc;\n}\n\n/** @internal **/\ntype QueryFeaturesFunc = typeof queryFeatures;\n/** @internal **/\ntype GetCollectionInfosFunc = typeof getCollectionInfos;\n/** @internal **/\ntype GetCollectionMetadataFunc = typeof getCollectionMetadata;\n/** @internal **/\ntype AddFeaturesFunc = (features: FeatureLike[]) => void;\n\n/** @internal **/\nexport interface LoadFeatureOptions {\n fullURL: string;\n httpService: HttpService;\n featureFormat: FeatureFormat | null;\n queryFeatures: QueryFeaturesFunc;\n addFeatures: AddFeaturesFunc;\n limit: number;\n maxConcurrentRequests: number;\n signal?: AbortSignal;\n collectionInfos?: CollectionInfos;\n}\n\n/**\n * @internal\n * Fetches _all_ features according to the given strategy.\n */\nfunction loadAllFeatures(\n strategy: \"next\" | \"offset\",\n options: LoadFeatureOptions\n): Promise<FeatureLike[]> {\n switch (strategy) {\n case \"next\":\n return loadAllFeaturesNextStrategy(options);\n case \"offset\":\n return loadAllFeaturesWithOffset(options);\n }\n}\n\n/**\n * @internal\n * Fetches features by following the `next` links in the server's response.\n */\nexport async function loadAllFeaturesNextStrategy(\n options: Omit<LoadFeatureOptions, \"offsetRequestProps\" | \"collectionInfos\">\n): Promise<FeatureLike[]> {\n const limit = options.limit;\n\n let url = new URL(options.fullURL);\n url.searchParams.set(\"limit\", limit.toString());\n let allFeatures: FeatureLike[] = [];\n do {\n const featureResp = await loadPages(\n [url.toString()],\n options.featureFormat,\n options.httpService,\n options.signal,\n options.addFeatures,\n options.queryFeatures\n );\n\n allFeatures = allFeatures.concat(featureResp.features);\n if (!featureResp.nextURL) {\n break;\n }\n\n url = new URL(featureResp.nextURL);\n // eslint-disable-next-line no-constant-condition\n } while (1);\n return allFeatures;\n}\n\nexport async function loadFeatures(\n requestUrl: string,\n featureFormat: FeatureFormat,\n httpService: HttpService,\n signal: AbortSignal | undefined,\n addFeaturesFunc: AddFeaturesFunc,\n queryFeaturesFunc: QueryFeaturesFunc = queryFeatures\n): Promise<FeatureResponse> {\n const featureResponse = await queryFeaturesFunc(requestUrl, featureFormat, httpService, signal);\n const features = featureResponse.features as FeatureLike[];\n addFeaturesFunc(features);\n return featureResponse;\n}\n\n/**\n * Loads features from multiple urls in parallel.\n * The URLs should represent pages of the same result set.\n * The `nextURL` of the last page (if any) is returned from this function.\n *\n * @internal\n */\nexport async function loadPages(\n allUrls: string[],\n featureFormat: FeatureFormat | null,\n httpService: HttpService,\n signal: AbortSignal | undefined,\n addFeaturesFunc: AddFeaturesFunc,\n queryFeaturesFunc: QueryFeaturesFunc = queryFeatures\n): Promise<FeatureResponse> {\n const allFeatureResponse: FeatureResponse = {\n nextURL: undefined,\n numberMatched: undefined,\n features: []\n };\n const allRequestPromises = allUrls.map(async (singleUrl, index): Promise<void> => {\n const isLast = index === allUrls.length - 1;\n\n const featureResponse = await queryFeaturesFunc(\n singleUrl,\n featureFormat,\n httpService,\n signal\n );\n addFeaturesFunc(featureResponse.features as FeatureLike[]);\n\n LOG.debug(\n `NextURL for index = ${index} (isLast = ${isLast}): ${\n featureResponse.nextURL || \"No Next URL\"\n }`\n );\n allFeatureResponse.features.push(...featureResponse.features);\n if (isLast) {\n allFeatureResponse.numberMatched = featureResponse.numberMatched;\n allFeatureResponse.nextURL = featureResponse.nextURL;\n }\n });\n await Promise.all(allRequestPromises);\n return allFeatureResponse;\n}\n"],"names":[],"mappings":"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"}
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.20260211111005",
4
+ "version": "1.3.0-dev.20260225083007",
5
5
  "description": "This package provides utilities to work with OGC API Features services.",
6
6
  "keywords": [
7
7
  "open-pioneer-trails"