---
title: "Units, vertical references, and grouped groundwater observations"
description: "Keep units, vertical datums, screened intervals, and water-bearing groups explicit before interpolation."
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Units, vertical references, and grouped groundwater observations}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(
  collapse = TRUE, comment = "#>", fig.bg = "white",
  dev.args = list(bg = "white"), fig.width = 8, fig.height = 6,
  dpi = 144, fig.align = "center"
)
```

Groundwater elevation calculated from depth requires an explicit sign and
measurement reference. With positive depth below land surface:

`groundwater elevation = land-surface elevation - depth to water`.

```{r units}
library(potentiomap)
data("synthetic_wells")
points <- ps_potentiometric_points(
  synthetic_wells,
  x = "x", y = "y", depth_col = "depth_to_water",
  surface_col = "surface_elevation", name_col = "well_id",
  crs = "EPSG:26916", depth_unit = "m", surface_unit = "m",
  output_unit = "ft", vertical_datum = "synthetic example datum",
  surface_reference = "land_surface", depth_sign = "positive_down",
  metadata_mode = "strict"
)
ps_metadata(points)
knitr::kable(data.frame(
  well = head(terra::values(points)$Name),
  groundwater_elevation_ft = head(terra::values(points)$Z)
))
```

```{r vertical-reference-plot, fig.alt="Land-surface and calculated groundwater elevations for synthetic wells, with vertical connecting lines representing positive depth to water."}
point_values <- terra::values(points)
land_surface_ft <- synthetic_wells$surface_elevation / 0.3048
order_wells <- order(land_surface_ft)
x <- seq_along(order_wells)

par(bg = "white")
plot(
  x, land_surface_ft[order_wells], type = "n",
  ylim = range(c(land_surface_ft, point_values$Z)),
  xlab = "Synthetic wells ordered by land-surface elevation",
  ylab = "Elevation above synthetic datum (ft)",
  main = "Vertical reference used to calculate groundwater elevation"
)
segments(
  x, land_surface_ft[order_wells],
  x, point_values$Z[order_wells],
  col = "#7b8790", lwd = 1.5
)
points(x, land_surface_ft[order_wells], pch = 24, bg = "#c77b30")
points(x, point_values$Z[order_wells], pch = 21, bg = "#2c7fb8")
legend(
  "topleft",
  legend = c("Land surface", "Groundwater elevation", "Depth to water"),
  pch = c(24, 21, NA), pt.bg = c("#c77b30", "#2c7fb8", NA),
  lty = c(NA, NA, 1), col = c("#17252d", "#17252d", "#7b8790"),
  bty = "n"
)
```

The supported length units are metres and international feet, using exactly
`1 ft = 0.3048 m`. A horizontal CRS does not supply a vertical datum.
potentiomap records the stated datum but does not transform between vertical
datums, infer datum compatibility, or guess measuring-point offsets. R-level
metadata may not survive every GIS vector format, so preserve a sidecar manifest
when those details must accompany exports.

Separate events and water-bearing units before fitting surfaces.

```{r grouped}
grouped_data <- transform(
  synthetic_wells,
  event = rep(c("spring", "autumn"), each = 16),
  unit = rep(c("upper", "lower"), times = 16)
)
grouped <- ps_interpolate_grouped(
  grouped_data, c("event", "unit"), value = "gw_elevation",
  name_col = "well_id", crs = "EPSG:26916",
  methods = "IDW", grid_res = 400
)
knitr::kable(grouped$manifest)
```

```{r grouped-maps, fig.width=10, fig.height=8, fig.alt="Four synthetic IDW surfaces for spring and autumn observations in upper and lower units, displayed with a common modeled-head scale."}
group_surfaces <- lapply(grouped$results, function(x) x$surfaces$IDW)
common_range <- range(vapply(
  group_surfaces,
  function(x) as.numeric(terra::minmax(x)),
  numeric(2)
))
common_range <- common_range + c(-1, 1) * diff(common_range) * 0.001

old_par <- par(mfrow = c(2, 2), bg = "white", mar = c(3, 3, 3, 4))
for (group_id in names(group_surfaces)) {
  terra::plot(
    group_surfaces[[group_id]],
    col = hcl.colors(48, "RdYlBu", rev = TRUE),
    type = "continuous", range = common_range, fill_range = TRUE,
    plg = list(nice = 5),
    main = gsub("__", " — ", group_id)
  )
}
par(old_par)
```

Each row is assigned to exactly one group. A shared template standardizes output
geometry but does not share observations or fitted information. Empty and failed
groups remain in the manifest. A simple progress callback can report group
completion without adding another package dependency.
