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)
5×4 DataFrame
Rowdatetimeazimuthelevationzenith
ZonedDat…Float64Float64Float64
12019-01-01T00:00:00-08:00348.359-74.9761164.976
22019-01-01T01:00:00-08:0037.6062-72.1323162.132
32019-01-01T02:00:00-08:0064.8196-62.7738152.774
42019-01-01T03:00:00-08:0079.6452-51.48141.48
52019-01-01T04:00:00-08:0089.958-39.6867129.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)
5×4 DataFrame
Rowdatetimeazimuthelevationzenith
DateTimeFloat64Float64Float64
12019-01-01T00:00:00231.2389.1319880.868
22019-01-01T01:00:00240.947-0.7029290.7029
32019-01-01T02:00:00249.639-11.4634101.463
42019-01-01T03:00:00257.833-22.8309112.831
52019-01-01T04:00:00266.149-34.5543124.554