@openwaters/almanac 0.7.0 → 0.8.1

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/dist/planets.js CHANGED
@@ -35,6 +35,13 @@ function visualMagnitude(planet, phase, helioDist, geoDist) {
35
35
  c0 = -9.40;
36
36
  c1 = 0.50;
37
37
  break;
38
+ case 'uranus':
39
+ c0 = -7.19;
40
+ c1 = 0.25;
41
+ break;
42
+ case 'neptune':
43
+ c0 = -6.87;
44
+ break;
38
45
  default: throw new Error('unsupported planet in visualMagnitude');
39
46
  }
40
47
  const x = phase / 100;
@@ -32,3 +32,18 @@ export declare function sunPosition(time: Date): SunPosition;
32
32
  * the Earth-Moon centre distance in kilometres.
33
33
  */
34
34
  export declare function moonPosition(time: Date): MoonPosition;
35
+ /**
36
+ * INTERNAL: equation of time for a TT instant, in minutes. Not from upstream:
37
+ * Meeus, *Astronomical Algorithms*, formula 28.1, `E = L0 − 0.0057183° − α +
38
+ * Δψ cos ε`, with `L0` the Sun's mean longitude from formula 28.2 and `α` the
39
+ * apparent right ascension `sunApparentAtTT` already computes. The constant
40
+ * is aberration plus the FK5 correction; `Δψ cos ε` moves `L0` from the mean
41
+ * equinox to the true one `α` is measured from.
42
+ */
43
+ export declare function equationOfTimeAtTT(tt: number): number;
44
+ /**
45
+ * Equation of time: apparent solar time minus mean solar time, in minutes.
46
+ * Positive when a sundial runs ahead of the clock; about +16.4 at the start
47
+ * of November and −14.2 in mid February.
48
+ */
49
+ export declare function equationOfTime(time: Date): number;
package/dist/positions.js CHANGED
@@ -13,7 +13,7 @@
13
13
  // parallax subtraction) is zero and drops out.
14
14
  import { assertSupported } from './types.js';
15
15
  import { ttDays } from './time.js';
16
- import { KM_PER_AU, equatorialFromVector } from './nutation.js';
16
+ import { DEG2RAD, KM_PER_AU, earthTilt, equatorialFromVector } from './nutation.js';
17
17
  import { moonGeoVector } from './moon.js';
18
18
  import { sunGeoVector } from './sun.js';
