Basic Example
This section demonstrates basic usage of SolarPosition.jl for calculating solar positions.
First, we need to import the package along with some supporting packages. Although not strictly necessary, it is common to work with time zone-aware datetimes using the TimeZones.jl
package.
using SolarPosition
# supporting packages
using Dates
using DataFrames
using TimeZones
We can define an observer location using the Observer
struct, which takes latitude, longitude, and altitude (in meters) as arguments.
obs = Observer(37.7749, -122.4194, 100.0) # San Francisco
Observer{Float64}(37.7749, -122.4194, 100.0, 0.659296379611606, -2.136621598315946)
Finally, we can calculate the solar position for a specific date and time using the solar_position
function. The time should be provided as a ZonedDateTime
to ensure correct handling of time zones.
tz = tz"America/Los_Angeles"
zdt = ZonedDateTime(2023, 6, 21, 12, 0, 0, tz) # Summer solstice noon
position = solar_position(obs, zdt)
println("Solar position at summer solstice noon in San Francisco:")
println("Azimuth: $(round(position.azimuth, digits=2))°")
println("Elevation: $(round(position.elevation, digits=2))°")
Solar position at summer solstice noon in San Francisco:
Azimuth: 128.06°
Elevation: 69.04°
Using DateTime in UTC
Alternatively, we can directly pass a DateTime (assumed to be in UTC)
zdt_utc = DateTime(zdt, UTC)
position_utc = solar_position(obs, zdt_utc)
println("Azimuth (UTC): $(round(position_utc.azimuth, digits=2))°")
println("Elevation (UTC): $(round(position_utc.elevation, digits=2))°")
Azimuth (UTC): 128.06°
Elevation (UTC): 69.04°
It is also possible to calculate solar positions for multiple timestamps at once by passing a vector or range of ZonedDateTime
or DateTime
objects.
# Generate hourly timestamps for a whole year
times = ZonedDateTime(DateTime(2019), tz):Hour(1):ZonedDateTime(DateTime(2020), tz)
# This returns a NamedTuple of Vectors
positions = solar_position(obs, times)
# We can inspect the first few entries by converting to a DataFrame
first(DataFrame(positions), 5)
Row | datetime | azimuth | elevation | zenith |
---|---|---|---|---|
ZonedDat… | Float64 | Float64 | Float64 | |
1 | 2019-01-01T00:00:00-08:00 | 348.359 | -74.9761 | 164.976 |
2 | 2019-01-01T01:00:00-08:00 | 37.6062 | -72.1323 | 162.132 |
3 | 2019-01-01T02:00:00-08:00 | 64.8196 | -62.7738 | 152.774 |
4 | 2019-01-01T03:00:00-08:00 | 79.6452 | -51.48 | 141.48 |
5 | 2019-01-01T04:00:00-08:00 | 89.958 | -39.6867 | 129.687 |
Passing Latitude and Longitude as keyword arguments
Lastly, we show that we can pass latitude and longitude directly without creating an Observer
object. In this case the library will create an Observer
object for us.
times = DateTime(2019):Hour(1):DateTime(2020)
position_direct = solar_position(times; latitude=37.7749, longitude=-122.4194, altitude=100.0)
first(DataFrame(position_direct), 5)
Row | datetime | azimuth | elevation | zenith |
---|---|---|---|---|
DateTime | Float64 | Float64 | Float64 | |
1 | 2019-01-01T00:00:00 | 231.238 | 9.13198 | 80.868 |
2 | 2019-01-01T01:00:00 | 240.947 | -0.70292 | 90.7029 |
3 | 2019-01-01T02:00:00 | 249.639 | -11.4634 | 101.463 |
4 | 2019-01-01T03:00:00 | 257.833 | -22.8309 | 112.831 |
5 | 2019-01-01T04:00:00 | 266.149 | -34.5543 | 124.554 |