Weather Data Sources

This guide explains how PlantMeteo handles weather ingestion and export.

Design Intent

The package separates concerns:

  • ingestion/parsing (read_weather, API backends)
  • canonical weather representation (Weather, Atmosphere)
  • optional conversion to sink formats (DataFrame, custom table sinks)

This keeps model code independent of source-specific quirks.

Path 1: Local Files with read_weather

Use this path when data comes from stations, archives, or partner pipelines.

You can map source-specific column names and units to PlantMeteo variables, and get a standardized Weather table with parsed dates/durations and preserved metadata.

using PlantMeteo
using Dates

file = joinpath(dirname(dirname(pathof(PlantMeteo))), "test", "data", "meteo.csv")

meteo = read_weather(
    file,
    :temperature => :T,
    :relativeHumidity => (x -> x ./ 100) => :Rh,
    :wind => :Wind,
    :atmosphereCO2_ppm => :Cₐ,
    date_format = DateFormat("yyyy/mm/dd")
)

(length(meteo), metadatakeys(meteo))
(3, ("name", "latitude", "altitude", "use", "file"))

Robust Date Parsing for Legacy Files

Some meteorological files encode the date only on the first row and leave it empty for following rows. read_weather can now try several date formats and forward-fill missing dates:

legacy = mktempdir() do tmp
    path = joinpath(tmp, "legacy_meteo.csv")
    write(path, """
date;hour_start;hour_end;temperature;relativeHumidity;wind;clearness
2016/06/12;08:30:00;09:00:00;25;60;1.0;0.6
;09:00:00;09:30:00;25;60;1.0;0.6
""")

    read_weather(
        path,
        :temperature => :T,
        :relativeHumidity => (x -> x ./ 100) => :Rh,
        :wind => :Wind,
        date_formats = (DateFormat("yyyy/mm/dd"), DateFormat("yyyy-mm-dd")),
        forward_fill_date = true,
    )
end

(legacy.date[1], legacy.date[2])
(DateTime("2016-06-12T08:30:00"), DateTime("2016-06-12T09:00:00"))

You can also validate chronology explicitly with:

Round-trip Export with write_weather

Write a clean weather file for reuse, including metadata.

For example we can verify the exported file reproduces the same records:

roundtrip_ok = mktempdir() do tmp
    out = joinpath(tmp, "meteo_out.csv")
    write_weather(out, meteo)
    meteo2 = read_weather(out; duration = Dates.Minute)
    length(meteo) == length(meteo2) && meteo2[1].T == meteo[1].T
end

roundtrip_ok
true

Path 2: API Retrieval with get_weather

Use this path when you don't have local data or need forecast/history for coordinates and dates.

The default API uses Open-Meteo. You can control units, timezone, and model selection before requests, and get a configured API object you can reuse.

params = OpenMeteo(
    units = OpenMeteoUnits(
        temperature_unit = "celsius",
        windspeed_unit = "ms",
        precipitation_unit = "mm"
    ),
    timezone = "UTC",
    models = ["best_match"]
)

(params.timezone, params.models, params.units.temperature_unit)
("UTC", ["best_match"], "celsius")

Define Your Own API Via Our Interface

Any type implementing get_forecast can be plugged into get_weather. For example, we can define a DemoAPI that returns a fixed weather series for any request:

struct DemoAPI <: PlantMeteo.AbstractAPI end

function PlantMeteo.get_forecast(::DemoAPI, lat, lon, period; verbose=true, kwargs...)
    hours = DateTime(period[1]):Hour(1):DateTime(period[end]) + Hour(23)
    rows = Atmosphere[
        Atmosphere(
            date = t,
            duration = Hour(1),
            T = 20.0,
            Wind = 1.0,
            Rh = 0.6,
            P = 101.3,
            Ri_SW_f = 250.0
        )
        for t in hours
    ]
    TimeStepTable(rows, (latitude = lat, longitude = lon, source = "demo"))
end

period = Date(2025, 1, 1):Day(1):Date(2025, 1, 2)
demo = get_weather(48.8566, 2.3522, period; api = DemoAPI())

(length(demo), first(demo).date, last(demo).date)
(48, DateTime("2025-01-01T00:00:00"), DateTime("2025-01-02T23:00:00"))

Switch the Output Sink

You can use the sink keyword to get a different output format. For example, we can get a summary of the demo API output instead of the full weather table:

summary = get_weather(
    48.8566,
    2.3522,
    period;
    api = DemoAPI(),
    sink = x -> (n = length(x), first_T = first(x.T), last_date = last(x.date))
)

summary
(n = 48, first_T = 20.0, last_date = DateTime("2025-01-02T23:00:00"))

The sink argument can be any sink that implements the Tables.jl interface, like DataFrame. This allows you to integrate with downstream code that expects specific formats without changing the core API logic.