switches to nominatim
This commit is contained in:
183
web/src/lib/stores/search_store.ts
Normal file
183
web/src/lib/stores/search_store.ts
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user