forked from MapComplete/MapComplete
Merge branch 'develop' into shrine_layer
This commit is contained in:
commit
50280bb072
18 changed files with 168 additions and 122 deletions
src
Logic
FeatureSource/Actors
Search
CombinedSearcher.tsGeocodingFeatureSource.tsGeocodingProvider.tsLocalElementSearch.tsNominatimGeocoding.ts
State
Models
UI
|
@ -76,7 +76,7 @@ export default class SaveFeatureSourceToLocalStorage {
|
|||
const storage = TileLocalStorage.construct<Feature[]>(backend, layername, maxCacheAge)
|
||||
this.storage = storage
|
||||
const singleTileSavers: Map<number, SingleTileSaver> = new Map<number, SingleTileSaver>()
|
||||
features.features.addCallbackAndRunD((features) => {
|
||||
features.features.stabilized(5000).addCallbackAndRunD((features) => {
|
||||
if (
|
||||
features.some((f) => {
|
||||
let totalPoints = 0
|
||||
|
@ -116,7 +116,7 @@ export default class SaveFeatureSourceToLocalStorage {
|
|||
tileSaver = new SingleTileSaver(src, featureProperties)
|
||||
singleTileSavers.set(tileIndex, tileSaver)
|
||||
}
|
||||
// Don't cache not-uploaded features yet - they'll be cached when the receive their id
|
||||
// Don't cache not-uploaded features yet - they'll be cached when they receive their id
|
||||
features = features.filter((f) => !f.properties.id.match(/(node|way)\/-[0-9]+/))
|
||||
tileSaver.saveFeatures(features)
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import UserRelatedState from "../Logic/State/UserRelatedState"
|
|||
import { Utils } from "../Utils"
|
||||
import Zoomcontrol from "../UI/Zoomcontrol"
|
||||
import { LocalStorageSource } from "../Logic/Web/LocalStorageSource"
|
||||
import { ProvidedImage } from "../Logic/ImageProviders/ImageProvider"
|
||||
|
||||
export type PageType = (typeof MenuState.pageNames)[number]
|
||||
|
||||
|
@ -27,7 +28,7 @@ export class MenuState {
|
|||
"favourites",
|
||||
"usersettings",
|
||||
"share",
|
||||
"menu",
|
||||
"menu"
|
||||
] as const
|
||||
|
||||
/**
|
||||
|
@ -38,6 +39,9 @@ export class MenuState {
|
|||
undefined
|
||||
)
|
||||
|
||||
public static readonly nearbyImagesFeature: UIEventSource<object> = new UIEventSource<object>(
|
||||
undefined
|
||||
)
|
||||
public readonly pageStates: Record<PageType, UIEventSource<boolean>>
|
||||
|
||||
public readonly highlightedLayerInFilters: UIEventSource<string> = new UIEventSource<string>(
|
||||
|
@ -45,6 +49,7 @@ export class MenuState {
|
|||
)
|
||||
public highlightedUserSetting: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||
private readonly _selectedElement: UIEventSource<any> | undefined
|
||||
private isClosingAll = false
|
||||
|
||||
constructor(selectedElement: UIEventSource<any> | undefined) {
|
||||
this._selectedElement = selectedElement
|
||||
|
@ -129,29 +134,49 @@ export class MenuState {
|
|||
* Returns 'true' if at least one menu was opened
|
||||
*/
|
||||
public closeAll(): boolean {
|
||||
console.log("Closing all")
|
||||
if (this.isClosingAll) {
|
||||
return true
|
||||
}
|
||||
this.isClosingAll = true
|
||||
const ps = this.pageStates
|
||||
if (ps.menu.data) {
|
||||
ps.menu.set(false)
|
||||
return true
|
||||
}
|
||||
try {
|
||||
|
||||
if (MenuState.previewedImage.data !== undefined) {
|
||||
MenuState.previewedImage.setData(undefined)
|
||||
return true
|
||||
}
|
||||
|
||||
for (const key in ps) {
|
||||
const toggle = ps[key]
|
||||
const wasOpen = toggle.data
|
||||
toggle.setData(false)
|
||||
if (wasOpen) {
|
||||
if (ps.menu.data) {
|
||||
ps.menu.set(false)
|
||||
return true
|
||||
}
|
||||
}
|
||||
if (this._selectedElement.data) {
|
||||
this._selectedElement.setData(undefined)
|
||||
return true
|
||||
|
||||
if (MenuState.previewedImage.data !== undefined) {
|
||||
MenuState.previewedImage.setData(undefined)
|
||||
return true
|
||||
}
|
||||
|
||||
if (MenuState.nearbyImagesFeature.data !== undefined) {
|
||||
MenuState.nearbyImagesFeature.setData(undefined)
|
||||
return true
|
||||
}
|
||||
for (const key in ps) {
|
||||
const toggle = ps[key]
|
||||
const wasOpen = toggle.data
|
||||
toggle.setData(false)
|
||||
if (wasOpen) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if (this._selectedElement.data) {
|
||||
this._selectedElement.setData(undefined)
|
||||
return true
|
||||
}
|
||||
} finally {
|
||||
this.isClosingAll = false
|
||||
}
|
||||
}
|
||||
|
||||
public setPreviewedImage(img?: Partial<ProvidedImage>) {
|
||||
if (img === undefined && !this.isClosingAll) {
|
||||
return
|
||||
}
|
||||
MenuState.previewedImage.setData(img)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ export class AvailableRasterLayers {
|
|||
availableLayersBboxes.map(
|
||||
(eliPolygons) => {
|
||||
const loc = location.data
|
||||
const lonlat: [number, number] = [loc.lon, loc.lat]
|
||||
const lonlat: [number, number] = [loc?.lon ?? 0, loc?.lat ?? 0]
|
||||
const matching: RasterLayerPolygon[] = eliPolygons.filter((eliPolygon) => {
|
||||
if (eliPolygon.geometry === null) {
|
||||
return true // global ELI-layer
|
||||
|
|
|
@ -14,6 +14,8 @@ export default class Hotkeys {
|
|||
}[]
|
||||
> = new UIEventSource([])
|
||||
|
||||
private static readonly seenKeys: Set<string> = new Set()
|
||||
|
||||
/**
|
||||
* Register a hotkey
|
||||
* @param key
|
||||
|
@ -51,6 +53,9 @@ export default class Hotkeys {
|
|||
}
|
||||
}
|
||||
|
||||
const keyString = JSON.stringify(key)
|
||||
this.seenKeys.add(keyString)
|
||||
|
||||
this._docs.data.push({ key, documentation, alsoTriggeredBy })
|
||||
this._docs.ping()
|
||||
if (Utils.runningFromConsole) {
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
<slot name="header" />
|
||||
</h1>
|
||||
{/if}
|
||||
<slot name="closebutton" />
|
||||
</svelte:fragment>
|
||||
<slot />
|
||||
{#if $$slots.footer}
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
import ImageOperations from "./ImageOperations.svelte"
|
||||
import Popup from "../Base/Popup.svelte"
|
||||
import { onDestroy } from "svelte"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import type { Feature, Point } from "geojson"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
|
@ -19,8 +18,9 @@
|
|||
import DotMenu from "../Base/DotMenu.svelte"
|
||||
import LoadingPlaceholder from "../Base/LoadingPlaceholder.svelte"
|
||||
import { MenuState } from "../../Models/MenuState"
|
||||
import ThemeViewState from "../../Models/ThemeViewState"
|
||||
|
||||
export let image: Partial<ProvidedImage>
|
||||
export let image: Partial<ProvidedImage> & { id: string; url: string }
|
||||
let fallbackImage: string = undefined
|
||||
if (image.provider === Mapillary.singleton) {
|
||||
fallbackImage = "./assets/svg/blocked.svg"
|
||||
|
@ -28,7 +28,7 @@
|
|||
|
||||
let imgEl: HTMLImageElement
|
||||
export let imgClass: string = undefined
|
||||
export let state: SpecialVisualizationState = undefined
|
||||
export let state: ThemeViewState = undefined
|
||||
export let attributionFormat: "minimal" | "medium" | "large" = "medium"
|
||||
let previewedImage: UIEventSource<Partial<ProvidedImage>> = MenuState.previewedImage
|
||||
export let canZoom = previewedImage !== undefined
|
||||
|
@ -36,9 +36,7 @@
|
|||
let showBigPreview = new UIEventSource(false)
|
||||
onDestroy(
|
||||
showBigPreview.addCallbackAndRun((shown) => {
|
||||
if (!shown) {
|
||||
previewedImage?.set(undefined)
|
||||
}
|
||||
state.guistate.setPreviewedImage(shown ? image : undefined)
|
||||
})
|
||||
)
|
||||
if (previewedImage) {
|
||||
|
|
|
@ -89,17 +89,6 @@
|
|||
imgClass="max-h-64 w-auto sm:h-32 md:h-64"
|
||||
attributionFormat="minimal"
|
||||
>
|
||||
<!--
|
||||
<div slot="preview-action" class="self-center" >
|
||||
<LoginToggle {state} silentFail={true}>
|
||||
{#if linkable}
|
||||
<label class="normal-background p-2 rounded-full pointer-events-auto">
|
||||
<input bind:checked={$isLinked} type="checkbox" />
|
||||
<SpecialTranslation t={t.link} {tags} {state} {layer} {feature} />
|
||||
</label>
|
||||
{/if}
|
||||
</LoginToggle>
|
||||
</div>-->
|
||||
</AttributedImage>
|
||||
<LoginToggle {state} silentFail={true}>
|
||||
{#if linkable}
|
||||
|
|
|
@ -1,22 +1,19 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { OsmTags } from "../../Models/OsmFeature"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import type { Feature } from "geojson"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import NearbyImages from "./NearbyImages.svelte"
|
||||
import { XCircleIcon } from "@babeard/svelte-heroicons/solid"
|
||||
import Camera_plus from "../../assets/svg/Camera_plus.svelte"
|
||||
import LoginToggle from "../Base/LoginToggle.svelte"
|
||||
import { ariaLabel } from "../../Utils/ariaLabel"
|
||||
import { Accordion, AccordionItem, Modal } from "flowbite-svelte"
|
||||
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
||||
import Popup from "../Base/Popup.svelte"
|
||||
import ThemeViewState from "../../Models/ThemeViewState"
|
||||
import { onDestroy } from "svelte"
|
||||
import { MenuState } from "../../Models/MenuState"
|
||||
import { CloseButton } from "flowbite-svelte"
|
||||
|
||||
export let tags: UIEventSource<OsmTags>
|
||||
export let state: SpecialVisualizationState
|
||||
export let state: ThemeViewState
|
||||
export let lon: number
|
||||
export let lat: number
|
||||
export let feature: Feature
|
||||
|
@ -27,6 +24,16 @@
|
|||
|
||||
let enableLogin = state.featureSwitches.featureSwitchEnableLogin
|
||||
export let shown = new UIEventSource(false)
|
||||
onDestroy(MenuState.nearbyImagesFeature.addCallback(something => {
|
||||
if (something !== feature) {
|
||||
shown.set(false)
|
||||
}
|
||||
}))
|
||||
onDestroy(shown.addCallbackAndRun(isShown => {
|
||||
if (isShown) {
|
||||
MenuState.nearbyImagesFeature.set(feature)
|
||||
}
|
||||
}))
|
||||
</script>
|
||||
|
||||
{#if enableLogin.data}
|
||||
|
@ -37,10 +44,9 @@
|
|||
>
|
||||
<Tr t={t.seeNearby} />
|
||||
</button>
|
||||
<Popup {shown} bodyPadding="p-4">
|
||||
<span slot="header">
|
||||
<Tr t={t.seeNearby} />
|
||||
</span>
|
||||
<Popup {shown} bodyPadding="p-4" dismissable={false}>
|
||||
<Tr slot="header" t={t.seeNearby} />
|
||||
<CloseButton slot="closebutton" on:click={() => shown?.set(false)} />
|
||||
<NearbyImages {tags} {state} {lon} {lat} {feature} {linkable} {layer} />
|
||||
</Popup>
|
||||
{/if}
|
||||
|
|
|
@ -24,13 +24,13 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
"dragRotate",
|
||||
"dragPan",
|
||||
"keyboard",
|
||||
"touchZoomRotate",
|
||||
"touchZoomRotate"
|
||||
]
|
||||
private static maplibre_zoom_handlers = [
|
||||
"scrollZoom",
|
||||
"boxZoom",
|
||||
"doubleClickZoom",
|
||||
"touchZoomRotate",
|
||||
"touchZoomRotate"
|
||||
]
|
||||
readonly location: UIEventSource<{ lon: number; lat: number }>
|
||||
private readonly isFlying = new UIEventSource(false)
|
||||
|
@ -44,14 +44,14 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
readonly lastClickLocation: Store<
|
||||
| undefined
|
||||
| {
|
||||
lon: number
|
||||
lat: number
|
||||
mode: "left" | "right" | "middle"
|
||||
/**
|
||||
* The nearest feature from a MapComplete layer
|
||||
*/
|
||||
nearestFeature?: Feature
|
||||
}
|
||||
lon: number
|
||||
lat: number
|
||||
mode: "left" | "right" | "middle"
|
||||
/**
|
||||
* The nearest feature from a MapComplete layer
|
||||
*/
|
||||
nearestFeature?: Feature
|
||||
}
|
||||
>
|
||||
readonly minzoom: UIEventSource<number>
|
||||
readonly maxzoom: UIEventSource<number>
|
||||
|
@ -141,7 +141,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
const features = map
|
||||
.queryRenderedFeatures([
|
||||
[point.x - buffer, point.y - buffer],
|
||||
[point.x + buffer, point.y + buffer],
|
||||
[point.x + buffer, point.y + buffer]
|
||||
])
|
||||
.filter((f) => f.source.startsWith("mapcomplete_"))
|
||||
if (features.length === 1) {
|
||||
|
@ -281,9 +281,9 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
return {
|
||||
map: mlmap,
|
||||
ui: new SvelteUIElement(MaplibreMap, {
|
||||
map: mlmap,
|
||||
map: mlmap
|
||||
}),
|
||||
mapproperties: new MapLibreAdaptor(mlmap),
|
||||
mapproperties: new MapLibreAdaptor(mlmap)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -347,7 +347,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
) {
|
||||
const event = {
|
||||
date: new Date(),
|
||||
key: key,
|
||||
key: key
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._onKeyNavigation.length; i++) {
|
||||
|
@ -536,7 +536,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
const bounds = map.getBounds()
|
||||
const bbox = new BBox([
|
||||
[bounds.getEast(), bounds.getNorth()],
|
||||
[bounds.getWest(), bounds.getSouth()],
|
||||
[bounds.getWest(), bounds.getSouth()]
|
||||
])
|
||||
if (this.bounds.data === undefined || !isSetup) {
|
||||
this.bounds.setData(bbox)
|
||||
|
@ -611,8 +611,11 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
if (!map) {
|
||||
return
|
||||
}
|
||||
console.log("Bounds are", bbox?.asGeometry())
|
||||
if (bbox) {
|
||||
map?.setMaxBounds(bbox.toLngLat())
|
||||
if (GeoOperations.surfaceAreaInSqMeters(bbox.asGeojsonCached()) > 1) {
|
||||
map?.setMaxBounds(bbox.toLngLat())
|
||||
}
|
||||
} else {
|
||||
map?.setMaxBounds(null)
|
||||
}
|
||||
|
@ -730,14 +733,14 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
type: "raster-dem",
|
||||
url:
|
||||
"https://api.maptiler.com/tiles/terrain-rgb/tiles.json?key=" +
|
||||
Constants.maptilerApiKey,
|
||||
Constants.maptilerApiKey
|
||||
})
|
||||
try {
|
||||
while (!map?.isStyleLoaded()) {
|
||||
await Utils.waitFor(250)
|
||||
}
|
||||
map.setTerrain({
|
||||
source: id,
|
||||
source: id
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
@ -762,7 +765,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
if (this.scaleControl === undefined) {
|
||||
this.scaleControl = new ScaleControl({
|
||||
maxWidth: 100,
|
||||
unit: "metric",
|
||||
unit: "metric"
|
||||
})
|
||||
}
|
||||
if (!map.hasControl(this.scaleControl)) {
|
||||
|
@ -775,7 +778,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
window.requestAnimationFrame(() => {
|
||||
this._maplibreMap.data?.flyTo({
|
||||
zoom,
|
||||
center: [lon, lat],
|
||||
center: [lon, lat]
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -590,7 +590,11 @@ export default class ShowDataLayer {
|
|||
}
|
||||
const bbox = BBox.bboxAroundAll(features.map(BBox.get))
|
||||
window.requestAnimationFrame(() => {
|
||||
map.resize()
|
||||
try {
|
||||
map.resize()
|
||||
} catch (e) {
|
||||
console.error("Could not resize the map in preparation of zoomToCurrentFeatures; the error is:", e)
|
||||
}
|
||||
map.fitBounds(bbox.toLngLat(), {
|
||||
padding: { top: 10, bottom: 10, left: 10, right: 10 },
|
||||
animate: false,
|
||||
|
|
|
@ -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
Reference in a new issue