@geogirafe/lib-geoportal 1.1.0-dev.2610902439 → 1.1.0-dev.2625220907
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/api/apicontext.d.ts +2 -0
- package/api/apicontext.js +4 -0
- package/assets/i18n/de.json +8 -0
- package/assets/i18n/en.json +8 -0
- package/assets/i18n/fr.json +8 -0
- package/assets/i18n/it.json +8 -0
- package/components/search/component.d.ts +6 -15
- package/components/search/component.js +71 -112
- package/components/search/style.css +21 -13
- package/components/search-mobile/component.js +2 -2
- package/components/search-mobile/style.css +25 -13
- package/package.json +1 -1
- package/templates/public/about.json +1 -1
- package/tools/auth/openidconnectmanager.js +10 -1
- package/tools/configuration/girafeconfig.d.ts +17 -2
- package/tools/configuration/girafeconfig.js +32 -12
- package/tools/context/context.d.ts +2 -0
- package/tools/context/context.js +4 -0
- package/tools/context/icontext.d.ts +2 -0
- package/tools/main.d.ts +7 -0
- package/tools/main.js +4 -0
- package/tools/search/abstractsearchclient.d.ts +42 -0
- package/tools/search/abstractsearchclient.js +85 -0
- package/tools/search/geoadminchsearchclient.d.ts +63 -0
- package/tools/search/geoadminchsearchclient.js +147 -0
- package/tools/search/gmfsearchclient.d.ts +23 -0
- package/tools/search/gmfsearchclient.js +37 -0
- package/tools/search/searchmanager.d.ts +33 -0
- package/tools/search/searchmanager.js +130 -0
- package/tools/tests/mockconfig.d.ts +4 -1
- package/tools/tests/mockconfig.js +1 -1
- package/tools/tests/mockcontext.d.ts +2 -0
- package/tools/tests/mockcontext.js +4 -0
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
#searchbox {
|
|
7
7
|
background-color: var(--bkg-color);
|
|
8
|
+
height: 3rem;
|
|
8
9
|
border-right: none;
|
|
9
10
|
border-radius: 4px;
|
|
10
11
|
border: none;
|
|
@@ -71,23 +72,35 @@
|
|
|
71
72
|
overflow-x: hidden;
|
|
72
73
|
scrollbar-width: thin;
|
|
73
74
|
bottom: 0;
|
|
74
|
-
padding-top
|
|
75
|
+
/* padding-top = top-position of search field + height of search field + gap */
|
|
76
|
+
padding-top: calc(max(2.5em, calc(env(safe-area-inset-top) + 0.5rem)) + 3rem + 1rem);
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
.
|
|
78
|
-
.title {
|
|
79
|
+
.entry {
|
|
79
80
|
display: inline-block;
|
|
80
81
|
padding: 0.3rem 1rem;
|
|
81
|
-
width:
|
|
82
|
+
width: calc(36rem - 2rem);
|
|
82
83
|
line-height: 1.3rem;
|
|
84
|
+
color: var(--text-color);
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
.
|
|
86
|
-
|
|
87
|
+
.provider,
|
|
88
|
+
.group {
|
|
87
89
|
font-weight: 600;
|
|
88
|
-
line-height:
|
|
90
|
+
line-height: 3rem;
|
|
89
91
|
font-size: 1.3rem;
|
|
90
|
-
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.provider {
|
|
95
|
+
font-size: 1.5rem;
|
|
96
|
+
margin-top: 1rem;
|
|
97
|
+
background-color: color-mix(in srgb, var(--bkg-color-grad1) 75%, transparent);
|
|
98
|
+
border: 0 solid var(--bkg-color-grad2);
|
|
99
|
+
border-width: 1px 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#results > .provider:first-child {
|
|
103
|
+
margin-top: 0;
|
|
91
104
|
}
|
|
92
105
|
|
|
93
106
|
.result {
|
|
@@ -97,20 +110,19 @@
|
|
|
97
110
|
background: transparent;
|
|
98
111
|
text-align: left;
|
|
99
112
|
width: 100%;
|
|
100
|
-
color: var(--text-color);
|
|
101
113
|
}
|
|
102
114
|
|
|
103
|
-
.
|
|
115
|
+
.entry img {
|
|
104
116
|
margin-right: 1rem;
|
|
105
|
-
width:
|
|
117
|
+
width: 0.8em;
|
|
106
118
|
}
|
|
107
119
|
|
|
108
|
-
.
|
|
120
|
+
.entry span {
|
|
109
121
|
text-transform: uppercase;
|
|
110
122
|
}
|
|
111
123
|
|
|
112
124
|
.result.selected {
|
|
113
|
-
background-color:
|
|
125
|
+
background-color: var(--bkg-color-grad2) !important;
|
|
114
126
|
color: var(--text-color);
|
|
115
127
|
}
|
|
116
128
|
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.1.0-dev.
|
|
1
|
+
{"version":"1.1.0-dev.2625220907", "build":"2625220907", "date":"24/06/2026"}
|
|
@@ -88,7 +88,16 @@ export default class OpenIdConnectManager extends AbstractConnectManager {
|
|
|
88
88
|
authorizationUrl.searchParams.set('code_challenge', code_challenge);
|
|
89
89
|
authorizationUrl.searchParams.set('code_challenge_method', this.issuerConfig.codeChallengeMethod);
|
|
90
90
|
if (silent) {
|
|
91
|
-
|
|
91
|
+
// Add custom parameters for silent login
|
|
92
|
+
for (const [key, value] of Object.entries(this.issuerConfig.customSilentLoginParams)) {
|
|
93
|
+
authorizationUrl.searchParams.set(key, value);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// Add custom parameters for login
|
|
98
|
+
for (const [key, value] of Object.entries(this.issuerConfig.customLoginParams)) {
|
|
99
|
+
authorizationUrl.searchParams.set(key, value);
|
|
100
|
+
}
|
|
92
101
|
}
|
|
93
102
|
return authorizationUrl;
|
|
94
103
|
}
|
|
@@ -43,8 +43,17 @@ declare class GirafeConfig {
|
|
|
43
43
|
};
|
|
44
44
|
};
|
|
45
45
|
search: {
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
providers: ({
|
|
47
|
+
type: 'geomapfish';
|
|
48
|
+
url: string;
|
|
49
|
+
resultSrid: string;
|
|
50
|
+
providerName?: string;
|
|
51
|
+
} | {
|
|
52
|
+
type: 'geoadminch';
|
|
53
|
+
bbox?: string;
|
|
54
|
+
locationOrigins?: string;
|
|
55
|
+
providerName?: string;
|
|
56
|
+
})[];
|
|
48
57
|
objectPreview?: boolean;
|
|
49
58
|
layerPreview?: boolean;
|
|
50
59
|
minResolution?: number;
|
|
@@ -207,6 +216,12 @@ declare class GirafeConfig {
|
|
|
207
216
|
audience: string[];
|
|
208
217
|
audienceExcludedPaths: string[];
|
|
209
218
|
alwaysSendCookies: boolean;
|
|
219
|
+
customSilentLoginParams: {
|
|
220
|
+
[key: string]: string;
|
|
221
|
+
};
|
|
222
|
+
customLoginParams: {
|
|
223
|
+
[key: string]: string;
|
|
224
|
+
};
|
|
210
225
|
};
|
|
211
226
|
geomapfish: {
|
|
212
227
|
userInfoUrl: string;
|
|
@@ -87,10 +87,7 @@ class GirafeConfig {
|
|
|
87
87
|
// The application can be started even if the search is not correctly configured
|
|
88
88
|
// We just display a warning in the console
|
|
89
89
|
console.warn(e);
|
|
90
|
-
this.search = {
|
|
91
|
-
url: '',
|
|
92
|
-
resultsSrid: config.map.srid
|
|
93
|
-
};
|
|
90
|
+
this.search = { providers: [{ type: 'geomapfish', url: '', resultSrid: this.map.srid }] };
|
|
94
91
|
}
|
|
95
92
|
try {
|
|
96
93
|
this.share = this.initConfigShare(config);
|
|
@@ -211,15 +208,36 @@ class GirafeConfig {
|
|
|
211
208
|
return config.print;
|
|
212
209
|
}
|
|
213
210
|
initConfigSearch(config) {
|
|
214
|
-
if (!config.search
|
|
215
|
-
throw new Error(
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
211
|
+
if (!config.search) {
|
|
212
|
+
throw new Error('Configuration for search is required. See https://doc.geogirafe.org/docs/configuration');
|
|
213
|
+
}
|
|
214
|
+
const providers = [];
|
|
215
|
+
for (const provider of config.search.providers ?? []) {
|
|
216
|
+
if (provider.type === 'geomapfish') {
|
|
217
|
+
if (!provider.url.includes('###SEARCHTERM###')) {
|
|
218
|
+
throw new Error(`Search provider url of type "geomapfish" is missing the expected pattern. See https://doc.geogirafe.org/docs/configuration`);
|
|
219
|
+
}
|
|
220
|
+
providers.push({
|
|
221
|
+
type: provider.type,
|
|
222
|
+
providerName: provider.providerName ?? provider.type,
|
|
223
|
+
url: provider.url,
|
|
224
|
+
resultSrid: provider.resultSrid ?? config.map.srid
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
else if (provider.type === 'geoadminch') {
|
|
228
|
+
providers.push({
|
|
229
|
+
type: provider.type,
|
|
230
|
+
providerName: provider.providerName ?? provider.type,
|
|
231
|
+
bbox: provider.bbox,
|
|
232
|
+
locationOrigins: provider.locationOrigins
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
console.warn(`Provider type is missing or not valid: ${provider['type']}`);
|
|
237
|
+
}
|
|
219
238
|
}
|
|
220
239
|
return {
|
|
221
|
-
|
|
222
|
-
resultsSrid: config.search.resultsSrid ?? config.map.srid,
|
|
240
|
+
providers: providers,
|
|
223
241
|
objectPreview: config.search.objectPreview ?? false,
|
|
224
242
|
layerPreview: config.search.layerPreview ?? false,
|
|
225
243
|
minResolution: config.search.minResolution ?? 0.5,
|
|
@@ -390,7 +408,9 @@ class GirafeConfig {
|
|
|
390
408
|
checkSessionOnLoad: config.oauth.issuer.checkSessionOnLoad ?? false,
|
|
391
409
|
audience: config.oauth.issuer.audience,
|
|
392
410
|
audienceExcludedPaths: config.oauth.issuer.audienceExcludedPaths ?? [],
|
|
393
|
-
alwaysSendCookies: config.oauth.issuer.alwaysSendCookies ?? false
|
|
411
|
+
alwaysSendCookies: config.oauth.issuer.alwaysSendCookies ?? false,
|
|
412
|
+
customSilentLoginParams: config.oauth.issuer.customSilentLoginParams ?? {},
|
|
413
|
+
customLoginParams: config.oauth.issuer.customLoginParams ?? {}
|
|
394
414
|
};
|
|
395
415
|
const geomapfishConfig = {
|
|
396
416
|
userInfoUrl: config.oauth.geomapfish.userInfoUrl,
|
|
@@ -31,6 +31,7 @@ import WfsManager from '../wfs/wfsmanager.js';
|
|
|
31
31
|
import WmsManager from '../wms/wmsmanager.js';
|
|
32
32
|
import IGirafeContext from './icontext.js';
|
|
33
33
|
import ThemeFavoritesManager from '../themes/themefavoritesmanager.js';
|
|
34
|
+
import SearchManager from '../search/searchmanager.js';
|
|
34
35
|
import FeedbackManager from '../feedback/feedbackmanager.js';
|
|
35
36
|
export default class GirafeContext implements IGirafeContext {
|
|
36
37
|
readonly userDataManager: UserDataManager;
|
|
@@ -65,6 +66,7 @@ export default class GirafeContext implements IGirafeContext {
|
|
|
65
66
|
readonly localFileManager: LocalFileManager;
|
|
66
67
|
readonly onBoardingManager: OnBoardingManager;
|
|
67
68
|
readonly themeFavoritesManager: ThemeFavoritesManager;
|
|
69
|
+
readonly searchManager: SearchManager;
|
|
68
70
|
readonly feedbackManager: FeedbackManager;
|
|
69
71
|
constructor();
|
|
70
72
|
initialize(): Promise<void>;
|
package/tools/context/context.js
CHANGED
|
@@ -31,6 +31,7 @@ import UserDataManager from '../userdata/userdatamanager.js';
|
|
|
31
31
|
import WfsManager from '../wfs/wfsmanager.js';
|
|
32
32
|
import WmsManager from '../wms/wmsmanager.js';
|
|
33
33
|
import ThemeFavoritesManager from '../themes/themefavoritesmanager.js';
|
|
34
|
+
import SearchManager from '../search/searchmanager.js';
|
|
34
35
|
import FeedbackManager from '../feedback/feedbackmanager.js';
|
|
35
36
|
export default class GirafeContext {
|
|
36
37
|
userDataManager;
|
|
@@ -65,6 +66,7 @@ export default class GirafeContext {
|
|
|
65
66
|
localFileManager;
|
|
66
67
|
onBoardingManager;
|
|
67
68
|
themeFavoritesManager;
|
|
69
|
+
searchManager;
|
|
68
70
|
feedbackManager;
|
|
69
71
|
constructor() {
|
|
70
72
|
this.componentManager = new ComponentManager(this);
|
|
@@ -99,6 +101,7 @@ export default class GirafeContext {
|
|
|
99
101
|
this.ogcApiFeaturesManager = new OgcApiFeaturesManager(this);
|
|
100
102
|
this.onBoardingManager = new OnBoardingManager(this);
|
|
101
103
|
this.themeFavoritesManager = new ThemeFavoritesManager(this);
|
|
104
|
+
this.searchManager = new SearchManager(this);
|
|
102
105
|
this.feedbackManager = new FeedbackManager(this);
|
|
103
106
|
}
|
|
104
107
|
async initialize() {
|
|
@@ -137,6 +140,7 @@ export default class GirafeContext {
|
|
|
137
140
|
this.ogcApiFeaturesManager.initializeSingleton();
|
|
138
141
|
this.onBoardingManager.initializeSingleton();
|
|
139
142
|
this.themeFavoritesManager.initializeSingleton();
|
|
143
|
+
this.searchManager.initializeSingleton();
|
|
140
144
|
this.feedbackManager.initializeSingleton();
|
|
141
145
|
}
|
|
142
146
|
}
|
|
@@ -30,6 +30,7 @@ import UserDataManager from '../userdata/userdatamanager.js';
|
|
|
30
30
|
import WfsManager from '../wfs/wfsmanager.js';
|
|
31
31
|
import WmsManager from '../wms/wmsmanager.js';
|
|
32
32
|
import ThemeFavoritesManager from '../themes/themefavoritesmanager.js';
|
|
33
|
+
import SearchManager from '../search/searchmanager.js';
|
|
33
34
|
import FeedbackManager from '../feedback/feedbackmanager.js';
|
|
34
35
|
export default interface IGirafeContext {
|
|
35
36
|
readonly userDataManager: UserDataManager;
|
|
@@ -64,6 +65,7 @@ export default interface IGirafeContext {
|
|
|
64
65
|
readonly localFileManager: LocalFileManager;
|
|
65
66
|
readonly onBoardingManager: OnBoardingManager;
|
|
66
67
|
readonly themeFavoritesManager: ThemeFavoritesManager;
|
|
68
|
+
readonly searchManager: SearchManager;
|
|
67
69
|
readonly feedbackManager: FeedbackManager;
|
|
68
70
|
initialize(): Promise<void>;
|
|
69
71
|
}
|
package/tools/main.d.ts
CHANGED
|
@@ -58,6 +58,13 @@ export { default as OrderingManager, LayerTreeStartOrder } from './ordering/orde
|
|
|
58
58
|
export type { GeoTransform } from './raster/rasterutils.js';
|
|
59
59
|
export { extractGeoTransform, mapToPixel, pixelToMap, getImage, getPixelValue } from './raster/rasterutils.js';
|
|
60
60
|
export { default as ResizeWindow } from './resizewindow.js';
|
|
61
|
+
export { default as AbstractSearchClient, SEARCH_PROVIDER_DEFAULT, SEARCH_GROUP_DEFAULT, SEARCH_GROUP_RECENTER_MAP } from './search/abstractsearchclient.js';
|
|
62
|
+
export type { GeoAdminChSearchConfig } from './search/geoadminchsearchclient.js';
|
|
63
|
+
export { default as GeoAdminChSearchClient } from './search/geoadminchsearchclient.js';
|
|
64
|
+
export type { GmfSearchConfig } from './search/gmfsearchclient.js';
|
|
65
|
+
export { default as GmfSearchClient } from './search/gmfsearchclient.js';
|
|
66
|
+
export type { SearchProviderConfig } from './search/searchmanager.js';
|
|
67
|
+
export { default as SearchManager } from './search/searchmanager.js';
|
|
61
68
|
export type { default as ISessionManager } from './share/isessionmanager.js';
|
|
62
69
|
export type { UrlShortenerResponse, IUrlShortener } from './share/iurlshortener.js';
|
|
63
70
|
export { default as ActiveBasemapsSerializer } from './share/serializers/activebasemapsserializer.js';
|
package/tools/main.js
CHANGED
|
@@ -48,6 +48,10 @@ export { default as OnBoardingManager } from './onboarding/onboardingmanager.js'
|
|
|
48
48
|
export { default as OrderingManager, LayerTreeStartOrder } from './ordering/orderingmanager.js';
|
|
49
49
|
export { extractGeoTransform, mapToPixel, pixelToMap, getImage, getPixelValue } from './raster/rasterutils.js';
|
|
50
50
|
export { default as ResizeWindow } from './resizewindow.js';
|
|
51
|
+
export { default as AbstractSearchClient, SEARCH_PROVIDER_DEFAULT, SEARCH_GROUP_DEFAULT, SEARCH_GROUP_RECENTER_MAP } from './search/abstractsearchclient.js';
|
|
52
|
+
export { default as GeoAdminChSearchClient } from './search/geoadminchsearchclient.js';
|
|
53
|
+
export { default as GmfSearchClient } from './search/gmfsearchclient.js';
|
|
54
|
+
export { default as SearchManager } from './search/searchmanager.js';
|
|
51
55
|
export { default as ActiveBasemapsSerializer } from './share/serializers/activebasemapsserializer.js';
|
|
52
56
|
export { default as CustomLayersSerializer } from './share/serializers/customlayersserializer.js';
|
|
53
57
|
export { default as GlobeSerializer } from './share/serializers/globeserializer.js';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Feature from 'ol/Feature.js';
|
|
2
|
+
import { GeoJSON } from 'ol/format.js';
|
|
3
|
+
import GirafeSingleton from '../../base/GirafeSingleton.js';
|
|
4
|
+
import IGirafeContext from '../context/icontext.js';
|
|
5
|
+
import { Extent } from 'ol/extent.js';
|
|
6
|
+
export declare const SEARCH_PROVIDER_DEFAULT = "default";
|
|
7
|
+
export declare const SEARCH_GROUP_DEFAULT = "default";
|
|
8
|
+
export declare const SEARCH_GROUP_RECENTER_MAP = "recenter_map";
|
|
9
|
+
declare abstract class AbstractSearchClient extends GirafeSingleton {
|
|
10
|
+
readonly type: string;
|
|
11
|
+
readonly providerName: string;
|
|
12
|
+
protected url: URL;
|
|
13
|
+
protected readonly resultSrid: string;
|
|
14
|
+
protected readonly searchTermPlaceholder = "###SEARCHTERM###";
|
|
15
|
+
protected readonly searchLangPlaceholder = "###SEARCHLANG###";
|
|
16
|
+
protected readonly geoJsonFormatter: GeoJSON<Feature<import("ol/geom.js").Geometry, {
|
|
17
|
+
[x: string]: any;
|
|
18
|
+
}>>;
|
|
19
|
+
protected constructor(context: IGirafeContext, type: string, name: string, url: string, resultSrid: string);
|
|
20
|
+
initializeSingleton(): void;
|
|
21
|
+
protected get mapProjection(): import("ol/proj.js").Projection;
|
|
22
|
+
requestSearch(term: string, abortController: AbortController): Promise<Feature[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Parses the response from the server and returns an array of features.
|
|
25
|
+
* Features must have the following properties to be used correctly with the search component:
|
|
26
|
+
* - `provider`
|
|
27
|
+
* - `label`
|
|
28
|
+
* - `label_html`: optional, for styled entries in the search results list
|
|
29
|
+
* - `actions`: optional, a list of SearchResultsActions
|
|
30
|
+
*/
|
|
31
|
+
protected parseResponse(response: Response): Promise<Feature[]>;
|
|
32
|
+
/**
|
|
33
|
+
* Groups results by the default group, which equivalates to no grouping. Should be overridden by the client class
|
|
34
|
+
* to group results by a custom criteria.
|
|
35
|
+
*/
|
|
36
|
+
groupedResults(results: Feature[]): Record<string, Feature[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Returns a bbox around the geometry that is 50% larger than the geometry.
|
|
39
|
+
*/
|
|
40
|
+
getZoomToBbox(feature: Feature): Extent | undefined;
|
|
41
|
+
}
|
|
42
|
+
export default AbstractSearchClient;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { GeoJSON } from 'ol/format.js';
|
|
2
|
+
import GirafeSingleton from '../../base/GirafeSingleton.js';
|
|
3
|
+
import { buffer, getHeight, getWidth } from 'ol/extent.js';
|
|
4
|
+
// The default provider is a container for search results that do not result from a provider search (e.g. map re-centering)
|
|
5
|
+
export const SEARCH_PROVIDER_DEFAULT = 'default';
|
|
6
|
+
// The default search group contains search results that do not belong to a specific category
|
|
7
|
+
export const SEARCH_GROUP_DEFAULT = 'default';
|
|
8
|
+
// The map recenter search group contains the coordinates for re-centering the map
|
|
9
|
+
export const SEARCH_GROUP_RECENTER_MAP = 'recenter_map';
|
|
10
|
+
class AbstractSearchClient extends GirafeSingleton {
|
|
11
|
+
type;
|
|
12
|
+
providerName;
|
|
13
|
+
url;
|
|
14
|
+
resultSrid;
|
|
15
|
+
searchTermPlaceholder = '###SEARCHTERM###';
|
|
16
|
+
searchLangPlaceholder = '###SEARCHLANG###';
|
|
17
|
+
geoJsonFormatter = new GeoJSON();
|
|
18
|
+
constructor(context, type, name, url, resultSrid) {
|
|
19
|
+
super(context);
|
|
20
|
+
this.type = type;
|
|
21
|
+
this.providerName = name;
|
|
22
|
+
this.url = new URL(url);
|
|
23
|
+
this.resultSrid = resultSrid;
|
|
24
|
+
}
|
|
25
|
+
initializeSingleton() {
|
|
26
|
+
super.initializeSingleton();
|
|
27
|
+
}
|
|
28
|
+
get mapProjection() {
|
|
29
|
+
return this.context.mapManager.getMap().getView().getProjection();
|
|
30
|
+
}
|
|
31
|
+
async requestSearch(term, abortController) {
|
|
32
|
+
const url = this.url
|
|
33
|
+
.toString()
|
|
34
|
+
.replace(this.searchTermPlaceholder, term)
|
|
35
|
+
.replace(this.searchLangPlaceholder, this.context.stateManager.state.language);
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetch(url, { signal: abortController.signal });
|
|
38
|
+
return await this.parseResponse(response);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error(`Error fetching search results from ${this.url.toString()}:`, error);
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Parses the response from the server and returns an array of features.
|
|
47
|
+
* Features must have the following properties to be used correctly with the search component:
|
|
48
|
+
* - `provider`
|
|
49
|
+
* - `label`
|
|
50
|
+
* - `label_html`: optional, for styled entries in the search results list
|
|
51
|
+
* - `actions`: optional, a list of SearchResultsActions
|
|
52
|
+
*/
|
|
53
|
+
async parseResponse(response) {
|
|
54
|
+
const data = (await response.json());
|
|
55
|
+
const features = this.geoJsonFormatter.readFeatures(data, {
|
|
56
|
+
dataProjection: this.resultSrid,
|
|
57
|
+
featureProjection: this.mapProjection
|
|
58
|
+
});
|
|
59
|
+
return features.map((f) => {
|
|
60
|
+
f.set('provider', this.providerName);
|
|
61
|
+
return f;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Groups results by the default group, which equivalates to no grouping. Should be overridden by the client class
|
|
66
|
+
* to group results by a custom criteria.
|
|
67
|
+
*/
|
|
68
|
+
groupedResults(results) {
|
|
69
|
+
return { [SEARCH_GROUP_DEFAULT]: results };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Returns a bbox around the geometry that is 50% larger than the geometry.
|
|
73
|
+
*/
|
|
74
|
+
getZoomToBbox(feature) {
|
|
75
|
+
const geom = feature.getGeometry();
|
|
76
|
+
if (geom) {
|
|
77
|
+
const extent = geom.getExtent();
|
|
78
|
+
// We create a buffer around the extent from 50% of the width/height
|
|
79
|
+
const bufferValue = Math.max((getWidth(extent) * 50) / 100, (getHeight(extent) * 50) / 100);
|
|
80
|
+
return buffer(extent, bufferValue);
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export default AbstractSearchClient;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import Feature from 'ol/Feature.js';
|
|
2
|
+
import AbstractSearchClient from './abstractsearchclient.js';
|
|
3
|
+
import IGirafeContext from '../context/icontext.js';
|
|
4
|
+
import { Extent } from 'ol/extent.js';
|
|
5
|
+
export type GeoAdminChSearchConfig = {
|
|
6
|
+
type: 'geoadminch';
|
|
7
|
+
providerName: string;
|
|
8
|
+
bbox?: string;
|
|
9
|
+
locationOrigins?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Search provider for the Swiss search service of geo.admin.ch.
|
|
13
|
+
* See documentation at https://docs.geo.admin.ch/access-data/search.html
|
|
14
|
+
*
|
|
15
|
+
* The GeoAdmin search provider allows 3 different types of searches:
|
|
16
|
+
* - locations: POIs like addresses, swissnames, boundaries, parcels
|
|
17
|
+
* - layers: layers of the GeoAdmin portal
|
|
18
|
+
* - features: features inside one or multiple layers of the GeoAdmin portal
|
|
19
|
+
*
|
|
20
|
+
* This client currently only supports location search.
|
|
21
|
+
* The API returns max 50 items per request.
|
|
22
|
+
*
|
|
23
|
+
* Configuration:
|
|
24
|
+
* - providerName: The name of the search provider, or geoadminch.
|
|
25
|
+
* - resultSrid: The spatial reference system (SRID) of the search results, always EPSG:2056.
|
|
26
|
+
* - bbox: Comma-separated list of bounding box coordinates in EPSG:2056 (e.g. "2580000,1150000,2600000,1200000")
|
|
27
|
+
* If no bbox is set, the max extent of the map is used.
|
|
28
|
+
* - locationOrigins: Array of categories that are searched. Default: All categories.
|
|
29
|
+
* Available categories and data origin:
|
|
30
|
+
* - zipcode (ch.swisstopo-vd.ortschaftenverzeichnis_plz)
|
|
31
|
+
* - gg25 (ch.swisstopo.swissboundaries3d-gemeinde-flaeche.fill)
|
|
32
|
+
* - district (ch.swisstopo.swissboundaries3d-bezirk-flaeche.fill)
|
|
33
|
+
* - kantone (ch.swisstopo.swissboundaries3d-kanton-flaeche.fill)
|
|
34
|
+
* - gazetteer (ch.swisstopo.swissnames3d, ch.bav.haltestellen-oev)
|
|
35
|
+
* - address (ch.swisstopo.amtliches-gebaeudeadressverzeichnis, including EGID)
|
|
36
|
+
* - parcel
|
|
37
|
+
*/
|
|
38
|
+
declare class GeoAdminChSearchClient extends AbstractSearchClient {
|
|
39
|
+
constructor(context: IGirafeContext, config: GeoAdminChSearchConfig);
|
|
40
|
+
private setSearchExtent;
|
|
41
|
+
requestSearch(term: string, abortController: AbortController): Promise<Feature[]>;
|
|
42
|
+
protected parseResponse(response: Response): Promise<Feature[]>;
|
|
43
|
+
/**
|
|
44
|
+
* GeoAdmin geoJson responses contain coordinates as [north, east], but the GeoJson standard is [east, north].
|
|
45
|
+
* To be able to use the ol GeoJson format reader, the coordinates are rearranged.
|
|
46
|
+
* Note that other spatial information in the GeoAdmin response is valid, e.g. the bbox has the correct order.
|
|
47
|
+
*/
|
|
48
|
+
private fixGeoAdminCoordinates;
|
|
49
|
+
/**
|
|
50
|
+
* Swap first (north) and second (east) coordinate in an array of coordinates.
|
|
51
|
+
*/
|
|
52
|
+
private swapCoordinates;
|
|
53
|
+
/**
|
|
54
|
+
* Property origin contains the result category:
|
|
55
|
+
*/
|
|
56
|
+
groupedResults(results: Feature[]): Record<string, Feature[]>;
|
|
57
|
+
getZoomToBbox(feature: Feature): Extent | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Remove bold and italic HTML tags from the label
|
|
60
|
+
*/
|
|
61
|
+
private sanitizeHtmlLabel;
|
|
62
|
+
}
|
|
63
|
+
export default GeoAdminChSearchClient;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import AbstractSearchClient from './abstractsearchclient.js';
|
|
2
|
+
import { transformExtent } from 'ol/proj.js';
|
|
3
|
+
/**
|
|
4
|
+
* Search provider for the Swiss search service of geo.admin.ch.
|
|
5
|
+
* See documentation at https://docs.geo.admin.ch/access-data/search.html
|
|
6
|
+
*
|
|
7
|
+
* The GeoAdmin search provider allows 3 different types of searches:
|
|
8
|
+
* - locations: POIs like addresses, swissnames, boundaries, parcels
|
|
9
|
+
* - layers: layers of the GeoAdmin portal
|
|
10
|
+
* - features: features inside one or multiple layers of the GeoAdmin portal
|
|
11
|
+
*
|
|
12
|
+
* This client currently only supports location search.
|
|
13
|
+
* The API returns max 50 items per request.
|
|
14
|
+
*
|
|
15
|
+
* Configuration:
|
|
16
|
+
* - providerName: The name of the search provider, or geoadminch.
|
|
17
|
+
* - resultSrid: The spatial reference system (SRID) of the search results, always EPSG:2056.
|
|
18
|
+
* - bbox: Comma-separated list of bounding box coordinates in EPSG:2056 (e.g. "2580000,1150000,2600000,1200000")
|
|
19
|
+
* If no bbox is set, the max extent of the map is used.
|
|
20
|
+
* - locationOrigins: Array of categories that are searched. Default: All categories.
|
|
21
|
+
* Available categories and data origin:
|
|
22
|
+
* - zipcode (ch.swisstopo-vd.ortschaftenverzeichnis_plz)
|
|
23
|
+
* - gg25 (ch.swisstopo.swissboundaries3d-gemeinde-flaeche.fill)
|
|
24
|
+
* - district (ch.swisstopo.swissboundaries3d-bezirk-flaeche.fill)
|
|
25
|
+
* - kantone (ch.swisstopo.swissboundaries3d-kanton-flaeche.fill)
|
|
26
|
+
* - gazetteer (ch.swisstopo.swissnames3d, ch.bav.haltestellen-oev)
|
|
27
|
+
* - address (ch.swisstopo.amtliches-gebaeudeadressverzeichnis, including EGID)
|
|
28
|
+
* - parcel
|
|
29
|
+
*/
|
|
30
|
+
class GeoAdminChSearchClient extends AbstractSearchClient {
|
|
31
|
+
constructor(context, config) {
|
|
32
|
+
super(context, 'geoadminch', config.providerName, 'https://api3.geo.admin.ch/rest/services/api/SearchServer', 'EPSG:2056');
|
|
33
|
+
this.url.searchParams.set('type', 'locations');
|
|
34
|
+
this.url.searchParams.set('sr', this.resultSrid.replace('EPSG:', ''));
|
|
35
|
+
this.url.searchParams.set('geometryFormat', 'geojson');
|
|
36
|
+
this.url.searchParams.set('sortbox', 'false');
|
|
37
|
+
if (config.locationOrigins) {
|
|
38
|
+
this.url.searchParams.set('origins', config.locationOrigins);
|
|
39
|
+
}
|
|
40
|
+
this.setSearchExtent(config.bbox);
|
|
41
|
+
}
|
|
42
|
+
setSearchExtent(bbox) {
|
|
43
|
+
let searchExtent;
|
|
44
|
+
if (bbox) {
|
|
45
|
+
searchExtent = bbox;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// If no bbox is configured, set the search extent to the max map extent
|
|
49
|
+
searchExtent = this.context.configManager.Config.map.maxExtent;
|
|
50
|
+
}
|
|
51
|
+
if (!searchExtent) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// If the default map extent isn't specified in EPSG:2056, transform it
|
|
55
|
+
const defaultSrid = this.context.configManager.getDefaultConfigValue('map.srid');
|
|
56
|
+
if (defaultSrid !== this.resultSrid) {
|
|
57
|
+
const extent = searchExtent.split(',').map(Number);
|
|
58
|
+
searchExtent = transformExtent(extent, defaultSrid, this.resultSrid).join(',');
|
|
59
|
+
}
|
|
60
|
+
this.url.searchParams.set('bbox', searchExtent);
|
|
61
|
+
}
|
|
62
|
+
async requestSearch(term, abortController) {
|
|
63
|
+
try {
|
|
64
|
+
const url = new URL(this.url);
|
|
65
|
+
url.searchParams.set('searchText', term);
|
|
66
|
+
url.searchParams.set('lang', this.context.stateManager.state.language);
|
|
67
|
+
const response = await fetch(url, { signal: abortController.signal });
|
|
68
|
+
return await this.parseResponse(response);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error('Error fetching search results from GeoAdminSearch:', error);
|
|
72
|
+
return new Promise(() => []);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async parseResponse(response) {
|
|
76
|
+
const data = this.fixGeoAdminCoordinates((await response.json()));
|
|
77
|
+
const features = this.geoJsonFormatter.readFeatures(data, {
|
|
78
|
+
dataProjection: this.resultSrid,
|
|
79
|
+
featureProjection: this.mapProjection
|
|
80
|
+
});
|
|
81
|
+
// Property `label` contains an HTML string. Rename `label` to `label_html` to use in the dropdown,
|
|
82
|
+
// and sanitize `label` to be used in the input field
|
|
83
|
+
return features.map((f) => {
|
|
84
|
+
f.set('provider', this.providerName);
|
|
85
|
+
f.set('label_html', f.get('label'));
|
|
86
|
+
f.set('label', this.sanitizeHtmlLabel(f.get('label')));
|
|
87
|
+
return f;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* GeoAdmin geoJson responses contain coordinates as [north, east], but the GeoJson standard is [east, north].
|
|
92
|
+
* To be able to use the ol GeoJson format reader, the coordinates are rearranged.
|
|
93
|
+
* Note that other spatial information in the GeoAdmin response is valid, e.g. the bbox has the correct order.
|
|
94
|
+
*/
|
|
95
|
+
fixGeoAdminCoordinates(data) {
|
|
96
|
+
if ('features' in data && Array.isArray(data.features)) {
|
|
97
|
+
for (const feature of data.features) {
|
|
98
|
+
if (feature.geometry) {
|
|
99
|
+
feature.geometry.coordinates = this.swapCoordinates(feature.geometry.coordinates);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return data;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Swap first (north) and second (east) coordinate in an array of coordinates.
|
|
107
|
+
*/
|
|
108
|
+
swapCoordinates(coordinates) {
|
|
109
|
+
if (Array.isArray(coordinates) &&
|
|
110
|
+
coordinates.length >= 2 &&
|
|
111
|
+
typeof coordinates[0] === 'number' &&
|
|
112
|
+
typeof coordinates[1] === 'number' &&
|
|
113
|
+
coordinates[0] < coordinates[1]) {
|
|
114
|
+
const pos = coordinates;
|
|
115
|
+
return [pos[1], pos[0], ...pos.slice(2)];
|
|
116
|
+
}
|
|
117
|
+
return coordinates.map(this.swapCoordinates);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Property origin contains the result category:
|
|
121
|
+
*/
|
|
122
|
+
groupedResults(results) {
|
|
123
|
+
const groupedResults = {};
|
|
124
|
+
for (const result of results) {
|
|
125
|
+
const group = `geoadminch_${result.get('origin') ?? 'Other'}`;
|
|
126
|
+
groupedResults[group] ??= [];
|
|
127
|
+
groupedResults[group].push(result);
|
|
128
|
+
}
|
|
129
|
+
return groupedResults;
|
|
130
|
+
}
|
|
131
|
+
getZoomToBbox(feature) {
|
|
132
|
+
const st_bbox = feature.get('geom_st_box2d');
|
|
133
|
+
if (st_bbox) {
|
|
134
|
+
const bbox_coords = st_bbox.replace('BOX(', '').replace(')', '').replaceAll(' ', ',');
|
|
135
|
+
const extent = bbox_coords.split(',').map((coord) => Number.parseFloat(coord));
|
|
136
|
+
return transformExtent(extent, this.resultSrid, this.mapProjection);
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Remove bold and italic HTML tags from the label
|
|
142
|
+
*/
|
|
143
|
+
sanitizeHtmlLabel(label) {
|
|
144
|
+
return label.replace(/<\/?[ib]>/g, '');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
export default GeoAdminChSearchClient;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import IGirafeContext from '../context/icontext.js';
|
|
2
|
+
import Feature from 'ol/Feature.js';
|
|
3
|
+
import AbstractSearchClient from './abstractsearchclient.js';
|
|
4
|
+
export type GmfSearchConfig = {
|
|
5
|
+
type: 'geomapfish';
|
|
6
|
+
providerName: string;
|
|
7
|
+
url: string;
|
|
8
|
+
resultSrid: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* A search provider for the GeoMapFish backend.
|
|
12
|
+
* Extends the AbstractSearchClient and provides methods tailored to processing GMF-specific search results.
|
|
13
|
+
*
|
|
14
|
+
* Configuration:
|
|
15
|
+
* - providerName: The name of the search provider, or geomapfish.
|
|
16
|
+
* - url: The URL of the GMF search service.
|
|
17
|
+
* - resultSrid: The spatial reference system (SRID) of the search results. Default same as map SRID.
|
|
18
|
+
*/
|
|
19
|
+
declare class GmfSearchClient extends AbstractSearchClient {
|
|
20
|
+
constructor(context: IGirafeContext, config: GmfSearchConfig);
|
|
21
|
+
groupedResults(results: Feature[]): Record<string, Feature[]>;
|
|
22
|
+
}
|
|
23
|
+
export default GmfSearchClient;
|