@typescriptify/sweph 1.0.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.
Files changed (211) hide show
  1. package/README.md +422 -0
  2. package/ephe/semo_18.se1 +0 -0
  3. package/ephe/sepl_18.se1 +0 -0
  4. package/originalCode/.eslintrc.json +124 -0
  5. package/originalCode/.gitattributes +2 -0
  6. package/originalCode/.github/FUNDING.yml +5 -0
  7. package/originalCode/.github/workflows/test.yml +35 -0
  8. package/originalCode/LICENSE +840 -0
  9. package/originalCode/README.md +91 -0
  10. package/originalCode/binding.gyp +41 -0
  11. package/originalCode/constants.js +366 -0
  12. package/originalCode/docs.gif +0 -0
  13. package/originalCode/index.d.ts +5115 -0
  14. package/originalCode/index.js +7 -0
  15. package/originalCode/index.mjs +109 -0
  16. package/originalCode/package.json +55 -0
  17. package/originalCode/src/functions/azalt.cpp +39 -0
  18. package/originalCode/src/functions/azalt_rev.cpp +35 -0
  19. package/originalCode/src/functions/calc.cpp +29 -0
  20. package/originalCode/src/functions/calc_pctr.cpp +31 -0
  21. package/originalCode/src/functions/calc_ut.cpp +29 -0
  22. package/originalCode/src/functions/close.cpp +6 -0
  23. package/originalCode/src/functions/cotrans.cpp +26 -0
  24. package/originalCode/src/functions/cotrans_sp.cpp +26 -0
  25. package/originalCode/src/functions/cs2degstr.cpp +19 -0
  26. package/originalCode/src/functions/cs2lonlatstr.cpp +23 -0
  27. package/originalCode/src/functions/cs2timestr.cpp +23 -0
  28. package/originalCode/src/functions/csnorm.cpp +15 -0
  29. package/originalCode/src/functions/csroundsec.cpp +15 -0
  30. package/originalCode/src/functions/d2l.cpp +15 -0
  31. package/originalCode/src/functions/date_conversion.cpp +30 -0
  32. package/originalCode/src/functions/day_of_week.cpp +15 -0
  33. package/originalCode/src/functions/degnorm.cpp +15 -0
  34. package/originalCode/src/functions/deltat.cpp +15 -0
  35. package/originalCode/src/functions/deltat_ex.cpp +24 -0
  36. package/originalCode/src/functions/difcs2n.cpp +19 -0
  37. package/originalCode/src/functions/difcsn.cpp +19 -0
  38. package/originalCode/src/functions/difdeg2n.cpp +19 -0
  39. package/originalCode/src/functions/difdegn.cpp +19 -0
  40. package/originalCode/src/functions/fixstar.cpp +32 -0
  41. package/originalCode/src/functions/fixstar2.cpp +32 -0
  42. package/originalCode/src/functions/fixstar2_mag.cpp +28 -0
  43. package/originalCode/src/functions/fixstar2_ut.cpp +32 -0
  44. package/originalCode/src/functions/fixstar_mag.cpp +28 -0
  45. package/originalCode/src/functions/fixstar_ut.cpp +32 -0
  46. package/originalCode/src/functions/gauquelin_sector.cpp +44 -0
  47. package/originalCode/src/functions/get_ayanamsa.cpp +15 -0
  48. package/originalCode/src/functions/get_ayanamsa_ex.cpp +27 -0
  49. package/originalCode/src/functions/get_ayanamsa_ex_ut.cpp +27 -0
  50. package/originalCode/src/functions/get_ayanamsa_name.cpp +19 -0
  51. package/originalCode/src/functions/get_ayanamsa_ut.cpp +15 -0
  52. package/originalCode/src/functions/get_current_file_data.cpp +28 -0
  53. package/originalCode/src/functions/get_library_path.cpp +8 -0
  54. package/originalCode/src/functions/get_orbital_elements.cpp +29 -0
  55. package/originalCode/src/functions/get_planet_name.cpp +19 -0
  56. package/originalCode/src/functions/get_tid_acc.cpp +7 -0
  57. package/originalCode/src/functions/heliacal_pheno_ut.cpp +52 -0
  58. package/originalCode/src/functions/heliacal_ut.cpp +52 -0
  59. package/originalCode/src/functions/helio_cross.cpp +33 -0
  60. package/originalCode/src/functions/helio_cross_ut.cpp +33 -0
  61. package/originalCode/src/functions/house_name.cpp +20 -0
  62. package/originalCode/src/functions/house_pos.cpp +36 -0
  63. package/originalCode/src/functions/houses.cpp +35 -0
  64. package/originalCode/src/functions/houses_armc.cpp +38 -0
  65. package/originalCode/src/functions/houses_armc_ex2.cpp +47 -0
  66. package/originalCode/src/functions/houses_ex.cpp +37 -0
  67. package/originalCode/src/functions/houses_ex2.cpp +46 -0
  68. package/originalCode/src/functions/jdet_to_utc.cpp +38 -0
  69. package/originalCode/src/functions/jdut1_to_utc.cpp +38 -0
  70. package/originalCode/src/functions/julday.cpp +25 -0
  71. package/originalCode/src/functions/lat_to_lmt.cpp +27 -0
  72. package/originalCode/src/functions/lmt_to_lat.cpp +27 -0
  73. package/originalCode/src/functions/lun_eclipse_how.cpp +34 -0
  74. package/originalCode/src/functions/lun_eclipse_when.cpp +31 -0
  75. package/originalCode/src/functions/lun_eclipse_when_loc.cpp +39 -0
  76. package/originalCode/src/functions/lun_occult_when_glob.cpp +35 -0
  77. package/originalCode/src/functions/lun_occult_when_loc.cpp +43 -0
  78. package/originalCode/src/functions/lun_occult_where.cpp +34 -0
  79. package/originalCode/src/functions/mooncross.cpp +26 -0
  80. package/originalCode/src/functions/mooncross_node.cpp +30 -0
  81. package/originalCode/src/functions/mooncross_node_ut.cpp +30 -0
  82. package/originalCode/src/functions/mooncross_ut.cpp +26 -0
  83. package/originalCode/src/functions/nod_aps.cpp +42 -0
  84. package/originalCode/src/functions/nod_aps_ut.cpp +42 -0
  85. package/originalCode/src/functions/orbit_max_min_true_distance.cpp +37 -0
  86. package/originalCode/src/functions/pheno.cpp +29 -0
  87. package/originalCode/src/functions/pheno_ut.cpp +29 -0
  88. package/originalCode/src/functions/radnorm.cpp +15 -0
  89. package/originalCode/src/functions/refrac.cpp +23 -0
  90. package/originalCode/src/functions/refrac_extended.cpp +32 -0
  91. package/originalCode/src/functions/revjul.cpp +33 -0
  92. package/originalCode/src/functions/rise_trans.cpp +44 -0
  93. package/originalCode/src/functions/rise_trans_true_hor.cpp +46 -0
  94. package/originalCode/src/functions/set_delta_t_userdef.cpp +14 -0
  95. package/originalCode/src/functions/set_ephe_path.cpp +14 -0
  96. package/originalCode/src/functions/set_jpl_file.cpp +14 -0
  97. package/originalCode/src/functions/set_sid_mode.cpp +20 -0
  98. package/originalCode/src/functions/set_tid_acc.cpp +14 -0
  99. package/originalCode/src/functions/set_topo.cpp +20 -0
  100. package/originalCode/src/functions/sidtime.cpp +15 -0
  101. package/originalCode/src/functions/sidtime0.cpp +21 -0
  102. package/originalCode/src/functions/sol_eclipse_how.cpp +34 -0
  103. package/originalCode/src/functions/sol_eclipse_when_glob.cpp +31 -0
  104. package/originalCode/src/functions/sol_eclipse_when_loc.cpp +39 -0
  105. package/originalCode/src/functions/sol_eclipse_where.cpp +30 -0
  106. package/originalCode/src/functions/solcross.cpp +26 -0
  107. package/originalCode/src/functions/solcross_ut.cpp +26 -0
  108. package/originalCode/src/functions/split_deg.cpp +35 -0
  109. package/originalCode/src/functions/time_equ.cpp +25 -0
  110. package/originalCode/src/functions/utc_time_zone.cpp +48 -0
  111. package/originalCode/src/functions/utc_to_jd.cpp +37 -0
  112. package/originalCode/src/functions/version.cpp +8 -0
  113. package/originalCode/src/functions/vis_limit_mag.cpp +50 -0
  114. package/originalCode/src/sweph.cpp +150 -0
  115. package/originalCode/src/sweph.h +119 -0
  116. package/originalCode/swisseph/swecl.c +6428 -0
  117. package/originalCode/swisseph/swedate.c +588 -0
  118. package/originalCode/swisseph/swedate.h +81 -0
  119. package/originalCode/swisseph/swehel.c +3511 -0
  120. package/originalCode/swisseph/swehouse.c +3143 -0
  121. package/originalCode/swisseph/swehouse.h +98 -0
  122. package/originalCode/swisseph/swejpl.c +958 -0
  123. package/originalCode/swisseph/swejpl.h +103 -0
  124. package/originalCode/swisseph/swemmoon.c +1930 -0
  125. package/originalCode/swisseph/swemplan.c +967 -0
  126. package/originalCode/swisseph/swemptab.h +10640 -0
  127. package/originalCode/swisseph/swenut2000a.h +2819 -0
  128. package/originalCode/swisseph/sweodef.h +326 -0
  129. package/originalCode/swisseph/sweph.c +8614 -0
  130. package/originalCode/swisseph/sweph.h +849 -0
  131. package/originalCode/swisseph/swephexp.h +1020 -0
  132. package/originalCode/swisseph/swephlib.c +4634 -0
  133. package/originalCode/swisseph/swephlib.h +189 -0
  134. package/package.json +28 -0
  135. package/scripts/gen-swemptab.js +177 -0
  136. package/scripts/gen-swenut2000a.js +106 -0
  137. package/src/SwissEph/README.md +268 -0
  138. package/src/SwissEph/UseCases/Ayanamsa.md +363 -0
  139. package/src/SwissEph/UseCases/AzimuthAltitude.md +408 -0
  140. package/src/SwissEph/UseCases/CoordinateSystems.md +337 -0
  141. package/src/SwissEph/UseCases/DateAndTime.md +368 -0
  142. package/src/SwissEph/UseCases/DeltaT.md +258 -0
  143. package/src/SwissEph/UseCases/EphemerisFiles.md +338 -0
  144. package/src/SwissEph/UseCases/FixedStars.md +300 -0
  145. package/src/SwissEph/UseCases/GauquelinSectors.md +304 -0
  146. package/src/SwissEph/UseCases/HeliacalEvents.md +396 -0
  147. package/src/SwissEph/UseCases/HelioCrossings.md +325 -0
  148. package/src/SwissEph/UseCases/HousePosition.md +254 -0
  149. package/src/SwissEph/UseCases/HouseSystems.md +279 -0
  150. package/src/SwissEph/UseCases/LunarEclipse.md +326 -0
  151. package/src/SwissEph/UseCases/MeridianTransit.md +279 -0
  152. package/src/SwissEph/UseCases/MoonCrossings.md +373 -0
  153. package/src/SwissEph/UseCases/NodesAndApsides.md +307 -0
  154. package/src/SwissEph/UseCases/Occultation.md +352 -0
  155. package/src/SwissEph/UseCases/OrbitalElements.md +469 -0
  156. package/src/SwissEph/UseCases/Phenomena.md +328 -0
  157. package/src/SwissEph/UseCases/PlanetPositions.md +366 -0
  158. package/src/SwissEph/UseCases/Planetocentric.md +278 -0
  159. package/src/SwissEph/UseCases/Refraction.md +314 -0
  160. package/src/SwissEph/UseCases/RiseAndSet.md +433 -0
  161. package/src/SwissEph/UseCases/SiderealTime.md +302 -0
  162. package/src/SwissEph/UseCases/SolarEclipse.md +379 -0
  163. package/src/SwissEph/UseCases/SunCrossings.md +275 -0
  164. package/src/SwissEph/UseCases/TopocentricCorrection.md +335 -0
  165. package/src/SwissEph/errors.ts +10 -0
  166. package/src/SwissEph/index.ts +823 -0
  167. package/src/SwissEph/types.ts +291 -0
  168. package/src/constants.ts +762 -0
  169. package/src/file-reader.ts +147 -0
  170. package/src/index.ts +10 -0
  171. package/src/swecl.ts +4526 -0
  172. package/src/swedate.ts +376 -0
  173. package/src/swehel.ts +1939 -0
  174. package/src/swehouse.ts +2167 -0
  175. package/src/swejpl.ts +470 -0
  176. package/src/swemmoon.ts +1318 -0
  177. package/src/swemplan.ts +585 -0
  178. package/src/swemptab.ts +4448 -0
  179. package/src/swenut2000a.ts +2763 -0
  180. package/src/sweph.ts +3993 -0
  181. package/src/swephlib.ts +2720 -0
  182. package/src/types.ts +490 -0
  183. package/tests/c-style/ayanamsa.test.ts +63 -0
  184. package/tests/c-style/config.test.ts +96 -0
  185. package/tests/c-style/crossings.test.ts +81 -0
  186. package/tests/c-style/date-time.test.ts +114 -0
  187. package/tests/c-style/eclipses.test.ts +84 -0
  188. package/tests/c-style/fixed-stars.test.ts +66 -0
  189. package/tests/c-style/heliacal.test.ts +34 -0
  190. package/tests/c-style/houses.test.ts +135 -0
  191. package/tests/c-style/math-utils.test.ts +160 -0
  192. package/tests/c-style/orbital.test.ts +78 -0
  193. package/tests/c-style/phenomena.test.ts +42 -0
  194. package/tests/c-style/planetocentric.test.ts +26 -0
  195. package/tests/c-style/planets.test.ts +117 -0
  196. package/tests/c-style/rise-set.test.ts +71 -0
  197. package/tests/helpers.ts +21 -0
  198. package/tests/modern/ayanamsa.test.ts +47 -0
  199. package/tests/modern/calc.test.ts +113 -0
  200. package/tests/modern/config.test.ts +46 -0
  201. package/tests/modern/crossings.test.ts +45 -0
  202. package/tests/modern/eclipses.test.ts +81 -0
  203. package/tests/modern/errors.test.ts +71 -0
  204. package/tests/modern/heliacal.test.ts +30 -0
  205. package/tests/modern/houses.test.ts +87 -0
  206. package/tests/modern/orbital.test.ts +79 -0
  207. package/tests/modern/phenomena.test.ts +41 -0
  208. package/tests/modern/rise-set.test.ts +60 -0
  209. package/tests/modern/statics.test.ts +99 -0
  210. package/tests/modern/utilities.test.ts +70 -0
  211. package/tsconfig.json +20 -0
