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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solarmoonpy
3
- Version: 1.0.6
3
+ Version: 1.0.8
4
4
  Summary: Precise solar and lunar calculations for astronomical applications
5
5
  Author-email: figorr <jdcuartero@yahoo.es>
6
6
  License: Apache License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "solarmoonpy"
7
- version = "1.0.6"
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,
@@ -0,0 +1,2 @@
1
+ # version.py
2
+ __version__ = "1.0.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solarmoonpy
3
- Version: 1.0.6
3
+ Version: 1.0.8
4
4
  Summary: Precise solar and lunar calculations for astronomical applications
5
5
  Author-email: figorr <jdcuartero@yahoo.es>
6
6
  License: Apache License
@@ -1,2 +0,0 @@
1
- # version.py
2
- __version__ = "1.0.6"
File without changes
File without changes
File without changes