forked from MapComplete/MapComplete
Fix: tweaking addNewPoint-flow
This commit is contained in:
parent
63ffa11238
commit
d0e0abdece
8 changed files with 149 additions and 62 deletions
|
@ -701,6 +701,20 @@ export class GeoOperations {
|
|||
return turf.bearing(a, b)
|
||||
}
|
||||
|
||||
public static along(a: Coord, b: Coord, distanceMeter: number): Coord {
|
||||
return turf.along(
|
||||
<any> {
|
||||
type:"Feature",
|
||||
geometry:{
|
||||
type:"LineString",
|
||||
coordinates: [a, b]
|
||||
}
|
||||
}, distanceMeter, {units: "meters"}
|
||||
|
||||
).geometry.coordinates
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns 'true' if one feature contains the other feature
|
||||
*
|
||||
|
|
|
@ -31,14 +31,11 @@ export default interface LineRenderingConfigJson {
|
|||
*/
|
||||
lineCap?: "round" | "square" | "butt" | string | TagRenderingConfigJson
|
||||
|
||||
/**
|
||||
* Whether or not to fill polygons
|
||||
*/
|
||||
fill?: "yes" | "no" | TagRenderingConfigJson
|
||||
|
||||
/**
|
||||
* The color to fill a polygon with.
|
||||
* If undefined, this will be slightly more opaque version of the stroke line
|
||||
* If undefined, this will be slightly more opaque version of the stroke line.
|
||||
* Use '#00000000' to make the fill invisible
|
||||
*/
|
||||
fillColor?: string | TagRenderingConfigJson
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import type {MapProperties} from "../../Models/MapProperties";
|
||||
import ShowDataLayer from "../Map/ShowDataLayer";
|
||||
import type {FeatureSource, FeatureSourceForLayer} from "../../Logic/FeatureSource/FeatureSource";
|
||||
|
||||
import SnappingFeatureSource from "../../Logic/FeatureSource/Sources/SnappingFeatureSource";
|
||||
import FeatureSourceMerger from "../../Logic/FeatureSource/Sources/FeatureSourceMerger";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
|
@ -31,6 +30,7 @@
|
|||
export let maxSnapDistance: number = undefined;
|
||||
|
||||
export let snappedTo: UIEventSource<string | undefined>;
|
||||
|
||||
export let value: UIEventSource<{ lon: number, lat: number }>;
|
||||
if (value.data === undefined) {
|
||||
value.setData(coordinate);
|
||||
|
@ -39,7 +39,8 @@
|
|||
let preciseLocation: UIEventSource<{ lon: number, lat: number }> = new UIEventSource<{
|
||||
lon: number;
|
||||
lat: number
|
||||
}>(coordinate);
|
||||
}>(undefined);
|
||||
|
||||
const xyz = Tiles.embedded_tile(coordinate.lat, coordinate.lon, 16);
|
||||
const map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined);
|
||||
let initialMapProperties: Partial<MapProperties> = {
|
||||
|
@ -52,14 +53,19 @@
|
|||
bounds: new UIEventSource<BBox>(undefined),
|
||||
allowMoving: new UIEventSource<boolean>(true),
|
||||
allowZooming: new UIEventSource<boolean>(true),
|
||||
minzoom: new UIEventSource<number>(18)
|
||||
minzoom: new UIEventSource<number>(18),
|
||||
rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer)
|
||||
};
|
||||
|
||||
initialMapProperties.bounds.addCallbackAndRunD((bounds: BBox) => {
|
||||
const max = bounds.pad(3).squarify();
|
||||
initialMapProperties.maxbounds.setData(max);
|
||||
return true; // unregister
|
||||
});
|
||||
|
||||
const featuresForLayer = state.perLayer.get(targetLayer.id)
|
||||
if(featuresForLayer){
|
||||
new ShowDataLayer(map, {
|
||||
layer: targetLayer,
|
||||
features: featuresForLayer
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
if (snapToLayers?.length > 0) {
|
||||
|
||||
|
@ -99,4 +105,4 @@
|
|||
|
||||
|
||||
<LocationInput {map} mapProperties={initialMapProperties}
|
||||
value={preciseLocation}></LocationInput>
|
||||
value={preciseLocation} initialCoordinate={{...coordinate}} maxDistanceInMeters=50 />
|
||||
|
|
|
@ -1,38 +1,82 @@
|
|||
<script lang="ts">
|
||||
import { Store, UIEventSource } from "../../../Logic/UIEventSource";
|
||||
import type { MapProperties } from "../../../Models/MapProperties";
|
||||
import { Map as MlMap } from "maplibre-gl";
|
||||
import { MapLibreAdaptor } from "../../Map/MapLibreAdaptor";
|
||||
import MaplibreMap from "../../Map/MaplibreMap.svelte";
|
||||
import DragInvitation from "../../Base/DragInvitation.svelte";
|
||||
import {Store, UIEventSource} from "../../../Logic/UIEventSource";
|
||||
import type {MapProperties} from "../../../Models/MapProperties";
|
||||
import {Map as MlMap} from "maplibre-gl";
|
||||
import {MapLibreAdaptor} from "../../Map/MapLibreAdaptor";
|
||||
import MaplibreMap from "../../Map/MaplibreMap.svelte";
|
||||
import DragInvitation from "../../Base/DragInvitation.svelte";
|
||||
import {GeoOperations} from "../../../Logic/GeoOperations";
|
||||
import ShowDataLayer from "../../Map/ShowDataLayer";
|
||||
import * as boundsdisplay from "../../../assets/generated/layers/range.json"
|
||||
import StaticFeatureSource from "../../../Logic/FeatureSource/Sources/StaticFeatureSource";
|
||||
import * as turf from "@turf/turf"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
||||
import {onDestroy} from "svelte";
|
||||
|
||||
/**
|
||||
* A visualisation to pick a direction on a map background
|
||||
*/
|
||||
export let value: UIEventSource<{lon: number, lat: number}>;
|
||||
export let mapProperties: Partial<MapProperties> & { readonly location: UIEventSource<{ lon: number; lat: number }> } = undefined;
|
||||
/**
|
||||
* Called when setup is done, can be used to add more layers to the map
|
||||
*/
|
||||
export let onCreated : (value: Store<{lon: number, lat: number}> , map: Store<MlMap>, mapProperties: MapProperties ) => void = undefined
|
||||
|
||||
export let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined);
|
||||
let mla = new MapLibreAdaptor(map, mapProperties);
|
||||
mapProperties.location.syncWith(value)
|
||||
if(onCreated){
|
||||
onCreated(value, map, mla)
|
||||
}
|
||||
/**
|
||||
* A visualisation to pick a direction on a map background
|
||||
*/
|
||||
export let value: UIEventSource<{ lon: number, lat: number }>;
|
||||
export let initialCoordinate : {lon: number, lat :number}
|
||||
initialCoordinate = initialCoordinate ?? value .data
|
||||
export let maxDistanceInMeters: number = undefined
|
||||
export let mapProperties: Partial<MapProperties> & {
|
||||
readonly location: UIEventSource<{ lon: number; lat: number }>
|
||||
} = undefined;
|
||||
/**
|
||||
* Called when setup is done, can be used to add more layers to the map
|
||||
*/
|
||||
export let onCreated: (value: Store<{
|
||||
lon: number,
|
||||
lat: number
|
||||
}>, map: Store<MlMap>, mapProperties: MapProperties) => void = undefined
|
||||
|
||||
export let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined);
|
||||
let mla = new MapLibreAdaptor(map, mapProperties);
|
||||
mapProperties.location.syncWith(value)
|
||||
if (onCreated) {
|
||||
onCreated(value, map, mla)
|
||||
}
|
||||
|
||||
let rangeIsShown = false
|
||||
if (maxDistanceInMeters) {
|
||||
onDestroy(mla.location.addCallbackD(newLocation => {
|
||||
const l = [newLocation.lon, newLocation.lat]
|
||||
const c: [number, number] = [initialCoordinate.lon, initialCoordinate.lat]
|
||||
const d = GeoOperations.distanceBetween(l, c)
|
||||
console.log("distance is", d, l, c)
|
||||
if (d <= maxDistanceInMeters) {
|
||||
return
|
||||
}
|
||||
// This is too far away - let's move back
|
||||
const correctLocation = GeoOperations.along(c, l, maxDistanceInMeters - 10)
|
||||
window.setTimeout(() => {
|
||||
mla.location.setData({lon: correctLocation[0], lat: correctLocation[1]})
|
||||
}, 25)
|
||||
|
||||
if (!rangeIsShown) {
|
||||
|
||||
new ShowDataLayer(map, {
|
||||
layer: new LayerConfig(boundsdisplay),
|
||||
features: new StaticFeatureSource(
|
||||
[turf.circle(c, maxDistanceInMeters, {units: "meters", properties: {"range":"yes", id: "0"}}, )]
|
||||
)
|
||||
})
|
||||
rangeIsShown = true
|
||||
}
|
||||
}))
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative h-full min-h-32 cursor-pointer overflow-hidden">
|
||||
<div class="w-full h-full absolute top-0 left-0 cursor-pointer">
|
||||
<MaplibreMap {map} attribution={false}></MaplibreMap>
|
||||
</div>
|
||||
<div class="w-full h-full absolute top-0 left-0 cursor-pointer">
|
||||
<MaplibreMap {map}/>
|
||||
</div>
|
||||
|
||||
<div class="w-full h-full absolute top-0 left-0 p-8 pointer-events-none opacity-50 flex items-center">
|
||||
<img src="./assets/svg/move-arrows.svg" class="h-full max-h-24"/>
|
||||
</div>
|
||||
|
||||
<DragInvitation hideSignal={mla.location.stabilized(3000)}></DragInvitation>
|
||||
<div class="w-full h-full absolute top-0 left-0 p-8 pointer-events-none opacity-50 flex items-center">
|
||||
<img class="h-full max-h-24" src="./assets/svg/move-arrows.svg"/>
|
||||
</div>
|
||||
|
||||
<DragInvitation hideSignal={mla.location.stabilized(3000)}></DragInvitation>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -228,30 +228,46 @@ class LineRenderingLayer {
|
|||
features.features.addCallbackAndRunD(() => self.update(features.features))
|
||||
}
|
||||
|
||||
public destruct(): void {
|
||||
this._map.removeLayer(this._layername + "_polygon")
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the feature-state for maplibre
|
||||
* @param properties
|
||||
* @private
|
||||
*/
|
||||
private calculatePropsFor(
|
||||
properties: Record<string, string>
|
||||
): Partial<Record<typeof LineRenderingLayer.lineConfigKeys[number], string>> {
|
||||
const calculatedProps = {}
|
||||
const config = this._config
|
||||
|
||||
const calculatedProps: Record<string, string | number> = {}
|
||||
for (const key of LineRenderingLayer.lineConfigKeys) {
|
||||
calculatedProps[key] = config[key]?.GetRenderValue(properties)?.Subs(properties).txt
|
||||
}
|
||||
calculatedProps.fillColor = calculatedProps.fillColor ?? calculatedProps.lineColor
|
||||
|
||||
for (const key of LineRenderingLayer.lineConfigKeysColor) {
|
||||
let v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt
|
||||
let v = <string>calculatedProps[key]
|
||||
if (v === undefined) {
|
||||
continue
|
||||
}
|
||||
if (v.length == 9 && v.startsWith("#")) {
|
||||
// This includes opacity
|
||||
calculatedProps[key + "-opacity"] = parseInt(v.substring(7), 16) / 256
|
||||
calculatedProps[`${key}-opacity`] = parseInt(v.substring(7), 16) / 256
|
||||
v = v.substring(0, 7)
|
||||
if (v.length == 9 && v.startsWith("#")) {
|
||||
// This includes opacity
|
||||
calculatedProps[`${key}-opacity`] = parseInt(v.substring(7), 16) / 256
|
||||
v = v.substring(0, 7)
|
||||
}
|
||||
}
|
||||
calculatedProps[key] = v
|
||||
}
|
||||
calculatedProps["fillColor-opacity"] = calculatedProps["fillColor-opacity"] ?? 0.1
|
||||
|
||||
for (const key of LineRenderingLayer.lineConfigKeysNumber) {
|
||||
const v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt
|
||||
calculatedProps[key] = Number(v)
|
||||
calculatedProps[key] = Number(calculatedProps[key])
|
||||
}
|
||||
|
||||
return calculatedProps
|
||||
|
@ -303,6 +319,7 @@ class LineRenderingLayer {
|
|||
this._onClick(e.features[0])
|
||||
})
|
||||
const polylayer = this._layername + "_polygon"
|
||||
|
||||
map.addLayer({
|
||||
source: this._layername,
|
||||
id: polylayer,
|
||||
|
@ -311,13 +328,15 @@ class LineRenderingLayer {
|
|||
layout: {},
|
||||
paint: {
|
||||
"fill-color": ["feature-state", "fillColor"],
|
||||
"fill-opacity": 0.1,
|
||||
"fill-opacity": ["feature-state", "fillColor-opacity"],
|
||||
},
|
||||
})
|
||||
map.on("click", polylayer, (e) => {
|
||||
e.originalEvent["consumed"] = true
|
||||
this._onClick(e.features[0])
|
||||
})
|
||||
if (this._onClick) {
|
||||
map.on("click", polylayer, (e) => {
|
||||
e.originalEvent["consumed"] = true
|
||||
this._onClick(e.features[0])
|
||||
})
|
||||
}
|
||||
|
||||
this._visibility?.addCallbackAndRunD((visible) => {
|
||||
try {
|
||||
|
@ -388,6 +407,8 @@ export default class ShowDataLayer {
|
|||
drawLines?: true | boolean
|
||||
}
|
||||
|
||||
private onDestroy: (() => void)[] = []
|
||||
|
||||
constructor(
|
||||
map: Store<MlMap>,
|
||||
options: ShowDataLayerOptions & {
|
||||
|
@ -399,7 +420,7 @@ export default class ShowDataLayer {
|
|||
this._map = map
|
||||
this._options = options
|
||||
const self = this
|
||||
map.addCallbackAndRunD((map) => self.initDrawFeatures(map))
|
||||
this.onDestroy.push(map.addCallbackAndRunD((map) => self.initDrawFeatures(map)))
|
||||
}
|
||||
|
||||
public static showMultipleLayers(
|
||||
|
@ -437,6 +458,10 @@ export default class ShowDataLayer {
|
|||
})
|
||||
}
|
||||
|
||||
public destruct() {
|
||||
|
||||
}
|
||||
|
||||
private zoomToCurrentFeatures(map: MlMap) {
|
||||
if (this._options.zoomToFeatures) {
|
||||
const features = this._options.features.features.data
|
||||
|
@ -458,7 +483,7 @@ export default class ShowDataLayer {
|
|||
if (this._options.drawLines !== false) {
|
||||
for (let i = 0; i < this._options.layer.lineRendering.length; i++) {
|
||||
const lineRenderingConfig = this._options.layer.lineRendering[i]
|
||||
new LineRenderingLayer(
|
||||
const l = new LineRenderingLayer(
|
||||
map,
|
||||
features,
|
||||
this._options.layer.id + "_linerendering_" + i,
|
||||
|
@ -467,6 +492,7 @@ export default class ShowDataLayer {
|
|||
fetchStore,
|
||||
onClick
|
||||
)
|
||||
this.onDestroy.push(l.destruct)
|
||||
}
|
||||
}
|
||||
if (this._options.drawMarkers !== false) {
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
import BackButton from "../../Base/BackButton.svelte";
|
||||
import ToSvelte from "../../Base/ToSvelte.svelte";
|
||||
import Svg from "../../../Svg";
|
||||
import RasterLayerOverview from "../../Map/RasterLayerOverview.svelte";
|
||||
|
||||
export let coordinate: { lon: number, lat: number };
|
||||
export let state: SpecialVisualizationState;
|
||||
|
@ -281,6 +282,9 @@
|
|||
</div>
|
||||
</NextButton>
|
||||
</div>
|
||||
|
||||
<RasterLayerOverview />
|
||||
|
||||
{:else}
|
||||
<Loading>Creating point...</Loading>
|
||||
{/if}
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
],
|
||||
"render": "<div class='relative'> <img src='./assets/svg/add_pin.svg' class='absolute' style='height: 50px'> <div class='absolute top-0 left-0 rounded-full overflow-hidden noselect' style='width: 40px; height: 40px'><div class='flex slide min-w-min' style='animation: slide linear {number_of_presets}s infinite; width: calc( (1 + {number_of_presets}) * 40px ); height: 40px'>{renderings}{first_preset}</div></div></div>"
|
||||
},
|
||||
"labelCssClasses": "text-sm min-w-min pl-1 pr-1 rounded-3xl text-white opacity-65 whitespace-nowrap block-ruby",
|
||||
"labelCssClasses": "text-sm min-w-min px-2 rounded-full text-white opacity-65 whitespace-nowrap block-ruby",
|
||||
"labelCss": "background: #00000088",
|
||||
"label": {
|
||||
"render": {
|
||||
|
|
|
@ -1245,10 +1245,6 @@ video {
|
|||
resize: both;
|
||||
}
|
||||
|
||||
.grid-cols-2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.grid-cols-1 {
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue