diff --git a/examples/advanced/src/example.tsx b/examples/advanced/src/example.tsx
index 36ad46db7..7b4fcda1b 100644
--- a/examples/advanced/src/example.tsx
+++ b/examples/advanced/src/example.tsx
@@ -1,50 +1,50 @@
/* eslint-env browser */
-import * as React from 'react';
+import { MapController, MapView } from '@deck.gl/core/typed';
import DeckGL from '@deck.gl/react/typed';
-import { MapView, MapController } from '@deck.gl/core/typed';
-import StaticMap from 'react-map-gl';
import GL from '@luma.gl/constants';
import circle from '@turf/circle';
+import * as React from 'react';
+import StaticMap from 'react-map-gl';
import {
- EditableGeoJsonLayer,
- SelectionLayer,
- ModifyMode,
- ResizeCircleMode,
- TranslateMode,
- TransformMode,
- ScaleMode,
- RotateMode,
- DuplicateMode,
- ExtendLineStringMode,
- SplitPolygonMode,
- ExtrudeMode,
- ElevationMode,
- DrawPointMode,
- DrawLineStringMode,
- DrawPolygonMode,
- DrawRectangleMode,
- DrawSquareMode,
- DrawRectangleFromCenterMode,
- DrawSquareFromCenterMode,
+ Color,
+ CompositeMode,
+ Draw90DegreePolygonMode,
DrawCircleByDiameterMode,
DrawCircleFromCenterMode,
DrawEllipseByBoundingBoxMode,
DrawEllipseUsingThreePointsMode,
- DrawRectangleUsingThreePointsMode,
- Draw90DegreePolygonMode,
+ DrawLineStringMode,
+ DrawPointMode,
DrawPolygonByDraggingMode,
- MeasureDistanceMode,
- MeasureAreaMode,
- MeasureAngleMode,
- ViewMode,
- CompositeMode,
- SnappableMode,
+ DrawPolygonMode,
+ DrawRectangleFromCenterMode,
+ DrawRectangleMode,
+ DrawRectangleUsingThreePointsMode,
+ DrawSquareFromCenterMode,
+ DrawSquareMode,
+ DuplicateMode,
+ EditableGeoJsonLayer,
ElevatedEditHandleLayer,
+ ElevationMode,
+ ExtendLineStringMode,
+ ExtrudeMode,
+ GeoJsonEditMode,
+ MeasureAngleMode,
+ MeasureAreaMode,
+ MeasureDistanceMode,
+ ModifyMode,
PathMarkerLayer,
+ ResizeCircleMode,
+ RotateMode,
SELECTION_TYPE,
- GeoJsonEditMode,
- Color,
+ ScaleMode,
+ SelectionLayer,
+ SnappableMode,
+ SplitPolygonMode,
+ TransformMode,
+ TranslateMode,
+ ViewMode,
} from 'nebula.gl';
import sampleGeoJson from '../../data/sample-geojson.json';
@@ -53,11 +53,11 @@ import iconSheet from '../../data/edit-handles.png';
import {
Toolbox,
- ToolboxControl,
- ToolboxTitle,
- ToolboxRow,
ToolboxButton,
ToolboxCheckbox,
+ ToolboxControl,
+ ToolboxRow,
+ ToolboxTitle,
} from './toolbox';
type RGBAColor = Color;
@@ -525,16 +525,37 @@ export default class Example extends React.Component<
_renderModifyModeControls() {
return (
-
- Allow removing points
-
- this.setState({ pointsRemovable: !this.state.pointsRemovable })}
- />
-
-
+ <>
+
+ Allow removing points
+
+ this.setState({ pointsRemovable: !this.state.pointsRemovable })}
+ />
+
+
+
+ Prevent overlapping lines
+
+
+ this.setState({
+ modeConfig: {
+ ...(this.state.modeConfig || {}),
+ preventOverlappingLines: Boolean(event.target.checked),
+ },
+ })
+ }
+ />
+
+
+ >
);
}
diff --git a/modules/edit-modes/src/lib/draw-polygon-mode.ts b/modules/edit-modes/src/lib/draw-polygon-mode.ts
index 270421c0b..5430131aa 100644
--- a/modules/edit-modes/src/lib/draw-polygon-mode.ts
+++ b/modules/edit-modes/src/lib/draw-polygon-mode.ts
@@ -8,8 +8,8 @@ import {
TentativeFeature,
GuideFeature,
} from '../types';
-import { Polygon, FeatureCollection } from '../geojson-types';
-import { getPickedEditHandle } from '../utils';
+import { Polygon, FeatureCollection, Position } from '../geojson-types';
+import { getPickedEditHandle, hasPolygonCrossingLines } from '../utils';
import { GeoJsonEditMode } from './geojson-edit-mode';
export class DrawPolygonMode extends GeoJsonEditMode {
@@ -119,9 +119,21 @@ export class DrawPolygonMode extends GeoJsonEditMode {
coordinates: [[...clickSequence, clickSequence[0]]],
};
+ const editAction = this.getAddFeatureOrBooleanPolygonAction(polygonToAdd, props);
+
+ if (
+ props.modeConfig?.preventOverlappingLines &&
+ editAction.updatedData &&
+ hasPolygonCrossingLines(
+ editAction.updatedData.features[editAction.editContext.featureIndexes[0]].geometry
+ .coordinates[0] as Position[]
+ )
+ ) {
+ return;
+ }
+
this.resetClickSequence();
- const editAction = this.getAddFeatureOrBooleanPolygonAction(polygonToAdd, props);
if (editAction) {
props.onEdit(editAction);
}
diff --git a/modules/edit-modes/src/lib/modify-mode.ts b/modules/edit-modes/src/lib/modify-mode.ts
index 6a7dcf2c0..8bc542528 100644
--- a/modules/edit-modes/src/lib/modify-mode.ts
+++ b/modules/edit-modes/src/lib/modify-mode.ts
@@ -1,32 +1,34 @@
import { point, lineString as toLineString } from '@turf/helpers';
+import { FeatureCollection, FeatureOf, LineString, Point, Polygon } from '../geojson-types';
import {
- recursivelyTraverseNestedArrays,
- nearestPointOnProjectedLine,
- nearestPointOnLine,
- getEditHandlesForGeometry,
- getPickedEditHandles,
- getPickedEditHandle,
- getPickedExistingEditHandle,
- getPickedIntermediateEditHandle,
- updateRectanglePosition,
- NearestPointType,
-} from '../utils';
-import { LineString, Point, Polygon, FeatureCollection, FeatureOf } from '../geojson-types';
-import {
- ModeProps,
ClickEvent,
+ DraggingEvent,
+ EditHandleFeature,
+ GuideFeatureCollection,
+ ModeProps,
PointerMoveEvent,
StartDraggingEvent,
StopDraggingEvent,
- DraggingEvent,
Viewport,
- GuideFeatureCollection,
- EditHandleFeature,
} from '../types';
+import {
+ NearestPointType,
+ getEditHandlesForGeometry,
+ getPickedEditHandle,
+ getPickedEditHandles,
+ getPickedExistingEditHandle,
+ getPickedIntermediateEditHandle,
+ hasPolygonCrossingLines,
+ nearestPointOnLine,
+ nearestPointOnProjectedLine,
+ recursivelyTraverseNestedArrays,
+ updateRectanglePosition,
+} from '../utils';
import { GeoJsonEditMode } from './geojson-edit-mode';
import { ImmutableFeatureCollection } from './immutable-feature-collection';
export class ModifyMode extends GeoJsonEditMode {
+ private _featureCollectionBeforeDrag: FeatureCollection;
getGuides(props: ModeProps): GuideFeatureCollection {
const handles = [];
@@ -207,7 +209,7 @@ export class ModifyMode extends GeoJsonEditMode {
const editHandleProperties = editHandle.properties;
const editedFeature = props.data.features[editHandleProperties.featureIndex];
- let updatedData;
+ let updatedData: FeatureCollection;
if (props.modeConfig?.lockRectangles && editedFeature.properties.shape === 'Rectangle') {
const coordinates = updateRectanglePosition(
editedFeature as FeatureOf,
@@ -245,8 +247,12 @@ export class ModifyMode extends GeoJsonEditMode {
}
handleStartDragging(event: StartDraggingEvent, props: ModeProps) {
- const selectedFeatureIndexes = props.selectedIndexes;
+ // Stores previous state to revert to if drag ends with overlappingLines
+ if (props?.modeConfig?.preventOverlappingLines) {
+ this._featureCollectionBeforeDrag = new ImmutableFeatureCollection(props.data).getObject();
+ }
+ const selectedFeatureIndexes = props.selectedIndexes;
const editHandle = getPickedIntermediateEditHandle(event.picks);
if (selectedFeatureIndexes.length && editHandle) {
const editHandleProperties = editHandle.properties;
@@ -274,11 +280,32 @@ export class ModifyMode extends GeoJsonEditMode {
handleStopDragging(event: StopDraggingEvent, props: ModeProps) {
const selectedFeatureIndexes = props.selectedIndexes;
const editHandle = getPickedEditHandle(event.picks);
- if (selectedFeatureIndexes.length && editHandle) {
- this._dragEditHandle('finishMovePosition', props, editHandle, event);
+
+ if (!editHandle || !selectedFeatureIndexes.length) {
+ return;
}
- }
+ if (
+ this._featureCollectionBeforeDrag &&
+ hasPolygonCrossingLines(
+ (props.data.features[editHandle.properties.featureIndex].geometry as Polygon).coordinates[0]
+ )
+ ) {
+ props.onEdit({
+ updatedData: this._featureCollectionBeforeDrag,
+ editType: 'finishMovePosition',
+ editContext: {
+ featureIndexes: [editHandle.properties.featureIndex],
+ positionIndexes: editHandle.properties.positionIndexes,
+ position: event.mapCoords,
+ },
+ });
+
+ return;
+ }
+
+ this._dragEditHandle('finishMovePosition', props, editHandle, event);
+ }
getCursor(event: PointerMoveEvent): string | null | undefined {
const picks = (event && event.picks) || [];
diff --git a/modules/edit-modes/src/utils.ts b/modules/edit-modes/src/utils.ts
index eac5e0069..15cd4daa3 100644
--- a/modules/edit-modes/src/utils.ts
+++ b/modules/edit-modes/src/utils.ts
@@ -511,3 +511,105 @@ export function mapCoords(
})
.filter(Boolean);
}
+
+/**
+ * Checks if two line segments intersect.
+ *
+ * @param {number} Ax - x-coordinate of the start point of the first line segment.
+ * @param {number} Ay - y-coordinate of the start point of the first line segment.
+ * @param {number} Bx - x-coordinate of the end point of the first line segment.
+ * @param {number} By - y-coordinate of the end point of the first line segment.
+ * @param {number} Cx - x-coordinate of the start point of the second line segment.
+ * @param {number} Cy - y-coordinate of the start point of the second line segment.
+ * @param {number} Dx - x-coordinate of the end point of the second line segment.
+ * @param {number} Dy - y-coordinate of the end point of the second line segment.
+ * @return {boolean} true if the line segments intersect, false otherwise.
+ */
+// eslint-disable-next-line max-params
+function lineSegmentIntersection(
+ Ax: number,
+ Ay: number,
+ Bx: number,
+ By: number,
+ Cx: number,
+ Cy: number,
+ Dx: number,
+ Dy: number
+): boolean {
+ let newX: number;
+
+ if ((Ax === Bx && Ay === By) || (Cx === Dx && Cy === Dy)) {
+ return false;
+ }
+
+ if (
+ (Ax === Cx && Ay === Cy) ||
+ (Bx === Cx && By === Cy) ||
+ (Ax === Dx && Ay === Dy) ||
+ (Bx === Dx && By === Dy)
+ ) {
+ return false;
+ }
+
+ Bx -= Ax;
+ By -= Ay;
+ Cx -= Ax;
+ Cy -= Ay;
+ Dx -= Ax;
+ Dy -= Ay;
+
+ const distAB = Math.sqrt(Bx * Bx + By * By);
+
+ const theCos = Bx / distAB;
+ const theSin = By / distAB;
+ newX = Cx * theCos + Cy * theSin;
+ Cy = Cy * theCos - Cx * theSin;
+ Cx = newX;
+ newX = Dx * theCos + Dy * theSin;
+ Dy = Dy * theCos - Dx * theSin;
+ Dx = newX;
+
+ if ((Cy < 0.0 && Dy < 0.0) || (Cy >= 0.0 && Dy >= 0.0)) {
+ return false;
+ }
+
+ const ABpos = Dx + ((Cx - Dx) * Dy) / (Dy - Cy);
+
+ if (ABpos < 0.0 || ABpos > distAB) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Checks if a polygon defined by the coordinates has any crossing lines.
+ *
+ * @param {Position[]} coordinates - Array of coordinates defining the polygon.
+ * @return {boolean} True if the polygon has crossing lines, false otherwise.
+ */
+export function hasPolygonCrossingLines(coordinates: Position[]): boolean {
+ for (let i = 0; i < coordinates.length - 2; i++) {
+ const point1 = coordinates[i];
+ const point2 = coordinates[i + 1];
+ for (let j = i + 1; j < coordinates.length - 1; j++) {
+ const point3 = coordinates[j];
+ const point4 = coordinates[j + 1];
+ if (
+ lineSegmentIntersection(
+ point1[0],
+ point1[1],
+ point2[0],
+ point2[1],
+ point3[0],
+ point3[1],
+ point4[0],
+ point4[1]
+ )
+ ) {
+ return true;
+ }
+ }
+ }
+ return false;
+}