forked from MapComplete/MapComplete
		
	Feature(geocoding): pressing enter will now zoom to the first search result; refactor away type synonym
This commit is contained in:
		
							parent
							
								
									9e8aaab086
								
							
						
					
					
						commit
						686ad70511
					
				
					 8 changed files with 64 additions and 49 deletions
				
			
		|  | @ -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<SearchResult[]> { | ||||
|     async search(query: string, options?: GeocodingOptions): Promise<GeocodeResult[]> { | ||||
|         const results = await Promise.all(this._providers.map((pr) => pr.search(query, options))) | ||||
|         return CombinedSearcher.merge(results) | ||||
|     } | ||||
| 
 | ||||
|     suggest(query: string, options?: GeocodingOptions): Store<SearchResult[]> { | ||||
|     suggest(query: string, options?: GeocodingOptions): Store<GeocodeResult[]> { | ||||
|         return Stores.concat( | ||||
|             this._providersWithSuggest.map((pr) => pr.suggest(query, options)) | ||||
|         ).map((gcrss) => CombinedSearcher.merge(gcrss)) | ||||
|  |  | |||
|  | @ -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<Feature<Geometry, Record<string, string>>[]> | ||||
| 
 | ||||
|     constructor(provider: Store<SearchResult[]>) { | ||||
|     constructor(provider: Store<GeocodeResult[]>) { | ||||
|         this.features = provider.map((geocoded) => { | ||||
|             if (geocoded === undefined) { | ||||
|                 return [] | ||||
|  |  | |||
|  | @ -42,7 +42,6 @@ export type GeocodeResult = { | |||
|     payload?: object | ||||
|     source?: string | ||||
| } | ||||
| export type SearchResult = GeocodeResult | ||||
| 
 | ||||
| export interface GeocodingOptions { | ||||
|     bbox?: BBox | ||||
|  |  | |||
|  | @ -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<SearchResult[]> { | ||||
|     async search(query: string, options?: GeocodingOptions): Promise<GeocodeResult[]> { | ||||
|         return this.searchEntries(query, options, false).data | ||||
|     } | ||||
| 
 | ||||
|  | @ -92,7 +92,7 @@ export default class LocalElementSearch implements GeocodingProvider { | |||
|         query: string, | ||||
|         options?: GeocodingOptions, | ||||
|         matchStart?: boolean | ||||
|     ): Store<SearchResult[]> { | ||||
|     ): Store<GeocodeResult[]> { | ||||
|         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 <SearchResult>{ | ||||
|                 return <GeocodeResult>{ | ||||
|                     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<SearchResult[]> { | ||||
|     suggest(query: string, options?: GeocodingOptions): Store<GeocodeResult[]> { | ||||
|         return this.searchEntries(query, options, true) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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<SearchResult[]> { | ||||
|     public search(query: string, options?: GeocodingOptions): Promise<GeocodeResult[]> { | ||||
|         const b = options?.bbox ?? BBox.global | ||||
|         const url = `${this._host}search?format=json&limit=${ | ||||
|             this.limit | ||||
|  |  | |||
|  | @ -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<Translation> = new UIEventSource<Translation>(undefined) | ||||
|     public readonly searchTerm: UIEventSource<string> = new UIEventSource<string>("") | ||||
|     public readonly searchIsFocused = new UIEventSource(false) | ||||
|     public readonly suggestions: Store<SearchResult[]> | ||||
|     public readonly suggestions: Store<GeocodeResult[]> | ||||
|     public readonly filterSuggestions: Store<FilterSearchResult[]> | ||||
|     public readonly themeSuggestions: Store<MinimalThemeInformation[]> | ||||
|     public readonly layerSuggestions: Store<LayerConfig[]> | ||||
|  | @ -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() | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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<Record<string, string>> | ||||
|  | @ -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) | ||||
|   } | ||||
| </script> | ||||
| 
 | ||||
| <button class="unstyled link-no-underline searchresult w-full" on:click={() => select()}> | ||||
|   <div class="flex w-full items-center gap-y-2 p-2"> | ||||
|     {#if layer} | ||||
|       <div class="h-6"> | ||||
|         <DefaultIcon {layer} properties={entry.feature.properties} clss="w-6 h-6" /> | ||||
|       <div class="h-6 w-6"> | ||||
|         <DefaultIcon {layer} properties={entry.feature.properties} /> | ||||
|       </div> | ||||
|     {:else if entry.category} | ||||
|       <Icon | ||||
|  |  | |||
|  | @ -367,6 +367,7 @@ | |||
|         <div class="flex flex-grow items-center justify-end"> | ||||
|           <div class="w-full sm:w-64"> | ||||
|             <Searchbar | ||||
|               on:search={() => state.searchState.moveToBestMatch()} | ||||
|               value={state.searchState.searchTerm} | ||||
|               isFocused={state.searchState.searchIsFocused} | ||||
|             /> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue