Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/iceberg/components/IcebergMap.vue
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ watch(map, (map) => {
toZoomLevel: 7,
}),
pluginReverseGeocoder({
type: 'wps',
url: 'https://geodienste.hamburg.de/HH_WPS',
coordinateSources: [
{
Expand Down
11 changes: 10 additions & 1 deletion examples/snowbox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
colorScheme,
startCenter: [565874, 5934140],
layers: [
// TODO: Add internalization to snowbox

Check warning on line 95 in examples/snowbox/index.js

View workflow job for this annotation

GitHub Actions / Linting

Unexpected 'todo' comment: 'TODO: Add internalization to snowbox'
{
id: basemapId,
visibility: true,
Expand Down Expand Up @@ -359,7 +359,10 @@
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',
Expand Down Expand Up @@ -509,6 +512,7 @@
displayComponent: true,
layoutTag: 'TOP_LEFT',
searchMethods: [
/*
{
queryParameters: {
searchStreets: true,
Expand All @@ -517,6 +521,11 @@
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',
},
],
minLength: 3,
waitMs: 300,
Expand Down
2 changes: 1 addition & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ function addPlugins(map: typeof PolarContainer, enabledPlugins: string[]) {
layoutTag: 'BOTTOM_LEFT',
}),
enabledPlugins.includes('reverseGeocoder') &&
ReverseGeocoder({ url: '' }),
ReverseGeocoder({ url: '', type: 'wps' }),
enabledPlugins.includes('scale') &&
Scale({
displayComponent: true,
Expand Down
17 changes: 6 additions & 11 deletions src/lib/getFeatures/bkg.ts
Original file line number Diff line number Diff line change
@@ -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(
Expand Down Expand Up @@ -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
),
Comment on lines +61 to +65

@dopenguin dopenguin Jun 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transformCoordinates includes the information

If there is no available transform between the two projection, the function will throw an error.

So a call with the same projection does not yield an error, correct?

// @ts-expect-error | It is always defined in this case
title: feature.properties.text,
}) as PolarGeoJsonFeature<Point>
Expand Down
45 changes: 45 additions & 0 deletions src/lib/getFeatures/nominatim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { GeoJSONFeatureCollection } from 'ol/format/GeoJSON'

import type { PolarGeoJsonFeatureCollection } from '@/core'

import type { NominatimParameters } from './types'

import { transformGeometry } from '../transformGeometry'

export default async function (
signal: AbortSignal,
url: string,
inputValue: string,
queryParameters: NominatimParameters
): Promise<PolarGeoJsonFeatureCollection> {
const { epsg, maxFeatures, ...native } = queryParameters

const fetchUrl = new URL(url)
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,
})
.then(async (response) => <GeoJSONFeatureCollection>await response.json())
.then((featureCollection) => ({
...featureCollection,
features: featureCollection.features.map((feature) => ({
...feature,
title: feature.properties?.display_name,
geometry: transformGeometry(feature.geometry, 'EPSG:4326', epsg),
})),
}))
}
84 changes: 83 additions & 1 deletion src/lib/getFeatures/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -288,3 +288,85 @@ export interface WfsParameters extends QueryParameters {
*/
useRightHandWildcard?: boolean
}

export interface NominatimParameters extends QueryParameters {
/**
* Whether to include address details in the 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.
*/
Comment thread
dopenguin marked this conversation as resolved.
county?: string

/**
* Whether to include entrances to buildings in the results.
*/
entrances?: boolean

/**
* Whether to add extra tags to the 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')[]

/**
* Whether to include more names in alternate languages.
*/
namedetails?: boolean

/**
* Postal code.
*/
postalcode?: string

/**
* State.
*/
state?: string
Comment thread
dopenguin marked this conversation as resolved.

/**
* House number and street name.
*/
street?: string

/**
* Viewbox to prefer for searching the given term.
*/
viewbox?: [number, number, number, number]
}
16 changes: 7 additions & 9 deletions src/lib/getFeatures/wfs/parse.ts
Original file line number Diff line number Diff line change
@@ -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'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this be the correct import, if #750 is merged or will there be a relative path? Especially as a relative path is used in src/lib/getFeatures/bkg.ts.

In this scenario, I prefer the way it is written in this file.


import { getFeatureTitleFromPattern } from './getFeatureTitleFromPattern'

const parser = new WFS()
Expand Down Expand Up @@ -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)
})
Expand Down
40 changes: 40 additions & 0 deletions src/lib/transformGeometry.ts
Original file line number Diff line number Diff line change
@@ -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}`)
}
4 changes: 2 additions & 2 deletions src/plugins/addressSearch/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion src/plugins/addressSearch/utils/methodContainer.ts
Original file line number Diff line number Diff line change
@@ -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: (
Expand Down
Loading
Loading