forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			179 lines
		
	
	
		
			No EOL
		
	
	
		
			6.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			179 lines
		
	
	
		
			No EOL
		
	
	
		
			6.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import {InputElement} from "./InputElement";
 | 
						|
import {UIEventSource} from "../../Logic/UIEventSource";
 | 
						|
import Combine from "../Base/Combine";
 | 
						|
import Svg from "../../Svg";
 | 
						|
import {Utils} from "../../Utils";
 | 
						|
import Loc from "../../Models/Loc";
 | 
						|
import {GeoOperations} from "../../Logic/GeoOperations";
 | 
						|
import Minimap from "../Base/Minimap";
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Selects a length after clicking on the minimap, in meters
 | 
						|
 */
 | 
						|
export default class LengthInput extends InputElement<string> {
 | 
						|
    public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
 | 
						|
    private readonly _location: UIEventSource<Loc>;
 | 
						|
    private readonly value: UIEventSource<string>;
 | 
						|
    private background;
 | 
						|
 | 
						|
    constructor(mapBackground: UIEventSource<any>,
 | 
						|
                location: UIEventSource<Loc>,
 | 
						|
                value?: UIEventSource<string>) {
 | 
						|
        super();
 | 
						|
        this._location = location;
 | 
						|
        this.value = value ?? new UIEventSource<string>(undefined);
 | 
						|
        this.background = mapBackground;
 | 
						|
        this.SetClass("block")
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
    GetValue(): UIEventSource<string> {
 | 
						|
        return this.value;
 | 
						|
    }
 | 
						|
 | 
						|
    IsValid(str: string): boolean {
 | 
						|
        const t = Number(str)
 | 
						|
        return !isNaN(t) && t >= 0 && t <= 360;
 | 
						|
    }
 | 
						|
 | 
						|
    protected InnerConstructElement(): HTMLElement {
 | 
						|
        // @ts-ignore
 | 
						|
        let map = undefined
 | 
						|
        if (!Utils.runningFromConsole) {
 | 
						|
            map = Minimap.createMiniMap({
 | 
						|
                background: this.background,
 | 
						|
                allowMoving: false,
 | 
						|
                location: this._location,
 | 
						|
                attribution:true,
 | 
						|
                leafletOptions: {
 | 
						|
                    tap: true
 | 
						|
                }
 | 
						|
            })
 | 
						|
        }
 | 
						|
        const element = new Combine([
 | 
						|
            new Combine([Svg.length_crosshair_svg().SetStyle(
 | 
						|
                `position: absolute;top: 0;left: 0;transform:rotate(${this.value.data ?? 0}deg);`)
 | 
						|
            ])
 | 
						|
                .SetClass("block length-crosshair-svg relative")
 | 
						|
                .SetStyle("z-index: 1000; visibility: hidden"),
 | 
						|
            map?.SetClass("w-full h-full block absolute top-0 left-O overflow-hidden"),
 | 
						|
        ])
 | 
						|
            .SetClass("relative block bg-white border border-black rounded-3xl overflow-hidden")
 | 
						|
            .ConstructElement()
 | 
						|
 | 
						|
 | 
						|
        this.RegisterTriggers(element, map?.leafletMap)
 | 
						|
        element.style.overflow = "hidden"
 | 
						|
        element.style.display = "block"
 | 
						|
 | 
						|
        return element
 | 
						|
    }
 | 
						|
 | 
						|
    private RegisterTriggers(htmlElement: HTMLElement, leafletMap: UIEventSource<L.Map>) {
 | 
						|
 | 
						|
        let firstClickXY: [number, number] = undefined
 | 
						|
        let lastClickXY: [number, number] = undefined
 | 
						|
        const self = this;
 | 
						|
 | 
						|
 | 
						|
        function onPosChange(x: number, y: number, isDown: boolean, isUp?: boolean) {
 | 
						|
            if (x === undefined || y === undefined) {
 | 
						|
                // Touch end
 | 
						|
                firstClickXY = undefined;
 | 
						|
                lastClickXY = undefined;
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            const rect = htmlElement.getBoundingClientRect();
 | 
						|
            // From the central part of location
 | 
						|
            const dx = x - rect.left;
 | 
						|
            const dy = y - rect.top;
 | 
						|
            if (isDown) {
 | 
						|
                if (lastClickXY === undefined && firstClickXY === undefined) {
 | 
						|
                    firstClickXY = [dx, dy];
 | 
						|
                } else if (firstClickXY !== undefined && lastClickXY === undefined) {
 | 
						|
                    lastClickXY = [dx, dy]
 | 
						|
                } else if (firstClickXY !== undefined && lastClickXY !== undefined) {
 | 
						|
                    // we measure again
 | 
						|
                    firstClickXY = [dx, dy]
 | 
						|
                    lastClickXY = undefined;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            if (isUp) {
 | 
						|
                const distance = Math.sqrt((dy - firstClickXY[1]) * (dy - firstClickXY[1]) + (dx - firstClickXY[0]) * (dx - firstClickXY[0]))
 | 
						|
                if (distance > 15) {
 | 
						|
                    lastClickXY = [dx, dy]
 | 
						|
                }
 | 
						|
 | 
						|
 | 
						|
            } else if (lastClickXY !== undefined) {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
 | 
						|
            const measurementCrosshair = htmlElement.getElementsByClassName("length-crosshair-svg")[0] as HTMLElement
 | 
						|
 | 
						|
            const measurementCrosshairInner: HTMLElement = <HTMLElement>measurementCrosshair.firstChild
 | 
						|
            if (firstClickXY === undefined) {
 | 
						|
                measurementCrosshair.style.visibility = "hidden"
 | 
						|
            } else {
 | 
						|
                measurementCrosshair.style.visibility = "unset"
 | 
						|
                measurementCrosshair.style.left = firstClickXY[0] + "px";
 | 
						|
                measurementCrosshair.style.top = firstClickXY[1] + "px"
 | 
						|
 | 
						|
                const angle = 180 * Math.atan2(firstClickXY[1] - dy, firstClickXY[0] - dx) / Math.PI;
 | 
						|
                const angleGeo = (angle + 270) % 360
 | 
						|
                measurementCrosshairInner.style.transform = `rotate(${angleGeo}deg)`;
 | 
						|
 | 
						|
                const distance = Math.sqrt((dy - firstClickXY[1]) * (dy - firstClickXY[1]) + (dx - firstClickXY[0]) * (dx - firstClickXY[0]))
 | 
						|
                measurementCrosshairInner.style.width = (distance * 2) + "px"
 | 
						|
                measurementCrosshairInner.style.marginLeft = -distance + "px"
 | 
						|
                measurementCrosshairInner.style.marginTop = -distance + "px"
 | 
						|
 | 
						|
 | 
						|
                const leaflet = leafletMap?.data
 | 
						|
                if (leaflet) {
 | 
						|
                    const first = leaflet.layerPointToLatLng(firstClickXY)
 | 
						|
                    const last = leaflet.layerPointToLatLng([dx, dy])
 | 
						|
                    const geoDist = Math.floor(GeoOperations.distanceBetween([first.lng, first.lat], [last.lng, last.lat]) * 10000) / 10
 | 
						|
                    self.value.setData("" + geoDist)
 | 
						|
                }
 | 
						|
 | 
						|
            }
 | 
						|
 | 
						|
        }
 | 
						|
 | 
						|
 | 
						|
        htmlElement.ontouchstart = (ev: TouchEvent) => {
 | 
						|
            onPosChange(ev.touches[0].clientX, ev.touches[0].clientY, true);
 | 
						|
            ev.preventDefault();
 | 
						|
        }
 | 
						|
 | 
						|
        htmlElement.ontouchmove = (ev: TouchEvent) => {
 | 
						|
            onPosChange(ev.touches[0].clientX, ev.touches[0].clientY, false);
 | 
						|
            ev.preventDefault();
 | 
						|
        }
 | 
						|
 | 
						|
        htmlElement.ontouchend = (ev: TouchEvent) => {
 | 
						|
            onPosChange(undefined, undefined, false, true);
 | 
						|
            ev.preventDefault();
 | 
						|
        }
 | 
						|
 | 
						|
        htmlElement.onmousedown = (ev: MouseEvent) => {
 | 
						|
            onPosChange(ev.clientX, ev.clientY, true);
 | 
						|
            ev.preventDefault();
 | 
						|
        }
 | 
						|
 | 
						|
        htmlElement.onmouseup = (ev) => {
 | 
						|
            onPosChange(ev.clientX, ev.clientY, false, true);
 | 
						|
            ev.preventDefault();
 | 
						|
        }
 | 
						|
 | 
						|
        htmlElement.onmousemove = (ev: MouseEvent) => {
 | 
						|
            onPosChange(ev.clientX, ev.clientY, false);
 | 
						|
            ev.preventDefault();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
} |