Skip to content

Commit 2907628

Browse files
Fixed #2703. turf-angle was not doing what was described in the docs
Reviewed the implementations of azimuthToBearing, radiansToDegrees, and degreesToRadians to be easier to follow. Small discrepancies in the angle() geojson test fixtures caused by the bearing be taken from the wrong end of each line needed to be fixed. These tests included additional illustrative info meaning they were overly fragile though, so retired those in favour of vanilla unit tests which should be sufficient for a simple function like angle. (#2714)
1 parent 6889237 commit 2907628

File tree

14 files changed

+216
-1407
lines changed

14 files changed

+216
-1407
lines changed

packages/turf-angle/index.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,23 @@ function angle(
4949
const B = endPoint;
5050

5151
// Main
52-
const azimuthAO = bearingToAzimuth(
53-
options.mercator !== true ? bearing(A, O) : rhumbBearing(A, O)
52+
const azimuthOA = bearingToAzimuth(
53+
options.mercator !== true ? bearing(O, A) : rhumbBearing(O, A)
5454
);
55-
const azimuthBO = bearingToAzimuth(
56-
options.mercator !== true ? bearing(B, O) : rhumbBearing(B, O)
55+
let azimuthOB = bearingToAzimuth(
56+
options.mercator !== true ? bearing(O, B) : rhumbBearing(O, B)
5757
);
58-
const angleAO = Math.abs(azimuthAO - azimuthBO);
58+
// If OB "trails" OA advance OB one revolution so we get the clockwise angle.
59+
if (azimuthOB < azimuthOA) {
60+
azimuthOB = azimuthOB + 360;
61+
}
62+
const angleAOB = azimuthOB - azimuthOA;
5963

6064
// Explementary angle
6165
if (options.explementary === true) {
62-
return 360 - angleAO;
66+
return 360 - angleAOB;
6367
}
64-
return angleAO;
68+
return angleAOB;
6569
}
6670

6771
export { angle };

packages/turf-angle/test.ts

Lines changed: 189 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,186 @@
11
import test from "tape";
2-
import path from "path";
3-
import { fileURLToPath } from "url";
4-
import { glob } from "glob";
5-
import { loadJsonFileSync } from "load-json-file";
6-
import { writeJsonFileSync } from "write-json-file";
7-
import { sector } from "@turf/sector";
8-
import { bearing } from "@turf/bearing";
9-
import { truncate } from "@turf/truncate";
10-
import { distance } from "@turf/distance";
11-
import { point, round, lineString, featureCollection } from "@turf/helpers";
2+
import { point, round } from "@turf/helpers";
123
import { angle } from "./index.js";
134

14-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
15-
16-
test("turf-angle", (t) => {
17-
glob
18-
.sync(path.join(__dirname, "test", "in", "*.json"))
19-
.forEach((filepath) => {
20-
// Input
21-
const { name } = path.parse(filepath);
22-
const geojson = loadJsonFileSync(filepath);
23-
const [start, mid, end] = geojson.features;
24-
25-
// Results
26-
const angleProperties = {
27-
interiorAngle: round(angle(start, mid, end), 6),
28-
interiorMercatorAngle: round(
29-
angle(start, mid, end, { mercator: true }),
30-
6
31-
),
32-
explementary: false,
33-
fill: "#F00",
34-
stroke: "#F00",
35-
"fill-opacity": 0.3,
36-
};
37-
const angleExplementaryProperties = {
38-
explementaryAngle: round(
39-
angle(start, mid, end, { explementary: true }),
40-
6
41-
),
42-
explementaryMercatorAngle: round(
43-
angle(start, mid, end, { explementary: true, mercator: true }),
44-
6
45-
),
5+
test("turf-angle -- across 0 bearing", (t) => {
6+
t.equal(round(angle([-1, 1], [0, 0], [1, 1])), 90, "90 degrees");
7+
8+
t.end();
9+
});
10+
11+
test("turf-angle -- 90 degrees", (t) => {
12+
t.equal(
13+
round(angle([124, -17], [124, -22], [131, -22]), 6),
14+
91.312527,
15+
"91.312527 degrees"
16+
);
17+
t.equal(
18+
round(angle([124, -17], [124, -22], [131, -22], { explementary: true }), 6),
19+
268.687473,
20+
"268.687473 degrees explementary"
21+
);
22+
t.equal(
23+
round(angle([124, -17], [124, -22], [131, -22], { mercator: true }), 6),
24+
90,
25+
"90 degrees mercator"
26+
);
27+
t.equal(
28+
round(
29+
angle([124, -17], [124, -22], [131, -22], {
30+
explementary: true,
31+
mercator: true,
32+
}),
33+
6
34+
),
35+
270,
36+
"270 degrees explementary mercator"
37+
);
38+
t.end();
39+
});
40+
41+
test("turf-angle -- 180 degrees", (t) => {
42+
t.equal(round(angle([3, -1], [2, 0], [1, 1]), 6), 180, "180 degrees");
43+
44+
t.end();
45+
});
46+
47+
test("turf-angle -- obtuse", (t) => {
48+
t.equal(
49+
round(angle([48.5, 5.5], [51.5, 12], [59, 15.5]), 6),
50+
218.715175,
51+
"218.715175 degrees"
52+
);
53+
t.equal(
54+
round(
55+
angle([48.5, 5.5], [51.5, 12], [59, 15.5], { explementary: true }),
56+
6
57+
),
58+
141.284825,
59+
"141.284825 degrees explementary"
60+
);
61+
t.equal(
62+
round(angle([48.5, 5.5], [51.5, 12], [59, 15.5], { mercator: true }), 6),
63+
219.826106,
64+
"219.826106 degrees mercator"
65+
);
66+
t.equal(
67+
round(
68+
angle([48.5, 5.5], [51.5, 12], [59, 15.5], {
69+
explementary: true,
70+
mercator: true,
71+
}),
72+
6
73+
),
74+
140.173894,
75+
"140.173894 degrees explementary mercator"
76+
);
77+
t.end();
78+
});
79+
80+
test("turf-angle -- obtuse bigger", (t) => {
81+
t.equal(
82+
round(angle([48.5, 5.5], [51.5, 12], [46.5, 19]), 6),
83+
121.330117,
84+
"121.330117 degrees"
85+
);
86+
t.equal(
87+
round(
88+
angle([48.5, 5.5], [51.5, 12], [46.5, 19], { explementary: true }),
89+
6
90+
),
91+
238.669883,
92+
"238.669883 degrees explementary"
93+
);
94+
t.equal(
95+
round(angle([48.5, 5.5], [51.5, 12], [46.5, 19], { mercator: true }), 6),
96+
120.970546,
97+
"120.970546"
98+
);
99+
t.equal(
100+
round(
101+
angle([48.5, 5.5], [51.5, 12], [46.5, 19], {
102+
explementary: true,
103+
mercator: true,
104+
}),
105+
6
106+
),
107+
239.029454,
108+
"239.029454 degrees explementary mercator"
109+
);
110+
t.end();
111+
});
112+
113+
test("turf-angle -- acute", (t) => {
114+
t.equal(
115+
round(angle([48.5, 5.5], [51.5, 12], [44.5, 10.5]), 6),
116+
53.608314,
117+
"53.608314 degrees"
118+
);
119+
t.equal(
120+
round(
121+
angle([48.5, 5.5], [51.5, 12], [44.5, 10.5], { explementary: true }),
122+
6
123+
),
124+
306.391686,
125+
"306.391686 degrees explementary"
126+
);
127+
t.equal(
128+
round(angle([48.5, 5.5], [51.5, 12], [44.5, 10.5], { mercator: true }), 6),
129+
53.166357,
130+
"53.166357 degrees mercator"
131+
);
132+
t.equal(
133+
round(
134+
angle([48.5, 5.5], [51.5, 12], [44.5, 10.5], {
46135
explementary: true,
47-
fill: "#00F",
48-
stroke: "#00F",
49-
"fill-opacity": 0.3,
50-
};
51-
const results = featureCollection([
52-
truncate(
53-
sector(
54-
mid,
55-
distance(mid, start) / 3,
56-
bearing(mid, start),
57-
bearing(mid, end),
58-
{ properties: angleProperties }
59-
)
60-
),
61-
truncate(
62-
sector(
63-
mid,
64-
distance(mid, start) / 2,
65-
bearing(mid, end),
66-
bearing(mid, start),
67-
{ properties: angleExplementaryProperties }
68-
)
69-
),
70-
lineString(
71-
[
72-
start.geometry.coordinates,
73-
mid.geometry.coordinates,
74-
end.geometry.coordinates,
75-
],
76-
{ "stroke-width": 4, stroke: "#222" }
77-
),
78-
start,
79-
mid,
80-
end,
81-
]);
82-
83-
// Save results
84-
const expected = filepath.replace(
85-
path.join("test", "in"),
86-
path.join("test", "out")
87-
);
88-
if (process.env.REGEN) writeJsonFileSync(expected, results);
89-
t.deepEqual(results, loadJsonFileSync(expected), name);
90-
});
136+
mercator: true,
137+
}),
138+
6
139+
),
140+
306.833643,
141+
"306.833643 degrees explementary mercator"
142+
);
143+
t.end();
144+
});
145+
146+
test("turf-angle -- acute inverse", (t) => {
147+
t.equal(
148+
round(angle([44.5, 10.5], [51.5, 12], [48.5, 5.5]), 6),
149+
306.391686,
150+
"306.391686 degrees"
151+
);
152+
t.equal(
153+
round(
154+
angle([44.5, 10.5], [51.5, 12], [48.5, 5.5], { explementary: true }),
155+
6
156+
),
157+
53.608314,
158+
"53.608314 degrees explementary"
159+
);
160+
t.equal(
161+
round(angle([44.5, 10.5], [51.5, 12], [48.5, 5.5], { mercator: true }), 6),
162+
306.833643,
163+
"306.833643 degrees mercator"
164+
);
165+
t.equal(
166+
round(
167+
angle([44.5, 10.5], [51.5, 12], [48.5, 5.5], {
168+
explementary: true,
169+
mercator: true,
170+
}),
171+
6
172+
),
173+
53.166357,
174+
"53.166357 degrees explementary mercator"
175+
);
91176
t.end();
92177
});
93178

94179
test("turf-angle -- simple", (t) => {
95180
t.equal(round(angle([5, 5], [5, 6], [3, 4])), 45, "45 degrees");
96-
t.equal(round(angle([3, 4], [5, 6], [5, 5])), 45, "45 degrees -- inverse");
181+
t.equal(round(angle([3, 4], [5, 6], [5, 5])), 315, "315 degrees -- inverse");
97182
t.equal(
98-
round(angle([3, 4], [5, 6], [5, 5], { explementary: true })),
183+
round(angle([5, 5], [5, 6], [3, 4], { explementary: true })),
99184
360 - 45,
100185
"explementary angle"
101186
);
@@ -139,3 +224,19 @@ test("turf-angle -- throws", (t) => {
139224

140225
t.end();
141226
});
227+
228+
test("turf-angle -- 2703", (t) => {
229+
const start = [0, 1];
230+
const mid = [0, 0];
231+
const end = [1, 0];
232+
const a = angle(start, mid, end);
233+
t.equal(a, 90, "90 clockwise");
234+
235+
const start2 = [0, 1];
236+
const mid2 = [0, 0];
237+
const end2 = [-1, 0];
238+
const a2 = angle(start2, mid2, end2);
239+
t.equal(a2, 270, "270 clockwise");
240+
241+
t.end();
242+
});

packages/turf-angle/test/in/90-degrees.json

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)