solarmoonpy 1.0.7__tar.gz → 1.0.9__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.
- {solarmoonpy-1.0.7 → solarmoonpy-1.0.9}/PKG-INFO +1 -1
- {solarmoonpy-1.0.7 → solarmoonpy-1.0.9}/pyproject.toml +1 -1
- {solarmoonpy-1.0.7 → solarmoonpy-1.0.9}/solarmoonpy/location.py +43 -2
- {solarmoonpy-1.0.7 → solarmoonpy-1.0.9}/solarmoonpy/moon.py +0 -4
- {solarmoonpy-1.0.7 → solarmoonpy-1.0.9}/solarmoonpy/sun.py +80 -0
- solarmoonpy-1.0.9/solarmoonpy/version.py +2 -0
- {solarmoonpy-1.0.7 → solarmoonpy-1.0.9}/solarmoonpy.egg-info/PKG-INFO +1 -1
- solarmoonpy-1.0.7/solarmoonpy/version.py +0 -2
- {solarmoonpy-1.0.7 → solarmoonpy-1.0.9}/LICENSE +0 -0
- {solarmoonpy-1.0.7 → solarmoonpy-1.0.9}/README.md +0 -0
- {solarmoonpy-1.0.7 → solarmoonpy-1.0.9}/setup.cfg +0 -0
- {solarmoonpy-1.0.7 → solarmoonpy-1.0.9}/solarmoonpy/__init__.py +0 -0
- {solarmoonpy-1.0.7 → solarmoonpy-1.0.9}/solarmoonpy.egg-info/SOURCES.txt +0 -0
- {solarmoonpy-1.0.7 → solarmoonpy-1.0.9}/solarmoonpy.egg-info/dependency_links.txt +0 -0
- {solarmoonpy-1.0.7 → solarmoonpy-1.0.9}/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.9"
|
|
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" }]
|
|
@@ -3,7 +3,7 @@ from datetime import date, datetime, timezone
|
|
|
3
3
|
from zoneinfo import ZoneInfo
|
|
4
4
|
from typing import Optional, Union, Literal, Dict
|
|
5
5
|
|
|
6
|
-
from .sun import sunrise_sunset, noon, dawn, dusk, midnight # Importar funciones de sun.py
|
|
6
|
+
from .sun import sunrise_sunset, noon, dawn, dusk, midnight, sun_position # Importar funciones de sun.py
|
|
7
7
|
|
|
8
8
|
@dataclass
|
|
9
9
|
class LocationInfo:
|
|
@@ -323,6 +323,39 @@ class Location:
|
|
|
323
323
|
timezone=self.tzinfo if local else timezone.utc
|
|
324
324
|
)
|
|
325
325
|
|
|
326
|
+
def sun_position(
|
|
327
|
+
self,
|
|
328
|
+
dt: Optional[datetime] = None,
|
|
329
|
+
local: bool = True,
|
|
330
|
+
elevation: Optional[float] = None
|
|
331
|
+
) -> dict:
|
|
332
|
+
"""
|
|
333
|
+
Calcula la posición actual del Sol.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
dt: Momento exacto. Si None, usa ahora.
|
|
337
|
+
local: True para usar zona horaria local.
|
|
338
|
+
elevation: Elevación del observador.
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
dict: elevation, azimuth, above_horizon, rising
|
|
342
|
+
"""
|
|
343
|
+
if local and self.timezone is None:
|
|
344
|
+
raise ValueError("Se solicitó hora local pero no se definió una zona horaria.")
|
|
345
|
+
|
|
346
|
+
tz = self.tzinfo if local else timezone.utc
|
|
347
|
+
dt = dt or datetime.now(tz)
|
|
348
|
+
elevation = elevation if elevation is not None else self.elevation
|
|
349
|
+
|
|
350
|
+
return sun_position(
|
|
351
|
+
latitude=self.latitude,
|
|
352
|
+
longitude=self.longitude,
|
|
353
|
+
dt=dt,
|
|
354
|
+
elevation=elevation,
|
|
355
|
+
timezone=tz,
|
|
356
|
+
with_refraction=True
|
|
357
|
+
)
|
|
358
|
+
|
|
326
359
|
def sun_events(
|
|
327
360
|
self,
|
|
328
361
|
date: Optional[date] = None,
|
|
@@ -356,7 +389,8 @@ class Location:
|
|
|
356
389
|
raise ValueError("Se solicitó hora local pero no se definió una zona horaria.")
|
|
357
390
|
|
|
358
391
|
tz = self.tzinfo if local else timezone.utc
|
|
359
|
-
|
|
392
|
+
now = datetime.now(tz)
|
|
393
|
+
date = date or now.date()
|
|
360
394
|
elevation = elevation if elevation is not None else self.elevation
|
|
361
395
|
|
|
362
396
|
# Calcular amanecer y atardecer
|
|
@@ -444,6 +478,9 @@ class Location:
|
|
|
444
478
|
with_refraction=True,
|
|
445
479
|
)
|
|
446
480
|
|
|
481
|
+
# Posición actual del Sol
|
|
482
|
+
pos = self.sun_position(dt=now, local=local, elevation=elevation)
|
|
483
|
+
|
|
447
484
|
return {
|
|
448
485
|
"dawn_civil": dawn_civil,
|
|
449
486
|
"dawn_nautical": dawn_nautical,
|
|
@@ -456,4 +493,8 @@ class Location:
|
|
|
456
493
|
"dusk_astronomical": dusk_astronomical,
|
|
457
494
|
"midnight": midnight_time,
|
|
458
495
|
"daylight_duration": daylight_duration,
|
|
496
|
+
"sun_elevation": pos["elevation"],
|
|
497
|
+
"sun_azimuth": pos["azimuth"],
|
|
498
|
+
"sun_horizon_position": pos["horizon_position"],
|
|
499
|
+
"sun_rising": pos["rising"],
|
|
459
500
|
}
|
|
@@ -392,8 +392,6 @@ def get_moon_phase_name(d: date) -> str:
|
|
|
392
392
|
last_new = find_last_phase_exact(d, 0.0)
|
|
393
393
|
if last_new and last_new.date() == d:
|
|
394
394
|
return "new_moon"
|
|
395
|
-
elif percentage < 1.0: # Backup para luna nueva cercana
|
|
396
|
-
return "new_moon"
|
|
397
395
|
|
|
398
396
|
last_first = find_last_phase_exact(d, 90.0)
|
|
399
397
|
if last_first and last_first.date() == d:
|
|
@@ -402,8 +400,6 @@ def get_moon_phase_name(d: date) -> str:
|
|
|
402
400
|
last_full = find_last_phase_exact(d, 180.0)
|
|
403
401
|
if last_full and last_full.date() == d:
|
|
404
402
|
return "full_moon"
|
|
405
|
-
elif percentage > 99.0: # Backup para luna llena cercana
|
|
406
|
-
return "full_moon"
|
|
407
403
|
|
|
408
404
|
last_last = find_last_phase_exact(d, 270.0)
|
|
409
405
|
if last_last and last_last.date() == d:
|
|
@@ -333,6 +333,86 @@ def midnight(
|
|
|
333
333
|
noon_time = noon(longitude, date, timezone)
|
|
334
334
|
return noon_time + datetime.timedelta(hours=12)
|
|
335
335
|
|
|
336
|
+
def sun_position(
|
|
337
|
+
latitude: float,
|
|
338
|
+
longitude: float,
|
|
339
|
+
dt: datetime.datetime,
|
|
340
|
+
elevation: float = 0.0,
|
|
341
|
+
timezone: datetime.timezone = datetime.timezone.utc,
|
|
342
|
+
with_refraction: bool = True
|
|
343
|
+
) -> dict:
|
|
344
|
+
"""
|
|
345
|
+
Calcula la posición del Sol con alta precisión.
|
|
346
|
+
"""
|
|
347
|
+
if dt.tzinfo is None:
|
|
348
|
+
dt = dt.replace(tzinfo=timezone)
|
|
349
|
+
dt_utc = dt.astimezone(datetime.timezone.utc)
|
|
350
|
+
|
|
351
|
+
jd = julianday(dt_utc)
|
|
352
|
+
jc = julianday_to_juliancentury(jd)
|
|
353
|
+
declination = sun_declination(jc)
|
|
354
|
+
eqtime = eq_of_time(jc)
|
|
355
|
+
|
|
356
|
+
# Tiempo solar verdadero en minutos desde medianoche
|
|
357
|
+
true_solar_time = (
|
|
358
|
+
dt_utc.hour * 60 + dt_utc.minute + dt_utc.second / 60.0
|
|
359
|
+
+ eqtime + 4.0 * longitude
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# Ángulo horario
|
|
363
|
+
hour_angle = (true_solar_time / 4.0) - 180.0
|
|
364
|
+
if hour_angle < -180:
|
|
365
|
+
hour_angle += 360
|
|
366
|
+
elif hour_angle > 180:
|
|
367
|
+
hour_angle -= 360
|
|
368
|
+
|
|
369
|
+
# Convertir a radianes
|
|
370
|
+
ha_rad = radians(hour_angle)
|
|
371
|
+
lat_rad = radians(latitude)
|
|
372
|
+
dec_rad = radians(declination)
|
|
373
|
+
|
|
374
|
+
# Elevación geométrica
|
|
375
|
+
sin_elev = sin(lat_rad) * sin(dec_rad) + cos(lat_rad) * cos(dec_rad) * cos(ha_rad)
|
|
376
|
+
elevation_geom = degrees(asin(max(min(sin_elev, 1.0), -1.0)))
|
|
377
|
+
|
|
378
|
+
# Corrección por altura del observador
|
|
379
|
+
elevation_adj = elevation_geom + adjust_to_horizon(elevation)
|
|
380
|
+
|
|
381
|
+
# Refracción atmosférica
|
|
382
|
+
if with_refraction and -10 <= elevation_adj <= 90:
|
|
383
|
+
refraction = refraction_at_zenith(90.0 - elevation_adj)
|
|
384
|
+
elevation_final = elevation_adj + refraction
|
|
385
|
+
else:
|
|
386
|
+
elevation_final = elevation_adj
|
|
387
|
+
|
|
388
|
+
# Azimut (0° = Norte)
|
|
389
|
+
sin_elev_final = sin(radians(elevation_final))
|
|
390
|
+
cos_elev_final = cos(radians(elevation_final))
|
|
391
|
+
cos_lat = cos(lat_rad)
|
|
392
|
+
sin_lat = sin(lat_rad)
|
|
393
|
+
sin_dec = sin(dec_rad)
|
|
394
|
+
|
|
395
|
+
if abs(cos_elev_final * cos_lat) < 1e-12:
|
|
396
|
+
azimuth = 180.0 if ha_rad < 0 else 0.0
|
|
397
|
+
else:
|
|
398
|
+
cos_az = (sin_dec - sin_elev_final * sin_lat) / (cos_elev_final * cos_lat)
|
|
399
|
+
cos_az = max(min(cos_az, 1.0), -1.0)
|
|
400
|
+
azimuth = degrees(acos(cos_az))
|
|
401
|
+
if hour_angle > 0: # PM
|
|
402
|
+
azimuth = 360.0 - azimuth
|
|
403
|
+
|
|
404
|
+
horizon_position: Literal["above_horizon", "below_horizon"] = (
|
|
405
|
+
"above_horizon" if elevation_final > 0 else "below_horizon"
|
|
406
|
+
)
|
|
407
|
+
rising = elevation_final > 0 and hour_angle < 0
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
"elevation": round(elevation_final, 2),
|
|
411
|
+
"azimuth": round(azimuth, 2),
|
|
412
|
+
"horizon_position": horizon_position,
|
|
413
|
+
"rising": rising
|
|
414
|
+
}
|
|
415
|
+
|
|
336
416
|
# 🎁 FUNCIÓN BONUS: Todo junto (opcional)
|
|
337
417
|
def all_sun_events(
|
|
338
418
|
latitude: float, longitude: float, date: datetime.date,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|