Skip to content

Commit

Permalink
fix: use project/unproject to get midpoints that are visually centered
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesLMilner committed Mar 12, 2023
1 parent 4073553 commit 3581da2
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 61 deletions.
17 changes: 12 additions & 5 deletions src/geometry/get-midpoint.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,20 @@ describe("getMidPointCoordinates", () => {
[0, 1],
[0, 0],
],
9
9,
(lng: number, lat: number) => {
return { x: lng * 100, y: lat * 100 };
},
(x: number, y: number) => {
return { lng: x / 100, lat: y / 100 };
}
);

expect(result).toStrictEqual([
[0, 0.499999309],
[0.499999309, 1.000038071],
[0.500000691, 1.000038071],
[0, 0.500000691],
[0, 0.5],
[0.5, 1],
[0.5, 1],
[0, 0.5],
]);
});
});
17 changes: 13 additions & 4 deletions src/geometry/get-midpoints.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { Point, Position } from "geojson";
import { Project, Unproject } from "../common";
import { JSONObject } from "../store/store";
import { midpointCoordinate } from "./midpoint-coordinate";


export function getMidPointCoordinates(
featureCoords: Position[],
precision: number
precision: number,
project: Project,
unproject: Unproject
) {
const midPointCoords: Position[] = [];
for (let i = 0; i < featureCoords.length - 1; i++) {
const mid = midpointCoordinate(
featureCoords[i],
featureCoords[i + 1],
precision
precision,
project,
unproject
);
midPointCoords.push(mid);
}
Expand All @@ -21,10 +27,13 @@ export function getMidPointCoordinates(
export function getMidPoints(
selectedCoords: Position[],
properties: (index: number) => JSONObject,
precision: number
precision: number,
project: Project,
unproject: Unproject
) {
return getMidPointCoordinates(selectedCoords, precision).map((coord, i) => ({
return getMidPointCoordinates(selectedCoords, precision, project, unproject).map((coord, i) => ({
geometry: { type: "Point", coordinates: coord } as Point,
properties: properties(i),
}));
}

42 changes: 15 additions & 27 deletions src/geometry/midpoint-coordinate.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,25 @@
import { Position } from "geojson";
import { destination } from "./shape/create-circle";
import { degreesToRadians, radiansToDegrees } from "./helpers";
import { limitPrecision } from "./limit-decimal-precision";
import { haversineDistanceKilometers } from "./measure/haversine-distance";

// Based on turf-bearing: https://github.com/Turfjs/turf/tree/master/packages/turf-bearing

function bearing(coordinates1: Position, coordinates2: Position) {
const lon1 = degreesToRadians(coordinates1[0]);
const lon2 = degreesToRadians(coordinates2[0]);
const lat1 = degreesToRadians(coordinates1[1]);
const lat2 = degreesToRadians(coordinates2[1]);
const a = Math.sin(lon2 - lon1) * Math.cos(lat2);
const b =
Math.cos(lat1) * Math.sin(lat2) -
Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);

return radiansToDegrees(Math.atan2(a, b));
}

// Based on turf-midpoint: https://github.com/Turfjs/turf/tree/master/packages/turf-midpoint
import { Project, Unproject } from "../common";

export function midpointCoordinate(
coordinates1: Position,
coordinates2: Position,
precision: number
precision: number,
project: Project,
unproject: Unproject
) {
const dist = haversineDistanceKilometers(coordinates1, coordinates2);
const heading = bearing(coordinates1, coordinates2);
const midpoint = destination(coordinates1, dist / 2, heading);

const projectedCoordinateOne = project(coordinates1[0], coordinates1[1]);
const projectedCoordinateTwo = project(coordinates2[0], coordinates2[1]);

const { lng, lat } = unproject(
(projectedCoordinateOne.x + projectedCoordinateTwo.x) / 2,
(projectedCoordinateOne.y + projectedCoordinateTwo.y) / 2
);

return [
limitPrecision(midpoint[0], precision),
limitPrecision(midpoint[1], precision),
limitPrecision(lng, precision),
limitPrecision(lat, precision),
];
}
}
33 changes: 23 additions & 10 deletions src/modes/select/behaviors/midpoint.behavior.spec.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,46 @@
import { Position } from "geojson";
import { Project, Unproject } from "../../../common";
import {
createStoreLineString,
createStorePolygon,
} from "../../../test/create-store-features";
import { mockBehaviorConfig } from "../../../test/mock-behavior-config";
import { BehaviorConfig } from "../../base.behavior";
import { MidPointBehavior } from "./midpoint.behavior";
import { SelectionPointBehavior } from "./selection-point.behavior";

describe("MidPointBehavior", () => {
const coordinatePrecision = 9;
let config: BehaviorConfig;

beforeEach(() => {
jest.resetAllMocks();
config = mockBehaviorConfig("test");

(config.project as jest.Mock)
.mockImplementation((lng: number, lat: number) => ({
x: lng * 100,
y: lat * 100,
}));

(config.unproject as jest.Mock)
.mockImplementation((x: number, y: number) => ({
lng: x / 100,
lat: y / 100,
}));
});

describe("constructor", () => {
it("constructs", () => {
const config = mockBehaviorConfig("test");

new MidPointBehavior(config, new SelectionPointBehavior(config));
});
});

describe("api", () => {
describe("api", () => {
it("get ids", () => {
const config = mockBehaviorConfig("test");

const midPointBehavior = new MidPointBehavior(
config,
new SelectionPointBehavior(config)
Expand All @@ -30,7 +50,6 @@ describe("MidPointBehavior", () => {
});

it("set ids fails", () => {
const config = mockBehaviorConfig("test");
const midPointBehavior = new MidPointBehavior(
config,
new SelectionPointBehavior(config)
Expand All @@ -42,7 +61,6 @@ describe("MidPointBehavior", () => {
});

it("create fails when the feature does not exist", () => {
const config = mockBehaviorConfig("test");
const midPointBehavior = new MidPointBehavior(
config,
new SelectionPointBehavior(config)
Expand All @@ -63,7 +81,6 @@ describe("MidPointBehavior", () => {
});

it("create", () => {
const config = mockBehaviorConfig("test");
const midPointBehavior = new MidPointBehavior(
config,
new SelectionPointBehavior(config)
Expand All @@ -88,7 +105,7 @@ describe("MidPointBehavior", () => {
});

it("delete", () => {
const config = mockBehaviorConfig("test");

const midPointBehavior = new MidPointBehavior(
config,
new SelectionPointBehavior(config)
Expand All @@ -112,7 +129,6 @@ describe("MidPointBehavior", () => {

describe("getUpdated", () => {
it("should return empty array if trying to get updated coordinates when non exist", () => {
const config = mockBehaviorConfig("test");
const midPointBehavior = new MidPointBehavior(
config,
new SelectionPointBehavior(config)
Expand All @@ -128,7 +144,6 @@ describe("MidPointBehavior", () => {
});

it("should get updated coordinates if lengths match", () => {
const config = mockBehaviorConfig("test");
const midPointBehavior = new MidPointBehavior(
config,
new SelectionPointBehavior(config)
Expand Down Expand Up @@ -172,7 +187,6 @@ describe("MidPointBehavior", () => {

describe("insert", () => {
it("insert midpoint into the linestring", () => {
const config = mockBehaviorConfig("test");
const midPointBehavior = new MidPointBehavior(
config,
new SelectionPointBehavior(config)
Expand Down Expand Up @@ -231,7 +245,6 @@ describe("MidPointBehavior", () => {
});

it("insert midpoint into the polygon", () => {
const config = mockBehaviorConfig("test");
const midPointBehavior = new MidPointBehavior(
config,
new SelectionPointBehavior(config)
Expand Down
34 changes: 19 additions & 15 deletions src/modes/select/behaviors/midpoint.behavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { SELECT_PROPERTIES } from "../../../common";

export class MidPointBehavior extends TerraDrawModeBehavior {
constructor(
readonly config: BehaviorConfig,
private readonly selectionPointBehavior: SelectionPointBehavior
readonly config: BehaviorConfig,
private readonly selectionPointBehavior: SelectionPointBehavior
) {
super(config);
}
Expand All @@ -21,32 +21,32 @@ export class MidPointBehavior extends TerraDrawModeBehavior {
return this._midPoints.concat();
}

set ids(_: string[]) {}
set ids(_: string[]) { }

public insert(midPointId: string, coordinatePrecision: number) {
const midPoint = this.store.getGeometryCopy(midPointId);
const { midPointFeatureId, midPointSegment } =
this.store.getPropertiesCopy(midPointId);
this.store.getPropertiesCopy(midPointId);
const geometry = this.store.getGeometryCopy<Polygon | LineString>(
midPointFeatureId as string
midPointFeatureId as string
);

// Update the coordinates to include inserted midpoint
const updatedCoordinates =
geometry.type === "Polygon"
? geometry.coordinates[0]
: geometry.coordinates;
geometry.type === "Polygon"
? geometry.coordinates[0]
: geometry.coordinates;

updatedCoordinates.splice(
(midPointSegment as number) + 1,
0,
midPoint.coordinates as Position
midPoint.coordinates as Position
);

// Update geometry coordinates depending
// on if a polygon or linestring
geometry.coordinates =
geometry.type === "Polygon" ? [updatedCoordinates] : updatedCoordinates;
geometry.type === "Polygon" ? [updatedCoordinates] : updatedCoordinates;

// Update the selected features geometry to insert
// the new midpoint
Expand All @@ -62,13 +62,13 @@ export class MidPointBehavior extends TerraDrawModeBehavior {
// because selection points are prerequiste for midpoints
this.create(
updatedCoordinates,
midPointFeatureId as string,
coordinatePrecision
midPointFeatureId as string,
coordinatePrecision
);
this.selectionPointBehavior.create(
updatedCoordinates,
geometry.type,
midPointFeatureId as string
midPointFeatureId as string
);
}

Expand All @@ -90,7 +90,9 @@ export class MidPointBehavior extends TerraDrawModeBehavior {
midPointSegment: i,
midPointFeatureId: featureId,
}),
coordinatePrecision
coordinatePrecision,
this.config.project,
this.config.unproject
)
);
}
Expand All @@ -109,7 +111,9 @@ export class MidPointBehavior extends TerraDrawModeBehavior {

return getMidPointCoordinates(
updatedCoordinates,
this.coordinatePrecision
this.coordinatePrecision,
this.config.project,
this.config.unproject
).map((updatedMidPointCoord, i) => ({
id: this._midPoints[i] as string,
geometry: {
Expand Down
26 changes: 26 additions & 0 deletions src/modes/select/select.mode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,19 @@ describe("TerraDrawSelectMode", () => {
},
});

project
.mockImplementation((lng: number, lat: number) => ({
x: lng * 100,
y: lat * 100,
}));

unproject
.mockImplementation((x: number, y: number) => ({
lng: x / 100,
lat: y / 100,
}));


addPolygonToStore([
[0, 0],
[0, 1],
Expand Down Expand Up @@ -868,6 +881,19 @@ describe("TerraDrawSelectMode", () => {
},
});

project
.mockImplementation((lng: number, lat: number) => ({
x: lng * 100,
y: lat * 100,
}));

unproject
.mockImplementation((x: number, y: number) => ({
lng: x / 100,
lat: y / 100,
}));


addPolygonToStore([
[0, 0],
[0, 1],
Expand Down

0 comments on commit 3581da2

Please sign in to comment.