# ============================================================================ # SCRIPT 40: Weekly Mosaic Creation (Per-Field CI Aggregation) # ============================================================================ # PURPOSE: # Aggregate daily per-field CI TIFFs into weekly mosaics. Handles multi-date # merging, cloud masking, and produces 5-band weekly output for reporting # and KPI calculations. Supports both per-field and grid-based tile architecture. # # INPUT DATA: # - Source: laravel_app/storage/app/{project}/field_tiles_CI/{FIELD}/{DATE}.tif # - Format: GeoTIFF (5-band: R,G,B,NIR,CI as float32) # - Dates: All available dates within week range # # OUTPUT DATA: # - Destination: laravel_app/storage/app/{project}/weekly_mosaic/{FIELD}/ # - Format: GeoTIFF (5-band: R,G,B,NIR,CI) # - Naming: week_WW_YYYY.tif (WW = ISO week, YYYY = ISO year) # # USAGE: # Rscript 40_mosaic_creation_per_field.R [end_date] [offset] [project] # # Example (Windows PowerShell): # & "C:\Program Files\R\R-4.4.3\bin\x64\Rscript.exe" r_app/40_mosaic_creation_per_field.R 2026-01-12 7 angata # # PARAMETERS: # - end_date: End date for processing (character, YYYY-MM-DD format, default today) # - offset: Days to look back (numeric, default 7 for one week) # - project: Project name (character) - angata, chemba, xinavane, esa, simba # # CLIENT TYPES: # - cane_supply (ANGATA): Yes - weekly monitoring # - agronomic_support (AURA): Yes - field health reporting # # DEPENDENCIES: # - Packages: terra, sf, tidyverse, lubridate # - Utils files: parameters_project.R, 40_mosaic_creation_per_field_utils.R # - Input data: Daily per-field CI TIFFs from Script 20 # - Data directories: field_tiles_CI/, weekly_mosaic/ # # NOTES: # - Aggregation method: Maximum CI value per pixel across week (handles clouds) # - ISO week-year used for consistent date handling across year boundaries # - Supports both single-file and tiled mosaic architectures # - Output feeds Scripts 80 (KPI calculations) and 90/91 (reporting) # - Critical for trend analysis: week-over-week CI comparison # # RELATED ISSUES: # SC-112: Script 40 cleanup (deleted legacy mosaic utils files) # SC-108: Core pipeline improvements # # ============================================================================ # 1. Load required packages # ----------------------- suppressPackageStartupMessages({ library(sf) library(terra) library(tidyverse) library(lubridate) library(here) }) # 2. Main execution function # ------------------------- main <- function() { cat("\n") cat("========================================================\n") cat(" Script 40: Per-Field Weekly Mosaic Creation\n") cat("========================================================\n\n") # Capture command line arguments args <- commandArgs(trailingOnly = TRUE) # ==== Process Arguments ==== # Project directory if (length(args) >= 3 && !is.na(args[3])) { project_dir <- as.character(args[3]) } else if (exists("project_dir", envir = .GlobalEnv)) { project_dir <- get("project_dir", envir = .GlobalEnv) } else { project_dir <- "angata" message("[INFO] No project_dir provided. Using default: angata") } assign("project_dir", project_dir, envir = .GlobalEnv) message(paste("[INFO] Project:", project_dir)) # End date if (length(args) >= 1 && !is.na(args[1])) { end_date <- as.Date(args[1], format = "%Y-%m-%d") if (is.na(end_date)) { message("[WARNING] Invalid end_date. Using current date.") end_date <- Sys.Date() } } else { end_date <- Sys.Date() message(paste("[INFO] No end_date provided. Using current date:", format(end_date))) } # Offset (days back) if (length(args) >= 2 && !is.na(args[2])) { offset <- as.numeric(args[2]) if (is.na(offset) || offset <= 0) { message("[WARNING] Invalid offset. Using default: 7 days") offset <- 7 } } else { offset <- 7 message("[INFO] No offset provided. Using default: 7 days") } # ==== Load Configuration ==== # Set working directory if needed if (basename(getwd()) == "r_app") { setwd("..") } tryCatch({ source("r_app/parameters_project.R") message("[INFO] ✓ Loaded parameters_project.R") }, error = function(e) { stop("[ERROR] Failed to load parameters_project.R: ", e$message) }) tryCatch({ source("r_app/40_mosaic_creation_per_field_utils.R") message("[INFO] ✓ Loaded 40_mosaic_creation_per_field_utils.R") }, error = function(e) { stop("[ERROR] Failed to load utilities: ", e$message) }) # ==== Get Project Directories ==== setup <- setup_project_directories(project_dir) # Determine input/output directories # Input: field_tiles_CI/ (from Script 20) field_tiles_ci_dir <- setup$field_tiles_ci_dir # Output: weekly_mosaic/ (for Scripts 90/91) weekly_mosaic_output_dir <- file.path(setup$laravel_storage_dir, "weekly_mosaic") message(paste("[INFO] Input directory:", field_tiles_ci_dir)) message(paste("[INFO] Output directory:", weekly_mosaic_output_dir)) # ==== Validate Input Directory ==== if (!dir.exists(field_tiles_ci_dir)) { stop(paste("[ERROR] Input directory not found:", field_tiles_ci_dir, "\nScript 20 (CI extraction) must be run first to create per-field TIFFs.")) } # Check if directory has any TIFFs field_dirs <- list.dirs(field_tiles_ci_dir, full.names = FALSE, recursive = FALSE) if (length(field_dirs) == 0) { stop(paste("[ERROR] No field subdirectories found in:", field_tiles_ci_dir)) } message(paste("[INFO] Found", length(field_dirs), "field directories")) # ==== Generate Date Range ==== dates <- date_list(end_date, offset) # Validate week calculation message(sprintf("[INFO] Requested offset: %d days", offset)) message(sprintf("[INFO] End date: %s", format(end_date, "%Y-%m-%d"))) message(sprintf("[INFO] Start date: %s", format(dates$start_date, "%Y-%m-%d"))) message(sprintf("[INFO] Calculating ISO week: %d", dates$week)) message(sprintf("[INFO] Calculating ISO year: %d", dates$year)) # ==== Create Per-Field Weekly Mosaics ==== created_files <- create_all_field_weekly_mosaics( dates = dates, field_tiles_ci_dir = field_tiles_ci_dir, output_dir = weekly_mosaic_output_dir ) # ==== Summary ==== message("\n") message("========================================================") message(paste(" COMPLETED")) message(paste(" Created:", length(created_files), "weekly field mosaics")) message("========================================================\n") if (length(created_files) > 0) { message("[SUCCESS] Weekly mosaics ready for reporting (Scripts 90/91)") } else { message("[WARNING] No mosaics created - check input data") } return(invisible(created_files)) } # Execute main if script is run directly if (sys.nframe() == 0) { tryCatch({ created <- main() quit(save = "no", status = 0) }, error = function(e) { message(paste("\n[FATAL ERROR]", e$message)) quit(save = "no", status = 1) }) }