import Constants from "../../Models/Constants" import GeocodingProvider, { GeoCodeResult, GeocodingCategory, GeocodingOptions, ReverseGeocodingProvider, ReverseGeocodingResult } from "./GeocodingProvider" import { Utils } from "../../Utils" import { Feature, FeatureCollection } from "geojson" import Locale from "../../UI/i18n/Locale" import { GeoOperations } from "../GeoOperations" import { Store, Stores } from "../UIEventSource" export default class PhotonSearch implements GeocodingProvider, ReverseGeocodingProvider { private _endpoint: string private supportedLanguages = ["en", "de", "fr"] private static readonly types = { "R": "relation", "W": "way", "N": "node" } constructor(endpoint?: string) { this._endpoint = endpoint ?? Constants.photonEndpoint ?? "https://photon.komoot.io/" } async reverseSearch(coordinate: { lon: number; lat: number }, zoom: number, language?: string): Promise { const url = `${this._endpoint}/reverse?lon=${coordinate.lon}&lat=${coordinate.lat}&${this.getLanguage(language)}` const result = await Utils.downloadJsonCached(url, 1000 * 60 * 60) for (const f of result.features) { f.properties.osm_type = PhotonSearch.types[f.properties.osm_type] } return 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}` } suggest(query: string, options?: GeocodingOptions): Store { return Stores.FromPromise(this.search(query, options)) } private buildDescription(entry: Feature) { const p = entry.properties const type = 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) if (!addr) { return p.city } return addr + ifdef(", ", p.city) } case "coordinate": case "street": return p.city ?? p.country case "city": case "locality": if (p.state) { return p.state + ifdef(", ", p.country) } return p.country case "country": return undefined } } private getCategory(entry: Feature) { const p = entry.properties if(p.osm_key === "shop"){ return "shop" } if (p.osm_value === "train_station" || p.osm_key === "railway") { return "train_station" } if (p.osm_value === "aerodrome" || p.osm_key === "aeroway") { return "airport" } return p.type } async search(query: string, options?: GeocodingOptions): Promise { 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(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] } return { 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, source: this._endpoint } }) } }