Fluxnet forcing data: LAI, radiation, and atmospheric variables

In this tutorial, we will demonstrate how we read in forcing data at a Fluxnet site using our ClimaLand infrastructure. To see an example running a simulation at a Fluxnet site, please see the corresponding tutorials for the SoilCanopyModel or LandModel To access the forcing data (LAI from MODIS, SWd, LWd, Tair, qair, Pair, and precipitation from fluxtower data), you first need the the fluxtower site ID. Currently, ClimaLand provides an interface for working with four fluxtower sites; adding support for a much larger set of sites is in progress. The sites we support are Vaira Ranch (USVar), Missouri Ozark (US-MOz), Niwot Ridge (US-NR1), and Harvard Forest (US-Ha1).

using Dates
import ClimaParams as CP
using ClimaLand
using ClimaLand.Domains: Column
import ClimaLand.Parameters as LP
using DelimitedFiles
using CairoMakie
import ClimaLand.FluxnetSimulations as FluxnetSimulations

Define the floating point precision desired (64 or 32 bit), and get the parameter set holding constants used across CliMA Models.

const FT = Float32;
earth_param_set = LP.LandParameters(FT);

Pick a site ID; convert the dash to an underscore:

site_ID = "US-NR1";
site_ID_val = FluxnetSimulations.replace_hyphen(site_ID)
:US_NR1

The functions we use below use multiple dispatch, treating the siteIDval as a Julia type.

First, we need the latitude and longitude of the site. These are used to get the zenith angle as a function of time, and to look up default parameters using the global ClimaLand parameter maps. We also need the offset of the local time of the site in hours from UTC. This is because ClimaLand simulations are carried out in UTC, while the fluxtower data is reported in local time.

(; time_offset, lat, long) =
    FluxnetSimulations.get_location(FT, Val(site_ID_val))
(time_offset = 7, lat = 40.0329f0, long = -105.5464f0)

ClimaLand also needs to know the height at which the atmospheric data was recorded.

(; atmos_h) = FluxnetSimulations.get_fluxtower_height(FT, Val(site_ID_val))
(atmos_h = 21.5f0,)

It is also useful to know the bounds of the data, in UTC, to use as the start and stop date of the simulation.

(start_date, stop_date) =
    FluxnetSimulations.get_data_dates(site_ID, time_offset)
(Dates.DateTime("2010-01-01T07:00:00"), Dates.DateTime("2011-01-01T06:30:00"))

Now we can construct the forcing objects. Under the hood, this function finds the local path to the fluxtower data (and downloads it if it is not present) and reads the data in. It then creates two objects, one called atmos, of type PrescibedAtmosphere, and one called radiation, of type PrescribedRadiativeFluxes. These encode the data in interpolating functions which allow us to estimate the forcing at any time during the simulation using linear interpolation across gaps in the data.

(; atmos, radiation) = FluxnetSimulations.prescribed_forcing_fluxnet(
    site_ID,
    lat,
    long,
    time_offset,
    atmos_h,
    start_date,
    earth_param_set,
    FT,
);

The atmosphere object holds the air temperature, pressure, specific humidity, wind speed, and liquid and solid precipitation fluxes. Since many fluxtower sites do not measure the snow and rain fluxes separately, we estimate them internally. This is optional, and by providing the kwarg split_precip = false, you can change the behavior to return all measured precip as a liquid water flux (no snow). The radiation object holds the downwelling short and long wave radiative fluxes. The diffuse fraction is estimated internally using an empirical function, and the zenith angle is computed analytically.

The simulation time is measured in seconds since the start_date. We can get the atmospheric temperature at the start_date as follows:

sim_time = 0.0
T = [NaN]
evaluate!(T, atmos.T, sim_time);
@show T
1-element Vector{Float64}:
 261.98999999999995

Note that evaluate! updates T in place. This is important: in our simulations, we allocate memory for the forcing, and then update the values at each step. If we did not do this, the dynamic memory allocation would cause the simulation to be incredibly slow. We can plot the interpolated air temperature for the first day:

sim_times = 0.0:1800.0:86400.0 # one day in seconds
air_temps = [];
for sim_time in sim_times
    evaluate!(T, atmos.T, sim_time)
    push!(air_temps, T[1])
