-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathterminator.ts
More file actions
81 lines (70 loc) · 2.62 KB
/
Copy pathterminator.ts
File metadata and controls
81 lines (70 loc) · 2.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// Terminator polygon for an equirectangular map with a configurable
// center longitude. Vertices are returned in **eastward pixel-space
// longitude** lonE ∈ [0, 360], where lonE = 0 is the canvas's left
// edge and lonE = 360 is its right edge. With this parameterization
// x walks monotonically 0 → W and the polygon never crosses the seam
// mid-walk — the seam (centerLon ± 180) sits at lonE ∈ {0, 360}, i.e.
// the polygon's start and end.
//
// At each vertex the terminator latitude solves sun-elevation = 0:
//
// 0 = sin(lat)·sin(δ) + cos(lat)·cos(δ)·cos(H)
// ⇒ tan(lat) = −cos(H) / tan(δ) (H = realLon − λ_subsolar)
//
// δ is floored at a small epsilon to avoid 0/0 at exact equinox.
import type { SubsolarPoint } from './sun.js';
const DEG = Math.PI / 180;
const RAD = 180 / Math.PI;
const MIN_ABS_DECL_DEG = 1e-4;
export type LonLat = readonly [number, number];
export interface TerminatorOptions {
/** longitude step in degrees; smaller = smoother polygon. Default 1°. */
stepDeg?: number;
/** longitude that should appear at the center of the map.
* Default 180 (antimeridian-centered). */
centerLon?: number;
}
/**
* Just the great-circle curve (open polyline) of the terminator at
* the given subsolar point. Use this when you want to stroke the
* terminator boundary alone — without the closing path that wraps
* around the dark pole.
*/
export function terminatorCurve(
sub: SubsolarPoint,
opts: TerminatorOptions = {},
): LonLat[] {
const step = opts.stepDeg ?? 1;
const centerLon = opts.centerLon ?? 180;
const leftEdgeLon = centerLon - 180;
let decl = sub.lat;
if (Math.abs(decl) < MIN_ABS_DECL_DEG) {
decl = decl >= 0 ? MIN_ABS_DECL_DEG : -MIN_ABS_DECL_DEG;
}
const tanDecl = Math.tan(decl * DEG);
const points: LonLat[] = [];
for (let lonE = 0; lonE <= 360; lonE += step) {
const realLon = leftEdgeLon + lonE;
const H = (realLon - sub.lon) * DEG;
const lat = Math.atan(-Math.cos(H) / tanDecl) * RAD;
points.push([lonE, lat]);
}
return points;
}
/**
* Closed polygon covering the night hemisphere. Same as `terminatorCurve`
* but with two extra vertices added at the dark pole's edge so the
* polygon, when filled, occupies the dark side of the planet.
*/
export function terminatorPolygon(
sub: SubsolarPoint,
opts: TerminatorOptions = {},
): LonLat[] {
const curve = terminatorCurve(sub, opts);
let decl = sub.lat;
if (Math.abs(decl) < MIN_ABS_DECL_DEG) {
decl = decl >= 0 ? MIN_ABS_DECL_DEG : -MIN_ABS_DECL_DEG;
}
const darkPoleLat = decl > 0 ? -90 : 90;
return [...curve, [360, darkPoleLat], [0, darkPoleLat]];
}