19
19
  /**
@@ -50,3 +50,30 @@ export function moonPosition(time) {
50
50
  assertSupported(time);
51
51
  return moonApparentAtTT(ttDays(time));
52
52
  }
53
+ /**
54
+ * INTERNAL: equation of time for a TT instant, in minutes. Not from upstream:
55
+ * Meeus, *Astronomical Algorithms*, formula 28.1, `E = L0 − 0.0057183° − α +
56
+ * Δψ cos ε`, with `L0` the Sun's mean longitude from formula 28.2 and `α` the
57
+ * apparent right ascension `sunApparentAtTT` already computes. The constant
58
+ * is aberration plus the FK5 correction; `Δψ cos ε` moves `L0` from the mean
59
+ * equinox to the true one `α` is measured from.
60
+ */
61
+ export function equationOfTimeAtTT(tt) {
62
+ const tau = tt / 365250;
63
+ const l0 = 280.4664567 + tau * (360007.6982779 + tau * (0.03032028
64
+ + tau * (1 / 49931 + tau * (-1 / 15300 + tau * (-1 / 2000000)))));
65
+ const tilt = earthTilt(tt);
66
+ const nutationRaDeg = (tilt.dpsi / 3600) * Math.cos(tilt.tobl * DEG2RAD);
67
+ let e = l0 - 0.0057183 - sunApparentAtTT(tt).raDeg + nutationRaDeg;
68
+ e = ((e % 360) + 540) % 360 - 180; // (−180, 180]
69
+ return e * 4; // 360° of right ascension is 1440 minutes
70
+ }
71
+ /**
72
+ * Equation of time: apparent solar time minus mean solar time, in minutes.
73
+ * Positive when a sundial runs ahead of the clock; about +16.4 at the start
74
+ * of November and −14.2 in mid February.
75
+ */
76
+ export function equationOfTime(time) {
77
+ assertSupported(time);
78
+ return equationOfTimeAtTT(ttDays(time));
79
+ }
@@ -0,0 +1,17 @@
1
+ /** The season the Sun's longitude starts: 0° March equinox, 90° June solstice, 180° September equinox, 270° December solstice. */
2
+ export type SeasonKind = 'marchEquinox' | 'juneSolstice' | 'septemberEquinox' | 'decemberSolstice';
3
+ /** An equinox or solstice. */
4
+ export interface SeasonEvent {
5
+ time: Date;
6
+ kind: SeasonKind;
7
+ }
8
+ /**
9
+ * The equinoxes and solstices within the half-open window `[startUtc, endUtc)`,
10
+ * sorted ascending.
11
+ *
12
+ * A season starts the instant the Sun's apparent geocentric ecliptic longitude
13
+ * reaches 0° (March equinox), 90° (June solstice), 180° (September equinox) or
14
+ * 270° (December solstice). The names are months, not seasons, because the
15
+ * same instant starts opposite seasons in the two hemispheres.
16
+ */
17
+ export declare function seasons(startUtc: Date, endUtc: Date): SeasonEvent[];
@@ -0,0 +1,73 @@
1
+ // L3 season search: the four instants a year when the Sun's apparent
2
+ // geocentric ecliptic longitude reaches 0°, 90°, 180° and 270°.
3
+ //
4
+ // Translated from the cosinekitty/astronomy upstream, pinned sha
5
+ // 865d3da7d8112bbc7911238052c6af4aaf877181, source/js/astronomy.ts:
6
+ // `SearchSunLongitude` (~4787), whose `sun_offset` and 0.01 s `Search` are
7
+ // kept. Upstream's `Seasons(year)` (~6130) brackets each search from a fixed
8
+ // calendar date. Here the bracket is a cell of a fixed 80-day grid anchored at
9
+ // J2000, so every window that contains a season finds it from the same bracket
10
+ // and reports the same millisecond — the fixed-seed rule the eclipse searches
11
+ // follow. A cell spans under 90° of solar longitude, so it holds at most one
12
+ // quarter crossing.
13
+ //
14
+ // INTERNAL except `seasons`: not part of the curated public API (see index.ts).
15
+ import { assertSupported, assertSupportedWindowEnd } from './types.js';
16
+ import { utDays, ttDaysFromUt, dateFromUt } from './time.js';
17
+ import { sunEclipticLonDeg } from './illumination.js';
18
+ import { search, angleOffset, quantizedUt, assertReached } from './events.js';
19
+ const SEASON_KINDS = ['marchEquinox', 'juneSolstice', 'septemberEquinox', 'decemberSolstice'];
20
+ /** Grid cell, in days since J2000 UT: under the 89-day shortest season, so a cell holds at most one crossing. */
21
+ const SEASON_CELL_DAYS = 80;
22
+ function sunLonDeg(ut) {
23
+ return sunEclipticLonDeg(ttDaysFromUt(ut));
24
+ }
25
+ /**
26
+ * UPSTREAM: `SearchSunLongitude` on one grid cell `[t1, t2]`, with
27
+ * `lon1 = sunLonDeg(t1)` already in hand. Returns the crossing of the next
28
+ * multiple of 90° after `lon1`, or `null` when the cell ends before it.
29
+ */
30
+ function searchSeasonInCell(t1, t2, lon1) {
31
+ const quarter = (Math.floor(lon1 / 90) + 1) % 4;
32
+ const targetLonDeg = 90 * quarter;
33
+ const sunOffset = (ut) => angleOffset(sunLonDeg(ut) - targetLonDeg);
34
+ if (sunOffset(t2) < 0)
35
+ return null;
36
+ const ut = search(sunOffset, t1, t2, 0.01, undefined, 'season search');
37
+ assertReached(ut !== null, 'season search');
38
+ return { ut: ut, quarter };
39
+ }
40
+ /**
41
+ * The equinoxes and solstices within the half-open window `[startUtc, endUtc)`,
42
+ * sorted ascending.
43
+ *
44
+ * A season starts the instant the Sun's apparent geocentric ecliptic longitude
45
+ * reaches 0° (March equinox), 90° (June solstice), 180° (September equinox) or
46
+ * 270° (December solstice). The names are months, not seasons, because the
47
+ * same instant starts opposite seasons in the two hemispheres.
48
+ */
49
+ export function seasons(startUtc, endUtc) {
50
+ assertSupported(startUtc);
51
+ assertSupportedWindowEnd(endUtc);
52
+ if (startUtc.getTime() >= endUtc.getTime())
53
+ return [];
54
+ const startUt = utDays(startUtc);
55
+ const endUt = utDays(endUtc);
56
+ const events = [];
57
+ let t1 = Math.floor(startUt / SEASON_CELL_DAYS) * SEASON_CELL_DAYS;
58
+ let lon1 = sunLonDeg(t1);
59
+ while (t1 < endUt) {
60
+ const t2 = t1 + SEASON_CELL_DAYS;
61
+ const found = searchSeasonInCell(t1, t2, lon1);
62
+ if (found !== null) {
63
+ const q = quantizedUt(found.ut); // judge the half-open bounds on the reported instant, not the raw root
64
+ if (q >= endUt)
65
+ break;
66
+ if (q >= startUt)
67
+ events.push({ time: dateFromUt(found.ut), kind: SEASON_KINDS[found.quarter] });
68
+ }
69
+ t1 = t2;
70
+ lon1 = sunLonDeg(t1);
71
+ }
72
+ return events;
73
+ }
@@ -7,13 +7,16 @@ export interface AltAz {
7
7
  }
