Skip to content

Commit ba6fc29

Browse files
@turf/line-arc: always reach bearing2 (fix dropped final vertex) (#3078)
The arc was generated with `while (alpha <= arcEndDegree)`, advancing `alpha = arcStartDegree + i * arcStep`. For some bearing/step combinations this accumulated angle overshoots `arcEndDegree` by a floating-point epsilon on the final iteration (e.g. `0 + 7 * (29 / 7)` === `29.000000000000004`), so the loop exits one step early. The result was an arc with `steps` vertices instead of the documented `steps + 1`, ending a full `arcStep` short of `bearing2` (e.g. `lineArc(center, 5, 0, 29, { steps: 7 })` stopped at ~24.857° instead of 29°). Iterate a fixed `steps + 1` times and pin the last vertex to `arcEndDegree` so the arc always ends exactly on `bearing2`. Behaviour is unchanged for inputs that did not trigger the rounding overshoot. Co-authored-by: mfedderly <24275386+mfedderly@users.noreply.github.com>
1 parent 0e86d5b commit ba6fc29

3 files changed

Lines changed: 37 additions & 7 deletions

File tree

packages/turf-line-arc/index.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,21 @@ function lineArc(
5757
const arcStartDegree = angle1;
5858
const arcEndDegree = angle1 < angle2 ? angle2 : angle2 + 360;
5959

60-
let alpha = arcStartDegree;
6160
const coordinates = [];
62-
let i = 0;
6361
// How many degrees we'll swing around between each step.
6462
const arcStep = (arcEndDegree - arcStartDegree) / steps;
65-
// Add coords to the list, increasing the angle from our start bearing
66-
// (alpha) by arcStep degrees until we reach the end bearing.
67-
while (alpha <= arcEndDegree) {
63+
// Add coords to the list, increasing the angle from our start bearing by
64+
// arcStep degrees until we reach the end bearing. Iterate a fixed number of
65+
// times (steps + 1 vertices) rather than comparing an accumulated angle
66+
// against arcEndDegree: floating-point drift in arcStartDegree + i * arcStep
67+
// could make the final value exceed arcEndDegree (e.g. 29.000000000000004),
68+
// dropping the last vertex so the arc never reached bearing2. Pin the last
69+
// vertex to arcEndDegree so the arc always ends exactly on bearing2.
70+
for (let i = 0; i <= steps; i++) {
71+
const alpha = i === steps ? arcEndDegree : arcStartDegree + i * arcStep;
6872
coordinates.push(
6973
destination(center, radius, alpha, options).geometry.coordinates
7074
);
71-
i++;
72-
alpha = arcStartDegree + i * arcStep;
7375
}
7476
return lineString(coordinates, properties);
7577
}

packages/turf-line-arc/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
"version": "7.3.5",
44
"description": "Creates a circular arc, of a circle of the given radius and center point, between two bearings.",
55
"author": "Turf Authors",
6+
"contributors": [
7+
"Alexander Kireev <@chatman-media>"
8+
],
69
"license": "MIT",
710
"bugs": {
811
"url": "https://github.com/Turfjs/turf/issues"

packages/turf-line-arc/test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { fileURLToPath } from "url";
55
import { loadJsonFileSync } from "load-json-file";
66
import { writeJsonFileSync } from "write-json-file";
77
import { truncate } from "@turf/truncate";
8+
import { destination } from "@turf/destination";
89
import { featureCollection, point } from "@turf/helpers";
910
import { lineArc } from "./index.js";
1011

@@ -79,3 +80,27 @@ test("turf-line-arc #2446", (t) => {
7980

8081
t.end();
8182
});
83+
84+
test("turf-line-arc -- reaches bearing2 despite floating-point drift", (t) => {
85+
// For some bearing/step combinations arcStartDegree + steps * arcStep
86+
// overshoots arcEndDegree by a rounding epsilon (e.g. 0 + 7 * (29 / 7) ===
87+
// 29.000000000000004 > 29). The old `while (alpha <= arcEndDegree)` loop then
88+
// dropped the final vertex, returning `steps` points instead of `steps + 1`
89+
// and ending the arc a full step short of bearing2.
90+
const center = point([-75, 40]);
91+
const arc = lineArc(center, 5, 0, 29, { steps: 7 });
92+
const coords = arc.geometry.coordinates;
93+
94+
t.equals(coords.length, 7 + 1, "arc has steps + 1 vertices");
95+
96+
// The final vertex must sit on bearing2 (29°). destination(center, r, 29)
97+
// gives the reference coordinate; the old code stopped at bearing ~24.857°.
98+
const expectedLast = destination(center, 5, 29).geometry.coordinates;
99+
t.deepEquals(
100+
truncate(point(coords[coords.length - 1])).geometry.coordinates,
101+
truncate(point(expectedLast)).geometry.coordinates,
102+
"last vertex lies on bearing2"
103+
);
104+
105+
t.end();
106+
});

0 commit comments

Comments
 (0)