Feature: allow to move and snap to a layer, fix #2120

This commit is contained in:
Pieter Vander Vennet 2024-09-04 00:07:23 +02:00
parent eb89427bfc
commit fdedb75954
34 changed files with 824 additions and 301 deletions

View file

@ -10,7 +10,6 @@
import type { MapProperties } from "../../Models/MapProperties"
import type { Feature, Point } from "geojson"
import { GeoOperations } from "../../Logic/GeoOperations"
import LocationInput from "../InputElement/Helpers/LocationInput.svelte"
import OpenBackgroundSelectorButton from "../BigComponents/OpenBackgroundSelectorButton.svelte"
import Geosearch from "../BigComponents/Geosearch.svelte"
import If from "../Base/If.svelte"
@ -21,6 +20,8 @@
import ChevronLeft from "@babeard/svelte-heroicons/solid/ChevronLeft"
import ThemeViewState from "../../Models/ThemeViewState"
import Icon from "../Map/Icon.svelte"
import NewPointLocationInput from "../BigComponents/NewPointLocationInput.svelte"
import type { WayId } from "../../Models/OsmFeature"
export let state: ThemeViewState
@ -36,20 +37,22 @@
let newLocation = new UIEventSource<{ lon: number; lat: number }>(undefined)
function initMapProperties() {
let snappedTo = new UIEventSource<WayId | undefined>(undefined)
function initMapProperties(reason: MoveReason) {
return <any>{
allowMoving: new UIEventSource(true),
allowRotating: new UIEventSource(false),
allowZooming: new UIEventSource(true),
bounds: new UIEventSource(undefined),
location: new UIEventSource({ lon, lat }),
minzoom: new UIEventSource($reason.minZoom),
minzoom: new UIEventSource(reason.minZoom),
rasterLayer: state.mapProperties.rasterLayer,
zoom: new UIEventSource($reason?.startZoom ?? 16),
zoom: new UIEventSource(reason?.startZoom ?? 16),
}
}
let moveWizardState = new MoveWizardState(id, layer.allowMove, state)
let moveWizardState = new MoveWizardState(id, layer.allowMove, layer, state)
if (moveWizardState.reasons.length === 1) {
reason.setData(moveWizardState.reasons[0])
}
@ -57,8 +60,8 @@
let currentMapProperties: MapProperties = undefined
</script>
<LoginToggle {state}>
{#if moveWizardState.reasons.length > 0}
{#if moveWizardState.reasons.length > 0}
<LoginToggle {state}>
{#if $notAllowed}
<div class="m-2 flex rounded-lg bg-gray-200 p-2">
<Move_not_allowed class="m-2 h-8 w-8" />
@ -81,7 +84,7 @@
<span class="flex flex-col p-2">
{#if currentStep === "reason" && moveWizardState.reasons.length > 1}
{#each moveWizardState.reasons as reasonSpec}
<button
<button class="flex justify-start"
on:click={() => {
reason.setData(reasonSpec)
currentStep = "pick_location"
@ -93,10 +96,16 @@
{/each}
{:else if currentStep === "pick_location" || currentStep === "reason"}
<div class="relative h-64 w-full">
<LocationInput
mapProperties={(currentMapProperties = initMapProperties())}
<NewPointLocationInput
mapProperties={(currentMapProperties = initMapProperties($reason))}
value={newLocation}
initialCoordinate={{ lon, lat }}
{state}
coordinate={{ lon, lat }}
{snappedTo}
maxSnapDistance={$reason.maxSnapDistance ?? 5}
snapToLayers={$reason.snapTo}
targetLayer={layer}
dontShow={[id]}
/>
<div class="absolute bottom-0 left-0">
<OpenBackgroundSelectorButton {state} />
@ -116,7 +125,7 @@
<button
class="primary w-full"
on:click={() => {
moveWizardState.moveFeature(newLocation.data, reason.data, featureToMove)
moveWizardState.moveFeature(newLocation.data, snappedTo.data, reason.data, featureToMove)
currentStep = "moved"
}}
>
@ -155,5 +164,5 @@
</span>
</AccordionSingle>
{/if}
{/if}
</LoginToggle>
</LoginToggle>
{/if}

View file

@ -12,6 +12,8 @@ import { Feature, Point } from "geojson"
import SvelteUIElement from "../Base/SvelteUIElement"
import Relocation from "../../assets/svg/Relocation.svelte"
import Location from "../../assets/svg/Location.svelte"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { WayId } from "../../Models/OsmFeature"
export interface MoveReason {
text: Translation | string
@ -24,25 +26,40 @@ export interface MoveReason {
startZoom: number
minZoom: number
eraseAddressFields: false | boolean
/**
* Snap to these layers
*/
snapTo?: string[]
maxSnapDistance?: number
}
export class MoveWizardState {
public readonly reasons: ReadonlyArray<MoveReason>
public readonly moveDisallowedReason = new UIEventSource<Translation>(undefined)
private readonly layer: LayerConfig
private readonly _state: SpecialVisualizationState
private readonly featureToMoveId: string
constructor(id: string, options: MoveConfig, state: SpecialVisualizationState) {
/**
* Initialize the movestate for the feature of the given ID
* @param id of the feature that should be moved
* @param options
* @param layer
* @param state
*/
constructor(id: string, options: MoveConfig, layer: LayerConfig, state: SpecialVisualizationState) {
this.layer = layer
this._state = state
this.reasons = MoveWizardState.initReasons(options)
this.featureToMoveId = id
this.reasons = this.initReasons(options)
if (this.reasons.length > 0) {
this.checkIsAllowed(id)
}
}
private static initReasons(options: MoveConfig): MoveReason[] {
private initReasons(options: MoveConfig): MoveReason[] {
const t = Translations.t.move
const reasons: MoveReason[] = []
if (options.enableRelocation) {
reasons.push({
@ -72,20 +89,52 @@ export class MoveWizardState {
eraseAddressFields: false,
})
}
const tags = this._state.featureProperties.getStore(this.featureToMoveId).data
const matchingPresets = this.layer.presets.filter(preset => preset.preciseInput.snapToLayers && new And(preset.tags).matchesProperties(tags))
const matchingPreset = matchingPresets.flatMap(pr => pr.preciseInput?.snapToLayers)
for (const layerId of matchingPreset) {
const snapOntoLayer = this._state.layout.getLayer(layerId)
const text = <Translation> t.reasons.reasonSnapTo.PartialSubsTr("name", snapOntoLayer.snapName)
reasons.push({
text,
invitingText: text,
icon: "snap",
changesetCommentValue: "snap",
lockBounds: true,
includeSearch: false,
background: "photo",
startZoom: 19,
minZoom: 16,
eraseAddressFields: false,
snapTo: [snapOntoLayer.id],
maxSnapDistance: 5,
})
}
return reasons
}
public async moveFeature(
loc: { lon: number; lat: number },
snappedTo: WayId,
reason: MoveReason,
featureToMove: Feature<Point>
featureToMove: Feature<Point>,
) {
const state = this._state
if(snappedTo !== undefined){
this.moveDisallowedReason.set(Translations.t.move.partOfAWay)
}
await state.changes.applyAction(
new ChangeLocationAction(featureToMove.properties.id, [loc.lon, loc.lat], {
reason: reason.changesetCommentValue,
theme: state.layout.id,
})
new ChangeLocationAction(state,
featureToMove.properties.id,
[loc.lon, loc.lat],
snappedTo,
{
reason: reason.changesetCommentValue,
theme: state.layout.id,
}),
)
featureToMove.properties._lat = loc.lat
featureToMove.properties._lon = loc.lon
@ -104,8 +153,8 @@ export class MoveWizardState {
{
changeType: "relocated",
theme: state.layout.id,
}
)
},
),
)
}