From 9757271a7e85f44b439c1e764cc2de2998454f8e Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Mon, 23 Mar 2026 17:20:46 +0100 Subject: [PATCH 01/18] feat: add nominatim support to addressSearch and reverseGeocoder --- examples/iceberg/components/IcebergMap.vue | 1 + examples/snowbox/index.js | 12 ++- src/lib/getFeatures/nominatim.ts | 62 ++++++++++++++ src/lib/getFeatures/types.ts | 82 +++++++++++++++++++ src/plugins/addressSearch/types.ts | 4 +- .../addressSearch/utils/methodContainer.ts | 3 +- src/plugins/reverseGeocoder/store.ts | 26 ++++-- src/plugins/reverseGeocoder/types.ts | 5 ++ .../utils/reverseGeocodeNominatim.ts | 50 +++++++++++ ...reverseGeocode.ts => reverseGeocodeWps.ts} | 4 +- 10 files changed, 234 insertions(+), 15 deletions(-) create mode 100644 src/lib/getFeatures/nominatim.ts create mode 100644 src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts rename src/plugins/reverseGeocoder/utils/{reverseGeocode.ts => reverseGeocodeWps.ts} (98%) diff --git a/examples/iceberg/components/IcebergMap.vue b/examples/iceberg/components/IcebergMap.vue index 89afc6868f..fb961916de 100644 --- a/examples/iceberg/components/IcebergMap.vue +++ b/examples/iceberg/components/IcebergMap.vue @@ -91,6 +91,7 @@ watch(map, (map) => { toZoomLevel: 7, }), pluginReverseGeocoder({ + type: 'wps', url: 'https://geodienste.hamburg.de/HH_WPS', coordinateSources: [ { diff --git a/examples/snowbox/index.js b/examples/snowbox/index.js index ca694b2d29..5b84e76219 100644 --- a/examples/snowbox/index.js +++ b/examples/snowbox/index.js @@ -275,6 +275,7 @@ addPlugin( map, pluginAddressSearch({ searchMethods: [ + /* { queryParameters: { searchStreets: true, @@ -283,6 +284,12 @@ addPlugin( type: 'mpapi', url: 'https://geodienste.hamburg.de/HH_WFS_GAGES?service=WFS&request=GetFeature&version=2.0.0', }, + */ + { + type: 'nominatim', + url: 'https://polar.dataport.de/nominatim/search', + queryParameters: {}, + }, ], minLength: 3, waitMs: 300, @@ -311,7 +318,10 @@ addPlugin( addPlugin( map, pluginReverseGeocoder({ - url: 'https://geodienste.hamburg.de/HH_WPS', + // type: 'wps', + // url: 'https://geodienste.hamburg.de/HH_WPS', + type: 'nominatim', + url: 'https://polar.dataport.de/nominatim/reverse', coordinateSources: [ { plugin: 'pins', diff --git a/src/lib/getFeatures/nominatim.ts b/src/lib/getFeatures/nominatim.ts new file mode 100644 index 0000000000..5a7f7634e9 --- /dev/null +++ b/src/lib/getFeatures/nominatim.ts @@ -0,0 +1,62 @@ +import type { Point } from 'geojson' + +import { transform as transformCoordinates } from 'ol/proj' + +import type { PolarGeoJsonFeatureCollection } from '@/core' + +import type { NominatimParameters } from './types' + +export default async function ( + signal: AbortSignal, + url: string, + inputValue: string, + queryParameters: NominatimParameters +): Promise { + const { epsg, maxFeatures, ...native } = queryParameters + + const fetchUrl = new URL(url) + Object.entries({ + ...Object.fromEntries( + Object.entries(native).map(([key, value]) => [ + key, + Array.isArray(value) ? value.join(',') : value, + ]) + ), + ...(maxFeatures ? { limit: maxFeatures } : {}), + q: inputValue, + format: 'geojson', + polygon_geojson: 0, // TODO: Setting this to 1 is currently not supported well by addressSearch plugin. The plugin expects a point geometry. + polygon_threshold: 5, + }).forEach(([key, value]) => { + fetchUrl.searchParams.set(key, value.toString()) + }) + + return await fetch(fetchUrl, { + signal, + }) + .then((response) => response.json()) + .then((featureCollection) => ({ + ...featureCollection, + features: featureCollection.features.map((feature) => ({ + ...feature, + title: feature.properties.display_name, + geometry: { + ...feature.geometry, + coordinates: + epsg === 'EPSG:4326' + ? feature.geometry.coordinates + : feature.geometry.type === 'Polygon' + ? feature.geometry.coordinates.map((ring) => + ring.map((coord) => + transformCoordinates(coord, 'EPSG:4326', epsg) + ) + ) + : transformCoordinates( + (feature.geometry as Point).coordinates, + 'EPSG:4326', + epsg + ), + }, + })), + })) +} diff --git a/src/lib/getFeatures/types.ts b/src/lib/getFeatures/types.ts index d73ac6150c..16f908397a 100644 --- a/src/lib/getFeatures/types.ts +++ b/src/lib/getFeatures/types.ts @@ -263,3 +263,85 @@ export interface WfsParameters extends QueryParameters { */ useRightHandWildcard?: boolean } + +export interface NominatimParameters extends QueryParameters { + /** + * Include address details in result? + */ + addressdetails?: boolean + + /** + * Name and/or type of POI. + */ + amenity?: string + + /** + * Whether to consider the viewbox as exact instead of fuzzy filter. + */ + bounded?: boolean + + /** + * City. + */ + city?: string + + /** + * Country. + */ + country?: string + + /** + * Country code (ISO 3166-1alpha2). + */ + countrycodes?: string[] + + /** + * County. + */ + county?: string + + /** + * Include entrances to buildings in results? + */ + entrances?: boolean + + /** + * Add extra tags to result? + */ + extratags?: boolean + + /** + * Which feature types to include. + */ + featureType?: 'country' | 'state' | 'city' | 'settlement' + + /** + * Which type(s) of results to return. + */ + layer?: ('address' | 'poi' | 'railway' | 'natural' | 'manmade')[] + + /** + * Include more names in alternate languages? + */ + namedetails?: boolean + + /** + * Postal code. + */ + postalcode?: string + + /** + * State. + */ + state?: string + + /** + * House number and street name. + */ + street?: string + + /** + * Viewbox to prefer for searching the given term. + */ + viewbox?: [number, number, number, number] +} diff --git a/src/plugins/addressSearch/types.ts b/src/plugins/addressSearch/types.ts index 5c99b26393..97442f8532 100644 --- a/src/plugins/addressSearch/types.ts +++ b/src/plugins/addressSearch/types.ts @@ -127,7 +127,7 @@ export interface AddressSearchPluginOptions extends PluginOptions { /** Possible search methods by type. */ // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -export type SearchType = 'bkg' | 'wfs' | 'mpapi' | string +export type SearchType = 'bkg' | 'wfs' | 'mpapi' | 'nominatim' | string export type SearchDisplayMode = 'mixed' | 'categorized' @@ -177,7 +177,7 @@ export interface SearchMethodConfiguration { /** * The object further describes details for the search request. - * Its contents vary by service type, see {@link BKGParameters}, {@link MpapiParameters} or {@link WfsParameters}. + * Its contents vary by service type, see {@link BKGParameters}, {@link MpapiParameters}, {@link WfsParameters} or {@link NominatimParameters}. */ queryParameters?: QueryParameters } diff --git a/src/plugins/addressSearch/utils/methodContainer.ts b/src/plugins/addressSearch/utils/methodContainer.ts index a960c67084..a10531139c 100644 --- a/src/plugins/addressSearch/utils/methodContainer.ts +++ b/src/plugins/addressSearch/utils/methodContainer.ts @@ -1,11 +1,12 @@ import bkg from '@/lib/getFeatures/bkg' import mpapi from '@/lib/getFeatures/mpapi' +import nominatim from '@/lib/getFeatures/nominatim' import { getWfsFeatures } from '@/lib/getFeatures/wfs' import type { SearchMethodFunction } from '../types' export function getMethodContainer() { - const methods = { bkg, mpapi, wfs: getWfsFeatures } + const methods = { bkg, mpapi, nominatim, wfs: getWfsFeatures } return { registerSearchMethods: ( diff --git a/src/plugins/reverseGeocoder/store.ts b/src/plugins/reverseGeocoder/store.ts index 31ac5cbdc4..517ff5aa8e 100644 --- a/src/plugins/reverseGeocoder/store.ts +++ b/src/plugins/reverseGeocoder/store.ts @@ -19,7 +19,8 @@ import { type ReverseGeocoderFeature, type ReverseGeocoderPluginOptions, } from './types' -import { reverseGeocode as reverseGeocodeUtil } from './utils/reverseGeocode' +import { reverseGeocodeNominatim } from './utils/reverseGeocodeNominatim' +import { reverseGeocodeWps } from './utils/reverseGeocodeWps' /* eslint-disable tsdoc/syntax */ /** @@ -83,9 +84,14 @@ export const useReverseGeocoderStore = defineStore( async function reverseGeocode(coordinate: [number, number]) { const finish = indicateLoading() try { + const reverseGeocodeUtil = { + wps: reverseGeocodeWps, + nominatim: reverseGeocodeNominatim, + }[configuration.value.type] const feature = await reverseGeocodeUtil( configuration.value.url, - coordinate + coordinate, + coreStore.configuration.epsg ) if (configuration.value.addressTarget) { passFeatureToTarget(configuration.value.addressTarget, feature) @@ -129,12 +135,12 @@ if (import.meta.vitest) { const { createPinia, setActivePinia } = await import('pinia') const { reactive } = await import('vue') const useCoreStoreFile = await import('@/core/stores') - const reverseGeocodeUtilFile = await import('./utils/reverseGeocode') + const reverseGeocodeUtilFile = await import('./utils/reverseGeocodeWps') const indicateLoadingFile = await import('@/lib/indicateLoading') /* eslint-disable no-empty-pattern */ const test = _test.extend<{ - reverseGeocodeUtil: Mock + reverseGeocodeUtil: Mock indicateLoading: Mock coreStore: Reactive> store: ReturnType @@ -142,7 +148,7 @@ if (import.meta.vitest) { reverseGeocodeUtil: [ async ({}, use) => { const reverseGeocodeUtil = vi - .spyOn(reverseGeocodeUtilFile, 'reverseGeocode') + .spyOn(reverseGeocodeUtilFile, 'reverseGeocodeWps') .mockResolvedValue(null as unknown as ReverseGeocoderFeature) await use(reverseGeocodeUtil) }, @@ -162,7 +168,9 @@ if (import.meta.vitest) { const fit = vi.fn() const coreStore = reactive({ configuration: { + epsg: 'EPSG:25832', [PluginId]: { + type: 'wps', url: 'https://wps.example', coordinateSources: [{ key: 'coordinateSource' }], addressTarget: { key: 'addressTarget' }, @@ -200,9 +208,10 @@ if (import.meta.vitest) { }) => { coreStore.coordinateSource = [1, 2] await new Promise((resolve) => setTimeout(resolve)) - expect(reverseGeocodeUtil).toHaveBeenCalledWith( + expect(reverseGeocodeUtil).toHaveBeenCalledExactlyOnceWith( 'https://wps.example', - [1, 2] + [1, 2], + 'EPSG:25832' ) }) @@ -216,8 +225,7 @@ if (import.meta.vitest) { feature as unknown as ReverseGeocoderFeature ) await store.reverseGeocode([3, 4]) - expect(coreStore.addressTarget).toHaveBeenCalledOnce() - expect(coreStore.addressTarget).toHaveBeenCalledWith(feature) + expect(coreStore.addressTarget).toHaveBeenCalledExactlyOnceWith(feature) }) test('zooms to input coordinate', async ({ diff --git a/src/plugins/reverseGeocoder/types.ts b/src/plugins/reverseGeocoder/types.ts index 4a7e962e78..80f7774daf 100644 --- a/src/plugins/reverseGeocoder/types.ts +++ b/src/plugins/reverseGeocoder/types.ts @@ -11,6 +11,11 @@ export const PluginId = 'reverseGeocoder' * Plugin options for reverse geocoder plugin. */ export interface ReverseGeocoderPluginOptions extends PluginOptions { + /** + * Type of reverse geocoding service. + */ + type: 'wps' | 'nominatim' + /** * URL of a WPS service to use for reverse geocoding. */ diff --git a/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts b/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts new file mode 100644 index 0000000000..b8acb8093a --- /dev/null +++ b/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts @@ -0,0 +1,50 @@ +import { transform as transformCoordinate } from 'ol/proj' + +import type { ReverseGeocoderFeature } from '../types' + +export async function reverseGeocodeNominatim( + url: string, + coordinate: [number, number], + epsg: string +): Promise { + const searchCoordinate = transformCoordinate( + coordinate, + epsg, + 'EPSG:4326' + ) as [number, number] + + const fetchUrl = new URL(url) + fetchUrl.searchParams.set('lat', searchCoordinate[1].toString()) + fetchUrl.searchParams.set('lon', searchCoordinate[0].toString()) + fetchUrl.searchParams.set('format', 'jsonv2') + + const result = await fetch(fetchUrl).then((response) => response.json()) + + const resultObject: ReverseGeocoderFeature = { + type: 'reverse_geocoded', + title: [ + [result.address.road, result.address.house_number] + .filter((x) => x) + .join(' '), + result.address.town || result.address.city || result.address.village, + ] + .filter((x) => x) + .join(', '), + properties: result.properties, + geometry: { + // as clicked by user - usually want to keep this since user is pointing at something + coordinates: coordinate, + type: 'Point', + }, + addressGeometry: { + // as returned by reverse geocoder + coordinates: transformCoordinate( + [result.lon, result.lat], + 'EPSG:4326', + epsg + ), + type: 'Point', + }, + } + return resultObject +} diff --git a/src/plugins/reverseGeocoder/utils/reverseGeocode.ts b/src/plugins/reverseGeocoder/utils/reverseGeocodeWps.ts similarity index 98% rename from src/plugins/reverseGeocoder/utils/reverseGeocode.ts rename to src/plugins/reverseGeocoder/utils/reverseGeocodeWps.ts index 6172e45859..bbccab621b 100644 --- a/src/plugins/reverseGeocoder/utils/reverseGeocode.ts +++ b/src/plugins/reverseGeocoder/utils/reverseGeocodeWps.ts @@ -32,7 +32,7 @@ const parser = new Parser({ tagNameProcessors: [processors.stripPrefix], }) -export async function reverseGeocode( +export async function reverseGeocodeWps( url: string, coordinate: [number, number] ): Promise { @@ -136,7 +136,7 @@ if (import.meta.vitest) { text: () => Promise.resolve(testResponse), } as Response) - const feature = await reverseGeocode(testUrl, testCoordinates) + const feature = await reverseGeocodeWps(testUrl, testCoordinates) expect(fetchMock).toHaveBeenCalledOnce() expect(fetchMock).toHaveBeenCalledWith(testUrl, { From b944581a1760cd8e8292b8333213cc3bafe25f69 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Tue, 26 May 2026 16:54:55 +0200 Subject: [PATCH 02/18] docs: use non-question sentences for attribute typedoc --- src/lib/getFeatures/types.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/getFeatures/types.ts b/src/lib/getFeatures/types.ts index 9bad088be8..becc799033 100644 --- a/src/lib/getFeatures/types.ts +++ b/src/lib/getFeatures/types.ts @@ -195,7 +195,7 @@ export interface WfsParameters extends QueryParameters { /** XML feature prefix from xmlns namespace without :; e.g. 'ave'. */ featurePrefix: string - /** Feature type to search for by name (see ?service=wfs&request=DescribeFeatureType). */ + /** Feature type to search for by name (see `?service=wfs&request=DescribeFeatureType`). */ typeName: string /** XML namespace of feature type to use in search. */ @@ -291,7 +291,7 @@ export interface WfsParameters extends QueryParameters { export interface NominatimParameters extends QueryParameters { /** - * Include address details in result? + * Whether to include address details in the result. */ addressdetails?: boolean @@ -326,12 +326,12 @@ export interface NominatimParameters extends QueryParameters { county?: string /** - * Include entrances to buildings in results? + * Whether to include entrances to buildings in the results. */ entrances?: boolean /** - * Add extra tags to result? + * Whether to add extra tags to the result. */ extratags?: boolean @@ -346,7 +346,7 @@ export interface NominatimParameters extends QueryParameters { layer?: ('address' | 'poi' | 'railway' | 'natural' | 'manmade')[] /** - * Include more names in alternate languages? + * Whether to include more names in alternate languages. */ namedetails?: boolean From 3221cd5151bd714d71bc78b0ccc5482bf84e7757 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings <142312676+oeninghe-dataport@users.noreply.github.com> Date: Tue, 26 May 2026 17:07:02 +0200 Subject: [PATCH 03/18] refactor(reverseGeocoder): simplify return object for nominatim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pascal Röhling <73653210+dopenguin@users.noreply.github.com> --- src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts b/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts index 444ef9936f..8b66f50a01 100644 --- a/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts +++ b/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts @@ -23,7 +23,7 @@ export async function reverseGeocodeNominatim( response.json() ) - const resultObject: ReverseGeocoderFeature = { + return { type: 'reverse_geocoded', title: [ [result.address.road, result.address.house_number] @@ -49,5 +49,4 @@ export async function reverseGeocodeNominatim( type: 'Point', }, } - return resultObject } From 6a58368bcf4bc1f7bab65c8c164cc671de4655dc Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Mon, 1 Jun 2026 10:40:12 +0200 Subject: [PATCH 04/18] chore: remove TODO and model idea as feature request The TODO is moved to https://github.com/Dataport/polar/issues/747 --- src/lib/getFeatures/nominatim.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/getFeatures/nominatim.ts b/src/lib/getFeatures/nominatim.ts index 5a7f7634e9..f6c1c9aea5 100644 --- a/src/lib/getFeatures/nominatim.ts +++ b/src/lib/getFeatures/nominatim.ts @@ -25,7 +25,8 @@ export default async function ( ...(maxFeatures ? { limit: maxFeatures } : {}), q: inputValue, format: 'geojson', - polygon_geojson: 0, // TODO: Setting this to 1 is currently not supported well by addressSearch plugin. The plugin expects a point geometry. + // Setting this to 1 is currently not supported well by addressSearch plugin. The plugin expects a point geometry. + polygon_geojson: 0, polygon_threshold: 5, }).forEach(([key, value]) => { fetchUrl.searchParams.set(key, value.toString()) From 9cae6a1731736b6e65dce4db129cf49c1a4634f8 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Mon, 1 Jun 2026 10:43:04 +0200 Subject: [PATCH 05/18] refactor: replace functional implementation with simpler procedural --- src/lib/getFeatures/nominatim.ts | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/lib/getFeatures/nominatim.ts b/src/lib/getFeatures/nominatim.ts index f6c1c9aea5..bbd40f622d 100644 --- a/src/lib/getFeatures/nominatim.ts +++ b/src/lib/getFeatures/nominatim.ts @@ -15,22 +15,20 @@ export default async function ( const { epsg, maxFeatures, ...native } = queryParameters const fetchUrl = new URL(url) - Object.entries({ - ...Object.fromEntries( - Object.entries(native).map(([key, value]) => [ - key, - Array.isArray(value) ? value.join(',') : value, - ]) - ), - ...(maxFeatures ? { limit: maxFeatures } : {}), - q: inputValue, - format: 'geojson', - // Setting this to 1 is currently not supported well by addressSearch plugin. The plugin expects a point geometry. - polygon_geojson: 0, - polygon_threshold: 5, - }).forEach(([key, value]) => { - fetchUrl.searchParams.set(key, value.toString()) - }) + for (const [key, value] of Object.entries(native)) { + fetchUrl.searchParams.set( + key, + Array.isArray(value) ? value.join(',') : String(value) + ) + } + if (maxFeatures) { + fetchUrl.searchParams.set('limit', String(maxFeatures)) + } + fetchUrl.searchParams.set('q', inputValue) + fetchUrl.searchParams.set('format', 'geojson') + // Setting this to 1 is currently not supported well by addressSearch plugin. The plugin expects a point geometry. + fetchUrl.searchParams.set('polygon_geojson', '0') + fetchUrl.searchParams.set('polygon_threshold', '5') return await fetch(fetchUrl, { signal, From 169d26efef88a1beed0b527683769e65868e9a31 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Mon, 1 Jun 2026 11:01:02 +0200 Subject: [PATCH 06/18] refactor: introduce generic transform util for geometries --- src/lib/getFeatures/bkg.ts | 17 +++++--------- src/lib/getFeatures/nominatim.ts | 26 ++++----------------- src/lib/getFeatures/wfs/parse.ts | 16 ++++++------- src/lib/transformGeometry.ts | 40 ++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 42 deletions(-) create mode 100644 src/lib/transformGeometry.ts diff --git a/src/lib/getFeatures/bkg.ts b/src/lib/getFeatures/bkg.ts index fb2742ec9d..8ad0045e11 100644 --- a/src/lib/getFeatures/bkg.ts +++ b/src/lib/getFeatures/bkg.ts @@ -1,12 +1,12 @@ import type { FeatureCollection, Point } from 'geojson' import { toMerged } from 'es-toolkit' -import { transform as transformCoordinates } from 'ol/proj' import type { PolarGeoJsonFeature, PolarGeoJsonFeatureCollection } from '@/core' import type { BKGParameters } from './types' +import { transformGeometry } from '../transformGeometry' import { errorCheck } from './errorCheck' function getRequestUrlQuery( @@ -58,16 +58,11 @@ export default async function ( features: featureCollection.features.map( (feature) => toMerged(feature, { - geometry: toMerged(feature.geometry, { - coordinates: - queryParameters.epsg === 'EPSG:4326' - ? (feature.geometry as Point).coordinates - : transformCoordinates( - (feature.geometry as Point).coordinates, - 'EPSG:4326', - queryParameters.epsg - ), - }), + geometry: transformGeometry( + feature.geometry, + 'EPSG:4326', + queryParameters.epsg + ), // @ts-expect-error | It is always defined in this case title: feature.properties.text, }) as PolarGeoJsonFeature diff --git a/src/lib/getFeatures/nominatim.ts b/src/lib/getFeatures/nominatim.ts index bbd40f622d..40e5db8d8c 100644 --- a/src/lib/getFeatures/nominatim.ts +++ b/src/lib/getFeatures/nominatim.ts @@ -1,11 +1,9 @@ -import type { Point } from 'geojson' - -import { transform as transformCoordinates } from 'ol/proj' - import type { PolarGeoJsonFeatureCollection } from '@/core' import type { NominatimParameters } from './types' +import { transformGeometry } from '../transformGeometry' + export default async function ( signal: AbortSignal, url: string, @@ -38,24 +36,8 @@ export default async function ( ...featureCollection, features: featureCollection.features.map((feature) => ({ ...feature, - title: feature.properties.display_name, - geometry: { - ...feature.geometry, - coordinates: - epsg === 'EPSG:4326' - ? feature.geometry.coordinates - : feature.geometry.type === 'Polygon' - ? feature.geometry.coordinates.map((ring) => - ring.map((coord) => - transformCoordinates(coord, 'EPSG:4326', epsg) - ) - ) - : transformCoordinates( - (feature.geometry as Point).coordinates, - 'EPSG:4326', - epsg - ), - }, + title: feature.properties?.display_name, + geometry: transformGeometry(feature.geometry, 'EPSG:4326', epsg), })), })) } diff --git a/src/lib/getFeatures/wfs/parse.ts b/src/lib/getFeatures/wfs/parse.ts index 8ca85c5c0c..b22dadd9bb 100644 --- a/src/lib/getFeatures/wfs/parse.ts +++ b/src/lib/getFeatures/wfs/parse.ts @@ -1,9 +1,9 @@ -import { toMerged } from 'es-toolkit' import { GeoJSON, WFS } from 'ol/format' -import { transform as transformCoordinates } from 'ol/proj' import type { PolarGeoJsonFeature, PolarGeoJsonFeatureCollection } from '@/core' +import { transformGeometry } from '@/lib/transformGeometry' + import { getFeatureTitleFromPattern } from './getFeatureTitleFromPattern' const parser = new WFS() @@ -53,13 +53,11 @@ export async function parseWfsResponse( } } if (epsgCode) { - featureObject.geometry = toMerged(featureObject.geometry, { - coordinates: transformCoordinates( - featureObject.geometry.coordinates, - `EPSG:${epsgCode}`, - epsg - ), - }) + featureObject.geometry = transformGeometry( + featureObject.geometry, + `EPSG:${epsgCode}`, + epsg + ) } features.push(featureObject) }) diff --git a/src/lib/transformGeometry.ts b/src/lib/transformGeometry.ts new file mode 100644 index 0000000000..b9ef869b22 --- /dev/null +++ b/src/lib/transformGeometry.ts @@ -0,0 +1,40 @@ +import type { Geometry, Point, Polygon } from 'geojson' + +import { transform as transformCoordinates } from 'ol/proj' + +/** + * Transforms the coordinates of a GeoJSON geometry from one EPSG projection to another. + * + * @remarks + * Currently supports only Point and Polygon geometries. + * For unsupported geometry types, an error is thrown. + * + * @param geometry - The GeoJSON geometry to transform. + * @param sourceEpsg - The EPSG code of the source projection. + * @param targetEpsg - The EPSG code of the target projection. + * @returns The transformed GeoJSON geometry. + */ +export function transformGeometry( + geometry: Geometry, + sourceEpsg: string, + targetEpsg: string +): Point | Polygon { + if (geometry.type === 'Point') { + return { + ...geometry, + coordinates: transformCoordinates( + geometry.coordinates, + sourceEpsg, + targetEpsg + ), + } + } else if (geometry.type === 'Polygon') { + return { + ...geometry, + coordinates: geometry.coordinates.map((ring) => + ring.map((coord) => transformCoordinates(coord, sourceEpsg, targetEpsg)) + ), + } + } + throw new Error(`Unsupported geometry type: ${geometry.type}`) +} From d19d95a16c597aa018d097332ec33a05a0f1d6ae Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Mon, 1 Jun 2026 11:06:48 +0200 Subject: [PATCH 07/18] refactor: type response of nominatim feature search --- src/lib/getFeatures/nominatim.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/getFeatures/nominatim.ts b/src/lib/getFeatures/nominatim.ts index 40e5db8d8c..5913cdcc90 100644 --- a/src/lib/getFeatures/nominatim.ts +++ b/src/lib/getFeatures/nominatim.ts @@ -1,3 +1,5 @@ +import type { GeoJSONFeatureCollection } from 'ol/format/GeoJSON' + import type { PolarGeoJsonFeatureCollection } from '@/core' import type { NominatimParameters } from './types' @@ -31,7 +33,7 @@ export default async function ( return await fetch(fetchUrl, { signal, }) - .then((response) => response.json()) + .then(async (response) => await response.json()) .then((featureCollection) => ({ ...featureCollection, features: featureCollection.features.map((feature) => ({ From 687b39c6f0e5fe5825d7c6036a1b2fa2c606bb0f Mon Sep 17 00:00:00 2001 From: Hendrik Oenings <142312676+oeninghe-dataport@users.noreply.github.com> Date: Mon, 1 Jun 2026 11:12:33 +0200 Subject: [PATCH 08/18] test(snowbox): remove optional queryParameters for nominatim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pascal Röhling <73653210+dopenguin@users.noreply.github.com> --- examples/snowbox/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/snowbox/index.js b/examples/snowbox/index.js index 6d42595254..a035fb5d37 100644 --- a/examples/snowbox/index.js +++ b/examples/snowbox/index.js @@ -525,7 +525,6 @@ addPlugin( { type: 'nominatim', url: 'https://polar.dataport.de/nominatim/search', - queryParameters: {}, }, ], minLength: 3, From 5c87397ec344821329ac576e73379f84ada4c62d Mon Sep 17 00:00:00 2001 From: Hendrik Oenings <142312676+oeninghe-dataport@users.noreply.github.com> Date: Wed, 17 Jun 2026 20:28:30 +0200 Subject: [PATCH 09/18] docs: fix typo for transformGeometry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pascal Röhling <73653210+dopenguin@users.noreply.github.com> --- src/lib/transformGeometry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/transformGeometry.ts b/src/lib/transformGeometry.ts index b9ef869b22..13f9433020 100644 --- a/src/lib/transformGeometry.ts +++ b/src/lib/transformGeometry.ts @@ -6,7 +6,7 @@ import { transform as transformCoordinates } from 'ol/proj' * Transforms the coordinates of a GeoJSON geometry from one EPSG projection to another. * * @remarks - * Currently supports only Point and Polygon geometries. + * Currently, supports only Point and Polygon geometries. * For unsupported geometry types, an error is thrown. * * @param geometry - The GeoJSON geometry to transform. From b902a4fb1b07711ac37c2e0659565364122fa8a1 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Fri, 10 Jul 2026 18:33:03 +0200 Subject: [PATCH 10/18] fix(reverseGeocoder): allow reverse-geocoding WPS with differing EPSG --- src/plugins/reverseGeocoder/store.ts | 24 ++++--- src/plugins/reverseGeocoder/types.ts | 9 +++ .../utils/reverseGeocodeNominatim.ts | 15 ++-- .../utils/reverseGeocodeWps.ts | 69 ++++++++++++------- 4 files changed, 77 insertions(+), 40 deletions(-) diff --git a/src/plugins/reverseGeocoder/store.ts b/src/plugins/reverseGeocoder/store.ts index 22af409dc2..691759f9c2 100644 --- a/src/plugins/reverseGeocoder/store.ts +++ b/src/plugins/reverseGeocoder/store.ts @@ -83,12 +83,13 @@ export const useReverseGeocoderStore = defineStore( wps: reverseGeocodeWps, nominatim: reverseGeocodeNominatim, }[configuration.value.type] - const feature = await reverseGeocodeUtil( - configuration.value.url, + const feature = await reverseGeocodeUtil({ + url: configuration.value.url, coordinate, - coreStore.configuration.epsg, - signal - ) + epsg: coreStore.configuration.epsg, + serviceEpsg: configuration.value.epsg || 'EPSG:25832', + signal, + }) if (configuration.value.addressTarget) { passFeatureToTarget(configuration.value.addressTarget, feature) } @@ -220,12 +221,13 @@ if (import.meta.vitest) { } pluginStore.pins.coordinate = [1, 2] await new Promise((resolve) => setTimeout(resolve)) - expect(reverseGeocodeUtil).toHaveBeenCalledExactlyOnceWith( - 'https://wps.example', - [1, 2], - 'EPSG:25832', - store.abortController?.signal - ) + expect(reverseGeocodeUtil).toHaveBeenCalledExactlyOnceWith({ + url: 'https://wps.example', + coordinate: [1, 2], + epsg: 'EPSG:25832', + serviceEpsg: 'EPSG:25832', + signal: store.abortController?.signal, + }) }) test('passes geocoding result to address target', async ({ diff --git a/src/plugins/reverseGeocoder/types.ts b/src/plugins/reverseGeocoder/types.ts index e435af2bb0..cdae5f4979 100644 --- a/src/plugins/reverseGeocoder/types.ts +++ b/src/plugins/reverseGeocoder/types.ts @@ -31,6 +31,15 @@ export interface ReverseGeocoderPluginOptions extends PluginOptions { */ coordinateSources?: StoreReference[] + /** + * EPSG code of the coordinate system used by the WPS service. + * + * This is only relevant for the WPS service, since Nominatim always uses EPSG:4326. + * + * @defaultValue `'EPSG:25832'` + */ + epsg?: string + /** * Zoom level to zoom to when a successful answer was received. */ diff --git a/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts b/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts index 500ead76be..077100b034 100644 --- a/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts +++ b/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts @@ -2,12 +2,17 @@ import type { ReverseGeocoderFeature } from '../types' import { transform as transformCoordinate } from 'ol/proj' -export async function reverseGeocodeNominatim( - url: string, - coordinate: [number, number], - epsg: string, +export async function reverseGeocodeNominatim({ + url, + coordinate, + epsg, + signal, +}: { + url: string + coordinate: [number, number] + epsg: string signal: AbortSignal -): Promise { +}): Promise { const searchCoordinate = transformCoordinate( coordinate, epsg, diff --git a/src/plugins/reverseGeocoder/utils/reverseGeocodeWps.ts b/src/plugins/reverseGeocoder/utils/reverseGeocodeWps.ts index e035b69087..da83d3d0c9 100644 --- a/src/plugins/reverseGeocoder/utils/reverseGeocodeWps.ts +++ b/src/plugins/reverseGeocoder/utils/reverseGeocodeWps.ts @@ -1,5 +1,7 @@ import type { ReverseGeocoderFeature } from '../types' +import { transform as transformCoordinate } from 'ol/proj' + const buildPostBody = ([x, y]: [number, number]) => ` { +}): Promise { const response = await fetch(url, { method: 'POST', - body: buildPostBody(coordinate), + body: buildPostBody( + transformCoordinate(coordinate, epsg, serviceEpsg) as [number, number] + ), signal, }) @@ -86,9 +97,16 @@ export async function reverseGeocodeWps( if (import.meta.vitest) { const { beforeEach, expect, test, vi } = import.meta.vitest + const { + default: { registerProjections }, + } = await import('@masterportal/masterportalapi/src/crs') + const { + default: { namedProjections }, + } = await import('@/core/utils/defaults') beforeEach(() => { vi.restoreAllMocks() + registerProjections(namedProjections) }) const testUrl = 'https://wps.example' @@ -144,12 +162,13 @@ if (import.meta.vitest) { const abortController = new AbortController() await expect( - reverseGeocodeWps( - testUrl, - testCoordinates, - 'EPSG:25832', - abortController.signal - ) + reverseGeocodeWps({ + url: testUrl, + coordinate: testCoordinates, + epsg: 'EPSG:25832', + serviceEpsg: 'EPSG:25832', + signal: abortController.signal, + }) ).rejects.toThrow('Failed to parse XML response') }) @@ -160,12 +179,13 @@ if (import.meta.vitest) { const abortController = new AbortController() await expect( - reverseGeocodeWps( - testUrl, - testCoordinates, - 'EPSG:25832', - abortController.signal - ) + reverseGeocodeWps({ + url: testUrl, + coordinate: testCoordinates, + epsg: 'EPSG:25832', + serviceEpsg: 'EPSG:25832', + signal: abortController.signal, + }) ).rejects.toThrow('Response does not contain an "Adresse" element.') }) @@ -175,12 +195,13 @@ if (import.meta.vitest) { } as Response) const abortController = new AbortController() - const feature = await reverseGeocodeWps( - testUrl, - testCoordinates, - 'EPSG:25832', - abortController.signal - ) + const feature = await reverseGeocodeWps({ + url: testUrl, + coordinate: testCoordinates, + epsg: 'EPSG:25832', + serviceEpsg: 'EPSG:25832', + signal: abortController.signal, + }) expect(fetchMock).toHaveBeenCalledOnce() expect(fetchMock).toHaveBeenCalledWith(testUrl, { From ad673714b51e9f467eea93093a252dc55bcb9f47 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Mon, 13 Jul 2026 08:42:48 +0200 Subject: [PATCH 11/18] refactor(reverseGeocoder): type nominatim response --- .../utils/reverseGeocodeNominatim.ts | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts b/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts index 077100b034..7bb75e9a04 100644 --- a/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts +++ b/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts @@ -2,6 +2,42 @@ import type { ReverseGeocoderFeature } from '../types' import { transform as transformCoordinate } from 'ol/proj' +interface NominatimReverseGeocodeResponse { + address: { + house_number?: string + road?: string + hamlet?: string + village?: string + town?: string + suburb?: string + city_district?: string + city?: string + county?: string + state_district?: string + state?: string + // eslint-disable-next-line @typescript-eslint/naming-convention + 'ISO3166-2-lvl4'?: string + postcode?: string + country?: string + country_code?: string + } + boundingbox: [string, string, string, string] + category: string + display_name: string + importance: number + lat: string + licence: string + lon: string + name: string + osm_id: string + osm_type: string + place_id: number + type: string + extratags?: Record + icon?: string + place_rank?: number +} + export async function reverseGeocodeNominatim({ url, coordinate, @@ -24,8 +60,8 @@ export async function reverseGeocodeNominatim({ fetchUrl.searchParams.set('lon', searchCoordinate[0].toString()) fetchUrl.searchParams.set('format', 'jsonv2') - const result = await fetch(fetchUrl, { signal }).then((response) => - response.json() + const result = await fetch(fetchUrl, { signal }).then( + (response) => >response.json() ) return { @@ -38,7 +74,7 @@ export async function reverseGeocodeNominatim({ ] .filter((x) => x) .join(', '), - properties: result.properties, + properties: {}, geometry: { // as clicked by user - usually want to keep this since user is pointing at something coordinates: coordinate, @@ -47,7 +83,7 @@ export async function reverseGeocodeNominatim({ addressGeometry: { // as returned by reverse geocoder coordinates: transformCoordinate( - [result.lon, result.lat], + [Number(result.lon), Number(result.lat)], 'EPSG:4326', epsg ), From 20d34b6724f8878aefcdb63861f6631039e7b008 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings <142312676+oeninghe-dataport@users.noreply.github.com> Date: Tue, 14 Jul 2026 11:26:50 +0200 Subject: [PATCH 12/18] refactor(reverseGeocoder): more intuitive syntax for typing fetch response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pascal Röhling <73653210+dopenguin@users.noreply.github.com> --- .../reverseGeocoder/utils/reverseGeocodeNominatim.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts b/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts index 7bb75e9a04..b2ab8f1b2a 100644 --- a/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts +++ b/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts @@ -60,9 +60,9 @@ export async function reverseGeocodeNominatim({ fetchUrl.searchParams.set('lon', searchCoordinate[0].toString()) fetchUrl.searchParams.set('format', 'jsonv2') - const result = await fetch(fetchUrl, { signal }).then( - (response) => >response.json() - ) + const result: NominatimReverseGeocodeResponse = await fetch(fetchUrl, { + signal, + }).then((response) => response.json()) return { type: 'reverse_geocoded', From ca44a6b1c8ae68e1eccffee3e5562887f528c5d2 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Thu, 30 Jul 2026 09:12:14 +0200 Subject: [PATCH 13/18] refactor(reverseGeocoder): more verbosity about serviceEpsg --- src/plugins/reverseGeocoder/store.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/reverseGeocoder/store.ts b/src/plugins/reverseGeocoder/store.ts index c375d26ca7..c050cfa32d 100644 --- a/src/plugins/reverseGeocoder/store.ts +++ b/src/plugins/reverseGeocoder/store.ts @@ -87,6 +87,7 @@ export const useReverseGeocoderStore = defineStore( url: configuration.value.url, coordinate, epsg: coreStore.configuration.epsg, + // unused for Nominatim, but required for WPS serviceEpsg: configuration.value.epsg || 'EPSG:25832', signal, }) From f250d7f8d79c85a65fbad5f1110d81f5b6cecb81 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Thu, 30 Jul 2026 09:17:37 +0200 Subject: [PATCH 14/18] refactor(reverseGeocoder): simplify local function signature --- src/plugins/reverseGeocoder/utils/reverseGeocodeWps.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/plugins/reverseGeocoder/utils/reverseGeocodeWps.ts b/src/plugins/reverseGeocoder/utils/reverseGeocodeWps.ts index da83d3d0c9..f30d280672 100644 --- a/src/plugins/reverseGeocoder/utils/reverseGeocodeWps.ts +++ b/src/plugins/reverseGeocoder/utils/reverseGeocodeWps.ts @@ -1,8 +1,9 @@ +import type { Coordinate } from 'ol/coordinate' import type { ReverseGeocoderFeature } from '../types' import { transform as transformCoordinate } from 'ol/proj' -const buildPostBody = ([x, y]: [number, number]) => ` ` { const response = await fetch(url, { method: 'POST', - body: buildPostBody( - transformCoordinate(coordinate, epsg, serviceEpsg) as [number, number] - ), + body: buildPostBody(transformCoordinate(coordinate, epsg, serviceEpsg)), signal, }) From de4b58830262968683954433de53aabcb82b6e82 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Fri, 31 Jul 2026 09:55:32 +0200 Subject: [PATCH 15/18] feat(reverseGeocoder): use geojson response instead of jsonv2 The GeoJSON properties of the reverse geocoder with nominatim are preserved as properties of the result. --- .../utils/reverseGeocodeNominatim.ts | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts b/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts index b2ab8f1b2a..4697afc94f 100644 --- a/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts +++ b/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts @@ -1,8 +1,9 @@ +import type { FeatureCollection } from 'geojson' import type { ReverseGeocoderFeature } from '../types' import { transform as transformCoordinate } from 'ol/proj' -interface NominatimReverseGeocodeResponse { +interface NominatimReverseGeocodeProperties { address: { house_number?: string road?: string @@ -21,13 +22,10 @@ interface NominatimReverseGeocodeResponse { country?: string country_code?: string } - boundingbox: [string, string, string, string] category: string display_name: string importance: number - lat: string licence: string - lon: string name: string osm_id: string osm_type: string @@ -58,23 +56,35 @@ export async function reverseGeocodeNominatim({ const fetchUrl = new URL(url) fetchUrl.searchParams.set('lat', searchCoordinate[1].toString()) fetchUrl.searchParams.set('lon', searchCoordinate[0].toString()) - fetchUrl.searchParams.set('format', 'jsonv2') + fetchUrl.searchParams.set('format', 'geojson') - const result: NominatimReverseGeocodeResponse = await fetch(fetchUrl, { + const result: FeatureCollection = await fetch(fetchUrl, { signal, }).then((response) => response.json()) + const feature = result.features[0] + if (!feature) { + throw new Error('No features returned from Nominatim reverse geocode') + } + if (feature.geometry.type !== 'Point') { + throw new Error('Nominatim reverse geocode returned non-point geometry') + } + + const properties = feature.properties as NominatimReverseGeocodeProperties + return { type: 'reverse_geocoded', title: [ - [result.address.road, result.address.house_number] + [properties.address.road, properties.address.house_number] .filter((x) => x) .join(' '), - result.address.town || result.address.city || result.address.village, + properties.address.town || + properties.address.city || + properties.address.village, ] .filter((x) => x) .join(', '), - properties: {}, + properties, geometry: { // as clicked by user - usually want to keep this since user is pointing at something coordinates: coordinate, @@ -83,7 +93,7 @@ export async function reverseGeocodeNominatim({ addressGeometry: { // as returned by reverse geocoder coordinates: transformCoordinate( - [Number(result.lon), Number(result.lat)], + feature.geometry.coordinates as [number, number], 'EPSG:4326', epsg ), From f3dc637a12833d17c8ad6fb167ce09e0a00a4c90 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Fri, 31 Jul 2026 10:11:10 +0200 Subject: [PATCH 16/18] docs(reverseGeocoder): use inter-linking for plugin options --- src/plugins/reverseGeocoder/types.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/plugins/reverseGeocoder/types.ts b/src/plugins/reverseGeocoder/types.ts index cdae5f4979..a57f01ca0b 100644 --- a/src/plugins/reverseGeocoder/types.ts +++ b/src/plugins/reverseGeocoder/types.ts @@ -32,9 +32,8 @@ export interface ReverseGeocoderPluginOptions extends PluginOptions { coordinateSources?: StoreReference[] /** - * EPSG code of the coordinate system used by the WPS service. - * - * This is only relevant for the WPS service, since Nominatim always uses EPSG:4326. + * EPSG code of the coordinate system used by the service. + * Considered only if {@link ReverseGeocoderPluginOptions.type | type} is set to `'wps'`. * * @defaultValue `'EPSG:25832'` */ From a5606c2b84adb796f6ca56f2e8208de480be4270 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Fri, 31 Jul 2026 10:13:53 +0200 Subject: [PATCH 17/18] refactor(reverseGeocoder): be even more explicit on serviceEpsg --- src/plugins/reverseGeocoder/store.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/plugins/reverseGeocoder/store.ts b/src/plugins/reverseGeocoder/store.ts index c050cfa32d..2f4f83b63a 100644 --- a/src/plugins/reverseGeocoder/store.ts +++ b/src/plugins/reverseGeocoder/store.ts @@ -80,15 +80,17 @@ export const useReverseGeocoderStore = defineStore( const signal = toRaw(abortController.value.signal) try { const reverseGeocodeUtil = { - wps: reverseGeocodeWps, + wps: (params) => + reverseGeocodeWps({ + ...params, + serviceEpsg: configuration.value.epsg || 'EPSG:25832', + }), nominatim: reverseGeocodeNominatim, }[configuration.value.type] const feature = await reverseGeocodeUtil({ url: configuration.value.url, coordinate, epsg: coreStore.configuration.epsg, - // unused for Nominatim, but required for WPS - serviceEpsg: configuration.value.epsg || 'EPSG:25832', signal, }) if (configuration.value.addressTarget) { From 59fe89ac103b02609661223f54555576a46f2df5 Mon Sep 17 00:00:00 2001 From: Hendrik Oenings Date: Mon, 3 Aug 2026 10:17:23 +0200 Subject: [PATCH 18/18] refactor(reverseGeocoder): use more precise type for nominatim response --- .../utils/reverseGeocodeNominatim.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts b/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts index 4697afc94f..0fec573fe0 100644 --- a/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts +++ b/src/plugins/reverseGeocoder/utils/reverseGeocodeNominatim.ts @@ -1,4 +1,4 @@ -import type { FeatureCollection } from 'geojson' +import type { FeatureCollection, Point } from 'geojson' import type { ReverseGeocoderFeature } from '../types' import { transform as transformCoordinate } from 'ol/proj' @@ -58,19 +58,17 @@ export async function reverseGeocodeNominatim({ fetchUrl.searchParams.set('lon', searchCoordinate[0].toString()) fetchUrl.searchParams.set('format', 'geojson') - const result: FeatureCollection = await fetch(fetchUrl, { - signal, - }).then((response) => response.json()) + const result: FeatureCollection = + await fetch(fetchUrl, { + signal, + }).then((response) => response.json()) const feature = result.features[0] if (!feature) { throw new Error('No features returned from Nominatim reverse geocode') } - if (feature.geometry.type !== 'Point') { - throw new Error('Nominatim reverse geocode returned non-point geometry') - } - const properties = feature.properties as NominatimReverseGeocodeProperties + const properties = feature.properties return { type: 'reverse_geocoded',