From 4c7f7afb6c7516ef674a19134ff66ce9a307f2fc Mon Sep 17 00:00:00 2001 From: Marion Date: Wed, 13 Nov 2024 08:20:22 +0100 Subject: [PATCH 1/2] implement the swisstopo geolocation search API --- src/app/_services/map.service.ts | 53 +++++++++++++++++++++++--- src/app/welcome/map/map.component.html | 2 +- src/app/welcome/map/map.component.ts | 21 ++++------ src/assets/configs/config.json.tmpl | 2 +- 4 files changed, 58 insertions(+), 20 deletions(-) diff --git a/src/app/_services/map.service.ts b/src/app/_services/map.service.ts index 1d92d4a2..bc415275 100644 --- a/src/app/_services/map.service.ts +++ b/src/app/_services/map.service.ts @@ -290,8 +290,12 @@ export class MapService { return of([]); } const url = new URL(urlText); - url.searchParams.append('partitionlimit', '10'); - url.searchParams.append('query', inputText); + url.searchParams.append('searchText', inputText); + url.searchParams.append('limit', '5'); // TODO find a good limit or this + url.searchParams.append('geometryFormat', 'geojson'); + url.searchParams.append('type', 'locations'); + url.searchParams.append('sr', '2056'); + url.searchParams.append('origins','district,gg25,parcel,address'); return this.httpClient.get(url.toString()).pipe( map((featureCollectionData) => { const featureCollection = this.geoJsonFormatter.readFeatures(featureCollectionData); @@ -303,6 +307,35 @@ export class MapService { ); } + public stripHtmlTags(html: string): string { + const div = document.createElement('div'); + div.innerHTML = html; + return div.textContent || div.innerText || ''; + } + + public createPolygonFromBBOX(bboxString: string): Polygon { + const coords = bboxString + .replace('BOX(', '') + .replace(')', '') + .split(',') + .map(coord => coord.trim().split(' ').map(Number)); + + const [minX, minY] = coords[0]; + const [maxX, maxY] = coords[1]; + console.log(coords); + console.log(minX, minY, maxX, maxY); + + const polygonCoords = [ + [minX, minY], + [maxX, minY], + [maxX, maxY], + [minX, maxY], + [minX, minY] + ]; + + return new Polygon([polygonCoords]); + } + /** * Sets two geometries on the map based on the feature returned by de geocoder: * - The extent as an order perimeter @@ -323,9 +356,19 @@ export class MapService { let poly: Polygon; const geometry = feature.getGeometry(); if (geometry instanceof Point) { - const text = boundingExtent([geometry.getCoordinates()]); - const bv = 50; - poly = fromExtent(buffer(text, bv)); + // TODO if the BBOX is just a point + const bboxstring = feature.get('geom_st_box2d'); + + poly = this.createPolygonFromBBOX(bboxstring); + feature.setGeometry(poly); + + this.drawingSource.addFeature(feature); + this.modifyInteraction.setActive(true); + + this.map.getView().fit(poly, { + padding: [75, 75, 75, 75] + }); + } else { const originalExtent = feature.getGeometry()?.getExtent(); if (originalExtent) { diff --git a/src/app/welcome/map/map.component.html b/src/app/welcome/map/map.component.html index 62fb7e2a..13587552 100644 --- a/src/app/welcome/map/map.component.html +++ b/src/app/welcome/map/map.component.html @@ -15,7 +15,7 @@ diff --git a/src/app/welcome/map/map.component.ts b/src/app/welcome/map/map.component.ts index 8721586a..21ccd7a9 100644 --- a/src/app/welcome/map/map.component.ts +++ b/src/app/welcome/map/map.component.ts @@ -109,26 +109,21 @@ export class MapComponent implements OnInit { this.shouldDisplayClearButton = true; this.geocoderGroupOptions = []; - for (const feature of features) { - const categoryId = feature.get('layer_name'); - if (this.configService.config?.geocoderLayers) { - if (this.configService.config.geocoderLayers.indexOf(categoryId) < 0) { - continue; - } - } + for (const feature of features) { // TODO the following part needs to be adapted for the swiss topo API search + const categoryId = feature.get('objectclass'); let currentCategory = this.geocoderGroupOptions.find(x => x.id === categoryId); if (currentCategory) { currentCategory.items.push({ - label: feature.get('label'), - feature + label: categoryId, // TODO this should be a translated ctegory + feature }); } else { currentCategory = { - id: categoryId, - label: nameOfCategoryForGeocoder[categoryId], + id: feature.get('objectclass'), + label: feature.get('objectclass'), // TODO this should be a translated ctegory items: [{ - label: feature.get('label'), + label: this.mapService.stripHtmlTags(feature.get('label')), feature }] }; @@ -143,7 +138,7 @@ export class MapComponent implements OnInit { return value.label; } - displayGeocoderResultOnTheMap(evt: MatAutocompleteSelectedEvent) { + displayGeocoderResultOnTheMap(evt: MatAutocompleteSelectedEvent) { // TODO this adds the feature from the auto complete on the map this.mapService.addFeatureFromGeocoderToDrawing(evt.option.value.feature); this.shouldDisplayClearButton = true; } diff --git a/src/assets/configs/config.json.tmpl b/src/assets/configs/config.json.tmpl index d776ec91..d8ebd184 100644 --- a/src/assets/configs/config.json.tmpl +++ b/src/assets/configs/config.json.tmpl @@ -2,7 +2,7 @@ "apiUrl": "${API_BASE_URL}/${API_ROOTURL}", "mediaUrl": "${MEDIA_URL}", "baseMapUrl": "https://wmts.geo.admin.ch/1.0.0/{layer}/{style}/current/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}", - "geocoderUrl": "https://nominatim.openstreetmap.org/search", + "geocoderUrl": "https://api3.geo.admin.ch/rest/services/api/SearchServer", "geocoderLayers": [ "adresses_sitn", "nom_local_lieu_dit", "search_cours_eau", "batiments_ofs", "axe_rue", "ImmeublesCanton", "communes", "cadastres", From 8f14c55a373ff25ceca6645729251b5ee6d891fa Mon Sep 17 00:00:00 2001 From: Marion Date: Wed, 13 Nov 2024 10:21:57 +0100 Subject: [PATCH 2/2] Categorize search results into the origins that can be queried for better UX --- src/app/_services/map.service.ts | 6 ++-- src/app/welcome/map/map.component.ts | 45 ++++++++++------------------ 2 files changed, 17 insertions(+), 34 deletions(-) diff --git a/src/app/_services/map.service.ts b/src/app/_services/map.service.ts index bc415275..61abbad0 100644 --- a/src/app/_services/map.service.ts +++ b/src/app/_services/map.service.ts @@ -291,7 +291,7 @@ export class MapService { } const url = new URL(urlText); url.searchParams.append('searchText', inputText); - url.searchParams.append('limit', '5'); // TODO find a good limit or this + url.searchParams.append('limit', '5'); // TODO find a good limit for this url.searchParams.append('geometryFormat', 'geojson'); url.searchParams.append('type', 'locations'); url.searchParams.append('sr', '2056'); @@ -322,9 +322,6 @@ export class MapService { const [minX, minY] = coords[0]; const [maxX, maxY] = coords[1]; - console.log(coords); - console.log(minX, minY, maxX, maxY); - const polygonCoords = [ [minX, minY], [maxX, minY], @@ -396,6 +393,7 @@ export class MapService { return; } const EPSG = this.configService.config.epsg || 'EPSG2056'; + // TODO is this correct or is this cauing the projection shift -> check this proj4.defs(EPSG, '+proj=somerc +lat_0=46.95240555555556 +lon_0=7.439583333333333' + ' +k_0=1 +x_0=2600000 +y_0=1200000 +ellps=bessel ' diff --git a/src/app/welcome/map/map.component.ts b/src/app/welcome/map/map.component.ts index 21ccd7a9..aba33f0c 100644 --- a/src/app/welcome/map/map.component.ts +++ b/src/app/welcome/map/map.component.ts @@ -11,29 +11,14 @@ import Feature from 'ol/Feature'; import {MatDialog} from '@angular/material/dialog'; import {ManualentryComponent} from './manualentry/manualentry.component'; -export const nameOfCategoryForGeocoder: { [prop: string]: string; } = { - neophytes: 'Plantes invasives', - search_satac: 'N° SATAC', - search_entree_sortie: 'Entrée/sortie autoroute', - rt16_giratoires: 'Giratoires', - batiments_ofs: 'Bâtiments regBL et n° egid', - axe_mistra: 'Routes et axes', - search_arrets_tp: 'Arrêts transports publics', - ImmeublesCantonHistorique: 'Biens-fonds historiques', - point_interet: 'Points d\'intérêt', - axe_rue: 'Axes et rues', - nom_local_lieu_dit: 'Noms locaux et lieux-dits', - search_cours_eau: 'Cours d\'eau', - ImmeublesCanton: 'Biens-fonds', - search_fo_administrations: 'Administrations forestières', - search_uap_publique: 'Unité d\'aménagement publique', - adresses_sitn: 'Adresses', - recenter_to: 'Recentrer sur', - localite: 'Localité', - search_fo09: 'Secours en forêt', - search_conc_hydr: 'Concessions hydrauliques', - communes: 'Communes', - cadastres: 'Cadastres', +export const nameOfCategoryForGeocoder: { [prop: string]: string; } = { // TODO this should be translated + zipcode: 'Ortschaftenverzeichnis PLZ', + gg25: 'Gemeinden', + district: 'Bezirke', + kantone: 'Kantone', + gazetteer: 'OEV Haltestellen', + address: 'Adressen', + parcel: 'Parzellen', }; @Component({ @@ -109,19 +94,19 @@ export class MapComponent implements OnInit { this.shouldDisplayClearButton = true; this.geocoderGroupOptions = []; - for (const feature of features) { // TODO the following part needs to be adapted for the swiss topo API search - const categoryId = feature.get('objectclass'); + for (const feature of features) { + const categoryId = feature.get('origin') || feature.get('origin') !== '' ? feature.get('origin') : 'Allgemein'; // TODO add to translation Allgemein let currentCategory = this.geocoderGroupOptions.find(x => x.id === categoryId); if (currentCategory) { currentCategory.items.push({ - label: categoryId, // TODO this should be a translated ctegory - feature + label: this.mapService.stripHtmlTags(feature.get('label')), + feature }); } else { currentCategory = { - id: feature.get('objectclass'), - label: feature.get('objectclass'), // TODO this should be a translated ctegory + id: categoryId, + label: nameOfCategoryForGeocoder[categoryId], items: [{ label: this.mapService.stripHtmlTags(feature.get('label')), feature @@ -138,7 +123,7 @@ export class MapComponent implements OnInit { return value.label; } - displayGeocoderResultOnTheMap(evt: MatAutocompleteSelectedEvent) { // TODO this adds the feature from the auto complete on the map + displayGeocoderResultOnTheMap(evt: MatAutocompleteSelectedEvent) { this.mapService.addFeatureFromGeocoderToDrawing(evt.option.value.feature); this.shouldDisplayClearButton = true; }