solarmoonpy 1.0.0__tar.gz → 1.0.2__tar.gz
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.
Potentially problematic release.
This version of solarmoonpy might be problematic. Click here for more details.
- {solarmoonpy-1.0.0 → solarmoonpy-1.0.2}/PKG-INFO +1 -1
- {solarmoonpy-1.0.0 → solarmoonpy-1.0.2}/pyproject.toml +1 -1
- {solarmoonpy-1.0.0 → solarmoonpy-1.0.2}/solarmoonpy/location.py +0 -1
- {solarmoonpy-1.0.0 → solarmoonpy-1.0.2}/solarmoonpy/moon.py +170 -201
- {solarmoonpy-1.0.0 → solarmoonpy-1.0.2}/solarmoonpy/sun.py +25 -15
- solarmoonpy-1.0.2/solarmoonpy/version.py +2 -0
- {solarmoonpy-1.0.0 → solarmoonpy-1.0.2}/solarmoonpy.egg-info/PKG-INFO +1 -1
- solarmoonpy-1.0.0/solarmoonpy/version.py +0 -2
- {solarmoonpy-1.0.0 → solarmoonpy-1.0.2}/LICENSE +0 -0
- {solarmoonpy-1.0.0 → solarmoonpy-1.0.2}/README.md +0 -0
- {solarmoonpy-1.0.0 → solarmoonpy-1.0.2}/setup.cfg +0 -0
- {solarmoonpy-1.0.0 → solarmoonpy-1.0.2}/solarmoonpy/__init__.py +0 -0
- {solarmoonpy-1.0.0 → solarmoonpy-1.0.2}/solarmoonpy.egg-info/SOURCES.txt +0 -0
- {solarmoonpy-1.0.0 → solarmoonpy-1.0.2}/solarmoonpy.egg-info/dependency_links.txt +0 -0
- {solarmoonpy-1.0.0 → solarmoonpy-1.0.2}/solarmoonpy.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "solarmoonpy"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.2"
|
|
8
8
|
description = "Precise solar and lunar calculations for astronomical applications"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "figorr", email = "jdcuartero@yahoo.es" }]
|
|
@@ -4,86 +4,36 @@ from typing import Optional, Tuple
|
|
|
4
4
|
from .sun import julianday, julianday_to_juliancentury, sun_apparent_long, sun_distance
|
|
5
5
|
|
|
6
6
|
_SYNODIC_MONTH = 29.530588853
|
|
7
|
-
AU = 149597870.7
|
|
7
|
+
AU = 149597870.7
|
|
8
|
+
JD_REF_PHASE = 2423436.0 # para moon_phase
|
|
9
|
+
JD_REF_LUNATION = 2423407.51818 # para lunation_number
|
|
8
10
|
|
|
9
11
|
def moon_phase(date_utc: date) -> float:
|
|
10
12
|
"""
|
|
11
|
-
Devuelve
|
|
12
|
-
|
|
13
|
-
Args:
|
|
14
|
-
date_utc (date): Fecha en UTC.
|
|
15
|
-
Returns:
|
|
16
|
-
float: Días desde la última luna nueva (0 a ~29.53).
|
|
13
|
+
Devuelve los días transcurridos desde la última Luna Nueva.
|
|
14
|
+
Basado en el algoritmo de Chapront-Touzé (usado por timeanddate/NASA).
|
|
17
15
|
"""
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
approx_phase = (lunations % 1) * _SYNODIC_MONTH
|
|
27
|
-
|
|
28
|
-
# Cálculo preciso usando elongación eclíptica
|
|
29
|
-
jd = julianday(dt.date()) + 0.5 # Mediodía UTC
|
|
30
|
-
jc = julianday_to_juliancentury(jd)
|
|
31
|
-
lambda_m, _, _, _ = _moon_ecliptic_position(jd)
|
|
32
|
-
lambda_s = sun_apparent_long(jc)
|
|
33
|
-
elong = _normalize_angle(lambda_m - lambda_s)
|
|
34
|
-
|
|
35
|
-
# Convertir elongación a fase (0°=nueva, 180°=llena, 360°=nueva)
|
|
36
|
-
precise_phase = (elong / 360.0) * _SYNODIC_MONTH
|
|
37
|
-
if precise_phase > _SYNODIC_MONTH:
|
|
38
|
-
precise_phase -= _SYNODIC_MONTH
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
|
+
|
|
18
|
+
# Convertimos a datetime UTC (mediodía del día solicitado)
|
|
19
|
+
dt = datetime(date_utc.year, date_utc.month, date_utc.day, 12, 0, tzinfo=timezone.utc)
|
|
20
|
+
jd = julianday(dt)
|
|
21
|
+
|
|
22
|
+
# Fracción del ciclo (0.0 = luna nueva, 0.5 = llena, 1.0 = nueva)
|
|
23
|
+
phase = ((jd - JD_REF_PHASE) / _SYNODIC_MONTH) % 1.0
|
|
39
24
|
|
|
40
|
-
#
|
|
41
|
-
return
|
|
25
|
+
# Días desde la última luna nueva
|
|
26
|
+
return phase * _SYNODIC_MONTH
|
|
42
27
|
|
|
43
28
|
def moon_day(date_utc: date, tol_hours: float = 0.01) -> int:
|
|
44
29
|
"""
|
|
45
30
|
Calcula el día lunar 0–29 usando la última luna nueva exacta calculada por elongación.
|
|
46
|
-
Usa el fin del día UTC para alinear con convenciones de timeanddate/NASA
|
|
47
|
-
tol_hours: precisión en horas.
|
|
31
|
+
Usa el fin del día UTC para alinear con convenciones de timeanddate/NASA.
|
|
32
|
+
tol_hours: precisión en horas (mantenido en 0.01 para consistencia).
|
|
48
33
|
"""
|
|
49
34
|
dt = datetime(date_utc.year, date_utc.month, date_utc.day, 23, 59, tzinfo=timezone.utc)
|
|
50
|
-
|
|
51
|
-
def elongation(t: datetime) -> float:
|
|
52
|
-
jd = julianday(t.date()) + (t.hour + t.minute/60 + t.second/3600)/24
|
|
53
|
-
jc = julianday_to_juliancentury(jd)
|
|
54
|
-
lambda_m, _, _, _ = _moon_ecliptic_position(jd)
|
|
55
|
-
lambda_s = sun_apparent_long(jc)
|
|
56
|
-
diff = (lambda_m - lambda_s) % 360.0
|
|
57
|
-
if diff > 180:
|
|
58
|
-
diff -= 360
|
|
59
|
-
return diff
|
|
60
|
-
|
|
61
|
-
# Buscar la última luna nueva antes de la fecha
|
|
62
|
-
step = timedelta(hours=6)
|
|
63
|
-
t_back = dt
|
|
64
|
-
prev_el = elongation(t_back)
|
|
65
|
-
for _ in range(int(30*24/6)):
|
|
66
|
-
t_back -= step
|
|
67
|
-
el = elongation(t_back)
|
|
68
|
-
if prev_el > 0 >= el: # cruce 0 hacia abajo indica luna nueva
|
|
69
|
-
break
|
|
70
|
-
prev_el = el
|
|
71
|
-
|
|
72
|
-
# Refinar el momento exacto con bisección
|
|
73
|
-
low = t_back
|
|
74
|
-
high = t_back + step
|
|
75
|
-
while (high - low).total_seconds() / 3600 > tol_hours:
|
|
76
|
-
mid = low + (high - low)/2
|
|
77
|
-
if elongation(mid) > 0:
|
|
78
|
-
high = mid
|
|
79
|
-
else:
|
|
80
|
-
low = mid
|
|
81
|
-
last_new_moon = low + (high - low)/2
|
|
82
|
-
|
|
83
|
-
# Edad lunar en días
|
|
35
|
+
last_new_moon = find_last_phase_exact(date_utc, target=0.0)
|
|
84
36
|
age_days = (dt - last_new_moon).total_seconds() / 86400.0
|
|
85
|
-
|
|
86
|
-
# Día lunar: 0–29, redondeado correctamente
|
|
87
37
|
if age_days < 0:
|
|
88
38
|
day = 0
|
|
89
39
|
else:
|
|
@@ -101,8 +51,8 @@ def illuminated_percentage(date_utc: date) -> float:
|
|
|
101
51
|
Returns:
|
|
102
52
|
float: Porcentaje de superficie lunar iluminada (0–100).
|
|
103
53
|
"""
|
|
104
|
-
dt = datetime(date_utc.year, date_utc.month, date_utc.day, 12, 0, 0, tzinfo=timezone.utc)
|
|
105
|
-
jd = julianday(dt
|
|
54
|
+
dt = datetime(date_utc.year, date_utc.month, date_utc.day, 12, 0, 0, tzinfo=timezone.utc)
|
|
55
|
+
jd = julianday(dt)
|
|
106
56
|
jc = julianday_to_juliancentury(jd)
|
|
107
57
|
lambda_m, beta_m, R, delta_psi = _moon_ecliptic_position(jd)
|
|
108
58
|
lambda_s = sun_apparent_long(jc)
|
|
@@ -124,130 +74,60 @@ def _normalize_angle(angle: float) -> float:
|
|
|
124
74
|
|
|
125
75
|
# Tabla completa 47.A para sigma_l y sigma_r (de PyMeeus/Meeus)
|
|
126
76
|
SIGMA_LR_TABLE = [
|
|
127
|
-
[0, 0, 1, 0, 6288774.0, -20905355.0],
|
|
128
|
-
[2, 0, -
|
|
129
|
-
[
|
|
130
|
-
[0, 0, 2, 0,
|
|
131
|
-
[0, 1, 0, 0, -
|
|
132
|
-
[0,
|
|
133
|
-
[
|
|
134
|
-
[
|
|
135
|
-
[
|
|
136
|
-
[
|
|
137
|
-
[0, 1, -1, 0, -
|
|
138
|
-
[1, 0, 0, 0, -
|
|
139
|
-
[0,
|
|
140
|
-
[2, 0, 0, -2,
|
|
141
|
-
[
|
|
142
|
-
[
|
|
143
|
-
[
|
|
144
|
-
[0, 0,
|
|
145
|
-
[
|
|
146
|
-
[2,
|
|
147
|
-
[2, 1, 0, 0, -
|
|
148
|
-
[1, 0, -1, 0, -
|
|
149
|
-
[
|
|
150
|
-
[
|
|
151
|
-
[
|
|
152
|
-
[
|
|
153
|
-
[
|
|
154
|
-
[0, 1,
|
|
155
|
-
[2, 0, -1,
|
|
156
|
-
[2, -1, -2, 0
|
|
157
|
-
[1, 0, 1, 0, -2348.0, 6322.0],
|
|
158
|
-
[2, -2, 0, 0, 2236.0, -9884.0],
|
|
159
|
-
[0, 1, 2, 0, -2120.0, 5751.0],
|
|
160
|
-
[0, 2, 0, 0, -2069.0, 0.0],
|
|
161
|
-
[2, -2, -1, 0, 2048.0, -4950.0],
|
|
162
|
-
[2, 0, 1, -2, -1773.0, 4130.0],
|
|
163
|
-
[2, 0, 0, 2, -1595.0, 0.0],
|
|
164
|
-
[4, -1, -1, 0, 1215.0, -3958.0],
|
|
165
|
-
[0, 0, 2, 2, -1110.0, 0.0],
|
|
166
|
-
[3, 0, -1, 0, -892.0, 3258.0],
|
|
167
|
-
[2, 1, 1, 0, -810.0, 2616.0],
|
|
168
|
-
[4, -1, -2, 0, 759.0, -1897.0],
|
|
169
|
-
[0, 2, -1, 0, -713.0, -2117.0],
|
|
170
|
-
[2, 2, -1, 0, -700.0, 2354.0],
|
|
171
|
-
[2, 1, -2, 0, 691.0, 0.0],
|
|
172
|
-
[2, -1, 0, -2, 596.0, 0.0],
|
|
173
|
-
[4, 0, 1, 0, 549.0, -1423.0],
|
|
174
|
-
[0, 0, 4, 0, 537.0, -1117.0],
|
|
175
|
-
[4, -1, 0, 0, 520.0, -1571.0],
|
|
176
|
-
[1, 0, -2, 0, -487.0, -1739.0],
|
|
177
|
-
[2, 1, 0, -2, -399.0, 0.0],
|
|
178
|
-
[0, 0, 2, -2, -381.0, -4421.0],
|
|
179
|
-
[1, 1, 1, 0, 351.0, 0.0],
|
|
180
|
-
[3, 0, -2, 0, -340.0, 0.0],
|
|
181
|
-
[4, 0, -3, 0, 330.0, 0.0],
|
|
182
|
-
[2, -1, 2, 0, 327.0, 0.0],
|
|
183
|
-
[0, 2, 1, 0, -323.0, 1165.0],
|
|
184
|
-
[1, 1, -1, 0, 299.0, 0.0],
|
|
185
|
-
[2, 0, 3, 0, 294.0, 0.0],
|
|
186
|
-
[2, 0, -1, -2, 0.0, 8752.0]
|
|
77
|
+
[0, 0, 1, 0, 6288774.0, -20905355.0], [2, 0, -1, 0, 1274027.0, -3699111.0],
|
|
78
|
+
[2, 0, 0, 0, 658314.0, -2955968.0], [0, 0, 2, 0, 213618.0, -569925.0],
|
|
79
|
+
[0, 1, 0, 0, -185116.0, 48888.0], [0, 0, 0, 2, -114332.0, -3149.0],
|
|
80
|
+
[2, 0, -2, 0, 58793.0, 246158.0], [2, -1, -1, 0, 57066.0, -152138.0],
|
|
81
|
+
[2, 0, 1, 0, 53322.0, -170733.0], [2, -1, 0, 0, 45758.0, -204586.0],
|
|
82
|
+
[0, 1, -1, 0, -40923.0, -129620.0], [1, 0, 0, 0, -34720.0, 108743.0],
|
|
83
|
+
[0, 1, 1, 0, -30383.0, 104755.0], [2, 0, 0, -2, 15327.0, 10321.0],
|
|
84
|
+
[0, 0, 1, 2, -12528.0, 0.0], [0, 0, 1, -2, 10980.0, 79661.0],
|
|
85
|
+
[4, 0, -1, 0, 10675.0, -34782.0], [0, 0, 3, 0, 10034.0, -23210.0],
|
|
86
|
+
[4, 0, -2, 0, 8548.0, -21636.0], [2, 1, -1, 0, -7888.0, 24208.0],
|
|
87
|
+
[2, 1, 0, 0, -6766.0, 30824.0], [1, 0, -1, 0, -5163.0, -8379.0],
|
|
88
|
+
[1, 1, 0, 0, 4987.0, -16675.0], [2, -1, 1, 0, 4036.0, -12831.0],
|
|
89
|
+
[2, 0, 2, 0, 3994.0, -10445.0], [4, 0, 0, 0, 3861.0, -11650.0],
|
|
90
|
+
[2, 0, -3, 0, 3665.0, 14403.0], [0, 1, -2, 0, -2689.0, -7003.0],
|
|
91
|
+
[2, 0, -1, 2, -2602.0, 0.0], [2, -1, -2, 0, 2390.0, 10056.0],
|
|
92
|
+
[1, 0, 1, 0, -2348.0, 6322.0], [2, -2, 0, 0, 2236.0, -9884.0],
|
|
93
|
+
[0, 1, 2, 0, -2120.0, 5751.0], [0, 2, 0, 0, -2069.0, 0.0],
|
|
94
|
+
[2, -2, -1, 0, 2048.0, -4950.0], [2, 0, 1, -2, -1773.0, 4130.0],
|
|
95
|
+
[2, 0, 0, 2, -1595.0, 0.0], [4, -1, -1, 0, 1215.0, -3958.0],
|
|
96
|
+
[0, 0, 2, 2, -1110.0, 0.0], [3, 0, -1, 0, -892.0, 3258.0],
|
|
97
|
+
[2, 1, 1, 0, -810.0, 2616.0], [4, -1, -2, 0, 759.0, -1897.0],
|
|
98
|
+
[0, 2, -1, 0, -713.0, -2117.0], [2, 2, -1, 0, -700.0, 2354.0],
|
|
99
|
+
[2, 1, -2, 0, 691.0, 0.0], [2, -1, 0, -2, 596.0, 0.0],
|
|
100
|
+
[4, 0, 1, 0, 549.0, -1423.0], [0, 0, 4, 0, 537.0, -1117.0],
|
|
101
|
+
[4, -1, 0, 0, 520.0, -1571.0], [1, 0, -2, 0, -487.0, -1739.0],
|
|
102
|
+
[2, 1, 0, -2, -399.0, 0.0], [0, 0, 2, -2, -381.0, -4421.0],
|
|
103
|
+
[1, 1, 1, 0, 351.0, 0.0], [3, 0, -2, 0, -340.0, 0.0],
|
|
104
|
+
[4, 0, -3, 0, 330.0, 0.0], [2, -1, 2, 0, 327.0, 0.0],
|
|
105
|
+
[0, 2, 1, 0, -323.0, 1165.0], [1, 1, -1, 0, 299.0, 0.0],
|
|
106
|
+
[2, 0, 3, 0, 294.0, 0.0], [2, 0, -1, -2, 0.0, 8752.0]
|
|
187
107
|
]
|
|
188
108
|
|
|
189
109
|
# Tabla completa 47.B para sigma_b (de PyMeeus/Meeus)
|
|
190
110
|
SIGMA_B_TABLE = [
|
|
191
|
-
[0, 0, 0, 1, 5128122.0],
|
|
192
|
-
[0, 0, 1, 1,
|
|
193
|
-
[0, 0, 1, -1,
|
|
194
|
-
[2, 0, 0, -1,
|
|
195
|
-
[2, 0, -1, 1,
|
|
196
|
-
[2, 0, -1, -1,
|
|
197
|
-
[
|
|
198
|
-
[0, 0,
|
|
199
|
-
[
|
|
200
|
-
[0, 0,
|
|
201
|
-
[
|
|
202
|
-
[2, 0, -2, -1,
|
|
203
|
-
[2, 0, 1, 1,
|
|
204
|
-
[2, 1, 0,
|
|
205
|
-
[2, -1, -1, 1,
|
|
206
|
-
[2, -1, 0, 1,
|
|
207
|
-
[2, -1, -1, -1,
|
|
208
|
-
[
|
|
209
|
-
[4, 0, -1, -1,
|
|
210
|
-
[0, 1, 0, 1, -
|
|
211
|
-
[0, 0, 0, 3, -1749.0],
|
|
212
|
-
[0, 1, -1, 1, -1565.0],
|
|
213
|
-
[1, 0, 0, 1, -1491.0],
|
|
214
|
-
[0, 1, 1, 1, -1475.0],
|
|
215
|
-
[0, 1, 1, -1, -1410.0],
|
|
216
|
-
[0, 1, 0, -1, -1344.0],
|
|
217
|
-
[1, 0, 0, -1, -1335.0],
|
|
218
|
-
[0, 0, 3, 1, 1107.0],
|
|
219
|
-
[4, 0, 0, -1, 1021.0],
|
|
220
|
-
[4, 0, -1, 1, 833.0],
|
|
221
|
-
[0, 0, 1, -3, 777.0],
|
|
222
|
-
[4, 0, -2, 1, 671.0],
|
|
223
|
-
[2, 0, 0, -3, 607.0],
|
|
224
|
-
[2, 0, 2, -1, 596.0],
|
|
225
|
-
[2, -1, 1, -1, 491.0],
|
|
226
|
-
[2, 0, -2, 1, -451.0],
|
|
227
|
-
[0, 0, 3, -1, 439.0],
|
|
228
|
-
[2, 0, 2, 1, 422.0],
|
|
229
|
-
[2, 0, -3, -1, 421.0],
|
|
230
|
-
[2, 1, -1, 1, -366.0],
|
|
231
|
-
[2, 1, 0, 1, -351.0],
|
|
232
|
-
[4, 0, 0, 1, 331.0],
|
|
233
|
-
[2, -1, 1, 1, 315.0],
|
|
234
|
-
[2, -2, 0, -1, 302.0],
|
|
235
|
-
[0, 0, 1, 3, -283.0],
|
|
236
|
-
[2, 1, 1, -1, -229.0],
|
|
237
|
-
[1, 1, 0, -1, 223.0],
|
|
238
|
-
[1, 1, 0, 1, 223.0],
|
|
239
|
-
[0, 1, -2, -1, -220.0],
|
|
240
|
-
[2, 1, -1, -1, -220.0],
|
|
241
|
-
[1, 0, 1, 1, -185.0],
|
|
242
|
-
[2, -1, -2, -1, 181.0],
|
|
243
|
-
[0, 1, 2, 1, -177.0],
|
|
244
|
-
[4, 0, -2, -1, 176.0],
|
|
245
|
-
[4, -1, -1, -1, 166.0],
|
|
246
|
-
[1, 0, 1, -1, -164.0],
|
|
247
|
-
[4, 0, 1, -1, 132.0],
|
|
248
|
-
[1, 0, -1, -1, -119.0],
|
|
249
|
-
[4, -1, 0, -1, 115.0],
|
|
250
|
-
[2, -2, 0, 1, 107.0]
|
|
111
|
+
[0, 0, 0, 1, 5128122.0], [0, 0, 1, 1, 280602.0], [0, 0, 1, -1, 277693.0],
|
|
112
|
+
[2, 0, 0, -1, 173237.0], [2, 0, -1, 1, 55413.0], [2, 0, -1, -1, 46271.0],
|
|
113
|
+
[2, 0, 0, 1, 32573.0], [0, 0, 2, 1, 17198.0], [2, 0, 1, -1, 9266.0],
|
|
114
|
+
[0, 0, 2, -1, 8822.0], [2, -1, 0, -1, 8216.0], [2, 0, -2, -1, 4324.0],
|
|
115
|
+
[2, 0, 1, 1, 4200.0], [2, 1, 0, -1, -3359.0], [2, -1, -1, 1, 2463.0],
|
|
116
|
+
[2, -1, 0, 1, 2211.0], [2, -1, -1, -1, 2065.0], [0, 1, -1, -1, -1870.0],
|
|
117
|
+
[4, 0, -1, -1, 1828.0], [0, 1, 0, 1, -1794.0], [0, 0, 0, 3, -1749.0],
|
|
118
|
+
[0, 1, -1, 1, -1565.0], [1, 0, 0, 1, -1491.0], [0, 1, 1, 1, -1475.0],
|
|
119
|
+
[0, 1, 1, -1, -1410.0], [0, 1, 0, -1, -1344.0], [1, 0, 0, -1, -1335.0],
|
|
120
|
+
[0, 0, 3, 1, 1107.0], [4, 0, 0, -1, 1021.0], [4, 0, -1, 1, 833.0],
|
|
121
|
+
[0, 0, 1, -3, 777.0], [4, 0, -2, 1, 671.0], [2, 0, 0, -3, 607.0],
|
|
122
|
+
[2, 0, 2, -1, 596.0], [2, -1, 1, -1, 491.0], [2, 0, -2, 1, -451.0],
|
|
123
|
+
[0, 0, 3, -1, 439.0], [2, 0, 2, 1, 422.0], [2, 0, -3, -1, 421.0],
|
|
124
|
+
[2, 1, -1, 1, -366.0], [2, 1, 0, 1, -351.0], [4, 0, 0, 1, 331.0],
|
|
125
|
+
[2, -1, 1, 1, 315.0], [2, -2, 0, -1, 302.0], [0, 0, 1, 3, -283.0],
|
|
126
|
+
[2, 1, 1, -1, -229.0], [1, 1, 0, -1, 223.0], [1, 1, 0, 1, 223.0],
|
|
127
|
+
[0, 1, -2, -1, -220.0], [2, 1, -1, -1, -220.0], [1, 0, 1, 1, -185.0],
|
|
128
|
+
[2, -1, -2, -1, 181.0], [0, 1, 2, 1, -177.0], [4, 0, -2, -1, 176.0],
|
|
129
|
+
[4, -1, -1, -1, 166.0], [1, 0, 1, -1, -164.0], [4, 0, 1, -1, 132.0],
|
|
130
|
+
[1, 0, -1, -1, -119.0], [4, -1, 0, -1, 115.0], [2, -2, 0, 1, 107.0]
|
|
251
131
|
]
|
|
252
132
|
|
|
253
133
|
def _periodic_terms(D, M, Mp, F, T):
|
|
@@ -280,7 +160,6 @@ def _periodic_terms(D, M, Mp, F, T):
|
|
|
280
160
|
sigma_l /= 1000000.0 # a grados
|
|
281
161
|
sigma_r *= 0.001 # a km
|
|
282
162
|
sigma_b /= 1000000.0 # a grados
|
|
283
|
-
|
|
284
163
|
return sigma_l, sigma_r, sigma_b
|
|
285
164
|
|
|
286
165
|
def _moon_ecliptic_position(jd: float) -> Tuple[float, float, float, float]:
|
|
@@ -300,7 +179,6 @@ def _moon_ecliptic_position(jd: float) -> Tuple[float, float, float, float]:
|
|
|
300
179
|
R = 385000.529 + sigma_r
|
|
301
180
|
delta_psi = (-17.20 * math.sin(math.radians(Omega)) - 1.32 * math.sin(math.radians(2 * (Lp - F))) + 0.23 * math.sin(math.radians(2 * Lp)) + 0.21 * math.sin(math.radians(2 * Omega))) / 3600.0
|
|
302
181
|
lambda_ += delta_psi
|
|
303
|
-
|
|
304
182
|
return lambda_, beta, R, delta_psi
|
|
305
183
|
|
|
306
184
|
def _calculate_position_and_alt(t: datetime, lat_r: float, lon: float) -> tuple[float, float]:
|
|
@@ -324,7 +202,6 @@ def _calculate_position_and_alt(t: datetime, lat_r: float, lon: float) -> tuple[
|
|
|
324
202
|
if ha < -math.pi: ha += 2 * math.pi
|
|
325
203
|
elif ha > math.pi: ha -= 2 * math.pi
|
|
326
204
|
alt = math.asin(math.sin(lat_r) * math.sin(dec) + math.cos(lat_r) * math.cos(dec) * math.cos(ha))
|
|
327
|
-
|
|
328
205
|
return alt, h0
|
|
329
206
|
|
|
330
207
|
def moon_rise_set(lat: float, lon: float, date_utc: date) -> Tuple[Optional[datetime], Optional[datetime]]:
|
|
@@ -376,7 +253,6 @@ def moon_rise_set(lat: float, lon: float, date_utc: date) -> Tuple[Optional[date
|
|
|
376
253
|
if intervalos_set and set_ is None:
|
|
377
254
|
start_t, end_t, start_alt, end_alt = intervalos_set[0]
|
|
378
255
|
set_ = refine_event(start_t, end_t, start_alt, end_alt, False)
|
|
379
|
-
|
|
380
256
|
return rise, set_
|
|
381
257
|
|
|
382
258
|
def moon_distance(date_utc: date) -> float:
|
|
@@ -387,8 +263,8 @@ def moon_distance(date_utc: date) -> float:
|
|
|
387
263
|
Returns:
|
|
388
264
|
float: Distancia en kilómetros.
|
|
389
265
|
"""
|
|
390
|
-
dt = datetime(date_utc.year, date_utc.month, date_utc.day, 12, 0, 0, tzinfo=timezone.utc)
|
|
391
|
-
jd = julianday(dt
|
|
266
|
+
dt = datetime(date_utc.year, date_utc.month, date_utc.day, 12, 0, 0, tzinfo=timezone.utc)
|
|
267
|
+
jd = julianday(dt)
|
|
392
268
|
_, _, R, _ = _moon_ecliptic_position(jd)
|
|
393
269
|
return R
|
|
394
270
|
|
|
@@ -400,9 +276,102 @@ def moon_angular_diameter(date_utc: date) -> float:
|
|
|
400
276
|
Returns:
|
|
401
277
|
float: Diámetro angular en arcosegundos.
|
|
402
278
|
"""
|
|
403
|
-
dt = datetime(date_utc.year, date_utc.month, date_utc.day, 12, 0, 0, tzinfo=timezone.utc)
|
|
404
|
-
jd = julianday(dt
|
|
279
|
+
dt = datetime(date_utc.year, date_utc.month, date_utc.day, 12, 0, 0, tzinfo=timezone.utc)
|
|
280
|
+
jd = julianday(dt)
|
|
405
281
|
_, _, R, _ = _moon_ecliptic_position(jd)
|
|
406
|
-
par = math.asin(6378.14 / R)
|
|
407
|
-
sd = 0.2725076 * par
|
|
408
|
-
return 2 * math.degrees(sd) * 3600
|
|
282
|
+
par = math.asin(6378.14 / R)
|
|
283
|
+
sd = 0.2725076 * par
|
|
284
|
+
return 2 * math.degrees(sd) * 3600
|
|
285
|
+
|
|
286
|
+
def lunation_number(date_utc: date) -> int:
|
|
287
|
+
"""
|
|
288
|
+
Calcula el número de lunación según el sistema de TimeandDate/NASA.
|
|
289
|
+
"""
|
|
290
|
+
last_new_moon = find_last_phase_exact(date_utc, target=0.0)
|
|
291
|
+
jd_new_moon = julianday(last_new_moon)
|
|
292
|
+
|
|
293
|
+
lun = (jd_new_moon - JD_REF_LUNATION) / _SYNODIC_MONTH
|
|
294
|
+
return round(lun)
|
|
295
|
+
|
|
296
|
+
def find_last_phase_exact(date_utc: date, target: float = 0.0) -> datetime:
|
|
297
|
+
"""
|
|
298
|
+
Devuelve la fase lunar exacta ANTERIOR o coincidente a date_utc.
|
|
299
|
+
target: 0.0 para new_moon, 90.0 para first_quarter, 180.0 para full_moon, 270.0 para last_quarter.
|
|
300
|
+
Método:
|
|
301
|
+
1) Usa moon_phase(date_utc) para estimar la fecha/hora aproximada desde la última Luna Nueva, luego ajusta para target.
|
|
302
|
+
2) Construye un intervalo alrededor de esa estimación y asegura que contiene un cruce
|
|
303
|
+
(elongation - target cambia de signo).
|
|
304
|
+
3) Refina con bisección hasta ~1 segundo de precisión.
|
|
305
|
+
Este método evita saltos de ciclo porque la estimación inicial está basada en la elongación.
|
|
306
|
+
"""
|
|
307
|
+
# punto central (mediodía UTC) y estimación inicial usando moon_phase para new_moon
|
|
308
|
+
dt_mid = datetime(date_utc.year, date_utc.month, date_utc.day, 12, 0, tzinfo=timezone.utc)
|
|
309
|
+
# phase_days es el número de días desde la última Luna Nueva (según moon_phase)
|
|
310
|
+
phase_days = moon_phase(date_utc)
|
|
311
|
+
approx_from_new = (target / 360.0) * _SYNODIC_MONTH # Días aproximados desde new_moon al target
|
|
312
|
+
approx_dt = dt_mid - timedelta(days=phase_days) + timedelta(days=approx_from_new)
|
|
313
|
+
|
|
314
|
+
def elongation_dt(t: datetime) -> float:
|
|
315
|
+
jd = julianday(t)
|
|
316
|
+
jc = julianday_to_juliancentury(jd)
|
|
317
|
+
lambda_m, _, _, _ = _moon_ecliptic_position(jd)
|
|
318
|
+
lambda_s = sun_apparent_long(jc)
|
|
319
|
+
# normalizamos a -180..+180 alrededor de target
|
|
320
|
+
diff = (lambda_m - lambda_s - target + 180.0) % 360.0 - 180.0
|
|
321
|
+
return diff
|
|
322
|
+
|
|
323
|
+
# bracket inicial ±2 días alrededor de la estimación
|
|
324
|
+
low = approx_dt - timedelta(days=2)
|
|
325
|
+
high = approx_dt + timedelta(days=2)
|
|
326
|
+
|
|
327
|
+
# Intentamos asegurar que low..high contiene un cambio de signo.
|
|
328
|
+
# Si no, expandimos gradualmente hasta un máximo razonable (p.ej. ±60 días).
|
|
329
|
+
max_expand_days = 60
|
|
330
|
+
step_expand_days = 2
|
|
331
|
+
el_low = elongation_dt(low)
|
|
332
|
+
el_high = elongation_dt(high)
|
|
333
|
+
|
|
334
|
+
expand_attempts = 0
|
|
335
|
+
while el_low * el_high > 0: # mismo signo -> no hay cruce dentro del intervalo
|
|
336
|
+
expand_attempts += 1
|
|
337
|
+
if (high - low).days >= max_expand_days:
|
|
338
|
+
raise RuntimeError(f"No se encontró fase con target {target} en el rango de búsqueda (±60 días).")
|
|
339
|
+
low -= timedelta(days=step_expand_days)
|
|
340
|
+
high += timedelta(days=step_expand_days)
|
|
341
|
+
el_low = elongation_dt(low)
|
|
342
|
+
el_high = elongation_dt(high)
|
|
343
|
+
|
|
344
|
+
# Si exactamente en un extremo es cero, devuelvo ese instante
|
|
345
|
+
if abs(el_low) < 1e-12:
|
|
346
|
+
return low
|
|
347
|
+
if abs(el_high) < 1e-12:
|
|
348
|
+
return high
|
|
349
|
+
|
|
350
|
+
# Bisección: refinamos hasta ~1 segundo de precisión
|
|
351
|
+
# (puedes relajar a 30-60 s si prefieres).
|
|
352
|
+
while (high - low).total_seconds() > 1:
|
|
353
|
+
mid = low + (high - low) / 2
|
|
354
|
+
el_mid = elongation_dt(mid)
|
|
355
|
+
# si mid es exactamente 0 (raro), lo devolvemos
|
|
356
|
+
if abs(el_mid) < 1e-12:
|
|
357
|
+
return mid
|
|
358
|
+
# si el signo de low y mid difiere, el cruce está en low..mid; si no, en mid..high
|
|
359
|
+
if el_low * el_mid <= 0:
|
|
360
|
+
high = mid
|
|
361
|
+
el_high = el_mid
|
|
362
|
+
else:
|
|
363
|
+
low = mid
|
|
364
|
+
el_low = el_mid
|
|
365
|
+
|
|
366
|
+
return low + (high - low) / 2
|
|
367
|
+
|
|
368
|
+
def moon_elongation(date_utc: date) -> float:
|
|
369
|
+
"""
|
|
370
|
+
Devuelve la elongación lunar (0-360°) para la fecha UTC al mediodía.
|
|
371
|
+
"""
|
|
372
|
+
dt = datetime(date_utc.year, date_utc.month, date_utc.day, 12, 0, 0, tzinfo=timezone.utc)
|
|
373
|
+
jd = julianday(dt)
|
|
374
|
+
jc = julianday_to_juliancentury(jd)
|
|
375
|
+
lambda_m, _, _, _ = _moon_ecliptic_position(jd)
|
|
376
|
+
lambda_s = sun_apparent_long(jc)
|
|
377
|
+
return _normalize_angle(lambda_m - lambda_s)
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
from math import acos, asin, atan2, cos, degrees, radians, sin, tan, sqrt
|
|
3
3
|
|
|
4
|
-
def julianday(
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
def julianday(date_or_dt: datetime.date | datetime.datetime) -> float:
|
|
5
|
+
"""
|
|
6
|
+
Calculate the Julian Day number for a date or datetime (UTC).
|
|
7
|
+
Handles both date (midnight UTC) and datetime (exact time).
|
|
8
|
+
"""
|
|
9
|
+
if isinstance(date_or_dt, datetime.datetime):
|
|
10
|
+
year = date_or_dt.year
|
|
11
|
+
month = date_or_dt.month
|
|
12
|
+
day = date_or_dt.day
|
|
13
|
+
hour = date_or_dt.hour
|
|
14
|
+
minute = date_or_dt.minute
|
|
15
|
+
second = date_or_dt.second
|
|
16
|
+
else:
|
|
17
|
+
year = date_or_dt.year
|
|
18
|
+
month = date_or_dt.month
|
|
19
|
+
day = date_or_dt.day
|
|
20
|
+
hour = minute = second = 0
|
|
9
21
|
|
|
10
22
|
if month <= 2:
|
|
11
23
|
year -= 1
|
|
@@ -14,7 +26,7 @@ def julianday(date: datetime.date) -> float:
|
|
|
14
26
|
a = year // 100
|
|
15
27
|
b = 2 - a + (a // 4)
|
|
16
28
|
jd = int(365.25 * (year + 4716)) + int(30.6001 * (month + 1)) + day + b - 1524.5
|
|
17
|
-
return jd
|
|
29
|
+
return jd + (hour + minute/60 + second/3600) / 24.0
|
|
18
30
|
|
|
19
31
|
def julianday_to_juliancentury(jd: float) -> float:
|
|
20
32
|
"""Convert a Julian Day number to a Julian Century."""
|
|
@@ -52,10 +64,14 @@ def sun_true_long(jc: float) -> float:
|
|
|
52
64
|
return l0 + c
|
|
53
65
|
|
|
54
66
|
def sun_apparent_long(jc: float) -> float:
|
|
55
|
-
"""Calculate the sun's apparent longitude."""
|
|
56
|
-
|
|
67
|
+
"""Calculate the sun's apparent longitude with nutation correction (Meeus Ch. 25)."""
|
|
68
|
+
l0 = geom_mean_long_sun(jc)
|
|
69
|
+
m = geom_mean_anomaly_sun(jc)
|
|
70
|
+
c = sun_eq_of_center(jc)
|
|
71
|
+
lambda_geo = l0 + c
|
|
57
72
|
omega = 125.04 - 1934.136 * jc
|
|
58
|
-
|
|
73
|
+
nutation_long = -0.0048 * sin(radians(2 * omega)) # Nutation correction
|
|
74
|
+
return (lambda_geo + nutation_long - 0.00569) % 360.0
|
|
59
75
|
|
|
60
76
|
def sun_distance(jc: float) -> float:
|
|
61
77
|
"""Calculate the distance to the Sun in astronomical units (AU)."""
|
|
@@ -108,10 +124,8 @@ def refraction_at_zenith(zenith: float) -> float:
|
|
|
108
124
|
"""Calculate the atmospheric refraction correction for the given zenith angle (in degrees)."""
|
|
109
125
|
if zenith < 0 or zenith > 90:
|
|
110
126
|
return 0.0
|
|
111
|
-
|
|
112
127
|
# Convert zenith to radians
|
|
113
128
|
zenith_rad = radians(zenith)
|
|
114
|
-
|
|
115
129
|
# Refraction formula (Bennett 1982, used in Astral)
|
|
116
130
|
tan_zenith = tan(zenith_rad)
|
|
117
131
|
if zenith > 85.0:
|
|
@@ -123,14 +137,12 @@ def refraction_at_zenith(zenith: float) -> float:
|
|
|
123
137
|
) / 3600.0
|
|
124
138
|
else:
|
|
125
139
|
refraction = (58.276 / tan_zenith) / 3600.0
|
|
126
|
-
|
|
127
140
|
return refraction
|
|
128
141
|
|
|
129
142
|
def adjust_to_horizon(elevation: float) -> float:
|
|
130
143
|
"""Calculate the extra degrees of depression due to the observer's elevation."""
|
|
131
144
|
if elevation <= 0:
|
|
132
145
|
return 0.0
|
|
133
|
-
|
|
134
146
|
r = 6356900 # Radius of the Earth in meters
|
|
135
147
|
a1 = r
|
|
136
148
|
h1 = r + elevation
|
|
@@ -213,11 +225,9 @@ def sunrise_sunset(
|
|
|
213
225
|
# Convert to local timezone
|
|
214
226
|
sunrise_local = sunrise_utc.astimezone(timezone)
|
|
215
227
|
sunset_local = sunset_utc.astimezone(timezone)
|
|
216
|
-
|
|
217
228
|
return sunrise_local, sunset_local
|
|
218
229
|
|
|
219
230
|
def noon(
|
|
220
|
-
latitude: float,
|
|
221
231
|
longitude: float,
|
|
222
232
|
date: datetime.date,
|
|
223
233
|
timezone: datetime.timezone = datetime.timezone.utc,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|