57 lines
2.2 KiB
TypeScript
57 lines
2.2 KiB
TypeScript
import { Marker, type Map as MapLibreMap } from "maplibre-gl";
|
|
import type { EventGpsCoordinate } from "./event-location-coordinate";
|
|
|
|
const arrowDirections: Record<string, [number, number]> = {
|
|
ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1]
|
|
};
|
|
|
|
export function createEventLocationMarker({ map, element, coordinate, disabled, onChange, onDragChange }: {
|
|
map: MapLibreMap;
|
|
element: HTMLButtonElement;
|
|
coordinate: EventGpsCoordinate;
|
|
disabled: boolean;
|
|
onChange: (coordinate: EventGpsCoordinate) => void;
|
|
onDragChange: (dragging: boolean) => void;
|
|
}) {
|
|
const marker = new Marker({ element, anchor: "bottom", draggable: !disabled })
|
|
.setLngLat(coordinate)
|
|
.addTo(map);
|
|
element.setAttribute("aria-label", "事件位置标记,可拖拽调整");
|
|
element.setAttribute("title", "拖拽调整事件位置,方向键微调,Shift 加速");
|
|
element.setAttribute("aria-keyshortcuts", "ArrowUp ArrowDown ArrowLeft ArrowRight");
|
|
marker.on("dragstart", () => {
|
|
element.dataset.dragging = "true";
|
|
onDragChange(true);
|
|
});
|
|
marker.on("dragend", () => {
|
|
element.dataset.dragging = "false";
|
|
onDragChange(false);
|
|
const position = marker.getLngLat();
|
|
onChange([normalizeLongitude(position.lng), clampLatitude(position.lat)]);
|
|
});
|
|
// Custom MapLibre markers own their keyboard interaction. Move in screen
|
|
// pixels and consume arrows so adjusting the pin cannot also pan the map.
|
|
element.onkeydown = (event) => {
|
|
if (!marker.isDraggable() || event.altKey || event.ctrlKey || event.metaKey) return;
|
|
const direction = arrowDirections[event.key];
|
|
if (!direction) return;
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
const point = map.project(marker.getLngLat());
|
|
const step = event.shiftKey ? 10 : 1;
|
|
const position = map.unproject([point.x + direction[0] * step, point.y + direction[1] * step]);
|
|
const next: EventGpsCoordinate = [normalizeLongitude(position.lng), clampLatitude(position.lat)];
|
|
marker.setLngLat(next);
|
|
onChange(next);
|
|
};
|
|
return marker;
|
|
}
|
|
|
|
export function normalizeLongitude(longitude: number) {
|
|
return ((longitude + 180) % 360 + 360) % 360 - 180;
|
|
}
|
|
|
|
export function clampLatitude(latitude: number) {
|
|
return Math.max(-90, Math.min(90, latitude));
|
|
}
|