Getting Started

This tutorial introduces the package through a realistic first workflow.

Goal

By the end, you will have:

  • loaded weather into a Weather table
  • inspected the standardized variables
  • aggregated fine-step weather into model-friendly steps

Why This Matters

Most weather workflows fail at the interface between raw data and model expectations:

  • columns are named differently across sources
  • durations and timestamps are not consistently encoded
  • model clocks rarely match source resolution

The examples below show how PlantMeteo addresses these issues step by step.

1. Read and Standardize a Weather File

Convert heterogeneous input columns into Atmosphere-compatible variables and get a typed weather table (TimeStepTable{Atmosphere}) with known fields:

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")
)

meteo
TimeStepTable{Atmosphere{(:date, :duration,...}(3 x 29):
date duration T Wind P Rh Precipitations Cₐ e eₛ VPD ρ λ γ ε Δ clearness Ri_SW_f Ri_PAR_f Ri_NIR_f Ri_TIR_f Ri_custom_f hour_start hour_end temperature relativeHumidity Re_SW_f wind atmosphereCO2_ppm
DateTime Dates.CompoundPeriod Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Time Time Float64 Float64 Float64 Float64 Float64
1 2016-06-12T12:00:00 30 minutes 25.0 1.0 101.325 0.6 0.0 380.0 1.90812 3.1802 1.27208 1.18389 2.44188e6 0.0675791 0.602345 0.190095 0.75 Inf Inf Inf Inf Inf 12:00:00 12:30:00 25.0 60.0 500.0 1.0 380.0
2 2016-06-12T12:30:00 30 minutes 26.0 1.5 101.325 0.62 0.0 380.0 2.09238 3.37481 1.28243 1.17993 2.43951e6 0.0676446 0.610038 0.200215 0.75 Inf Inf Inf Inf Inf 12:30:00 13:00:00 26.0 62.0 500.0 1.5 380.0
3 2016-06-12T13:00:00 30 minutes 25.3 1.5 101.325 0.58 0.0 380.0 1.87776 3.23752 1.35976 1.1827 2.44117e6 0.0675987 0.60088 0.193085 0.75 Inf Inf Inf Inf Inf 13:00:00 13:30:00 25.3 58.0 500.0 1.5 380.0
Metadata: `Dict{String, Any}("name" => "Aquiares", "latitude" => 15.0, "altitude" => 100.0, "use" => [:clearness], "file" => "/home/runner/work/PlantMeteo.jl/PlantMeteo.jl/test/data/meteo.csv")`

2. Inspect the Data You Will Feed to Models

Each row is an Atmosphere. You can get a row by indexing the table with a single index:

first_row = meteo[1]
first_row
╭──── TimeStepRow ─────────────────────────────────────────────────────────────╮
  Step 1: date=2016-06-12T12:00:00, duration=30 minutes, T=25.0, Wind=1.0,    
  P=101.325, Rh=0.6, Precipitations=0.0, Cₐ=380.0, e=1.9081174488870076,      
  eₛ=3.180195748145013 ...                                                    
╰──────────────────────────────────────────────────────────────────────────────╯

And because PlantMeteo implements the Tables.jl interface, table columns are easy to inspect and manipulate like a DataFrame. For example to get the temperature column as a vector:

meteo.T
3-element Vector{Float64}:
 25.0
 26.0
 25.3

TimeStepTable also supports matrix-like indexing on rows and columns:

meteo[:T]      # full column by Symbol
meteo["T"]     # full column by String
meteo[1]       # one row
meteo[1:2]     # row subset as TimeStepTable
meteo[1, :]    # one row (matrix-like syntax)
meteo[1, :T]   # one cell by row + Symbol column
meteo[1, "T"]  # one cell by row + String column
meteo[1:2, :T] # vector slice from one column
meteo[1:2, "T"]
2-element Vector{Float64}:
 25.0
 26.0

3. Sample Weather to Match a Model Time Step

Sometimes models require weather at a coarser time step than the source data. For example, you may have hourly weather but want daily values for a crop model. PlantMeteo's sampling functions let you aggregate weather from source resolution to model resolution, and get an aggregated Atmosphere row per query step.

For example we can build a fake 2-day hourly series and sample it to get a daily-like Atmosphere for the 24th hour, using a rolling 24-source-step window:

# Build a longer hourly series for sampling demonstrations.
base = DateTime(2025, 1, 1)
meteo_hourly = Weather([
    Atmosphere(
        date = base + Hour(i),
        duration = Hour(1),
        T = 10.0 + i,
        Wind = 1.0,
        Rh = 0.55,
        P = 100.0,
        Ri_SW_f = 200.0
    )
    for i in 0:47
])

prepared = prepare_weather_sampler(meteo_hourly)
window = RollingWindow(24.0)  # trailing 24-source-step window
sampled = sample_weather(prepared, 24; window = window)

# Get the temperature from the sampled row, which is a daily-averaged value of the 24 hourly source steps:
(;sampled.duration, sampled.date, sampled.T)
(duration = Millisecond(86400000), date = DateTime("2025-01-01T23:00:00"), T = 21.5)

This value is the mean of the 24 hourly temperatures from the source data, which matches our expectation for a daily-like sample:

using Statistics
mean(meteo_hourly[i].T for i in 1:24)
21.5

4. Precompute Sampling for Whole Runs

Avoid repeated sampling work in long simulation loops, and get a cached sampled table for each requested sampling window:

tables = materialize_weather(prepared; windows = [window])
daily_like = tables[window]

(length(daily_like), daily_like[24].T ≈ sampled.T, length(meteo_hourly))
(48, true, 48)

5. Override Default Sampling Rules (Optional)

Encode model-specific aggregation rules per variable. Transforms are normalized and applied for the current call:

custom_transforms = (
    T = MeanWeighted(),
    Tmax = (source = :T, reducer = MaxReducer()),
    Tsum = (source = :T, reducer = SumReducer())
)

custom_row = sample_weather(prepared, 24; window = window, transforms = custom_transforms)
(custom_row.T, custom_row.Tmax, custom_row.Tsum)
(21.5, 33.0, 516.0)

Next Steps