# filepath: c:\Users\timon\Resilience BV\4020 SCane ESA DEMO - Documenten\General\4020 SCDEMO Team\4020 TechnicalData\WP3\smartcane\r_app\mosaic_creation.R # # MOSAIC_CREATION.R # =============== # This script creates weekly mosaics from daily satellite imagery. # It handles command-line arguments and initiates the mosaic creation process. # # Usage: Rscript mosaic_creation.R [end_date] [offset] [project_dir] [file_name] [data_source] # - end_date: End date for processing (YYYY-MM-DD format) # - offset: Number of days to look back from end_date (typically 7 for one week) # - project_dir: Project directory name (e.g., "aura", "angata", "chemba", "esa") # - file_name: Optional custom output file name (leave empty "" to use default: week_WW_YYYY.tif) # - data_source: Optional data source folder (e.g., "merged_tif" or "merged_tif_8b") # If not provided, auto-detects which folder contains actual data # # Examples: # & "C:\Program Files\R\R-4.4.3\bin\x64\Rscript.exe" r_app/40_mosaic_creation.R 2026-01-12 7 aura # & "C:\Program Files\R\R-4.4.3\bin\x64\Rscript.exe" r_app/40_mosaic_creation.R 2025-12-24 7 aura "" "merged_tif" # # 1. Load required packages # ----------------------- suppressPackageStartupMessages({ library(sf) library(terra) library(tidyverse) library(lubridate) library(here) }) # 2. Process command line arguments and run mosaic creation # ------------------------------------------------------ main <- function() { # Capture command line arguments args <- commandArgs(trailingOnly = TRUE) # Process project_dir argument with default 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 { # Default project directory project_dir <- "angata" message("No project_dir provided. Using default:", project_dir) } # Make project_dir available globally so parameters_project.R can use it assign("project_dir", project_dir, envir = .GlobalEnv) # Process end_date argument with default if (length(args) >= 1 && !is.na(args[1])) { # Parse date explicitly in YYYY-MM-DD format from command line end_date <- as.Date(args[1], format = "%Y-%m-%d") if (is.na(end_date)) { message("Invalid end_date provided. Using current date.") end_date <- Sys.Date() } } else if (exists("end_date_str", envir = .GlobalEnv)) { end_date <- as.Date(get("end_date_str", envir = .GlobalEnv)) } else { # Default to current date if no argument is provided end_date <- Sys.Date() message("No end_date provided. Using current date: ", format(end_date)) } # Process offset argument with default if (length(args) >= 2 && !is.na(args[2])) { offset <- as.numeric(args[2]) if (is.na(offset) || offset <= 0) { message("Invalid offset provided. Using default (7 days).") offset <- 7 } } else { # Default to 7 days if no argument is provided offset <- 7 message("No offset provided. Using default:", offset, "days") } # Process data_source argument (optional, passed from pipeline) # If provided, use it; otherwise auto-detect data_source_from_args <- NULL if (length(args) >= 5 && !is.na(args[5]) && nchar(args[5]) > 0) { data_source_from_args <- as.character(args[5]) message("Data source explicitly provided via arguments: ", data_source_from_args) } # 3. Initialize project configuration # -------------------------------- # Detect which data source directory exists (merged_tif or merged_tif_8b) # IMPORTANT: Only consider a folder as valid if it contains actual files laravel_storage <- here::here("laravel_app/storage/app", project_dir) # If data_source was explicitly provided from pipeline, validate it; otherwise auto-detect if (!is.null(data_source_from_args)) { # Use the provided data_source, but verify it has data proposed_path <- file.path(laravel_storage, data_source_from_args) has_data <- dir.exists(proposed_path) && length(list.files(proposed_path, pattern = "\\.tif$")) > 0 if (has_data) { data_source <- data_source_from_args message("✓ Using provided data source '", data_source, "' - contains files") } else { message("WARNING: Provided data source '", data_source_from_args, "' is empty or doesn't exist. Auto-detecting...") data_source_from_args <- NULL # Fall through to auto-detection } } # Auto-detect if no valid data_source was provided if (is.null(data_source_from_args)) { # Check merged_tif_8b - only if it exists AND contains files merged_tif_8b_path <- file.path(laravel_storage, "merged_tif_8b") has_8b_data <- dir.exists(merged_tif_8b_path) && length(list.files(merged_tif_8b_path, pattern = "\\.tif$")) > 0 # Check merged_tif - only if it exists AND contains files merged_tif_path <- file.path(laravel_storage, "merged_tif") has_legacy_data <- dir.exists(merged_tif_path) && length(list.files(merged_tif_path, pattern = "\\.tif$")) > 0 # Select data source based on what has actual data data_source <- if (has_8b_data) { message("Auto-detected data source: merged_tif_8b (8-band optimized) - contains files") "merged_tif_8b" } else if (has_legacy_data) { message("Auto-detected data source: merged_tif (legacy 4-band) - contains files") "merged_tif" } else { message("Warning: No valid data source found (both folders empty or missing). Using default: merged_tif") "merged_tif" } } # Set global data_source for parameters_project.R assign("data_source", data_source, envir = .GlobalEnv) tryCatch({ source("r_app/parameters_project.R") source("r_app/40_mosaic_creation_utils.R") safe_log(paste("Successfully sourced files from 'r_app' directory.")) }, error = function(e) { message("Note: Could not open files from r_app directory") message("Attempting to source from default directory instead...") tryCatch({ source("parameters_project.R") source("40_mosaic_creation_utils.R") message("✓ Successfully sourced files from default directory") }, error = function(e) { stop("Failed to source required files from both 'r_app' and default directories.") }) }) # Extract path variables from global environment (set by parameters_project.R) merged_final <- if (exists("merged_final", envir = .GlobalEnv)) { get("merged_final", envir = .GlobalEnv) } else { file.path(laravel_storage, "merged_final_tif") } daily_vrt <- if (exists("daily_vrt", envir = .GlobalEnv)) { get("daily_vrt", envir = .GlobalEnv) } else { file.path(laravel_storage, "Data", "vrt") } safe_log(paste("Using merged_final_tif directory:", merged_final)) safe_log(paste("Using daily VRT directory:", daily_vrt)) # 4. Generate date range for processing # --------------------------------- dates <- date_list(end_date, offset) safe_log(paste("Processing data for week", dates$week, "of", dates$year)) # Create output filename # Only use custom filename if explicitly provided (not empty string) file_name_tif <- if (length(args) >= 4 && !is.na(args[4]) && nchar(args[4]) > 0) { as.character(args[4]) } else { paste0("week_", sprintf("%02d", dates$week), "_", dates$year, ".tif") } safe_log(paste("Output will be saved as:", file_name_tif)) # 5. Create weekly mosaics - route based on project tile detection # --------------------------------------------------------------- # The use_tile_mosaic flag is auto-detected by parameters_project.R # based on whether tiles exist in merged_final_tif/ if (!exists("use_tile_mosaic")) { # Fallback detection if flag not set (shouldn't happen) merged_final_dir <- file.path(laravel_storage, "merged_final_tif") tile_detection <- detect_tile_structure_from_merged_final(merged_final_dir) use_tile_mosaic <- tile_detection$has_tiles } if (use_tile_mosaic) { # TILE-BASED APPROACH: Create per-tile weekly MAX mosaics # This is used for projects like Angata with large ROIs requiring spatial partitioning # Input data comes from merged_final_tif/{grid_size}/{DATE}/{DATE}_XX.tif (5-band tiles from script 20) tryCatch({ safe_log("Starting per-tile mosaic creation (tile-based approach)...") # Detect grid size from merged_final_tif folder structure # Expected: merged_final_tif/5x5/ or merged_final_tif/10x10/ etc. merged_final_base <- file.path(laravel_storage, "merged_final_tif") grid_subfolders <- list.dirs(merged_final_base, full.names = FALSE, recursive = FALSE) # Look for grid size patterns like "5x5", "10x10", "20x20" grid_patterns <- grep("^\\d+x\\d+$", grid_subfolders, value = TRUE) if (length(grid_patterns) == 0) { stop("No grid size subfolder found in merged_final_tif/ (expected: 5x5, 10x10, etc.)") } grid_size <- grid_patterns[1] # Use first grid size found safe_log(paste("Detected grid size:", grid_size)) # Point to the grid-specific merged_final_tif directory merged_final_with_grid <- file.path(merged_final_base, grid_size) # Set output directory for per-tile mosaics, organized by grid size # Output: weekly_tile_max/{grid_size}/week_WW_YYYY_TT.tif tile_output_base <- file.path(laravel_storage, "weekly_tile_max", grid_size) dir.create(tile_output_base, recursive = TRUE, showWarnings = FALSE) created_tile_files <- create_weekly_mosaic_from_tiles( dates = dates, merged_final_dir = merged_final_with_grid, tile_output_dir = tile_output_base, field_boundaries = field_boundaries ) safe_log(paste("✓ Per-tile mosaic creation completed - created", length(created_tile_files), "tile files")) }, error = function(e) { safe_log(paste("ERROR in tile-based mosaic creation:", e$message), "ERROR") traceback() stop("Mosaic creation failed") }) } else { # SINGLE-FILE APPROACH: Create single weekly mosaic file # This is used for legacy projects (ESA, Chemba, Aura) expecting single-file output tryCatch({ safe_log("Starting single-file mosaic creation (backward-compatible approach)...") # Set output directory for single-file mosaics single_file_output_dir <- file.path(laravel_storage, "weekly_mosaic") created_file <- create_weekly_mosaic( dates = dates, field_boundaries = field_boundaries, daily_vrt_dir = daily_vrt, merged_final_dir = merged_final, output_dir = single_file_output_dir, file_name_tif = file_name_tif, create_plots = FALSE ) safe_log(paste("✓ Single-file mosaic creation completed:", created_file)) }, error = function(e) { safe_log(paste("ERROR in single-file mosaic creation:", e$message), "ERROR") traceback() stop("Mosaic creation failed") }) } } if (sys.nframe() == 0) { main() }