@open-pioneer/ogc-features 0.4.0 → 0.4.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 CHANGED
@@ -1,5 +1,26 @@
1
1
  # @open-pioneer/ogc-features
2
2
 
3
+ ## 0.4.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 4140646: Update trails dependencies
8
+ - 4140646: Update to react 18.3.1
9
+ - 6977f0b: Added `strategy` and `rewriteUrl` options to the vector source factory.
10
+ - 81bc7da: Update trails dependencies
11
+ - 2c092dc: Update dependencies
12
+ - Updated dependencies [4140646]
13
+ - Updated dependencies [4140646]
14
+ - Updated dependencies [81bc7da]
15
+ - Updated dependencies [2c092dc]
16
+ - @open-pioneer/search@0.4.2
17
+
18
+ ## 0.4.1
19
+
20
+ ### Patch Changes
21
+
22
+ - @open-pioneer/search@0.4.1
23
+
3
24
  ## 0.4.0
4
25
 
5
26
  ### Minor Changes
package/OffsetStrategy.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { loadPages } from './createVectorSource.js';
2
- import { getNextURL, createOffsetURL } from './requestUtils.js';
2
+ import { createOffsetURL, getNextURL } from './requestUtils.js';
3
3
 
4
4
  async function getCollectionInfos(collectionsItemsUrl, httpService) {
5
5
  const infos = {
@@ -63,7 +63,7 @@ async function fetchJson(httpService, url, signal) {
63
63
  const response = await httpService.fetch(url, {
64
64
  signal,
65
65
  headers: {
66
- "Accept": "application/json"
66
+ Accept: "application/json"
67
67
  }
68
68
  });
69
69
  if (!response.ok) {
@@ -1 +1 @@
1
- {"version":3,"file":"OgcFeatureSearchSource.js","sources":["OgcFeatureSearchSource.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { isAbortError } from \"@open-pioneer/core\";\nimport { SearchSource, SearchResult, SearchOptions } from \"@open-pioneer/search\";\nimport { v4 as uuid4v } from \"uuid\";\nimport GeoJSON from \"ol/format/GeoJSON\";\nimport { OgcFeatureSearchSourceOptions } from \"./api\";\nimport { HttpService } from \"@open-pioneer/http\";\n\n/** The general shape of features returned by an OGC API Features service. */\nexport interface FeatureResponse {\n /**\n * The type of the feature (e.g. `Feature`).\n */\n type: string;\n\n /**\n * The id of the feature.\n */\n id: string | number;\n\n /**\n * The geometry of the feature.\n */\n geometry: unknown;\n\n /**\n * The properties of the feature.\n */\n properties: Readonly<Record<string, unknown>>;\n}\n\n/**\n * An implementation of {@link SearchSource} that searches on an OGC Features API service.\n */\nexport class OgcFeatureSearchSource implements SearchSource {\n readonly label: string;\n #options: OgcFeatureSearchSourceOptions;\n #httpService: HttpService;\n #baseUrl: string;\n #params: URLSearchParams;\n\n constructor(options: OgcFeatureSearchSourceOptions, httpService: HttpService) {\n this.label = options.label;\n this.#options = options;\n this.#httpService = httpService;\n\n const { baseUrl, params } = getBaseUrl(options.baseUrl);\n this.#baseUrl = baseUrl;\n this.#params = params;\n }\n\n async search(\n inputValue: string,\n { mapProjection, maxResults, signal }: SearchOptions\n ): Promise<SearchResult[]> {\n const url = this.#getUrl(inputValue, maxResults);\n const geojson = new GeoJSON({\n dataProjection: \"EPSG:4326\",\n featureProjection: mapProjection\n });\n\n const responses = await fetchJson(this.#httpService, url, signal);\n return responses.features.map((feature) => this.#createResult(feature, geojson));\n }\n\n #createResult(feature: FeatureResponse, geojson: GeoJSON): SearchResult {\n const customLabel = this.#options.renderLabel?.(feature);\n\n const singleLabelProperty =\n feature.properties[this.#options.labelProperty as keyof typeof feature.properties];\n\n const singleSearchProperty =\n feature.properties[this.#options.searchProperty as keyof typeof feature.properties];\n\n const label = (() => {\n if (customLabel) {\n return customLabel;\n } else if (singleLabelProperty !== undefined) {\n return String(singleLabelProperty);\n } else if (singleSearchProperty !== undefined) {\n return String(singleSearchProperty);\n } else {\n return \"\";\n }\n })();\n\n return {\n id: feature.id ?? uuid4v(),\n label: label,\n geometry: geojson.readGeometry(feature.geometry),\n properties: feature.properties\n };\n }\n\n #getUrl(inputValue: string, limit: number): URL {\n const url = new URL(`${this.#baseUrl}/collections/${this.#options.collectionId}/items`);\n\n for (const [k, v] of this.#params) {\n url.searchParams.append(k, v);\n }\n url.searchParams.set(this.#options.searchProperty, `*${inputValue}*`);\n url.searchParams.set(\"limit\", String(limit));\n url.searchParams.set(\"f\", \"json\");\n\n // Passing a copy of the original URL to prevent accidental modifications.\n // Users should return a new URL instead.\n return this.#options.rewriteUrl?.(new URL(url)) ?? url;\n }\n}\n\n// Exported for test\nexport interface SearchResponse {\n features: FeatureResponse[];\n links?: Record<string, unknown>[];\n numberMatched?: number;\n numberReturned?: number;\n timeStamp?: string;\n type?: string;\n}\n\nasync function fetchJson(\n httpService: HttpService,\n url: URL,\n signal?: AbortSignal | undefined\n): Promise<SearchResponse> {\n try {\n const response = await httpService.fetch(url, {\n signal,\n headers: {\n \"Accept\": \"application/json\"\n }\n });\n if (!response.ok) {\n throw new Error(\"Request failed with status \" + response.status);\n }\n\n const result = await response.json();\n return result;\n } catch (error) {\n if (isAbortError(error)) {\n throw error;\n }\n throw new Error(\"Failed to search on OGC API Features service\", { cause: error });\n }\n}\n\n/**\n * Splits the base url into a \"clean\" base URL (no query params) and the original query params.\n */\nfunction getBaseUrl(baseUrl: string) {\n const url = new URL(baseUrl);\n const params = new URLSearchParams(url.searchParams);\n url.search = \"\";\n\n const cleanBaseUrl = url.href.replace(/\\/+$/, \"\"); // prevent double slash\n return { baseUrl: cleanBaseUrl, params };\n}\n"],"names":["uuid4v"],"mappings":"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"}
1
+ {"version":3,"file":"OgcFeatureSearchSource.js","sources":["OgcFeatureSearchSource.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { isAbortError } from \"@open-pioneer/core\";\nimport { SearchSource, SearchResult, SearchOptions } from \"@open-pioneer/search\";\nimport { v4 as uuid4v } from \"uuid\";\nimport GeoJSON from \"ol/format/GeoJSON\";\nimport { OgcFeatureSearchSourceOptions } from \"./api\";\nimport { HttpService } from \"@open-pioneer/http\";\n\n/** The general shape of features returned by an OGC API Features service. */\nexport interface FeatureResponse {\n /**\n * The type of the feature (e.g. `Feature`).\n */\n type: string;\n\n /**\n * The id of the feature.\n */\n id: string | number;\n\n /**\n * The geometry of the feature.\n */\n geometry: unknown;\n\n /**\n * The properties of the feature.\n */\n properties: Readonly<Record<string, unknown>>;\n}\n\n/**\n * An implementation of {@link SearchSource} that searches on an OGC Features API service.\n */\nexport class OgcFeatureSearchSource implements SearchSource {\n readonly label: string;\n #options: OgcFeatureSearchSourceOptions;\n #httpService: HttpService;\n #baseUrl: string;\n #params: URLSearchParams;\n\n constructor(options: OgcFeatureSearchSourceOptions, httpService: HttpService) {\n this.label = options.label;\n this.#options = options;\n this.#httpService = httpService;\n\n const { baseUrl, params } = getBaseUrl(options.baseUrl);\n this.#baseUrl = baseUrl;\n this.#params = params;\n }\n\n async search(\n inputValue: string,\n { mapProjection, maxResults, signal }: SearchOptions\n ): Promise<SearchResult[]> {\n const url = this.#getUrl(inputValue, maxResults);\n const geojson = new GeoJSON({\n dataProjection: \"EPSG:4326\",\n featureProjection: mapProjection\n });\n\n const responses = await fetchJson(this.#httpService, url, signal);\n return responses.features.map((feature) => this.#createResult(feature, geojson));\n }\n\n #createResult(feature: FeatureResponse, geojson: GeoJSON): SearchResult {\n const customLabel = this.#options.renderLabel?.(feature);\n\n const singleLabelProperty =\n feature.properties[this.#options.labelProperty as keyof typeof feature.properties];\n\n const singleSearchProperty =\n feature.properties[this.#options.searchProperty as keyof typeof feature.properties];\n\n const label = (() => {\n if (customLabel) {\n return customLabel;\n } else if (singleLabelProperty !== undefined) {\n return String(singleLabelProperty);\n } else if (singleSearchProperty !== undefined) {\n return String(singleSearchProperty);\n } else {\n return \"\";\n }\n })();\n\n return {\n id: feature.id ?? uuid4v(),\n label: label,\n geometry: geojson.readGeometry(feature.geometry),\n properties: feature.properties\n };\n }\n\n #getUrl(inputValue: string, limit: number): URL {\n const url = new URL(`${this.#baseUrl}/collections/${this.#options.collectionId}/items`);\n\n for (const [k, v] of this.#params) {\n url.searchParams.append(k, v);\n }\n url.searchParams.set(this.#options.searchProperty, `*${inputValue}*`);\n url.searchParams.set(\"limit\", String(limit));\n url.searchParams.set(\"f\", \"json\");\n\n // Passing a copy of the original URL to prevent accidental modifications.\n // Users should return a new URL instead.\n return this.#options.rewriteUrl?.(new URL(url)) ?? url;\n }\n}\n\n// Exported for test\nexport interface SearchResponse {\n features: FeatureResponse[];\n links?: Record<string, unknown>[];\n numberMatched?: number;\n numberReturned?: number;\n timeStamp?: string;\n type?: string;\n}\n\nasync function fetchJson(\n httpService: HttpService,\n url: URL,\n signal?: AbortSignal | undefined\n): Promise<SearchResponse> {\n try {\n const response = await httpService.fetch(url, {\n signal,\n headers: {\n Accept: \"application/json\"\n }\n });\n if (!response.ok) {\n throw new Error(\"Request failed with status \" + response.status);\n }\n\n const result = await response.json();\n return result;\n } catch (error) {\n if (isAbortError(error)) {\n throw error;\n }\n throw new Error(\"Failed to search on OGC API Features service\", { cause: error });\n }\n}\n\n/**\n * Splits the base url into a \"clean\" base URL (no query params) and the original query params.\n */\nfunction getBaseUrl(baseUrl: string) {\n const url = new URL(baseUrl);\n const params = new URLSearchParams(url.searchParams);\n url.search = \"\";\n\n const cleanBaseUrl = url.href.replace(/\\/+$/, \"\"); // prevent double slash\n return { baseUrl: cleanBaseUrl, params };\n}\n"],"names":["uuid4v"],"mappings":"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"}
package/README.md CHANGED
@@ -35,36 +35,74 @@ const vectorLayer = new VectorLayer({
35
35
  baseUrl: "https://ogc-api.nrw.de/inspire-us-kindergarten/v1",
36
36
  collectionId: "governmentalservice",
37
37
  crs: "http://www.opengis.net/def/crs/EPSG/0/25832",
38
-
39
- /**
40
- * The maximum number of features to fetch within a single request.
41
- * Corresponds to the `limit` parameter in the URL.
42
- *
43
- * When the `offset` strategy is used for feature fetching, the limit
44
- * is used for the page size.
45
- *
46
- * Defaults to `5000`.
47
- */
48
- limit: 5000,
49
-
50
- /** The maximum number of concurrent requests. Defaults to `6`. */
51
- maxConcurrentRequests: 6,
52
-
53
38
  attributions:
54
39
  "<a href='https://www.govdata.de/dl-de/by-2-0'>Datenlizenz Deutschland - Namensnennung - Version 2.0</a>",
55
40
 
56
- additionalOptions: {} // (Optional)
41
+ /** Optional: number of features loaded per request. */
42
+ limit: 5000,
43
+
44
+ /** Optional: passed to the Open Layers Vector Source constructor. */
45
+ additionalOptions: {},
57
46
  })
58
47
  });
59
48
  ```
60
49
 
61
- The optional `limit` configures the concurrent execution of requests by using the `offset` URL property for pagination.
62
- If the service returns a `numberMatched` property together with its results, it is used alongside the configured pageSize to calculate the optimal number of concurrent requests.
63
- The number of concurrent requests is never higher than `maxConcurrentRequests`.
64
-
65
50
  Additional options of the `VectorSource` (see [OpenLayers documentation](https://openlayers.org/en/latest/apidoc/module-ol_source_Vector-VectorSource.html)) can be given by the property
66
51
  `additionalOptions`.
67
52
 
53
+ #### Loading strategies
54
+
55
+ The vector source supports two different strategies to load features from the server:
56
+
57
+ - `"next"`: Fetch large feature results by walking the `next` link of the previous response.
58
+ This is well supported by most implementations, but can be slow for very large result sets
59
+ because it does not allow for parallel requests.
60
+ - `"offset"`: Fetch large feature results using parallel requests.
61
+ Each request fetches a page of results using an `"offset"` and `"limit"` parameter.
62
+ This can be much faster than the `"next"` strategy, but it is not supported by all server implementations.
63
+
64
+ By default, the vector source will attempt to detect the server's capabilities and will prefer `"offset"`, if supported.
65
+ You can overwrite the default behavior by explicitly defining the `strategy` option.
66
+
67
+ If the `"offset"` strategy is used, you can configure the maximum number of concurrent requests (default: 6).
68
+
69
+ Example:
70
+
71
+ ```ts
72
+ vectorSourceFactory.createVectorSource({
73
+ baseUrl: "https://ogc-api.nrw.de/inspire-us-kindergarten/v1",
74
+ collectionId: "governmentalservice",
75
+ crs: "http://www.opengis.net/def/crs/EPSG/0/25832",
76
+
77
+ strategy: "offset",
78
+ limit: 2500,
79
+ maxConcurrentRequests: 6
80
+ });
81
+ ```
82
+
83
+ #### Rewriting request URLs
84
+
85
+ The optional `rewriteUrl` option can be used to modify the feature requests made by the vector source.
86
+ This is useful, for example, to filter the OGC service on the server side.
87
+
88
+ Note that modifying the vector source's URL requires some care: existing query parameters should not be overwritten unless you know what you're doing.
89
+ The vector source may add additional query parameters in the future, which might conflict the changes done by custom `rewriteUrl` implementations.
90
+
91
+ Example:
92
+
93
+ ```ts
94
+ vectorSourceFactory.createVectorSource({
95
+ baseUrl: "https://ogc-api.nrw.de/inspire-us-kindergarten/v1",
96
+ collectionId: "governmentalservice",
97
+ crs: "http://www.opengis.net/def/crs/EPSG/0/25832",
98
+
99
+ rewriteUrl(url) {
100
+ url.searchParams.set("property", "value");
101
+ return url;
102
+ }
103
+ });
104
+ ```
105
+
68
106
  ### Search source
69
107
 
70
108
  This search source is used to search in OGC API features.
@@ -87,7 +125,7 @@ export default defineBuildConfig({
87
125
  });
88
126
  ```
89
127
 
90
- and create a search search instance:
128
+ and create a search source instance:
91
129
 
92
130
  ```ts
93
131
  const ogcSearchSourceFactory = ...; // injected
package/api.d.ts CHANGED
@@ -4,6 +4,17 @@ import Feature from "ol/Feature";
4
4
  import { Geometry } from "ol/geom";
5
5
  import { AttributionLike } from "ol/source/Source";
6
6
  import VectorSource, { Options } from "ol/source/Vector";
7
+ /**
8
+ * The strategy to fetch features from an OGC API Features service.
9
+ *
10
+ * - `"next"`: Fetch large feature results by walking the `next` link of the previous response.
11
+ * This is well supported by most implementations, but can be slow for very large result sets
12
+ * because it does not allow for parallel requests.
13
+ * - `"offset"`: Fetch large feature results using parallel requests.
14
+ * Each request fetches a page of results using an `"offset"` and `"limit"` parameter.
15
+ * This can be much faster than the `"next"` strategy, but it is not supported by all server implementations.
16
+ */
17
+ export type OgcFetchStrategy = "next" | "offset";
7
18
  /**
8
19
  * These are properties for OGC API Features vector source.
9
20
  */
@@ -31,6 +42,23 @@ export interface OgcFeatureVectorSourceOptions {
31
42
  attributions?: AttributionLike | undefined;
32
43
  /** Optional additional options for the VectorSource. */
33
44
  additionalOptions?: Options<Feature<Geometry>>;
45
+ /**
46
+ * Use this property to define the feature fetching strategy.
47
+ * Allowed value are `offset` and `next`.
48
+ * By default, the vector source attempts to detect the server's capabilities and will prefer `"offset"`, if possible.
49
+ */
50
+ strategy?: OgcFetchStrategy;
51
+ /**
52
+ * Use this function to rewrite the URL used to fetch features from the OGC API Features service.
53
+ * This is useful, for example, to filter the OGC service on the server side.
54
+ *
55
+ * NOTE: Do not update the `url` argument. Return a new `URL` instance instead.
56
+ *
57
+ * NOTE: Be careful with existing URL parameters. The vector source may not work correctly if
58
+ * predefined parameters (such as the CRS or the response format) are overwritten.
59
+ * The vector source might add additional parameters to its request URLs in the future.
60
+ */
61
+ rewriteUrl?: (url: URL) => URL | undefined;
34
62
  }
35
63
  /**
36
64
  * A factory that creates {@link VectorSource | vector sources} for an OGC API Features service.
@@ -44,7 +72,7 @@ export interface OgcFeaturesVectorSourceFactory extends DeclaredService<"ogc-fea
44
72
  */
45
73
  createVectorSource(options: OgcFeatureVectorSourceOptions): VectorSource;
46
74
  }
47
- /** Options for {@link OgcFeatureSearchSource}. */
75
+ /** Options for {@link OgcFeaturesVectorSourceFactory.createVectorSource()}. */
48
76
  export interface OgcFeatureSearchSourceOptions {
49
77
  /** The source's label. May be used as a title for results from this source. */
50
78
  label: string;
@@ -2,8 +2,8 @@ import { createLogger, isAbortError } from '@open-pioneer/core';
2
2
  import GeoJSON from 'ol/format/GeoJSON';
3
3
  import { bbox } from 'ol/loadingstrategy';
4
4
  import VectorSource from 'ol/source/Vector';
5
- import { getCollectionInfos, loadAllFeaturesWithOffset } from './OffsetStrategy.js';
6
- import { queryFeatures, createCollectionRequestUrl } from './requestUtils.js';
5
+ import { loadAllFeaturesWithOffset, getCollectionInfos } from './OffsetStrategy.js';
6
+ import { createCollectionRequestUrl, queryFeatures } from './requestUtils.js';
7
7
 
8
8
  const LOG = createLogger("ogc-features:OgcFeatureSourceFactory");
9
9
  const DEFAULT_LIMIT = 5e3;
@@ -41,8 +41,16 @@ function _createVectorSource(options, internals) {
41
41
  }
42
42
  abortController?.abort("Extent changed");
43
43
  abortController = new AbortController();
44
- const fullURL = createCollectionRequestUrl(collectionItemsURL, extent, options.crs);
45
- const strategy = collectionInfos?.supportsOffsetStrategy ? "offset" : "next";
44
+ const fullURL = createCollectionRequestUrl(
45
+ collectionItemsURL,
46
+ extent,
47
+ options.crs,
48
+ options.rewriteUrl
49
+ );
50
+ let strategy = options?.strategy || (collectionInfos?.supportsOffsetStrategy ? "offset" : "next");
51
+ if (strategy === "offset" && !collectionInfos?.supportsOffsetStrategy) {
52
+ strategy = "next";
53
+ }
46
54
  try {
47
55
  const features = await loadAllFeatures(strategy, {
48
56
  fullURL: fullURL.toString(),
@@ -1 +1 @@
1
- {"version":3,"file":"createVectorSource.js","sources":["createVectorSource.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { createLogger, isAbortError } from \"@open-pioneer/core\";\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 { CollectionInfos, getCollectionInfos, loadAllFeaturesWithOffset } from \"./OffsetStrategy\";\nimport { FeatureResponse, createCollectionRequestUrl, queryFeatures } from \"./requestUtils\";\nimport { OgcFeatureVectorSourceOptions } from \"./api\";\nimport { HttpService } from \"@open-pioneer/http\";\n\nconst LOG = createLogger(\"ogc-features:OgcFeatureSourceFactory\");\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(collectionItemsURL, extent, options.crs);\n const strategy = collectionInfos?.supportsOffsetStrategy ? \"offset\" : \"next\";\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;\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,\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 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { createLogger, isAbortError } from \"@open-pioneer/core\";\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 { CollectionInfos, getCollectionInfos, loadAllFeaturesWithOffset } from \"./OffsetStrategy\";\nimport { FeatureResponse, createCollectionRequestUrl, queryFeatures } from \"./requestUtils\";\nimport { OgcFeatureVectorSourceOptions } from \"./api\";\nimport { HttpService } from \"@open-pioneer/http\";\n\nconst LOG = createLogger(\"ogc-features:OgcFeatureSourceFactory\");\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;\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,\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": "0.4.0",
4
+ "version": "0.4.2",
5
5
  "description": "This package provides utilities to work with OGC API Features services.",
6
6
  "keywords": [
7
7
  "open-pioneer-trails"
@@ -14,11 +14,11 @@
14
14
  "directory": "src/packages/ogc-features"
15
15
  },
16
16
  "peerDependencies": {
17
- "@open-pioneer/core": "^1.2.1",
18
- "@open-pioneer/http": "^2.1.4",
17
+ "@open-pioneer/core": "^1.2.3",
18
+ "@open-pioneer/http": "^2.1.7",
19
19
  "ol": "^9.0.0",
20
20
  "uuid": "^9.0.1",
21
- "@open-pioneer/search": "^0.4.0"
21
+ "@open-pioneer/search": "^0.4.2"
22
22
  },
23
23
  "peerDependenciesMeta": {
24
24
  "@open-pioneer/search": {
package/requestUtils.d.ts CHANGED
@@ -5,7 +5,7 @@ import { HttpService } from "@open-pioneer/http";
5
5
  /**
6
6
  * Assembles the url to use for fetching features in the given extent.
7
7
  */
8
- export declare function createCollectionRequestUrl(collectionItemsURL: string, extent: Extent, crs: string): URL;
8
+ export declare function createCollectionRequestUrl(collectionItemsURL: string, extent: Extent, crs: string, rewriteUrl?: (url: URL) => URL | undefined): URL;
9
9
  /**
10
10
  * Adds (or replaces) offset/limit params on the given url.
11
11
  */
package/requestUtils.js CHANGED
@@ -1,12 +1,12 @@
1
1
  const NEXT_LINK_PROP = "next";
2
- function createCollectionRequestUrl(collectionItemsURL, extent, crs) {
2
+ function createCollectionRequestUrl(collectionItemsURL, extent, crs, rewriteUrl) {
3
3
  const urlObj = new URL(collectionItemsURL);
4
4
  const searchParams = urlObj.searchParams;
5
5
  searchParams.set("bbox", extent.join(","));
6
6
  searchParams.set("bbox-crs", crs);
7
7
  searchParams.set("crs", crs);
8
8
  searchParams.set("f", "json");
9
- return urlObj;
9
+ return rewriteUrl?.(new URL(urlObj)) ?? urlObj;
10
10
  }
11
11
  function createOffsetURL(fullURL, offset, pageSize) {
12
12
  const url = new URL(fullURL);
@@ -21,8 +21,7 @@ function getNextURL(rawLinks) {
21
21
  }
22
22
  const links = rawLinks;
23
23
  const nextLinks = links.filter((link) => link.rel === NEXT_LINK_PROP);
24
- if (nextLinks.length !== 1)
25
- return;
24
+ if (nextLinks.length !== 1) return;
26
25
  return nextLinks[0]?.href;
27
26
  }
28
27
  async function queryFeatures(fullURL, featureFormat, httpService, signal) {
@@ -1 +1 @@
1
- {"version":3,"file":"requestUtils.js","sources":["requestUtils.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { Extent } from \"ol/extent\";\nimport FeatureFormat from \"ol/format/Feature\";\nimport { FeatureLike } from \"ol/Feature\";\nimport { HttpService } from \"@open-pioneer/http\";\n\nconst NEXT_LINK_PROP = \"next\";\n\n/**\n * Assembles the url to use for fetching features in the given extent.\n */\nexport function createCollectionRequestUrl(\n collectionItemsURL: string,\n extent: Extent,\n crs: string\n): URL {\n const urlObj = new URL(collectionItemsURL);\n const searchParams = urlObj.searchParams;\n searchParams.set(\"bbox\", extent.join(\",\"));\n searchParams.set(\"bbox-crs\", crs);\n searchParams.set(\"crs\", crs);\n searchParams.set(\"f\", \"json\");\n return urlObj;\n}\n\n/**\n * Adds (or replaces) offset/limit params on the given url.\n */\nexport function createOffsetURL(fullURL: string, offset: number, pageSize: number): string {\n const url = new URL(fullURL);\n const searchParams = url.searchParams;\n searchParams.set(\"offset\", offset.toString());\n searchParams.set(\"limit\", pageSize.toString());\n return url.toString();\n}\n\n/**\n * Extracts the `next` link from the service response's `links` property.\n */\nexport function getNextURL(rawLinks: unknown): string | undefined {\n if (!Array.isArray(rawLinks)) {\n return undefined;\n }\n\n interface ObjWithRelAndHref {\n href: string;\n rel: string;\n }\n\n // We just assume the correct object shape\n const links = rawLinks as ObjWithRelAndHref[];\n\n const nextLinks = links.filter((link) => link.rel === NEXT_LINK_PROP);\n if (nextLinks.length !== 1) return;\n return nextLinks[0]?.href;\n}\n\nexport interface FeatureResponse {\n features: FeatureLike[];\n nextURL: string | undefined;\n numberMatched: number | undefined;\n}\n\n/**\n * Performs a single request against the service\n */\nexport async function queryFeatures(\n fullURL: string,\n featureFormat: FeatureFormat | undefined,\n httpService: HttpService,\n signal: AbortSignal | undefined\n): Promise<FeatureResponse> {\n let features: FeatureLike[] = [];\n const requestInit: RequestInit = {\n headers: {\n Accept: \"application/geo+json\"\n },\n signal\n };\n const response = await httpService.fetch(fullURL, requestInit);\n if (response.status !== 200) {\n throw new Error(`Failed to query features from service (status code ${response.status})`);\n }\n const geoJson = await response.json();\n if (featureFormat) {\n features = featureFormat.readFeatures(geoJson);\n }\n const nextURL = getNextURL(geoJson.links);\n return {\n features: features,\n numberMatched: geoJson.numberMatched,\n nextURL: nextURL\n };\n}\n"],"names":[],"mappings":"AAOA,MAAM,cAAiB,GAAA,MAAA,CAAA;AAKP,SAAA,0BAAA,CACZ,kBACA,EAAA,MAAA,EACA,GACG,EAAA;AACH,EAAM,MAAA,MAAA,GAAS,IAAI,GAAA,CAAI,kBAAkB,CAAA,CAAA;AACzC,EAAA,MAAM,eAAe,MAAO,CAAA,YAAA,CAAA;AAC5B,EAAA,YAAA,CAAa,GAAI,CAAA,MAAA,EAAQ,MAAO,CAAA,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA;AACzC,EAAa,YAAA,CAAA,GAAA,CAAI,YAAY,GAAG,CAAA,CAAA;AAChC,EAAa,YAAA,CAAA,GAAA,CAAI,OAAO,GAAG,CAAA,CAAA;AAC3B,EAAa,YAAA,CAAA,GAAA,CAAI,KAAK,MAAM,CAAA,CAAA;AAC5B,EAAO,OAAA,MAAA,CAAA;AACX,CAAA;AAKgB,SAAA,eAAA,CAAgB,OAAiB,EAAA,MAAA,EAAgB,QAA0B,EAAA;AACvF,EAAM,MAAA,GAAA,GAAM,IAAI,GAAA,CAAI,OAAO,CAAA,CAAA;AAC3B,EAAA,MAAM,eAAe,GAAI,CAAA,YAAA,CAAA;AACzB,EAAA,YAAA,CAAa,GAAI,CAAA,QAAA,EAAU,MAAO,CAAA,QAAA,EAAU,CAAA,CAAA;AAC5C,EAAA,YAAA,CAAa,GAAI,CAAA,OAAA,EAAS,QAAS,CAAA,QAAA,EAAU,CAAA,CAAA;AAC7C,EAAA,OAAO,IAAI,QAAS,EAAA,CAAA;AACxB,CAAA;AAKO,SAAS,WAAW,QAAuC,EAAA;AAC9D,EAAA,IAAI,CAAC,KAAA,CAAM,OAAQ,CAAA,QAAQ,CAAG,EAAA;AAC1B,IAAO,OAAA,KAAA,CAAA,CAAA;AAAA,GACX;AAQA,EAAA,MAAM,KAAQ,GAAA,QAAA,CAAA;AAEd,EAAA,MAAM,YAAY,KAAM,CAAA,MAAA,CAAO,CAAC,IAAS,KAAA,IAAA,CAAK,QAAQ,cAAc,CAAA,CAAA;AACpE,EAAA,IAAI,UAAU,MAAW,KAAA,CAAA;AAAG,IAAA,OAAA;AAC5B,EAAO,OAAA,SAAA,CAAU,CAAC,CAAG,EAAA,IAAA,CAAA;AACzB,CAAA;AAWA,eAAsB,aAClB,CAAA,OAAA,EACA,aACA,EAAA,WAAA,EACA,MACwB,EAAA;AACxB,EAAA,IAAI,WAA0B,EAAC,CAAA;AAC/B,EAAA,MAAM,WAA2B,GAAA;AAAA,IAC7B,OAAS,EAAA;AAAA,MACL,MAAQ,EAAA,sBAAA;AAAA,KACZ;AAAA,IACA,MAAA;AAAA,GACJ,CAAA;AACA,EAAA,MAAM,QAAW,GAAA,MAAM,WAAY,CAAA,KAAA,CAAM,SAAS,WAAW,CAAA,CAAA;AAC7D,EAAI,IAAA,QAAA,CAAS,WAAW,GAAK,EAAA;AACzB,IAAA,MAAM,IAAI,KAAA,CAAM,CAAsD,mDAAA,EAAA,QAAA,CAAS,MAAM,CAAG,CAAA,CAAA,CAAA,CAAA;AAAA,GAC5F;AACA,EAAM,MAAA,OAAA,GAAU,MAAM,QAAA,CAAS,IAAK,EAAA,CAAA;AACpC,EAAA,IAAI,aAAe,EAAA;AACf,IAAW,QAAA,GAAA,aAAA,CAAc,aAAa,OAAO,CAAA,CAAA;AAAA,GACjD;AACA,EAAM,MAAA,OAAA,GAAU,UAAW,CAAA,OAAA,CAAQ,KAAK,CAAA,CAAA;AACxC,EAAO,OAAA;AAAA,IACH,QAAA;AAAA,IACA,eAAe,OAAQ,CAAA,aAAA;AAAA,IACvB,OAAA;AAAA,GACJ,CAAA;AACJ;;;;"}
1
+ {"version":3,"file":"requestUtils.js","sources":["requestUtils.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { Extent } from \"ol/extent\";\nimport FeatureFormat from \"ol/format/Feature\";\nimport { FeatureLike } from \"ol/Feature\";\nimport { HttpService } from \"@open-pioneer/http\";\n\nconst NEXT_LINK_PROP = \"next\";\n\n/**\n * Assembles the url to use for fetching features in the given extent.\n */\nexport function createCollectionRequestUrl(\n collectionItemsURL: string,\n extent: Extent,\n crs: string,\n rewriteUrl?: (url: URL) => URL | undefined\n): URL {\n const urlObj = new URL(collectionItemsURL);\n const searchParams = urlObj.searchParams;\n searchParams.set(\"bbox\", extent.join(\",\"));\n searchParams.set(\"bbox-crs\", crs);\n searchParams.set(\"crs\", crs);\n searchParams.set(\"f\", \"json\");\n return rewriteUrl?.(new URL(urlObj)) ?? urlObj;\n}\n\n/**\n * Adds (or replaces) offset/limit params on the given url.\n */\nexport function createOffsetURL(fullURL: string, offset: number, pageSize: number): string {\n const url = new URL(fullURL);\n const searchParams = url.searchParams;\n searchParams.set(\"offset\", offset.toString());\n searchParams.set(\"limit\", pageSize.toString());\n return url.toString();\n}\n\n/**\n * Extracts the `next` link from the service response's `links` property.\n */\nexport function getNextURL(rawLinks: unknown): string | undefined {\n if (!Array.isArray(rawLinks)) {\n return undefined;\n }\n\n interface ObjWithRelAndHref {\n href: string;\n rel: string;\n }\n\n // We just assume the correct object shape\n const links = rawLinks as ObjWithRelAndHref[];\n\n const nextLinks = links.filter((link) => link.rel === NEXT_LINK_PROP);\n if (nextLinks.length !== 1) return;\n return nextLinks[0]?.href;\n}\n\nexport interface FeatureResponse {\n features: FeatureLike[];\n nextURL: string | undefined;\n numberMatched: number | undefined;\n}\n\n/**\n * Performs a single request against the service\n */\nexport async function queryFeatures(\n fullURL: string,\n featureFormat: FeatureFormat | undefined,\n httpService: HttpService,\n signal: AbortSignal | undefined\n): Promise<FeatureResponse> {\n let features: FeatureLike[] = [];\n const requestInit: RequestInit = {\n headers: {\n Accept: \"application/geo+json\"\n },\n signal\n };\n const response = await httpService.fetch(fullURL, requestInit);\n if (response.status !== 200) {\n throw new Error(`Failed to query features from service (status code ${response.status})`);\n }\n const geoJson = await response.json();\n if (featureFormat) {\n features = featureFormat.readFeatures(geoJson);\n }\n const nextURL = getNextURL(geoJson.links);\n return {\n features: features,\n numberMatched: geoJson.numberMatched,\n nextURL: nextURL\n };\n}\n"],"names":[],"mappings":"AAOA,MAAM,cAAiB,GAAA,MAAA,CAAA;AAKhB,SAAS,0BACZ,CAAA,kBAAA,EACA,MACA,EAAA,GAAA,EACA,UACG,EAAA;AACH,EAAM,MAAA,MAAA,GAAS,IAAI,GAAA,CAAI,kBAAkB,CAAA,CAAA;AACzC,EAAA,MAAM,eAAe,MAAO,CAAA,YAAA,CAAA;AAC5B,EAAA,YAAA,CAAa,GAAI,CAAA,MAAA,EAAQ,MAAO,CAAA,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA;AACzC,EAAa,YAAA,CAAA,GAAA,CAAI,YAAY,GAAG,CAAA,CAAA;AAChC,EAAa,YAAA,CAAA,GAAA,CAAI,OAAO,GAAG,CAAA,CAAA;AAC3B,EAAa,YAAA,CAAA,GAAA,CAAI,KAAK,MAAM,CAAA,CAAA;AAC5B,EAAA,OAAO,UAAa,GAAA,IAAI,GAAI,CAAA,MAAM,CAAC,CAAK,IAAA,MAAA,CAAA;AAC5C,CAAA;AAKgB,SAAA,eAAA,CAAgB,OAAiB,EAAA,MAAA,EAAgB,QAA0B,EAAA;AACvF,EAAM,MAAA,GAAA,GAAM,IAAI,GAAA,CAAI,OAAO,CAAA,CAAA;AAC3B,EAAA,MAAM,eAAe,GAAI,CAAA,YAAA,CAAA;AACzB,EAAA,YAAA,CAAa,GAAI,CAAA,QAAA,EAAU,MAAO,CAAA,QAAA,EAAU,CAAA,CAAA;AAC5C,EAAA,YAAA,CAAa,GAAI,CAAA,OAAA,EAAS,QAAS,CAAA,QAAA,EAAU,CAAA,CAAA;AAC7C,EAAA,OAAO,IAAI,QAAS,EAAA,CAAA;AACxB,CAAA;AAKO,SAAS,WAAW,QAAuC,EAAA;AAC9D,EAAA,IAAI,CAAC,KAAA,CAAM,OAAQ,CAAA,QAAQ,CAAG,EAAA;AAC1B,IAAO,OAAA,KAAA,CAAA,CAAA;AAAA,GACX;AAQA,EAAA,MAAM,KAAQ,GAAA,QAAA,CAAA;AAEd,EAAA,MAAM,YAAY,KAAM,CAAA,MAAA,CAAO,CAAC,IAAS,KAAA,IAAA,CAAK,QAAQ,cAAc,CAAA,CAAA;AACpE,EAAI,IAAA,SAAA,CAAU,WAAW,CAAG,EAAA,OAAA;AAC5B,EAAO,OAAA,SAAA,CAAU,CAAC,CAAG,EAAA,IAAA,CAAA;AACzB,CAAA;AAWA,eAAsB,aAClB,CAAA,OAAA,EACA,aACA,EAAA,WAAA,EACA,MACwB,EAAA;AACxB,EAAA,IAAI,WAA0B,EAAC,CAAA;AAC/B,EAAA,MAAM,WAA2B,GAAA;AAAA,IAC7B,OAAS,EAAA;AAAA,MACL,MAAQ,EAAA,sBAAA;AAAA,KACZ;AAAA,IACA,MAAA;AAAA,GACJ,CAAA;AACA,EAAA,MAAM,QAAW,GAAA,MAAM,WAAY,CAAA,KAAA,CAAM,SAAS,WAAW,CAAA,CAAA;AAC7D,EAAI,IAAA,QAAA,CAAS,WAAW,GAAK,EAAA;AACzB,IAAA,MAAM,IAAI,KAAA,CAAM,CAAsD,mDAAA,EAAA,QAAA,CAAS,MAAM,CAAG,CAAA,CAAA,CAAA,CAAA;AAAA,GAC5F;AACA,EAAM,MAAA,OAAA,GAAU,MAAM,QAAA,CAAS,IAAK,EAAA,CAAA;AACpC,EAAA,IAAI,aAAe,EAAA;AACf,IAAW,QAAA,GAAA,aAAA,CAAc,aAAa,OAAO,CAAA,CAAA;AAAA,GACjD;AACA,EAAM,MAAA,OAAA,GAAU,UAAW,CAAA,OAAA,CAAQ,KAAK,CAAA,CAAA;AACxC,EAAO,OAAA;AAAA,IACH,QAAA;AAAA,IACA,eAAe,OAAQ,CAAA,aAAA;AAAA,IACvB,OAAA;AAAA,GACJ,CAAA;AACJ;;;;"}