end
fig = CairoMakie.Figure()
ax = CairoMakie.Axis(
    fig[1, 1],
    ylabel = "Temperature (K)",
    xlabel = "Date",
    title = "Near-surface air temperature at Niwot Ridge",
)
lines!(ax, Second.(sim_times) .+ start_date, air_temps)
CairoMakie.save("air_temp.png", fig);

We do something very similar with LAI, but now the data is coming from MODIS. In this case, we use nearest-neighbor interpolatation to move from the gridded MODIS data to the LAI at the latitude and longitude of the site. To do spatial interpolation, we need to create a ClimaLand domain.

domain =
    Column(; zlim = (FT(-3.0), FT(0.0)), nelements = 10, longlat = (long, lat))
surface_space = domain.space.surface;

Get the paths to each year of MODIS data within the start and stop dates.

modis_lai_ncdata_path = ClimaLand.Artifacts.modis_lai_multiyear_paths(;
    start_date,
    end_date = stop_date,
)
LAI = ClimaLand.prescribed_lai_modis(
    modis_lai_ncdata_path,
    surface_space,
    start_date,
);

Just like with the air temperature, the LAI is an object that we can use to linearly interpolate observed LAI to any simulation time.

It can also be useful to know the maximum LAI at a site. To do so, we can call, for the first year of data (first element of modis_lai_ncdata_path):

maxLAI =
    FluxnetSimulations.get_maxLAI_at_site(modis_lai_ncdata_path[1], lat, long)
1.4455398f0

Fluxnet comparison data

To assess performance of the simulation, we need comparison data from the site. This is complicated since different sites provided different data (e.g. soil moisture and temperature and depth). ClimaLand provides a function to get the comparison data for a select number of variables:

VariableUnitClimaLand short nameFluxnet Column name
Gross primary prod.mol/m^2/s"gpp""GPP_DT_VUT_REF"
Latent heat fluxW/m^2/s"lhf""LE_CORR"
Sensible heat fluxW/m^2/s"shf""H_CORR"
Upwelling SWW/m^2/s"swu""SW_OUT"
Upwelling LWW/m^2/s"lwu""LW_OUT"
Soil water content"swc""SWC_F_MDS_1"
Soil temperatureK"tsoil""TS_F_MDS_1"
Total precipitationm/s"precip""P_F"
comparison_data = FluxnetSimulations.get_comparison_data(site_ID, time_offset);
@show propertynames(comparison_data)
(:UTC_datetime, :gpp, :lhf, :shf, :swu, :lwu, :swc, :tsoil, :precip)

If a column is missing, the column is not returned. Missing values are replaced with the mean of the not missing values.

The data we use to force the simulations and to compare the results against were obtained from Ameriflux:

US-Moz: https://doi.org/10.17190/AMF/1854370 Citation: Jeffrey Wood, Lianhong Gu (2025), AmeriFlux FLUXNET-1F US-MOz Missouri Ozark Site, Ver. 5-7, AmeriFlux AMP, (Dataset). https://doi.org/10.17190/AMF/1854370

US-NR1: https://doi.org/10.17190/AMF/1871141 Citation: Peter D. Blanken, Russel K. Monson, Sean P. Burns, David R. Bowling, Andrew A. Turnipseed (2022), AmeriFlux FLUXNET-1F US-NR1 Niwot Ridge Forest (LTER NWT1), Ver. 3-5, AmeriFlux AMP, (Dataset). https://doi.org/10.17190/AMF/1871141

US-Var: https://doi.org/10.17190/AMF/1993904 Citation: Siyan Ma, Liukang Xu, Joseph Verfaillie, Dennis Baldocchi (2023), AmeriFlux FLUXNET-1F US-Var Vaira Ranch- Ione, Ver. 3-5, AmeriFlux AMP, (Dataset). https://doi.org/10.17190/AMF/1993904

US-Ha1: https://doi.org/10.17190/AMF/1871137 Citation: J. William Munger (2022), AmeriFlux FLUXNET-1F US-Ha1 Harvard Forest EMS Tower (HFR1), Ver. 3-5, AmeriFlux AMP, (Dataset). https://doi.org/10.17190/AMF/1871137


This page was generated using Literate.jl.