@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,325 @@
1
+ # Heliocentric Crossings
2
+
3
+ A **heliocentric crossing** finds the exact moment when a planet reaches a specific **heliocentric longitude** -- that is, its position as seen from the Sun (not from Earth). In heliocentric coordinates, planets move smoothly and predictably along their orbits without the apparent retrograde loops and speed variations we see from Earth.
4
+
5
+ The `helioCrossing()` method finds the next Julian Day when a planet reaches a given heliocentric longitude, searching forward (or backward) from a starting date.
6
+
7
+ This is useful for:
8
+ - Tracking actual orbital positions of planets
9
+ - Computing synodic cycles (conjunctions as seen from the Sun)
10
+ - Orbital mechanics and mission planning
11
+ - Heliocentric astrology (a niche but established branch)
12
+ - Understanding the geometry behind retrograde motion
13
+
14
+ ---
15
+
16
+ ## Quick Example
17
+
18
+ ```typescript
19
+ import { SwissEph } from '../index';
20
+ import { SE_MARS } from '../../constants';
21
+
22
+ const swe = new SwissEph();
23
+ const startJd = SwissEph.julianDay(2025, 1, 1, 0);
24
+
25
+ // Find when Mars next reaches 0° heliocentric longitude (vernal equinox direction)
26
+ const result = swe.helioCrossing(SE_MARS, 0, startJd);
27
+
28
+ const d = SwissEph.fromJulianDay(result.jd);
29
+ console.log(`Mars crosses 0° heliocentric: ${d.year}-${String(d.month).padStart(2,'0')}-${String(Math.floor(d.day)).padStart(2,'0')}`);
30
+
31
+ swe.close();
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Detailed Examples
37
+
38
+ ### Tracking a planet's heliocentric ingresses through the zodiac
39
+
40
+ From the Sun's perspective, Mars takes about 687 days (1.88 years) to go around the full 360 degrees. We can find when it enters each 30-degree segment:
41
+
42
+ ```typescript
43
+ import { SwissEph } from '../index';
44
+ import { SE_MARS } from '../../constants';
45
+
46
+ const swe = new SwissEph();
47
+ let jd = SwissEph.julianDay(2025, 1, 1, 0);
48
+
49
+ const signs = [
50
+ 'Aries', 'Taurus', 'Gemini', 'Cancer',
51
+ 'Leo', 'Virgo', 'Libra', 'Scorpio',
52
+ 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces',
53
+ ];
54
+
55
+ console.log('Mars heliocentric sign ingresses (starting 2025):');
56
+
57
+ for (let i = 0; i < 12; i++) {
58
+ const lon = i * 30;
59
+ const result = swe.helioCrossing(SE_MARS, lon, jd);
60
+ const d = SwissEph.fromJulianDay(result.jd);
61
+
62
+ console.log(
63
+ ` Mars enters ${signs[i].padEnd(12)} (${String(lon).padStart(3)}°): ` +
64
+ `${d.year}-${String(d.month).padStart(2,'0')}-${String(Math.floor(d.day)).padStart(2,'0')}`
65
+ );
66
+
67
+ jd = result.jd + 1; // advance past this crossing
68
+ }
69
+
70
+ swe.close();
71
+ ```
72
+
73
+ ### Comparing heliocentric and geocentric positions
74
+
75
+ One of the most instructive uses of heliocentric coordinates is understanding how geocentric retrograde motion arises. When Earth "overtakes" a slower outer planet, the planet appears to move backward (retrograde) in geocentric coordinates, even though heliocentrically it never stops or reverses.
76
+
77
+ ```typescript
78
+ import { SwissEph } from '../index';
79
+ import { SE_MARS, SEFLG_HELCTR } from '../../constants';
80
+
81
+ const swe = new SwissEph();
82
+
83
+ // Check Mars geocentric vs heliocentric over a period that includes retrograde
84
+ const startJd = SwissEph.julianDay(2025, 10, 1, 0);
85
+
86
+ console.log('Date Geocentric Speed Heliocentric Speed Retro?');
87
+ console.log('---------- ---------- -------- ------------ ------ ------');
88
+
89
+ for (let day = 0; day < 180; day += 10) {
90
+ const jd = startJd + day;
91
+ const d = SwissEph.fromJulianDay(jd);
92
+
93
+ const geo = swe.calc(jd, SE_MARS); // geocentric (default)
94
+ const helio = swe.calc(jd, SE_MARS, SEFLG_HELCTR); // heliocentric
95
+
96
+ const retrograde = geo.longitudeSpeed < 0 ? ' YES' : ' no';
97
+
98
+ console.log(
99
+ `${d.year}-${String(d.month).padStart(2,'0')}-${String(Math.floor(d.day)).padStart(2,'0')} ` +
100
+ `${geo.longitude.toFixed(2).padStart(8)}° ` +
101
+ `${geo.longitudeSpeed.toFixed(4).padStart(8)} ` +
102
+ ` ${helio.longitude.toFixed(2).padStart(8)}° ` +
103
+ `${helio.longitudeSpeed.toFixed(4).padStart(6)} ` +
104
+ ` ${retrograde}`
105
+ );
106
+ }
107
+
108
+ swe.close();
109
+ ```
110
+
111
+ Notice that the heliocentric speed is always positive (the planet never reverses in reality), while the geocentric speed can become negative during retrograde periods.
112
+
113
+ ### Computing a synodic cycle heliocentrically
114
+
115
+ A **synodic cycle** is the time between two successive conjunctions of a planet with another body as seen from the Sun. The simplest case: how long between successive heliocentric conjunctions of Earth and Mars?
116
+
117
+ ```typescript
118
+ import { SwissEph } from '../index';
119
+ import { SE_EARTH } from '../../constants';
120
+
121
+ const swe = new SwissEph();
122
+
123
+ // First, find Earth's heliocentric longitude at a starting point
124
+ const jd1 = SwissEph.julianDay(2025, 1, 1, 0);
125
+ const earth1 = swe.calc(jd1, SE_EARTH, 256); // SEFLG_HELCTR = 256
126
+
127
+ // Find when Mars next reaches that same heliocentric longitude
128
+ // (this approximates the heliocentric conjunction)
129
+ const mars1 = swe.helioCrossing(4, earth1.longitude, jd1); // SE_MARS = 4
130
+ const d1 = SwissEph.fromJulianDay(mars1.jd);
131
+ console.log(`Mars-Earth heliocentric conjunction ~${d1.year}-${String(d1.month).padStart(2,'0')}-${String(Math.floor(d1.day)).padStart(2,'0')}`);
132
+
133
+ // Find the next one (~780 days later, the Mars synodic period)
134
+ const mars2 = swe.helioCrossing(4, earth1.longitude, mars1.jd + 700);
135
+ const d2 = SwissEph.fromJulianDay(mars2.jd);
136
+ console.log(`Next conjunction ~${d2.year}-${String(d2.month).padStart(2,'0')}-${String(Math.floor(d2.day)).padStart(2,'0')}`);
137
+
138
+ const synodicDays = mars2.jd - mars1.jd;
139
+ console.log(`Synodic period: ${synodicDays.toFixed(1)} days (${(synodicDays / 365.25).toFixed(2)} years)`);
140
+ // Mars synodic period is approximately 780 days (2.135 years)
141
+
142
+ swe.close();
143
+ ```
144
+
145
+ ### Searching backward in time
146
+
147
+ The `dir` parameter allows searching in the reverse direction:
148
+
149
+ ```typescript
150
+ import { SwissEph } from '../index';
151
+ import { SE_JUPITER } from '../../constants';
152
+
153
+ const swe = new SwissEph();
154
+ const now = SwissEph.julianDay(2025, 6, 15, 0);
155
+
156
+ // Find the PREVIOUS time Jupiter was at 0° heliocentric (searching backward)
157
+ const prev = swe.helioCrossing(SE_JUPITER, 0, now, 0, -1);
158
+ const dp = SwissEph.fromJulianDay(prev.jd);
159
+ console.log(`Jupiter last crossed 0° helio: ${dp.year}-${String(dp.month).padStart(2,'0')}-${String(Math.floor(dp.day)).padStart(2,'0')}`);
160
+
161
+ // Find the NEXT time (searching forward, the default)
162
+ const next = swe.helioCrossing(SE_JUPITER, 0, now, 0, 0);
163
+ const dn = SwissEph.fromJulianDay(next.jd);
164
+ console.log(`Jupiter next crosses 0° helio: ${dn.year}-${String(dn.month).padStart(2,'0')}-${String(Math.floor(dn.day)).padStart(2,'0')}`);
165
+
166
+ const period = next.jd - prev.jd;
167
+ console.log(`Jupiter heliocentric period: ${(period / 365.25).toFixed(2)} years`);
168
+ // Should be close to 11.86 years
169
+
170
+ swe.close();
171
+ ```
172
+
173
+ ### All outer planets: time to complete one heliocentric orbit
174
+
175
+ ```typescript
176
+ import { SwissEph } from '../index';
177
+ import {
178
+ SE_MARS, SE_JUPITER, SE_SATURN,
179
+ SE_URANUS, SE_NEPTUNE, SE_PLUTO,
180
+ } from '../../constants';
181
+
182
+ const swe = new SwissEph();
183
+
184
+ const planets = [
185
+ { id: SE_MARS, name: 'Mars' },
186
+ { id: SE_JUPITER, name: 'Jupiter' },
187
+ { id: SE_SATURN, name: 'Saturn' },
188
+ { id: SE_URANUS, name: 'Uranus' },
189
+ { id: SE_NEPTUNE, name: 'Neptune' },
190
+ { id: SE_PLUTO, name: 'Pluto' },
191
+ ];
192
+
193
+ const now = SwissEph.julianDay(2025, 1, 1, 0);
194
+
195
+ for (const pl of planets) {
196
+ // Find when the planet crosses 0° going forward from now
197
+ const cross1 = swe.helioCrossing(pl.id, 0, now);
198
+ // Find the next crossing of 0° (one full orbit later)
199
+ const cross2 = swe.helioCrossing(pl.id, 0, cross1.jd + 30);
200
+
201
+ const periodDays = cross2.jd - cross1.jd;
202
+ const periodYears = periodDays / 365.25;
203
+
204
+ console.log(`${pl.name.padEnd(9)} orbital period: ${periodYears.toFixed(2)} years (${periodDays.toFixed(0)} days)`);
205
+ }
206
+
207
+ swe.close();
208
+ ```
209
+
210
+ ---
211
+
212
+ ## Deep Explanation
213
+
214
+ ### Heliocentric vs. Geocentric Coordinates
215
+
216
+ **Geocentric** coordinates describe positions as seen from Earth. This is the traditional astronomical and astrological perspective. Geocentric positions include all the apparent effects of Earth's own orbital motion: retrograde loops, varying speeds, and the oscillation of inferior planets around the Sun.
217
+
218
+ **Heliocentric** coordinates describe positions as seen from the Sun (from the Greek "helios" = sun). From this viewpoint:
219
+ - All planets move in the same direction (counterclockwise as seen from above the ecliptic)
220
+ - Planets never go retrograde
221
+ - Orbital speeds are nearly constant (varying only due to orbital eccentricity)
222
+ - The geometry is much simpler -- it directly reflects the physical reality of the solar system
223
+
224
+ ```
225
+ Geocentric view: Heliocentric view:
226
+
227
+ Mars appears to Mars moves steadily
228
+ loop backward in one direction
229
+ (retrograde) (no retrograde)
230
+
231
+ * * * * * * * * *
232
+ * * (smooth curve)
233
+ * *
234
+ * *
235
+ * *
236
+ ```
237
+
238
+ ### Why Retrograde is a Geocentric Illusion
239
+
240
+ Retrograde motion occurs because Earth orbits faster than the outer planets. When Earth "overtakes" an outer planet, that planet appears to drift backward against the background stars -- much like a slower car on the highway appears to move backward when you pass it.
241
+
242
+ Heliocentrically, no planet ever reverses direction. The heliocentric speed is always positive (for prograde orbits). This is why `helioCrossing()` gives clean, unambiguous results -- there is exactly one crossing of any longitude per orbit.
243
+
244
+ ### Which Planets Can Use helioCrossing?
245
+
246
+ The `helioCrossing()` method works for any body that orbits the Sun:
247
+
248
+ | Planet | Works? | Orbital Period |
249
+ |--------|--------|---------------|
250
+ | Mercury | Yes | 0.24 years (88 days) |
251
+ | Venus | Yes | 0.62 years (225 days) |
252
+ | Earth | Yes (SE_EARTH = 14) | 1.00 year (365.25 days) |
253
+ | Mars | Yes | 1.88 years |
254
+ | Jupiter | Yes | 11.86 years |
255
+ | Saturn | Yes | 29.46 years |
256
+ | Uranus | Yes | 84.01 years |
257
+ | Neptune | Yes | 164.8 years |
258
+ | Pluto | Yes | 247.9 years |
259
+ | Chiron | Yes | ~50.7 years |
260
+ | Sun | No | The Sun IS the center in heliocentric coordinates |
261
+ | Moon | No | The Moon orbits Earth, not the Sun directly |
262
+
263
+ For the Moon, use `moonCrossing()` instead. For the Sun, use `sunCrossing()`.
264
+
265
+ ### The `dir` Parameter
266
+
267
+ - `dir = 0` (default): Search **forward** in time from `jd`
268
+ - `dir = -1` (or any negative value): Search **backward** in time from `jd`
269
+
270
+ This is useful when you want to find, for example, the most recent time a planet was at a particular longitude.
271
+
272
+ ### Synodic Periods
273
+
274
+ The **synodic period** is the time between successive conjunctions of two bodies as seen from a third (usually the Sun). For a planet and Earth:
275
+
276
+ ```
277
+ Synodic period = 1 / |1/P_earth - 1/P_planet|
278
+ ```
279
+
280
+ Where P is the orbital period. Some examples:
281
+ | Planet | Synodic Period |
282
+ |--------|---------------|
283
+ | Mercury | 116 days |
284
+ | Venus | 584 days |
285
+ | Mars | 780 days (2.14 years) |
286
+ | Jupiter | 399 days (1.09 years) |
287
+ | Saturn | 378 days (1.04 years) |
288
+
289
+ ### Heliocentric Astrology
290
+
291
+ Some astrologers use heliocentric charts alongside or instead of geocentric charts. In heliocentric astrology:
292
+ - The Sun is at the center (no Sun position in the chart)
293
+ - The Moon is not used (it orbits Earth, not the Sun)
294
+ - Earth replaces the Sun as a charted body
295
+ - There are no retrograde periods
296
+ - Aspects between planets reflect the actual geometric relationships in space
297
+
298
+ Proponents argue that heliocentric aspects represent the "objective" or "cosmic" energies, while geocentric aspects represent the "subjective" or "personal" experience of those energies.
299
+
300
+ ### Precision and Edge Cases
301
+
302
+ The `helioCrossing()` function uses iterative numerical methods to find the exact crossing time. For planets with nearly circular orbits (Venus, Earth), convergence is quick and very precise. For planets with higher eccentricity (Mercury, Mars, Pluto), the function still converges but may take slightly more iterations internally.
303
+
304
+ For very slow planets (Neptune, Pluto), note that a single heliocentric crossing takes a very long time -- Pluto takes nearly 248 years for one orbit, so it crosses a given longitude only once in a human lifetime. The function handles this correctly but be aware that searching for the "next" crossing of a slow planet may return a date far in the future.
305
+
306
+ ### API Details
307
+
308
+ ```typescript
309
+ swe.helioCrossing(
310
+ planet: number, // Planet constant (SE_MARS, SE_JUPITER, etc.)
311
+ longitude: number, // Target heliocentric longitude in degrees (0-360)
312
+ jd: number, // Starting Julian Day
313
+ flags?: number, // Calculation flags (optional, default 0)
314
+ dir?: number // Search direction: 0 = forward (default), negative = backward
315
+ ): CrossingResult
316
+ ```
317
+
318
+ **Returns:**
319
+ ```typescript
320
+ interface CrossingResult {
321
+ jd: number; // Julian Day of the crossing
322
+ }
323
+ ```
324
+
325
+ The returned Julian Day can be converted to a calendar date with `SwissEph.fromJulianDay(result.jd)`.
@@ -0,0 +1,254 @@
1
+ # House Position
2
+
3
+ Once you have calculated the house cusps and a planet's ecliptic coordinates, the next natural question is: **which house does the planet fall in?** The `housePosition()` method answers this by returning a fractional house number. For example, a return value of `10.05` means the planet is in the 10th house, very near the beginning (cusp); `3.75` means the planet is three-quarters of the way through the 3rd house.
4
+
5
+ This is essential for chart interpretation -- the house a planet occupies tells you in which area of life that planet's energy is most active.
6
+
7
+ ---
8
+
9
+ ## Quick Example
10
+
11
+ ```typescript
12
+ import { SwissEph } from '../index';
13
+ import { SE_SUN } from '../../constants';
14
+
15
+ const swe = new SwissEph();
16
+
17
+ const jd = SwissEph.julianDay(2000, 1, 1, 12);
18
+ const geo = { longitude: -0.1276, latitude: 51.5074 }; // London
19
+
20
+ // Step 1: Get house cusps (we need ARMC from the result)
21
+ const h = swe.houses(jd, geo, 'P'); // Placidus
22
+
23
+ // Step 2: Get the planet's ecliptic position
24
+ const sun = swe.calc(jd, SE_SUN);
25
+
26
+ // Step 3: Get the obliquity of the ecliptic
27
+ // (the ARMC and obliquity are needed for the house position calculation)
28
+ const eps = 23.4393; // approximate; for precision, compute from the engine
29
+
30
+ // Step 4: Find which house the Sun is in
31
+ const housePos = swe.housePosition(
32
+ h.armc, // ARMC (sidereal time in degrees)
33
+ geo.latitude, // geographic latitude
34
+ eps, // obliquity of the ecliptic
35
+ 'P', // house system (must match what you used in houses())
36
+ sun.longitude, // planet's ecliptic longitude
37
+ sun.latitude // planet's ecliptic latitude
38
+ );
39
+
40
+ const houseNumber = Math.floor(housePos);
41
+ console.log(`Sun is in house ${houseNumber} (exact position: ${housePos.toFixed(4)})`);
42
+ // Sun is in house 10 (exact position: 10.0536)
43
+
44
+ swe.close();
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Detailed Examples
50
+
51
+ ### Complete workflow: all planets with their house positions
52
+
53
+ ```typescript
54
+ import { SwissEph } from '../index';
55
+ import {
56
+ SE_SUN, SE_MOON, SE_MERCURY, SE_VENUS, SE_MARS,
57
+ SE_JUPITER, SE_SATURN, SE_URANUS, SE_NEPTUNE, SE_PLUTO,
58
+ } from '../../constants';
59
+
60
+ const swe = new SwissEph();
61
+
62
+ const jd = SwissEph.julianDay(2024, 4, 8, 18.28);
63
+ const geo = { longitude: -104.0, latitude: 25.0 };
64
+
65
+ // Step 1: Get houses (Placidus)
66
+ const h = swe.houses(jd, geo, 'P');
67
+
68
+ // We need the obliquity. A good approximation for current dates:
69
+ const eps = 23.4393;
70
+
71
+ // Step 2: Calculate each planet and its house position
72
+ const planets = [
73
+ { id: SE_SUN, name: 'Sun' },
74
+ { id: SE_MOON, name: 'Moon' },
75
+ { id: SE_MERCURY, name: 'Mercury' },
76
+ { id: SE_VENUS, name: 'Venus' },
77
+ { id: SE_MARS, name: 'Mars' },
78
+ { id: SE_JUPITER, name: 'Jupiter' },
79
+ { id: SE_SATURN, name: 'Saturn' },
80
+ { id: SE_URANUS, name: 'Uranus' },
81
+ { id: SE_NEPTUNE, name: 'Neptune' },
82
+ { id: SE_PLUTO, name: 'Pluto' },
83
+ ];
84
+
85
+ for (const p of planets) {
86
+ const pos = swe.calc(jd, p.id);
87
+ const hp = swe.housePosition(
88
+ h.armc, geo.latitude, eps, 'P',
89
+ pos.longitude, pos.latitude
90
+ );
91
+ const houseNum = Math.floor(hp);
92
+ const fraction = hp - houseNum;
93
+ const percent = (fraction * 100).toFixed(1);
94
+
95
+ console.log(
96
+ `${p.name.padEnd(9)} ` +
97
+ `lon: ${pos.longitude.toFixed(2).padStart(7)} ` +
98
+ `house: ${houseNum.toString().padStart(2)} ` +
99
+ `(${percent}% through the house)`
100
+ );
101
+ }
102
+
103
+ swe.close();
104
+ ```
105
+
106
+ ### Comparing house positions across different house systems
107
+
108
+ The same planet can fall in different houses depending on which house system you use:
109
+
110
+ ```typescript
111
+ import { SwissEph } from '../index';
112
+ import { SE_MOON } from '../../constants';
113
+
114
+ const swe = new SwissEph();
115
+
116
+ const jd = SwissEph.julianDay(2024, 1, 1, 0);
117
+ const geo = { longitude: -73.9857, latitude: 40.7484 }; // New York
118
+
119
+ const moon = swe.calc(jd, SE_MOON);
120
+ const eps = 23.4393;
121
+
122
+ const systems = ['P', 'K', 'E', 'W', 'R', 'C', 'O', 'T'];
123
+
124
+ for (const sys of systems) {
125
+ const h = swe.houses(jd, geo, sys);
126
+ const hp = swe.housePosition(
127
+ h.armc, geo.latitude, eps, sys,
128
+ moon.longitude, moon.latitude
129
+ );
130
+ console.log(
131
+ `${swe.houseName(sys).padEnd(25)} Moon in house ${Math.floor(hp).toString().padStart(2)} (${hp.toFixed(3)})`
132
+ );
133
+ }
134
+
135
+ swe.close();
136
+ ```
137
+
138
+ ### Using obliquity from the engine
139
+
140
+ For maximum precision, compute the obliquity rather than using a constant. You can derive it from the engine by computing the ecliptic/nutation values:
141
+
142
+ ```typescript
143
+ import { SwissEph } from '../index';
144
+ import { SE_SUN, SE_ECL_NUT } from '../../constants';
145
+
146
+ const swe = new SwissEph();
147
+ const jd = SwissEph.julianDay(2024, 1, 1, 12);
148
+ const geo = { longitude: -0.1276, latitude: 51.5074 };
149
+
150
+ // SE_ECL_NUT returns nutation and obliquity data:
151
+ // longitude = true obliquity of the ecliptic
152
+ // latitude = mean obliquity
153
+ const eclNut = swe.calc(jd, SE_ECL_NUT);
154
+ const trueObliquity = eclNut.longitude; // ~23.4362 deg for 2024
155
+
156
+ const h = swe.houses(jd, geo, 'P');
157
+ const sun = swe.calc(jd, SE_SUN);
158
+
159
+ const hp = swe.housePosition(
160
+ h.armc, geo.latitude, trueObliquity, 'P',
161
+ sun.longitude, sun.latitude
162
+ );
163
+ console.log(`Sun house position: ${hp.toFixed(4)} (using true obliquity ${trueObliquity.toFixed(4)} deg)`);
164
+
165
+ swe.close();
166
+ ```
167
+
168
+ **Note**: `SE_ECL_NUT` has value `-1` and is a special "planet" number that returns ecliptic/nutation data instead of a planetary position.
169
+
170
+ ### Interpreting the fractional house number
171
+
172
+ ```typescript
173
+ // The house position is a floating-point number from 1.000 to 12.999...
174
+
175
+ const housePos = 7.333;
176
+
177
+ // Integer part = house number
178
+ const houseNumber = Math.floor(housePos);
179
+ // houseNumber = 7 (planet is in the 7th house)
180
+
181
+ // Fractional part = how far through the house (0.0 = at cusp, 1.0 = at next cusp)
182
+ const fraction = housePos - houseNumber;
183
+ // fraction = 0.333 (one-third of the way through the 7th house)
184
+
185
+ // Some astrologers divide houses into thirds:
186
+ // 0.00-0.33 = "early in the house" or "angular third"
187
+ // 0.33-0.67 = "middle of the house" or "succedent third"
188
+ // 0.67-1.00 = "late in the house" or "cadent third"
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Deep Explanation
194
+
195
+ ### Parameters explained
196
+
197
+ The `housePosition()` method requires five pieces of information:
198
+
199
+ | Parameter | Description |
200
+ |-----------|-------------------------------------------------------------------------------------------------|
201
+ | `armc` | ARMC (sidereal time expressed in degrees). Get this from the `armc` field of `houses()` result. ARMC = sidereal time in hours * 15. |
202
+ | `lat` | Geographic latitude of the observer in degrees. Positive = north, negative = south. |
203
+ | `eps` | Obliquity of the ecliptic in degrees. Approximately 23.44 degrees for current dates. For precision, compute using `SE_ECL_NUT`. |
204
+ | `system` | House system letter code (e.g., `'P'` for Placidus). Must match the system used when computing cusps. |
205
+ | `eclLon` | Planet's ecliptic longitude in degrees (0-360). |
206
+ | `eclLat` | Planet's ecliptic latitude in degrees. For the Sun this is essentially 0. For the Moon it can be up to ~5 degrees. For most planets it is small but nonzero. |
207
+
208
+ ### The return value
209
+
210
+ The return value is a floating-point number:
211
+ - The integer part (1-12) is the house number.
212
+ - The fractional part (0.000 to 0.999...) indicates position within the house.
213
+ - The range is `1.0` to `12.999...` (never 13.0; after 12.999 it wraps to 1.0).
214
+
215
+ ### Why ecliptic latitude matters
216
+
217
+ Most astrology software ignores ecliptic latitude when assigning house positions, effectively projecting every body onto the ecliptic (latitude = 0). This is acceptable for the Sun and inner planets (which stay very close to the ecliptic) but can matter for:
218
+
219
+ - **The Moon**: up to ~5 degrees of latitude
220
+ - **Pluto**: up to ~17 degrees of latitude
221
+ - **Asteroids**: highly inclined orbits
222
+
223
+ The Swiss Ephemeris `housePosition()` properly accounts for ecliptic latitude by converting the body's position through the house system's projection geometry. For the most accurate house placements, always pass the actual latitude.
224
+
225
+ If you want the traditional "projected onto the ecliptic" behavior, pass `0` for `eclLat`.
226
+
227
+ ### House position vs. house cusp
228
+
229
+ These are different but related concepts:
230
+
231
+ - **House cusps** (`houses()`) give you the twelve boundary longitudes. They answer: "At what ecliptic degree does each house begin?"
232
+ - **House position** (`housePosition()`) gives you a planet's location within the house system. It answers: "Given this planet at this ecliptic longitude/latitude, which house is it in and how far through?"
233
+
234
+ You might think you could determine the house just by comparing the planet's longitude to the cusp longitudes. This works for simple systems (Equal, Whole Sign) but is incorrect for quadrant systems (Placidus, Koch, etc.) when the planet has nonzero ecliptic latitude. The `housePosition()` function handles the full 3D geometry correctly.
235
+
236
+ ### Edge cases
237
+
238
+ **Circumpolar regions**: At high latitudes (near or beyond the Arctic/Antarctic circles), Placidus and Koch houses can become distorted or undefined. The `housePosition()` function may still return a value, but its astronomical meaning becomes questionable. Consider using Equal, Whole Sign, or Topocentric houses for locations beyond ~60 degrees latitude.
239
+
240
+ **Planets on a cusp**: If a planet's house position is very close to a whole number (e.g., 6.002 or 5.998), it is very near the cusp boundary. In practice, with a fractional position like 5.998, the planet is technically still in the 5th house but essentially at the 6th cusp. Different astrologers have different orb allowances for "conjunct the cusp."
241
+
242
+ **Consistency of system**: Always use the same house system letter code in both `houses()` (to get the ARMC and cusps) and `housePosition()`. Using mismatched systems will give incorrect results.
243
+
244
+ ### Typical workflow summary
245
+
246
+ ```
247
+ 1. Choose a date/time and location
248
+ 2. Compute Julian Day: jd = SwissEph.julianDay(...)
249
+ 3. Compute house cusps: h = swe.houses(jd, geo, 'P')
250
+ 4. Compute planet position: p = swe.calc(jd, SE_SUN)
251
+ 5. Get obliquity: eps = 23.4393 (or compute via SE_ECL_NUT)
252
+ 6. Get house position: hp = swe.housePosition(h.armc, geo.latitude, eps, 'P', p.longitude, p.latitude)
253
+ 7. Interpret: Math.floor(hp) is the house number
254
+ ```