From 686ad70511f08f6b30807c25ce162774921e4513 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 11 Mar 2025 03:45:11 +0100 Subject: [PATCH] Feature(geocoding): pressing enter will now zoom to the first search result; refactor away type synonym --- src/Logic/Search/CombinedSearcher.ts | 10 ++--- src/Logic/Search/GeocodingFeatureSource.ts | 4 +- src/Logic/Search/GeocodingProvider.ts | 1 - src/Logic/Search/LocalElementSearch.ts | 10 ++--- src/Logic/Search/NominatimGeocoding.ts | 4 +- src/Logic/State/SearchState.ts | 51 +++++++++++++++++++--- src/UI/Search/GeocodeResult.svelte | 32 +++----------- src/UI/ThemeViewGUI.svelte | 1 + 8 files changed, 64 insertions(+), 49 deletions(-) diff --git a/src/Logic/Search/CombinedSearcher.ts b/src/Logic/Search/CombinedSearcher.ts index 07affc4c12..10bf7c2f9b 100644 --- a/src/Logic/Search/CombinedSearcher.ts +++ b/src/Logic/Search/CombinedSearcher.ts @@ -1,8 +1,4 @@ -import GeocodingProvider, { - GeocodeResult, - GeocodingOptions, - SearchResult, -} from "./GeocodingProvider" +import GeocodingProvider, { GeocodeResult, GeocodingOptions } from "./GeocodingProvider" import { Utils } from "../../Utils" import { Store, Stores } from "../UIEventSource" @@ -44,12 +40,12 @@ export default class CombinedSearcher implements GeocodingProvider { return results } - async search(query: string, options?: GeocodingOptions): Promise { + async search(query: string, options?: GeocodingOptions): Promise { const results = await Promise.all(this._providers.map((pr) => pr.search(query, options))) return CombinedSearcher.merge(results) } - suggest(query: string, options?: GeocodingOptions): Store { + suggest(query: string, options?: GeocodingOptions): Store { return Stores.concat( this._providersWithSuggest.map((pr) => pr.suggest(query, options)) ).map((gcrss) => CombinedSearcher.merge(gcrss)) diff --git a/src/Logic/Search/GeocodingFeatureSource.ts b/src/Logic/Search/GeocodingFeatureSource.ts index f4f3960f61..a5c6723d08 100644 --- a/src/Logic/Search/GeocodingFeatureSource.ts +++ b/src/Logic/Search/GeocodingFeatureSource.ts @@ -1,4 +1,4 @@ -import { SearchResult } from "./GeocodingProvider" +import { GeocodeResult } from "./GeocodingProvider" import { Store } from "../UIEventSource" import { FeatureSource } from "../FeatureSource/FeatureSource" import { Feature, Geometry } from "geojson" @@ -6,7 +6,7 @@ import { Feature, Geometry } from "geojson" export default class GeocodingFeatureSource implements FeatureSource { public features: Store>[]> - constructor(provider: Store) { + constructor(provider: Store) { this.features = provider.map((geocoded) => { if (geocoded === undefined) { return [] diff --git a/src/Logic/Search/GeocodingProvider.ts b/src/Logic/Search/GeocodingProvider.ts index a698e3aa9d..ebd2b9d8a5 100644 --- a/src/Logic/Search/GeocodingProvider.ts +++ b/src/Logic/Search/GeocodingProvider.ts @@ -42,7 +42,6 @@ export type GeocodeResult = { payload?: object source?: string } -export type SearchResult = GeocodeResult export interface GeocodingOptions { bbox?: BBox diff --git a/src/Logic/Search/LocalElementSearch.ts b/src/Logic/Search/LocalElementSearch.ts index 4e3d180f53..9db1de6542 100644 --- a/src/Logic/Search/LocalElementSearch.ts +++ b/src/Logic/Search/LocalElementSearch.ts @@ -1,4 +1,4 @@ -import GeocodingProvider, { GeocodingOptions, SearchResult } from "./GeocodingProvider" +import GeocodingProvider, { GeocodeResult, GeocodingOptions } from "./GeocodingProvider" import ThemeViewState from "../../Models/ThemeViewState" import { Utils } from "../../Utils" import { Feature } from "geojson" @@ -26,7 +26,7 @@ export default class LocalElementSearch implements GeocodingProvider { this._limit = limit } - async search(query: string, options?: GeocodingOptions): Promise { + async search(query: string, options?: GeocodingOptions): Promise { return this.searchEntries(query, options, false).data } @@ -92,7 +92,7 @@ export default class LocalElementSearch implements GeocodingProvider { query: string, options?: GeocodingOptions, matchStart?: boolean - ): Store { + ): Store { if (query.length < 3) { return new ImmutableStore([]) } @@ -126,7 +126,7 @@ export default class LocalElementSearch implements GeocodingProvider { } return results.map((entry) => { const [osm_type, osm_id] = entry.feature.properties.id.split("/") - return { + return { lon: entry.center[0], lat: entry.center[1], osm_type, @@ -141,7 +141,7 @@ export default class LocalElementSearch implements GeocodingProvider { }) } - suggest(query: string, options?: GeocodingOptions): Store { + suggest(query: string, options?: GeocodingOptions): Store { return this.searchEntries(query, options, true) } } diff --git a/src/Logic/Search/NominatimGeocoding.ts b/src/Logic/Search/NominatimGeocoding.ts index f191306093..9371fab457 100644 --- a/src/Logic/Search/NominatimGeocoding.ts +++ b/src/Logic/Search/NominatimGeocoding.ts @@ -3,7 +3,7 @@ import { BBox } from "../BBox" import Constants from "../../Models/Constants" import { FeatureCollection } from "geojson" import Locale from "../../UI/i18n/Locale" -import GeocodingProvider, { GeocodingOptions, SearchResult } from "./GeocodingProvider" +import GeocodingProvider, { GeocodeResult, GeocodingOptions } from "./GeocodingProvider" export class NominatimGeocoding implements GeocodingProvider { private readonly _host @@ -15,7 +15,7 @@ export class NominatimGeocoding implements GeocodingProvider { this._host = host } - public search(query: string, options?: GeocodingOptions): Promise { + public search(query: string, options?: GeocodingOptions): Promise { const b = options?.bbox ?? BBox.global const url = `${this._host}search?format=json&limit=${ this.limit diff --git a/src/Logic/State/SearchState.ts b/src/Logic/State/SearchState.ts index 642a64fa36..01746cb6a7 100644 --- a/src/Logic/State/SearchState.ts +++ b/src/Logic/State/SearchState.ts @@ -1,4 +1,4 @@ -import GeocodingProvider, { type SearchResult } from "../Search/GeocodingProvider" +import GeocodingProvider, { GeocodeResult, GeocodingUtils } from "../Search/GeocodingProvider" import { ImmutableStore, Store, Stores, UIEventSource } from "../UIEventSource" import CombinedSearcher from "../Search/CombinedSearcher" import FilterSearch, { FilterSearchResult } from "../Search/FilterSearch" @@ -16,12 +16,13 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { FeatureSource } from "../FeatureSource/FeatureSource" import { Feature } from "geojson" import OpenLocationCodeSearch from "../Search/OpenLocationCodeSearch" +import { BBox } from "../BBox" export default class SearchState { public readonly feedback: UIEventSource = new UIEventSource(undefined) public readonly searchTerm: UIEventSource = new UIEventSource("") public readonly searchIsFocused = new UIEventSource(false) - public readonly suggestions: Store + public readonly suggestions: Store public readonly filterSuggestions: Store public readonly themeSuggestions: Store public readonly layerSuggestions: Store @@ -60,7 +61,7 @@ export default class SearchState { return new ImmutableStore(true) } return Stores.concat(suggestions).map((suggestions) => - suggestions.some((list, i) => list === undefined) + suggestions.some(list => list === undefined) ) }) this.suggestions = suggestionsList.bindD((suggestions) => @@ -100,7 +101,7 @@ export default class SearchState { this.showSearchDrawer = new UIEventSource(false) - this.searchIsFocused.addCallbackAndRunD((sugg) => { + this.searchIsFocused.addCallbackAndRunD(sugg => { if (sugg) { this.showSearchDrawer.set(true) } @@ -124,7 +125,6 @@ export default class SearchState { const state = this.state const layersToShow = payload.map((fsr) => fsr.layer.id) - console.log("Layers to show are", layersToShow) for (const otherLayer of state.layerState.filteredLayers.values()) { const layer = otherLayer.layerDef if (!layer.isNormal()) { @@ -167,4 +167,45 @@ export default class SearchState { this.state.featureProperties.trackFeature(f) this.state.selectedElement.set(f) } + + public moveToBestMatch() { + const suggestion = this.suggestions.data?.[0] + if (suggestion) { + this.applyGeocodeResult(suggestion) + } + if (this.suggestionsSearchRunning.data) { + this.suggestionsSearchRunning.addCallback(() => { + this.applyGeocodeResult(this.suggestions.data?.[0]) + return true // unregister + }) + } + } + + applyGeocodeResult(entry: GeocodeResult) { + if (!entry) { + console.error("ApplyGeocodeResult got undefined/null") + } + console.log("Moving to", entry.description) + const state = this.state + if (entry.boundingbox) { + const [lat0, lat1, lon0, lon1] = entry.boundingbox + state.mapProperties.bounds.set( + new BBox([ + [lon0, lat0], + [lon1, lat1] + ]).pad(0.01) + ) + } else { + state.mapProperties.flyTo( + entry.lon, + entry.lat, + GeocodingUtils.categoryToZoomLevel[entry.category] ?? 17 + ) + } + if (entry.feature?.properties?.id) { + state.selectedElement.set(entry.feature) + } + state.userRelatedState.recentlyVisitedSearch.add(entry) + this.closeIfFullscreen() + } } diff --git a/src/UI/Search/GeocodeResult.svelte b/src/UI/Search/GeocodeResult.svelte index b9b9e37563..b5172501ac 100644 --- a/src/UI/Search/GeocodeResult.svelte +++ b/src/UI/Search/GeocodeResult.svelte @@ -5,17 +5,14 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { UIEventSource } from "../../Logic/UIEventSource" import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" - import { createEventDispatcher } from "svelte" - import type { SpecialVisualizationState } from "../SpecialVisualization" - import { BBox } from "../../Logic/BBox" - import ToSvelte from "../Base/ToSvelte.svelte" import Icon from "../Map/Icon.svelte" import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte" import ArrowUp from "@babeard/svelte-heroicons/mini/ArrowUp" import DefaultIcon from "../Map/DefaultIcon.svelte" + import { WithSearchState } from "../../Models/ThemeViewState/WithSearchState" export let entry: GeocodeResult - export let state: SpecialVisualizationState + export let state: WithSearchState let layer: LayerConfig let tags: UIEventSource> @@ -36,34 +33,15 @@ let inView = state.mapProperties.bounds.mapD((bounds) => bounds.contains([entry.lon, entry.lat])) function select() { - if (entry.boundingbox) { - const [lat0, lat1, lon0, lon1] = entry.boundingbox - state.mapProperties.bounds.set( - new BBox([ - [lon0, lat0], - [lon1, lat1], - ]).pad(0.01) - ) - } else { - state.mapProperties.flyTo( - entry.lon, - entry.lat, - GeocodingUtils.categoryToZoomLevel[entry.category] ?? 17 - ) - } - if (entry.feature?.properties?.id) { - state.selectedElement.set(entry.feature) - } - state.userRelatedState.recentlyVisitedSearch.add(entry) - state.searchState.closeIfFullscreen() + state.searchState.applyGeocodeResult(entry) }