Rangeland Analysis Platform (RAP) & JRN NPP

Author

Greg Maurer, Darren James

Published

July 31, 2026

library(tidyverse) library(EDIutils) library(sf) library(terra) library(rapr) library(tidyterra)

Data access at EDI requires authentication. The easiest way is to create

an API key at https://auth.edirepository.org. Once you have that, place

it in your script.

mykey <- “

Log in to EDI rogrammatically with an API key

EDIutils::login(key = mykey)

Set the dataset ID

datasetID <- “knb-lter-jrn.210011003.106”

Read the list of “entities” in the dataset

ents <- read_data_entity_names(datasetID)

Read the raw entity data

raw <- read_data_entity(datasetID, ents[1, “entityId”])

Now read the raw data as a CSV

anpp_data <- readr::read_csv(file = raw)

Explore the data a little

head(anpp_data) unique(anpp_data\(site) length(unique(anpp_data\)site))

Now make a simple plot

g <- ggplot(data=anpp_data, aes(x=year, y=npp_g_m2, col=site)) + geom_line()

Show it

g

Now lets subset and plot site==IBPE

ibpe_anpp <- anpp_data |> filter(site==“IBPE”) g2 <- ggplot(data=ibpe_anpp, aes(x=year, y=npp_g_m2, col=site)) + geom_line()

g2

Set working directory to location where script is saved

#setwd(dirname(rstudioapi::getActiveDocumentContext()$path)) #dir(“data”)

Import total annual ANPP data

downloaded from https://portal.edirepository.org/nis/mapbrowse?scope=knb-lter-jrn&identifier=210011003

#anpp_data <- read_csv(“data/JRN011003_NPP_annual_site_summary.csv”)

Subset IBPE total ANPP data

#ibpe_anpp <- anpp_data %>% # dplyr::filter(site == “IBPE”)

Check avialble years

range(ibpe_anpp$year) # 1990 to 2022

Quick graph of the data

ggplot(ibpe_anpp, aes(x = year, y = npp_g_m2)) + geom_line()

Import shapefile of NPP sites

This can be downloaded from the Jornada Geoportal if you already

have access

NPP_sf <- st_read(“shp/Jornada_LTER_net_primary_production_study_sites_NPP_study_prj011__polygons.shp”)

Look at the columns of the data in the shapefile

glimpse(NPP_sf)

Subset IBPE site

IBPE_sf <- NPP_sf %>% dplyr::filter(site == “IBPE”) %>% st_transform(“EPSG:4326”)

Check coordinate reference system

st_crs(IBPE_sf)

Quick figure of IBPE site polygon

ggplot(IBPE_sf) + geom_sf()

Use rapr::get_rap() function to access RAP data

Note that our shapefile is already in the “ESPG:4326” coordinate reference system

?get_rap

Currently, RAP biomass and production data are only available as a 30m pixel product

Each pixel is approximately 30m X 3m = 900m^2 in size

st_area(IBPE_sf) / 900 # The IBPE site is about 5.4 RAP pixels in size

Buffer IBPE area by 30m to ensure complete coverage

Note that the LENGTHUNIT is already in meters

IBPE_buffer <- IBPE_sf %>% st_buffer(dist = 30)

Figure of IBPE site with 30m buffer

ggplot() + geom_sf(data = IBPE_buffer) + geom_sf(data = IBPE_sf)

Import 30m Landsat production data

IBPE_prod30m <- get_rap( x = IBPE_buffer, years = c(1990:2022), product = “vegetation-biomass”, source = “rap-30m”, sds = FALSE, verbose = TRUE )

Inspect the SpatRaster

IBPE_prod30m

Check the names

names(IBPE_prod30m)

Use the default terra::plot() function to explore the data

Plots the first 16 rasters

plot(IBPE_prod30m)

Graph the second raster with ggplot and geom_spatraster()

Overlay the IBPE site polygon