8
8
  /**
9
9
  * INTERNAL: what {@link topoAltAzUnrefracted} already computes on the way to
10
- * az/alt. The L3 event layer needs all four from a single evaluation — the
11
- * local hour angle brackets each daily cycle's altitude extrema, and the
12
- * topocentric distance sets the Moon's semidiameter, hence its rise/set target.
10
+ * az/alt. The L3 event layer needs these from a single evaluation — the
11
+ * local hour angle brackets each daily cycle's altitude extrema, the
12
+ * topocentric distance sets the Moon's semidiameter, hence its rise/set target,
13
+ * and the topocentric RA/Dec orient the Moon's disc.
13
14
  */
14
15
  export interface TopoUnrefracted extends AltAz {
15
16
  hourAngleDeg: number;
16
17
  distanceAu: number;
18
+ raDeg: number;
19
+ decDeg: number;
17
20
  }
18
21
  /**
19
22
  * UPSTREAM: `sidereal_time`, astronomy.ts ~2031 — Greenwich Apparent Sidereal
@@ -160,7 +160,7 @@ export function topoAltAzUnrefracted(bodyEqj, ut, observer) {
160
160
  let hourAngleDeg = (gast + observer.longitudeDeg - raDeg) % 360;
161
161
  if (hourAngleDeg < 0)
162
162
  hourAngleDeg += 360;
163
- return { azDeg: az, altDeg: 90 - zd, hourAngleDeg, distanceAu };
163
+ return { azDeg: az, altDeg: 90 - zd, hourAngleDeg, distanceAu, raDeg, decDeg };
164
164
  }
165
165
  /**
166
166
  * UPSTREAM: `Refraction('normal', altitude)`, astronomy.ts ~7176 — the
package/dist/types.d.ts CHANGED
@@ -3,7 +3,7 @@ export interface Observer {
3
3
  longitudeDeg: number;
4
4
  elevationM?: number;
5
5
  }
6
- export type Planet = 'mercury' | 'venus' | 'earth' | 'mars' | 'jupiter' | 'saturn';
6
+ export type Planet = 'mercury' | 'venus' | 'earth' | 'mars' | 'jupiter' | 'saturn' | 'uranus' | 'neptune';
7
7
  /** INTERNAL: Earth has no planetary direction from an Earth-based observer. */
8
8
  export declare function assertPlanet(planet: Planet, earthAllowed?: boolean): void;
9
9
  export declare class AlmanacOutOfRangeError extends RangeError {
package/dist/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /** INTERNAL: Earth has no planetary direction from an Earth-based observer. */
2
2
  export function assertPlanet(planet, earthAllowed = false) {
3
- if (!['mercury', 'venus', 'earth', 'mars', 'jupiter', 'saturn'].includes(planet) || (!earthAllowed && planet === 'earth'))
3
+ if (!['mercury', 'venus', 'earth', 'mars', 'jupiter', 'saturn', 'uranus', 'neptune'].includes(planet) || (!earthAllowed && planet === 'earth'))
4
4
  throw new RangeError(`invalid planet: ${planet}`);
5
5
  }
6
6
  export class AlmanacOutOfRangeError extends RangeError {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwaters/almanac",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Offline astronomy: Sun, Moon and planet positions, apparent horizon, rise/set, phases and eclipses.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -18,7 +18,8 @@
18
18
  "ephemeris",
19
19
  "lunar-eclipse",
20
20
  "solar-eclipse",
21
- "marine"
21
+ "marine",
22
+ "suncalc"
22
23
  ],
23
24
  "publishConfig": {
24
25
  "access": "public"