Add search previews on the map

This commit is contained in:
Pieter Vander Vennet 2024-08-23 02:16:24 +02:00
parent 1c46a65c84
commit 4f52483a98
19 changed files with 315 additions and 87 deletions

View file

@ -356,9 +356,32 @@
} }
} }
} }
},
{
"#": "ignore-image-in-then",
"if": "osm_id~*",
"then": {
"special": {
"type": "link",
"text": "<img alt='on osm' textmode='🗺️' src='./assets/svg/osm-logo-us.svg'/>",
"href": "https://www.openstreetmap.org/{osm_id}",
"arialabel": {
"en": "Open on openstreetmap.org",
"nl": "Bekijk op openstreetmap.org",
"de": "Auf openstreetmap.org öffnen",
"pl": "Otwórz na openstreetmap.org",
"da": "Åbn på openstreetmap.org"
}
}
}
} }
], ],
"condition": "id~(node|way|relation)/[0-9]*" "condition": {
"or": [
"id~(node|way|relation)/[0-9]*",
"osm_id~*"
]
}
}, },
{ {
"id": "rating", "id": "rating",

View file

@ -0,0 +1,66 @@
{
"id": "search",
"description": {
"en": "Priviliged layer showing the search results"
},
"source": "special",
"title": "{display_name}",
"tagRenderings": [
{
"id": "intro",
"render": {
"en": "Search result"
}
},
{
"id": "osm",
"render": {
"*": "<a href='https://openstreetmap.org/{osm_type}/{osm_id}'>On OpenStreetMap</a>"
}
},
"all_tags"
],
"pointRendering": [
{
"location": [
"point",
"centroid"
],
"marker": [
{
"icon": "circle",
"color": "white"
},
{
"icon": {
"render": "globe_alt",
"mappings": [
{
"if": "category~city|locality|county",
"then": "building_office_2"
},
{
"if": "category=train_station",
"then": "train"
},
{
"if": "category=airport",
"then": "airport"
},
{
"if": "category=house",
"then": "house"
},
{
"if": "category=shop",
"then": "building_storefront"
}
]
}
}
],
"label": "{display_name}",
"labelCssClasses": "bg-white rounded p-2 no-wrap"
}
]
}

View file

@ -89,7 +89,7 @@
"generate:contributor-list": "vite-node scripts/generateContributors.ts", "generate:contributor-list": "vite-node scripts/generateContributors.ts",
"generate:service-worker": "tsc src/service-worker.ts --outFile public/service-worker.js && git_hash=$(git rev-parse HEAD) && sed -i.bak \"s/GITHUB-COMMIT/$git_hash/\" public/service-worker.js && rm public/service-worker.js.bak", "generate:service-worker": "tsc src/service-worker.ts --outFile public/service-worker.js && git_hash=$(git rev-parse HEAD) && sed -i.bak \"s/GITHUB-COMMIT/$git_hash/\" public/service-worker.js && rm public/service-worker.js.bak",
"reset:layeroverview": "npm run prep:layeroverview && npm run generate:layeroverview && npm run refresh:layeroverview", "reset:layeroverview": "npm run prep:layeroverview && npm run generate:layeroverview && npm run refresh:layeroverview",
"prep:layeroverview": "mkdir -p ./src/assets/generated/layers; echo {\\\"themes\\\":[]} > ./src/assets/generated/known_themes.json && echo {\\\"layers\\\": []} > ./src/assets/generated/known_layers.json && rm -f ./src/assets/generated/layers/*.json && rm -f ./src/assets/generated/themes/*.json && cp ./assets/layers/usersettings/usersettings.json ./src/assets/generated/layers/usersettings.json && echo '{}' > ./src/assets/generated/layers/favourite.json && echo '{}' > ./src/assets/generated/layers/summary.json && echo '{}' > ./src/assets/generated/layers/last_click.json && echo '[]' > ./src/assets/generated/theme_overview.json", "prep:layeroverview": "mkdir -p ./src/assets/generated/layers; echo {\\\"themes\\\":[]} > ./src/assets/generated/known_themes.json && echo {\\\"layers\\\": []} > ./src/assets/generated/known_layers.json && rm -f ./src/assets/generated/layers/*.json && rm -f ./src/assets/generated/themes/*.json && cp ./assets/layers/usersettings/usersettings.json ./src/assets/generated/layers/usersettings.json && echo '{}' > ./src/assets/generated/layers/favourite.json && echo '{}' > ./src/assets/generated/layers/summary.json && echo '{}' > ./src/assets/generated/layers/last_click.json && echo '[]' > ./src/assets/generated/theme_overview.json && echo '{}' > ./src/assets/generated/layers/search.json",
"generate": "npm run generate:licenses && npm run generate:images && npm run generate:charging-stations && npm run generate:translations && npm run refresh:layeroverview && npm run generate:service-worker", "generate": "npm run generate:licenses && npm run generate:images && npm run generate:charging-stations && npm run generate:translations && npm run refresh:layeroverview && npm run generate:service-worker",
"generate:charging-stations": "cd ./assets/layers/charging_station && vite-node csvToJson.ts && cd -", "generate:charging-stations": "cd ./assets/layers/charging_station && vite-node csvToJson.ts && cd -",
"clean:tests": "find . -type f -name \"*.doctest.ts\" | xargs -r rm", "clean:tests": "find . -type f -name \"*.doctest.ts\" | xargs -r rm",

View file

@ -21,7 +21,7 @@ export default class TitleHandler {
if (selected === undefined) { if (selected === undefined) {
return defaultTitle return defaultTitle
} }
const layer = state.layout.getMatchingLayer(selected.properties) const layer = state.getMatchingLayer(selected.properties)
if (layer === undefined) { if (layer === undefined) {
return defaultTitle return defaultTitle
} }

View file

@ -0,0 +1,41 @@
import { GeoCodeResult } from "./GeocodingProvider"
import { Store } from "../UIEventSource"
import { FeatureSource } from "../FeatureSource/FeatureSource"
import { Feature, Geometry } from "geojson"
export default class GeocodingFeatureSource implements FeatureSource {
public features: Store<Feature<Geometry, Record<string, string>>[]>
constructor(provider: Store<GeoCodeResult[]>) {
this.features = provider.mapD(geocoded => {
const features: Feature[] = []
for (const gc of geocoded) {
if (gc.lat === undefined || gc.lon === undefined) {
continue
}
features.push({
type: "Feature",
properties: {
id: "search_result_" + gc.osm_type + "/" + gc.osm_id,
category: gc.category,
description: gc.description,
display_name: gc.display_name,
osm_id: gc.osm_type + "/" + gc.osm_id,
osm_key: gc.feature?.properties?.osm_key,
osm_value: gc.feature?.properties?.osm_value
},
geometry: {
type: "Point",
coordinates: [gc.lon, gc.lat]
}
})
}
return features
})
}
}

View file

@ -2,8 +2,10 @@ import { BBox } from "../BBox"
import { Feature, Geometry } from "geojson" import { Feature, Geometry } from "geojson"
import { DefaultPinIcon } from "../../Models/Constants" import { DefaultPinIcon } from "../../Models/Constants"
import { Store } from "../UIEventSource" import { Store } from "../UIEventSource"
import * as search from "../../assets/generated/layers/search.json"
export type GeocodingCategory = "coordinate" | "city" | "house" | "street" | "locality" | "country" | "train_station" | "county" | "airport" import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
export type GeocodingCategory = "coordinate" | "city" | "house" | "street" | "locality" | "country" | "train_station" | "county" | "airport" | "shop"
export type GeoCodeResult = { export type GeoCodeResult = {
/** /**
@ -66,6 +68,8 @@ export interface ReverseGeocodingProvider {
export class GeocodingUtils { export class GeocodingUtils {
public static searchLayer= new LayerConfig(<LayerConfigJson> search, "search")
public static categoryToZoomLevel: Record<GeocodingCategory, number> = { public static categoryToZoomLevel: Record<GeocodingCategory, number> = {
city: 12, city: 12,
county: 10, county: 10,
@ -75,7 +79,8 @@ export class GeocodingUtils {
locality: 14, locality: 14,
street: 15, street: 15,
train_station: 14, train_station: 14,
airport: 13 airport: 13,
shop:16
} }
@ -89,7 +94,8 @@ export class GeocodingUtils {
street: "globe_alt", street: "globe_alt",
train_station: "train", train_station: "train",
county: "building_office_2", county: "building_office_2",
airport: "airport" airport: "airport",
shop: "building_storefront"
} }

View file

@ -95,6 +95,9 @@ export default class PhotonSearch implements GeocodingProvider, ReverseGeocoding
private getCategory(entry: Feature) { private getCategory(entry: Feature) {
const p = entry.properties const p = entry.properties
if(p.osm_key === "shop"){
return "shop"
}
if (p.osm_value === "train_station" || p.osm_key === "railway") { if (p.osm_value === "train_station" || p.osm_key === "railway") {
return "train_station" return "train_station"
} }

View file

@ -12,10 +12,41 @@ export class RecentSearch {
public readonly seenThisSession: Store<GeoCodeResult[]> public readonly seenThisSession: Store<GeoCodeResult[]>
constructor(state: { layout: LayoutConfig, osmConnection: OsmConnection, selectedElement: Store<Feature> }) { constructor(state: { layout: LayoutConfig, osmConnection: OsmConnection, selectedElement: Store<Feature> }) {
// const prefs = state.osmConnection.preferencesHandler.GetLongPreference("previous-searches") const prefs = state.osmConnection.preferencesHandler.GetLongPreference("previous-searches")
prefs.addCallbackAndRunD(prev => console.trace("Previous searches are:", prev))
prefs.set(null)
this._seenThisSession = new UIEventSource<GeoCodeResult[]>([])//UIEventSource.asObject<GeoCodeResult[]>(prefs, []) this._seenThisSession = new UIEventSource<GeoCodeResult[]>([])//UIEventSource.asObject<GeoCodeResult[]>(prefs, [])
this.seenThisSession = this._seenThisSession this.seenThisSession = this._seenThisSession
prefs.addCallbackAndRunD(prefs => {
if(prefs === ""){
return
}
const simpleArr = <GeoCodeResult[]> JSON.parse(prefs)
if(simpleArr.length > 0){
this._seenThisSession.set(simpleArr)
return true
}
})
this.seenThisSession.stabilized(2500).addCallbackAndRunD(seen => {
const results= []
for (let i = 0; i < Math.min(3, seen.length); i++) {
const gc = seen[i]
const simple = {
category: gc.category,
description: gc.description,
display_name: gc.display_name,
lat: gc.lat, lon: gc.lon,
osm_id: gc.osm_id,
osm_type: gc.osm_type
}
results.push(simple)
}
console.log("Setting", results)
prefs.setData(JSON.stringify(results))
})
state.selectedElement.addCallbackAndRunD(selected => { state.selectedElement.addCallbackAndRunD(selected => {
@ -23,6 +54,10 @@ export class RecentSearch {
if(!osm_id){ if(!osm_id){
return return
} }
console.log("Selected element is", selected)
if(["node","way","relation"].indexOf(osm_type) < 0){
return
}
const [lon, lat] = GeoOperations.centerpointCoordinates(selected) const [lon, lat] = GeoOperations.centerpointCoordinates(selected)
const entry = <GeoCodeResult>{ const entry = <GeoCodeResult>{
feature: selected, feature: selected,
@ -46,6 +81,7 @@ export class RecentSearch {
seenIds.add(id) seenIds.add(id)
} }
} }
console.log(">>>",arr)
this._seenThisSession.set(arr) this._seenThisSession.set(arr)
} }
} }

View file

@ -2,9 +2,7 @@ import { UIEventSource } from "../UIEventSource"
import UserDetails, { OsmConnection } from "./OsmConnection" import UserDetails, { OsmConnection } from "./OsmConnection"
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import { LocalStorageSource } from "../Web/LocalStorageSource" import { LocalStorageSource } from "../Web/LocalStorageSource"
// @ts-ignore import OSMAuthInstance = OSMAuth.osmAuth
import { osmAuth } from "osm-auth"
import OSMAuthInstance = OSMAuth.OSMAuthInstance
export class OsmPreferences { export class OsmPreferences {
/** /**
@ -53,7 +51,7 @@ export class OsmPreferences {
const subOptions = { prefix: "" } const subOptions = { prefix: "" }
// Gives the number of combined preferences // Gives the number of combined preferences
const length = this.GetPreference(allStartWith + "-length", "", subOptions) const length = this.GetPreference(allStartWith + "-length", "", subOptions)
const preferences = this.preferences
if ((allStartWith + "-length").length > 255) { if ((allStartWith + "-length").length > 255) {
throw ( throw (
"This preference key is too long, it has " + "This preference key is too long, it has " +
@ -64,7 +62,6 @@ export class OsmPreferences {
) )
} }
const self = this
source.addCallback((str) => { source.addCallback((str) => {
if (str === undefined || str === "") { if (str === undefined || str === "") {
return return
@ -74,9 +71,9 @@ export class OsmPreferences {
const count = parseInt(length.data) const count = parseInt(length.data)
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
// Delete all the preferences // Delete all the preferences
self.GetPreference(allStartWith + "-" + i, "", subOptions).setData("") this.GetPreference(allStartWith + "-" + i, "", subOptions).setData("")
} }
self.GetPreference(allStartWith + "-length", "", subOptions).setData("") this.GetPreference(allStartWith + "-length", "", subOptions).setData("")
return return
} }
@ -99,7 +96,7 @@ export class OsmPreferences {
if (i > 100) { if (i > 100) {
throw "This long preference is getting very long... " throw "This long preference is getting very long... "
} }
self.GetPreference(allStartWith + "-" + i, "", subOptions).setData( this.GetPreference(allStartWith + "-" + i, "", subOptions).setData(
str.substr(0, 255) str.substr(0, 255)
) )
str = str.substr(255) str = str.substr(255)
@ -108,8 +105,9 @@ export class OsmPreferences {
length.setData("" + i) // We use I, the number of preference fields used length.setData("" + i) // We use I, the number of preference fields used
}) })
function updateData(l: number) { function updateData(l: number) {
if (Object.keys(self.preferences.data).length === 0) { if (Object.keys(preferences.data).length === 0) {
// The preferences are still empty - they are not yet updated, so we delay updating for now // The preferences are still empty - they are not yet updated, so we delay updating for now
return return
} }
@ -120,15 +118,21 @@ export class OsmPreferences {
let str = "" let str = ""
for (let i = 0; i < prefsCount; i++) { for (let i = 0; i < prefsCount; i++) {
const key = allStartWith + "-" + i const key = allStartWith + "-" + i
if (self.preferences.data[key] === undefined) { if (preferences.data[key] === undefined) {
console.warn( console.warn(
"Detected a broken combined preference:", "Detected a broken combined preference:",
key, key,
"is undefined", "is undefined",
self.preferences preferences
) )
continue
} }
str += self.preferences.data[key] ?? "" const v = preferences.data[key]
if(v === "undefined"){
delete preferences.data[key]
continue
}
str += preferences.data[key] ?? ""
} }
source.setData(str) source.setData(str)
@ -137,7 +141,7 @@ export class OsmPreferences {
length.addCallback((l) => { length.addCallback((l) => {
updateData(Number(l)) updateData(Number(l))
}) })
this.preferences.addCallbackAndRun((_) => { this.preferences.addCallbackAndRun(() => {
updateData(Number(length.data)) updateData(Number(length.data))
}) })
@ -159,7 +163,7 @@ export class OsmPreferences {
) )
} }
key = prefix + key key = prefix + key
key = key.replace(/[:\\\/"' {}.%]/g, "") key = key.replace(/[:/"' {}.%\\]/g, "")
if (key.length >= 255) { if (key.length >= 255) {
throw "Preferences: key length to big" throw "Preferences: key length to big"
} }
@ -193,7 +197,6 @@ export class OsmPreferences {
public ClearPreferences() { public ClearPreferences() {
let isRunning = false let isRunning = false
const self = this
this.preferences.addCallback((prefs) => { this.preferences.addCallback((prefs) => {
console.log("Cleaning preferences...") console.log("Cleaning preferences...")
if (Object.keys(prefs).length == 0) { if (Object.keys(prefs).length == 0) {
@ -208,7 +211,7 @@ export class OsmPreferences {
const matches = prefixes.some((prefix) => key.startsWith(prefix)) const matches = prefixes.some((prefix) => key.startsWith(prefix))
if (matches) { if (matches) {
console.log("Clearing ", key) console.log("Clearing ", key)
self.GetPreference(key, "", { prefix: "" }).setData("") this.GetPreference(key, "", { prefix: "" }).setData("")
} }
} }
isRunning = false isRunning = false
@ -227,7 +230,6 @@ export class OsmPreferences {
} }
private UpdatePreferences(forceUpdate?: boolean) { private UpdatePreferences(forceUpdate?: boolean) {
const self = this
if (this._fakeUser) { if (this._fakeUser) {
return return
} }
@ -236,7 +238,7 @@ export class OsmPreferences {
method: "GET", method: "GET",
path: "/api/0.6/user/preferences", path: "/api/0.6/user/preferences",
}, },
function (error, value: XMLDocument) { (error, value: XMLDocument) => {
if (error) { if (error) {
console.log("Could not load preferences", error) console.log("Could not load preferences", error)
return return
@ -246,34 +248,33 @@ export class OsmPreferences {
for (let i = 0; i < prefs.length; i++) { for (let i = 0; i < prefs.length; i++) {
const pref = prefs[i] const pref = prefs[i]
const k = pref.getAttribute("k") const k = pref.getAttribute("k")
const v = pref.getAttribute("v") this.preferences.data[k] = pref.getAttribute("v")
self.preferences.data[k] = v
seenKeys.add(k) seenKeys.add(k)
} }
if (forceUpdate) { if (forceUpdate) {
for (let key in self.preferences.data) { for (const key in this.preferences.data) {
if (seenKeys.has(key)) { if (seenKeys.has(key)) {
continue continue
} }
console.log("Deleting key", key, "as we didn't find it upstream") console.log("Deleting key", key, "as we didn't find it upstream")
delete self.preferences.data[key] delete this.preferences.data[key]
} }
} }
// We merge all the preferences: new keys are uploaded // We merge all the preferences: new keys are uploaded
// For differing values, the server overrides local changes // For differing values, the server overrides local changes
self.preferenceSources.forEach((preference, key) => { this.preferenceSources.forEach((preference, key) => {
const osmValue = self.preferences.data[key] const osmValue = this.preferences.data[key]
if (osmValue === undefined && preference.data !== undefined) { if (osmValue === undefined && preference.data !== undefined) {
// OSM doesn't know this value yet // OSM doesn't know this value yet
self.UploadPreference(key, preference.data) this.UploadPreference(key, preference.data)
} else { } else {
// OSM does have a value - set it // OSM does have a value - set it
preference.setData(osmValue) preference.setData(osmValue)
} }
}) })
self.preferences.ping() this.preferences.ping()
} }
) )
} }
@ -287,7 +288,6 @@ export class OsmPreferences {
if (this.preferences.data[k] === v) { if (this.preferences.data[k] === v) {
return return
} }
const self = this
console.debug("Updating preference", k, " to ", Utils.EllipsesAfter(v, 15)) console.debug("Updating preference", k, " to ", Utils.EllipsesAfter(v, 15))
if (this._fakeUser) { if (this._fakeUser) {
return return
@ -299,13 +299,13 @@ export class OsmPreferences {
path: "/api/0.6/user/preferences/" + encodeURIComponent(k), path: "/api/0.6/user/preferences/" + encodeURIComponent(k),
headers: { "Content-Type": "text/plain" }, headers: { "Content-Type": "text/plain" },
}, },
function (error) { (error) => {
if (error) { if (error) {
console.warn("Could not remove preference", error) console.warn("Could not remove preference", error)
return return
} }
delete self.preferences.data[k] delete this.preferences.data[k]
self.preferences.ping() this.preferences.ping()
console.debug("Preference ", k, "removed!") console.debug("Preference ", k, "removed!")
} }
) )
@ -319,13 +319,13 @@ export class OsmPreferences {
headers: { "Content-Type": "text/plain" }, headers: { "Content-Type": "text/plain" },
content: v, content: v,
}, },
function (error) { (error)=> {
if (error) { if (error) {
console.warn(`Could not set preference "${k}"'`, error) console.warn(`Could not set preference "${k}"'`, error)
return return
} }
self.preferences.data[k] = v this.preferences.data[k] = v
self.preferences.ping() this.preferences.ping()
console.debug(`Preference ${k} written!`) console.debug(`Preference ${k} written!`)
} }
) )

View file

@ -351,8 +351,10 @@ export default class UserRelatedState {
const key = k.substring(0, k.length - "length".length) const key = k.substring(0, k.length - "length".length)
let combined = "" let combined = ""
for (let i = 0; i < l; i++) { for (let i = 0; i < l; i++) {
combined += newPrefs[key + i] console.log("Building preference:",key,i,">>>", newPrefs[key + i], "<<<", newPrefs, )
combined += (newPrefs[key + i])
} }
console.log("Combined",key,">>>",combined)
amendedPrefs.data[key.substring(0, key.length - "-combined-".length)] = combined amendedPrefs.data[key.substring(0, key.length - "-combined-".length)] = combined
} else { } else {
amendedPrefs.data[k] = newPrefs[k] amendedPrefs.data[k] = newPrefs[k]
@ -456,11 +458,15 @@ export default class UserRelatedState {
amendedPrefs.addCallbackD((tags) => { amendedPrefs.addCallbackD((tags) => {
for (const key in tags) { for (const key in tags) {
if (key.startsWith("_") || key === "mapcomplete-language") { if (key.startsWith("_") || key === "mapcomplete-language") {
// Language is managed seperately // Language is managed separately
continue continue
} }
if (tags[key + "-combined-0"]) { if (tags[key + "-combined-0"]) {
// A combined value exists // A combined value exists
if(tags[key].startsWith("undefined")){
// Sometimes, a long string of 'undefined' will show up, we ignore them
continue
}
this.osmConnection.GetLongPreference(key, "").setData(tags[key]) this.osmConnection.GetLongPreference(key, "").setData(tags[key])
} else { } else {
this.osmConnection this.osmConnection

View file

@ -91,6 +91,20 @@ export class Stores {
}) })
return stable return stable
} }
/**
*
* Constructs a new store, but tries to keep the value 'defined'
* If a defined value was in the stream once, a defined value will be returned
* @param store
*/
static holdDefined<T>(store: Store<T | undefined>): Store<T | undefined> {
const newStore = new UIEventSource(store.data)
store.addCallbackD(t => {
newStore.setData(t)
})
return newStore
}
} }
export abstract class Store<T> implements Readable<T> { export abstract class Store<T> implements Readable<T> {

View file

@ -26,6 +26,7 @@ export default class Constants {
"last_click", "last_click",
"favourite", "favourite",
"summary", "summary",
"search"
] as const ] as const
/** /**
* Special layers which are not included in a theme by default * Special layers which are not included in a theme by default
@ -38,7 +39,7 @@ export default class Constants {
"import_candidate", "import_candidate",
"usersettings", "usersettings",
"icons", "icons",
"filters", "filters"
] as const ] as const
/** /**
* Layer IDs of layers which have special properties through built-in hooks * Layer IDs of layers which have special properties through built-in hooks
@ -126,6 +127,7 @@ export default class Constants {
"brick_wall_round", "brick_wall_round",
"brick_wall_square", "brick_wall_square",
"building_office_2", "building_office_2",
"building_storefront",
"bug", "bug",
"checkmark", "checkmark",
"checkmark", "checkmark",

View file

@ -340,7 +340,7 @@ export default class LayoutConfig implements LayoutInformation {
} }
} }
} }
console.log("Fallthrough", this, tags) console.trace("Fallthrough: could not find the appropraite layer for an object with tags", tags, "within layout", this)
return undefined return undefined
} }
@ -354,7 +354,7 @@ export default class LayoutConfig implements LayoutInformation {
...json, ...json,
layers: json.layers.filter((l) => l["id"] !== "favourite"), layers: json.layers.filter((l) => l["id"] !== "favourite"),
} }
const usedImages = json._usedImages const usedImages = jsonNoFavourites._usedImages
usedImages.sort() usedImages.sort()
this.usedImages = Utils.Dedup(usedImages) this.usedImages = Utils.Dedup(usedImages)

View file

@ -68,7 +68,7 @@ import Locale from "../UI/i18n/Locale"
import Hash from "../Logic/Web/Hash" import Hash from "../Logic/Web/Hash"
import { GeoOperations } from "../Logic/GeoOperations" import { GeoOperations } from "../Logic/GeoOperations"
import { CombinedFetcher } from "../Logic/Web/NearbyImagesSearch" import { CombinedFetcher } from "../Logic/Web/NearbyImagesSearch"
import GeocodingProvider from "../Logic/Geocoding/GeocodingProvider" import GeocodingProvider, { GeocodingUtils } from "../Logic/Geocoding/GeocodingProvider"
import CombinedSearcher from "../Logic/Geocoding/CombinedSearcher" import CombinedSearcher from "../Logic/Geocoding/CombinedSearcher"
import CoordinateSearch from "../Logic/Geocoding/CoordinateSearch" import CoordinateSearch from "../Logic/Geocoding/CoordinateSearch"
import LocalElementSearch from "../Logic/Geocoding/LocalElementSearch" import LocalElementSearch from "../Logic/Geocoding/LocalElementSearch"
@ -774,6 +774,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
favourite: this.favourites, favourite: this.favourites,
summary: this.featureSummary, summary: this.featureSummary,
last_click: this.lastClickObject, last_click: this.lastClickObject,
search: undefined
} }
this.closestFeatures.registerSource(specialLayers.favourite, "favourite") this.closestFeatures.registerSource(specialLayers.favourite, "favourite")
@ -910,6 +911,34 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.selectedElement.setData(this.currentView.features?.data?.[0]) this.selectedElement.setData(this.currentView.features?.data?.[0])
} }
/**
* Searches the appropriate layer - will first try if a special layer matches; if not, a normal layer will be used by delegating to the layout
* @param tags
*/
public getMatchingLayer(properties: Record<string, string>){
const id = properties.id
if (id.startsWith("summary_")) {
// We don't select 'summary'-objects
return undefined
}
if (id === "settings") {
return UserRelatedState.usersettingsConfig
}
if (id.startsWith(LastClickFeatureSource.newPointElementId)) {
return this.layout.layers.find((l) => l.id === "last_click")
}
if (id.startsWith("search_result")) {
return GeocodingUtils.searchLayer
}
if (id === "location_track") {
return this.layout.layers.find((l) => l.id === "gps_track")
}
return this.layout.getMatchingLayer(properties)
}
public async reportError(message: string | Error | XMLHttpRequest) { public async reportError(message: string | Error | XMLHttpRequest) {
const isTesting = this.featureSwitchIsTesting.data const isTesting = this.featureSwitchIsTesting.data
console.log( console.log(

View file

@ -7,23 +7,17 @@
import { LastClickFeatureSource } from "../../Logic/FeatureSource/Sources/LastClickFeatureSource" import { LastClickFeatureSource } from "../../Logic/FeatureSource/Sources/LastClickFeatureSource"
import Loading from "./Loading.svelte" import Loading from "./Loading.svelte"
import { onDestroy } from "svelte" import { onDestroy } from "svelte"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { GeocodingUtils } from "../../Logic/Geocoding/GeocodingProvider"
import ThemeViewState from "../../Models/ThemeViewState"
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
export let selected: Feature export let selected: Feature
let tags = state.featureProperties.getStore(selected.properties.id) let tags = state.featureProperties.getStore(selected.properties.id)
export let absolute = true export let absolute = true
function getLayer(properties: Record<string, string>) { function getLayer(properties: Record<string, string>): LayerConfig {
if (properties.id === "settings") { return state.getMatchingLayer(properties)
return UserRelatedState.usersettingsConfig
}
if (properties.id.startsWith(LastClickFeatureSource.newPointElementId)) {
return state.layout.layers.find((l) => l.id === "last_click")
}
if (properties.id === "location_track") {
return state.layout.layers.find((l) => l.id === "gps_track")
}
return state.layout.getMatchingLayer(properties)
} }
let layer = getLayer(selected.properties) let layer = getLayer(selected.properties)

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Store, UIEventSource } from "../../Logic/UIEventSource" import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
import type { Feature } from "geojson" import type { Feature } from "geojson"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import Loading from "../Base/Loading.svelte" import Loading from "../Base/Loading.svelte"
@ -17,10 +17,14 @@
import type GeocodingProvider from "../../Logic/Geocoding/GeocodingProvider" import type GeocodingProvider from "../../Logic/Geocoding/GeocodingProvider"
import SearchResults from "./SearchResults.svelte" import SearchResults from "./SearchResults.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import MoreScreen from "./MoreScreen" import MoreScreen from "./MoreScreen"
import type { MinimalLayoutInformation } from "../../Models/ThemeConfig/LayoutConfig" import type { MinimalLayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
import { focusWithArrows } from "../../Utils/focusWithArrows" import { focusWithArrows } from "../../Utils/focusWithArrows"
import ShowDataLayer from "../Map/ShowDataLayer"
import ThemeViewState from "../../Models/ThemeViewState"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import GeocodingFeatureSource from "../../Logic/Geocoding/GeocodingFeatureSource"
import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson.js"
export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined
export let bounds: UIEventSource<BBox> export let bounds: UIEventSource<BBox>
@ -29,11 +33,11 @@
export let geolocationState: GeoLocationState | undefined = undefined export let geolocationState: GeoLocationState | undefined = undefined
export let clearAfterView: boolean = true export let clearAfterView: boolean = true
export let searcher: GeocodingProvider = new NominatimGeocoding() export let searcher: GeocodingProvider = new NominatimGeocoding()
export let state: SpecialVisualizationState export let state: ThemeViewState
let searchContents: UIEventSource<string> = new UIEventSource<string>("") let searchContents: UIEventSource<string> = new UIEventSource<string>("")
export let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined) export let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined)
onDestroy( onDestroy(
triggerSearch.addCallback((_) => { triggerSearch.addCallback(() => {
performSearch() performSearch()
}) })
) )
@ -139,7 +143,18 @@
if (search.length === 0) { if (search.length === 0) {
return undefined return undefined
} }
return searcher.suggest(search, { bbox: bounds.data }) return Stores.holdDefined(bounds.bindD(bbox => searcher.suggest(search, { bbox, limit: 15 })))
}
)
let geocededFeatures= new GeocodingFeatureSource(suggestions.stabilized(250))
state.featureProperties.trackFeatureSource(geocededFeatures)
new ShowDataLayer(
state.map,
{
layer: GeocodingUtils.searchLayer,
features: geocededFeatures,
selectedElement: state.selectedElement
} }
) )
@ -147,7 +162,7 @@
function checkFocus() { function checkFocus() {
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
if (geosearch.contains(document.activeElement)) { if (geosearch?.contains(document.activeElement)) {
return return
} }
isFocused.setData(false) isFocused.setData(false)
@ -159,7 +174,6 @@
}, true /* use 'capturing' instead of bubbling, needed for focus-events*/) }, true /* use 'capturing' instead of bubbling, needed for focus-events*/)
</script> </script>
<div bind:this={geosearch} use:focusWithArrows={"searchresult"}> <div bind:this={geosearch} use:focusWithArrows={"searchresult"}>

