Log 04: July 20th, 2025 - Polar Pi
Behold! We have OpenMeteo.jl now! It is undocumented, untested, and unregistered but it exists as a package now and fully utilizes the FlatBuffer specification from OpenMeteo! It supports all endpoints from OpenMeteo including the forecast, air quality, and additional modules here: 🌦️ Docs | Open-Meteo.com
It only has one function right now but here is an example on how to use it:
using OpenMeteo
url = "https://api.open-meteo.com/v1/forecast"
params = Dict(
"latitude" => "42.3751",
"longitude" => "-71.1056",
"daily" => [
"temperature_2m_max",
"temperature_2m_min",
"sunset",
"sunrise",
"wind_speed_10m_max",
"wind_gusts_10m_max",
"wind_speed_10m_min",
"wind_gusts_10m_min"
],
"hourly" => [
"temperature_2m",
"uv_index",
"precipitation_probability",
"precipitation",
"relative_humidity_2m",
"wind_speed_10m",
"wind_direction_10m"
],
"timezone" => "America/New_York",
"forecast_days" => "1",
"timeformat" => "unixtime",
"wind_speed_unit" => "mph",
"temperature_unit" => "fahrenheit",
"precipitation_unit" => "inch"
)
method = "GET"
verify = true
resp = OpenMeteo.request_weather_api(url, params = params, method = method, verify = verify)[1]
hourly_vars = Dict(params["hourly"] .=> [resp.hourly.variables[x].values for x in 1:length(params["hourly"])])
daily_vars = Dict(params["daily"] .=> [resp.daily.variables[x].values for x in 1:length(params["daily"])])
sunset = resp.daily.variables[3].values_int64
sunrise = resp.daily.variables[4].values_int64
Which gives an output response like:
julia> resp
OpenMeteo.WeatherApiResponse{OpenMeteo.VariablesWithTime{OpenMeteo.VariableWithValues}, OpenMeteo.VariablesWithTime{OpenMeteo.VariableWithValues}, OpenMeteo.Vari
ablesWithTime{OpenMeteo.VariableWithValues}, OpenMeteo.VariablesWithTime{OpenMeteo.VariableWithValues}, OpenMeteo.VariablesWithTime{OpenMeteo.VariableWithValues}
}
latitude: Float32 42.372776f0
longitude: Float32 -71.09675f0
elevation: Float32 12.0f0
generation_time_milliseconds: Float32 0.106573105f0
location_id: Int64 0
model: OpenMeteo.Model OpenMeteo.Modelbest_match
utc_offset_seconds: Int32 -14400
timezone: String "America/New_York"
timezone_abbreviation: String "GMT-4"
current: Nothing nothing
daily: OpenMeteo.VariablesWithTime{OpenMeteo.VariableWithValues}
hourly: OpenMeteo.VariablesWithTime{OpenMeteo.VariableWithValues}
minutely_15: Nothing nothing
six_hourly: Nothing nothing
On this occasion, I celebrate with a gif:
So what do I do with this unstoppable power? Well, visualize it of course!
Here are some rough cut attempts at the moment:
using CairoMakie
using OpenMeteo
url = "https://api.open-meteo.com/v1/forecast"
params = Dict(
"latitude" => "42.3751",
"longitude" => "-71.1056",
"daily" => [
"temperature_2m_max",
"temperature_2m_min",
"sunset",
"sunrise",
"wind_speed_10m_max",
"wind_gusts_10m_max",
"wind_speed_10m_min",
"wind_gusts_10m_min"
],
"hourly" => [
"temperature_2m",
"uv_index",
"precipitation_probability",
"precipitation",
"relative_humidity_2m",
"wind_speed_10m",
"wind_direction_10m"
],
"timezone" => "America/New_York",
"forecast_days" => "1",
"timeformat" => "unixtime",
"wind_speed_unit" => "mph",
"temperature_unit" => "fahrenheit",
"precipitation_unit" => "inch"
)
method = "GET"
verify = true
resp = OpenMeteo.request_weather_api(url, params = params, method = method, verify = verify)[1]
hourly_vars = Dict(params["hourly"] .=> [resp.hourly.variables[x].values for x in 1:length(params["hourly"])])
daily_vars = Dict(params["daily"] .=> [resp.daily.variables[x].values for x in 1:length(params["daily"])])
sunset = resp.daily.variables[3].values_int64
sunrise = resp.daily.variables[4].values_int64
f = Figure(size = (400, 240));
ax = PolarAxis(
f[1, 1],
rminorgridvisible = false,
rminorticksvisible = false,
rgridvisible = false,
rticks = LinearTicks(2),
rticklabelsize = 10,
rticklabelsvisible = false,
rgridcolor = :blue,
rlimits = (minimum(hourly_vars["temperature_2m"]) - 5, maximum(hourly_vars["temperature_2m"]) + 5),
thetaminorticksvisible = false,
thetaticks = (collect(-3pi/2:15 * pi / 180:pi/2)[1:end-1], vcat("0", ["$x" for x in 23:-1:1])),
thetaticklabelsize = 10,
thetaminorticks = IntervalsBetween(3),
thetagridcolor = :lightgray
)
lines!(ax, collect(-3pi/2:15 * pi / 180:pi/2), vcat(reverse(hourly_vars["temperature_2m"]), hourly_vars["temperature_2m"][1]), color = :dimgray)
markers = []
for val in reverse(hourly_vars["uv_index"])
println(val)
if 0 <= val < 3
push!(markers, :circle)
elseif 3 <= val < 6
push!(markers, :utriangle)
elseif 6 <= val < 8
push!(markers, :diamond)
elseif 8 <= val < 11
push!(markers, :star4)
elseif 11 <= val
push!(markers, :xcross)
end
end
scatter!(ax, collect(-3pi/2:15 * pi / 180:pi/2)[1:end-1], reverse(hourly_vars["temperature_2m"]), color = :black, markersize = 12, marker = markers)
Which gives the following:
The symbols correspond to UV index (with circle being least dangerous and xcross being the most dangerous) and this represents temperature change over 24 hours. I still need a better way to display the band minimum and maximum weather temperatures. Don’t know an elegant way yet…
From there, we have windspeed and direction!
f = Figure(size = (400, 240));
ax = PolarAxis(
f[1, 1],
rminorgridvisible = false,
rminorticksvisible = false,
rgridvisible = false,
rticks = LinearTicks(2),
rticklabelsize = 10,
rticklabelsvisible = true,
rgridcolor = :blue,
rlimits = (minimum(hourly_vars["wind_speed_10m"]), maximum(hourly_vars["wind_speed_10m"]) + 2),
thetaminorticksvisible = false,
thetaticks = (collect(-3pi/2:15 * pi / 180:pi/2)[1:end-1], vcat("0", ["$x" for x in 23:-1:1])),
thetaticklabelsize = 10,
thetaminorticks = IntervalsBetween(3),
thetagridcolor = :lightgray
)
lines!(ax, collect(-3pi/2:15 * pi / 180:pi/2), vcat(reverse(hourly_vars["wind_speed_10m"]), hourly_vars["wind_speed_10m"][1]), color = :dimgray)
scatter!(ax, collect(-3pi/2:15 * pi / 180:pi/2)[1:end-1], reverse(hourly_vars["wind_speed_10m"]), color = :black, markersize = 12, marker = '↑', rotation = hourly_vars["wind_direction_10m"] .* (pi / 180))
It’s not quite so clean yet, but we are getting there.
Now, I need to figure out how to plot a radial bar chart with probabilities of precipitation and precipitation amount. Additionally, I have no idea how to make this into one figure that shares concentric rings. Ideally, windspeed would be inside the temperature and UV plot. Does anyone know how to do this?
At any rate, we are progressing quite well! Until the next adventurous hacking – maybe at JuliaCon with @mitiemannn? I’ll be bringing my Pi and display with me.
Happy to hack when not engaged with JuliaHealth shenanigans or AlgebraicJulia stuff too! Just grab me!
~ tcp