# Global setup + packages
set.seed(42)
options(width = 100)
<- c(
pkgs "sf","terra","raster","dplyr","automap","gstat","mapview","stars",
"readxl","stringr","tidyr","purrr","lubridate","rprojroot",
"exactextractr","zoo","ggplot2","viridis","mgcv","randomForest","fields","sp","deldir",
"leaflet","DT","htmltools","jsonlite","shiny" # viewer deps
)<- setdiff(pkgs, rownames(installed.packages()))
need if (length(need)) install.packages(need, dependencies = TRUE)
invisible(lapply(pkgs, function(p) suppressPackageStartupMessages(library(p, character.only = TRUE))))
Ecowitt Air Temperature — End-to-End applied Workflow
EON Summer School 2025
1 Overview
This document demonstrates and explains a six-stage spatial pipeline for Ecowitt temperature data:
- Ingest & clean: load two loggers, harmonize names, and aggregate to 3-hourly.
- Interpolation preview: per-timestep KED (universal kriging) and a multi-panel plot.
- Scale inference (L): fit a global variogram to get L50 / L95 (spatial correlation ranges).
- Scale-matched predictors: build DEM-derived rasters (optionally slope/aspect/TRI) at the right scale.
- Tune \(R^*\): select an optimal drift radius \(R\) with block-CV (U-curve), then benchmark methods.
- Diagnostics: export products, report scales, and compute an optional error budget.
Key concept: We use two DEMs on purpose — DEM_scale
at native/coarser resolution drives scales, tuning, folds, CV, and error budget. DEM_render
is an upsampled/aggregated DEM for pretty maps and interpolation output. This separation prevents the tuner from collapsing to 10 m just because pixels are tiny.
1.1 0) Requirements & Helpers
# Small utilities (kept identical to your working script where applicable)
# SpatRaster sicher "pinnen" (Datei-basiert) und als gültiges Objekt zurückgeben
<- function(r, crs = NULL, dir = NULL, name = "pinned") {
.pin_rast stopifnot(inherits(r, "SpatRaster"))
if (is.null(dir)) dir <- file.path(getwd(), "run_cache")
if (!dir.exists(dir)) dir.create(dir, recursive = TRUE)
<- file.path(dir, paste0(name, ".tif"))
f # immer schreiben → garantiert datei-gestützt und voll materialisiert
::writeRaster(r, f, overwrite = TRUE)
terra<- terra::rast(f)
rp if (!is.null(crs)) {
# nur projezieren, wenn noch nicht identisch
<- try(terra::crs(rp, proj=TRUE) == as.character(crs), silent = TRUE)
same if (!isTRUE(same)) rp <- terra::project(rp, as.character(crs), method = "near")
}# Sanity check
invisible(terra::nlyr(rp))
rp
}
# Prüfer: „lebt“ der Pointer?
<- function(r) {
.is_alive_spatr inherits(r, "SpatRaster") && !inherits(try(terra::nlyr(r), silent = TRUE), "try-error")
}
# Safe, URL-friendly file slug
<- function(x) {
slug <- gsub("[^0-9A-Za-z_-]+","-", x)
x <- gsub("-+","-", x)
x gsub("(^-|-$)","", x)
}
# Human-readable time labels from AYYYY... keys
<- function(x) {
pretty_time vapply(x, function(s) {
if (grepl("^A\\d{14}$", s)) {
<- as.POSIXct(substr(s, 2, 15), format = "%Y%m%d%H%M%S", tz = "UTC")
ts format(ts, "%Y-%m-%d %H:%M")
else if (grepl("^A\\d{8}(_D)?$", s)) {
} <- as.Date(substr(s, 2, 9), format = "%Y%m%d")
ts format(ts, "%Y-%m-%d")
else s
} character(1))
},
}
# Pick the most data-dense time-slice (max number of finite observations)
<- function(sf_wide, var_names) {
pick_densest_index <- sapply(var_names, function(v) sum(is.finite(sf_wide[[v]])))
nn which.max(nn)
}
# Build figure descriptions (viewer)
<- function(fig_dir, pick_ts) {
build_explanations <- slug(pretty_time(pick_ts))
ts_label <- c(
files "timeseries_panel_grid.png",
"timeseries_panel_grid.pdf",
sprintf("u_curve_%s.png", ts_label),
sprintf("u_curve_extras_%s.png", ts_label),
sprintf("benchmark_%s.png", ts_label),
sprintf("benchmark_extras_%s.png", ts_label)
)<- file.path(fig_dir, files)
paths <- c(
desc "Per-timestep KED previews; dots=stations; red=plot boundary.",
"Same as PNG, vector PDF.",
"U-curve for tuning R via block-CV (drift-only).",
"U-curve with extra predictors.",
"Method comparison at R* (lower RMSE is better).",
"Benchmark with extras at R*."
)<- file.exists(paths)
keep <- as.list(desc[keep]); names(out) <- basename(paths[keep])
out
out }
Why this matters: using fine pixels for scale estimation will cap \(R\) at a few pixels, resulting in the “10 m fallback”. Keep
DEM_scale
at a realistic native/coarser resolution for L/R.
1.2 1) Project & Paths
# Robust project root finder and paths
<- rprojroot::find_rstudio_root_file()
wd
source(file.path(wd, "block4_5/all_functions_1.R")) # consolidated toolkit
<- file.path(wd, "block4_5/data_2024/copernicus_DEM.tif")
fn_DTM <- file.path(wd, "block4_5/data_2024/stations_prelim_modifiziert.gpkg")
fn_stations <- file.path(wd, "block4_5/data_2024/plot.shp")
fn_area <- file.path(wd, "block4_5/data_2024/all_GW1000A-WIFIFC29.xlsx")
fn_temp_FC29 <- file.path(wd, "block4_5/data_2024/all_GW1000A-WIFIDB2F.xlsx")
fn_temp_DB2F <- file.path(wd, "block4_5/data_2024/climdata.RDS")
cleandata_rds
<- file.path(wd, "block4_5/interpolated")
out_dir <- file.path(out_dir, "fig")
fig_dir <- file.path(out_dir, "methods_compare")
method_dir <- file.path(out_dir, "report")
report_dir for (d in c(out_dir, fig_dir, method_dir, report_dir)) if (!dir.exists(d)) dir.create(d, recursive = TRUE)
# CRS
<- "EPSG:32633" # UTM zone 33N
epsg <- sf::st_crs(epsg) sf_crs_utm33
1.3 2) Base Data & DEM Strategy
# DEMs
<- terra::rast("data_2024/DEM.tif") |> terra::project(epsg)
DEM_scale <- terra::aggregate(DEM_scale, c(20, 20)) # coarsen ~20–25 m (as in your working code)
DEM_scale names(DEM_scale) <- "altitude"
<- DEM_scale |> terra::aggregate(fact = c(10, 10)) # for rendering products
DEM_render
cat("DEM_scale res (m): ", paste(terra::res(DEM_scale), collapse=" x "), "\n")
DEM_scale res (m): 2.00223686801611 x 2.0022368680127
cat("DEM_render res (m):", paste(terra::res(DEM_render), collapse=" x "), "\n")
DEM_render res (m): 20.0223686801606 x 20.022368680127
# Stations and plot boundary → same CRS
<- sf::st_read(fn_stations, quiet = TRUE) |> sf::st_transform(sf_crs_utm33)
stations_pos <- sf::st_read(fn_area, quiet = TRUE) |> sf::st_transform(sf_crs_utm33) |> sf::st_make_valid()
plot_area
# Altitude from DEM_scale (not the upsampled one)
<- stations_pos %>%
stations_pos ::mutate(altitude = exactextractr::exact_extract(DEM_scale, sf::st_buffer(stations_pos, 1), "mean")) dplyr
|
| | 0%
|
|====== | 7%
|
|============= | 14%
|
|=================== | 21%
|
|========================== | 29%
|
|================================ | 36%
|
|======================================= | 43%
|
|============================================= | 50%
|
|=================================================== | 57%
|
|========================================================== | 64%
|
|================================================================ | 71%
|
|======================================================================= | 79%
|
|============================================================================= | 86%
|
|==================================================================================== | 93%
|
|==========================================================================================| 100%
1.4 3) Ecowitt Ingestion, Cleaning, Aggregation
<- extract_ecowitt_core_vars(fn_temp_FC29) temp_FC29
New names:
New names:
• `` -> `...1`
• `` -> `...2`
• `` -> `...3`
• `` -> `...4`
• `` -> `...5`
• `` -> `...6`
• `` -> `...7`
• `` -> `...8`
• `` -> `...9`
• `` -> `...10`
• `` -> `...11`
• `` -> `...12`
• `` -> `...13`
• `` -> `...14`
• `` -> `...15`
• `` -> `...16`
• `` -> `...17`
• `` -> `...18`
• `` -> `...19`
• `` -> `...20`
• `` -> `...21`
• `` -> `...22`
• `` -> `...23`
• `` -> `...24`
• `` -> `...25`
• `` -> `...26`
• `` -> `...27`
• `` -> `...28`
• `` -> `...29`
• `` -> `...30`
• `` -> `...31`
• `` -> `...32`
• `` -> `...33`
• `` -> `...34`
• `` -> `...35`
• `` -> `...36`
• `` -> `...37`
• `` -> `...38`
• `` -> `...39`
• `` -> `...40`
• `` -> `...41`
• `` -> `...42`
• `` -> `...43`
• `` -> `...44`
• `` -> `...45`
• `` -> `...46`
• `` -> `...47`
• `` -> `...48`
• `` -> `...49`
• `` -> `...50`
• `` -> `...51`
• `` -> `...52`
• `` -> `...53`
• `` -> `...54`
• `` -> `...55`
• `` -> `...56`
• `` -> `...57`
• `` -> `...58`
• `` -> `...59`
• `` -> `...60`
• `` -> `...61`
• `` -> `...62`
• `` -> `...63`
• `` -> `...64`
• `` -> `...65`
• `` -> `...66`
• `` -> `...67`
• `` -> `...68`
• `` -> `...69`
• `` -> `...70`
• `` -> `...71`
• `` -> `...72`
• `` -> `...73`
• `` -> `...74`
• `` -> `...75`
• `` -> `...76`
• `` -> `...77`
• `` -> `...78`
• `` -> `...79`
• `` -> `...80`
<- extract_ecowitt_core_vars(fn_temp_DB2F) temp_DB2F
New names:
New names:
• `` -> `...1`
• `` -> `...2`
• `` -> `...3`
• `` -> `...4`
• `` -> `...5`
• `` -> `...6`
• `` -> `...7`
• `` -> `...8`
• `` -> `...9`
• `` -> `...10`
• `` -> `...11`
• `` -> `...12`
• `` -> `...13`
• `` -> `...14`
• `` -> `...15`
• `` -> `...16`
• `` -> `...17`
• `` -> `...18`
• `` -> `...19`
• `` -> `...20`
• `` -> `...21`
• `` -> `...22`
• `` -> `...23`
• `` -> `...24`
• `` -> `...25`
• `` -> `...26`
• `` -> `...27`
• `` -> `...28`
• `` -> `...29`
• `` -> `...30`
• `` -> `...31`
• `` -> `...32`
• `` -> `...33`
• `` -> `...34`
• `` -> `...35`
• `` -> `...36`
• `` -> `...37`
• `` -> `...38`
• `` -> `...39`
• `` -> `...40`
• `` -> `...41`
• `` -> `...42`
• `` -> `...43`
• `` -> `...44`
• `` -> `...45`
• `` -> `...46`
• `` -> `...47`
• `` -> `...48`
• `` -> `...49`
• `` -> `...50`
• `` -> `...51`
• `` -> `...52`
• `` -> `...53`
• `` -> `...54`
• `` -> `...55`
• `` -> `...56`
• `` -> `...57`
<- merge_ecowitt_logger_vars(temp_FC29, temp_DB2F)
t_rh_all
# Clean display names and map to verbose station names
for (meas in c("temperature","humidity")) {
<- t_rh_all[[meas]] %>%
t_rh_all[[meas]] ::rename_with(~ to_verbose(.x, ifelse(meas=="temperature","Temperature","Humidity")), -Time) %>%
dplyrclean_names()
}
# Aggregate to 3-hour steps
<- t_rh_all$temperature %>%
temp_agg ::mutate(time = lubridate::floor_date(Time, "3 hours")) %>%
dplyr::group_by(time) %>%
dplyr::summarise(across(where(is.numeric), ~ mean(.x, na.rm = TRUE)), .groups = "drop")
dplyrnames(temp_agg) <- clean_ids(names(temp_agg))
# long → wide matrix: station rows, time columns
<- temp_agg %>%
temp_matrix ::pivot_longer(cols = -time, names_to = "stationid", values_to = "value") %>%
tidyr::pivot_wider(names_from = time, values_from = value)
tidyr
# Join to station geometry and altitude
<- stations_pos %>% dplyr::mutate(stationid = to_verbose(stationid))
stations_pos <- dplyr::left_join(stations_pos, temp_matrix, by = "stationid")
m
# Hygiene
$stationid <- gsub("\\(℃\\)|\\(%\\)|\\(\\%\\)", "", stations_pos$stationid)
stations_pos$stationid <- gsub("\\(℃\\)|\\(%\\)|\\(\\%\\)", "", m$stationid)
mnames(m) <- fix_names(names(m))
saveRDS(m, cleandata_rds)
1.5 4) Interpolation Preview (Per-Timestep KED)
<- 5
min_pts <- as.list(grep("^A\\d{8,14}", names(m), value = TRUE))
vars
<- lapply(vars, function(v) {
kriged_list interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty")
})
Interpolating: A20230827150000
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230827150000
Interpolating: A20230827180000
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230827180000
Interpolating: A20230828150000
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230828150000
Interpolating: A20230828180000
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230828180000
Interpolating: A20230828210000
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230828210000
Interpolating: A20230829
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230829
Interpolating: A20230829030000
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230829030000
Interpolating: A20230829060000
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230829060000
Interpolating: A20230829090000
[using universal kriging]
✔ Written: /home/creu/edu/gisma-courses/EON2025/block4_5/interpolated/A20230829090000_interpolated.tif
Interpolating: A20230829120000
[using universal kriging]
✔ Written: /home/creu/edu/gisma-courses/EON2025/block4_5/interpolated/A20230829120000_interpolated.tif
Interpolating: A20230829150000
[using universal kriging]
✔ Written: /home/creu/edu/gisma-courses/EON2025/block4_5/interpolated/A20230829150000_interpolated.tif
Interpolating: A20230829180000
[using universal kriging]
✔ Written: /home/creu/edu/gisma-courses/EON2025/block4_5/interpolated/A20230829180000_interpolated.tif
Interpolating: A20230829210000
[using universal kriging]
✔ Written: /home/creu/edu/gisma-courses/EON2025/block4_5/interpolated/A20230829210000_interpolated.tif
Interpolating: A20230830
[using universal kriging]
✔ Written: /home/creu/edu/gisma-courses/EON2025/block4_5/interpolated/A20230830_interpolated.tif
Interpolating: A20230830030000
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230830030000
Interpolating: A20230830060000
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230830060000
Interpolating: A20230830090000
[using universal kriging]
✔ Written: /home/creu/edu/gisma-courses/EON2025/block4_5/interpolated/A20230830090000_interpolated.tif
Interpolating: A20230830120000
[using universal kriging]
✔ Written: /home/creu/edu/gisma-courses/EON2025/block4_5/interpolated/A20230830120000_interpolated.tif
Interpolating: A20230830150000
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230830150000
Interpolating: A20230830180000
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230830180000
Interpolating: A20230830210000
[using universal kriging]
✔ Written: /home/creu/edu/gisma-courses/EON2025/block4_5/interpolated/A20230830210000_interpolated.tif
Interpolating: A20230831
[using universal kriging]
✔ Written: /home/creu/edu/gisma-courses/EON2025/block4_5/interpolated/A20230831_interpolated.tif
Interpolating: A20230831030000
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230831030000
Interpolating: A20230831060000
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230831060000
Interpolating: A20230831090000
[using universal kriging]
✔ Written: /home/creu/edu/gisma-courses/EON2025/block4_5/interpolated/A20230831090000_interpolated.tif
Interpolating: A20230831120000
[using universal kriging]
✔ Written: /home/creu/edu/gisma-courses/EON2025/block4_5/interpolated/A20230831120000_interpolated.tif
Interpolating: A20230831150000
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230831150000
Interpolating: A20230831180000
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230831180000
Interpolating: A20230831210000
[using universal kriging]
✔ Written: /home/creu/edu/gisma-courses/EON2025/block4_5/interpolated/A20230831210000_interpolated.tif
Interpolating: A20230901
Warning in interpolate_kriging(v, m, DEM_render, output_dir = out_dir, label = "pretty"): Variogram
failed for A20230901
names(kriged_list) <- vars
# Shared color scale across all timestamps
<- timeseries_panel(
panel kriged_list = kriged_list,
plot_area = plot_area,
stations_pos = stations_pos,
cells_target = 150000,
max_cols = 4,
label_pretty_time = TRUE,
out_png = file.path(fig_dir, "timeseries_panel_grid.png"),
out_pdf = file.path(fig_dir, "timeseries_panel_grid.pdf"),
fill_label = "Temperature"
)
Coordinate system already present. Adding new coordinate system, which will replace the existing
one.
$plot panel
1.6 5) Method Comparison for Densest Timestamp (Working Code)
# Densest timestamp
<- pick_densest_index(m, vars)
pick_idx <- names(m)[pick_idx]
pick_ts
# Extra predictors (kept as in your working code)
<- list(
extra_list slope = terra::terrain(DEM_scale, v = "slope", unit = "degrees"),
aspect = terra::terrain(DEM_scale, v = "aspect"),
tri = terra::terrain(DEM_scale, v = "TRI")
)
# One timestamp (densest), with extras
<- run_one(
res_one v = vars[[pick_idx]],
m = m,
DEM_render = DEM_render,
DEM_scale = DEM_scale,
method_dir = method_dir,
fig_dir = fig_dir,
report_dir = report_dir,
extra_preds = extra_list,
save_figs = TRUE
)
Warning in newton(lsp = lsp, X = G$X, y = G$y, Eb = G$Eb, UrS = G$UrS, L = G$L, : Fitting
terminated with step failure - check results carefully
Warning in log(b$scale.est): NaNs produced
Warning in log(b$scale.est): NaNs produced
Warning in newton(lsp = lsp, X = G$X, y = G$y, Eb = G$Eb, UrS = G$UrS, L = G$L, : Fitting
terminated with step failure - check results carefully
Warning in log(b$scale.est): NaNs produced
Warning in newton(lsp = lsp, X = G$X, y = G$y, Eb = G$Eb, UrS = G$UrS, L = G$L, : Fitting
terminated with step failure - check results carefully
Warning in newton(lsp = lsp, X = G$X, y = G$y, Eb = G$Eb, UrS = G$UrS, L = G$L, : Fitting
terminated with step failure - check results carefully
Warning in newton(lsp = lsp, X = G$X, y = G$y, Eb = G$Eb, UrS = G$UrS, L = G$L, : Fitting
terminated with step failure - check results carefully
Warning in log(b$scale.est): NaNs produced
Warning in newton(lsp = lsp, X = G$X, y = G$y, Eb = G$Eb, UrS = G$UrS, L = G$L, : Fitting
terminated with step failure - check results carefully
Warning in log(b$scale.est): NaNs produced
Warning in newton(lsp = lsp, X = G$X, y = G$y, Eb = G$Eb, UrS = G$UrS, L = G$L, : Fitting
terminated with step failure - check results carefully
Warning in log(b$scale.est): NaNs produced
Warning in newton(lsp = lsp, X = G$X, y = G$y, Eb = G$Eb, UrS = G$UrS, L = G$L, : Fitting
terminated with step failure - check results carefully
Warning in newton(lsp = lsp, X = G$X, y = G$y, Eb = G$Eb, UrS = G$UrS, L = G$L, : Fitting
terminated with step failure - check results carefully
Warning in newton(lsp = lsp, X = G$X, y = G$y, Eb = G$Eb, UrS = G$UrS, L = G$L, : Fitting
terminated with step failure - check results carefully
Warning in newton(lsp = lsp, X = G$X, y = G$y, Eb = G$Eb, UrS = G$UrS, L = G$L, : Fitting
terminated with step failure - check results carefully
Warning in log(b$scale.est): NaNs produced
Warning in newton(lsp = lsp, X = G$X, y = G$y, Eb = G$Eb, UrS = G$UrS, L = G$L, : Fitting
terminated with step failure - check results carefully
Warning in newton(lsp = lsp, X = G$X, y = G$y, Eb = G$Eb, UrS = G$UrS, L = G$L, : Fitting
terminated with step failure - check results carefully
[inverse distance weighted interpolation]
[using ordinary kriging]
[using universal kriging]
[inverse distance weighted interpolation]
[using ordinary kriging]
[using universal kriging]
R*: tuner failed; falling back to 56.8593 m.
Warning in x@pntr$rastDistance(target, exclude, keepNA, tolower(unit), TRUE, : GDAL Message 1:
Pixels not square, distances will be inaccurate.
[using ordinary kriging]
[inverse distance weighted interpolation]
[using universal kriging]
[using ordinary kriging]
[inverse distance weighted interpolation]
[using universal kriging]
[using ordinary kriging]
[inverse distance weighted interpolation]
[using universal kriging]
[using ordinary kriging]
[inverse distance weighted interpolation]
[using universal kriging]
[using ordinary kriging]
[inverse distance weighted interpolation]
[using universal kriging]
[using ordinary kriging]
[inverse distance weighted interpolation]
[using universal kriging]
R*: tuner failed; falling back to 56.8593 m.
Warning in x@pntr$rastDistance(target, exclude, keepNA, tolower(unit), TRUE, : GDAL Message 1:
Pixels not square, distances will be inaccurate.
[using ordinary kriging]
[inverse distance weighted interpolation]
[using universal kriging]
[using ordinary kriging]
[inverse distance weighted interpolation]
[using universal kriging]
[using ordinary kriging]
[inverse distance weighted interpolation]
[using universal kriging]
[using ordinary kriging]
[inverse distance weighted interpolation]
[using universal kriging]
[using ordinary kriging]
[inverse distance weighted interpolation]
[using universal kriging]
[using ordinary kriging]
[inverse distance weighted interpolation]
[using universal kriging]
[using ordinary kriging]
[using ordinary kriging]
[using ordinary kriging]
[using ordinary kriging]
[using ordinary kriging]
[using ordinary kriging]
[using ordinary kriging]
[using ordinary kriging]
[using ordinary kriging]
[using ordinary kriging]
[using ordinary kriging]
[using ordinary kriging]
[using ordinary kriging]
[using ordinary kriging]
[using ordinary kriging]
[using ordinary kriging]
1.7 6) (Optional) Compute All Time Steps
<- FALSE
compute_all
if (isTRUE(compute_all)) {
<- c("m","DEM_render","DEM_scale","method_dir","fig_dir","report_dir")
req <- req[!vapply(req, exists, logical(1), inherits = TRUE)]
miss if (length(miss)) stop("Fehlende Objekte im Environment: ", paste(miss, collapse = ", "))
<- function(bench_obj) {
.best_from_bench if (is.null(bench_obj) || !is.data.frame(bench_obj$table) || nrow(bench_obj$table) < 1)
return(NULL)
<- bench_obj$table
b <- b[is.finite(b$RMSE), , drop = FALSE]
b if (!nrow(b)) return(NULL)
<- b[order(b$RMSE), , drop = FALSE]
b 1, c("method","RMSE"), drop = FALSE]
b[
}
message(sprintf("Starte compute_all für %d Zeitschritte …", length(vars)))
<- setNames(lapply(vars, function(vv) {
res_all message("→ run_one: ", pretty_time(vv))
tryCatch(
run_one(
v = vv,
m = m,
DEM_render = DEM_render,
DEM_scale = DEM_scale,
method_dir = method_dir,
fig_dir = fig_dir,
report_dir = report_dir,
extra_preds = extra_list,
save_figs = TRUE,
save_tables = TRUE
),error = function(e) {
warning("run_one fehlgeschlagen für ", vv, ": ", conditionMessage(e))
NULL
}
)
}), vars)
saveRDS(res_all, file.path(report_dir, "all_results.RDS"))
<- do.call(rbind, lapply(names(res_all), function(k) {
summ <- res_all[[k]]
r if (is.null(r)) {
return(data.frame(
ts_key = k,
stamp = pretty_time(k),
R_star = NA_real_,
best_source = NA_character_, # "no_extras"/"with_extras"
best_method = NA_character_,
best_RMSE = NA_real_
))
}<- suppressWarnings(as.numeric(r$tune$R_star))
rstar if (!is.finite(rstar)) rstar <- NA_real_
<- .best_from_bench(r$bench)
b0 <- .best_from_bench(r$bench_ex)
bE
<- if (!is.null(b0) && isTRUE(is.finite(b0$RMSE))) b0$RMSE else Inf
score0 <- if (!is.null(bE) && isTRUE(is.finite(bE$RMSE))) bE$RMSE else Inf
scoreE
if (is.infinite(score0) && is.infinite(scoreE)) {
<- NA_character_; bm <- NA_character_; br <- NA_real_
src else if (score0 <= scoreE) {
} <- "no_extras"; bm <- b0$method; br <- score0
src else {
} <- "with_extras"; bm <- bE$method; br <- scoreE
src
}
data.frame(
ts_key = k,
stamp = pretty_time(k),
R_star = rstar,
best_source = src,
best_method = bm,
best_RMSE = br
)
}))
::write.csv(summ, file.path(report_dir, "summary_Rstar_bestmethod.csv"), row.names = FALSE)
utilsmessage("✔ Fertig: summary_Rstar_bestmethod.csv geschrieben.")
}
1.8 7) Save CSVs for the One Timestamp
<- pretty_time(pick_ts)
ts_label <- file.path(report_dir, sprintf("benchmark_%s.csv", slug(ts_label)))
bench_base_csv <- file.path(report_dir, sprintf("benchmark_extras_%s.csv", slug(ts_label)))
bench_ex_csv <- file.path(report_dir, sprintf("error_budget_%s.csv", slug(ts_label)))
eb_base_csv <- file.path(report_dir, sprintf("error_budget_extras_%s.csv", slug(ts_label)))
eb_ex_csv
if (is.list(res_one$bench) && is.data.frame(res_one$bench$table)) write.csv(res_one$bench$table, bench_base_csv, row.names = FALSE)
if (is.list(res_one$bench_ex) && is.data.frame(res_one$bench_ex$table)) write.csv(res_one$bench_ex$table, bench_ex_csv, row.names = FALSE)
if (is.data.frame(res_one$errtab)) write.csv(res_one$errtab, eb_base_csv, row.names = FALSE)
if (is.data.frame(res_one$errtab_ex)) write.csv(res_one$errtab_ex, eb_ex_csv, row.names = FALSE)
1.9 8) Console Summary
<- nrow(stations_pos)
n_stations <- sum(is.finite(m[[pick_ts]]))
n_pts_ts <- get_Ls(res_one$wf$L)
Ls <- if (!is.null(res_one$wf_ex)) get_Ls(res_one$wf_ex$L) else NULL
Ls_e <- suppressWarnings(as.numeric(res_one$tune$R_star))
Rstar_base <- suppressWarnings(as.numeric(res_one$tune_ex$R_star))
Rstar_ex
cat(sprintf("Chosen R (micro/local): %s / %s m\n",
ifelse(is.finite(res_one$wf$R['micro']), round(res_one$wf$R['micro']), "NA"),
ifelse(is.finite(res_one$wf$R['local']), round(res_one$wf$R['local']), "NA")
))
Chosen R (micro/local): 19 / 57 m
1.10 9) Shiny Viewer (Optional, uses existing files)
The viewer launches a Shiny app; to not block Quarto rendering, the launch is wrapped in
if (interactive())
.
# Minimal viewer injection: use your working run_mc_viewer() with robust file matching
<- function(method, ts) {
raster_path stopifnot(length(method) == 1, length(ts) == 1)
<- tolower(method)
m <- function(ts_key) {
.ts_tokens <- tolower(as.character(ts_key))
raw <- tolower(pretty_time(ts_key))
pty <- gsub("[^0-9A-Za-z_-]+","-", pty)
slug_pt <- sub("^a", "", raw)
d14 <- if (nchar(d14) >= 8) substr(d14,1,8) else NA_character_
ymd <- if (nchar(d14) >= 12) substr(d14,9,12) else NA_character_
hhmm <- if (!is.na(ymd) && !is.na(hhmm))
comp1 paste0(substr(ymd,1,4),"-",substr(ymd,5,6),"-",substr(ymd,7,8),"-",
substr(hhmm,1,2),"-",substr(hhmm,3,4)) else NA_character_
<- gsub("-", "", comp1)
comp2 <- if (!is.na(ymd)) paste0(substr(ymd,1,4),"-",substr(ymd,5,6),"-",substr(ymd,7,8)) else NA_character_
ymd_dash unique(na.omit(c(raw, slug_pt, comp1, comp2, ymd_dash, ymd)))
}<- .ts_tokens(ts)
toks <- gsub("[-_]", "[-_]", toks)
tok_rx
<- list.files(method_dir, pattern = "\\.tif$", full.names = TRUE, ignore.case = TRUE)
all_files if (!length(all_files)) return(NA_character_)
<- tolower(basename(all_files))
b
<- grepl(paste0("^", m, "_"), b)
keep_pref <- all_files[keep_pref]; b_m <- b[keep_pref]
files_m if (length(files_m)) {
<- vapply(seq_along(b_m), function(i) {
score max(c(0, vapply(tok_rx, function(rx) if (grepl(rx, b_m[i], perl = TRUE)) nchar(rx) else 0L, integer(1))))
numeric(1))
},
if (any(score > 0)) {
<- files_m[score == max(score)]
best <- tolower(basename(best))
bbest <- grep("_rstar\\.tif$", bbest)
idxR if (length(idxR)) return(best[idxR[1]])
<- grep("_l95\\.tif$", bbest)
idxL if (length(idxL)) return(best[idxL[1]])
return(best[1])
}
}
if (toupper(method) %in% c("KED","PREVIEW")) {
<- dirname(method_dir)
out_dir_local <- list.files(out_dir_local, pattern = "\\.tif$", full.names = TRUE, ignore.case = TRUE)
prev if (length(prev)) {
<- tolower(basename(prev))
bp <- paste0("^(", paste0(tok_rx, collapse = "|"), ")_interpolated(_wgs84)?\\.tif$")
rx_prev <- grepl(rx_prev, bp, perl = TRUE)
hit if (any(hit)) return(prev[which(hit)[1]])
}
}NA_character_
}
# Only launch interactively
if (interactive()) {
<- build_explanations(fig_dir = fig_dir, pick_ts = vars[[pick_idx]])
explanations run_mc_viewer(
vars = vars,
method_dir = method_dir,
fig_dir = fig_dir,
stations_pos = stations_pos,
plot_area = plot_area,
wf = res_one$wf,
wf_ex = res_one$wf_ex,
tune = res_one$tune,
tune_ex = res_one$tune_ex,
bench = res_one$bench,
bench_ex = res_one$bench_ex,
tab_err = res_one$errtab,
tab_err_ex = res_one$errtab_ex,
explanations = explanations
) }