Add search previews on the map
This commit is contained in:
parent
1c46a65c84
commit
4f52483a98
19 changed files with 315 additions and 87 deletions
|
@ -21,7 +21,7 @@ export default class TitleHandler {
|
|||
if (selected === undefined) {
|
||||
return defaultTitle
|
||||
}
|
||||
const layer = state.layout.getMatchingLayer(selected.properties)
|
||||
const layer = state.getMatchingLayer(selected.properties)
|
||||
if (layer === undefined) {
|
||||
return defaultTitle
|
||||
}
|
||||
|
|
41
src/Logic/Geocoding/GeocodingFeatureSource.ts
Normal file
41
src/Logic/Geocoding/GeocodingFeatureSource.ts
Normal 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
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -2,8 +2,10 @@ import { BBox } from "../BBox"
|
|||
import { Feature, Geometry } from "geojson"
|
||||
import { DefaultPinIcon } from "../../Models/Constants"
|
||||
import { Store } from "../UIEventSource"
|
||||
|
||||
export type GeocodingCategory = "coordinate" | "city" | "house" | "street" | "locality" | "country" | "train_station" | "county" | "airport"
|
||||
import * as search from "../../assets/generated/layers/search.json"
|
||||
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 = {
|
||||
/**
|
||||
|
@ -66,6 +68,8 @@ export interface ReverseGeocodingProvider {
|
|||
|
||||
export class GeocodingUtils {
|
||||
|
||||
public static searchLayer= new LayerConfig(<LayerConfigJson> search, "search")
|
||||
|
||||
public static categoryToZoomLevel: Record<GeocodingCategory, number> = {
|
||||
city: 12,
|
||||
county: 10,
|
||||
|
@ -75,7 +79,8 @@ export class GeocodingUtils {
|
|||
locality: 14,
|
||||
street: 15,
|
||||
train_station: 14,
|
||||
airport: 13
|
||||
airport: 13,
|
||||
shop:16
|
||||
|
||||
}
|
||||
|
||||
|
@ -89,7 +94,8 @@ export class GeocodingUtils {
|
|||
street: "globe_alt",
|
||||
train_station: "train",
|
||||
county: "building_office_2",
|
||||
airport: "airport"
|
||||
airport: "airport",
|
||||
shop: "building_storefront"
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -95,6 +95,9 @@ export default class PhotonSearch implements GeocodingProvider, ReverseGeocoding
|
|||
|
||||
private getCategory(entry: Feature) {
|
||||
const p = entry.properties
|
||||
if(p.osm_key === "shop"){
|
||||
return "shop"
|
||||
}
|
||||
if (p.osm_value === "train_station" || p.osm_key === "railway") {
|
||||
return "train_station"
|
||||
}
|
||||
|
|
|
@ -12,10 +12,41 @@ export class RecentSearch {
|
|||
public readonly seenThisSession: Store<GeoCodeResult[]>
|
||||
|
||||
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 = 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 => {
|
||||
|
||||
|
@ -23,6 +54,10 @@ export class RecentSearch {
|
|||
if(!osm_id){
|
||||
return
|
||||
}
|
||||
console.log("Selected element is", selected)
|
||||
if(["node","way","relation"].indexOf(osm_type) < 0){
|
||||
return
|
||||
}
|
||||
const [lon, lat] = GeoOperations.centerpointCoordinates(selected)
|
||||
const entry = <GeoCodeResult>{
|
||||
feature: selected,
|
||||
|
@ -46,6 +81,7 @@ export class RecentSearch {
|
|||
seenIds.add(id)
|
||||
}
|
||||
}
|
||||
console.log(">>>",arr)
|
||||
this._seenThisSession.set(arr)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,7 @@ import { UIEventSource } from "../UIEventSource"
|
|||
import UserDetails, { OsmConnection } from "./OsmConnection"
|
||||
import { Utils } from "../../Utils"
|
||||
import { LocalStorageSource } from "../Web/LocalStorageSource"
|
||||
// @ts-ignore
|
||||
import { osmAuth } from "osm-auth"
|
||||
import OSMAuthInstance = OSMAuth.OSMAuthInstance
|
||||
import OSMAuthInstance = OSMAuth.osmAuth
|
||||
|
||||
export class OsmPreferences {
|
||||
/**
|
||||
|
@ -53,7 +51,7 @@ export class OsmPreferences {
|
|||
const subOptions = { prefix: "" }
|
||||
// Gives the number of combined preferences
|
||||
const length = this.GetPreference(allStartWith + "-length", "", subOptions)
|
||||
|
||||
const preferences = this.preferences
|
||||
if ((allStartWith + "-length").length > 255) {
|
||||
throw (
|
||||
"This preference key is too long, it has " +
|
||||
|
@ -64,7 +62,6 @@ export class OsmPreferences {
|
|||
)
|
||||
}
|
||||
|
||||
const self = this
|
||||
source.addCallback((str) => {
|
||||
if (str === undefined || str === "") {
|
||||
return
|
||||
|
@ -74,9 +71,9 @@ export class OsmPreferences {
|
|||
const count = parseInt(length.data)
|
||||
for (let i = 0; i < count; i++) {
|
||||
// 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
|
||||
}
|
||||
|
||||
|
@ -99,7 +96,7 @@ export class OsmPreferences {
|
|||
if (i > 100) {
|
||||
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 = str.substr(255)
|
||||
|
@ -108,8 +105,9 @@ export class OsmPreferences {
|
|||
length.setData("" + i) // We use I, the number of preference fields used
|
||||
})
|
||||
|
||||
|
||||
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
|
||||
return
|
||||
}
|
||||
|
@ -120,15 +118,21 @@ export class OsmPreferences {
|
|||
let str = ""
|
||||
for (let i = 0; i < prefsCount; i++) {
|
||||
const key = allStartWith + "-" + i
|
||||
if (self.preferences.data[key] === undefined) {
|
||||
if (preferences.data[key] === undefined) {
|
||||
console.warn(
|
||||
"Detected a broken combined preference:",
|
||||
key,
|
||||
"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)
|
||||
|
@ -137,7 +141,7 @@ export class OsmPreferences {
|
|||
length.addCallback((l) => {
|
||||
updateData(Number(l))
|
||||
})
|
||||
this.preferences.addCallbackAndRun((_) => {
|
||||
this.preferences.addCallbackAndRun(() => {
|
||||
updateData(Number(length.data))
|
||||
})
|
||||
|
||||
|
@ -159,7 +163,7 @@ export class OsmPreferences {
|
|||
)
|
||||
}
|
||||
key = prefix + key
|
||||
key = key.replace(/[:\\\/"' {}.%]/g, "")
|
||||
key = key.replace(/[:/"' {}.%\\]/g, "")
|
||||
if (key.length >= 255) {
|
||||
throw "Preferences: key length to big"
|
||||
}
|
||||
|
@ -193,7 +197,6 @@ export class OsmPreferences {
|
|||
|
||||
public ClearPreferences() {
|
||||
let isRunning = false
|
||||
const self = this
|
||||
this.preferences.addCallback((prefs) => {
|
||||
console.log("Cleaning preferences...")
|
||||
if (Object.keys(prefs).length == 0) {
|
||||
|
@ -208,7 +211,7 @@ export class OsmPreferences {
|
|||
const matches = prefixes.some((prefix) => key.startsWith(prefix))
|
||||
if (matches) {
|
||||
console.log("Clearing ", key)
|
||||
self.GetPreference(key, "", { prefix: "" }).setData("")
|
||||
this.GetPreference(key, "", { prefix: "" }).setData("")
|
||||
}
|
||||
}
|
||||
isRunning = false
|
||||
|
@ -227,7 +230,6 @@ export class OsmPreferences {
|
|||
}
|
||||
|
||||
private UpdatePreferences(forceUpdate?: boolean) {
|
||||
const self = this
|
||||
if (this._fakeUser) {
|
||||
return
|
||||
}
|
||||
|
@ -236,7 +238,7 @@ export class OsmPreferences {
|
|||
method: "GET",
|
||||
path: "/api/0.6/user/preferences",
|
||||
},
|
||||
function (error, value: XMLDocument) {
|
||||
(error, value: XMLDocument) => {
|
||||
if (error) {
|
||||
console.log("Could not load preferences", error)
|
||||
return
|
||||
|
@ -246,34 +248,33 @@ export class OsmPreferences {
|
|||
for (let i = 0; i < prefs.length; i++) {
|
||||
const pref = prefs[i]
|
||||
const k = pref.getAttribute("k")
|
||||
const v = pref.getAttribute("v")
|
||||
self.preferences.data[k] = v
|
||||
this.preferences.data[k] = pref.getAttribute("v")
|
||||
seenKeys.add(k)
|
||||
}
|
||||
if (forceUpdate) {
|
||||
for (let key in self.preferences.data) {
|
||||
for (const key in this.preferences.data) {
|
||||
if (seenKeys.has(key)) {
|
||||
continue
|
||||
}
|
||||
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
|
||||
// For differing values, the server overrides local changes
|
||||
self.preferenceSources.forEach((preference, key) => {
|
||||
const osmValue = self.preferences.data[key]
|
||||
this.preferenceSources.forEach((preference, key) => {
|
||||
const osmValue = this.preferences.data[key]
|
||||
if (osmValue === undefined && preference.data !== undefined) {
|
||||
// OSM doesn't know this value yet
|
||||
self.UploadPreference(key, preference.data)
|
||||
this.UploadPreference(key, preference.data)
|
||||
} else {
|
||||
// OSM does have a value - set it
|
||||
preference.setData(osmValue)
|
||||
}
|
||||
})
|
||||
|
||||
self.preferences.ping()
|
||||
this.preferences.ping()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -287,7 +288,6 @@ export class OsmPreferences {
|
|||
if (this.preferences.data[k] === v) {
|
||||
return
|
||||
}
|
||||
const self = this
|
||||
console.debug("Updating preference", k, " to ", Utils.EllipsesAfter(v, 15))
|
||||
if (this._fakeUser) {
|
||||
return
|
||||
|
@ -299,13 +299,13 @@ export class OsmPreferences {
|
|||
path: "/api/0.6/user/preferences/" + encodeURIComponent(k),
|
||||
headers: { "Content-Type": "text/plain" },
|
||||
},
|
||||
function (error) {
|
||||
(error) => {
|
||||
if (error) {
|
||||
console.warn("Could not remove preference", error)
|
||||
return
|
||||
}
|
||||
delete self.preferences.data[k]
|
||||
self.preferences.ping()
|
||||
delete this.preferences.data[k]
|
||||
this.preferences.ping()
|
||||
console.debug("Preference ", k, "removed!")
|
||||
}
|
||||
)
|
||||
|
@ -319,13 +319,13 @@ export class OsmPreferences {
|
|||
headers: { "Content-Type": "text/plain" },
|
||||
content: v,
|
||||
},
|
||||
function (error) {
|
||||
(error)=> {
|
||||
if (error) {
|
||||
console.warn(`Could not set preference "${k}"'`, error)
|
||||
return
|
||||
}
|
||||
self.preferences.data[k] = v
|
||||
self.preferences.ping()
|
||||
this.preferences.data[k] = v
|
||||
this.preferences.ping()
|
||||
console.debug(`Preference ${k} written!`)
|
||||
}
|
||||
)
|
||||
|
|
|
@ -351,8 +351,10 @@ export default class UserRelatedState {
|
|||
const key = k.substring(0, k.length - "length".length)
|
||||
let combined = ""
|
||||
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
|
||||
} else {
|
||||
amendedPrefs.data[k] = newPrefs[k]
|
||||
|
@ -456,11 +458,15 @@ export default class UserRelatedState {
|
|||
amendedPrefs.addCallbackD((tags) => {
|
||||
for (const key in tags) {
|
||||
if (key.startsWith("_") || key === "mapcomplete-language") {
|
||||
// Language is managed seperately
|
||||
// Language is managed separately
|
||||
continue
|
||||
}
|
||||
if (tags[key + "-combined-0"]) {
|
||||
// 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])
|
||||
} else {
|
||||
this.osmConnection
|
||||
|
|
|
@ -91,6 +91,20 @@ export class Stores {
|
|||
})
|
||||
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> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue