MapComplete/src/Logic/Search/GeocodingProvider.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

157 lines
4.2 KiB
TypeScript
Raw Normal View History

2024-08-15 01:51:33 +02:00
import { BBox } from "../BBox"
2024-08-21 14:06:42 +02:00
import { Feature, Geometry } from "geojson"
import { DefaultPinIcon } from "../../Models/Constants"
2024-08-22 22:50:37 +02:00
import { Store } from "../UIEventSource"
2024-08-23 02:16:24 +02:00
import * as search from "../../assets/generated/layers/search.json"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
import { GeoOperations } from "../GeoOperations"
2024-08-26 13:09:46 +02:00
export type GeocodingCategory =
2024-10-19 14:44:55 +02:00
| "coordinate"
2024-08-26 13:09:46 +02:00
| "city"
| "house"
| "street"
| "locality"
| "country"
| "train_station"
| "county"
| "airport"
| "shop"
2024-10-19 14:44:55 +02:00
export type GeocodeResult = {
2024-08-21 14:06:42 +02:00
/**
* The name of the feature being displayed
*/
2024-08-15 01:51:33 +02:00
display_name: string
2024-08-21 14:06:42 +02:00
/**
* Some optional, extra information
*/
2024-10-19 14:44:55 +02:00
description?: string | Promise<string>
feature?: Feature
2024-08-15 01:51:33 +02:00
lat: number
lon: number
/**
* Format:
* [lat, lat, lon, lon]
*/
boundingbox?: number[]
osm_type?: "node" | "way" | "relation"
2024-10-19 14:44:55 +02:00
osm_id: string
category?: GeocodingCategory
payload?: object
source?: string
2024-08-15 01:51:33 +02:00
}
2024-10-19 14:44:55 +02:00
export type SearchResult = GeocodeResult
2024-08-15 01:51:33 +02:00
export interface GeocodingOptions {
bbox?: BBox
2024-08-15 01:51:33 +02:00
}
export default interface GeocodingProvider {
readonly name: string
2025-02-03 14:04:25 +01:00
/**
* Performs search.
* Note: the result _must_ return an empty list in the case of no results.
* Undefined might be interpreted by clients as "still running"
*/
search(query: string, options?: GeocodingOptions): Promise<GeocodeResult[]>
2024-08-15 01:51:33 +02:00
/**
* @param query
* @param options
*/
suggest?(query: string, options?: GeocodingOptions): Store<GeocodeResult[]>
2024-08-15 01:51:33 +02:00
}
2024-10-19 14:44:55 +02:00
export type ReverseGeocodingResult = Feature<
Geometry,
{
osm_id: number
osm_type: "node" | "way" | "relation"
country: string
city: string
countrycode: string
type: GeocodingCategory
street: string
}
>
2024-08-21 14:06:42 +02:00
2024-08-15 01:51:33 +02:00
export interface ReverseGeocodingProvider {
reverseSearch(
coordinate: { lon: number; lat: number },
zoom: number,
2024-10-19 14:44:55 +02:00
language?: string
): Promise<ReverseGeocodingResult[]>
2024-08-21 14:06:42 +02:00
}
export class GeocodingUtils {
2024-08-26 13:09:46 +02:00
public static searchLayer = GeocodingUtils.initSearchLayer()
private static initSearchLayer(): LayerConfig {
if (search["id"] === undefined) {
2024-08-23 02:30:51 +02:00
// We are resetting the layeroverview; trying to parse is useless
return undefined
}
2024-08-26 13:09:46 +02:00
return new LayerConfig(<LayerConfigJson>search, "search")
2024-08-23 02:30:51 +02:00
}
2024-08-23 02:16:24 +02:00
2024-08-21 14:06:42 +02:00
public static categoryToZoomLevel: Record<GeocodingCategory, number> = {
city: 12,
county: 10,
coordinate: 16,
country: 8,
house: 16,
locality: 14,
street: 15,
train_station: 14,
2024-08-23 02:16:24 +02:00
airport: 13,
2024-08-26 13:09:46 +02:00
shop: 16,
2024-08-21 14:06:42 +02:00
}
2024-10-19 14:44:55 +02:00
public static mergeSimilarResults(results: GeocodeResult[]) {
const byName: Record<string, GeocodeResult[]> = {}
for (const result of results) {
const nm = result.display_name
2024-10-19 14:44:55 +02:00
if (!byName[nm]) {
byName[nm] = []
}
byName[nm].push(result)
}
const merged: GeocodeResult[] = []
for (const nm in byName) {
const options = byName[nm]
const added = options[0]
merged.push(added)
2024-10-19 14:44:55 +02:00
const centers: [number, number][] = [[added.lon, added.lat]]
for (const other of options) {
2024-10-19 14:44:55 +02:00
const otherCenter: [number, number] = [other.lon, other.lat]
const nearbyFound = centers.some(
(center) => GeoOperations.distanceBetween(center, otherCenter) < 500
)
if (!nearbyFound) {
merged.push(other)
centers.push(otherCenter)
}
}
}
return merged
}
2024-08-21 14:06:42 +02:00
public static categoryToIcon: Record<GeocodingCategory, DefaultPinIcon> = {
city: "building_office_2",
coordinate: "globe_alt",
country: "globe_alt",
house: "house",
locality: "building_office_2",
street: "globe_alt",
train_station: "train",
county: "building_office_2",
2024-08-23 02:16:24 +02:00
airport: "airport",
2024-08-26 13:09:46 +02:00
shop: "building_storefront",
2024-08-21 14:06:42 +02:00
}
2024-08-15 01:51:33 +02:00
}