@geogirafe/lib-geoportal
Version:
GeoGirafe is a flexible application to build online geoportals.
148 lines (147 loc) • 6.56 kB
JavaScript
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;