From ae7a731d51b7f79f0b16cc18d0ce9347fa33f147 Mon Sep 17 00:00:00 2001 From: Ben Butterworth <24711048+ben-xD@users.noreply.github.com> Date: Sun, 10 Mar 2024 21:20:00 +0000 Subject: [PATCH 1/3] feat: add DrawLineStringByDraggingMode --- docs/api-reference/modes/overview.md | 4 ++ examples/advanced/src/example.tsx | 7 ++ modules/edit-modes/src/index.ts | 1 + .../lib/draw-line-string-by-dragging-mode.ts | 72 +++++++++++++++++++ .../src/layers/editable-geojson-layer.ts | 2 + modules/main/src/index.ts | 1 + modules/react-map-gl-draw/src/index.ts | 1 + 7 files changed, 88 insertions(+) create mode 100644 modules/edit-modes/src/lib/draw-line-string-by-dragging-mode.ts diff --git a/docs/api-reference/modes/overview.md b/docs/api-reference/modes/overview.md index ee9d2f1fd..3e3691737 100644 --- a/docs/api-reference/modes/overview.md +++ b/docs/api-reference/modes/overview.md @@ -113,6 +113,10 @@ User can draw a new `Polygon` feature with 90 degree corners (right angle) by cl User can draw a new `Polygon` feature by dragging (similar to the lasso tool commonly found in photo editing software). +## [DrawLineStringByDraggingMode](https://github.com/uber/nebula.gl/blob/master/modules/edit-modes/src/lib/draw-line-string-by-dragging-mode.ts) + +User can draw a new `LineString` feature by dragging (similar to the pencil tool commonly found in sketching software). + ### ModeConfig The following options can be provided in the `modeConfig` object: diff --git a/examples/advanced/src/example.tsx b/examples/advanced/src/example.tsx index 36ad46db7..34b5f6f3b 100644 --- a/examples/advanced/src/example.tsx +++ b/examples/advanced/src/example.tsx @@ -34,6 +34,7 @@ import { DrawRectangleUsingThreePointsMode, Draw90DegreePolygonMode, DrawPolygonByDraggingMode, + DrawLineStringByDraggingMode, MeasureDistanceMode, MeasureAreaMode, MeasureAngleMode, @@ -105,6 +106,7 @@ const ALL_MODES: any = [ { label: 'Draw Polygon', mode: DrawPolygonMode }, { label: 'Draw 90° Polygon', mode: Draw90DegreePolygonMode }, { label: 'Draw Polygon By Dragging', mode: DrawPolygonByDraggingMode }, + { label: 'Draw LineString By Dragging', mode: DrawLineStringByDraggingMode }, { label: 'Draw Rectangle', mode: DrawRectangleMode }, { label: 'Draw Rectangle From Center', mode: DrawRectangleFromCenterMode }, { label: 'Draw Rectangle Using 3 Points', mode: DrawRectangleUsingThreePointsMode }, @@ -966,6 +968,11 @@ export default class Example extends React.Component< ...modeConfig, throttleMs: 100, }; + } else if (mode === DrawLineStringByDraggingMode) { + modeConfig = { + ...modeConfig, + throttleMs: 100, + }; } // Demonstrate how to override sub layer properties diff --git a/modules/edit-modes/src/index.ts b/modules/edit-modes/src/index.ts index 2a3a66251..52dc95c3c 100644 --- a/modules/edit-modes/src/index.ts +++ b/modules/edit-modes/src/index.ts @@ -36,6 +36,7 @@ export { DrawEllipseUsingThreePointsMode } from './lib/draw-ellipse-using-three- export { DrawRectangleUsingThreePointsMode } from './lib/draw-rectangle-using-three-points-mode'; export { Draw90DegreePolygonMode } from './lib/draw-90degree-polygon-mode'; export { DrawPolygonByDraggingMode } from './lib/draw-polygon-by-dragging-mode'; +export { DrawLineStringByDraggingMode } from './lib/draw-line-string-by-dragging-mode'; export { ImmutableFeatureCollection } from './lib/immutable-feature-collection'; // Other modes diff --git a/modules/edit-modes/src/lib/draw-line-string-by-dragging-mode.ts b/modules/edit-modes/src/lib/draw-line-string-by-dragging-mode.ts new file mode 100644 index 000000000..85c4aabe6 --- /dev/null +++ b/modules/edit-modes/src/lib/draw-line-string-by-dragging-mode.ts @@ -0,0 +1,72 @@ +import throttle from 'lodash.throttle'; +import { DebouncedFunc } from 'lodash'; +import { + ClickEvent, + StartDraggingEvent, + StopDraggingEvent, + DraggingEvent, + ModeProps, +} from '../types'; +import { LineString, FeatureCollection } from '../geojson-types'; +import { getPickedEditHandle } from '../utils'; +import { DrawLineStringMode } from './draw-line-string-mode'; + +type DragHandler = (event: DraggingEvent, props: ModeProps) => void; + +type ThrottledDragHandler = DebouncedFunc; + +export class DrawLineStringByDraggingMode extends DrawLineStringMode { + handleDraggingThrottled: ThrottledDragHandler | DragHandler | null | undefined = null; + + // Override the default behavior of DrawLineStringMode to not add a point when the user clicks on the map + handleClick(event: ClickEvent, props: ModeProps) { + return; + } + + handleStartDragging(event: StartDraggingEvent, props: ModeProps) { + event.cancelPan(); + if (props.modeConfig && props.modeConfig.throttleMs) { + this.handleDraggingThrottled = throttle(this.handleDraggingAux, props.modeConfig.throttleMs); + } else { + this.handleDraggingThrottled = this.handleDraggingAux; + } + } + + handleStopDragging(event: StopDraggingEvent, props: ModeProps) { + this.addClickSequence(event); + const clickSequence = this.getClickSequence(); + if (this.handleDraggingThrottled && 'cancel' in this.handleDraggingThrottled) { + this.handleDraggingThrottled.cancel(); + } + + if (clickSequence.length > 2) { + const lineStringToAdd: LineString = { + type: 'LineString', + coordinates: clickSequence, + }; + + this.resetClickSequence(); + + const editAction = this.getAddFeatureAction(lineStringToAdd, props.data); + if (editAction) { + props.onEdit(editAction); + } + } + } + + handleDraggingAux(event: DraggingEvent, props: ModeProps) { + const { picks } = event; + const clickedEditHandle = getPickedEditHandle(picks); + + if (!clickedEditHandle) { + // Don't add another point right next to an existing one. + this.addClickSequence(event); + } + } + + handleDragging(event: DraggingEvent, props: ModeProps) { + if (this.handleDraggingThrottled) { + this.handleDraggingThrottled(event, props); + } + } +} diff --git a/modules/layers/src/layers/editable-geojson-layer.ts b/modules/layers/src/layers/editable-geojson-layer.ts index d414dabf9..97b78dd06 100644 --- a/modules/layers/src/layers/editable-geojson-layer.ts +++ b/modules/layers/src/layers/editable-geojson-layer.ts @@ -26,6 +26,7 @@ import { DrawEllipseUsingThreePointsMode, Draw90DegreePolygonMode, DrawPolygonByDraggingMode, + DrawLineStringByDraggingMode, SnappableMode, TransformMode, EditAction, @@ -260,6 +261,7 @@ const modeNameMapping = { drawEllipseUsing3Points: DrawEllipseUsingThreePointsMode, draw90DegreePolygon: Draw90DegreePolygonMode, drawPolygonByDragging: DrawPolygonByDraggingMode, + drawLineStringByDragging: DrawLineStringByDraggingMode, }; // type State = { diff --git a/modules/main/src/index.ts b/modules/main/src/index.ts index ce5fd669e..44378cbcc 100644 --- a/modules/main/src/index.ts +++ b/modules/main/src/index.ts @@ -57,6 +57,7 @@ export { DrawEllipseUsingThreePointsMode } from '@nebula.gl/edit-modes'; export { DrawRectangleUsingThreePointsMode } from '@nebula.gl/edit-modes'; export { Draw90DegreePolygonMode } from '@nebula.gl/edit-modes'; export { DrawPolygonByDraggingMode } from '@nebula.gl/edit-modes'; +export { DrawLineStringByDraggingMode } from '@nebula.gl/edit-modes'; export { ImmutableFeatureCollection } from '@nebula.gl/edit-modes'; // Other modes diff --git a/modules/react-map-gl-draw/src/index.ts b/modules/react-map-gl-draw/src/index.ts index a24f3dc5e..0d82ec609 100644 --- a/modules/react-map-gl-draw/src/index.ts +++ b/modules/react-map-gl-draw/src/index.ts @@ -14,6 +14,7 @@ export { DrawPolygonMode, DrawRectangleMode, DrawPolygonByDraggingMode, + DrawLineStringByDraggingMode, MeasureDistanceMode, MeasureAreaMode, MeasureAngleMode, From 06c8dfe17b0fe5a8ee00a741a0eddd7d7ec01d6f Mon Sep 17 00:00:00 2001 From: Ben Butterworth <24711048+ben-xD@users.noreply.github.com> Date: Mon, 11 Mar 2024 21:38:53 +0000 Subject: [PATCH 2/3] docs(edit-modes): update "What's new" with `DrawLineStringByDraggingMode` --- docs/whats-new.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/whats-new.md b/docs/whats-new.md index a304b6207..7a7aec0cf 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -10,6 +10,10 @@ Release date: TBD - new `DrawRectangleFromCenterMode`. User can draw a new rectangular `Polygon` feature by clicking the center, then along a corner of the rectangle. +### Draw LineString by Dragging Mode + +- new `DrawLineStringByDraggingMode`. User can draw a `LineString` feature by dragging on the map, and releasing their cursor. + ### Translate mode - `screenSpace` option can be provided in the `modeConfig` of Translate mode so the features will be translated without distortion in screen space. From 6818bc9ca4ebc67fae9fb58071ad4862cb986004 Mon Sep 17 00:00:00 2001 From: Ben Butterworth <24711048+ben-xD@users.noreply.github.com> Date: Mon, 11 Mar 2024 22:36:42 +0000 Subject: [PATCH 3/3] test(edit-modes): add test for `DrawLineStringByDraggingMode` --- .../draw-line-string-by-dragging-mode.test.ts | 51 +++++++++++++++++++ modules/edit-modes/test/test-utils.ts | 26 ++++++++-- 2 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 modules/edit-modes/test/lib/draw-line-string-by-dragging-mode.test.ts diff --git a/modules/edit-modes/test/lib/draw-line-string-by-dragging-mode.test.ts b/modules/edit-modes/test/lib/draw-line-string-by-dragging-mode.test.ts new file mode 100644 index 000000000..49784563a --- /dev/null +++ b/modules/edit-modes/test/lib/draw-line-string-by-dragging-mode.test.ts @@ -0,0 +1,51 @@ +import { FeatureCollection } from '@nebula.gl/edit-modes'; +import { DrawLineStringByDraggingMode } from '../../src/lib/draw-line-string-by-dragging-mode'; +import { + createFeatureCollectionProps, + createStartDraggingEvent, + createPointerMoveEvent, + createStopDraggingEvent, + createDraggingEvent, +} from '../test-utils'; +import { ModeProps } from '../../src'; + +let props: ModeProps; +let mode: DrawLineStringByDraggingMode; + +describe('after drag', () => { + beforeEach(() => { + mode = new DrawLineStringByDraggingMode(); + props = createFeatureCollectionProps({ + data: { + type: 'FeatureCollection', + features: [], + }, + }); + + mode.handleStartDragging(createStartDraggingEvent([2, 2], [1, 2]), props); + }); + + it("doesn't call onEdit on a very short line", () => { + mode.handleStopDragging(createStopDraggingEvent([4, 3], [1, 2]), props); + const mockedOnEdit = vi.mocked(props.onEdit); + expect(mockedOnEdit).toHaveBeenCalledTimes(0); + }); + + it('calls onEdit with new LineString feature', () => { + mode.handleDragging(createDraggingEvent([2, 3], [1, 2]), props); + mode.handleDragging(createDraggingEvent([3, 3], [1, 2]), props); + mode.handleStopDragging(createStopDraggingEvent([4, 3], [1, 2]), props); + + const mockedOnEdit = vi.mocked(props.onEdit); + expect(mockedOnEdit).toHaveBeenCalledTimes(1); + + expect(mockedOnEdit.mock.calls[0][0].editType).toEqual('addFeature'); + const features = mockedOnEdit.mock.calls[0][0].updatedData.features; + expect(features.length).toBe(1); + expect(features[0].geometry.coordinates).toEqual([ + [2, 3], + [3, 3], + [4, 3], + ]); + }); +}); diff --git a/modules/edit-modes/test/test-utils.ts b/modules/edit-modes/test/test-utils.ts index 798197ca7..1a96f64fc 100644 --- a/modules/edit-modes/test/test-utils.ts +++ b/modules/edit-modes/test/test-utils.ts @@ -1,4 +1,4 @@ -import { Position, FeatureCollection } from '@nebula.gl/edit-modes'; +import { Position, FeatureCollection, DraggingEvent } from '@nebula.gl/edit-modes'; import { ModeProps, ClickEvent, @@ -285,7 +285,7 @@ export function getFeatureCollectionFeatures(options?: { [K: string]: any }) { ]; } -export function createFeatureCollection(options?: { [K: string]: any }) { +export function createFeatureCollection(options?: { [K: string]: any }): FeatureCollection { return { type: 'FeatureCollection', features: getFeatureCollectionFeatures(options), @@ -338,6 +338,25 @@ export function createStartDraggingEvent( }; } +export function createDraggingEvent( + mapCoords: Position, + pointerDownMapCoords: Position, + picks: Pick[] = [] +): DraggingEvent { + lastCoords = mapCoords; + + return { + screenCoords: [-1, -1], + mapCoords, + picks, + pointerDownPicks: null, + pointerDownScreenCoords: [-1, -1], + pointerDownMapCoords, + cancelPan: vi.fn(), + sourceEvent: null, + }; +} + export function createStopDraggingEvent( mapCoords: Position, pointerDownMapCoords: Position, @@ -379,14 +398,13 @@ export function createFeatureCollectionProps( overrides: Partial> = {} ): ModeProps { return { - // @ts-ignore data: createFeatureCollection(), selectedIndexes: [], - // @ts-ignore lastPointerMoveEvent: createPointerMoveEvent(), modeConfig: null, onEdit: vi.fn(), onUpdateCursor: vi.fn(), + cursor: undefined, ...overrides, }; }