diff --git a/assets/layers/usersettings/usersettings.json b/assets/layers/usersettings/usersettings.json
index 855de0b76..c9f98f991 100644
--- a/assets/layers/usersettings/usersettings.json
+++ b/assets/layers/usersettings/usersettings.json
@@ -45,7 +45,9 @@
},
{
"id": "profile-title",
- "labels": ["hidden"],
+ "labels": [
+ "hidden"
+ ],
"icon": "user_circle",
"render": {
"*": "
{_name}
"
@@ -63,7 +65,8 @@
{
"id": "profile-description",
"labels": [
- "profile-content","hidden"
+ "profile-content",
+ "hidden"
],
"render": {
"*": "{_description_html}"
@@ -71,7 +74,6 @@
"mappings": [
{
"if": "_description=",
-
"then": {
"special": {
"type": "link",
@@ -98,7 +100,8 @@
{
"id": "edit-profile",
"labels": [
- "profile-content","hidden"
+ "profile-content",
+ "hidden"
],
"condition": "_description!=",
"render": {
@@ -126,7 +129,8 @@
{
"id": "verified-mastodon",
"labels": [
- "profile-content","hidden"
+ "profile-content",
+ "hidden"
],
"mappings": [
{
@@ -157,7 +161,8 @@
{
"id": "cscount-thanks",
"labels": [
- "profile-content","hidden"
+ "profile-content",
+ "hidden"
],
"mappings": [
{
@@ -180,7 +185,8 @@
{
"id": "translation-thanks",
"labels": [
- "profile-content","hidden"
+ "profile-content",
+ "hidden"
],
"mappings": [
{
@@ -197,7 +203,8 @@
{
"id": "contributor-thanks",
"labels": [
- "profile-content","hidden"
+ "profile-content",
+ "hidden"
],
"mappings": [
{
diff --git a/langs/layers/ca.json b/langs/layers/ca.json
index cf4593967..5a5e59f6e 100644
--- a/langs/layers/ca.json
+++ b/langs/layers/ca.json
@@ -8468,6 +8468,13 @@
}
}
},
+ "edit-profile": {
+ "render": {
+ "special": {
+ "text": "Editeu la descripció del vostre perfil"
+ }
+ }
+ },
"fixate-north": {
"mappings": {
"0": {
@@ -8529,6 +8536,17 @@
},
"question": "Sota quina llicència vols publicar les teves fotos?"
},
+ "profile-description": {
+ "mappings": {
+ "0": {
+ "then": {
+ "special": {
+ "text": "Afegeix una descripció del perfil"
+ }
+ }
+ }
+ }
+ },
"settings-link": {
"render": {
"special": {
diff --git a/langs/layers/cs.json b/langs/layers/cs.json
index 632be827e..41835caf4 100644
--- a/langs/layers/cs.json
+++ b/langs/layers/cs.json
@@ -8723,6 +8723,13 @@
}
}
},
+ "edit-profile": {
+ "render": {
+ "special": {
+ "text": "Úprava popisu vašeho profilu"
+ }
+ }
+ },
"fixate-north": {
"mappings": {
"0": {
@@ -8784,6 +8791,17 @@
},
"question": "Pod jakou licencí chcete své fotografie zveřejnit?"
},
+ "profile-description": {
+ "mappings": {
+ "0": {
+ "then": {
+ "special": {
+ "text": "Přidat popis profilu"
+ }
+ }
+ }
+ }
+ },
"settings-link": {
"render": {
"special": {
diff --git a/langs/layers/da.json b/langs/layers/da.json
index 3ff20dea0..9d7541137 100644
--- a/langs/layers/da.json
+++ b/langs/layers/da.json
@@ -2485,6 +2485,13 @@
}
}
},
+ "edit-profile": {
+ "render": {
+ "special": {
+ "text": "Ret din profilbeskrivelse"
+ }
+ }
+ },
"fixate-north": {
"mappings": {
"0": {
diff --git a/langs/layers/de.json b/langs/layers/de.json
index 595f678a4..ab37d4ee7 100644
--- a/langs/layers/de.json
+++ b/langs/layers/de.json
@@ -11119,6 +11119,13 @@
}
}
},
+ "edit-profile": {
+ "render": {
+ "special": {
+ "text": "Eigene Profilbeschreibung bearbeiten"
+ }
+ }
+ },
"fixate-north": {
"mappings": {
"0": {
@@ -11207,6 +11214,17 @@
},
"question": "Unter welcher Lizenz möchten Sie Ihre Bilder veröffentlichen?"
},
+ "profile-description": {
+ "mappings": {
+ "0": {
+ "then": {
+ "special": {
+ "text": "Profilbeschreibung hinzufügen"
+ }
+ }
+ }
+ }
+ },
"settings-link": {
"render": {
"special": {
diff --git a/langs/layers/en.json b/langs/layers/en.json
index 72f4da118..18de13279 100644
--- a/langs/layers/en.json
+++ b/langs/layers/en.json
@@ -11170,6 +11170,13 @@
"debug-title": {
"render": "Debugging options
"
},
+ "edit-profile": {
+ "render": {
+ "special": {
+ "text": "Edit your profile description"
+ }
+ }
+ },
"fixate-north": {
"mappings": {
"0": {
@@ -11258,6 +11265,17 @@
},
"question": "Under what license do you want to publish your pictures?"
},
+ "profile-description": {
+ "mappings": {
+ "0": {
+ "then": {
+ "special": {
+ "text": "Add a profile description"
+ }
+ }
+ }
+ }
+ },
"settings-link": {
"render": {
"special": {
diff --git a/langs/layers/fi.json b/langs/layers/fi.json
index 6ad318e18..2bb7ab510 100644
--- a/langs/layers/fi.json
+++ b/langs/layers/fi.json
@@ -122,6 +122,26 @@
}
},
"usersettings": {
+ "tagRenderings": {
+ "edit-profile": {
+ "render": {
+ "special": {
+ "text": "Muokkaa profiilin kuvausta"
+ }
+ }
+ },
+ "profile-description": {
+ "mappings": {
+ "0": {
+ "then": {
+ "special": {
+ "text": "Lisää profiilin kuvaus"
+ }
+ }
+ }
+ }
+ }
+ },
"title": {
"render": "Asetukset"
}
diff --git a/langs/layers/fr.json b/langs/layers/fr.json
index 88af4f47e..78c9a486b 100644
--- a/langs/layers/fr.json
+++ b/langs/layers/fr.json
@@ -6925,6 +6925,13 @@
}
}
},
+ "edit-profile": {
+ "render": {
+ "special": {
+ "text": "Modifier ton profil"
+ }
+ }
+ },
"fixate-north": {
"mappings": {
"0": {
diff --git a/langs/layers/nb_NO.json b/langs/layers/nb_NO.json
index 31e212e2c..83c930c1f 100644
--- a/langs/layers/nb_NO.json
+++ b/langs/layers/nb_NO.json
@@ -842,6 +842,17 @@
},
"usersettings": {
"tagRenderings": {
+ "profile-description": {
+ "mappings": {
+ "0": {
+ "then": {
+ "special": {
+ "text": "Legg til profilbeskrivelse"
+ }
+ }
+ }
+ }
+ },
"translation-completeness": {
"render": "Oversettelsen for {_theme} i {_language} har {_translation_percentage}% dekning: {_translation_translated_count} strenger av {_translation_total} har blitt oversatt"
}
diff --git a/langs/layers/nl.json b/langs/layers/nl.json
index a34b5fc90..1dd81b10d 100644
--- a/langs/layers/nl.json
+++ b/langs/layers/nl.json
@@ -8871,6 +8871,13 @@
}
}
},
+ "edit-profile": {
+ "render": {
+ "special": {
+ "text": "Pas je profielbeschrijving aan"
+ }
+ }
+ },
"fixate-north": {
"mappings": {
"0": {
@@ -8959,6 +8966,17 @@
},
"question": "Met welke licentie wil je je afbeeldingen toevoegen?"
},
+ "profile-description": {
+ "mappings": {
+ "0": {
+ "then": {
+ "special": {
+ "text": "Voeg een profielbeschrijving toe"
+ }
+ }
+ }
+ }
+ },
"settings-link": {
"render": {
"special": {
diff --git a/langs/layers/pl.json b/langs/layers/pl.json
index db175d09a..ad81f5ba7 100644
--- a/langs/layers/pl.json
+++ b/langs/layers/pl.json
@@ -3338,6 +3338,28 @@
}
}
},
+ "usersettings": {
+ "tagRenderings": {
+ "edit-profile": {
+ "render": {
+ "special": {
+ "text": "Edytuj opis swojego profilu"
+ }
+ }
+ },
+ "profile-description": {
+ "mappings": {
+ "0": {
+ "then": {
+ "special": {
+ "text": "Dodaj opis profilu"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"walls_and_buildings": {
"description": "Specjalna warstwa zabudowana zapewniająca wszystkie mury i budynki. Warstwa ta jest przydatna w ustawieniach wstępnych obiektów, które można umieścić przy ścianach (np. AED, skrzynki pocztowe, wejścia, adresy, kamery monitorujące itp.). Warstwa ta jest domyślnie niewidoczna i użytkownik nie może jej przełączać."
},
diff --git a/langs/layers/pt.json b/langs/layers/pt.json
index 5633ea9b8..604a9488b 100644
--- a/langs/layers/pt.json
+++ b/langs/layers/pt.json
@@ -1768,6 +1768,13 @@
}
}
},
+ "edit-profile": {
+ "render": {
+ "special": {
+ "text": "Editar a descrição do seu perfil"
+ }
+ }
+ },
"picture-license": {
"mappings": {
"0": {
@@ -1785,6 +1792,17 @@
},
"question": "Sob que licença você deseja publicar suas fotos?"
},
+ "profile-description": {
+ "mappings": {
+ "0": {
+ "then": {
+ "special": {
+ "text": "Adicionar uma descrição do perfil"
+ }
+ }
+ }
+ }
+ },
"show_debug": {
"mappings": {
"0": {
diff --git a/langs/layers/zh_Hant.json b/langs/layers/zh_Hant.json
index 615b0e8fd..370066886 100644
--- a/langs/layers/zh_Hant.json
+++ b/langs/layers/zh_Hant.json
@@ -782,6 +782,24 @@
},
"usersettings": {
"tagRenderings": {
+ "edit-profile": {
+ "render": {
+ "special": {
+ "text": "編輯你的個人檔敘述"
+ }
+ }
+ },
+ "profile-description": {
+ "mappings": {
+ "0": {
+ "then": {
+ "special": {
+ "text": "新增個人檔敘述"
+ }
+ }
+ }
+ }
+ },
"translation-completeness": {
"render": "{_theme} 的 {_language} 翻譯目前是 {_translation_percentage}%:{_translation_total} 中的 {_translation_translated_count} 已經翻譯了"
},
diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css
index 320d427ce..df125fb0e 100644
--- a/public/css/index-tailwind-output.css
+++ b/public/css/index-tailwind-output.css
@@ -711,6 +711,14 @@ video {
top: 2.5rem;
}
+.top-2 {
+ top: 0.5rem;
+}
+
+.right-2 {
+ right: 0.5rem;
+}
+
.left-1\/4 {
left: 25%;
}
@@ -779,10 +787,6 @@ video {
top: 0.25rem;
}
-.top-2 {
- top: 0.5rem;
-}
-
.top-\[calc\(100\%\+1rem\)\] {
top: calc(100% + 1rem);
}
@@ -1221,14 +1225,14 @@ video {
height: 6rem;
}
-.h-full {
- height: 100%;
-}
-
.h-screen {
height: 100vh;
}
+.h-full {
+ height: 100%;
+}
+
.h-fit {
height: -webkit-fit-content;
height: -moz-fit-content;
@@ -1280,6 +1284,10 @@ video {
height: 2.75rem;
}
+.h-2\/3 {
+ height: 66.666667%;
+}
+
.h-5 {
height: 1.25rem;
}
@@ -2043,10 +2051,6 @@ video {
column-gap: 0px;
}
-.gap-x-4 {
- column-gap: 1rem;
-}
-
.gap-y-8 {
row-gap: 2rem;
}
@@ -4627,6 +4631,17 @@ button.as-link {
padding: 0;
}
+button.unstyled {
+ background-color: unset;
+ display: inline-flex;
+ justify-content: start;
+ border: none;
+ border-radius: 0;
+ box-shadow: none;
+ margin: 0;
+ padding: 0;
+}
+
/******* Other input elements ******/
.hover-alert:hover {
@@ -5284,6 +5299,11 @@ svg.apply-fill path {
border-color: rgb(209 213 219 / var(--tw-border-opacity));
}
+.hover\:bg-stone-200:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(231 229 228 / var(--tw-bg-opacity));
+}
+
.hover\:bg-indigo-200:hover {
--tw-bg-opacity: 1;
background-color: rgb(199 210 254 / var(--tw-bg-opacity));
diff --git a/src/Logic/FeatureSource/TiledFeatureSource/SummaryTileSource.ts b/src/Logic/FeatureSource/TiledFeatureSource/SummaryTileSource.ts
index 74ffa9745..c0dcb8bb7 100644
--- a/src/Logic/FeatureSource/TiledFeatureSource/SummaryTileSource.ts
+++ b/src/Logic/FeatureSource/TiledFeatureSource/SummaryTileSource.ts
@@ -28,10 +28,10 @@ export class SummaryTileSourceRewriter implements FeatureSource {
!l.layerDef.id.startsWith("note_import")
)
this._summarySource = summarySource
- filteredLayers.forEach((v, k) => {
- v.isDisplayed.addCallback((_) => this.update())
+ filteredLayers.forEach((v) => {
+ v.isDisplayed.addCallback(() => this.update())
})
- this._summarySource.features.addCallbackAndRunD((_) => this.update())
+ this._summarySource.features.addCallbackAndRunD(() => this.update())
}
private update() {
@@ -78,6 +78,9 @@ export class SummaryTileSource extends DynamicTileSource {
isActive?: Store
}
) {
+ if(layers.length === 0){
+ return
+ }
const layersSummed = layers.join("+")
const zDiff = 2
super(
diff --git a/src/Logic/Geocoding/CombinedSearcher.ts b/src/Logic/Geocoding/CombinedSearcher.ts
new file mode 100644
index 000000000..68e0170ab
--- /dev/null
+++ b/src/Logic/Geocoding/CombinedSearcher.ts
@@ -0,0 +1,21 @@
+import GeocodingProvider, { GeoCodeResult, GeocodingOptions } from "./GeocodingProvider"
+
+export default class CombinedSearcher implements GeocodingProvider {
+ private _providers: ReadonlyArray
+ private _providersWithSuggest: ReadonlyArray
+
+ constructor(...providers: ReadonlyArray) {
+ this._providers = providers
+ this._providersWithSuggest = providers.filter(pr => pr.suggest !== undefined)
+ }
+
+ async search(query: string, options?: GeocodingOptions): Promise {
+ const results = await Promise.all(this._providers.map(pr => pr.search(query, options)))
+ return results.flatMap(x => x)
+ }
+
+ async suggest(query: string, options?: GeocodingOptions): Promise {
+ const results = await Promise.all(this._providersWithSuggest.map(pr => pr.suggest(query, options)))
+ return results.flatMap(x => x)
+ }
+}
diff --git a/src/Logic/Geocoding/CoordinateSearch.ts b/src/Logic/Geocoding/CoordinateSearch.ts
new file mode 100644
index 000000000..6a0a789fe
--- /dev/null
+++ b/src/Logic/Geocoding/CoordinateSearch.ts
@@ -0,0 +1,67 @@
+import GeocodingProvider, { GeoCodeResult, GeocodingOptions } from "./GeocodingProvider"
+import { Utils } from "../../Utils"
+
+/**
+ * A simple search-class which interprets possible locations
+ */
+export default class CoordinateSearch implements GeocodingProvider {
+ private static readonly latLonRegexes: ReadonlyArray = [
+ /([0-9]+\.[0-9]+)[ ,;]+([0-9]+\.[0-9]+)/,
+ /lat:?[ ]*([0-9]+\.[0-9]+)[ ,;]+lon:?[ ]*([0-9]+\.[0-9]+)/,
+ /https:\/\/www.openstreetmap.org\/.*#map=[0-9]+\/([0-9]+\.[0-9]+)\/([0-9]+\.[0-9]+)/,
+ /https:\/\/www.google.com\/maps\/@([0-9]+.[0-9]+),([0-9]+.[0-9]+).*/
+ ]
+
+ private static readonly lonLatRegexes: ReadonlyArray = [
+ /([0-9]+\.[0-9]+)[ ,;]+([0-9]+\.[0-9]+)/
+ ]
+
+ /**
+ *
+ * @param query
+ * @param options
+ *
+ * const ls = new CoordinateSearch()
+ * const results = await ls.search("https://www.openstreetmap.org/search?query=Brugge#map=11/51.2611/3.2217")
+ * results.length // => 1
+ * results[0] // => {lat: 51.2611, lon: 3.2217, display_name: "lon: 3.2217, lat: 51.2611"}
+ *
+ * const ls = new CoordinateSearch()
+ * const results = await ls.search("https://www.openstreetmap.org/#map=11/51.2611/3.2217")
+ * results.length // => 1
+ * results[0] // => {lat: 51.2611, lon: 3.2217, display_name: "lon: 3.2217, lat: 51.2611"}
+ *
+ * const ls = new CoordinateSearch()
+ * const results = await ls.search("51.2611 3.2217")
+ * results.length // => 2
+ * results[0] // => {lat: 51.2611, lon: 3.2217, display_name: "lon: 3.2217, lat: 51.2611"}
+ * results[1] // => {lon: 51.2611, lat: 3.2217, display_name: "lon: 51.2611, lat: 3.2217"}
+ *
+ */
+ async search(query: string, options?: GeocodingOptions): Promise {
+
+ const matches = Utils.NoNull(CoordinateSearch.latLonRegexes.map(r => query.match(r))).map(m => {
+ lat: Number(m[1]),
+ lon: Number(m[2]),
+ display_name: "lon: " + m[2] + ", lat: " + m[1],
+ source: "coordinateSearch"
+ })
+
+
+
+ const matchesLonLat = Utils.NoNull(CoordinateSearch.lonLatRegexes.map(r => query.match(r)))
+ .map(m => {
+ lat: Number(m[2]),
+ lon: Number(m[1]),
+ display_name: "lon: " + m[1] + ", lat: " + m[2],
+ source: "coordinateSearch"
+ })
+
+ return matches.concat(matchesLonLat)
+ }
+
+ suggest(query: string, options?: GeocodingOptions): Promise {
+ return this.search(query, options)
+ }
+
+}
diff --git a/src/Logic/Geocoding/GeocodingProvider.ts b/src/Logic/Geocoding/GeocodingProvider.ts
new file mode 100644
index 000000000..3d8ed7426
--- /dev/null
+++ b/src/Logic/Geocoding/GeocodingProvider.ts
@@ -0,0 +1,43 @@
+import { BBox } from "../BBox"
+import { Feature, FeatureCollection } from "geojson"
+
+export type GeoCodeResult = {
+ display_name: string
+ feature?: Feature,
+ lat: number
+ lon: number
+ /**
+ * Format:
+ * [lat, lat, lon, lon]
+ */
+ boundingbox?: number[]
+ osm_type?: "node" | "way" | "relation"
+ osm_id?: string
+}
+
+export interface GeocodingOptions {
+ bbox?: BBox,
+ limit?: number
+}
+
+
+export default interface GeocodingProvider {
+
+
+ search(query: string, options?: GeocodingOptions): Promise
+
+ /**
+ * @param query
+ * @param options
+ */
+ suggest?(query: string, options?: GeocodingOptions): Promise
+}
+
+export interface ReverseGeocodingProvider {
+ reverseSearch(
+ coordinate: { lon: number; lat: number },
+ zoom: number,
+ language?: string
+ ): Promise ;
+}
+
diff --git a/src/Logic/Geocoding/LocalElementSearch.ts b/src/Logic/Geocoding/LocalElementSearch.ts
new file mode 100644
index 000000000..a39920ed9
--- /dev/null
+++ b/src/Logic/Geocoding/LocalElementSearch.ts
@@ -0,0 +1,86 @@
+import GeocodingProvider, { GeoCodeResult, GeocodingOptions } from "./GeocodingProvider"
+import ThemeViewState from "../../Models/ThemeViewState"
+import { Utils } from "../../Utils"
+import { Feature } from "geojson"
+import { GeoOperations } from "../GeoOperations"
+
+export default class LocalElementSearch implements GeocodingProvider {
+ private readonly _state: ThemeViewState
+
+ constructor(state: ThemeViewState) {
+ this._state = state
+
+ }
+
+ async search(query: string, options?: GeocodingOptions): Promise {
+ return this.searchEntries(query, options, false)
+ }
+
+ searchEntries(query: string, options?: GeocodingOptions, matchStart?: boolean): GeoCodeResult[] {
+ if (query.length < 3) {
+ return []
+ }
+ const center: { lon: number; lat: number } = this._state.mapProperties.location.data
+ const centerPoint: [number, number] = [center.lon, center.lat]
+ let results: {
+ feature: Feature,
+ /**
+ * Lon, lat
+ */
+ center: [number, number],
+ levehnsteinD: number,
+ physicalDistance: number,
+ searchTerms: string[]
+ }[] = []
+ const properties = this._state.perLayer
+ query = Utils.simplifyStringForSearch(query)
+ for (const [_, geoIndexedStore] of properties) {
+ for (const feature of geoIndexedStore.features.data) {
+ const props = feature.properties
+ const searchTerms: string[] = Utils.NoNull([props.name, props.alt_name, props.local_name,
+ (props["addr:street"] && props["addr:number"]) ?
+ props["addr:street"] + props["addr:number"] : undefined])
+
+
+ const levehnsteinD = Math.min(...searchTerms.flatMap(entry => entry.split(/ /)).map(entry => {
+ let simplified = Utils.simplifyStringForSearch(entry)
+ if (matchStart) {
+ simplified = simplified.slice(0, query.length)
+ }
+ return Utils.levenshteinDistance(query, simplified)
+ }))
+ const center = GeoOperations.centerpointCoordinates(feature)
+ if (levehnsteinD <= 2) {
+ results.push({
+ feature,
+ center,
+ physicalDistance: GeoOperations.distanceBetween(centerPoint, center),
+ levehnsteinD,
+ searchTerms
+ })
+ }
+ }
+ }
+ results.sort((a, b) => (a.physicalDistance + a.levehnsteinD * 25) - (b.physicalDistance + b.levehnsteinD * 25))
+ if (options?.limit) {
+ results = results.slice(0, options.limit)
+ }
+ return results.map(entry => {
+ const id = entry.feature.properties.id.split("/")
+ return {
+ lon: entry.center[0],
+ lat: entry.center[1],
+ osm_type: id[0],
+ osm_id: id[1],
+ display_name: entry.searchTerms[0],
+ source: "localElementSearch",
+ feature: entry.feature
+ }
+ })
+ }
+
+ async suggest(query: string, options?: GeocodingOptions): Promise {
+ return this.searchEntries(query, options, true)
+ }
+
+}
diff --git a/src/Logic/Geocoding/NominatimGeocoding.ts b/src/Logic/Geocoding/NominatimGeocoding.ts
new file mode 100644
index 000000000..f693a0f4e
--- /dev/null
+++ b/src/Logic/Geocoding/NominatimGeocoding.ts
@@ -0,0 +1,39 @@
+import { Utils } from "../../Utils"
+import { BBox } from "../BBox"
+import Constants from "../../Models/Constants"
+import { FeatureCollection } from "geojson"
+import Locale from "../../UI/i18n/Locale"
+import GeocodingProvider, { GeoCodeResult, ReverseGeocodingProvider } from "./GeocodingProvider"
+
+export class NominatimGeocoding implements GeocodingProvider, ReverseGeocodingProvider {
+
+ private readonly _host ;
+
+ constructor(host: string = Constants.nominatimEndpoint) {
+ this._host = host
+ }
+
+ public async search(query: string, options?: { bbox?: BBox; limit?: number }): Promise {
+ const b = options?.bbox ?? BBox.global
+ const url = `${
+ this._host
+ }search?format=json&limit=${options?.limit ?? 1}&viewbox=${b.getEast()},${b.getNorth()},${b.getWest()},${b.getSouth()}&accept-language=${
+ Locale.language.data
+ }&q=${query}`
+ return await Utils.downloadJson(url)
+ }
+
+
+ async reverseSearch(
+ coordinate: { lon: number; lat: number },
+ zoom: number = 17,
+ language?: string
+ ): Promise {
+ // https://nominatim.org/release-docs/develop/api/Reverse/
+ // IF the zoom is low, it'll only return a country instead of an address
+ const url = `${this._host}reverse?format=geojson&lat=${coordinate.lat}&lon=${
+ coordinate.lon
+ }&zoom=${Math.ceil(zoom) + 1}&accept-language=${language}`
+ return Utils.downloadJson(url)
+ }
+}
diff --git a/src/Logic/Osm/Geocoding.ts b/src/Logic/Osm/Geocoding.ts
deleted file mode 100644
index 075a3745d..000000000
--- a/src/Logic/Osm/Geocoding.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { Utils } from "../../Utils"
-import { BBox } from "../BBox"
-import Constants from "../../Models/Constants"
-import { FeatureCollection } from "geojson"
-import Locale from "../../UI/i18n/Locale"
-
-export interface GeoCodeResult {
- display_name: string
- lat: number
- lon: number
- /**
- * Format:
- * [lat, lat, lon, lon]
- */
- boundingbox: number[]
- osm_type: "node" | "way" | "relation"
- osm_id: string
-}
-
-export class Geocoding {
- public static readonly host = Constants.nominatimEndpoint
-
- static async Search(query: string, bbox: BBox): Promise {
- const b = bbox ?? BBox.global
- const url = `${
- Geocoding.host
- }search?format=json&limit=1&viewbox=${b.getEast()},${b.getNorth()},${b.getWest()},${b.getSouth()}&accept-language=${
- Locale.language.data
- }&q=${query}`
- return Utils.downloadJson(url)
- }
-
- static async reverse(
- coordinate: { lon: number; lat: number },
- zoom: number = 17,
- language?: string
- ): Promise {
- // https://nominatim.org/release-docs/develop/api/Reverse/
- // IF the zoom is low, it'll only return a country instead of an address
- const url = `${Geocoding.host}reverse?format=geojson&lat=${coordinate.lat}&lon=${
- coordinate.lon
- }&zoom=${Math.ceil(zoom) + 1}&accept-language=${language}`
- return Utils.downloadJson(url)
- }
-}
diff --git a/src/Models/MapProperties.ts b/src/Models/MapProperties.ts
index eb86c31d6..39e8b6cd2 100644
--- a/src/Models/MapProperties.ts
+++ b/src/Models/MapProperties.ts
@@ -29,6 +29,9 @@ export interface MapProperties {
* @param f
*/
onKeyNavigationEvent(f: (event: KeyNavigationEvent) => void | boolean): () => void
+
+ flyTo(lon: number, lat: number, zoom: number): void
+
}
export interface ExportableMap {
diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts
index f0f860c51..daec62a41 100644
--- a/src/Models/ThemeConfig/Conversion/Validation.ts
+++ b/src/Models/ThemeConfig/Conversion/Validation.ts
@@ -1701,7 +1701,8 @@ export class ValidateLayer extends Conversion<
try {
layerConfig = new LayerConfig(json, "validation", true)
} catch (e) {
- context.err("Could not parse layer due to:" + e)
+ console.error("Could not parse layer due to", e)
+ context.err("Could not parse layer due to: " + e)
return undefined
}
diff --git a/src/Models/ThemeConfig/LayerConfig.ts b/src/Models/ThemeConfig/LayerConfig.ts
index 4f132bf9f..730417007 100644
--- a/src/Models/ThemeConfig/LayerConfig.ts
+++ b/src/Models/ThemeConfig/LayerConfig.ts
@@ -19,11 +19,10 @@ import { Utils } from "../../Utils"
import { TagsFilter } from "../../Logic/Tags/TagsFilter"
import FilterConfigJson from "./Json/FilterConfigJson"
import { Overpass } from "../../Logic/Osm/Overpass"
-import { ImmutableStore } from "../../Logic/UIEventSource"
-import { OsmTags } from "../OsmFeature"
import Constants from "../Constants"
import { QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRenderingConfigJson"
import MarkdownUtils from "../../Utils/MarkdownUtils"
+import Combine from "../../UI/Base/Combine"
export default class LayerConfig extends WithContextLoader {
public static readonly syncSelectionAllowed = ["no", "local", "theme-only", "global"] as const
@@ -344,15 +343,17 @@ export default class LayerConfig extends WithContextLoader {
this.popupInFloatover = json.popupInFloatover ?? false
}
- public defaultIcon(): BaseUIElement | undefined {
+ public defaultIcon(tags?: Record): BaseUIElement | undefined {
if (this.mapRendering === undefined || this.mapRendering === null) {
return undefined
}
- const mapRendering = this.mapRendering.filter((r) => r.location.has("point"))[0]
- if (mapRendering === undefined) {
+ const mapRenderings = this.mapRendering.filter((r) => r.location.has("point"))
+ if (mapRenderings.length === 0) {
return undefined
}
- return mapRendering.GetBaseIcon(this.GetBaseTags())
+ return new Combine(mapRenderings.map(
+ mr => mr.GetBaseIcon(tags ?? this.GetBaseTags()).SetClass("absolute left-0 top-0 w-full h-full"))
+ ).SetClass("relative block w-full h-full")
}
public GetBaseTags(): Record {
diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts
index b0f63f503..617d09498 100644
--- a/src/Models/ThemeViewState.ts
+++ b/src/Models/ThemeViewState.ts
@@ -74,6 +74,11 @@ import Locale from "../UI/i18n/Locale"
import Hash from "../Logic/Web/Hash"
import { GeoOperations } from "../Logic/GeoOperations"
import { CombinedFetcher } from "../Logic/Web/NearbyImagesSearch"
+import GeocodingProvider from "../Logic/Geocoding/GeocodingProvider"
+import CombinedSearcher from "../Logic/Geocoding/CombinedSearcher"
+import { NominatimGeocoding } from "../Logic/Geocoding/NominatimGeocoding"
+import CoordinateSearch from "../Logic/Geocoding/CoordinateSearch"
+import LocalElementSearch from "../Logic/Geocoding/LocalElementSearch"
/**
*
@@ -153,7 +158,8 @@ export default class ThemeViewState implements SpecialVisualizationState {
public readonly visualFeedback: UIEventSource = new UIEventSource(false)
public readonly toCacheSavers: ReadonlyMap
- public readonly nearbyImageSearcher
+ public readonly nearbyImageSearcher: CombinedFetcher
+ public readonly geosearch: GeocodingProvider
constructor(layout: LayoutConfig, mvtAvailableLayers: Set) {
Utils.initDomPurify()
@@ -379,6 +385,14 @@ export default class ThemeViewState implements SpecialVisualizationState {
new LayerConfig(summaryLayer, "summaryLayer", true)
)
this.toCacheSavers = layout.enableCache ? this.initSaveToLocalStorage() : undefined
+
+ this.geosearch = new CombinedSearcher(
+ new NominatimGeocoding(),
+ new CoordinateSearch(),
+ new LocalElementSearch(this)
+ )
+
+
this.initActors()
this.drawSpecialLayers()
this.initHotkeys()
diff --git a/src/UI/BigComponents/Geosearch.svelte b/src/UI/BigComponents/Geosearch.svelte
index 87369e37a..308743ac0 100644
--- a/src/UI/BigComponents/Geosearch.svelte
+++ b/src/UI/BigComponents/Geosearch.svelte
@@ -4,7 +4,6 @@
import Translations from "../i18n/Translations"
import Loading from "../Base/Loading.svelte"
import Hotkeys from "../Base/Hotkeys"
- import { Geocoding } from "../../Logic/Osm/Geocoding"
import { BBox } from "../../Logic/BBox"
import { GeoIndexedStoreForLayer } from "../../Logic/FeatureSource/Actors/GeoIndexedStore"
import { createEventDispatcher, onDestroy } from "svelte"
@@ -12,6 +11,12 @@
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid"
import { ariaLabel } from "../../Utils/ariaLabel"
import { GeoLocationState } from "../../Logic/State/GeoLocationState"
+ import { NominatimGeocoding } from "../../Logic/Geocoding/NominatimGeocoding"
+ import type GeocodingProvider from "../../Logic/Geocoding/GeocodingProvider"
+ import type { GeoCodeResult } from "../../Logic/Geocoding/GeocodingProvider"
+
+ import SearchResults from "./SearchResults.svelte"
+ import type { SpecialVisualizationState } from "../SpecialVisualization"
export let perLayer: ReadonlyMap | undefined = undefined
export let bounds: UIEventSource
@@ -19,6 +24,8 @@
export let geolocationState: GeoLocationState | undefined = undefined
export let clearAfterView: boolean = true
+ export let searcher : GeocodingProvider = new NominatimGeocoding()
+ export let state : SpecialVisualizationState
let searchContents: string = ""
export let triggerSearch: UIEventSource = new UIEventSource(undefined)
onDestroy(
@@ -54,6 +61,7 @@
}
}
+
async function performSearch() {
try {
isRunning = true
@@ -64,7 +72,8 @@
if (searchContents === "") {
return
}
- const result = await Geocoding.Search(searchContents, bounds.data)
+ const result = await searcher.search(searchContents, { bbox: bounds.data, limit: 10 })
+ console.log("Results are", result)
if (result.length == 0) {
feedback = Translations.t.general.search.nothing.txt
focusOnSearch()
@@ -104,6 +113,16 @@
isRunning = false
}
}
+
+ let suggestions: GeoCodeResult[] = []
+
+ async function updateSuggestions(search){
+
+ suggestions = await searcher.suggest(search, {limit: 5})
+ }
+
+ $: updateSuggestions(searchContents)
+
@@ -133,3 +152,7 @@
+
+
+
+
diff --git a/src/UI/BigComponents/MoreScreen.ts b/src/UI/BigComponents/MoreScreen.ts
index 5531ee056..9ae7510eb 100644
--- a/src/UI/BigComponents/MoreScreen.ts
+++ b/src/UI/BigComponents/MoreScreen.ts
@@ -60,7 +60,7 @@ export default class MoreScreen {
if (search === undefined) {
return true
}
- search = Utils.RemoveDiacritics(search.toLocaleLowerCase())
+ search = Utils.RemoveDiacritics(search.toLocaleLowerCase()) // See #1729
if (search.length > 3 && layout.id.toLowerCase().indexOf(search) >= 0) {
return true
}
diff --git a/src/UI/BigComponents/ReverseGeocoding.svelte b/src/UI/BigComponents/ReverseGeocoding.svelte
index 2674fa2be..90e91b69f 100644
--- a/src/UI/BigComponents/ReverseGeocoding.svelte
+++ b/src/UI/BigComponents/ReverseGeocoding.svelte
@@ -3,7 +3,7 @@
* Shows the current address when shaken
**/
import Motion from "../../Sensors/Motion"
- import { Geocoding } from "../../Logic/Osm/Geocoding"
+ import { NominatimGeocoding } from "../../Logic/Geocoding/NominatimGeocoding"
import Hotkeys from "../Base/Hotkeys"
import Translations from "../i18n/Translations"
import Locale from "../i18n/Locale"
@@ -15,9 +15,11 @@
let lastDisplayed: Date = undefined
let currentLocation: string = undefined
+ let geocoder = new NominatimGeocoding()
+
async function displayLocation() {
lastDisplayed = new Date()
- let result = await Geocoding.reverse(
+ let result = await geocoder.reverseSearch(
mapProperties.location.data,
mapProperties.zoom.data,
Locale.language.data
diff --git a/src/UI/BigComponents/SearchResult.svelte b/src/UI/BigComponents/SearchResult.svelte
new file mode 100644
index 000000000..8cd67cd5c
--- /dev/null
+++ b/src/UI/BigComponents/SearchResult.svelte
@@ -0,0 +1,46 @@
+
+
diff --git a/src/UI/BigComponents/SearchResults.svelte b/src/UI/BigComponents/SearchResults.svelte
new file mode 100644
index 000000000..124e1545b
--- /dev/null
+++ b/src/UI/BigComponents/SearchResults.svelte
@@ -0,0 +1,27 @@
+
+
+{#if results.length > 0}
+
+
+
+ {#each results as entry (entry)}
+ close()} {entry} {state} />
+ {/each}
+
+
close()}>
+
+
+
+{/if}
diff --git a/src/UI/BigComponents/ThemeIntroPanel.svelte b/src/UI/BigComponents/ThemeIntroPanel.svelte
index d0c88a4a6..12aa75195 100644
--- a/src/UI/BigComponents/ThemeIntroPanel.svelte
+++ b/src/UI/BigComponents/ThemeIntroPanel.svelte
@@ -100,6 +100,8 @@
{selectedElement}
{triggerSearch}
geolocationState={state.geolocation.geolocationState}
+ searcher={state.geosearch}
+ {state}
/>