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)
}

View file

@ -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<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> = {
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))
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("/")

View file

@ -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<SearchResult[]> {
public search(query: string, options?:GeocodingOptions): Promise<SearchResult[]> {
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)

View file

@ -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<GeocodeResult[]> {
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<GeocodeResult[]> {
async search(query: string, options?: GeocodingOptions, limit?: number): Promise<GeocodeResult[]> {
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<FeatureCollection>(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)
}
}

View file

@ -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<Set<string>>
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<SearchResult[]> {
return this.searchDirect(query, options)
async search(query: string): Promise<SearchResult[]> {
return this.searchDirect(query, 99)
}
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){
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 => <SearchResult> {
payload: match,

View file

@ -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)