MapComplete/src/Logic/Geocoding/PhotonSearch.ts

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

144 lines
4.6 KiB
TypeScript
Raw Normal View History

2024-08-21 14:06:42 +02:00
import Constants from "../../Models/Constants"
import GeocodingProvider, {
2024-08-26 13:09:46 +02:00
GeocodeResult,
2024-08-22 22:50:37 +02:00
GeocodingCategory,
2024-08-21 14:06:42 +02:00
GeocodingOptions,
ReverseGeocodingProvider,
2024-08-26 13:09:46 +02:00
ReverseGeocodingResult,
2024-08-21 14:06:42 +02:00
} from "./GeocodingProvider"
import { Utils } from "../../Utils"
import { Feature, FeatureCollection } from "geojson"
import Locale from "../../UI/i18n/Locale"
import { GeoOperations } from "../GeoOperations"
2024-08-22 22:50:37 +02:00
import { Store, Stores } from "../UIEventSource"
2024-08-21 14:06:42 +02:00
export default class PhotonSearch implements GeocodingProvider, ReverseGeocodingProvider {
private _endpoint: string
private supportedLanguages = ["en", "de", "fr"]
private static readonly types = {
"R": "relation",
"W": "way",
2024-08-26 13:09:46 +02:00
"N": "node",
2024-08-21 14:06:42 +02:00
}
constructor(endpoint?: string) {
this._endpoint = endpoint ?? Constants.photonEndpoint ?? "https://photon.komoot.io/"
}
async reverseSearch(coordinate: {
lon: number;
lat: number
}, zoom: number, language?: string): Promise<ReverseGeocodingResult[]> {
const url = `${this._endpoint}/reverse?lon=${coordinate.lon}&lat=${coordinate.lat}&${this.getLanguage(language)}`
const result = await Utils.downloadJsonCached<FeatureCollection>(url, 1000 * 60 * 60)
for (const f of result.features) {
f.properties.osm_type = PhotonSearch.types[f.properties.osm_type]
}
return <ReverseGeocodingResult[]>result.features
}
/**
* Gets a `&lang=en` if the current/requested language is supported
* @param language
* @private
*/
private getLanguage(language?: string): string {
language ??= Locale.language.data
if (this.supportedLanguages.indexOf(language) < 0) {
return ""
}
return `&lang=${language}`
}
2024-08-26 13:09:46 +02:00
suggest(query: string, options?: GeocodingOptions): Store<GeocodeResult[]> {
2024-08-22 22:50:37 +02:00
return Stores.FromPromise(this.search(query, options))
2024-08-21 14:06:42 +02:00
}
private buildDescription(entry: Feature) {
const p = entry.properties
const type = <GeocodingCategory>p.type
function ifdef(prefix: string, str: string) {
if (str) {
return prefix + str
}
return ""
}
switch (type) {
case "house": {
const addr = ifdef("", p.street) + ifdef(" ", p.housenumber)
2024-08-22 22:50:37 +02:00
if (!addr) {
2024-08-21 14:06:42 +02:00
return p.city
}
return addr + ifdef(", ", p.city)
}
case "coordinate":
case "street":
return p.city ?? p.country
case "city":
case "locality":
2024-08-22 22:50:37 +02:00
if (p.state) {
return p.state + ifdef(", ", p.country)
2024-08-21 14:06:42 +02:00
}
return p.country
case "country":
return undefined
}
}
2024-08-22 22:50:37 +02:00
private getCategory(entry: Feature) {
2024-08-21 14:06:42 +02:00
const p = entry.properties
2024-08-26 13:09:46 +02:00
if (p.osm_key === "shop") {
2024-08-23 02:16:24 +02:00
return "shop"
}
2024-08-22 22:50:37 +02:00
if (p.osm_value === "train_station" || p.osm_key === "railway") {
2024-08-21 14:06:42 +02:00
return "train_station"
}
2024-08-22 22:50:37 +02:00
if (p.osm_value === "aerodrome" || p.osm_key === "aeroway") {
2024-08-21 14:06:42 +02:00
return "airport"
}
return p.type
}
2024-08-26 13:09:46 +02:00
async search(query: string, options?: GeocodingOptions): Promise<GeocodeResult[]> {
2024-08-21 14:06:42 +02:00
if (query.length < 3) {
return []
}
const limit = options?.limit ?? 5
let bbox = ""
if (options?.bbox) {
const [lon, lat] = options.bbox.center()
bbox = `&lon=${lon}&lat=${lat}`
}
const url = `${this._endpoint}/api/?q=${encodeURIComponent(query)}&limit=${limit}${this.getLanguage()}${bbox}`
const results = await Utils.downloadJsonCached<FeatureCollection>(url, 1000 * 60 * 60)
return results.features.map(f => {
const [lon, lat] = GeoOperations.centerpointCoordinates(f)
let boundingbox: number[] = undefined
if (f.properties.extent) {
const [lon0, lat0, lon1, lat1] = f.properties.extent
boundingbox = [lat0, lat1, lon0, lon1]
}
2024-08-26 13:09:46 +02:00
return <GeocodeResult>{
2024-08-21 14:06:42 +02:00
feature: f,
osm_id: f.properties.osm_id,
display_name: f.properties.name,
description: this.buildDescription(f),
osm_type: PhotonSearch.types[f.properties.osm_type],
category: this.getCategory(f),
boundingbox,
lon, lat,
2024-08-26 13:09:46 +02:00
source: this._endpoint,
2024-08-21 14:06:42 +02:00
}
})
}
}