@open-pioneer/ogc-features 0.1.0 → 0.2.0

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,7 +1,27 @@
1
1
  # @open-pioneer/ogc-features
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 70349a8: Update to new core packages major versions
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [70349a8]
12
+ - @open-pioneer/search@0.2.0
13
+
14
+ ## 0.1.1
15
+
16
+ ### Patch Changes
17
+
18
+ - dfc0896: Add OGC API Features search source
19
+ - Updated dependencies [6209d6c]
20
+ - Updated dependencies [565bd8b]
21
+ - @open-pioneer/search@0.1.0
22
+
3
23
  ## 0.1.0
4
24
 
5
25
  ### Minor Changes
6
26
 
7
- - bc54925: Initial release.
27
+ - bc54925: Initial release.
@@ -1,10 +1,11 @@
1
1
  import { FeatureLike } from "ol/Feature";
2
- import { LoadFeatureOptions } from "./OgcFeatureSourceFactory";
2
+ import { LoadFeatureOptions } from "./createVectorSource";
3
+ import { HttpService } from "@open-pioneer/http";
3
4
  /** @internal */
4
5
  export interface CollectionInfos {
5
6
  /** True if features can be requested in pages using offset & limit parameters. */
6
7
  supportsOffsetStrategy: boolean;
7
8
  }
8
- export declare function getCollectionInfos(collectionsItemsUrl: string): Promise<CollectionInfos>;
9
+ export declare function getCollectionInfos(collectionsItemsUrl: string, httpService: HttpService): Promise<CollectionInfos>;
9
10
  /** @internal */
10
11
  export declare function loadAllFeaturesWithOffset(options: LoadFeatureOptions): Promise<FeatureLike[]>;
package/OffsetStrategy.js CHANGED
@@ -1,14 +1,14 @@
1
- import { loadPages } from './OgcFeatureSourceFactory.js';
2
- import { createOffsetURL, getNextURL } from './requestUtils.js';
1
+ import { loadPages } from './createVectorSource.js';
2
+ import { getNextURL, createOffsetURL } from './requestUtils.js';
3
3
 
