Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: provide snapping to line as well as feature coordinates for polygon mode #384

Merged
merged 1 commit into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions development/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,19 @@ const getModes = () => {
}),
new TerraDrawPointMode(),
new TerraDrawLineStringMode({
insertCoordinates: {
strategy: "amount",
value: 10,
snapping: {
toCoordinate: true,
},
// insertCoordinates: {
// strategy: "amount",
// value: 10,
// },
}),
new TerraDrawPolygonMode({
snapping: true,
snapping: {
toLine: true,
toCoordinate: true,
},
validation: (feature, { updateType }) => {
if (updateType === "finish" || updateType === "commit") {
return ValidateNotSelfIntersecting(feature);
Expand Down
18 changes: 18 additions & 0 deletions guides/4.MODES.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,24 @@ You'll notice there are two arguments to the `validation` function, the first be

Using these can help you write more customised behaviours, for example you may only want to run the validation when the update is a `finish` or `commit` type, ensuring that validation is not prematurely preventing user interactions to update the feature.

#### Snapping

Some specific modes support snapping, currently polygon and linestring mode. As of writing you can snap between features in the same mode. Currently polygon mode supports snapping to a the coordinates or line segments of existing features via the `toCoordinate` and/or `toLine` properties respectively:

```typescript
new TerraDrawPolygonMode({
snapping: {
toLine: true,
toCoordinate: true,
},
validation: (feature, { updateType }) => {
if (updateType === "finish" || updateType === "commit") {
return ValidateNotSelfIntersecting(feature);
}
return { valid: true };
},
})
```

#### Projections in Drawing Modes

Expand Down
4 changes: 3 additions & 1 deletion src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export interface TerraDrawAdapterStyling {
zIndex: number;
}

export type CartesianPoint = { x: number; y: number };

// Neither buttons nor touch/pen contact changed since last event -1
// Mouse move with no buttons pressed, Pen moved while hovering with no buttons pressed —
// Left Mouse, Touch Contact, Pen contact 0
Expand Down Expand Up @@ -62,7 +64,7 @@ export type SetCursor = (
| "move",
) => void;

export type Project = (lng: number, lat: number) => { x: number; y: number };
export type Project = (lng: number, lat: number) => CartesianPoint;
export type Unproject = (x: number, y: number) => { lat: number; lng: number };
export type GetLngLatFromEvent = (event: PointerEvent | MouseEvent) => {
lng: number;
Expand Down
7 changes: 4 additions & 3 deletions src/geometry/calculate-relative-angle.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CartesianPoint } from "../common";
import { webMercatorBearing } from "./measure/bearing";

/**
Expand All @@ -8,9 +9,9 @@ import { webMercatorBearing } from "./measure/bearing";
* @returns The relative angle between the two lines
*/
export function calculateRelativeAngle(
A: { x: number; y: number },
B: { x: number; y: number },
C: { x: number; y: number },
A: CartesianPoint,
B: CartesianPoint,
C: CartesianPoint,
): number {
const bearingAB = webMercatorBearing(A, B); // Bearing from A to B
const bearingBC = webMercatorBearing(B, C); // Bearing from B to C
Expand Down
8 changes: 5 additions & 3 deletions src/geometry/clockwise.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { CartesianPoint } from "../common";

export function isClockwiseWebMercator(
center: { x: number; y: number },
secondCoord: { x: number; y: number },
thirdCoord: { x: number; y: number },
center: CartesianPoint,
secondCoord: CartesianPoint,
thirdCoord: CartesianPoint,
): boolean {
// Calculate the vectors
const vector1 = { x: secondCoord.x - center.x, y: secondCoord.y - center.y };
Expand Down
8 changes: 5 additions & 3 deletions src/geometry/determine-halfplane.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { CartesianPoint } from "../common";

// Function to determine the relative position of a point to a line segment
export function determineHalfPlane(
point: { x: number; y: number },
lineStart: { x: number; y: number },
lineEnd: { x: number; y: number },
point: CartesianPoint,
lineStart: CartesianPoint,
lineEnd: CartesianPoint,
): string {
// Calculate the vectors
const vectorLine = { x: lineEnd.x - lineStart.x, y: lineEnd.y - lineStart.y };
Expand Down
5 changes: 3 additions & 2 deletions src/geometry/measure/bearing.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Position } from "geojson";
import { degreesToRadians, radiansToDegrees } from "../helpers";
import { CartesianPoint } from "../../common";

// Adapted from the @turf/bearing module which is MIT Licensed
// https://github.com/Turfjs/turf/tree/master/packages/turf-bearing
Expand All @@ -18,8 +19,8 @@ export function bearing(start: Position, end: Position): number {
}

export function webMercatorBearing(
{ x: x1, y: y1 }: { x: number; y: number },
{ x: x2, y: y2 }: { x: number; y: number },
{ x: x1, y: y1 }: CartesianPoint,
{ x: x2, y: y2 }: CartesianPoint,
): number {
const deltaX = x2 - x1;
const deltaY = y2 - y1;
Expand Down
5 changes: 3 additions & 2 deletions src/geometry/measure/destination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
lengthToRadians,
radiansToDegrees,
} from "../helpers";
import { CartesianPoint } from "../../common";

// Adapted from @turf/destination module which is MIT Licensed
// https://github.com/Turfjs/turf/blob/master/packages/turf-desination/index.ts
Expand Down Expand Up @@ -36,10 +37,10 @@ export function destination(

// Function to create a destination point in Web Mercator projection
export function webMercatorDestination(
{ x, y }: { x: number; y: number },
{ x, y }: CartesianPoint,
distance: number,
bearing: number,
): { x: number; y: number } {
): CartesianPoint {
// Convert origin to Web Mercator
const bearingRad = degreesToRadians(bearing);

Expand Down
16 changes: 9 additions & 7 deletions src/geometry/measure/pixel-distance-to-line.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { CartesianPoint } from "../../common";

export const pixelDistanceToLine = (
point: { x: number; y: number },
linePointOne: { x: number; y: number },
linePointTwo: { x: number; y: number },
point: CartesianPoint,
linePointOne: CartesianPoint,
linePointTwo: CartesianPoint,
) => {
const square = (x: number) => {
return x * x;
};
const dist2 = (v: { x: number; y: number }, w: { x: number; y: number }) => {
const dist2 = (v: CartesianPoint, w: CartesianPoint) => {
return square(v.x - w.x) + square(v.y - w.y);
};
const distToSegmentSquared = (
p: { x: number; y: number },
v: { x: number; y: number },
w: { x: number; y: number },
p: CartesianPoint,
v: CartesianPoint,
w: CartesianPoint,
) => {
const l2 = dist2(v, w);

Expand Down
6 changes: 4 additions & 2 deletions src/geometry/measure/pixel-distance.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { CartesianPoint } from "../../common";

export const cartesianDistance = (
pointOne: { x: number; y: number },
pointTwo: { x: number; y: number },
pointOne: CartesianPoint,
pointTwo: CartesianPoint,
) => {
const { x: x1, y: y1 } = pointOne;
const { x: x2, y: y2 } = pointTwo;
Expand Down
59 changes: 59 additions & 0 deletions src/geometry/point-on-line.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { nearestPointOnLine } from "./point-on-line";

describe("nearestPointOnLine", () => {
it("returns exact feature coordinate if source coordinate is that coordinate", () => {
const result = nearestPointOnLine(
[0, 1],
[
[
[0, 0],
[0, 1],
],
[
[0, 1],
[1, 1],
],
[
[1, 1],
[1, 0],
],
[
[1, 0],
[0, 0],
],
],
);

expect(result?.coordinate).toEqual([0, 1]);
expect(result?.distance).toEqual(0);
});

it("returns coordinate on line", () => {
const result = nearestPointOnLine(
[0, 2],
[
[
[0, 0],
[0, 1],
],
[
[0, 1],
[1, 1],
],
[
[1, 1],
[1, 0],
],
[
[1, 0],
[0, 0],
],
],
);

expect(result?.coordinate[0]).toBeCloseTo(0);
expect(result?.coordinate[1]).toBeCloseTo(1);

expect(result?.distance).toBeCloseTo(111.19492664455873);
});
});
Loading
Loading