@openwaters/almanac 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/globalSolar.d.ts +6 -0
- package/dist/globalSolar.js +55 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,7 +101,7 @@ const { azDeg, altDeg } = starAltAz(88.792939, 7.407064, today, observer);
|
|
|
101
101
|
### Swift
|
|
102
102
|
|
|
103
103
|
```swift
|
|
104
|
-
.package(url: "https://github.com/openwatersio/almanac.git", exact: "0.
|
|
104
|
+
.package(url: "https://github.com/openwatersio/almanac.git", exact: "0.6.0")
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
```swift
|
|
@@ -155,7 +155,7 @@ anchor. Search results are global; apply `lunarEclipseVisibility` for an observe
|
|
|
155
155
|
|
|
156
156
|
Solar eclipses are searched for an observer, because their contacts only exist for a place: `nextSolarEclipse(after, observer)`, `previousSolarEclipse(before, observer)`, and `solarEclipses(startUtc, endUtc, observer)`. An eclipse whose Sun is below the horizon at C1, the peak, and C4 is not returned.
|
|
157
157
|
|
|
158
|
-
To ask about the whole Earth instead, `nextGlobalSolarEclipse(after)`, `previousGlobalSolarEclipse(before)`, and `globalSolarEclipses(startUtc, endUtc)` need no observer. Each eclipse reports its greatest eclipse, the shadow axis's distance from the Earth's center, and where the axis meets the ground, with the kind and obscuration seen there. When the axis misses the Earth the eclipse is partial and has no ground point.
|
|
158
|
+
To ask about the whole Earth instead, `nextGlobalSolarEclipse(after)`, `previousGlobalSolarEclipse(before)`, and `globalSolarEclipses(startUtc, endUtc)` need no observer. Each eclipse reports its greatest eclipse, the shadow axis's distance from the Earth's center, and where the axis meets the ground, with the kind and obscuration seen there. When the axis misses the Earth the eclipse is partial and has no ground point, and greatest eclipse falls on the Earth's limb nearest the axis, with the Sun on the horizon: `greatestLatitudeDeg`, `greatestLongitudeDeg`, and `greatestObscuration` give that place for every eclipse.
|
|
159
159
|
|
|
160
160
|
`solarEclipseCentralLine(peak, stepSeconds)` samples where the axis meets the ground from its first contact to its last, and `solarEclipseAxisPoint(time)` gives that point at any instant. How far the next totality passes from a place is a distance to each point of a line:
|
|
161
161
|
|
package/dist/globalSolar.d.ts
CHANGED
|
@@ -13,6 +13,12 @@ export interface GlobalSolarEclipse {
|
|
|
13
13
|
longitudeDeg: number | null;
|
|
14
14
|
/** Fraction of the Sun's disc area covered at that point; exactly 1 for a total eclipse; `null` for a partial eclipse. */
|
|
15
15
|
obscuration: number | null;
|
|
16
|
+
/** Geodetic latitude of greatest eclipse, degrees: the ground point, or for a partial eclipse the point on the Earth's limb nearest the axis, with the Sun on the horizon. */
|
|
17
|
+
greatestLatitudeDeg: number;
|
|
18
|
+
/** Longitude of greatest eclipse, degrees east in (−180, 180]. */
|
|
19
|
+
greatestLongitudeDeg: number;
|
|
20
|
+
/** Fraction of the Sun's disc area covered at greatest eclipse. */
|
|
21
|
+
greatestObscuration: number;
|
|
16
22
|
}
|
|
17
23
|
/** A point on a solar eclipse's central line: where the Moon's shadow axis meets the ground at one instant. */
|
|
18
24
|
export interface SolarEclipseAxisPoint {
|
package/dist/globalSolar.js
CHANGED
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
// quadratic's discriminant, and an axis point rejects the crossing on the
|
|
31
31
|
// Sun's side of the Moon that upstream, asking only at greatest eclipse,
|
|
32
32
|
// never meets.
|
|
33
|
+
//
|
|
34
|
+
// Neither is greatest eclipse for an axis that misses the Earth: upstream
|
|
35
|
+
// reports no place at all, and this package reports the point on the limb
|
|
36
|
+
// nearest the axis, where the Sun is on the horizon.
|
|
33
37
|
import { assertSupported, assertSupportedWindowEnd, AlmanacOutOfRangeError, SUPPORTED_MIN, SUPPORTED_MAX } from './types.js';
|
|
34
38
|
import { dateFromUt, ttDaysFromUt, utDays } from './time.js';
|
|
35
39
|
import { EARTH_EQUATORIAL_RADIUS_KM, EARTH_FLATTENING, EARTH_FLATTENING_SQUARED, KM_PER_AU, RAD2DEG, PrecessDirection, gyration } from './nutation.js';
|
|
@@ -133,6 +137,24 @@ function axisGround(shadow, axis, u) {
|
|
|
133
137
|
const px = u * v.x - e.x;
|
|
134
138
|
const py = u * v.y - e.y;
|
|
135
139
|
const pz = (u * v.z - e.z) * EARTH_FLATTENING;
|
|
140
|
+
const { latitudeDeg, longitudeDeg, o } = geodeticPlace(shadow, tt, px, py, pz);
|
|
141
|
+
// Recalculate the shadow using a vector from the Moon's center toward the observer.
|
|
142
|
+
const surface = calcShadow(MOON_POLAR_RADIUS_KM, shadow.ut, o, shadow.dir);
|
|
143
|
+
// If we did everything right, the shadow distance should be very close to zero.
|
|
144
|
+
// That's because we already determined the observer 'o' is on the shadow axis!
|
|
145
|
+
// Upstream's bound is 1e-9 km; exceeding it is an implementation failure.
|
|
146
|
+
if (surface.r > 1.0e-9 || surface.r < 0.0)
|
|
147
|
+
throw new Error(`almanac internal: unexpected shadow distance from geoid intersection = ${surface.r}`);
|
|
148
|
+
const kind = eclipseKindFromUmbra(surface.k);
|
|
149
|
+
const obscuration = (kind === 'total') ? 1.0 : solarEclipseObscuration(shadow.dir, o);
|
|
150
|
+
return { latitudeDeg, longitudeDeg, kind, obscuration };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* UPSTREAM: the middle of `GeoidIntersect`, astronomy.ts ~8842 — geodetic
|
|
154
|
+
* latitude and longitude of a geocentric equator-of-date point in km, and the
|
|
155
|
+
* lunacentric EQJ vector toward it in AU.
|
|
156
|
+
*/
|
|
157
|
+
function geodeticPlace(shadow, tt, px, py, pz) {
|
|
136
158
|
// Convert cartesian coordinates into geodetic latitude/longitude.
|
|
137
159
|
const proj = Math.hypot(px, py) * EARTH_FLATTENING_SQUARED;
|
|
138
160
|
let latitudeDeg;
|
|
@@ -153,16 +175,31 @@ function axisGround(shadow, axis, u) {
|
|
|
153
175
|
const og = gyration({ x: px / KM_PER_AU, y: py / KM_PER_AU, z: pz / KM_PER_AU }, tt, PrecessDirection.Into2000);
|
|
154
176
|
// Convert geocentric vector to lunacentric vector.
|
|
155
177
|
const o = { x: og.x + shadow.target.x, y: og.y + shadow.target.y, z: og.z + shadow.target.z };
|
|
156
|
-
|
|
178
|
+
return { latitudeDeg, longitudeDeg, o };
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Greatest eclipse when the axis misses the geoid: the point on the Earth's
|
|
182
|
+
* limb nearest the axis. In the dilated coordinates the geoid is a sphere, and
|
|
183
|
+
* the axis's closest approach to its center, scaled onto it, stands for that point.
|
|
184
|
+
* The axis runs perpendicular to the radius there, so it lies in the sphere's
|
|
185
|
+
* tangent plane; dilation maps tangent planes to tangent planes, so on the
|
|
186
|
+
* ellipsoid the axis lies in the geodetic horizon and the Sun's center is at
|
|
187
|
+
* geometric altitude zero, as the catalog tabulates. The observer there sees
|
|
188
|
+
* what the local search would: a total or annular phase only inside the
|
|
189
|
+
* umbra, which a non-central eclipse can graze.
|
|
190
|
+
*/
|
|
191
|
+
function limbGround(shadow, axis) {
|
|
192
|
+
const { tt, v, e, A, B } = axis;
|
|
193
|
+
const u = -B / (2 * A);
|
|
194
|
+
const cx = u * v.x - e.x;
|
|
195
|
+
const cy = u * v.y - e.y;
|
|
196
|
+
const cz = u * v.z - e.z;
|
|
197
|
+
const scale = EARTH_EQUATORIAL_RADIUS_KM / Math.sqrt(cx * cx + cy * cy + cz * cz);
|
|
198
|
+
const { latitudeDeg, longitudeDeg, o } = geodeticPlace(shadow, tt, cx * scale, cy * scale, cz * scale * EARTH_FLATTENING);
|
|
157
199
|
const surface = calcShadow(MOON_POLAR_RADIUS_KM, shadow.ut, o, shadow.dir);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (surface.r > 1.0e-9 || surface.r < 0.0)
|
|
162
|
-
throw new Error(`almanac internal: unexpected shadow distance from geoid intersection = ${surface.r}`);
|
|
163
|
-
const kind = eclipseKindFromUmbra(surface.k);
|
|
164
|
-
const obscuration = (kind === 'total') ? 1.0 : solarEclipseObscuration(shadow.dir, o);
|
|
165
|
-
return { latitudeDeg, longitudeDeg, kind, obscuration };
|
|
200
|
+
const obscuration = (surface.r < Math.abs(surface.k) && eclipseKindFromUmbra(surface.k) === 'total')
|
|
201
|
+
? 1.0 : solarEclipseObscuration(shadow.dir, o);
|
|
202
|
+
return { latitudeDeg, longitudeDeg, obscuration };
|
|
166
203
|
}
|
|
167
204
|
/**
|
|
168
205
|
* UPSTREAM: `GeoidIntersect`'s choice of root — where the axis first meets
|
|
@@ -201,13 +238,18 @@ function grazingGround(shadow) {
|
|
|
201
238
|
function geoidIntersect(shadow, peak) {
|
|
202
239
|
const ground = nearAxisGround(shadow);
|
|
203
240
|
if (ground === null) {
|
|
204
|
-
// This is a partial solar eclipse
|
|
205
|
-
//
|
|
206
|
-
|
|
241
|
+
// This is a partial solar eclipse: no one stands on the axis, and
|
|
242
|
+
// greatest eclipse is on the limb nearest it.
|
|
243
|
+
const limb = limbGround(shadow, dilatedAxis(shadow));
|
|
244
|
+
return {
|
|
245
|
+
kind: 'partial', peak, axisDistanceKm: shadow.r, latitudeDeg: null, longitudeDeg: null, obscuration: null,
|
|
246
|
+
greatestLatitudeDeg: limb.latitudeDeg, greatestLongitudeDeg: limb.longitudeDeg, greatestObscuration: limb.obscuration
|
|
247
|
+
};
|
|
207
248
|
}
|
|
208
249
|
return {
|
|
209
250
|
kind: ground.kind, peak, axisDistanceKm: shadow.r,
|
|
210
|
-
latitudeDeg: ground.latitudeDeg, longitudeDeg: ground.longitudeDeg, obscuration: ground.obscuration
|
|
251
|
+
latitudeDeg: ground.latitudeDeg, longitudeDeg: ground.longitudeDeg, obscuration: ground.obscuration,
|
|
252
|
+
greatestLatitudeDeg: ground.latitudeDeg, greatestLongitudeDeg: ground.longitudeDeg, greatestObscuration: ground.obscuration
|
|
211
253
|
};
|
|
212
254
|
}
|
|
213
255
|
/**
|