4
- async function getCollectionInfos(collectionsItemsUrl) {
4
+ async function getCollectionInfos(collectionsItemsUrl, httpService) {
5
5
  const infos = {
6
6
  supportsOffsetStrategy: false
7
7
  };
8
8
  const url = new URL(collectionsItemsUrl);
9
9
  url.searchParams.set("limit", "1");
10
10
  url.searchParams.set("f", "json");
11
- const response = await fetch(url.toString(), {
11
+ const response = await httpService.fetch(url.toString(), {
12
12
  headers: {
13
13
  Accept: "application/geo+json"
14
14
  }
@@ -50,6 +50,7 @@ async function loadAllFeaturesWithOffset(options) {
50
50
  const allFeatureResp = await loadPages(
51
51
  urls,
52
52
  featureFormat,
53
+ options.httpService,
53
54
  signal,
54
55
  addFeatures,
55
56
  queryFeatures
@@ -1 +1 @@
1
- {"version":3,"file":"OffsetStrategy.js","sources":["OffsetStrategy.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { FeatureLike } from \"ol/Feature\";\nimport { LoadFeatureOptions, loadPages } from \"./OgcFeatureSourceFactory\";\nimport { createOffsetURL, getNextURL } from \"./requestUtils\";\n\n/** @internal */\nexport interface CollectionInfos {\n /** True if features can be requested in pages using offset & limit parameters. */\n supportsOffsetStrategy: boolean;\n}\n\nexport async function getCollectionInfos(collectionsItemsUrl: string): Promise<CollectionInfos> {\n const infos: CollectionInfos = {\n supportsOffsetStrategy: false\n };\n\n const url = new URL(collectionsItemsUrl);\n url.searchParams.set(\"limit\", \"1\");\n url.searchParams.set(\"f\", \"json\");\n const response = await fetch(url.toString(), {\n headers: {\n Accept: \"application/geo+json\"\n }\n });\n if (response.status !== 200) {\n throw new Error(`Failed to probe collection information (status code ${response.status})`);\n }\n\n const jsonResp = await response.json();\n const nextUrl = getNextURL(jsonResp.links);\n if (!nextUrl) {\n return infos;\n }\n\n const parsedURL = new URL(nextUrl);\n const hasOffset = parsedURL.searchParams.has(\"offset\");\n infos.supportsOffsetStrategy = hasOffset;\n return infos;\n}\n\n/** @internal */\nexport async function loadAllFeaturesWithOffset(\n options: LoadFeatureOptions\n): Promise<FeatureLike[]> {\n const { fullURL, featureFormat, signal, addFeatures, queryFeatures } = options;\n const pageSize = options.limit;\n const maxConcurrency = options.maxConcurrentRequests;\n\n let startOffset = 0;\n let currentUrl: string | undefined = fullURL;\n const allFeatures: FeatureLike[] = [];\n let totalFeatures: number | undefined;\n while (currentUrl) {\n let pagesInIteration: number;\n if (totalFeatures == undefined) {\n // We don't know the actual size of the result set yet.\n pagesInIteration = maxConcurrency;\n } else {\n pagesInIteration = Math.ceil((totalFeatures - startOffset) / pageSize);\n }\n pagesInIteration = Math.max(1, Math.min(pagesInIteration, maxConcurrency));\n\n const urls: string[] = [];\n for (let page = 0; page < pagesInIteration; ++page) {\n urls.push(createOffsetURL(fullURL, startOffset, pageSize));\n startOffset += pageSize;\n }\n\n const allFeatureResp = await loadPages(\n urls,\n featureFormat,\n signal,\n addFeatures,\n queryFeatures\n );\n\n allFeatures.push(...allFeatureResp.features);\n currentUrl = allFeatureResp.nextURL;\n if (allFeatureResp.numberMatched != null) {\n totalFeatures = allFeatureResp.numberMatched;\n }\n }\n while (currentUrl !== undefined);\n return allFeatures;\n}\n"],"names":[],"mappings":";;;AAYA,eAAsB,mBAAmB,mBAAuD,EAAA;AAC5F,EAAA,MAAM,KAAyB,GAAA;AAAA,IAC3B,sBAAwB,EAAA,KAAA;AAAA,GAC5B,CAAA;AAEA,EAAM,MAAA,GAAA,GAAM,IAAI,GAAA,CAAI,mBAAmB,CAAA,CAAA;AACvC,EAAI,GAAA,CAAA,YAAA,CAAa,GAAI,CAAA,OAAA,EAAS,GAAG,CAAA,CAAA;AACjC,EAAI,GAAA,CAAA,YAAA,CAAa,GAAI,CAAA,GAAA,EAAK,MAAM,CAAA,CAAA;AAChC,EAAA,MAAM,QAAW,GAAA,MAAM,KAAM,CAAA,GAAA,CAAI,UAAY,EAAA;AAAA,IACzC,OAAS,EAAA;AAAA,MACL,MAAQ,EAAA,sBAAA;AAAA,KACZ;AAAA,GACH,CAAA,CAAA;AACD,EAAI,IAAA,QAAA,CAAS,WAAW,GAAK,EAAA;AACzB,IAAA,MAAM,IAAI,KAAA,CAAM,CAAuD,oDAAA,EAAA,QAAA,CAAS,MAAM,CAAG,CAAA,CAAA,CAAA,CAAA;AAAA,GAC7F;AAEA,EAAM,MAAA,QAAA,GAAW,MAAM,QAAA,CAAS,IAAK,EAAA,CAAA;AACrC,EAAM,MAAA,OAAA,GAAU,UAAW,CAAA,QAAA,CAAS,KAAK,CAAA,CAAA;AACzC,EAAA,IAAI,CAAC,OAAS,EAAA;AACV,IAAO,OAAA,KAAA,CAAA;AAAA,GACX;AAEA,EAAM,MAAA,SAAA,GAAY,IAAI,GAAA,CAAI,OAAO,CAAA,CAAA;AACjC,EAAA,MAAM,SAAY,GAAA,SAAA,CAAU,YAAa,CAAA,GAAA,CAAI,QAAQ,CAAA,CAAA;AACrD,EAAA,KAAA,CAAM,sBAAyB,GAAA,SAAA,CAAA;AAC/B,EAAO,OAAA,KAAA,CAAA;AACX,CAAA;AAGA,eAAsB,0BAClB,OACsB,EAAA;AACtB,EAAA,MAAM,EAAE,OAAS,EAAA,aAAA,EAAe,MAAQ,EAAA,WAAA,EAAa,eAAkB,GAAA,OAAA,CAAA;AACvE,EAAA,MAAM,WAAW,OAAQ,CAAA,KAAA,CAAA;AACzB,EAAA,MAAM,iBAAiB,OAAQ,CAAA,qBAAA,CAAA;AAE/B,EAAA,IAAI,WAAc,GAAA,CAAA,CAAA;AAClB,EAAA,IAAI,UAAiC,GAAA,OAAA,CAAA;AACrC,EAAA,MAAM,cAA6B,EAAC,CAAA;AACpC,EAAI,IAAA,aAAA,CAAA;AACJ,EAAA,OAAO,UAAY,EAAA;AACf,IAAI,IAAA,gBAAA,CAAA;AACJ,IAAA,IAAI,iBAAiB,KAAW,CAAA,EAAA;AAE5B,MAAmB,gBAAA,GAAA,cAAA,CAAA;AAAA,KAChB,MAAA;AACH,MAAA,gBAAA,GAAmB,IAAK,CAAA,IAAA,CAAA,CAAM,aAAgB,GAAA,WAAA,IAAe,QAAQ,CAAA,CAAA;AAAA,KACzE;AACA,IAAA,gBAAA,GAAmB,KAAK,GAAI,CAAA,CAAA,EAAG,KAAK,GAAI,CAAA,gBAAA,EAAkB,cAAc,CAAC,CAAA,CAAA;AAEzE,IAAA,MAAM,OAAiB,EAAC,CAAA;AACxB,IAAA,KAAA,IAAS,IAAO,GAAA,CAAA,EAAG,IAAO,GAAA,gBAAA,EAAkB,EAAE,IAAM,EAAA;AAChD,MAAA,IAAA,CAAK,IAAK,CAAA,eAAA,CAAgB,OAAS,EAAA,WAAA,EAAa,QAAQ,CAAC,CAAA,CAAA;AACzD,MAAe,WAAA,IAAA,QAAA,CAAA;AAAA,KACnB;AAEA,IAAA,MAAM,iBAAiB,MAAM,SAAA;AAAA,MACzB,IAAA;AAAA,MACA,aAAA;AAAA,MACA,MAAA;AAAA,MACA,WAAA;AAAA,MACA,aAAA;AAAA,KACJ,CAAA;AAEA,IAAY,WAAA,CAAA,IAAA,CAAK,GAAG,cAAA,CAAe,QAAQ,CAAA,CAAA;AAC3C,IAAA,UAAA,GAAa,cAAe,CAAA,OAAA,CAAA;AAC5B,IAAI,IAAA,cAAA,CAAe,iBAAiB,IAAM,EAAA;AACtC,MAAA,aAAA,GAAgB,cAAe,CAAA,aAAA,CAAA;AAAA,KACnC;AAAA,GACJ;AAEA,EAAO,OAAA,WAAA,CAAA;AACX;;;;"}
1
+ {"version":3,"file":"OffsetStrategy.js","sources":["OffsetStrategy.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { FeatureLike } from \"ol/Feature\";\nimport { LoadFeatureOptions, loadPages } from \"./createVectorSource\";\nimport { createOffsetURL, getNextURL } from \"./requestUtils\";\nimport { HttpService } from \"@open-pioneer/http\";\n\n/** @internal */\nexport interface CollectionInfos {\n /** True if features can be requested in pages using offset & limit parameters. */\n supportsOffsetStrategy: boolean;\n}\n\nexport async function getCollectionInfos(\n collectionsItemsUrl: string,\n httpService: HttpService\n): Promise<CollectionInfos> {\n const infos: CollectionInfos = {\n supportsOffsetStrategy: false\n };\n\n const url = new URL(collectionsItemsUrl);\n url.searchParams.set(\"limit\", \"1\");\n url.searchParams.set(\"f\", \"json\");\n const response = await httpService.fetch(url.toString(), {\n headers: {\n Accept: \"application/geo+json\"\n }\n });\n if (response.status !== 200) {\n throw new Error(`Failed to probe collection information (status code ${response.status})`);\n }\n\n const jsonResp = await response.json();\n const nextUrl = getNextURL(jsonResp.links);\n if (!nextUrl) {\n return infos;\n }\n\n const parsedURL = new URL(nextUrl);\n const hasOffset = parsedURL.searchParams.has(\"offset\");\n infos.supportsOffsetStrategy = hasOffset;\n return infos;\n}\n\n/** @internal */\nexport async function loadAllFeaturesWithOffset(\n options: LoadFeatureOptions\n): Promise<FeatureLike[]> {\n const { fullURL, featureFormat, signal, addFeatures, queryFeatures } = options;\n const pageSize = options.limit;\n const maxConcurrency = options.maxConcurrentRequests;\n\n let startOffset = 0;\n let currentUrl: string | undefined = fullURL;\n const allFeatures: FeatureLike[] = [];\n let totalFeatures: number | undefined;\n while (currentUrl) {\n let pagesInIteration: number;\n if (totalFeatures == undefined) {\n // We don't know the actual size of the result set yet.\n pagesInIteration = maxConcurrency;\n } else {\n pagesInIteration = Math.ceil((totalFeatures - startOffset) / pageSize);\n }\n pagesInIteration = Math.max(1, Math.min(pagesInIteration, maxConcurrency));\n\n const urls: string[] = [];\n for (let page = 0; page < pagesInIteration; ++page) {\n urls.push(createOffsetURL(fullURL, startOffset, pageSize));\n startOffset += pageSize;\n }\n\n const allFeatureResp = await loadPages(\n urls,\n featureFormat,\n options.httpService,\n signal,\n addFeatures,\n queryFeatures\n );\n\n allFeatures.push(...allFeatureResp.features);\n currentUrl = allFeatureResp.nextURL;\n if (allFeatureResp.numberMatched != null) {\n totalFeatures = allFeatureResp.numberMatched;\n }\n }\n while (currentUrl !== undefined);\n return allFeatures;\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { SearchSource, SearchResult, SearchOptions } from "@open-pioneer/search";
2
+ import { OgcFeatureSearchSourceOptions } from "./api";
3
+ import { HttpService } from "@open-pioneer/http";
4
+ /** The general shape of features returned by an OGC API Features service. */
5
+ export interface FeatureResponse {
6
+ /**
7
+ * The type of the feature (e.g. `Feature`).
8
+ */
9
+ type: string;
10
+ /**
11
+ * The id of the feature.
12
+ */
13
+ id: string | number;
14
+ /**
15
+ * The geometry of the feature.
16
+ */
17
+ geometry: unknown;
18
+ /**
19
+ * The properties of the feature.
20
+ */
21
+ properties: Readonly<Record<string, unknown>>;
22
+ }
23
+ /**
24
+ * An implementation of {@link SearchSource} that searches on an OGC Features API service.
25
+ */
26
+ export declare class OgcFeatureSearchSource implements SearchSource {
27
+ #private;
28
+ readonly label: string;
29
+ constructor(options: OgcFeatureSearchSourceOptions, httpService: HttpService);
30
+ search(inputValue: string, { mapProjection, maxResults, signal }: SearchOptions): Promise<SearchResult[]>;
31
+ }
32
+ export interface SearchResponse {
33
+ features: FeatureResponse[];
34
+ links?: Record<string, unknown>[];
35
+ numberMatched?: number;
36
+ numberReturned?: number;
37
+ timeStamp?: string;
38
+ type?: string;
39
+ }
@@ -0,0 +1,90 @@
1
+ import { isAbortError } from '@open-pioneer/core';
2
+ import { v4 } from 'uuid';
3
+ import GeoJSON from 'ol/format/GeoJSON';
4
+
5
+ class OgcFeatureSearchSource {
6
+ label;
7
+ #options;
8
+ #httpService;
9
+ #baseUrl;
10
+ #params;
11
+ constructor(options, httpService) {
12
+ this.label = options.label;
13
+ this.#options = options;
14
+ this.#httpService = httpService;
15
+ const { baseUrl, params } = getBaseUrl(options.baseUrl);
16
+ this.#baseUrl = baseUrl;
17
+ this.#params = params;
18
+ }
19
+ async search(inputValue, { mapProjection, maxResults, signal }) {
20
+ const url = this.#getUrl(inputValue, maxResults);
21
+ const geojson = new GeoJSON({
22
+ dataProjection: "EPSG:4326",
23
+ featureProjection: mapProjection
24
+ });
25
+ const responses = await fetchJson(this.#httpService, url, signal);
26
+ return responses.features.map((feature) => this.#createResult(feature, geojson));
27
+ }
28
+ #createResult(feature, geojson) {
29
+ const customLabel = this.#options.renderLabel?.(feature);
30
+ const singleLabelProperty = feature.properties[this.#options.labelProperty];
31
+ const singleSearchProperty = feature.properties[this.#options.searchProperty];
32
+ const label = (() => {
33
+ if (customLabel) {
34
+ return customLabel;
35
+ } else if (singleLabelProperty !== void 0) {
36
+ return String(singleLabelProperty);
37
+ } else if (singleSearchProperty !== void 0) {
38
+ return String(singleSearchProperty);
39
+ } else {
40
+ return "";
41
+ }
42
+ })();
43
+ return {
44
+ id: feature.id ?? v4(),
45
+ label,
46
+ geometry: geojson.readGeometry(feature.geometry),
47
+ properties: feature.properties
48
+ };
49
+ }
50
+ #getUrl(inputValue, limit) {
51
+ const url = new URL(`${this.#baseUrl}/collections/${this.#options.collectionId}/items`);
52
+ for (const [k, v] of this.#params) {
53
+ url.searchParams.append(k, v);
54
+ }
55
+ url.searchParams.set(this.#options.searchProperty, `*${inputValue}*`);
56
+ url.searchParams.set("limit", String(limit));
57
+ url.searchParams.set("f", "json");
58
+ return this.#options.rewriteUrl?.(new URL(url)) ?? url;
59
+ }
60
+ }
61
+ async function fetchJson(httpService, url, signal) {
62
+ try {
63
+ const response = await httpService.fetch(url, {
64
+ signal,
65
+ headers: {
66
+ "Accept": "application/json"
67
+ }
68
+ });
69
+ if (!response.ok) {
70
+ throw new Error("Request failed with status " + response.status);
71
+ }
72
+ const result = await response.json();
73
+ return result;
74
+ } catch (error) {
75
+ if (isAbortError(error)) {
76
+ throw error;
77
+ }
78
+ throw new Error("Failed to search on OGC API Features service", { cause: error });
79
+ }
80
+ }
81
+ function getBaseUrl(baseUrl) {
82
+ const url = new URL(baseUrl);
83
+ const params = new URLSearchParams(url.searchParams);
84
+ url.search = "";
85
+ const cleanBaseUrl = url.href.replace(/\/+$/, "");
86
+ return { baseUrl: cleanBaseUrl, params };
87
+ }
88
+
89
+ export { OgcFeatureSearchSource };
90
+ //# sourceMappingURL=OgcFeatureSearchSource.js.map
@@ -0,0 +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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"}
package/README.md CHANGED
@@ -1,21 +1,37 @@
1
1
  # @open-pioneer/ogc-features
2
2
 
3
- This package provides a function to create an OpenLayers VectorSource to be used with OGC API Features service.
4
- This VectorSource should be used inside together with a VectorLayer.
3
+ This package provides utilities to work with OGC API Features services.
5
4
 
6
5
  ## Usage
7
6
 
8
- Just import the function with:
7
+ ### Vector source
8
+
9
+ This vector source should be used together with a vector layer.
10
+
11
+ Inject the vector source factory by referencing `"ogc-features.VectorSourceFactory"`:
9
12
 
10
13
  ```js
11
- import { createVectorSource } from "@open-pioneer/ogc-features";
14
+ // build.config.mjs
15
+ import { defineBuildConfig } from "@open-pioneer/build-support";
16
+
17
+ export default defineBuildConfig({
18
+ services: {
19
+ YourService: {
20
+ // ...
21
+ references: {
22
+ vectorSourceFactory: "ogc-features.VectorSourceFactory"
23
+ }
24
+ }
25
+ }
26
+ });
12
27
  ```
13
28
 
14
29
  and use it inside a VectorLayer:
15
30
 
16
31
  ```ts
17
- layer: new VectorLayer({
18
- source: createVectorSource({
32
+ const vectorSourceFactory = ...; // injected
33
+ const vectorLayer = new VectorLayer({
34
+ source: vectorSourceFactory.createVectorSource({
19
35
  baseUrl: "https://ogc-api.nrw.de/inspire-us-kindergarten/v1",
20
36
  collectionId: "governmentalservice",
21
37
  crs: "http://www.opengis.net/def/crs/EPSG/0/25832",
@@ -27,7 +43,7 @@ layer: new VectorLayer({
27
43
  * When the `offset` strategy is used for feature fetching, the limit
28
44
  * is used for the page size.
29
45
  *
30
- * Default limit is 5000
46
+ * Defaults to `5000`.
31
47
  */
32
48
  limit: 5000,
33
49
 
@@ -49,6 +65,79 @@ The number of concurrent requests is never higher than `maxConcurrentRequests`.
49
65
  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
50
66
  `additionalOptions`.
51
67
 
68
+ ### Search source
69
+
70
+ This search source is used to search in OGC API features.
71
+
72
+ Inject the search source factory by referencing `"ogc-features.SearchSourceFactory"`:
73
+
74
+ ```js
75
+ // build.config.mjs
76
+ import { defineBuildConfig } from "@open-pioneer/build-support";
77
+
78
+ export default defineBuildConfig({
79
+ services: {
80
+ YourService: {
81
+ // ...
82
+ references: {
83
+ searchSourceFactory: "ogc-features.SearchSourceFactory"
84
+ }
85
+ }
86
+ }
87
+ });
88
+ ```
89
+
90
+ and create a search search instance:
91
+
92
+ ```ts
93
+ const searchSourceFactory = ...; // injected
94
+ const searchSource = searchSourceFactory.createSearchSource({
95
+ label: this.intl.formatMessage({ id: "searchSources.miningPermissions" }),
96
+ baseUrl: "https://ogc-api.nrw.de/lika/v1",
97
+ collectionId: "flurstueck",
98
+ searchProperty: "flurstid",
99
+ labelProperty: "objid"
100
+ }),
101
+ ```
102
+
103
+ Use the callback function `renderLabel` to create a custom label for the result.
104
+
105
+ ```ts
106
+ searchSourceFactory.createSearchSource({
107
+ // ...
108
+ renderLabel(feature) {
109
+ const tntxt = feature?.properties?.tntxt;
110
+ const id = feature?.id;
111
+ if (typeof tntxt === "string") {
112
+ return tntxt + " (" + id + ")";
113
+ } else {
114
+ return String(id);
115
+ }
116
+ }
117
+ // ...
118
+ });
119
+ ```
120
+
121
+ The label is generated by one of the following methods, weighted by order:
122
+
123
+ 1. `renderLabel` function
124
+ 2. `labelProperty`
125
+ 3. `searchProperty`
126
+
127
+ To overwrite the service URL, use the callback function `rewriteUrl`.
128
+ This is useful, for example, to return only a specific attribute in the response, as in the following example:
129
+
130
+ ```ts
131
+ searchSourceFactory.createSearchSource({
132
+ // ...
133
+ rewriteUrl(url) {
134
+ url.searchParams.set("properties", "objid");
135
+ return url;
136
+ }
137
+ // ...
138
+ });
139
+ ```
140
+
52
141
  ## License
53
142
 
54
143
  Apache-2.0 (see `LICENSE` file)
package/api.d.ts ADDED
@@ -0,0 +1,124 @@
1
+ import { DeclaredService } from "@open-pioneer/runtime";
2
+ import { SearchSource } from "@open-pioneer/search";
3
+ import Feature from "ol/Feature";
4
+ import { Geometry } from "ol/geom";
5
+ import { AttributionLike } from "ol/source/Source";
6
+ import VectorSource, { Options } from "ol/source/Vector";
7
+ /**
8
+ * These are properties for OGC API Features vector source.
9
+ */
10
+ export interface OgcFeatureVectorSourceOptions {
11
+ /** The base-URL right to the "/collections"-part */
12
+ baseUrl: string;
13
+ /** The collection-ID */
14
+ collectionId: string;
15
+ /** the URL to the EPSG-Code, e.g. http://www.opengis.net/def/crs/EPSG/0/25832 */
16
+ crs: string;
17
+ /**
18
+ * The maximum number of features to fetch within a single request.
19
+ * Corresponds to the `limit` parameter in the URL.
20
+ *
21
+ * When the `offset` strategy is used for feature fetching, the limit
22
+ * is used for the page size
23
+ *
24
+ * Defaults to `5000` for Next-Strategy.
25
+ * Defaults to `2500` for Offset-Strategy.
26
+ */
27
+ limit?: number;
28
+ /** The maximum number of concurrent requests. Defaults to `6`. */
29
+ maxConcurrentRequests?: number;
30
+ /** Optional attribution for the layer (e.g. copyright hints). */
31
+ attributions?: AttributionLike | undefined;
32
+ /** Optional additional options for the VectorSource. */
33
+ additionalOptions?: Options<Feature<Geometry>>;
34
+ }
35
+ /**
36
+ * A factory that creates {@link VectorSource | vector sources} for an OGC API Features service.
37
+ * The resulting vector sources can be used in an OpenLayers `VectorLayer`.
38
+ *
39
+ * Use the interface name `"ogc-features.VectorSourceFactory"` to obtain an instance of this factory.
40
+ */
41
+ export interface OgcFeaturesVectorSourceFactory extends DeclaredService<"ogc-features.VectorSourceFactory"> {
42
+ /**
43
+ * Creates a new {@link VectorSource} that loads features from the specified feature service.
44
+ */
45
+ createVectorSource(options: OgcFeatureVectorSourceOptions): VectorSource;
46
+ }
47
+ /** Options for {@link OgcFeatureSearchSource}. */
48
+ export interface OgcFeatureSearchSourceOptions {
49
+ /** The source's label. May be used as a title for results from this source. */
50
+ label: string;
51
+ /**
52
+ * The URL to the service, not including the "/collections"-part.
53
+ *
54
+ * Query arguments here are also used for individual requests by default, for example:
55
+ *
56
+ * ```js
57
+ * new OgcFeatureSearchSource({
58
+ * // token is also used for all requests made by this class
59
+ * baseUrl: `https://example.com/ogc-service?token=...`
60
+ * })
61
+ * ```
62
+ */
63
+ baseUrl: string;
64
+ /**
65
+ * The ID of the collection.
66
+ */
67
+ collectionId: string;
68
+ /**
69
+ * Property used for filtering on OGC API Features.
70
+ */
71
+ searchProperty: string;
72
+ /**
73
+ * Property used for labelling.
74
+ *
75
+ * Defaults to `searchProperty`.
76
+ *
77
+ * This property can be useful if searchProperty is not returned by the service, or
78
+ * if another field shall be displayed instead.
79
+ */
80
+ labelProperty?: string;
81
+ /**
82
+ * Function to create custom a label for a given feature.
83
+ *
84
+ * If the label is not customized by this function, `labelProperty` (or `searchProperty`) will be used instead.
85
+ */
86
+ renderLabel?: (feature: FeatureResponse) => string | undefined;
87
+ /**
88
+ * Rewrite function to modify the original URL.
89
+ *
90
+ * NOTE: Do not update the `url` argument. Return a new `URL` instance instead.
91
+ */
92
+ rewriteUrl?: (url: URL) => URL | undefined;
93
+ }
94
+ /** The general shape of features returned by an OGC API Features service. */
95
+ export interface FeatureResponse {
96
+ /**
97
+ * The type of the feature (e.g. `Feature`).
98
+ */
99
+ type: string;
100
+ /**
101
+ * The id of the feature.
102
+ */
103
+ id: string | number;
104
+ /**
105
+ * The geometry of the feature.
106
+ */
107
+ geometry: unknown;
108
+ /**
109
+ * The properties of the feature.
110
+ */
111
+ properties: Readonly<Record<string, unknown>>;
112
+ }
113
+ /**
114
+ * A factory that creates {@link SearchSource | search sources} for an OGC API Features service.
115
+ * The resulting search sources can be used in combination with the `@open-pioneer/search` package.
116
+ *
117
+ * Use the interface name `"ogc-features.SearchSourceFactory"` to obtain an instance of this factory.
118
+ */
119
+ export interface OgcFeaturesSearchSourceFactory extends DeclaredService<"ogc-features.SearchSourceFactory"> {
120
+ /**
121
+ * Returns a new {@link SearchSource} that searches on the specified feature service.
122
+ */
123
+ createSearchSource(options: OgcFeatureSearchSourceOptions): SearchSource;
124
+ }
@@ -0,0 +1,64 @@
1
+ import { FeatureLike } from "ol/Feature";
2
+ import FeatureFormat from "ol/format/Feature";
3
+ import VectorSource from "ol/source/Vector";
4
+ import { CollectionInfos, getCollectionInfos } from "./OffsetStrategy";
5
+ import { FeatureResponse, queryFeatures } from "./requestUtils";
6
+ import { OgcFeatureVectorSourceOptions } from "./api";
7
+ import { HttpService } from "@open-pioneer/http";
8
+ /**
9
+ * This function creates an OpenLayers VectorSource for OGC API Features services to be used inside
10
+ * an OpenLayers VectorLayer.
11
+ *
12
+ * @param options Options for the vector source.
13
+ */
14
+ export declare function createVectorSource(options: OgcFeatureVectorSourceOptions, httpService: HttpService): VectorSource;
15
+ /**
16
+ * @internal
17
+ * Exported for tests
18
+ */
19
+ export interface InternalOptions {
20
+ httpService: HttpService;
21
+ queryFeaturesParam?: QueryFeaturesFunc | undefined;
22
+ addFeaturesParam?: AddFeaturesFunc | undefined;
23
+ getCollectionInfosParam?: GetCollectionInfosFunc | undefined;
24
+ }
25
+ /**
26
+ * @internal
27
+ * Creates the actual vector source.
28
+ * Exported for testing.
29
+ * Exposes `queryFeatures`, `addFeatures` and `getCollectionInfos` for easier testing.
30
+ */
31
+ export declare function _createVectorSource(options: OgcFeatureVectorSourceOptions, internals: InternalOptions): VectorSource;
32
+ /** @internal **/
33
+ type QueryFeaturesFunc = typeof queryFeatures;
34
+ /** @internal **/
35
+ type GetCollectionInfosFunc = typeof getCollectionInfos;
36
+ /** @internal **/
37
+ type AddFeaturesFunc = (features: FeatureLike[]) => void;
38
+ /** @internal **/
39
+ export interface LoadFeatureOptions {
40
+ fullURL: string;
41
+ httpService: HttpService;
42
+ featureFormat: FeatureFormat;
43
+ queryFeatures: QueryFeaturesFunc;
44
+ addFeatures: AddFeaturesFunc;
45
+ limit: number;
46
+ maxConcurrentRequests: number;
47
+ signal?: AbortSignal;
48
+ collectionInfos?: CollectionInfos;
49
+ }
50
+ /**
51
+ * @internal
52
+ * Fetches features by following the `next` links in the server's response.
53
+ */
54
+ export declare function loadAllFeaturesNextStrategy(options: Omit<LoadFeatureOptions, "offsetRequestProps" | "collectionInfos">): Promise<FeatureLike[]>;
55
+ export declare function loadFeatures(requestUrl: string, featureFormat: FeatureFormat, httpService: HttpService, signal: AbortSignal | undefined, addFeaturesFunc: AddFeaturesFunc, queryFeaturesFunc?: QueryFeaturesFunc): Promise<FeatureResponse>;
56
+ /**
57
+ * Loads features from multiple urls in parallel.
58
+ * The URLs should represent pages of the same result set.
59
+ * The `nextURL` of the last page (if any) is returned from this function.
60
+ *
61
+ * @internal
62
+ */
63
+ export declare function loadPages(allUrls: string[], featureFormat: FeatureFormat, httpService: HttpService, signal: AbortSignal | undefined, addFeaturesFunc: AddFeaturesFunc, queryFeaturesFunc?: QueryFeaturesFunc): Promise<FeatureResponse>;
64
+ export {};
@@ -2,16 +2,17 @@ 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 { loadAllFeaturesWithOffset, getCollectionInfos } from './OffsetStrategy.js';
6
- import { createCollectionRequestUrl, queryFeatures } from './requestUtils.js';
5
+ import { getCollectionInfos, loadAllFeaturesWithOffset } from './OffsetStrategy.js';
6
+ import { queryFeatures, createCollectionRequestUrl } from './requestUtils.js';
7
7
 
8
8
  const LOG = createLogger("ogc-features:OgcFeatureSourceFactory");
9
9
  const DEFAULT_LIMIT = 5e3;
10
10
  const DEFAULT_CONCURRENTY = 6;
11
- function createVectorSource(options) {
12
- return _createVectorSource(options, void 0, void 0, void 0);
11
+ function createVectorSource(options, httpService) {
12
+ return _createVectorSource(options, { httpService });
13
13
  }
14
- function _createVectorSource(options, queryFeaturesParam, addFeaturesParam, getCollectionInfosParam) {
14
+ function _createVectorSource(options, internals) {
15
+ const httpService = internals.httpService;
15
16
  const collectionItemsURL = `${options.baseUrl}/collections/${options.collectionId}/items?`;
16
17
  const vectorSrc = new VectorSource({
17
18
  format: new GeoJSON(),
@@ -19,16 +20,16 @@ function _createVectorSource(options, queryFeaturesParam, addFeaturesParam, getC
19
20
  attributions: options.attributions,
20
21
  ...options.additionalOptions
21
22
  });
22
- const queryFeaturesFunc = queryFeaturesParam ?? queryFeatures;
23
- const getCollectionInfosFunc = getCollectionInfosParam ?? getCollectionInfos;
24
- const addFeaturesFunc = addFeaturesParam || function(features) {
23
+ const queryFeaturesFunc = internals.queryFeaturesParam ?? queryFeatures;
24
+ const getCollectionInfosFunc = internals.getCollectionInfosParam ?? getCollectionInfos;
25
+ const addFeaturesFunc = internals.addFeaturesParam || function(features) {
25
26
  LOG.debug(`Adding ${features.length} features`);
26
27
  vectorSrc.addFeatures(features);
27
28
  };
28
29
  let abortController;
29
30
  let collectionInfosPromise;
30
31
  const loaderFunction = async (extent, _, __, success, failure) => {
31
- collectionInfosPromise ??= getCollectionInfosFunc(collectionItemsURL);
32
+ collectionInfosPromise ??= getCollectionInfosFunc(collectionItemsURL, httpService);
32
33
  let collectionInfos;
33
34
  try {
34
35
  collectionInfos = await collectionInfosPromise;
@@ -45,8 +46,8 @@ function _createVectorSource(options, queryFeaturesParam, addFeaturesParam, getC
45
46
  try {
46
47
  const features = await loadAllFeatures(strategy, {
47
48
  fullURL: fullURL.toString(),
49
+ httpService,
48
50
  featureFormat: vectorSrc.getFormat(),
49
- // TODO
50
51
  queryFeatures: queryFeaturesFunc,
51
52
  addFeatures: addFeaturesFunc,
52
53
  limit: options.limit ?? DEFAULT_LIMIT,
@@ -86,6 +87,7 @@ async function loadAllFeaturesNextStrategy(options) {
86
87
  const featureResp = await loadPages(
87
88
  [url.toString()],
88
89
  options.featureFormat,
90
+ options.httpService,
89
91
  options.signal,
90
92
  options.addFeatures,
91
93
  options.queryFeatures
@@ -98,7 +100,7 @@ async function loadAllFeaturesNextStrategy(options) {
98
100
  } while (1);
99
101
  return allFeatures;
100
102
  }
101
- async function loadPages(allUrls, featureFormat, signal, addFeaturesFunc, queryFeaturesFunc = queryFeatures) {
103
+ async function loadPages(allUrls, featureFormat, httpService, signal, addFeaturesFunc, queryFeaturesFunc = queryFeatures) {
102
104
  const allFeatureResponse = {
103
105
  nextURL: void 0,
104
106
  numberMatched: void 0,
@@ -106,7 +108,12 @@ async function loadPages(allUrls, featureFormat, signal, addFeaturesFunc, queryF
106
108
  };
107
109
  const allRequestPromises = allUrls.map(async (singleUrl, index) => {
108
110
  const isLast = index === allUrls.length - 1;
109
- const featureResponse = await queryFeaturesFunc(singleUrl, featureFormat, signal);
111
+ const featureResponse = await queryFeaturesFunc(
112
+ singleUrl,
113
+ featureFormat,
114
+ httpService,
115
+ signal
116
+ );
110
117
  addFeaturesFunc(featureResponse.features);
111
118
  LOG.debug(
112
119
  `NextURL for index = ${index} (isLast = ${isLast}): ${featureResponse.nextURL || "No Next URL"}`
@@ -122,4 +129,4 @@ async function loadPages(allUrls, featureFormat, signal, addFeaturesFunc, queryF
122
129
  }
123
130
 
124
131
  export { _createVectorSource, createVectorSource, loadAllFeaturesNextStrategy, loadPages };
125
- //# sourceMappingURL=OgcFeatureSourceFactory.js.map
132
+ //# sourceMappingURL=createVectorSource.js.map
@@ -0,0 +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 */\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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"}
package/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { createVectorSource, type OgcFeatureSourceOptions } from "./OgcFeatureSourceFactory";
1
+ export * from "./api";
package/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { createVectorSource } from './OgcFeatureSourceFactory.js';
1
+
2
2
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,21 +1,65 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@open-pioneer/ogc-features",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "license": "Apache-2.0",
6
6
  "peerDependencies": {
7
- "@open-pioneer/core": "^1.1.0",
8
- "ol": "^8.2.0"
7
+ "@open-pioneer/core": "^1.2.1",
8
+ "@open-pioneer/http": "^2.1.1",
9
+ "ol": "^8.2.0",
10
+ "uuid": "^9.0.1",
11
+ "@open-pioneer/search": "^0.2.0"
12
+ },
13
+ "peerDependenciesMeta": {
14
+ "@open-pioneer/search": {
15
+ "optional": true
16
+ }
9
17
  },
10
18
  "exports": {
11
19
  "./package.json": "./package.json",
12
20
  ".": {
13
21
  "import": "./index.js",
14
22
  "types": "./index.d.ts"
23
+ },
24
+ "./services": {
25
+ "import": "./services.js",
26
+ "types": "./services.d.ts"
15
27
  }
16
28
  },
17
29
  "openPioneerFramework": {
18
- "services": [],
30
+ "services": [
31
+ {
32
+ "serviceName": "VectorSourceFactory",
33
+ "provides": [
34
+ {
35
+ "interfaceName": "ogc-features.VectorSourceFactory"
36
+ }
37
+ ],
38
+ "references": [
39
+ {
40
+ "referenceName": "httpService",
41
+ "type": "unique",
42
+ "interfaceName": "http.HttpService"
43
+ }
44
+ ]
45
+ },
46
+ {
47
+ "serviceName": "SearchSourceFactory",
48
+ "provides": [
49
+ {
50
+ "interfaceName": "ogc-features.SearchSourceFactory"
51
+ }
52
+ ],
53
+ "references": [
54
+ {
55
+ "referenceName": "httpService",
56
+ "type": "unique",
57
+ "interfaceName": "http.HttpService"
58
+ }
59
+ ]
60
+ }
61
+ ],
62
+ "servicesModule": "./services",
19
63
  "i18n": {
20
64
  "languages": []
21
65
  },
package/requestUtils.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Extent } from "ol/extent";
2
2
  import FeatureFormat from "ol/format/Feature";
3
3
  import { FeatureLike } from "ol/Feature";
4
+ import { HttpService } from "@open-pioneer/http";
4
5
  /**
5
6
  * Assembles the url to use for fetching features in the given extent.
6
7
  */
@@ -19,7 +20,6 @@ export interface FeatureResponse {
19
20
  numberMatched: number | undefined;
20
21
  }
21
22
  /**
22
- * @internal
23
23
  * Performs a single request against the service
24
24
  */
25
- export declare function queryFeatures(fullURL: string, featureFormat: FeatureFormat | undefined, signal: AbortSignal | undefined): Promise<FeatureResponse>;
25
+ export declare function queryFeatures(fullURL: string, featureFormat: FeatureFormat | undefined, httpService: HttpService, signal: AbortSignal | undefined): Promise<FeatureResponse>;
package/requestUtils.js CHANGED
@@ -25,7 +25,7 @@ function getNextURL(rawLinks) {
25
25
  return;
26
26
  return nextLinks[0]?.href;
27
27
  }
28
- async function queryFeatures(fullURL, featureFormat, signal) {
28
+ async function queryFeatures(fullURL, featureFormat, httpService, signal) {
29
29
  let features = [];
30
30
  const requestInit = {
31
31
  headers: {
@@ -33,7 +33,7 @@ async function queryFeatures(fullURL, featureFormat, signal) {
33
33
  },
34
34
  signal
35
35
  };
36
- const response = await fetch(fullURL, requestInit);
36
+ const response = await httpService.fetch(fullURL, requestInit);
37
37
  if (response.status !== 200) {
38
38
  throw new Error(`Failed to query features from service (status code ${response.status})`);
39
39
  }
@@ -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\";\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 * @internal\n * Performs a single request against the service\n */\nexport async function queryFeatures(\n fullURL: string,\n featureFormat: FeatureFormat | undefined,\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 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":"AAMA,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;AAYsB,eAAA,aAAA,CAClB,OACA,EAAA,aAAA,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,KAAM,CAAA,OAAA,EAAS,WAAW,CAAA,CAAA;AACjD,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): 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;;;;"}
package/services.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { Feature } from "ol";
2
+ import { Geometry } from "ol/geom";
3
+ import VectorSource from "ol/source/Vector";
4
+ import { OgcFeatureSearchSourceOptions, OgcFeatureVectorSourceOptions, OgcFeaturesSearchSourceFactory, OgcFeaturesVectorSourceFactory } from "./api";
5
+ import { SearchSource } from "@open-pioneer/search";
6
+ import { HttpService } from "@open-pioneer/http";
7
+ import { ServiceOptions } from "@open-pioneer/runtime";
8
+ interface References {
9
+ httpService: HttpService;
10
+ }
11
+ export declare class VectorSourceFactory implements OgcFeaturesVectorSourceFactory {
12
+ #private;
13
+ constructor({ references }: ServiceOptions<References>);
14
+ createVectorSource(options: OgcFeatureVectorSourceOptions): VectorSource<Feature<Geometry>>;
15
+ }
16
+ export declare class SearchSourceFactory implements OgcFeaturesSearchSourceFactory {
17
+ #private;
18
+ constructor({ references }: ServiceOptions<References>);
19
+ createSearchSource(options: OgcFeatureSearchSourceOptions): SearchSource;
20
+ }
21
+ export {};
package/services.js ADDED
@@ -0,0 +1,24 @@
1
+ import { createVectorSource } from './createVectorSource.js';
2
+ import { OgcFeatureSearchSource } from './OgcFeatureSearchSource.js';
3
+
4
+ class VectorSourceFactory {
5
+ #httpService;
6
+ constructor({ references }) {
7
+ this.#httpService = references.httpService;
8
+ }
9
+ createVectorSource(options) {
10
+ return createVectorSource(options, this.#httpService);
11
+ }
12
+ }
13
+ class SearchSourceFactory {
14
+ #httpService;
15
+ constructor({ references }) {
16
+ this.#httpService = references.httpService;
17
+ }
18
+ createSearchSource(options) {
19
+ return new OgcFeatureSearchSource(options, this.#httpService);
20
+ }
21
+ }
22
+
23
+ export { SearchSourceFactory, VectorSourceFactory };
24
+ //# sourceMappingURL=services.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services.js","sources":["services.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { Feature } from \"ol\";\nimport { Geometry } from \"ol/geom\";\nimport VectorSource from \"ol/source/Vector\";\nimport {\n OgcFeatureSearchSourceOptions,\n OgcFeatureVectorSourceOptions,\n OgcFeaturesSearchSourceFactory,\n OgcFeaturesVectorSourceFactory\n} from \"./api\";\nimport { SearchSource } from \"@open-pioneer/search\";\nimport { createVectorSource } from \"./createVectorSource\";\nimport { OgcFeatureSearchSource } from \"./OgcFeatureSearchSource\";\nimport { HttpService } from \"@open-pioneer/http\";\nimport { ServiceOptions } from \"@open-pioneer/runtime\";\n\n// Both services share the same dependencies for now\ninterface References {\n httpService: HttpService;\n}\n\nexport class VectorSourceFactory implements OgcFeaturesVectorSourceFactory {\n #httpService: HttpService;\n\n constructor({ references }: ServiceOptions<References>) {\n this.#httpService = references.httpService;\n }\n\n createVectorSource(options: OgcFeatureVectorSourceOptions): VectorSource<Feature<Geometry>> {\n return createVectorSource(options, this.#httpService);\n }\n}\n\nexport class SearchSourceFactory implements OgcFeaturesSearchSourceFactory {\n #httpService: HttpService;\n\n constructor({ references }: ServiceOptions<References>) {\n this.#httpService = references.httpService;\n }\n\n createSearchSource(options: OgcFeatureSearchSourceOptions): SearchSource {\n return new OgcFeatureSearchSource(options, this.#httpService);\n }\n}\n"],"names":[],"mappings":";;;AAsBO,MAAM,mBAA8D,CAAA;AAAA,EACvE,YAAA,CAAA;AAAA,EAEA,WAAA,CAAY,EAAE,UAAA,EAA0C,EAAA;AACpD,IAAA,IAAA,CAAK,eAAe,UAAW,CAAA,WAAA,CAAA;AAAA,GACnC;AAAA,EAEA,mBAAmB,OAAyE,EAAA;AACxF,IAAO,OAAA,kBAAA,CAAmB,OAAS,EAAA,IAAA,CAAK,YAAY,CAAA,CAAA;AAAA,GACxD;AACJ,CAAA;AAEO,MAAM,mBAA8D,CAAA;AAAA,EACvE,YAAA,CAAA;AAAA,EAEA,WAAA,CAAY,EAAE,UAAA,EAA0C,EAAA;AACpD,IAAA,IAAA,CAAK,eAAe,UAAW,CAAA,WAAA,CAAA;AAAA,GACnC;AAAA,EAEA,mBAAmB,OAAsD,EAAA;AACrE,IAAA,OAAO,IAAI,sBAAA,CAAuB,OAAS,EAAA,IAAA,CAAK,YAAY,CAAA,CAAA;AAAA,GAChE;AACJ;;;;"}
@@ -1,77 +0,0 @@
1
- import Feature, { FeatureLike } from "ol/Feature";
2
- import FeatureFormat from "ol/format/Feature";
3
- import { Geometry } from "ol/geom";
4
- import { AttributionLike } from "ol/source/Source";
5
- import VectorSource, { Options } from "ol/source/Vector";
6
- import { CollectionInfos, getCollectionInfos } from "./OffsetStrategy";
7
- import { FeatureResponse, queryFeatures } from "./requestUtils";
8
- export interface OgcFeatureSourceOptions {
9
- /** The base-URL right to the "/collections"-part */
10
- baseUrl: string;
11
- /** The collection-ID */
12
- collectionId: string;
13
- /** the URL to the EPSG-Code, e.g. http://www.opengis.net/def/crs/EPSG/0/25832 */
14
- crs: string;
15
- /**
16
- * The maximum number of features to fetch within a single request.
17
- * Corresponds to the `limit` parameter in the URL.
18
- *
19
- * When the `offset` strategy is used for feature fetching, the limit
20
- * is used for the page size
21
- *
22
- * Default limit is 5000 for Next-Strategy and 2500 for Offset-Strategy
23
- */
24
- limit?: number;
25
- /** The maximum number of concurrent requests. Defaults to `6`. */
26
- maxConcurrentRequests?: number;
27
- /** Optional attribution for the layer (e.g. copyright hints). */
28
- attributions?: AttributionLike | undefined;
29
- /** Optional additional options for the VectorSource. */
30
- additionalOptions?: Options<Feature<Geometry>>;
31
- }
32
- /**
33
- * This function creates an OpenLayers VectorSource for OGC Features API services to be used inside
34
- * an OpenLayers VectorLayer.
35
- *
36
- * @param options Options for the vector source.
37
- */
38
- export declare function createVectorSource(options: OgcFeatureSourceOptions): VectorSource;
39
- /**
40
- * @internal
41
- * Creates the actual vector source.
42
- * Exported for testing.
43
- * Exposes `queryFeatures`, `addFeatures` and `getCollectionInfos` for easier testing.
44
- */
45
- export declare function _createVectorSource(options: OgcFeatureSourceOptions, queryFeaturesParam: QueryFeaturesFunc | undefined, addFeaturesParam: AddFeaturesFunc | undefined, getCollectionInfosParam: GetCollectionInfosFunc | undefined): VectorSource;
46
- /** @internal **/
47
- type QueryFeaturesFunc = typeof queryFeatures;
48
- /** @internal **/
49
- type GetCollectionInfosFunc = typeof getCollectionInfos;
50
- /** @internal **/
51
- type AddFeaturesFunc = (features: FeatureLike[]) => void;
52
- /** @internal **/
53
- export interface LoadFeatureOptions {
54
- fullURL: string;
55
- featureFormat: FeatureFormat;
56
- queryFeatures: QueryFeaturesFunc;
57
- addFeatures: AddFeaturesFunc;
58
- limit: number;
59
- maxConcurrentRequests: number;
60
- signal?: AbortSignal;
61
- collectionInfos?: CollectionInfos;
62
- }
63
- /**
64
- * @internal
65
- * Fetches features by following the `next` links in the server's response.
66
- */
67
- export declare function loadAllFeaturesNextStrategy(options: Omit<LoadFeatureOptions, "offsetRequestProps" | "collectionInfos">): Promise<FeatureLike[]>;
68
- export declare function loadFeatures(requestUrl: string, featureFormat: FeatureFormat, signal: AbortSignal | undefined, addFeaturesFunc: AddFeaturesFunc, queryFeaturesFunc?: QueryFeaturesFunc): Promise<FeatureResponse>;
69
- /**
70
- * Loads features from multiple urls in parallel.
71
- * The URLs should represent pages of the same result set.
72
- * The `nextURL` of the last page (if any) is returned from this function.
73
- *
74
- * @internal
75
- */
76
- export declare function loadPages(allUrls: Array<string>, featureFormat: FeatureFormat, signal: AbortSignal | undefined, addFeaturesFunc: AddFeaturesFunc, queryFeaturesFunc?: QueryFeaturesFunc): Promise<FeatureResponse>;
77
- export {};
@@ -1 +0,0 @@
1
- {"version":3,"file":"OgcFeatureSourceFactory.js","sources":["OgcFeatureSourceFactory.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 Feature, { FeatureLike } from \"ol/Feature\";\nimport { FeatureLoader } from \"ol/featureloader\";\nimport FeatureFormat from \"ol/format/Feature\";\nimport GeoJSON from \"ol/format/GeoJSON\";\nimport { Geometry } from \"ol/geom\";\nimport { bbox } from \"ol/loadingstrategy\";\nimport { AttributionLike } from \"ol/source/Source\";\nimport VectorSource, { Options } from \"ol/source/Vector\";\nimport { CollectionInfos, getCollectionInfos, loadAllFeaturesWithOffset } from \"./OffsetStrategy\";\nimport { FeatureResponse, createCollectionRequestUrl, queryFeatures } from \"./requestUtils\";\n\nconst LOG = createLogger(\"ogc-features:OgcFeatureSourceFactory\");\nconst DEFAULT_LIMIT = 5000;\nconst DEFAULT_CONCURRENTY = 6;\n\nexport interface OgcFeatureSourceOptions {\n /** The base-URL right to the \"/collections\"-part */\n baseUrl: string;\n\n /** The collection-ID */\n collectionId: string;\n\n /** the URL to the EPSG-Code, e.g. http://www.opengis.net/def/crs/EPSG/0/25832 */\n crs: string;\n\n /**\n * The maximum number of features to fetch within a single request.\n * Corresponds to the `limit` parameter in the URL.\n *\n * When the `offset` strategy is used for feature fetching, the limit\n * is used for the page size\n *\n * Default limit is 5000 for Next-Strategy and 2500 for Offset-Strategy\n */\n limit?: number;\n\n /** The maximum number of concurrent requests. Defaults to `6`. */\n maxConcurrentRequests?: number;\n\n /** Optional attribution for the layer (e.g. copyright hints). */\n attributions?: AttributionLike | undefined;\n\n /** Optional additional options for the VectorSource. */\n additionalOptions?: Options<Feature<Geometry>>;\n}\n\n/**\n * This function creates an OpenLayers VectorSource for OGC Features API services to be used inside\n * an OpenLayers VectorLayer.\n *\n * @param options Options for the vector source.\n */\nexport function createVectorSource(options: OgcFeatureSourceOptions): VectorSource {\n return _createVectorSource(options, undefined, undefined, 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: OgcFeatureSourceOptions,\n queryFeaturesParam: QueryFeaturesFunc | undefined,\n addFeaturesParam: AddFeaturesFunc | undefined,\n getCollectionInfosParam: GetCollectionInfosFunc | undefined\n): VectorSource {\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 = queryFeaturesParam ?? queryFeatures;\n const getCollectionInfosFunc = getCollectionInfosParam ?? getCollectionInfos;\n const addFeaturesFunc =\n 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);\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 featureFormat: vectorSrc.getFormat()!, // TODO\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 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.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 signal: AbortSignal | undefined,\n addFeaturesFunc: AddFeaturesFunc,\n queryFeaturesFunc: QueryFeaturesFunc = queryFeatures\n): Promise<FeatureResponse> {\n const featureResponse = await queryFeaturesFunc(requestUrl, featureFormat, 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: Array<string>,\n featureFormat: FeatureFormat,\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(singleUrl, featureFormat, signal);\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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"}