control/GeoImageDragControl.tstypescript
import { BaseGeoImage } from "../layer/BaseGeoImage";
import { Control, type IControlParams } from "./Control";
import { Layer } from "../layer/Layer";
import { ToggleButton } from "../ui/ToggleButton";
import type { IMouseState } from "../renderer/RendererEvents";

const ICON_BUTTON_SVG = `<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M16 13l6.964 4.062-2.973.85 2.125 3.681-1.732 1-2.125-3.68-2.223 2.15L16 13zm-2-7h2v2h5a1 1 0 0 1 1 1v4h-2v-3H10v10h4v2H9a1 1 0 0 1-1-1v-5H6v-2h2V9a1 1 0 0 1 1-1h5V6zM4 14v2H2v-2h2zm0-4v2H2v-2h2zm0-4v2H2V6h2zm0-4v2H2V2h2zm4 0v2H6V2h2zm4 0v2h-2V2h2zm4 0v2h-2V2h2z" fill="#000"/></svg>`;

interface IGeoImageDragControlParams extends IControlParams {}

export class GeoImageDragControl extends Control {
    protected _cornerIndex: number;
    protected _catchCorner: boolean;
    protected _toggleBtn: ToggleButton;

    constructor(options: IGeoImageDragControlParams = {}) {
        super(options);

        this._cornerIndex = -1;
        this._catchCorner = false;

        this._toggleBtn = new ToggleButton({
            classList: ["og-map-button", "og-geoimagegrag_button"],
            icon: ICON_BUTTON_SVG
        });
    }

    override oninit() {
        this._toggleBtn.appendTo(this.renderer!.topRightContainer());

        this.planet!.events.on(
            "layeradd",
            (e: Layer) => {
                if (this.isActive()) {
                    this._bindLayer(e);
                }
            },
            this
        );

        this._toggleBtn.events.on("change", (isActive: boolean) => {
            if (isActive) {
                this.activate();
            } else {
                this.deactivate();
            }
        });
    }

    override onactivate() {
        super.onactivate();

        const p = this.planet!;
        for (let i = 0; i < p.layers.length; i++) {
            this._bindLayer(p.layers[i]);
        }
    }

    override ondeactivate() {
        super.ondeactivate();
        const p = this.planet!;
        for (let i = 0; i < p.layers.length; i++) {
            this._unbindLayer(p.layers[i]);
        }
    }

    _bindLayer(layer: Layer) {
        if (layer instanceof BaseGeoImage) {
            layer.events.on("mousemove", this._onMouseMove, this);
            layer.events.on("mouseleave", this._onMouseLeave, this);
            layer.events.on("ldown", this._onLDown, this);
            layer.events.on("lup", this._onLUp, this);
        }
    }

    protected _unbindLayer(layer: Layer) {
        if (layer instanceof BaseGeoImage) {
            layer.events.off("mousemove", this._onMouseMove);
            layer.events.off("mouseleave", this._onMouseLeave);
            layer.events.off("ldown", this._onLDown);
            layer.events.off("lup", this._onLUp);
        }
    }

    protected _onLUp(ms: IMouseState) {
        this._catchCorner = false;
        ms.renderer.controls.navigation.activate();
    }

    protected _onLDown(ms: IMouseState) {
        if (this._cornerIndex !== -1) {
            this._catchCorner = true;
            ms.renderer.controls.navigation.deactivate();
        }
    }

    protected _onMouseLeave() {
        document.body.style.cursor = "auto";
    }

    protected _onMouseMove(ms: IMouseState) {
        let layer = ms.pickingObject;
        const p = this.planet!;
        if (this._catchCorner) {
            // mouse is catching a corner
            let corners = layer.getCornersLonLat();
            // corners[this._cornerIndex] = p.getLonLatFromPixelTerrain(ms, true);
            corners[this._cornerIndex] = p.getLonLatFromPixelTerrain(ms);
            layer.setCornersLonLat(corners);
        } else {
            // mouse isn't catching
            this._cornerIndex = -1;
            for (let i = 0; i < layer._cornersWgs84.length; i++) {
                // let ground = p.getLonLatFromPixelTerrain(ms, true);
                let ground = p.getLonLatFromPixelTerrain(ms);
                // mouse is near
                //if (ground && p.ellipsoid.getGreatCircleDistance(layer._cornersWgs84[i], ground) / p.getDistanceFromPixel(ms, true) <= 0.05) {
                if (
                    ground &&
                    p.ellipsoid.getGreatCircleDistance(layer._cornersWgs84[i], ground) / p.getDistanceFromPixel(ms) <=
                        0.05
                ) {
                    this._cornerIndex = i;
                    document.body.style.cursor = "move";
                    break;
                    // mouse is far
                } else {
                    document.body.style.cursor = "auto";
                }
            }
        }
    }
}