Search: move limit responsability to the constructor, merge similar results

This commit is contained in:
Pieter Vander Vennet 2024-08-27 23:56:54 +02:00
parent cdc1e05499
commit 6468e33d66
7 changed files with 62 additions and 23 deletions

View file

@ -12,7 +12,7 @@ export default class FilterSearch implements GeocodingProvider {
} }
async search(query: string, options?: GeocodingOptions): Promise<SearchResult[]> { async search(query: string): Promise<SearchResult[]> {
return this.searchDirectly(query) return this.searchDirectly(query)
} }

View file

@ -7,6 +7,7 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
import FilterConfig, { FilterConfigOption } from "../../Models/ThemeConfig/FilterConfig" import FilterConfig, { FilterConfigOption } from "../../Models/ThemeConfig/FilterConfig"
import { MinimalLayoutInformation } from "../../Models/ThemeConfig/LayoutConfig" import { MinimalLayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
import { GeoOperations } from "../GeoOperations"
export type GeocodingCategory = export type GeocodingCategory =
"coordinate" "coordinate"
@ -50,8 +51,7 @@ export type SearchResult =
| GeocodeResult | GeocodeResult
export interface GeocodingOptions { export interface GeocodingOptions {
bbox?: BBox, bbox?: BBox
limit?: number
} }
@ -111,6 +111,36 @@ export class GeocodingUtils {
} }
public static mergeSimilarResults(results: GeocodeResult[]){
const byName: Record<string, GeocodeResult[]> = {}
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<GeocodingCategory, DefaultPinIcon> = { public static categoryToIcon: Record<GeocodingCategory, DefaultPinIcon> = {
city: "building_office_2", city: "building_office_2",

View file

@ -95,8 +95,8 @@ export default class LocalElementSearch implements GeocodingProvider {
const listed: Store<IntermediateResult[]> = Stores.concat(partials).map(l => l.flatMap(x => x)) const listed: Store<IntermediateResult[]> = Stores.concat(partials).map(l => l.flatMap(x => x))
return listed.mapD(results => { return listed.mapD(results => {
results.sort((a, b) => (a.physicalDistance + a.levehnsteinD * 25) - (b.physicalDistance + b.levehnsteinD * 25)) results.sort((a, b) => (a.physicalDistance + a.levehnsteinD * 25) - (b.physicalDistance + b.levehnsteinD * 25))
if (this._limit || options?.limit) { if (this._limit) {
results = results.slice(0, Math.min(this._limit ?? options?.limit, options?.limit ?? this._limit)) results = results.slice(0, this._limit)
} }
return results.map(entry => { return results.map(entry => {
const [osm_type, osm_id] = entry.feature.properties.id.split("/") const [osm_type, osm_id] = entry.feature.properties.id.split("/")

View file

@ -3,21 +3,23 @@ import { BBox } from "../BBox"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
import { FeatureCollection } from "geojson" import { FeatureCollection } from "geojson"
import Locale from "../../UI/i18n/Locale" import Locale from "../../UI/i18n/Locale"
import GeocodingProvider, { SearchResult } from "./GeocodingProvider" import GeocodingProvider, { GeocodingOptions, SearchResult } from "./GeocodingProvider"
export class NominatimGeocoding implements GeocodingProvider { export class NominatimGeocoding implements GeocodingProvider {
private readonly _host ; 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 this._host = host
} }
public search(query: string, options?: { bbox?: BBox; limit?: number }): Promise<SearchResult[]> { public search(query: string, options?:GeocodingOptions): Promise<SearchResult[]> {
const b = options?.bbox ?? BBox.global const b = options?.bbox ?? BBox.global
const url = `${ const url = `${
this._host 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 Locale.language.data
}&q=${query}` }&q=${query}`
return Utils.downloadJson(url) return Utils.downloadJson(url)

View file

@ -2,7 +2,7 @@ import Constants from "../../Models/Constants"
import GeocodingProvider, { import GeocodingProvider, {
GeocodeResult, GeocodeResult,
GeocodingCategory, GeocodingCategory,
GeocodingOptions, GeocodingOptions, GeocodingUtils,
ReverseGeocodingProvider, ReverseGeocodingProvider,
ReverseGeocodingResult, ReverseGeocodingResult,
} from "./GeocodingProvider" } from "./GeocodingProvider"
@ -20,9 +20,13 @@ export default class PhotonSearch implements GeocodingProvider, ReverseGeocoding
"W": "way", "W": "way",
"N": "node", "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/" 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<GeocodeResult[]> { suggest(query: string, options?: GeocodingOptions): Store<GeocodeResult[]> {
return Stores.FromPromise(this.search(query, options)) return Stores.FromPromise(this.search(query, options, this.suggestionLimit))
} }
private buildDescription(entry: Feature) { private buildDescription(entry: Feature) {
@ -107,11 +111,11 @@ export default class PhotonSearch implements GeocodingProvider, ReverseGeocoding
return p.type return p.type
} }
async search(query: string, options?: GeocodingOptions): Promise<GeocodeResult[]> { async search(query: string, options?: GeocodingOptions, limit?: number): Promise<GeocodeResult[]> {
if (query.length < 3) { if (query.length < 3) {
return [] return []
} }
const limit = options?.limit ?? 5 limit ??= this.searchLimit
let bbox = "" let bbox = ""
if (options?.bbox) { if (options?.bbox) {
const [lon, lat] = options.bbox.center() 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 url = `${this._endpoint}/api/?q=${encodeURIComponent(query)}&limit=${limit}${this.getLanguage()}${bbox}`
const results = await Utils.downloadJsonCached<FeatureCollection>(url, 1000 * 60 * 60) const results = await Utils.downloadJsonCached<FeatureCollection>(url, 1000 * 60 * 60)
return results.features.map(f => { const encoded= results.features.map(f => {
const [lon, lat] = GeoOperations.centerpointCoordinates(f) const [lon, lat] = GeoOperations.centerpointCoordinates(f)
let boundingbox: number[] = undefined let boundingbox: number[] = undefined
if (f.properties.extent) { if (f.properties.extent) {
@ -138,6 +142,7 @@ export default class PhotonSearch implements GeocodingProvider, ReverseGeocoding
source: this._endpoint, source: this._endpoint,
} }
}) })
return GeocodingUtils.mergeSimilarResults(encoded)
} }
} }

View file

@ -11,31 +11,33 @@ export default class ThemeSearch implements GeocodingProvider {
private static allThemes: MinimalLayoutInformation[] = (themeOverview["default"] ?? themeOverview) private static allThemes: MinimalLayoutInformation[] = (themeOverview["default"] ?? themeOverview)
private readonly _state: SpecialVisualizationState private readonly _state: SpecialVisualizationState
private readonly _knownHiddenThemes: Store<Set<string>> private readonly _knownHiddenThemes: Store<Set<string>>
private readonly _suggestionLimit: number
constructor(state: SpecialVisualizationState) { constructor(state: SpecialVisualizationState, suggestionLimit: number) {
this._state = state this._state = state
this._suggestionLimit = suggestionLimit
this._knownHiddenThemes = MoreScreen.knownHiddenThemes(this._state.osmConnection) this._knownHiddenThemes = MoreScreen.knownHiddenThemes(this._state.osmConnection)
} }
async search(query: string, options?: GeocodingOptions): Promise<SearchResult[]> { async search(query: string): Promise<SearchResult[]> {
return this.searchDirect(query, options) return this.searchDirect(query, 99)
} }
suggest(query: string, options?: GeocodingOptions): Store<SearchResult[]> { suggest(query: string, options?: GeocodingOptions): Store<SearchResult[]> {
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){ if(query.length < 1){
return [] return []
} }
const limit = options?.limit ?? 4
query = Utils.simplifyStringForSearch(query) query = Utils.simplifyStringForSearch(query)
const withMatch = ThemeSearch.allThemes const withMatch = ThemeSearch.allThemes
.filter(th => !th.hideFromOverview || this._knownHiddenThemes.data.has(th.id)) .filter(th => !th.hideFromOverview || this._knownHiddenThemes.data.has(th.id))
.filter(th => th.id !== this._state.layout.id) .filter(th => th.id !== this._state.layout.id)
.filter(th => MoreScreen.MatchesLayout(th, query)) .filter(th => MoreScreen.MatchesLayout(th, query))
.slice(0, limit + 1) .slice(0, limit)
console.log("Matched", withMatch, limit)
return withMatch.map(match => <SearchResult> { return withMatch.map(match => <SearchResult> {
payload: match, payload: match,

View file

@ -388,9 +388,9 @@ export default class ThemeViewState implements SpecialVisualizationState {
new FilterSearch(this), new FilterSearch(this),
new LocalElementSearch(this, 5), new LocalElementSearch(this, 5),
new CoordinateSearch(), new CoordinateSearch(),
this.featureSwitches.featureSwitchBackToThemeOverview.data ? new ThemeSearch(this, 3) : undefined,
new OpenStreetMapIdSearch(this), new OpenStreetMapIdSearch(this),
new PhotonSearch(), // new NominatimGeocoding(), new PhotonSearch(), // new NominatimGeocoding(),
this.featureSwitches.featureSwitchBackToThemeOverview.data ? new ThemeSearch(this) : undefined
) )
this.recentlySearched = new RecentSearch(this) this.recentlySearched = new RecentSearch(this)