Skip to content

Commit 25f45fa

Browse files
Merge pull request #18 from NewGraphEnvironment/9-consumer-extract
Add consumer extract pipeline (#9, #10, #11)
2 parents 4de84ee + ffd4f38 commit 25f45fa

18 files changed

Lines changed: 659 additions & 0 deletions

NAMESPACE

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,9 @@
33
export(cd_cache_clear)
44
export(cd_cache_info)
55
export(cd_cache_path)
6+
export(cd_catalog)
7+
export(cd_catalog_default)
8+
export(cd_crop)
9+
export(cd_extract)
610
export(cd_periods)
711
export(cd_variables)

R/cd_catalog.R

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#' Load and parse a STAC catalog
2+
#'
3+
#' Reads a static STAC catalog JSON file and returns a tidy tibble
4+
#' of available climate data COGs. This is the entry point for
5+
#' consumer-side workflows.
6+
#'
7+
#' @param catalog Path or URL to a STAC catalog JSON file.
8+
#' Defaults to [cd_catalog_default()].
9+
#'
10+
#' @return A tibble with columns:
11+
#' \describe{
12+
#' \item{variable}{Climate variable short name (e.g., "tmean").}
13+
#' \item{period}{Temporal aggregation period (e.g., "annual").}
14+
#' \item{href}{Resolved path or URL to the COG file.}
15+
#' }
16+
#'
17+
#' @examples
18+
#' cd_catalog(
19+
#' system.file("extdata", "example_catalog.json", package = "cd")
20+
#' )
21+
#'
22+
#' @export
23+
cd_catalog <- function(catalog = cd_catalog_default()) {
24+
cat_json <- jsonlite::read_json(catalog)
25+
base_dir <- dirname(catalog)
26+
27+
items <- cat_json$items
28+
if (is.null(items) || length(items) == 0) {
29+
return(tibble::tibble(variable = character(), period = character(), href = character()))
30+
}
31+
32+
tibble::tibble(
33+
variable = vapply(items, function(x) x$properties$`cd:variable`, character(1)),
34+
period = vapply(items, function(x) x$properties$`cd:period`, character(1)),
35+
href = vapply(items, function(x) {
36+
h <- x$assets$data$href
37+
if (grepl("^(http|s3|/)", h)) h else file.path(base_dir, h)
38+
}, character(1))
39+
)
40+
}
41+
42+
#' Default STAC catalog URL
43+
#'
44+
#' Returns the default S3-hosted STAC catalog URL for the cd package.
45+
#' Override with `options(cd.catalog_url = "...")`.
46+
#'
47+
#' @return Character URL.
48+
#'
49+
#' @examples
50+
#' cd_catalog_default()
51+
#'
52+
#' @export
53+
cd_catalog_default <- function() {
54+
getOption("cd.catalog_url", default = "https://nge-bc-ce.s3.us-west-2.amazonaws.com/cd/catalog.json")
55+
}

R/cd_crop.R

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#' Crop and mask a raster to an AOI
2+
#'
3+
#' Reads a COG (local or remote) and crops it to a user-supplied area
4+
#' of interest polygon. Works with local file paths and remote URLs
5+
#' via GDAL's `/vsicurl/`.
6+
#'
7+
#' @param href Character. Path or URL to a COG or raster file.
8+
#' @param aoi An `sf` or `SpatVector` polygon to crop to.
9+
#'
10+
#' @return A [terra::SpatRaster] cropped and masked to the AOI.
11+
#'
12+
#' @examples
13+
#' href <- system.file("extdata", "example_climate.tif", package = "cd")
14+
#' aoi <- sf::st_read(
15+
#' system.file("extdata", "example_aoi.gpkg", package = "cd"),
16+
#' quiet = TRUE
17+
#' )
18+
#' r <- cd_crop(href, aoi)
19+
#' r
20+
#'
21+
#' @export
22+
cd_crop <- function(href, aoi) {
23+
r <- terra::rast(href)
24+
if (inherits(aoi, "sf") || inherits(aoi, "sfc")) {
25+
aoi <- terra::vect(aoi)
26+
}
27+
terra::crop(r, aoi, snap = "out", mask = TRUE)
28+
}

R/cd_extract.R

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#' Extract zonal mean time series for an AOI
2+
#'
3+
#' For each variable and period in the catalog, crops the COG to the AOI
4+
#' and computes the spatial mean per year (band). Returns a tidy tibble
5+
#' of raw climate values suitable for [cd_baseline()], [cd_anomaly()],
6+
#' or [cd_trend()].
7+
#'
8+
#' @param catalog A tibble from [cd_catalog()] with columns
9+
#' `variable`, `period`, `href`.
10+
#' @param aoi An `sf` or `SpatVector` polygon.
11+
#' @param variables Character vector of variables to extract.
12+
#' Defaults to all variables in `catalog`.
13+
#' @param periods Character vector of periods to extract.
14+
#' Defaults to all periods in `catalog`.
15+
#' @param years Optional integer vector to filter specific years.
16+
#'
17+
#' @return A tibble with columns:
18+
#' \describe{
19+
#' \item{variable}{Climate variable short name.}
20+
#' \item{period}{Temporal aggregation period.}
21+
#' \item{year}{Year (integer).}
22+
#' \item{value}{Spatial mean of the climate value for this AOI.}
23+
#' }
24+
#'
25+
#' @examples
26+
#' catalog <- cd_catalog(
27+
#' system.file("extdata", "example_catalog.json", package = "cd")
28+
#' )
29+
#' aoi <- sf::st_read(
30+
#' system.file("extdata", "example_aoi.gpkg", package = "cd"),
31+
#' quiet = TRUE
32+
#' )
33+
#' cd_extract(catalog, aoi)
34+
#'
35+
#' @export
36+
cd_extract <- function(catalog, aoi,
37+
variables = catalog$variable,
38+
periods = catalog$period,
39+
years = NULL) {
40+
rows <- catalog[catalog$variable %in% variables & catalog$period %in% periods, ]
41+
42+
results <- lapply(seq_len(nrow(rows)), function(i) {
43+
r <- cd_crop(rows$href[i], aoi)
44+
means <- terra::global(r, fun = "mean", na.rm = TRUE)
45+
yr <- as.integer(names(r))
46+
47+
tibble::tibble(
48+
variable = rows$variable[i],
49+
period = rows$period[i],
50+
year = yr,
51+
value = round(means$mean, 4)
52+
)
53+
})
54+
55+
out <- dplyr::bind_rows(results)
56+
57+
if (!is.null(years)) {
58+
out <- out[out$year %in% years, ]
59+
}
60+
61+
out
62+
}

data-raw/example_climate_tmean.R

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Create example data for cd package tests and vignettes
2+
#
3+
# Crops real ERA5-Land NC data to a small anonymous bbox.
4+
# Run interactively — requires bc_climate_anomaly repo at ../bc_climate_anomaly/
5+
#
6+
# Output:
7+
# inst/extdata/example_aoi.gpkg — simple bbox polygon (no place names)
8+
# inst/extdata/example_climate.tif — tmean annual, 10 years, COG format
9+
# inst/extdata/example_catalog.json — minimal STAC catalog pointing to the COG
10+
11+
library(terra)
12+
library(sf)
13+
library(jsonlite)
14+
15+
# -- Source data ---------------------------------------------------------------
16+
nc_path <- "../bc_climate_anomaly/ano_clm_trn_data/tmean_ano_annual_1950_+_res25.nc"
17+
stopifnot(file.exists(nc_path))
18+
19+
# -- Create AOI (anonymous bbox) ----------------------------------------------
20+
# Small area in northern BC — just coordinates, no place names
21+
bbox <- c(xmin = -126.75, ymin = 54.1, xmax = -125.75, ymax = 54.7)
22+
aoi <- st_as_sfc(st_bbox(bbox, crs = 4326))
23+
aoi <- st_sf(geometry = aoi)
24+
25+
sf::st_write(aoi, "inst/extdata/example_aoi.gpkg", delete_dsn = TRUE, quiet = TRUE)
26+
message("Wrote: inst/extdata/example_aoi.gpkg")
27+
28+
# -- Crop and subset raster ---------------------------------------------------
29+
r <- rast(nc_path)
30+
31+
# Use first 10 years (bands 1-10) for manageable size
32+
r_sub <- r[[1:10]]
33+
34+
# Crop to AOI
35+
r_crop <- crop(r_sub, vect(aoi), snap = "out")
36+
37+
# Set band names to years
38+
names(r_crop) <- 1951:1960
39+
40+
# Write as COG
41+
writeRaster(
42+
r_crop,
43+
"inst/extdata/example_climate.tif",
44+
filetype = "COG",
45+
overwrite = TRUE,
46+
gdal = c("COMPRESS=DEFLATE")
47+
)
48+
message("Wrote: inst/extdata/example_climate.tif (",
49+
file.size("inst/extdata/example_climate.tif"), " bytes)")
50+
51+
# -- Create minimal STAC catalog ----------------------------------------------
52+
catalog <- list(
53+
type = "Catalog",
54+
id = "cd-example",
55+
stac_version = "1.0.0",
56+
description = "Example climate data for cd package tests",
57+
links = list(
58+
list(rel = "root", href = "./example_catalog.json", type = "application/json"),
59+
list(rel = "item", href = "#tmean-annual", type = "application/json")
60+
),
61+
items = list(
62+
list(
63+
type = "Feature",
64+
stac_version = "1.0.0",
65+
id = "tmean-annual",
66+
geometry = NULL,
67+
bbox = as.numeric(bbox),
68+
properties = list(
69+
`cd:variable` = "tmean",
70+
`cd:period` = "annual",
71+
datetime = NULL,
72+
start_datetime = "1951-01-01T00:00:00Z",
73+
end_datetime = "1960-12-31T23:59:59Z"
74+
),
75+
links = list(),
76+
assets = list(
77+
data = list(
78+
href = "./example_climate.tif",
79+
type = "image/tiff; application=geotiff; profile=cloud-optimized",
80+
title = "Mean temperature annual values"
81+
)
82+
)
83+
)
84+
)
85+
)
86+
87+
write_json(catalog, "inst/extdata/example_catalog.json", pretty = TRUE, auto_unbox = TRUE)
88+
message("Wrote: inst/extdata/example_catalog.json")
89+
message("Done.")

inst/extdata/example_aoi.gpkg

96 KB
Binary file not shown.

inst/extdata/example_catalog.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"type": "Catalog",
3+
"id": "cd-example",
4+
"stac_version": "1.0.0",
5+
"description": "Example climate data for cd package tests",
6+
"links": [
7+
{
8+
"rel": "root",
9+
"href": "./example_catalog.json",
10+
"type": "application/json"
11+
},
12+
{
13+
"rel": "item",
14+
"href": "#tmean-annual",
15+
"type": "application/json"
16+
}
17+
],
18+
"items": [
19+
{
20+
"type": "Feature",
21+
"stac_version": "1.0.0",
22+
"id": "tmean-annual",
23+
"geometry": {},
24+
"bbox": [-126.75, 54.1, -125.75, 54.7],
25+
"properties": {
26+
"cd:variable": "tmean",
27+
"cd:period": "annual",
28+
"datetime": {},
29+
"start_datetime": "1951-01-01T00:00:00Z",
30+
"end_datetime": "1960-12-31T23:59:59Z"
31+
},
32+
"links": [],
33+
"assets": {
34+
"data": {
35+
"href": "./example_climate.tif",
36+
"type": "image/tiff; application=geotiff; profile=cloud-optimized",
37+
"title": "Mean temperature annual values"
38+
}
39+
}
40+
}
41+
]
42+
}

inst/extdata/example_climate.tif

15 KB
Binary file not shown.

man/cd_catalog.Rd

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/cd_catalog_default.Rd

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)