switches to nominatim

This commit is contained in:
Christian Beutel
2025-01-25 17:03:29 +01:00
parent ae5c0f8f62
commit eda94f3003
25 changed files with 526 additions and 179 deletions

View File

@@ -0,0 +1,183 @@
import { env } from "$env/dynamic/public";
import { APIError } from "$lib/util/api_util";
import type { Hits, MultiSearchParams, MultiSearchResponse, MultiSearchResult, SearchParams, SearchResponse } from "meilisearch";
export type LocationSearchResult = {
name: string;
description: string;
lat: number;
lon: number;
category: string;
type: string;
}
export type TrailSearchResult = {
id: string;
_geo: {
lat: number,
lon: number
}
auhtor: string;
category: string;
completed: boolean;
created: number;
date: number;
description: string;
difficulty: "easy" | "moderate" | "difficult"
distance: number;
duration: number
elevation_gain: number;
elevation_loss: number
location: string;
name: string;
public: boolean;
}
type NominatimResponse = {
type: string
licence: string
features: Feature[]
}
type Feature = {
type: string
properties: Properties
bbox: number[]
geometry: Geometry
}
type Address = {
amenity: string
road: string
neighbourhood: string
suburb: string
city_district: string
city: string
state: string
"ISO3166-2-lvl4": string
postcode: string
country: string
country_code: string
}
type Properties = {
place_id: number
osm_type: string
osm_id: number
place_rank: number
category: string
type: string
importance: number
addresstype: string
name: string
display_name: string
address: Address
}
type Geometry = {
type: string
coordinates: number[]
}
export async function searchTrails(q: string, options: SearchParams): Promise<Hits<TrailSearchResult>> {
const r = await fetch("/api/v1/search/trails", {
method: "POST",
body: JSON.stringify({
q,
options
}),
});
if (!r.ok) {
const response = await r.json();
throw new APIError(r.status, response.message, response.detail)
}
const response: SearchResponse<TrailSearchResult> = await r.json();
return response.hits
}
export async function searchLocations(q: string, limit?: number): Promise<Hits<LocationSearchResult>> {
const nominatimURL = env.PUBLIC_NOMINATIM_URL ?? "https://nominatim.openstreetmap.org"
const r = await fetch(`${nominatimURL}/search?q=${q}&format=geojson&addressdetails=1${limit ? '&limit=' + limit : ''}`, {
method: "GET",
});
if (!r.ok) {
const response = await r.json();
throw new APIError(r.status, response.message, response.detail)
}
const response: NominatimResponse = await r.json();
return response.features.map(f => ({
category: f.properties.category,
type: f.properties.type == "administrative" ? f.properties.addresstype : f.properties.type,
description: getLocationDescription(f.properties.address),
name: f.properties.name.length ? f.properties.name : f.properties.display_name,
lat: f.geometry.coordinates[1],
lon: f.geometry.coordinates[0],
}))
}
export async function searchLocationReverse(lat: number, lon: number) {
const nominatimURL = env.PUBLIC_NOMINATIM_URL ?? "https://nominatim.openstreetmap.org"
const r = await fetch(`${nominatimURL}/reverse?lat=${lat}&lon=${lon}&format=geojson&addressdetails=1`, {
method: "GET",
});
if (!r.ok) {
const response = await r.json();
throw new APIError(r.status, response.message, response.detail)
}
const response: NominatimResponse = await r.json();
if (response.features?.at(0)?.properties.address) {
return getLocationDescription(response.features[0].properties.address)
}
return ""
}
function getLocationDescription(address: Address) {
let description = ""
if (address.country) {
description += address.country;
}
if (address.state) {
description = `${address.state}, ` + description
}
if (address.city) {
description = `${address.city}, ` + description
}
return description;
}
export async function searchMulti(options: MultiSearchParams): Promise<MultiSearchResult<any>[]> {
const locationQuery = options.queries.find(q => q.indexUid === "locations");
const locationQueryIndex = locationQuery ? options.queries.indexOf(locationQuery) : -1
if (locationQueryIndex >= 0) {
options.queries.splice(locationQueryIndex, 1)
}
const r = await fetch("/api/v1/search/multi", {
method: "POST",
body: JSON.stringify(options),
});
if (!r.ok) {
const response = await r.json();
throw new APIError(r.status, response.message, response.detail)
}
const response: MultiSearchResponse<any> = await r.json();
if (locationQuery && locationQuery.q !== undefined && locationQuery.q !== null) {
const locationsResults = await searchLocations(locationQuery.q, locationQuery.limit)
response.results.splice(locationQueryIndex,
0,
{ hits: locationsResults, indexUid: "locations", query: locationQuery.q, processingTimeMs: 0 }
)
}
return response.results
}