@openwaters/almanac 0.11.0 → 0.12.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Almanac
2
2
 
3
- Offline sky engine for the Salish Sea and everywhere else: Sun, Moon, and planet positions, apparent horizon dip, rise, set, twilight, any Sun altitude, upper and lower transits, Moon phase and orientation, equinoxes and solstices, lunar perigee and apogee, the equation of time, planetary brightness, lunar and solar eclipses, and fixed-star altitude and azimuth from catalog positions, computed from pure geometry with zero network and zero runtime data files.
3
+ Offline sky engine for the Salish Sea and everywhere else: Sun, Moon, and planet positions, apparent horizon dip, rise, set, twilight, any Sun altitude, upper and lower transits, Moon phase and orientation, equinoxes and solstices, lunar perigee and apogee, the equation of time, planetary brightness, lunar and solar eclipses, fixed-star apparent places, altitude, and azimuth from catalog positions, and Greenwich sidereal time for sight reduction, computed from pure geometry with zero network and zero runtime data files.
4
4
 
5
5
  Three implementations, one behavior:
6
6
 
@@ -111,6 +111,7 @@ npm install @openwaters/almanac
111
111
  ```ts
112
112
  import {
113
113
  nextLunarEclipse, lunarEclipseVisibility, nextSolarEclipse, solarObscuration, nextGlobalSolarEclipse, sunEvents, starAltAz,
114
+ starPosition, greenwichSiderealTime,
114
115
  seasons, moonApsides, earthApsides, equationOfTime
115
116
  } from '@openwaters/almanac';
116
117
 
@@ -136,6 +137,10 @@ for (const { kind, time } of sunEvents(today, tomorrow, observer)) {
136
137
  // Betelgeuse from its J2000 catalog position: az/alt in degrees, refracted.
137
138
  const { azDeg, altDeg } = starAltAz(88.792939, 7.407064, today, observer);
138
139
 
140
+ // Its apparent place and Greenwich hour angle for sight reduction, in degrees.
141
+ const place = starPosition(88.792939, 7.407064, today);
142
+ const ghaDeg = (greenwichSiderealTime(today) - place.raDeg + 360) % 360;
143
+
139
144
  // The year's equinoxes and solstices, the Moon's perigees and apogees with
140
145
  // distances, and the equation of time in minutes.
141
146
  const year = new Date(today.getUTCFullYear() + 1, 0, 1);
@@ -161,7 +166,7 @@ const crossings = sunEvents(today, tomorrow, viewer, eyeHeightM);
161
166
  Requires a Swift 6 or newer toolchain.
162
167
 
163
168
  ```swift
164
- .package(url: "https://github.com/openwatersio/almanac.git", exact: "0.11.0")
169
+ .package(url: "https://github.com/openwatersio/almanac.git", exact: "0.12.1")
165
170
  ```
166
171
 
167
172
  ```swift
