solarmoonpy 1.0.6__tar.gz → 1.0.8__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.6 → solarmoonpy-1.0.8}/PKG-INFO +1 -1
- {solarmoonpy-1.0.6 → solarmoonpy-1.0.8}/pyproject.toml +1 -1
- {solarmoonpy-1.0.6 → solarmoonpy-1.0.8}/solarmoonpy/location.py +179 -54
- {solarmoonpy-1.0.6 → solarmoonpy-1.0.8}/solarmoonpy/sun.py +80 -0
- solarmoonpy-1.0.8/solarmoonpy/version.py +2 -0
- {solarmoonpy-1.0.6 → solarmoonpy-1.0.8}/solarmoonpy.egg-info/PKG-INFO +1 -1
- solarmoonpy-1.0.6/solarmoonpy/version.py +0 -2
- {solarmoonpy-1.0.6 → solarmoonpy-1.0.8}/LICENSE +0 -0
- {solarmoonpy-1.0.6 → solarmoonpy-1.0.8}/README.md +0 -0
- {solarmoonpy-1.0.6 → solarmoonpy-1.0.8}/setup.cfg +0 -0
- {solarmoonpy-1.0.6 → solarmoonpy-1.0.8}/solarmoonpy/__init__.py +0 -0
- {solarmoonpy-1.0.6 → solarmoonpy-1.0.8}/solarmoonpy/moon.py +0 -0
- {solarmoonpy-1.0.6 → solarmoonpy-1.0.8}/solarmoonpy.egg-info/SOURCES.txt +0 -0
- {solarmoonpy-1.0.6 → solarmoonpy-1.0.8}/solarmoonpy.egg-info/dependency_links.txt +0 -0
- {solarmoonpy-1.0.6 → solarmoonpy-1.0.8}/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.8"
|
|
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" }]
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from datetime import date, datetime, timezone
|
|
3
3
|
from zoneinfo import ZoneInfo
|
|
4
|
-
from typing import Optional, Union, Literal
|
|
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:
|
|
@@ -257,57 +257,6 @@ class Location:
|
|
|
257
257
|
date=date,
|
|
258
258
|
timezone=self.tzinfo if local else timezone.utc
|
|
259
259
|
)
|
|
260
|
-
|
|
261
|
-
def sun_events(
|
|
262
|
-
self,
|
|
263
|
-
date: Optional[date] = None,
|
|
264
|
-
local: bool = True,
|
|
265
|
-
elevation: Optional[float] = None
|
|
266
|
-
) -> dict:
|
|
267
|
-
"""
|
|
268
|
-
Devuelve un diccionario con sunrise, noon y sunset para una fecha dada.
|
|
269
|
-
|
|
270
|
-
Args:
|
|
271
|
-
date: Fecha para la cual calcular los eventos solares. Si es None, usa la fecha actual.
|
|
272
|
-
local: True para devolver las horas en la zona horaria local, False para UTC.
|
|
273
|
-
elevation: Elevación del observador en metros. Si es None, usa self.elevation.
|
|
274
|
-
|
|
275
|
-
Returns:
|
|
276
|
-
dict: {
|
|
277
|
-
"sunrise": datetime,
|
|
278
|
-
"noon": datetime,
|
|
279
|
-
"sunset": datetime
|
|
280
|
-
}
|
|
281
|
-
"""
|
|
282
|
-
if local and self.timezone is None:
|
|
283
|
-
raise ValueError("Se solicitó hora local pero no se definió una zona horaria.")
|
|
284
|
-
|
|
285
|
-
tz = self.tzinfo if local else timezone.utc
|
|
286
|
-
date = date or datetime.now(tz).date()
|
|
287
|
-
elevation = elevation if elevation is not None else self.elevation
|
|
288
|
-
|
|
289
|
-
# Calcular amanecer y atardecer
|
|
290
|
-
sunrise, sunset = sunrise_sunset(
|
|
291
|
-
latitude=self.latitude,
|
|
292
|
-
longitude=self.longitude,
|
|
293
|
-
date=date,
|
|
294
|
-
elevation=elevation,
|
|
295
|
-
timezone=tz,
|
|
296
|
-
with_refraction=True,
|
|
297
|
-
)
|
|
298
|
-
|
|
299
|
-
# Calcular mediodía solar
|
|
300
|
-
noon_time = noon(
|
|
301
|
-
longitude=self.longitude,
|
|
302
|
-
date=date,
|
|
303
|
-
timezone=tz,
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
return {
|
|
307
|
-
"sunrise": sunrise,
|
|
308
|
-
"noon": noon_time,
|
|
309
|
-
"sunset": sunset,
|
|
310
|
-
}
|
|
311
260
|
|
|
312
261
|
def dawn(
|
|
313
262
|
self,
|
|
@@ -372,4 +321,180 @@ class Location:
|
|
|
372
321
|
longitude=self.longitude,
|
|
373
322
|
date=date,
|
|
374
323
|
timezone=self.tzinfo if local else timezone.utc
|
|
375
|
-
)
|
|
324
|
+
)
|
|
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
|
+
|
|
359
|
+
def sun_events(
|
|
360
|
+
self,
|
|
361
|
+
date: Optional[date] = None,
|
|
362
|
+
local: bool = True,
|
|
363
|
+
elevation: Optional[float] = None,
|
|
364
|
+
) -> Dict:
|
|
365
|
+
"""
|
|
366
|
+
Devuelve un diccionario con los eventos solares para una fecha dada, incluyendo todos los tipos de crepúsculo.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
date: Fecha para la cual calcular los eventos solares. Si es None, usa la fecha actual.
|
|
370
|
+
local: True para devolver las horas en la zona horaria local, False para UTC.
|
|
371
|
+
elevation: Elevación del observador en metros. Si es None, usa self.elevation.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
dict: {
|
|
375
|
+
"dawn_civil": datetime | None,
|
|
376
|
+
"dawn_nautical": datetime | None,
|
|
377
|
+
"dawn_astronomical": datetime | None,
|
|
378
|
+
"sunrise": datetime,
|
|
379
|
+
"noon": datetime,
|
|
380
|
+
"sunset": datetime,
|
|
381
|
+
"dusk_civil": datetime | None,
|
|
382
|
+
"dusk_nautical": datetime | None,
|
|
383
|
+
"dusk_astronomical": datetime | None,
|
|
384
|
+
"midnight": datetime,
|
|
385
|
+
"daylight_duration": float | None
|
|
386
|
+
}
|
|
387
|
+
"""
|
|
388
|
+
if local and self.timezone is None:
|
|
389
|
+
raise ValueError("Se solicitó hora local pero no se definió una zona horaria.")
|
|
390
|
+
|
|
391
|
+
tz = self.tzinfo if local else timezone.utc
|
|
392
|
+
now = datetime.now(tz)
|
|
393
|
+
date = date or now.date()
|
|
394
|
+
elevation = elevation if elevation is not None else self.elevation
|
|
395
|
+
|
|
396
|
+
# Calcular amanecer y atardecer
|
|
397
|
+
sunrise, sunset = sunrise_sunset(
|
|
398
|
+
latitude=self.latitude,
|
|
399
|
+
longitude=self.longitude,
|
|
400
|
+
date=date,
|
|
401
|
+
elevation=elevation,
|
|
402
|
+
timezone=tz,
|
|
403
|
+
with_refraction=True,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
# Calcular mediodía solar
|
|
407
|
+
noon_time = noon(
|
|
408
|
+
longitude=self.longitude,
|
|
409
|
+
date=date,
|
|
410
|
+
timezone=tz,
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
# Calcular medianoche solar
|
|
414
|
+
midnight_time = midnight(
|
|
415
|
+
longitude=self.longitude,
|
|
416
|
+
date=date,
|
|
417
|
+
timezone=tz,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
# Calcular duración del día (en horas)
|
|
421
|
+
daylight_duration = None
|
|
422
|
+
if sunrise and sunset:
|
|
423
|
+
daylight_duration = (sunset - sunrise).total_seconds() / 3600
|
|
424
|
+
|
|
425
|
+
# Calcular dawn y dusk para cada tipo de crepúsculo
|
|
426
|
+
dawn_civil = dawn(
|
|
427
|
+
latitude=self.latitude,
|
|
428
|
+
longitude=self.longitude,
|
|
429
|
+
date=date,
|
|
430
|
+
twilight_type="civil",
|
|
431
|
+
elevation=elevation,
|
|
432
|
+
timezone=tz,
|
|
433
|
+
with_refraction=True,
|
|
434
|
+
)
|
|
435
|
+
dusk_civil = dusk(
|
|
436
|
+
latitude=self.latitude,
|
|
437
|
+
longitude=self.longitude,
|
|
438
|
+
date=date,
|
|
439
|
+
twilight_type="civil",
|
|
440
|
+
elevation=elevation,
|
|
441
|
+
timezone=tz,
|
|
442
|
+
with_refraction=True,
|
|
443
|
+
)
|
|
444
|
+
dawn_nautical = dawn(
|
|
445
|
+
latitude=self.latitude,
|
|
446
|
+
longitude=self.longitude,
|
|
447
|
+
date=date,
|
|
448
|
+
twilight_type="nautical",
|
|
449
|
+
elevation=elevation,
|
|
450
|
+
timezone=tz,
|
|
451
|
+
with_refraction=True,
|
|
452
|
+
)
|
|
453
|
+
dusk_nautical = dusk(
|
|
454
|
+
latitude=self.latitude,
|
|
455
|
+
longitude=self.longitude,
|
|
456
|
+
date=date,
|
|
457
|
+
twilight_type="nautical",
|
|
458
|
+
elevation=elevation,
|
|
459
|
+
timezone=tz,
|
|
460
|
+
with_refraction=True,
|
|
461
|
+
)
|
|
462
|
+
dawn_astronomical = dawn(
|
|
463
|
+
latitude=self.latitude,
|
|
464
|
+
longitude=self.longitude,
|
|
465
|
+
date=date,
|
|
466
|
+
twilight_type="astronomical",
|
|
467
|
+
elevation=elevation,
|
|
468
|
+
timezone=tz,
|
|
469
|
+
with_refraction=True,
|
|
470
|
+
)
|
|
471
|
+
dusk_astronomical = dusk(
|
|
472
|
+
latitude=self.latitude,
|
|
473
|
+
longitude=self.longitude,
|
|
474
|
+
date=date,
|
|
475
|
+
twilight_type="astronomical",
|
|
476
|
+
elevation=elevation,
|
|
477
|
+
timezone=tz,
|
|
478
|
+
with_refraction=True,
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
# Posición actual del Sol
|
|
482
|
+
pos = self.sun_position(dt=now, local=local, elevation=elevation)
|
|
483
|
+
|
|
484
|
+
return {
|
|
485
|
+
"dawn_civil": dawn_civil,
|
|
486
|
+
"dawn_nautical": dawn_nautical,
|
|
487
|
+
"dawn_astronomical": dawn_astronomical,
|
|
488
|
+
"sunrise": sunrise,
|
|
489
|
+
"noon": noon_time,
|
|
490
|
+
"sunset": sunset,
|
|
491
|
+
"dusk_civil": dusk_civil,
|
|
492
|
+
"dusk_nautical": dusk_nautical,
|
|
493
|
+
"dusk_astronomical": dusk_astronomical,
|
|
494
|
+
"midnight": midnight_time,
|
|
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"],
|
|
500
|
+
}
|
|
@@ -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
|
|
File without changes
|