Using the Makie.jl plotting extension

SolarPosition.jl provides a plotting extension based on Makie.jl.

To use it, simply import both the SolarPosition and Makie packages:

using SolarPosition
using CairoMakie

# supporting packages
using Dates
using TimeZones
using DataFrames

plotsDir = joinpath(dirname(pathof(SolarPosition)), "..", "docs", "build", "plots")
mkpath(plotsDir)
"/home/runner/work/SolarPosition.jl/SolarPosition.jl/src/../docs/build/plots"

This example notebook is based on the pvlib sun path example.

Basic Sun Path Plotting

Let's start by defining an observer location and calculating solar positions for a whole year:

# Define observer location (New Delhi, India)
# Parameters: latitude, longitude, altitude in meters
tz = tz"Asia/Kolkata"
obs = Observer(28.6, 77.2, 0.0)

# 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+05:30314.042-82.2189172.219
22019-01-01T01:00:00+05:3057.3871-80.2452170.245
32019-01-01T02:00:00+05:3081.0308-67.8108157.811
42019-01-01T03:00:00+05:3090.0973-54.6909144.691
52019-01-01T04:00:00+05:3096.4595-41.5545131.555

Simple Sun Path Plot in Cartesian Coordinates

We can visualize solar positions in cartesian coordinates using the sunpathplot function:

fig = Figure(backgroundcolor = (:white, 0.0), textcolor= "#f5ab35")
ax = Axis(fig[1, 1], backgroundcolor = (:white, 0.0))
sunpathplot!(ax, positions, hour_labels = false)
fig
Example block output

Polar Coordinates with Hour Labels

We can also work directly with a DataFrame.

Plotting in polar coordinates with sunpathpolarplot may yield a more intuitive representation of the solar path. Here, we also enable hourly labels for better readability:

df = DataFrame(positions)
fig2 = Figure(backgroundcolor = :transparent, textcolor= "#f5ab35", size = (800, 600))
ax2 = PolarAxis(fig2[1, 1], backgroundcolor = "#1f2424")
# ax2 = PolarAxis(fig2[1, 1], backgroundcolor = (:white, 0.0))
sunpathpolarplot!(ax2, df, hour_labels = true)

# Draw individual days
line_objects = []
for (date, label) in [(Date("2019-03-21"), "Mar 21"),
                      (Date("2019-06-21"), "Jun 21"),
                      (Date("2019-12-21"), "Dec 21")]
    times = ZonedDateTime(DateTime(date), tz):Minute(5):ZonedDateTime(DateTime(date) + Day(1), tz)
    solpos = solar_position(obs, times)
    above_horizon = solpos.elevation .> 0
    day_df = DataFrame(solpos)
    day_filtered = day_df[above_horizon, :]
    line_obj = lines!(ax2, deg2rad.(day_filtered.azimuth), day_filtered.zenith,
                      linewidth = 2, label = label)
    push!(line_objects, line_obj)
end

# Add legend below the plot
fig2[2, 1] = Legend(fig2, line_objects, ["Mar 21", "Jun 21", "Dec 21"],
                    orientation = :horizontal, tellheight = true, backgroundcolor = :transparent)
fig2

# save(joinpath(plotsDir,"sunpathpolarplot.png"), fig2)
Example block output

The figure-8 patterns are known as analemmas, which represent the sun's position at the same time of day throughout the year.

Note that in polar coordinates, the radial distance from the center represents the zenith angle (90° - elevation). Thus, points closer to the center indicate higher elevations. Conversely, a zenith angle of more than 90° (negative elevation) indicates that the sun is below the horizon. Tracing a path from right to left corresponds to the sun's movement from east to west.

It tells us when the sun rises, reaches its highest point, and sets. And hence also the length of the day. From the figure we can also read that in June the days are longest, while in December they are shortest.

Plotting without a custom axis

Finally, we can also create plots without explicitly defining an axis beforehand. This is a more concise way to create plots, but it offers less customization:

sunpathpolarplot(df, hour_labels = true, colorbar = true)
Example block output