UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

134 lines (133 loc) • 5.82 kB
import { GeoJSON } from 'ol/format.js'; import GirafeSingleton from '../../base/GirafeSingleton.js'; import { createBufferedExtentFromFeature } from '../geometrytools.js'; // The default provider is a container for search results that do not result from a provider search (e.g. map re-centering) export const SEARCH_PROVIDER_DEFAULT = 'default'; // The default search group contains search results that do not belong to a specific category, they are listed without a list header export const SEARCH_GROUP_DEFAULT = 'default'; // The map recenter search group contains the coordinates for re-centering the map export const SEARCH_GROUP_RECENTER_MAP = 'recenter_map'; class AbstractSearchClient extends GirafeSingleton { type; providerName; url; resultSrid; searchTermPlaceholder = '###SEARCHTERM###'; searchLangPlaceholder = '###SEARCHLANG###'; searchTermQueryParameter; searchLangQueryParameter; searchFilterQueryParameter; geoJsonFormatter = new GeoJSON(); constructor(context, config) { super(context); this.type = config.type; this.providerName = config.providerName; this.url = this.sanitizeUrl(config.url); this.resultSrid = config.resultSrid; } async initializeSingleton() { await super.initializeSingleton(); } get mapProjection() { return this.context.mapManager.getMap().getView().getProjection(); } /** * Sanitize the url by removing placeholders that are wrongfully interpreted as URL fragments by the URL parser. * The parameters are introduced again and set when fetching the search results. */ sanitizeUrl(url) { // First, replace problematic characters in the placeholders and url that cause issues with the URL parser const sanitizedSearchTermPlaceholder = this.sanitizePlaceholder(this.searchTermPlaceholder); const sanitizedSearchLangPlaceholder = this.sanitizePlaceholder(this.searchLangPlaceholder); const sanitizedUrl = new URL(this.sanitizePlaceholder(url)); // Then, save and remove the query parameters that contain placeholders; they'll be introduced again when fetching the search results this.searchTermQueryParameter = this.extractQueryParamWithPlaceholder(sanitizedUrl, sanitizedSearchTermPlaceholder); this.searchLangQueryParameter = this.extractQueryParamWithPlaceholder(sanitizedUrl, sanitizedSearchLangPlaceholder); return sanitizedUrl; } extractQueryParamWithPlaceholder(url, sanitizedPlaceholder) { const queryParameter = Array.from(url.searchParams.entries()).find((entry) => entry[1] === sanitizedPlaceholder)?.[0]; if (queryParameter) { url.searchParams.delete(queryParameter); } return queryParameter; } sanitizePlaceholder(urlOrPlaceholder) { // Replace ### type placeholders because they are interpreted as URL fragments const placeholderMarker = '###'; const urlSafePlaceholderMarker = '_'; return urlOrPlaceholder.replaceAll(placeholderMarker, urlSafePlaceholderMarker); } /** * Returns a list of categories that can be searched by. */ async getSearchCategories() { return []; } buildRequestUrl(term, filter) { const url = new URL(this.url); if (this.searchTermQueryParameter) { url.searchParams.set(this.searchTermQueryParameter, term); } if (this.searchLangQueryParameter && this.context.stateManager.state.language) { url.searchParams.set(this.searchLangQueryParameter, this.context.stateManager.state.language); } if (this.searchFilterQueryParameter && filter.length) { url.searchParams.set(this.searchFilterQueryParameter, filter.join(',')); } return url; } /** * Executes a search request with the provided search term. */ async requestSearch(term, filter, abortController) { const url = this.buildRequestUrl(term, filter); try { const response = await fetch(url, { signal: abortController.signal }); return await this.parseResponse(response); } catch (error) { console.error(`Error fetching search results from ${this.url.toString()}:`, error); return []; } } /** * Parses the response from the server and returns an array of features. * Features must have the following properties to be used correctly with the search component: * - `provider` * - `label` * - `label_html`: optional, for styled entries in the search results list * - `actions`: optional, a list of SearchResultsActions */ async parseResponse(response) { const data = (await response.json()); const features = this.geoJsonFormatter.readFeatures(data, { dataProjection: this.resultSrid, featureProjection: this.mapProjection }); return features.map((f) => { f.set('provider', this.providerName); return f; }); } /** * Groups the results by the default group, which equivalates to no grouping. Should be overridden by the client class * to group results by a custom criteria like search category. */ groupedResults(results) { return { [SEARCH_GROUP_DEFAULT]: results }; } /** * Returns a bbox around the geometry that is 50% larger than the geometry. */ getZoomToBbox(feature) { return createBufferedExtentFromFeature(feature); } /** * Retrieves the translation key for a given category. Default: use category string directly. */ getI18nKeyForCategory(category) { return category; } } export default AbstractSearchClient;