Add layers to search menu

This commit is contained in:
Pieter Vander Vennet 2024-09-11 01:46:55 +02:00
parent e6dab1a83f
commit c591770eab
33 changed files with 332 additions and 195 deletions

View file

@ -1,25 +1,26 @@
import GeocodingProvider, {
FilterPayload,
FilterPayload, FilterResult,
GeocodeResult,
GeocodingUtils,
type SearchResult
} from "../Geocoding/GeocodingProvider"
GeocodingUtils, LayerResult,
type SearchResult,
} from "../Search/GeocodingProvider"
import { ImmutableStore, Store, Stores, UIEventSource } from "../UIEventSource"
import CombinedSearcher from "../Geocoding/CombinedSearcher"
import FilterSearch from "../Geocoding/FilterSearch"
import LocalElementSearch from "../Geocoding/LocalElementSearch"
import CoordinateSearch from "../Geocoding/CoordinateSearch"
import ThemeSearch from "../Geocoding/ThemeSearch"
import OpenStreetMapIdSearch from "../Geocoding/OpenStreetMapIdSearch"
import PhotonSearch from "../Geocoding/PhotonSearch"
import CombinedSearcher from "../Search/CombinedSearcher"
import FilterSearch from "../Search/FilterSearch"
import LocalElementSearch from "../Search/LocalElementSearch"
import CoordinateSearch from "../Search/CoordinateSearch"
import ThemeSearch from "../Search/ThemeSearch"
import OpenStreetMapIdSearch from "../Search/OpenStreetMapIdSearch"
import PhotonSearch from "../Search/PhotonSearch"
import ThemeViewState from "../../Models/ThemeViewState"
import Translations from "../../UI/i18n/Translations"
import type { MinimalLayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
import MoreScreen from "../../UI/BigComponents/MoreScreen"
import { BBox } from "../BBox"
import { Translation } from "../../UI/i18n/Translation"
import GeocodingFeatureSource from "../Geocoding/GeocodingFeatureSource"
import GeocodingFeatureSource from "../Search/GeocodingFeatureSource"
import ShowDataLayer from "../../UI/Map/ShowDataLayer"
import LayerSearch from "../Search/LayerSearch"
export default class SearchState {
@ -28,8 +29,9 @@ export default class SearchState {
public readonly searchTerm: UIEventSource<string> = new UIEventSource<string>("")
public readonly searchIsFocused = new UIEventSource(false)
public readonly suggestions: Store<SearchResult[]>
public readonly filterSuggestions: Store<FilterPayload[]>
public readonly filterSuggestions: Store<FilterResult[]>
public readonly themeSuggestions: Store<MinimalLayoutInformation[]>
public readonly layerSuggestions: Store<LayerResult[]>
public readonly locationSearchers: ReadonlyArray<GeocodingProvider<GeocodeResult>>
private readonly state: ThemeViewState
@ -43,7 +45,7 @@ export default class SearchState {
// new LocalElementSearch(state, 5),
new CoordinateSearch(),
new OpenStreetMapIdSearch(state),
new PhotonSearch() // new NominatimGeocoding(),
new PhotonSearch(), // new NominatimGeocoding(),
]
const bounds = state.mapProperties.bounds
@ -53,33 +55,36 @@ export default class SearchState {
}
return this.locationSearchers.map(ls => ls.suggest(search, { bbox: bounds.data }))
}, [bounds]
}, [bounds],
)
this.suggestionsSearchRunning = suggestionsList.bind(suggestions => {
if(suggestions === undefined){
this.suggestionsSearchRunning = suggestionsList.bind(suggestions => {
if (suggestions === undefined) {
return new ImmutableStore(true)
}
return Stores.concat(suggestions).map(suggestions => suggestions.some(list => list === undefined))
})
this.suggestions = suggestionsList.bindD(suggestions =>
Stores.concat(suggestions).map(suggestions => CombinedSearcher.merge(suggestions))
Stores.concat(suggestions).map(suggestions => CombinedSearcher.merge(suggestions)),
)
const themeSearch = new ThemeSearch(state, 3)
this.themeSuggestions = this.searchTerm.mapD(query => themeSearch.searchDirect(query, 3))
const layerSearch = new LayerSearch(state, 5)
this.layerSuggestions = this.searchTerm.mapD(query => layerSearch.searchDirect(query, 5))
const filterSearch = new FilterSearch(state)
this.filterSuggestions = this.searchTerm.stabilized(50).mapD(query => filterSearch.searchDirectly(query)
).mapD(filterResult => {
const active = state.layerState.activeFilters.data
return filterResult.filter(({ filter, index, layer }) => {
const foundMatch = active.some(active =>
active.filter.id === filter.id && layer.id === active.layer.id && active.control.data === index)
this.filterSuggestions = this.searchTerm.stabilized(50)
.mapD(query => filterSearch.searchDirectly(query))
.mapD(filterResult => {
const active = state.layerState.activeFilters.data
return filterResult.filter(({ payload: { filter, index, layer } }) => {
const foundMatch = active.some(active =>
active.filter.id === filter.id && layer.id === active.layer.id && active.control.data === index)
return !foundMatch
})
}, [state.layerState.activeFilters])
return !foundMatch
})
}, [state.layerState.activeFilters])
const geocodedFeatures = new GeocodingFeatureSource(this.suggestions.stabilized(250))
state.featureProperties.trackFeatureSource(geocodedFeatures)
@ -88,8 +93,8 @@ export default class SearchState {
{
layer: GeocodingUtils.searchLayer,
features: geocodedFeatures,
selectedElement: state.selectedElement
}
selectedElement: state.selectedElement,
},
)
this.showSearchDrawer = new UIEventSource(false)
@ -103,21 +108,31 @@ export default class SearchState {
}
public async apply(payload: FilterPayload) {
const state = this.state
const { layer, filter, index } = payload
const flayer = state.layerState.filteredLayers.get(layer.id)
const filtercontrol = flayer.appliedFilters.get(filter.id)
for (const [name, otherLayer] of state.layerState.filteredLayers) {
if (name === layer.id) {
otherLayer.isDisplayed.setData(true)
continue
}
otherLayer.isDisplayed.setData(false)
public async apply(result: FilterResult | LayerResult) {
if (result.category === "filter") {
return this.applyFilter(result.payload)
}
if (result.category === "layer") {
return this.applyLayer(result)
}
}
private async applyLayer(layer: LayerResult) {
for (const [name, otherLayer] of this.state.layerState.filteredLayers) {
otherLayer.isDisplayed.setData(name === layer.osm_id)
}
}
private async applyFilter(payload: FilterPayload) {
const state = this.state
const { layer, filter, index } = payload
for (const [name, otherLayer] of state.layerState.filteredLayers) {
otherLayer.isDisplayed.setData(name === layer.id)
}
const flayer = state.layerState.filteredLayers.get(layer.id)
flayer.isDisplayed.set(true)
const filtercontrol = flayer.appliedFilters.get(filter.id)
console.log("Could not apply", layer.id, ".", filter.id, index)
if (filtercontrol.data === index) {
filtercontrol.setData(undefined)
@ -126,76 +141,4 @@ export default class SearchState {
}
}
/**
* Tries to search and goto a given location
* Returns 'false' if search failed
*/
public async performSearch(): Promise<boolean> {
const query = this.searchTerm.data?.trim() ?? ""
if (query === "") {
return
}
const geolocationState = this.state.geolocation.geolocationState
const bounds = this.state.mapProperties.bounds
const bbox = this.state.mapProperties.bounds.data
try {
this.isSearching.set(true)
geolocationState?.allowMoving.setData(true)
geolocationState?.requestMoment.setData(undefined) // If the GPS is still searching for a fix, we say that we don't want tozoom to it anymore
let poi: SearchResult
if(this.suggestions.data){
poi = this.suggestions.data[0]
}else{
const results = GeocodingUtils.mergeSimilarResults([].concat(...await Promise.all(this.locationSearchers.map(ls => ls.search(query, { bbox: bounds.data })))))
poi = results[0]
}
if (poi.category === "theme") {
const theme = <MinimalLayoutInformation>poi.payload
const url = MoreScreen.createUrlFor(theme)
window.location = <any>url
return true
}
if (poi.category === "filter") {
await this.apply(poi.payload)
return true
}
if (poi.boundingbox) {
const [lat0, lat1, lon0, lon1] = poi.boundingbox
// Will trigger a 'fly to'
bounds.set(
new BBox([
[lon0, lat0],
[lon1, lat1]
]).pad(0.01)
)
} else if (poi.lon && poi.lat) {
this.state.mapProperties.flyTo(poi.lon, poi.lat, GeocodingUtils.categoryToZoomLevel[poi.category] ?? 16)
}
const perLayer = this.state.perLayer
if (perLayer !== undefined) {
const id = poi.osm_type + "/" + poi.osm_id
const layers = Array.from(perLayer?.values() ?? [])
for (const layer of layers) {
const found = layer.features.data.find((f) => f.properties.id === id)
if (found === undefined) {
continue
}
this.state.selectedElement?.setData(found)
}
}
return true
} catch (e) {
console.error(e)
this.feedback.set(Translations.t.general.search.error)
return false
} finally {
this.isSearching.set(false)
}
}
}