diff --git a/src/components/my-map/drawing.ts b/src/components/my-map/drawing.ts index 13e6548..85bc274 100644 --- a/src/components/my-map/drawing.ts +++ b/src/components/my-map/drawing.ts @@ -1,6 +1,7 @@ import { FeatureLike } from "ol/Feature"; import { MultiPoint, MultiPolygon, Polygon } from "ol/geom"; import { Type } from "ol/geom/Geometry"; +import { noModifierKeys, primaryAction } from "ol/events/condition"; import { Draw, Modify, Snap } from "ol/interaction"; import { Vector as VectorLayer } from "ol/layer"; import { Vector as VectorSource } from "ol/source"; @@ -176,6 +177,8 @@ export function configureDraw( return new Draw({ source: drawingSource, type: drawType, + // Reserve right-click for undoing and deleting, rather than drawing + condition: (event) => noModifierKeys(event) && primaryAction(event), style: configureDrawInteractionStyle(drawType, drawPointer, drawColor), }); } @@ -185,12 +188,16 @@ export const snap = new Snap({ pixelTolerance: 15, }); +// Distance (px) from a vertex within which Modify shows the draw pointer, also used for right-click deletion +export const modifyPixelTolerance = 10; + export function configureModify( drawPointer: DrawPointerEnum, drawColor: string, ) { return new Modify({ source: drawingSource, + pixelTolerance: modifyPixelTolerance, style: new Style({ image: configureDrawPointerImage(drawPointer, drawColor), }), diff --git a/src/components/my-map/index.ts b/src/components/my-map/index.ts index 864f441..ce556cf 100644 --- a/src/components/my-map/index.ts +++ b/src/components/my-map/index.ts @@ -4,6 +4,7 @@ import apply from "ol-mapbox-style"; import { defaults as defaultControls, ScaleLine } from "ol/control"; import { containsCoordinate, Extent } from "ol/extent"; import { FeatureLike } from "ol/Feature"; +import { altKeyOnly } from "ol/events/condition"; import { GeoJSON } from "ol/format"; import { GeoJSONFeature, GeoJSONFeatureCollection } from "ol/format/GeoJSON"; import { Geometry, Point } from "ol/geom"; @@ -31,6 +32,7 @@ import { drawingSource, DrawPointerEnum, DrawTypeEnum, + modifyPixelTolerance, snap, } from "./drawing"; import pinIcon from "./icons/poi-alt.svg"; @@ -607,6 +609,71 @@ export class MyMap extends LitElement { // Snap must be added after draw and modify map.addInteraction(snap); + // Track whether a polygon is part-way through being drawn + let isDrawing = false; + draw.on("drawstart", () => (isDrawing = true)); + draw.on(["drawend", "drawabort"], () => (isDrawing = false)); + + // Delete the point under the draw pointer, returning whether one was deleted + const deletePointAtPixel = (pixel: number[]): boolean => { + // Only delete when close enough to the point's centre for Modify to show the draw pointer + const feature = drawingSource.getClosestFeatureToCoordinate( + map.getCoordinateFromPixel(pixel), + ); + const geom = feature?.getGeometry(); + if (!feature || !(geom instanceof Point)) return false; + const pointPixel = map.getPixelFromCoordinate(geom.getCoordinates()); + if ( + Math.hypot(pointPixel[0] - pixel[0], pointPixel[1] - pixel[1]) > + modifyPixelTolerance + ) + return false; + + drawingSource.removeFeature(feature); + + // Re-number remaining labels so the next drawn point doesn't duplicate an existing label + drawingSource + .getFeatures() + .forEach((sketch, i) => sketch.set("label", `${i + 1}`)); + + // The 'change' listener below only dispatches when features remain + if (drawingSource.getFeatures().length === 0) { + this.dispatch("geojsonChange", {}); + + // If limited to a single point, allow drawing again (snap must be added after draw) + if (!this.drawMany) { + map.removeInteraction(snap); + map.addInteraction(draw); + map.addInteraction(snap); + } + } + return true; + }; + + // Right-click to undo the last vertex while drawing, or delete the point/vertex under the draw pointer + map.getViewport().addEventListener("contextmenu", (event) => { + if (isDrawing) { + event.preventDefault(); + draw.removeLastPoint(); + return; + } + + const pixel = map.getEventPixel(event); + const deleted = + this.drawType === "Point" + ? deletePointAtPixel(pixel) + : // Modify only removes a vertex within its pixelTolerance, and won't reduce a polygon below a triangle + modify.removePoint(map.getCoordinateFromPixel(pixel)); + if (deleted) event.preventDefault(); + }); + + // Alt-click (Option-click on Mac) to delete a point, matching Modify's built-in vertex deletion for polygons + if (this.drawType === "Point") { + map.on("singleclick", (event) => { + if (altKeyOnly(event)) deletePointAtPixel(event.pixel); + }); + } + // 'change' listens for 'drawend' and modifications drawingSource.on("change", () => { const sketches = drawingSource.getFeatures(); @@ -905,9 +972,11 @@ export class MyMap extends LitElement {
` @@ -918,9 +987,11 @@ export class MyMap extends LitElement {
`;