View file

@ -42,6 +42,7 @@
import BuildingOffice2 from "@babeard/svelte-heroicons/outline/BuildingOffice2" import BuildingOffice2 from "@babeard/svelte-heroicons/outline/BuildingOffice2"
import Train from "../../assets/svg/Train.svelte" import Train from "../../assets/svg/Train.svelte"
import Airport from "../../assets/svg/Airport.svelte" import Airport from "../../assets/svg/Airport.svelte"
import BuildingStorefront from "@babeard/svelte-heroicons/outline/BuildingStorefront"
/** /**
* Renders a single icon. * Renders a single icon.
@ -159,6 +160,8 @@
<Train {color} class={clss}/> <Train {color} class={clss}/>
{:else if icon === "airport"} {:else if icon === "airport"}
<Airport {color} class={clss}/> <Airport {color} class={clss}/>
{:else if icon === "building_storefront"}
<BuildingStorefront {color} class={clss}/>
{:else if Utils.isEmoji(icon)} {:else if Utils.isEmoji(icon)}
<span style={`font-size: ${emojiHeight}px; line-height: ${emojiHeight}px`}> <span style={`font-size: ${emojiHeight}px; line-height: ${emojiHeight}px`}>
{icon} {icon}

View file

@ -4,7 +4,7 @@ import LayoutConfig, { MinimalLayoutInformation } from "../Models/ThemeConfig/La
import { import {
FeatureSource, FeatureSource,
IndexedFeatureSource, IndexedFeatureSource,
WritableFeatureSource, WritableFeatureSource
} from "../Logic/FeatureSource/FeatureSource" } from "../Logic/FeatureSource/FeatureSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection" import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { Changes } from "../Logic/Osm/Changes" import { Changes } from "../Logic/Osm/Changes"
@ -97,8 +97,10 @@ export interface SpecialVisualizationState {
readonly geolocation: GeoLocationHandler readonly geolocation: GeoLocationHandler
readonly recentlySearched: RecentSearch readonly recentlySearched: RecentSearch
getMatchingLayer(properties: Record<string, string>);
showCurrentLocationOn(map: Store<MlMap>): ShowDataLayer showCurrentLocationOn(map: Store<MlMap>): ShowDataLayer
reportError(message: string): Promise<void> reportError(message: string): Promise<void>
} }

View file

@ -19,7 +19,7 @@
EyeIcon, EyeIcon,
HeartIcon, HeartIcon,
MenuIcon, MenuIcon,
XCircleIcon, XCircleIcon
} from "@rgossiaux/svelte-heroicons/solid" } from "@rgossiaux/svelte-heroicons/solid"
import Tr from "./Base/Tr.svelte" import Tr from "./Base/Tr.svelte"
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte" import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
@ -72,6 +72,7 @@
import HotkeyTable from "./BigComponents/HotkeyTable.svelte" import HotkeyTable from "./BigComponents/HotkeyTable.svelte"
import SelectedElementPanel from "./Base/SelectedElementPanel.svelte" import SelectedElementPanel from "./Base/SelectedElementPanel.svelte"
import type { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson" import type { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
import { GeocodingUtils } from "../Logic/Geocoding/GeocodingProvider"
export let state: ThemeViewState export let state: ThemeViewState
let layout = state.layout let layout = state.layout
@ -98,22 +99,10 @@
}) })
let selectedLayer: Store<LayerConfig> = state.selectedElement.mapD((element) => { let selectedLayer: Store<LayerConfig> = state.selectedElement.mapD((element) => {
const id = element.properties.id if (element.properties.id.startsWith("current_view")) {
if (id.startsWith("current_view")) {
return currentViewLayer return currentViewLayer
} }
if (id.startsWith("summary_")) { return state.getMatchingLayer(element.properties)
console.log("Not selecting a summary object. The summary object is", element)
return undefined
}
if (id.startsWith(LastClickFeatureSource.newPointElementId)) {
return layout.layers.find((l) => l.id === "last_click")
}
if (id === "location_track") {
return layout.layers.find((l) => l.id === "gps_track")
}
return state.layout.getMatchingLayer(element.properties)
}) })
let currentZoom = state.mapProperties.zoom let currentZoom = state.mapProperties.zoom
let showCrosshair = state.userRelatedState.showCrosshair let showCrosshair = state.userRelatedState.showCrosshair
@ -144,7 +133,7 @@
const bottomRight = mlmap.unproject([rect.right, rect.bottom]) const bottomRight = mlmap.unproject([rect.right, rect.bottom])
const bbox = new BBox([ const bbox = new BBox([
[topLeft.lng, topLeft.lat], [topLeft.lng, topLeft.lat],
[bottomRight.lng, bottomRight.lat], [bottomRight.lng, bottomRight.lat]
]) ])
state.visualFeedbackViewportBounds.setData(bbox) state.visualFeedbackViewportBounds.setData(bbox)
} }