@@ -0,0 +1,279 @@
1
+ # Meridian Transit
2
+
3
+ A **meridian transit** (also called **culmination**) is the moment when a celestial body crosses the observer's **meridian** -- the imaginary north-south line passing through the zenith (the point directly overhead). At this moment, the body reaches its highest point in the sky for that day. This is called the **upper transit** or **upper culmination**.
4
+
5
+ There is also a **lower transit** (or **lower culmination**), when the body crosses the meridian below the pole -- its lowest point. For most observers, this happens below the horizon and is not visible. But for circumpolar objects (objects that never set), the lower transit is visible and represents the object's closest approach to the horizon.
6
+
7
+ Meridian transits are important in several contexts:
8
+
9
+ - **Solar noon**: The Sun's upper transit defines **local apparent noon** -- the moment when the Sun is highest in the sky and shadows point due north (in the Northern Hemisphere) or due south (in the Southern Hemisphere). This is the basis of sundial time.
10
+ - **Timekeeping**: Before atomic clocks, astronomical time was determined by observing the meridian transit of stars with a transit telescope.
11
+ - **Observation planning**: Objects are best observed near their transit, when they are highest above the horizon and atmospheric effects (seeing, extinction) are minimized.
12
+
13
+ ---
14
+
15
+ ## Quick Example
16
+
17
+ ```typescript
18
+ import { SwissEph } from '../index';
19
+ import { SE_SUN } from '../../constants';
20
+
21
+ const swe = new SwissEph();
22
+
23
+ // Find solar noon in New York on June 21, 2024
24
+ const jd = SwissEph.julianDay(2024, 6, 21, 0);
25
+ const nyc = { longitude: -74.006, latitude: 40.713 };
26
+
27
+ const noon = swe.transit(jd, SE_SUN, nyc);
28
+
29
+ const date = SwissEph.fromJulianDay(noon.jd);
30
+ const h = Math.floor(date.hour);
31
+ const m = Math.floor((date.hour - h) * 60);
32
+ const s = Math.round(((date.hour - h) * 60 - m) * 60);
33
+ console.log(`Solar noon: ${h}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')} UT`);
34
+ // New York is UTC-4 in summer (EDT), so add -4 to convert to local time
35
+
36
+ swe.close();
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Detailed Examples
42
+
43
+ ### Solar noon vs clock noon
44
+
45
+ Solar noon almost never occurs at exactly 12:00 clock time, even after accounting for time zones. The difference arises from two effects: your position within your time zone, and the equation of time.
46
+
47
+ ```typescript
48
+ import { SwissEph } from '../index';
49
+ import { SE_SUN } from '../../constants';
50
+
51
+ const swe = new SwissEph();
52
+
53
+ const fmt = (jd: number, tzOffset: number) => {
54
+ const d = SwissEph.fromJulianDay(jd);
55
+ let hours = d.hour + tzOffset;
56
+ if (hours < 0) hours += 24;
57
+ if (hours >= 24) hours -= 24;
58
+ const h = Math.floor(hours);
59
+ const m = Math.floor((hours - h) * 60);
60
+ const s = Math.round(((hours - h) * 60 - m) * 60);
61
+ return `${h}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`;
62
+ };
63
+
64
+ // Compare solar noon at different dates in New York (UTC-5 in winter, UTC-4 in summer)
65
+ const nyc = { longitude: -74.006, latitude: 40.713 };
66
+
67
+ const dates = [
68
+ { y: 2024, m: 2, d: 12, tz: -5, label: 'Feb 12 (EST)' }, // equation of time ~ -14 min
69
+ { y: 2024, m: 5, d: 14, tz: -4, label: 'May 14 (EDT)' }, // equation of time ~ +4 min
70
+ { y: 2024, m: 7, d: 26, tz: -4, label: 'Jul 26 (EDT)' }, // equation of time ~ -6 min
71
+ { y: 2024, m: 11, d: 3, tz: -5, label: 'Nov 3 (EST)' }, // equation of time ~ +16 min
72
+ ];
73
+
74
+ console.log('Solar noon in New York at different times of year:');
75
+ for (const d of dates) {
76
+ const jd = SwissEph.julianDay(d.y, d.m, d.d, 0);
77
+ const noon = swe.transit(jd, SE_SUN, nyc);
78
+ console.log(` ${d.label}: ${fmt(noon.jd, d.tz)} local time`);
79
+ }
80
+ // Solar noon varies by over 30 minutes across the year!
81
+
82
+ swe.close();
83
+ ```
84
+
85
+ ### Lower transit (anti-transit)
86
+
87
+ The lower transit is when the body crosses the meridian at its lowest point. For the Sun, this is approximately midnight (but not exactly, for the same reasons solar noon is not exactly 12:00).
88
+
89
+ ```typescript
90
+ import { SwissEph } from '../index';
91
+ import { SE_SUN } from '../../constants';
92
+
93
+ const swe = new SwissEph();
94
+
95
+ const jd = SwissEph.julianDay(2024, 6, 21, 0);
96
+ const london = { longitude: -0.128, latitude: 51.507 };
97
+
98
+ const upperTransit = swe.transit(jd, SE_SUN, london);
99
+ const lowerTransit = swe.antiTransit(jd, SE_SUN, london);
100
+
101
+ const fmt = (jd: number) => {
102
+ const d = SwissEph.fromJulianDay(jd);
103
+ const h = Math.floor(d.hour);
104
+ const m = Math.floor((d.hour - h) * 60);
105
+ return `${h}:${String(m).padStart(2,'0')} UT`;
106
+ };
107
+
108
+ console.log(`Sun upper transit (solar noon): ${fmt(upperTransit.jd)}`);
109
+ console.log(`Sun lower transit (solar midnight): ${fmt(lowerTransit.jd)}`);
110
+
111
+ swe.close();
112
+ ```
113
+
114
+ ### Planet meridian transits
115
+
116
+ ```typescript
117
+ import { SwissEph } from '../index';
118
+ import { SE_JUPITER, SE_SATURN, SE_MARS } from '../../constants';
119
+
120
+ const swe = new SwissEph();
121
+
122
+ const jd = SwissEph.julianDay(2024, 4, 15, 0);
123
+ const geo = { longitude: -118.243, latitude: 34.052 }; // Los Angeles
124
+
125
+ const fmt = (jd: number) => {
126
+ const d = SwissEph.fromJulianDay(jd);
127
+ const h = Math.floor(d.hour);
128
+ const m = Math.floor((d.hour - h) * 60);
129
+ return `${h}:${String(m).padStart(2,'0')} UT`;
130
+ };
131
+
132
+ const planets = [
133
+ { id: SE_JUPITER, name: 'Jupiter' },
134
+ { id: SE_SATURN, name: 'Saturn' },
135
+ { id: SE_MARS, name: 'Mars' },
136
+ ];
137
+
138
+ console.log('Next planet meridian transits from Los Angeles:');
139
+ for (const p of planets) {
140
+ const tr = swe.transit(jd, p.id, geo);
141
+ const date = SwissEph.fromJulianDay(tr.jd);
142
+ console.log(` ${p.name.padEnd(8)} ${date.year}-${String(date.month).padStart(2,'0')}-${String(date.day).padStart(2,'0')} ${fmt(tr.jd)}`);
143
+ }
144
+
145
+ swe.close();
146
+ ```
147
+
148
+ ### Solar noon over a year (tracking the equation of time)
149
+
150
+ ```typescript
151
+ import { SwissEph } from '../index';
152
+ import { SE_SUN } from '../../constants';
153
+
154
+ const swe = new SwissEph();
155
+
156
+ // Observer at the center of the UTC+0 timezone (longitude 0)
157
+ // Here, the deviation of solar noon from 12:00 UT is purely the equation of time
158
+ const greenwich = { longitude: 0, latitude: 51.477 };
159
+
160
+ console.log('Solar noon at Greenwich, 2024 (deviation from 12:00 UT):');
161
+ for (let month = 1; month <= 12; month++) {
162
+ const jd = SwissEph.julianDay(2024, month, 15, 0);
163
+ const noon = swe.transit(jd, SE_SUN, greenwich);
164
+
165
+ const d = SwissEph.fromJulianDay(noon.jd);
166
+ const deviationMinutes = (d.hour - 12) * 60;
167
+
168
+ const sign = deviationMinutes >= 0 ? '+' : '-';
169
+ const abs = Math.abs(deviationMinutes);
170
+ const dm = Math.floor(abs);
171
+ const ds = Math.round((abs - dm) * 60);
172
+
173
+ const monthNames = ['','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
174
+ console.log(
175
+ ` ${monthNames[month]} 15: noon at ${Math.floor(d.hour)}:${String(Math.floor((d.hour % 1) * 60)).padStart(2,'0')} UT` +
176
+ ` (${sign}${dm}m ${ds}s from 12:00)`
177
+ );
178
+ }
179
+ // The deviation ranges from about -14 minutes (Feb) to +16 minutes (Nov)
180
+
181
+ swe.close();
182
+ ```
183
+
184
+ ### Combining transit with azalt to find maximum altitude
185
+
186
+ ```typescript
187
+ import { SwissEph } from '../index';
188
+ import { SE_SUN } from '../../constants';
189
+
190
+ const swe = new SwissEph();
191
+
192
+ const jd = SwissEph.julianDay(2024, 6, 21, 0); // Summer solstice
193
+ const geo = { longitude: -0.128, latitude: 51.507 }; // London
194
+
195
+ // Find solar noon
196
+ const noon = swe.transit(jd, SE_SUN, geo);
197
+
198
+ // Get the Sun's position at transit
199
+ const sun = swe.calc(noon.jd, SE_SUN);
200
+
201
+ // Convert to azimuth/altitude
202
+ const hor = swe.azalt(noon.jd, geo, sun.longitude, sun.latitude, sun.distance);
203
+
204
+ console.log(`Sun at solar noon (London, summer solstice):`);
205
+ console.log(` Altitude: ${hor.trueAltitude.toFixed(2)} degrees`);
206
+ console.log(` Azimuth: ${hor.azimuth.toFixed(2)} degrees`);
207
+ // The Sun reaches about 62 degrees altitude at London on the summer solstice
208
+
209
+ swe.close();
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Deep Explanation
215
+
216
+ ### The meridian
217
+
218
+ The **celestial meridian** is the great circle on the celestial sphere that passes through the celestial poles and the observer's zenith. It divides the sky into an eastern half and a western half.
219
+
220
+ When a body crosses the meridian going from east to west (the normal direction due to Earth's rotation), it is at its highest point. This is the **upper transit**. About 12 hours later (for the Sun; for stars it is the sidereal day), it crosses the meridian again at its lowest point -- the **lower transit**. For bodies that are circumpolar (always above the horizon), both transits are observable.
221
+
222
+ ### Local apparent noon vs clock noon
223
+
224
+ **Local apparent noon** is the moment when the Sun crosses the observer's meridian. It differs from 12:00 clock time for two reasons:
225
+
226
+ 1. **Longitude within time zone**: Time zones are typically 15 degrees wide (1 hour). If you are at the eastern edge of your time zone, the Sun reaches your meridian earlier than for someone at the western edge. For example, within the US Eastern time zone, Detroit (lon -83.0) sees solar noon about 25 minutes later than New York (lon -74.0).
227
+
228
+ 2. **The equation of time**: Even at the center of a time zone, solar noon drifts back and forth throughout the year by up to 16 minutes. This is called the **equation of time**.
229
+
230
+ ### The equation of time
231
+
232
+ The equation of time is the difference between **apparent solar time** (based on the actual Sun's position) and **mean solar time** (based on a fictitious Sun that moves at a constant rate). It arises from two physical effects:
233
+
234
+ 1. **Orbital eccentricity**: Earth's orbit is slightly elliptical (eccentricity ~0.017). According to Kepler's second law, Earth moves faster when closer to the Sun (perihelion, around January 3) and slower when farther (aphelion, around July 4). This means the angular speed of the Sun along the ecliptic varies.
235
+
236
+ 2. **Obliquity of the ecliptic**: The Earth's rotational axis is tilted 23.4 degrees from the orbital plane. Even if the Sun moved at a constant speed along the ecliptic, its projection onto the celestial equator (which determines the time between successive meridian crossings) would vary because the ecliptic and equator make an angle.
237
+
238
+ The two effects combine to produce the equation of time, which follows an approximately sinusoidal curve:
239
+
240
+ | Time of year | Equation of time | Solar noon compared to 12:00 |
241
+ |-------------|-----------------|------------------------------|
242
+ | Early February | about -14 minutes | Sun is slow; noon is at ~12:14 |
243
+ | Mid-April | 0 (crossing) | Noon at ~12:00 |
244
+ | Mid-May | about +4 minutes | Sun is fast; noon at ~11:56 |
245
+ | Mid-June | 0 (crossing) | Noon at ~12:00 |
246
+ | Late July | about -6 minutes | Sun is slow; noon at ~12:06 |
247
+ | Early September | 0 (crossing) | Noon at ~12:00 |
248
+ | Early November | about +16 minutes | Sun is fast; noon at ~11:44 |
249
+ | Late December | 0 (crossing) | Noon at ~12:00 |
250
+
251
+ The Swiss Ephemeris also provides `swe.timeEquation(jd)`, which returns the equation of time in days (multiply by 24 * 60 to get minutes).
252
+
253
+ ### Circumpolar transits
254
+
255
+ At high latitudes, some celestial bodies are **circumpolar** -- they never set below the horizon. For these objects:
256
+
257
+ - The **upper transit** is when the body is at its maximum altitude (crossing the meridian above the pole).
258
+ - The **lower transit** is when the body is at its minimum altitude (crossing the meridian below the pole but still above the horizon).
259
+
260
+ For example, Polaris (the North Star) is circumpolar from most of the Northern Hemisphere. Its lower transit passes very close to the celestial north pole.
261
+
262
+ In the Arctic during summer, the Sun itself becomes circumpolar. Its upper transit (solar noon) still occurs at the highest altitude, but the lower transit (around midnight) also occurs above the horizon -- the midnight sun.
263
+
264
+ ### Transit vs rise/set
265
+
266
+ | Property | Transit | Rise/Set |
267
+ |----------|---------|----------|
268
+ | **Definition** | Body crosses the meridian | Body crosses the horizon |
269
+ | **Altitude** | Maximum (upper) or minimum (lower) | ~0 degrees (with refraction) |
270
+ | **Always occurs?** | Yes (for all visible objects) | No (circumpolar objects never set; some objects never rise) |
271
+ | **Affected by refraction?** | No (meridian crossing is a direction, not an altitude) | Yes (significantly) |
272
+ | **Atmospheric pressure/temperature** | Not applicable | Affects the result through refraction |
273
+
274
+ ### Tips
275
+
276
+ - The `transit()` and `antiTransit()` methods do not accept `pressure`, `temperature`, or `flags` like rise/set does, because meridian transit is a purely geometric event not affected by atmospheric refraction. However, they do accept the `options` parameter for API consistency.
277
+ - To find "solar noon" for a given date, start from midnight UT of that date (hour = 0) and call `swe.transit()`. The result will be the Sun's meridian passage on that day.
278
+ - The time between successive solar noons varies slightly throughout the year (from about 23h 59m 39s to 24h 0m 30s) due to the equation of time. The mean interval is exactly 24 hours.
279
+ - For stars, the time between successive meridian transits is one sidereal day (23h 56m 4.1s), which is why stars appear to shift about 4 minutes earlier each night.
@@ -0,0 +1,373 @@
1
+ # Moon Crossings
2
+
3
+ **Moon crossings** let you find the exact moment the Moon reaches a specific ecliptic longitude or crosses the ecliptic plane (a node crossing). The Moon moves much faster than the Sun -- about 13 degrees per day -- so it crosses any given longitude roughly once every 27.3 days (one sidereal month).
4
+
5
+ This library provides two related functions:
6
+ - **`moonCrossing(longitude, jd)`** -- finds when the Moon next reaches a specific ecliptic longitude
7
+ - **`moonNodeCrossing(jd)`** -- finds when the Moon next crosses through the ecliptic plane (ascending or descending node)
8
+
9
+ These are useful for:
10
+ - Finding exact New Moon and Full Moon times
11
+ - Tracking lunar transits to natal chart positions
12
+ - Finding when the Moon enters each zodiac sign
13
+ - Computing eclipse timing (eclipses occur near node crossings)
14
+ - Understanding the lunar node cycle (important in both Western and Vedic astrology)
15
+
16
+ ---
17
+
18
+ ## Quick Example
19
+
20
+ ```typescript
21
+ import { SwissEph } from '../index';
22
+ import { SE_SUN } from '../../constants';
23
+
24
+ const swe = new SwissEph();
25
+ const startJd = SwissEph.julianDay(2025, 3, 1, 0);
26
+
27
+ // A Full Moon occurs when the Moon is opposite the Sun (Sun's longitude + 180°).
28
+ // Step 1: Get the Sun's longitude
29
+ const sun = swe.calc(startJd, SE_SUN);
30
+
31
+ // Step 2: Find when the Moon reaches the opposite point
32
+ const fullMoonLon = (sun.longitude + 180) % 360;
33
+ const result = swe.moonCrossing(fullMoonLon, startJd);
34
+
35
+ const d = SwissEph.fromJulianDay(result.jd);
36
+ console.log(`Next Full Moon after Mar 1, 2025: JD ${result.jd.toFixed(6)}`);
37
+ console.log(`Date: ${d.year}-${String(d.month).padStart(2,'0')}-${String(Math.floor(d.day)).padStart(2,'0')}`);
38
+
39
+ swe.close();
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Detailed Examples
45
+
46
+ ### Finding the next New Moon and Full Moon
47
+
48
+ A **New Moon** occurs when the Moon has the same ecliptic longitude as the Sun (conjunction). A **Full Moon** occurs when the Moon is at the Sun's longitude + 180 degrees (opposition).
49
+
50
+ Note: For precise New/Full Moon timing, you should iteratively refine, because the Sun also moves during the time it takes the Moon to reach the target. Here is a simple approach:
51
+
52
+ ```typescript
53
+ import { SwissEph } from '../index';
54
+ import { SE_SUN } from '../../constants';
55
+
56
+ const swe = new SwissEph();
57
+ const startJd = SwissEph.julianDay(2025, 6, 1, 0);
58
+
59
+ // --- Find the next New Moon ---
60
+ // Iterative refinement: get Sun position, find Moon crossing, repeat
61
+ let jd = startJd;
62
+ for (let i = 0; i < 3; i++) {
63
+ const sun = swe.calc(jd, SE_SUN);
64
+ const result = swe.moonCrossing(sun.longitude, jd);
65
+ jd = result.jd;
66
+ }
67
+ const newMoon = SwissEph.fromJulianDay(jd);
68
+ const nmHours = (newMoon.day % 1) * 24;
69
+ console.log(
70
+ `New Moon: ${newMoon.year}-${String(newMoon.month).padStart(2,'0')}-` +
71
+ `${String(Math.floor(newMoon.day)).padStart(2,'0')} ` +
72
+ `${String(Math.floor(nmHours)).padStart(2,'0')}:${String(Math.floor((nmHours % 1) * 60)).padStart(2,'0')} UT`
73
+ );
74
+
75
+ // --- Find the next Full Moon ---
76
+ jd = startJd;
77
+ for (let i = 0; i < 3; i++) {
78
+ const sun = swe.calc(jd, SE_SUN);
79
+ const result = swe.moonCrossing((sun.longitude + 180) % 360, jd);
80
+ jd = result.jd;
81
+ }
82
+ const fullMoon = SwissEph.fromJulianDay(jd);
83
+ const fmHours = (fullMoon.day % 1) * 24;
84
+ console.log(
85
+ `Full Moon: ${fullMoon.year}-${String(fullMoon.month).padStart(2,'0')}-` +
86
+ `${String(Math.floor(fullMoon.day)).padStart(2,'0')} ` +
87
+ `${String(Math.floor(fmHours)).padStart(2,'0')}:${String(Math.floor((fmHours % 1) * 60)).padStart(2,'0')} UT`
88
+ );
89
+
90
+ swe.close();
91
+ ```
92
+
93
+ ### Finding when the Moon enters each zodiac sign
94
+
95
+ The Moon changes signs roughly every 2.3 days. Here is how to find each sign ingress during a month:
96
+
97
+ ```typescript
98
+ import { SwissEph } from '../index';
99
+
100
+ const swe = new SwissEph();
101
+ let jd = SwissEph.julianDay(2025, 4, 1, 0);
102
+ const endJd = SwissEph.julianDay(2025, 5, 1, 0);
103
+
104
+ const signs = [
105
+ 'Aries', 'Taurus', 'Gemini', 'Cancer',
106
+ 'Leo', 'Virgo', 'Libra', 'Scorpio',
107
+ 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces',
108
+ ];
109
+
110
+ console.log('Moon sign ingresses for April 2025:');
111
+
112
+ while (jd < endJd) {
113
+ // Find the Moon's current sign
114
+ const moon = swe.calc(jd, 1); // SE_MOON = 1
115
+ const currentSign = Math.floor(moon.longitude / 30);
116
+
117
+ // The next sign boundary
118
+ const nextSignLon = ((currentSign + 1) % 12) * 30;
119
+
120
+ const result = swe.moonCrossing(nextSignLon, jd);
121
+ if (result.jd >= endJd) break;
122
+
123
+ const d = SwissEph.fromJulianDay(result.jd);
124
+ const hours = (d.day % 1) * 24;
125
+ const h = Math.floor(hours);
126
+ const m = Math.floor((hours - h) * 60);
127
+ const signIndex = nextSignLon / 30;
128
+
129
+ console.log(
130
+ ` Moon enters ${signs[signIndex].padEnd(12)} ` +
131
+ `${d.year}-${String(d.month).padStart(2,'0')}-${String(Math.floor(d.day)).padStart(2,'0')} ` +
132
+ `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')} UT`
133
+ );
134
+
135
+ jd = result.jd + 0.1; // advance a bit past the crossing
136
+ }
137
+
138
+ swe.close();
139
+ ```
140
+
141
+ ### Finding lunar node crossings
142
+
143
+ The Moon's orbit is tilted about 5.1 degrees relative to the ecliptic. It crosses the ecliptic plane twice per orbit -- once going north (ascending node) and once going south (descending node).
144
+
145
+ ```typescript
146
+ import { SwissEph } from '../index';
147
+
148
+ const swe = new SwissEph();
149
+ let jd = SwissEph.julianDay(2025, 1, 1, 0);
150
+ const endJd = SwissEph.julianDay(2026, 1, 1, 0);
151
+
152
+ console.log('Moon node crossings in 2025:');
153
+ console.log('Date Node Longitude Latitude');
154
+ console.log('------------------ ----------- --------- --------');
155
+
156
+ let count = 0;
157
+ while (jd < endJd && count < 30) {
158
+ const result = swe.moonNodeCrossing(jd);
159
+ if (result.jd >= endJd) break;
160
+
161
+ const d = SwissEph.fromJulianDay(result.jd);
162
+ const hours = (d.day % 1) * 24;
163
+ const h = Math.floor(hours);
164
+ const m = Math.floor((hours - h) * 60);
165
+
166
+ // Latitude near zero at node; sign tells us which node
167
+ // Ascending node: Moon crosses from south to north (latitude was negative, becomes positive)
168
+ // We can check by computing the Moon slightly after the crossing
169
+ const moonAfter = swe.calc(result.jd + 0.01, 1);
170
+ const nodeType = moonAfter.latitude > 0 ? 'Ascending ' : 'Descending';
171
+
172
+ console.log(
173
+ `${d.year}-${String(d.month).padStart(2,'0')}-${String(Math.floor(d.day)).padStart(2,'0')} ` +
174
+ `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')} UT ` +
175
+ `${nodeType} ` +
176
+ `${result.longitude.toFixed(3).padStart(8)}° ` +
177
+ `${result.latitude.toFixed(5).padStart(8)}°`
178
+ );
179
+
180
+ jd = result.jd + 10; // Nodes are about 13.6 days apart
181
+ count++;
182
+ }
183
+
184
+ swe.close();
185
+ ```
186
+
187
+ ### Identifying potential eclipse seasons
188
+
189
+ Eclipses occur when a New Moon or Full Moon happens close to a lunar node. By combining `moonCrossing()` and `moonNodeCrossing()`, you can identify these windows:
190
+
191
+ ```typescript
192
+ import { SwissEph } from '../index';
193
+ import { SE_SUN, SE_MOON, SE_TRUE_NODE } from '../../constants';
194
+
195
+ const swe = new SwissEph();
196
+ let jd = SwissEph.julianDay(2025, 1, 1, 0);
197
+ const endJd = SwissEph.julianDay(2026, 1, 1, 0);
198
+
199
+ console.log('Potential eclipse windows in 2025:');
200
+ console.log('(New/Full Moons within 18° of a node)\n');
201
+
202
+ // Find each New and Full Moon in the year
203
+ while (jd < endJd) {
204
+ const sun = swe.calc(jd, SE_SUN);
205
+
206
+ // Find next New Moon (Moon at Sun's longitude)
207
+ const nm = swe.moonCrossing(sun.longitude, jd);
208
+ if (nm.jd >= endJd) break;
209
+
210
+ // Check distance from the True Node
211
+ const node = swe.calc(nm.jd, SE_TRUE_NODE);
212
+ const moon = swe.calc(nm.jd, SE_MOON);
213
+
214
+ // Angular distance from Moon to node (normalize to -180..180)
215
+ let distToNode = moon.longitude - node.longitude;
216
+ if (distToNode > 180) distToNode -= 360;
217
+ if (distToNode < -180) distToNode += 360;
218
+
219
+ if (Math.abs(distToNode) < 18) {
220
+ const d = SwissEph.fromJulianDay(nm.jd);
221
+ console.log(
222
+ ` NEW MOON ${d.year}-${String(d.month).padStart(2,'0')}-${String(Math.floor(d.day)).padStart(2,'0')} ` +
223
+ `Moon ${moon.longitude.toFixed(1)}° Node ${node.longitude.toFixed(1)}° ` +
224
+ `dist ${Math.abs(distToNode).toFixed(1)}° → possible SOLAR eclipse`
225
+ );
226
+ }
227
+
228
+ // Check opposite point for Full Moon
229
+ const fm = swe.moonCrossing((sun.longitude + 180) % 360, nm.jd + 1);
230
+ if (fm.jd < endJd) {
231
+ const nodeFm = swe.calc(fm.jd, SE_TRUE_NODE);
232
+ const moonFm = swe.calc(fm.jd, SE_MOON);
233
+
234
+ let distFm = moonFm.longitude - nodeFm.longitude;
235
+ if (distFm > 180) distFm -= 360;
236
+ if (distFm < -180) distFm += 360;
237
+
238
+ if (Math.abs(distFm) < 18) {
239
+ const d2 = SwissEph.fromJulianDay(fm.jd);
240
+ console.log(
241
+ ` FULL MOON ${d2.year}-${String(d2.month).padStart(2,'0')}-${String(Math.floor(d2.day)).padStart(2,'0')} ` +
242
+ `Moon ${moonFm.longitude.toFixed(1)}° Node ${nodeFm.longitude.toFixed(1)}° ` +
243
+ `dist ${Math.abs(distFm).toFixed(1)}° → possible LUNAR eclipse`
244
+ );
245
+ }
246
+ }
247
+
248
+ jd = nm.jd + 20; // advance past this lunation
249
+ }
250
+
251
+ swe.close();
252
+ ```
253
+
254
+ ---
255
+
256
+ ## Deep Explanation
257
+
258
+ ### The Moon's Motion
259
+
260
+ The Moon is the fastest-moving body in geocentric astronomy:
261
+ - **Sidereal period** (one complete orbit relative to the stars): ~27.32 days
262
+ - **Synodic period** (one New Moon to the next): ~29.53 days (longer because the Sun also moves)
263
+ - **Average daily motion**: ~13.18 degrees per day (ranges from about 11.8 to 15.4)
264
+ - **Sign change**: every ~2.3 days
265
+
266
+ Because the Moon moves so fast, its crossings happen frequently. The Moon crosses every degree of the ecliptic roughly once a month.
267
+
268
+ ### New Moon and Full Moon Geometry
269
+
270
+ ```
271
+ New Moon (conjunction):
272
+
273
+ Sun ---- Moon ---- Earth
274
+ (Moon between Sun and Earth; same longitude)
275
+
276
+
277
+ Full Moon (opposition):
278
+
279
+ Sun ---- Earth ---- Moon
280
+ (Earth between Sun and Moon; longitude differs by 180°)
281
+ ```
282
+
283
+ The Moon's synodic period (29.53 days) is the time between successive New Moons. This is the basis of lunar calendars used throughout history (Islamic, Hebrew, Chinese, Hindu).
284
+
285
+ ### Lunar Nodes
286
+
287
+ The Moon's orbit is tilted about **5.145 degrees** relative to the ecliptic plane. The two points where the Moon's orbit intersects the ecliptic are called **nodes**:
288
+
289
+ ```
290
+ Ecliptic plane
291
+ =====================================
292
+ / \
293
+ Moon's Moon's
294
+ orbit orbit
295
+ (below) (above)
296
+ \ /
297
+ =====================================
298
+ Descending Ascending
299
+ Node Node
300
+ ```
301
+
302
+ - **Ascending Node** (North Node): where the Moon crosses from south to north of the ecliptic. Called **Rahu** in Vedic astrology, or the "Dragon's Head" in Western tradition.
303
+ - **Descending Node** (South Node): where the Moon crosses from north to south. Called **Ketu** in Vedic astrology, or the "Dragon's Tail."
304
+
305
+ The `moonNodeCrossing()` method finds the actual physical moment when the Moon passes through the ecliptic plane. It returns:
306
+ - `jd`: the exact time
307
+ - `longitude`: the ecliptic longitude where the crossing occurs
308
+ - `latitude`: the Moon's latitude at the crossing (very close to 0, by definition)
309
+
310
+ ### The Nodal Cycle
311
+
312
+ The lunar nodes are not fixed -- they move slowly **retrograde** (backward through the zodiac) at about 19.3 degrees per year, completing a full cycle in approximately **18.613 years** (the "Metonic-related" nodal period, or more precisely the **nodal regression period**).
313
+
314
+ This 18.6-year cycle is important in:
315
+ - **Eclipse prediction**: Eclipses repeat in patterns related to the nodal cycle (the Saros cycle of ~18 years, 11 days)
316
+ - **Vedic astrology**: The nodes (Rahu/Ketu) are treated as shadow planets and are key factors in chart interpretation
317
+ - **Western astrology**: The nodes indicate karmic direction (North Node = future growth, South Node = past patterns)
318
+
319
+ ### Eclipse Connection
320
+
321
+ Eclipses occur when a New Moon (solar eclipse) or Full Moon (lunar eclipse) happens while the Moon is near a node:
322
+
323
+ - **Solar eclipse**: New Moon within ~18 degrees of a node (the Sun and Moon are both near the node, so the Moon can block the Sun)
324
+ - **Lunar eclipse**: Full Moon within ~12 degrees of a node (the Moon passes through Earth's shadow)
325
+
326
+ There are two "eclipse seasons" per year, each about 34 days long, when the Sun is near enough to a node for eclipses to be possible. The eclipse seasons shift backward through the year by about 19 days per year due to the retrograde motion of the nodes.
327
+
328
+ ### Ascending vs. Descending Node
329
+
330
+ To determine which type of node crossing occurred, check the Moon's latitude shortly after the crossing:
331
+ - If latitude becomes **positive** (Moon moving north), it was an **ascending node** crossing
332
+ - If latitude becomes **negative** (Moon moving south), it was a **descending node** crossing
333
+
334
+ The `moonNodeCrossing()` method finds the **next** node crossing of either type. To find specifically the next ascending or descending node, compute the Moon's latitude slightly after the crossing as shown in the examples above.
335
+
336
+ ### Return Types
337
+
338
+ ```typescript
339
+ // moonCrossing
340
+ interface CrossingResult {
341
+ jd: number; // Julian Day when the Moon reaches the target longitude
342
+ }
343
+
344
+ // moonNodeCrossing
345
+ interface MoonNodeCrossingResult {
346
+ jd: number; // Julian Day of the node crossing
347
+ longitude: number; // Ecliptic longitude at the crossing point (degrees)
348
+ latitude: number; // Moon's latitude at crossing (very close to 0)
349
+ }
350
+ ```
351
+
352
+ ### API Details
353
+
354
+ **`moonCrossing(longitude, jd, flags?)`**
355
+ - `longitude`: Target ecliptic longitude in degrees (0-360)
356
+ - `jd`: Starting Julian Day (search forward from here)
357
+ - `flags` (optional): Calculation flags
358
+
359
+ The function finds the next time the Moon reaches the specified longitude after `jd`. Since the Moon completes a full cycle in ~27.3 days, you will always get a result within about a month.
360
+
361
+ **`moonNodeCrossing(jd, flags?)`**
362
+ - `jd`: Starting Julian Day (search forward from here)
363
+ - `flags` (optional): Calculation flags
364
+
365
+ The function finds the next time the Moon crosses the ecliptic plane (latitude = 0). Node crossings happen about every 13.6 days (half the sidereal month), alternating between ascending and descending.
366
+
367
+ ### Rahu and Ketu in Vedic Astrology
368
+
369
+ In Vedic (Hindu) astrology, the lunar nodes are given planetary status:
370
+ - **Rahu** (ascending node): associated with ambition, materialism, worldly desires, and obsession. Considered a malefic that amplifies whatever it touches.
371
+ - **Ketu** (descending node): associated with spirituality, detachment, past-life karma, and liberation. Considered a malefic that strips away worldly attachments.
372
+
373
+ The node positions used in Vedic astrology are typically the **mean nodes** (computed from `SE_MEAN_NODE = 10`), not the true/osculating nodes. The mean nodes move smoothly backward; the true nodes (from `SE_TRUE_NODE = 11`) wobble due to solar and planetary perturbations. Both can be computed with `swe.calc()`.