@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
+ # House Systems
2
+
3
+ In astrology, the **houses** are twelve divisions of the sky (or the ecliptic) as seen from a specific location on Earth at a specific time. While the zodiac signs divide the ecliptic based on the Sun's annual path, the houses divide it based on the Earth's daily rotation. Each house represents a domain of life experience (self, finances, communication, home, creativity, etc.).
4
+
5
+ Because there are many different mathematical ways to divide the sky into twelve sections, there are many **house systems**. Each produces slightly (or sometimes dramatically) different house cusps. The choice of house system is one of the most debated topics in Western astrology. Vedic astrology predominantly uses Whole Sign houses.
6
+
7
+ The Swiss Ephemeris supports over 20 house systems. Given a time and geographic location, it computes the twelve house cusp longitudes plus several important angles (Ascendant, MC, Vertex, etc.).
8
+
9
+ ---
10
+
11
+ ## Quick Example
12
+
13
+ ```typescript
14
+ import { SwissEph } from '../index';
15
+
16
+ const swe = new SwissEph();
17
+
18
+ // January 1, 2000 at noon UT, London
19
+ const jd = SwissEph.julianDay(2000, 1, 1, 12);
20
+ const geo = { longitude: -0.1276, latitude: 51.5074 };
21
+
22
+ const houses = swe.houses(jd, geo, 'P'); // Placidus
23
+
24
+ console.log(`Ascendant: ${houses.ascendant.toFixed(2)} deg`);
25
+ console.log(`MC: ${houses.mc.toFixed(2)} deg`);
26
+
27
+ // House cusps are 1-indexed: cusps[1] through cusps[12]
28
+ for (let i = 1; i <= 12; i++) {
29
+ console.log(`House ${String(i).padStart(2)} cusp: ${houses.cusps[i].toFixed(2)} deg`);
30
+ }
31
+
32
+ swe.close();
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Detailed Examples
38
+
39
+ ### Comparing house systems
40
+
41
+ ```typescript
42
+ import { SwissEph } from '../index';
43
+
44
+ const swe = new SwissEph();
45
+ const jd = SwissEph.julianDay(2024, 3, 20, 12); // Vernal equinox 2024
46
+ const geo = { longitude: -73.9857, latitude: 40.7484 }; // New York
47
+
48
+ const systems = [
49
+ { code: 'P', name: 'Placidus' },
50
+ { code: 'K', name: 'Koch' },
51
+ { code: 'E', name: 'Equal (from Asc)' },
52
+ { code: 'W', name: 'Whole Sign' },
53
+ { code: 'R', name: 'Regiomontanus' },
54
+ { code: 'C', name: 'Campanus' },
55
+ { code: 'O', name: 'Porphyry' },
56
+ { code: 'T', name: 'Topocentric (Polich/Page)' },
57
+ ];
58
+
59
+ for (const sys of systems) {
60
+ const h = swe.houses(jd, geo, sys.code);
61
+ console.log(`\n--- ${sys.name} (${sys.code}) ---`);
62
+ console.log(` Asc: ${h.ascendant.toFixed(2)} MC: ${h.mc.toFixed(2)}`);
63
+ for (let i = 1; i <= 12; i++) {
64
+ process.stdout.write(` H${i}: ${h.cusps[i].toFixed(1)} `);
65
+ if (i % 4 === 0) process.stdout.write('\n');
66
+ }
67
+ }
68
+
69
+ swe.close();
70
+ ```
71
+
72
+ ### Whole Sign and Equal houses
73
+
74
+ Whole Sign and Equal houses are the simplest systems. In Whole Sign, each house is exactly one zodiac sign (30 degrees). In Equal, each house is 30 degrees starting from the Ascendant.
75
+
76
+ ```typescript
77
+ import { SwissEph } from '../index';
78
+
79
+ const swe = new SwissEph();
80
+ const jd = SwissEph.julianDay(2024, 1, 1, 0);
81
+ const geo = { longitude: 80.27, latitude: 13.08 }; // Chennai, India
82
+
83
+ // Whole Sign houses (popular in Vedic astrology)
84
+ const ws = swe.houses(jd, geo, 'W');
85
+ console.log('Whole Sign:');
86
+ for (let i = 1; i <= 12; i++) {
87
+ // Each cusp is the start of a sign boundary
88
+ console.log(` House ${i}: ${ws.cusps[i].toFixed(2)} deg`);
89
+ }
90
+
91
+ // Equal houses from Ascendant
92
+ const eq = swe.houses(jd, geo, 'E');
93
+ console.log('\nEqual from Ascendant:');
94
+ for (let i = 1; i <= 12; i++) {
95
+ // Each cusp is exactly 30 degrees apart, starting from the Ascendant
96
+ console.log(` House ${i}: ${eq.cusps[i].toFixed(2)} deg`);
97
+ }
98
+
99
+ swe.close();
100
+ ```
101
+
102
+ ### Accessing all the angle values
103
+
104
+ Beyond the 12 house cusps, the `houses()` function returns several important astronomical/astrological points:
105
+
106
+ ```typescript
107
+ import { SwissEph } from '../index';
108
+
109
+ const swe = new SwissEph();
110
+ const jd = SwissEph.julianDay(2024, 6, 21, 12);
111
+ const geo = { longitude: -0.1276, latitude: 51.5074 }; // London
112
+
113
+ const h = swe.houses(jd, geo, 'P');
114
+
115
+ console.log(`Ascendant (ASC): ${h.ascendant.toFixed(4)} deg`);
116
+ console.log(`Midheaven (MC): ${h.mc.toFixed(4)} deg`);
117
+ console.log(`ARMC (sidereal time): ${h.armc.toFixed(4)} deg`);
118
+ console.log(`Vertex: ${h.vertex.toFixed(4)} deg`);
119
+ console.log(`Equatorial Ascendant: ${h.equatorialAscendant.toFixed(4)} deg`);
120
+ console.log(`Co-Ascendant (Koch): ${h.coAscendantKoch.toFixed(4)} deg`);
121
+ console.log(`Co-Ascendant (Munkasey): ${h.coAscendantMunkasey.toFixed(4)} deg`);
122
+ console.log(`Polar Ascendant: ${h.polarAscendant.toFixed(4)} deg`);
123
+
124
+ swe.close();
125
+ ```
126
+
127
+ ### Computing houses from ARMC directly
128
+
129
+ If you already know the ARMC (sidereal time in degrees), geographic latitude, and obliquity of the ecliptic, you can compute houses without a Julian Day:
130
+
131
+ ```typescript
132
+ import { SwissEph } from '../index';
133
+
134
+ const swe = new SwissEph();
135
+
136
+ // ARMC in degrees (sidereal time * 15)
137
+ const armc = 279.50; // example value
138
+ const lat = 51.5074; // London latitude
139
+ const eps = 23.4393; // mean obliquity of the ecliptic (approx)
140
+
141
+ const h = swe.housesFromArmc(armc, lat, eps, 'P');
142
+ console.log(`Ascendant: ${h.ascendant.toFixed(4)} deg`);
143
+ console.log(`MC: ${h.mc.toFixed(4)} deg`);
144
+
145
+ swe.close();
146
+ ```
147
+
148
+ ### Getting the name of a house system
149
+
150
+ ```typescript
151
+ import { SwissEph } from '../index';
152
+
153
+ const swe = new SwissEph();
154
+
155
+ const codes = 'PKEWCRTADFGHIJLMNOQSUVXY'.split('');
156
+ for (const code of codes) {
157
+ console.log(`${code} = ${swe.houseName(code)}`);
158
+ }
159
+
160
+ swe.close();
161
+ ```
162
+
163
+ ### Gauquelin sectors (36 sectors)
164
+
165
+ The Gauquelin sector system uses 36 sectors instead of 12 houses. When you request system `'G'`, the `cusps` array contains 37 entries (cusps[1] through cusps[36]).
166
+
167
+ ```typescript
168
+ import { SwissEph } from '../index';
169
+
170
+ const swe = new SwissEph();
171
+ const jd = SwissEph.julianDay(2024, 1, 1, 12);
172
+ const geo = { longitude: 2.3522, latitude: 48.8566 }; // Paris
173
+
174
+ const g = swe.houses(jd, geo, 'G');
175
+ console.log('Gauquelin sectors (36):');
176
+ for (let i = 1; i <= 36; i++) {
177
+ console.log(` Sector ${String(i).padStart(2)}: ${g.cusps[i].toFixed(2)} deg`);
178
+ }
179
+
180
+ swe.close();
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Deep Explanation
186
+
187
+ ### All supported house systems
188
+
189
+ Each system is identified by a single-character code passed as the `system` parameter.
190
+
191
+ | Code | Name | Description |
192
+ |------|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
193
+ | `P` | Placidus | The most popular system in modern Western astrology. Divides the diurnal and nocturnal semi-arcs of the ecliptic into three equal time intervals. Fails at high latitudes (>66.5 deg) where some points never rise or set. |
194
+ | `K` | Koch | Also called GOH (Geburtsort-Haeuser). Similar to Placidus in concept but uses a different mathematical approach. Also fails at high latitudes. |
195
+ | `E` | Equal (from Ascendant) | Each house is exactly 30 degrees. House 1 starts at the Ascendant. Simple and unambiguous at all latitudes. The MC does not necessarily fall on the 10th cusp. |
196
+ | `W` | Whole Sign | Each house corresponds to an entire zodiac sign. House 1 is the sign containing the Ascendant (cusp = 0 deg of that sign). The oldest known house system, used in Hellenistic and Vedic astrology. |
197
+ | `B` | Alcabitius | Semi-arc system that divides the diurnal semi-arc of the Ascendant into three equal parts. Medieval European system. |
198
+ | `C` | Campanus | Divides the prime vertical (the great circle passing through the east point, zenith, west point, and nadir) into 12 equal arcs of 30 degrees, then projects them onto the ecliptic. |
199
+ | `R` | Regiomontanus | Divides the celestial equator into 12 equal arcs of 30 degrees, then projects them onto the ecliptic via great circles through the north and south points of the horizon. Popular in horary astrology. |
200
+ | `T` | Polich/Page (Topocentric) | Very similar to Placidus in practice but uses a different mathematical model. Claimed to be more accurate for topocentric purposes. Works at all latitudes. |
201
+ | `A` | Equal (from Ascendant) | Same as `E`. Alternative code. |
202
+ | `D` | Equal (from MC) | Each house is 30 degrees starting from the MC (Midheaven). The MC is exactly on the 10th cusp. |
203
+ | `F` | Carter poli-equatorial | Carter's system projecting from the equator. |
204
+ | `G` | Gauquelin sectors | 36 sectors (not 12 houses) used in the Gauquelin research on planetary positions in relation to the angles. |
205
+ | `H` | Horizon / Azimuthal | Divides the horizon into 12 equal segments of 30 degrees, starting from the east point. |
206
+ | `I` | Sunshine (Treindl) | Based on the proportion of daylight/nighttime hours. |
207
+ | `J` | Sunshine (alternative) | Alternative version of the Sunshine system. |
208
+ | `L` | Pullen SD (sinusoidal delta) | Pullen's sinusoidal delta house system. |
209
+ | `M` | Morinus | Divides the equator into 12 equal arcs, then converts each cusp to ecliptic longitude. Does not use the horizon at all -- only depends on sidereal time. |
210
+ | `N` | Pullen SR (sinusoidal ratio) | Pullen's sinusoidal ratio house system. |
211
+ | `O` | Porphyry | Trisects the arcs between the four angles (ASC, IC, DSC, MC). One of the oldest quadrant-based systems. Simple and works at all latitudes. |
212
+ | `Q` | Pullen | Pullen's general house system. |
213
+ | `S` | Sripati | Used in Indian astrology. Similar to Porphyry but shifts each cusp by half a house: each house cusp is the midpoint of the corresponding Porphyry houses. |
214
+ | `U` | Krusinski | Krusinski-Pisa house system. Divides the vertical joining the Ascendant and Descendant. |
215
+ | `V` | Pisa | Variation of the Krusinski system. |
216
+ | `X` | Axial rotation | Based on the Earth's axial rotation projected onto the ecliptic. |
217
+ | `Y` | APC houses | Astrological houses based on the Ascendant parallel circle. |
218
+
219
+ ### Cusps array indexing
220
+
221
+ The `cusps` array is **1-indexed**: house cusp longitudes are in `cusps[1]` through `cusps[12]` (or `cusps[36]` for Gauquelin sectors). `cusps[0]` is unused (always 0).
222
+
223
+ ```typescript
224
+ const h = swe.houses(jd, geo, 'P');
225
+ // h.cusps[0] -> 0 (unused)
226
+ // h.cusps[1] -> 1st house cusp (same as Ascendant for most systems)
227
+ // h.cusps[2] -> 2nd house cusp
228
+ // ...
229
+ // h.cusps[12] -> 12th house cusp
230
+ ```
231
+
232
+ For most quadrant systems (Placidus, Koch, Regiomontanus, Campanus, Topocentric, Porphyry), `cusps[1]` equals the Ascendant and `cusps[10]` equals the MC. For Equal and Whole Sign, `cusps[10]` may differ from the MC.
233
+
234
+ ### The angle values explained
235
+
236
+ | Field | Description |
237
+ |------------------------|------------------------------------------------------------------------------------------------|
238
+ | `ascendant` | The ecliptic degree rising on the eastern horizon. The most important angle in a chart. |
239
+ | `mc` | Medium Coeli (Midheaven). The ecliptic degree at the upper meridian (highest point the ecliptic reaches). |
240
+ | `armc` | ARMC = sidereal time expressed in degrees (sidereal time in hours * 15). Used internally for house position calculations. |
241
+ | `vertex` | The ecliptic degree on the prime vertical in the west. Used in synastry and as a "fated point." |
242
+ | `equatorialAscendant` | The Ascendant calculated on the celestial equator (sometimes called the "East Point"). |
243
+ | `coAscendantKoch` | Co-Ascendant as defined by Koch. |
244
+ | `coAscendantMunkasey` | Co-Ascendant as defined by Munkasey. |
245
+ | `polarAscendant` | The Ascendant of the polar opposite latitude (also called the "Anti-Vertex"). |
246
+
247
+ ### High-latitude issues
248
+
249
+ **Placidus and Koch** systems fail at latitudes beyond approximately 66.5 degrees (within the Arctic or Antarctic circles). At these latitudes, some ecliptic degrees never rise or set, making it impossible to compute the time-based house divisions these systems require. The engine will return results but they may be unreliable.
250
+
251
+ Systems that work at **all latitudes** include:
252
+ - Equal (`E`, `A`, `D`)
253
+ - Whole Sign (`W`)
254
+ - Porphyry (`O`)
255
+ - Morinus (`M`)
256
+ - Campanus (`C`)
257
+ - Regiomontanus (`R`)
258
+ - Topocentric/Polich-Page (`T`) -- designed to handle extreme latitudes
259
+ - Axial rotation (`X`)
260
+ - APC (`Y`)
261
+
262
+ ### Geographic position convention
263
+
264
+ - **Longitude**: Positive = east, negative = west. For example, New York is about -74 degrees, Tokyo is about +139.7 degrees.
265
+ - **Latitude**: Positive = north, negative = south. For example, Sydney is about -33.9 degrees.
266
+ - **Altitude**: In meters above sea level. Optional (defaults to 0). Only affects topocentric corrections.
267
+
268
+ ### Choosing a house system
269
+
270
+ There is no universally "correct" house system. The choice depends on your astrological tradition:
271
+
272
+ - **Modern Western astrology**: Placidus (`P`) is by far the most popular, followed by Koch (`K`) and Equal (`E`).
273
+ - **Traditional/Hellenistic astrology**: Whole Sign (`W`) is the historically oldest system and has seen a major revival.
274
+ - **Horary astrology**: Regiomontanus (`R`) is traditional for horary work.
275
+ - **Vedic/Indian astrology (Jyotish)**: Whole Sign (`W`) or Sripati (`S`).
276
+ - **Uranian/Hamburg School**: Often uses Meridian or equal systems.
277
+ - **Research (Gauquelin)**: The Gauquelin sector system (`G`) with 36 sectors.
278
+
279
+ When in doubt, Placidus is the safe default for Western astrology, and Whole Sign for Vedic or Hellenistic work.
@@ -0,0 +1,326 @@
1
+ # Lunar Eclipses
2
+
3
+ A **lunar eclipse** occurs when the Earth passes between the Sun and the Moon, casting Earth's shadow onto the Moon. Unlike solar eclipses, which are only visible from a narrow path on Earth, a lunar eclipse is visible from anywhere on the night side of the planet where the Moon is above the horizon. This makes lunar eclipses far more commonly observed than solar eclipses.
4
+
5
+ Lunar eclipses can only happen during a Full Moon, when the Sun and Moon are on opposite sides of the Earth. But just as with solar eclipses, the Moon's orbit is tilted about 5 degrees from the ecliptic, so most Full Moons pass above or below Earth's shadow. An eclipse only occurs when the Full Moon is near one of its orbital nodes.
6
+
7
+ During a total lunar eclipse, the Moon does not disappear entirely. Instead, it typically turns a deep red or copper color -- sometimes called a "Blood Moon." This happens because Earth's atmosphere bends (refracts) sunlight around the planet's edge, and the atmosphere filters out shorter blue wavelengths while allowing longer red wavelengths to reach the Moon.
8
+
9
+ ---
10
+
11
+ ## Quick Example
12
+
13
+ ```typescript
14
+ import { SwissEph } from '../index';
15
+
16
+ const swe = new SwissEph();
17
+
18
+ // Find the next lunar eclipse after January 1, 2025
19
+ const jd = SwissEph.julianDay(2025, 1, 1, 0);
20
+ const eclipse = swe.lunarEclipseGlobal(jd);
21
+
22
+ const date = SwissEph.fromJulianDay(eclipse.maximum);
23
+ console.log(`Next lunar eclipse: ${date.year}-${date.month}-${date.day}`);
24
+ console.log(`Maximum at JD: ${eclipse.maximum.toFixed(6)}`);
25
+
26
+ swe.close();
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Detailed Examples
32
+
33
+ ### The 2025 March 14 total lunar eclipse
34
+
35
+ ```typescript
36
+ import { SwissEph } from '../index';
37
+ import { SE_ECL_TOTAL } from '../../constants';
38
+
39
+ const swe = new SwissEph();
40
+
41
+ // Search for the next total lunar eclipse after 2025-01-01
42
+ const jd = SwissEph.julianDay(2025, 1, 1, 0);
43
+ const eclipse = swe.lunarEclipseGlobal(jd, SE_ECL_TOTAL);
44
+
45
+ const fmt = (jd: number) => {
46
+ if (jd === 0) return '(n/a)';
47
+ const d = SwissEph.fromJulianDay(jd);
48
+ const h = Math.floor(d.hour);
49
+ const m = Math.floor((d.hour - h) * 60);
50
+ return `${d.year}-${String(d.month).padStart(2,'0')}-${String(d.day).padStart(2,'0')} ${h}:${String(m).padStart(2,'0')} UT`;
51
+ };
52
+
53
+ console.log(`Total lunar eclipse`);
54
+ console.log(` Maximum: ${fmt(eclipse.maximum)}`);
55
+ console.log(` Penumbral begin: ${fmt(eclipse.penumbralBegin)}`);
56
+ console.log(` Partial begin: ${fmt(eclipse.partialBegin)}`);
57
+ console.log(` Total begin: ${fmt(eclipse.totalBegin)}`);
58
+ console.log(` Total end: ${fmt(eclipse.totalEnd)}`);
59
+ console.log(` Partial end: ${fmt(eclipse.partialEnd)}`);
60
+ console.log(` Penumbral end: ${fmt(eclipse.penumbralEnd)}`);
61
+ // 2025-03-14, maximum around 06:58 UT
62
+
63
+ swe.close();
64
+ ```
65
+
66
+ ### Eclipse attributes: magnitude and shadow sizes
67
+
68
+ ```typescript
69
+ import { SwissEph } from '../index';
70
+
71
+ const swe = new SwissEph();
72
+
73
+ const jd = SwissEph.julianDay(2025, 1, 1, 0);
74
+ const eclipse = swe.lunarEclipseGlobal(jd);
75
+
76
+ // Get detailed attributes at maximum
77
+ const how = swe.lunarEclipseHow(eclipse.maximum);
78
+
79
+ console.log(`Eclipse type flags: ${how.type}`);
80
+ console.log(`Umbral magnitude: ${how.umbraMagnitude.toFixed(4)}`);
81
+ console.log(`Penumbral magnitude: ${how.penumbraMagnitude.toFixed(4)}`);
82
+ console.log(`Moon diameter: ${how.moonDiameter.toFixed(2)} arc-minutes`);
83
+ console.log(`Umbra diameter: ${how.umbraDiameter.toFixed(2)} arc-minutes`);
84
+ console.log(`Penumbra diameter: ${how.penumbraDiameter.toFixed(2)} arc-minutes`);
85
+ console.log(`Sun dist from node: ${how.sunDistanceFromNode.toFixed(2)} deg`);
86
+
87
+ // For the 2025 Mar 14 eclipse, umbral magnitude is about 1.176
88
+ // (greater than 1.0 = total eclipse; the Moon fits entirely inside the umbra)
89
+
90
+ swe.close();
91
+ ```
92
+
93
+ ### Local visibility: is the eclipse visible from my city?
94
+
95
+ Use `lunarEclipseLocal` to find when the Moon rises and sets during the eclipse at your location, which tells you how much of the eclipse you can observe.
96
+
97
+ ```typescript
98
+ import { SwissEph } from '../index';
99
+
100
+ const swe = new SwissEph();
101
+
102
+ const jd = SwissEph.julianDay(2025, 1, 1, 0);
103
+
104
+ // Check visibility from different cities
105
+ const cities = [
106
+ { name: 'New York', geo: { longitude: -74.006, latitude: 40.713 } },
107
+ { name: 'London', geo: { longitude: -0.128, latitude: 51.507 } },
108
+ { name: 'Tokyo', geo: { longitude: 139.692, latitude: 35.690 } },
109
+ { name: 'Sydney', geo: { longitude: 151.209, latitude: -33.868 } },
110
+ ];
111
+
112
+ const fmt = (jd: number) => {
113
+ if (jd === 0) return '(below horizon)';
114
+ const d = SwissEph.fromJulianDay(jd);
115
+ const h = Math.floor(d.hour);
116
+ const m = Math.floor((d.hour - h) * 60);
117
+ return `${h}:${String(m).padStart(2,'0')} UT`;
118
+ };
119
+
120
+ for (const city of cities) {
121
+ const local = swe.lunarEclipseLocal(jd, city.geo);
122
+ const date = SwissEph.fromJulianDay(local.maximum);
123
+ console.log(`\n${city.name} (${date.year}-${date.month}-${date.day}):`);
124
+ console.log(` Maximum: ${fmt(local.maximum)}`);
125
+ console.log(` Moon rise: ${fmt(local.moonRise)}`);
126
+ console.log(` Moon set: ${fmt(local.moonSet)}`);
127
+ console.log(` Umbra magnitude: ${local.attributes.umbraMagnitude.toFixed(3)}`);
128
+
129
+ // Check if the full eclipse is visible
130
+ if (local.moonRise === 0 || local.moonRise < local.penumbralBegin) {
131
+ console.log(` Full eclipse visible (Moon is up the entire time)`);
132
+ } else {
133
+ console.log(` Partial visibility (Moon rises/sets during eclipse)`);
134
+ }
135
+ }
136
+
137
+ swe.close();
138
+ ```
139
+
140
+ ### Filtering by eclipse type
141
+
142
+ ```typescript
143
+ import { SwissEph } from '../index';
144
+ import { SE_ECL_TOTAL, SE_ECL_PARTIAL, SE_ECL_PENUMBRAL } from '../../constants';
145
+
146
+ const swe = new SwissEph();
147
+
148
+ // Find the next penumbral lunar eclipse
149
+ let jd = SwissEph.julianDay(2024, 1, 1, 0);
150
+ const penumbral = swe.lunarEclipseGlobal(jd, SE_ECL_PENUMBRAL);
151
+ const d1 = SwissEph.fromJulianDay(penumbral.maximum);
152
+ console.log(`Next penumbral: ${d1.year}-${d1.month}-${d1.day}`);
153
+
154
+ // Find the next partial lunar eclipse
155
+ const partial = swe.lunarEclipseGlobal(jd, SE_ECL_PARTIAL);
156
+ const d2 = SwissEph.fromJulianDay(partial.maximum);
157
+ console.log(`Next partial: ${d2.year}-${d2.month}-${d2.day}`);
158
+
159
+ // Find the next total lunar eclipse
160
+ const total = swe.lunarEclipseGlobal(jd, SE_ECL_TOTAL);
161
+ const d3 = SwissEph.fromJulianDay(total.maximum);
162
+ console.log(`Next total: ${d3.year}-${d3.month}-${d3.day}`);
163
+
164
+ swe.close();
165
+ ```
166
+
167
+ ### Listing all lunar eclipses over a period
168
+
169
+ ```typescript
170
+ import { SwissEph } from '../index';
171
+ import { SE_ECL_TOTAL, SE_ECL_PARTIAL, SE_ECL_PENUMBRAL } from '../../constants';
172
+
173
+ const swe = new SwissEph();
174
+
175
+ let jd = SwissEph.julianDay(2024, 1, 1, 0);
176
+ const endJd = SwissEph.julianDay(2028, 1, 1, 0);
177
+
178
+ const typeName = (flags: number): string => {
179
+ if (flags & SE_ECL_TOTAL) return 'Total';
180
+ if (flags & SE_ECL_PARTIAL) return 'Partial';
181
+ if (flags & SE_ECL_PENUMBRAL) return 'Penumbral';
182
+ return 'Unknown';
183
+ };
184
+
185
+ console.log('Lunar eclipses 2024-2027:');
186
+ while (jd < endJd) {
187
+ const ecl = swe.lunarEclipseGlobal(jd);
188
+ if (ecl.maximum > endJd) break;
189
+
190
+ const date = SwissEph.fromJulianDay(ecl.maximum);
191
+ const how = swe.lunarEclipseHow(ecl.maximum);
192
+
193
+ console.log(
194
+ ` ${date.year}-${String(date.month).padStart(2,'0')}-${String(date.day).padStart(2,'0')}` +
195
+ ` ${typeName(ecl.type).padEnd(10)}` +
196
+ ` umbra mag: ${how.umbraMagnitude.toFixed(3)}`
197
+ );
198
+
199
+ jd = ecl.maximum + 1;
200
+ }
201
+
202
+ swe.close();
203
+ ```
204
+
205
+ ### Searching backward in time
206
+
207
+ ```typescript
208
+ import { SwissEph } from '../index';
209
+
210
+ const swe = new SwissEph();
211
+
212
+ // Find the most recent lunar eclipse before 2025-01-01
213
+ const jd = SwissEph.julianDay(2025, 1, 1, 0);
214
+ const eclipse = swe.lunarEclipseGlobal(jd, 0, true); // backward = true
215
+
216
+ const date = SwissEph.fromJulianDay(eclipse.maximum);
217
+ console.log(`Most recent lunar eclipse: ${date.year}-${date.month}-${date.day}`);
218
+
219
+ swe.close();
220
+ ```
221
+
222
+ ---
223
+
224
+ ## Deep Explanation
225
+
226
+ ### Eclipse types
227
+
228
+ | Type | Constant | Description |
229
+ |------|----------|-------------|
230
+ | **Total** | `SE_ECL_TOTAL` (4) | The Moon passes completely into Earth's umbral shadow. The entire lunar disc turns dark/red. Umbral magnitude > 1.0. |
231
+ | **Partial** | `SE_ECL_PARTIAL` (16) | Only part of the Moon enters the umbra. Part of the Moon remains bright. Umbral magnitude between 0.0 and 1.0. |
232
+ | **Penumbral** | `SE_ECL_PENUMBRAL` (64) | The Moon passes only through Earth's penumbral shadow. The dimming is subtle and often hard to notice visually. Umbral magnitude < 0.0. |
233
+
234
+ ### Umbra and penumbra
235
+
236
+ Earth casts two nested shadows into space:
237
+
238
+ - **Umbra**: The dark inner cone of shadow where all direct sunlight is blocked. If you were standing on the Moon inside the umbra, you would see Earth completely blocking the Sun.
239
+ - **Penumbra**: The lighter outer shadow where only part of the Sun is blocked. From inside the penumbra, you would see Earth covering part of the Sun but not all of it.
240
+
241
+ The umbra is about 2.5 times the Moon's diameter at the Moon's distance, which is why total lunar eclipses are possible and can last over an hour.
242
+
243
+ ### Understanding magnitude
244
+
245
+ For lunar eclipses, **magnitude** has a specific meaning:
246
+
247
+ - **Umbral magnitude** (`umbraMagnitude`): How deeply the Moon penetrates into the umbra, measured as a fraction of the Moon's diameter. A value of 0.0 means the Moon just touches the umbral edge. A value of 1.0 means the Moon's edge just reaches the center of the umbra (the entire Moon is inside). Values greater than 1.0 mean the Moon is well inside the umbra (deeper totality). For the 2025 Mar 14 eclipse, the umbral magnitude is about 1.178.
248
+
249
+ - **Penumbral magnitude** (`penumbraMagnitude`): Same concept but for the penumbra. Always larger than the umbral magnitude.
250
+
251
+ A negative umbral magnitude means the Moon does not enter the umbra at all (purely penumbral eclipse).
252
+
253
+ ### The Danjon scale
254
+
255
+ Astronomers use the **Danjon scale** (L value) to classify the visual appearance of a total lunar eclipse:
256
+
257
+ | L | Description |
258
+ |---|-------------|
259
+ | 0 | Very dark eclipse; Moon almost invisible at mid-totality |
260
+ | 1 | Dark eclipse; gray or brownish color; details hard to see |
261
+ | 2 | Deep red or rust-colored; very dark central shadow with brighter edge |
262
+ | 3 | Brick-red; umbral shadow usually has a bright or yellow rim |
263
+ | 4 | Very bright copper-red or orange; umbral shadow has a bluish, very bright rim |
264
+
265
+ The Danjon value cannot be predicted by ephemeris calculations alone -- it depends on atmospheric conditions, particularly volcanic aerosols. Major volcanic eruptions (like Pinatubo in 1991) can produce very dark eclipses (L=0) for several years afterward. The Swiss Ephemeris predicts *when* and *where* eclipses occur, but not their color.
266
+
267
+ ### Why the Moon turns red
268
+
269
+ During totality, the only light reaching the Moon has been refracted (bent) through Earth's atmosphere. Earth's atmosphere acts like a lens that bends sunlight around the planet's edge and filters it:
270
+
271
+ 1. Short-wavelength light (blue, violet) is scattered away by the atmosphere (the same Rayleigh scattering that makes our sky blue).
272
+ 2. Long-wavelength light (red, orange) passes through more easily and is bent toward the Moon.
273
+
274
+ The result is that the Moon is illuminated by the combined light of all the sunrises and sunsets happening around Earth's edge at that moment. If you were standing on the Moon during a total lunar eclipse, you would see Earth surrounded by a bright red ring.
275
+
276
+ ### Eclipse timeline (the `lunarEclipseGlobal` result)
277
+
278
+ | Field | Description |
279
+ |-------|-------------|
280
+ | `penumbralBegin` | Moon enters the penumbra. Very subtle dimming begins. |
281
+ | `partialBegin` | Moon enters the umbra. A dark "bite" appears on the Moon's edge. |
282
+ | `totalBegin` | Moon is completely inside the umbra. Totality begins; Moon turns red. |
283
+ | `maximum` | Mid-eclipse. The Moon is deepest inside the shadow. |
284
+ | `totalEnd` | Moon begins to exit the umbra. Totality ends. |
285
+ | `partialEnd` | Moon completely exits the umbra. Only penumbral shading remains. |
286
+ | `penumbralEnd` | Moon exits the penumbra entirely. Eclipse is over. |
287
+
288
+ For a partial eclipse, `totalBegin` and `totalEnd` are 0 (totality never occurs).
289
+ For a penumbral eclipse, `partialBegin`, `partialEnd`, `totalBegin`, and `totalEnd` are all 0.
290
+
291
+ ### The `lunarEclipseHow` result
292
+
293
+ | Field | Description |
294
+ |-------|-------------|
295
+ | `type` | Bitmask of eclipse type flags |
296
+ | `umbraMagnitude` | Fraction of Moon's diameter inside the umbra (>1.0 = total) |
297
+ | `penumbraMagnitude` | Fraction of Moon's diameter inside the penumbra |
298
+ | `moonDiameter` | Apparent diameter of the Moon (arc minutes) |
299
+ | `umbraDiameter` | Apparent diameter of Earth's umbral shadow at the Moon's distance (arc minutes) |
300
+ | `penumbraDiameter` | Apparent diameter of Earth's penumbral shadow (arc minutes) |
301
+ | `sunDistanceFromNode` | Sun's angular distance from the nearest lunar node (degrees) |
302
+
303
+ ### Differences from solar eclipses
304
+
305
+ | Aspect | Solar Eclipse | Lunar Eclipse |
306
+ |--------|--------------|---------------|
307
+ | **When** | New Moon | Full Moon |
308
+ | **What happens** | Moon blocks Sun | Earth's shadow covers Moon |
309
+ | **Visibility** | Narrow path (100-200 km wide) | Entire night hemisphere |
310
+ | **Duration of totality** | Up to ~7 minutes | Up to ~1 hour 47 minutes |
311
+ | **Frequency** | 2-5 per year globally | 0-3 per year |
312
+ | **Danger** | Never look at Sun without filter | Safe to watch with naked eye |
313
+ | **Timing** | Different for every location | Same UT time for all observers |
314
+
315
+ ### Local visibility considerations
316
+
317
+ Unlike solar eclipses, the timing of a lunar eclipse (in UT) is the same for everyone -- the Moon enters and exits Earth's shadow at the same absolute moment. The question is whether the Moon is above the horizon at your location during that time.
318
+
319
+ The `lunarEclipseLocal` result includes `moonRise` and `moonSet` times, which tell you when the Moon is above the horizon at your location. If the Moon rises after penumbral begin or sets before penumbral end, you will miss part of the eclipse.
320
+
321
+ ### Tips
322
+
323
+ - When searching for eclipses, advance `jd` by at least 1 day past the previous result to find the next one.
324
+ - A `lunarEclipseHow` call without a `geo` parameter gives the eclipse attributes as seen from the geocenter (Earth's center). With a `geo` parameter, it accounts for the observer's location, which can slightly affect the apparent magnitude since the Moon's position in the umbra shifts slightly with parallax.
325
+ - Penumbral eclipses are very subtle visually. Most observers cannot notice a penumbral eclipse unless the penumbral magnitude exceeds about 0.7.
326
+ - The `sunDistanceFromNode` value indicates how close the geometry is to producing a central eclipse. Smaller values mean deeper eclipses.