ggplot() + geom_spatraster(data = IBPE_prod30m[[2]]) + geom_sf(data = IBPE_sf, col = “black”, fill = NA) + labs(title = “Single Layer from SpatRaster”)

Use the terra::extract() function to calculate mean biomass at the IBPE site for each raster

Using exact = TRUE will caclualte a weigthed mean that uses partial pixels in the site boundary

Using exact = FALSE (default) will only use the pixels whose centroids are within the site boundary (4 pixels in this case)

rap_biomass_extract <- terra::extract(IBPE_prod30m, IBPE_sf, fun = “mean”, exact = TRUE)

Check the names of the extracted values

names(rap_biomass_extract)

Use tidyr::pivot_longer() to transpose the date from wide to long

rap_biomass_df <- rap_biomass_extract %>% pivot_longer(cols = starts_with(“vegetation”), names_to = “raster”, values_to = “biomass”)

Check the text strings of raster names

We need to find a way to extract the year from each of thes text strings

unique(rap_biomass_df$raster)

We can use the paste() function to construct a vector of all our years

separated by the “bar” symbol which functions as a n “OR” operator

paste(1990:2022, collapse = “|”)

Use stringr::str_extract() to extract the year from the raster name, then covert to numeric

rap_biomass_df <- rap_biomass_extract %>% pivot_longer(cols = starts_with(“vegetation”), names_to = “raster”, values_to = “biomass”) %>% mutate(year = str_extract(string = raster, pattern = paste(1990:2022, collapse = “|”)) %>% as.numeric())

For each year, sum the biomass for annual forbs and grasses and perennial forbs and grasses

rap_biomass_year <- rap_biomass_df %>% group_by(year) %>% summarise(rap_herb_biomass = sum(biomass))

Quick plot of the data

ggplot(rap_biomass_year, aes(x = year, y = rap_herb_biomass)) + geom_line()

We want to join this data (by year) to the filed-based ANPP data

ibpe_anpp %>% left_join(rap_biomass_year)

Convert RAP from lbs/acre to grams/m^2

rap_biomass_convert <- rap_biomass_year %>% mutate(rap_g_m2 = rap_herb_biomass * 453.59237 / 4046.85642)

Now merge and transpose from wide to long

ibpe_biomass_merge <- ibpe_anpp %>% left_join(rap_biomass_convert %>% dplyr::select(year, rap_g_m2)) %>% pivot_longer(cols = c(npp_g_m2, rap_g_m2), names_to = “production_type”, values_to = “production_g_m2”)

Quick graph of the data

ggplot(ibpe_biomass_merge, aes(x = year, y = production_g_m2, col = production_type)) + geom_line()

Make the graph prettier

ibpe_biomass_figure <- ibpe_biomass_merge %>% mutate(source = case_when(production_type == “npp_g_m2” ~ “Field-based total ANPP (all vegetation except Yucca elata”, production_type == “rap_g_m2” ~ “RAP-based herbaceous biomass (forbs and grasses only)”))

Check the range of the production values

range(ibpe_biomass_figure$production_g_m2)

ggplot(data = ibpe_biomass_figure, aes(x = year, y = production_g_m2, col = source)) + theme_bw() + geom_line(linewidth = 1) + scale_x_continuous(limits = c(1989.5, 2022.5), expand = c(0, 0), breaks = seq(from = 1990, to = 2022, by = 5), minor_breaks = 1990:2022) + theme(legend.position = “bottom”, legend.direction = “vertical”) + xlab(““) + ylab(bquote(”Production (g m^-2 * “)”)) + ggtitle(“Comparison of RAP and field-based production at IBPE site”, subtitle = “Years: 1990-2022”) + theme(legend.title = element_text(size = 14), legend.text = element_text(size = 13)) + theme(axis.text = element_text(size = 11)) + theme(axis.title = element_text(size = 13)) + scale_color_manual(values = c(“turquoise”, “darksalmon”)) + scale_y_continuous(limits = c(0, 340), expand = c(0,0)) ````