From 6468e33d663d7970e5448aada28d3bfc0daba5d7 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 27 Aug 2024 23:56:54 +0200 Subject: [PATCH] Search: move limit responsability to the constructor, merge similar results --- src/Logic/Geocoding/FilterSearch.ts | 2 +- src/Logic/Geocoding/GeocodingProvider.ts | 34 +++++++++++++++++++++-- src/Logic/Geocoding/LocalElementSearch.ts | 4 +-- src/Logic/Geocoding/NominatimGeocoding.ts | 10 ++++--- src/Logic/Geocoding/PhotonSearch.ts | 17 ++++++++---- src/Logic/Geocoding/ThemeSearch.ts | 16 ++++++----- src/Models/ThemeViewState.ts | 2 +- 7 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/Logic/Geocoding/FilterSearch.ts b/src/Logic/Geocoding/FilterSearch.ts index f7255b855..e9d97f85f 100644 --- a/src/Logic/Geocoding/FilterSearch.ts +++ b/src/Logic/Geocoding/FilterSearch.ts @@ -12,7 +12,7 @@ export default class FilterSearch implements GeocodingProvider { } - async search(query: string, options?: GeocodingOptions): Promise { + async search(query: string): Promise { return this.searchDirectly(query) } diff --git a/src/Logic/Geocoding/GeocodingProvider.ts b/src/Logic/Geocoding/GeocodingProvider.ts index 639bc973b..ad7823598 100644 --- a/src/Logic/Geocoding/GeocodingProvider.ts +++ b/src/Logic/Geocoding/GeocodingProvider.ts @@ -7,6 +7,7 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" import FilterConfig, { FilterConfigOption } from "../../Models/ThemeConfig/FilterConfig" import { MinimalLayoutInformation } from "../../Models/ThemeConfig/LayoutConfig" +import { GeoOperations } from "../GeoOperations" export type GeocodingCategory = "coordinate" @@ -50,8 +51,7 @@ export type SearchResult = | GeocodeResult export interface GeocodingOptions { - bbox?: BBox, - limit?: number + bbox?: BBox } @@ -111,6 +111,36 @@ export class GeocodingUtils { } + public static mergeSimilarResults(results: GeocodeResult[]){ + const byName: Record = {} + + + for (const result of results) { + const nm = result.display_name + 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) + const centers: [number,number][] = [[added.lon, added.lat]] + for (const other of options) { + 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 + } + public static categoryToIcon: Record = { city: "building_office_2", diff --git a/src/Logic/Geocoding/LocalElementSearch.ts b/src/Logic/Geocoding/LocalElementSearch.ts index 38b8ee991..988402d99 100644 --- a/src/Logic/Geocoding/LocalElementSearch.ts +++ b/src/Logic/Geocoding/LocalElementSearch.ts @@ -95,8 +95,8 @@ export default class LocalElementSearch implements GeocodingProvider { const listed: Store = Stores.concat(partials).map(l => l.flatMap(x => x)) return listed.mapD(results => { results.sort((a, b) => (a.physicalDistance + a.levehnsteinD * 25) - (b.physicalDistance + b.levehnsteinD * 25)) - if (this._limit || options?.limit) { - results = results.slice(0, Math.min(this._limit ?? options?.limit, options?.limit ?? this._limit)) + if (this._limit) { + results = results.slice(0, this._limit) } return results.map(entry => { const [osm_type, osm_id] = entry.feature.properties.id.split("/") diff --git a/src/Logic/Geocoding/NominatimGeocoding.ts b/src/Logic/Geocoding/NominatimGeocoding.ts index 6248579c6..750be4fac 100644 --- a/src/Logic/Geocoding/NominatimGeocoding.ts +++ b/src/Logic/Geocoding/NominatimGeocoding.ts @@ -3,21 +3,23 @@ import { BBox } from "../BBox" import Constants from "../../Models/Constants" import { FeatureCollection } from "geojson" import Locale from "../../UI/i18n/Locale" -import GeocodingProvider, { SearchResult } from "./GeocodingProvider" +import GeocodingProvider, { GeocodingOptions, SearchResult } from "./GeocodingProvider" export class NominatimGeocoding implements GeocodingProvider { private readonly _host ; + private readonly limit: number - constructor(host: string = Constants.nominatimEndpoint) { + constructor(limit: number = 3, host: string = Constants.nominatimEndpoint) { + this.limit = limit this._host = host } - public search(query: string, options?: { bbox?: BBox; limit?: number }): Promise { + public search(query: string, options?:GeocodingOptions): Promise { const b = options?.bbox ?? BBox.global const url = `${ this._host - }search?format=json&limit=${options?.limit ?? 1}&viewbox=${b.getEast()},${b.getNorth()},${b.getWest()},${b.getSouth()}&accept-language=${ + }search?format=json&limit=${this.limit}&viewbox=${b.getEast()},${b.getNorth()},${b.getWest()},${b.getSouth()}&accept-language=${ Locale.language.data }&q=${query}` return Utils.downloadJson(url) diff --git a/src/Logic/Geocoding/PhotonSearch.ts b/src/Logic/Geocoding/PhotonSearch.ts index ff5eb4bfb..3bb2b66b9 100644 --- a/src/Logic/Geocoding/PhotonSearch.ts +++ b/src/Logic/Geocoding/PhotonSearch.ts @@ -2,7 +2,7 @@ import Constants from "../../Models/Constants" import GeocodingProvider, { GeocodeResult, GeocodingCategory, - GeocodingOptions, + GeocodingOptions, GeocodingUtils, ReverseGeocodingProvider, ReverseGeocodingResult, } from "./GeocodingProvider" @@ -20,9 +20,13 @@ export default class PhotonSearch implements GeocodingProvider, ReverseGeocoding "W": "way", "N": "node", } + private readonly suggestionLimit: number = 5 + private readonly searchLimit: number = 1 - constructor(endpoint?: string) { + constructor(suggestionLimit:number = 5, searchLimit:number = 1, endpoint?: string) { + this.suggestionLimit = suggestionLimit + this.searchLimit = searchLimit this._endpoint = endpoint ?? Constants.photonEndpoint ?? "https://photon.komoot.io/" } @@ -55,7 +59,7 @@ export default class PhotonSearch implements GeocodingProvider, ReverseGeocoding } suggest(query: string, options?: GeocodingOptions): Store { - return Stores.FromPromise(this.search(query, options)) + return Stores.FromPromise(this.search(query, options, this.suggestionLimit)) } private buildDescription(entry: Feature) { @@ -107,11 +111,11 @@ export default class PhotonSearch implements GeocodingProvider, ReverseGeocoding return p.type } - async search(query: string, options?: GeocodingOptions): Promise { + async search(query: string, options?: GeocodingOptions, limit?: number): Promise { if (query.length < 3) { return [] } - const limit = options?.limit ?? 5 + limit ??= this.searchLimit let bbox = "" if (options?.bbox) { const [lon, lat] = options.bbox.center() @@ -119,7 +123,7 @@ export default class PhotonSearch implements GeocodingProvider, ReverseGeocoding } 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 encoded= results.features.map(f => { const [lon, lat] = GeoOperations.centerpointCoordinates(f) let boundingbox: number[] = undefined if (f.properties.extent) { @@ -138,6 +142,7 @@ export default class PhotonSearch implements GeocodingProvider, ReverseGeocoding source: this._endpoint, } }) + return GeocodingUtils.mergeSimilarResults(encoded) } } diff --git a/src/Logic/Geocoding/ThemeSearch.ts b/src/Logic/Geocoding/ThemeSearch.ts index ca43041d8..8e25a10af 100644 --- a/src/Logic/Geocoding/ThemeSearch.ts +++ b/src/Logic/Geocoding/ThemeSearch.ts @@ -11,31 +11,33 @@ export default class ThemeSearch implements GeocodingProvider { private static allThemes: MinimalLayoutInformation[] = (themeOverview["default"] ?? themeOverview) private readonly _state: SpecialVisualizationState private readonly _knownHiddenThemes: Store> + private readonly _suggestionLimit: number - constructor(state: SpecialVisualizationState) { + constructor(state: SpecialVisualizationState, suggestionLimit: number) { this._state = state + this._suggestionLimit = suggestionLimit this._knownHiddenThemes = MoreScreen.knownHiddenThemes(this._state.osmConnection) } - async search(query: string, options?: GeocodingOptions): Promise { - return this.searchDirect(query, options) + async search(query: string): Promise { + return this.searchDirect(query, 99) } suggest(query: string, options?: GeocodingOptions): Store { - return new ImmutableStore(this.searchDirect(query, options)) + return new ImmutableStore(this.searchDirect(query, this._suggestionLimit ?? 4)) } - private searchDirect(query: string, options?: GeocodingOptions): SearchResult[] { + private searchDirect(query: string, limit: number): SearchResult[] { if(query.length < 1){ return [] } - const limit = options?.limit ?? 4 query = Utils.simplifyStringForSearch(query) const withMatch = ThemeSearch.allThemes .filter(th => !th.hideFromOverview || this._knownHiddenThemes.data.has(th.id)) .filter(th => th.id !== this._state.layout.id) .filter(th => MoreScreen.MatchesLayout(th, query)) - .slice(0, limit + 1) + .slice(0, limit) + console.log("Matched", withMatch, limit) return withMatch.map(match => { payload: match, diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index 9f7179ea7..afc2ebbc6 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -388,9 +388,9 @@ export default class ThemeViewState implements SpecialVisualizationState { new FilterSearch(this), new LocalElementSearch(this, 5), new CoordinateSearch(), + this.featureSwitches.featureSwitchBackToThemeOverview.data ? new ThemeSearch(this, 3) : undefined, new OpenStreetMapIdSearch(this), new PhotonSearch(), // new NominatimGeocoding(), - this.featureSwitches.featureSwitchBackToThemeOverview.data ? new ThemeSearch(this) : undefined ) this.recentlySearched = new RecentSearch(this)