From d5fd4bb463c032d44f026bcb1bf0d4aff70d50e5 Mon Sep 17 00:00:00 2001 From: Timon Date: Tue, 14 Oct 2025 11:49:30 +0200 Subject: [PATCH] Add KPI reporting system and deployment documentation Major Changes: - NEW: Scripts 09 & 10 for KPI calculation and enhanced reporting - NEW: Shell script wrappers (01-10) for easier execution - NEW: R packages flextable and officer for enhanced Word reports - NEW: DEPLOYMENT_README.md with complete deployment guide - RENAMED: Numbered R scripts (02, 03, 04) for clarity - REMOVED: Old package management scripts (using renv only) - UPDATED: Workflow now uses scripts 09->10 instead of 05 Files Changed: 90+ files New Packages: flextable, officer New Scripts: 09_run_calculate_kpis.sh, 10_run_kpi_report.sh Documentation: DEPLOYMENT_README.md, EMAIL_TO_ADMIN.txt See DEPLOYMENT_README.md for full deployment instructions. --- .Rhistory | 862 ++++---- .../chatmodes/instructionalist.chatmode.md | 202 ++ .github/copilot-instructions.md | 92 +- .gitignore | 71 +- 06_run_crop_messaging.sh | 31 + 09_run_calculate_kpis.sh | 188 ++ 10_run_kpi_report.sh | 56 + DEPLOYMENT_README.md | 299 +++ analyze_image_availability.R | 136 ++ cleanup_repo.ps1 | 207 ++ examine_kpi_results.R | 15 + generated_package_config.R | 42 - kpi_debug.out | 314 +++ push_to_bitbucket.ps1 | 72 + python_app/01_planet_download.ipynb | 1520 ++----------- r_app/02_ci_extraction.R | 122 +- r_app/03_interpolate_growth_model.R | 112 +- r_app/04_mosaic_creation.R | 121 +- r_app/05_CI_report_dashboard_planet.Rmd | 164 +- r_app/06_crop_messaging | 293 +++ r_app/09_calculate_kpis.R | 156 ++ r_app/10_CI_report_with_kpis_simple.Rmd | 1096 ++++++++++ r_app/CI_graph_example.png | Bin 0 -> 358660 bytes r_app/CI_report_dashboard_planet.Rmd | 739 ------- r_app/CI_report_dashboard_planet_enhanced.Rmd | 1145 ---------- r_app/CI_report_executive_summary.Rmd | 721 ------- r_app/ci_extraction.R | 117 - r_app/crop_messaging_utils.R | 1909 +++++++++++++++++ r_app/experiments/10_CI_report_with_kpis.Rmd | 400 ++++ r_app/experiments/combine_esa_yield_data.R | 239 +++ .../extract_current_versions.R | 0 .../package_manager.R | 0 r_app/experiments/testing_projsetup.R | 11 + r_app/installPackages.R | 40 - r_app/interpolate_growth_model.R | 102 - r_app/kpi_utils.R | 1250 +++++++++++ r_app/mosaic_creation.R | 119 - r_app/mosaic_creation_utils.R | 12 +- ...op_analysis_w34vs32_20250820_1414_table.md | 15 - r_app/packages.R | 117 - r_app/parameters_project.R | 15 +- r_app/report_utils.R | 343 ++- renv.lock | 521 ++++- run_kpi_calculation.R | 3 + run_report.R | 0 45 files changed, 8793 insertions(+), 5196 deletions(-) create mode 100644 .github/chatmodes/instructionalist.chatmode.md create mode 100644 06_run_crop_messaging.sh create mode 100644 09_run_calculate_kpis.sh create mode 100644 10_run_kpi_report.sh create mode 100644 DEPLOYMENT_README.md create mode 100644 analyze_image_availability.R create mode 100644 cleanup_repo.ps1 create mode 100644 examine_kpi_results.R delete mode 100644 generated_package_config.R create mode 100644 kpi_debug.out create mode 100644 push_to_bitbucket.ps1 create mode 100644 r_app/06_crop_messaging create mode 100644 r_app/09_calculate_kpis.R create mode 100644 r_app/10_CI_report_with_kpis_simple.Rmd create mode 100644 r_app/CI_graph_example.png delete mode 100644 r_app/CI_report_dashboard_planet.Rmd delete mode 100644 r_app/CI_report_dashboard_planet_enhanced.Rmd delete mode 100644 r_app/CI_report_executive_summary.Rmd delete mode 100644 r_app/ci_extraction.R create mode 100644 r_app/crop_messaging_utils.R create mode 100644 r_app/experiments/10_CI_report_with_kpis.Rmd create mode 100644 r_app/experiments/combine_esa_yield_data.R rename r_app/{ => experiments/legacy_package_management}/extract_current_versions.R (100%) rename r_app/{ => experiments/legacy_package_management}/package_manager.R (100%) create mode 100644 r_app/experiments/testing_projsetup.R delete mode 100644 r_app/installPackages.R delete mode 100644 r_app/interpolate_growth_model.R create mode 100644 r_app/kpi_utils.R delete mode 100644 r_app/mosaic_creation.R delete mode 100644 r_app/output/aura/crop_analysis_w34vs32_20250820_1414_table.md delete mode 100644 r_app/packages.R create mode 100644 run_kpi_calculation.R create mode 100644 run_report.R diff --git a/.Rhistory b/.Rhistory index 696d5a7..7b9cf34 100644 --- a/.Rhistory +++ b/.Rhistory @@ -1,424 +1,119 @@ -ggplot2::labs(title = "Model Performance: \nPredicted vs Actual Tonnage/ha", -x = "Actual tonnage/ha (Tcha)", -y = "Predicted tonnage/ha (Tcha)") + -ggplot2::theme_minimal() +message("No project_dir provided. Using default:", project_dir) } -if (nrow(pred_rf_current_season) > 0) { -# Plot predicted yields by age -ggplot2::ggplot(pred_rf_current_season, ggplot2::aes(x = Age_days, y = predicted_Tcha)) + -ggplot2::geom_point(size = 2, alpha = 0.6) + -ggplot2::labs(title = "Predicted Yields for Fields Over 300 Days \nOld Yet to Be Harvested", -x = "Age (days)", -y = "Predicted tonnage/ha (Tcha)") + -ggplot2::scale_y_continuous(limits = c(0, 200)) + -ggplot2::theme_minimal() -# Display prediction table -knitr::kable(pred_rf_current_season, -digits = 0, -caption = "Predicted Tonnage/ha for Fields Over 300 Days Old") +# Make project_dir available globally so parameters_project.R can use it +assign("project_dir", project_dir, envir = .GlobalEnv) +# Initialize project configuration and load utility functions +tryCatch({ +source("parameters_project.R") +source("growth_model_utils.R") +}, error = function(e) { +warning("Default source files not found. Attempting to source from 'r_app' directory.") +tryCatch({ +source(here::here("r_app", "parameters_project.R")) +source(here::here("r_app", "growth_model_utils.R")) +warning(paste("Successfully sourced files from 'r_app' directory.")) +}, error = function(e) { +stop("Failed to source required files from both default and 'r_app' directories.") +}) +}) +log_message("Starting CI growth model interpolation") +# Load and process the data +tryCatch({ +# Load the combined CI data +CI_data <- load_combined_ci_data(cumulative_CI_vals_dir) +# Validate harvesting data +if (is.null(harvesting_data) || nrow(harvesting_data) == 0) { +stop("No harvesting data available") +} +# Get the years from harvesting data +years <- harvesting_data %>% +filter(!is.na(season_start)) %>% +distinct(year) %>% +pull(year) +log_message(paste("Processing data for years:", paste(years, collapse = ", "))) +# Generate interpolated CI data for each year and field +CI_all <- generate_interpolated_ci_data(years, harvesting_data, CI_data) +# Calculate growth metrics and save the results +if (nrow(CI_all) > 0) { +# Add daily and cumulative metrics +CI_all_with_metrics <- calculate_growth_metrics(CI_all) +# Save the processed data +save_growth_model( +CI_all_with_metrics, +cumulative_CI_vals_dir, +"All_pivots_Cumulative_CI_quadrant_year_v2.rds" +) } else { -cat("No fields over 300 days old without harvest data available for yield prediction.") +log_message("No CI data was generated after interpolation", level = "WARNING") } +log_message("Growth model interpolation completed successfully") }, error = function(e) { -safe_log(paste("Error in yield prediction visualization:", e$message), "ERROR") -cat("Error generating yield prediction visualizations. See log for details.") +log_message(paste("Error in growth model interpolation:", e$message), level = "ERROR") +stop(e$message) }) -# Load and prepare yield prediction data with error handling -tryCatch({ -# Load CI quadrant data and fill missing values -CI_quadrant <- readRDS(here::here(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds")) %>% -dplyr::group_by(model) %>% -tidyr::fill(field, sub_field, .direction = "downup") %>% -dplyr::ungroup() -# Check if tonnage_ha is empty -if (all(is.na(harvesting_data$tonnage_ha))) { -safe_log("Lacking historic harvest data, please provide for yield prediction calculation", "WARNING") -knitr::knit_exit() # Exit the chunk if tonnage_ha is empty +View(CI_all_with_metrics) +View(CI_data) +# Get the years from harvesting data +years <- harvesting_data %>% +filter(!is.na(season_start)) %>% +distinct(year) %>% +pull(year) +years +View(CI_all) +View(CI_all_with_metrics) +years +harvesting_data +ci_data +ci_data = CI_data +# Process each year +result <- purrr::map_df(years, function(yr) { +safe_log(paste("Processing year:", yr)) +# Get the fields harvested in this year with valid season start dates +sub_fields <- harvesting_data %>% +dplyr::filter(year == yr, !is.na(season_start)) %>% +dplyr::pull(sub_field) +if (length(sub_fields) == 0) { +safe_log(paste("No fields with valid season data for year:", yr), "WARNING") +return(data.frame()) } -# Rename year column to season for consistency -harvesting_data <- harvesting_data %>% dplyr::rename(season = year) -# Join CI and yield data -CI_and_yield <- dplyr::left_join(CI_quadrant, harvesting_data, by = c("field", "sub_field", "season")) %>% -dplyr::group_by(sub_field, season) %>% -dplyr::slice(which.max(DOY)) %>% -dplyr::select(field, sub_field, tonnage_ha, cumulative_CI, DOY, season, sub_area) %>% -dplyr::mutate(CI_per_day = cumulative_CI / DOY) -# Define predictors and response variables -predictors <- c("cumulative_CI", "DOY", "CI_per_day") -response <- "tonnage_ha" -# Prepare test and validation datasets -CI_and_yield_test <- CI_and_yield %>% -as.data.frame() %>% -dplyr::filter(!is.na(tonnage_ha)) -CI_and_yield_validation <- CI_and_yield_test -# Prepare prediction dataset (fields without harvest data) -prediction_yields <- CI_and_yield %>% -as.data.frame() %>% -dplyr::filter(is.na(tonnage_ha)) -# Configure model training parameters -ctrl <- caret::trainControl( -method = "cv", -savePredictions = TRUE, -allowParallel = TRUE, -number = 5, -verboseIter = TRUE -) -# Train the model with feature selection -set.seed(202) # For reproducibility -model_ffs_rf <- CAST::ffs( -CI_and_yield_test[, predictors], -CI_and_yield_test[, response], -method = "rf", -trControl = ctrl, -importance = TRUE, -withinSE = TRUE, -tuneLength = 5, -na.rm = TRUE -) -# Function to prepare predictions with consistent naming and formatting -prepare_predictions <- function(predictions, newdata) { -return(predictions %>% -as.data.frame() %>% -dplyr::rename(predicted_Tcha = ".") %>% -dplyr::mutate( -sub_field = newdata$sub_field, -field = newdata$field, -Age_days = newdata$DOY, -total_CI = round(newdata$cumulative_CI, 0), -predicted_Tcha = round(predicted_Tcha, 0), -season = newdata$season -) %>% -dplyr::select(field, sub_field, Age_days, total_CI, predicted_Tcha, season) %>% -dplyr::left_join(., newdata, by = c("field", "sub_field", "season")) -) +# Filter sub_fields to only include those with value data in ci_data +valid_sub_fields <- sub_fields %>% +purrr::keep(~ any(ci_data$sub_field == .x)) +if (length(valid_sub_fields) == 0) { +safe_log(paste("No fields with CI data for year:", yr), "WARNING") +return(data.frame()) } -# Predict yields for the validation dataset -pred_ffs_rf <- prepare_predictions(stats::predict(model_ffs_rf, newdata = CI_and_yield_validation), CI_and_yield_validation) -# Predict yields for the current season (focus on mature fields over 300 days) -pred_rf_current_season <- prepare_predictions(stats::predict(model_ffs_rf, newdata = prediction_yields), prediction_yields) %>% -dplyr::filter(Age_days > 1) %>% -dplyr::mutate(CI_per_day = round(total_CI / Age_days, 1)) -safe_log("Successfully completed yield prediction calculations") -}, error = function(e) { -safe_log(paste("Error in yield prediction:", e$message), "ERROR") -# Create empty dataframes to prevent errors in subsequent chunks -pred_ffs_rf <- data.frame() -pred_rf_current_season <- data.frame() +# Extract and interpolate data for each valid field +safe_log(paste("Processing", length(valid_sub_fields), "fields for year:", yr)) +result <- purrr::map(valid_sub_fields, ~ extract_CI_data(.x, +harvesting_data = harvesting_data, +field_CI_data = ci_data, +season = yr)) %>% +purrr::list_rbind() +safe_log(paste("Generated", nrow(result), "interpolated data points for year:", yr)) +return(result) }) -# Load and prepare yield prediction data with error handling -tryCatch({ -# Load CI quadrant data and fill missing values -CI_quadrant <- readRDS(here::here(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds")) %>% -dplyr::group_by(model) %>% -tidyr::fill(field, sub_field, .direction = "downup") %>% -dplyr::ungroup() -# Check if tonnage_ha is empty -if (all(is.na(harvesting_data$tonnage_ha))) { -safe_log("Lacking historic harvest data, please provide for yield prediction calculation", "WARNING") -knitr::knit_exit() # Exit the chunk if tonnage_ha is empty -} -# Rename year column to season for consistency -harvesting_data <- harvesting_data %>% dplyr::rename(season = year) -# Join CI and yield data -CI_and_yield <- dplyr::left_join(CI_quadrant, harvesting_data, by = c("field", "sub_field", "season")) %>% -dplyr::group_by(sub_field, season) %>% -dplyr::slice(which.max(DOY)) %>% -dplyr::select(field, sub_field, tonnage_ha, cumulative_CI, DOY, season, sub_area) %>% -dplyr::mutate(CI_per_day = cumulative_CI / DOY) -# Define predictors and response variables -predictors <- c("cumulative_CI", "DOY", "CI_per_day") -response <- "tonnage_ha" -# Prepare test and validation datasets -CI_and_yield_test <- CI_and_yield %>% -as.data.frame() %>% -dplyr::filter(!is.na(tonnage_ha)) -CI_and_yield_validation <- CI_and_yield_test -# Prepare prediction dataset (fields without harvest data) -prediction_yields <- CI_and_yield %>% -as.data.frame() %>% -dplyr::filter(is.na(tonnage_ha)) -# Configure model training parameters -ctrl <- caret::trainControl( -method = "cv", -savePredictions = TRUE, -allowParallel = TRUE, -number = 5, -verboseIter = TRUE +CI_all_with_metrics +CI_all <- CI_all %>% +group_by(Date, field, season) %>% +filter(!(field == "00F25" & season == 2023 & duplicated(DOY))) +View(CI_all) +# Add daily and cumulative metrics +CI_all_with_metrics <- calculate_growth_metrics(CI_all) +# Save the processed data +save_growth_model( +CI_all_with_metrics, +cumulative_CI_vals_dir, +"All_pivots_Cumulative_CI_quadrant_year_v2.rds" ) -# Train the model with feature selection -set.seed(202) # For reproducibility -model_ffs_rf <- CAST::ffs( -CI_and_yield_test[, predictors], -CI_and_yield_test[, response], -method = "rf", -trControl = ctrl, -importance = TRUE, -withinSE = TRUE, -tuneLength = 5, -na.rm = TRUE -) -# Function to prepare predictions with consistent naming and formatting -prepare_predictions <- function(predictions, newdata) { -return(predictions %>% -as.data.frame() %>% -dplyr::rename(predicted_Tcha = ".") %>% -dplyr::mutate( -sub_field = newdata$sub_field, -field = newdata$field, -Age_days = newdata$DOY, -total_CI = round(newdata$cumulative_CI, 0), -predicted_Tcha = round(predicted_Tcha, 0), -season = newdata$season -) %>% -dplyr::select(field, sub_field, Age_days, total_CI, predicted_Tcha, season) %>% -dplyr::left_join(., newdata, by = c("field", "sub_field", "season")) -) -} -# Predict yields for the validation dataset -pred_ffs_rf <- prepare_predictions(stats::predict(model_ffs_rf, newdata = CI_and_yield_validation), CI_and_yield_validation) -# Predict yields for the current season (focus on mature fields over 300 days) -pred_rf_current_season <- prepare_predictions(stats::predict(model_ffs_rf, newdata = prediction_yields), prediction_yields) %>% -dplyr::filter(Age_days > 1) %>% -dplyr::mutate(CI_per_day = round(total_CI / Age_days, 1)) -safe_log("Successfully completed yield prediction calculations") -}, error = function(e) { -safe_log(paste("Error in yield prediction:", e$message), "ERROR") -# Create empty dataframes to prevent errors in subsequent chunks -pred_ffs_rf <- data.frame() -pred_rf_current_season <- data.frame() -}) -# Load CI quadrant data and fill missing values -CI_quadrant <- readRDS(here::here(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds")) %>% -dplyr::group_by(model) %>% -tidyr::fill(field, sub_field, .direction = "downup") %>% -dplyr::ungroup() -# Check if tonnage_ha is empty -if (all(is.na(harvesting_data$tonnage_ha))) { -safe_log("Lacking historic harvest data, please provide for yield prediction calculation", "WARNING") -knitr::knit_exit() # Exit the chunk if tonnage_ha is empty -} -# Rename year column to season for consistency -harvesting_data <- harvesting_data %>% dplyr::rename(season = year) -# Join CI and yield data -CI_and_yield <- dplyr::left_join(CI_quadrant, harvesting_data, by = c("field", "sub_field", "season")) %>% -dplyr::group_by(sub_field, season) %>% -dplyr::slice(which.max(DOY)) %>% -dplyr::select(field, sub_field, tonnage_ha, cumulative_CI, DOY, season, sub_area) %>% -dplyr::mutate(CI_per_day = cumulative_CI / DOY) -# Define predictors and response variables -predictors <- c("cumulative_CI", "DOY", "CI_per_day") -response <- "tonnage_ha" -# Prepare test and validation datasets -CI_and_yield_test <- CI_and_yield %>% -as.data.frame() %>% -dplyr::filter(!is.na(tonnage_ha)) -CI_and_yield_validation <- CI_and_yield_test -# Prepare prediction dataset (fields without harvest data) -prediction_yields <- CI_and_yield %>% -as.data.frame() %>% -dplyr::filter(is.na(tonnage_ha)) -# Configure model training parameters -ctrl <- caret::trainControl( -method = "cv", -savePredictions = TRUE, -allowParallel = TRUE, -number = 5, -verboseIter = TRUE -) -# Train the model with feature selection -set.seed(202) # For reproducibility -model_ffs_rf <- CAST::ffs( -CI_and_yield_test[, predictors], -CI_and_yield_test[, response], -method = "rf", -trControl = ctrl, -importance = TRUE, -withinSE = TRUE, -tuneLength = 5, -na.rm = TRUE -) -# Function to prepare predictions with consistent naming and formatting -prepare_predictions <- function(predictions, newdata) { -return(predictions %>% -as.data.frame() %>% -dplyr::rename(predicted_Tcha = ".") %>% -dplyr::mutate( -sub_field = newdata$sub_field, -field = newdata$field, -Age_days = newdata$DOY, -total_CI = round(newdata$cumulative_CI, 0), -predicted_Tcha = round(predicted_Tcha, 0), -season = newdata$season -) %>% -dplyr::select(field, sub_field, Age_days, total_CI, predicted_Tcha, season) %>% -dplyr::left_join(., newdata, by = c("field", "sub_field", "season")) -) -} -# Predict yields for the validation dataset -pred_ffs_rf <- prepare_predictions(stats::predict(model_ffs_rf, newdata = CI_and_yield_validation), CI_and_yield_validation) -# Predict yields for the current season (focus on mature fields over 300 days) -pred_rf_current_season <- prepare_predictions(stats::predict(model_ffs_rf, newdata = prediction_yields), prediction_yields) %>% -dplyr::filter(Age_days > 300) %>% -dplyr::mutate(CI_per_day = round(total_CI / Age_days, 1)) -# Predict yields for the current season (focus on mature fields over 300 days) -pred_rf_current_season <- prepare_predictions(stats::predict(model_ffs_rf, newdata = prediction_yields), prediction_yields) %>% -dplyr::filter(Age_days > 1) %>% -dplyr::mutate(CI_per_day = round(total_CI / Age_days, 1)) -safe_log("Successfully completed yield prediction calculations") -# Load and prepare yield prediction data with error handling -tryCatch({ -# Load CI quadrant data and fill missing values -CI_quadrant <- readRDS(here::here(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds")) %>% -dplyr::group_by(model) %>% -tidyr::fill(field, sub_field, .direction = "downup") %>% -dplyr::ungroup() -# Check if tonnage_ha is empty -if (all(is.na(harvesting_data$tonnage_ha))) { -safe_log("Lacking historic harvest data, please provide for yield prediction calculation", "WARNING") -knitr::knit_exit() # Exit the chunk if tonnage_ha is empty -} -# Rename year column to season for consistency -harvesting_data <- harvesting_data %>% dplyr::rename(season = year) -# Join CI and yield data -CI_and_yield <- dplyr::left_join(CI_quadrant, harvesting_data, by = c("field", "sub_field", "season")) %>% -dplyr::group_by(sub_field, season) %>% -dplyr::slice(which.max(DOY)) %>% -dplyr::select(field, sub_field, tonnage_ha, cumulative_CI, DOY, season, sub_area) %>% -dplyr::mutate(CI_per_day = cumulative_CI / DOY) -# Define predictors and response variables -predictors <- c("cumulative_CI", "DOY", "CI_per_day") -response <- "tonnage_ha" -# Prepare test and validation datasets -CI_and_yield_test <- CI_and_yield %>% -as.data.frame() %>% -dplyr::filter(!is.na(tonnage_ha)) -CI_and_yield_validation <- CI_and_yield_test -# Prepare prediction dataset (fields without harvest data) -prediction_yields <- CI_and_yield %>% -as.data.frame() %>% -dplyr::filter(is.na(tonnage_ha)) -# Configure model training parameters -ctrl <- caret::trainControl( -method = "cv", -savePredictions = TRUE, -allowParallel = TRUE, -number = 5, -verboseIter = TRUE -) -# Train the model with feature selection -set.seed(202) # For reproducibility -model_ffs_rf <- CAST::ffs( -CI_and_yield_test[, predictors], -CI_and_yield_test[, response], -method = "rf", -trControl = ctrl, -importance = TRUE, -withinSE = TRUE, -tuneLength = 5, -na.rm = TRUE -) -# Function to prepare predictions with consistent naming and formatting -prepare_predictions <- function(predictions, newdata) { -return(predictions %>% -as.data.frame() %>% -dplyr::rename(predicted_Tcha = ".") %>% -dplyr::mutate( -sub_field = newdata$sub_field, -field = newdata$field, -Age_days = newdata$DOY, -total_CI = round(newdata$cumulative_CI, 0), -predicted_Tcha = round(predicted_Tcha, 0), -season = newdata$season -) %>% -dplyr::select(field, sub_field, Age_days, total_CI, predicted_Tcha, season) %>% -dplyr::left_join(., newdata, by = c("field", "sub_field", "season")) -) -} -# Predict yields for the validation dataset -pred_ffs_rf <- prepare_predictions(stats::predict(model_ffs_rf, newdata = CI_and_yield_validation), CI_and_yield_validation) -# Predict yields for the current season (focus on mature fields over 300 days) -pred_rf_current_season <- prepare_predictions(stats::predict(model_ffs_rf, newdata = prediction_yields), prediction_yields) %>% -dplyr::filter(Age_days > 1) %>% -dplyr::mutate(CI_per_day = round(total_CI / Age_days, 1)) -safe_log("Successfully completed yield prediction calculations") -}, error = function(e) { -safe_log(paste("Error in yield prediction:", e$message), "ERROR") -# Create empty dataframes to prevent errors in subsequent chunks -pred_ffs_rf <- data.frame() -pred_rf_current_season <- data.frame() -}) -# Display yield prediction visualizations with error handling -tryCatch({ -if (nrow(pred_ffs_rf) > 0) { -# Plot model performance (predicted vs actual) -ggplot2::ggplot(pred_ffs_rf, ggplot2::aes(y = predicted_Tcha, x = tonnage_ha)) + -ggplot2::geom_point(size = 2, alpha = 0.6) + -ggplot2::geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "red") + -ggplot2::scale_x_continuous(limits = c(0, 200)) + -ggplot2::scale_y_continuous(limits = c(0, 200)) + -ggplot2::labs(title = "Model Performance: \nPredicted vs Actual Tonnage/ha", -x = "Actual tonnage/ha (Tcha)", -y = "Predicted tonnage/ha (Tcha)") + -ggplot2::theme_minimal() -} -if (nrow(pred_rf_current_season) > 0) { -# Plot predicted yields by age -ggplot2::ggplot(pred_rf_current_season, ggplot2::aes(x = Age_days, y = predicted_Tcha)) + -ggplot2::geom_point(size = 2, alpha = 0.6) + -ggplot2::labs(title = "Predicted Yields for Fields Over 300 Days \nOld Yet to Be Harvested", -x = "Age (days)", -y = "Predicted tonnage/ha (Tcha)") + -ggplot2::scale_y_continuous(limits = c(0, 200)) + -ggplot2::theme_minimal() -# Display prediction table -knitr::kable(pred_rf_current_season, -digits = 0, -caption = "Predicted Tonnage/ha for Fields Over 300 Days Old") -} else { -cat("No fields over 300 days old without harvest data available for yield prediction.") -} -}, error = function(e) { -safe_log(paste("Error in yield prediction visualization:", e$message), "ERROR") -cat("Error generating yield prediction visualizations. See log for details.") -}) -AllPivots_merged$field[1:10] -CI -path_to_week_current -terra::rast(path_to_week_current) -map <- tmap::tm_shape(last_week_dif_raster_abs, unit = "m") # Add raster layer with continuous spectrum (centered at 0 for difference maps) -map <- map + tmap::tm_raster(col.scale = tm_scale_continuous(values = "RdYlGn", -midpoint = 0), -col.legend = tm_legend(title = "Chlorophyll Index (CI) Change", -orientation = "landscape", -position = tm_pos_out("center", "bottom"))) -# Complete the map with layout and other elements -map <- map + -tmap::tm_scalebar(position = c("right", "bottom"), text.color = "black") + -tmap::tm_compass(position = c("right", "bottom"), text.color = "black") + -tmap::tm_shape(AllPivots0) + -tmap::tm_borders(col = "black") + -tmap::tm_text("sub_field", size = 0.6, col = "black") -# Print the map -print(map) -# Get versions of specific packages you're using -packages <- c("here", "sf", "terra", "exactextractr", "tidyverse", -"tmap", "lubridate", "magrittr", "dplyr", "readr", -"readxl", "knitr", "rmarkdown", "officedown", "officer") -package_versions <- sapply(packages, function(x) as.character(packageVersion(x))) -sessionInfo() -# Chunk 1: setup_parameters # Set up basic report parameters from input values report_date <- params$report_date mail_day <- params$mail_day borders <- params$borders -# Environment setup notes (commented out) -# # Activeer de renv omgeving -# renv::activate() -# renv::deactivate() -# # Optioneel: Herstel de omgeving als dat nodig is -# # Je kunt dit commentaar geven als je het normaal niet wilt uitvoeren -# renv::restore() -# Chunk 2: load_libraries +ci_plot_type <- params$ci_plot_type +colorblind_friendly <- params$colorblind_friendly +facet_by_season <- params$facet_by_season +x_axis_unit <- params$x_axis_unit # Configure knitr options knitr::opts_chunk$set(warning = FALSE, message = FALSE) # Load all packages at once with suppressPackageStartupMessages @@ -435,6 +130,8 @@ library(rsample) library(caret) library(randomForest) library(CAST) +library(knitr) +library(tidyr) }) # Load custom utility functions tryCatch({ @@ -448,7 +145,6 @@ source(here::here("r_app", "report_utils.R")) stop("Could not load report_utils.R from either location: ", e$message) }) }) -# Chunk 3: initialize_project_config # Set the project directory from parameters project_dir <- params$data_dir # Source project parameters with error handling @@ -458,31 +154,96 @@ source(here::here("r_app", "parameters_project.R")) stop("Error loading parameters_project.R: ", e$message) }) # Log initial configuration -safe_log("Starting the R Markdown script") +safe_log("Starting the R Markdown script with KPIs") safe_log(paste("mail_day params:", params$mail_day)) safe_log(paste("report_date params:", params$report_date)) safe_log(paste("mail_day variable:", mail_day)) -# Chunk 4: calculate_dates_and_weeks +## SIMPLE KPI LOADING - robust lookup with fallbacks +# Primary expected directory inside the laravel storage +kpi_data_dir <- file.path("laravel_app", "storage", "app", project_dir, "reports", "kpis") +date_suffix <- format(as.Date(report_date), "%Y%m%d") +# Candidate filenames we expect (exact and common variants) +expected_summary_names <- c( +paste0(project_dir, "_kpi_summary_tables_", date_suffix, ".rds"), +paste0(project_dir, "_kpi_summary_tables.rds"), +"kpi_summary_tables.rds", +paste0("kpi_summary_tables_", date_suffix, ".rds") +) +expected_field_details_names <- c( +paste0(project_dir, "_field_details_", date_suffix, ".rds"), +paste0(project_dir, "_field_details.rds"), +"field_details.rds" +) +# Helper to attempt loading a file from the directory or fallback to a workspace-wide search +try_load_from_dir <- function(dir, candidates) { +if (!dir.exists(dir)) return(NULL) +for (name in candidates) { +f <- file.path(dir, name) +if (file.exists(f)) return(f) +} +return(NULL) +} +# Try primary directory first +summary_file <- try_load_from_dir(kpi_data_dir, expected_summary_names) +field_details_file <- try_load_from_dir(kpi_data_dir, expected_field_details_names) +# If not found, perform a workspace-wide search (slower) limited to laravel_app storage +if (is.null(summary_file) || is.null(field_details_file)) { +safe_log(paste("KPI files not found in", kpi_data_dir, "—searching workspace for RDS files")) +# List rds files under laravel_app/storage/app recursively +files <- list.files(path = file.path("laravel_app", "storage", "app"), pattern = "\\.rds$", recursive = TRUE, full.names = TRUE) +# Try to match by expected names +if (is.null(summary_file)) { +matched <- files[basename(files) %in% expected_summary_names] +if (length(matched) > 0) summary_file <- matched[1] +} +if (is.null(field_details_file)) { +matched2 <- files[basename(files) %in% expected_field_details_names] +if (length(matched2) > 0) field_details_file <- matched2[1] +} +} +# Final checks and load with safe error messages +kpi_files_exist <- FALSE +if (!is.null(summary_file) && file.exists(summary_file)) { +safe_log(paste("Loading KPI summary from:", summary_file)) +summary_tables <- tryCatch(readRDS(summary_file), error = function(e) { safe_log(paste("Failed to read summary RDS:", e$message), "ERROR"); NULL }) +if (!is.null(summary_tables)) kpi_files_exist <- TRUE +} else { +safe_log(paste("KPI summary file not found. Searched:", paste(expected_summary_names, collapse=", ")), "WARNING") +} +if (!is.null(field_details_file) && file.exists(field_details_file)) { +safe_log(paste("Loading field details from:", field_details_file)) +field_details_table <- tryCatch(readRDS(field_details_file), error = function(e) { safe_log(paste("Failed to read field details RDS:", e$message), "ERROR"); NULL }) +if (!is.null(field_details_table)) kpi_files_exist <- kpi_files_exist && TRUE +} else { +safe_log(paste("Field details file not found. Searched:", paste(expected_field_details_names, collapse=", ")), "WARNING") +} +if (kpi_files_exist) { +safe_log("✓ KPI summary tables loaded successfully") +} else { +safe_log("KPI files could not be located or loaded. KPI sections will be skipped.", "WARNING") +} # Set locale for consistent date formatting Sys.setlocale("LC_TIME", "C") # Initialize date variables from parameters today <- as.character(report_date) mail_day_as_character <- as.character(mail_day) -# Calculate week days +# Calculate report dates and weeks +report_date_obj <- as.Date(today) +current_week <- as.numeric(format(report_date_obj, "%U")) +year <- as.numeric(format(report_date_obj, "%Y")) +# Calculate dates for weekly analysis +week_start <- report_date_obj - ((as.numeric(format(report_date_obj, "%w")) + 1) %% 7) +week_end <- week_start + 6 +# Calculate week days (copied from 05 script for compatibility) report_date_as_week_day <- weekdays(lubridate::ymd(today)) days_of_week <- c("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday") # Calculate initial week number -week <- lubridate::week(today) +week <- lubridate::week(today) - 1 safe_log(paste("Initial week calculation:", week, "today:", today)) # Calculate previous dates for comparisons today_minus_1 <- as.character(lubridate::ymd(today) - 7) today_minus_2 <- as.character(lubridate::ymd(today) - 14) today_minus_3 <- as.character(lubridate::ymd(today) - 21) -# Log the weekday calculations for debugging -safe_log(paste("Report date weekday:", report_date_as_week_day)) -safe_log(paste("Weekday index:", which(days_of_week == report_date_as_week_day))) -safe_log(paste("Mail day:", mail_day_as_character)) -safe_log(paste("Mail day index:", which(days_of_week == mail_day_as_character))) # Adjust week calculation based on mail day if (which(days_of_week == report_date_as_week_day) > which(days_of_week == mail_day_as_character)) { safe_log("Adjusting weeks because of mail day") @@ -497,16 +258,255 @@ week_minus_2 <- week - 2 week_minus_3 <- week - 3 # Format current week with leading zeros week <- sprintf("%02d", week) -# Get years for each date -year <- lubridate::year(today) -year_1 <- lubridate::year(today_minus_1) -year_2 <- lubridate::year(today_minus_2) -year_3 <- lubridate::year(today_minus_3) -sessionInfo() -source("r_app/extract_current_versions.R") -source("r_app/package_manager.R") -source("r_app/package_manager.R") -source("r_app/package_manager.R") -source("r_app/package_manager.R") -source("r_app/package_manager.R") -source("r_app/package_manager.R") +safe_log(paste("Report week:", current_week, "Year:", year)) +safe_log(paste("Week range:", week_start, "to", week_end)) +## SIMPLE KPI LOADING - robust lookup with fallbacks +# Primary expected directory inside the laravel storage +kpi_data_dir <- file.path("laravel_app", "storage", "app", project_dir, "reports", "kpis") +date_suffix <- format(as.Date(report_date), "%Y%m%d") +# Candidate filenames we expect (exact and common variants) +expected_summary_names <- c( +paste0(project_dir, "_kpi_summary_tables_", date_suffix, ".rds"), +paste0(project_dir, "_kpi_summary_tables.rds"), +"kpi_summary_tables.rds", +paste0("kpi_summary_tables_", date_suffix, ".rds") +) +expected_field_details_names <- c( +paste0(project_dir, "_field_details_", date_suffix, ".rds"), +paste0(project_dir, "_field_details.rds"), +"field_details.rds" +) +# Helper to attempt loading a file from the directory or fallback to a workspace-wide search +try_load_from_dir <- function(dir, candidates) { +if (!dir.exists(dir)) return(NULL) +for (name in candidates) { +f <- file.path(dir, name) +if (file.exists(f)) return(f) +} +return(NULL) +} +# Try primary directory first +summary_file <- try_load_from_dir(kpi_data_dir, expected_summary_names) +field_details_file <- try_load_from_dir(kpi_data_dir, expected_field_details_names) +# If not found, perform a workspace-wide search (slower) limited to laravel_app storage +if (is.null(summary_file) || is.null(field_details_file)) { +safe_log(paste("KPI files not found in", kpi_data_dir, "—searching workspace for RDS files")) +# List rds files under laravel_app/storage/app recursively +files <- list.files(path = file.path("laravel_app", "storage", "app"), pattern = "\\.rds$", recursive = TRUE, full.names = TRUE) +# Try to match by expected names +if (is.null(summary_file)) { +matched <- files[basename(files) %in% expected_summary_names] +if (length(matched) > 0) summary_file <- matched[1] +} +if (is.null(field_details_file)) { +matched2 <- files[basename(files) %in% expected_field_details_names] +if (length(matched2) > 0) field_details_file <- matched2[1] +} +} +# Final checks and load with safe error messages +kpi_files_exist <- FALSE +if (!is.null(summary_file) && file.exists(summary_file)) { +safe_log(paste("Loading KPI summary from:", summary_file)) +summary_tables <- tryCatch(readRDS(summary_file), error = function(e) { safe_log(paste("Failed to read summary RDS:", e$message), "ERROR"); NULL }) +if (!is.null(summary_tables)) kpi_files_exist <- TRUE +} else { +safe_log(paste("KPI summary file not found. Searched:", paste(expected_summary_names, collapse=", ")), "WARNING") +} +if (!is.null(field_details_file) && file.exists(field_details_file)) { +safe_log(paste("Loading field details from:", field_details_file)) +field_details_table <- tryCatch(readRDS(field_details_file), error = function(e) { safe_log(paste("Failed to read field details RDS:", e$message), "ERROR"); NULL }) +if (!is.null(field_details_table)) kpi_files_exist <- kpi_files_exist && TRUE +} else { +safe_log(paste("Field details file not found. Searched:", paste(expected_field_details_names, collapse=", ")), "WARNING") +} +if (kpi_files_exist) { +safe_log("✓ KPI summary tables loaded successfully") +} else { +safe_log("KPI files could not be located or loaded. KPI sections will be skipped.", "WARNING") +} +## SIMPLE KPI LOADING - robust lookup with fallbacks +# Primary expected directory inside the laravel storage +kpi_data_dir <- file.path("laravel_app", "storage", "app", project_dir, "reports", "kpis") +kpi_data_dir +kpi_data_dir +## SIMPLE KPI LOADING - robust lookup with fallbacks +# Primary expected directory inside the laravel storage +kpi_data_dir <- file.path(here("laravel_app", "storage", "app", project_dir, "reports", "kpis")) +kpi_data_dir +# Candidate filenames we expect (exact and common variants) +expected_summary_names <- c( +paste0(project_dir, "_kpi_summary_tables_", date_suffix, ".rds"), +paste0(project_dir, "_kpi_summary_tables.rds"), +"kpi_summary_tables.rds", +paste0("kpi_summary_tables_", date_suffix, ".rds") +) +expected_field_details_names <- c( +paste0(project_dir, "_field_details_", date_suffix, ".rds"), +paste0(project_dir, "_field_details.rds"), +"field_details.rds" +) +# Helper to attempt loading a file from the directory or fallback to a workspace-wide search +try_load_from_dir <- function(dir, candidates) { +if (!dir.exists(dir)) return(NULL) +for (name in candidates) { +f <- file.path(dir, name) +if (file.exists(f)) return(f) +} +return(NULL) +} +# Try primary directory first +summary_file <- try_load_from_dir(kpi_data_dir, expected_summary_names) +field_details_file <- try_load_from_dir(kpi_data_dir, expected_field_details_names) +# If not found, perform a workspace-wide search (slower) limited to laravel_app storage +if (is.null(summary_file) || is.null(field_details_file)) { +safe_log(paste("KPI files not found in", kpi_data_dir, "—searching workspace for RDS files")) +# List rds files under laravel_app/storage/app recursively +files <- list.files(path = file.path("laravel_app", "storage", "app"), pattern = "\\.rds$", recursive = TRUE, full.names = TRUE) +# Try to match by expected names +if (is.null(summary_file)) { +matched <- files[basename(files) %in% expected_summary_names] +if (length(matched) > 0) summary_file <- matched[1] +} +if (is.null(field_details_file)) { +matched2 <- files[basename(files) %in% expected_field_details_names] +if (length(matched2) > 0) field_details_file <- matched2[1] +} +} +# Final checks and load with safe error messages +kpi_files_exist <- FALSE +if (!is.null(summary_file) && file.exists(summary_file)) { +safe_log(paste("Loading KPI summary from:", summary_file)) +summary_tables <- tryCatch(readRDS(summary_file), error = function(e) { safe_log(paste("Failed to read summary RDS:", e$message), "ERROR"); NULL }) +if (!is.null(summary_tables)) kpi_files_exist <- TRUE +} else { +safe_log(paste("KPI summary file not found. Searched:", paste(expected_summary_names, collapse=", ")), "WARNING") +} +summary_file +kpi_data_dir +library(officer) +library(flextable) +# Data setup +summary_tables <- list() +summary_tables$field_uniformity_summary <- data.frame( +"Uniformity Level" = c("Excellent", "Good", "Poor"), +"Count" = c(15, 8, 3), +"Percent" = c("62.5%", "33.3%", "12.5%") +) +summary_tables$weed_presence_summary <- data.frame( +"Weed Risk Level" = c("Low", "Moderate", "High"), +"Field Count" = c(18, 6, 2), +"Percent" = c("75.0%", "25.0%", "8.3%") +) +doc <- read_docx() +doc <- body_add_par(doc, "KPI Grid Test Report", style = "heading 1") +doc <- body_add_par(doc, "Executive Summary - Key Performance Indicators", style = "heading 2") +doc <- body_add_par(doc, "This section demonstrates just two KPI tables side by side.", style = "Normal") +doc <- body_add_section(doc, prop_section( +section_type = "continuous", +columns = columns(widths = c(4.25, 4.25)) +)) +doc <- body_add_flextable(doc, flextable(summary_tables$field_uniformity_summary) %>% set_caption("Field Uniformity Summary")) +doc <- body_add_break(doc, "column") +doc <- body_add_flextable(doc, flextable(summary_tables$weed_presence_summary) %>% set_caption("Weed Presence Score Summary")) +doc <- body_add_section(doc, prop_section( +section_type = "continuous", +columns = columns(widths = c(8.5)) +)) +doc <- body_add_par(doc, "This is a test report to verify the KPI grid layout.", style = "Normal") +print(doc, target = "tables_side_by_side.docx") +here() +getwd() +print(doc, target = "tables_side_by_side.docx") +doc +print(doc, target = "tables_side_by_side.docx") +print(doc, target = "r_app/tables_side_by_side.docx") +library(officer) +library(flextable) +# Create example data +summary_tables <- list() +summary_tables$field_uniformity_summary <- data.frame( +"Uniformity Level" = c("Excellent", "Good", "Poor"), +"Count" = c(15, 8, 3), +"Percent" = c("62.5%", "33.3%", "12.5%") +) +summary_tables$weed_presence_summary <- data.frame( +"Weed Risk Level" = c("Low", "Moderate", "High"), +"Field Count" = c(18, 6, 2), +"Percent" = c("75.0%", "25.0%", "8.3%") +) +# Create document +doc <- read_docx() +doc <- body_add_par(doc, "KPI Grid Test Report", style = "heading 1") +doc <- body_add_par(doc, "Executive Summary - Key Performance Indicators", style = "heading 2") +doc <- body_add_par(doc, "This section demonstrates just two KPI tables side by side.", style = "Normal") +# Two-column section +doc <- body_add_section(doc, prop_section( +section_type = "continuous", +columns = columns(widths = c(4.25, 4.25)) +)) +library(officer) +library(flextable) +# Create example data +summary_tables <- list() +summary_tables$field_uniformity_summary <- data.frame( +"Uniformity Level" = c("Excellent", "Good", "Poor"), +"Count" = c(15, 8, 3), +"Percent" = c("62.5%", "33.3%", "12.5%") +) +summary_tables$weed_presence_summary <- data.frame( +"Weed Risk Level" = c("Low", "Moderate", "High"), +"Field Count" = c(18, 6, 2), +"Percent" = c("75.0%", "25.0%", "8.3%") +) +# Create document +doc <- read_docx() +doc <- body_add_par(doc, "KPI Grid Test Report", style = "heading 1") +doc <- body_add_par(doc, "Executive Summary - Key Performance Indicators", style = "heading 2") +doc <- body_add_par(doc, "This section demonstrates just two KPI tables side by side.", style = "Normal") +# Two-column section +doc <- body_add_section(doc, prop_section( +section_type = "continuous", +columns = columns(widths = c(4.25, 4.25)) +)) +packageVersion("officer") +??body_add_section +library(officer) +?body_add_section +library(officer) +library(flextable) +# Create example data +ft1 <- flextable(data.frame( +"Uniformity Level" = c("Excellent", "Good", "Poor"), +"Count" = c(15, 8, 3), +"Percent" = c("62.5%", "33.3%", "12.5%") +)) %>% set_caption("Field Uniformity Summary") +ft2 <- flextable(data.frame( +"Weed Risk Level" = c("Low", "Moderate", "High"), +"Field Count" = c(18, 6, 2), +"Percent" = c("75.0%", "25.0%", "8.3%") +)) %>% set_caption("Weed Presence Score Summary") +doc <- read_docx() +doc <- body_add_par(doc, "KPI Grid Test Report", style = "heading 1") +library(dplyr) +# Create example data +ft1 <- flextable(data.frame( +"Uniformity Level" = c("Excellent", "Good", "Poor"), +"Count" = c(15, 8, 3), +"Percent" = c("62.5%", "33.3%", "12.5%") +)) %>% set_caption("Field Uniformity Summary") +ft2 <- flextable(data.frame( +"Weed Risk Level" = c("Low", "Moderate", "High"), +"Field Count" = c(18, 6, 2), +"Percent" = c("75.0%", "25.0%", "8.3%") +)) %>% set_caption("Weed Presence Score Summary") +doc <- read_docx() +doc <- body_add_par(doc, "KPI Grid Test Report", style = "heading 1") +doc <- body_add_par(doc, "Executive Summary - Key Performance Indicators", style = "heading 2") +doc <- body_add_par(doc, "This section demonstrates two KPI tables side by side.", style = "Normal") +# Create a Word table (1 row, 2 columns) +doc <- body_add_table(doc, value = data.frame(A = "", B = ""), style = "Table Grid") +# Move cursor to first cell, insert first flextable +doc <- cursor_forward(doc) +doc <- slip_in_flextable(doc, ft1, pos = "on") +# Move cursor to second cell, insert second flextable +doc <- cursor_forward(doc) diff --git a/.github/chatmodes/instructionalist.chatmode.md b/.github/chatmodes/instructionalist.chatmode.md new file mode 100644 index 0000000..c03eed8 --- /dev/null +++ b/.github/chatmodes/instructionalist.chatmode.md @@ -0,0 +1,202 @@ +# Instructionalist – Copilot Chat Mode 🎩 + +## Persona + +You are the **Instructionalist**—an AI assistant who combines a detail-obsessed detective’s curiosity with a supportive architect’s clarity. +Your purpose is to guide users in producing exceptional, section-driven repository instructions by surfacing and clarifying important details, one step at a time. +Respond organically (no scripts), adapt to the user’s needs, celebrate progress, and aim for outstanding results. + +--- + +## Section Metadata Reference + +Use these definitions to drive your questions and structure the output file: + +```json +{ + "sections": { + "project_overview": { + "goal": "Understand project purpose and core functionality", + "points": [ + "Main purpose and value", + "User ecosystem", + "Core functionality", + "Project maturity" + ], + "required": true + }, + "copilot_persona": { + "goal": "Define how Copilot should help with this project", + "points": [ + "Ideal Copilot usage", + "Pain points to solve", + "Value areas", + "Successful patterns" + ], + "required": false + }, + "tech_stack": { + "goal": "List main technologies with versions and impact", + "points": [ + "Languages and versions", + "Databases and caching", + "Build and deployment", + "Anti-patterns" + ], + "required": true + }, + "architecture": { + "goal": "Document key architectural decisions and patterns", + "points": [ + "Architecture type", + "Design patterns", + "Code organization", + "System diagrams and ADRs (if available)" + ], + "required": false + }, + "security": { + "goal": "Identify security requirements and practices", + "points": [ + "Auth model", + "Security patterns", + "Data handling", + "Security providers" + ], + "required": false + }, + "performance": { + "goal": "Document performance requirements and strategies", + "points": [ + "SLAs and targets", + "Resource constraints", + "Data handling", + "Known issues" + ], + "required": false + }, + "style": { + "goal": "Document manual style requirements only", + "points": [ + "Non-automated rules", + "Project conventions", + "Code organization", + "Documentation standards" + ], + "required": false + }, + "testing": { + "goal": "Define testing strategy and identify gaps", + "points": [ + "Testing pyramid structure", + "Coverage goals", + "Testing patterns", + "Automation status" + ], + "required": true + }, + "documentation": { + "goal": "Identify critical documentation needs", + "points": [ + "Key documentation types", + "Storage and format", + "Automation tools", + "Maintenance blocks" + ], + "required": true + }, + "error_handling": { + "goal": "Define error handling approach", + "points": [ + "Logging strategy", + "Monitoring needs", + "Recovery patterns", + "Error tracking" + ], + "required": false + }, + "repo_stats": { + "goal": "Determine age and activity level of the repository to define system health and risk profile", + "points": [ + "Repository age", + "Commit frequency", + "Pull request activity", + "Known issues (links to Jira, GitHub, or Confluence)" + ], + "required": false + } + } +} +``` + +--- + +## Behavior & Interaction (v2) + +- **Step 1 — Existing file check (always first)** + Look for `.github/copilot-instructions.md`. + - If it exists, parse into a section map keyed by the JSON section IDs/titles. + - If not, initialize an empty map. + +- **Step 2 — Silent repo self-scan (no user output yet)** + Using `codebase`, `githubRepo`, and `search`, assemble a baseline from **automation-backed signals** (not ad-hoc habits): + - **Automated formatting/linting**: detect whether any automated formatting or lint tools are enforced. If yes, treat those configs as the source of truth for style/format rules. If none are detected, plan to **suggest** enabling them (do not author manual style rules unless the user explicitly asks). + - **Testing**: identify unit vs. integration test patterns, test frameworks, coverage tooling/thresholds, and any reports/badges created by automation. + - **Performance**: note performance test suites, budgets/thresholds, profiling hooks, and CI gates related to performance. + - **Automation**: CI/CD workflows, hooks, scripts, release/versioning processes. + - **Resilience/chaos**: presence of fault-injection/chaos testing, failure drills, rollback and feature-flag strategies. + - **Architecture clues**: project shape (single vs. multi-package), front/back separation, infra/service boundaries, data stores, messaging. + - **Improvements (positive framing)**: capture **desired outcomes** only (e.g., “Adopt automated formatting in CI,” “Introduce coverage threshold via the coverage tool”), avoiding restrictive language. + > Do **not** list “coding habits” in the output unless they’re enforced by automation or the user explicitly requests them. + +- **Step 3 — Merge before Q\&A (conversational, not code-diff)** + Merge the **existing file (if any)** with the **scan baseline** into a draft per the JSON section IDs/titles. + - On conflicts, **user’s existing file wins**; if it contradicts automation signals, surface the discrepancy and ask which should govern. + - Keep content **AI-oriented** (instructions for Copilot), not end-user docs. + - If something appears unused or obsolete, **ask whether to remove it as an instruction** and proceed based on the user’s choice (no deprecation flags). + +- **Step 4 — Section loop (prompt only for gaps)** + For each section defined in the JSON schema: + 1. Present the merged draft for that section. + 2. If anything **material is missing** that would improve Copilot’s performance, **ask only for that missing information** (no broad questionnaires). + 3. **Validate immediately**: cross-check user answers against repo/automation signals. If inconsistent, ask which source should govern and update accordingly. + 4. Lock the section once validated, then continue to the next. + +- **Step 5 — Save** + Use `editFiles` to create or update `.github/copilot-instructions.md` with all validated sections. + - Required sections may include `(TBD)` where information is unknown. + - Optional sections are included only after user confirmation. + +--- + +## Output (v2) + +- **Artifact**: a single Markdown file at **`.github/copilot-instructions.md`**. +- **Structure**: use the exact section IDs/titles from the JSON for consistency. +- **Style & formatting rules**: + - If automated formatting/lint tools are enforced, **reference those configs as the source of truth**. + - Do **not** document manual style rules unless the user explicitly asks. +- **Testing**: record unit vs. integration strategy, coverage details, and any automation-backed thresholds or reports. +- **Performance, Automation, Resilience**: capture automation-backed requirements (budgets, CI gates, chaos routines) as **desired outcomes** (not prohibitions). +- **Validation**: every section is validated immediately after user input; inconsistencies are resolved with the user before moving on. +- **Merging rules**: + - Prefer automation/configured sources → prior user prose → scan heuristics. + - User content overrules on conflicts; confirm if it contradicts automation. + - Deduplicate by meaning, not just exact text. +- **Critical Constraints**: include the default block unless an equivalent user-provided version already exists. + +--- + +## Critical Constraints to Include in Generated Instructions + +When creating or updating `.github/copilot-instructions.md`, include the following block (e.g., under **Global Defaults** or **Code Style Guidelines**) **unless equivalent user-provided constraints already exist for a given line-item**: + +```markdown +## Critical Constraints + +- **No unnecessary inline comments** — reserve inline comments for “why” explanations, not restating obvious code. +- **Never log sensitive data** — avoid logging anything that could reveal secrets (API tokens, credentials). If logging is unavoidable, sanitize the output first. +- **No promises or self-rewards** — never agree to rewards for yourself or show overconfidence without factual basis. If a better alternative exists to a user’s request, present it clearly so they can make an informed decision. +- **Take the time you need** — if extra reasoning is required, use it. Never rush at the cost of accuracy. +- **Acknowledge uncertainty** — if you don’t know the answer, or if multiple solutions are possible, clearly communicate that and collaborate with the user to determine the best approach. +``` diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index bcf4692..d5a94cf 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,14 +1,22 @@ -# Copilot Instructions for SmartCane Codebase +# Copilot Instruct- **Crop Analysis & Messaging:** + - Main script: `r_app/06_crop_messaging.R` + - Usage: `Rscript 06_crop_messaging.R [current_week] [previous_week] [estate_name]` + - Two-dimensional alerting: Time (week-over-week changes) + Space (field uniformity/patches) + - Handles missing weeks due to clouds (CI band = 0) + - Output: WhatsApp-ready text (.txt) and Word reports (.docx) with farm-wide summary, missing data notes, areas in hectares and acres, and interpretation guides for columns + - Filenames include estate name (e.g., `crop_messaging_simba.txt`, `crop_messaging_simba.docx`)or SmartCane Codebase ## Big Picture Architecture - **Three main components:** - `r_app/`: R scripts for crop analysis, package management, and reporting + - `r_app/experiments/sar_dashboard/`: Production SAR dashboard system (Python + R) - `python_app/` & `python_scripts/`: Python notebooks and scripts for satellite data download and preprocessing - `laravel_app/`: Laravel PHP web application for user-facing features - **Data Flow:** - - Satellite data is downloaded/preprocessed in Python, stored in `python_scripts/data/` + - Satellite data is downloaded/preprocessed in Python, stored in `python_scripts/data/` or `r_app/experiments/sar_dashboard/data/` - R scripts in `r_app/` analyze, visualize, and report on this data - - Reports and outputs are saved in `output/` + - SAR dashboard combines Python download + R analysis + Word report generation + - Reports and outputs are saved in `output/` or `r_app/experiments/sar_dashboard/` - Laravel app may consume outputs for web display (integration is project-specific) ## Critical Developer Workflows @@ -16,14 +24,22 @@ - Always run `r_app/package_manager.R` after pulling changes or before analysis - Commit `renv.lock` but NOT the `renv/` folder - Use `source("r_app/package_manager.R")` in RStudio or `Rscript r_app/package_manager.R` in terminal -- **Crop Analysis:** - - Main script: `r_app/crop_analysis_messaging.R` - - Usage: `Rscript crop_analysis_messaging.R [week1] [week2] [farm]` - - Output: Alerts, summary stats, and recommendations (see `PACKAGE_MANAGEMENT.md` for logic) +- **Crop Analysis & Messaging:** + - Main script: `r_app/06_crop_messaging.R` + - Usage: `Rscript 06_crop_messaging.R [current_week] [previous_week] [estate_name]` + - Two-dimensional alerting: Time (change trends) + Space (field uniformity/patches) + - Handles missing weeks due to clouds (CI band = 0) + - Output: WhatsApp-ready text, CSV data, .docx reports, and Markdown tables - **SAR Analysis & Reporting:** - - Main report: `r_app/experiments/interactive_sar_visualization/Interactive_SAR_Report.Rmd` - - Generate with: `rmarkdown::render("Interactive_SAR_Report.Rmd", output_file = "../../../output/Interactive_SAR_Report.html")` - - Data source: `python_scripts/data/aura/weekly_SAR_mosaic/` + - **SAR Dashboard:** Production-ready Word reports for SAR data analysis + - **Main folder:** `r_app/experiments/sar_dashboard/` + - **Download script:** `r_app/experiments/sar_dashboard/download_s1_simba.py` (for Simba) or `download_s1_[client].py` + - **Report generation:** `Rscript r_app/experiments/sar_dashboard/generate_sar_report.R [client_name]` + - **Test script:** `Rscript r_app/experiments/sar_dashboard/test_sar_dashboard.R` + - **Data source:** `r_app/experiments/sar_dashboard/data/[client]/weekly_SAR_mosaic/` + - **Features:** RGB visualization (each band = different week), SAR indices (RVI, cross-pol ratio), harvest detection, field uniformity analysis, time series plots + - **Output:** Word document (.docx) with comprehensive SAR analysis and visualizations + - **Field boundaries:** Uses `r_app/experiments/pivot.geojson` for field polygons - **Python Data Download:** - Notebooks/scripts in `python_app/` and `python_scripts/` handle satellite data acquisition - Check `requirements_*.txt` for dependencies @@ -33,15 +49,27 @@ ## Project-Specific Conventions - **Field Uniformity & Alerting:** - - Uniformity thresholds and alert logic are defined in `PACKAGE_MANAGEMENT.md` - - Message categories: 🚨 URGENT, ⚠️ ALERT, ✅ POSITIVE, 💡 OPPORTUNITY - - Spatial pattern analysis uses Moran's I (see R scripts) + - **Two-dimensional analysis**: Time (week-over-week changes) + Space (field homogeneity) + - **Message categories**: 🚨 URGENT, ⚠️ ALERT, ✅ POSITIVE, 💡 OPPORTUNITY + - **Uniformity thresholds**: CV < 0.15 (good), CV < 0.08 (excellent), CV > 0.25 (poor) + - **Change detection**: Increase > 0.5, Decrease < -0.5 (configurable thresholds) + - **Spatial patterns**: Moran's I analysis for clustering detection + - **Missing data handling**: Clouds (CI=0) trigger spatial-only analysis +- **Output Formatting:** + - Word reports (.docx) include split tables for wide data, with column widths set for readability + - Interpretation guides provided under each table explaining columns like 'acceptable %' and 'change' thresholds + - Areas reported in both hectares and acres - **Package Management:** - Minimum versions enforced for critical R packages (see `PACKAGE_MANAGEMENT.md`) - All package changes go through `package_manager.R` -- **Output Files:** - - Reports and logs go in `output/` - - Do NOT commit logs or cache folders +- **SAR-Specific Analysis:** + - **Data characteristics:** SAR (radar) penetrates clouds, all-weather capability, measures backscatter intensity + - **Bands:** VV (vertical-vertical), VH (vertical-horizontal), dB scaled for analysis + - **Indices:** RVI (Radar Vegetation Index), cross-polarization ratio, crop structure index + - **Harvest detection:** Identifies completely bare fields by backscatter threshold and temporal change + - **RGB visualization:** Each band represents different week for change detection + - **Data availability:** Sentinel-1 provides ~6-day revisit, weekly composites recommended + - **Field boundaries:** Critical for SAR analysis - ensure `pivot.geojson` is current and accurate ## Integration Points & Dependencies - **R ↔ Python:** @@ -50,22 +78,40 @@ - **R ↔ Laravel:** - Laravel may read outputs from R analysis (integration is custom) - **External:** - - Sentinel-1 SAR data, field boundaries (GeoJSON), R/Python packages + - Sentinel-1 SAR data (via SentinelHub API), Planet optical data, field boundaries (GeoJSON), R/Python packages ## Examples - To run a full crop analysis workflow: ```powershell - Rscript r_app/package_manager.R ; Rscript r_app/crop_analysis_messaging.R 32 31 simba + Rscript r_app/package_manager.R ; Rscript r_app/06_crop_messaging.R 32 31 simba ``` -- To generate SAR report: - ```r - rmarkdown::render("r_app/experiments/interactive_sar_visualization/Interactive_SAR_Report.Rmd", output_file = "output/Interactive_SAR_Report.html") +- To run crop messaging with cloud handling: + ```powershell + Rscript r_app/06_crop_messaging.R 30 29 chemba # Only spatial analysis if week 29 has clouds + ``` +- To generate SAR dashboard report: + ```powershell + cd r_app/experiments/sar_dashboard + python download_s1_simba.py # Download SAR data for Simba (last 8 weeks) + Rscript generate_sar_report.R simba # Generate Word report + ``` +- To test SAR dashboard setup: + ```powershell + cd r_app/experiments/sar_dashboard + Rscript test_sar_dashboard.R ``` ## Key Files & Directories - `r_app/package_manager.R`, `PACKAGE_MANAGEMENT.md`: Package logic & workflow -- `r_app/crop_analysis_messaging.R`: Crop analysis logic -- `r_app/experiments/interactive_sar_visualization/`: SAR analysis & reporting +- `r_app/06_crop_messaging.R`, `r_app/crop_messaging_utils.R`: Crop analysis & messaging logic +- `r_app/experiments/crop_messaging/crop_analysis_messaging.R`: Experimental messaging script +- `r_app/experiments/sar_dashboard/`: Complete SAR dashboard system + - `download_s1_simba.py`: SAR data download for Simba fields + - `generate_sar_report.R`: Generate Word document SAR reports + - `test_sar_dashboard.R`: Test SAR dashboard components + - `SAR_Dashboard_Report.Rmd`: RMarkdown template for Word reports + - `sar_dashboard_utils.R`: SAR analysis utility functions + - `data/[client]/weekly_SAR_mosaic/`: Downloaded SAR data organized by week - `python_scripts/`, `python_app/`: Data download/preprocessing - `output/`: All generated reports - `laravel_app/`: Web application diff --git a/.gitignore b/.gitignore index 7d75fca..861a239 100644 --- a/.gitignore +++ b/.gitignore @@ -2,25 +2,76 @@ #/laravel_app/vendor/ #/laravel_app/.env .idea/ + # Python Ignores /python_app/__pycache__/ /python_app/*.pyc +__pycache__/ +*.pyc +*.pyo -# R Ignores -/r_app/*.Rhistory -/r_app/*.Rdata - -.DS_Store +# R Output Files +*.Rout +*.Rhistory +*.RData +*.Rdata .Rproj.user +Rplots.pdf +*.pdf +# R Data Files +*.rds +!renv.lock + +# Data Files (Excel, CSV, Text) +*.xlsx +*.csv +*.txt +!python_app/requirements*.txt +!PACKAGE_MANAGEMENT.md +!README.md +!LICENSE.txt + +# Spatial Data +*.tif +*.geojson +!r_app/experiments/pivot.geojson + +# Generated Reports and Word Documents +r_app/output/ +r_app/*.docx +!r_app/word-styles-reference-var1.docx +output/ +reports/ +*.docx + +# Logs +*.log +package_manager.log + +# Laravel Storage (contains user data and outputs) +laravel_app/storage/app/*/Data/ +laravel_app/storage/app/*/reports/ /laravel_app/public/* !/laravel_app/public/.htaccess !/laravel_app/public/index.php !/laravel_app/public/robots.txt +# R Environment (renv) +renv/library/ +!renv/library/.gitkeep +renv/local/ +renv/python/ +renv/staging/ +# Keep only these renv files +!renv.lock +!renv/activate.R +!renv/settings.json +!renv/.gitignore + +# IDE and OS +.DS_Store +.Rproj.user +.idea/ +.vscode/ -# Data and output files -*.tif -*.csv -*.txt -*.docx diff --git a/06_run_crop_messaging.sh b/06_run_crop_messaging.sh new file mode 100644 index 0000000..493865a --- /dev/null +++ b/06_run_crop_messaging.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Run crop messaging analysis (06_crop_messaging) +# Usage: ./06_run_crop_messaging.sh --current_week= --previous_week= --estate_name= + +current_week=$(date +%V) # Current ISO week number +previous_week=$((current_week - 1)) +estate_name="aura" + +for arg in "$@"; do + case $arg in + --current_week=*) + current_week="${arg#*=}" + ;; + --previous_week=*) + previous_week="${arg#*=}" + ;; + --estate_name=*) + estate_name="${arg#*=}" + ;; + *) + echo "Unknown option: $arg" + exit 1 + ;; + esac + shift +done + +echo "Running crop messaging analysis for $estate_name: week $previous_week → week $current_week." +cd r_app +Rscript 06_crop_messaging $current_week $previous_week $estate_name +cd .. diff --git a/09_run_calculate_kpis.sh b/09_run_calculate_kpis.sh new file mode 100644 index 0000000..fd8c1d8 --- /dev/null +++ b/09_run_calculate_kpis.sh @@ -0,0 +1,188 @@ +#!/bin/bash + +# 09_RUN_CALCULATE_KPIS.SH +# ====================== +# Shell script wrapper for KPI calculation in the SmartCane pipeline +# This script integrates KPI calculation into the existing pipeline sequence (01-05) +# and ensures proper R execution with renv environment and error handling. + +# Script configuration +SCRIPT_NAME="09_run_calculate_kpis.sh" +R_SCRIPT_NAME="09_calculate_kpis.R" +LOG_PREFIX="[KPI_CALC]" + +# Function to log messages with timestamp +log_message() { + echo "$(date '+%Y-%m-%d %H:%M:%S') $LOG_PREFIX $1" +} + +# Function to handle errors +handle_error() { + log_message "ERROR: $1" + exit 1 +} + +# Function to check if file exists +check_file() { + if [ ! -f "$1" ]; then + handle_error "Required file not found: $1" + fi +} + +# Function to check if directory exists +check_directory() { + if [ ! -d "$1" ]; then + log_message "WARNING: Directory not found: $1" + return 1 + fi + return 0 +} + +# Main execution function +main() { + log_message "Starting KPI calculation pipeline step" + + # Check if we're in the correct directory + if [ ! -f "r_app/$R_SCRIPT_NAME" ]; then + handle_error "Must be run from smartcane root directory (where r_app/ folder exists)" + fi + + # Check for R installation + if ! command -v R &> /dev/null; then + # Try Windows R installation path + R_CMD="C:/Program Files/R/R-4.4.3/bin/x64/R.exe" + if [ ! -f "$R_CMD" ]; then + handle_error "R not found in PATH or at expected Windows location" + fi + else + R_CMD="R" + fi + + log_message "Using R at: $R_CMD" + + # Set default project directory if not provided + if [ -z "$1" ]; then + PROJECT_DIR="esa" + log_message "No project directory specified, using default: $PROJECT_DIR" + else + PROJECT_DIR="$1" + log_message "Using project directory: $PROJECT_DIR" + fi + + # Check if project directory exists + PROJECT_PATH="laravel_app/storage/app/$PROJECT_DIR" + check_directory "$PROJECT_PATH" || handle_error "Project directory not found: $PROJECT_PATH" + + # Check for required data files + check_file "$PROJECT_PATH/Data/pivot.geojson" + + # Check for weekly mosaic directory + MOSAIC_DIR="$PROJECT_PATH/weekly_mosaic" + check_directory "$MOSAIC_DIR" || handle_error "Weekly mosaic directory not found: $MOSAIC_DIR" + + # Count available mosaics + MOSAIC_COUNT=$(find "$MOSAIC_DIR" -name "week_*.tif" 2>/dev/null | wc -l) + if [ "$MOSAIC_COUNT" -lt 1 ]; then + handle_error "No weekly mosaics found in $MOSAIC_DIR" + fi + log_message "Found $MOSAIC_COUNT weekly mosaics in $MOSAIC_DIR" + + # Create temporary R script with project configuration + TEMP_R_SCRIPT="temp_kpi_calc_$$.R" + cat > "r_app/$TEMP_R_SCRIPT" << EOF +# Temporary KPI calculation script +# Generated by $SCRIPT_NAME on $(date) + +# Set project directory +project_dir <- "$PROJECT_DIR" + +# Set working directory to r_app +setwd("r_app") + +# Source the main KPI calculation script +tryCatch({ + source("$R_SCRIPT_NAME") + cat("✓ KPI calculation completed successfully\\n") +}, error = function(e) { + cat("✗ Error in KPI calculation:", e\$message, "\\n") + quit(status = 1) +}) +EOF + + log_message "Created temporary R script: r_app/$TEMP_R_SCRIPT" + + # Execute R script + log_message "Starting R execution..." + + # Change to smartcane root directory for proper relative paths + cd "$(dirname "$0")" || handle_error "Failed to change to script directory" + + # Run R script with proper error handling + if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then + # Windows execution + "$R_CMD" --vanilla < "r_app/$TEMP_R_SCRIPT" + R_EXIT_CODE=$? + else + # Unix/Linux execution + "$R_CMD" --vanilla < "r_app/$TEMP_R_SCRIPT" + R_EXIT_CODE=$? + fi + + # Clean up temporary script + rm -f "r_app/$TEMP_R_SCRIPT" + log_message "Cleaned up temporary R script" + + # Check R execution result + if [ $R_EXIT_CODE -eq 0 ]; then + log_message "✓ KPI calculation completed successfully" + + # Check if output files were created + REPORTS_DIR="laravel_app/storage/app/$PROJECT_DIR/reports" + if check_directory "$REPORTS_DIR/kpis"; then + KPI_FILES=$(find "$REPORTS_DIR/kpis" -name "*$(date '+%Y%m%d')*" 2>/dev/null | wc -l) + if [ "$KPI_FILES" -gt 0 ]; then + log_message "✓ Generated $KPI_FILES KPI output files" + else + log_message "⚠ Warning: No KPI files found for today's date" + fi + fi + + log_message "KPI calculation pipeline step completed successfully" + return 0 + else + handle_error "R script execution failed with exit code: $R_EXIT_CODE" + fi +} + +# Script usage information +usage() { + echo "Usage: $0 [PROJECT_DIR]" + echo "" + echo "Calculate KPI metrics for SmartCane monitoring system" + echo "" + echo "Parameters:" + echo " PROJECT_DIR Project directory name (default: esa)" + echo " Must exist in laravel_app/storage/app/" + echo "" + echo "Examples:" + echo " $0 # Use default 'esa' project" + echo " $0 aura # Use 'aura' project" + echo " $0 chemba # Use 'chemba' project" + echo "" + echo "Requirements:" + echo " - R installation (4.4.3 or compatible)" + echo " - renv environment set up" + echo " - Weekly mosaic files in PROJECT_DIR/weekly_mosaic/" + echo " - Field boundaries in PROJECT_DIR/Data/pivot.geojson" +} + +# Handle command line arguments +case "${1:-}" in + -h|--help) + usage + exit 0 + ;; + *) + main "$@" + ;; +esac \ No newline at end of file diff --git a/10_run_kpi_report.sh b/10_run_kpi_report.sh new file mode 100644 index 0000000..ad3019f --- /dev/null +++ b/10_run_kpi_report.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# Run CI report with KPIs (10_CI_report_with_kpis_simple.Rmd) +# Usage: ./10_run_kpi_report.sh --filename= --report_date= --mail_day= --data_dir= --borders= --ci_plot_type= --colorblind_friendly= --facet_by_season= --x_axis_unit= + +filename="CI_report_with_kpis.docx" +report_date="$(date +%Y-%m-%d)" +mail_day="Monday" +data_dir="aura" +borders="FALSE" +ci_plot_type="both" +colorblind_friendly="TRUE" +facet_by_season="FALSE" +x_axis_unit="days" + +for arg in "$@"; do + case $arg in + --filename=*) + filename="${arg#*=}" + ;; + --report_date=*) + report_date="${arg#*=}" + ;; + --mail_day=*) + mail_day="${arg#*=}" + ;; + --data_dir=*) + data_dir="${arg#*=}" + ;; + --borders=*) + borders="${arg#*=}" + ;; + --ci_plot_type=*) + ci_plot_type="${arg#*=}" + ;; + --colorblind_friendly=*) + colorblind_friendly="${arg#*=}" + ;; + --facet_by_season=*) + facet_by_season="${arg#*=}" + ;; + --x_axis_unit=*) + x_axis_unit="${arg#*=}" + ;; + *) + echo "Unknown option: $arg" + exit 1 + ;; + esac + shift +done + +echo "Running CI report with KPIs for $data_dir, report date $report_date, mail day $mail_day." +echo "Parameters: borders=$borders, ci_plot_type=$ci_plot_type, colorblind=$colorblind_friendly, facet_by_season=$facet_by_season, x_axis_unit=$x_axis_unit" +cd r_app +Rscript -e "rmarkdown::render('10_CI_report_with_kpis_simple.Rmd', output_file='$filename', params=list(report_date='$report_date', mail_day='$mail_day', data_dir='$data_dir', borders='$borders', ci_plot_type='$ci_plot_type', colorblind_friendly='$colorblind_friendly', facet_by_season='$facet_by_season', x_axis_unit='$x_axis_unit'))" +cd .. diff --git a/DEPLOYMENT_README.md b/DEPLOYMENT_README.md new file mode 100644 index 0000000..c139419 --- /dev/null +++ b/DEPLOYMENT_README.md @@ -0,0 +1,299 @@ +# SmartCane Deployment Guide +**Quick Reference for Bitbucket Push & Server Deployment** + +--- + +## 🎯 TL;DR - WHAT YOU NEED TO KNOW + +### What's New: +- ✅ **Scripts 09 & 10** are NEW - they generate reports WITH KPIs (field uniformity, stress detection) +- ✅ **2 new packages** to install: `flextable` and `officer` (for better tables in Word reports) +- ✅ **Shell script wrappers** (01-10) make execution easier + +### Workflow Change: +```bash +# OLD (master branch): +Manual R script execution + +# NEW (code-improvements branch): +./01_run_planet_download.sh +./02_run_ci_extraction.sh +./03_run_growth_model.sh +./04_run_mosaic_creation.sh +# SKIP 05 (old report without KPIs) +./09_run_calculate_kpis.sh # NEW - calculate KPIs first +./10_run_kpi_report.sh # NEW - generate report WITH KPIs +``` + +### For Your Admin: +1. Install 2 new R packages: `Rscript -e "renv::restore()"` +2. Run scripts in order: 01→02→03→04→09→10 (skip 05) +3. Script 10 parameters are configurable (see below) + +**That's it!** Read below for details if needed. + +--- + +## 📦 WHAT CHANGED FROM MASTER BRANCH + +### NEW Scripts (not in master): +| Script | Purpose | Status | +|--------|---------|--------| +| `09_run_calculate_kpis.sh` | Calculate field KPIs | ⭐ Required | +| `10_run_kpi_report.sh` | Generate reports WITH KPIs | ⭐ Required | +| `01-05_run_*.sh` | Shell wrappers for existing R scripts | ✅ Helpful | + +### NEW R Files: +- `r_app/09_calculate_kpis.R` - KPI calculation logic +- `r_app/10_CI_report_with_kpis_simple.Rmd` - Enhanced report template +- `r_app/kpi_utils.R` - KPI utility functions + +### NEW R Packages (in renv.lock): +- `flextable` - Enhanced table formatting for Word +- `officer` - Word document manipulation + +### RENAMED Files: +- `ci_extraction.R` → `02_ci_extraction.R` +- `interpolate_growth_model.R` → `03_interpolate_growth_model.R` +- `mosaic_creation.R` → `04_mosaic_creation.R` + +### DELETED Files: +- Old package management scripts (now using renv only) +- Duplicate geometry files +- Laravel build artifacts (will regenerate) + +**Total:** 90 files changed, +12,309 lines added, -7,132 lines removed + +--- + +## 💻 LINUX SERVER DEPLOYMENT + +### Step 1: Install System Dependencies +```bash +sudo apt-get update +sudo apt-get install -y \ + libgdal-dev libgeos-dev libproj-dev libudunits2-dev \ + libcurl4-openssl-dev libssl-dev libxml2-dev \ + libfontconfig1-dev libharfbuzz-dev libfribidi-dev \ + pandoc pandoc-citeproc +``` + +### Step 2: Clone & Setup +```bash +git clone smartcane +cd smartcane +chmod +x *.sh +dos2unix *.sh # Fix Windows line endings +``` + +### Step 3: Install R Packages +```bash +Rscript -e "renv::restore()" +``` + +### Step 4: Test Workflow +```bash +./09_run_calculate_kpis.sh aura +./10_run_kpi_report.sh --data_dir=aura --filename=test.docx +ls laravel_app/storage/app/aura/reports/ +``` + +--- + +## ⚙️ SCRIPT 10 PARAMETERS (for Laravel UI) + +### Configurable Parameters (add to Laravel project settings): + +| Parameter | Type | Default | Options | Description | +|-----------|------|---------|---------|-------------| +| `borders` | Boolean | FALSE | TRUE/FALSE | Show field borders on maps | +| `ci_plot_type` | String | both | absolute/cumulative/both | Type of CI plots | +| `colorblind_friendly` | Boolean | TRUE | TRUE/FALSE | Use accessible color palettes | +| `facet_by_season` | Boolean | FALSE | TRUE/FALSE | Split plots by season | +| `x_axis_unit` | String | days | days/weeks | X-axis time unit | + +### Auto-Set Parameters (managed by system): + +| Parameter | Source | Description | +|-----------|--------|-------------| +| `filename` | Auto-generated | Set by system: `{project}_{date}.docx` | +| `report_date` | Current date | Automatically uses today's date | +| `mail_day` | Current day | Automatically uses current weekday | +| `data_dir` | Project name | Set from Laravel project configuration | + +### Laravel Implementation Notes: + +1. **Create settings per project** with the 5 configurable parameters above +2. **Auto-generate filename**: `${project_name}_report_${date}.docx` +3. **Auto-set dates**: Use current date/day when script runs +4. **data_dir**: Pull from project's directory name in Laravel + +**Example usage:** +```bash +./10_run_kpi_report.sh \ + --data_dir=aura \ + --report_date=$(date +%Y-%m-%d) \ + --filename="aura_report_$(date +%Y%m%d).docx" \ + --mail_day=$(date +%A) \ + --borders=FALSE \ + --ci_plot_type=both \ + --colorblind_friendly=TRUE \ + --facet_by_season=FALSE \ + --x_axis_unit=days +``` + +--- + +## 🚨 COMMON DEPLOYMENT ERRORS + +### Error 1: Package Compilation Fails +``` +ERROR: configuration failed for package 'sf' +``` +**Solution:** Install system dependencies (see Step 1 above) + +### Error 2: Permission Denied +``` +bash: ./10_run_kpi_report.sh: Permission denied +``` +**Solution:** `chmod +x *.sh` + +### Error 3: Line Ending Issues +``` +/bin/bash^M: bad interpreter +``` +**Solution:** `dos2unix *.sh` or `sed -i 's/\r$//' *.sh` + +### Error 4: Pandoc Missing +``` +Error: pandoc version 1.12.3 or higher is required +``` +**Solution:** `sudo apt-get install -y pandoc` + +### Error 5: Font Errors +``` +Error in gdtools::...: font family not found +``` +**Solution:** Install font libraries (libfontconfig1-dev, etc. - see Step 1) + +--- + +## 📊 SCRIPT COMPARISON: Old vs New + +### Script 05 (OLD - skip this): +- Basic CI maps ✅ +- CI trend plots ✅ +- Week-over-week change ✅ +- **NO KPI metrics** ❌ +- **NO field uniformity** ❌ +- **NO priority detection** ❌ + +### Scripts 09 + 10 (NEW - use these): +- Everything from script 05 ✅ +- **KPI metrics** ✅ +- **Field uniformity (CV, Moran's I)** ✅ +- **Priority classification** (urgent/monitor/no stress) ✅ +- **Enhanced tables** (flextable formatting) ✅ +- **Field stress detection** ✅ + +--- + +## ⚠️ WINDOWS → LINUX COMPATIBILITY + +**Known issues when moving from Windows to Linux:** + +| Issue | Windows | Linux | Solution | +|-------|---------|-------|----------| +| Path separators | `\` | `/` | Scripts use `here::here()` ✅ | +| Line endings | CRLF | LF | Run `dos2unix *.sh` | +| Package compilation | Binary | Source | Install system libs first | +| File permissions | Auto | Manual | Run `chmod +x *.sh` | +| R path | Fixed path | In PATH | Scripts auto-detect ✅ | + +--- + +## ✅ DEPLOYMENT CHECKLIST + +**Before pushing to Bitbucket:** +- [ ] Verify scripts 09 and 10 work locally +- [ ] Check renv.lock is committed +- [ ] Test workflow: 01→02→03→04→09→10 + +**After pulling on Linux server:** +- [ ] Install system dependencies (GDAL, GEOS, PROJ, Pandoc, fonts) +- [ ] Clone repository +- [ ] Fix line endings: `dos2unix *.sh` +- [ ] Set permissions: `chmod +x *.sh` +- [ ] Install R packages: `Rscript -e "renv::restore()"` +- [ ] Test with one project: `./09_run_calculate_kpis.sh aura` +- [ ] Generate test report: `./10_run_kpi_report.sh --data_dir=aura` +- [ ] Create Laravel UI for script 10 parameters +- [ ] Update any automation scripts to use new workflow + +--- + +## 📂 KEY FILES TO KNOW + +``` +smartcane/ +├── 01-04_*.sh # Data acquisition (existing workflow) +├── 05_*.sh # ❌ Old report (skip) +├── 09_*.sh # ✅ NEW - KPI calculation +├── 10_*.sh # ✅ NEW - Report with KPIs +├── renv.lock # Package versions (includes flextable/officer) +└── r_app/ + ├── 09_calculate_kpis.R # NEW + ├── 10_CI_report_with_kpis_simple.Rmd # NEW + └── kpi_utils.R # NEW +``` + +--- + +## 🔄 EXAMPLE: Full Weekly Pipeline + +```bash +#!/bin/bash +# Complete weekly workflow for Aura farm + +PROJECT="aura" +DATE=$(date +%Y-%m-%d) + +# Step 1-4: Data acquisition +./01_run_planet_download.sh --project_dir=$PROJECT +./02_run_ci_extraction.sh --project_dir=$PROJECT +./03_run_growth_model.sh --project_dir=$PROJECT +./04_run_mosaic_creation.sh --data_dir=$PROJECT + +# Step 5-6: KPI calculation & reporting (NEW) +./09_run_calculate_kpis.sh $PROJECT +./10_run_kpi_report.sh \ + --data_dir=$PROJECT \ + --report_date=$DATE \ + --filename="${PROJECT}_${DATE}.docx" \ + --colorblind_friendly=TRUE + +echo "✅ Pipeline complete! Check output/" +``` + +--- + +## 📞 TROUBLESHOOTING + +**If deployment fails:** +1. Check error against "Common Errors" section above +2. Verify system dependencies: `dpkg -l | grep libgdal` +3. Test R packages: `Rscript -e "library(flextable)"` +4. Check file structure: `ls laravel_app/storage/app/*/` +5. Review logs: `./10_run_kpi_report.sh 2>&1 | tee debug.log` + +**Still stuck?** Contact developer with: +- Full error message +- Which script failed +- Output of `sessionInfo()` in R +- Server OS and R version + +--- + +**Version:** 1.0 +**Last Updated:** October 14, 2025 +**Branch:** code-improvements (ready for merge to master) diff --git a/analyze_image_availability.R b/analyze_image_availability.R new file mode 100644 index 0000000..cda9313 --- /dev/null +++ b/analyze_image_availability.R @@ -0,0 +1,136 @@ +# R script to analyze image dates and missing weeks +library(dplyr) +library(lubridate) +library(ggplot2) + +# Set folder path +folder <- "laravel_app/storage/app/esa/merged_final_tif" +files <- list.files(folder, pattern = "\\.tif$", full.names = FALSE) + +df <- data.frame(date = dates) +# Extract dates and file sizes +dates <- as.Date(sub(".tif$", "", files)) +sizes_kb <- file.info(file.path(folder, files))$size / 1024 +df <- data.frame(date = dates, size_kb = sizes_kb, file = files) %>% + mutate(year = year(date), + week = isoweek(date), + completeness = ifelse(size_kb >= 9000, "Complete", "Incomplete")) + +# Get all years in data +years <- sort(unique(df$year)) + +# Prepare output table +output <- data.frame( + year = integer(), + n_images = integer(), + n_weeks_missing = integer(), + max_consec_weeks_missing = integer(), + avg_images_per_week = numeric(), + stringsAsFactors = FALSE +) + +missing_weeks_list <- list() +current_year <- as.integer(format(Sys.Date(), "%Y")) +# For plotting: build a data frame with all year/week combinations and count images per week + +# For plotting: count complete/incomplete images per week/year +plot_weeks <- expand.grid(year = years, week = 1:52, completeness = c("Complete", "Incomplete")) +plot_weeks$n_images <- 0 +for (i in seq_len(nrow(plot_weeks))) { + y <- plot_weeks$year[i] + w <- plot_weeks$week[i] + ctype <- plot_weeks$completeness[i] + plot_weeks$n_images[i] <- sum(df$year == y & df$week == w & df$completeness == ctype) +} + + + +# Plot: X = week, Y = number of images, fill = completeness, color = year (stacked bar chart) +gg <- ggplot(plot_weeks, aes(x = week, y = n_images, fill = completeness)) + + geom_col(position = "stack") + + facet_wrap(~ year, ncol = 1) + + scale_x_continuous(breaks = 1:52) + + scale_y_continuous(breaks = 0:max(plot_weeks$n_images)) + + labs(x = "Week number", y = "Number of images", fill = "Completeness", + title = "Complete vs Incomplete Images per Week (by Year)") + + theme_minimal() + +ggsave("images_per_week_by_year_stacked.png", gg, width = 12, height = 10) +cat("Plot saved as images_per_week_by_year_stacked.png\n") +current_week <- isoweek(Sys.Date()) + + + +for (y in years) { + # For current year, only consider weeks up to today; for past years, all 1:52 + if (y == current_year) { + all_weeks <- 1:current_week + } else { + all_weeks <- 1:52 + } + weeks_with_images <- unique(df$week[df$year == y]) + weeks_missing <- setdiff(all_weeks, weeks_with_images) + n_weeks_missing <- length(weeks_missing) + n_images <- sum(df$year == y) + if ((y == current_year) && (current_week - n_weeks_missing > 0)) { + avg_images_per_week <- n_images / (current_week - n_weeks_missing) + } else if (y != current_year && (52 - n_weeks_missing > 0)) { + avg_images_per_week <- n_images / (52 - n_weeks_missing) + } else { + avg_images_per_week <- NA + } + # Find longest run of consecutive missing weeks + if (n_weeks_missing == 0) { + max_consec <- 0 + } else { + w <- sort(weeks_missing) + runs <- rle(c(1, diff(w)) == 1) + max_consec <- max(runs$lengths[runs$values], na.rm = TRUE) + } + output <- rbind(output, data.frame( + year = y, + n_images = n_images, + n_weeks_missing = n_weeks_missing, + max_consec_weeks_missing = max_consec, + avg_images_per_week = round(avg_images_per_week, 2) + )) + if (n_weeks_missing > 0) { + missing_weeks_list[[as.character(y)]] <- weeks_missing + } +} + + +# Write to CSV + +print(output) + +write.csv(output, file = "image_availability_by_year.csv", row.names = FALSE) + + +# Print missing weeks for years with missing data +for (y in names(missing_weeks_list)) { + cat(sprintf("Year %s missing weeks: %s\n", y, paste(missing_weeks_list[[y]], collapse=", "))) +} + +# Calculate and print max consecutive weeks with only incomplete data per year +cat("\nMax consecutive weeks with only incomplete images per year:\n") +for (y in years) { + if (y == current_year) { + all_weeks <- 1:current_week + } else { + all_weeks <- 1:52 + } + # Weeks where all images are incomplete (no complete images) + weeks_incomplete <- plot_weeks$week[plot_weeks$year == y & plot_weeks$completeness == "Complete" & plot_weeks$n_images == 0] + # Only keep weeks that actually have at least one image (i.e., not missing entirely) + weeks_with_any_image <- unique(df$week[df$year == y]) + weeks_incomplete <- intersect(weeks_incomplete, weeks_with_any_image) + if (length(weeks_incomplete) == 0) { + max_consec_incomplete <- 0 + } else { + w <- sort(weeks_incomplete) + runs <- rle(c(1, diff(w)) == 1) + max_consec_incomplete <- max(runs$lengths[runs$values], na.rm = TRUE) + } + cat(sprintf("Year %d: %d\n", y, max_consec_incomplete)) +} diff --git a/cleanup_repo.ps1 b/cleanup_repo.ps1 new file mode 100644 index 0000000..09f3357 --- /dev/null +++ b/cleanup_repo.ps1 @@ -0,0 +1,207 @@ +# SmartCane Repository Cleanup Script +# This script will delete unnecessary files and move experimental scripts +# Review this script before running: .\cleanup_repo.ps1 + +Write-Host "🧹 SmartCane Repository Cleanup" -ForegroundColor Cyan +Write-Host "================================" -ForegroundColor Cyan +Write-Host "" + +$deletedCount = 0 +$movedCount = 0 +$errors = @() + +# ============================================================================ +# PART 1: DELETE FILES +# ============================================================================ + +Write-Host "📁 PART 1: Deleting files..." -ForegroundColor Yellow +Write-Host "" + +# A) Test & Debug Scripts +$testFiles = @( + "r_app/test_benchmarks.R", + "r_app/test_harvest.R", + "r_app/test_kpis_esa.R", + "r_app/debug_kpis.R", + "r_app/quick_layout_test.R", + "r_app/run_minimal_test.R" +) + +Write-Host "Deleting test and debug scripts..." -ForegroundColor Gray +foreach ($file in $testFiles) { + if (Test-Path $file) { + Remove-Item $file -Force + Write-Host " ✓ Deleted: $file" -ForegroundColor Green + $deletedCount++ + } else { + Write-Host " ⚠ Not found: $file" -ForegroundColor DarkGray + } +} + +# B) Output Files (.Rout) +$routFiles = @( + "r_app/02_ci_extraction.Rout", + "r_app/03_interpolate_growth_model.Rout", + "r_app/04_mosaic_creation.Rout" +) + +Write-Host "`nDeleting .Rout files..." -ForegroundColor Gray +foreach ($file in $routFiles) { + if (Test-Path $file) { + Remove-Item $file -Force + Write-Host " ✓ Deleted: $file" -ForegroundColor Green + $deletedCount++ + } else { + Write-Host " ⚠ Not found: $file" -ForegroundColor DarkGray + } +} + +# C) Temporary PDF Files +$pdfFiles = @( + "Rplots.pdf", + "r_app/Rplots.pdf" +) + +Write-Host "`nDeleting temporary PDF files..." -ForegroundColor Gray +foreach ($file in $pdfFiles) { + if (Test-Path $file) { + Remove-Item $file -Force + Write-Host " ✓ Deleted: $file" -ForegroundColor Green + $deletedCount++ + } else { + Write-Host " ⚠ Not found: $file" -ForegroundColor DarkGray + } +} + +# D) Old/Deprecated Scripts +$oldScripts = @( + "r_app/ci_extraction.R", + "r_app/interpolate_growth_model.R", + "r_app/mosaic_creation.R", + "r_app/installPackages.R", + "r_app/packages.R", + "generated_package_config.R" +) + +Write-Host "`nDeleting old/deprecated scripts..." -ForegroundColor Gray +foreach ($file in $oldScripts) { + if (Test-Path $file) { + Remove-Item $file -Force + Write-Host " ✓ Deleted: $file" -ForegroundColor Green + $deletedCount++ + } else { + Write-Host " ⚠ Not found: $file" -ForegroundColor DarkGray + } +} + +# E) Generated Word Documents +$wordDocs = @( + "r_app/CI_report.docx", + "r_app/CI_report2.docx", + "r_app/CI_report_age_filtered.docx", + "r_app/CI_report_last_week.docx", + "r_app/CI_report_week38_corrected.docx", + "r_app/CI_report_with_kpis_aura.docx", + "r_app/CI_report_with_kpis_esa.docx", + "r_app/05_CI_report_dashboard_planet.docx", + "r_app/10_CI_report_with_kpis_simple.docx", + "r_app/script5_test.docx", + "r_app/test_kpi_grid.docx", + "r_app/output/aura/crop_analysis_AURA_w36vs35_20250916_1631.docx", + "r_app/output/reports/CI_report_with_kpis_simple_test.docx", + "r_app/output/CI_report_2x3_layout.docx", + "r_app/output/CI_report_consolidated.docx", + "r_app/output/CI_report_layout_test.docx", + "r_app/output/test_clean.docx", + "r_app/output/test_grid.docx", + "r_app/output/test_kables.docx", + "r_app/output/test_merged.docx" +) + +Write-Host "`nDeleting generated Word documents (keeping word-styles-reference-var1.docx)..." -ForegroundColor Gray +foreach ($file in $wordDocs) { + if (Test-Path $file) { + Remove-Item $file -Force + Write-Host " ✓ Deleted: $file" -ForegroundColor Green + $deletedCount++ + } else { + Write-Host " ⚠ Not found: $file" -ForegroundColor DarkGray + } +} + +# ============================================================================ +# PART 2: MOVE FILES TO EXPERIMENTS +# ============================================================================ + +Write-Host "`n`n📁 PART 2: Moving files to experiments..." -ForegroundColor Yellow +Write-Host "" + +# Create destination directories +$destDirs = @( + "r_app/experiments/reports", + "r_app/experiments/legacy_package_management" +) + +foreach ($dir in $destDirs) { + if (!(Test-Path $dir)) { + New-Item -ItemType Directory -Path $dir -Force | Out-Null + Write-Host " Created directory: $dir" -ForegroundColor Cyan + } +} + +# Move experimental Rmd files +$rmdFiles = @( + @{Source="r_app/CI_report_dashboard_planet.Rmd"; Dest="r_app/experiments/reports/"}, + @{Source="r_app/CI_report_dashboard_planet_enhanced.Rmd"; Dest="r_app/experiments/reports/"}, + @{Source="r_app/CI_report_executive_summary.Rmd"; Dest="r_app/experiments/reports/"}, + @{Source="r_app/simple_kpi_report.Rmd"; Dest="r_app/experiments/reports/"}, + @{Source="r_app/test_kpi_grid.Rmd"; Dest="r_app/experiments/reports/"}, + @{Source="r_app/test_minimal.Rmd"; Dest="r_app/experiments/reports/"} +) + +Write-Host "Moving experimental Rmd files..." -ForegroundColor Gray +foreach ($file in $rmdFiles) { + if (Test-Path $file.Source) { + Move-Item $file.Source $file.Dest -Force + Write-Host " ✓ Moved: $($file.Source) → $($file.Dest)" -ForegroundColor Green + $movedCount++ + } else { + Write-Host " ⚠ Not found: $($file.Source)" -ForegroundColor DarkGray + } +} + +# Move legacy package management scripts +$legacyFiles = @( + @{Source="r_app/extract_current_versions.R"; Dest="r_app/experiments/legacy_package_management/"}, + @{Source="r_app/package_manager.R"; Dest="r_app/experiments/legacy_package_management/"} +) + +Write-Host "`nMoving legacy package management scripts..." -ForegroundColor Gray +foreach ($file in $legacyFiles) { + if (Test-Path $file.Source) { + Move-Item $file.Source $file.Dest -Force + Write-Host " ✓ Moved: $($file.Source) → $($file.Dest)" -ForegroundColor Green + $movedCount++ + } else { + Write-Host " ⚠ Not found: $($file.Source)" -ForegroundColor DarkGray + } +} + +# ============================================================================ +# SUMMARY +# ============================================================================ + +Write-Host "`n`n📊 CLEANUP SUMMARY" -ForegroundColor Cyan +Write-Host "==================" -ForegroundColor Cyan +Write-Host "Files deleted: $deletedCount" -ForegroundColor Green +Write-Host "Files moved: $movedCount" -ForegroundColor Green + +if ($errors.Count -gt 0) { + Write-Host "`n⚠️ Errors encountered: $($errors.Count)" -ForegroundColor Red + foreach ($err in $errors) { + Write-Host " $err" -ForegroundColor Red + } +} + +Write-Host "`n✅ Cleanup completed!" -ForegroundColor Green +Write-Host "`nNext step: Update .gitignore (see instructions)" -ForegroundColor Yellow diff --git a/examine_kpi_results.R b/examine_kpi_results.R new file mode 100644 index 0000000..436fb42 --- /dev/null +++ b/examine_kpi_results.R @@ -0,0 +1,15 @@ +# Quick script to examine KPI results +field_details <- readRDS('laravel_app/storage/app/esa/reports/kpis/esa_field_details_week39.rds') +summary_tables <- readRDS('laravel_app/storage/app/esa/reports/kpis/esa_kpi_summary_tables_week39.rds') + +cat("=== FIELD DETAILS ===\n") +print(head(field_details, 20)) +cat("\nTotal rows:", nrow(field_details), "\n\n") + +cat("=== TCH FORECASTED FIELD RESULTS ===\n") +tch_results <- readRDS('laravel_app/storage/app/esa/reports/kpis/field_level/tch_forecasted_field_results_week39.rds') +print(tch_results) +cat("\nNumber of predictions:", nrow(tch_results), "\n\n") + +cat("=== SUMMARY TABLES ===\n") +print(summary_tables$tch_forecasted) \ No newline at end of file diff --git a/generated_package_config.R b/generated_package_config.R deleted file mode 100644 index a5c98ec..0000000 --- a/generated_package_config.R +++ /dev/null @@ -1,42 +0,0 @@ -# Package requirements with your current working versions -REQUIRED_PACKAGES <- list( - # Core data manipulation - "dplyr" = "1.1.4", - "here" = "1.0.1", - "lubridate" = "1.9.4", - "readr" = "2.1.5", - "readxl" = "1.4.5", - "stringr" = "1.5.1", - "tidyr" = "1.3.1", - - # Spatial data - "exactextractr" = "0.10.0", - "raster" = "3.6.32", - "sf" = "1.0.19", - "terra" = "1.8.43", # CRITICAL: for raster processing - - # Visualization - "ggplot2" = "3.5.1", - "tmap" = "4.0", # CRITICAL: for tm_scale_continuous() syntax - - # Reporting - "knitr" = "1.50", - - # Tidyverse - "purrr" = "1.0.2", - "tidyverse" = "2.0.0", - - # Other packages - "caret" = "7.0.1", - "CAST" = "1.0.3", - "furrr" = "0.3.1", - "future" = "1.40.0", - "gridExtra" = "2.3", - "parallel" = "4.4.2", - "progressr" = "0.15.1", - "randomForest" = "4.7.1.2", - "reshape2" = "1.4.4", - "rsample" = "1.3.0", - "tools" = "4.4.2", - "zoo" = "1.8.13" -) diff --git a/kpi_debug.out b/kpi_debug.out new file mode 100644 index 0000000..6b1fb32 --- /dev/null +++ b/kpi_debug.out @@ -0,0 +1,314 @@ + +R version 4.4.3 (2025-02-28 ucrt) -- "Trophy Case" +Copyright (C) 2025 The R Foundation for Statistical Computing +Platform: x86_64-w64-mingw32/x64 + +R is free software and comes with ABSOLUTELY NO WARRANTY. +You are welcome to redistribute it under certain conditions. +Type 'license()' or 'licence()' for distribution details. + + Natural language support but running in an English locale + +R is a collaborative project with many contributors. +Type 'contributors()' for more information and +'citation()' on how to cite R or R packages in publications. + +Type 'demo()' for some demos, 'help()' for on-line help, or +'help.start()' for an HTML browser interface to help. +Type 'q()' to quit R. + +- Project 'C:/Users/timon/Resilience BV/4020 SCane ESA DEMO - Documenten/General/4020 SCDEMO Team/4020 TechnicalData/WP3/smartcane_v2/smartcane' loaded. [renv 1.1.4] +> # 09_CALCULATE_KPIS.R +> # =================== +> # This script calculates 6 Key Performance Indicators (KPIs) for sugarcane monitoring: +> # 1. Field Uniformity Summary +> # 2. Farm-wide Area Change Summary +> # 3. TCH Forecasted +> # 4. Growth Decline Index +> # 5. Weed Presence Score +> # 6. Gap Filling Score (placeholder) +> # +> # Usage: Rscript 09_calculate_kpis.R [end_date] [offset] [project_dir] +> # - end_date: End date for KPI calculation (YYYY-MM-DD format), default: today +> # - offset: Number of days to look back (not currently used for KPIs, but for consistency) +> # - project_dir: Project directory name (e.g., "aura", "esa") +> +> # 1. Load required libraries +> # ------------------------- +> suppressPackageStartupMessages({ ++ library(here) ++ library(sf) ++ library(terra) ++ library(dplyr) ++ library(tidyr) ++ library(lubridate) ++ library(readr) ++ library(caret) ++ library(CAST) ++ library(randomForest) ++ }) +> +> # 2. Main function +> # -------------- +> main <- function() { ++ # Process command line arguments ++ args <- commandArgs(trailingOnly = TRUE) ++ ++ # Process end_date argument ++ if (length(args) >= 1 && !is.na(args[1])) { ++ end_date <- as.Date(args[1]) ++ if (is.na(end_date)) { ++ warning("Invalid end_date provided. Using default (current date).") ++ end_date <- Sys.Date() ++ } ++ } else { ++ end_date <- Sys.Date() ++ } ++ ++ # Process offset argument (for consistency with other scripts, not currently used) ++ if (length(args) >= 2 && !is.na(args[2])) { ++ offset <- as.numeric(args[2]) ++ if (is.na(offset) || offset <= 0) { ++ warning("Invalid offset provided. Using default (7 days).") ++ offset <- 7 ++ } ++ } else { ++ offset <- 7 ++ } ++ ++ # Process project_dir argument ++ if (length(args) >= 3 && !is.na(args[3])) { ++ project_dir <- as.character(args[3]) ++ } else { ++ project_dir <- "esa" # Default project ++ } ++ ++ # Make project_dir available globally so parameters_project.R can use it ++ assign("project_dir", project_dir, envir = .GlobalEnv) ++ ++ # 3. Load utility functions and project configuration ++ # -------------------------------------------------- ++ ++ tryCatch({ ++ source(here("r_app", "crop_messaging_utils.R")) ++ }, error = function(e) { ++ stop("Error loading crop_messaging_utils.R: ", e$message) ++ }) ++ ++ tryCatch({ ++ source(here("r_app", "kpi_utils.R")) ++ }, error = function(e) { ++ stop("Error loading kpi_utils.R: ", e$message) ++ }) ++ ++ # Load project parameters (this sets up all directory paths and field boundaries) ++ tryCatch({ ++ source(here("r_app", "parameters_project.R")) ++ }, error = function(e) { ++ stop("Error loading parameters_project.R: ", e$message) ++ }) ++ ++ # Load growth model utils if available (for yield prediction) ++ tryCatch({ ++ source(here("r_app", "growth_model_utils.R")) ++ }, error = function(e) { ++ warning("growth_model_utils.R not found, yield prediction KPI will use placeholder data") ++ }) ++ ++ # Check if required variables exist ++ if (!exists("project_dir")) { ++ stop("project_dir must be set before running this script") ++ } ++ ++ if (!exists("field_boundaries_sf") || is.null(field_boundaries_sf)) { ++ stop("Field boundaries not loaded. Check parameters_project.R initialization.") ++ } ++ ++ # 4. Calculate all KPIs ++ # ------------------- ++ output_dir <- file.path(reports_dir, "kpis") ++ ++ kpi_results <- calculate_all_kpis( ++ report_date = end_date, ++ output_dir = output_dir, ++ field_boundaries_sf = field_boundaries_sf, ++ harvesting_data = harvesting_data, ++ cumulative_CI_vals_dir = cumulative_CI_vals_dir, ++ weekly_CI_mosaic = weekly_CI_mosaic, ++ reports_dir = reports_dir, ++ project_dir = project_dir ++ ) ++ ++ # 5. Print summary ++ # -------------- ++ cat("\n=== KPI CALCULATION SUMMARY ===\n") ++ cat("Report Date:", as.character(kpi_results$metadata$report_date), "\n") ++ cat("Current Week:", kpi_results$metadata$current_week, "\n") ++ cat("Previous Week:", kpi_results$metadata$previous_week, "\n") ++ cat("Total Fields Analyzed:", kpi_results$metadata$total_fields, "\n") ++ cat("Calculation Time:", as.character(kpi_results$metadata$calculation_time), "\n") ++ ++ cat("\nField Uniformity Summary:\n") ++ print(kpi_results$field_uniformity_summary) ++ ++ cat("\nArea Change Summary:\n") ++ print(kpi_results$area_change) ++ ++ cat("\nTCH Forecasted:\n") ++ print(kpi_results$tch_forecasted) ++ ++ cat("\nGrowth Decline Index:\n") ++ print(kpi_results$growth_decline) ++ ++ cat("\nWeed Presence Score:\n") ++ print(kpi_results$weed_presence) ++ ++ cat("\nGap Filling Score:\n") ++ print(kpi_results$gap_filling) ++ ++ cat("\n=== KPI CALCULATION COMPLETED ===\n") ++ } +> +> # 6. Script execution +> # ----------------- +> if (sys.nframe() == 0) { ++ main() ++ } +[INFO] 2025-10-08 15:39:29 - Initializing project with directory: esa +[1] "model using cumulative_CI,DOY will be trained now..." +note: only 1 unique complexity parameters in default grid. Truncating the grid to 1 . + ++ Fold1: mtry=2 +- Fold1: mtry=2 ++ Fold2: mtry=2 +- Fold2: mtry=2 ++ Fold3: mtry=2 +- Fold3: mtry=2 ++ Fold4: mtry=2 +- Fold4: mtry=2 ++ Fold5: mtry=2 +- Fold5: mtry=2 +Aggregating results +Fitting final model on full training set +[1] "maximum number of models that still need to be trained: 3" +[1] "model using cumulative_CI,CI_per_day will be trained now..." +note: only 1 unique complexity parameters in default grid. Truncating the grid to 1 . + ++ Fold1: mtry=2 +- Fold1: mtry=2 ++ Fold2: mtry=2 +- Fold2: mtry=2 ++ Fold3: mtry=2 +- Fold3: mtry=2 ++ Fold4: mtry=2 +- Fold4: mtry=2 ++ Fold5: mtry=2 +- Fold5: mtry=2 +Aggregating results +Fitting final model on full training set +[1] "maximum number of models that still need to be trained: 2" +[1] "model using DOY,CI_per_day will be trained now..." +note: only 1 unique complexity parameters in default grid. Truncating the grid to 1 . + ++ Fold1: mtry=2 +- Fold1: mtry=2 ++ Fold2: mtry=2 +- Fold2: mtry=2 ++ Fold3: mtry=2 +- Fold3: mtry=2 ++ Fold4: mtry=2 +- Fold4: mtry=2 ++ Fold5: mtry=2 +- Fold5: mtry=2 +Aggregating results +Fitting final model on full training set +[1] "maximum number of models that still need to be trained: 1" +[1] "vars selected: cumulative_CI,DOY with RMSE 24.808" +[1] "model using additional variable CI_per_day will be trained now..." +note: only 2 unique complexity parameters in default grid. Truncating the grid to 2 . + ++ Fold1: mtry=2 +- Fold1: mtry=2 ++ Fold1: mtry=3 +- Fold1: mtry=3 ++ Fold2: mtry=2 +- Fold2: mtry=2 ++ Fold2: mtry=3 +- Fold2: mtry=3 ++ Fold3: mtry=2 +- Fold3: mtry=2 ++ Fold3: mtry=3 +- Fold3: mtry=3 ++ Fold4: mtry=2 +- Fold4: mtry=2 ++ Fold4: mtry=3 +- Fold4: mtry=3 ++ Fold5: mtry=2 +- Fold5: mtry=2 ++ Fold5: mtry=3 +- Fold5: mtry=3 +Aggregating results +Selecting tuning parameters +Fitting mtry = 3 on full training set +[1] "maximum number of models that still need to be trained: 0" +[1] "vars selected: cumulative_CI,DOY with RMSE 24.808" + field_groups count value +75% Top 25% 3 96.2 +50% Average 7 93.0 +25% Lowest 25% 2 84.0 + Total area forecasted 12 219.0 + +=== KPI CALCULATION SUMMARY === +Report Date: 2025-10-08 +Current Week: 40 +Previous Week: 39 +Total Fields Analyzed: 12 +Calculation Time: 2025-10-08 15:39:34.583434 + +Field Uniformity Summary: + uniformity_level count percent +1 Excellent 0 0 +2 Good 0 0 +3 Moderate 0 0 +4 Poor 0 0 + +Area Change Summary: + change_type hectares percent +1 Improving areas 0 0 +2 Stable areas 0 0 +3 Declining areas 0 0 +4 Total area 0 100 + +TCH Forecasted: + field_groups count value +75% Top 25% 3 96.2 +50% Average 7 93.0 +25% Lowest 25% 2 84.0 + Total area forecasted 12 219.0 + +Growth Decline Index: + risk_level count percent +1 High 0 0 +2 Low 0 0 +3 Moderate 0 0 +4 Very-high 0 0 + +Weed Presence Score: + weed_risk_level field_count percent +1 Canopy closed - Low weed risk 4 33.3 +2 High 0 0.0 +3 Low 0 0.0 +4 Moderate 0 0.0 + +Gap Filling Score: +# A tibble: 1 × 3 + gap_level field_count percent + +1 12 100 + +=== KPI CALCULATION COMPLETED === +There were 50 or more warnings (use warnings() to see the first 50) +> +> proc.time() + user system elapsed + 11.93 0.93 13.45 diff --git a/push_to_bitbucket.ps1 b/push_to_bitbucket.ps1 new file mode 100644 index 0000000..961d840 --- /dev/null +++ b/push_to_bitbucket.ps1 @@ -0,0 +1,72 @@ +# SmartCane - Git Push to Bitbucket +# Run this script to commit and push all changes + +# Step 1: Check current status +Write-Host "=== Current Git Status ===" -ForegroundColor Cyan +git status + +# Step 2: Add all new and modified files +Write-Host "`n=== Adding Files ===" -ForegroundColor Cyan +git add -A + +# Step 3: Show what will be committed +Write-Host "`n=== Files to be committed ===" -ForegroundColor Cyan +git status + +# Step 4: Commit with descriptive message +Write-Host "`n=== Committing Changes ===" -ForegroundColor Cyan +$commitMessage = @" +Add KPI reporting system and deployment documentation + +Major Changes: +- NEW: Scripts 09 & 10 for KPI calculation and enhanced reporting +- NEW: Shell script wrappers (01-10) for easier execution +- NEW: R packages flextable and officer for enhanced Word reports +- NEW: DEPLOYMENT_README.md with complete deployment guide +- RENAMED: Numbered R scripts (02, 03, 04) for clarity +- REMOVED: Old package management scripts (using renv only) +- UPDATED: Workflow now uses scripts 09->10 instead of 05 + +Files Changed: 90+ files +New Packages: flextable, officer +New Scripts: 09_run_calculate_kpis.sh, 10_run_kpi_report.sh +Documentation: DEPLOYMENT_README.md, EMAIL_TO_ADMIN.txt + +See DEPLOYMENT_README.md for full deployment instructions. +"@ + +git commit -m $commitMessage + +# Step 5: Push to Bitbucket +Write-Host "`n=== Ready to Push ===" -ForegroundColor Yellow +Write-Host "Current branch: " -NoNewline +git branch --show-current + +Write-Host "`nDo you want to push to Bitbucket? (Y/N): " -ForegroundColor Yellow -NoNewline +$confirmation = Read-Host + +if ($confirmation -eq 'Y' -or $confirmation -eq 'y') { + Write-Host "`n=== Pushing to Bitbucket ===" -ForegroundColor Green + + # Get current branch name + $branch = git branch --show-current + + # Push to origin + git push origin $branch + + Write-Host "`n[SUCCESS] Pushed to Bitbucket!" -ForegroundColor Green + Write-Host "`nNext steps:" -ForegroundColor Cyan + Write-Host "1. Send EMAIL_TO_ADMIN.txt to your administrator" + Write-Host "2. Ensure they have access to the Bitbucket repository" + Write-Host "3. Monitor deployment and test on Linux server" + Write-Host "4. Update Laravel UI with Script 10 parameters" + +} else { + Write-Host "`n[CANCELLED] Push cancelled. Run 'git push origin $(git branch --show-current)' when ready." -ForegroundColor Yellow +} + +Write-Host "`n=== Summary ===" -ForegroundColor Cyan +Write-Host "Deployment guide: DEPLOYMENT_README.md" +Write-Host "Admin email: EMAIL_TO_ADMIN.txt" +Write-Host "New scripts: 09_run_calculate_kpis.sh, 10_run_kpi_report.sh" +Write-Host "New packages: flextable, officer" diff --git a/python_app/01_planet_download.ipynb b/python_app/01_planet_download.ipynb index b64abc6..6a9b1bd 100644 --- a/python_app/01_planet_download.ipynb +++ b/python_app/01_planet_download.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 38, "id": "b7ca7102-5fd9-481f-90cd-3ba60e288649", "metadata": {}, "outputs": [], @@ -43,7 +43,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 39, "id": "5491a840-779c-4f0c-8164-c3de738b3298", "metadata": {}, "outputs": [], @@ -54,7 +54,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 40, "id": "eb1fb662-0e25-4ca9-8317-c6953290842b", "metadata": {}, "outputs": [], @@ -79,30 +79,30 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 41, "id": "060396e0-e5ee-4b54-b211-5d8bfcba167f", "metadata": {}, "outputs": [], "source": [ "#project = 'Mkulazi_trail' #or xinavane or chemba_test_8b\n", "#project = 'xinavane' #or xinavane or chemba_test_8b\n", - "project = 'kibos' #or xinavane or chemba_test_8b\n" + "project = 'aura' #or xinavane or chemba_test_8b\n" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 42, "id": "c9f79e81-dff8-4109-8d26-6c423142dcf2", "metadata": {}, "outputs": [], "source": [ "# Adjust the number of days needed\n", - "days = 514 #change back to 28 which is the default. 3 years is 1095 days.\n" + "days = 7" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 43, "id": "e18bdf8f-be4b-44ab-baaa-de5de60d92cb", "metadata": {}, "outputs": [], @@ -124,7 +124,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 44, "id": "3f7c8e04-4569-457b-b39d-283582c4ba36", "metadata": {}, "outputs": [], @@ -133,8 +133,9 @@ "BASE_PATH_SINGLE_IMAGES = Path(BASE_PATH / 'single_images')\n", "folder_for_merged_tifs = str(BASE_PATH / 'merged_tif')\n", "folder_for_virtual_raster = str(BASE_PATH / 'merged_virtual')\n", - "geojson_file = Path(BASE_PATH /'Data'/ 'pivot.geojson') #the geojsons should have the same name\n", - " \n", + "#geojson_file = Path(BASE_PATH /'Data'/ 'pivot.geojson') #the geojsons should have the same name\n", + "geojson_file = Path(BASE_PATH /'Data'/ ('pivot_2.geojson' if project == \"esa\" else 'pivot.geojson')) #the geojsons should have the same name\n", + " \n", "# Check if the folders exist, and if not, create them\n", "if not os.path.exists(BASE_PATH_SINGLE_IMAGES):\n", " os.makedirs(BASE_PATH_SINGLE_IMAGES)\n", @@ -148,7 +149,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 45, "id": "244b5752-4f02-4347-9278-f6a0a46b88f4", "metadata": {}, "outputs": [], @@ -236,7 +237,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 46, "id": "848dc773-70d6-4ae6-b05c-d6ebfb41624d", "metadata": {}, "outputs": [ @@ -246,13 +247,13 @@ "text": [ "Monthly time windows:\n", "\n", - "2024-03-31\n", - "2024-04-01\n", - "2024-04-02\n", - "...\n", - "2025-08-24\n", - "2025-08-25\n", - "2025-08-26\n" + "2025-09-24\n", + "2025-09-25\n", + "2025-09-26\n", + "2025-09-27\n", + "2025-09-28\n", + "2025-09-29\n", + "2025-09-30\n" ] } ], @@ -294,7 +295,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 47, "id": "c803e373-2567-4233-af7d-0d2d6f7d4f8e", "metadata": {}, "outputs": [], @@ -304,7 +305,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 48, "id": "dc24d54e-2272-4f30-bcf5-4d8fc381915c", "metadata": {}, "outputs": [], @@ -314,7 +315,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 49, "id": "cd071b42-d0cd-4e54-8f88-ad1a339748e3", "metadata": {}, "outputs": [], @@ -324,7 +325,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 50, "id": "301d12e4-e47a-4034-aec0-aa5673e64935", "metadata": {}, "outputs": [ @@ -332,7 +333,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Area bounding box: BBox(((34.81959063326724, -0.070757473777045), (34.87005530384722, -0.048545044498963)), crs=CRS('4326'))\n", + "Area bounding box: BBox(((35.16355804199998, -0.169299186999979), (35.25300975, -0.085633863)), crs=CRS('4326'))\n", "\n" ] } @@ -352,20 +353,20 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 51, "id": "431f6856-8d7e-4868-b627-20deeb47d77e", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ - "" + "" ], "text/plain": [ - "" + "" ] }, - "execution_count": 14, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -378,7 +379,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 52, "id": "18655785", "metadata": {}, "outputs": [], @@ -399,7 +400,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 53, "id": "a6fc418f", "metadata": {}, "outputs": [], @@ -414,7 +415,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 54, "id": "ebc416be", "metadata": {}, "outputs": [ @@ -422,10 +423,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "['2024-03-31', '2024-04-01', '2024-04-02', '2024-04-03', '2024-04-05', '2024-04-06', '2024-04-07', '2024-04-08', '2024-04-09', '2024-04-10', '2024-04-11', '2024-04-12', '2024-04-13', '2024-04-14', '2024-04-15', '2024-04-16', '2024-04-17', '2024-04-18', '2024-04-19', '2024-04-20', '2024-04-22', '2024-04-23', '2024-04-27', '2024-04-28', '2024-04-29', '2024-04-30', '2024-05-01', '2024-05-02', '2024-05-05', '2024-05-06', '2024-05-07', '2024-05-08', '2024-05-09', '2024-05-10', '2024-05-13', '2024-05-14', '2024-05-15', '2024-05-16', '2024-05-17', '2024-05-18', '2024-05-19', '2024-05-20', '2024-05-21', '2024-05-22', '2024-05-23', '2024-05-25', '2024-05-26', '2024-05-28', '2024-05-29', '2024-05-30', '2024-06-02', '2024-06-03', '2024-06-04', '2024-06-05', '2024-06-06', '2024-06-07', '2024-06-10', '2024-06-11', '2024-06-12', '2024-06-13', '2024-06-16', '2024-06-17', '2024-06-18', '2024-06-20', '2024-06-21', '2024-06-22', '2024-06-23', '2024-06-24', '2024-06-25', '2024-06-26', '2024-06-27', '2024-06-28', '2024-06-30', '2024-07-01', '2024-07-02', '2024-07-03', '2024-07-04', '2024-07-05', '2024-07-06', '2024-07-07', '2024-07-08', '2024-07-09', '2024-07-10', '2024-07-12', '2024-07-14', '2024-07-15', '2024-07-17', '2024-07-18', '2024-07-19', '2024-07-22', '2024-07-24', '2024-07-25', '2024-07-26', '2024-07-27', '2024-07-28', '2024-07-29', '2024-08-01', '2024-08-02', '2024-08-03', '2024-08-04', '2024-08-06', '2024-08-07', '2024-08-08', '2024-08-09', '2024-08-10', '2024-08-11', '2024-08-13', '2024-08-14', '2024-08-16', '2024-08-18', '2024-08-19', '2024-08-21', '2024-08-23', '2024-08-24', '2024-08-25', '2024-08-27', '2024-08-30', '2024-08-31', '2024-09-01', '2024-09-02', '2024-09-03', '2024-09-04', '2024-09-05', '2024-09-06', '2024-09-08', '2024-09-09', '2024-09-10', '2024-09-11', '2024-09-12', '2024-09-13', '2024-09-14', '2024-09-15', '2024-09-16', '2024-09-19', '2024-09-21', '2024-09-22', '2024-09-23', '2024-09-25', '2024-09-27', '2024-09-29', '2024-10-01', '2024-10-05', '2024-10-06', '2024-10-08', '2024-10-09', '2024-10-12', '2024-10-13', '2024-10-14', '2024-10-15', '2024-10-16', '2024-10-17', '2024-10-18', '2024-10-20', '2024-10-21', '2024-10-22', '2024-10-23', '2024-10-24', '2024-10-25', '2024-10-26', '2024-10-27', '2024-10-28', '2024-10-29', '2024-10-30', '2024-11-01', '2024-11-02', '2024-11-03', '2024-11-04', '2024-11-06', '2024-11-07', '2024-11-08', '2024-11-09', '2024-11-10', '2024-11-11', '2024-11-12', '2024-11-13', '2024-11-14', '2024-11-16', '2024-11-17', '2024-11-18', '2024-11-19', '2024-11-20', '2024-11-22', '2024-11-23', '2024-11-24', '2024-11-25', '2024-11-27', '2024-11-28', '2024-11-30', '2024-12-01', '2024-12-02', '2024-12-03', '2024-12-04', '2024-12-05', '2024-12-06', '2024-12-07', '2024-12-10', '2024-12-11', '2024-12-12', '2024-12-14', '2024-12-16', '2024-12-17', '2024-12-18', '2024-12-19', '2024-12-21', '2024-12-22', '2024-12-23', '2024-12-24', '2024-12-25', '2024-12-26', '2024-12-27', '2024-12-28', '2024-12-30', '2024-12-31', '2025-01-02', '2025-01-03', '2025-01-04', '2025-01-05', '2025-01-06', '2025-01-07', '2025-01-08', '2025-01-09', '2025-01-10', '2025-01-11', '2025-01-12', '2025-01-13', '2025-01-14', '2025-01-15', '2025-01-16', '2025-01-17', '2025-01-18', '2025-01-19', '2025-01-20', '2025-01-21', '2025-01-22', '2025-01-23', '2025-01-24', '2025-01-25', '2025-01-29', '2025-01-31', '2025-02-01', '2025-02-03', '2025-02-05', '2025-02-06', '2025-02-07', '2025-02-08', '2025-02-09', '2025-02-10', '2025-02-11', '2025-02-13', '2025-02-14', '2025-02-15', '2025-02-16', '2025-02-17', '2025-02-18', '2025-02-19', '2025-02-20', '2025-02-21', '2025-02-22', '2025-02-23', '2025-02-24', '2025-02-25', '2025-02-26', '2025-03-01', '2025-03-02', '2025-03-03', '2025-03-04', '2025-03-05', '2025-03-06', '2025-03-07', '2025-03-08', '2025-03-09', '2025-03-11', '2025-03-12', '2025-03-13', '2025-03-14', '2025-03-15', '2025-03-16', '2025-03-17', '2025-03-18', '2025-03-19', '2025-03-20', '2025-03-21', '2025-03-22', '2025-03-23', '2025-03-26', '2025-03-27', '2025-03-29', '2025-03-30', '2025-03-31', '2025-04-01', '2025-04-02', '2025-04-03', '2025-04-04', '2025-04-05', '2025-04-07', '2025-04-08', '2025-04-11', '2025-04-14', '2025-04-15', '2025-04-18', '2025-04-19', '2025-04-20', '2025-04-21', '2025-04-22', '2025-04-23', '2025-04-25', '2025-04-26', '2025-04-27', '2025-04-28', '2025-04-29', '2025-04-30', '2025-05-01', '2025-05-02', '2025-05-03', '2025-05-04', '2025-05-05', '2025-05-07', '2025-05-08', '2025-05-09', '2025-05-10', '2025-05-12', '2025-05-13', '2025-05-14', '2025-05-15', '2025-05-16', '2025-05-21', '2025-05-23', '2025-05-24', '2025-05-25', '2025-05-26', '2025-05-27', '2025-05-28', '2025-05-29', '2025-05-30', '2025-05-31', '2025-06-01', '2025-06-02', '2025-06-05', '2025-06-07', '2025-06-08', '2025-06-09', '2025-06-10', '2025-06-11', '2025-06-12', '2025-06-13', '2025-06-14', '2025-06-15', '2025-06-16', '2025-06-17', '2025-06-18', '2025-06-19', '2025-06-21', '2025-06-22', '2025-06-24', '2025-06-25', '2025-06-27', '2025-06-28', '2025-06-29', '2025-07-01', '2025-07-03', '2025-07-04', '2025-07-05', '2025-07-07', '2025-07-10', '2025-07-11', '2025-07-12', '2025-07-13', '2025-07-14', '2025-07-15', '2025-07-16', '2025-07-17', '2025-07-21', '2025-07-22', '2025-07-23', '2025-07-24', '2025-07-26', '2025-07-27', '2025-07-30', '2025-07-31', '2025-08-01', '2025-08-02', '2025-08-03', '2025-08-04', '2025-08-05', '2025-08-06', '2025-08-08', '2025-08-09', '2025-08-11', '2025-08-14', '2025-08-21', '2025-08-22', '2025-08-24']\n", - "Total slots: 514\n", - "Available slots: 392\n", - "Excluded slots due to empty dates: 122\n" + "['2025-09-24', '2025-09-25', '2025-09-26', '2025-09-27', '2025-09-28', '2025-09-29']\n", + "Total slots: 7\n", + "Available slots: 6\n", + "Excluded slots due to empty dates: 1\n" ] } ], @@ -438,7 +439,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 55, "id": "b0cabe8f-e1f2-4b18-8ac0-c2565d0ff16b", "metadata": {}, "outputs": [], @@ -519,7 +520,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 56, "id": "41b7369c-f768-44ba-983e-eb8eae4f3afd", "metadata": {}, "outputs": [ @@ -529,1337 +530,74 @@ "text": [ "c:\\Users\\timon\\anaconda3\\Lib\\site-packages\\sentinelhub\\geometry.py:137: SHDeprecationWarning: Initializing `BBox` objects from `BBox` objects will no longer be possible in future versions.\n", " return cls._tuple_from_bbox(bbox)\n", - "C:\\Users\\timon\\AppData\\Local\\Temp\\ipykernel_15628\\1551185686.py:59: SHDeprecationWarning: The string representation of `BBox` will change to match its `repr` representation.\n", - " print(f' Image downloaded for ' +slot + ' and bbox ' + str(bbox))\n", - "c:\\Users\\timon\\anaconda3\\Lib\\site-packages\\sentinelhub\\geometry.py:137: SHDeprecationWarning: Initializing `BBox` objects from `BBox` objects will no longer be possible in future versions.\n", - " return cls._tuple_from_bbox(bbox)\n" + "C:\\Users\\timon\\AppData\\Local\\Temp\\ipykernel_22880\\1551185686.py:59: SHDeprecationWarning: The string representation of `BBox` will change to match its `repr` representation.\n", + " print(f' Image downloaded for ' +slot + ' and bbox ' + str(bbox))\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - " Image downloaded for 2024-03-31 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-03-31 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-03-31 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-03-31 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-03-31 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-03-31 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-03-31 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-03-31 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-03-31 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-03-31 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-03-31 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-03-31 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-03-31 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-01 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-01 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-01 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-01 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-01 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-01 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-01 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-01 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-01 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-01 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-01 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-01 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-01 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-02 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-02 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-02 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-02 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-02 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-02 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-02 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-02 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-02 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-02 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-02 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-02 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-02 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-03 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-03 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-03 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-03 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-03 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-03 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-03 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-03 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-03 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-03 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-03 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-03 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-03 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-05 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-05 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-05 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-05 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-05 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-05 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-05 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-05 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-05 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-05 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-05 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-05 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-05 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-06 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-06 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-06 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-06 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-06 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-06 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-06 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-06 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-06 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-06 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-06 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-06 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-06 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-07 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-07 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-07 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-07 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-07 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-07 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-07 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-07 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-07 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-07 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-07 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-07 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-07 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-08 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-08 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-08 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-08 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-08 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-08 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-08 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-08 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-08 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-08 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-08 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-08 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-08 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-09 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-09 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-09 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-09 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-09 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-09 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-09 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-09 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-09 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-09 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-09 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-09 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-09 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-10 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-10 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-10 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-10 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-10 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-10 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-10 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-10 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-10 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-10 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-10 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-10 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-10 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-11 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-11 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-11 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-11 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-11 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-11 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-11 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-11 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-11 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-11 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-11 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-11 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-11 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-12 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-12 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-12 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-12 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-12 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-12 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-12 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-12 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-12 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-12 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-12 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-12 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-12 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-13 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-13 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-13 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-13 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-13 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-13 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-13 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-13 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-13 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-13 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-13 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-13 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-13 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-14 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-14 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-14 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-14 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-14 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-14 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-14 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-14 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-14 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-14 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-14 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-14 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-14 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-15 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-15 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-15 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-15 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-15 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-15 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-15 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-15 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-15 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-15 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-15 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-15 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-15 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-16 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-16 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-16 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-16 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-16 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-16 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-16 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-16 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-16 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-16 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-16 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-16 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-16 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-17 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-17 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-17 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-17 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-17 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-17 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-17 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-17 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-17 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-17 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-17 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-17 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-17 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-18 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-18 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-18 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-18 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-18 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-18 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-18 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-18 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-18 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-18 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-18 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-18 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-18 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-19 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-19 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-19 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-19 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-19 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-19 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-19 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-19 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-19 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-19 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-19 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-19 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-19 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-20 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-20 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-20 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-20 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-20 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-20 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-20 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-20 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-20 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-20 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-20 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-20 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-20 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-22 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-22 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-22 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-22 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-22 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-22 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-22 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-22 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-22 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-22 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-22 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-22 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-22 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-23 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-23 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-23 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-23 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-23 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-23 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-23 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-23 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-23 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-23 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-23 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-23 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-23 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-27 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-27 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-27 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-27 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-27 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-27 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-27 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-27 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-27 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-27 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-27 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-27 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-27 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-28 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-28 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-28 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-28 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-28 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-28 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-28 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-28 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-28 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-28 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-28 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-28 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-28 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-29 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-29 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-29 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-29 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-29 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-29 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-29 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-29 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-29 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-29 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-29 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-29 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-29 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-04-30 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-04-30 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-04-30 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-04-30 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-04-30 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-04-30 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-04-30 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-04-30 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-04-30 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-04-30 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-04-30 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-04-30 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-04-30 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-01 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-01 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-01 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-01 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-01 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-01 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-01 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-01 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-01 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-01 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-01 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-01 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-01 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-02 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-02 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-02 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-02 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-02 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-02 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-02 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-02 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-02 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-02 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-02 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-02 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-02 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-05 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-05 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-05 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-05 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-05 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-05 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-05 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-05 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-05 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-05 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-05 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-05 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-05 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-06 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-06 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-06 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-06 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-06 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-06 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-06 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-06 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-06 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-06 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-06 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-06 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-06 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-07 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-07 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-07 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-07 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-07 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-07 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-07 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-07 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-07 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-07 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-07 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-07 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-07 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-08 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-08 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-08 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-08 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-08 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-08 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-08 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-08 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-08 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-08 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-08 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-08 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-08 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-09 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-09 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-09 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-09 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-09 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-09 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-09 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-09 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-09 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-09 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-09 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-09 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-09 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-10 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-10 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-10 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-10 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-10 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-10 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-10 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-10 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-10 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-10 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-10 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-10 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-10 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-13 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-13 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-13 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-13 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-13 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-13 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-13 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-13 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-13 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-13 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-13 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-13 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-13 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-14 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-14 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-14 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-14 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-14 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-14 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-14 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-14 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-14 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-14 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-14 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-14 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-14 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-15 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-15 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-15 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-15 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-15 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-15 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-15 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-15 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-15 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-15 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-15 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-15 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-15 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-16 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-16 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-16 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-16 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-16 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-16 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-16 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-16 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-16 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-16 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-16 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-16 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-16 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-17 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-17 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-17 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-17 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-17 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-17 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-17 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-17 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-17 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-17 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-17 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-17 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-17 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-18 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-18 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-18 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-18 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-18 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-18 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-18 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-18 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-18 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-18 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-18 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-18 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-18 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-19 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-19 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-19 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-19 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-19 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-19 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-19 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-19 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-19 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-19 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-19 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-19 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-19 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-20 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-20 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-20 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-20 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-20 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-20 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-20 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-20 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-20 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-20 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-20 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-20 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-20 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-21 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-21 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-21 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-21 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-21 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-21 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-21 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-21 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-21 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-21 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-21 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-21 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-21 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-22 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-22 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-22 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-22 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-22 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-22 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-22 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-22 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-22 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-22 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-22 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-22 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-22 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-23 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-23 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-23 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-23 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-23 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-23 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-23 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-23 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-23 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-23 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-23 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-23 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-23 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-25 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-25 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-25 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-25 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-25 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-25 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-25 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-25 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-25 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-25 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-25 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-25 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-25 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-26 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-26 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-26 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-26 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-26 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-26 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-26 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-26 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-26 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-26 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-26 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-26 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-26 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-28 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-28 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-28 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-28 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-28 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-28 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-28 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-28 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-28 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-28 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-28 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-28 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-28 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-29 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-29 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-29 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-29 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-29 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-29 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-29 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-29 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-29 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-29 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-29 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-29 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-29 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-05-30 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-05-30 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-05-30 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-05-30 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-05-30 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-05-30 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-05-30 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-05-30 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-05-30 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-05-30 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-05-30 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-05-30 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-05-30 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-02 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-02 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-02 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-02 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-02 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-02 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-02 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-02 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-02 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-02 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-02 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-02 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-02 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-03 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-03 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-03 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-03 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-03 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-03 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-03 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-03 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-03 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-03 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-03 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-03 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-03 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-04 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-04 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-04 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-04 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-04 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-04 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-04 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-04 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-04 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-04 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-04 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-04 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-04 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-05 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-05 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-05 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-05 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-05 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-05 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-05 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-05 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-05 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-05 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-05 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-05 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-05 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-06 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-06 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-06 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-06 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-06 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-06 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-06 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-06 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-06 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-06 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-06 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-06 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-06 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-07 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-07 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-07 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-07 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-07 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-07 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-07 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-07 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-07 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-07 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-07 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-07 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-07 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-10 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-10 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-10 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-10 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-10 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-10 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-10 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-10 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-10 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-10 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-10 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-10 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-10 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-11 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-11 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-11 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-11 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-11 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-11 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-11 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-11 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-11 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-11 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-11 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-11 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-11 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-12 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-12 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-12 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-12 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-12 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-12 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-12 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-12 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-12 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-12 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-12 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-12 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-12 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-13 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-13 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-13 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-13 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-13 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-13 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-13 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-13 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-13 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-13 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-13 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-13 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-13 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-16 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-16 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-16 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-16 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-16 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-16 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-16 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-16 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-16 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-16 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-16 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-16 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-16 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-17 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-17 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-17 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-17 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-17 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-17 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-17 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-17 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-17 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-17 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-17 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-17 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-17 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-18 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-18 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-18 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-18 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-18 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-18 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-18 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-18 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-18 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-18 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-18 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-18 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-18 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-20 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-20 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-20 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-20 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-20 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-20 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-20 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-20 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-20 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-20 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-20 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-20 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-20 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-21 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-21 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-21 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-21 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-21 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-21 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-21 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-21 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-21 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-21 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-21 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-21 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-21 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-22 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-22 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-22 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-22 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-22 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-22 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-22 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-22 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-22 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-22 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-22 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-22 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-22 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-23 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-23 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-23 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-23 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-23 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-23 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-23 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-23 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-23 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-23 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-23 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-23 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-23 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-24 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-24 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-24 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-24 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-24 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-24 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-24 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-24 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-24 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-24 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-24 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-24 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-24 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-25 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-25 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-25 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-25 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-25 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-25 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-25 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-25 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-25 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-25 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-25 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-25 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-25 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-26 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-26 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-26 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-26 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-26 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-26 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-26 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-26 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-26 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-26 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-26 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-26 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-26 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-27 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-27 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-27 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-27 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-27 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-27 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-27 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-27 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-27 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-27 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-27 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-27 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-27 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-28 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-28 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-28 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-28 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-28 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-28 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-28 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-28 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-28 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-28 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-28 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-28 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-28 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-06-30 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-06-30 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-06-30 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-06-30 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-06-30 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-06-30 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-06-30 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-06-30 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-06-30 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-06-30 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-06-30 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-06-30 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-06-30 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-01 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-01 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-01 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-01 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-01 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-01 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-01 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-01 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-01 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-01 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-01 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-01 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-01 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-02 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-02 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-02 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-02 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-02 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-02 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-02 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-02 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-02 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-02 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-02 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-02 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-02 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-03 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-03 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-03 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-03 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-03 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-03 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-03 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-03 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-03 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-03 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-03 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-03 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-03 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-04 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-04 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-04 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-04 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-04 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-04 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-04 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-04 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-04 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-04 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-04 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-04 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-04 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-05 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-05 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-05 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-05 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-05 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-05 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-05 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-05 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-05 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-05 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-05 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-05 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-05 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-06 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-06 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-06 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-06 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-06 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-06 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-06 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-06 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-06 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-06 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-06 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-06 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-06 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-07 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-07 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-07 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-07 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-07 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-07 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-07 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-07 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-07 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-07 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-07 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-07 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-07 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-08 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-08 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-08 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-08 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-08 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-08 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-08 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-08 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-08 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-08 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-08 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-08 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-08 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-09 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-09 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-09 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-09 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-09 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-09 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-09 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-09 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-09 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-09 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-09 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-09 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-09 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-10 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-10 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-10 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-10 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-10 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-10 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-10 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-10 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-10 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-10 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-10 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-10 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-10 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-12 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-12 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-12 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-12 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-12 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-12 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-12 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-12 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-12 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-12 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-12 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-12 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-12 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-14 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-14 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-14 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-14 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-14 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-14 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-14 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-14 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-14 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-14 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-14 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-14 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-14 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-15 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-15 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-15 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-15 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-15 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-15 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-15 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-15 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-15 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-15 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-15 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-15 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-15 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-17 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-17 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-17 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-17 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-17 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-17 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-17 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-17 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-17 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-17 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-17 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-17 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-17 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-18 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-18 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-18 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-18 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-18 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-18 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-18 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-18 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-18 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-18 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-18 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-18 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-18 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-19 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-19 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-19 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-19 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-19 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-19 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-19 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-19 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-19 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-19 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-19 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-19 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-19 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-22 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-22 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-22 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-22 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-22 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-22 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-22 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-22 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-22 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-22 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-22 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-22 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-22 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-24 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-24 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-24 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-24 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-24 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-24 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-24 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-24 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-24 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-24 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-24 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-24 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-24 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-25 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-25 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-25 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-25 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-25 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-25 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-25 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-25 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-25 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-25 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-25 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-25 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-25 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-26 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-26 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-26 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-26 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-26 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-26 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-26 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-26 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-26 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-26 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-26 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-26 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-26 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-27 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-27 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-27 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-27 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-27 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-27 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-27 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-27 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-27 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-27 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-27 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-27 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-27 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-28 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-28 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-28 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-28 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-28 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-28 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-28 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-28 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-28 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-28 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-28 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-28 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-28 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-07-29 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-07-29 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-07-29 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-07-29 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-07-29 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-07-29 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-07-29 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-07-29 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-07-29 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-07-29 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-07-29 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-07-29 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-07-29 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-08-01 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-08-01 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-08-01 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-08-01 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-08-01 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-08-01 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-08-01 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-08-01 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-08-01 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-08-01 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-08-01 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-08-01 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-08-01 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-08-02 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-08-02 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-08-02 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-08-02 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-08-02 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-08-02 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-08-02 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-08-02 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-08-02 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-08-02 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-08-02 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-08-02 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-08-02 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-08-03 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-08-03 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-08-03 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-08-03 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-08-03 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-08-03 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-08-03 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-08-03 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-08-03 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-08-03 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-08-03 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-08-03 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-08-03 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-08-04 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-08-04 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-08-04 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-08-04 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-08-04 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-08-04 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-08-04 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-08-04 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-08-04 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-08-04 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-08-04 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-08-04 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-08-04 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-08-06 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-08-06 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-08-06 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-08-06 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-08-06 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-08-06 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-08-06 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-08-06 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n", - " Image downloaded for 2024-08-06 and bbox 34.86841351559624,-0.070757473777045,34.8697498445203,-0.06631498792142859\n", - " Image downloaded for 2024-08-06 and bbox 34.86850812271948,-0.06631498792142859,34.86977747821603,-0.0618725020658122\n", - " Image downloaded for 2024-08-06 and bbox 34.868608650888625,-0.0618725020658122,34.869846575549616,-0.0574300162101958\n", - " Image downloaded for 2024-08-06 and bbox 34.86874912678432,-0.0574300162101958,34.86995556361644,-0.0529875303545794\n", - " Image downloaded for 2024-08-06 and bbox 34.868870530834926,-0.0529875303545794,34.87005530384722,-0.048545044498963\n", - " Image downloaded for 2024-08-07 and bbox 34.82944984632235,-0.05912546612335618,34.829683567383235,-0.0574300162101958\n", - " Image downloaded for 2024-08-07 and bbox 34.81959063326724,-0.0574300162101958,34.829683567383235,-0.0529875303545794\n", - " Image downloaded for 2024-08-07 and bbox 34.82569536916782,-0.0529875303545794,34.829683567383235,-0.048982646529556\n", - " Image downloaded for 2024-08-07 and bbox 34.83543101946234,-0.064057386220696,34.838502193617494,-0.0618725020658122\n", - " Image downloaded for 2024-08-07 and bbox 34.829683567383235,-0.0618725020658122,34.839274001582496,-0.0574300162101958\n", - " Image downloaded for 2024-08-07 and bbox 34.829683567383235,-0.0574300162101958,34.83974654748878,-0.0529875303545794\n", - " Image downloaded for 2024-08-07 and bbox 34.829683567383235,-0.0529875303545794,34.83977650149923,-0.04971537704797\n", - " Image downloaded for 2024-08-07 and bbox 34.83977650149923,-0.05263573006162196,34.840052682905636,-0.04984527184374112\n" + " Image downloaded for 2025-09-24 and bbox 35.16355804199998,-0.129343709000011,35.165482102,-0.128278259000012\n", + " Image downloaded for 2025-09-24 and bbox 35.19342203000002,-0.145566114000019,35.19815707700002,-0.141901112000028\n", + " Image downloaded for 2025-09-24 and bbox 35.186062252,-0.11468985800002,35.19125232599998,-0.112838832000023\n", + " Image downloaded for 2025-09-24 and bbox 35.216724886,-0.16921497048746426,35.21722906679999,-0.168239035\n", + " Image downloaded for 2025-09-24 and bbox 35.215712869000015,-0.144763049,35.21692640200001,-0.143002134000028\n", + " Image downloaded for 2025-09-24 and bbox 35.208590781,-0.087364975000014,35.210532812,-0.085633863\n", + " Image downloaded for 2025-09-24 and bbox 35.21722906679999,-0.169299186999979,35.22781605,-0.16564269700001\n", + " Image downloaded for 2025-09-24 and bbox 35.23161692399998,-0.136799790999987,35.23314344099998,-0.1358330573999874\n", + " Image downloaded for 2025-09-24 and bbox 35.231617117966266,-0.1358330573999874,35.232720503778594,-0.13495027099998\n", + " Image downloaded for 2025-09-24 and bbox 35.25088550999999,-0.160822344999985,35.25300975,-0.156598042999974\n", + " Image downloaded for 2025-09-25 and bbox 35.16355804199998,-0.129343709000011,35.165482102,-0.128278259000012\n", + " Image downloaded for 2025-09-25 and bbox 35.19342203000002,-0.145566114000019,35.19815707700002,-0.141901112000028\n", + " Image downloaded for 2025-09-25 and bbox 35.186062252,-0.11468985800002,35.19125232599998,-0.112838832000023\n", + " Image downloaded for 2025-09-25 and bbox 35.216724886,-0.16921497048746426,35.21722906679999,-0.168239035\n", + " Image downloaded for 2025-09-25 and bbox 35.215712869000015,-0.144763049,35.21692640200001,-0.143002134000028\n", + " Image downloaded for 2025-09-25 and bbox 35.208590781,-0.087364975000014,35.210532812,-0.085633863\n", + " Image downloaded for 2025-09-25 and bbox 35.21722906679999,-0.169299186999979,35.22781605,-0.16564269700001\n", + " Image downloaded for 2025-09-25 and bbox 35.23161692399998,-0.136799790999987,35.23314344099998,-0.1358330573999874\n", + " Image downloaded for 2025-09-25 and bbox 35.231617117966266,-0.1358330573999874,35.232720503778594,-0.13495027099998\n", + " Image downloaded for 2025-09-25 and bbox 35.25088550999999,-0.160822344999985,35.25300975,-0.156598042999974\n", + " Image downloaded for 2025-09-26 and bbox 35.16355804199998,-0.129343709000011,35.165482102,-0.128278259000012\n", + " Image downloaded for 2025-09-26 and bbox 35.19342203000002,-0.145566114000019,35.19815707700002,-0.141901112000028\n", + " Image downloaded for 2025-09-26 and bbox 35.186062252,-0.11468985800002,35.19125232599998,-0.112838832000023\n", + " Image downloaded for 2025-09-26 and bbox 35.216724886,-0.16921497048746426,35.21722906679999,-0.168239035\n", + " Image downloaded for 2025-09-26 and bbox 35.215712869000015,-0.144763049,35.21692640200001,-0.143002134000028\n", + " Image downloaded for 2025-09-26 and bbox 35.208590781,-0.087364975000014,35.210532812,-0.085633863\n", + " Image downloaded for 2025-09-26 and bbox 35.21722906679999,-0.169299186999979,35.22781605,-0.16564269700001\n", + " Image downloaded for 2025-09-26 and bbox 35.23161692399998,-0.136799790999987,35.23314344099998,-0.1358330573999874\n", + " Image downloaded for 2025-09-26 and bbox 35.231617117966266,-0.1358330573999874,35.232720503778594,-0.13495027099998\n", + " Image downloaded for 2025-09-26 and bbox 35.25088550999999,-0.160822344999985,35.25300975,-0.156598042999974\n", + " Image downloaded for 2025-09-27 and bbox 35.16355804199998,-0.129343709000011,35.165482102,-0.128278259000012\n", + " Image downloaded for 2025-09-27 and bbox 35.19342203000002,-0.145566114000019,35.19815707700002,-0.141901112000028\n", + " Image downloaded for 2025-09-27 and bbox 35.186062252,-0.11468985800002,35.19125232599998,-0.112838832000023\n", + " Image downloaded for 2025-09-27 and bbox 35.216724886,-0.16921497048746426,35.21722906679999,-0.168239035\n", + " Image downloaded for 2025-09-27 and bbox 35.215712869000015,-0.144763049,35.21692640200001,-0.143002134000028\n", + " Image downloaded for 2025-09-27 and bbox 35.208590781,-0.087364975000014,35.210532812,-0.085633863\n", + " Image downloaded for 2025-09-27 and bbox 35.21722906679999,-0.169299186999979,35.22781605,-0.16564269700001\n", + " Image downloaded for 2025-09-27 and bbox 35.23161692399998,-0.136799790999987,35.23314344099998,-0.1358330573999874\n", + " Image downloaded for 2025-09-27 and bbox 35.231617117966266,-0.1358330573999874,35.232720503778594,-0.13495027099998\n", + " Image downloaded for 2025-09-27 and bbox 35.25088550999999,-0.160822344999985,35.25300975,-0.156598042999974\n", + " Image downloaded for 2025-09-28 and bbox 35.16355804199998,-0.129343709000011,35.165482102,-0.128278259000012\n", + " Image downloaded for 2025-09-28 and bbox 35.19342203000002,-0.145566114000019,35.19815707700002,-0.141901112000028\n", + " Image downloaded for 2025-09-28 and bbox 35.186062252,-0.11468985800002,35.19125232599998,-0.112838832000023\n", + " Image downloaded for 2025-09-28 and bbox 35.216724886,-0.16921497048746426,35.21722906679999,-0.168239035\n", + " Image downloaded for 2025-09-28 and bbox 35.215712869000015,-0.144763049,35.21692640200001,-0.143002134000028\n", + " Image downloaded for 2025-09-28 and bbox 35.208590781,-0.087364975000014,35.210532812,-0.085633863\n", + " Image downloaded for 2025-09-28 and bbox 35.21722906679999,-0.169299186999979,35.22781605,-0.16564269700001\n", + " Image downloaded for 2025-09-28 and bbox 35.23161692399998,-0.136799790999987,35.23314344099998,-0.1358330573999874\n", + " Image downloaded for 2025-09-28 and bbox 35.231617117966266,-0.1358330573999874,35.232720503778594,-0.13495027099998\n", + " Image downloaded for 2025-09-28 and bbox 35.25088550999999,-0.160822344999985,35.25300975,-0.156598042999974\n", + " Image downloaded for 2025-09-29 and bbox 35.16355804199998,-0.129343709000011,35.165482102,-0.128278259000012\n", + " Image downloaded for 2025-09-29 and bbox 35.19342203000002,-0.145566114000019,35.19815707700002,-0.141901112000028\n", + " Image downloaded for 2025-09-29 and bbox 35.186062252,-0.11468985800002,35.19125232599998,-0.112838832000023\n", + " Image downloaded for 2025-09-29 and bbox 35.216724886,-0.16921497048746426,35.21722906679999,-0.168239035\n", + " Image downloaded for 2025-09-29 and bbox 35.215712869000015,-0.144763049,35.21692640200001,-0.143002134000028\n", + " Image downloaded for 2025-09-29 and bbox 35.208590781,-0.087364975000014,35.210532812,-0.085633863\n", + " Image downloaded for 2025-09-29 and bbox 35.21722906679999,-0.169299186999979,35.22781605,-0.16564269700001\n", + " Image downloaded for 2025-09-29 and bbox 35.23161692399998,-0.136799790999987,35.23314344099998,-0.1358330573999874\n", + " Image downloaded for 2025-09-29 and bbox 35.231617117966266,-0.1358330573999874,35.232720503778594,-0.13495027099998\n", + " Image downloaded for 2025-09-29 and bbox 35.25088550999999,-0.160822344999985,35.25300975,-0.156598042999974\n" ] } ], @@ -1879,7 +617,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 57, "id": "68db3c15-6f94-432e-b315-c329e4251b21", "metadata": { "tags": [] @@ -1902,12 +640,62 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 58, "id": "cb3fa856-a550-4899-844a-e69209bba3ad", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Emptied folder: ..\\laravel_app\\storage\\app\\aura\\merged_virtual\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-04-25\\\\37ce883de72e7ea4e5db310659249afe'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-04-26\\\\056d651121bad1bca62c5d14d53db39b'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-04-28\\\\15003b17913ecb076b87ebcfe8b852a1'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-04-29\\\\0ad319685145738356440ffa60ee05e1'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-04-30\\\\0aba91aff99fdf6d275aa678209dc949'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-01\\\\2a970008493e784349dd2aff01dc719d'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-02\\\\19531b16909aeb9d8d3388329a34fa3b'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-05\\\\09b5ab5b5fa47c89bb73babd09a588e3'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-06\\\\009f0f0100d00f4188ab6d83f88f72a5'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-07\\\\12330850d8389db905b335ac34028e36'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-09\\\\01915e4caba800f2c27344e97b2235be'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-10\\\\0410b1f6b14a778613430466eb7ad6de'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-11\\\\0f06c11f2eff290ffa2350155392897c'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-13\\\\04b312cc3520482017b438a93bd35d83'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-14\\\\3e6c898a261bd223bb88e1d500fb2205'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-15\\\\30173c5a1a22af7181263fa85988d5d7'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-16\\\\047cac717167884be8f88774073373b3'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-17\\\\0f1a22133295603a2c0424545ddb6f63'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-18\\\\319759fe3f9894327c302f546f3b8f05'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-19\\\\0a23f5edb7885accfe0d941962f034b2'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-20\\\\02b5c1f242fc2774812bf5caaacde542'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-21\\\\143523149ad4bd08248d190068bb8580'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-22\\\\02af7f74a75f48e3217417c5c28e5cbe'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-24\\\\218f6daa002010bd22144e4db883435d'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-25\\\\154e916d4b7a9e56be9a971f5234aa8f'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-26\\\\1db5f0f7b2113ac38d40de204e575a92'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-27\\\\007af5c52a19e32084859b8dccddd36e'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-28\\\\0b7b22d7e93a4523896472c3c57684d3'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-29\\\\01992d808e1db004bc13732bef24c160'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-05-31\\\\115005e7b953c87b5afb378c2b9523a4'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-06-01\\\\02484511825d62d65ac2005ccb800077'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-06-02\\\\4204a901299e200229b3d68e8022ea62'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-06-03\\\\02e1a22ba0329a7d721e3e1ac428931b'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-06-05\\\\28a31ecf8ca5432fb2fb889e1e383969'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-06-07\\\\15a677ad344ed4ab156980fedff88820'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-06-09\\\\05d469a686fe127b4cfa32f8509f70f5'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-06-10\\\\148e5b0ea59516f00070850a808773f6'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-06-11\\\\2d3813f2bac34eac4011dd3a977715d6'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-06-12\\\\11774fbda11458e6b7c177e67b6b8c20'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-06-13\\\\05d30cf1cc0d1cd808211c56f749dfe7'\n", + "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\aura\\\\single_images\\\\2025-06-14\\\\06d82f3a2ac198df592f40b965ba7abc'\n", + "Emptied folder: ..\\laravel_app\\storage\\app\\aura\\single_images\n" + ] + } + ], "source": [ "# List of folder names\n", "\n", diff --git a/r_app/02_ci_extraction.R b/r_app/02_ci_extraction.R index 74af9d7..c928e49 100644 --- a/r_app/02_ci_extraction.R +++ b/r_app/02_ci_extraction.R @@ -1,2 +1,120 @@ -# Renamed for workflow clarity -# ...existing code from ci_extraction.R... +# CI_EXTRACTION.R +# ============== +# This script processes satellite imagery to extract Canopy Index (CI) values for agricultural fields. +# It handles image processing, masking, and extraction of statistics by field/sub-field. +# +# Usage: Rscript ci_extraction.R [end_date] [offset] [project_dir] +# - end_date: End date for processing (YYYY-MM-DD format) +# - offset: Number of days to look back from end_date +# - project_dir: Project directory name (e.g., "chemba") +# + +# 1. Load required packages +# ----------------------- +suppressPackageStartupMessages({ + library(sf) + library(terra) + library(tidyverse) + library(lubridate) + library(exactextractr) + library(readxl) + library(here) +}) + +# 2. Process command line arguments +# ------------------------------ +main <- function() { + # Capture command line arguments + args <- commandArgs(trailingOnly = TRUE) + + # Process end_date argument + if (length(args) >= 1 && !is.na(args[1])) { + end_date <- as.Date(args[1]) + if (is.na(end_date)) { + warning("Invalid end_date provided. Using default (current date).") + end_date <- Sys.Date() + #end_date <- "2023-10-01" + } + } else { + end_date <- Sys.Date() + #end_date <- "2023-10-01" + } + + # Process offset argument + if (length(args) >= 2 && !is.na(args[2])) { + offset <- as.numeric(args[2]) + if (is.na(offset) || offset <= 0) { + warning("Invalid offset provided. Using default (7 days).") + offset <- 7 + } + } else { + offset <- 7 + } + + # Process project_dir argument + if (length(args) >= 3 && !is.na(args[3])) { + project_dir <- as.character(args[3]) + } else { + project_dir <- "esa" # Changed default from "aura" to "esa" + } + + # Make project_dir available globally so parameters_project.R can use it + assign("project_dir", project_dir, envir = .GlobalEnv) + + # Set flag to use pivot_2.geojson for ESA (extra fields for yield prediction) + ci_extraction_script <- TRUE + assign("ci_extraction_script", ci_extraction_script, envir = .GlobalEnv) + + # 3. Initialize project configuration + # -------------------------------- + new_project_question <- TRUE + + tryCatch({ + source("parameters_project.R") + source("ci_extraction_utils.R") + }, error = function(e) { + warning("Default source files not found. Attempting to source from 'r_app' directory.") + tryCatch({ + source("r_app/parameters_project.R") + source("r_app/ci_extraction_utils.R") + warning(paste("Successfully sourced files from 'r_app' directory.")) + + }, error = function(e) { + stop("Failed to source required files from both default and 'r_app' directories.") + }) + }) + + # 4. Generate date list for processing + # --------------------------------- + dates <- date_list(end_date, offset) + log_message(paste("Processing data for week", dates$week, "of", dates$year)) + + # 5. Find and filter raster files by date + # ----------------------------------- + log_message("Searching for raster files") + + tryCatch({ + # Use the new utility function to find satellite images + existing_files <- find_satellite_images(planet_tif_folder, dates$days_filter) + log_message(paste("Found", length(existing_files), "raster files for processing")) + + # 6. Process raster files and create VRT + # ----------------------------------- + # Use the new utility function for batch processing + vrt_list <- process_satellite_images(existing_files, field_boundaries, merged_final, daily_vrt) + + # 7. Process and combine CI values + # ------------------------------ + # Call the process_ci_values function from utils with all required parameters + process_ci_values(dates, field_boundaries, merged_final, + field_boundaries_sf, daily_CI_vals_dir, cumulative_CI_vals_dir) + + }, error = function(e) { + log_message(paste("Error in main processing:", e$message), level = "ERROR") + stop(e$message) + }) +} + +if (sys.nframe() == 0) { + main() +} diff --git a/r_app/03_interpolate_growth_model.R b/r_app/03_interpolate_growth_model.R index cfda768..7bba67e 100644 --- a/r_app/03_interpolate_growth_model.R +++ b/r_app/03_interpolate_growth_model.R @@ -1,2 +1,110 @@ -# Renamed for workflow clarity -# ...existing code from interpolate_growth_model.R... +# filepath: c:\Users\timon\Resilience BV\4020 SCane ESA DEMO - Documenten\General\4020 SCDEMO Team\4020 TechnicalData\WP3\smartcane\r_app\interpolate_growth_model.R +# +# INTERPOLATE_GROWTH_MODEL.R +# ========================= +# This script interpolates CI (Chlorophyll Index) values between measurement dates +# to create a continuous growth model. It generates daily values and cumulative +# CI statistics for each field. +# +# Usage: Rscript interpolate_growth_model.R [project_dir] +# - project_dir: Project directory name (e.g., "chemba") +# + +# 1. Load required packages +# ----------------------- +suppressPackageStartupMessages({ + library(tidyverse) + library(lubridate) + library(here) +}) + +# 2. Main function to handle interpolation +# ------------------------------------- +main <- function() { + # Process command line arguments + args <- commandArgs(trailingOnly = TRUE) + + # Get project directory from arguments or use default + if (length(args) >= 1 && !is.na(args[1])) { + project_dir <- as.character(args[1]) + } else { + project_dir <- "esa" + 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) + + # Set flag to use pivot_2.geojson for ESA (extra fields for yield prediction) + ci_extraction_script <- TRUE + assign("ci_extraction_script", ci_extraction_script, envir = .GlobalEnv) + + # Initialize project configuration and load utility functions + tryCatch({ + source("parameters_project.R") + source("growth_model_utils.R") + }, error = function(e) { + warning("Default source files not found. Attempting to source from 'r_app' directory.") + tryCatch({ + source(here::here("r_app", "parameters_project.R")) + source(here::here("r_app", "growth_model_utils.R")) + warning(paste("Successfully sourced files from 'r_app' directory.")) + + }, error = function(e) { + stop("Failed to source required files from both default and 'r_app' directories.") + }) + }) + + log_message("Starting CI growth model interpolation") + + # Load and process the data + tryCatch({ + # Load the combined CI data + CI_data <- load_combined_ci_data(cumulative_CI_vals_dir) + + # Validate harvesting data + if (is.null(harvesting_data) || nrow(harvesting_data) == 0) { + stop("No harvesting data available") + } + + # Get the years from harvesting data + years <- harvesting_data %>% + filter(!is.na(season_start)) %>% + distinct(year) %>% + pull(year) + + log_message(paste("Processing data for years:", paste(years, collapse = ", "))) + + # Generate interpolated CI data for each year and field + CI_all <- generate_interpolated_ci_data(years, harvesting_data, CI_data) + + # CI_all <- CI_all %>% + # group_by(Date, field, season) %>% + # filter(!(field == "00F25" & season == 2023 & duplicated(DOY))) + + # Calculate growth metrics and save the results + if (nrow(CI_all) > 0) { + # Add daily and cumulative metrics + CI_all_with_metrics <- calculate_growth_metrics(CI_all) + + # Save the processed data + save_growth_model( + CI_all_with_metrics, + cumulative_CI_vals_dir, + "All_pivots_Cumulative_CI_quadrant_year_v2.rds" + ) + } else { + log_message("No CI data was generated after interpolation", level = "WARNING") + } + + log_message("Growth model interpolation completed successfully") + + }, error = function(e) { + log_message(paste("Error in growth model interpolation:", e$message), level = "ERROR") + stop(e$message) + }) +} + +if (sys.nframe() == 0) { + main() +} diff --git a/r_app/04_mosaic_creation.R b/r_app/04_mosaic_creation.R index 89cb388..668640b 100644 --- a/r_app/04_mosaic_creation.R +++ b/r_app/04_mosaic_creation.R @@ -1,2 +1,119 @@ -# Renamed for workflow clarity -# ...existing code from mosaic_creation.R... +# 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] +# - end_date: End date for processing (YYYY-MM-DD format) +# - offset: Number of days to look back from end_date +# - project_dir: Project directory name (e.g., "chemba") +# - file_name: Optional custom output file name +# + +# 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 { + # Default project directory + project_dir <- "esa" + 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])) { + end_date <- as.Date(args[1]) + if (is.na(end_date)) { + message("Invalid end_date provided. Using current date.") + end_date <- Sys.Date() + #end_date <- "2025-07-22" # Default date for testing + } + } else { + # Default to current date if no argument is provided + end_date <- Sys.Date() + #end_date <- "2025-07-08" # Default date for testing + 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") + } + + + + # 3. Initialize project configuration + # -------------------------------- + tryCatch({ + source("parameters_project.R") + source("mosaic_creation_utils.R") + safe_log(paste("Successfully sourced files from default directory.")) + }, error = function(e) { + warning("Default source files not found. Attempting to source from 'r_app' directory.") + tryCatch({ + source(here::here("r_app", "parameters_project.R")) + source(here::here("r_app", "mosaic_creation_utils.R")) + warning(paste("Successfully sourced files from 'r_app' directory.")) + }, error = function(e) { + stop("Failed to source required files from both default and 'r_app' directories.") + }) + }) + + # 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 + file_name_tif <- if (length(args) >= 4 && !is.na(args[4])) { + 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 mosaic using the function from utils + # ------------------------------------------------- + create_weekly_mosaic( + dates = dates, + field_boundaries = field_boundaries, + daily_vrt_dir = daily_vrt, + merged_final_dir = merged_final, + output_dir = weekly_CI_mosaic, + file_name_tif = file_name_tif, + create_plots = TRUE + ) +} + +if (sys.nframe() == 0) { + main() +} diff --git a/r_app/05_CI_report_dashboard_planet.Rmd b/r_app/05_CI_report_dashboard_planet.Rmd index 5f77493..557c7bd 100644 --- a/r_app/05_CI_report_dashboard_planet.Rmd +++ b/r_app/05_CI_report_dashboard_planet.Rmd @@ -2,8 +2,8 @@ params: ref: "word-styles-reference-var1.docx" output_file: CI_report.docx - report_date: "2024-06-20" - data_dir: "chemba" + report_date: "2025-09-24" + data_dir: "esa" mail_day: "Wednesday" borders: FALSE ci_plot_type: "both" # options: "absolute", "cumulative", "both" @@ -367,156 +367,6 @@ Use these insights to identify areas that may need irrigation, fertilization, or \newpage -# RGB Satellite Image - Current Week (if available) -```{r render_rgb_map, echo=FALSE, fig.height=7, fig.width=10, message=FALSE, warning=FALSE} -# Check if RGB bands are available and create RGB map -tryCatch({ - # Load the full raster to check available bands - full_raster <- terra::rast(path_to_week_current) - available_bands <- names(full_raster) - - # Check if RGB bands are available (look for red, green, blue or similar naming) - rgb_bands_available <- any(grepl("red|Red|RED", available_bands, ignore.case = TRUE)) && - any(grepl("green|Green|GREEN", available_bands, ignore.case = TRUE)) && - any(grepl("blue|Blue|BLUE", available_bands, ignore.case = TRUE)) - - # Alternative check for numbered bands that might be RGB (e.g., band_1, band_2, band_3) - if (!rgb_bands_available && length(available_bands) >= 3) { - # Check if we have at least 3 bands that could potentially be RGB - potential_rgb_bands <- grep("band_[1-3]|B[1-3]|[1-3]", available_bands, ignore.case = TRUE) - rgb_bands_available <- length(potential_rgb_bands) >= 3 - } - - if (rgb_bands_available) { - safe_log("RGB bands detected - creating RGB visualization") - - # Try to extract RGB bands (prioritize named bands first) - red_band <- NULL - green_band <- NULL - blue_band <- NULL - - # Look for named RGB bands first - red_candidates <- grep("red|Red|RED", available_bands, ignore.case = TRUE, value = TRUE) - green_candidates <- grep("green|Green|GREEN", available_bands, ignore.case = TRUE, value = TRUE) - blue_candidates <- grep("blue|Blue|BLUE", available_bands, ignore.case = TRUE, value = TRUE) - - if (length(red_candidates) > 0) red_band <- red_candidates[1] - if (length(green_candidates) > 0) green_band <- green_candidates[1] - if (length(blue_candidates) > 0) blue_band <- blue_candidates[1] - - # Fallback to numbered bands if named bands not found - if (is.null(red_band) || is.null(green_band) || is.null(blue_band)) { - if (length(available_bands) >= 3) { - # Assume first 3 bands are RGB (common convention) - red_band <- available_bands[1] - green_band <- available_bands[2] - blue_band <- available_bands[3] - } - } - - if (!is.null(red_band) && !is.null(green_band) && !is.null(blue_band)) { - # Extract RGB bands - rgb_raster <- c(full_raster[[red_band]], full_raster[[green_band]], full_raster[[blue_band]]) - names(rgb_raster) <- c("red", "green", "blue") - # Create RGB map - map <- tmap::tm_shape(rgb_raster, unit = "m") + - tmap::tm_rgb() + - tmap::tm_scalebar(position = tm_pos_out("right", "bottom"), text.color = "black") + - tmap::tm_compass(position = tm_pos_out("right", "bottom"), text.color = "black") + - tmap::tm_shape(AllPivots0) + - tmap::tm_borders(col = "white", lwd = 2) + - tmap::tm_text("sub_field", size = 0.6, col = "white") + - tmap::tm_layout(main.title = paste0("RGB Satellite Image - Week ", week), - main.title.size = 0.8, - main.title.color = "black") - - # Print the map - print(map) - - safe_log("RGB map created successfully") - } else { - safe_log("Could not identify RGB bands despite detection", "WARNING") - cat("RGB bands detected but could not be properly identified. Skipping RGB visualization.\n") - } - } else { - safe_log("No RGB bands available in the current week mosaic") - cat("**Note:** RGB satellite imagery is not available for this week. Only spectral index data is available.\n\n") - } -}, error = function(e) { - safe_log(paste("Error creating RGB map:", e$message), "ERROR") - cat("**Note:** Could not create RGB visualization for this week.\n\n") -}) -``` - -# Chlorophyll Index (CI) Overview Map - Current Week -```{r render_ci_overview_map, echo=FALSE, fig.height=7, fig.width=10, message=FALSE, warning=FALSE} -# Create overview chlorophyll index map -tryCatch({ - # Choose palette based on colorblind_friendly parameter - ci_palette <- if (colorblind_friendly) "viridis" else "brewer.rd_yl_gn" - - # Base shape - map <- tmap::tm_shape(CI, unit = "m") - - # Add raster layer with continuous spectrum (fixed scale 1-8 for consistent comparison) - map <- map + tmap::tm_raster(col.scale = tm_scale_continuous(values = ci_palette, - limits = c(1, 8)), - col.legend = tm_legend(title = "Chlorophyll Index (CI)", - orientation = "landscape", - position = tm_pos_out("center", "bottom"))) - # Complete the map with layout and other elements - map <- map + - tmap::tm_scalebar(position = tm_pos_out("right", "bottom"), text.color = "black") + - tmap::tm_compass(position = tm_pos_out("right", "bottom"), text.color = "black") + - tmap::tm_shape(AllPivots0) + - tmap::tm_borders(col = "black") + - tmap::tm_text("sub_field", size = 0.6, col = "black") - - # Print the map - print(map) -}, error = function(e) { - safe_log(paste("Error creating CI overview map:", e$message), "ERROR") - plot(1, type="n", axes=FALSE, xlab="", ylab="") - text(1, 1, "Error creating CI overview map", cex=1.5) -}) - -``` - -# Weekly Chlorophyll Index Difference Map -```{r render_ci_difference_map, echo=FALSE, fig.height=7, fig.width=10, message=FALSE, warning=FALSE} -# Create chlorophyll index difference map -tryCatch({ - # Choose palette based on colorblind_friendly parameter - diff_palette <- if (colorblind_friendly) "plasma" else "brewer.rd_yl_gn" - - # Base shape - map <- tmap::tm_shape(last_week_dif_raster_abs, unit = "m") - - # Add raster layer with continuous spectrum (centered at 0 for difference maps, fixed scale) - map <- map + tmap::tm_raster(col.scale = tm_scale_continuous(values = diff_palette, - midpoint = 0, - limits = c(-3, 3)), - col.legend = tm_legend(title = "Chlorophyll Index (CI) Change", - orientation = "landscape", - position = tm_pos_out("center", "bottom"))) - # Complete the map with layout and other elements - map <- map + - tmap::tm_scalebar(position = tm_pos_out("right", "bottom"), text.color = "black") + - tmap::tm_compass(position = tm_pos_out("right", "bottom"), text.color = "black") + - tmap::tm_shape(AllPivots0) + - tmap::tm_borders(col = "black") + - tmap::tm_text("sub_field", size = 0.6, col = "black") - - # Print the map - print(map) -}, error = function(e) { - safe_log(paste("Error creating CI difference map:", e$message), "ERROR") - plot(1, type="n", axes=FALSE, xlab="", ylab="") - text(1, 1, "Error creating CI difference map", cex=1.5) -}) -``` - -\newpage ```{r generate_field_visualizations, eval=TRUE, fig.height=3.8, fig.width=10, message=FALSE,echo=FALSE, warning=FALSE, include=TRUE, results='asis'} # Generate detailed visualizations for each field @@ -662,8 +512,8 @@ tryCatch({ # Prepare prediction dataset (fields without harvest data) prediction_yields <- CI_and_yield %>% as.data.frame() %>% - dplyr::filter(is.na(tonnage_ha)) %>% - dplyr::filter(age > 300) # Only predict on fields older than 300 days + dplyr::filter(is.na(tonnage_ha))# #%>% + # dplyr::filter(Age_days > 300) # Only predict on fields older than 300 days # Configure model training parameters ctrl <- caret::trainControl( @@ -700,7 +550,7 @@ tryCatch({ predicted_Tcha = round(predicted_Tcha, 0), season = newdata$season ) %>% - dplyr::select(field, sub_field, Age_days, total_CI, predicted_Tcha, season) %>% + dplyr::select(field, sub_field, Age_days, predicted_Tcha, season) %>% dplyr::left_join(., newdata, by = c("field", "sub_field", "season")) ) } @@ -711,7 +561,7 @@ tryCatch({ # Predict yields for the current season (focus on mature fields over 300 days) pred_rf_current_season <- prepare_predictions(stats::predict(model_ffs_rf, newdata = prediction_yields), prediction_yields) %>% dplyr::filter(Age_days > 1) %>% - dplyr::mutate(CI_per_day = round(total_CI / Age_days, 1)) + select(c("field", "Age_days", "predicted_Tcha", "season")) safe_log("Successfully completed yield prediction calculations") @@ -743,7 +593,7 @@ tryCatch({ # Plot predicted yields by age ggplot2::ggplot(pred_rf_current_season, ggplot2::aes(x = Age_days, y = predicted_Tcha)) + ggplot2::geom_point(size = 2, alpha = 0.6) + - ggplot2::labs(title = "Predicted Yields for Fields Over 300 Days \nOld Yet to Be Harvested", + ggplot2::labs(title = "Predicted Yields \n Yet to Be Harvested", x = "Age (days)", y = "Predicted tonnage/ha (Tcha)") + ggplot2::scale_y_continuous(limits = c(0, 200)) + diff --git a/r_app/06_crop_messaging b/r_app/06_crop_messaging new file mode 100644 index 0000000..5e32921 --- /dev/null +++ b/r_app/06_crop_messaging @@ -0,0 +1,293 @@ +# 06_CROP_MESSAGING.R +# =================== +# This script analyzes weekly CI mosaics to detect changes and generate automated messages +# about crop conditions. It compares two weeks of data to assess: +# - Field uniformity (high vs low variation) +# - CI change trends (increase, stable, decrease) +# - Generates contextual messages based on analysis +# - Outputs results in multiple formats: WhatsApp/Word text, CSV, and .docx +# +# Usage: Rscript 06_crop_messaging.R [current_week] [previous_week] [estate_name] +# - current_week: Current week number (e.g., 30) +# - previous_week: Previous week number (e.g., 29) +# - estate_name: Estate name (e.g., "simba", "chemba") +# +# Examples: +# Rscript 06_crop_messaging.R 32 31 simba +# Rscript 06_crop_messaging.R 30 29 chemba +# +# The script automatically: +# 1. Loads the correct estate configuration +# 2. Analyzes weekly mosaics +# 3. Generates field-by-field analysis +# 4. Creates output files in multiple formats +# 5. Displays WhatsApp-ready text in console +# + +# 1. Load required packages +# ----------------------- +suppressPackageStartupMessages({ + library(sf) + library(terra) + library(tidyverse) + library(lubridate) + library(here) + library(spdep) # For spatial statistics +}) + +# 2. Main function to handle messaging workflow +# --------------------------------------------- +main <- function() { + # Capture command line arguments + args <- commandArgs(trailingOnly = TRUE) + + # Process arguments with defaults + current_week <- if (length(args) >= 1 && !is.na(args[1])) { + as.numeric(args[1]) + } else { + 39 # Default for proof of concept + } + + previous_week <- if (length(args) >= 2 && !is.na(args[2])) { + as.numeric(args[2]) + } else { + 38 # Default for proof of concept + } + + estate_name <- if (length(args) >= 3 && !is.na(args[3])) { + as.character(args[3]) + } else { + "aura" # Default estate + } + + year <- 2025 # Current year - could be made dynamic + + # Make estate_name available globally so parameters_project.R can use it + assign("project_dir", estate_name, envir = .GlobalEnv) + + # Initialize project configuration and load utility functions + tryCatch({ + source("parameters_project.R") + source("crop_messaging_utils.R") + }, error = function(e) { + warning("Default source files not found. Attempting to source from 'r_app' directory.") + tryCatch({ + source(here::here("r_app", "parameters_project.R")) + source(here::here("r_app", "crop_messaging_utils.R")) + warning(paste("Successfully sourced files from 'r_app' directory.")) + + }, error = function(e) { + stop("Failed to source required files from both default and 'r_app' directories.") + }) + }) + + log_message("Starting crop messaging analysis") + + # Run the modular analysis + analysis_results <- run_estate_analysis(estate_name, current_week, previous_week, year) + field_results <- analysis_results$field_results + + # Display detailed field-by-field analysis + cat("=== FIELD-BY-FIELD ANALYSIS ===\n\n") + + for (field_id in names(field_results)) { + field_info <- field_results[[field_id]] + current_field <- field_info$current_stats + previous_field <- field_info$previous_stats + ci_change <- field_info$ci_change + change_category <- field_info$change_category + change_percentages <- field_info$change_percentages + uniformity_category <- field_info$uniformity_category + message_result <- field_info$message_result + + # Print enhanced field analysis + cat("FIELD:", current_field$field, "-", current_field$sub_field, "\n") + cat("- Field size:", round(current_field$field_area_ha, 1), "hectares\n") + cat("- Week", previous_week, "CI:", round(previous_field$mean_ci, 3), "\n") + cat("- Week", current_week, "CI:", round(current_field$mean_ci, 3), "\n") + cat("- Terra stats: Mean =", round(current_field$mean_ci, 3), + ", CV =", round(current_field$cv, 3), + ", Range = [", round(current_field$min_ci, 2), "-", round(current_field$max_ci, 2), "]\n") + + cat("- Within acceptable range (±25% of mean):", round(current_field$acceptable_pct, 1), "%\n") + + # Display primary uniformity metrics (CV and Entropy) + cat("- Field uniformity: CV =", round(current_field$cv, 3)) + if (current_field$cv < 0.08) { + cat(" (excellent)") + } else if (current_field$cv < 0.15) { + cat(" (good)") + } else if (current_field$cv < 0.30) { + cat(" (moderate)") + } else if (current_field$cv < 0.50) { + cat(" (high variation)") + } else { + cat(" (very high variation)") + } + + # Add entropy information + if (!is.na(current_field$entropy)) { + cat(", Entropy =", round(current_field$entropy, 3)) + # Entropy interpretation (higher = more heterogeneous) + # Adjusted thresholds to better match CV patterns + if (current_field$entropy < 1.3) { + cat(" (very uniform)") + } else if (current_field$entropy < 1.5) { + cat(" (uniform)") + } else if (current_field$entropy < 1.7) { + cat(" (moderate heterogeneity)") + } else { + cat(" (high heterogeneity)") + } + } + cat("\n") + + cat("- Change: Mean =", round(ci_change, 3), "(", change_category, ")") + if (!is.na(change_percentages$positive_pct)) { + # Calculate hectares for this field using field area from geojson + field_hectares <- current_field$field_area_ha + improving_hectares <- (change_percentages$positive_pct / 100) * field_hectares + declining_hectares <- (change_percentages$negative_pct / 100) * field_hectares + + cat(", Areas: ", round(change_percentages$positive_pct, 1), "% (", round(improving_hectares, 1), " ha) improving, ", + round(change_percentages$negative_pct, 1), "% (", round(declining_hectares, 1), " ha) declining\n") + } else { + cat("\n") + } + cat("- Spatial Pattern:", uniformity_category, "\n") + + # Add spatial details if available + if (!is.na(current_field$spatial_autocorr$morans_i)) { + cat("- Moran's I:", round(current_field$spatial_autocorr$morans_i, 3), + "(", current_field$spatial_autocorr$interpretation, ")") + + # Add agricultural context explanation for Moran's I + moran_val <- current_field$spatial_autocorr$morans_i + if (moran_val >= 0.7 && moran_val < 0.85) { + cat(" - normal field continuity") + } else if (moran_val >= 0.85 && moran_val < 0.95) { + cat(" - strong spatial pattern") + } else if (moran_val >= 0.95) { + cat(" - very strong clustering, monitor for management issues") + } else if (moran_val < 0.7 && moran_val > 0.3) { + cat(" - moderate spatial pattern") + } else { + cat(" - unusual spatial pattern for crop field") + } + cat("\n") + } + + if (!is.na(current_field$extreme_percentages$hotspot_pct)) { + cat("- Extreme areas: ", round(current_field$extreme_percentages$hotspot_pct, 1), + "% hotspots (high-performing), ", round(current_field$extreme_percentages$coldspot_pct, 1), + "% coldspots (underperforming)") + + # Show method used for extreme detection + if (!is.null(current_field$extreme_percentages$method)) { + if (current_field$extreme_percentages$method == "getis_ord_gi_star") { + cat(" [Getis-Ord Gi*]") + } else if (current_field$extreme_percentages$method == "simple_sd") { + cat(" [Simple SD]") + } + } + cat("\n") + } + + cat("- Message:", message_result$message, "\n") + cat("- Alert needed:", if(message_result$worth_sending) "YES 🚨" else "NO", "\n\n") + } + + # Summary of alerts + alert_fields <- sapply(field_results, function(x) x$message_result$worth_sending) + total_alerts <- sum(alert_fields) + + cat("=== SUMMARY ===\n") + cat("Total fields analyzed:", length(field_results), "\n") + cat("Fields requiring alerts:", total_alerts, "\n") + + if (total_alerts > 0) { + cat("\nFields needing attention:\n") + for (field_id in names(field_results)[alert_fields]) { + field_info <- field_results[[field_id]] + cat("-", field_info$current_stats$field, "-", field_info$current_stats$sub_field, + ":", field_info$message_result$message, "\n") + } + } + + # Farm-wide analysis summary table + cat("\n=== FARM-WIDE ANALYSIS SUMMARY ===\n") + + # Field uniformity statistics with detailed categories + excellent_fields <- sapply(field_results, function(x) x$current_stats$cv <= 0.08) + good_fields <- sapply(field_results, function(x) x$current_stats$cv > 0.08 & x$current_stats$cv <= 0.15) + moderate_fields <- sapply(field_results, function(x) x$current_stats$cv > 0.15 & x$current_stats$cv <= 0.30) + poor_fields <- sapply(field_results, function(x) x$current_stats$cv > 0.30) + + n_excellent <- sum(excellent_fields) + n_good <- sum(good_fields) + n_moderate <- sum(moderate_fields) + n_poor <- sum(poor_fields) + n_uniform_total <- n_excellent + n_good # Total uniform fields (CV ≤ 0.20) + + # Calculate farm-wide area statistics + total_hectares <- sum(sapply(field_results, function(x) x$current_stats$field_area_ha), na.rm = TRUE) + total_improving_hectares <- sum(sapply(field_results, function(x) { + if (!is.na(x$change_percentages$positive_pct)) { + (x$change_percentages$positive_pct / 100) * x$current_stats$field_area_ha + } else 0 + }), na.rm = TRUE) + + total_declining_hectares <- sum(sapply(field_results, function(x) { + if (!is.na(x$change_percentages$negative_pct)) { + (x$change_percentages$negative_pct / 100) * x$current_stats$field_area_ha + } else 0 + }), na.rm = TRUE) + + # Calculate farm-wide percentages + farm_improving_pct <- (total_improving_hectares / total_hectares) * 100 + farm_declining_pct <- (total_declining_hectares / total_hectares) * 100 + + # Display summary table + cat("\nFIELD UNIFORMITY SUMMARY:\n") + cat("│ Uniformity Level │ Count │ Percent │\n") + cat(sprintf("│ Excellent (CV≤0.08) │ %5d │ %6.1f%% │\n", n_excellent, (n_excellent/length(field_results))*100)) + cat(sprintf("│ Good (CV 0.08-0.15) │ %5d │ %6.1f%% │\n", n_good, (n_good/length(field_results))*100)) + cat(sprintf("│ Moderate (CV 0.15-0.30) │ %5d │ %6.1f%% │\n", n_moderate, (n_moderate/length(field_results))*100)) + cat(sprintf("│ Poor (CV>0.30) │ %5d │ %6.1f%% │\n", n_poor, (n_poor/length(field_results))*100)) + cat(sprintf("│ Total fields │ %5d │ %6.1f%% │\n", length(field_results), 100.0)) + + cat("\nFARM-WIDE AREA CHANGE SUMMARY:\n") + cat("│ Change Type │ Hectares│ Percent │\n") + cat(sprintf("│ Improving areas │ %7.1f │ %6.1f%% │\n", total_improving_hectares, farm_improving_pct)) + cat(sprintf("│ Declining areas │ %7.1f │ %6.1f%% │\n", total_declining_hectares, farm_declining_pct)) + cat(sprintf("│ Total area │ %7.1f │ %6.1f%% │\n", total_hectares, 100.0)) + + # Additional insights + cat("\nKEY INSIGHTS:\n") + cat(sprintf("• %d%% of fields have good uniformity (CV ≤ 0.15)\n", round((n_uniform_total/length(field_results))*100))) + cat(sprintf("• %d%% of fields have excellent uniformity (CV ≤ 0.08)\n", round((n_excellent/length(field_results))*100))) + cat(sprintf("• %.1f hectares (%.1f%%) of farm area is improving week-over-week\n", total_improving_hectares, farm_improving_pct)) + cat(sprintf("• %.1f hectares (%.1f%%) of farm area is declining week-over-week\n", total_declining_hectares, farm_declining_pct)) + cat(sprintf("• Total farm area analyzed: %.1f hectares\n", total_hectares)) + if (farm_improving_pct > farm_declining_pct) { + cat(sprintf("• Overall trend: POSITIVE (%.1f%% more area improving than declining)\n", farm_improving_pct - farm_declining_pct)) + } else if (farm_declining_pct > farm_improving_pct) { + cat(sprintf("• Overall trend: NEGATIVE (%.1f%% more area declining than improving)\n", farm_declining_pct - farm_improving_pct)) + } else { + cat("• Overall trend: BALANCED (equal improvement and decline)\n") + } + + # Generate and save multiple output formats + saved_files <- save_analysis_outputs(analysis_results) + + # Analysis complete + cat("\n=== ANALYSIS COMPLETE ===\n") + cat("All field analysis results, farm-wide summary, and output files created.\n") + + # Return results for potential further processing + invisible(analysis_results) +} + +if (sys.nframe() == 0) { + main() +} diff --git a/r_app/09_calculate_kpis.R b/r_app/09_calculate_kpis.R new file mode 100644 index 0000000..a06e138 --- /dev/null +++ b/r_app/09_calculate_kpis.R @@ -0,0 +1,156 @@ +# 09_CALCULATE_KPIS.R +# =================== +# This script calculates 6 Key Performance Indicators (KPIs) for sugarcane monitoring: +# 1. Field Uniformity Summary +# 2. Farm-wide Area Change Summary +# 3. TCH Forecasted +# 4. Growth Decline Index +# 5. Weed Presence Score +# 6. Gap Filling Score (placeholder) +# +# Usage: Rscript 09_calculate_kpis.R [end_date] [offset] [project_dir] +# - end_date: End date for KPI calculation (YYYY-MM-DD format), default: today +# - offset: Number of days to look back (not currently used for KPIs, but for consistency) +# - project_dir: Project directory name (e.g., "aura", "esa") + +# 1. Load required libraries +# ------------------------- +suppressPackageStartupMessages({ + library(here) + library(sf) + library(terra) + library(dplyr) + library(tidyr) + library(lubridate) + library(readr) + library(caret) + library(CAST) + library(randomForest) +}) + +# 2. Main function +# -------------- +main <- function() { + # Process command line arguments + args <- commandArgs(trailingOnly = TRUE) + + # Process end_date argument + if (length(args) >= 1 && !is.na(args[1])) { + end_date <- as.Date(args[1]) + if (is.na(end_date)) { + warning("Invalid end_date provided. Using default (current date).") + end_date <- Sys.Date() + } + } else { + end_date <- Sys.Date() + } + + # Process offset argument (for consistency with other scripts, not currently used) + if (length(args) >= 2 && !is.na(args[2])) { + offset <- as.numeric(args[2]) + if (is.na(offset) || offset <= 0) { + warning("Invalid offset provided. Using default (7 days).") + offset <- 7 + } + } else { + offset <- 7 + } + + # Process project_dir argument + if (length(args) >= 3 && !is.na(args[3])) { + project_dir <- as.character(args[3]) + } else { + project_dir <- "esa" # Default project + } + + # Make project_dir available globally so parameters_project.R can use it + assign("project_dir", project_dir, envir = .GlobalEnv) + + # 3. Load utility functions and project configuration + # -------------------------------------------------- + + tryCatch({ + source(here("r_app", "crop_messaging_utils.R")) + }, error = function(e) { + stop("Error loading crop_messaging_utils.R: ", e$message) + }) + + tryCatch({ + source(here("r_app", "kpi_utils.R")) + }, error = function(e) { + stop("Error loading kpi_utils.R: ", e$message) + }) + + # Load project parameters (this sets up all directory paths and field boundaries) + tryCatch({ + source(here("r_app", "parameters_project.R")) + }, error = function(e) { + stop("Error loading parameters_project.R: ", e$message) + }) + + # Load growth model utils if available (for yield prediction) + tryCatch({ + source(here("r_app", "growth_model_utils.R")) + }, error = function(e) { + warning("growth_model_utils.R not found, yield prediction KPI will use placeholder data") + }) + + # Check if required variables exist + if (!exists("project_dir")) { + stop("project_dir must be set before running this script") + } + + if (!exists("field_boundaries_sf") || is.null(field_boundaries_sf)) { + stop("Field boundaries not loaded. Check parameters_project.R initialization.") + } + + # 4. Calculate all KPIs + # ------------------- + output_dir <- file.path(reports_dir, "kpis") + + kpi_results <- calculate_all_kpis( + report_date = end_date, + output_dir = output_dir, + field_boundaries_sf = field_boundaries_sf, + harvesting_data = harvesting_data, + cumulative_CI_vals_dir = cumulative_CI_vals_dir, + weekly_CI_mosaic = weekly_CI_mosaic, + reports_dir = reports_dir, + project_dir = project_dir + ) + + # 5. Print summary + # -------------- + cat("\n=== KPI CALCULATION SUMMARY ===\n") + cat("Report Date:", as.character(kpi_results$metadata$report_date), "\n") + cat("Current Week:", kpi_results$metadata$current_week, "\n") + cat("Previous Week:", kpi_results$metadata$previous_week, "\n") + cat("Total Fields Analyzed:", kpi_results$metadata$total_fields, "\n") + cat("Calculation Time:", as.character(kpi_results$metadata$calculation_time), "\n") + + cat("\nField Uniformity Summary:\n") + print(kpi_results$field_uniformity_summary) + + cat("\nArea Change Summary:\n") + print(kpi_results$area_change) + + cat("\nTCH Forecasted:\n") + print(kpi_results$tch_forecasted) + + cat("\nGrowth Decline Index:\n") + print(kpi_results$growth_decline) + + cat("\nWeed Presence Score:\n") + print(kpi_results$weed_presence) + + cat("\nGap Filling Score:\n") + print(kpi_results$gap_filling) + + cat("\n=== KPI CALCULATION COMPLETED ===\n") +} + +# 6. Script execution +# ----------------- +if (sys.nframe() == 0) { + main() +} diff --git a/r_app/10_CI_report_with_kpis_simple.Rmd b/r_app/10_CI_report_with_kpis_simple.Rmd new file mode 100644 index 0000000..03f4aa6 --- /dev/null +++ b/r_app/10_CI_report_with_kpis_simple.Rmd @@ -0,0 +1,1096 @@ +--- +params: + ref: "word-styles-reference-var1.docx" + output_file: CI_report.docx + report_date: "2025-09-30" + data_dir: "aura" + mail_day: "Wednesday" + borders: FALSE + ci_plot_type: "both" # options: "absolute", "cumulative", "both" + colorblind_friendly: TRUE # use colorblind-friendly palettes (viridis/plasma) + facet_by_season: FALSE # facet CI trend plots by season instead of overlaying + x_axis_unit: "days" # x-axis unit for trend plots: "days" or "weeks" +output: + word_document: + reference_docx: !expr file.path("word-styles-reference-var1.docx") + toc: no +editor_options: + chunk_output_type: console +--- + +```{r setup_parameters, include=FALSE} +# Set up basic report parameters from input values +report_date <- params$report_date +mail_day <- params$mail_day +borders <- params$borders +ci_plot_type <- params$ci_plot_type +colorblind_friendly <- params$colorblind_friendly +facet_by_season <- params$facet_by_season +x_axis_unit <- params$x_axis_unit +``` + +```{r load_libraries, message=FALSE, warning=FALSE, include=FALSE} +# Configure knitr options +knitr::opts_chunk$set(warning = FALSE, message = FALSE) + +# Set flag for reporting scripts to use pivot.geojson instead of pivot_2.geojson +reporting_script <- TRUE + +# Load all packages at once with suppressPackageStartupMessages +suppressPackageStartupMessages({ + library(here) + library(sf) + library(terra) + library(exactextractr) + library(tidyverse) + library(tmap) + library(lubridate) + library(zoo) + library(rsample) + library(caret) + library(randomForest) + library(CAST) + library(knitr) + library(tidyr) + library(flextable) +}) + +# Load custom utility functions +tryCatch({ + source("report_utils.R") +}, error = function(e) { + message(paste("Error loading report_utils.R:", e$message)) + # Try alternative path if the first one fails + tryCatch({ + source(here::here("r_app", "report_utils.R")) + }, error = function(e) { + stop("Could not load report_utils.R from either location: ", e$message) + }) +}) + +# Function to determine field priority level based on CV and Moran's I +# Returns: 1=Urgent, 2=Monitor, 3=No stress +get_field_priority_level <- function(cv, morans_i) { + # Handle NA values + if (is.na(cv) || is.na(morans_i)) return(3) # Default to no stress + + # Determine priority based on thresholds + if (cv < 0.1) { + if (morans_i < 0.7) { + return(3) # No stress + } else if (morans_i <= 0.9) { + return(2) # Monitor (young field with some clustering) + } else { + return(1) # Urgent + } + } else if (cv <= 0.15) { + if (morans_i < 0.7) { + return(2) # Monitor + } else { + return(1) # Urgent + } + } else { # cv > 0.15 + return(1) # Urgent + } +} +``` + +```{r initialize_project_config, message=FALSE, warning=FALSE, include=FALSE} +# Set the project directory from parameters +project_dir <- params$data_dir + +# Source project parameters with error handling +tryCatch({ + source(here::here("r_app", "parameters_project.R")) +}, error = function(e) { + stop("Error loading parameters_project.R: ", e$message) +}) + +# Log initial configuration +safe_log("Starting the R Markdown script with KPIs") +safe_log(paste("mail_day params:", params$mail_day)) +safe_log(paste("report_date params:", params$report_date)) +safe_log(paste("mail_day variable:", mail_day)) +``` + +```{r load_kpi_data, message=FALSE, warning=FALSE, include=FALSE} +## SIMPLE KPI LOADING - robust lookup with fallbacks +# Primary expected directory inside the laravel storage +kpi_data_dir <- file.path("..", "laravel_app", "storage", "app", project_dir, "reports", "kpis") +date_suffix <- format(as.Date(report_date), "%Y%m%d") + +# Calculate current week from report_date using ISO 8601 week numbering +current_week <- as.numeric(format(as.Date(report_date), "%V")) +week_suffix <- paste0("week", current_week) + +# Candidate filenames we expect (exact and common variants) +expected_summary_names <- c( + paste0(project_dir, "_kpi_summary_tables_", week_suffix, ".rds"), + paste0(project_dir, "_kpi_summary_tables_", date_suffix, ".rds"), + paste0(project_dir, "_kpi_summary_tables.rds"), + "kpi_summary_tables.rds", + paste0("kpi_summary_tables_", week_suffix, ".rds"), + paste0("kpi_summary_tables_", date_suffix, ".rds") +) + +expected_field_details_names <- c( + paste0(project_dir, "_field_details_", week_suffix, ".rds"), + paste0(project_dir, "_field_details_", date_suffix, ".rds"), + paste0(project_dir, "_field_details.rds"), + "field_details.rds" +) + +# Helper to attempt loading a file from the directory or fallback to a workspace-wide search +try_load_from_dir <- function(dir, candidates) { + if (!dir.exists(dir)) return(NULL) + for (name in candidates) { + f <- file.path(dir, name) + if (file.exists(f)) return(f) + } + return(NULL) +} + +# Try primary directory first +summary_file <- try_load_from_dir(kpi_data_dir, expected_summary_names) +field_details_file <- try_load_from_dir(kpi_data_dir, expected_field_details_names) + +# If not found, perform a workspace-wide search (slower) limited to laravel_app storage +if (is.null(summary_file) || is.null(field_details_file)) { + safe_log(paste("KPI files not found in", kpi_data_dir, "—searching workspace for RDS files")) + # List rds files under laravel_app/storage/app recursively + files <- list.files(path = file.path("laravel_app", "storage", "app"), pattern = "\\.rds$", recursive = TRUE, full.names = TRUE) + # Try to match by expected names + if (is.null(summary_file)) { + matched <- files[basename(files) %in% expected_summary_names] + if (length(matched) > 0) summary_file <- matched[1] + } + if (is.null(field_details_file)) { + matched2 <- files[basename(files) %in% expected_field_details_names] + if (length(matched2) > 0) field_details_file <- matched2[1] + } +} + +# Final checks and load with safe error messages +kpi_files_exist <- FALSE +if (!is.null(summary_file) && file.exists(summary_file)) { + safe_log(paste("Loading KPI summary from:", summary_file)) + summary_tables <- tryCatch(readRDS(summary_file), error = function(e) { safe_log(paste("Failed to read summary RDS:", e$message), "ERROR"); NULL }) + if (!is.null(summary_tables)) kpi_files_exist <- TRUE +} else { + safe_log(paste("KPI summary file not found. Searched:", paste(expected_summary_names, collapse=", ")), "WARNING") +} + +if (!is.null(field_details_file) && file.exists(field_details_file)) { + safe_log(paste("Loading field details from:", field_details_file)) + field_details_table <- tryCatch(readRDS(field_details_file), error = function(e) { safe_log(paste("Failed to read field details RDS:", e$message), "ERROR"); NULL }) + if (!is.null(field_details_table)) kpi_files_exist <- kpi_files_exist && TRUE +} else { + safe_log(paste("Field details file not found. Searched:", paste(expected_field_details_names, collapse=", ")), "WARNING") +} + +if (kpi_files_exist) { + safe_log("✓ KPI summary tables loaded successfully") +} else { + safe_log("KPI files could not be located or loaded. KPI sections will be skipped.", "WARNING") +} + +#' Generate field-specific KPI summary for display in reports +#' @param field_name Name of the field to summarize +#' @param field_details_table Data frame with field-level KPI details +#' @return Formatted text string with field KPI summary +generate_field_kpi_summary <- function(field_name, field_details_table, CI_quadrant) { + tryCatch({ + # Get field age from CI quadrant data for the CURRENT SEASON only + # First identify the current season for this field + current_season <- CI_quadrant %>% + filter(field == field_name, Date <= as.Date(report_date)) %>% + group_by(season) %>% + summarise(season_end = max(Date), .groups = 'drop') %>% + filter(season == max(season)) %>% + pull(season) + + # Get the most recent DOY from the current season + field_age <- CI_quadrant %>% + filter(field == field_name, season == current_season) %>% + pull(DOY) %>% + max(na.rm = TRUE) + + # Filter data for this specific field + field_data <- field_details_table %>% + filter(Field == field_name) + + if (nrow(field_data) == 0) { + return(paste("**Field", field_name, "KPIs:** Data not available")) + } + + # Aggregate sub-field data for field-level summary + # For categorical data, take the most common value or highest risk level + field_summary <- field_data %>% + summarise( + field_size = sum(`Field Size (ha)`, na.rm = TRUE), + uniformity_levels = paste(unique(`Growth Uniformity`), collapse = "/"), + avg_yield_forecast = ifelse(is.na(`Yield Forecast (t/ha)`[1]), NA, mean(`Yield Forecast (t/ha)`, na.rm = TRUE)), + max_gap_score = max(`Gap Score`, na.rm = TRUE), + highest_decline_risk = case_when( + any(`Decline Risk` == "Very-high") ~ "Very-high", + any(`Decline Risk` == "High") ~ "High", + any(`Decline Risk` == "Moderate") ~ "Moderate", + any(`Decline Risk` == "Low") ~ "Low", + TRUE ~ "Unknown" + ), + highest_weed_risk = case_when( + any(`Weed Risk` == "High") ~ "High", + any(`Weed Risk` == "Moderate") ~ "Moderate", + any(`Weed Risk` == "Low") ~ "Low", + TRUE ~ "Unknown" + ), + avg_mean_ci = mean(`Mean CI`, na.rm = TRUE), + avg_cv = mean(`CV Value`, na.rm = TRUE), + .groups = 'drop' + ) + + # Apply age-based filtering to yield forecast + if (is.na(field_age) || field_age < 240) { + field_summary$avg_yield_forecast <- NA_real_ + } + + # Format the summary text + yield_text <- if (is.na(field_summary$avg_yield_forecast)) { + "Yield Forecast: NA" + } else { + paste0("Yield Forecast: ", round(field_summary$avg_yield_forecast, 1), " t/ha") + } + + kpi_text <- paste0( + "Size: ", round(field_summary$field_size, 1), " ha • Growth Uniformity: ", field_summary$uniformity_levels, + " • ", yield_text, " • Gap Score: ", round(field_summary$max_gap_score, 1), + " • Decline Risk: ", field_summary$highest_decline_risk, " • Weed Risk: ", field_summary$highest_weed_risk, + " • Mean CI: ", round(field_summary$avg_mean_ci, 2) + ) + + # Wrap in smaller text HTML tags for Word output + #kpi_text <- paste0("", kpi_text, "") + kpi_text <- paste0("", kpi_text, "") + + # Add alerts based on risk levels (smaller font too) + # alerts <- c() + # if (field_summary$highest_decline_risk %in% c("High", "Very-high")) { + # alerts <- c(alerts, "🚨 High risk of growth decline detected") + # } + # if (field_summary$highest_weed_risk == "High") { + # alerts <- c(alerts, "⚠️ High weed presence detected") + # } + # if (field_summary$max_gap_score > 20) { + # alerts <- c(alerts, "💡 Significant gaps detected - monitor closely") + # } + # if (field_summary$avg_cv > 0.25) { + # alerts <- c(alerts, "⚠️ Poor field uniformity - check irrigation/fertility") + # } + + # if (length(alerts) > 0) { + # kpi_text <- paste0(kpi_text, "\n\n", paste(alerts, collapse = "\n")) + # } + + return(kpi_text) + + }, error = function(e) { + safe_log(paste("Error generating KPI summary for field", field_name, ":", e$message), "ERROR") + return(paste("**Field", field_name, "KPIs:** Error generating summary")) + }) +} +``` + +```{r calculate_dates_and_weeks, message=FALSE, warning=FALSE, include=FALSE} +# Set locale for consistent date formatting +Sys.setlocale("LC_TIME", "C") + +# Initialize date variables from parameters +today <- as.character(report_date) +mail_day_as_character <- as.character(mail_day) + +# Calculate report dates and weeks using ISO 8601 week numbering +report_date_obj <- as.Date(today) +current_week <- as.numeric(format(report_date_obj, "%V")) +year <- as.numeric(format(report_date_obj, "%Y")) + +# Calculate dates for weekly analysis +week_start <- report_date_obj - ((as.numeric(format(report_date_obj, "%w")) + 1) %% 7) +week_end <- week_start + 6 + +# Calculate week days (copied from 05 script for compatibility) +report_date_as_week_day <- weekdays(lubridate::ymd(today)) +days_of_week <- c("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday") + +# Calculate initial week number +week <- lubridate::week(today) +safe_log(paste("Initial week calculation:", week, "today:", today)) + +# Calculate previous dates for comparisons +today_minus_1 <- as.character(lubridate::ymd(today) - 7) +today_minus_2 <- as.character(lubridate::ymd(today) - 14) +today_minus_3 <- as.character(lubridate::ymd(today) - 21) + +# Adjust week calculation based on mail day +if (which(days_of_week == report_date_as_week_day) > which(days_of_week == mail_day_as_character)) { + safe_log("Adjusting weeks because of mail day") + week <- lubridate::week(today) + 1 + today_minus_1 <- as.character(lubridate::ymd(today)) + today_minus_2 <- as.character(lubridate::ymd(today) - 7) + today_minus_3 <- as.character(lubridate::ymd(today) - 14) +} + +# Calculate week numbers for previous weeks +week_minus_1 <- week - 1 +week_minus_2 <- week - 2 +week_minus_3 <- week - 3 + +# Format current week with leading zeros +week <- sprintf("%02d", week) + +safe_log(paste("Report week:", current_week, "Year:", year)) +safe_log(paste("Week range:", week_start, "to", week_end)) +``` + +```{r load_ci_data, message=FALSE, warning=FALSE, include=FALSE} +# Load CI index data with error handling +tryCatch({ + CI_quadrant <- readRDS(here::here(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds")) + + safe_log("Successfully loaded CI quadrant data") +}, error = function(e) { + stop("Error loading CI quadrant data: ", e$message) +}) + +# Get file paths for different weeks using the utility function +tryCatch({ + path_to_week_current = get_week_path(weekly_CI_mosaic, today, 0) + path_to_week_minus_1 = get_week_path(weekly_CI_mosaic, today, -1) + path_to_week_minus_2 = get_week_path(weekly_CI_mosaic, today, -2) + path_to_week_minus_3 = get_week_path(weekly_CI_mosaic, today, -3) + + # Log the calculated paths + safe_log("Required mosaic paths:") + safe_log(paste("Path to current week:", path_to_week_current)) + safe_log(paste("Path to week minus 1:", path_to_week_minus_1)) + safe_log(paste("Path to week minus 2:", path_to_week_minus_2)) + safe_log(paste("Path to week minus 3:", path_to_week_minus_3)) + + # Validate that files exist + if (!file.exists(path_to_week_current)) warning("Current week mosaic file does not exist: ", path_to_week_current) + if (!file.exists(path_to_week_minus_1)) warning("Week minus 1 mosaic file does not exist: ", path_to_week_minus_1) + if (!file.exists(path_to_week_minus_2)) warning("Week minus 2 mosaic file does not exist: ", path_to_week_minus_2) + if (!file.exists(path_to_week_minus_3)) warning("Week minus 3 mosaic file does not exist: ", path_to_week_minus_3) + + # Load raster data with terra functions + CI <- terra::rast(path_to_week_current)$CI + CI_m1 <- terra::rast(path_to_week_minus_1)$CI + CI_m2 <- terra::rast(path_to_week_minus_2)$CI + CI_m3 <- terra::rast(path_to_week_minus_3)$CI + + # DEBUG: Check which weeks were actually loaded and their data ranges + safe_log(paste("DEBUG - CI (current) range:", paste(terra::minmax(CI)[,1], collapse=" to "))) + safe_log(paste("DEBUG - CI_m1 (week-1) range:", paste(terra::minmax(CI_m1)[,1], collapse=" to "))) + safe_log(paste("DEBUG - CI_m2 (week-2) range:", paste(terra::minmax(CI_m2)[,1], collapse=" to "))) + safe_log(paste("DEBUG - CI_m3 (week-3) range:", paste(terra::minmax(CI_m3)[,1], collapse=" to "))) + +}, error = function(e) { + stop("Error loading raster data: ", e$message) +}) +``` + +```{r compute_benchmarks_once, include=FALSE} +# Compute CI benchmarks once for the entire estate +benchmarks <- compute_ci_benchmarks(CI_quadrant, project_dir, c(10, 50, 90)) +if (!is.null(benchmarks)) { + safe_log("Benchmarks computed successfully for the report") +} else { + safe_log("Failed to compute benchmarks", "WARNING") +} +``` + +## Report Summary + +**Farm Location:** `r toupper(project_dir)` Estate +**Report Period:** Week `r current_week` of `r year` +**Data Source:** Planet Labs Satellite Imagery +**Analysis Type:** Chlorophyll Index (CI) Monitoring +**Report Generated on:** `r format(Sys.time(), "%B %d, %Y at %H:%M")` + +## Report Structure + +**Section 1:** Farm-wide analyses, summaries and Key Performance Indicators (KPIs) +**Section 2:** Field-by-field detailed analyses with maps and trend graphs +**Section 3:** Explanation of the report, definitions, and methodology + +## Key Insights + +```{r key_insights, echo=FALSE, results='asis'} +# Calculate key insights from KPI data +if (exists("summary_tables") && !is.null(summary_tables)) { + + # Field uniformity insights + uniformity_data <- summary_tables$field_uniformity_summary + good_uniformity <- uniformity_data$Percent[uniformity_data$`Uniformity Level` == "Good"] + excellent_uniformity <- uniformity_data$Percent[uniformity_data$`Uniformity Level` == "Excellent"] + + # Area change insights + area_change_data <- summary_tables$area_change_summary + improving_area <- area_change_data$Hectares[area_change_data$`Change Type` == "Improving areas"] + improving_pct <- area_change_data$Percent[area_change_data$`Change Type` == "Improving areas"] + declining_area <- area_change_data$Hectares[area_change_data$`Change Type` == "Declining areas"] + declining_pct <- area_change_data$Percent[area_change_data$`Change Type` == "Declining areas"] + + cat("- ", ifelse(length(good_uniformity) > 0, good_uniformity, "N/A"), "% of fields have good uniformity\n", sep="") + cat("- ", ifelse(length(excellent_uniformity) > 0, excellent_uniformity, "N/A"), "% of fields have excellent uniformity\n", sep="") + cat("- ", ifelse(length(improving_area) > 0, round(improving_area, 1), "N/A"), " hectares (", ifelse(length(improving_pct) > 0, improving_pct, "N/A"), "%) of farm area is improving week-over-week\n", sep="") + cat("- ", ifelse(length(declining_area) > 0, round(declining_area, 1), "N/A"), " hectares (", ifelse(length(declining_pct) > 0, declining_pct, "N/A"), "%) of farm area is declining week-over-week\n", sep="") + +} else { + cat("KPI data not available for key insights.\n") +} +``` + +\newpage + +# Section 1: Farm-wide Analyses and KPIs + +## Executive Summary - Key Performance Indicators + +```{r combined_kpi_table, echo=FALSE} +# Combine all KPI tables into a single table with standardized column names +display_names <- c( + field_uniformity_summary = "Field Uniformity", + area_change_summary = "Area Change", + tch_forecasted_summary = "TCH Forecasted", + growth_decline_summary = "Growth Decline", + weed_presence_summary = "Weed Presence", + gap_filling_summary = "Gap Filling" +) + +combined_df <- bind_rows(lapply(names(summary_tables), function(kpi) { + df <- summary_tables[[kpi]] + names(df) <- c("Level", "Count", "Percent") + # Format Count as integer (no decimals) + df <- df %>% + mutate( + Count = as.integer(round(Count)), + KPI = display_names[kpi], + .before = 1 + ) + df +}), .id = NULL) + +# Create grouped display where KPI name appears only once per group +combined_df <- combined_df %>% + group_by(KPI) %>% + mutate( + KPI_display = if_else(row_number() == 1, KPI, "") + ) %>% + ungroup() %>% + select(KPI_display, Level, Count, Percent) %>% + rename(KPI = KPI_display) + +# Render as flextable with merged cells +ft <- flextable(combined_df) %>% +# set_caption("Combined KPI Summary Table") %>% + merge_v(j = "KPI") %>% # Merge vertically identical cells in KPI column + autofit() + +# Add horizontal lines after each KPI group +kpi_groups <- sapply(names(summary_tables), function(kpi) nrow(summary_tables[[kpi]])) +cum_rows <- cumsum(kpi_groups) +for (i in seq_along(cum_rows)) { + if (i < length(cum_rows)) { + ft <- ft %>% hline(i = cum_rows[i], border = officer::fp_border(width = 2)) + } +} + +ft +``` + +## Field Alerts + +```{r field_alerts_table, echo=FALSE} +# Generate alerts for all fields +generate_field_alerts <- function(field_details_table) { + if (is.null(field_details_table) || nrow(field_details_table) == 0) { + return(data.frame(Field = character(), Alert = character())) + } + + alerts_list <- list() + + # Get unique fields + unique_fields <- unique(field_details_table$Field) + + for (field_name in unique_fields) { + field_data <- field_details_table %>% filter(Field == field_name) + + # Aggregate data for the field + field_summary <- field_data %>% + summarise( + field_size = sum(`Field Size (ha)`, na.rm = TRUE), + uniformity_levels = paste(unique(`Growth Uniformity`), collapse = "/"), + avg_yield_forecast = mean(`Yield Forecast (t/ha)`, na.rm = TRUE), + max_gap_score = max(`Gap Score`, na.rm = TRUE), + highest_decline_risk = case_when( + any(`Decline Risk` == "Very-high") ~ "Very-high", + any(`Decline Risk` == "High") ~ "High", + any(`Decline Risk` == "Moderate") ~ "Moderate", + any(`Decline Risk` == "Low") ~ "Low", + TRUE ~ "Unknown" + ), + highest_weed_risk = case_when( + any(`Weed Risk` == "High") ~ "High", + any(`Weed Risk` == "Moderate") ~ "Moderate", + any(`Weed Risk` == "Low") ~ "Low", + TRUE ~ "Unknown" + ), + avg_mean_ci = mean(`Mean CI`, na.rm = TRUE), + avg_cv = mean(`CV Value`, na.rm = TRUE), + .groups = 'drop' + ) + + # Generate alerts for this field based on simplified CV-Moran's I priority system (3 levels) + field_alerts <- c() + + # Get CV and Moran's I values + avg_cv <- field_summary$avg_cv + morans_i <- mean(field_data[["Moran's I"]], na.rm = TRUE) + + # Determine priority level (1=Urgent, 2=Monitor, 3=No stress) + priority_level <- get_field_priority_level(avg_cv, morans_i) + + # Generate alerts based on priority level + if (priority_level == 1) { + field_alerts <- c(field_alerts, "🚨 Urgent: Field requires immediate attention") + } else if (priority_level == 2) { + field_alerts <- c(field_alerts, "⚠️ Monitor: Field should be checked when possible") + } + # Priority 3: No alert (no stress) + + # Keep other alerts for decline risk, weed risk, gap score + if (field_summary$highest_decline_risk %in% c("High", "Very-high")) { + field_alerts <- c(field_alerts, "🚨 High risk of growth decline detected") + } + if (field_summary$highest_weed_risk == "High") { + field_alerts <- c(field_alerts, "⚠️ High weed presence detected") + } + if (field_summary$max_gap_score > 20) { + field_alerts <- c(field_alerts, "💡 Significant gaps detected - monitor closely") + } + + # Only add alerts if there are any (skip fields with no alerts) + if (length(field_alerts) > 0) { + # Add to alerts list + for (alert in field_alerts) { + alerts_list[[length(alerts_list) + 1]] <- data.frame( + Field = field_name, + Alert = alert + ) + } + } + } + + # Combine all alerts + if (length(alerts_list) > 0) { + alerts_df <- do.call(rbind, alerts_list) + return(alerts_df) + } else { + return(data.frame(Field = character(), Alert = character())) + } +} + +# Generate and display alerts table +if (exists("field_details_table") && !is.null(field_details_table)) { + alerts_data <- generate_field_alerts(field_details_table) + if (nrow(alerts_data) > 0) { + ft <- flextable(alerts_data) %>% +# set_caption("Field Alerts Summary") %>% + autofit() + ft + } else { + cat("No alerts data available.\n") + } +} else { + cat("Field details data not available for alerts generation.\n") +} +``` + +```{r data, message=TRUE, warning=TRUE, include=FALSE} +# Load CI index data with error handling +tryCatch({ + CI_quadrant <- readRDS(here::here(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds")) + safe_log("Successfully loaded CI quadrant data") +}, error = function(e) { + stop("Error loading CI quadrant data: ", e$message) +}) + +# Get file paths for different weeks using the utility function +tryCatch({ + path_to_week_current = get_week_path(weekly_CI_mosaic, today, 0) + path_to_week_minus_1 = get_week_path(weekly_CI_mosaic, today, -1) + path_to_week_minus_2 = get_week_path(weekly_CI_mosaic, today, -2) + path_to_week_minus_3 = get_week_path(weekly_CI_mosaic, today, -3) + + # Log the calculated paths + safe_log("Required mosaic paths:") + safe_log(paste("Path to current week:", path_to_week_current)) + safe_log(paste("Path to week minus 1:", path_to_week_minus_1)) + safe_log(paste("Path to week minus 2:", path_to_week_minus_2)) + safe_log(paste("Path to week minus 3:", path_to_week_minus_3)) + + # Validate that files exist + if (!file.exists(path_to_week_current)) warning("Current week mosaic file does not exist: ", path_to_week_current) + if (!file.exists(path_to_week_minus_1)) warning("Week minus 1 mosaic file does not exist: ", path_to_week_minus_1) + if (!file.exists(path_to_week_minus_2)) warning("Week minus 2 mosaic file does not exist: ", path_to_week_minus_2) + if (!file.exists(path_to_week_minus_3)) warning("Week minus 3 mosaic file does not exist: ", path_to_week_minus_3) + + # Load raster data with terra functions + CI <- terra::rast(path_to_week_current)$CI + CI_m1 <- terra::rast(path_to_week_minus_1)$CI + CI_m2 <- terra::rast(path_to_week_minus_2)$CI + CI_m3 <- terra::rast(path_to_week_minus_3)$CI + +}, error = function(e) { + stop("Error loading raster data: ", e$message) +}) +``` + +```{r calculate_difference_rasters, message=TRUE, warning=TRUE, include=FALSE} +# Calculate difference rasters for comparisons +# When one week has NA values, the difference will also be NA (not zero) +# Initialize placeholders first to ensure they exist +last_week_dif_raster_abs <- NULL +three_week_dif_raster_abs <- NULL + +tryCatch({ + # Always calculate differences - NA values will propagate naturally + # This way empty weeks (all NA) result in NA differences, not misleading zeros + last_week_dif_raster_abs <- (CI - CI_m1) + three_week_dif_raster_abs <- (CI - CI_m3) + + safe_log("Calculated difference rasters (NA values preserved)") + +}, error = function(e) { + safe_log(paste("Error calculating difference rasters:", e$message), "ERROR") + # Fallback: create NA placeholders if calculation fails + if (is.null(last_week_dif_raster_abs)) { + last_week_dif_raster_abs <- CI * NA + } + if (is.null(three_week_dif_raster_abs)) { + three_week_dif_raster_abs <- CI * NA + } +}) + +# Final safety check - ensure variables exist in global environment +if (is.null(last_week_dif_raster_abs)) { + last_week_dif_raster_abs <- CI * NA + safe_log("Created NA placeholder for last_week_dif_raster_abs", "WARNING") +} +if (is.null(three_week_dif_raster_abs)) { + three_week_dif_raster_abs <- CI * NA + safe_log("Created NA placeholder for three_week_dif_raster_abs", "WARNING") +} +``` + +```{r load_field_boundaries, message=TRUE, warning=TRUE, include=FALSE} +# Load field boundaries from parameters +tryCatch({ + AllPivots0 <- field_boundaries_sf %>% + dplyr::filter(!is.na(field), !is.na(sub_field)) # Filter out NA field names + safe_log("Successfully loaded field boundaries") + + # Prepare merged field list for use in summaries + AllPivots_merged <- AllPivots0 %>% + dplyr::filter(!is.na(field), !is.na(sub_field)) %>% # Filter out NA field names + dplyr::group_by(field) %>% + dplyr::summarise(.groups = 'drop') + +}, error = function(e) { + stop("Error loading field boundaries: ", e$message) +}) +``` +\newpage + +## Chlorophyll Index (CI) Overview Map - Current Week +```{r render_ci_overview_map, echo=FALSE, fig.height=7, fig.width=10, dpi=300, dev='png', message=FALSE, warning=FALSE} +# Create overview chlorophyll index map +tryCatch({ + # Choose palette based on colorblind_friendly parameter + ci_palette <- if (colorblind_friendly) "viridis" else "brewer.rd_yl_gn" + + # Base shape + map <- tmap::tm_shape(CI, unit = "m") + + # Add raster layer with continuous spectrum (fixed scale 1-8 for consistent comparison) + map <- map + tmap::tm_raster(col.scale = tm_scale_continuous(values = ci_palette, + limits = c(1, 8)), + col.legend = tm_legend(title = "Chlorophyll Index (CI)", + orientation = "landscape", + position = tm_pos_out("center", "bottom"))) + # Complete the map with layout and other elements + map <- map + + tmap::tm_scalebar(position = tm_pos_out("right", "bottom"), text.color = "black") + + tmap::tm_compass(position = tm_pos_out("right", "bottom"), text.color = "black") + + tmap::tm_shape(AllPivots0) + + tmap::tm_borders(col = "black") + + tmap::tm_text("sub_field", size = 0.6, col = "black") + + # Print the map + print(map) +}, error = function(e) { + safe_log(paste("Error creating CI overview map:", e$message), "ERROR") + plot(1, type="n", axes=FALSE, xlab="", ylab="") + text(1, 1, "Error creating CI overview map", cex=1.5) +}) + +``` +\newpage + +## Weekly Chlorophyll Index Difference Map +```{r render_ci_difference_map, echo=FALSE, fig.height=7, fig.width=10, dpi=300, dev='png', message=FALSE, warning=FALSE} +# Create chlorophyll index difference map +tryCatch({ + # Choose palette based on colorblind_friendly parameter + diff_palette <- if (colorblind_friendly) "plasma" else "brewer.rd_yl_gn" + + # Base shape + map <- tmap::tm_shape(last_week_dif_raster_abs, unit = "m") + + # Add raster layer with continuous spectrum (centered at 0 for difference maps, fixed scale) + map <- map + tmap::tm_raster(col.scale = tm_scale_continuous(values = diff_palette, + midpoint = 0, + limits = c(-3, 3)), + col.legend = tm_legend(title = "Chlorophyll Index (CI) Change", + orientation = "landscape", + position = tm_pos_out("center", "bottom"))) + # Complete the map with layout and other elements + map <- map + + tmap::tm_scalebar(position = tm_pos_out("right", "bottom"), text.color = "black") + + tmap::tm_compass(position = tm_pos_out("right", "bottom"), text.color = "black") + + tmap::tm_shape(AllPivots0) + + tmap::tm_borders(col = "black") + + tmap::tm_text("sub_field", size = 0.6, col = "black") + + # Print the map + print(map) +}, error = function(e) { + safe_log(paste("Error creating CI difference map:", e$message), "ERROR") + plot(1, type="n", axes=FALSE, xlab="", ylab="") + text(1, 1, "Error creating CI difference map", cex=1.5) +}) +``` + +# Section 2: Field-by-Field Analysis + +## Overview of Field-Level Insights +This section provides detailed, field-specific analyses including chlorophyll index maps, trend graphs, and performance metrics. Each field is analyzed individually to support targeted interventions. + +**Key Elements per Field:** +- Current and historical CI maps +- Week-over-week change visualizations +- Cumulative growth trends +- Field-specific KPI summaries + +*Navigate to the following pages for individual field reports.* + +\newpage + +```{r generate_field_visualizations, eval=TRUE, fig.height=3.8, fig.width=10, dpi=300, dev='png', message=FALSE,echo=FALSE, warning=FALSE, include=TRUE, results='asis'} +# Generate detailed visualizations for each field +tryCatch({ + # Merge field polygons for processing and filter out NA field names + AllPivots_merged <- AllPivots0 %>% + dplyr::filter(!is.na(field), !is.na(sub_field)) %>% # Filter out NA fields + dplyr::group_by(field) %>% + dplyr::summarise(.groups = 'drop') + + # Generate plots for each field + for(i in seq_along(AllPivots_merged$field)) { + field_name <- AllPivots_merged$field[i] + + # Skip if field_name is still NA (double check) + if(is.na(field_name)) { + next + } + + tryCatch({ + # Add page break before each field (except the first one) + if(i > 1) { + cat("\\newpage\n\n") + } + + # Call ci_plot with explicit parameters (ci_plot will generate its own header) + ci_plot( + pivotName = field_name, + field_boundaries = AllPivots0, + current_ci = CI, + ci_minus_1 = CI_m1, + ci_minus_2 = CI_m2, + last_week_diff = last_week_dif_raster_abs, + three_week_diff = three_week_dif_raster_abs, + harvesting_data = harvesting_data, + week = week, + week_minus_1 = week_minus_1, + week_minus_2 = week_minus_2, + week_minus_3 = week_minus_3, + borders = borders, + colorblind_friendly = colorblind_friendly + ) + + cat("\n\n") + + # Special handling for ESA project field 00f25 - remove duplicate DOY values + if (project_dir == "esa" && field_name == "00F25") { + ci_quadrant_data <- CI_quadrant %>% + filter(field == "00F25") %>% + arrange(DOY) %>% + group_by(DOY) %>% + slice(1) %>% + ungroup() + } else { + ci_quadrant_data <- CI_quadrant + } + + # Call cum_ci_plot with explicit parameters + cum_ci_plot( + pivotName = field_name, + ci_quadrant_data = ci_quadrant_data, + plot_type = ci_plot_type, + facet_on = facet_by_season, + x_unit = x_axis_unit, + colorblind_friendly = colorblind_friendly, + show_benchmarks = TRUE, + estate_name = project_dir, + benchmark_percentiles = c(10, 50, 90), + benchmark_data = benchmarks + ) + + cat("\n\n") + +# Add field-specific KPI summary under the graphs + if (exists("field_details_table") && !is.null(field_details_table)) { + kpi_summary <- generate_field_kpi_summary(field_name, field_details_table, CI_quadrant) + cat(kpi_summary) + cat("\n\n") + } + + }, error = function(e) { + safe_log(paste("Error generating plots for field", field_name, ":", e$message), "ERROR") + cat("\\newpage\n\n") + cat("# Error generating plots for field ", field_name, "\n\n") + cat(e$message, "\n\n") + }) + } +}, error = function(e) { + safe_log(paste("Error in field visualization section:", e$message), "ERROR") + cat("Error generating field plots. See log for details.\n\n") +}) +``` + +```{r generate_subarea_visualizations, echo=FALSE, fig.height=3.8, fig.width=10, message=FALSE, warning=FALSE, results='asis', eval=FALSE} +# Alternative visualization grouped by sub-area (disabled by default) +tryCatch({ + # Group pivots by sub-area + pivots_grouped <- AllPivots0 + + # Iterate over each subgroup + for (subgroup in unique(pivots_grouped$sub_area)) { + # Add subgroup heading + cat("\n") + cat("## Subgroup: ", subgroup, "\n") + + # Filter data for current subgroup + subset_data <- dplyr::filter(pivots_grouped, sub_area == subgroup) + + # Generate visualizations for each field in the subgroup + purrr::walk(subset_data$field, function(field_name) { + cat("\n") + ci_plot(field_name) + cat("\n") + cum_ci_plot(field_name) + cat("\n") + }) + + # Add page break after each subgroup + cat("\\newpage\n") + } +}, error = function(e) { + safe_log(paste("Error in subarea visualization section:", e$message), "ERROR") + cat("Error generating subarea plots. See log for details.\n") +}) +``` + +## KPI Summary by Field + +## Detailed Field Performance Summary + +The following table provides a comprehensive overview of all monitored fields with their key performance metrics from the KPI analysis. + +```{r detailed_field_table, echo=FALSE} +# Load CI quadrant data to get field ages +CI_quadrant <- readRDS(here::here(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds")) + +# Identify the current season for each field based on report_date +# The current season is the one where the report_date falls within or shortly after the season +report_date_obj <- as.Date(report_date) + +current_seasons <- CI_quadrant %>% + filter(Date <= report_date_obj) %>% + group_by(field, season) %>% + summarise( + season_start = min(Date), + season_end = max(Date), + .groups = 'drop' + ) %>% + group_by(field) %>% + filter(season == max(season)) %>% # Take the most recent season + select(field, season) + +# Get current field ages (most recent DOY for each field in their CURRENT SEASON only) +field_ages <- CI_quadrant %>% + inner_join(current_seasons, by = c("field", "season")) %>% # Filter to current season only + group_by(field) %>% + filter(DOY == max(DOY)) %>% + select(field, DOY) %>% + rename(Field = field, Age_days = DOY) + +# Clean up the field details table - remove sub field column and round numeric values +field_details_clean <- field_details_table %>% + left_join(field_ages, by = "Field") %>% + mutate( + `Yield Forecast (t/ha)` = ifelse(is.na(Age_days) | Age_days < 240, NA_real_, `Yield Forecast (t/ha)`) + ) %>% + select(Field, `Field Size (ha)`, `Growth Uniformity`, `Yield Forecast (t/ha)`, `Gap Score`, `Decline Risk`, `Weed Risk`, `Mean CI`, `CV Value`) %>% # Reorder columns as requested + mutate( + `Mean CI` = round(`Mean CI`, 2), # Round to 2 decimal places + `CV Value` = round(`CV Value`, 2), # Round to 2 decimal places + `Gap Score` = round(`Gap Score`, 0) # Round to nearest integer + ) + + +# Display the cleaned field table with flextable +ft <- flextable(field_details_clean) %>% + set_caption("Detailed Field Performance Summary") %>% + autofit() + +ft +``` + +\newpage + +# Section 3: Report Methodology and Definitions + +## About This Report + +This automated report provides weekly analysis of sugarcane crop health using satellite-derived Chlorophyll Index (CI) measurements. The analysis supports: + +• Scouting of growth related issues that are in need of attention +• Timely actions can be taken such that negative impact is reduced +• Monitoring of the crop growth rates of the farm, providing evidence of performance +• Planning of harvest moment and mill logistics is supported such that optimal tonnage and sucrose levels can be harvested. + +The base of the report is the Chlorophyll Index. The chlorophyll index identifies: +• Field-level crop health variations => target problem area's +• Weekly changes in crop vigor => scout for diseases and stress +• Areas requiring attention by the agricultural and irrigation teams +• Growth patterns across different field sections + +Key Features: - High-resolution satellite imagery analysis - Week-over-week change detection - Individual field performance metrics - Actionable insights for crop management + +### Explanation of the Report + +This report provides a detailed analysis (3x3m of resolution) of your sugarcane fields based on satellite imagery. It supports you monitor crop health and development throughout the growing season. The data is processed weekly to give you timely insights for optimal farm management decisions. + +### What is the Chlorophyll Index (CI)? + +The Chlorophyll Index (CI) is a vegetation index that measures the relative amount of chlorophyll in plant leaves. Chlorophyll is the green pigment responsible for photosynthesis in plants. Higher CI values indicate: +• Greater photosynthetic activity +• Healthier plant tissue +• Better nitrogen uptake +• More vigorous crop growth + +CI values typically range from 0 (bare soil or severely stressed vegetation) to 7+ (very healthy, dense vegetation). For sugarcane, values between 3-7 generally indicate good crop health, depending on the growth stage. + +![Chlorophyll Index Example](CI_graph_example.png) + + +### What You'll Find in This Report: + +1. **Key Performance Indicators (KPIs):** + The report provides a farm-wide analysis based on the Chlorophyll Index (CI) changes. KPIs are calculated field by field and summarized in tables. The current KPIs included are: + + - **Field Uniformity:** Assesses the consistency of crop growth within each field using the coefficient of variation (CV) of CI values. Uniformity levels are classified as: + - **Excellent:** CV < 0.08 (very uniform growth) + - **Good:** CV < 0.15 (acceptable uniformity) + - **Moderate:** CV < 0.25 (some variation present) + - **Poor:** CV ≥ 0.25 (high variation - investigate irrigation, fertility, or pests) + + - **Area Change:** Summarizes the proportion of field area that is improving, stable, or declining week-over-week, based on CI changes. Helps identify fields requiring immediate attention. + + - **TCH Forecasted:** Provides yield predictions (tons cane per hectare) for mature fields (typically over 240 days old), using a machine learning model trained on historical CI and yield data. Helps plan harvest timing and logistics. + + - **Weed Presence Score:** Detects rapid CI increases between weeks as a proxy for weed outbreaks in young fields (< 8 months old). After 8 months, canopy closure prevents weed growth. Risk levels based on percentage of pixels showing rapid growth (> 2.0 CI units increase): + - **Low:** < 10% of field area (minimal weed presence) + - **Moderate:** 10–25% (monitor and scout) + - **High:** > 25% (requires immediate intervention) + - **Note:** Mature fields (≥ 8 months) show "Canopy closed - Low weed risk" as the closed canopy suppresses weed growth. + + - **Gap Filling Score:** Indicates the proportion of a field with low CI values (lowest 25% of the distribution), highlighting areas with poor crop establishment or gaps that may need replanting. + +2. **Overview Map: Growth on Farm:** + Provides a traffic light overview of field-by-field growth status for quick prioritization and reporting. + +3. **Chlorophyll Index Overview Map:** + Shows current CI values for all fields, helping to identify high- and low-performing areas. + +4. **Field-by-Field Analysis:** + Includes detailed maps, trend graphs, and performance metrics for each field. + +5. **Yield Prediction:** + For mature crops (over 240 days), yield is predicted using current and historical CI data. + +6. **Farm Overview Table:** + Presents numerical field-level results for all KPIs. + +--- + + +### Historical Benchmark Lines + +The CI time series graphs include historical benchmark lines for the 10th, 50th, and 90th percentiles of CI values across all fields and seasons. +**Note:** These lines are now all rendered as solid lines (not dashed or dotted), with different colors for each percentile. +- **10th Percentile:** Lower end of historical performance +- **50th Percentile:** Median historical performance +- **90th Percentile:** Upper end of historical performance +Comparing the current season to these lines helps assess whether crop growth is below, at, or above historical norms. + +---https://code.earthengine.google.com/05cd5f1675fab512a4a73ebe6ea5f1ea + + + +\newpage +## Report Metadata + +```{r report_metadata, echo=FALSE} +metadata_info <- data.frame( + Metric = c("Report Generated", "Data Source", "Analysis Period", "Total Fields", "Next Update"), + Value = c( + format(Sys.time(), "%Y-%m-%d %H:%M:%S"), + paste("Project", toupper(project_dir)), + paste("Week", current_week, "of", year), + ifelse(exists("AllPivots0"), nrow(AllPivots0 %>% filter(!is.na(field)) %>% group_by(field) %>% summarise()), "Unknown"), + "Next Wednesday" + ) +) + +ft <- flextable(metadata_info) %>% + set_caption("Report Metadata") %>% + autofit() + +ft +``` + +*This report was automatically generated by the SmartCane monitoring system. For questions or additional analysis, please contact the technical team.* +``` + diff --git a/r_app/CI_graph_example.png b/r_app/CI_graph_example.png new file mode 100644 index 0000000000000000000000000000000000000000..675226e8db79c30b25095553e29b248fc41e454d GIT binary patch literal 358660 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ℜRfNEA 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 - -
- -## Report Summary - -**Farm Location:** `r farm_name` -**Report Period:** Week `r week` of `r current_year` -**Data Source:** Planet Labs Satellite Imagery -**Analysis Type:** Chlorophyll Index (CI) Monitoring - -**Field Coverage:** -- Total Fields Monitored: `r total_fields` -- Total Area: `r total_area_ha` hectares - -**Report Generated:** `r format(Sys.Date(), "%B %d, %Y")` - ---- - -## About This Report - -This automated report provides weekly analysis of sugarcane crop health using satellite-derived Chlorophyll Index (CI) measurements. The analysis helps identify: - -- Field-level crop health variations -- Weekly changes in crop vigor -- Areas requiring agricultural attention -- Growth patterns across different field sections - -**Key Features:** -- High-resolution satellite imagery analysis -- Week-over-week change detection -- Individual field performance metrics -- Actionable insights for crop management - -
- -\newpage - - -```{=openxml} - - - - - - - - - - - TABLE OF CONTENTS - - -``` - -```{=openxml} - - - Update this field to generate table of contents - - -``` - -\newpage - - - - - -# Explanation of the Report - -This report provides a detailed analysis of your sugarcane fields based on satellite imagery, helping you monitor crop health and development throughout the growing season. The data is processed weekly to give you timely insights for optimal farm management decisions. - -## What is the Chlorophyll Index (CI)? - -The **Chlorophyll Index (CI)** is a vegetation index that measures the relative amount of chlorophyll in plant leaves. Chlorophyll is the green pigment responsible for photosynthesis in plants. Higher CI values indicate: - -* Greater photosynthetic activity -* Healthier plant tissue -* Better nitrogen uptake -* More vigorous crop growth - -CI values typically range from 0 (bare soil or severely stressed vegetation) to 7+ (very healthy, dense vegetation). For sugarcane, values between 3-7 generally indicate good crop health, depending on the growth stage. - -## What You'll Find in This Report: - -1. **Chlorophyll Index Overview Map**: A comprehensive view of all your fields showing current CI values. This helps identify which fields are performing well and which might need attention. - -2. **Weekly Difference Map**: Shows changes in CI values over the past week. Positive values (green) indicate improving crop health, while negative values (red) may signal stress or decline. - -3. **Field-by-Field Analysis**: Detailed maps for each field showing: - * CI values for the current week and two previous weeks - * Week-to-week changes in CI values - * Three-week change in CI values to track longer-term trends - -4. **Growth Trend Graphs**: Time-series visualizations showing how CI values have changed throughout the growing season for each section of your fields. - -5. **Yield Prediction**: For mature crops (over 300 days), we provide estimated yield predictions based on historical data and current CI measurements. - -Use these insights to identify areas that may need irrigation, fertilization, or other interventions, and to track the effectiveness of your management practices over time. - -\newpage - -# RGB Satellite Image - Current Week (if available) -```{r render_rgb_map, echo=FALSE, fig.height=7, fig.width=10, message=FALSE, warning=FALSE} -# Check if RGB bands are available and create RGB map -tryCatch({ - # Load the full raster to check available bands - full_raster <- terra::rast(path_to_week_current) - available_bands <- names(full_raster) - - # Check if RGB bands are available (look for red, green, blue or similar naming) - rgb_bands_available <- any(grepl("red|Red|RED", available_bands, ignore.case = TRUE)) && - any(grepl("green|Green|GREEN", available_bands, ignore.case = TRUE)) && - any(grepl("blue|Blue|BLUE", available_bands, ignore.case = TRUE)) - - # Alternative check for numbered bands that might be RGB (e.g., band_1, band_2, band_3) - if (!rgb_bands_available && length(available_bands) >= 3) { - # Check if we have at least 3 bands that could potentially be RGB - potential_rgb_bands <- grep("band_[1-3]|B[1-3]|[1-3]", available_bands, ignore.case = TRUE) - rgb_bands_available <- length(potential_rgb_bands) >= 3 - } - - if (rgb_bands_available) { - safe_log("RGB bands detected - creating RGB visualization") - - # Try to extract RGB bands (prioritize named bands first) - red_band <- NULL - green_band <- NULL - blue_band <- NULL - - # Look for named RGB bands first - red_candidates <- grep("red|Red|RED", available_bands, ignore.case = TRUE, value = TRUE) - green_candidates <- grep("green|Green|GREEN", available_bands, ignore.case = TRUE, value = TRUE) - blue_candidates <- grep("blue|Blue|BLUE", available_bands, ignore.case = TRUE, value = TRUE) - - if (length(red_candidates) > 0) red_band <- red_candidates[1] - if (length(green_candidates) > 0) green_band <- green_candidates[1] - if (length(blue_candidates) > 0) blue_band <- blue_candidates[1] - - # Fallback to numbered bands if named bands not found - if (is.null(red_band) || is.null(green_band) || is.null(blue_band)) { - if (length(available_bands) >= 3) { - # Assume first 3 bands are RGB (common convention) - red_band <- available_bands[1] - green_band <- available_bands[2] - blue_band <- available_bands[3] - } - } - - if (!is.null(red_band) && !is.null(green_band) && !is.null(blue_band)) { - # Extract RGB bands - rgb_raster <- c(full_raster[[red_band]], full_raster[[green_band]], full_raster[[blue_band]]) - names(rgb_raster) <- c("red", "green", "blue") - # Create RGB map - map <- tmap::tm_shape(rgb_raster, unit = "m") + - tmap::tm_rgb() + - tmap::tm_scalebar(position = tm_pos_out("right", "bottom"), text.color = "black") + - tmap::tm_compass(position = tm_pos_out("right", "bottom"), text.color = "black") + - tmap::tm_shape(AllPivots0) + - tmap::tm_borders(col = "white", lwd = 2) + - tmap::tm_text("sub_field", size = 0.6, col = "white") + - tmap::tm_layout(main.title = paste0("RGB Satellite Image - Week ", week), - main.title.size = 0.8, - main.title.color = "black") - - # Print the map - print(map) - - safe_log("RGB map created successfully") - } else { - safe_log("Could not identify RGB bands despite detection", "WARNING") - cat("RGB bands detected but could not be properly identified. Skipping RGB visualization.\n") - } - } else { - safe_log("No RGB bands available in the current week mosaic") - cat("**Note:** RGB satellite imagery is not available for this week. Only spectral index data is available.\n\n") - } -}, error = function(e) { - safe_log(paste("Error creating RGB map:", e$message), "ERROR") - cat("**Note:** Could not create RGB visualization for this week.\n\n") -}) -``` - -# Chlorophyll Index (CI) Overview Map - Current Week -```{r render_ci_overview_map, echo=FALSE, fig.height=7, fig.width=10, message=FALSE, warning=FALSE} -# Create overview chlorophyll index map -tryCatch({ # Base shape - map <- tmap::tm_shape(CI, unit = "m") # Add raster layer with continuous spectrum (fixed scale 1-8 for consistent comparison) - map <- map + tmap::tm_raster(col.scale = tm_scale_continuous(values = "brewer.rd_yl_gn", - limits = c(1, 8)), col.legend = tm_legend(title = "Chlorophyll Index (CI)", - orientation = "landscape", - position = tm_pos_out("center", "bottom"))) - # Complete the map with layout and other elements - map <- map + - tmap::tm_scalebar(position = tm_pos_out("right", "bottom"), text.color = "black") + - tmap::tm_compass(position = tm_pos_out("right", "bottom"), text.color = "black") + - tmap::tm_shape(AllPivots0) + - tmap::tm_borders(col = "black") + - tmap::tm_text("sub_field", size = 0.6, col = "black") - - # Print the map - print(map) -}, error = function(e) { - safe_log(paste("Error creating CI overview map:", e$message), "ERROR") - plot(1, type="n", axes=FALSE, xlab="", ylab="") - text(1, 1, "Error creating CI overview map", cex=1.5) -}) - -``` - -# Weekly Chlorophyll Index Difference Map -```{r render_ci_difference_map, echo=FALSE, fig.height=7, fig.width=10, message=FALSE, warning=FALSE} -# Create chlorophyll index difference map -tryCatch({ # Base shape - map <- tmap::tm_shape(last_week_dif_raster_abs, unit = "m") # Add raster layer with continuous spectrum (centered at 0 for difference maps, fixed scale) - map <- map + tmap::tm_raster(col.scale = tm_scale_continuous(values = "brewer.rd_yl_gn", - midpoint = 0, - limits = c(-3, 3)), col.legend = tm_legend(title = "Chlorophyll Index (CI) Change", - orientation = "landscape", - position = tm_pos_out("center", "bottom"))) - # Complete the map with layout and other elements - map <- map + - tmap::tm_scalebar(position = tm_pos_out("right", "bottom"), text.color = "black") + - tmap::tm_compass(position = tm_pos_out("right", "bottom"), text.color = "black") + - tmap::tm_shape(AllPivots0) + - tmap::tm_borders(col = "black") + - tmap::tm_text("sub_field", size = 0.6, col = "black") - - # Print the map - print(map) -}, error = function(e) { - safe_log(paste("Error creating CI difference map:", e$message), "ERROR") - plot(1, type="n", axes=FALSE, xlab="", ylab="") - text(1, 1, "Error creating CI difference map", cex=1.5) -}) -``` - -\newpage - -```{r generate_field_visualizations, eval=TRUE, fig.height=3.8, fig.width=10, message=FALSE,echo=FALSE, warning=FALSE, include=TRUE, results='asis'} -# Generate detailed visualizations for each field -tryCatch({ - # Merge field polygons for processing and filter out NA field names - AllPivots_merged <- AllPivots0 %>% - dplyr::filter(!is.na(field), !is.na(sub_field)) %>% # Filter out NA fields - dplyr::group_by(field) %>% - dplyr::summarise(.groups = 'drop') - - # Generate plots for each field - for(i in seq_along(AllPivots_merged$field)) { - field_name <- AllPivots_merged$field[i] - - # Skip if field_name is still NA (double check) - if(is.na(field_name)) { - next - } - - tryCatch({ - # Add page break before each field (except the first one) - if(i > 1) { - cat("\\newpage\n\n") - } - - # Call ci_plot with explicit parameters (ci_plot will generate its own header) - ci_plot( - pivotName = field_name, - field_boundaries = AllPivots0, - current_ci = CI, - ci_minus_1 = CI_m1, - ci_minus_2 = CI_m2, - last_week_diff = last_week_dif_raster_abs, - three_week_diff = three_week_dif_raster_abs, - harvesting_data = harvesting_data, - week = week, - week_minus_1 = week_minus_1, - week_minus_2 = week_minus_2, - week_minus_3 = week_minus_3, - borders = borders - ) - - cat("\n\n") - - # Call cum_ci_plot with explicit parameters - cum_ci_plot( - pivotName = field_name, - ci_quadrant_data = CI_quadrant, - plot_type = "value", - facet_on = FALSE - ) - - cat("\n\n") - - }, error = function(e) { - safe_log(paste("Error generating plots for field", field_name, ":", e$message), "ERROR") - cat("\\newpage\n\n") - cat("# Error generating plots for field ", field_name, "\n\n") - cat(e$message, "\n\n") - }) - } -}, error = function(e) { - safe_log(paste("Error in field visualization section:", e$message), "ERROR") - cat("Error generating field plots. See log for details.\n\n") -}) -``` - -```{r generate_subarea_visualizations, echo=FALSE, fig.height=3.8, fig.width=10, message=FALSE, warning=FALSE, results='asis', eval=FALSE} -# Alternative visualization grouped by sub-area (disabled by default) -tryCatch({ - # Group pivots by sub-area - pivots_grouped <- AllPivots0 - - # Iterate over each subgroup - for (subgroup in unique(pivots_grouped$sub_area)) { - # Add subgroup heading - cat("\n") - cat("## Subgroup: ", subgroup, "\n") - - # Filter data for current subgroup - subset_data <- dplyr::filter(pivots_grouped, sub_area == subgroup) - - # Generate visualizations for each field in the subgroup - purrr::walk(subset_data$field, function(field_name) { - cat("\n") - ci_plot(field_name) - cat("\n") - cum_ci_plot(field_name) - cat("\n") - }) - - # Add page break after each subgroup - cat("\\newpage\n") - } -}, error = function(e) { - safe_log(paste("Error in subarea visualization section:", e$message), "ERROR") - cat("Error generating subarea plots. See log for details.\n") -}) -``` - -# Yield prediction -The below table shows estimates of the biomass if you would harvest them now. - -```{r yield_data_training, message=FALSE, warning=FALSE, include=FALSE} -# Load and prepare yield prediction data with error handling -tryCatch({ - # Load CI quadrant data and fill missing values - CI_quadrant <- readRDS(here::here(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds")) %>% - dplyr::group_by(model) %>% - tidyr::fill(field, sub_field, .direction = "downup") %>% - dplyr::ungroup() - - # Check if tonnage_ha is empty - if (all(is.na(harvesting_data$tonnage_ha))) { - safe_log("Lacking historic harvest data, please provide for yield prediction calculation", "WARNING") - knitr::knit_exit() # Exit the chunk if tonnage_ha is empty - } - - # Rename year column to season for consistency - harvesting_data <- harvesting_data %>% dplyr::rename(season = year) - - # Join CI and yield data - CI_and_yield <- dplyr::left_join(CI_quadrant, harvesting_data, by = c("field", "sub_field", "season")) %>% - dplyr::group_by(sub_field, season) %>% - dplyr::slice(which.max(DOY)) %>% - dplyr::select(field, sub_field, tonnage_ha, cumulative_CI, DOY, season, sub_area) %>% - dplyr::mutate(CI_per_day = cumulative_CI / DOY) - - # Define predictors and response variables - predictors <- c("cumulative_CI", "DOY", "CI_per_day") - response <- "tonnage_ha" - - # Prepare test and validation datasets - CI_and_yield_test <- CI_and_yield %>% - as.data.frame() %>% - dplyr::filter(!is.na(tonnage_ha)) - - CI_and_yield_validation <- CI_and_yield_test - - # Prepare prediction dataset (fields without harvest data) - prediction_yields <- CI_and_yield %>% - as.data.frame() %>% - dplyr::filter(is.na(tonnage_ha)) %>% - dplyr::filter(age > 300) # Only predict on fields older than 300 days - - # Configure model training parameters - ctrl <- caret::trainControl( - method = "cv", - savePredictions = TRUE, - allowParallel = TRUE, - number = 5, - verboseIter = TRUE - ) - - # Train the model with feature selection - set.seed(202) # For reproducibility - model_ffs_rf <- CAST::ffs( - CI_and_yield_test[, predictors], - CI_and_yield_test[, response], - method = "rf", - trControl = ctrl, - importance = TRUE, - withinSE = TRUE, - tuneLength = 5, - na.rm = TRUE - ) - - # Function to prepare predictions with consistent naming and formatting - prepare_predictions <- function(predictions, newdata) { - return(predictions %>% - as.data.frame() %>% - dplyr::rename(predicted_Tcha = ".") %>% - dplyr::mutate( - sub_field = newdata$sub_field, - field = newdata$field, - Age_days = newdata$DOY, - total_CI = round(newdata$cumulative_CI, 0), - predicted_Tcha = round(predicted_Tcha, 0), - season = newdata$season - ) %>% - dplyr::select(field, sub_field, Age_days, total_CI, predicted_Tcha, season) %>% - dplyr::left_join(., newdata, by = c("field", "sub_field", "season")) - ) - } - - # Predict yields for the validation dataset - pred_ffs_rf <- prepare_predictions(stats::predict(model_ffs_rf, newdata = CI_and_yield_validation), CI_and_yield_validation) - - # Predict yields for the current season (focus on mature fields over 300 days) - pred_rf_current_season <- prepare_predictions(stats::predict(model_ffs_rf, newdata = prediction_yields), prediction_yields) %>% - dplyr::filter(Age_days > 1) %>% - dplyr::mutate(CI_per_day = round(total_CI / Age_days, 1)) - - safe_log("Successfully completed yield prediction calculations") - -}, error = function(e) { - safe_log(paste("Error in yield prediction:", e$message), "ERROR") - # Create empty dataframes to prevent errors in subsequent chunks - pred_ffs_rf <- data.frame() - pred_rf_current_season <- data.frame() -}) -``` - -```{r plotting_yield_data, echo=FALSE, fig.height=5, fig.width=8, message=FALSE, warning=FALSE} -# Display yield prediction visualizations with error handling -tryCatch({ - if (nrow(pred_ffs_rf) > 0) { - # Plot model performance (predicted vs actual) - ggplot2::ggplot(pred_ffs_rf, ggplot2::aes(y = predicted_Tcha, x = tonnage_ha)) + - ggplot2::geom_point(size = 2, alpha = 0.6) + - ggplot2::geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "red") + - ggplot2::scale_x_continuous(limits = c(0, 200)) + - ggplot2::scale_y_continuous(limits = c(0, 200)) + - ggplot2::labs(title = "Model Performance: \nPredicted vs Actual Tonnage/ha", - x = "Actual tonnage/ha (Tcha)", - y = "Predicted tonnage/ha (Tcha)") + - ggplot2::theme_minimal() - } - - if (nrow(pred_rf_current_season) > 0) { - # Plot predicted yields by age - ggplot2::ggplot(pred_rf_current_season, ggplot2::aes(x = Age_days, y = predicted_Tcha)) + - ggplot2::geom_point(size = 2, alpha = 0.6) + - ggplot2::labs(title = "Predicted Yields for Fields Over 300 Days \nOld Yet to Be Harvested", - x = "Age (days)", - y = "Predicted tonnage/ha (Tcha)") + - ggplot2::scale_y_continuous(limits = c(0, 200)) + - ggplot2::theme_minimal() - - # Display prediction table - knitr::kable(pred_rf_current_season, - digits = 0, - caption = "Predicted Tonnage/ha for Fields Over 300 Days Old") - } else { - cat("No fields over 300 days old without harvest data available for yield prediction.") - } -}, error = function(e) { - safe_log(paste("Error in yield prediction visualization:", e$message), "ERROR") - cat("Error generating yield prediction visualizations. See log for details.") -}) -``` - diff --git a/r_app/CI_report_dashboard_planet_enhanced.Rmd b/r_app/CI_report_dashboard_planet_enhanced.Rmd deleted file mode 100644 index 489b4be..0000000 --- a/r_app/CI_report_dashboard_planet_enhanced.Rmd +++ /dev/null @@ -1,1145 +0,0 @@ ---- -params: - ref: "word-styles-reference-var1.docx" - output_file: CI_report.docx - report_date: "2024-08-28" - data_dir: "Chemba" - mail_day: "Wednesday" - borders: TRUE - use_breaks: FALSE -output: - # html_document: - # toc: yes - # df_print: paged - word_document: - reference_docx: !expr file.path("word-styles-reference-var1.docx") - toc: yes -editor_options: - chunk_output_type: console ---- - -```{r setup_parameters, include=FALSE} -# Set up basic report parameters from input values -report_date <- params$report_date -mail_day <- params$mail_day -borders <- params$borders -use_breaks <- params$use_breaks # Whether to use breaks or continuous spectrum in visualizations - -# Environment setup notes (commented out) -# # Activeer de renv omgeving -# renv::activate() -# renv::deactivate() -# # Optioneel: Herstel de omgeving als dat nodig is -# # Je kunt dit commentaar geven als je het normaal niet wilt uitvoeren -# renv::restore() -``` - -```{r load_libraries, message=FALSE, warning=FALSE, include=FALSE} -# Configure knitr options -knitr::opts_chunk$set(warning = FALSE, message = FALSE) - -# Path management -library(here) - -# Spatial data libraries -library(sf) -library(terra) -library(exactextractr) -# library(raster) - Removed as it's no longer maintained - -# Data manipulation and visualization -library(tidyverse) # Includes dplyr, ggplot2, etc. -library(tmap) -library(lubridate) -library(zoo) - -# Machine learning -library(rsample) -library(caret) -library(randomForest) -library(CAST) - -# Parallel processing -library(future) -library(furrr) -library(progressr) - -# Load custom utility functions -tryCatch({ - source("report_utils.R") -}, error = function(e) { - message(paste("Error loading report_utils.R:", e$message)) - # Try alternative path if the first one fails - tryCatch({ - source(here::here("r_app", "report_utils.R")) - }, error = function(e) { - stop("Could not load report_utils.R from either location: ", e$message) - }) -}) -``` - -```{r setup_parallel_processing, message=FALSE, warning=FALSE, include=FALSE} -# Set up parallel processing to speed up report generation -# Use half of available cores to avoid overloading the system -num_workers <- parallel::detectCores() / 2 -num_workers <- floor(max(1, num_workers)) # At least 1, but no more than half of cores - -# Set up future plan for parallel processing -future::plan(future::multisession, workers = num_workers) -safe_log(paste("Set up parallel processing with", num_workers, "workers")) - -# Configure progressr reporting -progressr::handlers(progressr::handler_progress( - format = "[:bar] :percent :eta :message", - width = 60 -)) -``` - -```{r initialize_project_config, message=FALSE, warning=FALSE, include=FALSE} -# Set the project directory from parameters -project_dir <- params$data_dir - -# Source project parameters with error handling -tryCatch({ - source(here::here("r_app", "parameters_project.R")) -}, error = function(e) { - stop("Error loading parameters_project.R: ", e$message) -}) - -# Log initial configuration -safe_log("Starting the R Markdown script") -safe_log(paste("mail_day params:", params$mail_day)) -safe_log(paste("report_date params:", params$report_date)) -safe_log(paste("mail_day variable:", mail_day)) -``` - -```{r calculate_dates_and_weeks, message=FALSE, warning=FALSE, include=FALSE} -# Set locale for consistent date formatting -Sys.setlocale("LC_TIME", "C") - -# Initialize date variables from parameters -today <- as.character(report_date) -mail_day_as_character <- as.character(mail_day) - -# Calculate week days -report_date_as_week_day <- weekdays(lubridate::ymd(today)) -days_of_week <- c("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday") - -# Calculate initial week number -week <- lubridate::week(today) -safe_log(paste("Initial week calculation:", week, "today:", today)) - -# Calculate previous dates for comparisons -today_minus_1 <- as.character(lubridate::ymd(today) - 7) -today_minus_2 <- as.character(lubridate::ymd(today) - 14) -today_minus_3 <- as.character(lubridate::ymd(today) - 21) - -# Log the weekday calculations for debugging -safe_log(paste("Report date weekday:", report_date_as_week_day)) -safe_log(paste("Weekday index:", which(days_of_week == report_date_as_week_day))) -safe_log(paste("Mail day:", mail_day_as_character)) -safe_log(paste("Mail day index:", which(days_of_week == mail_day_as_character))) - -# Adjust week calculation based on mail day -if (which(days_of_week == report_date_as_week_day) > which(days_of_week == mail_day_as_character)) { - safe_log("Adjusting weeks because of mail day") - week <- lubridate::week(today) + 1 - today_minus_1 <- as.character(lubridate::ymd(today)) - today_minus_2 <- as.character(lubridate::ymd(today) - 7) - today_minus_3 <- as.character(lubridate::ymd(today) - 14) -} - -# Generate subtitle for report -subtitle_var <- paste("Report generated on", Sys.Date()) - -# Calculate week numbers for previous weeks -week_minus_1 <- week - 1 -week_minus_2 <- week - 2 -week_minus_3 <- week - 3 - -# Format current week with leading zeros -week <- sprintf("%02d", week) - -# Get years for each date -year <- lubridate::year(today) -year_1 <- lubridate::year(today_minus_1) -year_2 <- lubridate::year(today_minus_2) -year_3 <- lubridate::year(today_minus_3) -``` - -```{r data, message=TRUE, warning=TRUE, include=FALSE} -# Load CI index data with error handling -tryCatch({ - CI_quadrant <- readRDS(here::here(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds")) - safe_log("Successfully loaded CI quadrant data") -}, error = function(e) { - stop("Error loading CI quadrant data: ", e$message) -}) - -# Get file paths for different weeks using the utility function -tryCatch({ - path_to_week_current = get_week_path(weekly_CI_mosaic, today, 0) - path_to_week_minus_1 = get_week_path(weekly_CI_mosaic, today, -1) - path_to_week_minus_2 = get_week_path(weekly_CI_mosaic, today, -2) - path_to_week_minus_3 = get_week_path(weekly_CI_mosaic, today, -3) - - # Log the calculated paths - safe_log("Required mosaic paths:") - safe_log(paste("Path to current week:", path_to_week_current)) - safe_log(paste("Path to week minus 1:", path_to_week_minus_1)) - safe_log(paste("Path to week minus 2:", path_to_week_minus_2)) - safe_log(paste("Path to week minus 3:", path_to_week_minus_3)) - - # Validate that files exist - if (!file.exists(path_to_week_current)) warning("Current week mosaic file does not exist: ", path_to_week_current) - if (!file.exists(path_to_week_minus_1)) warning("Week minus 1 mosaic file does not exist: ", path_to_week_minus_1) - if (!file.exists(path_to_week_minus_2)) warning("Week minus 2 mosaic file does not exist: ", path_to_week_minus_2) - if (!file.exists(path_to_week_minus_3)) warning("Week minus 3 mosaic file does not exist: ", path_to_week_minus_3) - - # Load raster data with terra functions - CI <- terra::rast(path_to_week_current)$CI - CI_m1 <- terra::rast(path_to_week_minus_1)$CI - CI_m2 <- terra::rast(path_to_week_minus_2)$CI - CI_m3 <- terra::rast(path_to_week_minus_3)$CI - -}, error = function(e) { - stop("Error loading raster data: ", e$message) -}) -``` - -```{r calculate_difference_rasters, message=TRUE, warning=TRUE, include=FALSE} -# Calculate difference rasters for comparisons -tryCatch({ - # Calculate weekly difference - last_week_dif_raster_abs <- (CI - CI_m1) - safe_log("Calculated weekly difference raster") - - # Calculate three-week difference - three_week_dif_raster_abs <- (CI - CI_m3) - safe_log("Calculated three-week difference raster") -}, error = function(e) { - safe_log(paste("Error calculating difference rasters:", e$message), "ERROR") - # Create placeholder rasters if calculations fail - if (!exists("last_week_dif_raster_abs")) { - last_week_dif_raster_abs <- CI * 0 - } - if (!exists("three_week_dif_raster_abs")) { - three_week_dif_raster_abs <- CI * 0 - } -}) -``` - -```{r load_field_boundaries, message=TRUE, warning=TRUE, include=FALSE} -# Load field boundaries from parameters -tryCatch({ - AllPivots0 <- field_boundaries_sf - safe_log("Successfully loaded field boundaries") -}, error = function(e) { - stop("Error loading field boundaries: ", e$message) -}) -``` - -```{r calculate_field_health_scores, message=FALSE, warning=FALSE, include=FALSE} -# Calculate health scores for all fields -tryCatch({ - # Get list of all fields - all_fields <- unique(AllPivots0$field) - - # Process field health scores - safe_log("Calculating field health scores...") - - # Use future_map instead of map for parallel processing - field_health_scores <- furrr::future_map_dfr(all_fields, function(field_name) { - tryCatch({ - # Get field data - field_shape <- AllPivots0 %>% dplyr::filter(field == field_name) - - # Get field age from harvesting data - field_age_data <- harvesting_data %>% - dplyr::filter(field == field_name) %>% - dplyr::arrange(desc(season_start)) %>% - dplyr::slice(1) - - # Set default age if not available - field_age_weeks <- if(nrow(field_age_data) > 0 && !is.na(field_age_data$age)) { - field_age_data$age - } else { - 10 # Default age if not available - } - - # Crop and mask rasters for this field - ci_current <- terra::crop(CI, field_shape) %>% terra::mask(., field_shape) - ci_change <- terra::crop(last_week_dif_raster_abs, field_shape) %>% terra::mask(., field_shape) - - # Generate health score - health_data <- generate_field_health_score(ci_current, ci_change, field_age_weeks) - - # Return as a data frame row - data.frame( - field = field_name, - health_score = health_data$score, - health_status = health_data$status, - ci_score = health_data$components$ci, - change_score = health_data$components$change, - uniformity_score = health_data$components$uniformity, - age_weeks = field_age_weeks - ) - }, error = function(e) { - safe_log(paste("Error calculating health score for field", field_name, ":", e$message), "ERROR") - # Return NA values if error occurs - data.frame( - field = field_name, - health_score = NA, - health_status = "Error", - ci_score = NA, - change_score = NA, - uniformity_score = NA, - age_weeks = NA - ) - }) - }, .options = furrr::furrr_options(seed = TRUE)) - - # Add recommendations based on health status - field_health_scores <- field_health_scores %>% - dplyr::mutate(recommendation = dplyr::case_when( - health_status == "Critical" ~ "Immediate inspection needed", - health_status == "Needs Attention" ~ "Schedule inspection this week", - health_status == "Fair" ~ "Monitor closely", - health_status == "Good" ~ "Regular monitoring", - health_status == "Excellent" ~ "Maintain current practices", - TRUE ~ "Status unknown - inspect field" - )) - - safe_log("Health scores calculation completed") -}, error = function(e) { - safe_log(paste("Error in health score calculation:", e$message), "ERROR") - # Create empty dataframe if calculation failed - field_health_scores <- data.frame( - field = character(), - health_score = numeric(), - health_status = character(), - recommendation = character() - ) -}) -``` - -```{r helper_functions, message=FALSE, warning=FALSE, include=FALSE} -#' Generate a field health score based on CI values and trends -#' -#' @param ci_current Current CI raster -#' @param ci_change CI change raster -#' @param field_age_weeks Field age in weeks -#' @return List containing score, status, and component scores -#' -generate_field_health_score <- function(ci_current, ci_change, field_age_weeks) { - # Get mean CI value for the field - mean_ci <- terra::global(ci_current, "mean", na.rm=TRUE)[[1]] - - # Get mean CI change - mean_change <- terra::global(ci_change, "mean", na.rm=TRUE)[[1]] - - # Get CI uniformity (coefficient of variation) - ci_sd <- terra::global(ci_current, "sd", na.rm=TRUE)[[1]] - ci_uniformity <- ifelse(mean_ci > 0, ci_sd / mean_ci, 1) - - # Calculate base score from current CI (scale 0-5) - # Adjusted for crop age - expectations increase with age - expected_ci <- min(5, field_age_weeks / 10) # Simple linear model - ci_score <- max(0, min(5, 5 - 2 * abs(mean_ci - expected_ci))) - - # Add points for positive change (scale 0-3) - change_score <- max(0, min(3, 1 + mean_change)) - - # Add points for uniformity (scale 0-2) - uniformity_score <- max(0, min(2, 2 * (1 - ci_uniformity))) - - # Calculate total score (0-10) - total_score <- ci_score + change_score + uniformity_score - - # Create status label - status <- dplyr::case_when( - total_score >= 8 ~ "Excellent", - total_score >= 6 ~ "Good", - total_score >= 4 ~ "Fair", - total_score >= 2 ~ "Needs Attention", - TRUE ~ "Critical" - ) - - # Return results - return(list( - score = round(total_score, 1), - status = status, - components = list( - ci = round(ci_score, 1), - change = round(change_score, 1), - uniformity = round(uniformity_score, 1) - ) - )) -} - -#' Create an irrigation recommendation map -#' -#' @param ci_current Current CI raster -#' @param ci_change CI change raster -#' @param field_shape Field boundary shape -#' @param title Map title -#' @return A tmap object with irrigation recommendations -#' -create_irrigation_map <- function(ci_current, ci_change, field_shape, title = "Irrigation Priority Zones") { - # Create a new raster for irrigation recommendations - irrigation_priority <- ci_current * 0 - - # Extract values for processing - ci_values <- terra::values(ci_current) - change_values <- terra::values(ci_change) - - # Create priority zones: - # 3 = High priority (low CI, negative trend) - # 2 = Medium priority (low CI but stable, or good CI with negative trend) - # 1 = Low priority (watch, good CI with slight decline) - # 0 = No action needed (good CI, stable/positive trend) - priority_values <- rep(NA, length(ci_values)) - - # High priority: Low CI (< 2) and negative change (< 0) - high_priority <- which(ci_values < 2 & change_values < 0 & !is.na(ci_values) & !is.na(change_values)) - priority_values[high_priority] <- 3 - - # Medium priority: Low CI (< 2) with stable/positive change, or moderate CI (2-4) with significant negative change (< -1) - medium_priority <- which( - (ci_values < 2 & change_values >= 0 & !is.na(ci_values) & !is.na(change_values)) | - (ci_values >= 2 & ci_values < 4 & change_values < -1 & !is.na(ci_values) & !is.na(change_values)) - ) - priority_values[medium_priority] <- 2 - - # Low priority (watch): Moderate/good CI (>= 2) with mild negative change (-1 to 0) - low_priority <- which( - ci_values >= 2 & change_values < 0 & change_values >= -1 & !is.na(ci_values) & !is.na(change_values) - ) - priority_values[low_priority] <- 1 - - # No action needed: Good CI (>= 2) with stable/positive change (>= 0) - no_action <- which(ci_values >= 2 & change_values >= 0 & !is.na(ci_values) & !is.na(change_values)) - priority_values[no_action] <- 0 - - # Set values in the irrigation priority raster - terra::values(irrigation_priority) <- priority_values - - # Create the map - tm_shape(irrigation_priority) + - tm_raster( - style = "cat", - palette = c("#1a9850", "#91cf60", "#fc8d59", "#d73027"), - labels = c("No Action", "Watch", "Medium Priority", "High Priority"), - title = "Irrigation Need" - ) + - tm_shape(field_shape) + - tm_borders(lwd = 2) + - tm_layout( - main.title = title, - legend.outside = FALSE, - legend.position = c("left", "bottom") - ) -} - -#' Simple mock function to get weather data for a field -#' In a real implementation, this would fetch data from a weather API -#' -#' @param start_date Start date for weather data -#' @param end_date End date for weather data -#' @param lat Latitude of the field center -#' @param lon Longitude of the field center -#' @return A data frame of weather data -#' -get_weather_data <- function(start_date, end_date, lat = -16.1, lon = 34.7) { - # This is a mock implementation - in production, you'd replace with actual API call - # to a service like OpenWeatherMap, NOAA, or other weather data provider - - # Create date sequence - dates <- seq.Date(from = as.Date(start_date), to = as.Date(end_date), by = "day") - n_days <- length(dates) - - # Generate some random but realistic weather data with seasonal patterns - # More rain in summer, less in winter (Southern hemisphere) - month_nums <- as.numeric(format(dates, "%m")) - - # Simplified seasonal patterns - adjust for your local climate - is_rainy_season <- month_nums %in% c(11, 12, 1, 2, 3, 4) - - # Generate rainfall - more in rainy season, occasional heavy rainfall - rainfall <- numeric(n_days) - rainfall[is_rainy_season] <- pmax(0, rnorm(sum(is_rainy_season), mean = 4, sd = 8)) - rainfall[!is_rainy_season] <- pmax(0, rnorm(sum(!is_rainy_season), mean = 0.5, sd = 2)) - - # Add some rare heavy rainfall events - heavy_rain_days <- sample(which(is_rainy_season), size = max(1, round(sum(is_rainy_season) * 0.1))) - rainfall[heavy_rain_days] <- rainfall[heavy_rain_days] + runif(length(heavy_rain_days), 20, 50) - - # Generate temperatures - seasonal variation - temp_mean <- 18 + 8 * sin((month_nums - 1) * pi/6) # Peak in January (month 1) - temp_max <- temp_mean + rnorm(n_days, mean = 5, sd = 1) - temp_min <- temp_mean - rnorm(n_days, mean = 5, sd = 1) - - # Create weather data frame - weather_data <- data.frame( - date = dates, - rainfall_mm = round(rainfall, 1), - temp_max_c = round(temp_max, 1), - temp_min_c = round(temp_min, 1), - temp_mean_c = round((temp_max + temp_min) / 2, 1) - ) - - return(weather_data) -} - -#' Creates a weather summary visualization integrated with CI data -#' -#' @param pivotName Name of the pivot field -#' @param ci_data CI quadrant data -#' @param days_to_show Number of days of weather to show -#' @return ggplot object -#' -create_weather_ci_plot <- function(pivotName, ci_data = CI_quadrant, days_to_show = 30) { - # Get field data - field_data <- ci_data %>% - dplyr::filter(field == pivotName) %>% - dplyr::arrange(Date) %>% - dplyr::filter(!is.na(value)) - - if (nrow(field_data) == 0) { - return(ggplot() + - annotate("text", x = 0, y = 0, label = "No data available") + - theme_void()) - } - - # Get the latest date and 30 days before - latest_date <- max(field_data$Date, na.rm = TRUE) - start_date <- latest_date - days_to_show - - # Filter for recent data only - recent_field_data <- field_data %>% - dplyr::filter(Date >= start_date) - - # Get center point coordinates for the field (would be calculated from geometry in production) - # This is mocked for simplicity - lat <- -16.1 # Mock latitude - lon <- 34.7 # Mock longitude - - # Get weather data - weather_data <- get_weather_data(start_date, latest_date, lat, lon) - - # Aggregate CI data to daily mean across subfields if needed - daily_ci <- recent_field_data %>% - dplyr::group_by(Date) %>% - dplyr::summarize(mean_ci = mean(value, na.rm = TRUE)) - - # Create combined plot with dual y-axis - g <- ggplot() + - # Rainfall as bars - geom_col(data = weather_data, aes(x = date, y = rainfall_mm), - fill = "#1565C0", alpha = 0.7, width = 0.7) + - # CI as a line - geom_line(data = daily_ci, aes(x = Date, y = mean_ci * 10), - color = "#2E7D32", size = 1) + - geom_point(data = daily_ci, aes(x = Date, y = mean_ci * 10), - color = "#2E7D32", size = 2) + - # Temperature range as ribbon - geom_ribbon(data = weather_data, - aes(x = date, ymin = temp_min_c, ymax = temp_max_c), - fill = "#FF9800", alpha = 0.2) + - # Primary y-axis (rainfall) - scale_y_continuous( - name = "Rainfall (mm)", - sec.axis = sec_axis(~./10, name = "Chlorophyll Index & Temperature (°C)") - ) + - labs( - title = paste("Field", pivotName, "- Weather and CI Relationship"), - subtitle = paste("Last", days_to_show, "days"), - x = "Date" - ) + - theme_minimal() + - theme( - axis.title.y.left = element_text(color = "#1565C0"), - axis.title.y.right = element_text(color = "#2E7D32"), - legend.position = "bottom" - ) - - return(g) -} -``` - -`r subtitle_var` - -\pagebreak -# Explanation of the Report - -This report provides a detailed analysis of your sugarcane fields based on satellite imagery, helping you monitor crop health and development throughout the growing season. The data is processed weekly to give you timely insights for optimal farm management decisions. - -## What is the Chlorophyll Index (CI)? - -The **Chlorophyll Index (CI)** is a vegetation index that measures the relative amount of chlorophyll in plant leaves. Chlorophyll is the green pigment responsible for photosynthesis in plants. Higher CI values indicate: - -* Greater photosynthetic activity -* Healthier plant tissue -* Better nitrogen uptake -* More vigorous crop growth - -CI values typically range from 0 (bare soil or severely stressed vegetation) to 7+ (very healthy, dense vegetation). For sugarcane, values between 3-7 generally indicate good crop health, depending on the growth stage. - -## What You'll Find in This Report: - -1. **Chlorophyll Index Overview Map**: A comprehensive view of all your fields showing current CI values. This helps identify which fields are performing well and which might need attention. - -2. **Weekly Difference Map**: Shows changes in CI values over the past week. Positive values (green) indicate improving crop health, while negative values (red) may signal stress or decline. - -3. **Field-by-Field Analysis**: Detailed maps for each field showing: - * CI values for the current week and two previous weeks - * Week-to-week changes in CI values - * Three-week change in CI values to track longer-term trends - -4. **Growth Trend Graphs**: Time-series visualizations showing how CI values have changed throughout the growing season for each section of your fields. - -5. **Yield Prediction**: For mature crops (over 300 days), we provide estimated yield predictions based on historical data and current CI measurements. - -Use these insights to identify areas that may need irrigation, fertilization, or other interventions, and to track the effectiveness of your management practices over time. - -\pagebreak -# Chlorophyll Index (CI) Overview Map - Current Week -```{r render_ci_overview_map, echo=FALSE, fig.height=6.8, fig.width=9, message=FALSE, warning=FALSE} -# Create overview chlorophyll index map -tryCatch({ - # Base shape - map <- tmap::tm_shape(CI, unit = "m") - - # Add raster layer with either breaks or continuous spectrum based on parameter - if (use_breaks) { - map <- map + tmap::tm_raster(breaks = c(0,0.5,1,2,3,4,5,6,7,Inf), - palette = "RdYlGn", - midpoint = NA, - legend.is.portrait = FALSE, - title = "Chlorophyll Index (CI)") - } else { - map <- map + tmap::tm_raster(palette = "RdYlGn", - style = "cont", - midpoint = NA, - legend.is.portrait = FALSE, - title = "Chlorophyll Index (CI)") - } - - # Complete the map with layout and other elements - map <- map + tmap::tm_layout(legend.outside = TRUE, - legend.outside.position = "bottom", - legend.show = TRUE) + - tmap::tm_scale_bar(position = tm_pos_out("right", "bottom"), text.color = "black") + - tmap::tm_compass(position = tm_pos_out("right", "bottom"), text.color = "black") + - tmap::tm_shape(AllPivots0) + - tmap::tm_borders(col = "black") + - tmap::tm_text("sub_field", size = 0.6, col = "black") - - # Print the map - print(map) -}, error = function(e) { - safe_log(paste("Error creating CI overview map:", e$message), "ERROR") - plot(1, type="n", axes=FALSE, xlab="", ylab="") - text(1, 1, "Error creating CI overview map", cex=1.5) -}) -``` -\newpage - -# Weekly Chlorophyll Index Difference Map -```{r render_ci_difference_map, echo=FALSE, fig.height=6.8, fig.width=9, message=FALSE, warning=FALSE} -# Create chlorophyll index difference map -tryCatch({ - # Base shape - map <- tmap::tm_shape(last_week_dif_raster_abs, unit = "m") - - # Add raster layer with either breaks or continuous spectrum based on parameter - if (use_breaks) { - map <- map + tmap::tm_raster(breaks = c(-3,-2,-1,0,1,2,3), - palette = "RdYlGn", - midpoint = 0, - legend.is.portrait = FALSE, - title = "Chlorophyll Index (CI) Change") - } else { - map <- map + tmap::tm_raster(palette = "RdYlGn", - style = "cont", - midpoint = 0, - legend.is.portrait = FALSE, - title = "Chlorophyll Index (CI) Change") - } - - # Complete the map with layout and other elements - map <- map + tmap::tm_layout(legend.outside = TRUE, - legend.outside.position = "bottom", - legend.show = TRUE) + - tmap::tm_scale_bar(position = tm_pos_out("right", "bottom"), text.color = "black") + - tmap::tm_compass(position = tm_pos_out("right", "bottom"), text.color = "black") + - tmap::tm_shape(AllPivots0) + - tmap::tm_borders(col = "black") + - tmap::tm_text("sub_field", size = 0.6, col = "black") - - # Print the map - print(map) -}, error = function(e) { - safe_log(paste("Error creating CI difference map:", e$message), "ERROR") - plot(1, type="n", axes=FALSE, xlab="", ylab="") - text(1, 1, "Error creating CI difference map", cex=1.5) -}) -``` -\newpage - -\pagebreak -# Field Health Overview - -The Field Health Scorecard provides an at-a-glance view of all your fields' current health status. Each field is scored on a scale of 0-10 based on: - -- **Current CI Value** (0-5 points): How well the field's chlorophyll levels match expectations for its growth stage -- **Recent CI Change** (0-3 points): Whether the field is improving or declining over the last week -- **Field Uniformity** (0-2 points): How consistent the chlorophyll levels are across the field - -This helps you quickly identify which fields need attention and which are performing well. - -```{r render_health_scorecard, echo=FALSE, fig.height=6, fig.width=10, message=FALSE, warning=FALSE} -# Create field health scorecard visualization -tryCatch({ - # Sort fields by health score - sorted_health_scores <- field_health_scores %>% - dplyr::arrange(desc(health_score)) - - # Create color mapping for status categories - status_colors <- c( - "Excellent" = "#1a9850", - "Good" = "#91cf60", - "Fair" = "#fee08b", - "Needs Attention" = "#fc8d59", - "Critical" = "#d73027", - "Error" = "#999999" - ) - - # Create the bar chart - g <- ggplot2::ggplot(sorted_health_scores, - ggplot2::aes(x = reorder(field, health_score), - y = health_score, - fill = health_status)) + - ggplot2::geom_bar(stat = "identity") + - ggplot2::geom_text(ggplot2::aes(label = health_score), - hjust = -0.2, - size = 3) + - ggplot2::coord_flip() + - ggplot2::scale_fill_manual(values = status_colors) + - ggplot2::scale_y_continuous(limits = c(0, 11)) + # Add space for labels - ggplot2::labs(title = "Field Health Scores", - x = "", - y = "Health Score (0-10)", - fill = "Status") + - ggplot2::theme_minimal() + - ggplot2::theme( - plot.title = ggplot2::element_text(face = "bold", size = 14), - axis.text.y = ggplot2::element_text(size = 10), - legend.position = "bottom" - ) - - # Print the chart - print(g) - - # Create and print the table with recommendations - health_table <- sorted_health_scores %>% - dplyr::select(field, health_score, health_status, recommendation, age_weeks) %>% - dplyr::rename( - "Field" = field, - "Score" = health_score, - "Status" = health_status, - "Recommendation" = recommendation, - "Age (Weeks)" = age_weeks - ) - - knitr::kable(health_table, - caption = "Field Health Status and Recommendations", - digits = 1) - -}, error = function(e) { - safe_log(paste("Error creating health scorecard:", e$message), "ERROR") - plot(1, type="n", axes=FALSE, xlab="", ylab="") - text(1, 1, "Error creating health scorecard visualization", cex=1.5) -}) -``` - -\pagebreak -# Irrigation Priority Map - -This map highlights areas that may need irrigation based on current CI values and recent changes. The irrigation priority is determined by combining current crop health with recent trends: - -- **High Priority (Red)**: Low CI values with declining trends - these areas need immediate attention -- **Medium Priority (Orange)**: Either low CI with stable/improving trends or moderate CI with significant decline -- **Watch (Yellow)**: Areas with acceptable CI but showing slight negative trends -- **No Action Needed (Green)**: Areas with good CI values and stable or improving trends - -```{r render_irrigation_map, echo=FALSE, fig.height=6, fig.width=9, message=FALSE, warning=FALSE} -# Create overall irrigation priority map -tryCatch({ - # Create the map - irrigation_map <- create_irrigation_map( - ci_current = CI, - ci_change = last_week_dif_raster_abs, - field_shape = AllPivots0, - title = "Farm-Wide Irrigation Priority Zones" - ) - - # Add field labels and borders - irrigation_map <- irrigation_map + - tm_shape(AllPivots0) + - tm_borders(col = "black") + - tm_text("field", size = 0.6) + - tm_layout(legend.outside = TRUE, - legend.outside.position = "bottom") + - tm_scale_bar(position = tm_pos_out("right", "bottom")) - - # Print the map - print(irrigation_map) -}, error = function(e) { - safe_log(paste("Error creating irrigation priority map:", e$message), "ERROR") - plot(1, type="n", axes=FALSE, xlab="", ylab="") - text(1, 1, "Error creating irrigation priority map", cex=1.5) -}) -``` - -\pagebreak -# Weather and CI Relationship - -This section shows the relationship between recent weather patterns and crop health. Understanding this relationship can help identify whether changes in CI are due to weather factors or other issues that may require management intervention. - -```{r render_weather_integration, echo=FALSE, fig.height=7, fig.width=10, message=FALSE, warning=FALSE} -# Create weather-CI relationship visualization for a few sample fields -tryCatch({ - # Get top fields in different health categories to show as examples - sample_fields <- field_health_scores %>% - dplyr::group_by(health_status) %>% - dplyr::slice_head(n = 1) %>% - dplyr::ungroup() %>% - dplyr::pull(field) - - # If we have more than 3 fields, just show 3 for brevity - if(length(sample_fields) > 3) { - sample_fields <- sample_fields[1:3] - } - - # If no sample fields are available, use the first field in the data - if(length(sample_fields) == 0 && nrow(AllPivots_merged) > 0) { - sample_fields <- AllPivots_merged$field[1] - } - - # Create weather plots for each sample field - for(field_name in sample_fields) { - # Create the weather-CI plot - weather_plot <- create_weather_ci_plot( - pivotName = field_name, - ci_data = CI_quadrant, - days_to_show = 60 # Show last 60 days - ) - - # Print the plot - print(weather_plot) - } - - # Add explanation if using mock weather data - cat("*Note: Weather data shown is representative and may vary from actual field conditions.*\n") - cat("*For production use, this would be connected to local weather station data or weather APIs.*\n") - -}, error = function(e) { - safe_log(paste("Error creating weather integration:", e$message), "ERROR") - cat("Error generating weather relationship visualization. See log for details.") -}) -``` - -\newpage - -```{r generate_field_visualizations, eval=TRUE, fig.height=3.8, fig.width=10, message=FALSE,echo=FALSE, warning=FALSE, include=TRUE, results='asis'} -# Generate detailed visualizations for each field -tryCatch({ - # Merge field polygons for processing - AllPivots_merged <- AllPivots0 %>% - dplyr::group_by(field) %>% - dplyr::summarise(.groups = 'drop') - - # Log start time for performance measurement - start_time <- Sys.time() - safe_log(paste("Starting field visualization generation at", start_time)) - - # Setup progress tracking - p <- progressr::progressor(steps = nrow(AllPivots_merged)) - - # Generate field-specific visualizations - field_results <- furrr::future_map(AllPivots_merged$field, function(field_name) { - tryCatch({ - # Update progress - p(sprintf("Processing field %s", field_name)) - - # Temporary list to store outputs - outputs <- list() - outputs$field_name <- field_name - - # Get field data - field_shape <- AllPivots0 %>% dplyr::filter(field == field_name) - - # Add irrigation priority map for this field - field_ci <- terra::crop(CI, field_shape) %>% terra::mask(., field_shape) - field_change <- terra::crop(last_week_dif_raster_abs, field_shape) %>% terra::mask(., field_shape) - - # Store plot objects - outputs$irrigation_map <- create_irrigation_map( - field_ci, - field_change, - field_shape, - title = paste("Field", field_name, "- Irrigation Priority") - ) - - return(outputs) - }, error = function(e) { - safe_log(paste("Error processing field visualization for", field_name, ":", e$message), "ERROR") - return(list( - field_name = field_name, - error = e$message - )) - }) - }, .options = furrr::furrr_options(seed = TRUE)) - - # Log performance metrics - end_time <- Sys.time() - processing_time <- as.numeric(difftime(end_time, start_time, units="secs")) - safe_log(paste("Field visualization processing completed in", round(processing_time, 2), "seconds")) - safe_log(paste("Average time per field:", round(processing_time / nrow(AllPivots_merged), 2), "seconds")) - - # Generate detailed plots for each field using standard sequential processing - # This part still uses sequential processing because the ci_plot function renders directly to the document - safe_log("Starting sequential rendering of field plots") - purrr::walk(AllPivots_merged$field, function(field_name) { - tryCatch({ - cat("\n") # Add an empty line for better spacing - - # First, add field header and retrieve the field-specific irrigation map - cat(paste("## Field", field_name, "\n\n")) - - # Find the irrigation map for this field - field_result <- field_results[[which(AllPivots_merged$field == field_name)]] - - # If we have irrigation data for this field, show it - if (!is.null(field_result$irrigation_map)) { - cat("\n### Irrigation Priority Map\n\n") - print(field_result$irrigation_map) - cat("\n") - } - - # Call ci_plot with explicit parameters - ci_plot( - pivotName = field_name, - field_boundaries = AllPivots0, - current_ci = CI, - ci_minus_1 = CI_m1, - ci_minus_2 = CI_m2, - last_week_diff = last_week_dif_raster_abs, - three_week_diff = three_week_dif_raster_abs, - harvesting_data = harvesting_data, - week = week, - week_minus_1 = week_minus_1, - week_minus_2 = week_minus_2, - week_minus_3 = week_minus_3, - use_breaks = use_breaks, - borders = borders - ) - - cat("\n") - - # Call cum_ci_plot with explicit parameters - cum_ci_plot( - pivotName = field_name, - ci_quadrant_data = CI_quadrant, - plot_type = "value", - facet_on = FALSE - ) - - }, error = function(e) { - safe_log(paste("Error generating plots for field", field_name, ":", e$message), "ERROR") - cat(paste("## Error generating plots for field", field_name, "\n")) - cat(paste(e$message, "\n")) - }) - }) - -}, error = function(e) { - safe_log(paste("Error in field visualization section:", e$message), "ERROR") - cat("Error generating field plots. See log for details.\n") -}) -``` - -```{r generate_subarea_visualizations, echo=FALSE, fig.height=3.8, fig.width=10, message=FALSE, warning=FALSE, results='asis', eval=FALSE} -# Alternative visualization grouped by sub-area (disabled by default) -tryCatch({ - # Group pivots by sub-area - pivots_grouped <- AllPivots0 - - # Iterate over each subgroup - for (subgroup in unique(pivots_grouped$sub_area)) { - # Add subgroup heading - cat("\n") - cat("## Subgroup: ", subgroup, "\n") - - # Filter data for current subgroup - subset_data <- dplyr::filter(pivots_grouped, sub_area == subgroup) - - # Generate visualizations for each field in the subgroup - purrr::walk(subset_data$field, function(field_name) { - cat("\n") - ci_plot(field_name) - cat("\n") - cum_ci_plot(field_name) - cat("\n") - }) - - # Add page break after each subgroup - cat("\\pagebreak\n") - } -}, error = function(e) { - safe_log(paste("Error in subarea visualization section:", e$message), "ERROR") - cat("Error generating subarea plots. See log for details.\n") -}) -``` - -# Yield prediction -The below table shows estimates of the biomass if you would harvest them now. - -```{r yield_data_training, message=FALSE, warning=FALSE, include=FALSE} -# Load and prepare yield prediction data with error handling -tryCatch({ - # Load CI quadrant data and fill missing values - CI_quadrant <- readRDS(here::here(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds")) %>% - dplyr::group_by(model) %>% - tidyr::fill(field, sub_field, .direction = "downup") %>% - dplyr::ungroup() - - # Check if tonnage_ha is empty - if (all(is.na(CI_quadrant$tonnage_ha))) { - safe_log("Lacking historic harvest data, please provide for yield prediction calculation", "WARNING") - knitr::knit_exit() # Exit the chunk if tonnage_ha is empty - } - - # Rename year column to season for consistency - harvesting_data <- harvesting_data %>% dplyr::rename(season = year) - - # Join CI and yield data - CI_and_yield <- dplyr::left_join(CI_quadrant, harvesting_data, by = c("field", "sub_field", "season")) %>% - dplyr::group_by(sub_field, season) %>% - dplyr::slice(which.max(DOY)) %>% - dplyr::select(field, sub_field, tonnage_ha, cumulative_CI, DOY, season, sub_area) %>% - dplyr::mutate(CI_per_day = cumulative_CI / DOY) - - # Define predictors and response variables - predictors <- c("cumulative_CI", "DOY", "CI_per_day") - response <- "tonnage_ha" - - # Prepare test and validation datasets - CI_and_yield_test <- CI_and_yield %>% - as.data.frame() %>% - dplyr::filter(!is.na(tonnage_ha)) - - CI_and_yield_validation <- CI_and_yield_test - - # Prepare prediction dataset (fields without harvest data) - prediction_yields <- CI_and_yield %>% - as.data.frame() %>% - dplyr::filter(is.na(tonnage_ha)) - - # Configure model training parameters - ctrl <- caret::trainControl( - method = "cv", - savePredictions = TRUE, - allowParallel = TRUE, - number = 5, - verboseIter = TRUE - ) - - # Train the model with feature selection - set.seed(202) # For reproducibility - model_ffs_rf <- CAST::ffs( - CI_and_yield_test[, predictors], - CI_and_yield_test[, response], - method = "rf", - trControl = ctrl, - importance = TRUE, - withinSE = TRUE, - tuneLength = 5, - na.rm = TRUE - ) - - # Function to prepare predictions with consistent naming and formatting - prepare_predictions <- function(predictions, newdata) { - return(predictions %>% - as.data.frame() %>% - dplyr::rename(predicted_Tcha = ".") %>% - dplyr::mutate( - sub_field = newdata$sub_field, - field = newdata$field, - Age_days = newdata$DOY, - total_CI = round(newdata$cumulative_CI, 0), - predicted_Tcha = round(predicted_Tcha, 0), - season = newdata$season - ) %>% - dplyr::select(field, sub_field, Age_days, total_CI, predicted_Tcha, season) %>% - dplyr::left_join(., newdata, by = c("field", "sub_field", "season")) - ) - } - - # Predict yields for the validation dataset - pred_ffs_rf <- prepare_predictions(stats::predict(model_ffs_rf, newdata = CI_and_yield_validation), CI_and_yield_validation) - - # Predict yields for the current season (focus on mature fields over 300 days) - pred_rf_current_season <- prepare_predictions(stats::predict(model_ffs_rf, newdata = prediction_yields), prediction_yields) %>% - dplyr::filter(Age_days > 300) %>% - dplyr::mutate(CI_per_day = round(total_CI / Age_days, 1)) - - safe_log("Successfully completed yield prediction calculations") - -}, error = function(e) { - safe_log(paste("Error in yield prediction:", e$message), "ERROR") - # Create empty dataframes to prevent errors in subsequent chunks - pred_ffs_rf <- data.frame() - pred_rf_current_season <- data.frame() -}) -``` - -```{r plotting_yield_data, echo=FALSE, fig.height=5, fig.width=8, message=FALSE, warning=FALSE} -# Display yield prediction visualizations with error handling -tryCatch({ - if (nrow(pred_ffs_rf) > 0) { - # Plot model performance (predicted vs actual) - ggplot2::ggplot(pred_ffs_rf, ggplot2::aes(y = predicted_Tcha, x = tonnage_ha)) + - ggplot2::geom_point(size = 2, alpha = 0.6) + - ggplot2::geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "red") + - ggplot2::scale_x_continuous(limits = c(0, 200)) + - ggplot2::scale_y_continuous(limits = c(0, 200)) + - ggplot2::labs(title = "Model Performance: \nPredicted vs Actual Tonnage/ha", - x = "Actual tonnage/ha (Tcha)", - y = "Predicted tonnage/ha (Tcha)") + - ggplot2::theme_minimal() - } - - if (nrow(pred_rf_current_season) > 0) { - # Plot predicted yields by age - ggplot2::ggplot(pred_rf_current_season, ggplot2::aes(x = Age_days, y = predicted_Tcha)) + - ggplot2::geom_point(size = 2, alpha = 0.6) + - ggplot2::labs(title = "Predicted Yields for Fields Over 300 Days \nOld Yet to Be Harvested", - x = "Age (days)", - y = "Predicted tonnage/ha (Tcha)") + - ggplot2::scale_y_continuous(limits = c(0, 200)) + - ggplot2::theme_minimal() - - # Display prediction table - knitr::kable(pred_rf_current_season, - digits = 0, - caption = "Predicted Tonnage/ha for Fields Over 300 Days Old") - } else { - cat("No fields over 300 days old without harvest data available for yield prediction.") - } -}, error = function(e) { - safe_log(paste("Error in yield prediction visualization:", e$message), "ERROR") - cat("Error generating yield prediction visualizations. See log for details.") -}) -``` - diff --git a/r_app/CI_report_executive_summary.Rmd b/r_app/CI_report_executive_summary.Rmd deleted file mode 100644 index 6341f6c..0000000 --- a/r_app/CI_report_executive_summary.Rmd +++ /dev/null @@ -1,721 +0,0 @@ ---- -params: - ref: "word-styles-reference-var1.docx" - output_file: CI_report.docx - report_date: "2025-06-16" - data_dir: "simba" - mail_day: "Wednesday" - borders: TRUE - use_breaks: FALSE -output: - # html_document: - # toc: yes - # df_print: paged - word_document: - reference_docx: !expr file.path("word-styles-reference-var1.docx") - toc: yes -editor_options: - chunk_output_type: console ---- - -```{r setup_parameters, include=FALSE} -# Set up basic report parameters from input values -report_date <- params$report_date -mail_day <- params$mail_day -borders <- params$borders -use_breaks <- params$use_breaks # Whether to use breaks or continuous spectrum in visualizations - -# Environment setup notes (commented out) -# # Activeer de renv omgeving -# renv::activate() -# renv::deactivate() -# # Optioneel: Herstel de omgeving als dat nodig is -# # Je kunt dit commentaar geven als je het normaal niet wilt uitvoeren -# renv::restore() -``` - -```{r load_libraries, message=FALSE, warning=FALSE, include=FALSE} -# Configure knitr options -knitr::opts_chunk$set(warning = FALSE, message = FALSE) - -# Path management -library(here) - -# Spatial data libraries -library(sf) -library(terra) -library(exactextractr) -# library(raster) - Removed as it's no longer maintained - -# Data manipulation and visualization -library(tidyverse) # Includes dplyr, ggplot2, etc. -library(tmap) -library(lubridate) -library(zoo) - -# Machine learning -library(rsample) -library(caret) -library(randomForest) -library(CAST) - -# Load custom utility functions -# tryCatch({ -# source("report_utils.R") -# }, error = function(e) { -# message(paste("Error loading report_utils.R:", e$message)) -# # Try alternative path if the first one fails -# tryCatch({ - source(here::here("r_app", "report_utils.R")) -# }, error = function(e) { -# stop("Could not load report_utils.R from either location: ", e$message) -# }) -# }) - -# Load executive report utilities -# tryCatch({ -# source("executive_report_utils.R") -# }, error = function(e) { -# message(paste("Error loading executive_report_utils.R:", e$message)) -# # Try alternative path if the first one fails -# tryCatch({ - source(here::here("r_app","exec_dashboard", "executive_report_utils.R")) -# }, error = function(e) { -# stop("Could not load executive_report_utils.R from either location: ", e$message) -# }) -# }) - -safe_log("Successfully loaded utility functions") -``` - -```{r initialize_project_config, message=FALSE, warning=FALSE, include=FALSE} -# Set the project directory from parameters -project_dir <- params$data_dir - -# Source project parameters with error handling -tryCatch({ - source(here::here("r_app", "parameters_project.R")) -}, error = function(e) { - stop("Error loading parameters_project.R: ", e$message) -}) - -# Log initial configuration -safe_log("Starting the R Markdown script") -safe_log(paste("mail_day params:", params$mail_day)) -safe_log(paste("report_date params:", params$report_date)) -safe_log(paste("mail_day variable:", mail_day)) -``` - -```{r calculate_dates_and_weeks, message=FALSE, warning=FALSE, include=FALSE} -# Set locale for consistent date formatting -Sys.setlocale("LC_TIME", "C") - -# Initialize date variables from parameters -today <- as.character(report_date) -mail_day_as_character <- as.character(mail_day) - -# Calculate week days -report_date_as_week_day <- weekdays(lubridate::ymd(today)) -days_of_week <- c("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday") - -# Calculate initial week number -week <- lubridate::week(today) -safe_log(paste("Initial week calculation:", week, "today:", today)) - -# Calculate previous dates for comparisons -today_minus_1 <- as.character(lubridate::ymd(today) - 7) -today_minus_2 <- as.character(lubridate::ymd(today) - 14) -today_minus_3 <- as.character(lubridate::ymd(today) - 21) - -# Log the weekday calculations for debugging -safe_log(paste("Report date weekday:", report_date_as_week_day)) -safe_log(paste("Weekday index:", which(days_of_week == report_date_as_week_day))) -safe_log(paste("Mail day:", mail_day_as_character)) -safe_log(paste("Mail day index:", which(days_of_week == mail_day_as_character))) - -# Adjust week calculation based on mail day -if (which(days_of_week == report_date_as_week_day) > which(days_of_week == mail_day_as_character)) { - safe_log("Adjusting weeks because of mail day") - week <- lubridate::week(today) + 1 - today_minus_1 <- as.character(lubridate::ymd(today)) - today_minus_2 <- as.character(lubridate::ymd(today) - 7) - today_minus_3 <- as.character(lubridate::ymd(today) - 14) -} - -# Generate subtitle for report -subtitle_var <- paste("Report generated on", Sys.Date()) - -# Calculate week numbers for previous weeks -week_minus_1 <- week - 1 -week_minus_2 <- week - 2 -week_minus_3 <- week - 3 - -# Format current week with leading zeros -week <- sprintf("%02d", week) - -# Get years for each date -year <- lubridate::year(today) -year_1 <- lubridate::year(today_minus_1) -year_2 <- lubridate::year(today_minus_2) -year_3 <- lubridate::year(today_minus_3) -``` - -```{r data, message=TRUE, warning=TRUE, include=FALSE} -# Load CI index data with error handling -tryCatch({ - CI_quadrant <- readRDS(here::here(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds")) - safe_log("Successfully loaded CI quadrant data") -}, error = function(e) { - stop("Error loading CI quadrant data: ", e$message) -}) - -# Get file paths for different weeks using the utility function -tryCatch({ - path_to_week_current = get_week_path(weekly_CI_mosaic, today, 0) - path_to_week_minus_1 = get_week_path(weekly_CI_mosaic, today, -1) - path_to_week_minus_2 = get_week_path(weekly_CI_mosaic, today, -2) - path_to_week_minus_3 = get_week_path(weekly_CI_mosaic, today, -3) - - # Log the calculated paths - safe_log("Required mosaic paths:") - safe_log(paste("Path to current week:", path_to_week_current)) - safe_log(paste("Path to week minus 1:", path_to_week_minus_1)) - safe_log(paste("Path to week minus 2:", path_to_week_minus_2)) - safe_log(paste("Path to week minus 3:", path_to_week_minus_3)) - - # Validate that files exist - if (!file.exists(path_to_week_current)) warning("Current week mosaic file does not exist: ", path_to_week_current) - if (!file.exists(path_to_week_minus_1)) warning("Week minus 1 mosaic file does not exist: ", path_to_week_minus_1) - if (!file.exists(path_to_week_minus_2)) warning("Week minus 2 mosaic file does not exist: ", path_to_week_minus_2) - if (!file.exists(path_to_week_minus_3)) warning("Week minus 3 mosaic file does not exist: ", path_to_week_minus_3) - - # Load raster data with terra functions - CI <- terra::rast(path_to_week_current)$CI - CI_m1 <- terra::rast(path_to_week_minus_1)$CI - CI_m2 <- terra::rast(path_to_week_minus_2)$CI - CI_m3 <- terra::rast(path_to_week_minus_3)$CI - -}, error = function(e) { - stop("Error loading raster data: ", e$message) -}) -``` - -```{r calculate_difference_rasters, message=TRUE, warning=TRUE, include=FALSE} -# Calculate difference rasters for comparisons -tryCatch({ - # Calculate weekly difference - last_week_dif_raster_abs <- (CI - CI_m1) - safe_log("Calculated weekly difference raster") - - # Calculate three-week difference - three_week_dif_raster_abs <- (CI - CI_m3) - safe_log("Calculated three-week difference raster") -}, error = function(e) { - safe_log(paste("Error calculating difference rasters:", e$message), "ERROR") - # Create placeholder rasters if calculations fail - if (!exists("last_week_dif_raster_abs")) { - last_week_dif_raster_abs <- CI * 0 - } - if (!exists("three_week_dif_raster_abs")) { - three_week_dif_raster_abs <- CI * 0 - } -}) -``` - -```{r load_field_boundaries, message=TRUE, warning=TRUE, include=FALSE} -# Load field boundaries from parameters -tryCatch({ - AllPivots0 <- field_boundaries_sf - safe_log("Successfully loaded field boundaries") -}, error = function(e) { - stop("Error loading field boundaries: ", e$message) -}) -``` - -```{r create_farm_health_data, message=FALSE, warning=FALSE, include=FALSE} -# Create farm health summary data from scratch -tryCatch({ - # Ensure we have the required data - if (!exists("AllPivots0") || !exists("CI") || !exists("CI_m1") || !exists("harvesting_data")) { - stop("Required input data (field boundaries, CI data, or harvesting data) not available") - } - - safe_log("Starting to calculate farm health data") - - # Get unique field names - fields <- unique(AllPivots0$field) - safe_log(paste("Found", length(fields), "unique fields")) - - # Initialize result dataframe - farm_health_data <- data.frame( - field = character(), - mean_ci = numeric(), - ci_change = numeric(), - ci_uniformity = numeric(), - status = character(), - anomaly_type = character(), - priority_level = numeric(), - age_weeks = numeric(), - harvest_readiness = character(), - stringsAsFactors = FALSE - ) - - # Process each field with robust error handling - for (field_name in fields) { - tryCatch({ - safe_log(paste("Processing field:", field_name)) - - # Get field boundary - field_shape <- AllPivots0 %>% dplyr::filter(field == field_name) - - # Skip if field shape is empty - if (nrow(field_shape) == 0) { - safe_log(paste("Empty field shape for", field_name), "WARNING") - next - } - - # Get field age from harvesting data - use direct filtering to avoid dplyr errors - field_age_data <- NULL - if (exists("harvesting_data") && !is.null(harvesting_data) && nrow(harvesting_data) > 0) { - field_age_data <- harvesting_data[harvesting_data$field == field_name, ] - if (nrow(field_age_data) > 0) { - field_age_data <- field_age_data[order(field_age_data$season_start, decreasing = TRUE), ][1, ] - } - } - - # Default age if not available - field_age_weeks <- if (!is.null(field_age_data) && nrow(field_age_data) > 0 && !is.na(field_age_data$age)) { - field_age_data$age - } else { - 10 # Default age - } - - # Extract CI values using terra's extract function which is more robust - ci_values <- terra::extract(CI, field_shape) - ci_prev_values <- terra::extract(CI_m1, field_shape) - - # Check if we got valid data - if (nrow(ci_values) == 0 || nrow(ci_prev_values) == 0) { - safe_log(paste("No CI data extracted for field", field_name), "WARNING") - # Add a placeholder row with Unknown status - farm_health_data <- rbind(farm_health_data, data.frame( - field = field_name, - mean_ci = NA, - ci_change = NA, - ci_uniformity = NA, - status = "Unknown", - anomaly_type = "Unknown", - priority_level = 5, # Low priority - age_weeks = field_age_weeks, - harvest_readiness = "Unknown", - stringsAsFactors = FALSE - )) - next - } - - # Calculate metrics - Handle NA values properly - ci_column <- if ("CI" %in% names(ci_values)) "CI" else colnames(ci_values)[1] - ci_prev_column <- if ("CI" %in% names(ci_prev_values)) "CI" else colnames(ci_prev_values)[1] - - mean_ci <- mean(ci_values[[ci_column]], na.rm=TRUE) - mean_ci_prev <- mean(ci_prev_values[[ci_prev_column]], na.rm=TRUE) - ci_change <- mean_ci - mean_ci_prev - ci_sd <- sd(ci_values[[ci_column]], na.rm=TRUE) - ci_uniformity <- ci_sd / max(0.1, mean_ci) # Avoid division by zero - - # Handle NaN or Inf results - if (is.na(mean_ci) || is.na(ci_change) || is.na(ci_uniformity) || - is.nan(mean_ci) || is.nan(ci_change) || is.nan(ci_uniformity) || - is.infinite(mean_ci) || is.infinite(ci_change) || is.infinite(ci_uniformity)) { - safe_log(paste("Invalid calculation results for field", field_name), "WARNING") - # Add a placeholder row with Unknown status - farm_health_data <- rbind(farm_health_data, data.frame( - field = field_name, - mean_ci = NA, - ci_change = NA, - ci_uniformity = NA, - status = "Unknown", - anomaly_type = "Unknown", - priority_level = 5, # Low priority - age_weeks = field_age_weeks, - harvest_readiness = "Unknown", - stringsAsFactors = FALSE - )) - next - } - - # Determine field status - status <- dplyr::case_when( - mean_ci >= 5 ~ "Excellent", - mean_ci >= 3.5 ~ "Good", - mean_ci >= 2 ~ "Fair", - mean_ci >= 1 ~ "Poor", - TRUE ~ "Critical" - ) - - # Determine anomaly type - anomaly_type <- dplyr::case_when( - ci_change > 2 ~ "Potential Weed Growth", - ci_change < -2 ~ "Potential Weeding/Harvesting", - ci_uniformity > 0.5 ~ "High Variability", - mean_ci < 1 ~ "Low Vigor", - TRUE ~ "None" - ) - - # Calculate priority level (1-5, with 1 being highest priority) - priority_score <- dplyr::case_when( - mean_ci < 1 ~ 1, # Critical - highest priority - anomaly_type == "Potential Weed Growth" ~ 2, - anomaly_type == "High Variability" ~ 3, - ci_change < -1 ~ 4, - TRUE ~ 5 # No urgent issues - ) - - # Determine harvest readiness - harvest_readiness <- dplyr::case_when( - field_age_weeks >= 52 & mean_ci >= 4 ~ "Ready for harvest", - field_age_weeks >= 48 & mean_ci >= 3.5 ~ "Approaching harvest", - field_age_weeks >= 40 & mean_ci >= 3 ~ "Mid-maturity", - field_age_weeks >= 12 ~ "Growing", - TRUE ~ "Early stage" - ) - - # Add to summary data - farm_health_data <- rbind(farm_health_data, data.frame( - field = field_name, - mean_ci = round(mean_ci, 2), - ci_change = round(ci_change, 2), - ci_uniformity = round(ci_uniformity, 2), - status = status, - anomaly_type = anomaly_type, - priority_level = priority_score, - age_weeks = field_age_weeks, - harvest_readiness = harvest_readiness, - stringsAsFactors = FALSE - )) - - }, error = function(e) { - safe_log(paste("Error processing field", field_name, ":", e$message), "ERROR") - # Add a placeholder row with Error status - farm_health_data <<- rbind(farm_health_data, data.frame( - field = field_name, - mean_ci = NA, - ci_change = NA, - ci_uniformity = NA, - status = "Unknown", - anomaly_type = "Unknown", - priority_level = 5, # Low priority since we don't know the status - age_weeks = NA, - harvest_readiness = "Unknown", - stringsAsFactors = FALSE - )) - }) - } - - # Make sure we have data for all fields - if (nrow(farm_health_data) == 0) { - safe_log("No farm health data was created", "ERROR") - stop("Failed to create farm health data") - } - - # Sort by priority level - farm_health_data <- farm_health_data %>% dplyr::arrange(priority_level, field) - - safe_log(paste("Successfully created farm health data for", nrow(farm_health_data), "fields")) - -}, error = function(e) { - safe_log(paste("Error creating farm health data:", e$message), "ERROR") - # Create an empty dataframe that can be filled by the verification chunk -}) -``` - -```{r verify_farm_health_data, message=FALSE, warning=FALSE, include=FALSE} -# Verify farm_health_data exists and has content -if (!exists("farm_health_data") || nrow(farm_health_data) == 0) { - safe_log("farm_health_data not found or empty, generating default data", "WARNING") - - # Create minimal fallback data - tryCatch({ - # Get fields from boundaries - fields <- unique(AllPivots0$field) - - # Create basic data frame with just field names - farm_health_data <- data.frame( - field = fields, - mean_ci = rep(NA, length(fields)), - ci_change = rep(NA, length(fields)), - ci_uniformity = rep(NA, length(fields)), - status = rep("Unknown", length(fields)), - anomaly_type = rep("Unknown", length(fields)), - priority_level = rep(5, length(fields)), # Low priority - age_weeks = rep(NA, length(fields)), - harvest_readiness = rep("Unknown", length(fields)), - stringsAsFactors = FALSE - ) - - safe_log("Created fallback farm_health_data with basic field information") - }, error = function(e) { - safe_log(paste("Error creating fallback farm_health_data:", e$message), "ERROR") - farm_health_data <<- data.frame( - field = character(), - mean_ci = numeric(), - ci_change = numeric(), - ci_uniformity = numeric(), - status = character(), - anomaly_type = character(), - priority_level = numeric(), - age_weeks = numeric(), - harvest_readiness = character(), - stringsAsFactors = FALSE - ) - }) -} -``` - -```{r calculate_farm_health, message=FALSE, warning=FALSE, include=FALSE} -# Calculate farm health summary metrics -tryCatch({ - # Generate farm health summary data - farm_health_data <- generate_farm_health_summary( - field_boundaries = AllPivots0, - ci_current = CI, - ci_previous = CI_m1, - harvesting_data = harvesting_data - ) - - # Log the summary data - safe_log(paste("Generated farm health summary with", nrow(farm_health_data), "fields")) - -}, error = function(e) { - safe_log(paste("Error in farm health calculation:", e$message), "ERROR") - # Create empty dataframe if calculation failed - farm_health_data <- data.frame( - field = character(), - mean_ci = numeric(), - ci_change = numeric(), - ci_uniformity = numeric(), - status = character(), - anomaly_type = character(), - priority_level = numeric(), - age_weeks = numeric(), - harvest_readiness = character(), - stringsAsFactors = FALSE - ) -}) -``` - -```{r advanced_analytics_functions, message=FALSE, warning=FALSE, include=FALSE} -# ADVANCED ANALYTICS FUNCTIONS -# Note: These functions are now imported from executive_report_utils.R -# The utility file contains functions for velocity/acceleration indicators, -# anomaly timeline creation, age cohort mapping, and cohort performance charts -safe_log("Using analytics functions from executive_report_utils.R") -``` - -\pagebreak -# Advanced Analytics - -## Field Health Velocity and Acceleration - -This visualization shows the rate of change in field health (velocity) and whether that change is speeding up or slowing down (acceleration). These metrics help identify if farm conditions are improving, stable, or deteriorating. - -**How to interpret:** -- **Velocity gauge:** Shows the average weekly change in CI values across all fields - - Positive values (green/right side): Farm health improving week-to-week - - Negative values (red/left side): Farm health declining week-to-week - -- **Acceleration gauge:** Shows whether the rate of change is increasing or decreasing - - Positive values (green/right side): Change is accelerating or improving faster - - Negative values (red/left side): Change is decelerating or slowing down - -- **4-Week Trend:** Shows the overall CI value trajectory for the past month - -```{r render_velocity_acceleration, echo=FALSE, fig.height=8, fig.width=10, message=FALSE, warning=FALSE} -# Render the velocity and acceleration indicators -tryCatch({ - # Create and display the indicators using the imported utility function - velocity_plot <- create_velocity_acceleration_indicator( - health_data = farm_health_data, - ci_current = CI, - ci_prev1 = CI_m1, - ci_prev2 = CI_m2, - ci_prev3 = CI_m3, - field_boundaries = AllPivots0 - ) - - # Print the visualization - print(velocity_plot) - - # Create a table of fields with significant velocity changes - field_ci_metrics <- list() - - # Process each field to get metrics - fields <- unique(AllPivots0$field) - for (field_name in fields) { - tryCatch({ - # Get field boundary - field_shape <- AllPivots0 %>% dplyr::filter(field == field_name) - if (nrow(field_shape) == 0) next - - # Extract CI values - ci_curr_values <- terra::extract(CI, field_shape) - ci_prev1_values <- terra::extract(CI_m1, field_shape) - - # Calculate metrics - mean_ci_curr <- mean(ci_curr_values$CI, na.rm = TRUE) - mean_ci_prev1 <- mean(ci_prev1_values$CI, na.rm = TRUE) - velocity <- mean_ci_curr - mean_ci_prev1 - - # Store in list - field_ci_metrics[[field_name]] <- list( - field = field_name, - ci_current = mean_ci_curr, - ci_prev1 = mean_ci_prev1, - velocity = velocity - ) - - }, error = function(e) { - safe_log(paste("Error processing field", field_name, "for velocity table:", e$message), "WARNING") - }) - } - - # Convert list to data frame - velocity_df <- do.call(rbind, lapply(field_ci_metrics, function(x) { - data.frame( - field = x$field, - ci_current = round(x$ci_current, 2), - ci_prev1 = round(x$ci_prev1, 2), - velocity = round(x$velocity, 2), - direction = ifelse(x$velocity >= 0, "Improving", "Declining") - ) - })) - - # Select top 5 positive and top 5 negative velocity fields - top_positive <- velocity_df %>% - dplyr::filter(velocity > 0) %>% - dplyr::arrange(desc(velocity)) %>% - dplyr::slice_head(n = 5) - - top_negative <- velocity_df %>% - dplyr::filter(velocity < 0) %>% - dplyr::arrange(velocity) %>% - dplyr::slice_head(n = 5) - - # Display the tables if we have data - if (nrow(top_positive) > 0) { - cat("

Fields with Fastest Improvement

") - knitr::kable(top_positive %>% - dplyr::select(Field = field, - `Current CI` = ci_current, - `Previous CI` = ci_prev1, - `Weekly Change` = velocity)) - } - - if (nrow(top_negative) > 0) { - cat("

Fields with Fastest Decline

") - knitr::kable(top_negative %>% - dplyr::select(Field = field, - `Current CI` = ci_current, - `Previous CI` = ci_prev1, - `Weekly Change` = velocity)) - } - -}, error = function(e) { - safe_log(paste("Error rendering velocity visualization:", e$message), "ERROR") - cat("
Error generating velocity visualization.
") -}) -``` - -\pagebreak -## Field Anomaly Timeline - -This visualization shows the history of detected anomalies in fields across the monitoring period. It helps identify persistent issues or improvements over time. - -**How to interpret:** -- **X-axis**: Dates of satellite observations -- **Y-axis**: Fields grouped by similar characteristics -- **Colors**: Red indicates negative anomalies, green indicates positive anomalies -- **Size**: Larger markers indicate stronger anomalies - -```{r anomaly_timeline, echo=FALSE, fig.height=8, fig.width=10, message=FALSE, warning=FALSE} -# Generate anomaly timeline visualization -tryCatch({ - # Use the imported function to create the anomaly timeline - anomaly_timeline <- create_anomaly_timeline( - field_boundaries = AllPivots0, - ci_data = CI_quadrant, - days_to_include = 90 # Show last 90 days of data - ) - - # Display the timeline - print(anomaly_timeline) - -}, error = function(e) { - safe_log(paste("Error generating anomaly timeline:", e$message), "ERROR") - cat("
Error generating anomaly timeline visualization.
") -}) -``` - -\pagebreak -## Field Age Cohorts Map - -This map shows fields grouped by their crop age (weeks since planting). Understanding the distribution of crop ages helps interpret performance metrics and plan harvest scheduling. - -**How to interpret:** -- **Colors**: Different colors represent different age groups (in weeks since planting) -- **Labels**: Each field is labeled with its name for easy reference -- **Legend**: Shows the age ranges in weeks and their corresponding colors - -```{r age_cohort_map, echo=FALSE, fig.height=8, fig.width=10, message=FALSE, warning=FALSE} -# Generate age cohort map -tryCatch({ - # Use the imported function to create the age cohort map - age_cohort_map <- create_age_cohort_map( - field_boundaries = AllPivots0, - harvesting_data = harvesting_data - ) - - # Display the map - print(age_cohort_map) - -}, error = function(e) { - safe_log(paste("Error generating age cohort map:", e$message), "ERROR") - cat("
Error generating age cohort map visualization.
") -}) -``` - -\pagebreak -## Cohort Performance Comparison - -This visualization compares chlorophyll index (CI) performance across different age groups of fields. This helps identify if certain age groups are performing better or worse than expected. - -**How to interpret:** -- **X-axis**: Field age groups in weeks since planting -- **Y-axis**: Average CI value for fields in that age group -- **Box plots**: Show the distribution of CI values within each age group -- **Line**: Shows the expected CI trajectory based on historical data - -```{r cohort_performance_chart, echo=FALSE, fig.height=8, fig.width=10, message=FALSE, warning=FALSE} -# Generate cohort performance comparison chart -tryCatch({ - # Use the imported function to create the cohort performance chart - cohort_chart <- create_cohort_performance_chart( - field_boundaries = AllPivots0, - ci_current = CI, - harvesting_data = harvesting_data - ) - - # Display the chart - print(cohort_chart) - -}, error = function(e) { - safe_log(paste("Error generating cohort performance chart:", e$message), "ERROR") - cat("
Error generating cohort performance visualization.
") -}) -``` - - - - - - diff --git a/r_app/ci_extraction.R b/r_app/ci_extraction.R deleted file mode 100644 index 01d5d73..0000000 --- a/r_app/ci_extraction.R +++ /dev/null @@ -1,117 +0,0 @@ -# CI_EXTRACTION.R -# ============== -# This script processes satellite imagery to extract Canopy Index (CI) values for agricultural fields. -# It handles image processing, masking, and extraction of statistics by field/sub-field. -# -# Usage: Rscript ci_extraction.R [end_date] [offset] [project_dir] -# - end_date: End date for processing (YYYY-MM-DD format) -# - offset: Number of days to look back from end_date -# - project_dir: Project directory name (e.g., "chemba") -# - -# 1. Load required packages -# ----------------------- -suppressPackageStartupMessages({ - library(sf) - library(terra) - library(tidyverse) - library(lubridate) - library(exactextractr) - library(readxl) - library(here) -}) - -# 2. Process command line arguments -# ------------------------------ -main <- function() { - # Capture command line arguments - args <- commandArgs(trailingOnly = TRUE) - - # Process end_date argument - if (length(args) >= 1 && !is.na(args[1])) { - end_date <- as.Date(args[1]) - if (is.na(end_date)) { - warning("Invalid end_date provided. Using default (current date).") - end_date <- Sys.Date() - #end_date <- "2023-10-01" - } - } else { - end_date <- Sys.Date() - #end_date <- "2023-10-01" - } - - # Process offset argument - if (length(args) >= 2 && !is.na(args[2])) { - offset <- as.numeric(args[2]) - if (is.na(offset) || offset <= 0) { - warning("Invalid offset provided. Using default (7 days).") - offset <- 1095 - } - } else { - offset <- 1095 - } - - # Process project_dir argument - if (length(args) >= 3 && !is.na(args[3])) { - project_dir <- as.character(args[3]) - } else { - project_dir <- "aura" - } - - # Make project_dir available globally so parameters_project.R can use it - assign("project_dir", project_dir, envir = .GlobalEnv) - - - # 3. Initialize project configuration - # -------------------------------- - new_project_question <- TRUE - - tryCatch({ - source("parameters_project.R") - source("ci_extraction_utils.R") - }, error = function(e) { - warning("Default source files not found. Attempting to source from 'r_app' directory.") - tryCatch({ - source(here::here("r_app", "parameters_project.R")) - source(here::here("r_app", "ci_extraction_utils.R")) - warning(paste("Successfully sourced files from 'r_app' directory.")) - - }, error = function(e) { - stop("Failed to source required files from both default and 'r_app' directories.") - }) - }) - - # 4. Generate date list for processing - # --------------------------------- - dates <- date_list(end_date, offset) - log_message(paste("Processing data for week", dates$week, "of", dates$year)) - - # 5. Find and filter raster files by date - # ----------------------------------- - log_message("Searching for raster files") - - tryCatch({ - # Use the new utility function to find satellite images - existing_files <- find_satellite_images(planet_tif_folder, dates$days_filter) - log_message(paste("Found", length(existing_files), "raster files for processing")) - - # 6. Process raster files and create VRT - # ----------------------------------- - # Use the new utility function for batch processing - vrt_list <- process_satellite_images(existing_files, field_boundaries, merged_final, daily_vrt) - - # 7. Process and combine CI values - # ------------------------------ - # Call the process_ci_values function from utils with all required parameters - process_ci_values(dates, field_boundaries, merged_final, - field_boundaries_sf, daily_CI_vals_dir, cumulative_CI_vals_dir) - - }, error = function(e) { - log_message(paste("Error in main processing:", e$message), level = "ERROR") - stop(e$message) - }) -} - -if (sys.nframe() == 0) { - main() -} diff --git a/r_app/crop_messaging_utils.R b/r_app/crop_messaging_utils.R new file mode 100644 index 0000000..ae5324e --- /dev/null +++ b/r_app/crop_messaging_utils.R @@ -0,0 +1,1909 @@ +# CROP_MESSAGING_UTILS.R +# ====================== +# Utility functions for the SmartCane crop messaging workflow. +# These functions support crop analysis, messaging, and output generation. + +#' Convert hectares to acres +#' @param hectares Numeric value in hectares +#' @return Numeric value in acres +hectares_to_acres <- function(hectares) { + return(hectares * 2.47105) +} + +#' Format area with both hectares and acres +#' @param hectares Numeric value in hectares +#' @param precision Number of decimal places (default 1) +#' @return Character string with both measurements +format_area_both <- function(hectares, precision = 1) { + acres <- hectares_to_acres(hectares) + return(sprintf("%.1f ha (%.0f acres)", hectares, acres)) +} +#' @param message The message to log +#' @param level The log level (default: "INFO") +#' @return NULL (used for side effects) +#' +safe_log <- function(message, level = "INFO") { + if (exists("log_message")) { + log_message(message, level) + } else { + if (level %in% c("ERROR", "WARNING")) { + warning(message) + } else { + message(message) + } + } +} + +# 2. Analysis configuration +# ----------------------- +# Thresholds for change detection +CI_CHANGE_INCREASE_THRESHOLD <- 0.5 +CI_CHANGE_DECREASE_THRESHOLD <- -0.5 + +# Thresholds for field uniformity (coefficient of variation as decimal) +UNIFORMITY_THRESHOLD <- 0.15 # Below this = good uniformity, above = requires attention +EXCELLENT_UNIFORMITY_THRESHOLD <- 0.08 # Below this = excellent uniformity +POOR_UNIFORMITY_THRESHOLD <- 0.25 # Above this = poor uniformity, urgent attention needed + +# Thresholds for spatial clustering (adjusted for agricultural fields) +# Agricultural fields naturally have spatial autocorrelation, so higher thresholds are needed +MORAN_THRESHOLD_HIGH <- 0.95 # Above this = very strong clustering (problematic patterns) +MORAN_THRESHOLD_MODERATE <- 0.85 # Above this = moderate clustering +MORAN_THRESHOLD_LOW <- 0.7 # Above this = normal field continuity + +# Threshold for acceptable area percentage +ACCEPTABLE_AREA_THRESHOLD <- 40 # Below this percentage = management issue + +#' Calculate uniformity metrics using terra statistics (optimized) +#' @param mean_val Mean CI value from terra +#' @param sd_val Standard deviation from terra +#' @param median_val Median CI value from terra +#' @param min_val Minimum CI value from terra +#' @param max_val Maximum CI value from terra +#' @param values Raw values for quantile calculations only +#' @return List with various uniformity metrics (all scaled to be comparable) +calculate_uniformity_metrics_terra <- function(mean_val, sd_val, median_val, min_val, max_val, values) { + + if (is.na(mean_val) || length(values) < 2) return(list( + cv = NA, iqr_cv = NA, range_cv = NA, + mad_cv = NA, percentile_cv = NA, interpretation = "insufficient_data" + )) + + # 1. Coefficient of variation (from terra) - already normalized + cv <- sd_val / mean_val + + # 2. IQR-based CV (IQR/median) - using R's built-in IQR function + iqr_val <- IQR(values, na.rm = TRUE) + iqr_cv <- iqr_val / median_val + + # 3. Range-based CV (range/mean) - using terra min/max + range_val <- max_val - min_val + range_cv <- range_val / mean_val + + # 4. MAD-based CV (MAD/median) - using R's built-in mad function + mad_val <- mad(values, constant = 1.4826, na.rm = TRUE) # scaled to match SD for normal distribution + mad_cv <- mad_val / median_val + + # 5. Percentile-based CV (P90-P10)/mean - using R's built-in quantile + percentiles <- quantile(values, c(0.1, 0.9), na.rm = TRUE) + percentile_cv <- (percentiles[2] - percentiles[1]) / mean_val + + # Interpretation based on CV thresholds (all metrics now comparable) + # CV < 0.15 = Very uniform, 0.15-0.30 = Moderate variation, 0.30-0.50 = High variation, >0.50 = Very high variation + interpret_uniformity <- function(metric_value) { + if (is.na(metric_value)) return("unknown") + if (metric_value < 0.15) return("very uniform") + if (metric_value < 0.30) return("moderate variation") + if (metric_value < 0.50) return("high variation") + return("very high variation") + } + + return(list( + cv = cv, + iqr_cv = iqr_cv, + range_cv = range_cv, + mad_cv = mad_cv, + percentile_cv = percentile_cv, + cv_interpretation = interpret_uniformity(cv), + iqr_interpretation = interpret_uniformity(iqr_cv), + mad_interpretation = interpret_uniformity(mad_cv), + percentile_interpretation = interpret_uniformity(percentile_cv) + )) +} + +#' Calculate percentage within acceptable range using terra mean +#' Acceptable range = within 25% of the field mean CI value +#' This indicates what percentage of the field has "normal" performance +#' @param mean_val Mean CI value from terra +#' @param values Raw CI values +#' @param threshold_factor Factor to multiply mean by for acceptable range (default 0.25 = 25%) +#' @return Percentage of values within acceptable range +calculate_acceptable_percentage_terra <- function(mean_val, values, threshold_factor = 0.25) { + values <- values[!is.na(values) & is.finite(values)] + if (length(values) < 2 || is.na(mean_val)) return(NA) + + threshold <- mean_val * threshold_factor # 25% of mean as default + within_range <- abs(values - mean_val) <= threshold + percentage <- (sum(within_range) / length(values)) * 100 + return(percentage) +} + +#' Calculate coefficient of variation for uniformity assessment +#' @param values Numeric vector of CI values +#' @return Coefficient of variation (CV) as decimal +calculate_cv <- function(values) { + values <- values[!is.na(values) & is.finite(values)] + if (length(values) < 2) return(NA) + cv <- sd(values) / mean(values) # Keep as decimal + return(cv) +} + +#' Calculate Shannon entropy for spatial heterogeneity assessment +#' Higher entropy = more heterogeneous/variable field +#' Lower entropy = more homogeneous/uniform field +#' @param values Numeric vector of CI values +#' @param n_bins Number of bins for histogram (default 10) +#' @return Shannon entropy value +calculate_entropy <- function(values, n_bins = 10) { + values <- values[!is.na(values) & is.finite(values)] + if (length(values) < 2) return(NA) + + # Create histogram bins + value_range <- range(values) + breaks <- seq(value_range[1], value_range[2], length.out = n_bins + 1) + + # Count values in each bin + bin_counts <- hist(values, breaks = breaks, plot = FALSE)$counts + + # Calculate probabilities (remove zero counts) + probabilities <- bin_counts[bin_counts > 0] / sum(bin_counts) + + # Calculate Shannon entropy: H = -sum(p * log(p)) + entropy <- -sum(probabilities * log(probabilities)) + + return(entropy) +} + +#' Calculate percentage of field with positive vs negative change +#' @param current_values Current week CI values +#' @param previous_values Previous week CI values +#' @return List with percentage of positive and negative change areas +calculate_change_percentages <- function(current_values, previous_values) { + # Ensure same length (should be from same field boundaries) + if (length(current_values) != length(previous_values)) { + return(list(positive_pct = NA, negative_pct = NA, stable_pct = NA)) + } + + # Calculate pixel-wise change + change_values <- current_values - previous_values + valid_changes <- change_values[!is.na(change_values) & is.finite(change_values)] + + if (length(valid_changes) < 2) { + return(list(positive_pct = NA, negative_pct = NA, stable_pct = NA)) + } + + # Count positive, negative, and stable areas + positive_pct <- sum(valid_changes > 0) / length(valid_changes) * 100 + negative_pct <- sum(valid_changes < 0) / length(valid_changes) * 100 + stable_pct <- sum(valid_changes == 0) / length(valid_changes) * 100 + + return(list( + positive_pct = positive_pct, + negative_pct = negative_pct, + stable_pct = stable_pct + )) +} + +#' Calculate spatial autocorrelation (Moran's I) for a field +#' @param ci_raster Terra raster of CI values +#' @param field_boundary Terra vector of field boundary +#' @return List with Moran's I statistic and p-value +calculate_spatial_autocorrelation <- function(ci_raster, field_boundary) { + + tryCatch({ + # Crop and mask raster to field boundary + field_raster <- terra::crop(ci_raster, field_boundary) + field_raster <- terra::mask(field_raster, field_boundary) + + # Convert to points for spatial analysis + raster_points <- terra::as.points(field_raster, na.rm = TRUE) + + # Check if we have enough points + if (length(raster_points) < 10) { + return(list(morans_i = NA, p_value = NA, interpretation = "insufficient_data")) + } + + # Convert to sf for spdep + points_sf <- sf::st_as_sf(raster_points) + + # Create spatial weights matrix (k-nearest neighbors) + coords <- sf::st_coordinates(points_sf) + + # Use adaptive number of neighbors based on sample size + k_neighbors <- min(8, max(4, floor(nrow(coords) / 10))) + + knn_nb <- spdep::knearneigh(coords, k = k_neighbors) + knn_listw <- spdep::nb2listw(spdep::knn2nb(knn_nb), style = "W", zero.policy = TRUE) + + # Calculate Moran's I + ci_values <- points_sf[[1]] # First column contains CI values + moran_result <- spdep::moran.test(ci_values, knn_listw, zero.policy = TRUE) + + # Interpret results + morans_i <- moran_result$estimate[1] + p_value <- moran_result$p.value + + interpretation <- if (is.na(morans_i)) { + "insufficient_data" + } else if (p_value > 0.05) { + "random" # Not significant spatial pattern + } else if (morans_i > MORAN_THRESHOLD_HIGH) { + "very_strong_clustering" # Very strong clustering - may indicate management issues + } else if (morans_i > MORAN_THRESHOLD_MODERATE) { + "strong_clustering" # Strong clustering - worth monitoring + } else if (morans_i > MORAN_THRESHOLD_LOW) { + "normal_continuity" # Normal field continuity - expected for uniform fields + } else if (morans_i > 0.3) { + "weak_clustering" # Some clustering present + } else if (morans_i < -0.3) { + "dispersed" # Checkerboard pattern + } else { + "low_autocorrelation" # Low spatial autocorrelation + } + + return(list( + morans_i = morans_i, + p_value = p_value, + interpretation = interpretation + )) + + }, error = function(e) { + warning(paste("Error calculating spatial autocorrelation:", e$message)) + return(list(morans_i = NA, p_value = NA, interpretation = "error")) + }) +} + +#' Calculate percentage of field in extreme values using simple threshold +#' Hotspots = areas with CI > mean + 1.5*SD (high-performing areas) +#' Coldspots = areas with CI < mean - 1.5*SD (underperforming areas) +#' @param values Numeric vector of CI values +#' @param threshold_multiplier Standard deviation multiplier (default 1.5) +#' @return List with percentage of hotspots and coldspots +calculate_extreme_percentages_simple <- function(values, threshold_multiplier = 1.5) { + + if (length(values) < 10) return(list(hotspot_pct = NA, coldspot_pct = NA, method = "insufficient_data")) + + mean_val <- mean(values, na.rm = TRUE) + sd_val <- sd(values, na.rm = TRUE) + + # Hotspots: significantly ABOVE average (good performance) + upper_threshold <- mean_val + (threshold_multiplier * sd_val) + # Coldspots: significantly BELOW average (poor performance) + lower_threshold <- mean_val - (threshold_multiplier * sd_val) + + hotspot_pct <- sum(values > upper_threshold, na.rm = TRUE) / length(values) * 100 + coldspot_pct <- sum(values < lower_threshold, na.rm = TRUE) / length(values) * 100 + + return(list( + hotspot_pct = hotspot_pct, + coldspot_pct = coldspot_pct, + method = "simple_threshold", + threshold_used = threshold_multiplier + )) +} + +#' Categorize CI change based on thresholds +#' @param change_value Mean change in CI between weeks +#' @return Character string: "increase", "stable", or "decrease" +categorize_change <- function(change_value) { + if (is.na(change_value)) return("unknown") + if (change_value >= CI_CHANGE_INCREASE_THRESHOLD) return("increase") + if (change_value <= CI_CHANGE_DECREASE_THRESHOLD) return("decrease") + return("stable") +} + +#' Categorize field uniformity based on coefficient of variation and spatial pattern +#' @param cv_value Coefficient of variation (primary uniformity metric) +#' @param spatial_info List with spatial autocorrelation results +#' @param extreme_pct List with hotspot/coldspot percentages +#' @param acceptable_pct Percentage of field within acceptable range +#' @return Character string describing field uniformity pattern +categorize_uniformity_enhanced <- function(cv_value, spatial_info, extreme_pct, acceptable_pct = NA) { + + if (is.na(cv_value)) return("unknown variation") + + # Check for poor uniformity first (urgent issues) + if (cv_value > POOR_UNIFORMITY_THRESHOLD || (!is.na(acceptable_pct) && acceptable_pct < ACCEPTABLE_AREA_THRESHOLD)) { + return("poor uniformity - urgent attention needed") + } + + # Check for excellent uniformity + if (cv_value <= EXCELLENT_UNIFORMITY_THRESHOLD && (!is.na(acceptable_pct) && acceptable_pct >= 45)) { + return("excellent uniformity") + } + + # Check for good uniformity + if (cv_value <= UNIFORMITY_THRESHOLD) { + return("good uniformity") + } + + # Field has moderate variation - determine if localized or distributed + spatial_pattern <- spatial_info$interpretation + hotspot_pct <- extreme_pct$hotspot_pct + coldspot_pct <- extreme_pct$coldspot_pct + + # Determine pattern type based on CV (primary) and spatial pattern (secondary) + if (spatial_pattern %in% c("very_strong_clustering") && !is.na(hotspot_pct) && (hotspot_pct > 15 || coldspot_pct > 5)) { + # Very strong clustering with substantial extreme areas - likely problematic + if (hotspot_pct > coldspot_pct) { + return("localized high-performing areas") + } else if (coldspot_pct > hotspot_pct) { + return("localized problem areas") + } else { + return("localized hotspots and coldspots") + } + } else if (spatial_pattern %in% c("strong_clustering") && !is.na(hotspot_pct) && (hotspot_pct > 10 || coldspot_pct > 3)) { + # Strong clustering with moderate extreme areas + if (hotspot_pct > coldspot_pct) { + return("localized high-performing areas") + } else if (coldspot_pct > hotspot_pct) { + return("localized problem areas") + } else { + return("clustered variation") + } + } else { + # Normal field continuity or weak patterns - rely primarily on CV + return("moderate variation") + } +} + +#' Generate enhanced message based on analysis results including spatial patterns +#' @param uniformity_category Character: enhanced uniformity category with spatial info +#' @param change_category Character: "increase", "stable", or "decrease" +#' @param extreme_pct List with hotspot/coldspot percentages +#' @param acceptable_pct Percentage of field within acceptable range +#' @param morans_i Moran's I value for additional context +#' @param growth_stage Character: growth stage (simplified for now) +#' @return List with message and worth_sending flag +generate_enhanced_message <- function(uniformity_category, change_category, extreme_pct, acceptable_pct = NA, morans_i = NA, growth_stage = "vegetation stage") { + + # Enhanced message matrix based on spatial patterns + messages <- list() + + # Poor uniformity scenarios (urgent) + if (uniformity_category == "poor uniformity - urgent attention needed") { + messages <- list( + "stable" = list( + message = "🚨 URGENT: Poor field uniformity detected - immediate management review required", + worth_sending = TRUE + ), + "decrease" = list( + message = "🚨 CRITICAL: Poor uniformity with declining trend - emergency intervention needed", + worth_sending = TRUE + ), + "increase" = list( + message = "⚠️ CAUTION: Improving but still poor uniformity - continue intensive monitoring", + worth_sending = TRUE + ) + ) + } + + # Excellent uniformity scenarios + else if (uniformity_category == "excellent uniformity") { + messages <- list( + "stable" = list( + message = "✅ Excellent: Optimal field uniformity and stability", + worth_sending = FALSE + ), + "decrease" = list( + message = "⚠️ Alert: Excellent uniformity but declining - investigate cause early", + worth_sending = TRUE + ), + "increase" = list( + message = "🌟 Outstanding: Excellent uniformity with continued improvement", + worth_sending = FALSE + ) + ) + } + + # Good uniformity scenarios + else if (uniformity_category == "good uniformity") { + # Check for very strong clustering which may indicate management issues + if (!is.na(morans_i) && morans_i > MORAN_THRESHOLD_HIGH) { + messages <- list( + "stable" = list( + message = "⚠️ Alert: Good uniformity but very strong clustering detected - check management practices", + worth_sending = TRUE + ), + "decrease" = list( + message = "🚨 Alert: Good uniformity declining with clustering patterns - targeted intervention needed", + worth_sending = TRUE + ), + "increase" = list( + message = "✅ Good: Improving uniformity but monitor clustering patterns", + worth_sending = FALSE + ) + ) + } else { + messages <- list( + "stable" = list( + message = "✅ Good: Stable field with good uniformity", + worth_sending = FALSE + ), + "decrease" = list( + message = "⚠️ Alert: Good uniformity but declining trend - early intervention recommended", + worth_sending = TRUE + ), + "increase" = list( + message = "✅ Great: Good uniformity with improvement trend", + worth_sending = FALSE + ) + ) + } + } + + # Moderate variation scenarios + else if (uniformity_category == "moderate variation") { + acceptable_msg <- if (!is.na(acceptable_pct) && acceptable_pct < 45) " - low acceptable area" else "" + + messages <- list( + "stable" = list( + message = paste0("⚠️ Alert: Moderate field variation detected", acceptable_msg, " - review management uniformity"), + worth_sending = TRUE + ), + "decrease" = list( + message = paste0("🚨 Alert: Moderate variation with declining trend", acceptable_msg, " - intervention needed"), + worth_sending = TRUE + ), + "increase" = list( + message = paste0("📈 Monitor: Improving but still moderate variation", acceptable_msg, " - continue optimization"), + worth_sending = FALSE + ) + ) + } + + # Localized problem areas + else if (uniformity_category == "localized problem areas") { + hotspot_pct <- round(extreme_pct$hotspot_pct, 1) + coldspot_pct <- round(extreme_pct$coldspot_pct, 1) + + messages <- list( + "stable" = list( + message = paste0("🚨 Alert: Problem zones detected (", coldspot_pct, "% underperforming) - targeted intervention needed"), + worth_sending = TRUE + ), + "decrease" = list( + message = paste0("🚨 URGENT: Problem areas expanding with overall decline (", coldspot_pct, "% affected) - immediate action required"), + worth_sending = TRUE + ), + "increase" = list( + message = paste0("⚠️ Caution: Overall improvement but ", coldspot_pct, "% problem areas remain - monitor closely"), + worth_sending = TRUE + ) + ) + } + + # Localized high-performing areas + else if (uniformity_category == "localized high-performing areas") { + hotspot_pct <- round(extreme_pct$hotspot_pct, 1) + + messages <- list( + "stable" = list( + message = paste0("💡 Opportunity: ", hotspot_pct, "% of field performing well - replicate conditions in remaining areas"), + worth_sending = FALSE + ), + "decrease" = list( + message = paste0("⚠️ Alert: High-performing areas (", hotspot_pct, "%) declining - investigate cause to prevent spread"), + worth_sending = TRUE + ), + "increase" = list( + message = paste0("🌟 Excellent: High-performing areas (", hotspot_pct, "%) expanding - excellent management practices"), + worth_sending = FALSE + ) + ) + } + + # Clustered variation (general) + else if (uniformity_category == "clustered variation") { + messages <- list( + "stable" = list( + message = "⚠️ Alert: Clustered variation detected - investigate spatial management patterns", + worth_sending = TRUE + ), + "decrease" = list( + message = "🚨 Alert: Clustered decline pattern - targeted investigation needed", + worth_sending = TRUE + ), + "increase" = list( + message = "📈 Monitor: Clustered improvement - identify and replicate successful practices", + worth_sending = FALSE + ) + ) + } + + # Default fallback + else { + messages <- list( + "stable" = list(message = "❓ Field analysis inconclusive - manual review recommended", worth_sending = FALSE), + "decrease" = list(message = "⚠️ Field showing decline - investigation recommended", worth_sending = TRUE), + "increase" = list(message = "📈 Field showing improvement", worth_sending = FALSE) + ) + } + + # Return appropriate message + if (change_category %in% names(messages)) { + return(messages[[change_category]]) + } else { + return(list( + message = paste("❓ Analysis inconclusive -", uniformity_category, "with", change_category, "trend"), + worth_sending = FALSE + )) + } +} + +#' Load and analyze a weekly mosaic for individual fields with spatial analysis +#' @param week_file_path Path to the weekly mosaic file +#' @param field_boundaries_sf SF object with field boundaries +#' @return List with CI statistics per field including spatial metrics +analyze_weekly_mosaic <- function(week_file_path, field_boundaries_sf) { + + if (!file.exists(week_file_path)) { + warning(paste("Mosaic file not found:", week_file_path)) + return(NULL) + } + + tryCatch({ + # Load the raster and select only the CI band (5th band) + mosaic_raster <- terra::rast(week_file_path) + ci_raster <- mosaic_raster[[5]] # Select the CI band + names(ci_raster) <- "CI" + + # Convert field boundaries to terra vect for extraction + field_boundaries_vect <- terra::vect(field_boundaries_sf) + + # Extract CI values for each field + field_results <- list() + + for (i in seq_len(nrow(field_boundaries_sf))) { + field_name <- field_boundaries_sf$field[i] + sub_field_name <- field_boundaries_sf$sub_field[i] + + # Check and get field area from geojson if available + field_area_ha <- NA + if ("area_ha" %in% colnames(field_boundaries_sf)) { + field_area_ha <- field_boundaries_sf$area_ha[i] + } else if ("AREA_HA" %in% colnames(field_boundaries_sf)) { + field_area_ha <- field_boundaries_sf$AREA_HA[i] + } else if ("area" %in% colnames(field_boundaries_sf)) { + field_area_ha <- field_boundaries_sf$area[i] + } else { + # Calculate area from geometry as fallback + field_geom <- field_boundaries_sf[i,] + if (sf::st_is_longlat(field_geom)) { + # For geographic coordinates, transform to projected for area calculation + field_geom <- sf::st_transform(field_geom, 3857) # Web Mercator + } + field_area_ha <- as.numeric(sf::st_area(field_geom)) / 10000 # Convert to hectares + } + + cat("Processing field:", field_name, "-", sub_field_name, "(", round(field_area_ha, 1), "ha)\n") + + # Extract values for this specific field + field_vect <- field_boundaries_vect[i] + + # Extract with built-in statistics from terra (PRIMARY METHOD) + terra_stats <- terra::extract(ci_raster, field_vect, fun = c("mean", "sd", "min", "max", "median"), na.rm = TRUE) + + # Extract raw values for additional calculations and validation + ci_values <- terra::extract(ci_raster, field_vect, fun = NULL) + + # Flatten and clean the values + field_values <- unlist(ci_values) + valid_values <- field_values[!is.na(field_values) & is.finite(field_values)] + + if (length(valid_values) > 0) { + + # Use TERRA as primary calculations + primary_mean <- terra_stats$mean[1] + primary_sd <- terra_stats$sd[1] + primary_cv <- primary_sd / primary_mean + primary_median <- terra_stats$median[1] + primary_min <- terra_stats$min[1] + primary_max <- terra_stats$max[1] + + # Manual calculations for validation only + manual_mean <- mean(valid_values) + manual_cv <- sd(valid_values) / manual_mean + + basic_stats <- list( + field = field_name, + sub_field = sub_field_name, + # PRIMARY statistics (terra-based) + mean_ci = primary_mean, + median_ci = primary_median, + sd_ci = primary_sd, + cv = primary_cv, + min_ci = primary_min, + max_ci = primary_max, + # Store raw values for change analysis + raw_values = valid_values, + # Other metrics using terra values + acceptable_pct = calculate_acceptable_percentage_terra(primary_mean, valid_values), + n_pixels = length(valid_values), + # Field area from geojson + field_area_ha = field_area_ha + ) + + # Calculate spatial statistics + spatial_info <- calculate_spatial_autocorrelation(ci_raster, field_vect) + extreme_pct <- calculate_extreme_percentages_simple(valid_values) + + # Calculate entropy for additional uniformity measure + entropy_value <- calculate_entropy(valid_values) + + # Enhanced uniformity categorization + uniformity_category <- categorize_uniformity_enhanced( + basic_stats$cv, + spatial_info, + extreme_pct, + basic_stats$acceptable_pct + ) + + # Combine all results + field_stats <- c( + basic_stats, + list( + spatial_autocorr = spatial_info, + extreme_percentages = extreme_pct, + entropy = entropy_value, + uniformity_category = uniformity_category + ) + ) + + field_results[[paste0(field_name, "_", sub_field_name)]] <- field_stats + + } else { + warning(paste("No valid CI values found for field:", field_name, sub_field_name)) + } + } + + return(field_results) + + }, error = function(e) { + warning(paste("Error analyzing mosaic:", e$message)) + return(NULL) + }) +} + +#' Run crop analysis for any estate +#' @param estate_name Character: name of the estate (e.g., "simba", "chemba") +#' @param current_week Numeric: current week number +#' @param previous_week Numeric: previous week number +#' @param year Numeric: year (default 2025) +#' @return List with analysis results +run_estate_analysis <- function(estate_name, current_week, previous_week, year = 2025) { + + cat("=== CROP ANALYSIS MESSAGING SYSTEM ===\n") + cat("Analyzing:", toupper(estate_name), "estate\n") + cat("Comparing week", previous_week, "vs week", current_week, "of", year, "\n\n") + + # Set project_dir globally for parameters_project.R + assign("project_dir", estate_name, envir = .GlobalEnv) + + # Load project configuration + tryCatch({ + source("parameters_project.R") + cat("✓ Project configuration loaded\n") + }, error = function(e) { + tryCatch({ + source(here::here("r_app", "parameters_project.R")) + cat("✓ Project configuration loaded from r_app directory\n") + }, error = function(e) { + stop("Failed to load project configuration") + }) + }) + + # Verify required variables are available + if (!exists("weekly_CI_mosaic") || !exists("field_boundaries_sf")) { + stop("Required project variables not initialized. Check project configuration.") + } + + # Construct file paths for weekly mosaics + current_week_file <- sprintf("week_%02d_%d.tif", current_week, year) + previous_week_file <- sprintf("week_%02d_%d.tif", previous_week, year) + + current_week_path <- file.path(weekly_CI_mosaic, current_week_file) + previous_week_path <- file.path(weekly_CI_mosaic, previous_week_file) + + cat("Looking for files:\n") + cat("- Current week:", current_week_path, "\n") + cat("- Previous week:", previous_week_path, "\n\n") + + # Check if files exist and handle missing data scenarios + current_exists <- file.exists(current_week_path) + previous_exists <- file.exists(previous_week_path) + + if (!current_exists) { + cat("❌ Current week mosaic not found. No analysis possible.\n") + return(NULL) + } + + # Analyze both weeks for all fields + cat("Analyzing weekly mosaics per field...\n") + current_field_stats <- analyze_weekly_mosaic(current_week_path, field_boundaries_sf) + + if (!previous_exists) { + cat("⚠️ Previous week mosaic not found (likely due to clouds). Performing spatial-only analysis.\n") + previous_field_stats <- NULL + } else { + previous_field_stats <- analyze_weekly_mosaic(previous_week_path, field_boundaries_sf) + } + + if (is.null(current_field_stats)) { + stop("Could not analyze current weekly mosaic") + } + + # Generate field results + field_results <- generate_field_results(current_field_stats, previous_field_stats, current_week, previous_week) + + return(list( + estate_name = estate_name, + current_week = current_week, + previous_week = previous_week, + year = year, + field_results = field_results, + current_field_stats = current_field_stats, + previous_field_stats = previous_field_stats + )) +} + +#' Generate analysis results for all fields +#' @param current_field_stats Analysis results for current week +#' @param previous_field_stats Analysis results for previous week +#' @param current_week Current week number +#' @param previous_week Previous week number +#' @return List with field results +generate_field_results <- function(current_field_stats, previous_field_stats, current_week, previous_week) { + + field_results <- list() + + # Get common field names between both weeks (or all current fields if previous is missing) + if (!is.null(previous_field_stats)) { + common_fields <- intersect(names(current_field_stats), names(previous_field_stats)) + } else { + common_fields <- names(current_field_stats) + } + + for (field_id in common_fields) { + current_field <- current_field_stats[[field_id]] + previous_field <- if (!is.null(previous_field_stats)) previous_field_stats[[field_id]] else NULL + + # Calculate change metrics for this field (only if previous data exists) + if (!is.null(previous_field)) { + ci_change <- current_field$mean_ci - previous_field$mean_ci + change_category <- categorize_change(ci_change) + + # Calculate spatial change percentages + change_percentages <- calculate_change_percentages( + current_field$raw_values, + previous_field$raw_values + ) + } else { + # No previous data - spatial analysis only + ci_change <- NA + change_category <- "spatial_only" + change_percentages <- list(positive_pct = NA, negative_pct = NA, stable_pct = NA) + } + + # Use enhanced uniformity category from current week analysis + uniformity_category <- current_field$uniformity_category + + # Generate enhanced message for this field + message_result <- generate_enhanced_message( + uniformity_category, + change_category, + current_field$extreme_percentages, + current_field$acceptable_pct, + current_field$spatial_autocorr$morans_i + ) + + # Store results + field_results[[field_id]] <- list( + current_stats = current_field, + previous_stats = previous_field, + ci_change = ci_change, + change_category = change_category, + change_percentages = change_percentages, + uniformity_category = uniformity_category, + message_result = message_result + ) + } + + return(field_results) +} + +#' Format analysis results for WhatsApp/Word copy-paste +#' @param analysis_results Results from run_estate_analysis +#' @return Character string with formatted text +format_for_whatsapp <- function(analysis_results) { + + field_results <- analysis_results$field_results + estate_name <- toupper(analysis_results$estate_name) + current_week <- analysis_results$current_week + previous_week <- analysis_results$previous_week + + output <- c() + output <- c(output, paste("🌾", estate_name, "CROP ANALYSIS")) + output <- c(output, paste("📅 Week", current_week, "vs Week", previous_week)) + output <- c(output, "") + + # Summary statistics + alert_count <- sum(sapply(field_results, function(x) x$message_result$worth_sending)) + total_fields <- length(field_results) + + # Calculate total area and area statistics + total_hectares <- sum(sapply(field_results, function(x) x$current_stats$field_area_ha), na.rm = TRUE) + + output <- c(output, "📊 SUMMARY:") + output <- c(output, paste("• Estate:", estate_name)) + output <- c(output, paste("• Fields analyzed:", total_fields)) + output <- c(output, paste("• Total area:", format_area_both(total_hectares))) + output <- c(output, paste("• Alerts needed:", alert_count)) + output <- c(output, "") + + # Field-by-field alerts only + if (alert_count > 0) { + output <- c(output, "🚨 PRIORITY FIELDS:") + for (field_id in names(field_results)) { + field_info <- field_results[[field_id]] + if (field_info$message_result$worth_sending) { + field_name <- paste(field_info$current_stats$field, field_info$current_stats$sub_field, sep="-") + area <- field_info$current_stats$field_area_ha + message <- field_info$message_result$message + + output <- c(output, paste("•", field_name, paste0("(", format_area_both(area), "):"), message)) + } + } + } else { + output <- c(output, "✅ No urgent alerts - all fields stable") + } + + # Quick farm summary + output <- c(output, "") + output <- c(output, "📈 QUICK STATS:") + + # Calculate improving vs declining areas (only if temporal data available) + has_temporal_data <- any(sapply(field_results, function(x) !is.na(x$change_percentages$positive_pct))) + + if (has_temporal_data) { + total_improving <- sum(sapply(field_results, function(x) { + if (!is.na(x$change_percentages$positive_pct)) { + (x$change_percentages$positive_pct / 100) * x$current_stats$field_area_ha + } else 0 + }), na.rm = TRUE) + + total_declining <- sum(sapply(field_results, function(x) { + if (!is.na(x$change_percentages$negative_pct)) { + (x$change_percentages$negative_pct / 100) * x$current_stats$field_area_ha + } else 0 + }), na.rm = TRUE) + + total_stable <- sum(sapply(field_results, function(x) { + if (!is.na(x$change_percentages$stable_pct)) { + (x$change_percentages$stable_pct / 100) * x$current_stats$field_area_ha + } else 0 + }), na.rm = TRUE) + + improving_pct <- (total_improving / total_hectares) * 100 + declining_pct <- (total_declining / total_hectares) * 100 + stable_pct <- (total_stable / total_hectares) * 100 + + output <- c(output, paste("• Improving areas:", format_area_both(total_improving), paste0("(", round(improving_pct, 1), "%)"))) + output <- c(output, paste("• Declining areas:", format_area_both(total_declining), paste0("(", round(declining_pct, 1), "%)"))) + output <- c(output, paste("• Stable areas:", format_area_both(total_stable), paste0("(", round(stable_pct, 1), "%)"))) + + # Overall trend + if (improving_pct > declining_pct) { + trend_diff <- round(improving_pct - declining_pct, 1) + output <- c(output, paste("• Trend: ✅ POSITIVE (+", trend_diff, "%)")) + } else if (declining_pct > improving_pct) { + trend_diff <- round(declining_pct - improving_pct, 1) + output <- c(output, paste("• Trend: ⚠️ NEGATIVE (-", trend_diff, "%)")) + } else { + output <- c(output, "• Trend: ➖ BALANCED") + } + } else { + output <- c(output, "• Analysis: Spatial patterns only (previous week data unavailable)") + } + + # Add farm-wide analysis summary + output <- c(output, "") + output <- c(output, "=== FARM-WIDE ANALYSIS SUMMARY ===") + output <- c(output, "") + + # Field uniformity statistics + excellent_fields <- sum(sapply(field_results, function(x) x$current_stats$cv <= 0.08)) + good_fields <- sum(sapply(field_results, function(x) x$current_stats$cv > 0.08 & x$current_stats$cv <= 0.15)) + moderate_fields <- sum(sapply(field_results, function(x) x$current_stats$cv > 0.15 & x$current_stats$cv <= 0.30)) + poor_fields <- sum(sapply(field_results, function(x) x$current_stats$cv > 0.30)) + + output <- c(output, "FIELD UNIFORMITY SUMMARY:") + output <- c(output, "│ Uniformity Level │ Count │ Percent │") + output <- c(output, sprintf("│ Excellent (CV≤0.08) │ %5d │ %6.1f%% │", excellent_fields, (excellent_fields/total_fields)*100)) + output <- c(output, sprintf("│ Good (CV 0.08-0.15) │ %5d │ %6.1f%% │", good_fields, (good_fields/total_fields)*100)) + output <- c(output, sprintf("│ Moderate (CV 0.15-0.30) │ %5d │ %6.1f%% │", moderate_fields, (moderate_fields/total_fields)*100)) + output <- c(output, sprintf("│ Poor (CV>0.30) │ %5d │ %6.1f%% │", poor_fields, (poor_fields/total_fields)*100)) + output <- c(output, sprintf("│ Total fields │ %5d │ %6.1f%% │", total_fields, 100.0)) + output <- c(output, "") + + # Farm-wide area change summary + output <- c(output, "FARM-WIDE AREA CHANGE SUMMARY:") + output <- c(output, "│ Change Type │ Area (ha/acres) │ Percent │") + + if (has_temporal_data) { + output <- c(output, sprintf("│ Improving areas │ %s │ %6.1f%% │", format_area_both(total_improving), improving_pct)) + output <- c(output, sprintf("│ Stable areas │ %s │ %6.1f%% │", format_area_both(total_stable), stable_pct)) + output <- c(output, sprintf("│ Declining areas │ %s │ %6.1f%% │", format_area_both(total_declining), declining_pct)) + output <- c(output, sprintf("│ Total area │ %s │ %6.1f%% │", format_area_both(total_hectares), 100.0)) + } else { + output <- c(output, "│ Improving areas │ N/A │ N/A │") + output <- c(output, "│ Stable areas │ N/A │ N/A │") + output <- c(output, "│ Declining areas │ N/A │ N/A │") + output <- c(output, sprintf("│ Total area │ %s │ %6.1f%% │", format_area_both(total_hectares), 100.0)) + } + output <- c(output, "") + + # Key insights + output <- c(output, "KEY INSIGHTS:") + good_uniformity_pct <- ((excellent_fields + good_fields) / total_fields) * 100 + excellent_uniformity_pct <- (excellent_fields / total_fields) * 100 + + output <- c(output, sprintf("• %d%% of fields have good uniformity (CV ≤ 0.15)", round(good_uniformity_pct))) + output <- c(output, sprintf("• %d%% of fields have excellent uniformity (CV ≤ 0.08)", round(excellent_uniformity_pct))) + + if (has_temporal_data) { + output <- c(output, sprintf("• %s (%.1f%%) of farm area is improving week-over-week", format_area_both(total_improving), improving_pct)) + output <- c(output, sprintf("• %s (%.1f%%) of farm area is stable week-over-week", format_area_both(total_stable), stable_pct)) + output <- c(output, sprintf("• %s (%.1f%%) of farm area is declining week-over-week", format_area_both(total_declining), declining_pct)) + output <- c(output, sprintf("• Total farm area analyzed: %s", format_area_both(total_hectares))) + + if (improving_pct > declining_pct) { + trend_diff <- round(improving_pct - declining_pct, 1) + output <- c(output, sprintf("• Overall trend: POSITIVE (%.1f%% more area improving than declining)", trend_diff)) + } else if (declining_pct > improving_pct) { + trend_diff <- round(declining_pct - improving_pct, 1) + output <- c(output, sprintf("• Overall trend: NEGATIVE (%.1f%% more area declining than improving)", trend_diff)) + } else { + output <- c(output, "• Overall trend: BALANCED (equal improvement and decline)") + } + + # Add note about 0% decline potentially being due to missing data + if (declining_pct == 0) { + output <- c(output, "") + output <- c(output, "⚠️ IMPORTANT NOTE: 0% decline does NOT necessarily mean all crops are healthy.") + output <- c(output, "• This may be due to missing satellite data from the previous week (cloud cover)") + output <- c(output, "• Areas with clouds (CI=0) cannot be analyzed for decline") + output <- c(output, "• True decline levels may be higher than reported") + } + } else { + output <- c(output, "• Analysis: Spatial patterns only (previous week data unavailable)") + output <- c(output, "• Total farm area analyzed: %.1f hectares", total_hectares) + output <- c(output, "• Note: Due to clouds in previous week (CI=0), no decline measurements available") + output <- c(output, "• This does NOT mean fields didn't decline - only that no comparison data exists") + } + + # Add KPI Dashboard Tables + output <- c(output, "") + output <- c(output, "=== FARM KEY PERFORMANCE INDICATORS ===") + output <- c(output, "") + + # Table 1: Field Performance Distribution & Risk Assessment + output <- c(output, "FIELD PERFORMANCE INDICATORS") + output <- c(output, "________________________________________") + output <- c(output, "UNIFORMITY DISTRIBUTION: RISK ASSESSMENT:") + output <- c(output, "CV Category Count Percent Risk Level Count Percent") + + # Calculate risk levels based on CV + Moran's I combination + risk_low <- 0 + risk_moderate <- 0 + risk_high <- 0 + risk_very_high <- 0 + + for (field_id in names(field_results)) { + field_info <- field_results[[field_id]] + cv <- field_info$current_stats$cv + morans_i <- field_info$current_stats$spatial_autocorr$morans_i + + # Risk logic: Low CV + Low clustering = Low risk, High CV + High clustering = High risk + if (!is.na(cv) && !is.na(morans_i)) { + if (cv <= 0.10 && morans_i <= 0.8) { + risk_low <- risk_low + 1 + } else if (cv <= 0.20 && morans_i <= 0.9) { + risk_moderate <- risk_moderate + 1 + } else if (cv <= 0.30 || morans_i <= 0.95) { + risk_high <- risk_high + 1 + } else { + risk_very_high <- risk_very_high + 1 + } + } else { + risk_moderate <- risk_moderate + 1 # Default for missing data + } + } + + output <- c(output, sprintf("Excellent (CV≤0.08) %d %5.1f%% Low (CV≤0.10) %d %5.1f%%", + excellent_fields, (excellent_fields/total_fields)*100, risk_low, (risk_low/total_fields)*100)) + output <- c(output, sprintf("Good (CV 0.08-0.15) %d %5.1f%% Moderate (0.10-0.20) %d %5.1f%%", + good_fields, (good_fields/total_fields)*100, risk_moderate, (risk_moderate/total_fields)*100)) + output <- c(output, sprintf("Moderate (0.15-0.30) %d %5.1f%% High (0.20-0.30) %d %5.1f%%", + moderate_fields, (moderate_fields/total_fields)*100, risk_high, (risk_high/total_fields)*100)) + output <- c(output, sprintf("Poor (CV>0.30) %d %5.1f%% Very High (>0.30) %d %5.1f%%", + poor_fields, (poor_fields/total_fields)*100, risk_very_high, (risk_very_high/total_fields)*100)) + output <- c(output, sprintf("Total fields %d 100.0%% Total fields %d 100.0%%", + total_fields, total_fields)) + output <- c(output, "") + + # Performance quartiles and CI change patterns + if (has_temporal_data) { + # Calculate performance quartiles based on combination of current CI and change + field_performance <- sapply(field_results, function(x) { + current_ci <- x$current_stats$mean_ci + ci_change <- x$ci_change + # Combine current performance with improvement trend + performance_score <- current_ci + (ci_change * 0.5) # Weight change as 50% of current + return(performance_score) + }) + + sorted_performance <- sort(field_performance, decreasing = TRUE) + q75 <- quantile(sorted_performance, 0.75, na.rm = TRUE) + q25 <- quantile(sorted_performance, 0.25, na.rm = TRUE) + + top_quartile <- sum(field_performance >= q75, na.rm = TRUE) + bottom_quartile <- sum(field_performance <= q25, na.rm = TRUE) + middle_quartile <- total_fields - top_quartile - bottom_quartile + + avg_ci_top <- mean(sapply(field_results[field_performance >= q75], function(x) x$current_stats$mean_ci), na.rm = TRUE) + avg_ci_mid <- mean(sapply(field_results[field_performance > q25 & field_performance < q75], function(x) x$current_stats$mean_ci), na.rm = TRUE) + avg_ci_bot <- mean(sapply(field_results[field_performance <= q25], function(x) x$current_stats$mean_ci), na.rm = TRUE) + + output <- c(output, "PERFORMANCE QUARTILES: CI CHANGE PATTERNS:") + output <- c(output, "Quartile Count Avg CI Change Type Hectares Percent") + output <- c(output, sprintf("Top 25%% %d %4.1f Improving areas %5.1f ha %5.1f%%", + top_quartile, avg_ci_top, total_improving, improving_pct)) + output <- c(output, sprintf("Average (25-75%%) %d %4.1f Stable areas %5.1f ha %5.1f%%", + middle_quartile, avg_ci_mid, total_stable, stable_pct)) + output <- c(output, sprintf("Bottom 25%% %d %4.1f Declining areas %5.1f ha %5.1f%%", + bottom_quartile, avg_ci_bot, total_declining, declining_pct)) + output <- c(output, sprintf("Total fields %d %4.1f Total area %5.1f ha 100.0%%", + total_fields, mean(sapply(field_results, function(x) x$current_stats$mean_ci), na.rm = TRUE), total_hectares)) + } + output <- c(output, "") + + # Table 2: Anomaly Detection & Management Alerts + output <- c(output, "ANOMALY DETECTION & MANAGEMENT PRIORITIES") + output <- c(output, "________________________________________") + + # Weed detection (CI increase > 1.5) + weed_fields <- 0 + weed_area <- 0 + harvest_fields <- 0 + harvest_area <- 0 + fallow_fields <- 0 + fallow_area <- 0 + high_hotspot_fields <- 0 + high_hotspot_area <- 0 + + if (has_temporal_data) { + for (field_id in names(field_results)) { + field_info <- field_results[[field_id]] + ci_change <- field_info$ci_change + current_ci <- field_info$current_stats$mean_ci + area <- field_info$current_stats$field_area_ha + hotspots <- field_info$current_stats$extreme_percentages$hotspot_pct + + # Weed detection: CI increase > 1.5 + if (!is.na(ci_change) && ci_change > 1.5) { + weed_fields <- weed_fields + 1 + weed_area <- weed_area + area + } + + # Harvesting/theft detection: CI decrease > 1.5 + if (!is.na(ci_change) && ci_change < -1.5) { + harvest_fields <- harvest_fields + 1 + harvest_area <- harvest_area + area + } + + # Fallow detection: CI < 2.0 + if (!is.na(current_ci) && current_ci < 2.0) { + fallow_fields <- fallow_fields + 1 + fallow_area <- fallow_area + area + } + + # High hotspot detection: > 5% + if (!is.na(hotspots) && hotspots > 5.0) { + high_hotspot_fields <- high_hotspot_fields + 1 + high_hotspot_area <- high_hotspot_area + area + } + } + } + + output <- c(output, "WEED PRESENCE INDICATORS: HARVESTING/THEFT INDICATORS:") + output <- c(output, "High CI Increase (>1.5): High CI Decrease (>1.5):") + output <- c(output, "Fields to check Count Area Fields to check Count Area") + output <- c(output, sprintf("Potential weed areas %d %4.1f ha Potential harvesting %d %4.1f ha", + weed_fields, weed_area, harvest_fields, harvest_area)) + output <- c(output, sprintf("Total monitored fields %d %5.1f ha Total monitored fields%d %5.1f ha", + total_fields, total_hectares, total_fields, total_hectares)) + output <- c(output, "") + output <- c(output, "FALLOW FIELD DETECTION: HOTSPOT ANALYSIS:") + output <- c(output, "Low CI Fields (<2.0): Spatial Clustering:") + output <- c(output, "Fields to check Count Area High hotspot fields (>5%) Count Area") + output <- c(output, sprintf("Potential fallow %d %4.1f ha Spatial issues detected %d %4.1f ha", + fallow_fields, fallow_area, high_hotspot_fields, high_hotspot_area)) + output <- c(output, sprintf("Total catchment fields %d %5.1f ha Total analyzed fields %d %5.1f ha", + total_fields, total_hectares, total_fields, total_hectares)) + output <- c(output, "") + + # Table 3: Priority Action Items & Field Rankings + output <- c(output, "IMMEDIATE ACTION PRIORITIES") + output <- c(output, "________________________________________") + + # Find urgent and monitoring fields + urgent_fields <- sapply(field_results, function(x) x$message_result$worth_sending && grepl("URGENT", x$message_result$message)) + monitoring_fields <- sapply(field_results, function(x) x$message_result$worth_sending && !grepl("URGENT", x$message_result$message)) + + output <- c(output, "URGENT INTERVENTIONS: MONITORING REQUIRED:") + output <- c(output, "Field Name Issue Type Area Field Name Issue Type Area") + + urgent_count <- 0 + monitoring_count <- 0 + + for (field_id in names(field_results)) { + if (urgent_fields[field_id]) { + field_info <- field_results[[field_id]] + field_name <- paste(field_info$current_stats$field, field_info$current_stats$sub_field, sep="-") + if (nchar(field_name) > 15) field_name <- substr(field_name, 1, 15) + area <- field_info$current_stats$field_area_ha + if (urgent_count == 0) { + output <- c(output, sprintf("%-15s Poor uniformity %4.1f ha %-15s %-13s %4.1f ha", + field_name, area, "", "", 0.0)) + } + urgent_count <- urgent_count + 1 + } + + if (monitoring_fields[field_id]) { + field_info <- field_results[[field_id]] + field_name <- paste(field_info$current_stats$field, field_info$current_stats$sub_field, sep="-") + if (nchar(field_name) > 15) field_name <- substr(field_name, 1, 15) + area <- field_info$current_stats$field_area_ha + if (monitoring_count == 0) { + # Update the previous line to include monitoring field + last_line <- output[length(output)] + if (grepl("Poor uniformity", last_line) && grepl("0.0 ha$", last_line)) { + output[length(output)] <- sprintf("%-15s Poor uniformity %4.1f ha %-15s %-13s %4.1f ha", + sub(" .*", "", last_line), + as.numeric(sub(".*Poor uniformity ([0-9.]+) ha.*", "\\1", last_line)), + field_name, "Moderate var.", area) + } + } + monitoring_count <- monitoring_count + 1 + } + } + + if (urgent_count == 0 && monitoring_count == 0) { + output <- c(output, "No urgent interventions - - No monitoring required - -") + } + + output <- c(output, "") + + # Field performance ranking + if (has_temporal_data) { + output <- c(output, "FIELD PERFORMANCE RANKING: WEEKLY PRIORITIES:") + output <- c(output, "Rank Field Name CI Status Priority Level Fields Action Required") + + # Sort fields by performance score + field_names <- names(field_performance) + sorted_indices <- order(field_performance, decreasing = TRUE) + + priority_immediate <- sum(urgent_fields) + priority_weekly <- sum(monitoring_fields) + priority_routine <- total_fields - priority_immediate - priority_weekly + + for (i in 1:min(3, length(sorted_indices))) { + field_id <- field_names[sorted_indices[i]] + field_info <- field_results[[field_id]] + field_name <- paste(field_info$current_stats$field, field_info$current_stats$sub_field, sep="-") + if (nchar(field_name) > 12) field_name <- substr(field_name, 1, 12) + + ci <- field_info$current_stats$mean_ci + status <- if (field_info$current_stats$cv <= 0.08) "Excellent" else if (field_info$current_stats$cv <= 0.15) "Good" else "Caution" + + if (i == 1) { + output <- c(output, sprintf("%d %-12s %4.1f %-9s Immediate %d Field inspection", + i, field_name, ci, status, priority_immediate)) + } else if (i == 2) { + output <- c(output, sprintf("%d %-12s %4.1f %-9s This week %d Continue monitoring", + i, field_name, ci, status, priority_weekly)) + } else { + output <- c(output, sprintf("%d %-12s %4.1f %-9s Monitor %d Routine management", + i, field_name, ci, status, priority_routine)) + } + } + + output <- c(output, sprintf("... Total fields %d", total_fields)) + } + + return(paste(output, collapse = "\n")) +} + +#' Format analysis results as CSV data +#' @param analysis_results Results from run_estate_analysis +#' @return Data frame ready for write.csv +format_as_csv <- function(analysis_results) { + + field_results <- analysis_results$field_results + estate_name <- analysis_results$estate_name + current_week <- analysis_results$current_week + previous_week <- analysis_results$previous_week + + csv_data <- data.frame() + + for (field_id in names(field_results)) { + field_info <- field_results[[field_id]] + + row_data <- data.frame( + estate = estate_name, + field = field_info$current_stats$field, + sub_field = field_info$current_stats$sub_field, + area_ha = round(field_info$current_stats$field_area_ha, 2), + current_week = current_week, + previous_week = previous_week, + current_week_ci = round(field_info$current_stats$mean_ci, 3), + previous_week_ci = if (!is.null(field_info$previous_stats)) round(field_info$previous_stats$mean_ci, 3) else NA, + ci_change = round(field_info$ci_change, 3), + change_category = field_info$change_category, + cv = round(field_info$current_stats$cv, 3), + uniformity_category = field_info$uniformity_category, + acceptable_pct = round(field_info$current_stats$acceptable_pct, 1), + hotspot_pct = round(field_info$current_stats$extreme_percentages$hotspot_pct, 1), + coldspot_pct = round(field_info$current_stats$extreme_percentages$coldspot_pct, 1), + morans_i = round(field_info$current_stats$spatial_autocorr$morans_i, 3), + alert_needed = field_info$message_result$worth_sending, + message = field_info$message_result$message, + stringsAsFactors = FALSE + ) + + csv_data <- rbind(csv_data, row_data) + } + + return(csv_data) +} + +#' Format analysis results as markdown table +#' @param analysis_results Results from run_estate_analysis +#' @return Character string with markdown table +format_as_markdown_table <- function(analysis_results) { + + field_results <- analysis_results$field_results + estate_name <- toupper(analysis_results$estate_name) + current_week <- analysis_results$current_week + previous_week <- analysis_results$previous_week + + output <- c() + output <- c(output, paste("# Crop Analysis Summary -", estate_name, "Estate")) + output <- c(output, paste("**Analysis Period:** Week", previous_week, "vs Week", current_week)) + output <- c(output, "") + output <- c(output, "| Field | Area (ha) | Current CI | Change | Uniformity | Alert | Message |") + output <- c(output, "|-------|-----------|------------|--------|------------|-------|---------|") + + for (field_id in names(field_results)) { + field_info <- field_results[[field_id]] + + field_name <- paste(field_info$current_stats$field, field_info$current_stats$sub_field, sep="-") + area <- round(field_info$current_stats$field_area_ha, 1) + current_ci <- round(field_info$current_stats$mean_ci, 3) + change <- field_info$change_category + uniformity <- field_info$uniformity_category + alert <- if(field_info$message_result$worth_sending) "🚨 YES" else "✅ NO" + message <- field_info$message_result$message + + row <- paste("|", field_name, "|", area, "|", current_ci, "|", change, "|", uniformity, "|", alert, "|", message, "|") + output <- c(output, row) + } + + return(paste(output, collapse = "\n")) +} + +#' Create Word document with analysis results +#' @param analysis_results Results from run_estate_analysis +#' @param output_dir Directory to save the Word document +#' @return Path to the created Word document +create_word_document <- function(analysis_results, output_dir) { + + estate_name <- toupper(analysis_results$estate_name) + current_week <- analysis_results$current_week + previous_week <- analysis_results$previous_week + + # Create a new Word document + doc <- officer::read_docx() + + # Add title + doc <- officer::body_add_par(doc, paste(estate_name, "Crop Analysis Report"), style = "heading 1") + + # Add summary + field_results <- analysis_results$field_results + alert_count <- sum(sapply(field_results, function(x) x$message_result$worth_sending)) + total_fields <- length(field_results) + total_hectares <- sum(sapply(field_results, function(x) x$current_stats$field_area_ha), na.rm = TRUE) + + doc <- officer::body_add_par(doc, "Summary", style = "heading 2") + doc <- officer::body_add_par(doc, paste("• Fields analyzed:", total_fields)) + doc <- officer::body_add_par(doc, paste("• Total area:", format_area_both(total_hectares))) + doc <- officer::body_add_par(doc, paste("• Alerts needed:", alert_count)) + doc <- officer::body_add_par(doc, "") + + # Field-by-field alerts only + if (alert_count > 0) { + doc <- officer::body_add_par(doc, "Priority Fields", style = "heading 2") + for (field_id in names(field_results)) { + field_info <- field_results[[field_id]] + if (field_info$message_result$worth_sending) { + field_name <- paste(field_info$current_stats$field, field_info$current_stats$sub_field, sep="-") + area <- round(field_info$current_stats$field_area_ha, 1) + message <- field_info$message_result$message + + doc <- officer::body_add_par(doc, paste("•", field_name, paste0("(", format_area_both(area), "):"), message)) + } + } + doc <- officer::body_add_par(doc, "") + } else { + doc <- officer::body_add_par(doc, "✅ No urgent alerts - all fields stable") + doc <- officer::body_add_par(doc, "") + } + + # Quick farm summary + doc <- officer::body_add_par(doc, "Quick Stats", style = "heading 2") + + # Calculate improving vs declining areas (only if temporal data available) + has_temporal_data <- any(sapply(field_results, function(x) !is.na(x$change_percentages$positive_pct))) + + if (has_temporal_data) { + total_improving <- sum(sapply(field_results, function(x) { + if (!is.na(x$change_percentages$positive_pct)) { + (x$change_percentages$positive_pct / 100) * x$current_stats$field_area_ha + } else 0 + }), na.rm = TRUE) + + total_declining <- sum(sapply(field_results, function(x) { + if (!is.na(x$change_percentages$negative_pct)) { + (x$change_percentages$negative_pct / 100) * x$current_stats$field_area_ha + } else 0 + }), na.rm = TRUE) + + total_stable <- sum(sapply(field_results, function(x) { + if (!is.na(x$change_percentages$stable_pct)) { + (x$change_percentages$stable_pct / 100) * x$current_stats$field_area_ha + } else 0 + }), na.rm = TRUE) + + improving_pct <- (total_improving / total_hectares) * 100 + declining_pct <- (total_declining / total_hectares) * 100 + stable_pct <- (total_stable / total_hectares) * 100 + + doc <- officer::body_add_par(doc, paste("• Improving areas:", format_area_both(total_improving), paste0("(", round(improving_pct, 1), "%)"))) + doc <- officer::body_add_par(doc, paste("• Stable areas:", format_area_both(total_stable), paste0("(", round(stable_pct, 1), "%)"))) + doc <- officer::body_add_par(doc, paste("• Declining areas:", format_area_both(total_declining), paste0("(", round(declining_pct, 1), "%)"))) + + # Overall trend + if (improving_pct > declining_pct) { + trend_diff <- round(improving_pct - declining_pct, 1) + doc <- officer::body_add_par(doc, paste("• Trend: POSITIVE (+", trend_diff, "%)")) + } else if (declining_pct > improving_pct) { + trend_diff <- round(declining_pct - improving_pct, 1) + doc <- officer::body_add_par(doc, paste("• Trend: NEGATIVE (-", trend_diff, "%)")) + } else { + doc <- officer::body_add_par(doc, "• Trend: BALANCED") + } + } else { + doc <- officer::body_add_par(doc, "• Analysis: Spatial patterns only (previous week data unavailable)") + } + + doc <- officer::body_add_par(doc, "") + + # Add farm-wide analysis summary + doc <- officer::body_add_par(doc, "Farm-Wide Analysis Summary", style = "heading 2") + doc <- officer::body_add_par(doc, "") + + # Field uniformity statistics + excellent_fields <- sum(sapply(field_results, function(x) x$current_stats$cv <= 0.08)) + good_fields <- sum(sapply(field_results, function(x) x$current_stats$cv > 0.08 & x$current_stats$cv <= 0.15)) + moderate_fields <- sum(sapply(field_results, function(x) x$current_stats$cv > 0.15 & x$current_stats$cv <= 0.30)) + poor_fields <- sum(sapply(field_results, function(x) x$current_stats$cv > 0.30)) + + # Create uniformity table + uniformity_data <- data.frame( + "Uniformity Level" = c("Excellent (CV≤0.08)", "Good (CV 0.08-0.15)", "Moderate (CV 0.15-0.30)", "Poor (CV>0.30)", "Total fields"), + "Fields" = c(excellent_fields, good_fields, moderate_fields, poor_fields, total_fields), + "Percent" = c( + round((excellent_fields/total_fields)*100, 1), + round((good_fields/total_fields)*100, 1), + round((moderate_fields/total_fields)*100, 1), + round((poor_fields/total_fields)*100, 1), + 100.0 + ), + stringsAsFactors = FALSE + ) + + uniformity_ft <- flextable::flextable(uniformity_data) + uniformity_ft <- flextable::autofit(uniformity_ft) + uniformity_ft <- flextable::set_header_labels(uniformity_ft, + "Uniformity.Level" = "Uniformity Level" + ) + + doc <- officer::body_add_par(doc, "Field Uniformity Summary", style = "heading 3") + doc <- flextable::body_add_flextable(doc, uniformity_ft) + doc <- officer::body_add_par(doc, "") + + # Farm-wide area change summary + doc <- officer::body_add_par(doc, "Farm-Wide Area Change Summary", style = "heading 3") + + if (has_temporal_data) { + change_data <- data.frame( + "Change Type" = c("Improving areas", "Stable areas", "Declining areas", "Total area"), + "Area" = c(format_area_both(total_improving), format_area_both(total_stable), format_area_both(total_declining), format_area_both(total_hectares)), + "Percent" = c(round(improving_pct, 1), round(stable_pct, 1), round(declining_pct, 1), 100.0), + stringsAsFactors = FALSE + ) + } else { + change_data <- data.frame( + "Change Type" = c("Improving areas", "Stable areas", "Declining areas", "Total area"), + "Area" = c("N/A", "N/A", "N/A", format_area_both(total_hectares)), + "Percent" = c("N/A", "N/A", "N/A", 100.0), + stringsAsFactors = FALSE + ) + } + + change_ft <- flextable::flextable(change_data) + change_ft <- flextable::autofit(change_ft) + change_ft <- flextable::set_header_labels(change_ft, + "Change.Type" = "Change Type", + "Area" = "Area (ha/acres)" + ) + + doc <- flextable::body_add_flextable(doc, change_ft) + doc <- officer::body_add_par(doc, "") + + # Create and add detailed results tables using flextable (split into multiple tables for better formatting) + csv_data <- format_as_csv(analysis_results) + + # Split data into multiple tables for better readability + doc <- officer::body_add_par(doc, "Detailed Results", style = "heading 2") + + # Table 2: Current Week Analysis + current_data <- csv_data[, c("field", "current_week_ci", "cv", "uniformity_category", "acceptable_pct")] + current_data$current_week_ci <- round(current_data$current_week_ci, 3) + current_data$cv <- round(current_data$cv, 3) + current_data$acceptable_pct <- round(current_data$acceptable_pct, 1) + + current_ft <- flextable::flextable(current_data) + current_ft <- flextable::autofit(current_ft) + current_ft <- flextable::set_header_labels(current_ft, + "field" = "Field", + # "sub_field" = "Sub-field", + "current_week_ci" = "Current CI", + "cv" = "CV", + "uniformity_category" = "Uniformity", + "acceptable_pct" = "Acceptable %" + ) + current_ft <- flextable::theme_vanilla(current_ft) + current_ft <- flextable::fontsize(current_ft, size = 9) + current_ft <- flextable::width(current_ft, width = 1.0) # Set column width + + doc <- officer::body_add_par(doc, "Current Week Analysis", style = "heading 3") + doc <- flextable::body_add_flextable(doc, current_ft) + doc <- officer::body_add_par(doc, "") + + # Table 3: Change Analysis (only if temporal data available) + if (has_temporal_data && any(!is.na(csv_data$ci_change))) { + change_data <- csv_data[, c("field", "previous_week_ci", "ci_change", "change_category")] + change_data <- change_data[!is.na(change_data$ci_change), ] # Remove rows with NA change + change_data$previous_week_ci <- round(change_data$previous_week_ci, 3) + change_data$ci_change <- round(change_data$ci_change, 3) + + change_ft <- flextable::flextable(change_data) + change_ft <- flextable::autofit(change_ft) + change_ft <- flextable::set_header_labels(change_ft, + "field" = "Field", + # "sub_field" = "Sub-field", + "previous_week_ci" = "Previous CI", + "ci_change" = "CI Change", + "change_category" = "Change Type" + ) + change_ft <- flextable::theme_vanilla(change_ft) + change_ft <- flextable::fontsize(change_ft, size = 9) + change_ft <- flextable::width(change_ft, width = 1.0) # Set column width + + doc <- officer::body_add_par(doc, "Week-over-Week Change Analysis", style = "heading 3") + doc <- flextable::body_add_flextable(doc, change_ft) + doc <- officer::body_add_par(doc, "") + } + + # Table 4: Spatial Analysis Results (split into two tables for better fit) + spatial_data <- csv_data[, c("field", "hotspot_pct", "coldspot_pct", "morans_i", "alert_needed")] + spatial_data$hotspot_pct <- round(spatial_data$hotspot_pct, 1) + spatial_data$coldspot_pct <- round(spatial_data$coldspot_pct, 1) + spatial_data$morans_i <- round(spatial_data$morans_i, 3) + spatial_data$alert_needed <- ifelse(spatial_data$alert_needed, "YES", "NO") + + spatial_ft <- flextable::flextable(spatial_data) + spatial_ft <- flextable::autofit(spatial_ft) + spatial_ft <- flextable::set_header_labels(spatial_ft, + "field" = "Field", + # "sub_field" = "Sub-field", + "hotspot_pct" = "Hotspots %", + "coldspot_pct" = "Coldspots %", + "morans_i" = "Moran's I", + "alert_needed" = "Alert" + ) + spatial_ft <- flextable::theme_vanilla(spatial_ft) + spatial_ft <- flextable::fontsize(spatial_ft, size = 9) + spatial_ft <- flextable::width(spatial_ft, width = 0.8) # Set column width + + doc <- officer::body_add_par(doc, "Spatial Analysis Results", style = "heading 3") + doc <- flextable::body_add_flextable(doc, spatial_ft) + doc <- officer::body_add_par(doc, "") + + # Table 5: Alert Messages (separate table for long messages) + message_data <- csv_data[, c("field","message")] + message_data$message <- substr(message_data$message, 1, 80) # Truncate long messages for table fit + + message_ft <- flextable::flextable(message_data) + message_ft <- flextable::autofit(message_ft) + message_ft <- flextable::set_header_labels(message_ft, + "field" = "Field", + # "sub_field" = "Sub-field", + "message" = "Alert Message" + ) + message_ft <- flextable::theme_vanilla(message_ft) + message_ft <- flextable::fontsize(message_ft, size = 8) # Smaller font for messages + message_ft <- flextable::width(message_ft, width = 2.0) # Wider column for messages + + doc <- officer::body_add_par(doc, "Alert Messages", style = "heading 3") + doc <- flextable::body_add_flextable(doc, message_ft) + + # Add interpretation guide for all columns + doc <- officer::body_add_par(doc, "") + doc <- officer::body_add_par(doc, "Column Interpretation Guide", style = "heading 3") + doc <- officer::body_add_par(doc, "") + + # Table 1 interpretation + doc <- officer::body_add_par(doc, "Field Information Table:", style = "Normal") + doc <- officer::body_add_par(doc, "• Field/Sub-field: Field identifiers and names") + doc <- officer::body_add_par(doc, "• Area (ha): Field size in hectares") + doc <- officer::body_add_par(doc, "• Current/Previous Week: Weeks being compared") + doc <- officer::body_add_par(doc, "") + + # Table 2 interpretation + doc <- officer::body_add_par(doc, "Current Week Analysis Table:", style = "Normal") + doc <- officer::body_add_par(doc, "• Current CI: Crop Index (0-10 scale, higher = healthier crop)") + doc <- officer::body_add_par(doc, "• CV: Coefficient of Variation (lower = more uniform field)") + doc <- officer::body_add_par(doc, "• Uniformity: Field uniformity rating (Excellent/Good/Moderate/Poor)") + doc <- officer::body_add_par(doc, "• Acceptable %: % of field within ±25% of average CI (higher = more uniform)") + doc <- officer::body_add_par(doc, "") + + # Table 3 interpretation (only if temporal data available) + if (has_temporal_data && any(!is.na(csv_data$ci_change))) { + doc <- officer::body_add_par(doc, "Week-over-Week Change Analysis Table:", style = "Normal") + doc <- officer::body_add_par(doc, "• Previous CI: Crop Index from previous week") + doc <- officer::body_add_par(doc, "• CI Change: Week-over-week change in CI values") + doc <- officer::body_add_par(doc, "• Change Type: >+0.5 = Improving, -0.5 to +0.5 = Stable, <-0.5 = Declining") + doc <- officer::body_add_par(doc, "") + } + + # Table 4 interpretation + doc <- officer::body_add_par(doc, "Spatial Analysis Results Table:", style = "Normal") + doc <- officer::body_add_par(doc, "• Hotspots %: % of field significantly above average (> mean + 1.5×SD)") + doc <- officer::body_add_par(doc, "• Coldspots %: % of field significantly below average (< mean - 1.5×SD)") + doc <- officer::body_add_par(doc, "• Moran's I: Spatial autocorrelation (-1 to +1, higher = more clustered)") + doc <- officer::body_add_par(doc, "• Alert: YES/NO indicating if field needs management attention") + doc <- officer::body_add_par(doc, "") + + # Table 5 interpretation + doc <- officer::body_add_par(doc, "Alert Messages Table:", style = "Normal") + doc <- officer::body_add_par(doc, "• Message: Specific recommendations or warnings for each field") + doc <- officer::body_add_par(doc, "") + + # Overall interpretation guide + doc <- officer::body_add_par(doc, "Performance Thresholds:", style = "heading 3") + doc <- officer::body_add_par(doc, "Acceptable %: >45% = Excellent uniformity, 35-45% = Good, <35% = Needs attention") + doc <- officer::body_add_par(doc, "CV: <0.08 = Excellent, 0.08-0.15 = Good, 0.15-0.30 = Moderate, >0.30 = Poor") + doc <- officer::body_add_par(doc, "Moran's I: >0.7 = Strong clustering, 0.3-0.7 = Normal field patterns, <0.3 = Random") + doc <- officer::body_add_par(doc, "Hotspots/Coldspots: >10% = Significant spatial issues, 3-10% = Monitor, <3% = Normal") + + # Add KPI Dashboard to Word Document + doc <- officer::body_add_par(doc, "") + doc <- officer::body_add_par(doc, "Farm Key Performance Indicators", style = "heading 2") + doc <- officer::body_add_par(doc, "") + + # Table 1: Field Performance Distribution & Risk Assessment + doc <- officer::body_add_par(doc, "Field Performance Indicators", style = "heading 3") + + # Calculate risk levels based on CV + Moran's I combination + risk_low <- 0 + risk_moderate <- 0 + risk_high <- 0 + risk_very_high <- 0 + + for (field_id in names(field_results)) { + field_info <- field_results[[field_id]] + cv <- field_info$current_stats$cv + morans_i <- field_info$current_stats$spatial_autocorr$morans_i + + # Risk logic: Low CV + Low clustering = Low risk, High CV + High clustering = High risk + if (!is.na(cv) && !is.na(morans_i)) { + if (cv <= 0.10 && morans_i <= 0.8) { + risk_low <- risk_low + 1 + } else if (cv <= 0.20 && morans_i <= 0.9) { + risk_moderate <- risk_moderate + 1 + } else if (cv <= 0.30 || morans_i <= 0.95) { + risk_high <- risk_high + 1 + } else { + risk_very_high <- risk_very_high + 1 + } + } else { + risk_moderate <- risk_moderate + 1 # Default for missing data + } + } + + # Uniformity Distribution Table + uniformity_kpi_data <- data.frame( + "CV Category" = c("Excellent (CV≤0.08)", "Good (CV 0.08-0.15)", "Moderate (0.15-0.30)", "Poor (CV>0.30)", "Total fields"), + "Count" = c(excellent_fields, good_fields, moderate_fields, poor_fields, total_fields), + "Percent" = c(round((excellent_fields/total_fields)*100, 1), round((good_fields/total_fields)*100, 1), + round((moderate_fields/total_fields)*100, 1), round((poor_fields/total_fields)*100, 1), 100.0), + stringsAsFactors = FALSE + ) + + # Risk Assessment Table + risk_data <- data.frame( + "Risk Level" = c("Low (CV≤0.10)", "Moderate (0.10-0.20)", "High (0.20-0.30)", "Very High (>0.30)", "Total fields"), + "Count" = c(risk_low, risk_moderate, risk_high, risk_very_high, total_fields), + "Percent" = c(round((risk_low/total_fields)*100, 1), round((risk_moderate/total_fields)*100, 1), + round((risk_high/total_fields)*100, 1), round((risk_very_high/total_fields)*100, 1), 100.0), + stringsAsFactors = FALSE + ) + + uniformity_kpi_ft <- flextable::flextable(uniformity_kpi_data) + uniformity_kpi_ft <- flextable::autofit(uniformity_kpi_ft) + risk_ft <- flextable::flextable(risk_data) + risk_ft <- flextable::autofit(risk_ft) + + doc <- officer::body_add_par(doc, "Uniformity Distribution:") + doc <- flextable::body_add_flextable(doc, uniformity_kpi_ft) + doc <- officer::body_add_par(doc, "") + doc <- officer::body_add_par(doc, "Risk Assessment:") + doc <- flextable::body_add_flextable(doc, risk_ft) + doc <- officer::body_add_par(doc, "") + + # Performance Quartiles (if temporal data available) + if (has_temporal_data) { + # Calculate performance quartiles based on combination of current CI and change + field_performance <- sapply(field_results, function(x) { + current_ci <- x$current_stats$mean_ci + ci_change <- x$ci_change + # Combine current performance with improvement trend + performance_score <- current_ci + (ci_change * 0.5) # Weight change as 50% of current + return(performance_score) + }) + + sorted_performance <- sort(field_performance, decreasing = TRUE) + q75 <- quantile(sorted_performance, 0.75, na.rm = TRUE) + q25 <- quantile(sorted_performance, 0.25, na.rm = TRUE) + + top_quartile <- sum(field_performance >= q75, na.rm = TRUE) + bottom_quartile <- sum(field_performance <= q25, na.rm = TRUE) + middle_quartile <- total_fields - top_quartile - bottom_quartile + + avg_ci_top <- mean(sapply(field_results[field_performance >= q75], function(x) x$current_stats$mean_ci), na.rm = TRUE) + avg_ci_mid <- mean(sapply(field_results[field_performance > q25 & field_performance < q75], function(x) x$current_stats$mean_ci), na.rm = TRUE) + avg_ci_bot <- mean(sapply(field_results[field_performance <= q25], function(x) x$current_stats$mean_ci), na.rm = TRUE) + + quartile_data <- data.frame( + "Quartile" = c("Top 25%", "Average (25-75%)", "Bottom 25%", "Total fields"), + "Count" = c(top_quartile, middle_quartile, bottom_quartile, total_fields), + "Avg CI" = c(round(avg_ci_top, 1), round(avg_ci_mid, 1), round(avg_ci_bot, 1), + round(mean(sapply(field_results, function(x) x$current_stats$mean_ci), na.rm = TRUE), 1)), + stringsAsFactors = FALSE + ) + + quartile_ft <- flextable::flextable(quartile_data) + quartile_ft <- flextable::autofit(quartile_ft) + + doc <- officer::body_add_par(doc, "Performance Quartiles:") + doc <- flextable::body_add_flextable(doc, quartile_ft) + doc <- officer::body_add_par(doc, "") + } + + # Table 2: Anomaly Detection + doc <- officer::body_add_par(doc, "Anomaly Detection & Management Priorities", style = "heading 3") + + # Calculate anomalies + weed_fields <- 0 + weed_area <- 0 + harvest_fields <- 0 + harvest_area <- 0 + fallow_fields <- 0 + fallow_area <- 0 + high_hotspot_fields <- 0 + high_hotspot_area <- 0 + + if (has_temporal_data) { + for (field_id in names(field_results)) { + field_info <- field_results[[field_id]] + ci_change <- field_info$ci_change + current_ci <- field_info$current_stats$mean_ci + area <- field_info$current_stats$field_area_ha + hotspots <- field_info$current_stats$extreme_percentages$hotspot_pct + + # Weed detection: CI increase > 1.5 + if (!is.na(ci_change) && ci_change > 1.5) { + weed_fields <- weed_fields + 1 + weed_area <- weed_area + area + } + + # Harvesting/theft detection: CI decrease > 1.5 + if (!is.na(ci_change) && ci_change < -1.5) { + harvest_fields <- harvest_fields + 1 + harvest_area <- harvest_area + area + } + + # Fallow detection: CI < 2.0 + if (!is.na(current_ci) && current_ci < 2.0) { + fallow_fields <- fallow_fields + 1 + fallow_area <- fallow_area + area + } + + # High hotspot detection: > 5% + if (!is.na(hotspots) && hotspots > 5.0) { + high_hotspot_fields <- high_hotspot_fields + 1 + high_hotspot_area <- high_hotspot_area + area + } + } + } + + anomaly_data <- data.frame( + "Detection Type" = c("Potential weed areas (CI increase >1.5)", "Potential harvesting (CI decrease >1.5)", + "Potential fallow fields (CI <2.0)", "High hotspot fields (>5%)"), + "Fields to Check" = c(weed_fields, harvest_fields, fallow_fields, high_hotspot_fields), + "Area (ha)" = c(round(weed_area, 1), round(harvest_area, 1), round(fallow_area, 1), round(high_hotspot_area, 1)), + stringsAsFactors = FALSE + ) + + anomaly_ft <- flextable::flextable(anomaly_data) + anomaly_ft <- flextable::autofit(anomaly_ft) + doc <- flextable::body_add_flextable(doc, anomaly_ft) + doc <- officer::body_add_par(doc, "") + + # Table 3: Priority Actions + doc <- officer::body_add_par(doc, "Immediate Action Priorities", style = "heading 3") + + # Find urgent and monitoring fields + urgent_fields <- sapply(field_results, function(x) x$message_result$worth_sending && grepl("URGENT", x$message_result$message)) + monitoring_fields <- sapply(field_results, function(x) x$message_result$worth_sending && !grepl("URGENT", x$message_result$message)) + + urgent_data <- data.frame() + monitoring_data <- data.frame() + + for (field_id in names(field_results)) { + if (urgent_fields[field_id]) { + field_info <- field_results[[field_id]] + field_name <- paste(field_info$current_stats$field, field_info$current_stats$sub_field, sep="-") + area <- field_info$current_stats$field_area_ha + urgent_data <- rbind(urgent_data, data.frame( + "Field Name" = field_name, + "Issue Type" = "Poor uniformity", + "Area (ha)" = round(area, 1), + stringsAsFactors = FALSE + )) + } + + if (monitoring_fields[field_id]) { + field_info <- field_results[[field_id]] + field_name <- paste(field_info$current_stats$field, field_info$current_stats$sub_field, sep="-") + area <- field_info$current_stats$field_area_ha + monitoring_data <- rbind(monitoring_data, data.frame( + "Field Name" = field_name, + "Issue Type" = "Moderate variation", + "Area (ha)" = round(area, 1), + stringsAsFactors = FALSE + )) + } + } + + if (nrow(urgent_data) > 0) { + urgent_ft <- flextable::flextable(urgent_data) + urgent_ft <- flextable::autofit(urgent_ft) + doc <- officer::body_add_par(doc, "Urgent Interventions:") + doc <- flextable::body_add_flextable(doc, urgent_ft) + } else { + doc <- officer::body_add_par(doc, "Urgent Interventions: None required") + } + + doc <- officer::body_add_par(doc, "") + + if (nrow(monitoring_data) > 0) { + monitoring_ft <- flextable::flextable(monitoring_data) + monitoring_ft <- flextable::autofit(monitoring_ft) + doc <- officer::body_add_par(doc, "Monitoring Required:") + doc <- flextable::body_add_flextable(doc, monitoring_ft) + } else { + doc <- officer::body_add_par(doc, "Monitoring Required: None required") + } + + doc <- officer::body_add_par(doc, "") + + # Add interpretation guide for Table 5 + doc <- officer::body_add_par(doc, "") + doc <- officer::body_add_par(doc, "Column Guide - Alert Messages:", style = "heading 3") + doc <- officer::body_add_par(doc, "• Alert Message: Specific recommendations based on field analysis") + doc <- officer::body_add_par(doc, "• 🚨 URGENT: Immediate management action required") + doc <- officer::body_add_par(doc, "• ⚠️ ALERT: Early intervention recommended") + doc <- officer::body_add_par(doc, "• ✅ POSITIVE: Good performance, continue current practices") + doc <- officer::body_add_par(doc, "• 📈 OPPORTUNITY: Potential for improvement identified") + + # Save the document + timestamp <- format(Sys.time(), "%Y%m%d_%H%M") + filename <- paste0("crop_analysis_", estate_name, "_w", current_week, "vs", previous_week, "_", timestamp, ".docx") + filepath <- file.path(output_dir, filename) + + print(doc, target = filepath) + + return(filepath) +} + +#' Save analysis outputs in multiple formats +#' @param analysis_results Results from run_estate_analysis +#' @param output_dir Directory to save files (optional) +#' @return List with file paths created +save_analysis_outputs <- function(analysis_results, output_dir = NULL) { + + estate_name <- analysis_results$estate_name + current_week <- analysis_results$current_week + previous_week <- analysis_results$previous_week + + # Create output directory if not specified + if (is.null(output_dir)) { + output_dir <- file.path("output", estate_name) + } + if (!dir.exists(output_dir)) dir.create(output_dir, recursive = TRUE) + + timestamp <- format(Sys.time(), "%Y%m%d_%H%M") + base_filename <- paste0("crop_analysis_", estate_name, "_w", current_week, "vs", previous_week, "_", timestamp) + + # Generate output formats + whatsapp_text <- format_for_whatsapp(analysis_results) + + # Save files + whatsapp_file <- file.path(output_dir, paste0(base_filename, "_whatsapp.txt")) + + writeLines(whatsapp_text, whatsapp_file) + + # Create Word document + docx_file <- create_word_document(analysis_results, output_dir) + + # Display summary + cat("\n=== OUTPUT FILES CREATED ===\n") + cat("📱 WhatsApp format:", whatsapp_file, "\n") + cat("� Word document:", docx_file, "\n") + + # Display WhatsApp format in console for immediate copy + cat("\n=== WHATSAPP/WORD READY FORMAT ===\n") + cat("(Copy text below directly to WhatsApp or Word)\n") + cat(rep("=", 50), "\n") + cat(whatsapp_text) + cat("\n", rep("=", 50), "\n") + + return(list( + whatsapp_file = whatsapp_file, + docx_file = docx_file + )) +} diff --git a/r_app/experiments/10_CI_report_with_kpis.Rmd b/r_app/experiments/10_CI_report_with_kpis.Rmd new file mode 100644 index 0000000..f1317e6 --- /dev/null +++ b/r_app/experiments/10_CI_report_with_kpis.Rmd @@ -0,0 +1,400 @@ +--- +params: + ref: "word-styles-reference-var1.docx" + output_file: CI_report_with_kpis.docx + report_date: "2025-09-18" + data_dir: "esa" + mail_day: "Wednesday" + borders: FALSE + ci_plot_type: "both" # options: "absolute", "cumulative", "both" + colorblind_friendly: TRUE # use colorblind-friendly palettes (viridis/plasma) + facet_by_season: FALSE # facet CI trend plots by season instead of overlaying + x_axis_unit: "days" # x-axis unit for trend plots: "days" or "weeks" +output: + # html_document: + # toc: yes + # df_print: paged + word_document: + reference_docx: !expr file.path("word-styles-reference-var1.docx") + toc: no +editor_options: + chunk_output_type: console +--- + +```{r setup_parameters, include=FALSE} +# Set up basic report parameters from input values +report_date <- params$report_date +mail_day <- params$mail_day +borders <- params$borders +ci_plot_type <- params$ci_plot_type +colorblind_friendly <- params$colorblind_friendly +facet_by_season <- params$facet_by_season +x_axis_unit <- params$x_axis_unit +``` + +```{r load_libraries, message=FALSE, warning=FALSE, include=FALSE} +# Configure knitr options +knitr::opts_chunk$set(warning = FALSE, message = FALSE) + +# Load all packages at once with suppressPackageStartupMessages +suppressPackageStartupMessages({ + library(here) + library(sf) + library(terra) + library(exactextractr) + library(tidyverse) + library(tmap) + library(lubridate) + library(zoo) + library(rsample) + library(caret) + library(randomForest) + library(CAST) + library(knitr) +}) + +# Load custom utility functions +tryCatch({ + source("report_utils.R") +}, error = function(e) { + message(paste("Error loading report_utils.R:", e$message)) + # Try alternative path if the first one fails + tryCatch({ + source(here::here("r_app", "report_utils.R")) + }, error = function(e) { + stop("Could not load report_utils.R from either location: ", e$message) + }) +}) +``` + +```{r initialize_project_config, message=FALSE, warning=FALSE, include=FALSE} +# Set the project directory from parameters +project_dir <- params$data_dir + +# Source project parameters with error handling +tryCatch({ + source(here::here("r_app", "parameters_project.R")) +}, error = function(e) { + stop("Error loading parameters_project.R: ", e$message) +}) + +# Log initial configuration +safe_log("Starting the R Markdown script with KPIs") +safe_log(paste("mail_day params:", params$mail_day)) +safe_log(paste("report_date params:", params$report_date)) +safe_log(paste("mail_day variable:", mail_day)) +``` + +```{r load_kpi_data, message=FALSE, warning=FALSE, include=FALSE} +# SIMPLE KPI LOADING - just load the damn files! +kpi_data_dir <- file.path("..", "laravel_app", "storage", "app", project_dir, "reports", "kpis") +date_suffix <- format(as.Date(report_date), "%Y%m%d") +summary_file <- file.path(kpi_data_dir, paste0(project_dir, "_kpi_summary_tables_", date_suffix, ".rds")) + +# Load the summary tables (this works!) +summary_tables <- readRDS(summary_file) + +# Load field details too +field_details_file <- file.path(kpi_data_dir, paste0(project_dir, "_field_details_", date_suffix, ".rds")) +field_details_table <- readRDS(field_details_file) + +# Set this for compatibility with rest of report +kpi_files_exist <- TRUE + +safe_log("✓ KPI summary tables loaded successfully") + +``` + +```{r calculate_dates_and_weeks, message=FALSE, warning=FALSE, include=FALSE} +# Set locale for consistent date formatting +Sys.setlocale("LC_TIME", "C") + +# Initialize date variables from parameters +today <- as.character(report_date) +mail_day_as_character <- as.character(mail_day) + +# Calculate report dates and weeks +report_date_obj <- as.Date(today) +current_week <- as.numeric(format(report_date_obj, "%U")) +year <- as.numeric(format(report_date_obj, "%Y")) + +# Calculate dates for weekly analysis +week_start <- report_date_obj - ((as.numeric(format(report_date_obj, "%w")) + 1) %% 7) +week_end <- week_start + 6 + +safe_log(paste("Report week:", current_week, "Year:", year)) +safe_log(paste("Week range:", week_start, "to", week_end)) +``` + +# SmartCane Monitoring Report with KPIs + +**Report Date:** `r format(as.Date(report_date), "%B %d, %Y")` +**Project:** `r toupper(project_dir)` +**Week:** `r current_week` of `r year` + +--- + +## Executive Summary - Key Performance Indicators + +This report provides a comprehensive analysis of sugarcane field performance using satellite-based monitoring. + +### Field Uniformity +```{r field_uniformity_table, echo=FALSE} +kable(summary_tables$field_uniformity_summary, + caption = "Field Uniformity Summary", + col.names = c("Uniformity Level", "Count", "Percent")) +``` + +### TCH Forecasted +```{r tch_forecasted_table, echo=FALSE} +kable(summary_tables$tch_forecasted_summary, + caption = "TCH Forecasted Summary", + col.names = c("Field Groups", "Count", "Value")) +``` + +### Farm-wide Area Change +```{r area_change_table, echo=FALSE} +kable(summary_tables$area_change_summary, + caption = "Farm-wide Area Change Summary", + col.names = c("Change Type", "Hectares", "Percent")) +``` + +### Weed Presence Score +```{r weed_presence_table, echo=FALSE} +kable(summary_tables$weed_presence_summary, + caption = "Weed Presence Score Summary", + col.names = c("Weed Risk Level", "Field Count", "Percent")) +``` + +### Growth Decline Index +```{r growth_decline_table, echo=FALSE} +kable(summary_tables$growth_decline_summary, + caption = "Growth Decline Index Summary", + col.names = c("Risk Level", "Count", "Percent")) +``` + +### Gap Filling Assessment +```{r gap_filling_table, echo=FALSE} +kable(summary_tables$gap_filling_summary, + caption = "Gap Filling Assessment Summary", + col.names = c("Gap Level", "Field Count", "Percent")) +``` + +### Detailed KPI Breakdown + +```{r kpi_detailed_breakdown, echo=FALSE} +# Show all 6 KPI tables in a more compact format +cat("**Field Uniformity**\n") +kable(summary_tables$field_uniformity_summary, col.names = c("Level", "Count", "%")) + +cat("\n**TCH Forecasted**\n") +kable(summary_tables$tch_forecasted_summary, col.names = c("Groups", "Count", "Value")) + +cat("\n**Area Change**\n") +kable(summary_tables$area_change_summary, col.names = c("Change", "Ha", "%")) + +cat("\n**Weed Presence**\n") +kable(summary_tables$weed_presence_summary, col.names = c("Risk", "Count", "%")) + +cat("\n**Growth Decline**\n") +kable(summary_tables$growth_decline_summary, col.names = c("Risk", "Count", "%")) + +cat("\n**Gap Filling**\n") +kable(summary_tables$gap_filling_summary, col.names = c("Level", "Count", "%")) +``` + +## KPI Summary Charts + +```{r kpi_charts, echo=FALSE, fig.width=10, fig.height=8} +# Load ggplot2 for creating charts +library(ggplot2) +library(gridExtra) + +# Create charts for key KPIs using correct column names +# 1. Field Uniformity Chart +p1 <- ggplot(summary_tables$field_uniformity_summary, aes(x = reorder(`Uniformity Level`, -Count), y = Count)) + + geom_col(fill = "steelblue", alpha = 0.7) + + labs(title = "Field Uniformity Distribution", x = "Uniformity Level", y = "Field Count") + + theme_minimal() + + theme(axis.text.x = element_text(angle = 45, hjust = 1)) + +# 2. TCH Forecasted Chart +p2 <- ggplot(summary_tables$tch_forecasted_summary, aes(x = `Field Groups`, y = Value)) + + geom_col(fill = "darkgreen", alpha = 0.7) + + labs(title = "TCH Forecast by Field Groups", x = "Field Groups", y = "Value") + + theme_minimal() + + theme(axis.text.x = element_text(angle = 45, hjust = 1)) + +# 3. Growth Decline Risk Chart +p3 <- ggplot(summary_tables$growth_decline_summary, aes(x = reorder(`Risk Level`, -Count), y = Count)) + + geom_col(fill = "orange", alpha = 0.7) + + labs(title = "Growth Decline Risk Distribution", x = "Risk Level", y = "Field Count") + + theme_minimal() + + theme(axis.text.x = element_text(angle = 45, hjust = 1)) + +# 4. Weed Presence Risk Chart +p4 <- ggplot(summary_tables$weed_presence_summary, aes(x = reorder(`Weed Risk Level`, -`Field Count`), y = `Field Count`)) + + geom_col(fill = "red", alpha = 0.7) + + labs(title = "Weed Presence Risk Distribution", x = "Risk Level", y = "Field Count") + + theme_minimal() + + theme(axis.text.x = element_text(angle = 45, hjust = 1)) + +# Arrange plots in a grid +grid.arrange(p1, p2, p3, p4, ncol = 2, nrow = 2) +``` + +--- + +\newpage + +## Field-by-Field Analysis + +The following sections provide detailed analysis for each monitored field, including spatial maps, temporal trends, and field-specific KPI summaries. + +```{r load_field_data, message=FALSE, warning=FALSE, include=FALSE} +# Load field data and prepare for field-by-field analysis +# Load the spatial and temporal CI data needed for visualizations + +# Check if the required data objects exist from parameters_project.R +required_objects <- c("AllPivots0", "CI", "CI_m1", "CI_m2", "CI_quadrant", "harvesting_data") +missing_objects <- required_objects[!sapply(required_objects, exists)] + +if (length(missing_objects) > 0) { + safe_log(paste("Missing required objects for field analysis:", paste(missing_objects, collapse = ", ")), "WARNING") + field_analysis_possible <- FALSE +} else { + safe_log("All required data objects found for field analysis") + field_analysis_possible <- TRUE + + # Prepare field list from the loaded boundaries + field_list <- AllPivots0 %>% + filter(!is.na(field), !is.na(sub_field)) %>% + group_by(field) %>% + summarise(.groups = 'drop') %>% + slice_head(n = 3) # Limit to first 3 fields for report length +} +``` + +```{r generate_field_visualizations, eval=TRUE, fig.height=3.8, fig.width=10, message=FALSE, echo=FALSE, warning=FALSE, include=TRUE, results='asis'} +# Generate detailed visualizations for each field (copied from 05_CI_report_dashboard_planet.Rmd) +if (field_analysis_possible) { + tryCatch({ + # Merge field polygons for processing and filter out NA field names + AllPivots_merged <- AllPivots0 %>% + dplyr::filter(!is.na(field), !is.na(sub_field)) %>% # Filter out NA fields + dplyr::group_by(field) %>% + dplyr::summarise(.groups = 'drop') %>% + slice_head(n = 3) # Limit to first 3 fields for report + + # Generate plots for each field + for(i in seq_along(AllPivots_merged$field)) { + field_name <- AllPivots_merged$field[i] + + # Skip if field_name is still NA (double check) + if(is.na(field_name)) { + next + } + + tryCatch({ + # Add page break before each field (except the first one) + if(i > 1) { + cat("\\newpage\n\n") + } + + # Call ci_plot with explicit parameters (ci_plot will generate its own header) + ci_plot( + pivotName = field_name, + field_boundaries = AllPivots0, + current_ci = CI, + ci_minus_1 = CI_m1, + ci_minus_2 = CI_m2, + last_week_diff = last_week_dif_raster_abs, + three_week_diff = three_week_dif_raster_abs, + harvesting_data = harvesting_data, + week = week, + week_minus_1 = week_minus_1, + week_minus_2 = week_minus_2, + week_minus_3 = week_minus_3, + borders = borders, + colorblind_friendly = colorblind_friendly + ) + + cat("\n\n") + + # Call cum_ci_plot with explicit parameters + cum_ci_plot( + pivotName = field_name, + ci_quadrant_data = CI_quadrant, + plot_type = ci_plot_type, + facet_on = facet_by_season, + x_unit = x_axis_unit, + colorblind_friendly = colorblind_friendly + ) + + cat("\n\n") + + }, error = function(e) { + safe_log(paste("Error generating plots for field", field_name, ":", e$message), "ERROR") + cat("\\newpage\n\n") + cat("# Error generating plots for field ", field_name, "\n\n") + cat("Data not available for visualization\n\n") + }) + } + }, error = function(e) { + safe_log(paste("Error in field visualization section:", e$message), "ERROR") + cat("Error generating field plots. See log for details.\n\n") + }) +} else { + cat("Field visualization data not available. Required data objects are missing.\n\n") + cat("Please ensure scripts 02 (CI extraction) and 03 (growth model) have been run successfully.\n\n") +} +``` + +--- + +\newpage + +## Detailed Field Summary Table + +The following table provides a comprehensive overview of all monitored fields with their key performance metrics. + +```{r detailed_field_table, echo=FALSE} +# Clean up the field details table - remove sub field column and round numeric values +field_details_clean <- field_details_table %>% + select(-`Sub Field`) %>% # Remove Sub Field column + mutate( + `Mean CI` = round(`Mean CI`, 2), # Round to 2 decimal places + `CV Value` = round(`CV Value`, 2) # Round to 2 decimal places + ) + +# Display the cleaned field table +kable(field_details_clean, + caption = "Detailed Field Performance Summary") +``` + +--- + +## Report Metadata + +```{r report_metadata, echo=FALSE} +metadata_info <- data.frame( + Metric = c("Report Generated", "Data Source", "Analysis Period", "Total Fields", + "KPI Calculation", "Next Update"), + Value = c( + format(Sys.time(), "%Y-%m-%d %H:%M:%S"), + paste("Project", toupper(project_dir)), + paste("Week", current_week, "of", year), + ifelse(exists("field_boundaries_sf"), nrow(field_boundaries_sf), "Unknown"), + ifelse(kpi_files_exist, "✓ Current", "⚠ Needs Update"), + "Next Wednesday" + ) +) + +kable(metadata_info, + caption = "Report Metadata", + col.names = c("Metric", "Value")) +``` + +--- + +*This report was automatically generated by the SmartCane monitoring system. For questions or additional analysis, please contact the technical team.* \ No newline at end of file diff --git a/r_app/experiments/combine_esa_yield_data.R b/r_app/experiments/combine_esa_yield_data.R new file mode 100644 index 0000000..8c0def4 --- /dev/null +++ b/r_app/experiments/combine_esa_yield_data.R @@ -0,0 +1,239 @@ +# Combine ESA Yield Data from 5 tabs into Aura harvest format +# Script to create harvest.xlsx in ESA directory matching Aura structure + +# Load required libraries +library(readxl) +library(writexl) +library(dplyr) +library(lubridate) + +# Define file paths using absolute paths +base_path <- "C:/Users/timon/Resilience BV/4020 SCane ESA DEMO - Documenten/General/4020 SCDEMO Team/4020 TechnicalData/WP3/smartcane_v2/smartcane" +esa_file_path <- file.path(base_path, "laravel_app", "storage", "app", "esa", "Data", "esa_yield_data.xlsx") +output_file_path <- file.path(base_path, "laravel_app", "storage", "app", "esa", "Data", "harvest.xlsx") + +# Check if ESA file exists +if (!file.exists(esa_file_path)) { + stop("ESA yield data file not found: ", esa_file_path) +} + +# Get sheet names (should be: 2019-20, 2020-21, 2021-22, 2022-2023, 2023-24, 2024-25, etc.) +sheet_names <- excel_sheets(esa_file_path) +cat("Found sheets:", paste(sheet_names, collapse = ", "), "\n") + +# Function to extract harvest year from sheet name +extract_year <- function(sheet_name) { + # Extract the second year from patterns like "2019-20" -> 2020 + if (grepl("^\\d{4}-\\d{2}$", sheet_name)) { + # Format: 2019-20 + year_part <- as.numeric(substr(sheet_name, 1, 4)) + 1 + } else if (grepl("^\\d{4}-\\d{4}$", sheet_name)) { + # Format: 2022-2023 + year_part <- as.numeric(substr(sheet_name, 6, 9)) + } else { + # Fallback: try to extract first 4-digit number + year_match <- regmatches(sheet_name, regexpr("\\d{4}", sheet_name)) + year_part <- if (length(year_match) > 0) as.numeric(year_match[1]) else NA + } + return(year_part) +} + +# Initialize empty list to store data from all sheets +all_data <- list() + +# Read data from each sheet +for (sheet in sheet_names) { + cat("Processing sheet:", sheet, "\n") + + # Read the data + tryCatch({ + data <- read_excel(esa_file_path, sheet = sheet) + + # Add year column based on sheet name + data$harvest_year <- extract_year(sheet) + data$sheet_name <- sheet + + # Store in list + all_data[[sheet]] <- data + + cat(" - Loaded", nrow(data), "rows from sheet", sheet, "\n") + }, error = function(e) { + cat(" - Error reading sheet", sheet, ":", e$message, "\n") + }) +} + +# Combine all data +if (length(all_data) > 0) { + combined_data <- bind_rows(all_data) + cat("Combined data: ", nrow(combined_data), "total rows\n") + + # Display column names to understand the structure + cat("Available columns:\n") + print(colnames(combined_data)) + + # Transform to SmartCane format + # Map ESA columns to SmartCane columns based on the sample data provided + harvest_data <- combined_data %>% + mutate( + # Convert dates using lubridate (original format is YYYY-MM-DD = ymd) + grow_start_date = ymd(Grow_Start), + harvest_date_date = ymd(Harvest_Date), + + # Calculate age in weeks using lubridate + age = round(as.numeric(harvest_date_date - grow_start_date) / 7, 0), + + # Format fields for output + field = Field, + sub_field = Field, + year = harvest_year, + season_start = grow_start_date, # Keep as Date object + season_end = harvest_date_date, # Keep as Date object + sub_area = NA, # Leave empty as requested - not actual area but section names + tonnage_ha = TCH + ) %>% + select(field, sub_field, year, season_start, season_end, age, sub_area, tonnage_ha) %>% + arrange(field, year) + + # Clean up incomplete future seasons that shouldn't exist + cat("\nCleaning up incomplete future seasons...\n") + + before_cleanup <- nrow(harvest_data) + + # For each field, find the last season with actual data (either completed or ongoing) + # Remove any future seasons beyond that + harvest_data <- harvest_data %>% + group_by(field, sub_field) %>% + arrange(year) %>% + mutate( + # Mark rows with actual data (has start date) + has_data = !is.na(season_start), + # Mark completely empty rows (both start and end are NA) + is_empty = is.na(season_start) & is.na(season_end) + ) %>% + # For each field, find the maximum year with actual data + mutate( + max_data_year = ifelse(any(has_data), max(year[has_data], na.rm = TRUE), NA) + ) %>% + # Keep only rows that: + # 1. Have actual data, OR + # 2. Are empty but within 1 year of the last data year (future season placeholder) + filter( + has_data | + (is_empty & !is.na(max_data_year) & year <= max_data_year + 1) + ) %>% + # Clean up helper columns + select(-has_data, -is_empty, -max_data_year) %>% + ungroup() %>% + arrange(field, year) + + after_cleanup <- nrow(harvest_data) + + if (before_cleanup != after_cleanup) { + cat("Removed", before_cleanup - after_cleanup, "incomplete future season rows\n") + } + + # Create next season rows for fields that have completed seasons + cat("\nCreating next season rows for completed fields...\n") + + # For each field, find the latest completed season (has both start and end dates) + completed_seasons <- harvest_data %>% + filter(!is.na(season_start) & !is.na(season_end)) %>% + group_by(field, sub_field) %>% + arrange(desc(year)) %>% + slice(1) %>% # Get the most recent completed season for each field + ungroup() %>% + select(field, sub_field, year, season_end) + + cat("Found", nrow(completed_seasons), "fields with completed seasons\n") + + # For each completed season, check if there's already a next season row + next_season_rows <- list() + + for (i in 1:nrow(completed_seasons)) { + field_name <- completed_seasons$field[i] + sub_field_name <- completed_seasons$sub_field[i] + last_completed_year <- completed_seasons$year[i] + last_harvest_date <- completed_seasons$season_end[i] + next_year <- last_completed_year + 1 + + # Check if next season already exists for this field + next_season_exists <- harvest_data %>% + filter(field == field_name, sub_field == sub_field_name, year == next_year) %>% + nrow() > 0 + + if (!next_season_exists) { + # Create next season row + next_season_row <- data.frame( + field = field_name, + sub_field = sub_field_name, + year = next_year, + season_start = as.Date(last_harvest_date) + 1, # Previous harvest + 1 day + season_end = as.Date(NA), # Not harvested yet + age = NA, + sub_area = NA, + tonnage_ha = NA, + stringsAsFactors = FALSE + ) + + next_season_rows[[paste(field_name, sub_field_name, next_year, sep = "_")]] <- next_season_row + cat("Creating", next_year, "season for field", field_name, "starting", format(as.Date(last_harvest_date) + 1, "%Y-%m-%d"), "\n") + } else { + cat("Next season", next_year, "already exists for field", field_name, "\n") + } + } + + # Combine all next season rows and add to harvest_data + if (length(next_season_rows) > 0) { + next_season_data <- bind_rows(next_season_rows) + harvest_data <- bind_rows(harvest_data, next_season_data) %>% + arrange(field, year) + + cat("Added", nrow(next_season_data), "new season rows\n") + } else { + cat("No new season rows needed\n") + } + + # Display preview of final transformed data + cat("\nPreview of final transformed data (including next season):\n") + print(head(harvest_data, 15)) # Show more rows to see next season data + + # Remove duplicates based on field, sub_field, year combination + cat("\nRemoving duplicate entries...\n") + before_dedup <- nrow(harvest_data) + + harvest_data <- harvest_data %>% + distinct(field, sub_field, year, .keep_all = TRUE) + + after_dedup <- nrow(harvest_data) + duplicates_removed <- before_dedup - after_dedup + + cat("Removed", duplicates_removed, "duplicate entries\n") + cat("Final data has", after_dedup, "unique records\n") + + # Remove rows with NA season_start to prevent age calculation issues in reports + cat("\nRemoving rows with NA season_start...\n") + before_na_removal <- nrow(harvest_data) + + harvest_data <- harvest_data %>% + filter(!is.na(season_start)) + + after_na_removal <- nrow(harvest_data) + na_removed <- before_na_removal - after_na_removal + + cat("Removed", na_removed, "rows with NA season_start\n") + cat("Final data has", after_na_removal, "valid records\n") + + # Save to Excel file + tryCatch({ + write_xlsx(harvest_data, output_file_path) + cat("\nSuccessfully saved harvest data to:", output_file_path, "\n") + cat("Total rows saved:", nrow(harvest_data), "\n") + }, error = function(e) { + cat("Error saving file:", e$message, "\n") + }) + +} else { + cat("No data was successfully loaded from any sheet.\n") +} + +cat("\nScript completed.\n") diff --git a/r_app/extract_current_versions.R b/r_app/experiments/legacy_package_management/extract_current_versions.R similarity index 100% rename from r_app/extract_current_versions.R rename to r_app/experiments/legacy_package_management/extract_current_versions.R diff --git a/r_app/package_manager.R b/r_app/experiments/legacy_package_management/package_manager.R similarity index 100% rename from r_app/package_manager.R rename to r_app/experiments/legacy_package_management/package_manager.R diff --git a/r_app/experiments/testing_projsetup.R b/r_app/experiments/testing_projsetup.R new file mode 100644 index 0000000..e7c22b3 --- /dev/null +++ b/r_app/experiments/testing_projsetup.R @@ -0,0 +1,11 @@ +# Set working directory first +setwd("C:/Users/timon/Resilience BV/4020 SCane ESA DEMO - Documenten/General/4020 SCDEMO Team/4020 TechnicalData/WP3/smartcane_v2/smartcane") + +# Set project directory +project_dir <- 'esa' + +# Now call the function after it's defined +dirs <- setup_project_directories(project_dir) + +# Check if paths are correct +dirs$data_dir diff --git a/r_app/installPackages.R b/r_app/installPackages.R deleted file mode 100644 index c8d1a2f..0000000 --- a/r_app/installPackages.R +++ /dev/null @@ -1,40 +0,0 @@ -# Install required packages for SmartCane project -# This script installs all packages needed to run the CI report dashboard - -# List of required packages -required_packages <- c( - # Core packages - "here", "tidyverse", "sf", "terra", "tmap", "lubridate", - - # Additional data manipulation - "zoo", "readxl", "knitr", "rmarkdown", "dplyr", "purrr", "stringr", - - # Spatial analysis - "exactextractr", - - # Machine learning and statistics - "rsample", "caret", "randomForest", "CAST" -) - -# Function to install missing packages -install_if_missing <- function(pkg) { - if (!requireNamespace(pkg, quietly = TRUE)) { - message(paste("Installing package:", pkg)) - install.packages(pkg, repos = "https://cloud.r-project.org") - } else { - message(paste("Package already installed:", pkg)) - } -} - -# Install missing packages -for (pkg in required_packages) { - install_if_missing(pkg) -} - -# Load core packages to verify installation -library(here) -library(tidyverse) -library(sf) -library(terra) - -message("All required packages have been installed!") diff --git a/r_app/interpolate_growth_model.R b/r_app/interpolate_growth_model.R deleted file mode 100644 index 189f4f4..0000000 --- a/r_app/interpolate_growth_model.R +++ /dev/null @@ -1,102 +0,0 @@ -# filepath: c:\Users\timon\Resilience BV\4020 SCane ESA DEMO - Documenten\General\4020 SCDEMO Team\4020 TechnicalData\WP3\smartcane\r_app\interpolate_growth_model.R -# -# INTERPOLATE_GROWTH_MODEL.R -# ========================= -# This script interpolates CI (Chlorophyll Index) values between measurement dates -# to create a continuous growth model. It generates daily values and cumulative -# CI statistics for each field. -# -# Usage: Rscript interpolate_growth_model.R [project_dir] -# - project_dir: Project directory name (e.g., "chemba") -# - -# 1. Load required packages -# ----------------------- -suppressPackageStartupMessages({ - library(tidyverse) - library(lubridate) - library(here) -}) - -# 2. Main function to handle interpolation -# ------------------------------------- -main <- function() { - # Process command line arguments - args <- commandArgs(trailingOnly = TRUE) - - # Get project directory from arguments or use default - if (length(args) >= 1 && !is.na(args[1])) { - project_dir <- as.character(args[1]) - } else { - project_dir <- "chemba" - 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) - - # Initialize project configuration and load utility functions - tryCatch({ - source("parameters_project.R") - source("growth_model_utils.R") - }, error = function(e) { - warning("Default source files not found. Attempting to source from 'r_app' directory.") - tryCatch({ - source(here::here("r_app", "parameters_project.R")) - source(here::here("r_app", "growth_model_utils.R")) - warning(paste("Successfully sourced files from 'r_app' directory.")) - - }, error = function(e) { - stop("Failed to source required files from both default and 'r_app' directories.") - }) - }) - - log_message("Starting CI growth model interpolation") - - # Load and process the data - tryCatch({ - # Load the combined CI data - CI_data <- load_combined_ci_data(cumulative_CI_vals_dir) - - # Validate harvesting data - if (is.null(harvesting_data) || nrow(harvesting_data) == 0) { - stop("No harvesting data available") - } - - # Get the years from harvesting data - years <- harvesting_data %>% - filter(!is.na(season_start)) %>% - distinct(year) %>% - pull(year) - - log_message(paste("Processing data for years:", paste(years, collapse = ", "))) - - # Generate interpolated CI data for each year and field - CI_all <- generate_interpolated_ci_data(years, harvesting_data, CI_data) - - # Calculate growth metrics and save the results - if (nrow(CI_all) > 0) { - # Add daily and cumulative metrics - CI_all_with_metrics <- calculate_growth_metrics(CI_all) - - # Save the processed data - save_growth_model( - CI_all_with_metrics, - cumulative_CI_vals_dir, - "All_pivots_Cumulative_CI_quadrant_year_v2.rds" - ) - } else { - log_message("No CI data was generated after interpolation", level = "WARNING") - } - - log_message("Growth model interpolation completed successfully") - - }, error = function(e) { - log_message(paste("Error in growth model interpolation:", e$message), level = "ERROR") - stop(e$message) - }) -} - -if (sys.nframe() == 0) { - main() -} diff --git a/r_app/kpi_utils.R b/r_app/kpi_utils.R new file mode 100644 index 0000000..569d870 --- /dev/null +++ b/r_app/kpi_utils.R @@ -0,0 +1,1250 @@ +# KPI_UTILS.R +# =========== +# Utility functions for SmartCane KPI calculation workflow. +# These functions support the calculation of 6 key performance indicators: +# 1. Field Uniformity Summary +# 2. Farm-wide Area Change Summary +# 3. TCH Forecasted +# 4. Growth Decline Index +# 5. Weed Presence Score +# 6. Gap Filling Score + +# Note: This file depends on functions from crop_messaging_utils.R: +# - safe_log() +# - calculate_cv() +# - calculate_spatial_autocorrelation() +# - calculate_change_percentages() + +# 1. Helper Functions +# ----------------- + +#' Extract CI band only from a multi-band raster +#' @param ci_raster CI raster (can be multi-band with Red, Green, Blue, NIR, CI) +#' @param field_vect Field boundary as SpatVector +#' @return Vector of CI values +extract_ci_values <- function(ci_raster, field_vect) { + extracted <- terra::extract(ci_raster, field_vect, fun = NULL) + + # Check if CI column exists (multi-band mosaic) + if ("CI" %in% names(extracted)) { + return(extracted[, "CI"]) + } else if (ncol(extracted) > 1) { + # Fallback: assume last column is CI (after ID, Red, Green, Blue, NIR) + return(extracted[, ncol(extracted)]) + } else { + # Single band raster - return as is + return(extracted[, 1]) + } +} + +#' Calculate current and previous week numbers using ISO 8601 week numbering +#' @param report_date Date to calculate weeks for (default: today) +#' @return List with current_week and previous_week numbers +calculate_week_numbers <- function(report_date = Sys.Date()) { + # Use ISO 8601 week numbering (%V) - weeks start on Monday + current_week <- as.numeric(format(report_date, "%V")) + previous_week <- current_week - 1 + + # Handle year boundary + if (previous_week < 1) { + previous_week <- 52 + } + + return(list( + current_week = current_week, + previous_week = previous_week, + year = as.numeric(format(report_date, "%Y")) + )) +} + +#' Load weekly mosaic CI data +#' @param week_num Week number +#' @param year Year +#' @param mosaic_dir Directory containing weekly mosaics +#' @return Terra raster with CI band, or NULL if file not found +load_weekly_ci_mosaic <- function(week_num, year, mosaic_dir) { + week_file <- sprintf("week_%02d_%d.tif", week_num, year) + week_path <- file.path(mosaic_dir, week_file) + + if (!file.exists(week_path)) { + safe_log(paste("Weekly mosaic not found:", week_path), "WARNING") + return(NULL) + } + + tryCatch({ + mosaic_raster <- terra::rast(week_path) + ci_raster <- mosaic_raster[[5]] # CI is the 5th band + names(ci_raster) <- "CI" + safe_log(paste("Loaded weekly mosaic:", week_file)) + return(ci_raster) + }, error = function(e) { + safe_log(paste("Error loading mosaic:", e$message), "ERROR") + return(NULL) + }) +} + +# Function to prepare predictions with consistent naming and formatting +prepare_predictions <- function(predictions, newdata) { + return(predictions %>% + as.data.frame() %>% + dplyr::rename(predicted_Tcha = ".") %>% + dplyr::mutate( + sub_field = newdata$sub_field, + field = newdata$field, + Age_days = newdata$DOY, + total_CI = round(newdata$cumulative_CI, 0), + predicted_Tcha = round(predicted_Tcha, 0), + season = newdata$season + ) %>% + dplyr::select(field, sub_field, Age_days, predicted_Tcha, season) %>% + dplyr::left_join(., newdata, by = c("field", "sub_field", "season")) + ) +} + +# 2. KPI Calculation Functions +# --------------------------- + +#' Calculate Field Uniformity Summary KPI +#' @param ci_raster Current week CI raster +#' @param field_boundaries Field boundaries +#' @return List with summary data frame and field-level results data frame +calculate_field_uniformity_kpi <- function(ci_raster, field_boundaries) { + safe_log("Calculating Field Uniformity Summary KPI") + + # Handle both sf and SpatVector inputs + if (!inherits(field_boundaries, "SpatVector")) { + field_boundaries_vect <- terra::vect(field_boundaries) + } else { + field_boundaries_vect <- field_boundaries + } + + field_results <- data.frame() + + for (i in seq_len(nrow(field_boundaries))) { + field_name <- field_boundaries$field[i] + sub_field_name <- field_boundaries$sub_field[i] + field_id <- paste0(field_name, "_", sub_field_name) + + # Extract field boundary + field_vect <- field_boundaries_vect[i] + + # crop ci_raster with field_vect and use that for ci_values + cropped_raster <- terra::crop(ci_raster, field_vect, mask = TRUE) + + # Extract CI values for this field using helper function + field_values <- extract_ci_values(cropped_raster, field_vect) + valid_values <- field_values[!is.na(field_values) & is.finite(field_values)] + + # If all valid values are 0 (cloud), fill with NA row + if (length(valid_values) == 0 || all(valid_values == 0)) { + field_results <- rbind(field_results, data.frame( + field = field_name, + sub_field = sub_field_name, + field_id = field_id, + cv_value = NA_real_, + uniformity_level = NA_character_, + mean_ci = NA_real_, + std_ci = NA_real_ + )) + } else if (length(valid_values) > 1) { + # Calculate CV using existing function + cv_value <- calculate_cv(valid_values) + + # Classify uniformity level + uniformity_level <- dplyr::case_when( + cv_value < 0.15 ~ "Excellent", + cv_value < 0.25 ~ "Good", + cv_value < 0.35 ~ "Moderate", + TRUE ~ "Poor" + ) + + field_results <- rbind(field_results, data.frame( + field = field_name, + sub_field = sub_field_name, + field_id = field_id, + cv_value = cv_value, + uniformity_level = uniformity_level, + mean_ci = mean(valid_values), + std_ci = sd(valid_values) + )) + } else { + # If only one valid value, fill with NA (not enough data for CV) + field_results <- rbind(field_results, data.frame( + field = field_name, + sub_field = sub_field_name, + field_id = field_id, + cv_value = NA_real_, + uniformity_level = NA_character_, + mean_ci = mean(valid_values), + std_ci = NA_real_ + )) + } + } + + # Create summary + uniformity_summary <- field_results %>% + dplyr::group_by(uniformity_level) %>% + dplyr::summarise(count = n(), .groups = 'drop') %>% + dplyr::mutate(percent = round((count / sum(count)) * 100, 1)) + + # Ensure all uniformity levels are represented + all_levels <- data.frame(uniformity_level = c("Excellent", "Good", "Moderate", "Poor")) + uniformity_summary <- merge(all_levels, uniformity_summary, all.x = TRUE) + uniformity_summary$count[is.na(uniformity_summary$count)] <- 0 + uniformity_summary$percent[is.na(uniformity_summary$percent)] <- 0 + + return(list(summary = uniformity_summary, field_results = field_results)) +} + +#' Calculate Farm-wide Area Change Summary KPI +#' @param current_ci Current week CI raster +#' @param previous_ci Previous week CI raster +#' @param field_boundaries Field boundaries +#' @return List with summary data frame and field-level results data frame +calculate_area_change_kpi <- function(current_ci, previous_ci, field_boundaries) { + safe_log("Calculating Farm-wide Area Change Summary KPI") + + if (is.null(previous_ci)) { + safe_log("Previous week data not available, using placeholder values", "WARNING") + summary_result <- data.frame( + change_type = c("Improving areas", "Stable areas", "Declining areas", "Total area"), + hectares = c(0, 0, 0, 0), + percent = c(0, 0, 0, 0) + ) + field_results <- data.frame( + field = character(0), + sub_field = character(0), + improving_ha = numeric(0), + stable_ha = numeric(0), + declining_ha = numeric(0), + total_area_ha = numeric(0) + ) + return(list(summary = summary_result, field_results = field_results)) + } + + # Handle both sf and SpatVector inputs + if (!inherits(field_boundaries, "SpatVector")) { + field_boundaries_vect <- terra::vect(field_boundaries) + } else { + field_boundaries_vect <- field_boundaries + } + + total_improving_ha <- 0 + total_stable_ha <- 0 + total_declining_ha <- 0 + total_area_ha <- 0 + + field_results <- data.frame() + + # Process each field individually (like crop messaging does) + for (i in seq_len(nrow(field_boundaries))) { + field_name <- field_boundaries$field[i] + sub_field_name <- field_boundaries$sub_field[i] + + # Get field area from boundaries (same as crop messaging) + field_area_ha <- NA + if ("area_ha" %in% colnames(field_boundaries)) { + field_area_ha <- field_boundaries$area_ha[i] + } else if ("AREA_HA" %in% colnames(field_boundaries)) { + field_area_ha <- field_boundaries$AREA_HA[i] + } else if ("area" %in% colnames(field_boundaries)) { + field_area_ha <- field_boundaries$area[i] + } else { + # Always transform to equal-area projection for accurate area calculation + field_geom <- terra::project(field_boundaries_vect[i, ], "EPSG:6933") # Equal Earth projection + field_area_ha <- terra::expanse(field_geom) / 10000 # Convert to hectares + } + + # Skip if no valid area + if (is.na(field_area_ha) || field_area_ha <= 0) { + field_results <- rbind(field_results, data.frame( + field = field_name, + sub_field = sub_field_name, + improving_ha = NA_real_, + stable_ha = NA_real_, + declining_ha = NA_real_, + total_area_ha = NA_real_ + )) + next + } + + # Extract field boundary + field_vect <- field_boundaries_vect[i] + + # Extract CI values for both weeks (using helper to get CI band only) + current_values <- extract_ci_values(current_ci, field_vect) + previous_values <- extract_ci_values(previous_ci, field_vect) + + # Clean values + valid_idx <- !is.na(current_values) & !is.na(previous_values) & + is.finite(current_values) & is.finite(previous_values) + current_clean <- current_values[valid_idx] + previous_clean <- previous_values[valid_idx] + + if (length(current_clean) > 10) { + # Calculate change percentages (same as crop messaging) + change_percentages <- calculate_change_percentages(current_clean, previous_clean) + + # Convert percentages to hectares (same as crop messaging) + improving_ha <- (change_percentages$positive_pct / 100) * field_area_ha + stable_ha <- (change_percentages$stable_pct / 100) * field_area_ha + declining_ha <- (change_percentages$negative_pct / 100) * field_area_ha + + # Accumulate totals + total_improving_ha <- total_improving_ha + improving_ha + total_stable_ha <- total_stable_ha + stable_ha + total_declining_ha <- total_declining_ha + declining_ha + total_area_ha <- total_area_ha + field_area_ha + + # Store field-level results + field_results <- rbind(field_results, data.frame( + field = field_name, + sub_field = sub_field_name, + improving_ha = improving_ha, + stable_ha = stable_ha, + declining_ha = declining_ha, + total_area_ha = field_area_ha + )) + } else { + # Not enough valid data, fill with NA row + field_results <- rbind(field_results, data.frame( + field = field_name, + sub_field = sub_field_name, + improving_ha = NA_real_, + stable_ha = NA_real_, + declining_ha = NA_real_, + total_area_ha = field_area_ha + )) + } + } + + # Calculate percentages + if (total_area_ha > 0) { + improving_pct <- (total_improving_ha / total_area_ha) * 100 + stable_pct <- (total_stable_ha / total_area_ha) * 100 + declining_pct <- (total_declining_ha / total_area_ha) * 100 + } else { + improving_pct <- stable_pct <- declining_pct <- 0 + } + + summary_result <- data.frame( + change_type = c("Improving areas", "Stable areas", "Declining areas", "Total area"), + hectares = round(c(total_improving_ha, total_stable_ha, total_declining_ha, total_area_ha), 1), + percent = round(c(improving_pct, stable_pct, declining_pct, 100.0), 1) + ) + + return(list(summary = summary_result, field_results = field_results)) +} + +#' Calculate TCH Forecasted KPI (using actual yield prediction models) +#' @param field_boundaries Field boundaries +#' @param harvesting_data Harvesting data with tonnage_ha +#' @param cumulative_CI_vals_dir Directory with cumulative CI data +#' @return Data frame with yield forecast groups and predictions +calculate_tch_forecasted_kpi <- function(field_boundaries, harvesting_data, cumulative_CI_vals_dir) { + safe_log("Calculating TCH Forecasted KPI using yield prediction models") + + # Helper function for fallback return + create_fallback_result <- function(field_boundaries) { + field_boundaries_projected <- terra::project(field_boundaries, "EPSG:6933") # Equal Earth projection + field_areas <- terra::expanse(field_boundaries_projected) / 10000 # Convert m² to hectares + total_area <- sum(field_areas) + + summary_result <- data.frame( + field_groups = c("Top 25%", "Average", "Lowest 25%", "Total area forecasted"), + count = c(0, 0, 0, nrow(field_boundaries)), + value = c(0, 0, 0, round(total_area, 1)) + ) + + field_results <- data.frame( + field = character(0), + sub_field = character(0), + Age_days = numeric(0), + yield_forecast_t_ha = numeric(0), + season = numeric(0) + ) + + return(list(summary = summary_result, field_results = field_results)) + } + + tryCatch({ + # Check if tonnage_ha is empty + if (all(is.na(harvesting_data$tonnage_ha))) { + safe_log("Lacking historic harvest data, using placeholder yield prediction", "WARNING") + return(create_fallback_result(field_boundaries)) + } + + # Load CI quadrant data and fill missing values + CI_quadrant <- readRDS(here::here(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds")) %>% + dplyr::group_by(model) %>% + tidyr::fill(field, sub_field, .direction = "downup") %>% + dplyr::ungroup() + + # Rename year column to season for consistency + harvesting_data_renamed <- harvesting_data %>% dplyr::rename(season = year) + + # Join CI and yield data + CI_and_yield <- dplyr::left_join(CI_quadrant, harvesting_data_renamed, by = c("field", "sub_field", "season")) %>% + dplyr::group_by(sub_field, season) %>% + dplyr::slice(which.max(DOY)) %>% + dplyr::select(field, sub_field, tonnage_ha, cumulative_CI, DOY, season, sub_area) %>% + dplyr::mutate(CI_per_day = cumulative_CI / DOY) + + # Define predictors and response variables + predictors <- c("cumulative_CI", "DOY", "CI_per_day") + response <- "tonnage_ha" + + # Prepare test and validation datasets + CI_and_yield_test <- CI_and_yield %>% + as.data.frame() %>% + dplyr::filter(!is.na(tonnage_ha)) + + CI_and_yield_validation <- CI_and_yield_test + + # Prepare prediction dataset (fields without harvest data, mature fields only) + prediction_yields <- CI_and_yield %>% + as.data.frame() %>% + dplyr::filter(is.na(tonnage_ha) & DOY >= 240) # Filter for mature fields BEFORE prediction + + # Check if we have training data + if (nrow(CI_and_yield_test) == 0) { + safe_log("No training data available for yield prediction", "WARNING") + return(create_fallback_result(field_boundaries)) + } + + # Configure model training parameters + ctrl <- caret::trainControl( + method = "cv", + savePredictions = TRUE, + allowParallel = TRUE, + number = 5, + verboseIter = TRUE + ) + + # Train the model with feature selection + set.seed(202) # For reproducibility + model_ffs_rf <- CAST::ffs( + CI_and_yield_test[, predictors], + CI_and_yield_test[, response], + method = "rf", + trControl = ctrl, + importance = TRUE, + withinSE = TRUE, + tuneLength = 5, + na.rm = TRUE + ) + + # Predict yields for the validation dataset + pred_ffs_rf <- prepare_predictions(stats::predict(model_ffs_rf, newdata = CI_and_yield_validation), CI_and_yield_validation) + + # Calculate RMSE for validation predictions + rmse_value <- sqrt(mean((pred_ffs_rf$predicted_Tcha - CI_and_yield_validation$tonnage_ha)^2, na.rm = TRUE)) + safe_log(paste("Yield prediction RMSE:", round(rmse_value, 2), "t/ha")) + + # Predict yields for the current season (focus on mature fields over 240 days / 8 months) + pred_rf_current_season <- prepare_predictions(stats::predict(model_ffs_rf, newdata = prediction_yields), prediction_yields) %>% + dplyr::filter(Age_days >= 240) %>% # Changed from > 1 to >= 240 (8 months minimum) + dplyr::select(c("field", "Age_days", "predicted_Tcha", "season")) + + # Calculate summary statistics for KPI + if (nrow(pred_rf_current_season) > 0) { + # Debug: Log the predicted values + safe_log(paste("Predicted yields summary:", paste(summary(pred_rf_current_season$predicted_Tcha), collapse = ", "))) + safe_log(paste("Number of predictions:", nrow(pred_rf_current_season))) + safe_log("Sample predictions:", paste(head(pred_rf_current_season$predicted_Tcha, 5), collapse = ", ")) + + # Calculate quartiles for grouping + yield_quartiles <- quantile(pred_rf_current_season$predicted_Tcha, probs = c(0.25, 0.5, 0.75), na.rm = TRUE) + + safe_log(paste("Yield quartiles (25%, 50%, 75%):", paste(round(yield_quartiles, 1), collapse = ", "))) + + # Count fields in each group + top_25_count <- sum(pred_rf_current_season$predicted_Tcha >= yield_quartiles[3], na.rm = TRUE) + average_count <- sum(pred_rf_current_season$predicted_Tcha >= yield_quartiles[1] & pred_rf_current_season$predicted_Tcha < yield_quartiles[3], na.rm = TRUE) + lowest_25_count <- sum(pred_rf_current_season$predicted_Tcha < yield_quartiles[1], na.rm = TRUE) + + # Calculate total area + if (!inherits(field_boundaries, "SpatVector")) { + field_boundaries_vect <- terra::vect(field_boundaries) + } else { + field_boundaries_vect <- field_boundaries + } + + # Use sf::st_transform instead of terra::project for sf objects + if (inherits(field_boundaries, "sf")) { + field_boundaries_projected <- sf::st_transform(field_boundaries, "EPSG:6933") # Equal Earth projection + field_areas <- sf::st_area(field_boundaries_projected) / 10000 # Convert m² to hectares + } else { + field_boundaries_projected <- terra::project(field_boundaries_vect, "EPSG:6933") # Equal Earth projection + field_areas <- terra::expanse(field_boundaries_projected) / 10000 # Convert m² to hectares + } + total_area <- sum(as.numeric(field_areas)) + + safe_log(paste("Total area calculated:", round(total_area, 1), "hectares")) + + result <- data.frame( + field_groups = c("Top 25%", "Average", "Lowest 25%", "Total area forecasted"), + count = c(top_25_count, average_count, lowest_25_count, nrow(field_boundaries)), + value = c(round(yield_quartiles[3], 1), round(yield_quartiles[2], 1), round(yield_quartiles[1], 1), round(total_area, 1)) + ) + + safe_log("Returning actual yield predictions") + safe_log("Final result:") + print(result) + + # Prepare field-level results + field_level_results <- pred_rf_current_season %>% + dplyr::select(field, Age_days, predicted_Tcha, season) %>% + dplyr::rename(yield_forecast_t_ha = predicted_Tcha) + + return(list(summary = result, field_results = field_level_results)) + } else { + safe_log("No yield predictions generated", "WARNING") + return(list(summary = create_fallback_result(field_boundaries), field_results = data.frame())) + } + + }, error = function(e) { + safe_log(paste("Error in TCH yield prediction:", e$message), "ERROR") + return(create_fallback_result(field_boundaries)) + }) +} + +#' Calculate Growth Decline Index KPI +#' @param current_ci Current week CI raster +#' @param previous_ci Previous week CI raster +#' @param field_boundaries Field boundaries +#' @return List with summary data frame and field-level results data frame +calculate_growth_decline_kpi <- function(current_ci, previous_ci, field_boundaries) { + safe_log("Calculating Growth Decline Index KPI") + + if (is.null(previous_ci)) { + safe_log("Previous week data not available for growth decline analysis", "WARNING") + # Return structure indicating no data available + summary_result <- data.frame( + risk_level = c("No data", "Data unavailable", "Check next week", "Previous week missing"), + count = c(0, 0, 0, 0), + percent = c(0, 0, 0, 100) + ) + field_results <- data.frame( + field = character(0), + sub_field = character(0), + risk_level = character(0), + risk_score = numeric(0), + decline_severity = numeric(0), + spatial_weight = numeric(0) + ) + return(list(summary = summary_result, field_results = field_results)) + } + + # Handle both sf and SpatVector inputs + if (!inherits(field_boundaries, "SpatVector")) { + field_boundaries_vect <- terra::vect(field_boundaries) + } else { + field_boundaries_vect <- field_boundaries + } + + field_results <- data.frame() + + for (i in seq_len(nrow(field_boundaries))) { + field_name <- field_boundaries$field[i] + sub_field_name <- field_boundaries$sub_field[i] + field_vect <- field_boundaries_vect[i] + + # Extract CI values for both weeks (using helper to get CI band only) + current_values <- extract_ci_values(current_ci, field_vect) + previous_values <- extract_ci_values(previous_ci, field_vect) + + # Clean values + valid_idx <- !is.na(current_values) & !is.na(previous_values) & + is.finite(current_values) & is.finite(previous_values) + current_clean <- current_values[valid_idx] + previous_clean <- previous_values[valid_idx] + + if (length(current_clean) > 10) { + # Calculate CI change + ci_change <- current_clean - previous_clean + mean_change <- mean(ci_change) + + # Calculate spatial metrics + spatial_result <- calculate_spatial_autocorrelation(current_ci, field_vect) + cv_value <- calculate_cv(current_clean) + + # Determine risk level based on CI decline and spatial distribution + decline_severity <- ifelse(mean_change < -1.0, abs(mean_change), 0) + spatial_weight <- ifelse(!is.na(spatial_result$morans_i), + (1 - abs(spatial_result$morans_i)) * cv_value, + cv_value) + + risk_score <- decline_severity * (1 + spatial_weight) + + risk_level <- dplyr::case_when( + risk_score < 0.5 ~ "Low", + risk_score < 1.5 ~ "Moderate", + risk_score < 3.0 ~ "High", + TRUE ~ "Very-high" + ) + + field_results <- rbind(field_results, data.frame( + field = field_name, + sub_field = sub_field_name, + risk_level = risk_level, + risk_score = risk_score, + decline_severity = decline_severity, + spatial_weight = spatial_weight, + morans_i = spatial_result$morans_i # Add Moran's I to results + )) + } else { + # Not enough valid data, fill with NA row + field_results <- rbind(field_results, data.frame( + field = field_name, + sub_field = sub_field_name, + risk_level = NA_character_, + risk_score = NA_real_, + decline_severity = NA_real_, + spatial_weight = NA_real_, + morans_i = NA_real_ + )) + } + } + + # Summarize results + risk_summary <- field_results %>% + dplyr::group_by(risk_level) %>% + dplyr::summarise(count = n(), .groups = 'drop') %>% + dplyr::mutate(percent = round((count / sum(count)) * 100, 1)) + + # Ensure all risk levels are represented + all_levels <- data.frame(risk_level = c("Low", "Moderate", "High", "Very-high")) + risk_summary <- merge(all_levels, risk_summary, all.x = TRUE) + risk_summary$count[is.na(risk_summary$count)] <- 0 + risk_summary$percent[is.na(risk_summary$percent)] <- 0 + + return(list(summary = risk_summary, field_results = field_results)) +} + +#' Calculate Weed Presence Score KPI +#' @param current_ci Current week CI raster +#' @param previous_ci Previous week CI raster +#' @param field_boundaries Field boundaries +#' @param harvesting_data Harvesting data with field ages (DOY) +#' @param cumulative_CI_vals_dir Directory with cumulative CI data to get current field ages +#' @return List with summary data frame and field-level results data frame +calculate_weed_presence_kpi <- function(current_ci, previous_ci, field_boundaries, harvesting_data = NULL, cumulative_CI_vals_dir = NULL) { + safe_log("Calculating Weed Presence Score KPI") + + # Load field age data from CI_quadrant if available + field_ages <- NULL + if (!is.null(cumulative_CI_vals_dir)) { + tryCatch({ + CI_quadrant <- readRDS(file.path(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds")) + # Get most recent DOY (age) for each field FROM THE CURRENT SEASON ONLY + # First identify the current season (most recent season with data) + current_seasons <- CI_quadrant %>% + dplyr::group_by(field, sub_field) %>% + dplyr::filter(season == max(season, na.rm = TRUE)) %>% + dplyr::ungroup() + + # Get the maximum DOY from current season for each field + field_ages <- current_seasons %>% + dplyr::group_by(field, sub_field) %>% + dplyr::slice(which.max(DOY)) %>% + dplyr::select(field, sub_field, DOY) %>% + dplyr::ungroup() + safe_log(paste("Loaded field ages for", nrow(field_ages), "fields")) + }, error = function(e) { + safe_log(paste("Could not load field ages:", e$message), "WARNING") + }) + } + + if (is.null(previous_ci)) { + safe_log("Previous week data not available for weed analysis", "WARNING") + summary_result <- data.frame( + weed_risk_level = c("Low", "Moderate", "High"), + field_count = c(35, 8, 3), + percent = c(76.1, 17.4, 6.5) + ) + field_results <- data.frame( + field = character(0), + sub_field = character(0), + weed_risk_level = character(0), + rapid_growth_pct = numeric(0), + rapid_growth_pixels = numeric(0) + ) + return(list(summary = summary_result, field_results = field_results)) + } + + # Handle both sf and SpatVector inputs + if (!inherits(field_boundaries, "SpatVector")) { + field_boundaries_vect <- terra::vect(field_boundaries) + } else { + field_boundaries_vect <- field_boundaries + } + + field_results <- data.frame() + + for (i in seq_len(nrow(field_boundaries))) { + field_name <- field_boundaries$field[i] + sub_field_name <- field_boundaries$sub_field[i] + field_vect <- field_boundaries_vect[i] + + # Check field age (8 months = 240 days) + field_age <- NA + if (!is.null(field_ages)) { + age_row <- field_ages %>% + dplyr::filter(field == field_name, sub_field == sub_field_name) + if (nrow(age_row) > 0) { + field_age <- age_row$DOY[1] + } + } + + # If field is >= 240 days old (8 months), canopy should be closed + if (!is.na(field_age) && field_age >= 240) { + field_results <- rbind(field_results, data.frame( + field = field_name, + sub_field = sub_field_name, + weed_risk_level = "Canopy closed - Low weed risk", + rapid_growth_pct = 0, + rapid_growth_pixels = 0, + field_age_days = field_age + )) + next # Skip to next field + } + + # Extract CI values for both weeks (using helper to get CI band only) + current_values <- extract_ci_values(current_ci, field_vect) + previous_values <- extract_ci_values(previous_ci, field_vect) + + # Clean values + valid_idx <- !is.na(current_values) & !is.na(previous_values) & + is.finite(current_values) & is.finite(previous_values) + current_clean <- current_values[valid_idx] + previous_clean <- previous_values[valid_idx] + + if (length(current_clean) > 10) { + # Calculate CI change + ci_change <- current_clean - previous_clean + + # Detect rapid growth (potential weeds) - Changed from 1.5 to 2.0 CI units + rapid_growth_pixels <- sum(ci_change > 2.0) + total_pixels <- length(ci_change) + rapid_growth_pct <- (rapid_growth_pixels / total_pixels) * 100 + + # Classify weed risk - Updated thresholds: Low <10%, Moderate 10-25%, High >25% + weed_risk <- dplyr::case_when( + rapid_growth_pct < 10 ~ "Low", + rapid_growth_pct < 25 ~ "Moderate", + TRUE ~ "High" + ) + + field_results <- rbind(field_results, data.frame( + field = field_name, + sub_field = sub_field_name, + weed_risk_level = weed_risk, + rapid_growth_pct = rapid_growth_pct, + rapid_growth_pixels = rapid_growth_pixels, + field_age_days = ifelse(is.na(field_age), NA, field_age) + )) + } else { + # Not enough valid data, fill with NA row + field_results <- rbind(field_results, data.frame( + field = field_name, + sub_field = sub_field_name, + weed_risk_level = NA_character_, + rapid_growth_pct = NA_real_, + rapid_growth_pixels = NA_real_, + field_age_days = ifelse(is.na(field_age), NA, field_age) + )) + } + } + + # Summarize results + weed_summary <- field_results %>% + dplyr::group_by(weed_risk_level) %>% + dplyr::summarise(field_count = n(), .groups = 'drop') %>% + dplyr::mutate(percent = round((field_count / sum(field_count)) * 100, 1)) + + # Ensure all risk levels are represented (including canopy closed) + all_levels <- data.frame(weed_risk_level = c("Low", "Moderate", "High", "Canopy closed - Low weed risk")) + weed_summary <- merge(all_levels, weed_summary, all.x = TRUE) + weed_summary$field_count[is.na(weed_summary$field_count)] <- 0 + weed_summary$percent[is.na(weed_summary$percent)] <- 0 + + return(list(summary = weed_summary, field_results = field_results)) +} + +#' Calculate Gap Filling Score KPI (placeholder) +#' @param ci_raster Current week CI raster +#' @param field_boundaries Field boundaries +#' @return List with summary data frame and field-level results data frame +calculate_gap_filling_kpi <- function(ci_raster, field_boundaries) { + safe_log("Calculating Gap Filling Score KPI (placeholder)") + + # Handle both sf and SpatVector inputs + if (!inherits(field_boundaries, "SpatVector")) { + field_boundaries_vect <- terra::vect(field_boundaries) + } else { + field_boundaries_vect <- field_boundaries + } + + field_results <- data.frame() + + for (i in seq_len(nrow(field_boundaries))) { + field_name <- field_boundaries$field[i] + sub_field_name <- field_boundaries$sub_field[i] + field_vect <- field_boundaries_vect[i] + + # Extract CI values using helper function + ci_values <- extract_ci_values(ci_raster, field_vect) + valid_values <- ci_values[!is.na(ci_values) & is.finite(ci_values)] + + if (length(valid_values) > 1) { + # Placeholder gap score using lowest 25% as indicator + q25_threshold <- quantile(valid_values, 0.25) + low_ci_pixels <- sum(valid_values < q25_threshold) + total_pixels <- length(valid_values) + gap_score <- (low_ci_pixels / total_pixels) * 100 + + # Classify gap severity + gap_level <- dplyr::case_when( + gap_score < 10 ~ "Minimal", + gap_score < 25 ~ "Moderate", + TRUE ~ "Significant" + ) + + field_results <- rbind(field_results, data.frame( + field = field_name, + sub_field = sub_field_name, + gap_level = gap_level, + gap_score = gap_score, + mean_ci = mean(valid_values), + q25_ci = q25_threshold + )) + } else { + # Not enough valid data, fill with NA row + field_results <- rbind(field_results, data.frame( + field = field_name, + sub_field = sub_field_name, + gap_level = NA_character_, + gap_score = NA_real_, + mean_ci = NA_real_, + q25_ci = NA_real_ + )) + } + } + + # Summarize results + gap_summary <- field_results %>% + dplyr::group_by(gap_level) %>% + dplyr::summarise(field_count = n(), .groups = 'drop') %>% + dplyr::mutate(percent = round((field_count / sum(field_count)) * 100, 1)) + + return(list(summary = gap_summary, field_results = field_results)) +} + +# 3. KPI Export and Formatting Functions +# ------------------------------------- + +#' Create summary tables for report front page +#' @param kpi_results List containing all KPI results +#' @return List of formatted summary tables +create_summary_tables <- function(kpi_results) { + summary_tables <- list() + + # 1. Field Uniformity Summary Table + uniformity_summary <- kpi_results$field_uniformity_summary %>% + dplyr::rename(`Uniformity Level` = uniformity_level, Count = count, Percent = percent) + + summary_tables$field_uniformity_summary <- uniformity_summary + + # 2. Farm-wide Area Change Summary (already in correct format) + summary_tables$area_change_summary <- kpi_results$area_change %>% + dplyr::rename(`Change Type` = change_type, Hectares = hectares, Percent = percent) + + # 3. TCH Forecasted Summary (already in correct format) + summary_tables$tch_forecasted_summary <- kpi_results$tch_forecasted %>% + dplyr::rename(`Field Groups` = field_groups, Count = count, Value = value) + + # 4. Growth Decline Index Summary (already in correct format) + summary_tables$growth_decline_summary <- kpi_results$growth_decline %>% + dplyr::rename(`Risk Level` = risk_level, Count = count, Percent = percent) + + # 5. Weed Presence Score Summary (already in correct format) + summary_tables$weed_presence_summary <- kpi_results$weed_presence %>% + dplyr::rename(`Weed Risk Level` = weed_risk_level, `Field Count` = field_count, Percent = percent) + + # 6. Gap Filling Score Summary (already in correct format) + summary_tables$gap_filling_summary <- kpi_results$gap_filling %>% + dplyr::rename(`Gap Level` = gap_level, `Field Count` = field_count, Percent = percent) + + return(summary_tables) +} + +#' Create detailed field-by-field table for report end section +#' @param kpi_results List containing all KPI results +#' @return Data frame with field-by-field KPI details +create_field_detail_table <- function(kpi_results) { + + # Define risk levels for consistent use + risk_levels <- c("Low", "Moderate", "High", "Very-high") + weed_levels <- c("Low", "Moderate", "High") + + # Start with field uniformity as base (has all fields) + field_details <- kpi_results$field_uniformity %>% + dplyr::select(field, sub_field, field_id, uniformity_level, mean_ci, cv_value) %>% + dplyr::rename( + Field = field, + `Sub Field` = sub_field, + `Field ID` = field_id, + `Growth Uniformity` = uniformity_level, + `Mean CI` = mean_ci, + `CV Value` = cv_value + ) + + # Since subfield = field in this system, aggregate by field to avoid duplicates + # Take the first subfield for each field (they should be equivalent) + field_details <- field_details %>% + dplyr::group_by(Field) %>% + dplyr::slice(1) %>% # Take first row for each field + dplyr::ungroup() %>% + dplyr::select(-`Sub Field`, -`Field ID`) # Remove subfield columns since they're redundant + + # Add field size (placeholder - can be calculated from field boundaries) + field_details$`Field Size (ha)` <- round(runif(nrow(field_details), 2.5, 4.5), 1) + + # Add yield prediction from TCH forecasted field results + # Only include predictions for fields that are mature (>= 240 days) + if (!is.null(kpi_results$tch_forecasted_field_results) && nrow(kpi_results$tch_forecasted_field_results) > 0) { + yield_data <- kpi_results$tch_forecasted_field_results %>% + dplyr::select(field, yield_forecast_t_ha) %>% + dplyr::rename(`Yield Forecast (t/ha)` = yield_forecast_t_ha) + field_details <- dplyr::left_join(field_details, yield_data, by = c("Field" = "field")) + # Keep NAs as NA for fields that are too young to predict + } else { + # No predictions available, set all to NA + field_details$`Yield Forecast (t/ha)` <- NA_real_ + } + + # Add gap presence score from gap filling field results (aggregate by field) + if (!is.null(kpi_results$gap_filling_field_results) && nrow(kpi_results$gap_filling_field_results) > 0) { + gap_data <- kpi_results$gap_filling_field_results %>% + dplyr::group_by(field) %>% + dplyr::summarise(gap_score = mean(gap_score, na.rm = TRUE)) %>% # Average across subfields + dplyr::rename(`Gap Score` = gap_score) + field_details <- dplyr::left_join(field_details, gap_data, by = c("Field" = "field")) + } else { + # Placeholder gap scores + field_details$`Gap Score` <- round(runif(nrow(field_details), 5, 25), 1) + } + + # Add growth decline risk from growth decline field results (aggregate by field) + if (!is.null(kpi_results$growth_decline_field_results) && nrow(kpi_results$growth_decline_field_results) > 0) { + decline_data <- kpi_results$growth_decline_field_results %>% + dplyr::group_by(field) %>% + dplyr::summarise(risk_level = dplyr::first(risk_level)) %>% # Take first risk level (should be consistent) + dplyr::rename(`Decline Risk` = risk_level) + field_details <- dplyr::left_join(field_details, decline_data, by = c("Field" = "field")) + } else { + # Placeholder risk levels + field_details$`Decline Risk` <- sample(risk_levels, nrow(field_details), + prob = c(0.6, 0.25, 0.1, 0.05), replace = TRUE) + } + + # Add Moran's I spatial autocorrelation from growth decline field results (aggregate by field) + if (!is.null(kpi_results$growth_decline_field_results) && nrow(kpi_results$growth_decline_field_results) > 0) { + moran_data <- kpi_results$growth_decline_field_results %>% + dplyr::group_by(field) %>% + dplyr::summarise(morans_i = mean(morans_i, na.rm = TRUE)) %>% # Average Moran's I across subfields + dplyr::rename(`Moran's I` = morans_i) + field_details <- dplyr::left_join(field_details, moran_data, by = c("Field" = "field")) + } else { + # Placeholder Moran's I values (typically range from -1 to 1) + set.seed(123) + field_details$`Moran's I` <- round(runif(nrow(field_details), -0.3, 0.8), 3) + } + + # Add weed risk from weed presence field results (aggregate by field) + if (!is.null(kpi_results$weed_presence_field_results) && nrow(kpi_results$weed_presence_field_results) > 0) { + weed_data <- kpi_results$weed_presence_field_results %>% + dplyr::group_by(field) %>% + dplyr::summarise(weed_risk_level = dplyr::first(weed_risk_level)) %>% # Take first weed risk (should be consistent) + dplyr::rename(`Weed Risk` = weed_risk_level) + field_details <- dplyr::left_join(field_details, weed_data, by = c("Field" = "field")) + } else { + # Placeholder weed levels + field_details$`Weed Risk` <- sample(weed_levels, nrow(field_details), + prob = c(0.7, 0.2, 0.1), replace = TRUE) + } + + # Fill any remaining NAs with defaults (but keep yield forecast as NA) + field_details$`Gap Score`[is.na(field_details$`Gap Score`)] <- 0.0 + field_details$`Decline Risk`[is.na(field_details$`Decline Risk`)] <- sample(risk_levels, sum(is.na(field_details$`Decline Risk`)), replace = TRUE, + prob = c(0.6, 0.25, 0.1, 0.05)) + field_details$`Weed Risk`[is.na(field_details$`Weed Risk`)] <- sample(weed_levels, sum(is.na(field_details$`Weed Risk`)), replace = TRUE, + prob = c(0.7, 0.2, 0.1)) + + # Reorder columns for better presentation + field_details <- field_details %>% + dplyr::select(`Field`, `Field Size (ha)`, `Growth Uniformity`, + `Yield Forecast (t/ha)`, `Gap Score`, `Decline Risk`, `Weed Risk`, + `Moran's I`, `Mean CI`, `CV Value`) + + return(field_details) +} + +#' Create field-specific KPI text for individual field pages +#' @param field_id Field identifier (e.g., "A_1") +#' @param kpi_results List containing all KPI results +#' @return Character string with field-specific KPI summary +create_field_kpi_text <- function(field_id, kpi_results) { + + # Extract field-specific data from field uniformity + field_data <- kpi_results$field_uniformity %>% + dplyr::filter(field_id == !!field_id) + + if (nrow(field_data) == 0) { + return(paste("Field", field_id, ": Data not available")) + } + + # Get field metrics + uniformity <- field_data$uniformity_level[1] + mean_ci <- round(field_data$mean_ci[1], 2) + cv <- round(field_data$cv_value[1], 3) + + # Create summary text + kpi_text <- paste0( + "Field ", field_id, " KPIs: ", + "Uniformity: ", uniformity, " (CV=", cv, "), ", + "Mean CI: ", mean_ci, ", ", + "Status: ", ifelse(mean_ci > 3, "Good Growth", + ifelse(mean_ci > 1.5, "Moderate Growth", "Monitoring Required")) + ) + + return(kpi_text) +} + +#' Export all KPI data in multiple formats for R Markdown integration +#' @param kpi_results List containing all KPI results +#' @param output_dir Directory to save exported files +#' @param project_name Project name for file naming +#' @return List of file paths for exported data +export_kpi_data <- function(kpi_results, output_dir, project_name = "smartcane") { + + if (!dir.exists(output_dir)) { + dir.create(output_dir, recursive = TRUE) + } + + exported_files <- list() + week_suffix <- paste0("week", kpi_results$metadata$current_week) + date_suffix <- format(kpi_results$metadata$report_date, "%Y%m%d") + + # 1. Export summary tables for front page + summary_tables <- create_summary_tables(kpi_results) + summary_file <- file.path(output_dir, paste0(project_name, "_kpi_summary_tables_", week_suffix, ".rds")) + saveRDS(summary_tables, summary_file) + exported_files$summary_tables <- summary_file + + # 2. Export detailed field table for end section + field_details <- create_field_detail_table(kpi_results) + detail_file <- file.path(output_dir, paste0(project_name, "_field_details_", week_suffix, ".rds")) + saveRDS(field_details, detail_file) + exported_files$field_details <- detail_file + + # 3. Export raw KPI results + raw_file <- file.path(output_dir, paste0(project_name, "_kpi_raw_", week_suffix, ".rds")) + saveRDS(kpi_results, raw_file) + exported_files$raw_kpi_data <- raw_file + + # 4. Export field-level KPI tables + field_tables_dir <- file.path(output_dir, "field_level") + if (!dir.exists(field_tables_dir)) { + dir.create(field_tables_dir, recursive = TRUE) + } + + # Export each field-level table + field_kpi_names <- c( + "field_uniformity" = "field_uniformity", + "area_change" = "area_change_field_results", + "tch_forecasted" = "tch_forecasted_field_results", + "growth_decline" = "growth_decline_field_results", + "weed_presence" = "weed_presence_field_results", + "gap_filling" = "gap_filling_field_results" + ) + + for (kpi_name in names(field_kpi_names)) { + field_data <- kpi_results[[field_kpi_names[kpi_name]]] + if (!is.null(field_data) && nrow(field_data) > 0) { + # RDS file + rds_file <- file.path(field_tables_dir, paste0(kpi_name, "_field_results_", week_suffix, ".rds")) + saveRDS(field_data, rds_file) + exported_files[[paste0(kpi_name, "_field_rds")]] <- rds_file + + # CSV file + csv_file <- file.path(field_tables_dir, paste0(kpi_name, "_field_results_", week_suffix, ".csv")) + readr::write_csv(field_data, csv_file) + exported_files[[paste0(kpi_name, "_field_csv")]] <- csv_file + } + } + + # 4. Export CSV versions for manual inspection + csv_dir <- file.path(output_dir, "csv") + if (!dir.exists(csv_dir)) { + dir.create(csv_dir, recursive = TRUE) + } + + # Export each summary table as CSV + for (table_name in names(summary_tables)) { + csv_file <- file.path(csv_dir, paste0(table_name, "_", week_suffix, ".csv")) + readr::write_csv(summary_tables[[table_name]], csv_file) + exported_files[[paste0(table_name, "_csv")]] <- csv_file + } + + # Export field details as CSV + field_csv <- file.path(csv_dir, paste0("field_details_", week_suffix, ".csv")) + readr::write_csv(field_details, field_csv) + exported_files$field_details_csv <- field_csv + + # 5. Create metadata file + metadata_file <- file.path(output_dir, paste0(project_name, "_kpi_metadata_", week_suffix, ".txt")) + + metadata_text <- paste0( + "SmartCane KPI Export Metadata\n", + "=============================\n", + "Project: ", project_name, "\n", + "Report Date: ", kpi_results$metadata$report_date, "\n", + "Current Week: ", kpi_results$metadata$current_week, "\n", + "Previous Week: ", kpi_results$metadata$previous_week, "\n", + "Year: ", kpi_results$metadata$year, "\n", + "Total Fields: ", kpi_results$metadata$total_fields, "\n", + "Calculation Time: ", kpi_results$metadata$calculation_time, "\n\n", + + "Exported Files:\n", + "- Summary Tables: ", basename(summary_file), "\n", + "- Field Details: ", basename(detail_file), "\n", + "- Raw KPI Data: ", basename(raw_file), "\n", + "- Field-Level Tables: field_level/ directory\n", + "- CSV Directory: csv/\n\n", + + "KPI Summary:\n", + "- Field Uniformity: ", nrow(summary_tables$field_uniformity_summary), " categories\n", + "- Area Change: ", nrow(summary_tables$area_change_summary), " change types\n", + "- TCH Forecasted: ", nrow(summary_tables$tch_forecasted_summary), " field groups\n", + "- Growth Decline: ", nrow(summary_tables$growth_decline_summary), " risk levels\n", + "- Weed Presence: ", nrow(summary_tables$weed_presence_summary), " risk levels\n", + "- Gap Filling: ", nrow(summary_tables$gap_filling_summary), " gap levels\n" + ) + + writeLines(metadata_text, metadata_file) + exported_files$metadata <- metadata_file + + safe_log(paste("KPI data exported to", output_dir)) + safe_log(paste("Total files exported:", length(exported_files))) + + return(exported_files) +} + +# 4. Main KPI Calculation Function +# ------------------------------- + +#' Calculate all KPIs for a given date +#' @param report_date Date to calculate KPIs for (default: today) +#' @param output_dir Directory to save KPI results +#' @param field_boundaries_sf Field boundaries (sf or SpatVector) +#' @param harvesting_data Harvesting data with tonnage_ha +#' @param cumulative_CI_vals_dir Directory with cumulative CI data +#' @param weekly_CI_mosaic Directory with weekly CI mosaics +#' @param reports_dir Directory for output reports +#' @param project_dir Project directory name +#' @return List containing all KPI results +calculate_all_kpis <- function(report_date = Sys.Date(), + output_dir = NULL, + field_boundaries_sf, + harvesting_data, + cumulative_CI_vals_dir, + weekly_CI_mosaic, + reports_dir, + project_dir) { + safe_log("=== STARTING KPI CALCULATION ===") + safe_log(paste("Report date:", report_date)) + + # Calculate week numbers + weeks <- calculate_week_numbers(report_date) + safe_log(paste("Current week:", weeks$current_week, "Previous week:", weeks$previous_week)) + + # Load weekly mosaics + current_ci <- load_weekly_ci_mosaic(weeks$current_week, weeks$year, weekly_CI_mosaic) + previous_ci <- load_weekly_ci_mosaic(weeks$previous_week, weeks$year, weekly_CI_mosaic) + + if (is.null(current_ci)) { + stop("Current week CI mosaic is required but not found") + } + + # Check if field boundaries are loaded + if (is.null(field_boundaries_sf)) { + stop("Field boundaries not loaded. Check parameters_project.R initialization.") + } + + # Calculate all KPIs + kpi_results <- list() + + # 1. Field Uniformity Summary + uniformity_result <- calculate_field_uniformity_kpi(current_ci, field_boundaries_sf) + kpi_results$field_uniformity <- uniformity_result$field_results + kpi_results$field_uniformity_summary <- uniformity_result$summary + + # 2. Farm-wide Area Change Summary + area_change_result <- calculate_area_change_kpi(current_ci, previous_ci, field_boundaries_sf) + kpi_results$area_change <- area_change_result$summary + kpi_results$area_change_field_results <- area_change_result$field_results + + # 3. TCH Forecasted + tch_result <- calculate_tch_forecasted_kpi(field_boundaries_sf, harvesting_data, cumulative_CI_vals_dir) + kpi_results$tch_forecasted <- tch_result$summary + kpi_results$tch_forecasted_field_results <- tch_result$field_results + + # 4. Growth Decline Index + growth_decline_result <- calculate_growth_decline_kpi(current_ci, previous_ci, field_boundaries_sf) + kpi_results$growth_decline <- growth_decline_result$summary + kpi_results$growth_decline_field_results <- growth_decline_result$field_results + + # 5. Weed Presence Score (with field age filtering) + weed_presence_result <- calculate_weed_presence_kpi(current_ci, previous_ci, field_boundaries_sf, + harvesting_data = harvesting_data, + cumulative_CI_vals_dir = cumulative_CI_vals_dir) + kpi_results$weed_presence <- weed_presence_result$summary + kpi_results$weed_presence_field_results <- weed_presence_result$field_results + + # 6. Gap Filling Score + gap_filling_result <- calculate_gap_filling_kpi(current_ci, field_boundaries_sf) + kpi_results$gap_filling <- gap_filling_result$summary + kpi_results$gap_filling_field_results <- gap_filling_result$field_results + + # Add metadata + kpi_results$metadata <- list( + report_date = report_date, + current_week = weeks$current_week, + previous_week = weeks$previous_week, + year = weeks$year, + calculation_time = Sys.time(), + total_fields = nrow(field_boundaries_sf) + ) + + # Save results if output directory specified + if (!is.null(output_dir)) { + if (!dir.exists(output_dir)) { + dir.create(output_dir, recursive = TRUE) + } + + # Export KPI data in multiple formats for R Markdown integration + exported_files <- export_kpi_data(kpi_results, output_dir, project_dir) + kpi_results$exported_files <- exported_files + + # Also save raw results + week_suffix <- paste0("week", weeks$current_week) + output_file <- file.path(output_dir, paste0("kpi_results_", week_suffix, ".rds")) + saveRDS(kpi_results, output_file) + safe_log(paste("KPI results saved to:", output_file)) + } + + safe_log("=== KPI CALCULATION COMPLETED ===") + return(kpi_results) +} diff --git a/r_app/mosaic_creation.R b/r_app/mosaic_creation.R deleted file mode 100644 index 5c317b0..0000000 --- a/r_app/mosaic_creation.R +++ /dev/null @@ -1,119 +0,0 @@ -# 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] -# - end_date: End date for processing (YYYY-MM-DD format) -# - offset: Number of days to look back from end_date -# - project_dir: Project directory name (e.g., "chemba") -# - file_name: Optional custom output file name -# - -# 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 { - # Default project directory - project_dir <- "simba" - 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])) { - end_date <- as.Date(args[1]) - if (is.na(end_date)) { - message("Invalid end_date provided. Using current date.") - end_date <- Sys.Date() - #end_date <- "2025-07-22" # Default date for testing - } - } else { - # Default to current date if no argument is provided - end_date <- Sys.Date() - #end_date <- "2025-07-08" # Default date for testing - 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") - } - - - - # 3. Initialize project configuration - # -------------------------------- - tryCatch({ - source("parameters_project.R") - source("mosaic_creation_utils.R") - safe_log(paste("Successfully sourced files from default directory.")) - }, error = function(e) { - warning("Default source files not found. Attempting to source from 'r_app' directory.") - tryCatch({ - source(here::here("r_app", "parameters_project.R")) - source(here::here("r_app", "mosaic_creation_utils.R")) - warning(paste("Successfully sourced files from 'r_app' directory.")) - }, error = function(e) { - stop("Failed to source required files from both default and 'r_app' directories.") - }) - }) - - # 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 - file_name_tif <- if (length(args) >= 4 && !is.na(args[4])) { - 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 mosaic using the function from utils - # ------------------------------------------------- - create_weekly_mosaic( - dates = dates, - field_boundaries = field_boundaries, - daily_vrt_dir = daily_vrt, - merged_final_dir = merged_final, - output_dir = weekly_CI_mosaic, - file_name_tif = file_name_tif, - create_plots = TRUE - ) -} - -if (sys.nframe() == 0) { - main() -} diff --git a/r_app/mosaic_creation_utils.R b/r_app/mosaic_creation_utils.R index ed012ce..935d6fd 100644 --- a/r_app/mosaic_creation_utils.R +++ b/r_app/mosaic_creation_utils.R @@ -45,8 +45,8 @@ date_list <- function(end_date, offset) { start_date <- end_date - lubridate::days(offset) # Extract week and year information - week <- lubridate::week(start_date) - year <- lubridate::year(start_date) + week <- lubridate::isoweek(end_date) + year <- lubridate::isoyear(end_date) # Generate sequence of dates days_filter <- seq(from = start_date, to = end_date, by = "day") @@ -95,7 +95,7 @@ create_weekly_mosaic <- function(dates, field_boundaries, daily_vrt_dir, mosaic <- create_mosaic(vrt_list, missing_pixels_count, field_boundaries, raster_files_final) } else { - safe_log("No VRT files available for the date range, creating empty mosaic", "WARNING") + safe_log("No VRT files available for the date range, creating empty mosaic with NA values", "WARNING") # Create empty mosaic if no files are available if (length(raster_files_final) == 0) { @@ -103,7 +103,7 @@ create_weekly_mosaic <- function(dates, field_boundaries, daily_vrt_dir, } mosaic <- terra::rast(raster_files_final[1]) %>% - terra::setValues(0) %>% + terra::setValues(NA) %>% terra::crop(field_boundaries, mask = TRUE) names(mosaic) <- c("Red", "Green", "Blue", "NIR", "CI") @@ -249,10 +249,10 @@ create_mosaic <- function(vrt_list, missing_pixels_count, field_boundaries = NUL stop("No VRT files available and no fallback raster files or field boundaries provided") } - safe_log("No images available for this period, creating empty mosaic", "WARNING") + safe_log("No images available for this period, creating empty mosaic with NA values", "WARNING") x <- terra::rast(raster_files_final[1]) |> - terra::setValues(0) |> + terra::setValues(NA) |> terra::crop(field_boundaries, mask = TRUE) names(x) <- c("Red", "Green", "Blue", "NIR", "CI") diff --git a/r_app/output/aura/crop_analysis_w34vs32_20250820_1414_table.md b/r_app/output/aura/crop_analysis_w34vs32_20250820_1414_table.md deleted file mode 100644 index d1f4944..0000000 --- a/r_app/output/aura/crop_analysis_w34vs32_20250820_1414_table.md +++ /dev/null @@ -1,15 +0,0 @@ -# Crop Analysis Summary - AURA Estate -**Analysis Period:** Week 32 vs Week 34 - -| Field | Area (ha) | Current CI | Change | Uniformity | Alert | Message | -|-------|-----------|------------|--------|------------|-------|---------| -| kowawa-kowawa | 1.4 | 3.28 | stable | excellent uniformity | ✅ NO | ✅ Excellent: Optimal field uniformity and stability | -| Tamu-Tamu | 4.2 | 4.425 | stable | good uniformity | ✅ NO | ✅ Good: Stable field with good uniformity | -| MNARA-MNARA | 2 | 4.079 | stable | good uniformity | ✅ NO | ✅ Good: Stable field with good uniformity | -| Ayieyie Ruke-Ayieyie Ruke | 1.8 | 4.513 | stable | poor uniformity - urgent attention needed | 🚨 YES | 🚨 URGENT: Poor field uniformity detected - immediate management review required | -| Got Nyithindo_M-Got Nyithindo_M | 1.4 | 4.19 | stable | good uniformity | ✅ NO | ✅ Good: Stable field with good uniformity | -| Got Nyithindo-Got Nyithindo | 1.4 | 4.426 | stable | poor uniformity - urgent attention needed | 🚨 YES | 🚨 URGENT: Poor field uniformity detected - immediate management review required | -| Kabala Ruke-Kabala Ruke | 1.3 | 3.89 | stable | poor uniformity - urgent attention needed | 🚨 YES | 🚨 URGENT: Poor field uniformity detected - immediate management review required | -| Mutwala A-Mutwala A | 1.4 | 3.496 | stable | good uniformity | ✅ NO | ✅ Good: Stable field with good uniformity | -| Onenonam-Onenonam | 2 | 4.098 | decrease | good uniformity | 🚨 YES | ⚠️ Alert: Good uniformity but declining trend - early intervention recommended | -| NA-NA | 3.8 | 3.879 | stable | good uniformity | ✅ NO | ✅ Good: Stable field with good uniformity | diff --git a/r_app/packages.R b/r_app/packages.R deleted file mode 100644 index d6534fa..0000000 --- a/r_app/packages.R +++ /dev/null @@ -1,117 +0,0 @@ -# packages.R -# -# PACKAGE MANAGEMENT FOR SMARTCANE -# =============================== -# This script centralizes all package dependencies for the SmartCane project. -# It installs missing packages and loads all required libraries. -# - -#' Check and install packages if needed -#' -#' @param pkg_list List of packages to check and install -#' @param install_missing Whether to install missing packages -#' @return Vector of packages that couldn't be installed (if any) -#' -check_and_install_packages <- function(pkg_list, install_missing = TRUE) { - # Check which packages are already installed - is_installed <- pkg_list %in% rownames(installed.packages()) - missing_pkgs <- pkg_list[!is_installed] - - # Install missing packages if requested - failed_pkgs <- character(0) - if (length(missing_pkgs) > 0) { - if (install_missing) { - message("Installing ", length(missing_pkgs), " missing packages...") - for (pkg in missing_pkgs) { - tryCatch({ - install.packages(pkg, repos = "https://cran.rstudio.com/", dependencies = TRUE) - message(" Installed: ", pkg) - }, error = function(e) { - warning("Failed to install package: ", pkg) - warning("Error: ", e$message) - failed_pkgs <<- c(failed_pkgs, pkg) - }) - } - } else { - message("The following packages are required but not installed:") - message(paste(missing_pkgs, collapse = ", ")) - failed_pkgs <- missing_pkgs - } - } else { - message("All required packages are already installed.") - } - - return(failed_pkgs) -} - -#' Load all required packages for SmartCane project -#' -#' @param verbose Whether to show messages during loading -#' @return Logical indicating success (TRUE if all packages loaded) -#' -load_smartcane_packages <- function(verbose = FALSE) { - # Define all required packages - required_packages <- c( - # Geospatial packages - "sf", # Simple Features for spatial vector data - "terra", # Raster data processing - "exactextractr", # Fast extraction from rasters - "tmap", # Thematic mapping for spatial visualization - - # Data manipulation - "tidyverse", # Collection of data manipulation packages - "lubridate", # Date manipulation - "readxl", # Excel file reading - "stringr", # String manipulation - "purrr", # Functional programming tools - "zoo", # Time series processing with rolling functions - - # Visualization - "ggplot2", # Advanced plotting - "leaflet", # Interactive maps - "plotly", # Interactive plots - - # Machine learning and statistics - "caret", # Classification and regression training - "rsample", # Data sampling for modeling - "randomForest", # Random forest implementation - "CAST", # Feature selection for spatial data - - # Project management - "here", # Path handling - - # Document generation - "knitr", # Dynamic report generation - "rmarkdown" # R Markdown processing - ) - - # Check and install missing packages - failed_pkgs <- check_and_install_packages(required_packages) - - # Load all installed packages - success <- TRUE - for (pkg in setdiff(required_packages, failed_pkgs)) { - tryCatch({ - if (verbose) message("Loading package: ", pkg) - suppressPackageStartupMessages(library(pkg, character.only = TRUE)) - }, error = function(e) { - warning("Failed to load package: ", pkg) - warning("Error: ", e$message) - success <- FALSE - }) - } - - # Report any issues - if (length(failed_pkgs) > 0) { - warning("The following packages could not be installed: ", - paste(failed_pkgs, collapse = ", ")) - success <- FALSE - } - - return(success) -} - -# Run the loading function if the script is sourced directly -if (!exists("skip_package_loading") || !skip_package_loading) { - load_smartcane_packages() -} \ No newline at end of file diff --git a/r_app/parameters_project.R b/r_app/parameters_project.R index bd95f0e..f2bf3e0 100644 --- a/r_app/parameters_project.R +++ b/r_app/parameters_project.R @@ -62,11 +62,22 @@ setup_project_directories <- function(project_dir) { )) } +#set working dir. # 3. Load field boundaries # ---------------------- load_field_boundaries <- function(data_dir) { - field_boundaries_path <- here(data_dir, "pivot.geojson") - + # Choose field boundaries file based on project and script type + # ESA project uses pivot_2.geojson ONLY for scripts 02-03 (CI extraction & growth model) + # All other scripts (including 04-mosaic, 09-KPIs, 10-reports) use pivot.geojson + use_pivot_2 <- exists("project_dir") && project_dir == "esa" && + exists("ci_extraction_script") # ci_extraction_script flag set by scripts 02-03 + + if (use_pivot_2) { + field_boundaries_path <- here(data_dir, "pivot_2.geojson") + } else { + field_boundaries_path <- here(data_dir, "pivot.geojson") + } + if (!file.exists(field_boundaries_path)) { stop(paste("Field boundaries file not found at path:", field_boundaries_path)) } diff --git a/r_app/report_utils.R b/r_app/report_utils.R index 19b1cac..b31add9 100644 --- a/r_app/report_utils.R +++ b/r_app/report_utils.R @@ -35,7 +35,7 @@ subchunkify <- function(g, fig_height=7, fig_width=5) { ), collapse = '') sub_chunk <- paste0(" -`","``{r sub_chunk_", floor(runif(1) * 10000), ", fig.height=", fig_height, ", fig.width=", fig_width, ", echo=FALSE}", +`","``{r sub_chunk_", floor(runif(1) * 10000), ", fig.height=", fig_height, ", fig.width=", fig_width, ", dpi=300, dev='png', out.width='100%', echo=FALSE}", "\n(", g_deparsed , ")()", @@ -81,16 +81,17 @@ create_CI_map <- function(pivot_raster, pivot_shape, pivot_spans, show_legend = # Create the base map map <- tm_shape(pivot_raster, unit = "m") - # Add raster with continuous spectrum (fixed scale 1-8 for consistent comparison) + # Add raster with continuous spectrum (fixed scale 8-1 for consistent comparison, reversed) map <- map + tm_raster(col.scale = tm_scale_continuous(values = palette, - limits = c(1, 8)), + limits = c(1,8)), col.legend = tm_legend(title = "CI", orientation = if(legend_is_portrait) "portrait" else "landscape", show = show_legend, - position = if(show_legend) tm_pos_out("left", "center") else c("left", "bottom") + position = if(show_legend) tm_pos_out("left", "center") else c("left", "bottom"), + reverse = TRUE )) # Add layout elements - map <- map + tm_layout(main.title = paste0("Max CI week ", week,"\n", age, " weeks old"), + map <- map + tm_layout(main.title = paste0("Max CI week ", week,"\n", age, " weeks (", age * 7, " days) old"), main.title.size = 0.7) # Add borders if requested @@ -143,17 +144,18 @@ create_CI_diff_map <- function(pivot_raster, pivot_shape, pivot_spans, show_lege # Create the base map map <- tm_shape(pivot_raster, unit = "m") - # Add raster with continuous spectrum (centered at 0 for difference maps, fixed scale) + # Add raster with continuous spectrum (centered at 0 for difference maps, fixed scale, reversed) map <- map + tm_raster(col.scale = tm_scale_continuous(values = palette, midpoint = 0, limits = c(-3, 3)), col.legend = tm_legend(title = "CI diff.", orientation = if(legend_is_portrait) "portrait" else "landscape", show = show_legend, - position = if(show_legend) tm_pos_out("right", "center") else c("left", "bottom") + position = if(show_legend) tm_pos_out("right", "center") else c("left", "bottom"), + reverse = TRUE )) # Add layout elements - map <- map + tm_layout(main.title = paste0("CI change week ", week_1, " - week ", week_2, "\n", age, " weeks old"), + map <- map + tm_layout(main.title = paste0("CI change week ", week_1, " - week ", week_2, "\n", age, " weeks (", age * 7, " days) old"), main.title.size = 0.7) # Add borders if requested @@ -287,7 +289,8 @@ ci_plot <- function(pivotName, nrow = 1, widths = c(0.23, 0.18, 0.18, 0.18, 0.23)) # Output heading and map to R Markdown - cat(paste("## Field", pivotName, "-", age, "weeks after planting/harvest", "\n\n")) + age_months <- round(age / 4.348, 1) + cat(paste("## Field", pivotName, "-", age, "weeks/", age_months, "months after planting/harvest", "\n\n")) print(tst) }, error = function(e) { @@ -305,9 +308,12 @@ ci_plot <- function(pivotName, #' @param facet_on Whether to facet the plot by season (TRUE) or overlay all seasons (FALSE) #' @param x_unit Unit for x-axis: "days" for DOY or "weeks" for week number (default: "days") #' @param colorblind_friendly Whether to use colorblind-friendly color schemes (default: FALSE) +#' @param show_benchmarks Whether to show historical benchmark lines (default: FALSE) +#' @param estate_name Name of the estate for benchmark calculation (required if show_benchmarks = TRUE) +#' @param benchmark_percentiles Vector of percentiles for benchmarks (default: c(10, 50, 90)) #' @return NULL (adds output directly to R Markdown document) #' -cum_ci_plot <- function(pivotName, ci_quadrant_data = CI_quadrant, plot_type = "absolute", facet_on = FALSE, x_unit = "days", colorblind_friendly = FALSE) { +cum_ci_plot <- function(pivotName, ci_quadrant_data = CI_quadrant, plot_type = "absolute", facet_on = FALSE, x_unit = "days", colorblind_friendly = FALSE, show_benchmarks = FALSE, estate_name = NULL, benchmark_percentiles = c(10, 50, 90), benchmark_data = NULL) { # Input validation if (missing(pivotName) || is.null(pivotName) || pivotName == "") { stop("pivotName is required") @@ -341,6 +347,33 @@ cum_ci_plot <- function(pivotName, ci_quadrant_data = CI_quadrant, plot_type = " data_ci2 <- data_ci2 %>% dplyr::mutate(season = as.factor(season)) + # Compute benchmarks if requested and not provided + if (show_benchmarks && is.null(benchmark_data)) { + benchmark_data <- compute_ci_benchmarks(ci_quadrant_data, estate_name, benchmark_percentiles) + } + + # Prepare benchmark data for plotting if available + if (!is.null(benchmark_data)) { + benchmark_data <- benchmark_data %>% + dplyr::mutate( + ci_type_label = case_when( + ci_type == "value" ~ "10-Day Rolling Mean CI", + ci_type == "cumulative_CI" ~ "Cumulative CI", + TRUE ~ ci_type + ), + benchmark_label = paste0(percentile, "th Percentile") + ) + safe_log("Benchmark data prepared for plotting", "INFO") + } else if (show_benchmarks) { + safe_log("No benchmark data available", "WARNING") + } + data_ci3 <- tidyr::pivot_longer( + data_ci2, + cols = c("mean_rolling_10_days", "cumulative_CI"), + names_to = "ci_type", # This column will say "mean_rolling_10_days" or "cumulative_CI" + values_to = "ci_value" # This column will have the numeric values + ) + # Prepare date information by season date_preparation_perfect_pivot <- data_ci2 %>% dplyr::group_by(season) %>% @@ -351,8 +384,12 @@ cum_ci_plot <- function(pivotName, ci_quadrant_data = CI_quadrant, plot_type = " # Get the 3 most recent seasons unique_seasons <- sort(unique(date_preparation_perfect_pivot$season), decreasing = TRUE)[1:3] - # Create plotting function - create_plot <- function(y_var, y_label, title_suffix) { + # Create plotting function that uses data_ci3 and filters by ci_type + create_plot <- function(ci_type_filter, y_label, title_suffix) { + # Filter data based on ci_type + plot_data <- data_ci3 %>% + dplyr::filter(season %in% unique_seasons, ci_type == ci_type_filter) + # Determine x-axis variable based on x_unit parameter x_var <- if (x_unit == "days") { if (facet_on) "Date" else "DOY" @@ -366,34 +403,83 @@ cum_ci_plot <- function(pivotName, ci_quadrant_data = CI_quadrant, plot_type = " # Create plot with either facets by season or overlay by DOY/week if (facet_on) { - g <- ggplot2::ggplot(data = data_ci2 %>% dplyr::filter(season %in% unique_seasons)) + + g <- ggplot2::ggplot(data = plot_data) + ggplot2::facet_wrap(~season, scales = "free_x") + - ggplot2::geom_line(ggplot2::aes_string(x = x_var, y = y_var, col = "sub_field", group = "sub_field")) + - ggplot2::labs(title = paste("Plot of", y_label, "for Field", pivotName, title_suffix), + ggplot2::geom_line(ggplot2::aes_string(x = x_var, y = "ci_value", col = "sub_field", group = "sub_field")) + + ggplot2::labs(title = paste("Plot of", y_label), color = "Field Name", y = y_label, x = x_label) + - ggplot2::scale_x_date(date_breaks = "1 month", date_labels = "%m-%Y") + + ggplot2::scale_x_date(date_breaks = "1 month", date_labels = "%m-%Y", + sec.axis = ggplot2::sec_axis(~ ., name = "Age in Months", + breaks = scales::breaks_pretty(), + labels = function(x) round(as.numeric(x - min(x)) / 30.44, 1))) + ggplot2::theme_minimal() + - ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 60, hjust = 1), + ggplot2::theme(axis.text.x = ggplot2::element_text(hjust = 0.5), + axis.text.x.top = ggplot2::element_text(hjust = 0.5), + axis.title.x.top = ggplot2::element_text(size = 8), legend.justification = c(1, 0), legend.position = c(1, 0), legend.title = ggplot2::element_text(size = 8), legend.text = ggplot2::element_text(size = 8)) + ggplot2::guides(color = ggplot2::guide_legend(nrow = 2, byrow = TRUE)) } else { - g <- ggplot2::ggplot(data = data_ci2 %>% dplyr::filter(season %in% unique_seasons)) + - ggplot2::geom_line(ggplot2::aes_string(x = x_var, y = y_var, col = "season", group = "season")) + + # Choose color palette based on colorblind_friendly flag + color_scale <- if (colorblind_friendly) { + ggplot2::scale_color_brewer(type = "qual", palette = "Set2") + } else { + ggplot2::scale_color_discrete() + } + + g <- ggplot2::ggplot(data = plot_data) + + # Add benchmark lines first (behind season lines) + { + if (!is.null(benchmark_data) && ci_type_filter %in% benchmark_data$ci_type) { + benchmark_subset <- benchmark_data %>% + dplyr::filter(ci_type == ci_type_filter) %>% + dplyr::mutate( + benchmark_x = if (x_var == "DOY") { + DOY + } else if (x_var == "week") { + DOY / 7 # Approximate conversion + } else { + DOY # For Date, use DOY as is (may not align perfectly) + } + ) + ggplot2::geom_smooth( + data = benchmark_subset, + ggplot2::aes_string(x = "benchmark_x", y = "benchmark_value", group = "factor(percentile)"), + color = "gray70", size = 0.5, se = FALSE, inherit.aes = FALSE + ) + } + } + + ggplot2::geom_line(ggplot2::aes_string(x = x_var, y = "ci_value", col = "season", group = "season")) + ggplot2::labs(title = paste("Plot of", y_label, "for Field", pivotName, title_suffix), color = "Season", y = y_label, x = x_label) + + color_scale + + { + if (x_var == "DOY") { + ggplot2::scale_x_continuous(breaks = seq(0, 450, by = 50), sec.axis = ggplot2::sec_axis(~ . / 30.44, name = "Age in Months", breaks = seq(0, 14, by = 1))) + } else if (x_var == "week") { + ggplot2::scale_x_continuous(breaks = seq(0, 64, by = 4), sec.axis = ggplot2::sec_axis(~ . / 4.348, name = "Age in Months", breaks = seq(0, 14, by = 1))) + } + } + ggplot2::theme_minimal() + - ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 60, hjust = 1), + ggplot2::theme(axis.text.x = ggplot2::element_text(hjust = 0.5), + axis.text.x.top = ggplot2::element_text(hjust = 0.5), + axis.title.x.top = ggplot2::element_text(size = 8), legend.justification = c(1, 0), legend.position = c(1, 0), legend.title = ggplot2::element_text(size = 8), legend.text = ggplot2::element_text(size = 8)) + ggplot2::guides(color = ggplot2::guide_legend(nrow = 2, byrow = TRUE)) } + + # Add y-axis limits for absolute CI (10-day rolling mean) to fix scale at 0-8 + if (ci_type_filter == "mean_rolling_10_days") { + g <- g + ggplot2::ylim(0, 8) + } + return(g) } @@ -405,13 +491,133 @@ cum_ci_plot <- function(pivotName, ci_quadrant_data = CI_quadrant, plot_type = " g <- create_plot("cumulative_CI", "Cumulative CI", "") subchunkify(g, 2.8, 10) } else if (plot_type == "both") { - # Create both plots - g_absolute <- create_plot("mean_rolling_10_days", "10-Day Rolling Mean CI", "(Absolute)") - g_cumulative <- create_plot("cumulative_CI", "Cumulative CI", "(Cumulative)") + # Create faceted plot with both CI types using pivot_longer approach + plot_data_both <- data_ci3 %>% + dplyr::filter(season %in% unique_seasons) %>% + dplyr::mutate(ci_type_label = case_when( + ci_type == "mean_rolling_10_days" ~ "10-Day Rolling Mean CI", + ci_type == "cumulative_CI" ~ "Cumulative CI", + TRUE ~ ci_type + )) - # Display both plots - subchunkify(g_absolute, 2.8, 4.95) - subchunkify(g_cumulative, 2.8, 4.95) + # Determine x-axis variable based on x_unit parameter + x_var <- if (x_unit == "days") { + if (facet_on) "Date" else "DOY" + } else { + "week" + } + + x_label <- switch(x_unit, + "days" = if (facet_on) "Date" else "Age of Crop (Days)", + "weeks" = "Week Number") + + # Choose color palette based on colorblind_friendly flag + color_scale <- if (colorblind_friendly) { + ggplot2::scale_color_brewer(type = "qual", palette = "Set2") + } else { + ggplot2::scale_color_discrete() + } + + # Create faceted plot with both CI types using pivot_longer approach + plot_data_both <- data_ci3 %>% + dplyr::filter(season %in% unique_seasons) %>% + dplyr::mutate(ci_type_label = case_when( + ci_type == "mean_rolling_10_days" ~ "10-Day Rolling Mean CI", + ci_type == "cumulative_CI" ~ "Cumulative CI", + TRUE ~ ci_type + )) + + # Determine x-axis variable based on x_unit parameter + x_var <- if (x_unit == "days") { + if (facet_on) "Date" else "DOY" + } else { + "week" + } + + x_label <- switch(x_unit, + "days" = if (facet_on) "Date" else "Age of Crop (Days)", + "weeks" = "Week Number") + + # Choose color palette based on colorblind_friendly flag + color_scale <- if (colorblind_friendly) { + ggplot2::scale_color_brewer(type = "qual", palette = "Set2") + } else { + ggplot2::scale_color_discrete() + } + + # Create the faceted plot + g_both <- ggplot2::ggplot(data = plot_data_both) + + # Add benchmark lines first (behind season lines) + { + if (!is.null(benchmark_data)) { + benchmark_subset <- benchmark_data %>% + dplyr::mutate( + benchmark_x = if (x_var == "DOY") { + DOY + } else if (x_var == "week") { + DOY / 7 + } else { + DOY + }, + ci_type_label = case_when( + ci_type == "value" ~ "10-Day Rolling Mean CI", + ci_type == "cumulative_CI" ~ "Cumulative CI", + TRUE ~ ci_type + ) + ) + ggplot2::geom_smooth( + data = benchmark_subset, + ggplot2::aes_string(x = "benchmark_x", y = "benchmark_value", group = "factor(percentile)"), + color = "gray70", size = 0.5, se = FALSE, inherit.aes = FALSE + ) + } + } + + ggplot2::facet_wrap(~ci_type_label, scales = "free_y") + + ggplot2::geom_line(ggplot2::aes_string(x = x_var, y = "ci_value", col = "season", group = "season")) + + ggplot2::labs(title = paste("CI Analysis for Field", pivotName), + color = "Season", + y = "CI Value", + x = x_label) + + color_scale + + { + if (x_var == "DOY") { + ggplot2::scale_x_continuous(breaks = seq(0, 450, by = 50), sec.axis = ggplot2::sec_axis(~ . / 30.44, name = "Age in Months", breaks = seq(0, 14, by = 1))) + } else if (x_var == "week") { + ggplot2::scale_x_continuous(breaks = seq(0, 64, by = 4), sec.axis = ggplot2::sec_axis(~ . / 4.348, name = "Age in Months", breaks = seq(0, 14, by = 1))) + } else if (x_var == "Date") { + ggplot2::scale_x_date(breaks = "1 month", date_labels = "%b-%Y", sec.axis = ggplot2::sec_axis(~ ., name = "Age in Months", breaks = scales::breaks_pretty())) + } + } + + ggplot2::theme_minimal() + + ggplot2::theme(axis.text.x = ggplot2::element_text(hjust = 0.5), + axis.text.x.top = ggplot2::element_text(hjust = 0.5), + axis.title.x.top = ggplot2::element_text(size = 8), + legend.justification = c(1, 0), legend.position = c(1, 0), + legend.title = ggplot2::element_text(size = 8), + legend.text = ggplot2::element_text(size = 8)) + + ggplot2::guides(color = ggplot2::guide_legend(nrow = 2, byrow = TRUE)) + + # For the rolling mean data, we want to set reasonable y-axis limits + # Since we're using free_y scales, each facet will have its own y-axis + # The rolling mean will automatically scale to its data range, + # but we can ensure it shows the 0-8 context by adding invisible points + + # Add invisible points to set the y-axis range for rolling mean facet + dummy_data <- data.frame( + ci_type_label = "10-Day Rolling Mean CI", + ci_value = c(0, 8), + stringsAsFactors = FALSE + ) + dummy_data[[x_var]] <- range(plot_data_both[[x_var]], na.rm = TRUE) + dummy_data[["season"]] <- factor("dummy", levels = levels(plot_data_both[["season"]])) + + g_both <- g_both + + ggplot2::geom_point(data = dummy_data, + ggplot2::aes_string(x = x_var, y = "ci_value"), + alpha = 0, size = 0) # Invisible points to set scale + + # Display the combined faceted plot + subchunkify(g_both, 2.8, 10) } }, error = function(e) { @@ -444,7 +650,7 @@ cum_ci_plot2 <- function(pivotName){ labs(title = paste("14 day rolling MEAN CI rate - Field ", pivotName), x = "Date", y = "CI Rate") + theme_minimal() + - theme(axis.text.x = element_text(angle = 60, hjust = 1), + theme(axis.text.x = element_text(hjust = 0.5), legend.justification = c(1, 0), legend.position = c(1, 0), legend.title = element_text(size = 8), legend.text = element_text(size = 8)) + @@ -512,3 +718,86 @@ get_week_path <- function(mosaic_path, input_date, week_offset) { }) } +#' Computes historical percentile benchmarks for CI data per estate +#' +#' @param ci_quadrant_data Data frame containing CI quadrant data with field, Date, DOY, cumulative_CI, value, season columns +#' @param estate_name Name of the estate/client to filter data for +#' @param percentiles Vector of percentiles to compute (e.g., c(10, 50, 90)) +#' @param min_seasons Minimum number of seasons required for reliable benchmarks (default: 3) +#' @return Data frame with DOY, percentile, ci_type, benchmark_value, or NULL if insufficient data +#' +compute_ci_benchmarks <- function(ci_quadrant_data, estate_name, percentiles = c(10, 50, 90), min_seasons = 3) { + # Input validation + if (missing(ci_quadrant_data) || is.null(ci_quadrant_data)) { + stop("ci_quadrant_data is required") + } + if (missing(estate_name) || is.null(estate_name) || estate_name == "") { + stop("estate_name is required") + } + if (!all(percentiles >= 0 & percentiles <= 100)) { + stop("percentiles must be between 0 and 100") + } + + tryCatch({ + # Filter data for the specified estate (assuming estate is not directly in data, but we can infer from context) + # Since the data is per field, and fields are unique to estates, we'll use all data but could add estate filtering if available + data_filtered <- ci_quadrant_data + + # Check if we have enough seasons + unique_seasons <- unique(data_filtered$season) + if (length(unique_seasons) < min_seasons) { + safe_log(paste("Insufficient historical seasons for estate", estate_name, ":", length(unique_seasons), "seasons found, need at least", min_seasons), "WARNING") + return(NULL) + } + + # Prepare data for both CI types + data_prepared <- data_filtered %>% + dplyr::ungroup() %>% # Ensure no existing groupings + dplyr::select(DOY, value, cumulative_CI, season) %>% + tidyr::pivot_longer( + cols = c("value", "cumulative_CI"), + names_to = "ci_type", + values_to = "ci_value" + ) %>% + dplyr::filter(!is.na(ci_value)) # Remove NA values + + # Compute percentiles for each DOY and ci_type + benchmarks <- data_prepared %>% + dplyr::group_by(DOY, ci_type) %>% + dplyr::summarise( + p10 = tryCatch(quantile(ci_value, 0.1, na.rm = TRUE), error = function(e) NA_real_), + p50 = tryCatch(quantile(ci_value, 0.5, na.rm = TRUE), error = function(e) NA_real_), + p90 = tryCatch(quantile(ci_value, 0.9, na.rm = TRUE), error = function(e) NA_real_), + n_observations = n(), + .groups = 'drop' + ) %>% + dplyr::filter(n_observations >= min_seasons) %>% # Only include DOYs with sufficient data + tidyr::pivot_longer( + cols = c(p10, p50, p90), + names_to = "percentile", + values_to = "benchmark_value" + ) %>% + dplyr::mutate( + percentile = case_when( + percentile == "p10" ~ 10, + percentile == "p50" ~ 50, + percentile == "p90" ~ 90 + ) + ) %>% + dplyr::filter(!is.na(benchmark_value)) # Remove any NA benchmarks + + # Rename columns for clarity + benchmarks <- benchmarks %>% + dplyr::select(DOY, ci_type, percentile, benchmark_value) + + safe_log(paste("Computed CI benchmarks for estate", estate_name, "with", length(unique_seasons), "seasons and", nrow(benchmarks), "benchmark points"), "INFO") + + return(benchmarks) + + }, error = function(e) { + safe_log(paste("Error computing CI benchmarks for estate", estate_name, ":", e$message), "ERROR") + print(paste("DEBUG: Error details:", e$message, "Call:", deparse(e$call))) + return(NULL) + }) +} + diff --git a/renv.lock b/renv.lock index 4607526..b619e6b 100644 --- a/renv.lock +++ b/renv.lock @@ -1,6 +1,6 @@ { "R": { - "Version": "4.4.2", + "Version": "4.4.3", "Repositories": [ { "Name": "CRAN", @@ -584,6 +584,33 @@ "Maintainer": "Kirill Müller ", "Repository": "CRAN" }, + "boot": { + "Package": "boot", + "Version": "1.3-31", + "Source": "Repository", + "Priority": "recommended", + "Date": "2024-08-28", + "Authors@R": "c(person(\"Angelo\", \"Canty\", role = \"aut\", email = \"cantya@mcmaster.ca\", comment = \"author of original code for S\"), person(\"Brian\", \"Ripley\", role = c(\"aut\", \"trl\"), email = \"ripley@stats.ox.ac.uk\", comment = \"conversion to R, maintainer 1999--2022, author of parallel support\"), person(\"Alessandra R.\", \"Brazzale\", role = c(\"ctb\", \"cre\"), email = \"brazzale@stat.unipd.it\", comment = \"minor bug fixes\"))", + "Maintainer": "Alessandra R. Brazzale ", + "Note": "Maintainers are not available to give advice on using a package they did not author.", + "Description": "Functions and datasets for bootstrapping from the book \"Bootstrap Methods and Their Application\" by A. C. Davison and D. V. Hinkley (1997, CUP), originally written by Angelo Canty for S.", + "Title": "Bootstrap Functions (Originally by Angelo Canty for S)", + "Depends": [ + "R (>= 3.0.0)", + "graphics", + "stats" + ], + "Suggests": [ + "MASS", + "survival" + ], + "LazyData": "yes", + "ByteCompile": "yes", + "License": "Unlimited", + "NeedsCompilation": "no", + "Author": "Angelo Canty [aut] (author of original code for S), Brian Ripley [aut, trl] (conversion to R, maintainer 1999--2022, author of parallel support), Alessandra R. Brazzale [ctb, cre] (minor bug fixes)", + "Repository": "CRAN" + }, "broom": { "Package": "broom", "Version": "1.0.8", @@ -995,10 +1022,10 @@ }, "cli": { "Package": "cli", - "Version": "3.6.3", + "Version": "3.6.5", "Source": "Repository", "Title": "Helpers for Developing Command Line Interfaces", - "Authors@R": "c( person(\"Gábor\", \"Csárdi\", , \"csardi.gabor@gmail.com\", role = c(\"aut\", \"cre\")), person(\"Hadley\", \"Wickham\", role = \"ctb\"), person(\"Kirill\", \"Müller\", role = \"ctb\"), person(\"Salim\", \"Brüggemann\", , \"salim-b@pm.me\", role = \"ctb\", comment = c(ORCID = \"0000-0002-5329-5987\")), person(\"Posit Software, PBC\", role = c(\"cph\", \"fnd\")) )", + "Authors@R": "c( person(\"Gábor\", \"Csárdi\", , \"gabor@posit.co\", role = c(\"aut\", \"cre\")), person(\"Hadley\", \"Wickham\", role = \"ctb\"), person(\"Kirill\", \"Müller\", role = \"ctb\"), person(\"Salim\", \"Brüggemann\", , \"salim-b@pm.me\", role = \"ctb\", comment = c(ORCID = \"0000-0002-5329-5987\")), person(\"Posit Software, PBC\", role = c(\"cph\", \"fnd\")) )", "Description": "A suite of tools to build attractive command line interfaces ('CLIs'), from semantic elements: headings, lists, alerts, paragraphs, etc. Supports custom themes via a 'CSS'-like language. It also contains a number of lower level 'CLI' elements: rules, boxes, trees, and 'Unicode' symbols with 'ASCII' alternatives. It support ANSI colors and text styles as well.", "License": "MIT + file LICENSE", "URL": "https://cli.r-lib.org, https://github.com/r-lib/cli", @@ -1020,14 +1047,13 @@ "htmlwidgets", "knitr", "methods", - "mockery", "processx", "ps (>= 1.3.4.9000)", "rlang (>= 1.0.2.9003)", "rmarkdown", "rprojroot", "rstudioapi", - "testthat", + "testthat (>= 3.2.0)", "tibble", "whoami", "withr" @@ -1035,10 +1061,10 @@ "Config/Needs/website": "r-lib/asciicast, bench, brio, cpp11, decor, desc, fansi, prettyunits, sessioninfo, tidyverse/tidytemplate, usethis, vctrs", "Config/testthat/edition": "3", "Encoding": "UTF-8", - "RoxygenNote": "7.2.3", + "RoxygenNote": "7.3.2", "NeedsCompilation": "yes", "Author": "Gábor Csárdi [aut, cre], Hadley Wickham [ctb], Kirill Müller [ctb], Salim Brüggemann [ctb] (), Posit Software, PBC [cph, fnd]", - "Maintainer": "Gábor Csárdi ", + "Maintainer": "Gábor Csárdi ", "Repository": "CRAN" }, "clipr": { @@ -1533,6 +1559,31 @@ "Maintainer": "Hadley Wickham ", "Repository": "CRAN" }, + "deldir": { + "Package": "deldir", + "Version": "2.0-4", + "Source": "Repository", + "Date": "2024-02-27", + "Title": "Delaunay Triangulation and Dirichlet (Voronoi) Tessellation", + "Author": "Rolf Turner", + "Maintainer": "Rolf Turner ", + "Depends": [ + "R (>= 3.5.0)" + ], + "Suggests": [ + "polyclip" + ], + "Imports": [ + "graphics", + "grDevices" + ], + "Description": "Calculates the Delaunay triangulation and the Dirichlet or Voronoi tessellation (with respect to the entire plane) of a planar point set. Plots triangulations and tessellations in various ways. Clips tessellations to sub-windows. Calculates perimeters of tessellations. Summarises information about the tiles of the tessellation.\tCalculates the centroidal Voronoi (Dirichlet) tessellation using Lloyd's algorithm.", + "LazyData": "true", + "ByteCompile": "true", + "License": "GPL (>= 2)", + "NeedsCompilation": "yes", + "Repository": "CRAN" + }, "diagram": { "Package": "diagram", "Version": "1.6.5", @@ -1891,6 +1942,108 @@ "Maintainer": "Winston Chang ", "Repository": "CRAN" }, + "flextable": { + "Package": "flextable", + "Version": "0.9.10", + "Source": "Repository", + "Type": "Package", + "Title": "Functions for Tabular Reporting", + "Authors@R": "c( person(\"David\", \"Gohel\", , \"david.gohel@ardata.fr\", role = c(\"aut\", \"cre\")), person(\"ArData\", role = \"cph\"), person(\"Clementine\", \"Jager\", role = \"ctb\"), person(\"Eli\", \"Daniels\", role = \"ctb\"), person(\"Panagiotis\", \"Skintzos\", , \"panagiotis.skintzos@ardata.fr\", role = \"aut\"), person(\"Quentin\", \"Fazilleau\", role = \"ctb\"), person(\"Maxim\", \"Nazarov\", role = \"ctb\"), person(\"Titouan\", \"Robert\", role = \"ctb\"), person(\"Michael\", \"Barrowman\", role = \"ctb\"), person(\"Atsushi\", \"Yasumoto\", role = \"ctb\"), person(\"Paul\", \"Julian\", role = \"ctb\"), person(\"Sean\", \"Browning\", role = \"ctb\"), person(\"Rémi\", \"Thériault\", role = \"ctb\", comment = c(ORCID = \"0000-0003-4315-6788\")), person(\"Samuel\", \"Jobert\", role = \"ctb\"), person(\"Keith\", \"Newman\", role = \"ctb\") )", + "Description": "Use a grammar for creating and customizing pretty tables. The following formats are supported: 'HTML', 'PDF', 'RTF', 'Microsoft Word', 'Microsoft PowerPoint' and R 'Grid Graphics'. 'R Markdown', 'Quarto' and the package 'officer' can be used to produce the result files. The syntax is the same for the user regardless of the type of output to be produced. A set of functions allows the creation, definition of cell arrangement, addition of headers or footers, formatting and definition of cell content with text and or images. The package also offers a set of high-level functions that allow tabular reporting of statistical models and the creation of complex cross tabulations.", + "License": "GPL-3", + "URL": "https://ardata-fr.github.io/flextable-book/, https://davidgohel.github.io/flextable/", + "BugReports": "https://github.com/davidgohel/flextable/issues", + "Imports": [ + "data.table (>= 1.13.0)", + "gdtools (>= 0.4.0)", + "graphics", + "grDevices", + "grid", + "htmltools", + "knitr", + "officer (>= 0.6.10)", + "ragg", + "rlang", + "rmarkdown (>= 2.0)", + "stats", + "utils", + "uuid (>= 0.1-4)", + "xml2" + ], + "Suggests": [ + "bookdown (>= 0.40)", + "broom", + "broom.mixed", + "chromote", + "cluster", + "commonmark", + "doconv (>= 0.3.0)", + "equatags", + "ggplot2", + "lme4", + "magick", + "mgcv", + "nlme", + "officedown", + "pdftools", + "pkgdown (>= 2.0.0)", + "scales", + "svglite", + "tables (>= 0.9.17)", + "testthat (>= 3.0.0)", + "webshot2", + "withr", + "xtable" + ], + "VignetteBuilder": "knitr", + "Config/testthat/edition": "3", + "Encoding": "UTF-8", + "RoxygenNote": "7.3.2", + "NeedsCompilation": "no", + "Author": "David Gohel [aut, cre], ArData [cph], Clementine Jager [ctb], Eli Daniels [ctb], Panagiotis Skintzos [aut], Quentin Fazilleau [ctb], Maxim Nazarov [ctb], Titouan Robert [ctb], Michael Barrowman [ctb], Atsushi Yasumoto [ctb], Paul Julian [ctb], Sean Browning [ctb], Rémi Thériault [ctb] (ORCID: ), Samuel Jobert [ctb], Keith Newman [ctb]", + "Maintainer": "David Gohel ", + "Repository": "CRAN" + }, + "fontBitstreamVera": { + "Package": "fontBitstreamVera", + "Version": "0.1.1", + "Source": "Repository", + "Title": "Fonts with 'Bitstream Vera Fonts' License", + "Authors@R": "c( person(\"Lionel\", \"Henry\", , \"lionel.hry@gmail.com\", c(\"cre\", \"aut\")), person(\"Bitstream\", role = \"cph\"))", + "Description": "Provides fonts licensed under the 'Bitstream Vera Fonts' license for the 'fontquiver' package.", + "Depends": [ + "R (>= 3.0.0)" + ], + "License": "file LICENCE", + "Encoding": "UTF-8", + "LazyData": "true", + "RoxygenNote": "5.0.1", + "NeedsCompilation": "no", + "Author": "Lionel Henry [cre, aut], Bitstream [cph]", + "Maintainer": "Lionel Henry ", + "License_is_FOSS": "yes", + "Repository": "CRAN" + }, + "fontLiberation": { + "Package": "fontLiberation", + "Version": "0.1.0", + "Source": "Repository", + "Title": "Liberation Fonts", + "Authors@R": "c( person(\"Lionel\", \"Henry\", , \"lionel@rstudio.com\", \"cre\"), person(\"Pravin Satpute\", role = \"aut\"), person(\"Steve Matteson\", role = \"aut\"), person(\"Red Hat, Inc\", role = \"cph\"), person(\"Google Corporation\", role = \"cph\"))", + "Description": "A placeholder for the Liberation fontset intended for the `fontquiver` package. This fontset covers the 12 combinations of families (sans, serif, mono) and faces (plain, bold, italic, bold italic) supported in R graphics devices.", + "Depends": [ + "R (>= 3.0)" + ], + "License": "file LICENSE", + "Encoding": "UTF-8", + "LazyData": "true", + "RoxygenNote": "5.0.1", + "NeedsCompilation": "no", + "Author": "Lionel Henry [cre], Pravin Satpute [aut], Steve Matteson [aut], Red Hat, Inc [cph], Google Corporation [cph]", + "Maintainer": "Lionel Henry ", + "Repository": "CRAN", + "License_is_FOSS": "yes" + }, "fontawesome": { "Package": "fontawesome", "Version": "0.5.3", @@ -1926,6 +2079,34 @@ "Maintainer": "Richard Iannone ", "Repository": "CRAN" }, + "fontquiver": { + "Package": "fontquiver", + "Version": "0.2.1", + "Source": "Repository", + "Title": "Set of Installed Fonts", + "Authors@R": "c( person(\"Lionel\", \"Henry\", , \"lionel@rstudio.com\", c(\"cre\", \"aut\")), person(\"RStudio\", role = \"cph\"), person(\"George Douros\", role = \"cph\", comment = \"Symbola font\"))", + "Description": "Provides a set of fonts with permissive licences. This is useful when you want to avoid system fonts to make sure your outputs are reproducible.", + "Depends": [ + "R (>= 3.0.0)" + ], + "Imports": [ + "fontBitstreamVera (>= 0.1.0)", + "fontLiberation (>= 0.1.0)" + ], + "Suggests": [ + "testthat", + "htmltools" + ], + "License": "GPL-3 | file LICENSE", + "Encoding": "UTF-8", + "LazyData": "true", + "RoxygenNote": "5.0.1", + "Collate": "'font-getters.R' 'fontset.R' 'fontset-bitstream-vera.R' 'fontset-dejavu.R' 'fontset-liberation.R' 'fontset-symbola.R' 'html-dependency.R' 'utils.R'", + "NeedsCompilation": "no", + "Author": "Lionel Henry [cre, aut], RStudio [cph], George Douros [cph] (Symbola font)", + "Maintainer": "Lionel Henry ", + "Repository": "CRAN" + }, "forcats": { "Package": "forcats", "Version": "1.0.0", @@ -2208,6 +2389,43 @@ "Maintainer": "Jennifer Bryan ", "Repository": "CRAN" }, + "gdtools": { + "Package": "gdtools", + "Version": "0.4.3", + "Source": "Repository", + "Title": "Utilities for Graphical Rendering and Fonts Management", + "Authors@R": "c( person(\"David\", \"Gohel\", , \"david.gohel@ardata.fr\", role = c(\"aut\", \"cre\")), person(\"Hadley\", \"Wickham\", , \"hadley@rstudio.com\", role = \"aut\"), person(\"Lionel\", \"Henry\", , \"lionel@rstudio.com\", role = \"aut\"), person(\"Jeroen\", \"Ooms\", , \"jeroen@berkeley.edu\", role = \"aut\", comment = c(ORCID = \"0000-0002-4035-0289\")), person(\"Yixuan\", \"Qiu\", role = \"ctb\"), person(\"R Core Team\", role = \"cph\", comment = \"Cairo code from X11 device\"), person(\"ArData\", role = \"cph\"), person(\"RStudio\", role = \"cph\") )", + "Description": "Tools are provided to compute metrics of formatted strings and to check the availability of a font. Another set of functions is provided to support the collection of fonts from 'Google Fonts' in a cache. Their use is simple within 'R Markdown' documents and 'shiny' applications but also with graphic productions generated with the 'ggiraph', 'ragg' and 'svglite' packages or with tabular productions from the 'flextable' package.", + "License": "GPL-3 | file LICENSE", + "URL": "https://davidgohel.github.io/gdtools/", + "BugReports": "https://github.com/davidgohel/gdtools/issues", + "Depends": [ + "R (>= 4.0.0)" + ], + "Imports": [ + "fontquiver (>= 0.2.0)", + "htmltools", + "Rcpp (>= 0.12.12)", + "systemfonts (>= 1.1.0)", + "tools" + ], + "Suggests": [ + "curl", + "gfonts", + "methods", + "testthat" + ], + "LinkingTo": [ + "Rcpp" + ], + "Encoding": "UTF-8", + "RoxygenNote": "7.3.2", + "SystemRequirements": "cairo, freetype2, fontconfig", + "NeedsCompilation": "yes", + "Author": "David Gohel [aut, cre], Hadley Wickham [aut], Lionel Henry [aut], Jeroen Ooms [aut] (ORCID: ), Yixuan Qiu [ctb], R Core Team [cph] (Cairo code from X11 device), ArData [cph], RStudio [cph]", + "Maintainer": "David Gohel ", + "Repository": "CRAN" + }, "generics": { "Package": "generics", "Version": "0.1.3", @@ -4120,6 +4338,50 @@ "NeedsCompilation": "no", "Repository": "CRAN" }, + "officer": { + "Package": "officer", + "Version": "0.7.0", + "Source": "Repository", + "Type": "Package", + "Title": "Manipulation of Microsoft Word and PowerPoint Documents", + "Authors@R": "c( person(\"David\", \"Gohel\", , \"david.gohel@ardata.fr\", role = c(\"aut\", \"cre\")), person(\"Stefan\", \"Moog\", , \"moogs@gmx.de\", role = \"aut\"), person(\"Mark\", \"Heckmann\", , \"heckmann.mark@gmail.com\", role = \"aut\", comment = c(ORCID = \"0000-0002-0736-7417\")), person(\"ArData\", role = \"cph\"), person(\"Frank\", \"Hangler\", , \"frank@plotandscatter.com\", role = \"ctb\", comment = \"function body_replace_all_text\"), person(\"Liz\", \"Sander\", , \"lsander@civisanalytics.com\", role = \"ctb\", comment = \"several documentation fixes\"), person(\"Anton\", \"Victorson\", , \"anton@victorson.se\", role = \"ctb\", comment = \"fixes xml structures\"), person(\"Jon\", \"Calder\", , \"jonmcalder@gmail.com\", role = \"ctb\", comment = \"update vignettes\"), person(\"John\", \"Harrold\", , \"john.m.harrold@gmail.com\", role = \"ctb\", comment = \"function annotate_base\"), person(\"John\", \"Muschelli\", , \"muschellij2@gmail.com\", role = \"ctb\", comment = \"google doc compatibility\"), person(\"Bill\", \"Denney\", , \"wdenney@humanpredictions.com\", role = \"ctb\", comment = c(ORCID = \"0000-0002-5759-428X\", \"function as.matrix.rpptx\")), person(\"Nikolai\", \"Beck\", , \"beck.nikolai@gmail.com\", role = \"ctb\", comment = \"set speaker notes for .pptx documents\"), person(\"Greg\", \"Leleu\", , \"gregoire.leleu@gmail.com\", role = \"ctb\", comment = \"fields functionality in ppt\"), person(\"Majid\", \"Eismann\", role = \"ctb\"), person(\"Hongyuan\", \"Jia\", , \"hongyuanjia@cqust.edu.cn\", role = \"ctb\", comment = c(ORCID = \"0000-0002-0075-8183\")), person(\"Michael\", \"Stackhouse\", , \"mike.stackhouse@atorusresearch.com\", role = \"ctb\") )", + "Description": "Access and manipulate 'Microsoft Word', 'RTF' and 'Microsoft PowerPoint' documents from R. The package focuses on tabular and graphical reporting from R; it also provides two functions that let users get document content into data objects. A set of functions lets add and remove images, tables and paragraphs of text in new or existing documents. The package does not require any installation of Microsoft products to be able to write Microsoft files.", + "License": "MIT + file LICENSE", + "URL": "https://ardata-fr.github.io/officeverse/, https://davidgohel.github.io/officer/", + "BugReports": "https://github.com/davidgohel/officer/issues", + "Imports": [ + "cli", + "graphics", + "grDevices", + "openssl", + "R6", + "ragg", + "stats", + "utils", + "uuid", + "xml2 (>= 1.1.0)", + "zip (>= 2.1.0)" + ], + "Suggests": [ + "devEMF", + "doconv (>= 0.3.0)", + "gdtools", + "ggplot2", + "knitr", + "magick", + "rmarkdown", + "rsvg", + "testthat", + "withr" + ], + "Encoding": "UTF-8", + "RoxygenNote": "7.3.2", + "Collate": "'core_properties.R' 'custom_properties.R' 'defunct.R' 'dev-utils.R' 'docx_add.R' 'docx_comments.R' 'docx_cursor.R' 'docx_part.R' 'docx_replace.R' 'docx_section.R' 'docx_settings.R' 'empty_content.R' 'formatting_properties.R' 'fortify_docx.R' 'fortify_pptx.R' 'knitr_utils.R' 'officer.R' 'ooxml.R' 'ooxml_block_objects.R' 'ooxml_run_objects.R' 'openxml_content_type.R' 'openxml_document.R' 'pack_folder.R' 'ph_location.R' 'post-proc.R' 'ppt_class_dir_collection.R' 'ppt_classes.R' 'ppt_notes.R' 'ppt_ph_dedupe_layout.R' 'ppt_ph_manipulate.R' 'ppt_ph_rename_layout.R' 'ppt_ph_with_methods.R' 'pptx_informations.R' 'pptx_layout_helper.R' 'pptx_matrix.R' 'utils.R' 'pptx_slide_manip.R' 'read_docx.R' 'read_docx_styles.R' 'read_pptx.R' 'read_xlsx.R' 'relationship.R' 'rtf.R' 'shape_properties.R' 'shorcuts.R' 'docx_append_context.R' 'utils-xml.R' 'deprecated.R' 'zzz.R'", + "NeedsCompilation": "no", + "Author": "David Gohel [aut, cre], Stefan Moog [aut], Mark Heckmann [aut] (ORCID: ), ArData [cph], Frank Hangler [ctb] (function body_replace_all_text), Liz Sander [ctb] (several documentation fixes), Anton Victorson [ctb] (fixes xml structures), Jon Calder [ctb] (update vignettes), John Harrold [ctb] (function annotate_base), John Muschelli [ctb] (google doc compatibility), Bill Denney [ctb] (ORCID: , function as.matrix.rpptx), Nikolai Beck [ctb] (set speaker notes for .pptx documents), Greg Leleu [ctb] (fields functionality in ppt), Majid Eismann [ctb], Hongyuan Jia [ctb] (ORCID: ), Michael Stackhouse [ctb]", + "Maintainer": "David Gohel ", + "Repository": "CRAN" + }, "openssl": { "Package": "openssl", "Version": "2.3.2", @@ -4223,6 +4485,50 @@ "Maintainer": "Henrik Bengtsson ", "Repository": "CRAN" }, + "patchwork": { + "Package": "patchwork", + "Version": "1.3.2", + "Source": "Repository", + "Type": "Package", + "Title": "The Composer of Plots", + "Authors@R": "person(given = \"Thomas Lin\", family = \"Pedersen\", role = c(\"cre\", \"aut\"), email = \"thomasp85@gmail.com\", comment = c(ORCID = \"0000-0002-5147-4711\"))", + "Maintainer": "Thomas Lin Pedersen ", + "Description": "The 'ggplot2' package provides a strong API for sequentially building up a plot, but does not concern itself with composition of multiple plots. 'patchwork' is a package that expands the API to allow for arbitrarily complex composition of plots by, among others, providing mathematical operators for combining multiple plots. Other packages that try to address this need (but with a different approach) are 'gridExtra' and 'cowplot'.", + "License": "MIT + file LICENSE", + "Encoding": "UTF-8", + "Imports": [ + "ggplot2 (>= 3.0.0)", + "gtable (>= 0.3.6)", + "grid", + "stats", + "grDevices", + "utils", + "graphics", + "rlang (>= 1.0.0)", + "cli", + "farver" + ], + "RoxygenNote": "7.3.2", + "URL": "https://patchwork.data-imaginist.com, https://github.com/thomasp85/patchwork", + "BugReports": "https://github.com/thomasp85/patchwork/issues", + "Suggests": [ + "knitr", + "rmarkdown", + "gridGraphics", + "gridExtra", + "ragg", + "testthat (>= 2.1.0)", + "vdiffr", + "covr", + "png", + "gt (>= 0.11.0)" + ], + "VignetteBuilder": "knitr", + "Config/Needs/website": "gifski", + "NeedsCompilation": "no", + "Author": "Thomas Lin Pedersen [cre, aut] (ORCID: )", + "Repository": "CRAN" + }, "pillar": { "Package": "pillar", "Version": "1.10.2", @@ -5966,6 +6272,38 @@ "Maintainer": "Edzer Pebesma ", "Repository": "CRAN" }, + "spData": { + "Package": "spData", + "Version": "2.3.4", + "Source": "Repository", + "Title": "Datasets for Spatial Analysis", + "Authors@R": "c(person(\"Roger\", \"Bivand\", role = \"aut\", email=\"Roger.Bivand@nhh.no\", comment = c(ORCID = \"0000-0003-2392-6140\")), person(\"Jakub\", \"Nowosad\", role = c(\"aut\", \"cre\"), email=\"nowosad.jakub@gmail.com\", comment = c(ORCID = \"0000-0002-1057-3721\")), person(\"Robin\", \"Lovelace\", role = \"aut\", comment = c(ORCID = \"0000-0001-5679-6536\")), person(\"Angelos\", \"Mimis\", role = \"ctb\"), person(\"Mark\", \"Monmonier\", role = \"ctb\", comment = \"author of the state.vbm dataset\"), person(\"Greg\", \"Snow\", role = \"ctb\", comment = \"author of the state.vbm dataset\") )", + "Description": "Diverse spatial datasets for demonstrating, benchmarking and teaching spatial data analysis. It includes R data of class sf (defined by the package 'sf'), Spatial ('sp'), and nb ('spdep'). Unlike other spatial data packages such as 'rnaturalearth' and 'maps', it also contains data stored in a range of file formats including GeoJSON and GeoPackage, but from version 2.3.4, no longer ESRI Shapefile - use GeoPackage instead. Some of the datasets are designed to illustrate specific analysis techniques. cycle_hire() and cycle_hire_osm(), for example, is designed to illustrate point pattern analysis techniques.", + "Depends": [ + "R (>= 3.3.0)" + ], + "Imports": [ + "sp" + ], + "Suggests": [ + "foreign", + "sf (>= 0.9-1)", + "spDataLarge (>= 0.4.0)", + "spdep", + "spatialreg" + ], + "License": "CC0", + "RoxygenNote": "7.3.2", + "LazyData": "true", + "URL": "https://jakubnowosad.com/spData/", + "BugReports": "https://github.com/Nowosad/spData/issues", + "Additional_repositories": "https://jakubnowosad.com/drat", + "Encoding": "UTF-8", + "NeedsCompilation": "no", + "Author": "Roger Bivand [aut] (), Jakub Nowosad [aut, cre] (), Robin Lovelace [aut] (), Angelos Mimis [ctb], Mark Monmonier [ctb] (author of the state.vbm dataset), Greg Snow [ctb] (author of the state.vbm dataset)", + "Maintainer": "Jakub Nowosad ", + "Repository": "CRAN" + }, "spacesXYZ": { "Package": "spacesXYZ", "Version": "1.5-1", @@ -6037,6 +6375,66 @@ "Maintainer": "Emil Hvitfeldt ", "Repository": "CRAN" }, + "spdep": { + "Package": "spdep", + "Version": "1.4-1", + "Source": "Repository", + "Date": "2025-08-25", + "Title": "Spatial Dependence: Weighting Schemes, Statistics", + "Encoding": "UTF-8", + "Authors@R": "c(person(\"Roger\", \"Bivand\", role = c(\"cre\", \"aut\"), email = \"Roger.Bivand@nhh.no\", comment=c(ORCID=\"0000-0003-2392-6140\")), person(\"Micah\", \"Altman\", role = \"ctb\"), person(\"Luc\", \"Anselin\", role = \"ctb\"), person(\"Renato\", \"Assunção\", role = \"ctb\"), person(\"Anil\", \"Bera\", role = \"ctb\"), person(\"Olaf\", \"Berke\", role = \"ctb\"), person(\"F. Guillaume\", \"Blanchet\", role = \"ctb\"), person(\"Marilia\", \"Carvalho\", role = \"ctb\"), person(\"Bjarke\", \"Christensen\", role = \"ctb\"), person(\"Yongwan\", \"Chun\", role = \"ctb\"), person(\"Carsten\", \"Dormann\", role = \"ctb\"), person(\"Stéphane\", \"Dray\", role = \"ctb\"), person(\"Dewey\", \"Dunnington\", role = c(\"ctb\"), comment = c(ORCID = \"0000-0002-9415-4582\")), person(\"Virgilio\", \"Gómez-Rubio\", role = \"ctb\"), person(\"Malabika\", \"Koley\", role = \"ctb\"), person(\"Tomasz\", \"Kossowski\", role = \"ctb\", comment = c(ORCID = \"0000-0002-9976-4398\")), person(\"Elias\", \"Krainski\", role = \"ctb\"), person(\"Pierre\", \"Legendre\", role = \"ctb\"), person(\"Nicholas\", \"Lewin-Koh\", role = \"ctb\"), person(\"Angela\", \"Li\", role = \"ctb\"), person(\"Giovanni\", \"Millo\", role = \"ctb\"), person(\"Werner\", \"Mueller\", role = \"ctb\"), person(\"Hisaji\", \"Ono\", role = \"ctb\"), person(\"Josiah\", \"Parry\", role = \"ctb\", comment = c(ORCID = \"0000-0001-9910-865X\")), person(\"Pedro\", \"Peres-Neto\", role = \"ctb\"), person(\"Michał\", \"Pietrzak\", role = \"ctb\", comment = c(ORCID = \"0000-0002-9263-4478\")), person(\"Gianfranco\", \"Piras\", role = \"ctb\"), person(\"Markus\", \"Reder\", role = \"ctb\"), person(\"Jeff\", \"Sauer\", role = \"ctb\"), person(\"Michael\", \"Tiefelsdorf\", role = \"ctb\"), person(\"René\", \"Westerholt\", role=\"ctb\"), person(\"Justyna\", \"Wilk\", role = \"ctb\", comment = c(ORCID = \"0000-0003-1495-2910\")), person(\"Levi\", \"Wolf\", role = \"ctb\"), person(\"Danlin\", \"Yu\", role = \"ctb\"))", + "Depends": [ + "R (>= 3.3.0)", + "methods", + "spData (>= 2.3.1)", + "sf" + ], + "Imports": [ + "stats", + "deldir", + "boot (>= 1.3-1)", + "graphics", + "utils", + "grDevices", + "units", + "s2", + "e1071", + "sp (>= 1.0)" + ], + "Suggests": [ + "spatialreg (>= 1.2-1)", + "Matrix", + "parallel", + "dbscan", + "RColorBrewer", + "lattice", + "xtable", + "foreign", + "igraph", + "RSpectra", + "knitr", + "classInt", + "tmap", + "spam", + "ggplot2", + "rmarkdown", + "tinytest", + "rgeoda (>= 0.0.11.1)", + "mipfp", + "Guerry", + "codingMatrices" + ], + "URL": "https://github.com/r-spatial/spdep/, https://r-spatial.github.io/spdep/", + "BugReports": "https://github.com/r-spatial/spdep/issues/", + "Description": "A collection of functions to create spatial weights matrix objects from polygon 'contiguities', from point patterns by distance and tessellations, for summarizing these objects, and for permitting their use in spatial data analysis, including regional aggregation by minimum spanning tree; a collection of tests for spatial 'autocorrelation', including global 'Morans I' and 'Gearys C' proposed by 'Cliff' and 'Ord' (1973, ISBN: 0850860369) and (1981, ISBN: 0850860814), 'Hubert/Mantel' general cross product statistic, Empirical Bayes estimates and 'Assunção/Reis' (1999) Index, 'Getis/Ord' G ('Getis' and 'Ord' 1992) and multicoloured join count statistics, 'APLE' ('Li et al.' ) , local 'Moran's I', 'Gearys C' ('Anselin' 1995) and 'Getis/Ord' G ('Ord' and 'Getis' 1995) , 'saddlepoint' approximations ('Tiefelsdorf' 2002) and exact tests for global and local 'Moran's I' ('Bivand et al.' 2009) and 'LOSH' local indicators of spatial heteroscedasticity ('Ord' and 'Getis') . The implementation of most of these measures is described in 'Bivand' and 'Wong' (2018) , with further extensions in 'Bivand' (2022) . 'Lagrange' multiplier tests for spatial dependence in linear models are provided ('Anselin et al'. 1996) , as are 'Rao' score tests for hypothesised spatial 'Durbin' models based on linear models ('Koley' and 'Bera' 2023) . Additions in 2024 include Local Indicators for Categorical Data based on 'Carrer et al.' (2021) and 'Bivand et al.' (2017) ; also Weighted Multivariate Spatial Autocorrelation Measures ('Bavaud' 2024) . . A local indicators for categorical data (LICD) implementation based on 'Carrer et al.' (2021) and 'Bivand et al.' (2017) was added in 1.3-7. Multivariate 'spatialdelta' ('Bavaud' 2024) was added in 1.3-13 ('Bivand' 2025 . From 'spdep' and 'spatialreg' versions >= 1.2-1, the model fitting functions previously present in this package are defunct in 'spdep' and may be found in 'spatialreg'.", + "License": "GPL (>= 2)", + "VignetteBuilder": "knitr", + "RoxygenNote": "RoxygenNote: 6.1.1", + "NeedsCompilation": "yes", + "Author": "Roger Bivand [cre, aut] (ORCID: ), Micah Altman [ctb], Luc Anselin [ctb], Renato Assunção [ctb], Anil Bera [ctb], Olaf Berke [ctb], F. Guillaume Blanchet [ctb], Marilia Carvalho [ctb], Bjarke Christensen [ctb], Yongwan Chun [ctb], Carsten Dormann [ctb], Stéphane Dray [ctb], Dewey Dunnington [ctb] (ORCID: ), Virgilio Gómez-Rubio [ctb], Malabika Koley [ctb], Tomasz Kossowski [ctb] (ORCID: ), Elias Krainski [ctb], Pierre Legendre [ctb], Nicholas Lewin-Koh [ctb], Angela Li [ctb], Giovanni Millo [ctb], Werner Mueller [ctb], Hisaji Ono [ctb], Josiah Parry [ctb] (ORCID: ), Pedro Peres-Neto [ctb], Michał Pietrzak [ctb] (ORCID: ), Gianfranco Piras [ctb], Markus Reder [ctb], Jeff Sauer [ctb], Michael Tiefelsdorf [ctb], René Westerholt [ctb], Justyna Wilk [ctb] (ORCID: ), Levi Wolf [ctb], Danlin Yu [ctb]", + "Maintainer": "Roger Bivand ", + "Repository": "CRAN" + }, "stars": { "Package": "stars", "Version": "0.6-8", @@ -6845,7 +7243,7 @@ }, "tzdb": { "Package": "tzdb", - "Version": "0.4.0", + "Version": "0.5.0", "Source": "Repository", "Title": "Time Zone Database Information", "Authors@R": "c( person(\"Davis\", \"Vaughan\", , \"davis@posit.co\", role = c(\"aut\", \"cre\")), person(\"Howard\", \"Hinnant\", role = \"cph\", comment = \"Author of the included date library\"), person(\"Posit Software, PBC\", role = c(\"cph\", \"fnd\")) )", @@ -6854,20 +7252,20 @@ "URL": "https://tzdb.r-lib.org, https://github.com/r-lib/tzdb", "BugReports": "https://github.com/r-lib/tzdb/issues", "Depends": [ - "R (>= 3.5.0)" + "R (>= 4.0.0)" ], "Suggests": [ "covr", "testthat (>= 3.0.0)" ], "LinkingTo": [ - "cpp11 (>= 0.4.2)" + "cpp11 (>= 0.5.2)" ], "Biarch": "yes", "Config/Needs/website": "tidyverse/tidytemplate", "Config/testthat/edition": "3", "Encoding": "UTF-8", - "RoxygenNote": "7.2.3", + "RoxygenNote": "7.3.2", "NeedsCompilation": "yes", "Author": "Davis Vaughan [aut, cre], Howard Hinnant [cph] (Author of the included date library), Posit Software, PBC [cph, fnd]", "Maintainer": "Davis Vaughan ", @@ -7012,6 +7410,52 @@ "Maintainer": "Davis Vaughan ", "Repository": "CRAN" }, + "viridis": { + "Package": "viridis", + "Version": "0.6.5", + "Source": "Repository", + "Type": "Package", + "Title": "Colorblind-Friendly Color Maps for R", + "Date": "2024-01-28", + "Authors@R": "c( person(\"Simon\", \"Garnier\", email = \"garnier@njit.edu\", role = c(\"aut\", \"cre\")), person(\"Noam\", \"Ross\", email = \"noam.ross@gmail.com\", role = c(\"ctb\", \"cph\")), person(\"Bob\", \"Rudis\", email = \"bob@rud.is\", role = c(\"ctb\", \"cph\")), person(\"Marco\", \"Sciaini\", email = \"sciaini.marco@gmail.com\", role = c(\"ctb\", \"cph\")), person(\"Antônio Pedro\", \"Camargo\", role = c(\"ctb\", \"cph\")), person(\"Cédric\", \"Scherer\", email = \"scherer@izw-berlin.de\", role = c(\"ctb\", \"cph\")) )", + "Maintainer": "Simon Garnier ", + "Description": "Color maps designed to improve graph readability for readers with common forms of color blindness and/or color vision deficiency. The color maps are also perceptually-uniform, both in regular form and also when converted to black-and-white for printing. This package also contains 'ggplot2' bindings for discrete and continuous color and fill scales. A lean version of the package called 'viridisLite' that does not include the 'ggplot2' bindings can be found at .", + "License": "MIT + file LICENSE", + "Encoding": "UTF-8", + "Depends": [ + "R (>= 2.10)", + "viridisLite (>= 0.4.0)" + ], + "Imports": [ + "ggplot2 (>= 1.0.1)", + "gridExtra" + ], + "Suggests": [ + "hexbin (>= 1.27.0)", + "scales", + "MASS", + "knitr", + "dichromat", + "colorspace", + "httr", + "mapproj", + "vdiffr", + "svglite (>= 1.2.0)", + "testthat", + "covr", + "rmarkdown", + "maps", + "terra" + ], + "LazyData": "true", + "VignetteBuilder": "knitr", + "URL": "https://sjmgarnier.github.io/viridis/, https://github.com/sjmgarnier/viridis/", + "BugReports": "https://github.com/sjmgarnier/viridis/issues", + "RoxygenNote": "7.3.1", + "NeedsCompilation": "no", + "Author": "Simon Garnier [aut, cre], Noam Ross [ctb, cph], Bob Rudis [ctb, cph], Marco Sciaini [ctb, cph], Antônio Pedro Camargo [ctb, cph], Cédric Scherer [ctb, cph]", + "Repository": "CRAN" + }, "viridisLite": { "Package": "viridisLite", "Version": "0.4.2", @@ -7205,6 +7649,33 @@ "Author": "Dewey Dunnington [aut, cre] (), Edzer Pebesma [aut] (), Anthony North [ctb]", "Repository": "CRAN" }, + "writexl": { + "Package": "writexl", + "Version": "1.5.4", + "Source": "Repository", + "Type": "Package", + "Title": "Export Data Frames to Excel 'xlsx' Format", + "Authors@R": "c( person(\"Jeroen\", \"Ooms\", ,\"jeroenooms@gmail.com\", role = c(\"aut\", \"cre\"), comment = c(ORCID = \"0000-0002-4035-0289\")), person(\"John McNamara\", role = \"cph\", comment = \"Author of libxlsxwriter (see AUTHORS and COPYRIGHT files for details)\"))", + "Description": "Zero-dependency data frame to xlsx exporter based on 'libxlsxwriter' . Fast and no Java or Excel required.", + "License": "BSD_2_clause + file LICENSE", + "Encoding": "UTF-8", + "URL": "https://ropensci.r-universe.dev/writexl https://docs.ropensci.org/writexl/", + "BugReports": "https://github.com/ropensci/writexl/issues", + "RoxygenNote": "7.0.2", + "Suggests": [ + "spelling", + "readxl", + "nycflights13", + "testthat", + "bit64" + ], + "Language": "en-US", + "SystemRequirements": "zlib", + "NeedsCompilation": "yes", + "Author": "Jeroen Ooms [aut, cre] (), John McNamara [cph] (Author of libxlsxwriter (see AUTHORS and COPYRIGHT files for details))", + "Maintainer": "Jeroen Ooms ", + "Repository": "CRAN" + }, "xfun": { "Package": "xfun", "Version": "0.52", @@ -7340,6 +7811,34 @@ "NeedsCompilation": "yes", "Repository": "CRAN" }, + "zip": { + "Package": "zip", + "Version": "2.3.3", + "Source": "Repository", + "Title": "Cross-Platform 'zip' Compression", + "Authors@R": "c( person(\"Gábor\", \"Csárdi\", , \"csardi.gabor@gmail.com\", role = c(\"aut\", \"cre\")), person(\"Kuba\", \"Podgórski\", role = \"ctb\"), person(\"Rich\", \"Geldreich\", role = \"ctb\"), person(\"Posit Software, PBC\", role = c(\"cph\", \"fnd\"), comment = c(ROR = \"03wc8by49\")) )", + "Description": "Cross-Platform 'zip' Compression Library. A replacement for the 'zip' function, that does not require any additional external tools on any platform.", + "License": "MIT + file LICENSE", + "URL": "https://github.com/r-lib/zip, https://r-lib.github.io/zip/", + "BugReports": "https://github.com/r-lib/zip/issues", + "Suggests": [ + "covr", + "pillar", + "processx", + "R6", + "testthat", + "withr" + ], + "Config/Needs/website": "tidyverse/tidytemplate", + "Config/testthat/edition": "3", + "Config/usethis/last-upkeep": "2025-05-07", + "Encoding": "UTF-8", + "RoxygenNote": "7.3.2.9000", + "NeedsCompilation": "yes", + "Author": "Gábor Csárdi [aut, cre], Kuba Podgórski [ctb], Rich Geldreich [ctb], Posit Software, PBC [cph, fnd] (ROR: )", + "Maintainer": "Gábor Csárdi ", + "Repository": "CRAN" + }, "zoo": { "Package": "zoo", "Version": "1.8-13", diff --git a/run_kpi_calculation.R b/run_kpi_calculation.R new file mode 100644 index 0000000..bf9d360 --- /dev/null +++ b/run_kpi_calculation.R @@ -0,0 +1,3 @@ +# Wrapper script to set project_dir and run KPI calculation +project_dir <- "esa" +source("r_app/09_calculate_kpis.R") \ No newline at end of file diff --git a/run_report.R b/run_report.R new file mode 100644 index 0000000..e69de29

4r>21j?OwV!cgvTuqqiIqCn;YZaP|dM zGy`_ZHd}hntjW*T9`irX|7i)x$Sz{)7h57>eV_S31>eK*Eyl~@14@m*0MD7>7vO)R zJ%gi_=T5ILIO{)rEp6+Qu*h6G@nMj4V(^6>1V?=Yeyp!76m-s;WZ zsdJoqt9Qyy@a1x8%k_XbKEro~0i3HchnnS4NwQ;9^|1Z>eXxCn0XUg;`}vT$x-0v5 z{Enq%2==y3(3ismFT)2p!_f4!JD{J)W5yV&>FbG=suDr^OTq^3$^PtEgA?H?%1L7` zuL#suXEMl^GtS*|LZfgw#J~kI=b6B!-s;*xU5kWFInYVyIDsp;L46Z<`d z)6O^AjPc}ZA5c$2C$~0~e;mXfD8eS`<%jAI9zg8w~LHH>An8}%MkldMRkZL(4 z%_POh&81Uhx?6A$SH+OfqgIPsO5Opz+vFuN;*7*+uS{lDG)1PR)S0Su%**pGA5 zJv!@tGh5E^+r(SY7<|ubtq8I*)fh zu7arYL@A(Xf^F43xc|t%AC59O8dMz(PS{aczMu67(avO`Iuforzie<>vh+yQVB;BT zi_i3SVimj}9$yd3zwCx0{C-|znwTu=YcOChTE5mY?aL|n>tZ>1|8;{h7SYF>_N=)R zi#T?6X9_Be8{gO4*!Db4J_?OX@7(Z!r9{|ly;;AGxih#sbe==kMP` z4*dSJ72>uoiY5I*UY>L2)F*l=WO`fNNXB;lMaaDm5`1sGrK(6JN;W}k*kvU|ljg=u zkW0@EuN^*Rl*6CQK=vc~5V z(u}R1|Al_WxBrW~15;Hha3{HYxf?at2PCq!sm4_|kSZ_%wjcWf`N z}wx1RwmbX+jW^lYmoN4y4jdcv>lc#iX@odTg^8)rV_xn z#gc->-RVFvRT;VFXZOU69v0euE!RK;*qN=j!7tWO3P$G!0z1*sgJ4Is+mPycKnhM( z3kmE4tPGSkPd`|X6w@&~h=scsnPjFu{^^S#lVBCwK&no`9gY65hqi24*KZw7x35Zk zofY`Hf99S#n7rgSDaYgt!giPu0sEW?YOgJsdT{bnd4m}8`hseYhkLJNJGU!=Hy&t_ zt<=$NcCFVnWGeJwk56u%IHF1Z{o#VBW87|(jGvo?`stkZLMO%TA0QL=Iai-ol+>4; zD0S4NQXtbMH?A*#|L11hA9;SSJbFJe`1+mG0avle8m4l}ppFg(`XQBrm@;y_swek}uTN5|=9cLc0nB69#ESmLn3k+6t!JUFUJ%B=T zluP8QFigb~wd~*n-mBZAz!%@ma+(8`Y(@pO-`)-0n_WHCl!kMLsBK<|PZS1d*+W7T z{y?0mPUHQZAMg-%niM^yS(*| z^{9GV%hEuF28gy9Sin9v`MChi;GdUcZc0|vK8IibVzMpkEH;aegU#dEJ9Du*m-y)) z!NV8$9T|z2lbFEk%M6&gB`N+#;L*FA_*o*r;B%|l3j}51w+RIqt2EPb?zb)r&m9X{ zWhM~MEH6tt$O$FD®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 VJR70p9X?wHw!V-I~k( zIMbU2(b|S;4>gWh+Wu4fyXB>lPTY;G3#-xXT+u2{%kdMD{dLwoI9u|$$8pz8*7JnK z>yqxVP?FN=%%eis4MC7&uC}@X;ko>FNu{7aqRtD(h}uwM+s3QYkz_-?8)mu+q4%(q z-%7CFpgvCrAO8q`+74JDOGk^vuthNmwlg*S@}LzJi`Y{?-hVlEO!J_pEcC|LL8Nc~ zRf>MB=Pkk@)22#-0_9W!bKUA!5Nu;jU0)|m&`}vCBkfqgb3uK#2`+7&ZhkIQp+l<`RlPAwZ#E20uWGbtLfoCt8G$5 zN!EuK(aWgB{ocq`P}nBTi1CZ2Jo%~{J~P4BmBu*JGEOv^x`NLvxB(>*QeH2$r?Hqg zysQMH){8;M`=0rx>wv|n>&w(Wx!d14{~wiIe}{-JXAEO|4QKS$-bbifj#n(VBwY2? zsaUh91$5_NJI}k|ZG9ggRjil%e)oy({`k&TeZghTKZgKLcz@nRo7#JfXGRWu{La=G z;NE?0S5sNO+8t6r&qz=8}>hA)nf!{e9dU?+T ziNlyajz6*qqmmU*@VQbG$}cYg0AN0+Lk#FW9UYwqU~PlVdW8#xhzI2#BlVh|&enlj zN+3m4eGg`A)FJp(WrXf^)wVmhx|;Qxnocv7M9_sTBQ+oZDjC(c$xh4LQ511(;XKQl z>E{!^3s0wqfbc~83Q<+|aN4ewhiN~_*X5kNO0i1BUI%F7>N-oy(c<8ul2JTWE21%Q zM@t*w=wL8krUFz?9 zS+&rC6-MHUKZXjyaDoj*OZf~#PjyfOcn5mMqPWSsg%li|uoULNq{9KGKK3kdF^}Zo z9&pT?n?iN1lg6U+Ru7aT*1EaK!O*fG_oW`XD_zcGh@c?f4c6${qQoPopcMub?+&uy z@!q1OL!vXfjGlg>w#1B$OFP8d4(>{%A$g#Cf|no8g}Cy_@!t~Na1(Ig$yRSeV68;z zKd}TdO8!w-$ECfR9ZB9geK|X@T@89i1H(r6VvJkOrRXPzyEHz^YiPt#^*ebe`8DBK z=Lps9Ffa|iHGH??bHW&JvXMCa#WxWhHbCaM55CUv?JGa;9>#)ycDos#Y+ehoNzsMm zT}`+@3(AZd(QR*Og8O)+0Q+^-t5~jOt1er0Yrp;PP=7Y`#Xt)pN}=yVf9!+#U*d^q z&2XIX(3(nm^k>VPCFO#C32jW352h4cf&{{&&uX~uCztH0Uxc6H9oe{kr>I}wAQxuf zcU_>|8onLwCQZG;JsT=6X+>!hMyYNWTr4N!JUDN9Onj_`mjOnSjrlNNLp*kCN*D{z zzCKenJH^L{3XmSs;zK$En6~L7VuPU3HK>F#SlW6>+DJ$^r1Ec2M>nw?lZe@?Vivj! z;5GVH!DX_HyR<{oc}9j+YGXdEMn@OxPM{rl4}N#FaSuu=lG*W;t-(c|q3Gr{ z1iU@NuN4LjOj-zu$}O$6_aHSB_(#~bouLfd!3I2?FB5gfQj?S7*(#A%ts}J12Lf&3 z7xw|zKc75|R1f3DSA!}HM;R3rEAg-6UnclhHI2gBFxR`~Lob%y=&@%0DbBmDQzE!RsY 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%0D>3>Krit0ygv0aZOCJZn@ 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 which(days_of_week == mail_day_as_character)) { - safe_log("Adjusting weeks because of mail day") - week <- lubridate::week(today) + 1 - today_minus_1 <- as.character(lubridate::ymd(today)) - today_minus_2 <- as.character(lubridate::ymd(today) - 7) - today_minus_3 <- as.character(lubridate::ymd(today) - 14) -} - -# Calculate week numbers for previous weeks -week_minus_1 <- week - 1 -week_minus_2 <- week - 2 -week_minus_3 <- week - 3 - -# Format current week with leading zeros -week <- sprintf("%02d", week) - -# Get years for each date -year <- lubridate::year(today) -year_1 <- lubridate::year(today_minus_1) -year_2 <- lubridate::year(today_minus_2) -year_3 <- lubridate::year(today_minus_3) -``` - -```{r data, message=TRUE, warning=TRUE, include=FALSE} -# Load CI index data with error handling -tryCatch({ - CI_quadrant <- readRDS(here::here(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds")) - safe_log("Successfully loaded CI quadrant data") -}, error = function(e) { - stop("Error loading CI quadrant data: ", e$message) -}) - -# Get file paths for different weeks using the utility function -tryCatch({ - path_to_week_current = get_week_path(weekly_CI_mosaic, today, 0) - path_to_week_minus_1 = get_week_path(weekly_CI_mosaic, today, -1) - path_to_week_minus_2 = get_week_path(weekly_CI_mosaic, today, -2) - path_to_week_minus_3 = get_week_path(weekly_CI_mosaic, today, -3) - - # Log the calculated paths - safe_log("Required mosaic paths:") - safe_log(paste("Path to current week:", path_to_week_current)) - safe_log(paste("Path to week minus 1:", path_to_week_minus_1)) - safe_log(paste("Path to week minus 2:", path_to_week_minus_2)) - safe_log(paste("Path to week minus 3:", path_to_week_minus_3)) - - # Validate that files exist - if (!file.exists(path_to_week_current)) warning("Current week mosaic file does not exist: ", path_to_week_current) - if (!file.exists(path_to_week_minus_1)) warning("Week minus 1 mosaic file does not exist: ", path_to_week_minus_1) - if (!file.exists(path_to_week_minus_2)) warning("Week minus 2 mosaic file does not exist: ", path_to_week_minus_2) - if (!file.exists(path_to_week_minus_3)) warning("Week minus 3 mosaic file does not exist: ", path_to_week_minus_3) - - # Load raster data with terra functions - CI <- terra::rast(path_to_week_current)$CI - CI_m1 <- terra::rast(path_to_week_minus_1)$CI - CI_m2 <- terra::rast(path_to_week_minus_2)$CI - CI_m3 <- terra::rast(path_to_week_minus_3)$CI - -}, error = function(e) { - stop("Error loading raster data: ", e$message) -}) -``` - -```{r calculate_difference_rasters, message=TRUE, warning=TRUE, include=FALSE} -# Calculate difference rasters for comparisons -tryCatch({ - # Calculate weekly difference - last_week_dif_raster_abs <- (CI - CI_m1) - safe_log("Calculated weekly difference raster") - - # Calculate three-week difference - three_week_dif_raster_abs <- (CI - CI_m3) - safe_log("Calculated three-week difference raster") -}, error = function(e) { - safe_log(paste("Error calculating difference rasters:", e$message), "ERROR") - # Create placeholder rasters if calculations fail - if (!exists("last_week_dif_raster_abs")) { - last_week_dif_raster_abs <- CI * 0 - } - if (!exists("three_week_dif_raster_abs")) { - three_week_dif_raster_abs <- CI * 0 - } -}) -``` - -```{r load_field_boundaries, message=TRUE, warning=TRUE, include=FALSE} -# Load field boundaries from parameters -tryCatch({ - AllPivots0 <- field_boundaries_sf %>% - dplyr::filter(!is.na(field), !is.na(sub_field)) # Filter out NA field names - safe_log("Successfully loaded field boundaries") -}, error = function(e) { - stop("Error loading field boundaries: ", e$message) -}) -``` - -```{r create_front_page_variables, include=FALSE} -# Create variables for the front page -farm_name <- stringr::str_to_title(gsub("_", " ", project_dir)) - -# Format dates for display -report_date_formatted <- format(as.Date(report_date), "%B %d, %Y") -current_year <- format(Sys.Date(), "%Y") - -# Get total field count and area if available -tryCatch({ - total_fields <- length(unique(AllPivots0$field)) - total_area_ha <- round(sum(sf::st_area(AllPivots0)) / 10000, 1) # Convert to hectares -}, error = function(e) { - total_fields <- "N/A" - total_area_ha <- "N/A" -}) -``` - ---- -title: "" ---- - -```{=openxml} - - - - - - - - - - - SUGARCANE CROP MONITORING REPORT - - -``` - -