@@ -193,6 +198,10 @@ for event in try sunEvents(from: today, to: tomorrow, observer: observer) {
193
198
  // Betelgeuse from its J2000 catalog position: az/alt in degrees, refracted.
194
199
  let star = try starAltAz(raDeg: 88.792939, decDeg: 7.407064, at: today, observer: observer)
195
200
 
201
+ // Its apparent place and Greenwich hour angle for sight reduction, in degrees.
202
+ let place = try starPosition(raDeg: 88.792939, decDeg: 7.407064, at: today)
203
+ let ghaDeg = (try greenwichSiderealTime(today) - place.raDeg + 360).truncatingRemainder(dividingBy: 360)
204
+
196
205
  // The year's equinoxes and solstices, the Moon's perigees and apogees with
197
206
  // distances, and the equation of time in minutes.
198
207
  let year = today.addingTimeInterval(366 * 24 * 60 * 60)
@@ -204,7 +213,7 @@ print(try equationOfTime(today)) // sundial minus clock, minutes
204
213
 
205
214
  ### Kotlin/JVM and Android
206
215
 
207
- The Kotlin library provides the same 38 functions and result fields, using `java.time.Instant` and immutable data classes. It targets JVM 8 and Android minSdk 26, builds with JDK 17, and has no runtime dependencies beyond the Kotlin standard library and platform APIs. Maven publication and a Kotlin benchmark runner are deferred; consume the source at a reviewed Git SHA as described in [`kotlin/README.md`](kotlin/README.md).
216
+ The Kotlin library provides the same functions and result fields, using `java.time.Instant` and immutable data classes. It targets JVM 8 and Android minSdk 26, builds with JDK 17, and has no runtime dependencies beyond the Kotlin standard library and platform APIs. Maven publication and a Kotlin benchmark runner are deferred; consume the source at a reviewed Git SHA as described in [`kotlin/README.md`](kotlin/README.md).
208
217
 
209
218
  ```kotlin
210
219
  import io.openwaters.almanac.*
package/dist/index.d.ts CHANGED
@@ -4,14 +4,14 @@ export type { HeliocentricPosition, PlanetPosition, PlanetIllumination } from '.
4
4
  export { planetHeliocentricPosition, planetPosition, planetAltAz, planetIllumination } from './planets.js';
5
5
  export { horizonDip } from './horizon.js';
6
6
  export { AlmanacOutOfRangeError } from './types.js';
7
- export type { SunPosition, MoonPosition } from './positions.js';
8
- export { sunPosition, moonPosition, equationOfTime } from './positions.js';
7
+ export type { SunPosition, MoonPosition, StarPosition } from './positions.js';
8
+ export { sunPosition, moonPosition, starPosition, equationOfTime } from './positions.js';
9
9
  export type { SeasonKind, SeasonEvent } from './seasons.js';
10
10
  export { seasons } from './seasons.js';
11
11
  export type { MoonApsisKind, MoonApsis } from './apsides.js';
12
12
  export { moonApsides } from './apsides.js';
13
13
  export type { AltAz } from './transforms.js';
14
- export { sunAltAz, moonAltAz, starAltAz } from './transforms.js';
14
+ export { sunAltAz, moonAltAz, starAltAz, greenwichSiderealTime } from './transforms.js';
15
15
  export type { MoonIllumination, MoonOrientation } from './illumination.js';
16
16
  export { moonIllumination, moonOrientation } from './illumination.js';
17
17
  export type { SunEvent, SunEventKind, MoonEvent, MoonEventKind, MoonPhaseEvent, MoonPhaseName, PlanetEvent, PlanetEventKind, AltitudeEvent, AltitudeEventKind, TransitEvent, TransitEventKind } from './events.js';
package/dist/index.js CHANGED
@@ -3,10 +3,10 @@ export { degreesToRadians, radiansToDegrees } from './angles.js';
3
3
  export { planetHeliocentricPosition, planetPosition, planetAltAz, planetIllumination } from './planets.js';
4
4
  export { horizonDip } from './horizon.js';
5
5
  export { AlmanacOutOfRangeError } from './types.js';
6
- export { sunPosition, moonPosition, equationOfTime } from './positions.js';
6
+ export { sunPosition, moonPosition, starPosition, equationOfTime } from './positions.js';
7
7
  export { seasons } from './seasons.js';
8
8
  export { moonApsides } from './apsides.js';
9
- export { sunAltAz, moonAltAz, starAltAz } from './transforms.js';
9
+ export { sunAltAz, moonAltAz, starAltAz, greenwichSiderealTime } from './transforms.js';
10
10
  export { moonIllumination, moonOrientation } from './illumination.js';
11
11
  export { sunIsUp, sunEvents, moonEvents, searchMoonPhases, planetEvents, sunAltitudeEvents, sunTransits, moonTransits } from './events.js';
12
12
  export { nextLunarEclipse, previousLunarEclipse, lunarEclipses, lunarEclipseVisibility } from './eclipse.js';
@@ -34,6 +34,12 @@ export declare enum PrecessDirection {
34
34
  }
35
35
  /** UPSTREAM: `Frac`, astronomy.ts line 235. */
36
36
  export declare function frac(x: number): number;
37
+ /**
38
+ * Not upstream: an angle wrapped into [0, 360). Upstream adds 360 to a
39
+ * negative remainder, and a remainder within an ulp below zero then rounds
40
+ * to 360 itself.
41
+ */
42
+ export declare function wrapDeg360(deg: number): number;
37
43
  /** UPSTREAM: `mean_obliq`, astronomy.ts lines 1437-1447. Degrees. */
38
44
  export declare function meanObliquityDeg(tt: number): number;
39
45
  /** UPSTREAM: `EarthTiltInfo`, astronomy.ts lines 1449-1457. dpsi/deps arcsec, mobl/tobl degrees. */
package/dist/nutation.js CHANGED
@@ -37,6 +37,19 @@ export var PrecessDirection;
37
37
  export function frac(x) {
38
38
  return x - Math.floor(x);
39
39
  }
40
+ /**
41
+ * Not upstream: an angle wrapped into [0, 360). Upstream adds 360 to a
42
+ * negative remainder, and a remainder within an ulp below zero then rounds
43
+ * to 360 itself.
44
+ */
45
+ export function wrapDeg360(deg) {
46
+ let r = deg % 360;
47
+ if (r < 0)
48
+ r += 360;
49
+ if (r >= 360)
50
+ r -= 360;
51
+ return r;
52
+ }
40
53
  /**
41
54
  * UPSTREAM: `iau2000b`, astronomy.ts lines 1392-1435. The IAU 2000B nutation
42
55
  * series truncated to its five largest terms; returns arcseconds.
@@ -247,9 +260,7 @@ export function equatorialFromVector(pos) {
247
260
  throw new Error('Indeterminate sky coordinates');
248
261
  return { raDeg: 0, decDeg: (pos.z < 0) ? -90 : +90, distanceAu: dist };
249
262
  }
250
- let raDeg = RAD2DEG * Math.atan2(pos.y, pos.x);
251
- if (raDeg < 0)
252
- raDeg += 360;
263
+ const raDeg = wrapDeg360(RAD2DEG * Math.atan2(pos.y, pos.x));
253
264
  const decDeg = RAD2DEG * Math.atan2(pos.z, Math.sqrt(xyproj));
254
265
  return { raDeg, decDeg, distanceAu: dist };
255
266
  }
@@ -10,6 +10,11 @@ export interface MoonPosition {
10
10
  decDeg: number;
11
11
  distanceKm: number;
12
12
  }
13
+ /** Apparent geocentric position of a fixed star: equator of date, RA in [0, 360). */
14
+ export interface StarPosition {
15
+ raDeg: number;
16
+ decDeg: number;
17
+ }
13
18
  /**
14
19
  * INTERNAL: apparent Sun for a TT instant, expressed as days since the J2000
15
20
  * epoch in Terrestrial Time. This is the entry point the TT-labeled position
@@ -32,6 +37,17 @@ export declare function sunPosition(time: Date): SunPosition;
32
37
  * the Earth-Moon centre distance in kilometres.
33
38
  */
34
39
  export declare function moonPosition(time: Date): MoonPosition;
40
+ /**
41
+ * Apparent geocentric position of a fixed star from its J2000 (ICRS) catalog
42
+ * right ascension and declination, both in degrees — right ascension and
43
+ * declination on the true equator and equinox of date, with annual
44
+ * aberration, precession and nutation applied. Proper motion and stellar
45
+ * parallax are not applied.
46
+ *
47
+ * UPSTREAM: `Equator(star, date, observer, ofdate=true, aberration=true)`
48
+ * (astronomy.ts ~2803) with the geocentric observer term dropped.
49
+ */
50
+ export declare function starPosition(raDeg: number, decDeg: number, time: Date): StarPosition;
35
51
  /**
36
52
  * INTERNAL: equation of time for a TT instant, in minutes. Not from upstream:
37
53
  * Meeus, *Astronomical Algorithms*, formula 28.1, `E = L0 − 0.0057183° − α +
package/dist/positions.js CHANGED
@@ -1,5 +1,5 @@
1
- // L1 public positions: apparent geocentric Sun and Moon on the true equator and
2
- // equinox of date.
1
+ // L1 public positions: apparent geocentric Sun, Moon and fixed stars on the true
2
+ // equator and equinox of date.
3
3
  //
4
4
  // The composition chain mirrors the cosinekitty/astronomy upstream (pinned sha
5
5
  // 865d3da7d8112bbc7911238052c6af4aaf877181):
@@ -13,9 +13,10 @@
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 { DEG2RAD, KM_PER_AU, earthTilt, equatorialFromVector } from './nutation.js';
16
+ import { DEG2RAD, KM_PER_AU, PrecessDirection, earthTilt, equatorialFromVector, gyration } from './nutation.js';
17
17
  import { moonGeoVector } from './moon.js';
18
18
  import { sunGeoVector } from './sun.js';
19
+ import { assertStarCatalog, starGeoVectorEqj } from './transforms.js';
19
20
  /**
20
21
  * INTERNAL: apparent Sun for a TT instant, expressed as days since the J2000
21
22
  * epoch in Terrestrial Time. This is the entry point the TT-labeled position
@@ -50,6 +51,23 @@ export function moonPosition(time) {
50
51
  assertSupported(time);
51
52
  return moonApparentAtTT(ttDays(time));
52
53
  }
54
+ /**
55
+ * Apparent geocentric position of a fixed star from its J2000 (ICRS) catalog
56
+ * right ascension and declination, both in degrees — right ascension and
57
+ * declination on the true equator and equinox of date, with annual
58
+ * aberration, precession and nutation applied. Proper motion and stellar
59
+ * parallax are not applied.
60
+ *
61
+ * UPSTREAM: `Equator(star, date, observer, ofdate=true, aberration=true)`
62
+ * (astronomy.ts ~2803) with the geocentric observer term dropped.
63
+ */
64
+ export function starPosition(raDeg, decDeg, time) {
65
+ assertSupported(time);
66
+ assertStarCatalog(raDeg, decDeg);
67
+ const tt = ttDays(time);
68
+ const { raDeg: ra, decDeg: dec } = equatorialFromVector(gyration(starGeoVectorEqj(raDeg, decDeg, tt), tt, PrecessDirection.From2000));
69
+ return { raDeg: ra, decDeg: dec };
70
+ }
53
71
  /**
54
72
  * INTERNAL: equation of time for a TT instant, in minutes. Not from upstream:
55
73
  * Meeus, *Astronomical Algorithms*, formula 28.1, `E = L0 − 0.0057183° − α +
package/dist/sun.d.ts CHANGED
@@ -17,6 +17,18 @@ export declare function earthHelioDistance(tt: number): number;
17
17
  * `vsop.Earth` — i.e. `HelioVector(Body.Earth, time)`. J2000 mean equator (EQJ).
18
18
  */
19
19
  export declare function earthHelioVector(tt: number): Vec3;
20
+ /** Heliocentric position in AU and velocity in AU per day, J2000 mean equator (EQJ). */
21
+ export interface HelioState {
22
+ pos: Vec3;
23
+ vel: Vec3;
24
+ }
25
+ /**
26
+ * UPSTREAM: `CalcVsopPosVel`, astronomy.ts lines 3264-3314, specialised to
27
+ * `vsop.Earth` — i.e. `HelioState(Body.Earth, time)`. Not upstream: a
28
+ * one-entry cache on the exact instant, because a star catalog drawn for one
29
+ * moment asks for the same Earth state once per star.
30
+ */
31
+ export declare function earthHelioState(tt: number): HelioState;
20
32
  /**
21
33
  * UPSTREAM: `CorrectLightTravel` (astronomy.ts 4170-4191) driving
22
34
  * `BodyPosition.Position` (4193-4241) with observerBody = Earth,
package/dist/sun.js CHANGED
@@ -237,6 +237,75 @@ export function earthHelioVector(tt) {
237
237
  const eclip = vsopSphereToRect(lon, lat, rad);
238
238
  return vsopRotate(eclip);
239
239
  }
240
+ /**
241
+ * UPSTREAM: `VsopDeriv`, astronomy.ts lines 3207-3227 — the time derivative
242
+ * of a VSOP series, per millennium. It loops over the table rather than
243
+ * unrolling like `earthLon`: only the star path needs it, once per call.
244
+ */
245
+ function vsopDeriv(formula, t) {
246
+ let tpower = 1; // t^s
247
+ let dpower = 0; // t^(s-1)
248
+ let deriv = 0;
249
+ let s = 0;
250
+ for (const series of formula) {
251
+ let sin_sum = 0;
252
+ let cos_sum = 0;
253
+ for (const [ampl, phas, freq] of series) {
254
+ const angle = phas + (t * freq);
255
+ sin_sum += ampl * freq * Math.sin(angle);
256
+ if (s > 0)
257
+ cos_sum += ampl * Math.cos(angle);
258
+ }
259
+ deriv += (s * dpower * cos_sum) - (tpower * sin_sum);
260
+ dpower = tpower;
261
+ tpower *= t;
262
+ ++s;
263
+ }
264
+ return deriv;
265
+ }
266
+ let cacheEarthState;
267
+ let cacheEarthStateTt = NaN;
268
+ /**
269
+ * UPSTREAM: `CalcVsopPosVel`, astronomy.ts lines 3264-3314, specialised to
270
+ * `vsop.Earth` — i.e. `HelioState(Body.Earth, time)`. Not upstream: a
271
+ * one-entry cache on the exact instant, because a star catalog drawn for one
272
+ * moment asks for the same Earth state once per star.
273
+ */
274
+ export function earthHelioState(tt) {
275
+ if (!cacheEarthState || cacheEarthStateTt !== tt) {
276
+ cacheEarthState = computeEarthHelioState(tt);
277
+ cacheEarthStateTt = tt;
278
+ }
279
+ return cacheEarthState;
280
+ }
281
+ function computeEarthHelioState(tt) {
282
+ const t = tt / DAYS_PER_MILLENNIUM;
283
+ const lon = earthLon(t);
284
+ const lat = earthLat(t);
285
+ const rad = earthRad(t);
286
+ const dlon_dt = vsopDeriv(vsopEarth[0], t);
287
+ const dlat_dt = vsopDeriv(vsopEarth[1], t);
288
+ const drad_dt = vsopDeriv(vsopEarth[2], t);
289
+ const coslon = Math.cos(lon);
290
+ const sinlon = Math.sin(lon);
291
+ const coslat = Math.cos(lat);
292
+ const sinlat = Math.sin(lat);
293
+ const vx = (+(drad_dt * coslat * coslon)
294
+ - (rad * sinlat * coslon * dlat_dt)
295
+ - (rad * coslat * sinlon * dlon_dt));
296
+ const vy = (+(drad_dt * coslat * sinlon)
297
+ - (rad * sinlat * sinlon * dlat_dt)
298
+ + (rad * coslat * coslon * dlon_dt));
299
+ const vz = (+(drad_dt * sinlat)
300
+ + (rad * coslat * dlat_dt));
301
+ // AU per millennium to AU per day.
302
+ const eclipVel = {
303
+ x: vx / DAYS_PER_MILLENNIUM,
304
+ y: vy / DAYS_PER_MILLENNIUM,
305
+ z: vz / DAYS_PER_MILLENNIUM
306
+ };
307
+ return { pos: vsopRotate(vsopSphereToRect(lon, lat, rad)), vel: vsopRotate(eclipVel) };
308
+ }
240
309
  /**
241
310
  * UPSTREAM: `CorrectLightTravel` (astronomy.ts 4170-4191) driving
242
311
  * `BodyPosition.Position` (4193-4241) with observerBody = Earth,
@@ -72,15 +72,35 @@ export declare function sunAltAz(time: Date, observer: Observer): AltAz;
72
72
  * refraction (upstream `'normal'` model) both applied.
73
73
  */
74
74
  export declare function moonAltAz(time: Date, observer: Observer): AltAz;
75
+ /** INTERNAL: shared argument checks for the J2000 catalog position the star functions take. */
76
+ export declare function assertStarCatalog(raDeg: number, decDeg: number): void;
77
+ /**
78
+ * UPSTREAM: `GeoVector` for a `DefineStar` body with aberration = true —
79
+ * `HelioVector`'s `VectorFromSphere(Spherical(dec, 15*ra, dist))`
80
+ * (astronomy.ts ~4097, 7015) through the user-defined-star branch of
81
+ * `BackdatePosition` (4270-4300). The star is not backdated; the vector from
82
+ * Earth to the star is shifted by Earth's heliocentric velocity over the
83
+ * light travel time, which is the annual aberration, at most 20.5″.
84
+ *
85
+ * @returns geocentric position in AU, J2000 mean equator (EQJ).
86
+ */
87
+ export declare function starGeoVectorEqj(raDeg: number, decDeg: number, tt: number): Vec3;
75
88
  /**
76
89
  * Topocentric azimuth/altitude of a fixed star from its J2000 (ICRS)
77
90
  * catalog right ascension and declination, both in degrees.
78
91
  *
79
- * Applied: precession and nutation to the equator of date, Greenwich
80
- * apparent sidereal time, and atmospheric refraction (upstream `'normal'`
81
- * model, the same as sunAltAz/moonAltAz). Not applied: annual aberration
82
- * (at most 20.5″ — see the roadmap), proper motion (pass catalog
83
- * coordinates; fast movers such as Arcturus drift ~1′ in 26 years), and
84
- * stellar parallax (negligible at the 1000 ly the star is placed at).
92
+ * Applied: annual aberration, precession and nutation to the equator of
93
+ * date, Greenwich apparent sidereal time, and atmospheric refraction
94
+ * (upstream `'normal'` model, the same as sunAltAz/moonAltAz). Not applied:
95
+ * proper motion (pass catalog coordinates; fast movers such as Arcturus
96
+ * drift ~1′ in 26 years) and stellar parallax (negligible at the 1000 ly the
97
+ * star is placed at).
85
98
  */
86
99
  export declare function starAltAz(raDeg: number, decDeg: number, time: Date, observer: Observer): AltAz;
100
+ /**
101
+ * Greenwich apparent sidereal time in degrees, [0, 360): the hour angle of
102
+ * the true equinox of date at Greenwich, including the equation of the
103
+ * equinoxes. A body's Greenwich hour angle is this minus its right ascension
104
+ * of date.
105
+ */
106
+ export declare function greenwichSiderealTime(time: Date): number;
@@ -13,20 +13,17 @@
13
13
  // refinement since this layer only needs az/alt; Refraction 'normal' (~7176).
14
14
  //
15
15
  // INTERNAL: not part of the curated public API (see index.ts) except
16
- // sunAltAz/moonAltAz.
16
+ // sunAltAz, moonAltAz, starAltAz and greenwichSiderealTime.
17
17
  import { assertObserver, assertSupported } from './types.js';
18
18
  import { ttDaysFromUt, utDays } from './time.js';
19
- import { DEG2RAD, RAD2DEG, EARTH_EQUATORIAL_RADIUS_KM, EARTH_FLATTENING, EARTH_FLATTENING_SQUARED, KM_PER_AU, PrecessDirection, earthTilt, equatorialFromVector, gyration } from './nutation.js';
20
- import { sunGeoVectorEqj } from './sun.js';
19
+ import { C_AUDAY, DEG2RAD, RAD2DEG, EARTH_EQUATORIAL_RADIUS_KM, EARTH_FLATTENING, EARTH_FLATTENING_SQUARED, KM_PER_AU, PrecessDirection, earthTilt, equatorialFromVector, gyration, wrapDeg360 } from './nutation.js';
20
+ import { earthHelioState, sunGeoVectorEqj } from './sun.js';
21
21
  import { moonGeoVectorEqj } from './moon.js';
22
22
  /** UPSTREAM: `era` (Earth Rotation Angle), astronomy.ts ~2014. */
23
23
  function earthRotationAngleDeg(ut) {
24
24
  const thet1 = 0.7790572732640 + 0.00273781191135448 * ut;
25
25
  const thet3 = ut % 1;
26
- let theta = 360 * ((thet1 + thet3) % 1);
27
- if (theta < 0)
28
- theta += 360;
29
- return theta;
26
+ return wrapDeg360(360 * ((thet1 + thet3) % 1));
30
27
  }
31
28
  /**
32
29
  * UPSTREAM: `sidereal_time`, astronomy.ts ~2031 — Greenwich Apparent Sidereal
@@ -46,10 +43,7 @@ export function siderealDeg(ut) {
46
43
  - 0.00000044) * t
47
44
  + 1.3915817) * t
48
45
  + 4612.156534) * t;
49
- let gastDeg = (st / 3600 + theta) % 360;
50
- if (gastDeg < 0)
51
- gastDeg += 360;
52
- return gastDeg;
46
+ return wrapDeg360(st / 3600 + theta);
53
47
  }
54
48
  /**
55
49
  * UPSTREAM: `terra` (astronomy.ts ~2147 — position only; its velocity output
@@ -149,17 +143,13 @@ export function topoAltAzUnrefracted(bodyEqj, ut, observer) {
149
143
  const proj = Math.hypot(pn, pw);
150
144
  let az;
151
145
  if (proj > 0) {
152
- az = -RAD2DEG * Math.atan2(pw, pn);
153
- if (az < 0)
154
- az += 360;
146
+ az = wrapDeg360(-RAD2DEG * Math.atan2(pw, pn));
155
147
  }
156
148
  else {
157
149
  az = 0;
158
150
  }
159
151
  const zd = RAD2DEG * Math.atan2(proj, pz);
160
- let hourAngleDeg = (gast + observer.longitudeDeg - raDeg) % 360;
161
- if (hourAngleDeg < 0)
162
- hourAngleDeg += 360;
152
+ const hourAngleDeg = wrapDeg360(gast + observer.longitudeDeg - raDeg);
163
153
  return { azDeg: az, altDeg: 90 - zd, hourAngleDeg, distanceAu, raDeg, decDeg };
164
154
  }
165
155
  /**
@@ -208,40 +198,64 @@ export function moonAltAz(time, observer) {
208
198
  return refract(topoAltAzUnrefracted(moonGeoVectorEqj(ttDaysFromUt(ut)), ut, observer));
209
199
  }
210
200
  /**
211
- * UPSTREAM: the user-defined star path — `HelioVector` for a `DefineStar`
212
- * body (astronomy.ts ~4097: `VectorFromSphere(Spherical(dec, 15*ra, dist))`)
213
- * into `Equator(body, date, observer, ofdate=true, aberration=false)` (~2803)
214
- * and `Horizon` (~2565): the same topocentric path sunAltAz/moonAltAz take.
215
201
  * Upstream floors a star's distance at 1 light-year and suggests 1000 when
216
202
  * unknown; the star sits at 1000 ly here (`AU_PER_LY`, astronomy.ts line 52).
217
- * Upstream's geocentric step (star vector minus Earth's heliocentric
218
- * position) is skipped: at 1000 ly it moves the star 0.003″.
219
203
  */
220
204
  const STAR_DISTANCE_AU = 1000 * 63241.07708807546;
221
- /**
222
- * Topocentric azimuth/altitude of a fixed star from its J2000 (ICRS)
223
- * catalog right ascension and declination, both in degrees.
224
- *
225
- * Applied: precession and nutation to the equator of date, Greenwich
226
- * apparent sidereal time, and atmospheric refraction (upstream `'normal'`
227
- * model, the same as sunAltAz/moonAltAz). Not applied: annual aberration
228
- * (at most 20.5″ — see the roadmap), proper motion (pass catalog
229
- * coordinates; fast movers such as Arcturus drift ~1′ in 26 years), and
230
- * stellar parallax (negligible at the 1000 ly the star is placed at).
231
- */
232
- export function starAltAz(raDeg, decDeg, time, observer) {
233
- assertSupported(time);
234
- assertObserver(observer);
205
+ /** INTERNAL: shared argument checks for the J2000 catalog position the star functions take. */
206
+ export function assertStarCatalog(raDeg, decDeg) {
235
207
  if (!(raDeg >= 0 && raDeg < 360))
236
208
  throw new RangeError(`raDeg out of range: ${raDeg}`);
237
209
  if (!(decDeg >= -90 && decDeg <= 90))
238
210
  throw new RangeError(`decDeg out of range: ${decDeg}`);
239
- const ut = utDays(time);
211
+ }
212
+ /**
213
+ * UPSTREAM: `GeoVector` for a `DefineStar` body with aberration = true —
214
+ * `HelioVector`'s `VectorFromSphere(Spherical(dec, 15*ra, dist))`
215
+ * (astronomy.ts ~4097, 7015) through the user-defined-star branch of
216
+ * `BackdatePosition` (4270-4300). The star is not backdated; the vector from
217
+ * Earth to the star is shifted by Earth's heliocentric velocity over the
218
+ * light travel time, which is the annual aberration, at most 20.5″.
219
+ *
220
+ * @returns geocentric position in AU, J2000 mean equator (EQJ).
221
+ */
222
+ export function starGeoVectorEqj(raDeg, decDeg, tt) {
240
223
  const rCosDec = STAR_DISTANCE_AU * Math.cos(decDeg * DEG2RAD);
241
- const eqj = {
224
+ const tvec = {
242
225
  x: rCosDec * Math.cos(raDeg * DEG2RAD),
243
226
  y: rCosDec * Math.sin(raDeg * DEG2RAD),
244
227
  z: STAR_DISTANCE_AU * Math.sin(decDeg * DEG2RAD),
245
228
  };
246
- return refract(topoAltAzUnrefracted(eqj, ut, observer));
229
+ const ostate = earthHelioState(tt);
230
+ const rvec = { x: tvec.x - ostate.pos.x, y: tvec.y - ostate.pos.y, z: tvec.z - ostate.pos.z };
231
+ const s = C_AUDAY / Math.sqrt(rvec.x * rvec.x + rvec.y * rvec.y + rvec.z * rvec.z);
232
+ return { x: rvec.x + ostate.vel.x / s, y: rvec.y + ostate.vel.y / s, z: rvec.z + ostate.vel.z / s };
233
+ }
234
+ /**
235
+ * Topocentric azimuth/altitude of a fixed star from its J2000 (ICRS)
236
+ * catalog right ascension and declination, both in degrees.
237
+ *
238
+ * Applied: annual aberration, precession and nutation to the equator of
239
+ * date, Greenwich apparent sidereal time, and atmospheric refraction
240
+ * (upstream `'normal'` model, the same as sunAltAz/moonAltAz). Not applied:
241
+ * proper motion (pass catalog coordinates; fast movers such as Arcturus
242
+ * drift ~1′ in 26 years) and stellar parallax (negligible at the 1000 ly the
243
+ * star is placed at).
244
+ */
245
+ export function starAltAz(raDeg, decDeg, time, observer) {
246
+ assertSupported(time);
247
+ assertObserver(observer);
248
+ assertStarCatalog(raDeg, decDeg);
249
+ const ut = utDays(time);
250
+ return refract(topoAltAzUnrefracted(starGeoVectorEqj(raDeg, decDeg, ttDaysFromUt(ut)), ut, observer));
251
+ }
252
+ /**
253
+ * Greenwich apparent sidereal time in degrees, [0, 360): the hour angle of
254
+ * the true equinox of date at Greenwich, including the equation of the
255
+ * equinoxes. A body's Greenwich hour angle is this minus its right ascension
256
+ * of date.
257
+ */
258
+ export function greenwichSiderealTime(time) {
259
+ assertSupported(time);
260
+ return siderealDeg(utDays(time));
247
261
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwaters/almanac",
3
- "version": "0.11.0",
3
+ "version": "0.12.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": {