UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

148 lines (147 loc) 6.56 kB
import AbstractSearchClient from './abstractsearchclient.js'; import { transformExtent } from 'ol/proj.js'; /** * Search provider for the Swiss search service of geo.admin.ch. * See documentation at https://docs.geo.admin.ch/access-data/search.html * * The GeoAdmin search provider allows 3 different types of searches: * - locations: POIs like addresses, swissnames, boundaries, parcels * - layers: layers of the GeoAdmin portal * - features: features inside one or multiple layers of the GeoAdmin portal * * This client currently only supports location search. * The API returns max 50 items per request. * * Configuration: * - providerName: The name of the search provider, or geoadminch. * - resultSrid: The spatial reference system (SRID) of the search results, always EPSG:2056. * - bbox: Comma-separated list of bounding box coordinates in EPSG:2056 (e.g. "2580000,1150000,2600000,1200000") * If no bbox is set, the max extent of the map is used. * - locationOrigins: Array of categories that are searched. Default: All categories. * Available categories and data origin: * - zipcode (ch.swisstopo-vd.ortschaftenverzeichnis_plz) * - gg25 (ch.swisstopo.swissboundaries3d-gemeinde-flaeche.fill) * - district (ch.swisstopo.swissboundaries3d-bezirk-flaeche.fill) * - kantone (ch.swisstopo.swissboundaries3d-kanton-flaeche.fill) * - gazetteer (ch.swisstopo.swissnames3d, ch.bav.haltestellen-oev) * - address (ch.swisstopo.amtliches-gebaeudeadressverzeichnis, including EGID) * - parcel */ class GeoAdminChSearchClient extends AbstractSearchClient { constructor(context, config) { super(context, 'geoadminch', config.providerName, 'https://api3.geo.admin.ch/rest/services/api/SearchServer', 'EPSG:2056'); this.url.searchParams.set('type', 'locations'); this.url.searchParams.set('sr', this.resultSrid.replace('EPSG:', '')); this.url.searchParams.set('geometryFormat', 'geojson'); this.url.searchParams.set('sortbox', 'false'); if (config.locationOrigins) { this.url.searchParams.set('origins', config.locationOrigins); } this.setSearchExtent(config.bbox); } setSearchExtent(bbox) { let searchExtent; if (bbox) { searchExtent = bbox; } else { // If no bbox is configured, set the search extent to the max map extent searchExtent = this.context.configManager.Config.map.maxExtent; } if (!searchExtent) { return; } // If the default map extent isn't specified in EPSG:2056, transform it const defaultSrid = this.context.configManager.getDefaultConfigValue('map.srid'); if (defaultSrid !== this.resultSrid) { const extent = searchExtent.split(',').map(Number); searchExtent = transformExtent(extent, defaultSrid, this.resultSrid).join(','); } this.url.searchParams.set('bbox', searchExtent); } async requestSearch(term, abortController) { try { const url = new URL(this.url); url.searchParams.set('searchText', term); url.searchParams.set('lang', this.context.stateManager.state.language); const response = await fetch(url, { signal: abortController.signal }); return await this.parseResponse(response); } catch (error) { console.error('Error fetching search results from GeoAdminSearch:', error); return new Promise(() => []); } } async parseResponse(response) { const data = this.fixGeoAdminCoordinates((await response.json())); const features = this.geoJsonFormatter.readFeatures(data, { dataProjection: this.resultSrid, featureProjection: this.mapProjection }); // Property `label` contains an HTML string. Rename `label` to `label_html` to use in the dropdown, // and sanitize `label` to be used in the input field return features.map((f) => { f.set('provider', this.providerName); f.set('label_html', f.get('label')); f.set('label', this.sanitizeHtmlLabel(f.get('label'))); return f; }); } /** * GeoAdmin geoJson responses contain coordinates as [north, east], but the GeoJson standard is [east, north]. * To be able to use the ol GeoJson format reader, the coordinates are rearranged. * Note that other spatial information in the GeoAdmin response is valid, e.g. the bbox has the correct order. */ fixGeoAdminCoordinates(data) { if ('features' in data && Array.isArray(data.features)) { for (const feature of data.features) { if (feature.geometry) { feature.geometry.coordinates = this.swapCoordinates(feature.geometry.coordinates); } } } return data; } /** * Swap first (north) and second (east) coordinate in an array of coordinates. */ swapCoordinates(coordinates) { if (Array.isArray(coordinates) && coordinates.length >= 2 && typeof coordinates[0] === 'number' && typeof coordinates[1] === 'number' && coordinates[0] < coordinates[1]) { const pos = coordinates; return [pos[1], pos[0], ...pos.slice(2)]; } return coordinates.map(this.swapCoordinates); } /** * Property origin contains the result category: */ groupedResults(results) { const groupedResults = {}; for (const result of results) { const group = `geoadminch_${result.get('origin') ?? 'Other'}`; groupedResults[group] ??= []; groupedResults[group].push(result); } return groupedResults; } getZoomToBbox(feature) { const st_bbox = feature.get('geom_st_box2d'); if (st_bbox) { const bbox_coords = st_bbox.replace('BOX(', '').replace(')', '').replaceAll(' ', ','); const extent = bbox_coords.split(',').map((coord) => Number.parseFloat(coord)); return transformExtent(extent, this.resultSrid, this.mapProjection); } return undefined; } /** * Remove bold and italic HTML tags from the label */ sanitizeHtmlLabel(label) { return label.replace(/<\/?[ib]>/g, ''); } } export default GeoAdminChSearchClient;