Skip to content

Commit 00eb14c

Browse files
Merge pull request #13 from gregory-halverson-jpl/main
updating `calculate_daylight` for processing tables of timestamps
2 parents dc7197a + 5d0c5c6 commit 00eb14c

14 files changed

+223
-222
lines changed

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ requires = ["setuptools>=60", "setuptools-scm>=8.0", "wheel"]
33

44
[project]
55
name = "sun-angles"
6-
version = "1.4.0"
6+
version = "1.5.0"
77
description = "calculates solar zenith and azimuth and daylight hours"
88
readme = "README.md"
99
authors = [
@@ -16,7 +16,7 @@ classifiers = [
1616
dependencies = [
1717
"numpy",
1818
"rasters",
19-
"solar-apparent-time"
19+
"solar-apparent-time>=1.4.0"
2020
]
2121

2222
requires-python = ">=3.10"

sun_angles/SHA.py renamed to sun_angles/SHA_deg_from_DOY_lat.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import numpy as np
44
from rasters import Raster
55

6-
from .day_angle import day_angle_rad_from_DOY
7-
from .declination import solar_dec_deg_from_day_angle_rad
6+
from .day_angle_rad_from_DOY import day_angle_rad_from_DOY
7+
from .solar_dec_deg_from_day_angle_rad import solar_dec_deg_from_day_angle_rad
88

99
def SHA_deg_from_DOY_lat(DOY: Union[Raster, np.ndarray], latitude: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
1010
"""
@@ -32,6 +32,11 @@ def SHA_deg_from_DOY_lat(DOY: Union[Raster, np.ndarray], latitude: Union[Raster,
3232
References:
3333
Duffie, J. A., & Beckman, W. A. (2013). Solar Engineering of Thermal Processes (4th ed.). Wiley.
3434
"""
35+
# Accept lists, scalars, or arrays; convert to np.ndarray if needed
36+
if isinstance(DOY, list):
37+
DOY = np.array(DOY)
38+
if isinstance(latitude, list):
39+
latitude = np.array(latitude)
3540
# calculate day angle in radians
3641
day_angle_rad = day_angle_rad_from_DOY(DOY)
3742

Lines changed: 1 addition & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
from typing import Union
2-
from datetime import datetime
3-
4-
import numpy as np
52
from rasters import Raster, CoordinateArray
6-
7-
from solar_apparent_time import solar_day_of_year_for_longitude, solar_hour_of_day_for_area
8-
9-
from .day_angle import day_angle_rad_from_DOY
10-
from .declination import solar_dec_deg_from_day_angle_rad
3+
import numpy as np
114

125
def SZA_deg_from_lat_dec_hour(
136
latitude: Union[Raster, np.ndarray],
@@ -56,48 +49,3 @@ def SZA_deg_from_lat_dec_hour(
5649

5750
# Return the solar zenith angle in degrees
5851
return SZA_deg
59-
60-
def calculate_SZA_from_DOY_and_hour(
61-
lat: Union[float, np.ndarray],
62-
lon: Union[float, np.ndarray],
63-
DOY: Union[float, np.ndarray, Raster],
64-
hour: Union[float, np.ndarray, Raster]) -> Union[float, np.ndarray, Raster]:
65-
"""
66-
Calculates the solar zenith angle (SZA) in degrees based on the given UTC time, latitude, longitude, day of year, and hour of day.
67-
68-
Args:
69-
lat (Union[float, np.ndarray]): The latitude in degrees.
70-
lon (Union[float, np.ndarray]): The longitude in degrees.
71-
doy (Union[float, np.ndarray, Raster]): The day of year.
72-
hour (Union[float, np.ndarray, Raster]): The hour of the day.
73-
74-
Returns:
75-
Union[float, np.ndarray, Raster]: The calculated solar zenith angle in degrees.
76-
"""
77-
day_angle_rad = day_angle_rad_from_DOY(DOY)
78-
solar_dec_deg = solar_dec_deg_from_day_angle_rad(day_angle_rad)
79-
SZA = SZA_deg_from_lat_dec_hour(lat, solar_dec_deg, hour)
80-
81-
return SZA
82-
83-
def calculate_SZA_from_datetime(time_UTC: datetime, lat: float, lon: float):
84-
"""
85-
Calculates the solar zenith angle (SZA) in degrees based on the given UTC time, latitude, and longitude.
86-
87-
Args:
88-
time_UTC (datetime.datetime): The UTC time to calculate the SZA for.
89-
lat (float): The latitude in degrees.
90-
lon (float): The longitude in degrees.
91-
92-
Returns:
93-
float: The calculated solar zenith angle in degrees.
94-
"""
95-
# Calculate the day of year based on the UTC time and longitude
96-
doy = solar_day_of_year_for_longitude(time_UTC, lon)
97-
# Calculate the hour of the day based on the UTC time and longitude
98-
hour = solar_hour_of_day_for_area(time_UTC=time_UTC, geometry=CoordinateArray(x=lon, y=lat))
99-
# Calculate the solar zenith angle in degrees based on the latitude, solar declination angle, and hour of the day
100-
SZA = calculate_SZA_from_DOY_and_hour(lat, lon, doy, hour)
101-
102-
# Return the calculated solar zenith angle
103-
return SZA
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from typing import Union
2+
import numpy as np
3+
from rasters import Raster
4+
from .day_angle_rad_from_DOY import day_angle_rad_from_DOY
5+
from .solar_dec_deg_from_day_angle_rad import solar_dec_deg_from_day_angle_rad
6+
from .SZA_deg_from_lat_dec_hour import SZA_deg_from_lat_dec_hour
7+
8+
def calculate_SZA_from_DOY_and_hour(
9+
lat: Union[float, np.ndarray],
10+
lon: Union[float, np.ndarray],
11+
DOY: Union[float, np.ndarray, Raster],
12+
hour: Union[float, np.ndarray, Raster]) -> Union[float, np.ndarray, Raster]:
13+
"""
14+
Calculates the solar zenith angle (SZA) in degrees based on the given UTC time, latitude, longitude, day of year, and hour of day.
15+
16+
Args:
17+
lat (Union[float, np.ndarray]): The latitude in degrees.
18+
lon (Union[float, np.ndarray]): The longitude in degrees.
19+
doy (Union[float, np.ndarray, Raster]): The day of year.
20+
hour (Union[float, np.ndarray, Raster]): The hour of the day.
21+
22+
Returns:
23+
Union[float, np.ndarray, Raster]: The calculated solar zenith angle in degrees.
24+
"""
25+
day_angle_rad = day_angle_rad_from_DOY(DOY)
26+
solar_dec_deg = solar_dec_deg_from_day_angle_rad(day_angle_rad)
27+
SZA = SZA_deg_from_lat_dec_hour(lat, solar_dec_deg, hour)
28+
29+
return SZA
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from datetime import datetime
2+
from rasters import CoordinateArray
3+
from .SZA_deg_from_lat_dec_hour import SZA_deg_from_lat_dec_hour
4+
from .calculate_SZA_from_DOY_and_hour import calculate_SZA_from_DOY_and_hour
5+
from solar_apparent_time import solar_day_of_year_for_longitude, solar_hour_of_day_for_area
6+
7+
def calculate_SZA_from_datetime(time_UTC: datetime, lat: float, lon: float):
8+
"""
9+
Calculates the solar zenith angle (SZA) in degrees based on the given UTC time, latitude, and longitude.
10+
11+
Args:
12+
time_UTC (datetime.datetime): The UTC time to calculate the SZA for.
13+
lat (float): The latitude in degrees.
14+
lon (float): The longitude in degrees.
15+
16+
Returns:
17+
float: The calculated solar zenith angle in degrees.
18+
"""
19+
# Calculate the day of year based on the UTC time and longitude
20+
doy = solar_day_of_year_for_longitude(time_UTC, lon)
21+
# Calculate the hour of the day based on the UTC time and longitude
22+
hour = solar_hour_of_day_for_area(time_UTC=time_UTC, geometry=CoordinateArray(x=lon, y=lat))
23+
# Calculate the solar zenith angle in degrees based on the latitude, solar declination angle, and hour of the day
24+
SZA = calculate_SZA_from_DOY_and_hour(lat, lon, doy, hour)
25+
26+
# Return the calculated solar zenith angle
27+
return SZA

sun_angles/calculate_daylight.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Type hinting for flexible input types
2+
from typing import Union
3+
import numpy as np
4+
from rasters import Raster, SpatialGeometry
5+
from datetime import datetime
6+
from dateutil import parser
7+
import pandas as pd
8+
9+
from solar_apparent_time import solar_day_of_year_for_longitude
10+
11+
from .SHA_deg_from_DOY_lat import SHA_deg_from_DOY_lat
12+
from .daylight_from_SHA import daylight_from_SHA
13+
14+
def calculate_daylight(
15+
time_UTC: Union[datetime, str, list, np.ndarray] = None,
16+
geometry: SpatialGeometry = None,
17+
day_of_year: Union[Raster, np.ndarray, int] = None,
18+
lat: Union[Raster, np.ndarray, float] = None,
19+
lon: Union[Raster, np.ndarray, float] = None,
20+
SHA_deg: Union[Raster, np.ndarray, float] = None
21+
) -> Union[Raster, np.ndarray]:
22+
"""
23+
Calculate the number of daylight hours for a given day and location.
24+
25+
This function flexibly computes daylight hours based on the information provided:
26+
27+
- If `SHA_deg` (sunrise hour angle in degrees) is provided, it is used directly to compute daylight hours.
28+
- If `SHA_deg` is not provided, it is calculated from `day_of_year` and `lat` (latitude in degrees).
29+
- If `lat` is not provided but a `geometry` object is given, latitude is extracted from `geometry.lat`.
30+
- If `day_of_year` is not provided but `time_UTC` is given, the function converts the UTC time(s) to day of year, accounting for longitude if available.
31+
32+
Parameters
33+
----------
34+
time_UTC : Union[datetime, str, list, np.ndarray], optional
35+
Datetime(s) in UTC. Used to determine `day_of_year` if not provided. Accepts a single datetime, a string, or an array/list of datetimes or strings.
36+
Example: `time_UTC=['2025-06-21', '2025-12-21']` or `time_UTC=datetime(2025, 6, 21)`
37+
geometry : SpatialGeometry, optional
38+
Geometry object containing latitude (and optionally longitude) information. Used if `lat` or `lon` are not provided.
39+
day_of_year : Union[Raster, np.ndarray, int], optional
40+
Day of year (1-366). Can be a Raster, numpy array, or integer. If not provided, will be inferred from `time_UTC` if available.
41+
lat : Union[Raster, np.ndarray, float], optional
42+
Latitude in degrees. Can be a Raster, numpy array, or float. If not provided, will be inferred from `geometry` if available.
43+
lon : Union[Raster, np.ndarray, float], optional
44+
Longitude in degrees. Can be a Raster, numpy array, or float. If not provided, will be inferred from `geometry` if available.
45+
SHA_deg : Union[Raster, np.ndarray, float], optional
46+
Sunrise hour angle in degrees. If provided, it is used directly and other parameters are ignored.
47+
48+
Returns
49+
-------
50+
Union[Raster, np.ndarray]
51+
Daylight hours for the given inputs. The output type matches the input type (e.g., float, array, or Raster).
52+
53+
Examples
54+
--------
55+
1. Provide SHA directly:
56+
daylight = calculate_daylight(SHA_deg=100)
57+
2. Provide day of year and latitude:
58+
daylight = calculate_daylight(day_of_year=172, lat=34.0)
59+
3. Provide UTC time and latitude:
60+
daylight = calculate_daylight(time_UTC='2025-06-21', lat=34.0)
61+
4. Provide geometry and UTC time:
62+
daylight = calculate_daylight(time_UTC='2025-06-21', geometry=my_geometry)
63+
"""
64+
65+
# If SHA_deg is not provided, calculate it from DOY and latitude
66+
if SHA_deg is None:
67+
# If latitude is not provided, try to extract from geometry
68+
if lat is None and geometry is not None:
69+
lat = geometry.lat
70+
71+
if lon is None and geometry is not None:
72+
lon = geometry.lon
73+
74+
# Handle day_of_year input: convert lists to np.ndarray
75+
if day_of_year is not None:
76+
if isinstance(day_of_year, list):
77+
day_of_year = np.array(day_of_year)
78+
79+
# Handle lat input: convert lists to np.ndarray
80+
if lat is not None:
81+
if isinstance(lat, list):
82+
lat = np.array(lat)
83+
84+
# If day_of_year is not provided, try to infer from time_UTC
85+
if day_of_year is None:
86+
# Handle string or list of strings for time_UTC
87+
if isinstance(time_UTC, str):
88+
time_UTC = parser.parse(time_UTC)
89+
elif isinstance(time_UTC, list):
90+
time_UTC = [parser.parse(t) if isinstance(t, str) else t for t in time_UTC]
91+
elif isinstance(time_UTC, np.ndarray) and time_UTC.dtype.type is np.str_:
92+
time_UTC = np.array([parser.parse(t) for t in time_UTC])
93+
94+
# If lon is None, raise a clear error
95+
if lon is None:
96+
raise ValueError("Longitude (lon) must be provided when using time_UTC to infer day_of_year.")
97+
98+
day_of_year = solar_day_of_year_for_longitude(
99+
time_UTC=time_UTC,
100+
lon=lon
101+
)
102+
103+
SHA_deg = SHA_deg_from_DOY_lat(day_of_year, lat)
104+
105+
# Compute daylight hours from SHA_deg
106+
daylight_hours = daylight_from_SHA(SHA_deg)
107+
108+
return daylight_hours
File renamed without changes.

sun_angles/day_angle.py renamed to sun_angles/day_angle_rad_from_DOY.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,7 @@ def day_angle_rad_from_DOY(DOY: Union[Raster, np.ndarray]) -> Union[Raster, np.n
2424
Reference:
2525
Duffie, J. A., & Beckman, W. A. (2013). Solar Engineering of Thermal Processes (4th ed.). Wiley.
2626
"""
27+
# Accept lists, scalars, or arrays; convert to np.ndarray if needed
28+
if isinstance(DOY, list):
29+
DOY = np.array(DOY)
2730
return (2 * np.pi * (DOY - 1)) / 365

sun_angles/daylight.py

Lines changed: 0 additions & 113 deletions
This file was deleted.

0 commit comments

Comments
 (0)