solarmoonpy 0.1.0__py3-none-any.whl → 1.0.1__py3-none-any.whl
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/__init__.py +3 -2
- solarmoonpy/moon.py +184 -163
- solarmoonpy/sun.py +25 -15
- solarmoonpy/version.py +1 -1
- {solarmoonpy-0.1.0.dist-info → solarmoonpy-1.0.1.dist-info}/METADATA +16 -3
- solarmoonpy-1.0.1.dist-info/RECORD +10 -0
- solarmoonpy-0.1.0.dist-info/RECORD +0 -10
- {solarmoonpy-0.1.0.dist-info → solarmoonpy-1.0.1.dist-info}/WHEEL +0 -0
- {solarmoonpy-0.1.0.dist-info → solarmoonpy-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {solarmoonpy-0.1.0.dist-info → solarmoonpy-1.0.1.dist-info}/top_level.txt +0 -0
solarmoonpy/__init__.py
CHANGED
|
@@ -9,11 +9,12 @@ https://github.com/figorr/solarmoonpy
|
|
|
9
9
|
|
|
10
10
|
# solarmoonpy/__init__.py
|
|
11
11
|
from .version import __version__
|
|
12
|
-
from .moon import moon_phase, moon_rise_set, illuminated_percentage, moon_distance, moon_angular_diameter
|
|
12
|
+
from .moon import moon_phase, moon_day, moon_rise_set, illuminated_percentage, moon_distance, moon_angular_diameter
|
|
13
13
|
from .location import Location, LocationInfo
|
|
14
14
|
|
|
15
15
|
__all__ = [
|
|
16
|
-
"moon_phase",
|
|
16
|
+
"moon_phase",
|
|
17
|
+
"moon_day",
|
|
17
18
|
"moon_rise_set",
|
|
18
19
|
"illuminated_percentage",
|
|
19
20
|
"moon_distance",
|
solarmoonpy/moon.py
CHANGED
|
@@ -4,41 +4,43 @@ 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
|
-
# Cálculo aproximado con mes sinódico
|
|
25
|
-
lunations = days / _SYNODIC_MONTH
|
|
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)
|
|
39
21
|
|
|
40
|
-
#
|
|
41
|
-
|
|
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
|
|
24
|
+
|
|
25
|
+
# Días desde la última luna nueva
|
|
26
|
+
return phase * _SYNODIC_MONTH
|
|
27
|
+
|
|
28
|
+
def moon_day(date_utc: date, tol_hours: float = 0.01) -> int:
|
|
29
|
+
"""
|
|
30
|
+
Calcula el día lunar 0–29 usando la última luna nueva exacta calculada por elongación.
|
|
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).
|
|
33
|
+
"""
|
|
34
|
+
dt = datetime(date_utc.year, date_utc.month, date_utc.day, 23, 59, tzinfo=timezone.utc)
|
|
35
|
+
last_new_moon = find_last_phase_exact(date_utc, target=0.0)
|
|
36
|
+
age_days = (dt - last_new_moon).total_seconds() / 86400.0
|
|
37
|
+
if age_days < 0:
|
|
38
|
+
day = 0
|
|
39
|
+
else:
|
|
40
|
+
day = int(math.floor(age_days))
|
|
41
|
+
if day > 29:
|
|
42
|
+
day = 29
|
|
43
|
+
return day
|
|
42
44
|
|
|
43
45
|
def illuminated_percentage(date_utc: date) -> float:
|
|
44
46
|
"""
|
|
@@ -49,8 +51,8 @@ def illuminated_percentage(date_utc: date) -> float:
|
|
|
49
51
|
Returns:
|
|
50
52
|
float: Porcentaje de superficie lunar iluminada (0–100).
|
|
51
53
|
"""
|
|
52
|
-
dt = datetime(date_utc.year, date_utc.month, date_utc.day, 12, 0, 0, tzinfo=timezone.utc)
|
|
53
|
-
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)
|
|
54
56
|
jc = julianday_to_juliancentury(jd)
|
|
55
57
|
lambda_m, beta_m, R, delta_psi = _moon_ecliptic_position(jd)
|
|
56
58
|
lambda_s = sun_apparent_long(jc)
|
|
@@ -72,130 +74,60 @@ def _normalize_angle(angle: float) -> float:
|
|
|
72
74
|
|
|
73
75
|
# Tabla completa 47.A para sigma_l y sigma_r (de PyMeeus/Meeus)
|
|
74
76
|
SIGMA_LR_TABLE = [
|
|
75
|
-
[0, 0, 1, 0, 6288774.0, -20905355.0],
|
|
76
|
-
[2, 0, -
|
|
77
|
-
[
|
|
78
|
-
[0, 0, 2, 0,
|
|
79
|
-
[0, 1, 0, 0, -
|
|
80
|
-
[0,
|
|
81
|
-
[
|
|
82
|
-
[
|
|
83
|
-
[
|
|
84
|
-
[
|
|
85
|
-
[0, 1, -1, 0, -
|
|
86
|
-
[1, 0, 0, 0, -
|
|
87
|
-
[0,
|
|
88
|
-
[2, 0, 0, -2,
|
|
89
|
-
[
|
|
90
|
-
[
|
|
91
|
-
[
|
|
92
|
-
[0, 0,
|
|
93
|
-
[
|
|
94
|
-
[2,
|
|
95
|
-
[2, 1, 0, 0, -
|
|
96
|
-
[1, 0, -1, 0, -
|
|
97
|
-
[
|
|
98
|
-
[
|
|
99
|
-
[
|
|
100
|
-
[
|
|
101
|
-
[
|
|
102
|
-
[0, 1,
|
|
103
|
-
[2, 0, -1,
|
|
104
|
-
[2, -1, -2, 0
|
|
105
|
-
[1, 0, 1, 0, -2348.0, 6322.0],
|
|
106
|
-
[2, -2, 0, 0, 2236.0, -9884.0],
|
|
107
|
-
[0, 1, 2, 0, -2120.0, 5751.0],
|
|
108
|
-
[0, 2, 0, 0, -2069.0, 0.0],
|
|
109
|
-
[2, -2, -1, 0, 2048.0, -4950.0],
|
|
110
|
-
[2, 0, 1, -2, -1773.0, 4130.0],
|
|
111
|
-
[2, 0, 0, 2, -1595.0, 0.0],
|
|
112
|
-
[4, -1, -1, 0, 1215.0, -3958.0],
|
|
113
|
-
[0, 0, 2, 2, -1110.0, 0.0],
|
|
114
|
-
[3, 0, -1, 0, -892.0, 3258.0],
|
|
115
|
-
[2, 1, 1, 0, -810.0, 2616.0],
|
|
116
|
-
[4, -1, -2, 0, 759.0, -1897.0],
|
|
117
|
-
[0, 2, -1, 0, -713.0, -2117.0],
|
|
118
|
-
[2, 2, -1, 0, -700.0, 2354.0],
|
|
119
|
-
[2, 1, -2, 0, 691.0, 0.0],
|
|
120
|
-
[2, -1, 0, -2, 596.0, 0.0],
|
|
121
|
-
[4, 0, 1, 0, 549.0, -1423.0],
|
|
122
|
-
[0, 0, 4, 0, 537.0, -1117.0],
|
|
123
|
-
[4, -1, 0, 0, 520.0, -1571.0],
|
|
124
|
-
[1, 0, -2, 0, -487.0, -1739.0],
|
|
125
|
-
[2, 1, 0, -2, -399.0, 0.0],
|
|
126
|
-
[0, 0, 2, -2, -381.0, -4421.0],
|
|
127
|
-
[1, 1, 1, 0, 351.0, 0.0],
|
|
128
|
-
[3, 0, -2, 0, -340.0, 0.0],
|
|
129
|
-
[4, 0, -3, 0, 330.0, 0.0],
|
|
130
|
-
[2, -1, 2, 0, 327.0, 0.0],
|
|
131
|
-
[0, 2, 1, 0, -323.0, 1165.0],
|
|
132
|
-
[1, 1, -1, 0, 299.0, 0.0],
|
|
133
|
-
[2, 0, 3, 0, 294.0, 0.0],
|
|
134
|
-
[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]
|
|
135
107
|
]
|
|
136
108
|
|
|
137
109
|
# Tabla completa 47.B para sigma_b (de PyMeeus/Meeus)
|
|
138
110
|
SIGMA_B_TABLE = [
|
|
139
|
-
[0, 0, 0, 1, 5128122.0],
|
|
140
|
-
[0, 0, 1, 1,
|
|
141
|
-
[0, 0, 1, -1,
|
|
142
|
-
[2, 0, 0, -1,
|
|
143
|
-
[2, 0, -1, 1,
|
|
144
|
-
[2, 0, -1, -1,
|
|
145
|
-
[
|
|
146
|
-
[0, 0,
|
|
147
|
-
[
|
|
148
|
-
[0, 0,
|
|
149
|
-
[
|
|
150
|
-
[2, 0, -2, -1,
|
|
151
|
-
[2, 0, 1, 1,
|
|
152
|
-
[2, 1, 0,
|
|
153
|
-
[2, -1, -1, 1,
|
|
154
|
-
[2, -1, 0, 1,
|
|
155
|
-
[2, -1, -1, -1,
|
|
156
|
-
[
|
|
157
|
-
[4, 0, -1, -1,
|
|
158
|
-
[0, 1, 0, 1, -
|
|
159
|
-
[0, 0, 0, 3, -1749.0],
|
|
160
|
-
[0, 1, -1, 1, -1565.0],
|
|
161
|
-
[1, 0, 0, 1, -1491.0],
|
|
162
|
-
[0, 1, 1, 1, -1475.0],
|
|
163
|
-
[0, 1, 1, -1, -1410.0],
|
|
164
|
-
[0, 1, 0, -1, -1344.0],
|
|
165
|
-
[1, 0, 0, -1, -1335.0],
|
|
166
|
-
[0, 0, 3, 1, 1107.0],
|
|
167
|
-
[4, 0, 0, -1, 1021.0],
|
|
168
|
-
[4, 0, -1, 1, 833.0],
|
|
169
|
-
[0, 0, 1, -3, 777.0],
|
|
170
|
-
[4, 0, -2, 1, 671.0],
|
|
171
|
-
[2, 0, 0, -3, 607.0],
|
|
172
|
-
[2, 0, 2, -1, 596.0],
|
|
173
|
-
[2, -1, 1, -1, 491.0],
|
|
174
|
-
[2, 0, -2, 1, -451.0],
|
|
175
|
-
[0, 0, 3, -1, 439.0],
|
|
176
|
-
[2, 0, 2, 1, 422.0],
|
|
177
|
-
[2, 0, -3, -1, 421.0],
|
|
178
|
-
[2, 1, -1, 1, -366.0],
|
|
179
|
-
[2, 1, 0, 1, -351.0],
|
|
180
|
-
[4, 0, 0, 1, 331.0],
|
|
181
|
-
[2, -1, 1, 1, 315.0],
|
|
182
|
-
[2, -2, 0, -1, 302.0],
|
|
183
|
-
[0, 0, 1, 3, -283.0],
|
|
184
|
-
[2, 1, 1, -1, -229.0],
|
|
185
|
-
[1, 1, 0, -1, 223.0],
|
|
186
|
-
[1, 1, 0, 1, 223.0],
|
|
187
|
-
[0, 1, -2, -1, -220.0],
|
|
188
|
-
[2, 1, -1, -1, -220.0],
|
|
189
|
-
[1, 0, 1, 1, -185.0],
|
|
190
|
-
[2, -1, -2, -1, 181.0],
|
|
191
|
-
[0, 1, 2, 1, -177.0],
|
|
192
|
-
[4, 0, -2, -1, 176.0],
|
|
193
|
-
[4, -1, -1, -1, 166.0],
|
|
194
|
-
[1, 0, 1, -1, -164.0],
|
|
195
|
-
[4, 0, 1, -1, 132.0],
|
|
196
|
-
[1, 0, -1, -1, -119.0],
|
|
197
|
-
[4, -1, 0, -1, 115.0],
|
|
198
|
-
[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]
|
|
199
131
|
]
|
|
200
132
|
|
|
201
133
|
def _periodic_terms(D, M, Mp, F, T):
|
|
@@ -228,7 +160,6 @@ def _periodic_terms(D, M, Mp, F, T):
|
|
|
228
160
|
sigma_l /= 1000000.0 # a grados
|
|
229
161
|
sigma_r *= 0.001 # a km
|
|
230
162
|
sigma_b /= 1000000.0 # a grados
|
|
231
|
-
|
|
232
163
|
return sigma_l, sigma_r, sigma_b
|
|
233
164
|
|
|
234
165
|
def _moon_ecliptic_position(jd: float) -> Tuple[float, float, float, float]:
|
|
@@ -248,7 +179,6 @@ def _moon_ecliptic_position(jd: float) -> Tuple[float, float, float, float]:
|
|
|
248
179
|
R = 385000.529 + sigma_r
|
|
249
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
|
|
250
181
|
lambda_ += delta_psi
|
|
251
|
-
|
|
252
182
|
return lambda_, beta, R, delta_psi
|
|
253
183
|
|
|
254
184
|
def _calculate_position_and_alt(t: datetime, lat_r: float, lon: float) -> tuple[float, float]:
|
|
@@ -272,7 +202,6 @@ def _calculate_position_and_alt(t: datetime, lat_r: float, lon: float) -> tuple[
|
|
|
272
202
|
if ha < -math.pi: ha += 2 * math.pi
|
|
273
203
|
elif ha > math.pi: ha -= 2 * math.pi
|
|
274
204
|
alt = math.asin(math.sin(lat_r) * math.sin(dec) + math.cos(lat_r) * math.cos(dec) * math.cos(ha))
|
|
275
|
-
|
|
276
205
|
return alt, h0
|
|
277
206
|
|
|
278
207
|
def moon_rise_set(lat: float, lon: float, date_utc: date) -> Tuple[Optional[datetime], Optional[datetime]]:
|
|
@@ -324,7 +253,6 @@ def moon_rise_set(lat: float, lon: float, date_utc: date) -> Tuple[Optional[date
|
|
|
324
253
|
if intervalos_set and set_ is None:
|
|
325
254
|
start_t, end_t, start_alt, end_alt = intervalos_set[0]
|
|
326
255
|
set_ = refine_event(start_t, end_t, start_alt, end_alt, False)
|
|
327
|
-
|
|
328
256
|
return rise, set_
|
|
329
257
|
|
|
330
258
|
def moon_distance(date_utc: date) -> float:
|
|
@@ -335,8 +263,8 @@ def moon_distance(date_utc: date) -> float:
|
|
|
335
263
|
Returns:
|
|
336
264
|
float: Distancia en kilómetros.
|
|
337
265
|
"""
|
|
338
|
-
dt = datetime(date_utc.year, date_utc.month, date_utc.day, 12, 0, 0, tzinfo=timezone.utc)
|
|
339
|
-
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)
|
|
340
268
|
_, _, R, _ = _moon_ecliptic_position(jd)
|
|
341
269
|
return R
|
|
342
270
|
|
|
@@ -348,9 +276,102 @@ def moon_angular_diameter(date_utc: date) -> float:
|
|
|
348
276
|
Returns:
|
|
349
277
|
float: Diámetro angular en arcosegundos.
|
|
350
278
|
"""
|
|
351
|
-
dt = datetime(date_utc.year, date_utc.month, date_utc.day, 12, 0, 0, tzinfo=timezone.utc)
|
|
352
|
-
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)
|
|
353
281
|
_, _, R, _ = _moon_ecliptic_position(jd)
|
|
354
|
-
par = math.asin(6378.14 / R)
|
|
355
|
-
sd = 0.2725076 * par
|
|
356
|
-
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)
|
solarmoonpy/sun.py
CHANGED
|
@@ -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,
|
solarmoonpy/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# version.py
|
|
2
|
-
__version__ = "0.1
|
|
2
|
+
__version__ = "1.0.1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: solarmoonpy
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Precise solar and lunar calculations for astronomical applications
|
|
5
5
|
Author-email: figorr <jdcuartero@yahoo.es>
|
|
6
6
|
License: Apache License
|
|
@@ -220,5 +220,18 @@ Description-Content-Type: text/markdown
|
|
|
220
220
|
License-File: LICENSE
|
|
221
221
|
Dynamic: license-file
|
|
222
222
|
|
|
223
|
-
# solarmoonpy
|
|
224
|
-
|
|
223
|
+
# ☀️🌙 solarmoonpy
|
|
224
|
+
Solar and moon calculations for Meteocat Home Assistant integration.
|
|
225
|
+
|
|
226
|
+
**solarmoonpy** is a Python library that provides accurate calculations of solar and lunar positions, specifically designed to integrate with Home Assistant and Meteocat data.
|
|
227
|
+
|
|
228
|
+
This project enables you to obtain information such as sunrise, sunset, moon phases, and more, for advanced automations in your home automation system.
|
|
229
|
+
|
|
230
|
+
## 🚀 Features
|
|
231
|
+
|
|
232
|
+
- ☀️ Solar position calculations (sunrise, sunset, zenith, etc.).
|
|
233
|
+
- 🌙 Moon phase and position calculations.
|
|
234
|
+
- Integration with meteorological data from Meteocat.
|
|
235
|
+
- Compatible with Home Assistant for automations based on solar and lunar events.
|
|
236
|
+
- Lightweight, easy to use, and well-documented.
|
|
237
|
+
- Configurable for different geographic locations.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
solarmoonpy/__init__.py,sha256=cS0ZjePNX75usK8mgvbk_uguVpXRixqZU4zjrEtg42c,722
|
|
2
|
+
solarmoonpy/location.py,sha256=ELx1BVGMRwMPW6uhtyz2LUjxMyBNQM_RXgwRuLo1J7s,10386
|
|
3
|
+
solarmoonpy/moon.py,sha256=SSa4pwUX-r70NSg2Fv3TSAnoyMAdTq2vQ8JRZqDBONo,17587
|
|
4
|
+
solarmoonpy/sun.py,sha256=dleHQWXzjnqtPAZr9ONdXiZvTVWvdW6gW775A2ASsNk,8937
|
|
5
|
+
solarmoonpy/version.py,sha256=Pk3OGNvW6xVheWgr6AQzYbPTYigdhML663W3JHGsF6U,35
|
|
6
|
+
solarmoonpy-1.0.1.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
7
|
+
solarmoonpy-1.0.1.dist-info/METADATA,sha256=7Xwsrseo2d_crVrBPp7iya7m71r2ULM3-onQlewxgU8,14850
|
|
8
|
+
solarmoonpy-1.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
solarmoonpy-1.0.1.dist-info/top_level.txt,sha256=egsoDe9E0QEu5GfAA5jSvy_3qTCWMLMe_Kh3gnM6-dY,12
|
|
10
|
+
solarmoonpy-1.0.1.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
solarmoonpy/__init__.py,sha256=6oTXMLEtrwgFemMgPxNTrWSebn3_pZjBkFmyVGol6d0,695
|
|
2
|
-
solarmoonpy/location.py,sha256=ELx1BVGMRwMPW6uhtyz2LUjxMyBNQM_RXgwRuLo1J7s,10386
|
|
3
|
-
solarmoonpy/moon.py,sha256=eyrh1CHDjMAI1gFGoW4Ig_RYP5hAsHbQklKLjLGvqLc,14266
|
|
4
|
-
solarmoonpy/sun.py,sha256=ai53vZEJ6k9fvb8JyaZNxKueqHXLnKZxFBz-QAAAHjc,8289
|
|
5
|
-
solarmoonpy/version.py,sha256=XJYgmMWwwuGBlSVGAS1tn_sPe0VvcX2XS3T-VPrSZ4I,35
|
|
6
|
-
solarmoonpy-0.1.0.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
7
|
-
solarmoonpy-0.1.0.dist-info/METADATA,sha256=fyCsilbXyW-t99jPC1u9JdgSR_QOLYEIph1kdgQXcts,14084
|
|
8
|
-
solarmoonpy-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
-
solarmoonpy-0.1.0.dist-info/top_level.txt,sha256=egsoDe9E0QEu5GfAA5jSvy_3qTCWMLMe_Kh3gnM6-dY,12
|
|
10
|
-
solarmoonpy-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|