Search: move limit responsability to the constructor, merge similar results
This commit is contained in:
parent
cdc1e05499
commit
6468e33d66
7 changed files with 62 additions and 23 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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("/")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue