diff --git a/.Rhistory b/.Rhistory
new file mode 100644
index 0000000..19c58ab
--- /dev/null
+++ b/.Rhistory
@@ -0,0 +1,512 @@
+})
+# 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
+)
+})
+}
+# 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
+)
+})
+# 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.
")
+})
+# 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.
")
+})
+# 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
+)
+})
+}
+# 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")
+# 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
+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()
+# Chunk 2: load_libraries
+# 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", "executive_report_utils.R"))
+}, error = function(e) {
+stop("Could not load executive_report_utils.R from either location: ", e$message)
+})
+})
+# 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
+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()
+# Chunk 2: load_libraries
+# 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", "executive_report_utils.R"))
+}, error = function(e) {
+stop("Could not load executive_report_utils.R from either location: ", e$message)
+})
+})
+# 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
+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()
+# Chunk 2: load_libraries
+# 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", "executive_report_utils.R"))
+# 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
+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()
+# Chunk 2: load_libraries
+# 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)
+# })
+# })
+# Chunk 3: initialize_project_config
+# 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))
+# 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)
+# 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)
+})
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..642ff51
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "python.REPL.enableREPLSmartSend": false
+}
\ No newline at end of file
diff --git a/Current - Pivots planting date and harevsting data.xlsx b/Current - Pivots planting date and harevsting data.xlsx
deleted file mode 100644
index f5445d7..0000000
Binary files a/Current - Pivots planting date and harevsting data.xlsx and /dev/null differ
diff --git a/Rplots.pdf b/Rplots.pdf
deleted file mode 100644
index a68f07a..0000000
Binary files a/Rplots.pdf and /dev/null differ
diff --git a/figure/sub_chunk_8433-1.png b/figure/sub_chunk_8433-1.png
new file mode 100644
index 0000000..160a733
Binary files /dev/null and b/figure/sub_chunk_8433-1.png differ
diff --git a/python_app/planet_download.ipynb b/python_app/planet_download.ipynb
index 7457816..ed5606f 100644
--- a/python_app/planet_download.ipynb
+++ b/python_app/planet_download.ipynb
@@ -12,7 +12,7 @@
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "b7ca7102-5fd9-481f-90cd-3ba60e288649",
"metadata": {},
"outputs": [],
@@ -43,28 +43,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
- "id": "330c967c-2742-4a7a-9a61-28bfdaf8eeca",
- "metadata": {},
- "outputs": [],
- "source": [
- "# pip install geopandas"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "id": "49f8496a-a267-4b74-9500-a168e031ed68",
- "metadata": {},
- "outputs": [],
- "source": [
- "#import pipreqs\n",
- "#pipreqs Resilience BV/4002 CMD App - General/4002 CMD Team/4002 TechnicalData/04 WP2 technical/python/Chemba_download.ipynb"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"id": "5491a840-779c-4f0c-8164-c3de738b3298",
"metadata": {},
"outputs": [],
@@ -75,7 +54,7 @@
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": null,
"id": "eb1fb662-0e25-4ca9-8317-c6953290842b",
"metadata": {},
"outputs": [],
@@ -100,36 +79,36 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": null,
"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 = 'chemba' #or xinavane or chemba_test_8b\n"
+ "project = 'citrus_brazil_trial' #or xinavane or chemba_test_8b\n"
]
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": null,
"id": "c9f79e81-dff8-4109-8d26-6c423142dcf2",
"metadata": {},
"outputs": [],
"source": [
"# Adjust the number of days needed\n",
- "days = 2 #change back to 28 which is the default. 3 years is 1095 days."
+ "days = 30 #change back to 28 which is the default. 3 years is 1095 days.\n"
]
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": null,
"id": "e18bdf8f-be4b-44ab-baaa-de5de60d92cb",
"metadata": {},
"outputs": [],
"source": [
"#delete all the satellite outputs -> then True\n",
- "empty_folder_question = False"
+ "empty_folder_question = True"
]
},
{
@@ -145,7 +124,7 @@
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": null,
"id": "3f7c8e04-4569-457b-b39d-283582c4ba36",
"metadata": {},
"outputs": [],
@@ -169,7 +148,7 @@
},
{
"cell_type": "code",
- "execution_count": 10,
+ "execution_count": null,
"id": "244b5752-4f02-4347-9278-f6a0a46b88f4",
"metadata": {},
"outputs": [],
@@ -183,34 +162,92 @@
" bands: [\"red\", \"green\", \"blue\", \"nir\", \"udm1\"]\n",
" }],\n",
" output: {\n",
- " bands: 4 \n",
- " //sampleType: \"FLOAT32\"\n",
+ " bands: 4,\n",
+ " sampleType: \"FLOAT32\"\n",
" }\n",
" };\n",
" }\n",
"\n",
" function evaluatePixel(sample) {\n",
" // Scale the bands\n",
- " var scaledBlue = [2.5 * sample.blue / 10000];\n",
- " var scaledGreen = [2.5 * sample.green / 10000];\n",
- " var scaledRed = [2.5 * sample.red / 10000];\n",
- " var scaledNIR = [2.5 * sample.nir / 10000];\n",
- " \n",
- " // Calculate the CI (Chlorophyll Index) using the scaled values\n",
- " // var CI = [scaledNIR / scaledGreen - 1] ;\n",
+ " var scaledBlue = 2.5 * sample.blue / 10000;\n",
+ " var scaledGreen = 2.5 * sample.green / 10000;\n",
+ " var scaledRed = 2.5 * sample.red / 10000;\n",
+ " var scaledNIR = 2.5 * sample.nir / 10000;\n",
+ " \n",
+ " // Calculate indices for cloud and shadow detection\n",
+ " var brightness = (scaledBlue + scaledGreen + scaledRed) / 3;\n",
+ " var ndvi = (scaledNIR - scaledRed) / (scaledNIR + scaledRed);\n",
+ " var blue_ratio = scaledBlue / (scaledRed + 0.01); // Add 0.01 to prevent division by zero\n",
+ " \n",
+ " // CLOUD DETECTION\n",
+ " // Clouds are typically bright in all bands\n",
+ " var bright_pixels = (scaledBlue > 0.3) && (scaledGreen > 0.3) && (scaledRed > 0.3);\n",
+ " \n",
+ " // Clouds often have higher blue reflectance\n",
+ " var blue_dominant = scaledBlue > (scaledRed * 1.2);\n",
+ " \n",
+ " // Low NDVI areas that are bright are likely clouds\n",
+ " var low_ndvi = ndvi < 0.1;\n",
+ " \n",
+ " // Combine cloud criteria\n",
+ " var is_cloud = bright_pixels && (blue_dominant || low_ndvi);\n",
+ " \n",
+ " // SHADOW DETECTION\n",
+ " // Shadows are typically dark\n",
+ " var dark_pixels = brightness < 0.1;\n",
+ " \n",
+ " // Shadows have lower NIR reflectance\n",
+ " var low_nir = scaledNIR < 0.15;\n",
+ " \n",
+ " // Shadows often have higher blue proportion relative to NIR\n",
+ " var blue_enhanced = blue_ratio > 0.8;\n",
+ " \n",
+ " // Combine shadow criteria\n",
+ " var is_shadow = dark_pixels && (low_nir || blue_enhanced);\n",
+ " \n",
+ " // Calculate CI (Chlorophyll Index) using the scaled values\n",
+ " var CI = (scaledNIR / scaledRed) - 1;\n",
+ " \n",
+ " // Use built-in usable data mask (udm1) and our own cloud/shadow detection\n",
+ " // udm1 == 0 means pixel is usable according to Planet's metadata\n",
+ " if (sample.udm1 == 0 && !is_cloud && !is_shadow) {\n",
+ " return [scaledRed, scaledGreen, scaledBlue, scaledNIR];\n",
+ " } else {\n",
+ " return [NaN, NaN, NaN, NaN];\n",
+ " }\n",
+ " }\n",
+ "\"\"\"\n",
"\n",
- "// Output the scaled bands and CI\n",
- " if (sample.udm1 == 0) { \n",
- " return [\n",
- " scaledRed,\n",
- " scaledGreen,\n",
- " scaledBlue,\n",
- " scaledNIR\n",
- " // sample.UDM,\n",
- " // CI,\n",
- " ]\n",
- " } else {\n",
- " return [NaN, NaN, NaN, NaN]}\n",
+ "# Original evalscript without cloud/shadow detection (for comparison)\n",
+ "evalscript_original = \"\"\"\n",
+ " //VERSION=3\n",
+ "\n",
+ " function setup() {\n",
+ " return {\n",
+ " input: [{\n",
+ " bands: [\"red\", \"green\", \"blue\", \"nir\", \"udm1\"]\n",
+ " }],\n",
+ " output: {\n",
+ " bands: 4,\n",
+ " sampleType: \"FLOAT32\"\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function evaluatePixel(sample) {\n",
+ " // Scale the bands\n",
+ " var scaledBlue = 2.5 * sample.blue / 10000;\n",
+ " var scaledGreen = 2.5 * sample.green / 10000;\n",
+ " var scaledRed = 2.5 * sample.red / 10000;\n",
+ " var scaledNIR = 2.5 * sample.nir / 10000;\n",
+ " \n",
+ " // Only use udm1 mask (Planet's usable data mask)\n",
+ " if (sample.udm1 == 0) {\n",
+ " return [scaledRed, scaledGreen, scaledBlue, scaledNIR];\n",
+ " } else {\n",
+ " return [NaN, NaN, NaN, NaN];\n",
+ " }\n",
" }\n",
"\"\"\"\n",
"\n",
@@ -233,6 +270,24 @@
"\n",
" )\n",
"\n",
+ "# Added function to get original image for comparison\n",
+ "def get_original_request_day(time_interval, bbox, size):\n",
+ " return SentinelHubRequest(\n",
+ " evalscript=evalscript_original,\n",
+ " input_data=[\n",
+ " SentinelHubRequest.input_data(\n",
+ " data_collection=DataCollection.planet_data2,\n",
+ " time_interval=(time_interval, time_interval)\n",
+ " )\n",
+ " ],\n",
+ " responses=[\n",
+ " SentinelHubRequest.output_response('default', MimeType.TIFF)\n",
+ " ],\n",
+ " bbox=bbox,\n",
+ " size=size,\n",
+ " config=config,\n",
+ " )\n",
+ "\n",
"def download_function(slot, bbox, size):\n",
" # create a list of requests\n",
" list_of_requests = [get_true_color_request_day(slot, bbox, size)]\n",
@@ -265,21 +320,10 @@
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": null,
"id": "848dc773-70d6-4ae6-b05c-d6ebfb41624d",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Monthly time windows:\n",
- "\n",
- "2024-10-13\n",
- "2024-10-14\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
"days_needed = int(os.environ.get(\"DAYS\", days))\n",
"date_str = os.environ.get(\"DATE\")\n",
@@ -318,7 +362,7 @@
},
{
"cell_type": "code",
- "execution_count": 12,
+ "execution_count": null,
"id": "c803e373-2567-4233-af7d-0d2d6f7d4f8e",
"metadata": {},
"outputs": [],
@@ -328,7 +372,7 @@
},
{
"cell_type": "code",
- "execution_count": 13,
+ "execution_count": null,
"id": "dc24d54e-2272-4f30-bcf5-4d8fc381915c",
"metadata": {},
"outputs": [],
@@ -338,7 +382,7 @@
},
{
"cell_type": "code",
- "execution_count": 14,
+ "execution_count": null,
"id": "cd071b42-d0cd-4e54-8f88-ad1a339748e3",
"metadata": {},
"outputs": [],
@@ -348,22 +392,13 @@
},
{
"cell_type": "code",
- "execution_count": 15,
+ "execution_count": null,
"id": "301d12e4-e47a-4034-aec0-aa5673e64935",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Area bounding box: BBox(((34.883117060422094, -17.35152458627111), (34.983858813209004, -17.29173171459206)), crs=CRS('4326'))\n",
- "\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
"bbox_splitter = BBoxSplitter(\n",
- " shapely_geometries, CRS.WGS84, (5, 5), reduce_bbox_sizes=True\n",
+ " shapely_geometries, CRS.WGS84, (1, 1), reduce_bbox_sizes=True\n",
") # bounding box will be split into a grid of 5x4 bounding boxes\n",
"\n",
"# based on https://github.com/sentinel-hub/sentinelhub-py/blob/master/examples/large_area_utilities.ipynb \n",
@@ -376,24 +411,10 @@
},
{
"cell_type": "code",
- "execution_count": 16,
+ "execution_count": null,
"id": "431f6856-8d7e-4868-b627-20deeb47d77e",
"metadata": {},
- "outputs": [
- {
- "data": {
- "image/svg+xml": [
- " "
- ],
- "text/plain": [
- ""
- ]
- },
- "execution_count": 16,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"geometry_list = bbox_splitter.get_geometry_list()\n",
"\n",
@@ -402,7 +423,7 @@
},
{
"cell_type": "code",
- "execution_count": 17,
+ "execution_count": null,
"id": "18655785",
"metadata": {},
"outputs": [],
@@ -423,43 +444,35 @@
},
{
"cell_type": "code",
- "execution_count": 18,
+ "execution_count": null,
"id": "a6fc418f",
"metadata": {},
"outputs": [],
"source": [
"# Filter slots to only include dates with available images\n",
"available_slots = [slot for slot in slots if is_image_available(slot)]\n",
+ "\n",
+ "# Store the first 5 available slots for comparison later (if available)\n",
+ "comparison_slots = available_slots[:min(5, len(available_slots))]\n",
"\n"
]
},
{
"cell_type": "code",
- "execution_count": 19,
+ "execution_count": null,
"id": "ebc416be",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "['2024-10-13']\n",
- "Total slots: 2\n",
- "Available slots: 1\n",
- "Excluded slots due to clouds: 1\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
"print(available_slots)\n",
"print(f\"Total slots: {len(slots)}\")\n",
"print(f\"Available slots: {len(available_slots)}\")\n",
- "print(f\"Excluded slots due to clouds: {len(slots) - len(available_slots)}\")\n"
+ "print(f\"Excluded slots due to empty dates: {len(slots) - len(available_slots)}\")\n"
]
},
{
"cell_type": "code",
- "execution_count": 20,
+ "execution_count": null,
"id": "b0cabe8f-e1f2-4b18-8ac0-c2565d0ff16b",
"metadata": {},
"outputs": [],
@@ -540,78 +553,10 @@
},
{
"cell_type": "code",
- "execution_count": 21,
- "id": "8f2c3e20-894c-4fe5-95d3-482ee3bac117",
- "metadata": {},
- "outputs": [],
- "source": [
- "#from mpl_toolkits.basemap import Basemap # Available here: https://github.com/matplotlib/basemap\n",
- "#from matplotlib.patches import Polygon as PltPolygon\n",
- "\n",
- "#show_splitter(bbox_splitter, show_legend=True)\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 22,
+ "execution_count": null,
"id": "41b7369c-f768-44ba-983e-eb8eae4f3afd",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- " Image downloaded for 2024-10-13 and bbox 34.893901568473275,-17.347991154602312,34.902244620706924,-17.34035402194366\n"
- ]
- },
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "/usr/local/anaconda3/lib/python3.11/site-packages/sentinelhub/geometry.py:136: SHDeprecationWarning: Initializing `BBox` objects from `BBox` objects will no longer be possible in future versions.\n",
- " return BBox._tuple_from_bbox(bbox)\n",
- "/var/folders/qt/jcd_lqsd6nq6_5902w6403_h0000gn/T/ipykernel_37871/170088608.py:67: 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-10-13 and bbox 34.88396660293689,-17.329532375657408,34.90142638883001,-17.32760743759949\n"
- ]
- },
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "/usr/local/anaconda3/lib/python3.11/site-packages/sentinelhub/geometry.py:136: SHDeprecationWarning: Initializing `BBox` objects from `BBox` objects will no longer be possible in future versions.\n",
- " return BBox._tuple_from_bbox(bbox)\n"
- ]
- },
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- " Image downloaded for 2024-10-13 and bbox 34.88382012875465,-17.32760743759949,34.90326541097948,-17.315648863263682\n",
- " Image downloaded for 2024-10-13 and bbox 34.883117060422094,-17.315648863263682,34.9023997776829,-17.30369028892787\n",
- " Image downloaded for 2024-10-13 and bbox 34.883228421674296,-17.30369028892787,34.90087977072043,-17.29173171459206\n",
- " Image downloaded for 2024-10-13 and bbox 34.905630133532,-17.344205996328164,34.92341376153686,-17.3395660119353\n",
- " Image downloaded for 2024-10-13 and bbox 34.905710855573936,-17.3395660119353,34.92341376153686,-17.32760743759949\n",
- " Image downloaded for 2024-10-13 and bbox 34.90326541097948,-17.32760743759949,34.9146041448172,-17.316916692391352\n",
- " Image downloaded for 2024-10-13 and bbox 34.92341376153686,-17.35152458627111,34.93795515789262,-17.3395660119353\n",
- " Image downloaded for 2024-10-13 and bbox 34.92341376153686,-17.3395660119353,34.930609068174576,-17.334652123669677\n",
- " Image downloaded for 2024-10-13 and bbox 34.95572747068288,-17.346631434137443,34.96371046265162,-17.3395660119353\n",
- " Image downloaded for 2024-10-13 and bbox 34.95078266733916,-17.3395660119353,34.96371046265162,-17.32760743759949\n",
- " Image downloaded for 2024-10-13 and bbox 34.947001852567226,-17.32760743759949,34.96371046265162,-17.315648863263682\n",
- " Image downloaded for 2024-10-13 and bbox 34.95178751078458,-17.315648863263682,34.96371046265162,-17.311133447223234\n",
- " Image downloaded for 2024-10-13 and bbox 34.96371046265162,-17.34987611658989,34.983858813209004,-17.3395660119353\n",
- " Image downloaded for 2024-10-13 and bbox 34.96371046265162,-17.3395660119353,34.982903717937305,-17.32760743759949\n",
- " Image downloaded for 2024-10-13 and bbox 34.96371046265162,-17.32760743759949,34.977469765322226,-17.322351372243453\n",
- " Image downloaded for 2024-10-13 and bbox 34.96371046265162,-17.315106596091614,34.96405511470557,-17.312526972941786\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
"# Load areas outside the loop if they remain constant\n",
"#bbox_area = json.dumps(chosen_area)\n",
@@ -627,179 +572,12 @@
},
{
"cell_type": "code",
- "execution_count": 23,
- "id": "7c0f8f0e-b1bc-4b5a-939d-e4358a485c06",
- "metadata": {},
- "outputs": [],
- "source": [
- "\n",
- "#if project == 'chemba':\n",
- "# chosen_area = [[34.9460, -17.3500, 34.9839, -17.3110], [34.8830, -17.3516, 34.9380, -17.2917]]\n",
- "\n",
- "#if project == 'chemba_test_8b':\n",
- "# chosen_area = [[34.946, -17.3516, 34.938, -17.2917], [34.883, -17.3516, 34.938, -17.2917]]\n",
- "\n",
- "#if project == 'xinavane':\n",
- "# chosen_area = [[32.6790, -25.0333, 32.7453, -25.0235], [32.6213, -25.0647, 32.6284, -25.0570]]"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 24,
- "id": "16c449ab-eb40-4ed9-9a26-6a64dae114e1",
- "metadata": {},
- "outputs": [],
- "source": [
- "#bbox_area = json.dumps(chosen_area)\n",
- "\n",
- "#areas = json.loads(os.getenv('BBOX', bbox_area))\n",
- "#areas"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 25,
- "id": "6c02d7de-cddf-4fc3-8d23-8431415d07b8",
- "metadata": {},
- "outputs": [],
- "source": [
- "# Load areas outside the loop if they remain constant\n",
- "#bbox_area = json.dumps(chosen_area)\n",
- "#areas = json.loads(os.getenv('BBOX', bbox_area))\n",
- "#resolution = 3\n",
- "\n",
- "#for slot in slots:\n",
- "# for area in areas:\n",
- "# bbox = BBox(bbox=area, crs=CRS.WGS84)\n",
- "# size = bbox_to_dimensions(bbox, resolution=resolution)\n",
- "# download_function(slot, bbox, size)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 26,
- "id": "f62d8af8-1c5c-4a83-b888-7205bbe191af",
- "metadata": {},
- "outputs": [],
- "source": [
- "#size"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 27,
+ "execution_count": null,
"id": "68db3c15-6f94-432e-b315-c329e4251b21",
"metadata": {
"tags": []
},
- "outputs": [
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/82e0bec78647a7e631bf46c7826a095a/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/f3a481c8a831f1096ad4a2924f7744ae/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/0f2fabfb5a98064d90a0efbab3c88137/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/b0517c30996ba1b4084987a606399db0/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/881f7ee0060d76182bd363cc0241c6b4/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/e2e16ebfcf17c0e954d5251fb42c3551/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/e651ed7a8ba7ce8ab6af12a272dc3fae/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/a5171cb8730a5d1eb6575afe3106d133/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/a9d5d3d8699ef77dc450496a196e7242/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/27e0e42c71a971cf4454256bc4978d91/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/4c86b96edcea1fb8aa4b3bc3f8d496da/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/eda0b30e16fdea0302acb62086f71169/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/7b17b7ffe207fded6a7f217d1b3a9a27/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/dc258ae8b2bae1a4c8cff9afd2a5f0a1/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/61b7ddaaa809eae197a1b595532146d2/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/cc7adecb78b95df872685b587dd0efc2/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/d73a9e0204c5149a819798778dfc9392/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/4babd2b91a1516c7c99da04d99ad4a19/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/82e0bec78647a7e631bf46c7826a095a/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/f3a481c8a831f1096ad4a2924f7744ae/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/0f2fabfb5a98064d90a0efbab3c88137/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/b0517c30996ba1b4084987a606399db0/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/881f7ee0060d76182bd363cc0241c6b4/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/e2e16ebfcf17c0e954d5251fb42c3551/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/e651ed7a8ba7ce8ab6af12a272dc3fae/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/a5171cb8730a5d1eb6575afe3106d133/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/a9d5d3d8699ef77dc450496a196e7242/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/27e0e42c71a971cf4454256bc4978d91/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/4c86b96edcea1fb8aa4b3bc3f8d496da/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/eda0b30e16fdea0302acb62086f71169/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/7b17b7ffe207fded6a7f217d1b3a9a27/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/dc258ae8b2bae1a4c8cff9afd2a5f0a1/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/61b7ddaaa809eae197a1b595532146d2/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/cc7adecb78b95df872685b587dd0efc2/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/d73a9e0204c5149a819798778dfc9392/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/4babd2b91a1516c7c99da04d99ad4a19/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/82e0bec78647a7e631bf46c7826a095a/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/f3a481c8a831f1096ad4a2924f7744ae/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/0f2fabfb5a98064d90a0efbab3c88137/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/b0517c30996ba1b4084987a606399db0/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/881f7ee0060d76182bd363cc0241c6b4/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/e2e16ebfcf17c0e954d5251fb42c3551/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/e651ed7a8ba7ce8ab6af12a272dc3fae/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/a5171cb8730a5d1eb6575afe3106d133/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/a9d5d3d8699ef77dc450496a196e7242/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/27e0e42c71a971cf4454256bc4978d91/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/d73a9e0204c5149a819798778dfc9392/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/61b7ddaaa809eae197a1b595532146d2/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/4c86b96edcea1fb8aa4b3bc3f8d496da/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/7b17b7ffe207fded6a7f217d1b3a9a27/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/4babd2b91a1516c7c99da04d99ad4a19/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/eda0b30e16fdea0302acb62086f71169/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/dc258ae8b2bae1a4c8cff9afd2a5f0a1/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: ../laravel_app/storage/app/chemba/single_images/2024-10-13/cc7adecb78b95df872685b587dd0efc2/response.tiff: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n",
- "Warning 1: TIFFReadDirectory:Sum of Photometric type-related color channels and ExtraSamples doesn't match SamplesPerPixel. Defining non-color channels as ExtraSamples.\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
"for slot in available_slots:\n",
" merge_files(slot)"
@@ -817,20 +595,12 @@
},
{
"cell_type": "code",
- "execution_count": 28,
+ "execution_count": null,
"id": "cb3fa856-a550-4899-844a-e69209bba3ad",
"metadata": {
"tags": []
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Skipping empty_folders function.\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
"# List of folder names\n",
"\n",
@@ -866,7 +636,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "Python 3 (ipykernel)",
+ "display_name": "base",
"language": "python",
"name": "python3"
},
@@ -880,7 +650,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.11.4"
+ "version": "3.12.3"
}
},
"nbformat": 4,
diff --git a/python_app/planet_download_8band.ipynb b/python_app/planet_download_8band.ipynb
index 7450286..d2847b9 100644
--- a/python_app/planet_download_8band.ipynb
+++ b/python_app/planet_download_8band.ipynb
@@ -29,14 +29,32 @@
"from osgeo import gdal\n",
"\n",
"from sentinelhub import MimeType, CRS, BBox, SentinelHubRequest, SentinelHubDownloadClient, \\\n",
- " DataCollection, bbox_to_dimensions, DownloadRequest, SHConfig, BBoxSplitter, read_data\n",
+ " DataCollection, bbox_to_dimensions, DownloadRequest, SHConfig, BBoxSplitter, read_data, Geometry, SentinelHubCatalog\n",
"\n",
"config = SHConfig()\n",
+ "catalog = SentinelHubCatalog(config=config)\n",
"\n",
"import time\n",
- "import shutil"
+ "import shutil\n",
+ "\n",
+ "import geopandas as gpd\n",
+ "from shapely.geometry import MultiLineString, MultiPolygon, Polygon, box, shape\n"
]
},
+ {
+ "cell_type": "markdown",
+ "id": "d19f7f18",
+ "metadata": {},
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "80dc31ce",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
{
"cell_type": "code",
"execution_count": 2,
@@ -103,29 +121,29 @@
"source": [
"#project = 'chemba' #or xinavane or chemba_test_8b\n",
"#project = 'xinavane' #or xinavane or chemba_test_8b\n",
- "project = 'chemba_test_8b' #or xinavane or chemba_test_8b\n"
+ "project = 'citrus_brazil_trial' #or xinavane or chemba_test_8b\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
- "id": "c9f79e81-dff8-4109-8d26-6c423142dcf2",
+ "id": "d5a99e68",
"metadata": {},
"outputs": [],
"source": [
"# Adjust the number of days needed\n",
- "days = 7 #change back to 28 which is the default. 3 years is 1095 days."
+ "days = 200 #change back to 28 which is the default. 3 years is 1095 days."
]
},
{
"cell_type": "code",
"execution_count": 8,
- "id": "e18bdf8f-be4b-44ab-baaa-de5de60d92cb",
+ "id": "f2b0e629",
"metadata": {},
"outputs": [],
"source": [
"#delete all the satellite outputs -> then True\n",
- "empty_folder_question = False"
+ "empty_folder_question = True"
]
},
{
@@ -146,10 +164,11 @@
"metadata": {},
"outputs": [],
"source": [
- "BASE_PATH = Path('../laravel_app/storage/app') / os.getenv('PROJECT_DIR', project) \n",
+ "BASE_PATH = Path('../laravel_app/storage/app') / os.getenv('PROJECT_DIR',project) \n",
"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",
"# Check if the folders exist, and if not, create them\n",
"if not os.path.exists(BASE_PATH_SINGLE_IMAGES):\n",
@@ -164,7 +183,7 @@
},
{
"cell_type": "code",
- "execution_count": 15,
+ "execution_count": 10,
"id": "244b5752-4f02-4347-9278-f6a0a46b88f4",
"metadata": {},
"outputs": [],
@@ -177,10 +196,10 @@
" input: [{\n",
" bands: \n",
" [\"CoastalBlue\", \"Blue\", \"Green\", \"GreenI\", \"Yellow\", \"Red\", \n",
- " \"RedEdge\", \"NIR\", \"UDM2_Clear\"]\n",
+ " \"RedEdge\", \"NIR\", \"udm1\" ]\n",
" }],\n",
" output: {\n",
- " bands: 9 \n",
+ " bands: 8\n",
" //sampleType: \"FLOAT32\"\n",
" }\n",
" };\n",
@@ -195,11 +214,10 @@
" var scaledYellow = [2.5 * sample.Yellow / 10000];\n",
" var scaledRedEdge = [2.5 * sample.RedEdge / 10000];\n",
" var scaledNIR = [2.5 * sample.NIR / 10000];\n",
- " var UDM2_Clear = UDM2_Clear\n",
" \n",
" // Output the scaled bands\n",
" \n",
- " // if (sample.UDM2_Clear != 0) { \n",
+ " // if (sample.udm1 == 0) { \n",
" return [\n",
" scaledCoastalBlue,\n",
" scaledBlue,\n",
@@ -209,8 +227,7 @@
" scaledRed, \n",
" scaledRedEdge,\n",
" scaledNIR,\n",
- " UDM2_Clear\n",
- " ]\n",
+ " ]\n",
" // } else {\n",
" // return [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN]}\n",
" \n",
@@ -278,19 +295,20 @@
"text": [
"Monthly time windows:\n",
"\n",
- "2024-03-19\n",
- "2024-03-20\n",
- "2024-03-21\n",
- "2024-03-22\n",
- "2024-03-23\n",
- "2024-03-24\n",
- "2024-03-25\n"
+ "2024-09-06\n",
+ "2024-09-07\n",
+ "2024-09-08\n",
+ "...\n",
+ "2025-03-22\n",
+ "2025-03-23\n",
+ "2025-03-24\n"
]
}
],
"source": [
"days_needed = int(os.environ.get(\"DAYS\", days))\n",
"date_str = os.environ.get(\"DATE\")\n",
+ "\n",
"if date_str:\n",
" # Parse de datumstring naar een datetime.date object\n",
" end = datetime.datetime.strptime(date_str, \"%Y-%m-%d\").date()\n",
@@ -315,40 +333,6 @@
"\n"
]
},
- {
- "cell_type": "code",
- "execution_count": 12,
- "id": "93c715f7-4f7e-428e-bbb9-53a2d8f6e2c8",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Monthly time windows:\n",
- "\n",
- "2024-03-19\n"
- ]
- }
- ],
- "source": [
- "end = datetime.datetime(2024, 3, 19)\n",
- "start = end \n",
- "days_needed =1\n",
- "slots = [(start + datetime.timedelta(days=i)).strftime('%Y-%m-%d') for i in range(days_needed)]\n",
- "\n",
- "print('Monthly time windows:\\n')\n",
- "if len(slots) > 10:\n",
- " for slot in slots[:3]:\n",
- " print(slot)\n",
- " print(\"...\")\n",
- " for slot in slots[-3:]:\n",
- " print(slot)\n",
- "else:\n",
- " for slot in slots:\n",
- " print(slot)"
- ]
- },
{
"cell_type": "markdown",
"id": "f8ea846f-783b-4460-a951-7b522273555f",
@@ -359,70 +343,415 @@
},
{
"cell_type": "code",
- "execution_count": 13,
- "id": "1fb5dc6c-de83-4fb2-b3f7-ee9e7060a80d",
+ "execution_count": 12,
+ "id": "f145bdd1",
"metadata": {},
"outputs": [],
"source": [
- "if project == 'chemba':\n",
- " chosen_area = [[34.946, -17.3516, 34.938, -17.2917], [34.883, -17.3516, 34.938, -17.2917]]\n",
- "\n",
- "if project == 'chemba_test_8b':\n",
- " chosen_area = [[34.946, -17.3516, 34.938, -17.2917], [34.883, -17.3516, 34.938, -17.2917]]\n",
- "\n",
- "if project == 'xinavane':\n",
- " chosen_area = [[32.6790, -25.0333, 32.7453, -25.0235], [32.6213, -25.0647, 32.6284, -25.0570]]"
+ "geo_json = gpd.read_file(str(geojson_file))"
]
},
{
"cell_type": "code",
- "execution_count": 16,
- "id": "6c02d7de-cddf-4fc3-8d23-8431415d07b8",
+ "execution_count": 13,
+ "id": "3e44fc64",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "geometries = [Geometry(geometry, crs=CRS.WGS84) for geometry in geo_json.geometry]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "50419beb",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "shapely_geometries = [geometry.geometry for geometry in geometries]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "308089ac",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- " Image downloaded for 2024-03-19\n",
- " Image downloaded for 2024-03-19\n"
+ "Area bounding box: BBox(((-47.09879025717693, -22.67132809994226), (-47.09188307701802, -22.66813642658124)), crs=CRS('4326'))\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "bbox_splitter = BBoxSplitter(\n",
+ " shapely_geometries, CRS.WGS84, (1,1), reduce_bbox_sizes=True\n",
+ ") # bounding box will be split into a grid of 5x4 bounding boxes\n",
+ "\n",
+ "# based on https://github.com/sentinel-hub/sentinelhub-py/blob/master/examples/large_area_utilities.ipynb \n",
+ "\n",
+ "print(\"Area bounding box: {}\\n\".format(bbox_splitter.get_area_bbox().__repr__()))\n",
+ "\n",
+ "bbox_list = bbox_splitter.get_bbox_list()\n",
+ "info_list = bbox_splitter.get_info_list()\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "8e330d6b",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 16,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "geometry_list = bbox_splitter.get_geometry_list()\n",
+ "\n",
+ "geometry_list[0]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "62bc676c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Function to check if images are available for a given date\n",
+ "def is_image_available(date):\n",
+ " for bbox in bbox_list:\n",
+ " search_iterator = catalog.search(\n",
+ " collection=byoc,\n",
+ " bbox=bbox, # Define your bounding box\n",
+ " time=(date, date)\n",
+ " )\n",
+ " if len(list(search_iterator)) > 0:\n",
+ " return True\n",
+ " return False\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "8d686f8e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Filter slots to only include dates with available images\n",
+ "available_slots = [slot for slot in slots if is_image_available(slot)]\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "8536fb09",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "['2024-09-06', '2024-09-07', '2024-09-08', '2024-09-09', '2024-09-10', '2024-09-11', '2024-09-12', '2024-09-13', '2024-09-14', '2024-09-17', '2024-09-18', '2024-09-19', '2024-09-20', '2024-09-22', '2024-09-23', '2024-09-25', '2024-09-26', '2024-09-27', '2024-09-28', '2024-09-29', '2024-09-30', '2024-10-01', '2024-10-03', '2024-10-08', '2024-10-13', '2024-10-15', '2024-10-16', '2024-10-18', '2024-10-22', '2024-10-23', '2024-10-29', '2024-10-30', '2024-10-31', '2024-11-01', '2024-11-05', '2024-11-06', '2024-11-09', '2024-11-10', '2024-11-11', '2024-11-13', '2024-11-14', '2024-11-15', '2024-11-17', '2024-11-19', '2024-11-20', '2024-11-24', '2024-11-25', '2024-11-26', '2024-11-27', '2024-12-01', '2024-12-02', '2024-12-04', '2024-12-05', '2024-12-06', '2024-12-07', '2024-12-08', '2024-12-09', '2024-12-10', '2024-12-11', '2024-12-15', '2024-12-17', '2024-12-18', '2024-12-19', '2024-12-21', '2024-12-24', '2024-12-25', '2024-12-28', '2024-12-29', '2024-12-30', '2025-01-01', '2025-01-02', '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-19', '2025-01-20', '2025-01-21', '2025-01-22', '2025-01-23', '2025-01-24', '2025-01-25', '2025-01-27', '2025-01-28', '2025-01-30', '2025-02-05', '2025-02-06', '2025-02-08', '2025-02-10', '2025-02-12', '2025-02-13', '2025-02-14', '2025-02-15', '2025-02-16', '2025-02-17', '2025-02-18', '2025-02-20', '2025-02-21', '2025-02-22', '2025-02-23', '2025-02-25', '2025-02-27', '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-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']\n",
+ "Total slots: 200\n",
+ "Available slots: 132\n",
+ "Excluded slots due to clouds: 68\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(available_slots)\n",
+ "print(f\"Total slots: {len(slots)}\")\n",
+ "print(f\"Available slots: {len(available_slots)}\")\n",
+ "print(f\"Excluded slots due to clouds: {len(slots) - len(available_slots)}\")\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "id": "5059aa6e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def show_splitter(splitter, alpha=0.2, area_buffer=0.2, show_legend=False):\n",
+ " area_bbox = splitter.get_area_bbox()\n",
+ " minx, miny, maxx, maxy = area_bbox\n",
+ " lng, lat = area_bbox.middle\n",
+ " w, h = maxx - minx, maxy - miny\n",
+ " minx = minx - area_buffer * w\n",
+ " miny = miny - area_buffer * h\n",
+ " maxx = maxx + area_buffer * w\n",
+ " maxy = maxy + area_buffer * h\n",
+ "\n",
+ " fig = plt.figure(figsize=(10, 10))\n",
+ " ax = fig.add_subplot(111)\n",
+ "\n",
+ " base_map = Basemap(\n",
+ " projection=\"mill\",\n",
+ " lat_0=lat,\n",
+ " lon_0=lng,\n",
+ " llcrnrlon=minx,\n",
+ " llcrnrlat=miny,\n",
+ " urcrnrlon=maxx,\n",
+ " urcrnrlat=maxy,\n",
+ " resolution=\"l\",\n",
+ " epsg=4326,\n",
+ " )\n",
+ " base_map.drawcoastlines(color=(0, 0, 0, 0))\n",
+ "\n",
+ " area_shape = splitter.get_area_shape()\n",
+ "\n",
+ " if isinstance(area_shape, Polygon):\n",
+ " polygon_iter = [area_shape]\n",
+ " elif isinstance(area_shape, MultiPolygon):\n",
+ " polygon_iter = area_shape.geoms\n",
+ " else:\n",
+ " raise ValueError(f\"Geometry of type {type(area_shape)} is not supported\")\n",
+ "\n",
+ " for polygon in polygon_iter:\n",
+ " if isinstance(polygon.boundary, MultiLineString):\n",
+ " for linestring in polygon.boundary:\n",
+ " ax.add_patch(PltPolygon(np.array(linestring), closed=True, facecolor=(0, 0, 0, 0), edgecolor=\"red\"))\n",
+ " else:\n",
+ " ax.add_patch(\n",
+ " PltPolygon(np.array(polygon.boundary.coords), closed=True, facecolor=(0, 0, 0, 0), edgecolor=\"red\")\n",
+ " )\n",
+ "\n",
+ " bbox_list = splitter.get_bbox_list()\n",
+ " info_list = splitter.get_info_list()\n",
+ "\n",
+ " cm = plt.get_cmap(\"jet\", len(bbox_list))\n",
+ " legend_shapes = []\n",
+ " for i, bbox in enumerate(bbox_list):\n",
+ " wgs84_bbox = bbox.transform(CRS.WGS84).get_polygon()\n",
+ "\n",
+ " tile_color = tuple(list(cm(i))[:3] + [alpha])\n",
+ " ax.add_patch(PltPolygon(np.array(wgs84_bbox), closed=True, facecolor=tile_color, edgecolor=\"green\"))\n",
+ "\n",
+ " if show_legend:\n",
+ " legend_shapes.append(plt.Rectangle((0, 0), 1, 1, fc=cm(i)))\n",
+ "\n",
+ " if show_legend:\n",
+ " legend_names = []\n",
+ " for info in info_list:\n",
+ " legend_name = \"{},{}\".format(info[\"index_x\"], info[\"index_y\"])\n",
+ "\n",
+ " for prop in [\"grid_index\", \"tile\"]:\n",
+ " if prop in info:\n",
+ " legend_name = \"{},{}\".format(info[prop], legend_name)\n",
+ "\n",
+ " legend_names.append(legend_name)\n",
+ "\n",
+ " plt.legend(legend_shapes, legend_names)\n",
+ " plt.tight_layout()\n",
+ " plt.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "id": "39fda909",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "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"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " Image downloaded for 2024-09-06\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "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"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " Image downloaded for 2024-09-07\n",
+ " Image downloaded for 2024-09-08\n",
+ " Image downloaded for 2024-09-09\n",
+ " Image downloaded for 2024-09-10\n",
+ " Image downloaded for 2024-09-11\n",
+ " Image downloaded for 2024-09-12\n",
+ " Image downloaded for 2024-09-13\n",
+ " Image downloaded for 2024-09-14\n",
+ " Image downloaded for 2024-09-17\n",
+ " Image downloaded for 2024-09-18\n",
+ " Image downloaded for 2024-09-19\n",
+ " Image downloaded for 2024-09-20\n",
+ " Image downloaded for 2024-09-22\n",
+ " Image downloaded for 2024-09-23\n",
+ " Image downloaded for 2024-09-25\n",
+ " Image downloaded for 2024-09-26\n",
+ " Image downloaded for 2024-09-27\n",
+ " Image downloaded for 2024-09-28\n",
+ " Image downloaded for 2024-09-29\n",
+ " Image downloaded for 2024-09-30\n",
+ " Image downloaded for 2024-10-01\n",
+ " Image downloaded for 2024-10-03\n",
+ " Image downloaded for 2024-10-08\n",
+ " Image downloaded for 2024-10-13\n",
+ " Image downloaded for 2024-10-15\n",
+ " Image downloaded for 2024-10-16\n",
+ " Image downloaded for 2024-10-18\n",
+ " Image downloaded for 2024-10-22\n",
+ " Image downloaded for 2024-10-23\n",
+ " Image downloaded for 2024-10-29\n",
+ " Image downloaded for 2024-10-30\n",
+ " Image downloaded for 2024-10-31\n",
+ " Image downloaded for 2024-11-01\n",
+ " Image downloaded for 2024-11-05\n",
+ " Image downloaded for 2024-11-06\n",
+ " Image downloaded for 2024-11-09\n",
+ " Image downloaded for 2024-11-10\n",
+ " Image downloaded for 2024-11-11\n",
+ " Image downloaded for 2024-11-13\n",
+ " Image downloaded for 2024-11-14\n",
+ " Image downloaded for 2024-11-15\n",
+ " Image downloaded for 2024-11-17\n",
+ " Image downloaded for 2024-11-19\n",
+ " Image downloaded for 2024-11-20\n",
+ " Image downloaded for 2024-11-24\n",
+ " Image downloaded for 2024-11-25\n",
+ " Image downloaded for 2024-11-26\n",
+ " Image downloaded for 2024-11-27\n",
+ " Image downloaded for 2024-12-01\n",
+ " Image downloaded for 2024-12-02\n",
+ " Image downloaded for 2024-12-04\n",
+ " Image downloaded for 2024-12-05\n",
+ " Image downloaded for 2024-12-06\n",
+ " Image downloaded for 2024-12-07\n",
+ " Image downloaded for 2024-12-08\n",
+ " Image downloaded for 2024-12-09\n",
+ " Image downloaded for 2024-12-10\n",
+ " Image downloaded for 2024-12-11\n",
+ " Image downloaded for 2024-12-15\n",
+ " Image downloaded for 2024-12-17\n",
+ " Image downloaded for 2024-12-18\n",
+ " Image downloaded for 2024-12-19\n",
+ " Image downloaded for 2024-12-21\n",
+ " Image downloaded for 2024-12-24\n",
+ " Image downloaded for 2024-12-25\n",
+ " Image downloaded for 2024-12-28\n",
+ " Image downloaded for 2024-12-29\n",
+ " Image downloaded for 2024-12-30\n",
+ " Image downloaded for 2025-01-01\n",
+ " Image downloaded for 2025-01-02\n",
+ " Image downloaded for 2025-01-04\n",
+ " Image downloaded for 2025-01-05\n",
+ " Image downloaded for 2025-01-06\n",
+ " Image downloaded for 2025-01-07\n",
+ " Image downloaded for 2025-01-08\n",
+ " Image downloaded for 2025-01-09\n",
+ " Image downloaded for 2025-01-10\n",
+ " Image downloaded for 2025-01-11\n",
+ " Image downloaded for 2025-01-12\n",
+ " Image downloaded for 2025-01-13\n",
+ " Image downloaded for 2025-01-14\n",
+ " Image downloaded for 2025-01-15\n",
+ " Image downloaded for 2025-01-16\n",
+ " Image downloaded for 2025-01-19\n",
+ " Image downloaded for 2025-01-20\n",
+ " Image downloaded for 2025-01-21\n",
+ " Image downloaded for 2025-01-22\n",
+ " Image downloaded for 2025-01-23\n",
+ " Image downloaded for 2025-01-24\n",
+ " Image downloaded for 2025-01-25\n",
+ " Image downloaded for 2025-01-27\n",
+ " Image downloaded for 2025-01-28\n",
+ " Image downloaded for 2025-01-30\n",
+ " Image downloaded for 2025-02-05\n",
+ " Image downloaded for 2025-02-06\n",
+ " Image downloaded for 2025-02-08\n",
+ " Image downloaded for 2025-02-10\n",
+ " Image downloaded for 2025-02-12\n",
+ " Image downloaded for 2025-02-13\n",
+ " Image downloaded for 2025-02-14\n",
+ " Image downloaded for 2025-02-15\n",
+ " Image downloaded for 2025-02-16\n",
+ " Image downloaded for 2025-02-17\n",
+ " Image downloaded for 2025-02-18\n",
+ " Image downloaded for 2025-02-20\n",
+ " Image downloaded for 2025-02-21\n",
+ " Image downloaded for 2025-02-22\n",
+ " Image downloaded for 2025-02-23\n",
+ " Image downloaded for 2025-02-25\n",
+ " Image downloaded for 2025-02-27\n",
+ " Image downloaded for 2025-03-01\n",
+ " Image downloaded for 2025-03-02\n",
+ " Image downloaded for 2025-03-03\n",
+ " Image downloaded for 2025-03-04\n",
+ " Image downloaded for 2025-03-05\n",
+ " Image downloaded for 2025-03-06\n",
+ " Image downloaded for 2025-03-07\n",
+ " Image downloaded for 2025-03-08\n",
+ " Image downloaded for 2025-03-09\n",
+ " Image downloaded for 2025-03-11\n",
+ " Image downloaded for 2025-03-12\n",
+ " Image downloaded for 2025-03-14\n",
+ " Image downloaded for 2025-03-15\n",
+ " Image downloaded for 2025-03-16\n",
+ " Image downloaded for 2025-03-17\n",
+ " Image downloaded for 2025-03-18\n",
+ " Image downloaded for 2025-03-19\n",
+ " Image downloaded for 2025-03-20\n",
+ " Image downloaded for 2025-03-21\n",
+ " Image downloaded for 2025-03-22\n",
+ " Image downloaded for 2025-03-23\n"
]
}
],
"source": [
"# Load areas outside the loop if they remain constant\n",
- "bbox_area = json.dumps(chosen_area)\n",
- "areas = json.loads(os.getenv('BBOX', bbox_area))\n",
+ "#bbox_area = json.dumps(chosen_area)\n",
+ "#areas = json.loads(os.getenv('BBOX', bbox_area))\n",
"resolution = 3\n",
"\n",
- "for slot in slots:\n",
- " for area in areas:\n",
- " bbox = BBox(bbox=area, crs=CRS.WGS84)\n",
+ "for slot in available_slots:\n",
+ " for bbox in bbox_list:\n",
+ " bbox = BBox(bbox=bbox, crs=CRS.WGS84)\n",
" size = bbox_to_dimensions(bbox, resolution=resolution)\n",
" download_function(slot, bbox, size)"
]
},
{
"cell_type": "code",
- "execution_count": 17,
- "id": "68db3c15-6f94-432e-b315-c329e4251b21",
- "metadata": {
- "tags": []
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Merging complete\n"
- ]
- }
- ],
+ "execution_count": 22,
+ "id": "b7df8a5a",
+ "metadata": {},
+ "outputs": [],
"source": [
- "for slot in slots:\n",
- " merge_files(slot)\n",
- "\n",
- "print('Merging complete')"
+ "for slot in available_slots:\n",
+ " merge_files(slot)"
]
},
{
@@ -437,10 +766,109 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 23,
"id": "cb3fa856-a550-4899-844a-e69209bba3ad",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Emptied folder: ..\\laravel_app\\storage\\app\\citrus_brazil_trial\\merged_virtual\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-06\\\\b043cc2d8d4c16ad7136d80bc773d9a6'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-07\\\\0c7fd3960f4f89065e4763012ee2d5bf'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-08\\\\863975cad69c994bee3cbcf8c4c66969'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-09\\\\71cb610837ce5a64d07e0ecedcf8a1f4'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-10\\\\4612a3bd3f125c866f0a5ac03be77d7d'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-11\\\\e1b17c3480bfbcc90893a4922e34ee75'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-12\\\\2ae0d2333c294e6c756ed52d156a0847'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-13\\\\3c30518183fb168f51b5642df92d386e'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-14\\\\14ca5b977611b62d8175b2eadf79b9a4'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-17\\\\5feb7fd93dc82d95616ca072e0092e48'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-18\\\\2c3c82d1217e30ba8b784d2006dc6eda'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-19\\\\0c82b031dacac3898a0d9b201832fe58'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-20\\\\8184249ce19d16a5eb46849fb71735f9'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-22\\\\11aed9763515aa396b91af85196cdae9'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-23\\\\88fe8d2633709c6e133667bd4b40899f'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-25\\\\cf8b1c1ffd3fef5752ceb69750b27547'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-26\\\\f7ca306f684f53ed384fac004491a613'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-27\\\\9d3276f2df5feae36237bf88882d5653'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-28\\\\6cbcccc4dfa89b7b1adbe765d676a970'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-29\\\\b452dd0597eac790f3556266165cf6eb'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-09-30\\\\bd77c0078ff31b690f18b7e47120cbe2'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-10-01\\\\674447e677f39c5fd90cf16d1532762b'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-10-03\\\\8a621381e180b7c6561d63463c98f6c6'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-10-08\\\\d774ecb97260c424f0ee978e9ad60ffc'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-10-13\\\\8b199b4d58832a4423030090e4fc0b73'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-10-15\\\\d40037ddaf70ecfdb7bc129935829f4a'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-10-16\\\\e16a77670903d84d205c6696e76463d7'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-10-18\\\\c39346f991a2ce0d049626064bcb81b7'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-10-22\\\\49104dad0d9cd58782679ab9f2fbe0f6'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-10-23\\\\b90dda221ad426daca9ebb7848558f4d'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-10-29\\\\e1b930c1d8a4ea4348d6283bb88b1605'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-10-30\\\\403e7bd13f7e7095ed52d444b99f77a2'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-10-31\\\\c2f7e70356620fd6c0a8acb428629b2b'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-11-01\\\\3a5fd53572d0739709008f079db0141d'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-11-05\\\\6d7966d56889b59ab95e6d8dd07e1629'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-11-06\\\\7f0d3dff7bcce30ba35b3fe948b94d06'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-11-09\\\\1bfaabaa18d20650876f3f0f837fc464'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-11-10\\\\4df7433c53fb91d39fbd8d6eb203a15f'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-11-11\\\\4a85241d10149b508127c5c42c58ce99'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-11-13\\\\4fee940f019c897926febd3ce122242d'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-11-14\\\\9870499724f5f8a700d5c9b71256c62f'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-11-15\\\\4d05dbbb8da72096ca77c283e804a022'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-11-17\\\\e6272d213276d0dde53704378704c813'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-11-19\\\\af8d29be427d674b55748ec8903848da'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-11-20\\\\793f4696d7ea76612f7f1cf673301505'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-11-24\\\\5f6e5af6b78179063b799391c6597d61'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-11-25\\\\208ea4f8505eab64fd572e25093a8f4b'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-11-26\\\\fac9b5276038bea8718b3f571336c3cf'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-11-27\\\\3d6409a209cda70e048113cf83da1c90'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-01\\\\7ac210da516cb232e9d76267f8ee39c7'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-02\\\\700b4040e3e2b720a1dbf7b25faf9059'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-04\\\\641f5a937bd11cfd6d24d85654f734e9'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-05\\\\eaeec4515a330c2343940e91620860a0'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-06\\\\f0bd756aa6772d9c3ff9f84af6434cb4'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-07\\\\94ad9369c6400b1acbdf8b894b1cb1c8'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-08\\\\284dd45d9d1813a2329460cc42cfdb4c'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-09\\\\aeeaa22f6830a9c85b45dd9c7f4c0d50'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-10\\\\0fe500fdba20435186533274c3209605'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-11\\\\9def53810a174b79a049ad9e1efec24e'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-15\\\\47195abd1c0e772244cf441056f5e991'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-17\\\\34982fe049a0fc1dbbe2f30f05705e6c'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-18\\\\eb4436f4ddfa0e59bf26c03183bd15eb'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-19\\\\f8c1b9e189ec749d62bee897ba91f10f'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-21\\\\ab2d84cebe8244281e0ff7d9006f0fb5'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-24\\\\b6e260c9430f266d1c8fd733c7a12c1e'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-25\\\\bd1b758761ac2ba6c943b00b5163bc2a'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-28\\\\dc365a6a3662767d6322833d515a1761'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-29\\\\67852f7ef1f65898221ce463ea2f51a2'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2024-12-30\\\\df1ffaa3e2ef4a0a56d4fddb87b02cd0'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-01\\\\914a1eedf54d698ded4481193249be40'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-02\\\\12e7c086459b3546676025cd992bc2ec'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-04\\\\9a345a95b91a71ca0d097d867aa0caf9'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-05\\\\d9662fb43eac4f6c78a99d8f18ad5615'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-06\\\\b5464b7cca316a27176c22bd43c77310'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-07\\\\ff8b06ca380223236930942f024e8c9d'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-08\\\\cd5476dfb101f204fee49eb4b3a6f009'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-09\\\\9fb60730334264b42a496aa1e050620d'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-10\\\\b26157b68926589158b98a1b0969f3f4'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-11\\\\af7aa729b49095a402cbdf11cf017297'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-12\\\\234bc64b3c483c4748f6246243614a88'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-13\\\\54748c54f681b300b70e2883fec8ea96'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-14\\\\df8c9ba68dfab8d405f80bc8dc26db88'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-15\\\\99b61d572a1be3143a067f74eff66079'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-16\\\\e1e1d6b5aaca9fba11e7bf62579a14ec'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-19\\\\f417a4934ae7a1e36f08c3d6489f17b8'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-20\\\\c8b490408b2264c6448192f12e792a75'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-21\\\\43dd07df050993853e78b63d6d956fe8'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-22\\\\7597b91f9fef0a84f0259802912723ac'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-23\\\\477ff86feb1c2850f1a24ee962f6f6ec'\n",
+ "Error: [WinError 5] Toegang geweigerd: '..\\\\laravel_app\\\\storage\\\\app\\\\citrus_brazil_trial\\\\single_images\\\\2025-01-24\\\\bee206b01468e0cc104bf60fdfe33874'\n",
+ "Emptied folder: ..\\laravel_app\\storage\\app\\citrus_brazil_trial\\single_images\n"
+ ]
+ }
+ ],
"source": [
"# List of folder names\n",
"\n",
@@ -475,16 +903,29 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 24,
"id": "0145b399-dfad-448a-9f0d-fa975fb01ad2",
"metadata": {},
- "outputs": [],
- "source": []
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 24,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "empty_folder_question"
+ ]
}
],
"metadata": {
"kernelspec": {
- "display_name": "Python 3 (ipykernel)",
+ "display_name": "base",
"language": "python",
"name": "python3"
},
@@ -498,7 +939,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.11.4"
+ "version": "3.12.3"
}
},
"nbformat": 4,
diff --git a/python_app/planet_download_with_ocm.ipynb b/python_app/planet_download_with_ocm.ipynb
new file mode 100644
index 0000000..68d1b99
--- /dev/null
+++ b/python_app/planet_download_with_ocm.ipynb
@@ -0,0 +1,1097 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "bee51aa9",
+ "metadata": {},
+ "source": [
+ "# Planet Data Download & Processing with OmniCloudMask\n",
+ "\n",
+ "This notebook extends the functionality of the original `planet_download.ipynb` by incorporating OmniCloudMask (OCM) for improved cloud and shadow detection in PlanetScope imagery. OCM is a state-of-the-art cloud masking tool that was originally trained on Sentinel-2 data but generalizes exceptionally well to PlanetScope imagery.\n",
+ "\n",
+ "## Key Features Added:\n",
+ "- OmniCloudMask integration for advanced cloud and shadow detection\n",
+ "- Comparison visualization between standard UDM masks and OCM masks\n",
+ "- Options for both local processing and direct integration with SentinelHub\n",
+ "- Support for batch processing multiple images"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6e8cbe80",
+ "metadata": {},
+ "source": [
+ "## 1. Load packages and connect to SentinelHub\n",
+ "First, we'll install required packages and import dependencies"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "88d787b3",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Standard packages from original notebook\n",
+ "import os\n",
+ "import json\n",
+ "import datetime\n",
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "from pathlib import Path\n",
+ "from osgeo import gdal\n",
+ "\n",
+ "from sentinelhub import MimeType, CRS, BBox, SentinelHubRequest, SentinelHubDownloadClient, \\\n",
+ " DataCollection, bbox_to_dimensions, DownloadRequest, SHConfig, BBoxSplitter, read_data, Geometry, SentinelHubCatalog\n",
+ "\n",
+ "import time\n",
+ "import shutil\n",
+ "import geopandas as gpd\n",
+ "from shapely.geometry import MultiLineString, MultiPolygon, Polygon, box, shape\n",
+ "\n",
+ "# Install OmniCloudMask if not present\n",
+ "# Uncomment these lines to install dependencies\n",
+ "# %pip install omnicloudmask rasterio"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "967d917d",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "OmniCloudMask successfully loaded\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Import OmniCloudMask after installation\n",
+ "try:\n",
+ " from omnicloudmask import predict_from_array, load_multiband, predict_from_load_func\n",
+ " from functools import partial\n",
+ " import rasterio as rio\n",
+ " HAS_OCM = True\n",
+ " print(\"OmniCloudMask successfully loaded\")\n",
+ "except ImportError:\n",
+ " print(\"OmniCloudMask not installed. Run the cell above to install it or install manually with pip.\")\n",
+ " HAS_OCM = False"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "39bd6361",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Configure SentinelHub connection\n",
+ "config = SHConfig()\n",
+ "config.sh_client_id = '1a72d811-4f0e-4447-8282-df09608cff44'\n",
+ "config.sh_client_secret = 'FcBlRL29i9ZmTzhmKTv1etSMFs5PxSos'\n",
+ "\n",
+ "catalog = SentinelHubCatalog(config=config)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "99f4f255",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Configure BYOC data collection\n",
+ "collection_id = 'c691479f-358c-46b1-b0f0-e12b70a9856c'\n",
+ "byoc = DataCollection.define_byoc(\n",
+ " collection_id,\n",
+ " name='planet_data2',\n",
+ " is_timeless=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "04ad9f39",
+ "metadata": {},
+ "source": [
+ "## 2. Configure project settings"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "id": "672bd92c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Project selection\n",
+ "project = 'chemba' # Change this to your project name\n",
+ "\n",
+ "# Number of days to process\n",
+ "days = 30\n",
+ "\n",
+ "# Set this to True to delete intermediate files after processing\n",
+ "empty_folder_question = True\n",
+ "\n",
+ "# Output directories setup\n",
+ "BASE_PATH = Path('../laravel_app/storage/app') / os.getenv('PROJECT_DIR', project) \n",
+ "BASE_PATH_SINGLE_IMAGES = Path(BASE_PATH / 'single_images')\n",
+ "OCM_MASKS_DIR = Path(BASE_PATH / 'ocm_masks') # Directory for OmniCloudMask results\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')\n",
+ "\n",
+ "# Create directories if they don't exist\n",
+ "for directory in [BASE_PATH_SINGLE_IMAGES, OCM_MASKS_DIR, \n",
+ " Path(folder_for_merged_tifs), Path(folder_for_virtual_raster)]:\n",
+ " directory.mkdir(exist_ok=True, parents=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a69df5ab",
+ "metadata": {},
+ "source": [
+ "## 3. Define OmniCloudMask Functions\n",
+ "\n",
+ "Here we implement the functionality to use OmniCloudMask for cloud/shadow detection"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "51f33368",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def process_with_ocm(image_path, output_dir=None, save_mask=True, resample_res=10):\n",
+ " \"\"\"\n",
+ " Process a PlanetScope image with OmniCloudMask\n",
+ " \n",
+ " Parameters:\n",
+ " -----------\n",
+ " image_path : str or Path\n",
+ " Path to the PlanetScope image (TIFF format)\n",
+ " output_dir : str or Path, optional\n",
+ " Directory to save the mask, if None, uses same directory as image\n",
+ " save_mask : bool, default=True\n",
+ " Whether to save the mask to disk\n",
+ " resample_res : int, default=10\n",
+ " Resolution in meters to resample the image to (OCM works best at 10m)\n",
+ " \n",
+ " Returns:\n",
+ " --------\n",
+ " tuple: (mask_array, profile)\n",
+ " The cloud/shadow mask as a numpy array and the rasterio profile\n",
+ " \"\"\"\n",
+ " if not HAS_OCM:\n",
+ " print(\"OmniCloudMask not available. Please install with pip install omnicloudmask\")\n",
+ " return None, None\n",
+ " \n",
+ " # Ensure image_path is a Path object\n",
+ " image_path = Path(image_path)\n",
+ " \n",
+ " # If no output directory specified, use same directory as image\n",
+ " if output_dir is None:\n",
+ " output_dir = image_path.parent\n",
+ " else:\n",
+ " output_dir = Path(output_dir)\n",
+ " output_dir.mkdir(exist_ok=True, parents=True)\n",
+ " \n",
+ " # Define output path for mask\n",
+ " mask_path = output_dir / f\"{image_path.stem}_ocm_mask.tif\"\n",
+ " \n",
+ " try:\n",
+ " # For PlanetScope 4-band images, bands are [B,G,R,NIR]\n",
+ " # We need [R,G,NIR] for OmniCloudMask in this order\n",
+ " # Set band_order=[3, 2, 4] for the standard 4-band PlanetScope imagery\n",
+ " band_order = [3, 2, 4] # For 4-band images: [R,G,NIR]\n",
+ " \n",
+ " # Load and resample image\n",
+ " print(f\"Loading image: {image_path}\")\n",
+ " rgn_data, profile = load_multiband(\n",
+ " input_path=image_path,\n",
+ " resample_res=resample_res,\n",
+ " band_order=band_order\n",
+ " )\n",
+ " \n",
+ " # Generate cloud and shadow mask\n",
+ " print(\"Applying OmniCloudMask...\")\n",
+ " prediction = predict_from_array(rgn_data)\n",
+ " \n",
+ " # Save the mask if requested\n",
+ " if save_mask:\n",
+ " profile.update(count=1, dtype='uint8')\n",
+ " with rio.open(mask_path, 'w', **profile) as dst:\n",
+ " dst.write(prediction.astype('uint8'), 1)\n",
+ " print(f\"Saved mask to: {mask_path}\")\n",
+ " \n",
+ " # Summary of detected features\n",
+ " n_total = prediction.size\n",
+ " n_clear = np.sum(prediction == 0)\n",
+ " n_thick = np.sum(prediction == 1)\n",
+ " n_thin = np.sum(prediction == 2)\n",
+ " n_shadow = np.sum(prediction == 3)\n",
+ " \n",
+ " print(f\"OCM Classification Results:\")\n",
+ " print(f\" Clear pixels: {n_clear} ({100*n_clear/n_total:.1f}%)\")\n",
+ " print(f\" Thick clouds: {n_thick} ({100*n_thick/n_total:.1f}%)\")\n",
+ " print(f\" Thin clouds: {n_thin} ({100*n_thin/n_total:.1f}%)\")\n",
+ " print(f\" Cloud shadows: {n_shadow} ({100*n_shadow/n_total:.1f}%)\")\n",
+ " \n",
+ " return prediction, profile\n",
+ " \n",
+ " except Exception as e:\n",
+ " print(f\"Error processing image with OmniCloudMask: {str(e)}\")\n",
+ " return None, None"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "bac2f620",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def apply_ocm_mask_to_image(image_path, mask_array, output_path=None):\n",
+ " \"\"\"\n",
+ " Apply an OmniCloudMask to a Planet image and save the masked version\n",
+ " \n",
+ " Parameters:\n",
+ " -----------\n",
+ " image_path : str or Path\n",
+ " Path to the input image\n",
+ " mask_array : numpy.ndarray\n",
+ " The cloud/shadow mask from OmniCloudMask\n",
+ " output_path : str or Path, optional\n",
+ " Path to save the masked image, if None, uses image_path with '_masked' suffix\n",
+ " \n",
+ " Returns:\n",
+ " --------\n",
+ " str: Path to the masked image\n",
+ " \"\"\"\n",
+ " image_path = Path(image_path)\n",
+ " \n",
+ " if output_path is None:\n",
+ " output_path = image_path.parent / f\"{image_path.stem}_masked.tif\"\n",
+ " \n",
+ " try:\n",
+ " # Open the original image\n",
+ " with rio.open(image_path) as src:\n",
+ " data = src.read()\n",
+ " profile = src.profile.copy()\n",
+ " \n",
+ " # Check dimensions match or make them match\n",
+ " if data.shape[1:] != mask_array.shape:\n",
+ " # Need to resample the mask\n",
+ " from rasterio.warp import reproject, Resampling\n",
+ " # TODO: Implement resampling if needed\n",
+ " print(\"Warning: Mask and image dimensions don't match\")\n",
+ " \n",
+ " # Create a binary mask (0 = cloud/shadow, 1 = clear)\n",
+ " # OmniCloudMask: 0=clear, 1=thick cloud, 2=thin cloud, 3=shadow\n",
+ " binary_mask = np.ones_like(mask_array)\n",
+ " binary_mask[mask_array > 0] = 0 # Set non-clear pixels to 0\n",
+ " \n",
+ " # Apply the mask to all bands\n",
+ " masked_data = data.copy()\n",
+ " for i in range(data.shape[0]):\n",
+ " # Where mask is 0, set the pixel to nodata\n",
+ " masked_data[i][binary_mask == 0] = profile.get('nodata', 0)\n",
+ " \n",
+ " # Write the masked image\n",
+ " with rio.open(output_path, 'w', **profile) as dst:\n",
+ " dst.write(masked_data)\n",
+ " \n",
+ " print(f\"Masked image saved to: {output_path}\")\n",
+ " return str(output_path)\n",
+ " \n",
+ " except Exception as e:\n",
+ " print(f\"Error applying mask to image: {str(e)}\")\n",
+ " return None"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "ad6770ac",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def process_all_images_with_ocm(directory, output_dir=None, pattern=\"*.tif\"):\n",
+ " \"\"\"\n",
+ " Process all images in a directory with OmniCloudMask\n",
+ " \n",
+ " Parameters:\n",
+ " -----------\n",
+ " directory : str or Path\n",
+ " Directory containing PlanetScope images\n",
+ " output_dir : str or Path, optional\n",
+ " Directory to save results, defaults to a subfolder of input directory\n",
+ " pattern : str, default=\"*.tif\"\n",
+ " Glob pattern to match image files\n",
+ " \n",
+ " Returns:\n",
+ " --------\n",
+ " list: Paths to processed images\n",
+ " \"\"\"\n",
+ " directory = Path(directory)\n",
+ " \n",
+ " if output_dir is None:\n",
+ " output_dir = directory / \"ocm_processed\"\n",
+ " else:\n",
+ " output_dir = Path(output_dir)\n",
+ " \n",
+ " output_dir.mkdir(exist_ok=True, parents=True)\n",
+ " \n",
+ " # Find all matching image files\n",
+ " image_files = list(directory.glob(pattern))\n",
+ " \n",
+ " if not image_files:\n",
+ " print(f\"No files matching pattern '{pattern}' found in {directory}\")\n",
+ " return []\n",
+ " \n",
+ " print(f\"Found {len(image_files)} images to process\")\n",
+ " processed_images = []\n",
+ " \n",
+ " # Process each image\n",
+ " for img_path in image_files:\n",
+ " print(f\"\\nProcessing: {img_path.name}\")\n",
+ " mask_array, profile = process_with_ocm(img_path, output_dir=output_dir)\n",
+ " \n",
+ " if mask_array is not None:\n",
+ " # Apply mask to create cloud-free image\n",
+ " output_path = output_dir / f\"{img_path.stem}_masked.tif\"\n",
+ " masked_path = apply_ocm_mask_to_image(img_path, mask_array, output_path)\n",
+ " if masked_path:\n",
+ " processed_images.append(masked_path)\n",
+ " \n",
+ " return processed_images"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "46e34d74",
+ "metadata": {},
+ "source": [
+ "## 4. Define functions from the original notebook (modified for OCM integration)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "85e07fa8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Define evalscripts (from original notebook)\n",
+ "\n",
+ "# Original evalscript without cloud/shadow detection (for comparison)\n",
+ "evalscript_original = \"\"\"\n",
+ " //VERSION=3\n",
+ " function setup() {\n",
+ " return {\n",
+ " input: [{\n",
+ " bands: [\"red\", \"green\", \"blue\", \"nir\", \"udm1\"]\n",
+ " }],\n",
+ " output: {\n",
+ " bands: 4,\n",
+ " sampleType: \"FLOAT32\"\n",
+ " }\n",
+ " };\n",
+ " }\n",
+ "\n",
+ " function evaluatePixel(sample) {\n",
+ " // Scale the bands\n",
+ " var scaledBlue = 2.5 * sample.blue / 10000;\n",
+ " var scaledGreen = 2.5 * sample.green / 10000;\n",
+ " var scaledRed = 2.5 * sample.red / 10000;\n",
+ " var scaledNIR = 2.5 * sample.nir / 10000;\n",
+ " \n",
+ " // Only use udm1 mask (Planet's usable data mask)\n",
+ " if (sample.udm1 == 0) {\n",
+ " return [scaledRed, scaledGreen, scaledBlue, scaledNIR];\n",
+ " } else {\n",
+ " return [NaN, NaN, NaN, NaN];\n",
+ " }\n",
+ " }\n",
+ "\"\"\"\n",
+ "\n",
+ "# Placeholder for code to be replaced by OCM-processed imagery later\n",
+ "evalscript_true_color = evalscript_original"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "9dee95dd",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def get_true_color_request_day(time_interval, bbox, size):\n",
+ " \"\"\"Request with original evalscript (will be replaced by OCM results later)\"\"\"\n",
+ " return SentinelHubRequest(\n",
+ " evalscript=evalscript_true_color,\n",
+ " input_data=[\n",
+ " SentinelHubRequest.input_data(\n",
+ " data_collection=DataCollection.planet_data2,\n",
+ " time_interval=(time_interval, time_interval)\n",
+ " )\n",
+ " ],\n",
+ " responses=[\n",
+ " SentinelHubRequest.output_response('default', MimeType.TIFF)\n",
+ " ],\n",
+ " bbox=bbox,\n",
+ " size=size,\n",
+ " config=config,\n",
+ " data_folder=str(BASE_PATH_SINGLE_IMAGES / time_interval),\n",
+ " )\n",
+ "\n",
+ "def get_original_request_day(time_interval, bbox, size):\n",
+ " \"\"\"Request with Planet UDM-only mask (for comparison)\"\"\"\n",
+ " return SentinelHubRequest(\n",
+ " evalscript=evalscript_original,\n",
+ " input_data=[\n",
+ " SentinelHubRequest.input_data(\n",
+ " data_collection=DataCollection.planet_data2,\n",
+ " time_interval=(time_interval, time_interval)\n",
+ " )\n",
+ " ],\n",
+ " responses=[\n",
+ " SentinelHubRequest.output_response('default', MimeType.TIFF)\n",
+ " ],\n",
+ " bbox=bbox,\n",
+ " size=size,\n",
+ " config=config,\n",
+ " )\n",
+ "\n",
+ "def download_function(slot, bbox, size):\n",
+ " \"\"\"Download imagery for a given date and bbox\"\"\"\n",
+ " list_of_requests = [get_true_color_request_day(slot, bbox, size)]\n",
+ " list_of_requests = [request.download_list[0] for request in list_of_requests]\n",
+ " data = SentinelHubDownloadClient(config=config).download(list_of_requests, max_threads=15)\n",
+ " print(f'Image downloaded for {slot} and bbox {str(bbox)}')\n",
+ " time.sleep(.1)\n",
+ " \n",
+ "def merge_files(slot):\n",
+ " \"\"\"Merge downloaded tiles into a single image\"\"\"\n",
+ " # Get all response.tiff files\n",
+ " slot_folder = Path(BASE_PATH_SINGLE_IMAGES / slot)\n",
+ " if not slot_folder.exists():\n",
+ " raise ValueError(f\"Folder not found: {slot_folder}\")\n",
+ " \n",
+ " file_list = [f\"{x}/response.tiff\" for x in slot_folder.iterdir() if Path(f\"{x}/response.tiff\").exists()]\n",
+ " \n",
+ " if not file_list:\n",
+ " raise ValueError(f\"No response.tiff files found in {slot_folder}\")\n",
+ " \n",
+ " print(f\"Found {len(file_list)} files to merge\")\n",
+ " \n",
+ " folder_for_merged_tifs = str(BASE_PATH / 'merged_tif' / f\"{slot}.tif\")\n",
+ " folder_for_virtual_raster = str(BASE_PATH / 'merged_virtual' / f\"merged{slot}.vrt\")\n",
+ " \n",
+ " # Make sure parent directories exist\n",
+ " Path(folder_for_merged_tifs).parent.mkdir(exist_ok=True, parents=True)\n",
+ " Path(folder_for_virtual_raster).parent.mkdir(exist_ok=True, parents=True)\n",
+ "\n",
+ " try:\n",
+ " # Create a virtual raster\n",
+ " print(f\"Building VRT from {len(file_list)} files\")\n",
+ " vrt_all = gdal.BuildVRT(folder_for_virtual_raster, file_list)\n",
+ " \n",
+ " if vrt_all is None:\n",
+ " raise ValueError(f\"Failed to create virtual raster: {folder_for_virtual_raster}\")\n",
+ " \n",
+ " # Write VRT to disk\n",
+ " vrt_all.FlushCache()\n",
+ " \n",
+ " # Convert to GeoTIFF\n",
+ " print(f\"Translating VRT to GeoTIFF: {folder_for_merged_tifs}\")\n",
+ " result = gdal.Translate(\n",
+ " folder_for_merged_tifs,\n",
+ " folder_for_virtual_raster,\n",
+ " xRes=10,\n",
+ " yRes=10,\n",
+ " resampleAlg=\"bilinear\" # or \"nearest\" if you prefer\n",
+ " )\n",
+ " \n",
+ " if result is None:\n",
+ " raise ValueError(f\"Failed to translate VRT to GeoTIFF: {folder_for_merged_tifs}\")\n",
+ " \n",
+ " # Make sure the file was created\n",
+ " if not Path(folder_for_merged_tifs).exists():\n",
+ " raise ValueError(f\"Output GeoTIFF file was not created: {folder_for_merged_tifs}\")\n",
+ " \n",
+ " return folder_for_merged_tifs\n",
+ " except Exception as e:\n",
+ " print(f\"Error during merging: {str(e)}\")\n",
+ " # If we have individual files but merging failed, return the first one as a fallback\n",
+ " if file_list:\n",
+ " print(f\"Returning first file as fallback: {file_list[0]}\")\n",
+ " return file_list[0]\n",
+ " raise"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d21a132b",
+ "metadata": {},
+ "source": [
+ "## 5. Setup date ranges and test data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "c00fc762",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Time windows to process:\n",
+ "\n",
+ "2025-04-17\n",
+ "2025-04-18\n",
+ "2025-04-19\n",
+ "...\n",
+ "2025-05-14\n",
+ "2025-05-15\n",
+ "2025-05-16\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Configure date ranges (from original notebook)\n",
+ "days_needed = int(os.environ.get(\"DAYS\", days))\n",
+ "date_str = os.environ.get(\"DATE\")\n",
+ "\n",
+ "if date_str:\n",
+ " end = datetime.datetime.strptime(date_str, \"%Y-%m-%d\").date()\n",
+ "else:\n",
+ " end = datetime.date.today() \n",
+ "\n",
+ "start = end - datetime.timedelta(days=days_needed - 1)\n",
+ "slots = [(start + datetime.timedelta(days=i)).strftime('%Y-%m-%d') for i in range(days_needed)]\n",
+ "\n",
+ "print('Time windows to process:\\n')\n",
+ "if len(slots) > 10:\n",
+ " for slot in slots[:3]:\n",
+ " print(slot)\n",
+ " print(\"...\")\n",
+ " for slot in slots[-3:]:\n",
+ " print(slot)\n",
+ "else:\n",
+ " for slot in slots:\n",
+ " print(slot)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "id": "8947de86",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# For testing, use a specific date with known clouds/shadows\n",
+ "# Comment this out to process all dates defined above\n",
+ "slots = ['2024-10-22'] # Change to a date with clouds/shadows in your area"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ede9e761",
+ "metadata": {},
+ "source": [
+ "## 6. Load geospatial data and prepare for processing"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "485e5fa1",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Area bounding box: BBox(((-47.09879025717693, -22.67132809994226), (-47.09188307701802, -22.66813642658124)), crs=CRS('4326'))\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Load field boundaries and prepare bounding boxes\n",
+ "geo_json = gpd.read_file(str(geojson_file))\n",
+ "geometries = [Geometry(geometry, crs=CRS.WGS84) for geometry in geo_json.geometry]\n",
+ "shapely_geometries = [geometry.geometry for geometry in geometries]\n",
+ "\n",
+ "# Split area into manageable bounding boxes\n",
+ "bbox_splitter = BBoxSplitter(\n",
+ " shapely_geometries, CRS.WGS84, (1, 1), reduce_bbox_sizes=True\n",
+ ")\n",
+ "print(\"Area bounding box:\", bbox_splitter.get_area_bbox().__repr__())\n",
+ "bbox_list = bbox_splitter.get_bbox_list()\n",
+ "info_list = bbox_splitter.get_info_list()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "0eb2ccf1",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "['2024-12-30']\n",
+ "Total slots: 1\n",
+ "Available slots: 1\n",
+ "Excluded slots due to empty dates: 0\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Function to check if images are available for each date\n",
+ "def is_image_available(date):\n",
+ " for bbox in bbox_list:\n",
+ " search_iterator = catalog.search(\n",
+ " collection=byoc,\n",
+ " bbox=bbox,\n",
+ " time=(date, date)\n",
+ " )\n",
+ " if len(list(search_iterator)) > 0:\n",
+ " return True\n",
+ " return False\n",
+ "\n",
+ "# Filter slots to only include dates with available images\n",
+ "available_slots = [slot for slot in slots if is_image_available(slot)]\n",
+ "comparison_slots = available_slots[:min(5, len(available_slots))]\n",
+ "\n",
+ "print(available_slots)\n",
+ "print(f\"Total slots: {len(slots)}\")\n",
+ "print(f\"Available slots: {len(available_slots)}\")\n",
+ "print(f\"Excluded slots due to empty dates: {len(slots) - len(available_slots)}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d628f797",
+ "metadata": {},
+ "source": [
+ "## 7. Download and process images"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "8966f944",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Downloading images for date: 2024-12-30\n",
+ " Processing bbox 1/1\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "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"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Image downloaded for 2024-12-30 and bbox -47.09879025717693,-22.67132809994226,-47.09188307701802,-22.66813642658124\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "C:\\Users\\timon\\AppData\\Local\\Temp\\ipykernel_25312\\3091203660.py:43: 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"
+ ]
+ }
+ ],
+ "source": [
+ "# Download images\n",
+ "resolution = 10 # Using 10m resolution for better OmniCloudMask results\n",
+ "\n",
+ "for slot in available_slots:\n",
+ " print(f\"\\nDownloading images for date: {slot}\")\n",
+ " \n",
+ " for i, bbox in enumerate(bbox_list):\n",
+ " bbox_obj = BBox(bbox=bbox, crs=CRS.WGS84)\n",
+ " size = bbox_to_dimensions(bbox_obj, resolution=resolution)\n",
+ " print(f\" Processing bbox {i+1}/{len(bbox_list)}\")\n",
+ " download_function(slot, bbox_obj, size)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "43a8b55e",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "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"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Image downloaded for 2024-12-30 and bbox -47.09879025717693,-22.67132809994226,-47.09188307701802,-22.66813642658124\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "C:\\Users\\timon\\AppData\\Local\\Temp\\ipykernel_25312\\3091203660.py:43: 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"
+ ]
+ }
+ ],
+ "source": [
+ "resolution = 3\n",
+ "\n",
+ "for slot in available_slots:\n",
+ " for bbox in bbox_list:\n",
+ " bbox = BBox(bbox=bbox, crs=CRS.WGS84)\n",
+ " size = bbox_to_dimensions(bbox, resolution=resolution)\n",
+ " download_function(slot, bbox, size)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "f15f04f3",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Found 2 files to merge\n",
+ "Building VRT from 2 files\n",
+ "Translating VRT to GeoTIFF: ..\\laravel_app\\storage\\app\\citrus_brazil_trial\\merged_tif\\2024-12-30.tif\n",
+ "Error during merging: Failed to translate VRT to GeoTIFF: ..\\laravel_app\\storage\\app\\citrus_brazil_trial\\merged_tif\\2024-12-30.tif\n",
+ "Returning first file as fallback: ..\\laravel_app\\storage\\app\\citrus_brazil_trial\\single_images\\2024-12-30\\0aeb88ec276c5a05278127eb769d73ec/response.tiff\n"
+ ]
+ }
+ ],
+ "source": [
+ "for slot in available_slots:\n",
+ " merge_files(slot)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ee0ae99e",
+ "metadata": {},
+ "source": [
+ "## 8. Clean up intermediate files"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "0fe25a4d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Clean up intermediate files if requested\n",
+ "folders_to_empty = [BASE_PATH / 'merged_virtual', BASE_PATH_SINGLE_IMAGES]\n",
+ "\n",
+ "def empty_folders(folders, run=True):\n",
+ " if not run:\n",
+ " print(\"Skipping empty_folders function.\")\n",
+ " return\n",
+ " \n",
+ " for folder in folders:\n",
+ " try:\n",
+ " for filename in os.listdir(folder):\n",
+ " file_path = os.path.join(folder, filename)\n",
+ " try:\n",
+ " if os.path.isfile(file_path):\n",
+ " os.unlink(file_path)\n",
+ " elif os.path.isdir(file_path):\n",
+ " shutil.rmtree(file_path)\n",
+ " except Exception as e:\n",
+ " print(f\"Error: {e}\")\n",
+ " print(f\"Emptied folder: {folder}\")\n",
+ " except OSError as e:\n",
+ " print(f\"Error: {e}\")\n",
+ "\n",
+ "# Call the function to empty folders only if requested\n",
+ "empty_folders(folders_to_empty, run=False) # Change to True if you want to clean up"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "25638297",
+ "metadata": {},
+ "source": [
+ "## 9. Visualize and compare cloud masks"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
+ "id": "7d3a73e4",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Processing ..\\laravel_app\\storage\\app\\chemba\\merged_tif\\2024-10-22.tif with c:\\\\Users\\\\timon\\\\Resilience BV\\\\4020 SCane ESA DEMO - Documenten\\\\General\\\\4020 SCDEMO Team\\\\4020 TechnicalData\\\\WP3\\\\smartcane\\\\python_app\\\\planet_ocm_processor.py...\n",
+ "Input image: ..\\laravel_app\\storage\\app\\chemba\\merged_tif\\2024-10-22.tif\n",
+ "Output directory: ..\\laravel_app\\storage\\app\\chemba\\ocm_masks\n",
+ "--- Running gdalinfo for 2024-10-22.tif ---\n",
+ "--- gdalinfo STDOUT ---\n",
+ "Driver: GTiff/GeoTIFF\n",
+ "Files: ..\\laravel_app\\storage\\app\\chemba\\merged_tif\\2024-10-22.tif\n",
+ "Size is 3605, 2162\n",
+ "Coordinate System is:\n",
+ "GEOGCRS[\"WGS 84\",\n",
+ " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n",
+ " MEMBER[\"World Geodetic System 1984 (Transit)\"],\n",
+ " MEMBER[\"World Geodetic System 1984 (G730)\"],\n",
+ " MEMBER[\"World Geodetic System 1984 (G873)\"],\n",
+ " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n",
+ " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n",
+ " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n",
+ " MEMBER[\"World Geodetic System 1984 (G2139)\"],\n",
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n",
+ " LENGTHUNIT[\"metre\",1]],\n",
+ " ENSEMBLEACCURACY[2.0]],\n",
+ " PRIMEM[\"Greenwich\",0,\n",
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n",
+ " CS[ellipsoidal,2],\n",
+ " AXIS[\"geodetic latitude (Lat)\",north,\n",
+ " ORDER[1],\n",
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n",
+ " AXIS[\"geodetic longitude (Lon)\",east,\n",
+ " ORDER[2],\n",
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n",
+ " USAGE[\n",
+ " SCOPE[\"Horizontal component of 3D system.\"],\n",
+ " AREA[\"World.\"],\n",
+ " BBOX[-90,-180,90,180]],\n",
+ " ID[\"EPSG\",4326]]\n",
+ "Data axis to CRS axis mapping: 2,1\n",
+ "Origin = (34.883117060422094,-17.291731714592061)\n",
+ "Pixel Size = (0.000027942347249,-0.000027653607237)\n",
+ "Metadata:\n",
+ " AREA_OR_POINT=Area\n",
+ "Image Structure Metadata:\n",
+ " INTERLEAVE=PIXEL\n",
+ "Corner Coordinates:\n",
+ "Upper Left ( 34.8831171, -17.2917317) ( 34d52'59.22\"E, 17d17'30.23\"S)\n",
+ "Lower Left ( 34.8831171, -17.3515188) ( 34d52'59.22\"E, 17d21' 5.47\"S)\n",
+ "Upper Right ( 34.9838492, -17.2917317) ( 34d59' 1.86\"E, 17d17'30.23\"S)\n",
+ "Lower Right ( 34.9838492, -17.3515188) ( 34d59' 1.86\"E, 17d21' 5.47\"S)\n",
+ "Center ( 34.9334831, -17.3216253) ( 34d56' 0.54\"E, 17d19'17.85\"S)\n",
+ "Band 1 Block=3605x1 Type=Byte, ColorInterp=Gray\n",
+ "Band 2 Block=3605x1 Type=Byte, ColorInterp=Undefined\n",
+ "Band 3 Block=3605x1 Type=Byte, ColorInterp=Undefined\n",
+ "Band 4 Block=3605x1 Type=Byte, ColorInterp=Undefined\n",
+ "\n",
+ "--- Attempting to run OCM processor for 2024-10-22.tif ---\n",
+ "--- Script STDOUT ---\n",
+ "--- Starting OCM processing for 2024-10-22.tif ---\n",
+ "Input 3m image (absolute): C:\\Users\\timon\\Resilience BV\\4020 SCane ESA DEMO - Documenten\\General\\4020 SCDEMO Team\\4020 TechnicalData\\WP3\\smartcane\\laravel_app\\storage\\app\\chemba\\merged_tif\\2024-10-22.tif\n",
+ "Output base directory (absolute): C:\\Users\\timon\\Resilience BV\\4020 SCane ESA DEMO - Documenten\\General\\4020 SCDEMO Team\\4020 TechnicalData\\WP3\\smartcane\\laravel_app\\storage\\app\\chemba\\ocm_masks\n",
+ "Intermediate 10m image path: C:\\Users\\timon\\Resilience BV\\4020 SCane ESA DEMO - Documenten\\General\\4020 SCDEMO Team\\4020 TechnicalData\\WP3\\smartcane\\laravel_app\\storage\\app\\chemba\\ocm_masks\\intermediate_ocm_files\\2024-10-22_10m.tif\n",
+ "Resampling C:\\Users\\timon\\Resilience BV\\4020 SCane ESA DEMO - Documenten\\General\\4020 SCDEMO Team\\4020 TechnicalData\\WP3\\smartcane\\laravel_app\\storage\\app\\chemba\\merged_tif\\2024-10-22.tif to (10, 10)m resolution -> C:\\Users\\timon\\Resilience BV\\4020 SCane ESA DEMO - Documenten\\General\\4020 SCDEMO Team\\4020 TechnicalData\\WP3\\smartcane\\laravel_app\\storage\\app\\chemba\\ocm_masks\\intermediate_ocm_files\\2024-10-22_10m.tif\n",
+ "Reprojected raster saved to: C:\\Users\\timon\\Resilience BV\\4020 SCane ESA DEMO - Documenten\\General\\4020 SCDEMO Team\\4020 TechnicalData\\WP3\\smartcane\\laravel_app\\storage\\app\\chemba\\ocm_masks\\intermediate_ocm_files\\2024-10-22_10m_reprojected.tif\n",
+ "Successfully resampled image saved to: C:\\Users\\timon\\Resilience BV\\4020 SCane ESA DEMO - Documenten\\General\\4020 SCDEMO Team\\4020 TechnicalData\\WP3\\smartcane\\laravel_app\\storage\\app\\chemba\\ocm_masks\\intermediate_ocm_files\\2024-10-22_10m.tif\n",
+ "Loading 10m image for OCM: C:\\Users\\timon\\Resilience BV\\4020 SCane ESA DEMO - Documenten\\General\\4020 SCDEMO Team\\4020 TechnicalData\\WP3\\smartcane\\laravel_app\\storage\\app\\chemba\\ocm_masks\\intermediate_ocm_files\\2024-10-22_10m.tif\n",
+ "Applying OmniCloudMask...\n",
+ "Error processing 10m image with OmniCloudMask: Source shape (1, 1, 673, 1078) is inconsistent with given indexes 1\n",
+ "OCM processing failed. Exiting.\n",
+ "\n",
+ "--- Script STDERR ---\n",
+ "c:\\Users\\timon\\anaconda3\\Lib\\site-packages\\omnicloudmask\\cloud_mask.py:145: UserWarning: Significant no-data areas detected. Adjusting patch size to 336px and overlap to 168px to minimize no-data patches.\n",
+ " warnings.warn(\n",
+ "\n",
+ "Successfully processed 2024-10-22.tif with c:\\\\Users\\\\timon\\\\Resilience BV\\\\4020 SCane ESA DEMO - Documenten\\\\General\\\\4020 SCDEMO Team\\\\4020 TechnicalData\\\\WP3\\\\smartcane\\\\python_app\\\\planet_ocm_processor.py\n",
+ "--- Script STDOUT ---\n",
+ "--- Starting OCM processing for 2024-10-22.tif ---\n",
+ "Input 3m image (absolute): C:\\Users\\timon\\Resilience BV\\4020 SCane ESA DEMO - Documenten\\General\\4020 SCDEMO Team\\4020 TechnicalData\\WP3\\smartcane\\laravel_app\\storage\\app\\chemba\\merged_tif\\2024-10-22.tif\n",
+ "Output base directory (absolute): C:\\Users\\timon\\Resilience BV\\4020 SCane ESA DEMO - Documenten\\General\\4020 SCDEMO Team\\4020 TechnicalData\\WP3\\smartcane\\laravel_app\\storage\\app\\chemba\\ocm_masks\n",
+ "Intermediate 10m image path: C:\\Users\\timon\\Resilience BV\\4020 SCane ESA DEMO - Documenten\\General\\4020 SCDEMO Team\\4020 TechnicalData\\WP3\\smartcane\\laravel_app\\storage\\app\\chemba\\ocm_masks\\intermediate_ocm_files\\2024-10-22_10m.tif\n",
+ "Resampling C:\\Users\\timon\\Resilience BV\\4020 SCane ESA DEMO - Documenten\\General\\4020 SCDEMO Team\\4020 TechnicalData\\WP3\\smartcane\\laravel_app\\storage\\app\\chemba\\merged_tif\\2024-10-22.tif to (10, 10)m resolution -> C:\\Users\\timon\\Resilience BV\\4020 SCane ESA DEMO - Documenten\\General\\4020 SCDEMO Team\\4020 TechnicalData\\WP3\\smartcane\\laravel_app\\storage\\app\\chemba\\ocm_masks\\intermediate_ocm_files\\2024-10-22_10m.tif\n",
+ "Reprojected raster saved to: C:\\Users\\timon\\Resilience BV\\4020 SCane ESA DEMO - Documenten\\General\\4020 SCDEMO Team\\4020 TechnicalData\\WP3\\smartcane\\laravel_app\\storage\\app\\chemba\\ocm_masks\\intermediate_ocm_files\\2024-10-22_10m_reprojected.tif\n",
+ "Successfully resampled image saved to: C:\\Users\\timon\\Resilience BV\\4020 SCane ESA DEMO - Documenten\\General\\4020 SCDEMO Team\\4020 TechnicalData\\WP3\\smartcane\\laravel_app\\storage\\app\\chemba\\ocm_masks\\intermediate_ocm_files\\2024-10-22_10m.tif\n",
+ "Loading 10m image for OCM: C:\\Users\\timon\\Resilience BV\\4020 SCane ESA DEMO - Documenten\\General\\4020 SCDEMO Team\\4020 TechnicalData\\WP3\\smartcane\\laravel_app\\storage\\app\\chemba\\ocm_masks\\intermediate_ocm_files\\2024-10-22_10m.tif\n",
+ "Applying OmniCloudMask...\n",
+ "Error processing 10m image with OmniCloudMask: Source shape (1, 1, 673, 1078) is inconsistent with given indexes 1\n",
+ "OCM processing failed. Exiting.\n",
+ "\n",
+ "--- Script STDERR ---\n",
+ "c:\\Users\\timon\\anaconda3\\Lib\\site-packages\\omnicloudmask\\cloud_mask.py:145: UserWarning: Significant no-data areas detected. Adjusting patch size to 336px and overlap to 168px to minimize no-data patches.\n",
+ " warnings.warn(\n",
+ "\n",
+ "Successfully processed 2024-10-22.tif with c:\\\\Users\\\\timon\\\\Resilience BV\\\\4020 SCane ESA DEMO - Documenten\\\\General\\\\4020 SCDEMO Team\\\\4020 TechnicalData\\\\WP3\\\\smartcane\\\\python_app\\\\planet_ocm_processor.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "import subprocess\n",
+ "import sys # Added for more detailed error printing\n",
+ "\n",
+ "# Path to the Python script\n",
+ "script_path = r\"c:\\\\Users\\\\timon\\\\Resilience BV\\\\4020 SCane ESA DEMO - Documenten\\\\General\\\\4020 SCDEMO Team\\\\4020 TechnicalData\\\\WP3\\\\smartcane\\\\python_app\\\\planet_ocm_processor.py\"\n",
+ "\n",
+ "# Directory containing the recently downloaded images (merged TIFFs)\n",
+ "images_dir = BASE_PATH / 'merged_tif'\n",
+ "\n",
+ "# Output directory for OCM processor (defined in cell 8)\n",
+ "# OCM_MASKS_DIR should be defined earlier in your notebook, e.g.,\n",
+ "# OCM_MASKS_DIR = Path(BASE_PATH / 'ocm_masks')\n",
+ "# OCM_MASKS_DIR.mkdir(exist_ok=True, parents=True) # Ensure it exists\n",
+ "available_slots = [\"2024-10-22\"] # Change this to the available slots you want to process\n",
+ "# Run the script for each available slot (date)\n",
+ "for slot in available_slots:\n",
+ " image_file = images_dir / f\"{slot}.tif\"\n",
+ " if image_file.exists():\n",
+ " print(f\"Processing {image_file} with {script_path}...\")\n",
+ " print(f\"Input image: {str(image_file)}\")\n",
+ " print(f\"Output directory: {str(OCM_MASKS_DIR)}\")\n",
+ " \n",
+ " try:\n",
+ " # Run gdalinfo to inspect the image before processing\n",
+ " print(f\"--- Running gdalinfo for {image_file.name} ---\")\n",
+ " gdalinfo_result = subprocess.run(\n",
+ " [\"gdalinfo\", str(image_file)],\n",
+ " capture_output=True,\n",
+ " text=True,\n",
+ " check=True\n",
+ " )\n",
+ " print(\"--- gdalinfo STDOUT ---\")\n",
+ " print(gdalinfo_result.stdout)\n",
+ " if gdalinfo_result.stderr:\n",
+ " print(\"--- gdalinfo STDERR ---\")\n",
+ " print(gdalinfo_result.stderr)\n",
+ " except subprocess.CalledProcessError as e:\n",
+ " print(f\"gdalinfo failed for {image_file.name}:\")\n",
+ " print(\"--- gdalinfo STDOUT ---\")\n",
+ " print(e.stdout)\n",
+ " print(\"--- gdalinfo STDERR ---\")\n",
+ " print(e.stderr)\n",
+ " # Decide if you want to continue to the next image or stop\n",
+ " # continue \n",
+ " except FileNotFoundError:\n",
+ " print(\"Error: gdalinfo command not found. Make sure GDAL is installed and in your system's PATH.\")\n",
+ " # Decide if you want to continue or stop\n",
+ " # break # or continue\n",
+ " \n",
+ " print(f\"--- Attempting to run OCM processor for {image_file.name} ---\")\n",
+ " try:\n",
+ " # Run the script, passing the image file and OCM_MASKS_DIR as arguments\n",
+ " process_result = subprocess.run(\n",
+ " [sys.executable, str(script_path), str(image_file), str(OCM_MASKS_DIR)], \n",
+ " capture_output=True, # Capture stdout and stderr\n",
+ " text=True, # Decode output as text\n",
+ " check=False # Do not raise an exception for non-zero exit codes, we'll check manually\n",
+ " )\n",
+ " \n",
+ " # Print the output from the script\n",
+ " print(\"--- Script STDOUT ---\")\n",
+ " print(process_result.stdout)\n",
+ " \n",
+ " if process_result.stderr:\n",
+ " print(\"--- Script STDERR ---\")\n",
+ " print(process_result.stderr)\n",
+ " \n",
+ " if process_result.returncode != 0:\n",
+ " print(f\"Error: Script {script_path} failed for {image_file.name} with exit code {process_result.returncode}\")\n",
+ " else:\n",
+ " print(f\"Successfully processed {image_file.name} with {script_path}\")\n",
+ " \n",
+ " except subprocess.CalledProcessError as e:\n",
+ " # This block will be executed if check=True and the script returns a non-zero exit code\n",
+ " print(f\"Error running script {script_path} for {image_file.name}:\")\n",
+ " print(\"--- Script STDOUT ---\")\n",
+ " print(e.stdout) # stdout from the script\n",
+ " print(\"--- Script STDERR ---\")\n",
+ " print(e.stderr) # stderr from the script (this will contain the GDAL error)\n",
+ " except Exception as e:\n",
+ " print(f\"An unexpected error occurred while trying to run {script_path} for {image_file.name}: {e}\")\n",
+ " \n",
+ " else:\n",
+ " print(f\"Image file not found: {image_file}\")\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7cb00e6a",
+ "metadata": {},
+ "source": [
+ "## 10. Understanding OmniCloudMask Results\n",
+ "\n",
+ "OmniCloudMask produces a classified raster with these values:\n",
+ "- **0 = Clear**: No clouds or shadows detected\n",
+ "- **1 = Thick Cloud**: Dense clouds that completely obscure the ground\n",
+ "- **2 = Thin Cloud**: Semi-transparent clouds or haze\n",
+ "- **3 = Shadow**: Cloud shadows on the ground\n",
+ "\n",
+ "The masked images have had all non-zero classes (clouds and shadows) removed, which provides cleaner data for analysis of crop conditions. This can significantly improve the accuracy of vegetation indices and other agricultural metrics derived from the imagery.\n",
+ "\n",
+ "For more information about OmniCloudMask, visit:\n",
+ "- GitHub repository: https://github.com/DPIRD-DMA/OmniCloudMask\n",
+ "- Paper: https://www.sciencedirect.com/science/article/pii/S0034425725000987"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2837be37",
+ "metadata": {},
+ "source": [
+ "### 9a. Upsample OCM mask to 3x3m and apply to original high-res image\n",
+ "\n",
+ "This step ensures that the OCM cloud/shadow mask (generated at 10x10m) is upsampled to match the original 3x3m PlanetScope image, so the final masked output preserves the native resolution for downstream analysis."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "base",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/python_app/planet_ocm_processor.py b/python_app/planet_ocm_processor.py
new file mode 100644
index 0000000..ed434f2
--- /dev/null
+++ b/python_app/planet_ocm_processor.py
@@ -0,0 +1,319 @@
+import os
+import argparse
+import numpy as np
+from pathlib import Path
+from osgeo import gdal
+import rasterio as rio
+from rasterio.enums import Resampling
+from rasterio.warp import reproject
+from osgeo import osr
+
+# Attempt to import OmniCloudMask and set a flag
+try:
+ from omnicloudmask import predict_from_array, load_multiband
+ HAS_OCM = True
+except ImportError:
+ HAS_OCM = False
+
+def calculate_utm_zone_and_hemisphere(longitude, latitude):
+ """
+ Calculate the UTM zone and hemisphere based on longitude and latitude.
+ """
+ utm_zone = int((longitude + 180) / 6) + 1
+ is_southern = latitude < 0
+ return utm_zone, is_southern
+
+def reproject_to_projected_crs(input_path, output_path):
+ """
+ Reprojects a raster to a projected coordinate system (e.g., UTM).
+ """
+ input_ds = gdal.Open(str(input_path))
+ if not input_ds:
+ raise ValueError(f"Failed to open input raster: {input_path}")
+
+ # Get the source spatial reference
+ source_srs = osr.SpatialReference()
+ source_srs.ImportFromWkt(input_ds.GetProjection())
+
+ # Get the geographic coordinates of the image's center
+ geo_transform = input_ds.GetGeoTransform()
+ width = input_ds.RasterXSize
+ height = input_ds.RasterYSize
+ center_x = geo_transform[0] + (width / 2) * geo_transform[1]
+ center_y = geo_transform[3] + (height / 2) * geo_transform[5]
+
+ # Calculate the UTM zone and hemisphere dynamically
+ utm_zone, is_southern = calculate_utm_zone_and_hemisphere(center_x, center_y)
+
+ # Define the target spatial reference
+ target_srs = osr.SpatialReference()
+ target_srs.SetWellKnownGeogCS("WGS84")
+ target_srs.SetUTM(utm_zone, is_southern)
+
+ # Create the warp options
+ warp_options = gdal.WarpOptions(
+ dstSRS=target_srs.ExportToWkt(),
+ format="GTiff"
+ )
+
+ # Perform the reprojection
+ gdal.Warp(str(output_path), input_ds, options=warp_options)
+ input_ds = None # Close the dataset
+ print(f"Reprojected raster saved to: {output_path}")
+ return output_path
+
+def resample_image(input_path, output_path, resolution=(10, 10), resample_alg="bilinear"):
+ """
+ Resamples a raster to a specified resolution using gdal.Translate.
+ """
+ print(f"Resampling {input_path} to {resolution}m resolution -> {output_path}")
+
+ # Reproject the input image to a projected CRS
+ reprojected_path = str(Path(output_path).with_name(f"{Path(output_path).stem}_reprojected.tif"))
+ reproject_to_projected_crs(input_path, reprojected_path)
+
+ # Open the reprojected dataset
+ input_ds = gdal.Open(reprojected_path)
+ if not input_ds:
+ raise ValueError(f"Failed to open reprojected raster: {reprojected_path}")
+
+ # Perform the resampling
+ result = gdal.Translate(
+ str(output_path),
+ input_ds,
+ xRes=resolution[0],
+ yRes=resolution[1],
+ resampleAlg=resample_alg
+ )
+ input_ds = None # Explicitly dereference the GDAL dataset
+ if result is None:
+ raise ValueError(f"Failed to resample image to {output_path}")
+ print(f"Successfully resampled image saved to: {output_path}")
+ return output_path
+
+def run_ocm_on_image(image_path_10m, ocm_output_dir, save_mask=True):
+ """
+ Processes a 10m resolution image with OmniCloudMask.
+ Adapted from process_with_ocm in the notebook.
+ """
+ if not HAS_OCM:
+ print("OmniCloudMask not available. Please install with: pip install omnicloudmask")
+ return None, None
+
+ image_path_10m = Path(image_path_10m)
+ ocm_output_dir = Path(ocm_output_dir)
+ ocm_output_dir.mkdir(exist_ok=True, parents=True)
+
+ mask_10m_path = ocm_output_dir / f"{image_path_10m.stem}_ocm_mask_10m.tif"
+
+ try:
+ # Open the image to check dimensions
+ with rio.open(image_path_10m) as src:
+ width, height = src.width, src.height
+
+ # Check if the image is too small for OmniCloudMask
+ if width < 50 or height < 50:
+ print(f"Warning: Image {image_path_10m} is too small for OmniCloudMask (width: {width}, height: {height}). Skipping.")
+ return None, None
+
+ # PlanetScope 4-band images are typically [B,G,R,NIR]
+ # OCM expects [R,G,NIR] for its default model.
+ # Band numbers for load_multiband are 1-based.
+ # If original is B(1),G(2),R(3),NIR(4), then R=3, G=2, NIR=4
+ band_order = [3, 2, 4]
+
+ print(f"Loading 10m image for OCM: {image_path_10m}")
+ # load_multiband resamples if resample_res is different from source,
+ # but here image_path_10m is already 10m.
+ # We pass resample_res=None to use the image's own resolution.
+ rgn_data, profile = load_multiband(
+ input_path=str(image_path_10m),
+ resample_res=10, # Explicitly set target resolution for OCM
+ band_order=band_order
+ )
+
+ print("Applying OmniCloudMask...")
+ prediction = predict_from_array(rgn_data)
+
+ if save_mask:
+ profile.update(count=1, dtype='uint8')
+ with rio.open(mask_10m_path, 'w', **profile) as dst:
+ dst.write(prediction.astype('uint8'), 1)
+ print(f"Saved 10m OCM mask to: {mask_10m_path}")
+
+ # Summary (optional, can be removed for cleaner script output)
+ n_total = prediction.size
+ n_clear = np.sum(prediction == 0)
+ n_thick = np.sum(prediction == 1)
+ n_thin = np.sum(prediction == 2)
+ n_shadow = np.sum(prediction == 3)
+ print(f" OCM: Clear: {100*n_clear/n_total:.1f}%, Thick: {100*n_thick/n_total:.1f}%, Thin: {100*n_thin/n_total:.1f}%, Shadow: {100*n_shadow/n_total:.1f}%")
+
+ return str(mask_10m_path), profile
+ except Exception as e:
+ print(f"Error processing 10m image with OmniCloudMask: {str(e)}")
+ return None, None
+
+
+def upsample_mask_to_3m(mask_10m_path, target_3m_image_path, output_3m_mask_path):
+ """
+ Upsamples a 10m OCM mask to match the 3m target image.
+ Adapted from upsample_mask_to_highres in the notebook.
+ """
+ print(f"Upsampling 10m mask {mask_10m_path} to 3m, referencing {target_3m_image_path}")
+ with rio.open(mask_10m_path) as src_mask, rio.open(target_3m_image_path) as src_img_3m:
+ mask_data_10m = src_mask.read(1)
+
+ img_shape_3m = (src_img_3m.height, src_img_3m.width)
+ img_transform_3m = src_img_3m.transform
+ img_crs_3m = src_img_3m.crs
+
+ upsampled_mask_3m_data = np.zeros(img_shape_3m, dtype=mask_data_10m.dtype)
+
+ reproject(
+ source=mask_data_10m,
+ destination=upsampled_mask_3m_data,
+ src_transform=src_mask.transform,
+ src_crs=src_mask.crs,
+ dst_transform=img_transform_3m,
+ dst_crs=img_crs_3m,
+ resampling=Resampling.nearest
+ )
+
+ profile_3m_mask = src_img_3m.profile.copy()
+ profile_3m_mask.update({
+ 'count': 1,
+ 'dtype': upsampled_mask_3m_data.dtype
+ })
+
+ with rio.open(output_3m_mask_path, 'w', **profile_3m_mask) as dst:
+ dst.write(upsampled_mask_3m_data, 1)
+ print(f"Upsampled 3m OCM mask saved to: {output_3m_mask_path}")
+ return str(output_3m_mask_path)
+
+
+def apply_3m_mask_to_3m_image(image_3m_path, mask_3m_path, final_masked_output_path):
+ """
+ Applies an upsampled 3m OCM mask to the original 3m image.
+ Adapted from apply_upsampled_mask_to_highres in the notebook.
+ """
+ print(f"Applying 3m mask {mask_3m_path} to 3m image {image_3m_path}")
+ image_3m_path = Path(image_3m_path)
+ mask_3m_path = Path(mask_3m_path)
+ final_masked_output_path = Path(final_masked_output_path)
+ final_masked_output_path.parent.mkdir(parents=True, exist_ok=True)
+
+ try:
+ with rio.open(image_3m_path) as src_img_3m, rio.open(mask_3m_path) as src_mask_3m:
+ img_data_3m = src_img_3m.read()
+ img_profile_3m = src_img_3m.profile.copy()
+ mask_data_3m = src_mask_3m.read(1)
+
+ if img_data_3m.shape[1:] != mask_data_3m.shape:
+ print(f"Warning: 3m image shape {img_data_3m.shape[1:]} and 3m mask shape {mask_data_3m.shape} do not match.")
+ # This should ideally not happen if upsampling was correct.
+
+ # OCM: 0=clear, 1=thick cloud, 2=thin cloud, 3=shadow
+ # We want to mask out (set to nodata) pixels where OCM is > 0
+ binary_mask = np.ones_like(mask_data_3m, dtype=np.uint8)
+ binary_mask[mask_data_3m > 0] = 0 # 0 for cloud/shadow, 1 for clear
+
+ masked_img_data_3m = img_data_3m.copy()
+ nodata_val = img_profile_3m.get('nodata', 0) # Use existing nodata or 0
+
+ for i in range(img_profile_3m['count']):
+ masked_img_data_3m[i][binary_mask == 0] = nodata_val
+
+ # Ensure dtype of profile matches data to be written
+ # If original image was float, but nodata is int (0), rasterio might complain
+ # It's safer to use the original image's dtype for the output.
+ img_profile_3m.update(dtype=img_data_3m.dtype)
+
+ with rio.open(final_masked_output_path, 'w', **img_profile_3m) as dst:
+ dst.write(masked_img_data_3m)
+
+ print(f"Final masked 3m image saved to: {final_masked_output_path}")
+ return str(final_masked_output_path)
+
+ except Exception as e:
+ print(f"Error applying 3m mask to 3m image: {str(e)}")
+ return None
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Process PlanetScope 3m imagery with OmniCloudMask.")
+ parser.add_argument("input_3m_image", type=str, help="Path to the input merged 3m PlanetScope GeoTIFF image.")
+ parser.add_argument("output_dir", type=str, help="Directory to save processed files (10m image, masks, final 3m masked image).")
+
+ args = parser.parse_args()
+
+ try:
+ # Resolve paths to absolute paths immediately
+ input_3m_path = Path(args.input_3m_image).resolve(strict=True)
+ # output_base_dir is the directory where outputs will be saved.
+ # It should exist when the script is called (created by the notebook).
+ output_base_dir = Path(args.output_dir).resolve(strict=True)
+ except FileNotFoundError as e:
+ print(f"Error: Path resolution failed. Input image or output base directory may not exist or is not accessible: {e}")
+ return
+ except Exception as e:
+ print(f"Error resolving paths: {e}")
+ return
+
+ # The check for input_3m_path.exists() is now covered by resolve(strict=True)
+
+ # Define intermediate and final file paths using absolute base paths
+ intermediate_dir = output_base_dir / "intermediate_ocm_files"
+ intermediate_dir.mkdir(parents=True, exist_ok=True)
+
+ image_10m_path = intermediate_dir / f"{input_3m_path.stem}_10m.tif"
+ # OCM mask (10m) will be saved inside run_ocm_on_image, in a subdir of intermediate_dir
+ ocm_mask_output_dir = intermediate_dir / "ocm_10m_mask_output"
+
+ # Upsampled OCM mask (3m)
+ mask_3m_upsampled_path = intermediate_dir / f"{input_3m_path.stem}_ocm_mask_3m_upsampled.tif"
+
+ # Final masked image (3m)
+ final_masked_3m_path = output_base_dir / f"{input_3m_path.stem}_ocm_masked_3m.tif"
+
+ print(f"--- Starting OCM processing for {input_3m_path.name} ---")
+ print(f"Input 3m image (absolute): {input_3m_path}")
+ print(f"Output base directory (absolute): {output_base_dir}")
+ print(f"Intermediate 10m image path: {image_10m_path}")
+
+ # 1. Resample 3m input to 10m for OCM
+ try:
+ resample_image(input_3m_path, image_10m_path, resolution=(10, 10))
+ except Exception as e:
+ print(f"Failed to resample to 10m: {e}")
+ return
+
+ # 2. Run OCM on the 10m image
+ mask_10m_generated_path, _ = run_ocm_on_image(image_10m_path, ocm_mask_output_dir)
+ if not mask_10m_generated_path:
+ print("OCM processing failed. Exiting.")
+ return
+
+ # 3. Upsample the 10m OCM mask to 3m
+ try:
+ upsample_mask_to_3m(mask_10m_generated_path, input_3m_path, mask_3m_upsampled_path)
+ except Exception as e:
+ print(f"Failed to upsample 10m OCM mask to 3m: {e}")
+ return
+
+ # 4. Apply the 3m upsampled mask to the original 3m image
+ try:
+ apply_3m_mask_to_3m_image(input_3m_path, mask_3m_upsampled_path, final_masked_3m_path)
+ except Exception as e:
+ print(f"Failed to apply 3m mask to 3m image: {e}")
+ return
+
+ print(f"--- Successfully completed OCM processing for {input_3m_path.name} ---")
+ print(f"Final 3m masked output: {final_masked_3m_path}")
+
+if __name__ == "__main__":
+ if not HAS_OCM:
+ print("OmniCloudMask library is not installed. Please install it to run this script.")
+ print("You can typically install it using: pip install omnicloudmask")
+ else:
+ main()
\ No newline at end of file
diff --git a/r_app.Rproj b/r_app.Rproj
index 8e3c2eb..28dc9cc 100644
--- a/r_app.Rproj
+++ b/r_app.Rproj
@@ -1,4 +1,5 @@
Version: 1.0
+ProjectId: c69046c6-a50f-4ab9-8f53-a06b294d469f
RestoreWorkspace: Default
SaveWorkspace: Default
diff --git a/r_app/CI_report_dashboard_planet.Rmd b/r_app/CI_report_dashboard_planet.Rmd
index 21fe4f1..9b24420 100644
--- a/r_app/CI_report_dashboard_planet.Rmd
+++ b/r_app/CI_report_dashboard_planet.Rmd
@@ -2,8 +2,8 @@
params:
ref: "word-styles-reference-var1.docx"
output_file: CI_report.docx
- report_date: "2024-08-28"
- data_dir: "Chemba"
+ report_date: "2024-07-18"
+ data_dir: "chemba"
mail_day: "Wednesday"
borders: TRUE
output:
@@ -12,266 +12,694 @@ output:
# df_print: paged
word_document:
reference_docx: !expr file.path("word-styles-reference-var1.docx")
- toc: yes
+ toc: no
editor_options:
chunk_output_type: console
---
-```{r setup, include=FALSE}
-#set de filename van de output
-# knitr::opts_chunk$set(echo = TRUE)
-# output_file <- params$output_file
+```{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
-#
-#
+
+# 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
+# # Optioneel: Herstel de omgeving als dat nodig is
+# # Je kunt dit commentaar geven als je het normaal niet wilt uitvoeren
# renv::restore()
```
-```{r libraries, message=FALSE, warning=FALSE, include=FALSE}
+```{r load_libraries, message=FALSE, warning=FALSE, include=FALSE}
+# Configure knitr options
knitr::opts_chunk$set(warning = FALSE, message = FALSE)
-library(here)
-library(sf)
-library(tidyverse)
-library(tmap)
-library(lubridate)
-library(exactextractr)
-library(zoo)
-library(raster)
-library(terra)
-library(rsample)
-library(caret)
-library(randomForest)
-library(CAST)
-source("report_utils.R")
-# source(here("r_app", "report_utils.R"))
+# 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)
+})
-```
-
-```{r directories, message=FALSE, warning=FALSE, include=FALSE}
-project_dir <- params$data_dir
-source(here("r_app", "parameters_project.R"))
-
-log_message("Starting the R Markdown script")
-log_message(paste("mail_day params:", params$mail_day))
-log_message(paste("report_date params:", params$report_date))
-log_message(paste("mail_day variable:", mail_day))
-
-# s2_dir <- "C:/Users/timon/Resilience BV/4002 CMD App - General/4002 CMD Team/4002 TechnicalData/04 WP2 technical/python/chemba_S2/"
-```
-
-
-```{r week, message=FALSE, warning=FALSE, include=FALSE}
-Sys.setlocale("LC_TIME", "C")
-today <- as.character(report_date)
-mail_day_as_character <- as.character(mail_day)
-
-report_date_as_week_day <- weekdays(ymd(today))
-
-days_of_week <- c("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")
-#als de index of report_date_as_week_day groter dan de index van de mail_day dan moet de week + 1
-week <- week(today)
-log_message(paste("week", week, "today", today))
-today_minus_1 <- as.character(ymd(today) - 7)
-today_minus_2 <- as.character(ymd(today) - 14)
-today_minus_3 <- as.character(ymd(today) - 21)
-
-log_message(paste("report_date_as_week_day", report_date_as_week_day))
-log_message(paste("which(days_of_week == report_date_as_week_day)", which(days_of_week == report_date_as_week_day)))
-log_message(paste("mail_day_as_character", mail_day_as_character))
-log_message(paste(" which(days_of_week == mail_day_as_character)", which(days_of_week == mail_day_as_character)))
-
-if (which(days_of_week == report_date_as_week_day) > which(days_of_week == mail_day_as_character)){
- log_message("adjusting weeks because of mail day")
- week <- week(today) + 1
- today_minus_1 <- as.character(ymd(today))
- today_minus_2 <- as.character(ymd(today) - 7)
- today_minus_3 <- as.character(ymd(today) - 14)
- }
-
-# week <- week(today)
-
-# week <- 25
-# today = "2024-06-21"
-
-
-#today = as.character(Sys.Date())
-#week = lubridate::week(Sys.time())
-## week = 26
-#title_var <- paste0("CI dashboard week ", week, " - all pivots dashboard using 3x3 meter resolution")
-subtitle_var <- paste("Report generated on", Sys.Date())
-
-
-week_minus_1 <- week -1 # sprintf("%02d", week(today_minus_1))
-week_minus_2 <- week -2 # sprintf("%02d", week(today_minus_2))
-week_minus_3 <- week -3 # sprintf("%02d", week(today_minus_3))
-week <- sprintf("%02d", week)
-
-
-year = year(today)
-year_1 = year(today_minus_1)
-year_2 = year(today_minus_2)
-year_3 = year(today_minus_3)
-
-```
-
-`r subtitle_var`
-
-\pagebreak
-# Explanation of the maps
-
-This PDF-dashboard shows the status of your fields on a weekly basis. We will show this in different ways:
-
-1) The first way is with a general overview of field heterogeneity using ‘variation’ – a higher number indicates more differences between plants in the same field.
-2) The second map shows a normal image of the latest week in colour, of the demo fields.
-3) Then come the maps per field, which show the status for three weeks ago, two weeks ago, last week, and this week, as well as a percentage difference map between last week and this week. The percentage difference maps shows the relative difference in growth over the last week, with positive numbers showing growth, and negative numbers showing decline.
-4) Below the maps are graphs that show how each pivot quadrant is doing, measured through the chlorophyll index.
-
-```{r data, message=TRUE, warning=TRUE, include=FALSE}
-# get latest CI index
-
-# remove_pivots <- c("1.1", "1.12", "1.8", "1.9", "1.11", "1.14")
-CI_quadrant <- readRDS(here(cumulative_CI_vals_dir,"All_pivots_Cumulative_CI_quadrant_year_v2.rds"))# %>%
- # rename(pivot_quadrant = field)
-
-
-# path_to_week_current = here(weekly_CI_mosaic, paste0("week_",week, "_", year, ".tif"))
-# path_to_week_minus_1 = here(weekly_CI_mosaic, paste0("week_",week_minus_1, "_", year_1, ".tif"))
-# path_to_week_minus_2 = here(weekly_CI_mosaic, paste0("week_",week_minus_2, "_", year_2, ".tif"))
-# path_to_week_minus_3 = here(weekly_CI_mosaic, paste0("week_",week_minus_3, "_", year_3, ".tif"))
-
-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_message("required mosaic paths")
-log_message(paste("path to week current",path_to_week_current))
-log_message(paste("path to week minus 1",path_to_week_minus_1))
-log_message(paste("path to week minus 2",path_to_week_minus_2))
-log_message(paste("path to week minus 3",path_to_week_minus_3))
-
-CI <- brick(path_to_week_current) %>% subset("CI")
-CI_m1 <- brick(path_to_week_minus_1) %>% subset("CI")
-CI_m2 <- brick(path_to_week_minus_2) %>% subset("CI")
-CI_m3 <- brick(path_to_week_minus_3) %>% subset("CI")
-
-
-# last_week_dif_raster <- ((CI - CI_m1) / CI_m1) * 100
-last_week_dif_raster_abs <- (CI - CI_m1)
-```
-```{r data_129, message=TRUE, warning=TRUE, include=FALSE}
-three_week_dif_raster_abs <- (CI - CI_m3)
-```
-```{r data_132, message=TRUE, warning=TRUE, include=FALSE}
-
-# AllPivots0 <-st_read(here(data_dir_project, "pivot.geojson"))
-
-# AllPivots0$pivot <- factor(AllPivots0$pivot, levels = c("1.1", "1.2", "1.3", "1.4", "1.6", "1.7", "1.8", "1.9", "1.10", "1.11", "1.12", "1.13", "1.14" , "1.16" , "1.17" , "1.18" ,"2.1", "2.2", "2.3" , "2.4", "2.5", "3.1", "3.2", "3.3", "4.1", "4.2", "4.3", "4.4", "4.5", "4.6", "5.1" ,"5.2", "5.3", "5.4", "6.1", "6.2", "DL1.1", "DL1.3"))
-AllPivots0 <- field_boundaries_sf
-
-# joined_spans <-st_read(here(data_dir_project, "span.geojson")) %>% st_transform(crs(AllPivots0))
-
-# pivots_dates <- readRDS(here(harvest_dir, "harvest_data_new"))
-# pivots_dates$pivot <- factor(pivots_dates$pivot, levels = c("1.1", "1.2", "1.3", "1.4", "1.6", "1.7", "1.8", "1.9", "1.10", "1.11", "1.12", "1.13", "1.14" , "1.16" , "1.17" , "1.18" ,"2.1", "2.2", "2.3" , "2.4", "2.5", "3.1", "3.2", "3.3", "4.1", "4.2", "4.3", "4.4", "4.5", "4.6", "5.1" ,"5.2", "5.3", "5.4", "6.1", "6.2", "DL1.1", "DL1.3"))
-
-#AllPivots <- merge(AllPivots0, harvesting_data, by = c("field", "sub_field")) #%>%
-#rename(field = pivot, sub_field = pivot_quadrant) #%>% select(-pivot.y)
-#head(AllPivots)
-
-#AllPivots_merged <- AllPivots %>% #dplyr::select(field, sub_field, sub_area) %>% unique() %>%
-# group_by(field) %>% summarise(sub_area = first(sub_area))
-
-#AllPivots_merged <- st_transform(AllPivots_merged, crs = proj4string(CI))
-
-#pivot_names <- unique(CI_quadrant$field)
-
-```
-
-
-```{r eval=FALSE, fig.height=7.2, fig.width=10, message=FALSE, warning=FALSE, include=FALSE}
- RGB_raster <- list.files(paste0(s2_dir,week),full.names = T, pattern = ".tiff", recursive = TRUE)[1] #use pattern = '.tif$' or something else if you have multiple files in this folder
-
-
-RGB_raster <- brick(RGB_raster)
-# RGB_raster <- brick(here("planet", paste0("week_",week, ".tif")))
-tm_shape(RGB_raster, unit = "m") + tm_rgb(r=1, g=2, b=3, max.value = 255) +
- tm_layout(main.title = paste0("RGB image of the fields - week ", week),
- main.title.position = 'center') +
- tm_scale_bar(position = c("right", "top"), text.color = "white") +
-
- tm_compass(position = c("right", "top"), text.color = "white") +
- tm_shape(AllPivots0)+ tm_borders( col = "white") +
- tm_text("pivot_quadrant", size = .6, col = "white")
-
-```
-\newpage
-
-```{r ci_overzicht_kaart, echo=FALSE, fig.height=7.3, fig.width=9, message=FALSE, warning=FALSE}
-tm_shape(CI, unit = "m")+
- tm_raster(breaks = c(0,0.5,1,2,3,4,5,6,7,Inf), palette = "RdYlGn", midpoint = NA,legend.is.portrait = F) +
- tm_layout(legend.outside = TRUE,legend.outside.position = "bottom",legend.show = T, main.title = "Overview all fields (CI)")+
- tm_scale_bar(position = c("right", "top"), text.color = "black") +
-
- tm_compass(position = c("right", "top"), text.color = "black") +
- tm_shape(AllPivots0)+ tm_borders( col = "black") +
- tm_text("sub_field", size = .6, col = "black")
-
-```
-\newpage
-
-```{r ci_diff_kaart, echo=FALSE, fig.height=7.3, fig.width=9, message=FALSE, warning=FALSE}
-
- tm_shape(last_week_dif_raster_abs, unit = "m")+
- tm_raster(breaks = c(-3,-2,-1,0,1,2, 3), palette = "RdYlGn", midpoint = NA,legend.is.portrait = F) +
- tm_layout(legend.outside = TRUE,legend.outside.position = "bottom",legend.show = T, main.title = "Overview all fields - CI difference")+
- tm_scale_bar(position = c("right", "top"), text.color = "black") +
-
- tm_compass(position = c("right", "top"), text.color = "black") +
- tm_shape(AllPivots0)+ tm_borders( col = "black") +
- tm_text("sub_field", size = .6, col = "black")
-
-```
-
-\newpage
-```{r plots_ci_estate, eval=TRUE, fig.height=3.8, fig.width=10, message=FALSE,echo=FALSE, warning=FALSE, include=TRUE, results='asis'}
-AllPivots_merged <- AllPivots0 %>% dplyr::group_by(field) %>% summarise()
-
-walk(AllPivots_merged$field, ~ {
- cat("\n") # Add an empty line for better spacing
- ci_plot(.x)
- cat("\n")
- cum_ci_plot(.x)
+# 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 looping_over_sub_area, echo=FALSE, fig.height=3.8, fig.width=10, message=FALSE, warning=FALSE, results='asis', eval=FALSE}
-pivots_grouped <- AllPivots0
+```{r initialize_project_config, message=FALSE, warning=FALSE, include=FALSE}
+# Set the project directory from parameters
+project_dir <- params$data_dir
-# Iterate over each subgroup
-for (subgroup in unique(pivots_grouped$sub_area)) {
-cat("# HELLO!!!")
-print(" PRINT")
-# cat("\n")
-# cat("# Subgroup: ", subgroup, "\n") # Add a title for the subgroup
- subset_data <- filter(pivots_grouped, sub_area == subgroup)
-# cat("\\pagebreak")
- walk(subset_data$field, ~ {
- # cat("\n") # Add an empty line for better spacing
- ci_plot(.x)
- # cat("\n")
- cum_ci_plot(.x)
- # cat("\n")
- })
+# 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)
}
-```
\ No newline at end of file
+
+# 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_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
+
+
+```
+
+
+
+**`r farm_name`**
+
+**Chlorophyll Index Analysis**
+
+Report Date: **`r report_date_formatted`**
+
+---
+
+
+
+
+
+## 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 at %H:%M")`
+
+---
+
+## 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
+
+
+
+\pagebreak
+
+
+
+
+
+\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
+# RGB Satellite Image - Current Week (if available)
+```{r render_rgb_map, echo=FALSE, fig.height=6.9, fig.width=9, 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 = c("right", "bottom"), text.color = "white") +
+ tmap::tm_compass(position = c("right", "bottom"), text.color = "white") +
+ 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")
+})
+```
+
+\pagebreak
+# Chlorophyll Index (CI) Overview Map - Current Week
+```{r render_ci_overview_map, echo=FALSE, fig.height=6.9, 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 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 = 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)
+}, 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)
+})
+```
+\pagebreak
+
+# Weekly Chlorophyll Index Difference Map
+```{r render_ci_difference_map, echo=FALSE, fig.height=6.9, 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 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 = 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)
+}, 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)
+})
+```
+\pagebreak
+
+
+```{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')
+
+ # Generate plots for each field
+ purrr::walk(AllPivots_merged$field[1:5], function(field_name) {
+ tryCatch({
+ cat("\n") # Add an empty line for better spacing
+
+ # 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,
+ 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(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()
+})
+```
+
+```{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.")
+})
+```
+
+
+
+\pagebreak
+
diff --git a/r_app/CI_report_dashboard_planet_enhanced.Rmd b/r_app/CI_report_dashboard_planet_enhanced.Rmd
new file mode 100644
index 0000000..489b4be
--- /dev/null
+++ b/r_app/CI_report_dashboard_planet_enhanced.Rmd
@@ -0,0 +1,1145 @@
+---
+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_dashboard_planet_files/figure-html/ci_diff_kaart-1.png b/r_app/CI_report_dashboard_planet_files/figure-html/ci_diff_kaart-1.png
deleted file mode 100644
index 10c8573..0000000
Binary files a/r_app/CI_report_dashboard_planet_files/figure-html/ci_diff_kaart-1.png and /dev/null differ
diff --git a/r_app/CI_report_dashboard_planet_files/figure-html/ci_overzicht_kaart-1.png b/r_app/CI_report_dashboard_planet_files/figure-html/ci_overzicht_kaart-1.png
deleted file mode 100644
index c28951a..0000000
Binary files a/r_app/CI_report_dashboard_planet_files/figure-html/ci_overzicht_kaart-1.png and /dev/null differ
diff --git a/r_app/CI_report_executive_summary.Rmd b/r_app/CI_report_executive_summary.Rmd
new file mode 100644
index 0000000..6341f6c
--- /dev/null
+++ b/r_app/CI_report_executive_summary.Rmd
@@ -0,0 +1,721 @@
+---
+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/Rplots.pdf b/r_app/Rplots.pdf
deleted file mode 100644
index 1541128..0000000
Binary files a/r_app/Rplots.pdf and /dev/null differ
diff --git a/r_app/ci_extraction.R b/r_app/ci_extraction.R
index f1b4d26..e7490e9 100644
--- a/r_app/ci_extraction.R
+++ b/r_app/ci_extraction.R
@@ -1,137 +1,117 @@
-# nolint start: commented_code_linter, line_length_linter,object_usage_linter.
-library(sf)
-library(terra)
-library(tidyverse)
-library(lubridate)
-library(exactextractr)
-library(readxl)
+# 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")
+#
-# Vang alle command line argumenten op
-args <- commandArgs(trailingOnly = TRUE)
+# 1. Load required packages
+# -----------------------
+suppressPackageStartupMessages({
+ library(sf)
+ library(terra)
+ library(tidyverse)
+ library(lubridate)
+ library(exactextractr)
+ library(readxl)
+ library(here)
+})
-# Controleer of er ten minste één argument is doorgegeven
-if (length(args) == 0) {
- stop("Geen argumenten doorgegeven aan het script")
-}
-
-# Converteer het eerste argument naar een numerieke waarde
-end_date <- as.Date(args[1])
-if (is.na(end_date)) {
- end_date <- lubridate::dmy("28-08-2024")
-}
-
-offset <- as.numeric(args[2])
-# Controleer of weeks_ago een geldig getal is
-if (is.na(offset)) {
- # stop("Het argument is geen geldig getal")
- offset <- 7
-}
-
-week <- week(end_date)
-# Converteer het tweede argument naar een string waarde
-project_dir <- as.character(args[3])
-
-# Controleer of data_dir een geldige waarde is
-if (!is.character(project_dir)) {
- project_dir <- "chemba"
-}
-new_project_question = FALSE
-
-source("parameters_project.R")
-source("ci_extraction_utils.R")
-
-dates <- date_list(end_date, offset)
-print(dates)
-
-raster_files <- list.files(planet_tif_folder,full.names = T, pattern = ".tif")
-
-filtered_files <- map(dates$days_filter, ~ raster_files[grepl(pattern = .x, x = raster_files)]) %>%
- compact() %>%
- flatten_chr()
-
- # Remove files that do not exist
- existing_files <- filtered_files[file.exists(filtered_files)]
-
- # Check if the list of existing files is empty
- if (length(existing_files) == 0) {
- message("No files exist for the given date(s).")
- stop("Terminating script.")
+# 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"
}
-
- # Continue with the rest of the script
- print(existing_files)
-
-
-vrt_list <- list()
-
-for (file in existing_files) {
- v_crop <- create_mask_and_crop(file, field_boundaries, merged_final)
- emtpy_or_full <- global(v_crop, "notNA")
-
- vrt_file <- here(daily_vrt, paste0(tools::file_path_sans_ext(basename(file)), ".vrt"))
- if(emtpy_or_full[1,] > 100){
- vrt_list[vrt_file] <- vrt_file
-
- }else{
- file.remove(vrt_file)
+
+ # 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 <- "chemba"
+ }
+
+ # Make project_dir available globally so parameters_project.R can use it
+ assign("project_dir", project_dir, envir = .GlobalEnv)
+
- message(file, " processed")
- gc()
+ # 3. Initialize project configuration
+ # --------------------------------
+ new_project_question <- FALSE
+
+ 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)
+ })
}
-raster_files_NEW <- list.files(merged_final,full.names = T, pattern = ".tif")
-
-# Define the path to the file
-file_path <- here(cumulative_CI_vals_dir, "combined_CI_data.rds")
-
-# Check if the file exists
-if (!file.exists(file_path)) {
- # File does not exist, create it with all available data
- print("combined_CI_data.rds does not exist. Preparing combined_CI_data.rds file for all available images.")
-
- # Extract data from all raster files
- walk(raster_files_NEW, extract_rasters_daily, field_geojson = field_boundaries, quadrants = FALSE, daily_CI_vals_dir)
-
- # Combine all extracted data
- extracted_values <- list.files(here(daily_CI_vals_dir), full.names = TRUE)
-
- pivot_stats <- extracted_values %>%
- map(readRDS) %>% list_rbind() %>%
- group_by(sub_field)
-
- # Save the combined data to the file
- saveRDS(pivot_stats, file_path)
-
- print("All CI values extracted from all historic images and saved to combined_CI_data.rds.")
-
-} else {
- # File exists, add new data
- print("combined_CI_data.rds exists, adding the latest image data to the table.")
-
- # Filter and process the latest data
- filtered_files <- map(dates$days_filter, ~ raster_files_NEW[grepl(pattern = .x, x = raster_files_NEW)]) %>%
- compact() %>%
- flatten_chr()
-
- walk(filtered_files, extract_rasters_daily, field_geojson = field_boundaries, quadrants = TRUE, daily_CI_vals_dir)
-
- # Extract new values
- extracted_values <- list.files(daily_CI_vals_dir, full.names = TRUE)
- extracted_values <- map(dates$days_filter, ~ extracted_values[grepl(pattern = .x, x = extracted_values)]) %>%
- compact() %>%
- flatten_chr()
-
- pivot_stats <- extracted_values %>%
- map(readRDS) %>% list_rbind() %>%
- group_by(sub_field)
-
- # Load existing data and append new data
- combined_CI_data <- readRDS(file_path)
- pivot_stats2 <- bind_rows(pivot_stats, combined_CI_data)
-
- # Save the updated combined data
- saveRDS(pivot_stats2, file_path)
-
- print("All CI values extracted from the latest images and added to combined_CI_data.rds.")
-}
+if (sys.nframe() == 0) {
+ main()
+}
\ No newline at end of file
diff --git a/r_app/ci_extraction_utils.R b/r_app/ci_extraction_utils.R
index f757480..87f8feb 100644
--- a/r_app/ci_extraction_utils.R
+++ b/r_app/ci_extraction_utils.R
@@ -1,53 +1,426 @@
-# Utils for ci extraction
-date_list <- function(end_date, offset){
- offset <- as.numeric(offset) - 1
- end_date <- as.Date(end_date)
+# CI_EXTRACTION_UTILS.R
+# =====================
+# Utility functions for the SmartCane CI (Chlorophill Index) extraction workflow.
+# These functions support date handling, raster processing, and data extraction.
+
+#' Safe logging function that works whether log_message exists or not
+#'
+#' @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)
+ }
+ }
+}
+
+#' Generate a sequence of dates for processing
+#'
+#' @param end_date The end date for the sequence (Date object)
+#' @param offset Number of days to look back from end_date
+#' @return A list containing week number, year, and a sequence of dates for filtering
+#'
+date_list <- function(end_date, offset) {
+ # Input validation
+ if (!lubridate::is.Date(end_date)) {
+ end_date <- as.Date(end_date)
+ if (is.na(end_date)) {
+ stop("Invalid end_date provided. Expected a Date object or a string convertible to Date.")
+ }
+ }
+
+ offset <- as.numeric(offset)
+ if (is.na(offset) || offset < 1) {
+ stop("Invalid offset provided. Expected a positive number.")
+ }
+
+ # Calculate date range
+ offset <- offset - 1 # Adjust offset to include end_date
start_date <- end_date - lubridate::days(offset)
- week <- week(start_date)
- year <- year(start_date)
+ # Extract week and year information
+ week <- lubridate::week(start_date)
+ year <- lubridate::year(start_date)
+
+ # Generate sequence of dates
days_filter <- seq(from = start_date, to = end_date, by = "day")
+ days_filter <- format(days_filter, "%Y-%m-%d") # Format for consistent filtering
- return(list("week" = week, "year" = year, "days_filter" = days_filter))
+ # Log the date range
+ safe_log(paste("Date range generated from", start_date, "to", end_date))
+
+ return(list(
+ "week" = week,
+ "year" = year,
+ "days_filter" = days_filter,
+ "start_date" = start_date,
+ "end_date" = end_date
+ ))
}
+#' Create a Chlorophill Index (CI) mask from satellite imagery and crop to field boundaries
+#'
+#' @param file Path to the satellite image file
+#' @param field_boundaries Field boundaries vector object
+#' @param merged_final_dir Directory to save the processed raster
+#' @return Processed raster object with CI band
+#'
create_mask_and_crop <- function(file, field_boundaries, merged_final_dir) {
- message("starting ", file)
- loaded_raster <- rast(file)
- names(loaded_raster) <- c("Red", "Green", "Blue", "NIR")
- message("raster loaded")
+ # Validate inputs
+ if (!file.exists(file)) {
+ stop(paste("File not found:", file))
+ }
- CI <- loaded_raster$NIR / loaded_raster$Green - 1
+ if (is.null(field_boundaries)) {
+ stop("Field boundaries are required but were not provided")
+ }
- loaded_raster$CI <- CI
- message("CI calculated")
- loaded_raster <- terra::crop(loaded_raster, field_boundaries, mask = TRUE) #%>% CI_func()
+ # Establish file names for output
+ basename_no_ext <- tools::file_path_sans_ext(basename(file))
+ new_file <- here::here(merged_final_dir, paste0(basename_no_ext, ".tif"))
+ vrt_file <- here::here(daily_vrt, paste0(basename_no_ext, ".vrt"))
- loaded_raster[loaded_raster == 0] <- NA
- new_file <- here(merged_final_dir, paste0(tools::file_path_sans_ext(basename(file)), ".tif"))
- writeRaster(loaded_raster, new_file, overwrite = TRUE)
-
- vrt_file <- here(daily_vrt, paste0(tools::file_path_sans_ext(basename(file)), ".vrt"))
- terra::vrt(new_file, vrt_file, overwrite = TRUE)
-
- return(loaded_raster)
+ # Process with error handling
+ tryCatch({
+ # Log processing start
+ safe_log(paste("Processing", basename(file)))
+
+ # Load and prepare raster
+ loaded_raster <- terra::rast(file)
+
+ # Validate raster has necessary bands
+ if (terra::nlyr(loaded_raster) < 4) {
+ stop("Raster must have at least 4 bands (Red, Green, Blue, NIR)")
+ }
+
+ # Name bands for clarity
+ names(loaded_raster) <- c("Red", "Green", "Blue", "NIR")
+
+ # Calculate Canopy Index
+ CI <- loaded_raster$NIR / loaded_raster$Green - 1
+
+ # Add CI to raster and mask invalid values
+ loaded_raster$CI <- CI
+ loaded_raster <- terra::crop(loaded_raster, field_boundaries, mask = TRUE)
+
+ # Replace zeros with NA for better visualization and analysis
+ loaded_raster[loaded_raster == 0] <- NA
+
+ # Write output files
+ terra::writeRaster(loaded_raster, new_file, overwrite = TRUE)
+ terra::vrt(new_file, vrt_file, overwrite = TRUE)
+
+ # Check if the result has enough valid pixels
+ valid_pixels <- terra::global(loaded_raster$CI, "notNA", na.rm=TRUE)
+
+ # Log completion
+ safe_log(paste("Completed processing", basename(file),
+ "- Valid pixels:", valid_pixels[1,]))
+
+ return(loaded_raster)
+
+ }, error = function(e) {
+ err_msg <- paste("Error processing", basename(file), "-", e$message)
+ safe_log(err_msg, "ERROR")
+ return(NULL)
+ }, finally = {
+ # Clean up memory
+ gc()
+ })
}
+#' Process a batch of satellite images and create VRT files
+#'
+#' @param files Vector of file paths to process
+#' @param field_boundaries Field boundaries vector object for cropping
+#' @param merged_final_dir Directory to save processed rasters
+#' @param daily_vrt_dir Directory to save VRT files
+#' @param min_valid_pixels Minimum number of valid pixels for a raster to be kept (default: 100)
+#' @return List of valid VRT files created
+#'
+process_satellite_images <- function(files, field_boundaries, merged_final_dir, daily_vrt_dir, min_valid_pixels = 100) {
+ vrt_list <- list()
+
+ safe_log(paste("Starting batch processing of", length(files), "files"))
+
+ # Process each file
+ for (file in files) {
+ # Process each raster file
+ v_crop <- create_mask_and_crop(file, field_boundaries, merged_final_dir)
+
+ # Skip if processing failed
+ if (is.null(v_crop)) {
+ next
+ }
+
+ # Check if the raster has enough valid data
+ valid_data <- terra::global(v_crop, "notNA")
+ vrt_file <- here::here(daily_vrt_dir, paste0(tools::file_path_sans_ext(basename(file)), ".vrt"))
+
+ if (valid_data[1,] > min_valid_pixels) {
+ vrt_list[[vrt_file]] <- vrt_file
+ } else {
+ # Remove VRT files with insufficient data
+ if (file.exists(vrt_file)) {
+ file.remove(vrt_file)
+ }
+ safe_log(paste("Skipping", basename(file), "- insufficient valid data"), "WARNING")
+ }
+
+ # Clean up memory
+ rm(v_crop)
+ gc()
+ }
+
+ safe_log(paste("Completed processing", length(vrt_list), "raster files"))
+
+ return(vrt_list)
+}
+
+#' Find satellite image files filtered by date
+#'
+#' @param tif_folder Directory containing satellite imagery files
+#' @param dates_filter Character vector of dates in YYYY-MM-DD format
+#' @return Vector of file paths matching the date filter
+#'
+find_satellite_images <- function(tif_folder, dates_filter) {
+ # Find all raster files
+ raster_files <- list.files(tif_folder, full.names = TRUE, pattern = "\\.tif$")
+
+ if (length(raster_files) == 0) {
+ stop("No raster files found in directory: ", tif_folder)
+ }
+
+ # Filter files by dates
+ filtered_files <- purrr::map(dates_filter, ~ raster_files[grepl(pattern = .x, x = raster_files)]) %>%
+ purrr::compact() %>%
+ purrr::flatten_chr()
+
+ # Remove files that do not exist
+ existing_files <- filtered_files[file.exists(filtered_files)]
+
+ # Check if the list of existing files is empty
+ if (length(existing_files) == 0) {
+ stop("No files found matching the date filter: ", paste(dates_filter, collapse = ", "))
+ }
+
+ return(existing_files)
+}
+
+#' Extract date from file path
+#'
+#' @param file_path Path to the file
+#' @return Extracted date in YYYY-MM-DD format
+#'
date_extract <- function(file_path) {
- str_extract(file_path, "\\d{4}-\\d{2}-\\d{2}")
+ date <- stringr::str_extract(file_path, "\\d{4}-\\d{2}-\\d{2}")
+
+ if (is.na(date)) {
+ warning(paste("Could not extract date from file path: ", file_path))
+ }
+
+ return(date)
}
+#' Extract CI values from a raster for each field or subfield
+#'
+#' @param file Path to the raster file
+#' @param field_geojson Field boundaries as SF object
+#' @param quadrants Boolean indicating whether to extract by quadrants
+#' @param save_dir Directory to save the extracted values
+#' @return Path to the saved RDS file
+#'
extract_rasters_daily <- function(file, field_geojson, quadrants = TRUE, save_dir) {
- field_geojson <- sf::st_as_sf(field_geojson)
- x <- rast(file[1])
+ # Validate inputs
+ if (!file.exists(file)) {
+ stop(paste("File not found: ", file))
+ }
+
+ if (!inherits(field_geojson, "sf") && !inherits(field_geojson, "sfc")) {
+ field_geojson <- sf::st_as_sf(field_geojson)
+ }
+
+ # Extract date from file path
date <- date_extract(file)
+ if (is.na(date)) {
+ stop(paste("Could not extract date from file path:", file))
+ }
- pivot_stats <- cbind(field_geojson, mean_CI = round(exactextractr::exact_extract(x$CI, field_geojson, fun = "mean"), 2)) %>%
- st_drop_geometry() %>% rename("{date}" := mean_CI)
+ # Log extraction start
+ safe_log(paste("Extracting CI values for", date, "- Using quadrants:", quadrants))
- save_suffix <- if (quadrants){"quadrant"} else {"whole_field"}
- save_path <- here(save_dir, paste0("extracted_", date, "_", save_suffix, ".rds"))
-
- saveRDS(pivot_stats, save_path)
+ # Process with error handling
+ tryCatch({
+ # Load raster
+ x <- terra::rast(file)
+
+ # Check if CI band exists
+ if (!"CI" %in% names(x)) {
+ stop("CI band not found in raster")
+ }
+
+ # Extract statistics
+ pivot_stats <- cbind(
+ field_geojson,
+ mean_CI = round(exactextractr::exact_extract(x$CI, field_geojson, fun = "mean"), 2)
+ ) %>%
+ sf::st_drop_geometry() %>%
+ dplyr::rename("{date}" := mean_CI)
+
+ # Determine save path
+ save_suffix <- if (quadrants) {"quadrant"} else {"whole_field"}
+ save_path <- here::here(save_dir, paste0("extracted_", date, "_", save_suffix, ".rds"))
+
+ # Save extracted data
+ saveRDS(pivot_stats, save_path)
+
+ # Log success
+ safe_log(paste("Successfully extracted and saved CI values for", date))
+
+ return(save_path)
+
+ }, error = function(e) {
+ err_msg <- paste("Error extracting CI values for", date, "-", e$message)
+ safe_log(err_msg, "ERROR")
+ return(NULL)
+ })
}
+#' Combine daily CI values into a single dataset
+#'
+#' @param daily_CI_vals_dir Directory containing daily CI values
+#' @param output_file Path to save the combined dataset
+#' @return Combined dataset as a tibble
+#'
+combine_ci_values <- function(daily_CI_vals_dir, output_file = NULL) {
+ # List all RDS files in the daily CI values directory
+ files <- list.files(path = daily_CI_vals_dir, pattern = "^extracted_.*\\.rds$", full.names = TRUE)
+
+ if (length(files) == 0) {
+ stop("No extracted CI values found in directory:", daily_CI_vals_dir)
+ }
+
+ # Log process start
+ safe_log(paste("Combining", length(files), "CI value files"))
+
+ # Load and combine all files
+ combined_data <- files %>%
+ purrr::map(readRDS) %>%
+ purrr::list_rbind() %>%
+ dplyr::group_by(sub_field)
+
+ # Save if output file is specified
+ if (!is.null(output_file)) {
+ saveRDS(combined_data, output_file)
+ safe_log(paste("Combined CI values saved to", output_file))
+ }
+
+ return(combined_data)
+}
+
+#' Update existing CI data with new values
+#'
+#' @param new_data New CI data to be added
+#' @param existing_data_file Path to the existing data file
+#' @return Updated combined dataset
+#'
+update_ci_data <- function(new_data, existing_data_file) {
+ if (!file.exists(existing_data_file)) {
+ warning(paste("Existing data file not found:", existing_data_file))
+ return(new_data)
+ }
+
+ # Load existing data
+ existing_data <- readRDS(existing_data_file)
+
+ # Combine data, handling duplicates by keeping the newer values
+ combined_data <- dplyr::bind_rows(new_data, existing_data) %>%
+ dplyr::distinct() %>%
+ dplyr::group_by(sub_field)
+
+ # Save updated data
+ saveRDS(combined_data, existing_data_file)
+ safe_log(paste("Updated CI data saved to", existing_data_file))
+
+ return(combined_data)
+}
+
+#' Process and combine CI values from raster files
+#'
+#' @param dates List of dates from date_list()
+#' @param field_boundaries Field boundaries as vector object
+#' @param merged_final_dir Directory with processed raster files
+#' @param field_boundaries_sf Field boundaries as SF object
+#' @param daily_CI_vals_dir Directory to save daily CI values
+#' @param cumulative_CI_vals_dir Directory to save cumulative CI values
+#' @return NULL (used for side effects)
+#'
+process_ci_values <- function(dates, field_boundaries, merged_final_dir,
+ field_boundaries_sf, daily_CI_vals_dir,
+ cumulative_CI_vals_dir) {
+ # Find processed raster files
+ raster_files <- list.files(merged_final_dir, full.names = TRUE, pattern = "\\.tif$")
+
+ # Define path for combined CI data
+ combined_ci_path <- here::here(cumulative_CI_vals_dir, "combined_CI_data.rds")
+
+ # Check if the combined CI data file exists
+ if (!file.exists(combined_ci_path)) {
+ # Process all available data if file doesn't exist
+ safe_log("combined_CI_data.rds does not exist. Creating new file with all available data.")
+
+ # Extract data from all raster files
+ purrr::walk(
+ raster_files,
+ extract_rasters_daily,
+ field_geojson = field_boundaries_sf,
+ quadrants = FALSE,
+ save_dir = daily_CI_vals_dir
+ )
+
+ # Combine all extracted data
+ pivot_stats <- combine_ci_values(daily_CI_vals_dir, combined_ci_path)
+ safe_log("All CI values extracted from historic images and saved.")
+
+ } else {
+ # Process only the latest data and add to existing file
+ safe_log("combined_CI_data.rds exists, adding the latest image data.")
+
+ # Filter files by dates
+ filtered_files <- purrr::map(dates$days_filter, ~ raster_files[grepl(pattern = .x, x = raster_files)]) %>%
+ purrr::compact() %>%
+ purrr::flatten_chr()
+
+ # Extract data for the new files
+ purrr::walk(
+ filtered_files,
+ extract_rasters_daily,
+ field_geojson = field_boundaries_sf,
+ quadrants = TRUE,
+ save_dir = daily_CI_vals_dir
+ )
+
+ # Filter extracted values files by the current date range
+ extracted_values <- list.files(daily_CI_vals_dir, full.names = TRUE)
+ extracted_values <- purrr::map(dates$days_filter, ~ extracted_values[grepl(pattern = .x, x = extracted_values)]) %>%
+ purrr::compact() %>%
+ purrr::flatten_chr()
+
+ # Combine new values
+ new_pivot_stats <- extracted_values %>%
+ purrr::map(readRDS) %>%
+ purrr::list_rbind() %>%
+ dplyr::group_by(sub_field)
+
+ # Update the combined data file
+ update_ci_data(new_pivot_stats, combined_ci_path)
+ safe_log("CI values from latest images added to combined_CI_data.rds")
+ }
+}
diff --git a/r_app/experiments/ci_extraction_and_yield_prediction.R b/r_app/experiments/ci_extraction_and_yield_prediction.R
new file mode 100644
index 0000000..83f233b
--- /dev/null
+++ b/r_app/experiments/ci_extraction_and_yield_prediction.R
@@ -0,0 +1,572 @@
+# CI_EXTRACTION_AND_YIELD_PREDICTION.R
+# =====================================
+#
+# This standalone script demonstrates:
+# 1. How Chlorophyll Index (CI) is extracted from satellite imagery
+# 2. How yield prediction is performed based on CI values
+#
+# Created for sharing with colleagues to illustrate the core functionality
+# of the SmartCane monitoring system.
+#
+
+# -----------------------------
+# PART 1: LIBRARY DEPENDENCIES
+# -----------------------------
+
+suppressPackageStartupMessages({
+ # Spatial data processing
+ library(sf)
+ library(terra)
+ library(exactextractr)
+
+ # Data manipulation
+ library(tidyverse)
+ library(lubridate)
+ library(here)
+
+ # Machine learning for yield prediction
+ library(rsample)
+ library(caret)
+ library(randomForest)
+ library(CAST)
+})
+
+# ----------------------------------
+# PART 2: LOGGING & UTILITY FUNCTIONS
+# ----------------------------------
+
+#' Safe logging function that works in any environment
+#'
+#' @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") {
+ timestamp <- format(Sys.time(), "%Y-%m-%d %H:%M:%S")
+ formatted_msg <- paste0("[", timestamp, "][", level, "] ", message)
+
+ if (level %in% c("ERROR", "WARNING")) {
+ warning(formatted_msg)
+ } else {
+ message(formatted_msg)
+ }
+}
+
+#' Generate a sequence of dates for processing
+#'
+#' @param end_date The end date for the sequence (Date object)
+#' @param offset Number of days to look back from end_date
+#' @return A list containing week number, year, and a sequence of dates for filtering
+#'
+date_list <- function(end_date, offset) {
+ # Input validation
+ if (!lubridate::is.Date(end_date)) {
+ end_date <- as.Date(end_date)
+ if (is.na(end_date)) {
+ stop("Invalid end_date provided. Expected a Date object or a string convertible to Date.")
+ }
+ }
+
+ offset <- as.numeric(offset)
+ if (is.na(offset) || offset < 1) {
+ stop("Invalid offset provided. Expected a positive number.")
+ }
+
+ # Calculate date range
+ offset <- offset - 1 # Adjust offset to include end_date
+ start_date <- end_date - lubridate::days(offset)
+
+ # Extract week and year information
+ week <- lubridate::week(start_date)
+ year <- lubridate::year(start_date)
+
+ # Generate sequence of dates
+ days_filter <- seq(from = start_date, to = end_date, by = "day")
+ days_filter <- format(days_filter, "%Y-%m-%d") # Format for consistent filtering
+
+ # Log the date range
+ safe_log(paste("Date range generated from", start_date, "to", end_date))
+
+ return(list(
+ "week" = week,
+ "year" = year,
+ "days_filter" = days_filter,
+ "start_date" = start_date,
+ "end_date" = end_date
+ ))
+}
+
+# -----------------------------
+# PART 3: CI EXTRACTION PROCESS
+# -----------------------------
+
+#' Find satellite imagery files within a specific date range
+#'
+#' @param image_folder Path to the folder containing satellite images
+#' @param date_filter Vector of dates to filter by (in YYYY-MM-DD format)
+#' @return Vector of file paths matching the date filter
+#'
+find_satellite_images <- function(image_folder, date_filter) {
+ # Validate inputs
+ if (!dir.exists(image_folder)) {
+ stop(paste("Image folder not found:", image_folder))
+ }
+
+ # List all files in the directory
+ all_files <- list.files(image_folder, pattern = "\\.tif$", full.names = TRUE, recursive = TRUE)
+
+ if (length(all_files) == 0) {
+ safe_log("No TIF files found in the specified directory", "WARNING")
+ return(character(0))
+ }
+
+ # Filter files by date pattern in filename
+ filtered_files <- character(0)
+
+ for (date in date_filter) {
+ # Format date for matching (remove dashes)
+ date_pattern <- gsub("-", "", date)
+
+ # Find files with matching date pattern
+ matching_files <- all_files[grepl(date_pattern, all_files)]
+
+ if (length(matching_files) > 0) {
+ filtered_files <- c(filtered_files, matching_files)
+ safe_log(paste("Found", length(matching_files), "files for date", date))
+ }
+ }
+
+ return(filtered_files)
+}
+
+#' Create a Chlorophyll Index (CI) from satellite imagery
+#'
+#' @param raster_obj A SpatRaster object with Red, Green, Blue, and NIR bands
+#' @return A SpatRaster object with a CI band
+#'
+calculate_ci <- function(raster_obj) {
+ # Validate input has required bands
+ if (terra::nlyr(raster_obj) < 4) {
+ stop("Raster must have at least 4 bands (Red, Green, Blue, NIR)")
+ }
+
+ # Extract bands (assuming standard order: B, G, R, NIR)
+ blue_band <- raster_obj[[1]]
+ green_band <- raster_obj[[2]]
+ red_band <- raster_obj[[3]]
+ nir_band <- raster_obj[[4]]
+
+ # CI formula: (NIR / Red) - 1
+ # This highlights chlorophyll content in vegetation
+ ci_raster <- (nir_band / red_band) - 1
+
+ # Filter extreme values that may result from division operations
+ ci_raster[ci_raster > 10] <- 10 # Cap max value
+ ci_raster[ci_raster < 0] <- 0 # Cap min value
+
+ # Name the layer
+ names(ci_raster) <- "CI"
+
+ return(ci_raster)
+}
+
+#' Create a mask for cloudy pixels and shadows using thresholds
+#'
+#' @param raster_obj A SpatRaster object with multiple bands
+#' @return A binary mask where 1=clear pixel, 0=cloudy or shadow pixel
+#'
+create_cloud_mask <- function(raster_obj) {
+ # Extract bands
+ blue_band <- raster_obj[[1]]
+ green_band <- raster_obj[[2]]
+ red_band <- raster_obj[[3]]
+ nir_band <- raster_obj[[4]]
+
+ # Create initial mask (all pixels valid)
+ mask <- blue_band * 0 + 1
+
+ # Calculate indices used for detection
+ ndvi <- (nir_band - red_band) / (nir_band + red_band)
+ brightness <- (blue_band + green_band + red_band) / 3
+
+ # CLOUD DETECTION CRITERIA
+ # ------------------------
+ # Clouds are typically very bright in all bands
+ bright_pixels <- (blue_band > 0.3) & (green_band > 0.3) & (red_band > 0.3)
+
+ # Snow/high reflectance clouds have high blue values
+ blue_dominant <- blue_band > (red_band * 1.2)
+
+ # Low NDVI areas that are bright are likely clouds
+ low_ndvi <- ndvi < 0.1
+
+ # Combine cloud criteria
+ cloud_pixels <- bright_pixels & (blue_dominant | low_ndvi)
+
+ # SHADOW DETECTION CRITERIA
+ # ------------------------
+ # Shadows typically have:
+ # 1. Low overall brightness across all bands
+ # 2. Lower NIR reflectance
+ # 3. Can still have reasonable NDVI (if over vegetation)
+
+ # Dark pixels in visible spectrum
+ dark_pixels <- brightness < 0.1
+
+ # Low NIR reflectance
+ low_nir <- nir_band < 0.15
+
+ # Shadows often have higher blue proportion relative to NIR
+ blue_nir_ratio <- blue_band / (nir_band + 0.01) # Add small constant to avoid division by zero
+ blue_enhanced <- blue_nir_ratio > 0.8
+
+ # Combine shadow criteria
+ shadow_pixels <- dark_pixels & (low_nir | blue_enhanced)
+
+ # Update mask (0 for cloud or shadow pixels)
+ mask[cloud_pixels | shadow_pixels] <- 0
+
+ # Optional: create different values for clouds vs shadows for visualization
+ # mask[cloud_pixels] <- 0 # Clouds
+ # mask[shadow_pixels] <- 0 # Shadows
+
+ return(mask)
+}
+
+#' Process satellite image, calculate CI, and crop to field boundaries
+#'
+#' @param file Path to the satellite image file
+#' @param field_boundaries Field boundaries vector object
+#' @param output_dir Directory to save the processed raster
+#' @return Path to the processed raster file
+#'
+process_satellite_image <- function(file, field_boundaries, output_dir) {
+ # Validate inputs
+ if (!file.exists(file)) {
+ stop(paste("File not found:", file))
+ }
+
+ if (is.null(field_boundaries)) {
+ stop("Field boundaries are required but were not provided")
+ }
+
+ # Create output filename
+ basename_no_ext <- tools::file_path_sans_ext(basename(file))
+ output_file <- here::here(output_dir, paste0(basename_no_ext, "_CI.tif"))
+
+ # Process with error handling
+ tryCatch({
+ # Load and prepare raster
+ loaded_raster <- terra::rast(file)
+
+ # Calculate CI
+ ci_raster <- calculate_ci(loaded_raster)
+
+ # Create cloud mask
+ cloud_mask <- create_cloud_mask(loaded_raster)
+
+ # Apply cloud mask to CI
+ ci_masked <- ci_raster * cloud_mask
+
+ # Crop to field boundaries extent (for efficiency)
+ field_extent <- terra::ext(field_boundaries)
+ ci_cropped <- terra::crop(ci_masked, field_extent)
+
+ # Write output
+ terra::writeRaster(ci_cropped, output_file, overwrite = TRUE)
+
+ safe_log(paste("Successfully processed", basename(file)))
+
+ return(output_file)
+
+ }, error = function(e) {
+ safe_log(paste("Error processing", basename(file), ":", e$message), "ERROR")
+ return(NULL)
+ })
+}
+
+#' Extract CI statistics for each field
+#'
+#' @param ci_raster A SpatRaster with CI values
+#' @param field_boundaries An sf object with field polygons
+#' @return A data frame with CI statistics by field
+#'
+extract_ci_by_field <- function(ci_raster, field_boundaries) {
+ # Validate inputs
+ if (is.null(ci_raster)) {
+ stop("CI raster is required but was NULL")
+ }
+
+ if (is.null(field_boundaries) || nrow(field_boundaries) == 0) {
+ stop("Field boundaries are required but were empty")
+ }
+
+ # Extract statistics using exact extraction (weighted by coverage)
+ ci_stats <- exactextractr::exact_extract(
+ ci_raster,
+ field_boundaries,
+ fun = c("mean", "median", "min", "max", "stdev", "count"),
+ progress = FALSE
+ )
+
+ # Add field identifiers
+ ci_stats$field <- field_boundaries$field
+ if ("sub_field" %in% names(field_boundaries)) {
+ ci_stats$sub_field <- field_boundaries$sub_field
+ } else {
+ ci_stats$sub_field <- field_boundaries$field
+ }
+
+ # Add date info
+ ci_stats$date <- Sys.Date()
+
+ # Clean up names
+ names(ci_stats) <- gsub("CI\\.", "", names(ci_stats))
+
+ return(ci_stats)
+}
+
+# -----------------------------------------
+# PART 4: YIELD PREDICTION IMPLEMENTATION
+# -----------------------------------------
+
+#' Prepare data for yield prediction model
+#'
+#' @param ci_data Data frame with cumulative CI values
+#' @param harvest_data Data frame with harvest information
+#' @return Data frame ready for modeling
+#'
+prepare_yield_prediction_data <- function(ci_data, harvest_data) {
+ # Join CI and yield data
+ ci_and_yield <- dplyr::left_join(ci_data, harvest_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)
+
+ # Split into training and prediction sets
+ ci_and_yield_train <- ci_and_yield %>%
+ as.data.frame() %>%
+ dplyr::filter(!is.na(tonnage_ha))
+
+ prediction_yields <- ci_and_yield %>%
+ as.data.frame() %>%
+ dplyr::filter(is.na(tonnage_ha))
+
+ return(list(
+ train = ci_and_yield_train,
+ predict = prediction_yields
+ ))
+}
+
+#' Train a random forest model for yield prediction
+#'
+#' @param training_data Data frame with training data
+#' @param predictors Vector of predictor variable names
+#' @param response Name of the response variable
+#' @return Trained model
+#'
+train_yield_model <- function(training_data, predictors = c("cumulative_CI", "DOY", "CI_per_day"), response = "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(
+ training_data[, predictors],
+ training_data[, response],
+ method = "rf",
+ trControl = ctrl,
+ importance = TRUE,
+ withinSE = TRUE,
+ tuneLength = 5,
+ na.rm = TRUE
+ )
+
+ return(model_ffs_rf)
+}
+
+#' Format predictions into a clean data frame
+#'
+#' @param predictions Raw prediction results
+#' @param newdata Original data frame with field information
+#' @return Formatted predictions data frame
+#'
+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 mature fields
+#'
+#' @param model Trained model
+#' @param prediction_data Data frame with fields to predict
+#' @param min_age Minimum age in days to qualify as mature (default: 300)
+#' @return Data frame with yield predictions
+#'
+predict_yields <- function(model, prediction_data, min_age = 300) {
+ # Make predictions
+ predictions <- stats::predict(model, newdata = prediction_data)
+
+ # Format predictions
+ pred_formatted <- prepare_predictions(predictions, prediction_data) %>%
+ dplyr::filter(Age_days > min_age) %>%
+ dplyr::mutate(CI_per_day = round(total_CI / Age_days, 1))
+
+ return(pred_formatted)
+}
+
+# ------------------------------
+# PART 5: DEMONSTRATION WORKFLOW
+# ------------------------------
+
+#' Demonstration workflow showing how to use the functions
+#'
+#' @param end_date The end date for processing satellite images
+#' @param offset Number of days to look back
+#' @param image_folder Path to the folder containing satellite images
+#' @param field_boundaries_path Path to field boundaries shapefile
+#' @param output_dir Path to save processed outputs
+#' @param harvest_data_path Path to historical harvest data
+#'
+demo_workflow <- function(end_date = Sys.Date(), offset = 7,
+ image_folder = "path/to/satellite/images",
+ field_boundaries_path = "path/to/field_boundaries.shp",
+ output_dir = "path/to/output",
+ harvest_data_path = "path/to/harvest_data.csv") {
+
+ # Step 1: Generate date list for processing
+ dates <- date_list(end_date, offset)
+ safe_log(paste("Processing data for week", dates$week, "of", dates$year))
+
+ # Step 2: Load field boundaries
+ field_boundaries <- sf::read_sf(field_boundaries_path)
+ safe_log(paste("Loaded", nrow(field_boundaries), "field boundaries"))
+
+ # Step 3: Find satellite images for the specified date range
+ image_files <- find_satellite_images(image_folder, dates$days_filter)
+ safe_log(paste("Found", length(image_files), "satellite images for processing"))
+
+ # Step 4: Process each satellite image and calculate CI
+ ci_files <- list()
+ for (file in image_files) {
+ ci_file <- process_satellite_image(file, field_boundaries, output_dir)
+ if (!is.null(ci_file)) {
+ ci_files <- c(ci_files, ci_file)
+ }
+ }
+
+ # Step 5: Extract CI statistics for each field
+ ci_stats_list <- list()
+ for (ci_file in ci_files) {
+ ci_raster <- terra::rast(ci_file)
+ ci_stats <- extract_ci_by_field(ci_raster, field_boundaries)
+ ci_stats_list[[basename(ci_file)]] <- ci_stats
+ }
+
+ # Combine all stats
+ all_ci_stats <- dplyr::bind_rows(ci_stats_list)
+ safe_log(paste("Extracted CI statistics for", nrow(all_ci_stats), "field-date combinations"))
+
+ # Step 6: Prepare for yield prediction
+ if (file.exists(harvest_data_path)) {
+ # Load harvest data
+ harvest_data <- read.csv(harvest_data_path)
+ safe_log("Loaded harvest data for yield prediction")
+
+ # Make up cumulative_CI data for demonstration purposes
+ # In a real scenario, this would come from accumulating CI values over time
+ ci_data <- all_ci_stats %>%
+ dplyr::group_by(field, sub_field) %>%
+ dplyr::summarise(
+ cumulative_CI = sum(mean, na.rm = TRUE),
+ DOY = n(), # Days of year as the count of observations
+ season = lubridate::year(max(date, na.rm = TRUE)),
+ .groups = "drop"
+ )
+
+ # Prepare data for modeling
+ modeling_data <- prepare_yield_prediction_data(ci_data, harvest_data)
+
+ if (nrow(modeling_data$train) > 0) {
+ # Train yield prediction model
+ yield_model <- train_yield_model(modeling_data$train)
+ safe_log("Trained yield prediction model")
+
+ # Predict yields for mature fields
+ yield_predictions <- predict_yields(yield_model, modeling_data$predict)
+ safe_log(paste("Generated yield predictions for", nrow(yield_predictions), "fields"))
+
+ # Return results
+ return(list(
+ ci_stats = all_ci_stats,
+ yield_predictions = yield_predictions,
+ model = yield_model
+ ))
+ } else {
+ safe_log("No training data available for yield prediction", "WARNING")
+ return(list(ci_stats = all_ci_stats))
+ }
+ } else {
+ safe_log("Harvest data not found, skipping yield prediction", "WARNING")
+ return(list(ci_stats = all_ci_stats))
+ }
+}
+
+# ------------------------------
+# PART 6: USAGE EXAMPLE
+# ------------------------------
+
+# Uncomment and modify paths to run the demo workflow
+# results <- demo_workflow(
+# end_date = "2023-10-01",
+# offset = 7,
+# image_folder = "data/satellite_images",
+# field_boundaries_path = "data/field_boundaries.shp",
+# output_dir = "output/processed",
+# harvest_data_path = "data/harvest_history.csv"
+# )
+#
+# # Access results
+# ci_stats <- results$ci_stats
+# yield_predictions <- results$yield_predictions
+#
+# # Example: Plot CI distribution by field
+# if (require(ggplot2)) {
+# ggplot(ci_stats, aes(x = field, y = mean, fill = field)) +
+# geom_boxplot() +
+# labs(title = "CI Distribution by Field",
+# x = "Field",
+# y = "Mean CI") +
+# theme_minimal() +
+# theme(axis.text.x = element_text(angle = 45, hjust = 1))
+# }
+#
+# # Example: Plot predicted yield vs age
+# if (exists("yield_predictions") && require(ggplot2)) {
+# ggplot(yield_predictions, aes(x = Age_days, y = predicted_Tcha, color = field)) +
+# geom_point(size = 3) +
+# geom_text(aes(label = field), hjust = -0.2, vjust = -0.2) +
+# labs(title = "Predicted Yield by Field Age",
+# x = "Age (Days)",
+# y = "Predicted Yield (Tonnes/ha)") +
+# theme_minimal()
+# }
diff --git a/r_app/counting_clouds.R b/r_app/experiments/counting_clouds.R
similarity index 100%
rename from r_app/counting_clouds.R
rename to r_app/experiments/counting_clouds.R
diff --git a/r_app/experiments/delete_cloud_exploratoin b/r_app/experiments/delete_cloud_exploratoin
new file mode 100644
index 0000000..2cf9a19
--- /dev/null
+++ b/r_app/experiments/delete_cloud_exploratoin
@@ -0,0 +1,556 @@
+# Cloud and Shadow Detection Analysis
+# This script analyzes cloud and shadow detection parameters using the diagnostic GeoTIFF files
+# and polygon-based classification to help optimize the detection algorithms
+
+# Load required packages
+library(terra)
+library(sf)
+library(dplyr)
+library(ggplot2)
+library(reshape2)
+library(exactextractr) # For accurate polygon extraction
+
+# Define diagnostic directory
+diagnostic_dir <- "C:/Users/timon/Resilience BV/4020 SCane ESA DEMO - Documenten/General/4020 SCDEMO Team/4020 TechnicalData/WP3/smartcane/cloud_mask_diagnostics_20250515-164357"
+
+# Simple logging function for this standalone script
+safe_log <- function(message, level = "INFO") {
+ cat(paste0("[", level, "] ", message, "\n"))
+}
+
+safe_log("Starting cloud detection analysis on diagnostic rasters")
+
+# Load all diagnostic rasters
+safe_log("Loading diagnostic raster files...")
+
+
+# Load original bands
+red_band <- terra::rast(file.path(diagnostic_dir, "diagnostic_red_band.tif"))
+green_band <- terra::rast(file.path(diagnostic_dir, "diagnostic_green_band.tif"))
+blue_band <- terra::rast(file.path(diagnostic_dir, "diagnostic_blue_band.tif"))
+nir_band <- terra::rast(file.path(diagnostic_dir, "diagnostic_nir_band.tif"))
+
+# Load derived indices
+brightness <- terra::rast(file.path(diagnostic_dir, "diagnostic_brightness.tif"))
+ndvi <- terra::rast(file.path(diagnostic_dir, "diagnostic_ndvi.tif"))
+blue_ratio <- terra::rast(file.path(diagnostic_dir, "diagnostic_blue_ratio.tif"))
+green_nir_ratio <- terra::rast(file.path(diagnostic_dir, "diagnostic_green_nir_ratio.tif"))
+ndwi <- terra::rast(file.path(diagnostic_dir, "diagnostic_ndwi.tif"))
+
+# Load cloud detection parameters
+bright_pixels <- terra::rast(file.path(diagnostic_dir, "param_bright_pixels.tif"))
+very_bright_pixels <- terra::rast(file.path(diagnostic_dir, "param_very_bright_pixels.tif"))
+blue_dominant <- terra::rast(file.path(diagnostic_dir, "param_blue_dominant.tif"))
+low_ndvi <- terra::rast(file.path(diagnostic_dir, "param_low_ndvi.tif"))
+green_dominant_nir <- terra::rast(file.path(diagnostic_dir, "param_green_dominant_nir.tif"))
+high_ndwi <- terra::rast(file.path(diagnostic_dir, "param_high_ndwi.tif"))
+
+# Load shadow detection parameters
+dark_pixels <- terra::rast(file.path(diagnostic_dir, "param_dark_pixels.tif"))
+very_dark_pixels <- terra::rast(file.path(diagnostic_dir, "param_very_dark_pixels.tif"))
+low_nir <- terra::rast(file.path(diagnostic_dir, "param_low_nir.tif"))
+shadow_ndvi <- terra::rast(file.path(diagnostic_dir, "param_shadow_ndvi.tif"))
+low_red_to_blue <- terra::rast(file.path(diagnostic_dir, "param_low_red_to_blue.tif"))
+high_blue_to_nir_ratio <- terra::rast(file.path(diagnostic_dir, "param_high_blue_to_nir_ratio.tif"))
+blue_nir_ratio_raw <- terra::rast(file.path(diagnostic_dir, "param_blue_nir_ratio_raw.tif"))
+red_blue_ratio_raw <- terra::rast(file.path(diagnostic_dir, "param_red_blue_ratio_raw.tif"))
+
+# Load edge detection parameters
+brightness_focal_sd <- terra::rast(file.path(diagnostic_dir, "param_brightness_focal_sd.tif"))
+edge_pixels <- terra::rast(file.path(diagnostic_dir, "param_edge_pixels.tif"))
+
+# Load final masks
+cloud_mask <- terra::rast(file.path(diagnostic_dir, "mask_cloud.tif"))
+shadow_mask <- terra::rast(file.path(diagnostic_dir, "mask_shadow.tif"))
+combined_mask <- terra::rast(file.path(diagnostic_dir, "mask_combined.tif"))
+dilated_mask <- terra::rast(file.path(diagnostic_dir, "mask_dilated.tif"))
+
+safe_log("Raster data loaded successfully")
+
+# Try to read the classification polygons if they exist
+tryCatch({
+ # Check if the classes.geojson file exists in the diagnostic directory
+ classes_file <- file.path(diagnostic_dir, "classes.geojson")
+
+ # If no classes file in this directory, look for the most recent one
+ if (!file.exists(classes_file)) {
+ # Look in parent directory for most recent cloud_mask_diagnostics folder
+ potential_dirs <- list.dirs(path = dirname(diagnostic_dir),
+ full.names = TRUE,
+ recursive = FALSE)
+
+ # Filter for diagnostic directories and find the most recent one that has classes.geojson
+ diagnostic_dirs <- potential_dirs[grepl("cloud_mask_diagnostics_", potential_dirs)]
+
+ for (dir in rev(sort(diagnostic_dirs))) { # Reverse sort to get newest first
+ potential_file <- file.path(dir, "classes.geojson")
+ if (file.exists(potential_file)) {
+ classes_file <- potential_file
+ break
+ }
+ }
+ }
+
+ # Check if we found a classes file
+ if (file.exists(classes_file)) {
+ safe_log(paste("Using classification polygons from:", classes_file))
+
+ # Load the classification polygons
+ classifications <- sf::st_read(classes_file, quiet = TRUE) %>% rename(class = type)
+ # Remove empty polygons
+ classifications <- classifications[!sf::st_is_empty(classifications), ]
+
+ # Create a list to store all rasters we want to extract values from
+ extraction_rasters <- list(
+ # Original bands
+ red = red_band,
+ green = green_band,
+ blue = blue_band,
+ nir = nir_band,
+
+ # Derived indices
+ brightness = brightness,
+ ndvi = ndvi,
+ blue_ratio = blue_ratio,
+ green_nir_ratio = green_nir_ratio,
+ ndwi = ndwi,
+
+ # Cloud detection parameters
+ bright_pixels = terra::ifel(bright_pixels, 1, 0),
+ very_bright_pixels = terra::ifel(very_bright_pixels, 1, 0),
+ blue_dominant = terra::ifel(blue_dominant, 1, 0),
+ low_ndvi = terra::ifel(low_ndvi, 1, 0),
+ green_dominant_nir = terra::ifel(green_dominant_nir, 1, 0),
+ high_ndwi = terra::ifel(high_ndwi, 1, 0),
+
+ # Shadow detection parameters
+ dark_pixels = terra::ifel(dark_pixels, 1, 0),
+ very_dark_pixels = terra::ifel(very_dark_pixels, 1, 0),
+ low_nir = terra::ifel(low_nir, 1, 0),
+ shadow_ndvi = terra::ifel(shadow_ndvi, 1, 0),
+ low_red_to_blue = terra::ifel(low_red_to_blue, 1, 0),
+ high_blue_to_nir_ratio = terra::ifel(high_blue_to_nir_ratio, 1, 0),
+ blue_nir_ratio_raw = (blue_band / (nir_band + 0.01)),
+ red_blue_ratio_raw = (red_band / (blue_band + 0.01)),
+
+ # Edge detection parameters
+ brightness_focal_sd = brightness_focal_sd,
+ edge_pixels = terra::ifel(edge_pixels, 1, 0),
+
+ # Final masks
+ cloud_mask = terra::ifel(cloud_mask, 1, 0),
+ shadow_mask = terra::ifel(shadow_mask, 1, 0),
+ combined_mask = terra::ifel(combined_mask, 1, 0),
+ dilated_mask = terra::ifel(dilated_mask, 1, 0)
+ )
+
+ # Create a stack of all rasters
+ extraction_stack <- terra::rast(extraction_rasters)
+
+ # User-provided simplified extraction for mean statistics per polygon
+ pivot_stats_sf <- cbind(
+ classifications,
+ round(exactextractr::exact_extract(extraction_stack, classifications, fun = "mean", progress = FALSE), 2)
+ ) %>%
+ sf::st_drop_geometry()
+
+ # Convert to a regular data frame for easier downstream processing
+ all_stats <- sf::st_drop_geometry(pivot_stats_sf)
+
+ # Ensure 'class_name' column exists, if not, use 'class' as 'class_name'
+ if (!("class_name" %in% colnames(all_stats)) && ("class" %in% colnames(all_stats))) {
+ all_stats$class_name <- all_stats$class
+
+ if (length(valid_class_ids) == 0) {
+ safe_log("No valid (non-NA) class IDs found for exactextractr processing.", "WARNING")
+ }
+
+ for (class_id in valid_class_ids) {
+ # Subset polygons for this class
+ class_polygons_sf <- classifications[which(classifications$class == class_id), ] # Use which for NA-safe subsetting
+
+ if (nrow(class_polygons_sf) == 0) {
+ safe_log(paste("Skipping empty class (no polygons after filtering):", class_id), "WARNING")
+ next
+ }
+
+ tryCatch({
+ safe_log(paste("Processing class:", class_id))
+
+ # Check if the polygon overlaps with the raster extent (check based on the combined extent of class polygons)
+ rast_extent <- terra::ext(extraction_stack)
+ poly_extent <- sf::st_bbox(class_polygons_sf)
+
+ if (poly_extent["xmin"] > rast_extent["xmax"] ||
+ poly_extent["xmax"] < rast_extent["xmin"] ||
+ poly_extent["ymin"] > rast_extent["ymax"] ||
+ poly_extent["ymax"] < rast_extent["ymin"]) {
+ safe_log(paste("Skipping class that doesn't overlap with raster:", class_id), "WARNING")
+ next
+ }
+
+ # exact_extract will process each feature in class_polygons_sf
+ # and return a list of data frames (one per feature)
+ per_polygon_stats_list <- exactextractr::exact_extract(
+ extraction_stack,
+ class_polygons_sf,
+ function(values, coverage_fraction) {
+ # Filter pixels by coverage (e.g., >50% of the pixel is covered by the polygon)
+ valid_pixels_idx <- coverage_fraction > 0.5
+ df_filtered <- values[valid_pixels_idx, , drop = FALSE]
+
+ if (nrow(df_filtered) == 0) {
+ # If no pixels meet coverage, return a data frame with NAs
+ # to maintain structure, matching expected column names.
+ # Column names are derived from the extraction_stack
+ stat_cols <- paste0(names(extraction_stack), "_mean")
+ na_df <- as.data.frame(matrix(NA_real_, nrow = 1, ncol = length(stat_cols)))
+ names(na_df) <- stat_cols
+ return(na_df)
+ }
+
+ # Calculate mean for each band (column in df_filtered)
+ stats_per_band <- lapply(names(df_filtered), function(band_name) {
+ col_data <- df_filtered[[band_name]]
+ if (length(col_data) > 0 && sum(!is.na(col_data)) > 0) {
+ mean_val <- mean(col_data, na.rm = TRUE)
+ return(setNames(mean_val, paste0(band_name, "_mean")))
+ } else {
+ return(setNames(NA_real_, paste0(band_name, "_mean")))
+ }
+ })
+
+ # Combine all stats (named values) into a single named vector then data frame
+ return(as.data.frame(t(do.call(c, stats_per_band))))
+ },
+ summarize_df = FALSE, # Important: get a list of DFs, one per polygon
+ force_df = TRUE # Ensure the output of the summary function is treated as a DF
+ )
+
+ # Combine all stats for this class if we have any
+ if (length(per_polygon_stats_list) > 0) {
+ # per_polygon_stats_list is now a list of single-row data.frames
+ class_stats_df <- do.call(rbind, per_polygon_stats_list)
+
+ # Remove rows that are all NA (from polygons with no valid pixels)
+ class_stats_df <- class_stats_df[rowSums(is.na(class_stats_df)) < ncol(class_stats_df), ]
+
+ if (nrow(class_stats_df) > 0) {
+ # Add class information
+ class_stats_df$class <- class_id
+ # Get class_name from the first polygon (assuming it's consistent for the class_id)
+ # Ensure class_polygons_sf is not empty before accessing class_name
+ if ("class_name" %in% names(class_polygons_sf) && nrow(class_polygons_sf) > 0) {
+ class_stats_df$class_name <- as.character(class_polygons_sf$class_name[1])
+ } else {
+ class_stats_df$class_name <- as.character(class_id) # Fallback
+ }
+
+ # Add to overall results
+ all_stats <- rbind(all_stats, class_stats_df)
+ safe_log(paste("Successfully extracted data for", nrow(class_stats_df), "polygons in class", class_id))
+ } else {
+ safe_log(paste("No valid data extracted for class (after NA removal):", class_id), "WARNING")
+ }
+ } else {
+ safe_log(paste("No data frames returned by exact_extract for class:", class_id), "WARNING")
+ }
+ }, error = function(e) {
+ safe_log(paste("Error processing class", class_id, "with exact_extract:", e$message), "ERROR")
+ })
+ }
+ # Save the extracted statistics to a CSV file
+ if (nrow(all_stats) > 0) {
+ stats_file <- file.path(diagnostic_dir, "class_spectral_stats_mean.csv") # New filename
+ write.csv(all_stats, stats_file, row.names = FALSE)
+ safe_log(paste("Saved MEAN spectral statistics by class to:", stats_file))
+ } else {
+ safe_log("No statistics were generated to save.", "WARNING")
+ }
+
+ # Calculate optimized thresholds for cloud/shadow detection (using only _mean columns)
+ if (nrow(all_stats) > 0 && ncol(all_stats) > 2) { # Check if all_stats has data and parameter columns
+ threshold_results <- data.frame(
+ parameter = character(),
+ best_threshold = numeric(),
+ direction = character(),
+ target_class = character(),
+ vs_class = character(),
+ accuracy = numeric(),
+ stringsAsFactors = FALSE
+ )
+
+ # Define class pairs to analyze
+ class_pairs <- list(
+ # Cloud vs various surfaces
+ c("cloud", "crop"),
+ c("cloud", "bare_soil_dry"),
+ c("cloud", "bare_soil_wet"),
+
+ # Shadow vs various surfaces
+ c("shadow_over_crop", "crop"),
+ c("shadow_over_bare_soil", "bare_soil_dry"),
+ c("shadow_over_bare_soil", "bare_soil_wet")
+ )
+
+ # For now, let's assume all _mean parameters derived from extraction_rasters are relevant for clouds/shadows
+ # This part might need more specific logic if you want to distinguish cloud/shadow params cloud_params <- grep("_mean$", names(extraction_rasters), value = TRUE)
+ params logic
+ # Parameters to analyze for shadows (now only _mean versions)tatistics by class to:", stats_file))
+ shadow_params <- cloud_params # Simplified: using the same set for now, adjust if specific shadow params are needed
+lds for cloud/shadow detection
+ # Find optimal thresholdsframe(
+ if (length(class_pairs) > 0 && (length(cloud_params) > 0 || length(shadow_params) > 0)) {
+ for (pair in class_pairs) {c(),
+ target_class <- pair[1](),
+ vs_class <- pair[2](),
+ vs_class = character(),
+ # Select appropriate parameters based on whether we're analyzing clouds or shadows accuracy = numeric(),
+ if (grepl("cloud", target_class)) {
+ params_to_check <- cloud_params
+ } else {
+ params_to_check <- shadow_paramsto analyze
+ }
+
+ # For each parameter, find the best threshold to separate the classesc("cloud", "crop"),
+ for (param in params_to_check) {
+ if (param %in% colnames(all_stats)) {
+ # Get values for both classes
+ target_values <- all_stats[all_stats$class_name == target_class, param]
+ vs_values <- all_stats[all_stats$class_name == vs_class, param] c("shadow_over_crop", "crop"),
+ c("shadow_over_bare_soil", "bare_soil_dry"),
+ if (length(target_values) > 0 && length(vs_values) > 0) {_soil_wet")
+ # Calculate mean and sd for both classes
+ target_mean <- mean(target_values, na.rm = TRUE)
+ target_sd <- sd(target_values, na.rm = TRUE)# Parameters to analyze for clouds
+ vs_mean <- mean(vs_values, na.rm = TRUE), "blue_ratio_mean", "ndvi_mean",
+ vs_sd <- sd(vs_values, na.rm = TRUE)
+
+
+ # Determine if higher or lower values indicate the target classshadow_params <- c("brightness_mean", "dark_pixels_mean", "very_dark_pixels_mean",
+ if (target_mean > vs_mean) {r_mean", "shadow_ndvi_mean", "blue_nir_ratio_raw_mean",
+ direction <- ">"_ratio_raw_mean", "low_red_to_blue_mean")
+ # Try different thresholds
+ potential_thresholds <- seq(olds
+ min(min(target_values, na.rm = TRUE), vs_mean + 0.5 * vs_sd),r (pair in class_pairs) {
+ max(max(vs_values, na.rm = TRUE), target_mean - 0.5 * target_sd),
+ length.out = 20
+ )
+ } else { appropriate parameters based on whether we're analyzing clouds or shadows
+ direction <- "<"{
+ # Try different thresholds params_to_check <- cloud_params
+ potential_thresholds <- seq(} else {
+ min(min(vs_values, na.rm = TRUE), target_mean + 0.5 * target_sd),
+ max(max(target_values, na.rm = TRUE), vs_mean - 0.5 * vs_sd),
+ length.out = 20
+ )st threshold to separate the classes
+ }
+
+ # Calculate accuracy for each threshold# Get values for both classes
+ best_accuracy <- 0_class, param]
+ best_threshold <- ifelse(direction == ">", min(potential_thresholds), max(potential_thresholds))e == vs_class, param]
+
+ for (threshold in potential_thresholds) {ues) > 0) {
+ if (direction == ">") {
+ correct_target <- sum(target_values > threshold, na.rm = TRUE)a.rm = TRUE)
+ correct_vs <- sum(vs_values <= threshold, na.rm = TRUE)target_sd <- sd(target_values, na.rm = TRUE)
+ } else { vs_mean <- mean(vs_values, na.rm = TRUE)
+ correct_target <- sum(target_values < threshold, na.rm = TRUE)
+ correct_vs <- sum(vs_values >= threshold, na.rm = TRUE)
+ }
+ er values indicate the target class
+ total_target <- length(target_values)
+ total_vs <- length(vs_values)
+
+ accuracy <- (correct_target + correct_vs) / (total_target + total_vs)lds <- seq(
+ min(min(target_values, na.rm = TRUE), vs_mean + 0.5 * vs_sd),
+ if (accuracy > best_accuracy) {max(vs_values, na.rm = TRUE), target_mean - 0.5 * target_sd),
+ best_accuracy <- accuracy0
+ best_threshold <- threshold
+ }
+ }
+
+ # Add to resultslds <- seq(
+ threshold_results <- rbind(threshold_results, data.frame( min(min(vs_values, na.rm = TRUE), target_mean + 0.5 * target_sd),
+ parameter = gsub("_mean", "", param), max(max(target_values, na.rm = TRUE), vs_mean - 0.5 * vs_sd),
+ best_threshold = best_threshold, length.out = 20
+ direction = direction,
+ target_class = target_class,
+ vs_class = vs_class,
+ accuracy = best_accuracy,# Calculate accuracy for each threshold
+ stringsAsFactors = FALSE
+ ))direction == ">", min(potential_thresholds), max(potential_thresholds))
+ }
+ }
+ }ction == ">") {
+ }
+ }
+ else {
+ # Save threshold results correct_target <- sum(target_values < threshold, na.rm = TRUE)
+ thresholds_file <- file.path(diagnostic_dir, "optimal_thresholds.csv")shold, na.rm = TRUE)
+ write.csv(threshold_results, thresholds_file, row.names = FALSE)
+ safe_log(paste("Saved optimal threshold recommendations to:", thresholds_file))
+
+ # Generate box plots for key parameters to visualize class differencestotal_vs <- length(vs_values)
+ if (requireNamespace("ggplot2", quietly = TRUE) && nrow(all_stats) > 0) {
+ # Reshape data for plotting (only _mean columns) + correct_vs) / (total_target + total_vs)
+ mean_cols <- grep("_mean$", colnames(all_stats), value = TRUE)
+ if (length(mean_cols) > 0) {f (accuracy > best_accuracy) {
+ plot_data <- reshape2::melt(all_stats, best_accuracy <- accuracy
+ id.vars = c("class", "class_name"), best_threshold <- threshold
+ measure.vars = mean_cols, # Use only _mean columns
+ variable.name = "parameter",
+ value.name = "value")
+
+ # Create directory for plotsnd(threshold_results, data.frame(
+ plots_dir <- file.path(diagnostic_dir, "class_plots"), param),
+ dir.create(plots_dir, showWarnings = FALSE, recursive = TRUE)t_threshold,
+
+ # Create plots for selected key parameters (ensure they are _mean versions)ass,
+ # Adjust key_params to reflect the new column names (e.g., "brightness_mean")vs_class = vs_class,
+ key_params_plot <- intersect(c( accuracy = best_accuracy,
+ "brightness_mean", "ndvi_mean", "blue_ratio_mean", "ndwi_mean", stringsAsFactors = FALSE
+ "blue_nir_ratio_raw_mean", "red_blue_ratio_raw_mean" ))
+ ), mean_cols) # Ensure these params exist }
+ }
+ for (param in key_params_plot) {
+ # param_data <- plot_data[plot_data$parameter == param,] # Exact match for parameter
+ # No, grepl was fine if plot_data only contains _mean parameters now.
+ # Let's ensure plot_data only has the _mean parameters for simplicity here.
+ param_data <- plot_data[plot_data$parameter == param, ]thresholds_file <- file.path(diagnostic_dir, "optimal_thresholds.csv")
+
+ if (nrow(param_data) > 0) {file))
+ param_name <- gsub("_mean", "", param)
+ o visualize class differences
+ p <- ggplot2::ggplot(param_data, ggplot2::aes(x = class_name, y = value, fill = class_name)) +s) > 0) {
+ ggplot2::geom_boxplot() +
+ ggplot2::theme_minimal() +
+ ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) + id.vars = c("class", "class_name"),
+ ggplot2::labs(ariable.name = "parameter",
+ title = paste("Distribution of", param_name, "by Land Cover Class"),
+ x = "Class",
+ y = param_name,# Create directory for plots
+ fill = "Class"ass_plots")
+ )_dir, showWarnings = FALSE, recursive = TRUE)
+
+ # Save the plot
+ plot_file <- file.path(plots_dir, paste0("boxplot_", param_name, ".png"))ey_params <- c(
+ ggplot2::ggsave(plot_file, p, width = 10, height = 6, dpi = 150) "brightness_mean", "ndvi_mean", "blue_ratio_mean", "ndwi_mean",
+ }, "red_blue_ratio_raw_mean"
+ }
+
+ # Create a summary plot showing multiple parameters
+ summary_data <- plot_data[plot_data$parameter %in% ram_data <- plot_data[grepl(param, plot_data$parameter),]
+ c("brightness_mean", "ndvi_mean",
+ "blue_nir_ratio_raw_mean", "red_blue_ratio_raw_mean"),] "", param)
+
+ if (nrow(summary_data) > 0) {= class_name)) +
+ # Clean up parameter names for displayboxplot() +
+ summary_data$parameter <- gsub("_mean$", "", summary_data$parameter) # Remove _mean suffix for display
+ summary_data$parameter <- gsub("_raw$", "", summary_data$parameter) # Keep this if _raw_mean was a thing(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) +
+
+ # Create faceted plot"Distribution of", param_name, "by Land Cover Class"),
+ p <- ggplot2::ggplot(summary_data, x = "Class",
+ ggplot2::aes(x = class_name, y = value, fill = class_name)) + y = param_name,
+ ggplot2::geom_boxplot() +ss"
+ ggplot2::facet_wrap(~parameter, scales = "free_y") +
+ ggplot2::theme_minimal() +
+ ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) + # Save the plot
+ ggplot2::labs( plot_file <- file.path(plots_dir, paste0("boxplot_", param_name, ".png"))
+ title = "Key Spectral Parameters by Land Cover Class", ggplot2::ggsave(plot_file, p, width = 10, height = 6, dpi = 150)
+ x = "Class",
+ y = "Value",
+ fill = "Class"
+ )
+ summary_data <- plot_data[plot_data$parameter %in%
+ # Save the summary plot"brightness_mean", "ndvi_mean",
+ summary_file <- file.path(plots_dir, "spectral_parameters_summary.png")atio_raw_mean", "red_blue_ratio_raw_mean"),]
+ ggplot2::ggsave(summary_file, p, width = 12, height = 8, dpi = 150)
+ }
+ # Clean up parameter names for display
+ safe_log(paste("Generated spectral parameter plots in:", plots_dir))r <- gsub("_mean", "", summary_data$parameter)
+ }w", "", summary_data$parameter)
+ } else {
+ safe_log("Package 'exactextractr' not available. Install it for more accurate polygon extraction.", "WARNING")
+
+ # Fall back to simple extraction using terra (calculating only mean)::aes(x = class_name, y = value, fill = class_name)) +
+ class_stats <- data.frame()
+ _wrap(~parameter, scales = "free_y") +
+ valid_class_names_fallback <- unique(classifications$class_name)
+ valid_class_names_fallback <- valid_class_names_fallback[!is.na(valid_class_names_fallback)](axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) +
+
+ if (length(valid_class_names_fallback) == 0) {pectral Parameters by Land Cover Class",
+ safe_log("No valid (non-NA) class names found for fallback terra::extract processing.", "WARNING") x = "Class",
+ } y = "Value",
+
+ for (class_name_fb in valid_class_names_fallback) {
+ class_polygons_fb <- classifications[which(classifications$class_name == class_name_fb), ]
+ # Save the summary plot
+ if(nrow(class_polygons_fb) == 0) next summary_file <- file.path(plots_dir, "spectral_parameters_summary.png")
+)
+ class_vect_fb <- terra::vect(class_polygons_fb) }
+
+ # Extract values for each raster
+ for (i in seq_along(extraction_rasters)) {}
+ raster_name <- names(extraction_rasters)[i]
+ # terra::extract returns a data.frame with ID and layer valuesractr' not available. Install it for more accurate polygon extraction.", "WARNING")
+ # For multiple polygons, it will have multiple rows per polygon if ID is not unique
+ # We need to aggregate per polygon, then per class if not already handled by exact_extract style
+ # However, for simplicity here, let's assume terra::extract gives one value per polygon for the mean
+ # This part of fallback might need more robust aggregation if polygons are complex
+ r (class_name in unique(classifications$class_name)) {
+ # A more robust terra::extract approach for means per polygon:s[classifications$class_name == class_name, ]
+ extracted_values_list <- terra::extract(extraction_rasters[[i]], class_vect_fb, fun = mean, na.rm = TRUE, ID = FALSE)
+ # extracted_values_list will be a data.frame with one column (the layer) and rows corresponding to polygons
+
+ if (nrow(extracted_values_list) > 0 && ncol(extracted_values_list) > 0) {r (i in seq_along(extraction_rasters)) {
+ # Average over all polygons in this class for this rastertraction_rasters)[i]
+ mean_val_for_class <- mean(extracted_values_list[[1]], na.rm = TRUE)ct(extraction_rasters[[i]], class_vect)
+
+ if (!is.na(mean_val_for_class)) {
+ stats_row <- data.frame(
+ class_name = class_name_fb, # Using class_name as the identifier here
+ parameter = paste0(raster_name, "_mean"),
+ value = mean_val_for_class),
+ ) TRUE),
+ class_stats <- rbind(class_stats, stats_row) sd = sd(values[,2], na.rm = TRUE),
+ } min = min(values[,2], na.rm = TRUE),
+ }
+ } )
+ }
+ class_stats <- rbind(class_stats, stats)
+ # Save the statistics (if any were generated) }
+ if(nrow(class_stats) > 0) {
+ # Reshape class_stats from long to wide for consistency if needed, or save as is.
+ # For now, save as long format.
+ stats_file <- file.path(diagnostic_dir, "class_spectral_stats_simple_mean_long.csv")
+ write.csv(class_stats, stats_file, row.names = FALSE) stats_file <- file.path(diagnostic_dir, "class_spectral_stats_simple.csv")
+ safe_log(paste("Saved simple MEAN (long format) spectral statistics by class to:", stats_file)) write.csv(class_stats, stats_file, row.names = FALSE)
+ } else {e spectral statistics by class to:", stats_file))
+ safe_log("No statistics generated by fallback method.", "WARNING")
+ }
+ }ve RMarkdown generation
+
+ # Remove RMarkdown generation
+ # safe_log("RMarkdown report generation has been removed as per user request.")
+ NING")
+ } else {}
+ safe_log("No classification polygons file (classes.geojson) found. Skipping spectral analysis.", "WARNING")}, error = function(e) {
+ }cessing or spectral analysis:", e$message), "ERROR")
+}, error = function(e) {})
+ safe_log(paste("Error in classification polygon processing or spectral analysis:", e$message), "ERROR")
+}) detection analysis script finished.")
+
+safe_log("Cloud detection analysis script finished.")# Clean up workspace
+rm(list = ls())
+# Clean up workspace
+rm(list = ls())
+
+
+
+
+
+
+
+
+
diff --git a/r_app/experiments/delete_cloud_exploratoin.R b/r_app/experiments/delete_cloud_exploratoin.R
new file mode 100644
index 0000000..356258e
--- /dev/null
+++ b/r_app/experiments/delete_cloud_exploratoin.R
@@ -0,0 +1,191 @@
+```r
+# Cloud detection analysis script
+
+# Load necessary libraries
+library(terra)
+library(exactextractr)
+library(sf)
+library(dplyr)
+library(ggplot2)
+library(tidyr)
+library(reshape2)
+
+# Define file paths (these should be set to your actual file locations)
+classes_file <- "path/to/classes.geojson"
+rasters_dir <- "path/to/rasters"
+diagnostic_dir <- "path/to/diagnostics"
+
+# Helper function for logging
+safe_log <- function(message, level = "INFO") {
+ timestamp <- format(Sys.time(), "%Y-%m-%d %H:%M:%S")
+ cat(paste0("[", timestamp, "] [", level, "] ", message, "\n"))
+}
+
+# Main processing block
+# Load classification polygons
+safe_log(paste("Loading classification polygons from:", classes_file))
+classifications <- sf::st_read(classes_file, quiet = TRUE)
+# Ensure the CRS is set (assuming WGS84 here, adjust if necessary)
+safe_log("No CRS found for the classifications. Setting to WGS84 (EPSG:4326).", "WARNING")
+sf::st_crs(classifications) <- 4326
+# List all raster files in the directory
+raster_files <- list.files(rasters_dir, pattern = "\\.tif$", full.names = TRUE)
+# Create a named vector for extraction_rasters based on base names
+extraction_rasters <- setNames(raster_files, tools::file_path_sans_ext(basename(raster_files)))
+# Create a stack of all rasters
+extraction_stack <- terra::rast(extraction_rasters)
+# User-provided simplified extraction for mean statistics per polygon
+safe_log("Extracting mean statistics per polygon using exactextractr...")
+all_stats <- cbind(
+ classifications,
+ round(exactextractr::exact_extract(extraction_stack, classifications, fun = "mean", progress = FALSE), 2)
+) %>%
+ sf::st_drop_geometry() # Ensures all_stats is a data frame
+# Ensure 'class_name' column exists, if not, use 'class' as 'class_name'
+all_stats$class_name <- all_stats$class
+
+# Save the extracted statistics to a CSV file
+stats_file <- file.path(diagnostic_dir, "polygon_mean_spectral_stats.csv")
+write.csv(all_stats, stats_file, row.names = FALSE)
+
+
+
+safe_log(paste("Saved mean spectral statistics per polygon to:", stats_file))
+# Calculate optimized thresholds for cloud/shadow detection
+threshold_results <- data.frame(
+ parameter = character(),
+ best_threshold = numeric(),
+ direction = character(),
+ target_class = character(),
+ vs_class = character(),
+ accuracy = numeric(),
+ stringsAsFactors = FALSE
+)
+class_pairs <- list(
+ c("cloud", "crop"),
+ c("cloud", "bare_soil_dry"),
+ c("cloud", "bare_soil_wet"),
+ c("shadow_over_crop", "crop"),
+ c("shadow_over_bare_soil", "bare_soil_dry"),
+ c("shadow_over_bare_soil", "bare_soil_wet")
+)
+cloud_detection_params_for_threshold <- intersect(
+ c("mean.brightness", "mean.very_bright_pixels", "mean.blue_dominant", "mean.low_ndvi", "mean.green_dominant_nir", "mean.high_ndwi", "mean.blue_ratio", "mean.ndvi"),
+ colnames(all_stats)
+)
+shadow_detection_params_for_threshold <- intersect(
+ c("mean.brightness", "mean.dark_pixels", "mean.very_dark_pixels", "mean.low_nir", "mean.shadow_ndvi", "mean.low_red_to_blue", "mean.high_blue_to_nir_ratio", "mean.blue_nir_ratio_raw", "mean.red_blue_ratio_raw"),
+ colnames(all_stats)
+)
+for (pair in class_pairs) {
+ target_class <- pair[1]
+ vs_class <- pair[2]
+ params_to_check <- c(cloud_detection_params_for_threshold, shadow_detection_params_for_threshold)
+ for (param in params_to_check) {
+ target_values <- all_stats[all_stats$class_name == target_class, param]
+ vs_values <- all_stats[all_stats$class_name == vs_class, param]
+ target_values <- target_values[!is.na(target_values)]
+ vs_values <- vs_values[!is.na(vs_values)]
+ # Only proceed if both groups have at least one value
+ if (length(target_values) > 0 && length(vs_values) > 0) {
+ target_mean <- mean(target_values)
+ target_sd <- sd(target_values)
+ vs_mean <- mean(vs_values)
+ vs_sd <- sd(vs_values)
+ target_sd[is.na(target_sd)] <- 0
+ vs_sd[is.na(vs_sd)] <- 0
+ direction <- ifelse(target_mean > vs_mean, ">", "<")
+ all_values <- c(target_values, vs_values)
+ min_val <- min(all_values)
+ max_val <- max(all_values)
+ # Only proceed if min and max are finite and not equal
+ if (is.finite(min_val) && is.finite(max_val) && min_val != max_val) {
+ potential_thresholds <- seq(min_val, max_val, length.out = 20)
+ best_accuracy <- -1
+ best_threshold <- ifelse(direction == ">", min(potential_thresholds), max(potential_thresholds))
+ for (threshold in potential_thresholds) {
+ if (direction == ">") {
+ correct_target <- sum(target_values > threshold)
+ correct_vs <- sum(vs_values <= threshold)
+ } else {
+ correct_target <- sum(target_values < threshold)
+ correct_vs <- sum(vs_values >= threshold)
+ }
+ accuracy <- (correct_target + correct_vs) / (length(target_values) + length(vs_values))
+ if (accuracy > best_accuracy) {
+ best_accuracy <- accuracy
+ best_threshold <- threshold
+ }
+ }
+ threshold_results <- rbind(threshold_results, data.frame(
+ parameter = param,
+ best_threshold = best_threshold,
+ direction = direction,
+ target_class = target_class,
+ vs_class = vs_class,
+ accuracy = best_accuracy,
+ stringsAsFactors = FALSE
+ ))
+ }
+ }
+ }
+}
+
+thresholds_file <- file.path(diagnostic_dir, "optimal_thresholds.csv")
+write.csv(threshold_results, thresholds_file, row.names = FALSE)
+
+safe_log(paste("Saved optimal threshold recommendations to:", thresholds_file))
+
+# Fix: get plot_measure_cols by matching raster base names to all_stats columns with 'mean.' prefix
+plot_measure_cols <- intersect(names(extraction_rasters), gsub('^mean\\.', '', colnames(all_stats)))
+plot_data <- reshape2::melt(
+ all_stats,
+ id.vars = c("class", "class_name"),
+ measure.vars = paste0("mean.", plot_measure_cols),
+ variable.name = "parameter",
+ value.name = "value"
+)
+# Remove 'mean.' prefix from parameter column for clarity
+plot_data$parameter <- sub("^mean\\.", "", plot_data$parameter)
+
+plots_dir <- file.path(diagnostic_dir, "class_plots")
+dir.create(plots_dir, showWarnings = FALSE, recursive = TRUE)
+key_params_for_plot_list <- c("brightness", "ndvi", "blue_ratio", "ndwi",
+ "blue_nir_ratio_raw", "red_blue_ratio_raw")
+key_params_to_plot <- intersect(key_params_for_plot_list, plot_measure_cols)
+for (param_to_plot in key_params_to_plot) {
+ param_data_subset <- plot_data[plot_data$parameter == param_to_plot, ]
+ p <- ggplot2::ggplot(param_data_subset, ggplot2::aes(x = class_name, y = value, fill = class_name)) +
+ ggplot2::geom_boxplot() +
+ ggplot2::theme_minimal() +
+ ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) +
+ ggplot2::labs(
+ title = paste("Distribution of", param_to_plot, "by Land Cover Class"),
+ x = "Class",
+ y = param_to_plot,
+ fill = "Class"
+ )
+ plot_file <- file.path(plots_dir, paste0("boxplot_", param_to_plot, ".png"))
+ ggplot2::ggsave(plot_file, p, width = 10, height = 6, dpi = 150)
+}
+summary_params_for_plot_list <- c("brightness", "ndvi",
+ "blue_nir_ratio_raw", "red_blue_ratio_raw")
+summary_params_to_plot <- intersect(summary_params_for_plot_list, plot_measure_cols)
+summary_data_subset <- plot_data[plot_data$parameter %in% summary_params_to_plot,]
+p_summary <- ggplot2::ggplot(summary_data_subset, ggplot2::aes(x = class_name, y = value, fill = class_name)) +
+ ggplot2::geom_boxplot() +
+ ggplot2::facet_wrap(~parameter, scales = "free_y") +
+ ggplot2::theme_minimal() +
+ ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1),
+ strip.text = ggplot2::element_text(size = 8)) +
+ ggplot2::labs(
+ title = "Summary of Key Spectral Parameters by Land Cover Class",
+ x = "Class",
+ y = "Value",
+ fill = "Class"
+ )
+summary_file <- file.path(plots_dir, "spectral_parameters_summary.png")
+ggplot2::ggsave(summary_file, p_summary, width = 12, height = 8, dpi = 150)
+safe_log(paste("Generated spectral parameter plots in:", plots_dir))
+safe_log("Cloud detection analysis script finished.")
+```
\ No newline at end of file
diff --git a/r_app/experiments/executive_summary/CI_report_executive_summary.Rmd b/r_app/experiments/executive_summary/CI_report_executive_summary.Rmd
new file mode 100644
index 0000000..2d4c014
--- /dev/null
+++ b/r_app/experiments/executive_summary/CI_report_executive_summary.Rmd
@@ -0,0 +1,718 @@
+---
+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)
+
+# 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", "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/experiments/executive_summary/CI_report_executive_summary_v2.Rmd b/r_app/experiments/executive_summary/CI_report_executive_summary_v2.Rmd
new file mode 100644
index 0000000..6e33804
--- /dev/null
+++ b/r_app/experiments/executive_summary/CI_report_executive_summary_v2.Rmd
@@ -0,0 +1,1531 @@
+---
+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)
+# })
+# })
+```
+
+```{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 executive_summary_functions, message=FALSE, warning=FALSE, include=FALSE}
+# EXECUTIVE SUMMARY HELPER FUNCTIONS
+
+#' Generate a summary of farm health status
+#'
+#' @param field_boundaries Field boundaries spatial data (sf object)
+#' @param ci_current Current CI raster
+#' @param ci_previous Previous week's CI raster
+#' @param harvesting_data Data frame with harvesting information
+#' @return A data frame with farm status summary metrics
+#'
+generate_farm_health_summary <- function(field_boundaries, ci_current, ci_previous, harvesting_data) {
+ # Generate a summary data frame of farm health by field
+ tryCatch({
+ # Get unique field names
+ fields <- unique(field_boundaries$field)
+
+ # Initialize result dataframe
+ summary_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 better error handling
+ for (field_name in fields) {
+ tryCatch({
+ # Get field boundary
+ field_shape <- field_boundaries %>% 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
+ field_age_data <- harvesting_data %>%
+ dplyr::filter(field == field_name) %>%
+ dplyr::arrange(desc(season_start)) %>%
+ dplyr::slice(1)
+
+ # 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
+ }
+
+ # Extract CI values for this field using extract instead of crop/mask to avoid pointer issues
+ # This is more robust than the crop+mask approach
+ field_bbox <- sf::st_bbox(field_shape)
+ extent_vec <- c(field_bbox$xmin, field_bbox$xmax, field_bbox$ymin, field_bbox$ymax)
+
+ # Use terra extract function instead of crop+mask
+ ci_values <- terra::extract(ci_current, field_shape)
+ ci_prev_values <- terra::extract(ci_previous, field_shape)
+
+ # Calculate metrics
+ mean_ci <- mean(ci_values$CI, na.rm=TRUE)
+ mean_ci_prev <- mean(ci_prev_values$CI, na.rm=TRUE)
+ ci_change <- mean_ci - mean_ci_prev
+ ci_sd <- sd(ci_values$CI, na.rm=TRUE)
+ ci_uniformity <- ci_sd / max(0.1, mean_ci) # Avoid division by zero
+
+ # 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
+ summary_data <- rbind(summary_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 calculating health score for field", field_name, ":", e$message), "ERROR")
+ # Add a row with NA values for this field to ensure it still appears in outputs
+ summary_data <- rbind(summary_data, data.frame(
+ field = field_name,
+ mean_ci = NA,
+ ci_change = NA,
+ ci_uniformity = NA,
+ status = "Error",
+ anomaly_type = "Error",
+ priority_level = 1, # High priority because it needs investigation
+ age_weeks = NA,
+ harvest_readiness = "Unknown",
+ stringsAsFactors = FALSE
+ ))
+ })
+ }
+
+ # Sort by priority level
+ summary_data <- summary_data %>% dplyr::arrange(priority_level, field)
+
+ return(summary_data)
+
+ }, error = function(e) {
+ safe_log(paste("Error in generate_farm_health_summary:", e$message), "ERROR")
+ return(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
+ )) })
+}
+
+#' Create a raster-based CI map showing pixel values
+#'
+#' @param ci_raster CI raster data
+#' @param field_boundaries Field boundaries spatial data (sf object) for overlay
+#' @param title Map title
+#' @param legend_title Legend title
+#' @return A tmap object with raster visualization
+#'
+create_ci_raster_map <- function(ci_raster, field_boundaries,
+ title = "Chlorophyll Index (CI) - Pixel Values",
+ legend_title = "CI Value") {
+ tryCatch({
+ # Create raster map
+ map <- tm_shape(ci_raster) +
+ tm_raster(
+ palette = "RdYlGn",
+ title = legend_title,
+ style = "cont"
+ # n = 10,
+ # alpha = 0.8
+ ) +
+ # Add field boundaries as overlay
+ tm_shape(field_boundaries) +
+ tm_borders(col = "black", lwd = 0.5, alpha = 0.7) +
+ tm_text("field", size = 0.6, col = "black", alpha = 0.8) +
+ tm_layout(
+ main.title = title,
+ legend.outside = TRUE,
+ legend.outside.position = "bottom"
+ ) +
+ tm_scale_bar(position = tm_pos_out("right", "bottom"))
+
+ return(map)
+
+ }, error = function(e) {
+ safe_log(paste("Error in create_ci_raster_map:", e$message), "ERROR")
+ return(NULL)
+ })
+}
+
+#' Create a farm-wide anomaly detection map
+#'
+#' @param ci_current Current CI raster
+#' @param ci_previous Previous week's CI raster
+#' @param field_boundaries Field boundaries spatial data (sf object)
+#' @return A tmap object with anomaly visualization
+#'
+create_anomaly_map <- function(ci_current, ci_previous, field_boundaries) {
+ tryCatch({
+ # Calculate difference raster
+ ci_diff <- ci_current - ci_previous
+
+ # Create a categorical raster for anomalies
+ anomaly_raster <- ci_current * 0 # Initialize with same extent/resolution
+
+ # Extract values to manipulate
+ diff_values <- terra::values(ci_diff)
+ curr_values <- terra::values(ci_current)
+
+ # Define anomaly categories:
+ # 4: Significant growth (potential weeds) - CI increase > 2
+ # 3: Moderate growth - CI increase 1-2
+ # 2: Stable - CI change between -1 and 1
+ # 1: Moderate decline - CI decrease 1-2
+ # 0: Significant decline (potential weeding/harvesting) - CI decrease > 2
+
+ # Apply classification
+ anomaly_values <- rep(NA, length(diff_values))
+
+ # Significant growth (potential weeds)
+ sig_growth <- which(diff_values > 2 & !is.na(diff_values))
+ anomaly_values[sig_growth] <- 4
+
+ # Moderate growth
+ mod_growth <- which(diff_values > 1 & diff_values <= 2 & !is.na(diff_values))
+ anomaly_values[mod_growth] <- 3
+
+ # Stable
+ stable <- which(diff_values >= -1 & diff_values <= 1 & !is.na(diff_values))
+ anomaly_values[stable] <- 2
+
+ # Moderate decline
+ mod_decline <- which(diff_values < -1 & diff_values >= -2 & !is.na(diff_values))
+ anomaly_values[mod_decline] <- 1
+
+ # Significant decline (potential weeding)
+ sig_decline <- which(diff_values < -2 & !is.na(diff_values))
+ anomaly_values[sig_decline] <- 0
+
+ # Set values in raster
+ terra::values(anomaly_raster) <- anomaly_values
+
+ # Create anomaly map
+ map <- tm_shape(anomaly_raster) +
+ tm_raster(
+ style = "cat",
+ palette = c("#d73027", "#fc8d59", "#ffffbf", "#91cf60", "#1a9850"),
+ labels = c("Significant Decline", "Moderate Decline", "Stable", "Moderate Growth", "Significant Growth"),
+ title = "Weekly CI Change"
+ ) +
+ tm_shape(field_boundaries) +
+ tm_borders(col = "black", lwd = 1.5) +
+ tm_text("field", size = 0.6) +
+ tm_layout(
+ main.title = "Farm-Wide Anomaly Detection",
+ legend.outside = TRUE,
+ legend.outside.position = "bottom"
+ ) +
+ tm_scale_bar(position = tm_pos_out("right", "bottom"))
+
+ return(map)
+
+ }, error = function(e) {
+ safe_log(paste("Error in create_anomaly_map:", e$message), "ERROR")
+ return(NULL)
+ })
+}
+
+#' Create a choropleth map of field health status
+#'
+#' @param field_boundaries Field boundaries with health data
+#' @param attribute Field to visualize (e.g., "priority_level", "mean_ci")
+#' @param title Map title
+#' @param palette Color palette to use
+#' @param legend_title Legend title
+#' @return A tmap object
+#'
+create_field_status_map <- function(field_boundaries, health_data, attribute,
+ title = "Field Status Overview",
+ legend_title = "Status") {
+ tryCatch({
+ # Join health data to field boundaries
+ field_data <- field_boundaries %>%
+ dplyr::left_join(health_data, by = "field")
+
+ # Create style based on attribute type
+ if (attribute == "status") {
+ # Categorical styling for status
+ map <- tm_shape(field_data) +
+ tm_fill(
+ col = attribute,
+ palette = c("Excellent" = "#1a9850", "Good" = "#91cf60",
+ "Fair" = "#ffffbf", "Poor" = "#fc8d59", "Critical" = "#d73027",
+ "Error" = "#999999"), # Added Error category
+ title = legend_title
+ )
+ } else if (attribute == "priority_level") {
+ # Numeric with custom breaks for priority (5 to 1, with 1 being highest priority)
+ map <- tm_shape(field_data) +
+ tm_fill(
+ col = attribute,
+ palette = "-RdYlGn", # Reversed so red is high priority
+ breaks = c(0.5, 1.5, 2.5, 3.5, 4.5, 5.5),
+ labels = c("Critical", "High", "Medium", "Low", "Minimal"),
+ title = legend_title
+ )
+ } else if (attribute == "anomaly_type") {
+ # Categorical styling for anomalies
+ map <- tm_shape(field_data) +
+ tm_fill(
+ col = attribute,
+ palette = c("Potential Weed Growth" = "#d73027",
+ "Potential Weeding/Harvesting" = "#4575b4",
+ "High Variability" = "#f46d43",
+ "Low Vigor" = "#fee090",
+ "None" = "#91cf60",
+ "Error" = "#999999"), # Added Error category
+ title = legend_title
+ )
+ } else if (attribute == "harvest_readiness") {
+ # Categorical styling for harvest readiness
+ map <- tm_shape(field_data) +
+ tm_fill(
+ col = attribute,
+ palette = c("Ready for harvest" = "#1a9850",
+ "Approaching harvest" = "#91cf60",
+ "Mid-maturity" = "#ffffbf",
+ "Growing" = "#fc8d59",
+ "Early stage" = "#d73027", "Unknown" = "#999999"), # Added Unknown category
+ title = legend_title
+ )
+ } else {
+ # Default numerical styling
+ map <- tm_shape(field_data) +
+ tm_fill(
+ col = attribute,
+ palette = "RdYlGn",
+ title = legend_title,
+ style = "cont",
+ na.color = "#999999" # Color for NA values
+ )
+ }
+
+ # Complete the map with borders and labels
+ map <- map +
+ tm_borders(col = "black", lwd = 1) +
+ tm_text("field", size = 0.7) +
+ tm_layout(
+ main.title = title,
+ legend.outside = TRUE,
+ legend.outside.position = "bottom"
+ ) +
+ tm_scale_bar(position = tm_pos_out("right", "bottom"))
+
+ return(map)
+
+ }, error = function(e) {
+ safe_log(paste("Error in create_field_status_map:", e$message), "ERROR")
+ return(NULL)
+ })
+}
+
+#' Create a summary statistics visualization
+#'
+#' @param health_data Farm health summary data
+#' @return A ggplot2 object
+#'
+create_summary_stats <- function(health_data) {
+ tryCatch({
+ # Handle empty dataframe case
+ if (nrow(health_data) == 0) {
+ return(ggplot2::ggplot() +
+ ggplot2::annotate("text", x = 0, y = 0, label = "No field data available") +
+ ggplot2::theme_void())
+ }
+
+ # Count fields by status
+ status_counts <- health_data %>%
+ dplyr::group_by(status) %>%
+ dplyr::summarise(count = n()) %>%
+ dplyr::mutate(status = factor(status, levels = c("Excellent", "Good", "Fair", "Poor", "Critical", "Error")))
+
+ # Create colors for status categories
+ status_colors <- c(
+ "Excellent" = "#1a9850",
+ "Good" = "#91cf60",
+ "Fair" = "#ffffbf",
+ "Poor" = "#fc8d59",
+ "Critical" = "#d73027",
+ "Error" = "#999999"
+ )
+
+ # Create bar chart
+ p <- ggplot2::ggplot(status_counts, ggplot2::aes(x = status, y = count, fill = status)) +
+ ggplot2::geom_bar(stat = "identity") +
+ ggplot2::scale_fill_manual(values = status_colors) +
+ ggplot2::labs(
+ title = "Field Status Summary",
+ x = "Status",
+ y = "Number of Fields",
+ fill = "Field Status"
+ ) +
+ ggplot2::theme_minimal() +
+ ggplot2::theme(
+ axis.text.x = ggplot2::element_text(angle = 45, hjust = 1),
+ legend.position = "bottom"
+ )
+
+ return(p)
+
+ }, error = function(e) {
+ safe_log(paste("Error in create_summary_stats:", e$message), "ERROR")
+ return(ggplot2::ggplot() +
+ ggplot2::annotate("text", x = 0, y = 0, label = paste("Error:", e$message)) +
+ ggplot2::theme_void())
+ })
+}
+
+#' Create a bar chart of fields requiring attention
+#'
+#' @param health_data Farm health summary data
+#' @param max_fields Maximum number of fields to display
+#' @return A ggplot2 object
+#'
+create_priority_fields_chart <- function(health_data, max_fields = 10) {
+ tryCatch({
+ # Handle empty dataframe case
+ if (nrow(health_data) == 0) {
+ return(ggplot2::ggplot() +
+ ggplot2::annotate("text", x = 0, y = 0, label = "No field data available") +
+ ggplot2::theme_void())
+ }
+
+ # Filter for fields that need attention (priority 1-3)
+ priority_fields <- health_data %>%
+ dplyr::filter(priority_level <= 3) %>%
+ dplyr::arrange(priority_level) %>%
+ dplyr::slice_head(n = max_fields)
+
+ # If no priority fields, return message
+ if (nrow(priority_fields) == 0) {
+ return(ggplot2::ggplot() +
+ ggplot2::annotate("text", x = 0, y = 0, label = "No priority fields requiring attention") +
+ ggplot2::theme_void())
+ }
+
+ # Create priority labels
+ priority_fields$priority_label <- factor(
+ dplyr::case_when(
+ priority_fields$priority_level == 1 ~ "Critical",
+ priority_fields$priority_level == 2 ~ "High",
+ priority_fields$priority_level == 3 ~ "Medium",
+ TRUE ~ "Low"
+ ),
+ levels = c("Critical", "High", "Medium", "Low")
+ )
+
+ # Priority colors
+ priority_colors <- c(
+ "Critical" = "#d73027",
+ "High" = "#fc8d59",
+ "Medium" = "#fee090",
+ "Low" = "#91cf60"
+ )
+
+ # Create chart
+ p <- ggplot2::ggplot(priority_fields,
+ ggplot2::aes(x = reorder(field, -priority_level),
+ y = mean_ci,
+ fill = priority_label)) +
+ ggplot2::geom_bar(stat = "identity") +
+ ggplot2::geom_text(ggplot2::aes(label = anomaly_type),
+ position = ggplot2::position_stack(vjust = 0.5),
+ size = 3, angle = 90, hjust = 0) +
+ ggplot2::scale_fill_manual(values = priority_colors) +
+ ggplot2::labs(
+ title = "Priority Fields Requiring Attention",
+ subtitle = "With anomaly types and CI values",
+ x = "Field",
+ y = "Chlorophyll Index (CI)",
+ fill = "Priority"
+ ) +
+ ggplot2::theme_minimal() +
+ ggplot2::theme(
+ axis.text.x = ggplot2::element_text(angle = 45, hjust = 1),
+ legend.position = "bottom"
+ )
+
+ return(p)
+
+ }, error = function(e) {
+ safe_log(paste("Error in create_priority_fields_chart:", e$message), "ERROR")
+ return(ggplot2::ggplot() +
+ ggplot2::annotate("text", x = 0, y = 0, label = paste("Error:", e$message)) +
+ ggplot2::theme_void())
+ })
+}
+
+#' Creates a harvest readiness visualization
+#'
+#' @param health_data Farm health summary data
+#' @return A ggplot2 object
+create_harvest_readiness_chart <- function(health_data) {
+ tryCatch({
+ # Handle empty dataframe case
+ if (nrow(health_data) == 0) {
+ return(ggplot2::ggplot() +
+ ggplot2::annotate("text", x = 0, y = 0, label = "No field data available") +
+ ggplot2::theme_void())
+ }
+
+ # Count fields by harvest readiness
+ harvest_counts <- health_data %>%
+ dplyr::group_by(harvest_readiness) %>%
+ dplyr::summarise(count = n())
+
+ # Order factor levels
+ harvest_order <- c("Ready for harvest", "Approaching harvest", "Mid-maturity", "Growing", "Early stage", "Unknown")
+ harvest_counts$harvest_readiness <- factor(harvest_counts$harvest_readiness, levels = harvest_order)
+
+ # Create colors for harvest readiness categories
+ harvest_colors <- c(
+ "Ready for harvest" = "#1a9850",
+ "Approaching harvest" = "#91cf60",
+ "Mid-maturity" = "#ffffbf",
+ "Growing" = "#fc8d59",
+ "Early stage" = "#d73027",
+ "Unknown" = "#999999"
+ )
+
+ # Create pie chart
+ p <- ggplot2::ggplot(harvest_counts, ggplot2::aes(x="", y=count, fill=harvest_readiness)) +
+ ggplot2::geom_bar(stat="identity", width=1) +
+ ggplot2::coord_polar("y", start=0) +
+ ggplot2::scale_fill_manual(values = harvest_colors) +
+ ggplot2::labs(
+ title = "Harvest Readiness Overview",
+ fill = "Harvest Stage"
+ ) +
+ ggplot2::theme_minimal() +
+ ggplot2::theme(
+ axis.title.x = ggplot2::element_blank(),
+ axis.title.y = ggplot2::element_blank(),
+ panel.border = ggplot2::element_blank(),
+ panel.grid = ggplot2::element_blank(),
+ axis.ticks = ggplot2::element_blank(),
+ axis.text = ggplot2::element_blank(),
+ plot.title = ggplot2::element_text(size=14, face="bold")
+ )
+
+ return(p)
+
+ }, error = function(e) {
+ safe_log(paste("Error in create_harvest_readiness_chart:", e$message), "ERROR")
+ return(ggplot2::ggplot() +
+ ggplot2::annotate("text", x = 0, y = 0, label = paste("Error:", e$message)) +
+ ggplot2::theme_void())
+ })
+}
+
+#' Generate recommendations based on farm health
+#'
+#' @param health_data Farm health summary data
+#' @return HTML formatted recommendations
+generate_executive_recommendations <- function(health_data) {
+ tryCatch({
+ # Handle empty dataframe case
+ if (nrow(health_data) == 0) {
+ return("Executive Recommendations No field data available to generate recommendations.
")
+ }
+
+ # Count fields by priority level
+ priority_counts <- health_data %>%
+ dplyr::group_by(priority_level) %>%
+ dplyr::summarise(count = n())
+
+ # Get critical and high priority fields
+ critical_fields <- health_data %>%
+ dplyr::filter(priority_level == 1) %>%
+ dplyr::pull(field)
+
+ high_priority_fields <- health_data %>%
+ dplyr::filter(priority_level == 2) %>%
+ dplyr::pull(field)
+
+ # Count harvest-ready fields
+ harvest_ready <- health_data %>%
+ dplyr::filter(harvest_readiness == "Ready for harvest") %>%
+ dplyr::pull(field)
+
+ approaching_harvest <- health_data %>%
+ dplyr::filter(harvest_readiness == "Approaching harvest") %>%
+ dplyr::pull(field)
+
+ # Count anomalies by type
+ anomaly_counts <- health_data %>%
+ dplyr::filter(anomaly_type != "None" & anomaly_type != "Error") %>%
+ dplyr::group_by(anomaly_type) %>%
+ dplyr::summarise(count = n())
+
+ # Generate HTML recommendations
+ html_output <- ""
+ html_output <- paste0(html_output, "
Executive Recommendations ")
+
+ # Priority recommendations
+ html_output <- paste0(html_output, "
Priority Actions: ")
+
+ if (length(critical_fields) > 0) {
+ html_output <- paste0(html_output,
+ sprintf("Critical attention needed for fields: %s ",
+ paste(critical_fields, collapse = ", ")))
+ }
+
+ if (length(high_priority_fields) > 0) {
+ html_output <- paste0(html_output,
+ sprintf("High priority inspection for fields: %s ",
+ paste(high_priority_fields, collapse = ", ")))
+ }
+
+ if (length(harvest_ready) > 0) {
+ html_output <- paste0(html_output,
+ sprintf("Ready for harvest : %s ",
+ paste(harvest_ready, collapse = ", ")))
+ }
+
+ if (length(approaching_harvest) > 0) {
+ html_output <- paste0(html_output,
+ sprintf("Approaching harvest readiness : %s ",
+ paste(approaching_harvest, collapse = ", ")))
+ }
+
+ # If no specific recommendations, add general one
+ if (length(critical_fields) == 0 && length(high_priority_fields) == 0 &&
+ length(harvest_ready) == 0 && length(approaching_harvest) == 0) {
+ html_output <- paste0(html_output, "No urgent actions required this week. ")
+ }
+
+ html_output <- paste0(html_output, " ")
+
+ # Anomaly notifications
+ if (nrow(anomaly_counts) > 0) {
+ html_output <- paste0(html_output, "
Anomaly Notifications: ")
+
+ for (i in 1:nrow(anomaly_counts)) {
+ html_output <- paste0(html_output,
+ sprintf("%s detected in %d fields ",
+ anomaly_counts$anomaly_type[i], anomaly_counts$count[i]))
+ }
+
+ html_output <- paste0(html_output, " ")
+ }
+
+ # Farm status summary
+ html_output <- paste0(html_output, "
Farm Status Overview: ")
+
+ status_counts <- health_data %>%
+ dplyr::filter(status != "Error") %>%
+ dplyr::group_by(status) %>%
+ dplyr::summarise(count = n())
+
+ for (i in 1:nrow(status_counts)) {
+ html_output <- paste0(html_output,
+ sprintf("%s: %d fields ",
+ status_counts$status[i], status_counts$count[i]))
+ }
+
+ html_output <- paste0(html_output, " ")
+
+ return(html_output)
+
+ }, error = function(e) {
+ safe_log(paste("Error in generate_executive_recommendations:", e$message), "ERROR")
+ return("Error generating recommendations.
")
+ })
+}
+```
+
+`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.
+
+# Executive Dashboard
+
+## Farm Health Status
+
+The map below shows the overall health status of all fields based on current Chlorophyll Index values. This provides a quick overview of which areas of your farm are performing well and which might need intervention.
+
+**How it works:** Field health status is determined by the average Chlorophyll Index (CI) value across each field:
+- **Excellent** (dark green): CI ≥ 5.0
+- **Good** (light green): CI 3.5-4.99
+- **Fair** (yellow): CI 2.0-3.49
+- **Poor** (orange): CI 1.0-1.99
+- **Critical** (red): CI < 1.0
+
+Fields with higher CI values indicate better crop vigor and photosynthetic activity, which typically correlate with healthier plants.
+
+```{r render_ci_continuous_map, echo=FALSE, fig.height=6, fig.width=9, message=FALSE, warning=FALSE}
+# Create CI continuous raster map
+tryCatch({
+ # Create and display the CI raster map with pixel values
+ ci_raster_map <- create_ci_raster_map(
+ ci_raster = CI,
+ field_boundaries = AllPivots0,
+ title = "Chlorophyll Index (CI) - Pixel Values",
+ legend_title = "CI Value"
+ )
+
+ # Print the map
+ print(ci_raster_map)
+}, error = function(e) {
+ safe_log(paste("Error creating CI raster map:", e$message), "ERROR")
+ plot(1, type="n", axes=FALSE, xlab="", ylab="")
+ text(1, 1, "Error creating CI raster map", cex=1.5)
+})
+```
+
+```{r render_field_status_map, echo=FALSE, fig.height=6, fig.width=9, message=FALSE, warning=FALSE}
+# Create field status map
+tryCatch({
+ # Create and display the field status map
+ field_status_map <- create_field_status_map(
+ field_boundaries = AllPivots0,
+ health_data = farm_health_data,
+ attribute = "status",
+ title = "Field Health Status Overview",
+ legend_title = "Health Status"
+ )
+
+ # Print the map
+ print(field_status_map)
+}, error = function(e) {
+ safe_log(paste("Error creating field status map:", e$message), "ERROR")
+ plot(1, type="n", axes=FALSE, xlab="", ylab="")
+ text(1, 1, "Error creating field status map", cex=1.5)
+})
+```
+
+## Management Priorities
+
+This map highlights which fields require priority management attention based on current health indicators and trends. Fields in red require immediate attention, while green fields are performing well with minimal intervention needed.
+
+**How it works:** Priority levels are calculated based on a combination of factors:
+- **Critical Priority** (dark red): Fields with CI < 1.0 or critical health issues
+- **High Priority** (red): Fields with potential weed growth (CI increase > 2)
+- **Medium Priority** (orange): Fields with high internal variability
+- **Low Priority** (light green): Fields with moderate decline in CI
+- **Minimal Priority** (dark green): Stable, healthy fields
+
+The priority algorithm considers both absolute CI values and week-to-week changes to identify fields that need immediate management attention.
+
+```{r render_priority_map, echo=FALSE, fig.height=6, fig.width=9, message=FALSE, warning=FALSE}
+# Create priority management map
+tryCatch({
+ # Fix the priority mapping so red = high priority, green = low priority
+ # Reverse the priority levels before mapping (1=critical becomes 5, 5=minimal becomes 1)
+ farm_health_data$display_priority <- 6 - farm_health_data$priority_level
+
+ # Create and display the priority map with corrected priority levels
+ priority_map <- tm_shape(AllPivots0 %>% dplyr::left_join(farm_health_data, by = "field")) +
+ tm_fill(
+ col = "display_priority",
+ palette = "-RdYlGn", # Now properly oriented: red = high priority, green = low priority
+ breaks = c(0.5, 1.5, 2.5, 3.5, 4.5, 5.5),
+ labels = c("Minimal", "Low", "Medium", "High", "Critical"),
+ title = "Priority Level"
+ ) +
+ tm_borders(col = "black", lwd = 1) +
+ tm_text("field", size = 0.7) +
+ tm_layout(
+ main.title = "Field Management Priority",
+ legend.outside = TRUE,
+ legend.outside.position = "bottom"
+ ) +
+ tm_scale_bar(position = tm_pos_out("right", "bottom"))
+
+ # Print the map
+ print(priority_map)
+}, error = function(e) {
+ safe_log(paste("Error creating priority map:", e$message), "ERROR")
+ plot(1, type="n", axes=FALSE, xlab="", ylab="")
+ text(1, 1, "Error creating priority map", cex=1.5)
+})
+```
+
+\pagebreak
+## Crop Anomaly Detection
+
+The map below highlights potential anomalies in your fields that may require investigation. Areas with sudden changes in CI values could indicate weeding activities, rapid weed growth, or other management interventions.
+
+**How it works:** This map compares current week's CI values with those from the previous week:
+- **Significant Growth** (dark green): CI increase > 2 units (potential weed growth)
+- **Moderate Growth** (light green): CI increase of 1-2 units
+- **Stable** (yellow): CI change between -1 and +1 units
+- **Moderate Decline** (orange): CI decrease of 1-2 units
+- **Significant Decline** (red): CI decrease > 2 units (potential weeding/harvesting activities)
+
+Areas with significant growth (dark green) may indicate rapid weed growth that requires monitoring, while significant declines (red) often indicate recent management activities like weeding or harvesting.
+
+```{r render_anomaly_map, echo=FALSE, fig.height=6, fig.width=9, message=FALSE, warning=FALSE}
+# Create anomaly detection map
+tryCatch({
+ # Create and display the anomaly map
+ anomaly_map <- create_anomaly_map(
+ ci_current = CI,
+ ci_previous = CI_m1,
+ field_boundaries = AllPivots0
+ )
+
+ # Print the map
+ print(anomaly_map)
+}, error = function(e) {
+ safe_log(paste("Error creating anomaly map:", e$message), "ERROR")
+ plot(1, type="n", axes=FALSE, xlab="", ylab="")
+ text(1, 1, "Error creating anomaly map", cex=1.5)
+})
+```
+
+\pagebreak
+## Harvest Planning
+
+This map shows the harvest readiness status of all fields, helping you plan harvest operations and logistics. Fields in dark green are ready for harvest, while those in yellow through red are at earlier growth stages.
+
+**How it works:** Harvest readiness is determined by combining field age and CI values:
+- **Ready for harvest** (dark green): Fields ≥52 weeks old with CI ≥4.0
+- **Approaching harvest** (light green): Fields ≥48 weeks old with CI ≥3.5
+- **Mid-maturity** (yellow): Fields ≥40 weeks old with CI ≥3.0
+- **Growing** (orange): Fields ≥12 weeks old
+- **Early stage** (red): Fields <12 weeks old
+
+This classification helps prioritize harvesting operations and logistical planning by identifying fields that are at optimal maturity for maximum sugar content.
+
+```{r render_harvest_map, echo=FALSE, fig.height=6, fig.width=9, message=FALSE, warning=FALSE}
+# Create harvest planning map
+tryCatch({
+ # Create and display the harvest readiness map
+ harvest_map <- create_field_status_map(
+ field_boundaries = AllPivots0,
+ health_data = farm_health_data,
+ attribute = "harvest_readiness",
+ title = "Harvest Readiness Status",
+ legend_title = "Harvest Status"
+ )
+
+ # Print the map
+ print(harvest_map)
+}, error = function(e) {
+ safe_log(paste("Error creating harvest map:", e$message), "ERROR")
+ plot(1, type="n", axes=FALSE, xlab="", ylab="")
+ text(1, 1, "Error creating harvest map", cex=1.5)
+})
+```
+
+\pagebreak
+## Field Status Summary
+
+The charts below provide an overview of your farm's health and harvest readiness status, showing the distribution of fields across different health categories and maturity stages.
+
+**How the Field Status Chart works:** This bar chart displays the count of fields in each health status category, based on the same CI thresholds described in the Farm Health Status section:
+- **Excellent** (dark green): CI ≥ 5.0
+- **Good** (light green): CI 3.5-4.99
+- **Fair** (yellow): CI 2.0-3.49
+- **Poor** (orange): CI 1.0-1.99
+- **Critical** (red): CI < 1.0
+
+**How the Harvest Readiness Chart works:** This pie chart shows the distribution of fields by harvest readiness, allowing you to see at a glance how many fields are in each stage of development. Fields are categorized based on both age and CI values as described in the Harvest Planning section above.
+
+```{r render_status_charts, echo=FALSE, fig.height=5, fig.width=10, message=FALSE, warning=FALSE}
+# Create field status summary visualization
+tryCatch({
+ # Create field status charts
+ status_chart <- create_summary_stats(farm_health_data)
+
+ # Print the chart
+ print(status_chart)
+
+ # Create a second row with harvest readiness chart
+ harvest_chart <- create_harvest_readiness_chart(farm_health_data)
+
+ # Print the chart
+ print(harvest_chart)
+}, error = function(e) {
+ safe_log(paste("Error creating status summary charts:", e$message), "ERROR")
+ plot(1, type="n", axes=FALSE, xlab="", ylab="")
+ text(1, 1, "Error creating status summary charts", cex=1.5)
+})
+```
+
+## Priority Fields Requiring Attention
+
+The chart below highlights fields that require immediate management attention based on their health scores and anomaly detection. These should be prioritized for field inspections.
+
+**How it works:** This chart shows fields with priority levels 1-3 (critical, high, and medium):
+- Fields are ordered by priority level, with the most critical fields on the left
+- Bar height represents the Chlorophyll Index (CI) value
+- Bar colors indicate priority level: red (critical), orange (high), yellow (medium)
+- Text labels show the detected anomaly type for each field
+
+The table below the chart provides detailed metrics for these priority fields, including CI values, weekly changes, anomaly types, and harvest status. Only fields requiring management attention (priority levels 1-3) are included.
+
+```{r render_priority_fields_chart, echo=FALSE, fig.height=5, fig.width=10, message=FALSE, warning=FALSE}
+# Create priority fields chart
+tryCatch({
+ # Create and display priority fields chart
+ priority_chart <- create_priority_fields_chart(farm_health_data)
+
+ # Print the chart
+ print(priority_chart)
+
+ # Create a table of priority fields
+ priority_table <- farm_health_data %>%
+ dplyr::filter(priority_level <= 3) %>%
+ dplyr::arrange(priority_level, field) %>%
+ dplyr::select(
+ Field = field,
+ Status = status,
+ `CI Value` = mean_ci,
+ `Weekly Change` = ci_change,
+ `Anomaly Type` = anomaly_type,
+ `Age (Weeks)` = age_weeks,
+ `Harvest Status` = harvest_readiness
+ )
+
+ # Display the table if there are priority fields
+ if (nrow(priority_table) > 0) {
+ knitr::kable(priority_table, caption = "Priority Fields Requiring Management Attention")
+ } else {
+ cat("No priority fields requiring immediate attention this week.")
+ }
+
+}, error = function(e) {
+ safe_log(paste("Error creating priority fields chart:", e$message), "ERROR")
+ cat("Error generating priority fields visualization. See log for details.")
+})
+```
+
+\pagebreak
+## Management Recommendations
+
+```{r render_recommendations, echo=FALSE, results='asis', message=FALSE, warning=FALSE}
+# Generate executive recommendations
+tryCatch({
+ # Create and display recommendations
+ recommendations_html <- generate_executive_recommendations(farm_health_data)
+
+ # Print the HTML recommendations
+ cat(recommendations_html)
+}, error = function(e) {
+ safe_log(paste("Error creating recommendations:", e$message), "ERROR")
+ cat("Error generating recommendations. Please see system administrator.
")
+})
+```
+
+## Yield Prediction Overview
+
+This section provides yield predictions for mature fields (over 300 days old) based on their Chlorophyll Index values and growth patterns. These predictions can help with harvest planning and yield forecasting.
+
+```{r render_yield_summary, echo=FALSE, fig.height=5, fig.width=10, message=FALSE, warning=FALSE}
+# Create yield summary
+tryCatch({
+ if (exists("pred_rf_current_season") && nrow(pred_rf_current_season) > 0) {
+ # Calculate total estimated production
+ total_yield <- sum(pred_rf_current_season$predicted_Tcha, na.rm = TRUE)
+
+ # Create summary box
+ cat("")
+ cat("
Yield Summary ")
+ cat("
")
+ cat(sprintf("Total estimated production : %s tonnes/ha ",
+ format(round(total_yield, 0), big.mark=",")))
+ cat(sprintf("Number of harvest-ready fields : %d ",
+ nrow(pred_rf_current_season)))
+ cat(sprintf("Average predicted yield : %s tonnes/ha ",
+ format(round(mean(pred_rf_current_season$predicted_Tcha, na.rm=TRUE), 1), big.mark=",")))
+ cat(" ")
+ cat("
")
+
+ # Display yield prediction table
+ harvest_ready_fields <- pred_rf_current_season %>%
+ dplyr::arrange(desc(predicted_Tcha)) %>%
+ dplyr::select(
+ Field = field,
+ `Sub Field` = sub_field,
+ `Age (Days)` = Age_days,
+ `Cumulative CI` = total_CI,
+ `Predicted Yield (Tonnes/ha)` = predicted_Tcha
+ )
+
+ knitr::kable(harvest_ready_fields,
+ caption = "Predicted Yields for Harvest-Ready Fields",
+ digits = 1)
+ } else {
+ cat("")
+ cat("
Yield Summary ")
+ cat("
No fields currently meet harvest readiness criteria (>300 days) for yield prediction.
")
+ cat("
")
+ }
+}, error = function(e) {
+ safe_log(paste("Error creating yield summary:", e$message), "ERROR")
+ cat("Error generating yield summary. Please see system administrator.
")
+})
+```
+
diff --git a/r_app/experiments/executive_summary/executive_report_utils.R b/r_app/experiments/executive_summary/executive_report_utils.R
new file mode 100644
index 0000000..398249c
--- /dev/null
+++ b/r_app/experiments/executive_summary/executive_report_utils.R
@@ -0,0 +1,437 @@
+# EXECUTIVE REPORT UTILITIES
+# This file contains functions for creating advanced visualizations for the executive summary report
+
+#' Create a velocity and acceleration indicator for CI change
+#'
+#' @param health_data Current farm health data
+#' @param ci_current Current CI raster
+#' @param ci_prev1 CI raster from 1 week ago
+#' @param ci_prev2 CI raster from 2 weeks ago
+#' @param ci_prev3 CI raster from 3 weeks ago
+#' @param field_boundaries Field boundaries spatial data (sf object)
+#' @return A ggplot2 object with velocity and acceleration gauges
+#'
+create_velocity_acceleration_indicator <- function(health_data, ci_current, ci_prev1, ci_prev2, ci_prev3, field_boundaries) {
+ tryCatch({
+ # Calculate farm-wide metrics for multiple weeks
+ mean_ci_current <- mean(health_data$mean_ci, na.rm = TRUE)
+
+ # Calculate previous week metrics
+ # Extract CI values for previous weeks
+ field_ci_metrics <- data.frame(field = character(),
+ week_current = numeric(),
+ week_minus_1 = numeric(),
+ week_minus_2 = numeric(),
+ week_minus_3 = numeric(),
+ stringsAsFactors = FALSE)
+
+ # Process each field
+ fields <- unique(field_boundaries$field)
+ for (field_name in fields) {
+ tryCatch({
+ # Get field boundary
+ field_shape <- field_boundaries %>% dplyr::filter(field == field_name)
+ if (nrow(field_shape) == 0) next
+
+ # Extract CI values for all weeks
+ ci_curr_values <- terra::extract(ci_current, field_shape)
+ ci_prev1_values <- terra::extract(ci_prev1, field_shape)
+ ci_prev2_values <- terra::extract(ci_prev2, field_shape)
+ ci_prev3_values <- terra::extract(ci_prev3, field_shape)
+
+ # Calculate mean CI for each week
+ mean_ci_curr <- mean(ci_curr_values$CI, na.rm = TRUE)
+ mean_ci_prev1 <- mean(ci_prev1_values$CI, na.rm = TRUE)
+ mean_ci_prev2 <- mean(ci_prev2_values$CI, na.rm = TRUE)
+ mean_ci_prev3 <- mean(ci_prev3_values$CI, na.rm = TRUE)
+
+ # Add to metrics table
+ field_ci_metrics <- rbind(field_ci_metrics, data.frame(
+ field = field_name,
+ week_current = mean_ci_curr,
+ week_minus_1 = mean_ci_prev1,
+ week_minus_2 = mean_ci_prev2,
+ week_minus_3 = mean_ci_prev3,
+ stringsAsFactors = FALSE
+ ))
+ }, error = function(e) {
+ message(paste("Error processing field", field_name, "for velocity indicator:", e$message))
+ })
+ }
+
+ # Calculate farm-wide averages
+ farm_avg <- colMeans(field_ci_metrics[, c("week_current", "week_minus_1", "week_minus_2", "week_minus_3")], na.rm = TRUE)
+
+ # Calculate velocity (rate of change) - current week compared to last week
+ velocity <- farm_avg["week_current"] - farm_avg["week_minus_1"]
+
+ # Calculate previous velocity (last week compared to two weeks ago)
+ prev_velocity <- farm_avg["week_minus_1"] - farm_avg["week_minus_2"]
+
+ # Calculate acceleration (change in velocity)
+ acceleration <- velocity - prev_velocity
+
+ # Prepare data for velocity gauge
+ velocity_data <- data.frame(
+ label = "Weekly CI Change",
+ value = velocity
+ )
+
+ # Prepare data for acceleration gauge
+ acceleration_data <- data.frame(
+ label = "Change Acceleration",
+ value = acceleration
+ )
+
+ # Create velocity trend data
+ trend_data <- data.frame(
+ week = c(-3, -2, -1, 0),
+ ci_value = c(farm_avg["week_minus_3"], farm_avg["week_minus_2"],
+ farm_avg["week_minus_1"], farm_avg["week_current"])
+ )
+
+ # Create layout grid for the visualizations
+ layout_matrix <- matrix(c(1, 1, 2, 2, 3, 3), nrow = 2, byrow = TRUE)
+
+ # Create velocity gauge
+ velocity_gauge <- ggplot2::ggplot(velocity_data, ggplot2::aes(x = 0, y = 0)) +
+ ggplot2::geom_arc_bar(ggplot2::aes(
+ x0 = 0, y0 = 0,
+ r0 = 0.5, r = 1,
+ start = -pi/2, end = pi/2,
+ fill = "background"
+ ), fill = "#f0f0f0") +
+ ggplot2::geom_arc_bar(ggplot2::aes(
+ x0 = 0, y0 = 0,
+ r0 = 0.5, r = 1,
+ start = -pi/2,
+ end = -pi/2 + (pi * (0.5 + (velocity / 2))), # Scale to range -1 to +1
+ fill = "velocity"
+ ), fill = ifelse(velocity >= 0, "#1a9850", "#d73027")) +
+ ggplot2::geom_text(ggplot2::aes(label = sprintf("%.2f", velocity)),
+ size = 8, fontface = "bold") +
+ ggplot2::geom_text(ggplot2::aes(label = "Velocity"), y = -0.3, size = 4) +
+ ggplot2::coord_fixed() +
+ ggplot2::theme_void() +
+ ggplot2::scale_fill_manual(values = c("background" = "#f0f0f0", "velocity" = "steelblue"),
+ guide = "none") +
+ ggplot2::annotate("text", x = -0.85, y = 0, label = "Declining",
+ angle = 90, size = 3.5) +
+ ggplot2::annotate("text", x = 0.85, y = 0, label = "Improving",
+ angle = -90, size = 3.5) +
+ ggplot2::labs(title = "Farm Health Velocity",
+ subtitle = "CI change per week") +
+ ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5, size = 14, face = "bold"),
+ plot.subtitle = ggplot2::element_text(hjust = 0.5, size = 12))
+
+ # Create acceleration gauge
+ acceleration_gauge <- ggplot2::ggplot(acceleration_data, ggplot2::aes(x = 0, y = 0)) +
+ ggplot2::geom_arc_bar(ggplot2::aes(
+ x0 = 0, y0 = 0,
+ r0 = 0.5, r = 1,
+ start = -pi/2, end = pi/2,
+ fill = "background"
+ ), fill = "#f0f0f0") +
+ ggplot2::geom_arc_bar(ggplot2::aes(
+ x0 = 0, y0 = 0,
+ r0 = 0.5, r = 1,
+ start = -pi/2,
+ end = -pi/2 + (pi * (0.5 + (acceleration / 1))), # Scale to range -0.5 to +0.5
+ fill = "acceleration"
+ ), fill = ifelse(acceleration >= 0, "#1a9850", "#d73027")) +
+ ggplot2::geom_text(ggplot2::aes(label = sprintf("%.2f", acceleration)),
+ size = 8, fontface = "bold") +
+ ggplot2::geom_text(ggplot2::aes(label = "Acceleration"), y = -0.3, size = 4) +
+ ggplot2::coord_fixed() +
+ ggplot2::theme_void() +
+ ggplot2::scale_fill_manual(values = c("background" = "#f0f0f0", "acceleration" = "steelblue"),
+ guide = "none") +
+ ggplot2::annotate("text", x = -0.85, y = 0, label = "Slowing",
+ angle = 90, size = 3.5) +
+ ggplot2::annotate("text", x = 0.85, y = 0, label = "Accelerating",
+ angle = -90, size = 3.5) +
+ ggplot2::labs(title = "Change Acceleration",
+ subtitle = "Increasing or decreasing trend") +
+ ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5, size = 14, face = "bold"),
+ plot.subtitle = ggplot2::element_text(hjust = 0.5, size = 12))
+
+ # Create trend chart
+ trend_chart <- ggplot2::ggplot(trend_data, ggplot2::aes(x = week, y = ci_value)) +
+ ggplot2::geom_line(size = 1.5, color = "steelblue") +
+ ggplot2::geom_point(size = 3, color = "steelblue") +
+ ggplot2::geom_hline(yintercept = trend_data$ci_value[1], linetype = "dashed", color = "gray50") +
+ ggplot2::labs(
+ title = "4-Week CI Trend",
+ x = "Weeks from current",
+ y = "Average CI Value"
+ ) +
+ ggplot2::theme_minimal() +
+ ggplot2::scale_x_continuous(breaks = c(-3, -2, -1, 0))
+
+ # Create table of top velocity changes
+ field_ci_metrics$velocity <- field_ci_metrics$week_current - field_ci_metrics$week_minus_1
+ top_velocity_fields <- field_ci_metrics %>%
+ dplyr::arrange(desc(abs(velocity))) %>%
+ dplyr::slice_head(n = 5) %>%
+ dplyr::select(field, velocity) %>%
+ dplyr::mutate(direction = ifelse(velocity >= 0, "Improving", "Declining"))
+
+ # Combine into multi-panel figure
+ main_plot <- gridExtra::grid.arrange(
+ gridExtra::grid.arrange(velocity_gauge, acceleration_gauge, ncol = 2),
+ trend_chart,
+ heights = c(1.5, 1),
+ nrow = 2
+ )
+
+ return(main_plot)
+
+ }, error = function(e) {
+ message(paste("Error in create_velocity_acceleration_indicator:", e$message))
+ return(ggplot2::ggplot() +
+ ggplot2::annotate("text", x = 0, y = 0, label = paste("Error creating velocity indicator:", e$message)) +
+ ggplot2::theme_void())
+ })
+}
+
+#' 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)
+}
\ No newline at end of file
diff --git a/r_app/experiments/max_ci_field.r b/r_app/experiments/max_ci_field.r
new file mode 100644
index 0000000..9a72636
--- /dev/null
+++ b/r_app/experiments/max_ci_field.r
@@ -0,0 +1,54 @@
+library(sf)
+library(terra)
+library(tidyverse)
+library(lubridate)
+library(exactextractr)
+library(readxl)
+
+
+# Vang alle command line argumenten op
+args <- commandArgs(trailingOnly = TRUE)
+
+# Controleer of er ten minste één argument is doorgegeven
+if (length(args) == 0) {
+ stop("Geen argumenten doorgegeven aan het script")
+}
+
+# Converteer het eerste argument naar een numerieke waarde
+end_date <- as.Date(args[1])
+if (is.na(end_date)) {
+ end_date <- lubridate::dmy("28-08-2024")
+}
+
+offset <- as.numeric(args[2])
+# Controleer of weeks_ago een geldig getal is
+if (is.na(offset)) {
+ # stop("Het argument is geen geldig getal")
+ offset <- 7
+}
+
+week <- week(end_date)
+# Converteer het tweede argument naar een string waarde
+project_dir <- as.character(args[3])
+
+# Controleer of data_dir een geldige waarde is
+if (!is.character(project_dir)) {
+ project_dir <- "chemba"
+}
+new_project_question = FALSE
+
+source("parameters_project.R")
+source("ci_extraction_utils.R")
+
+
+
+raster_files <- list.files(merged_final,full.names = T, pattern = ".tif")
+
+# Load all raster files
+rasters <- lapply(raster_files, function(file) {
+ r <- rast(file)
+ r[[which(names(r) == "CI")]] # Select the CI band
+})
+# Calculate the maximum CI per pixel
+max_ci_raster <- do.call(terra::app, c(rasters, fun = mean, na.rm = TRUE))
+plot(max_ci_raster)
diff --git a/r_app/experiments/mosaic_creation_fixed.R b/r_app/experiments/mosaic_creation_fixed.R
new file mode 100644
index 0000000..e69de29
diff --git a/r_app/experiments/optimal_ci_analysis.R b/r_app/experiments/optimal_ci_analysis.R
new file mode 100644
index 0000000..d353274
--- /dev/null
+++ b/r_app/experiments/optimal_ci_analysis.R
@@ -0,0 +1,421 @@
+# Optimal CI Analysis - Day 30 CI values with quadratic fitting
+# Author: SmartCane Analysis Team
+# Date: 2025-06-12
+
+# Load required libraries
+library(ggplot2)
+library(dplyr)
+library(readr)
+
+# Set file path
+rds_file_path <- "C:/Users/timon/Resilience BV/4020 SCane ESA DEMO - Documenten/General/4020 SCDEMO Team/4020 TechnicalData/WP3/smartcane/laravel_app/storage/app/chemba/Data/extracted_ci/cumulative_vals/All_pivots_Cumulative_CI_quadrant_year_v2.rds"
+
+# Check if file exists
+if (!file.exists(rds_file_path)) {
+ stop("RDS file not found at specified path: ", rds_file_path)
+}
+
+# Load the data
+cat("Loading RDS file...\n")
+ci_data <- readRDS(rds_file_path)
+
+# Display structure of the data to understand it better
+cat("Data structure:\n")
+str(ci_data)
+cat("\nFirst few rows:\n")
+head(ci_data)
+cat("\nColumn names:\n")
+print(colnames(ci_data))
+
+# Filter data based on requirements
+cat("\nApplying data filters...\n")
+
+# 1. Filter out models that don't reach at least DOY 300
+model_doy_max <- ci_data %>%
+ group_by(model) %>%
+ summarise(max_doy = max(DOY, na.rm = TRUE), .groups = 'drop')
+
+valid_models <- model_doy_max %>%
+ filter(max_doy >= 300) %>%
+ pull(model)
+
+cat(paste("Models before filtering:", n_distinct(ci_data$model), "\n"))
+cat(paste("Models reaching at least DOY 300:", length(valid_models), "\n"))
+
+ci_data <- ci_data %>%
+ filter(model %in% valid_models)
+
+# 2. Apply IQR filtering per DOY (remove outliers outside IQR)
+cat("Applying IQR filtering per DOY...\n")
+original_rows <- nrow(ci_data)
+
+ci_data <- ci_data %>%
+ group_by(DOY) %>%
+ mutate(
+ Q1 = quantile(FitData, 0.25, na.rm = TRUE),
+ Q3 = quantile(FitData, 0.75, na.rm = TRUE),
+ IQR = Q3 - Q1,
+ lower_bound = Q1 - 1.5 * IQR,
+ upper_bound = Q3 + 1.5 * IQR
+ ) %>%
+ filter(FitData >= lower_bound & FitData <= upper_bound) %>%
+ select(-Q1, -Q3, -IQR, -lower_bound, -upper_bound) %>%
+ ungroup()
+
+filtered_rows <- nrow(ci_data)
+cat(paste("Rows before IQR filtering:", original_rows, "\n"))
+cat(paste("Rows after IQR filtering:", filtered_rows, "\n"))
+cat(paste("Removed", original_rows - filtered_rows, "outliers (",
+ round(100 * (original_rows - filtered_rows) / original_rows, 1), "%)\n"))
+
+# Check what day values are available after filtering
+if ("DOY" %in% colnames(ci_data)) {
+ cat("\nUnique DOY values after filtering:\n")
+ print(sort(unique(ci_data$DOY)))
+} else {
+ cat("\nNo 'DOY' column found. Available columns:\n")
+ print(colnames(ci_data))
+}
+
+# Extract CI values at day 30 for each field
+cat("\nExtracting day 30 CI values...\n")
+
+# Set column names based on known structure
+day_col <- "DOY"
+ci_col <- "FitData"
+field_col <- "model"
+
+# Try different possible field column name combinations
+if ("field" %in% colnames(ci_data)) {
+ field_col <- "field"
+} else if ("Field" %in% colnames(ci_data)) {
+ field_col <- "Field"
+} else if ("pivot" %in% colnames(ci_data)) {
+ field_col <- "pivot"
+} else if ("Pivot" %in% colnames(ci_data)) {
+ field_col <- "Pivot"
+} else if ("field_id" %in% colnames(ci_data)) {
+ field_col <- "field_id"
+} else if ("Field_ID" %in% colnames(ci_data)) {
+ field_col <- "Field_ID"
+}
+
+# Check if we found the required columns
+if (!("DOY" %in% colnames(ci_data))) {
+ stop("DOY column not found in data")
+}
+if (!("FitData" %in% colnames(ci_data))) {
+ stop("FitData column not found in data")
+}
+if (is.null(field_col)) {
+ cat("Could not automatically identify field column. Please check column names.\n")
+ cat("Available columns: ", paste(colnames(ci_data), collapse = ", "), "\n")
+ stop("Manual field column identification required")
+}
+
+cat(paste("Using columns - DOY:", day_col, "Field:", field_col, "FitData:", ci_col, "\n"))
+
+# Extract day 30 data
+day_30_data <- ci_data %>%
+ filter(DOY %% 30 == 0) %>%
+ select(field = !!sym(field_col), ci = !!sym(ci_col), DOY) %>%
+ na.omit() %>%
+ arrange(field)
+
+cat(paste("Found", nrow(day_30_data), "fields with day 30 CI values\n"))
+
+if (nrow(day_30_data) == 0) {
+ stop("No data found for day 30. Check if day 30 exists in the dataset.")
+}
+
+# Display summary of day 30 data
+cat("\nSummary of day 30 CI values:\n")
+print(summary(day_30_data))
+
+# Add field index for plotting (assuming fields represent some spatial or sequential order)
+#day_30_data$field_index <- 1:nrow(day_30_data)
+
+# Create scatter plot
+p1 <- ggplot(day_30_data, aes(x = DOY, y = ci)) +
+ geom_point(color = "blue", size = 3, alpha = 0.7) +
+ labs(
+ title = "CI Values at Day 30 for All Fields",
+ x = "Day of Year (DOY)",
+ y = "CI Value",
+ subtitle = paste("Total fields:", nrow(day_30_data))
+ ) +
+ theme_minimal() +
+ theme(
+ plot.title = element_text(size = 14, face = "bold"),
+ axis.title = element_text(size = 12),
+ axis.text = element_text(size = 10)
+ )
+
+print(p1)
+
+# Try multiple curve fitting approaches
+cat("\nTrying different curve fitting approaches...\n")
+
+# Aggregate data by DOY (take mean CI for each DOY to reduce noise)
+aggregated_data <- day_30_data %>%
+ group_by(DOY) %>%
+ summarise(
+ mean_ci = mean(ci, na.rm = TRUE),
+ median_ci = median(ci, na.rm = TRUE),
+ count = n(),
+ .groups = 'drop'
+ ) %>%
+ filter(count >= 5) # Only keep DOY values with sufficient data points
+
+cat(paste("Aggregated to", nrow(aggregated_data), "DOY points with sufficient data\n"))
+
+# Create prediction data for smooth curves
+x_smooth <- seq(min(aggregated_data$DOY), max(aggregated_data$DOY), length.out = 100)
+
+# 1. Quadratic model (as requested)
+cat("\n1. Fitting quadratic model...\n")
+quad_model <- lm(mean_ci ~ poly(DOY, 2, raw = TRUE), data = aggregated_data)
+quad_pred <- predict(quad_model, newdata = data.frame(DOY = x_smooth))
+quad_r2 <- summary(quad_model)$r.squared
+cat(paste("Quadratic R² =", round(quad_r2, 3), "\n"))
+
+# 2. Logistic growth model: y = K / (1 + exp(-r*(x-x0))) - biologically realistic
+cat("\n2. Fitting logistic growth model...\n")
+tryCatch({
+ # Estimate starting values
+ K_start <- max(aggregated_data$mean_ci) * 1.1 # Carrying capacity
+ r_start <- 0.05 # Growth rate
+ x0_start <- mean(aggregated_data$DOY) # Inflection point
+
+ logistic_model <- nls(mean_ci ~ K / (1 + exp(-r * (DOY - x0))),
+ data = aggregated_data,
+ start = list(K = K_start, r = r_start, x0 = x0_start),
+ control = nls.control(maxiter = 1000))
+
+ logistic_pred <- predict(logistic_model, newdata = data.frame(DOY = x_smooth))
+ logistic_r2 <- 1 - sum(residuals(logistic_model)^2) / sum((aggregated_data$mean_ci - mean(aggregated_data$mean_ci))^2)
+ cat(paste("Logistic growth R² =", round(logistic_r2, 3), "\n"))
+}, error = function(e) {
+ cat("Logistic growth model failed to converge\n")
+ logistic_model <- NULL
+ logistic_pred <- NULL
+ logistic_r2 <- NA
+})
+
+# 3. Beta function model: good for crop growth curves with clear peak
+cat("\n3. Fitting Beta function model...\n")
+tryCatch({
+ # Normalize DOY to 0-1 range for Beta function
+ doy_min <- min(aggregated_data$DOY)
+ doy_max <- max(aggregated_data$DOY)
+ aggregated_data$doy_norm <- (aggregated_data$DOY - doy_min) / (doy_max - doy_min)
+
+ # Beta function: y = a * (x^(p-1)) * ((1-x)^(q-1)) + c
+ beta_model <- nls(mean_ci ~ a * (doy_norm^(p-1)) * ((1-doy_norm)^(q-1)) + c,
+ data = aggregated_data,
+ start = list(a = max(aggregated_data$mean_ci) * 20, p = 2, q = 3, c = min(aggregated_data$mean_ci)),
+ control = nls.control(maxiter = 1000))
+
+ # Predict on normalized scale then convert back
+ x_smooth_norm <- (x_smooth - doy_min) / (doy_max - doy_min)
+ beta_pred <- predict(beta_model, newdata = data.frame(doy_norm = x_smooth_norm))
+ beta_r2 <- 1 - sum(residuals(beta_model)^2) / sum((aggregated_data$mean_ci - mean(aggregated_data$mean_ci))^2)
+ cat(paste("Beta function R² =", round(beta_r2, 3), "\n"))
+}, error = function(e) {
+ cat("Beta function model failed to converge\n")
+ beta_model <- NULL
+ beta_pred <- NULL
+ beta_r2 <- NA
+})
+
+# 4. Gaussian (normal) curve: good for symmetric growth patterns
+cat("\n4. Fitting Gaussian curve...\n")
+tryCatch({
+ # Gaussian: y = a * exp(-((x-mu)^2)/(2*sigma^2)) + c
+ gaussian_model <- nls(mean_ci ~ a * exp(-((DOY - mu)^2)/(2 * sigma^2)) + c,
+ data = aggregated_data,
+ start = list(a = max(aggregated_data$mean_ci),
+ mu = aggregated_data$DOY[which.max(aggregated_data$mean_ci)],
+ sigma = 50,
+ c = min(aggregated_data$mean_ci)),
+ control = nls.control(maxiter = 1000))
+
+ gaussian_pred <- predict(gaussian_model, newdata = data.frame(DOY = x_smooth))
+ gaussian_r2 <- 1 - sum(residuals(gaussian_model)^2) / sum((aggregated_data$mean_ci - mean(aggregated_data$mean_ci))^2)
+ cat(paste("Gaussian R² =", round(gaussian_r2, 3), "\n"))
+}, error = function(e) {
+ cat("Gaussian model failed to converge\n")
+ gaussian_model <- NULL
+ gaussian_pred <- NULL
+ gaussian_r2 <- NA
+})
+
+# 5. LOESS (local regression) - for comparison
+cat("\n5. Fitting LOESS smoothing...\n")
+loess_model <- loess(mean_ci ~ DOY, data = aggregated_data, span = 0.5)
+loess_pred <- predict(loess_model, newdata = data.frame(DOY = x_smooth))
+loess_r2 <- 1 - sum(residuals(loess_model)^2) / sum((aggregated_data$mean_ci - mean(aggregated_data$mean_ci))^2)
+cat(paste("LOESS R² =", round(loess_r2, 3), "\n"))
+
+# Calculate confidence intervals for both models
+cat("\nCalculating confidence intervals...\n")
+
+# Function to calculate confidence intervals using residual-based method
+calculate_ci <- function(model, data, newdata, alpha = 0.5) {
+ # Get model predictions
+ pred_vals <- predict(model, newdata = newdata)
+
+ # For parametric models (lm), use built-in prediction intervals
+ if(class(model)[1] == "lm") {
+ pred_intervals <- predict(model, newdata = newdata, interval = "confidence", level = 1 - alpha)
+ return(list(
+ lower = pred_intervals[, "lwr"],
+ upper = pred_intervals[, "upr"]
+ ))
+ }
+
+ # For LOESS, calculate confidence intervals using residual bootstrap
+ if(class(model)[1] == "loess") {
+ # Calculate residuals from the original model
+ fitted_vals <- fitted(model)
+ residuals_vals <- residuals(model)
+ residual_sd <- sd(residuals_vals, na.rm = TRUE)
+
+ # Use normal approximation for confidence intervals
+ # For 50% CI, use 67% quantile (approximately 0.67 standard deviations)
+ margin <- qnorm(1 - alpha/2) * residual_sd
+
+ return(list(
+ lower = pred_vals - margin,
+ upper = pred_vals + margin
+ ))
+ }
+
+ # Fallback method
+ residual_sd <- sd(residuals(model), na.rm = TRUE)
+ margin <- qnorm(1 - alpha/2) * residual_sd
+ return(list(
+ lower = pred_vals - margin,
+ upper = pred_vals + margin
+ ))
+}# Calculate CIs for quadratic model using aggregated data (same as model fitting)
+quad_ci <- calculate_ci(quad_model, aggregated_data, data.frame(DOY = x_smooth))
+
+# Calculate CIs for LOESS model using aggregated data (same as model fitting)
+loess_ci <- calculate_ci(loess_model, aggregated_data, data.frame(DOY = x_smooth))
+
+# Create separate plots for LOESS and Quadratic models
+cat("\nCreating LOESS plot with confidence intervals...\n")
+
+# LOESS plot
+p_loess <- ggplot(day_30_data, aes(x = DOY, y = ci)) +
+ geom_point(color = "lightblue", size = 1.5, alpha = 0.4) +
+ geom_point(data = aggregated_data, aes(x = DOY, y = mean_ci),
+ color = "darkblue", size = 3, alpha = 0.8) +
+ geom_ribbon(data = data.frame(DOY = x_smooth,
+ lower = loess_ci$lower,
+ upper = loess_ci$upper),
+ aes(x = DOY, ymin = lower, ymax = upper),
+ alpha = 0.3, fill = "purple", inherit.aes = FALSE) +
+ geom_line(data = data.frame(DOY = x_smooth, loess = loess_pred),
+ aes(x = DOY, y = loess),
+ color = "purple", size = 1.5) +
+ labs(
+ title = "LOESS Model - CI Values Over Growing Season",
+ x = "Day of Year (DOY)",
+ y = "CI Value",
+ subtitle = paste("LOESS R² =", round(loess_r2, 3), "| 50% Confidence Interval")
+ ) +
+ theme_minimal() +
+ theme(
+ plot.title = element_text(size = 14, face = "bold"),
+ axis.title = element_text(size = 12),
+ axis.text = element_text(size = 10)
+ )
+
+# Find optimal point for LOESS
+loess_max_idx <- which.max(loess_pred)
+loess_optimal_doy <- x_smooth[loess_max_idx]
+loess_optimal_ci <- loess_pred[loess_max_idx]
+
+p_loess <- p_loess +
+ geom_point(aes(x = loess_optimal_doy, y = loess_optimal_ci),
+ color = "red", size = 5, shape = 8) +
+ annotate("text",
+ x = loess_optimal_doy + 30,
+ y = loess_optimal_ci,
+ label = paste("Optimal: DOY", round(loess_optimal_doy, 1), "\nCI =", round(loess_optimal_ci, 3)),
+ color = "red", size = 4, fontface = "bold")
+
+print(p_loess)
+
+cat("\nCreating Quadratic plot with confidence intervals...\n")
+
+# Quadratic plot
+p_quadratic <- ggplot(day_30_data, aes(x = DOY, y = ci)) +
+ geom_point(color = "lightcoral", size = 1.5, alpha = 0.4) +
+ geom_point(data = aggregated_data, aes(x = DOY, y = mean_ci),
+ color = "darkred", size = 3, alpha = 0.8) +
+ geom_ribbon(data = data.frame(DOY = x_smooth,
+ lower = quad_ci$lower,
+ upper = quad_ci$upper),
+ aes(x = DOY, ymin = lower, ymax = upper),
+ alpha = 0.3, fill = "red", inherit.aes = FALSE) +
+ geom_line(data = data.frame(DOY = x_smooth, quadratic = quad_pred),
+ aes(x = DOY, y = quadratic),
+ color = "red", size = 1.5) +
+ labs(
+ title = "Quadratic Model - CI Values Over Growing Season",
+ x = "Day of Year (DOY)",
+ y = "CI Value",
+ subtitle = paste("Quadratic R² =", round(quad_r2, 3), "| 50% Confidence Interval")
+ ) +
+ theme_minimal() +
+ theme(
+ plot.title = element_text(size = 14, face = "bold"),
+ axis.title = element_text(size = 12),
+ axis.text = element_text(size = 10)
+ )
+
+# Find optimal point for Quadratic
+quad_coeffs <- coef(quad_model)
+a <- quad_coeffs[3]
+b <- quad_coeffs[2]
+
+if(a != 0) {
+ quad_optimal_doy <- -b / (2*a)
+ doy_range <- range(aggregated_data$DOY)
+ if(quad_optimal_doy >= doy_range[1] && quad_optimal_doy <= doy_range[2]) {
+ quad_optimal_ci <- predict(quad_model, newdata = data.frame(DOY = quad_optimal_doy))
+ } else {
+ quad_optimal_doy <- x_smooth[which.max(quad_pred)]
+ quad_optimal_ci <- max(quad_pred)
+ }
+} else {
+ quad_optimal_doy <- x_smooth[which.max(quad_pred)]
+ quad_optimal_ci <- max(quad_pred)
+}
+
+p_quadratic <- p_quadratic +
+ geom_point(aes(x = quad_optimal_doy, y = quad_optimal_ci),
+ color = "darkred", size = 5, shape = 8) +
+ annotate("text",
+ x = quad_optimal_doy + 30,
+ y = quad_optimal_ci,
+ label = paste("Optimal: DOY", round(quad_optimal_doy, 1), "\nCI =", round(quad_optimal_ci, 3)),
+ color = "darkred", size = 4, fontface = "bold")
+
+print(p_quadratic)
+print(p_loess)
+
+# Print results summary
+cat("\n=== RESULTS SUMMARY ===\n")
+cat(paste("LOESS Model - R² =", round(loess_r2, 3), "\n"))
+cat(paste(" Optimal DOY:", round(loess_optimal_doy, 1), "\n"))
+cat(paste(" Optimal CI:", round(loess_optimal_ci, 4), "\n\n"))
+
+cat(paste("Quadratic Model - R² =", round(quad_r2, 3), "\n"))
+cat(paste(" Optimal DOY:", round(quad_optimal_doy, 1), "\n"))
+cat(paste(" Optimal CI:", round(quad_optimal_ci, 4), "\n"))
+
+
diff --git a/r_app/experiments/pivot.geojson b/r_app/experiments/pivot.geojson
new file mode 100644
index 0000000..a7d9a46
--- /dev/null
+++ b/r_app/experiments/pivot.geojson
@@ -0,0 +1,159 @@
+{
+"type": "FeatureCollection",
+"name": "pivot",
+"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
+"features": [
+{ "type": "Feature", "properties": { "field": "6.2", "sub_field": "6.2B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.933686532028823, -17.34741850738671 ], [ 34.936458268544008, -17.344213063126752 ], [ 34.936487787462688, -17.34423678523423 ], [ 34.936654047751475, -17.344385275553996 ], [ 34.936811988565154, -17.344541918958814 ], [ 34.936961177001955, -17.344706286119994 ], [ 34.93710120414535, -17.344877926538839 ], [ 34.937231686184816, -17.345056369781194 ], [ 34.937352265468007, -17.345241126766645 ], [ 34.937462611481052, -17.345431691108853 ], [ 34.937562421754727, -17.345627540503298 ], [ 34.937651422693591, -17.345828138158684 ], [ 34.937729370326288, -17.346032934268045 ], [ 34.937796050974356, -17.346241367515468 ], [ 34.937851281838412, -17.34645286661458 ], [ 34.937894911499427, -17.346666851874129 ], [ 34.93792682033417, -17.346882736786782 ], [ 34.937946920843501, -17.347099929636563 ], [ 34.937955157892617, -17.347317835120631 ], [ 34.937951508862682, -17.347535855980816 ], [ 34.937935983713146, -17.347753394640662 ], [ 34.937908624955057, -17.347969854843281 ], [ 34.937869507534906, -17.348184643285634 ], [ 34.937818738629701, -17.348397171244734 ], [ 34.937756457353686, -17.348606856191328 ], [ 34.937682834377377, -17.348813123386648 ], [ 34.937598071460293, -17.349015407457767 ], [ 34.937502400898275, -17.349213153947368 ], [ 34.937396084887148, -17.349405820833582 ], [ 34.937279414804458, -17.349592880015777 ], [ 34.93715271041102, -17.349773818762202 ], [ 34.937016318974827, -17.349948141115476 ], [ 34.936891437505039, -17.35009147006711 ], [ 34.933686532028823, -17.34741850738671 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "5.2", "sub_field": "5.2B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.892982579947684, -17.30260313717654 ], [ 34.8969126991338, -17.302747852699646 ], [ 34.896848733623173, -17.306522026027363 ], [ 34.896761992373065, -17.306520666974571 ], [ 34.89655566821915, -17.306507035570725 ], [ 34.896350369530914, -17.306483049995649 ], [ 34.896146659052505, -17.306448775992763 ], [ 34.895945095174199, -17.306404307507115 ], [ 34.895746230401478, -17.306349766427822 ], [ 34.895550609840363, -17.306285302254011 ], [ 34.895358769702952, -17.306211091684883 ], [ 34.895171235837296, -17.306127338135401 ], [ 34.894988522285892, -17.306034271178618 ], [ 34.894811129876366, -17.305932145916291 ], [ 34.894639544848609, -17.305821242279585 ], [ 34.894474237521642, -17.3057018642616 ], [ 34.894315661004462, -17.305574339084064 ], [ 34.894164249953917, -17.305439016300223 ], [ 34.89402041938321, -17.305296266836596 ], [ 34.89388456352436, -17.305146481976095 ], [ 34.893757054747589, -17.304990072285385 ], [ 34.893638242540632, -17.304827466489353 ], [ 34.893528452551024, -17.304659110295812 ], [ 34.89342798569345, -17.304485465173695 ], [ 34.893337117325196, -17.304307007087981 ], [ 34.893256096491513, -17.304124225194958 ], [ 34.893185145243294, -17.30393762050133 ], [ 34.893124458028609, -17.303747704490792 ], [ 34.893074201160012, -17.303554997721992 ], [ 34.893034512359016, -17.303360028401542 ], [ 34.893005500378912, -17.303163330936176 ], [ 34.892987244707008, -17.302965444467794 ], [ 34.892979795347131, -17.302766911395732 ], [ 34.892982579947684, -17.30260313717654 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "5.3", "sub_field": "5.3B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.887065228066767, -17.304559537671949 ], [ 34.887585923201613, -17.308341731698718 ], [ 34.887518594228453, -17.308350893542517 ], [ 34.887312586440835, -17.308368425006687 ], [ 34.887105905758595, -17.30837557560691 ], [ 34.886899118713622, -17.308372325739303 ], [ 34.886692792129786, -17.308358684308679 ], [ 34.886487491568985, -17.308334688704125 ], [ 34.88628377978047, -17.308300404696595 ], [ 34.886082215158112, -17.308255926258568 ], [ 34.885883350209426, -17.308201375306449 ], [ 34.885687730040928, -17.308136901366442 ], [ 34.885495890863602, -17.308062681164554 ], [ 34.885308358522892, -17.307978918142162 ], [ 34.885125647057059, -17.30788584189828 ], [ 34.884948257288038, -17.307783707560155 ], [ 34.884776675448421, -17.307672795083811 ], [ 34.884611371848493, -17.307553408486619 ], [ 34.884452799587045, -17.307425875013813 ], [ 34.88430139330918, -17.307290544241372 ], [ 34.884157568015056, -17.307147787117731 ], [ 34.884021717922174, -17.306997994946784 ], [ 34.883894215384913, -17.306841578315254 ], [ 34.883775409873941, -17.30667896596702 ], [ 34.88366562701836, -17.306510603627871 ], [ 34.8835651677133, -17.306336952783568 ], [ 34.883474307295344, -17.306158489414777 ], [ 34.883393294787901, -17.305975702692255 ], [ 34.883322352219061, -17.305789093635923 ], [ 34.883261674013042, -17.305599173741417 ], [ 34.883211426457713, -17.305406463578016 ], [ 34.883171747249051, -17.305211491361643 ], [ 34.883142745114029, -17.305014791506924 ], [ 34.883138407553353, -17.304967747142655 ], [ 34.887065228066767, -17.304559537671949 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "5.1", "sub_field": "5.1D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.893567231271753, -17.309696371939275 ], [ 34.897496240357995, -17.309278976679899 ], [ 34.897513960068423, -17.309470988039859 ], [ 34.897521415315232, -17.309669520314646 ], [ 34.897518043544963, -17.309868155299348 ], [ 34.897503853964437, -17.310066348552152 ], [ 34.897478885431291, -17.310263556840273 ], [ 34.897443206347809, -17.310459239628845 ], [ 34.897396914473845, -17.310652860562591 ], [ 34.897340136659309, -17.310843888935889 ], [ 34.897273028496706, -17.311031801147418 ], [ 34.897195773895142, -17.311216082135513 ], [ 34.897108584576472, -17.311396226789807 ], [ 34.89701169949538, -17.311571741335957 ], [ 34.896905384184613, -17.311742144689092 ], [ 34.896789930027452, -17.311906969772526 ], [ 34.896665653459273, -17.312065764798231 ], [ 34.896532895100407, -17.312218094505198 ], [ 34.896392018822652, -17.312363541352671 ], [ 34.896243410752028, -17.312501706664793 ], [ 34.896087478210511, -17.312632211723482 ], [ 34.895924648599603, -17.312754698806646 ], [ 34.895755368228862, -17.312868832168913 ], [ 34.895580101092513, -17.312974298962004 ], [ 34.895399327597637, -17.313070810092483 ], [ 34.89521354324723, -17.313158101014249 ], [ 34.895023257281863, -17.313235932453882 ], [ 34.894828991283774, -17.313304091066559 ], [ 34.894631277746932, -17.313362390021041 ], [ 34.89443065861731, -17.313410669511875 ], [ 34.894227683807067, -17.313448797197559 ], [ 34.894135431768206, -17.313461353424515 ], [ 34.893567231271753, -17.309696371939275 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.5", "sub_field": "4.5D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.893769553711472, -17.316586264213559 ], [ 34.892115685045027, -17.314291416092694 ], [ 34.892195700790658, -17.314237469887129 ], [ 34.892325067135602, -17.314159627332327 ], [ 34.892458497355165, -17.314088394803694 ], [ 34.892595625734742, -17.314023967536052 ], [ 34.892736076425152, -17.313966522111834 ], [ 34.892879464472585, -17.313916215977194 ], [ 34.893025396873647, -17.313873187010579 ], [ 34.893173473652432, -17.313837553144793 ], [ 34.893323288956616, -17.313809412043945 ], [ 34.893474432169747, -17.313788840835716 ], [ 34.893626489036507, -17.3137758959001 ], [ 34.893779042797945, -17.313770612714848 ], [ 34.89393167533364, -17.313773005758303 ], [ 34.894083968307477, -17.313783068469746 ], [ 34.894235504314096, -17.313800773267371 ], [ 34.894385868022724, -17.313826071623883 ], [ 34.894534647315439, -17.313858894199562 ], [ 34.894681434416484, -17.313899151032253 ], [ 34.894825827009804, -17.313946731783918 ], [ 34.89496742934147, -17.314001506043073 ], [ 34.895105853304429, -17.314063323682145 ], [ 34.895240719501928, -17.31413201526895 ], [ 34.895371658287381, -17.314207392530999 ], [ 34.895498310777313, -17.314289248871496 ], [ 34.895620329835019, -17.314377359935495 ], [ 34.895737381021917, -17.3144714842248 ], [ 34.895849143514049, -17.314571363759772 ], [ 34.895955310981499, -17.31467672478637 ], [ 34.896055592427977, -17.314787278526357 ], [ 34.896149712988276, -17.314902721968778 ], [ 34.896237414681828, -17.315022738700307 ], [ 34.896250865251233, -17.315043362242964 ], [ 34.893769553711472, -17.316586264213559 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.6", "sub_field": "4.6C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.90130835758503, -17.31122562620051 ], [ 34.899440097999843, -17.313485312578923 ], [ 34.8972189524941, -17.311647539775638 ], [ 34.897298142992156, -17.311556674113714 ], [ 34.897402122142452, -17.311449320113248 ], [ 34.897511807803241, -17.311347340442076 ], [ 34.897626899332451, -17.311251014610363 ], [ 34.897747081272875, -17.311160606631386 ], [ 34.897872024216817, -17.311076364297964 ], [ 34.898001385709044, -17.310998518503435 ], [ 34.898134811185194, -17.310927282608827 ], [ 34.898271934943708, -17.310862851858154 ], [ 34.898412381148042, -17.310805402843425 ], [ 34.898555764856667, -17.310755093020635 ], [ 34.898701693078088, -17.310712060278291 ], [ 34.898849765847828, -17.310676422559613 ], [ 34.898999577324581, -17.310648277539208 ], [ 34.899150716902355, -17.310627702355518 ], [ 34.89930277033578, -17.31061475339942 ], [ 34.899455320875333, -17.310609466159704 ], [ 34.899607950409418, -17.310611855125785 ], [ 34.899760240610121, -17.310621913748125 ], [ 34.899911774079676, -17.310639614456139 ], [ 34.900062135494302, -17.31066490873372 ], [ 34.900210912742331, -17.310697727252315 ], [ 34.900357698053639, -17.310737980060885 ], [ 34.900502089117012, -17.310785556832428 ], [ 34.90064369018279, -17.310840327166343 ], [ 34.900782113147358, -17.310902140945849 ], [ 34.900916978616706, -17.310970828749291 ], [ 34.901047916946233, -17.311046202314571 ], [ 34.901174569253754, -17.311128055055008 ], [ 34.901296588403042, -17.31121616262552 ], [ 34.90130835758503, -17.31122562620051 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.4", "sub_field": "4.4C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.900201998898332, -17.316577029381111 ], [ 34.901510659033576, -17.318761975282452 ], [ 34.89921991385566, -17.319939028684551 ], [ 34.89919319521254, -17.31989285674155 ], [ 34.899133472197327, -17.319775587027554 ], [ 34.899080220785628, -17.319655475739637 ], [ 34.899033586926492, -17.319532852100672 ], [ 34.898993698429557, -17.31940805221921 ], [ 34.898960664614833, -17.319281418168284 ], [ 34.898934576013126, -17.319153297047684 ], [ 34.898915504118094, -17.319024040032534 ], [ 34.898903501190432, -17.318894001410737 ], [ 34.898898600114762, -17.318763537611808 ], [ 34.89890081430967, -17.318633006229952 ], [ 34.898910137691097, -17.318502765043846 ], [ 34.898926544689147, -17.318373171036008 ], [ 34.898949990318393, -17.318244579414355 ], [ 34.898980410301313, -17.318117342638544 ], [ 34.899017721244583, -17.317991809453979 ], [ 34.899061820867914, -17.317868323935873 ], [ 34.89911258828446, -17.317747224546256 ], [ 34.899169884332323, -17.317628843206275 ], [ 34.899233551956158, -17.317513504386454 ], [ 34.899303416637665, -17.317401524217455 ], [ 34.899379286874172, -17.317293209623546 ], [ 34.899460954703535, -17.317188857481462 ], [ 34.899548196274232, -17.317088753806768 ], [ 34.899640772459016, -17.316993172969916 ], [ 34.899738429510414, -17.31690237694437 ], [ 34.899840899756157, -17.316816614588575 ], [ 34.899947902332954, -17.316736120963917 ], [ 34.900059143956319, -17.316661116690586 ], [ 34.900174319724371, -17.316591807342899 ], [ 34.900201998898332, -17.316577029381111 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.3", "sub_field": "4.3C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.895357321246472, -17.319337751577674 ], [ 34.896432140987109, -17.321371469783127 ], [ 34.894337657341168, -17.322447678138754 ], [ 34.894309541855741, -17.3223990902598 ], [ 34.89425566308077, -17.32229329109223 ], [ 34.894207622969382, -17.322184928461439 ], [ 34.894165553188593, -17.322074299386902 ], [ 34.894129569040125, -17.321961707099845 ], [ 34.894099769144624, -17.321847460212147 ], [ 34.894076235171276, -17.321731871870391 ], [ 34.894059031614177, -17.321615258897467 ], [ 34.89404820561564, -17.321497940924189 ], [ 34.894043786837109, -17.321380239513154 ], [ 34.894045787378019, -17.321262477277379 ], [ 34.894054201742712, -17.321144976995981 ], [ 34.894069006855688, -17.321028060729436 ], [ 34.894090162124932, -17.32091204893689 ], [ 34.894117609553369, -17.320797259597772 ], [ 34.894151273897926, -17.320684007340216 ], [ 34.89419106287589, -17.320572602578771 ], [ 34.894236867418009, -17.32046335066353 ], [ 34.894288561967485, -17.32035655104325 ], [ 34.894346004824278, -17.320252496444606 ], [ 34.894409038533638, -17.320151472069909 ], [ 34.894477490317634, -17.320053754815419 ], [ 34.894551172548908, -17.319959612512406 ], [ 34.894629883264997, -17.319869303193162 ], [ 34.894713406721877, -17.319783074383764 ], [ 34.894801513985477, -17.319701162425709 ], [ 34.894893963559014, -17.319623791828143 ], [ 34.894990502045104, -17.319551174652602 ], [ 34.895090864840206, -17.319483509931821 ], [ 34.895194776859846, -17.319420983124225 ], [ 34.895301953292638, -17.319363765605722 ], [ 34.895357321246472, -17.319337751577674 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.1", "sub_field": "4.1C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.885846577829994, -17.324065205990067 ], [ 34.886782334569538, -17.326739205117487 ], [ 34.883964047061163, -17.327599439232444 ], [ 34.883926891813566, -17.3274831654618 ], [ 34.883889797650568, -17.327340927904032 ], [ 34.883860504808354, -17.3271970206277 ], [ 34.883839093559693, -17.327051838077736 ], [ 34.883825622573575, -17.326905778193673 ], [ 34.88382012875465, -17.326759241318918 ], [ 34.883822627142258, -17.326612629103337 ], [ 34.883833110869425, -17.326466343402387 ], [ 34.883851551181877, -17.326320785175589 ], [ 34.883877897517046, -17.326176353387591 ], [ 34.88391207764294, -17.326033443914554 ], [ 34.883953997856267, -17.325892448459154 ], [ 34.884003543239437, -17.325753753476985 ], [ 34.884060577975809, -17.32561773911727 ], [ 34.884124945722114, -17.325484778181053 ], [ 34.884196470037082, -17.325355235099337 ], [ 34.884274954865283, -17.325229464934313 ], [ 34.884360185074598, -17.325107812406213 ], [ 34.884451927046008, -17.324990610948547 ], [ 34.884549929313991, -17.324878181794265 ], [ 34.884653923255925, -17.324770833095336 ], [ 34.884763623828356, -17.324668859078276 ], [ 34.884878730348319, -17.324572539237757 ], [ 34.884998927317575, -17.324482137570598 ], [ 34.885123885287243, -17.324397901852347 ], [ 34.885253261760887, -17.324320062958176 ], [ 34.885386702133168, -17.324248834230222 ], [ 34.885523840661676, -17.324184410892851 ], [ 34.885664301469411, -17.324126969517753 ], [ 34.885807699574862, -17.324076667539991 ], [ 34.885846577829994, -17.324065205990067 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.2", "sub_field": "4.2D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.892592473294087, -17.324940604483569 ], [ 34.890807409243124, -17.32342733014784 ], [ 34.890881858516011, -17.32335047371787 ], [ 34.890969969192497, -17.323268563141767 ], [ 34.891062422186693, -17.323191194008754 ], [ 34.891158964091794, -17.32311857837632 ], [ 34.891259330294872, -17.32305091527299 ], [ 34.891363245702173, -17.322988390152755 ], [ 34.891470425492983, -17.322931174386909 ], [ 34.891580575900392, -17.322879424794326 ], [ 34.891693395016318, -17.322833283211736 ], [ 34.891808573619024, -17.322792876105019 ], [ 34.891925796020537, -17.322758314222579 ], [ 34.892044740931865, -17.322729692291919 ], [ 34.892165082343496, -17.322707088759937 ], [ 34.892286490418904, -17.32269056557811 ], [ 34.892408632398393, -17.32268016803253 ], [ 34.89253117351118, -17.322675924619965 ], [ 34.892653777892725, -17.322677846969675 ], [ 34.892776109505235, -17.322685929811669 ], [ 34.892897833058612, -17.322700150991011 ], [ 34.893018614929247, -17.322720471528676 ], [ 34.893138124074405, -17.322746835728307 ], [ 34.893256032939391, -17.322779171328932 ], [ 34.893372018355308, -17.322817389702962 ], [ 34.893485762424611, -17.322861386099067 ], [ 34.893596953392439, -17.322911039929352 ], [ 34.89370528650096, -17.322966215099775 ], [ 34.893810464824533, -17.323026760383154 ], [ 34.893912200083612, -17.3230925098336 ], [ 34.894010213434655, -17.323163283241353 ], [ 34.894104236234512, -17.323238886626648 ], [ 34.894194010776616, -17.323319112771372 ], [ 34.89423852225147, -17.323363284353789 ], [ 34.892592473294087, -17.324940604483569 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "3.3", "sub_field": "3.3C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.900331858463275, -17.322163720229909 ], [ 34.900551100160158, -17.32491517010353 ], [ 34.897565743794004, -17.325142207305205 ], [ 34.89756504241015, -17.325134608564934 ], [ 34.89755953787251, -17.324988073109029 ], [ 34.897562025399424, -17.324841461768788 ], [ 34.897572498153679, -17.324695176397274 ], [ 34.897590927411045, -17.324549617953146 ], [ 34.897617262639329, -17.324405185401652 ], [ 34.897651431636909, -17.324262274621056 ], [ 34.897693340730996, -17.324121277317641 ], [ 34.897742875034517, -17.323982579952059 ], [ 34.897799898761171, -17.323846562680117 ], [ 34.897864255597824, -17.32371359831081 ], [ 34.897935769133142, -17.323584051284573 ], [ 34.898014243341194, -17.323458276674447 ], [ 34.898099463118982, -17.323336619212824 ], [ 34.898191194876041, -17.323219412346742 ], [ 34.898289187174854, -17.323106977324002 ], [ 34.898393171420096, -17.322999622312651 ], [ 34.898502862594867, -17.322897641556505 ], [ 34.89861796004196, -17.322801314568704 ], [ 34.898738148287975, -17.322710905365678 ], [ 34.898863097907935, -17.322626661743598 ], [ 34.898992466428268, -17.322548814599326 ], [ 34.899125899265393, -17.322477577297583 ], [ 34.89926303069759, -17.322413145086237 ], [ 34.899403484867264, -17.322355694561299 ], [ 34.899546876811073, -17.322305383182883 ], [ 34.89969281351496, -17.322262348843783 ], [ 34.899840894991208, -17.322226709491549 ], [ 34.899990715374656, -17.322198562805294 ], [ 34.900141864034907, -17.322177985928029 ], [ 34.900293926701728, -17.322165035255239 ], [ 34.900331858463275, -17.322163720229909 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "3.2", "sub_field": "3.2C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.907099870341185, -17.322607987848503 ], [ 34.906021160934294, -17.32453810278799 ], [ 34.903896908377526, -17.323502846894009 ], [ 34.903917242324283, -17.323466009089106 ], [ 34.903980271340309, -17.323364982123337 ], [ 34.904048718674474, -17.323267261990232 ], [ 34.904122396711607, -17.323173116528974 ], [ 34.904201103500704, -17.323082803780501 ], [ 34.904284623308548, -17.322996571280285 ], [ 34.904372727211005, -17.322914655379957 ], [ 34.90446517372056, -17.32283728059944 ], [ 34.90456170944816, -17.32276465901176 ], [ 34.904662069797851, -17.322696989661715 ], [ 34.904765979691838, -17.322634458020435 ], [ 34.904873154324569, -17.322577235477077 ], [ 34.90498329994324, -17.322525478869093 ], [ 34.905096114652899, -17.322479330052449 ], [ 34.905211289243852, -17.322438915512812 ], [ 34.905328508039148, -17.322404346018985 ], [ 34.905447449759642, -17.322375716319311 ], [ 34.905567788404575, -17.322353104881994 ], [ 34.905689194144998, -17.322336573680126 ], [ 34.905811334227622, -17.322326168021803 ], [ 34.905933873886852, -17.322321916426024 ], [ 34.906056477262126, -17.322323830544466 ], [ 34.90617880831843, -17.32233190512968 ], [ 34.906300531767172, -17.322346118049349 ], [ 34.906421313985021, -17.322366430347106 ], [ 34.906540823928317, -17.322392786349138 ], [ 34.906658734040178, -17.322425113816962 ], [ 34.906774721148288, -17.322463324145243 ], [ 34.90688846735047, -17.32250731260471 ], [ 34.906999660886036, -17.322556958629232 ], [ 34.907099870341185, -17.322607987848503 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "3.1", "sub_field": "3.1D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.911721763444021, -17.327270799151147 ], [ 34.909685584378018, -17.325148690849765 ], [ 34.909715691499358, -17.325120697051958 ], [ 34.90983078373403, -17.325024364110629 ], [ 34.909950967164427, -17.324933948628292 ], [ 34.910075912378765, -17.32484969841833 ], [ 34.910205276915569, -17.324771844395649 ], [ 34.910338706202126, -17.3247005999438 ], [ 34.910475834526437, -17.324636160330265 ], [ 34.910616286039428, -17.324578702171344 ], [ 34.910759675785044, -17.324528382948049 ], [ 34.910905610755215, -17.32448534057465 ], [ 34.911053690967016, -17.324449693020743 ], [ 34.911203510558707, -17.324421537987895 ], [ 34.911354658902077, -17.324400952641973 ], [ 34.911506721727747, -17.324387993401679 ], [ 34.911659282260466, -17.324382695783971 ], [ 34.911811922361267, -17.324385074306729 ], [ 34.911964223673273, -17.324395122449015 ], [ 34.912115768768309, -17.324412812668925 ], [ 34.912266142290733, -17.32443809647912 ], [ 34.912414932095714, -17.324470904579723 ], [ 34.912561730378727, -17.324511147048252 ], [ 34.912706134793041, -17.324558713586011 ], [ 34.912847749552412, -17.32461347382046 ], [ 34.91298618651571, -17.324675277662489 ], [ 34.913121066250604, -17.32474395571769 ], [ 34.913252019073362, -17.32481931975067 ], [ 34.913378686062146, -17.324901163200888 ], [ 34.91350072004051, -17.32498926174873 ], [ 34.913617786528953, -17.325083373930308 ], [ 34.913729564661651, -17.325183241799166 ], [ 34.913835748065821, -17.325288591633232 ], [ 34.913936045701512, -17.325399134684982 ], [ 34.914030182659175, -17.325514567972732 ], [ 34.914031362474837, -17.325516182075784 ], [ 34.911721763444021, -17.327270799151147 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.4", "sub_field": "2.4D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.896270521422032, -17.345778057340958 ], [ 34.894633955948727, -17.344108401435214 ], [ 34.894659373611468, -17.344084773603896 ], [ 34.894751834385303, -17.344007402050345 ], [ 34.894848384675079, -17.343934783858305 ], [ 34.894948759844887, -17.343867118063262 ], [ 34.895052684776374, -17.343804590126624 ], [ 34.895159874622749, -17.343747371427341 ], [ 34.895270035589526, -17.343695618792371 ], [ 34.895382865739712, -17.343649474066748 ], [ 34.895498055821321, -17.343609063724955 ], [ 34.895615290114932, -17.343574498524291 ], [ 34.895734247298968, -17.343545873201311 ], [ 34.895854601330292, -17.343523266212244 ], [ 34.895976022337791, -17.343506739517984 ], [ 34.896098177526369, -17.343496338414269 ], [ 34.896220732089027, -17.343492091407629 ], [ 34.896343350124383, -17.343494010137192 ], [ 34.896465695557261, -17.343502089342831 ], [ 34.896587433059651, -17.343516306879611 ], [ 34.896708228969764, -17.343536623778476 ], [ 34.896827752206391, -17.343562984353067 ], [ 34.896945675176234, -17.343595316352321 ], [ 34.897061674671747, -17.343633531158535 ], [ 34.897175432756811, -17.343677524030166 ], [ 34.897286637638175, -17.34372717438897 ], [ 34.897394984519835, -17.34378234615043 ], [ 34.897500176438477, -17.343842888096699 ], [ 34.897601925077247, -17.343908634291068 ], [ 34.897699951556, -17.343979404532686 ], [ 34.89779398719557, -17.344055004850485 ], [ 34.89788377425419, -17.344135228034745 ], [ 34.897969066633934, -17.344219854204994 ], [ 34.898018534309401, -17.34427437726421 ], [ 34.896270521422032, -17.345778057340958 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.3", "sub_field": "2.3C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.8993698935897, -17.3404132919784 ], [ 34.899977306418968, -17.342598676400389 ], [ 34.897632654475345, -17.343156624592137 ], [ 34.897616064985897, -17.343093039404593 ], [ 34.897592524699924, -17.342977452198006 ], [ 34.897575315514821, -17.342860840179966 ], [ 34.897564484588315, -17.342743522978669 ], [ 34.897560061595343, -17.342625822154599 ], [ 34.897562058646876, -17.342508060319165 ], [ 34.897570470256781, -17.342390560250386 ], [ 34.897585273357109, -17.34227364400817 ], [ 34.89760642736136, -17.342157632051599 ], [ 34.897633874275868, -17.34204284236052 ], [ 34.897667538859004, -17.341929589564067 ], [ 34.897707328827337, -17.341818184078249 ], [ 34.8977531351089, -17.341708931255134 ], [ 34.897804832142121, -17.341602130545965 ], [ 34.89786227822016, -17.341498074680427 ], [ 34.89792531587937, -17.341397048864266 ], [ 34.89799377233102, -17.341299329997696 ], [ 34.898067459934943, -17.341205185916401 ], [ 34.898146176713922, -17.341114874657549 ], [ 34.898229706907351, -17.341028643752509 ], [ 34.898317821562657, -17.34094672954852 ], [ 34.898410279162817, -17.340869356560894 ], [ 34.898506826288433, -17.340796736857698 ], [ 34.89860719831227, -17.340729069478559 ], [ 34.898711120124595, -17.340666539889227 ], [ 34.898818306887151, -17.340609319473213 ], [ 34.898928464813913, -17.340557565062127 ], [ 34.899041291976218, -17.34051141850593 ], [ 34.899156479130262, -17.34047100628408 ], [ 34.899273710564628, -17.340436439159028 ], [ 34.8993698935897, -17.3404132919784 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.2", "sub_field": "2.2C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.906533452371185, -17.338050305737823 ], [ 34.909113153334729, -17.34030905206204 ], [ 34.906860112803329, -17.342790160574449 ], [ 34.906785146846069, -17.342729896748654 ], [ 34.90665424850053, -17.342612951146734 ], [ 34.906529902534977, -17.342489586312865 ], [ 34.906412449774884, -17.342360140393918 ], [ 34.906302212149164, -17.34222496820469 ], [ 34.906199491807826, -17.342084440255348 ], [ 34.906104570293813, -17.341938941735673 ], [ 34.906017707771433, -17.341788871459226 ], [ 34.905939142313287, -17.341634640770003 ], [ 34.905869089247915, -17.341476672414917 ], [ 34.905807740569742, -17.341315399384907 ], [ 34.905755264412988, -17.341151263728026 ], [ 34.905711804591036, -17.340984715337761 ], [ 34.905677480202478, -17.340816210719719 ], [ 34.90565238530489, -17.340646211740392 ], [ 34.905636588657302, -17.340475184361022 ], [ 34.905630133532, -17.340303597360474 ], [ 34.905633037596203, -17.340131921050247 ], [ 34.905645292863881, -17.339960625985302 ], [ 34.905666865717976, -17.339790181674402 ], [ 34.905697697002793, -17.339621055293083 ], [ 34.905737702186485, -17.339453710403209 ], [ 34.905786771592958, -17.339288605682437 ], [ 34.905844770702799, -17.339126193667006 ], [ 34.905911540522254, -17.338966919511417 ], [ 34.905986898019179, -17.338811219768395 ], [ 34.906070636625088, -17.338659521192326 ], [ 34.906162526801396, -17.338512239569724 ], [ 34.906262316668872, -17.338369778579619 ], [ 34.90636973269811, -17.338232528687239 ], [ 34.906484480459412, -17.338100866073905 ], [ 34.906533452371185, -17.338050305737823 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.1", "sub_field": "2.1D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.913379761071603, -17.335625170111705 ], [ 34.911437392245574, -17.333407717834426 ], [ 34.911460462998669, -17.333388407832093 ], [ 34.911580650515376, -17.333297990941542 ], [ 34.911705600115177, -17.333213739250976 ], [ 34.911834969324552, -17.33313588367934 ], [ 34.911968403558056, -17.333064637614456 ], [ 34.912105537090085, -17.333000196328197 ], [ 34.912245994057315, -17.332942736441435 ], [ 34.91238938948873, -17.33289241543994 ], [ 34.912535330360718, -17.332849371242844 ], [ 34.912683416674128, -17.332813721824717 ], [ 34.912833242550533, -17.332785564892241 ], [ 34.912984397344523, -17.332764977616499 ], [ 34.913136466769025, -17.332752016421484 ], [ 34.913289034030726, -17.33274671682949 ], [ 34.913441680972198, -17.332749093363816 ], [ 34.913593989217865, -17.332759139508962 ], [ 34.91374554132053, -17.332776827728509 ], [ 34.913895921905365, -17.332802109540605 ], [ 34.914044718808199, -17.332834915650842 ], [ 34.914191524205023, -17.332875156142183 ], [ 34.914335935729667, -17.332922720721363 ], [ 34.914477557576397, -17.332977479021213 ], [ 34.914616001584655, -17.333039280957927 ], [ 34.914750888302791, -17.333107957142325 ], [ 34.914881848028003, -17.333183319344126 ], [ 34.915008521819459, -17.333265161007834 ], [ 34.915130562482133, -17.333353257818729 ], [ 34.915247635518178, -17.333447368317675 ], [ 34.915359420043842, -17.33354723456284 ], [ 34.915465609668836, -17.333652582836624 ], [ 34.915518445366438, -17.33371081141938 ], [ 34.913379761071603, -17.335625170111705 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.5", "sub_field": "2.5B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.920240082361659, -17.336946735747041 ], [ 34.922942052048946, -17.33414882953203 ], [ 34.923015700072284, -17.334214619847671 ], [ 34.92315957577275, -17.334357343654762 ], [ 34.923295477304066, -17.334507103771536 ], [ 34.923423032168401, -17.33466348973268 ], [ 34.923541890741852, -17.334826072912001 ], [ 34.923651727232865, -17.334994407696968 ], [ 34.923752240575283, -17.335168032709991 ], [ 34.923843155253664, -17.335346472072818 ], [ 34.923924222058574, -17.335529236710677 ], [ 34.92399521876996, -17.335715825692713 ], [ 34.924055950766373, -17.335905727604761 ], [ 34.924106251558761, -17.336098421951011 ], [ 34.924145983247044, -17.336293380580464 ], [ 34.924175036898419, -17.336490069134513 ], [ 34.924193332846301, -17.336687948511432 ], [ 34.924200820909014, -17.336886476343878 ], [ 34.924197480527702, -17.337085108485553 ], [ 34.924183320823069, -17.337283300502477 ], [ 34.924158380570788, -17.337480509165285 ], [ 34.924122728095575, -17.337676193938162 ], [ 34.924076461084262, -17.337869818460412 ], [ 34.924019706318504, -17.338060852016596 ], [ 34.923952619327601, -17.338248770991257 ], [ 34.923875383962496, -17.338433060304176 ], [ 34.923788211892266, -17.338613214822228 ], [ 34.923691342024206, -17.338788740744029 ], [ 34.923585039849272, -17.338959156953514 ], [ 34.923469596714675, -17.339123996338795 ], [ 34.923345329025501, -17.339282807072621 ], [ 34.923212577377683, -17.339435153850914 ], [ 34.923199307657669, -17.339448856263918 ], [ 34.920240082361659, -17.336946735747041 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "6.1", "sub_field": "6.1B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.926975301324241, -17.341756277068452 ], [ 34.929874621707043, -17.3387198647094 ], [ 34.929976827889384, -17.338811156788797 ], [ 34.930134758291622, -17.338967806549938 ], [ 34.930283936250547, -17.339132179782727 ], [ 34.930423952878314, -17.339303825971783 ], [ 34.930554424393229, -17.339482274667148 ], [ 34.930674993171756, -17.339667036773445 ], [ 34.930785328728838, -17.339857605890288 ], [ 34.930885128623856, -17.340053459700087 ], [ 34.930974119289864, -17.340254061399367 ], [ 34.931052056783614, -17.340458861170085 ], [ 34.931118727454496, -17.340667297686302 ], [ 34.931173948530414, -17.340878799652643 ], [ 34.931217568619211, -17.341092787369995 ], [ 34.93124946812388, -17.341308674324267 ], [ 34.931269559570865, -17.34152586879387 ], [ 34.931277787850235, -17.341743775471489 ], [ 34.931274130367186, -17.341961797095689 ], [ 34.931258597104346, -17.342179336087895 ], [ 34.931231230595053, -17.342395796190306 ], [ 34.931192105807014, -17.342610584100129 ], [ 34.931141329937518, -17.342823111095818 ], [ 34.93107904211984, -17.343032794650775 ], [ 34.93100541304252, -17.343239060030005 ], [ 34.930920644481787, -17.343441341865585 ], [ 34.930824968748979, -17.34363908570629 ], [ 34.930718648054125, -17.343831749537522 ], [ 34.930601973787617, -17.344018805267019 ], [ 34.930475265721768, -17.344199740172474 ], [ 34.930338871134673, -17.344374058307057 ], [ 34.930206135800262, -17.344526394378821 ], [ 34.926975301324241, -17.341756277068452 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "6.2", "sub_field": "6.2C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.930918907739319, -17.350619195960071 ], [ 34.933686532028823, -17.34741850738671 ], [ 34.936891437505039, -17.35009147006711 ], [ 34.936870614319417, -17.350115369252237 ], [ 34.936715995799332, -17.350275044792941 ], [ 34.936552887205806, -17.350426730058501 ], [ 34.936381735605124, -17.350570009270172 ], [ 34.936203010113339, -17.350704489689271 ], [ 34.936017200610422, -17.35082980269403 ], [ 34.93582481639752, -17.350945604790095 ], [ 34.935626384800806, -17.351051578552209 ], [ 34.935422449725991, -17.351147433494578 ], [ 34.935213570167356, -17.351232906867171 ], [ 34.935000318675286, -17.351307764376163 ], [ 34.934783279786686, -17.351371800826296 ], [ 34.934563048422454, -17.351424840683471 ], [ 34.93434022825658, -17.351466738556034 ], [ 34.934115430061013, -17.351497379593461 ], [ 34.933889270031244, -17.351516679801193 ], [ 34.933662368096904, -17.351524586271111 ], [ 34.933435346222147, -17.351521077326474 ], [ 34.933208826700387, -17.351506162581522 ], [ 34.932983430448253, -17.351479882915079 ], [ 34.932759775303161, -17.351442310358582 ], [ 34.932538474329434, -17.351393547898589 ], [ 34.932320134137491, -17.351333729194508 ], [ 34.932105353220734, -17.351263018212201 ], [ 34.931894720314659, -17.351181608774432 ], [ 34.931688812782838, -17.351089724029563 ], [ 34.931488195034035, -17.350987615839795 ], [ 34.931293416974796, -17.350875564090668 ], [ 34.931105012501995, -17.350753875923818 ], [ 34.930923498039114, -17.350622884894868 ], [ 34.930918907739319, -17.350619195960071 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.8", "sub_field": "1.8B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.961161225337996, -17.342695153682683 ], [ 34.963463180156758, -17.345951999810669 ], [ 34.963432746238198, -17.345972531590462 ], [ 34.963250401228407, -17.34608232777347 ], [ 34.963062322565051, -17.346182808203306 ], [ 34.962869025775234, -17.346273697452876 ], [ 34.962671040692122, -17.346354746384645 ], [ 34.962468910002343, -17.346425732833538 ], [ 34.962263187758481, -17.346486462216212 ], [ 34.962054437859955, -17.346536768064457 ], [ 34.961843232507221, -17.346576512481615 ], [ 34.961630150633034, -17.34660558652071 ], [ 34.961415776315228, -17.346623910483206 ], [ 34.961200697175478, -17.346631434137443 ], [ 34.960985502768224, -17.346628136856513 ], [ 34.960770782964332, -17.346614027674772 ], [ 34.960557126333896, -17.346589145263163 ], [ 34.960345118532622, -17.346553557823199 ], [ 34.960135340696091, -17.346507362900038 ], [ 34.959928367846587, -17.346450687115105 ], [ 34.959724767316587, -17.346383685818981 ], [ 34.959525097193278, -17.346306542665452 ], [ 34.959329904788703, -17.346219469108206 ], [ 34.959139725139103, -17.34612270382096 ], [ 34.958955079538242, -17.346016512043306 ], [ 34.958776474108255, -17.345901184853453 ], [ 34.958604398412149, -17.345777038370297 ], [ 34.958439324111843, -17.345644412886852 ], [ 34.958281703675084, -17.345503671937216 ], [ 34.958131969135259, -17.345355201300144 ], [ 34.957990530907111, -17.345199407941248 ], [ 34.95785777666174, -17.345036718897553 ], [ 34.957734070264131, -17.344867580106648 ], [ 34.957665162047512, -17.344762020273741 ], [ 34.961161225337996, -17.342695153682683 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.6", "sub_field": "1.6C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.978354668011164, -17.346604682049772 ], [ 34.977997053286906, -17.349855632180109 ], [ 34.977869135434879, -17.349840746730006 ], [ 34.977691568440811, -17.349810957379173 ], [ 34.977515868251459, -17.349772283910664 ], [ 34.977342516473946, -17.349724832328775 ], [ 34.977171988277505, -17.349668732699339 ], [ 34.977004751090774, -17.349604138793072 ], [ 34.976841263320381, -17.349531227664105 ], [ 34.976681973094188, -17.349450199164586 ], [ 34.976527317032911, -17.349361275396888 ], [ 34.97637771905309, -17.34926470010463 ], [ 34.976233589205009, -17.349160738004564 ], [ 34.976095322548673, -17.349049674060865 ], [ 34.975963298070845, -17.348931812703938 ], [ 34.975837877646171, -17.348807476995859 ], [ 34.97571940504529, -17.348677007744797 ], [ 34.97560820499254, -17.348540762570682 ], [ 34.975504582275924, -17.34839911492486 ], [ 34.975408820911731, -17.348252453066394 ], [ 34.975321183366191, -17.348101178997709 ], [ 34.9752419098361, -17.347945707362602 ], [ 34.975171217590642, -17.347786464309539 ], [ 34.975109300375976, -17.347623886323589 ], [ 34.975056327884417, -17.347458419029859 ], [ 34.975012445289508, -17.347290515971977 ], [ 34.974977772848305, -17.34712063736885 ], [ 34.974952405572012, -17.346949248853143 ], [ 34.974936412965867, -17.346776820194972 ], [ 34.974929838838861, -17.346603824014135 ], [ 34.974932701183953, -17.346430734484802 ], [ 34.974944992128989, -17.346258026035596 ], [ 34.974946239091686, -17.346248144213522 ], [ 34.978354668011164, -17.346604682049772 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.5", "sub_field": "1.5C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.981679444479695, -17.339111001077271 ], [ 34.981414279389675, -17.341507128753143 ], [ 34.979069772171925, -17.341221754105334 ], [ 34.97907218780167, -17.341187800312291 ], [ 34.979087294041641, -17.341068065924567 ], [ 34.979108904300226, -17.340949254926347 ], [ 34.979136959332656, -17.340831692969534 ], [ 34.979171382229808, -17.340715702281784 ], [ 34.979212078629267, -17.340601600783437 ], [ 34.979258936974006, -17.340489701216075 ], [ 34.979311828818311, -17.340380310285358 ], [ 34.979370609179981, -17.340273727820453 ], [ 34.979435116937729, -17.340170245952184 ], [ 34.979505175273005, -17.340070148312417 ], [ 34.979580592154655, -17.339973709256714 ], [ 34.979661160865334, -17.339881193112319 ], [ 34.979746660568168, -17.339792853453819 ], [ 34.979836856912151, -17.339708932408108 ], [ 34.979931502674376, -17.33962965999083 ], [ 34.9800303384378, -17.339555253475954 ], [ 34.980133093302214, -17.339485916800367 ], [ 34.980239485626747, -17.339421840004896 ], [ 34.980349223801831, -17.339363198713549 ], [ 34.980462007048374, -17.339310153652161 ], [ 34.980577526242143, -17.339262850207973 ], [ 34.980695464761006, -17.339221418031112 ], [ 34.98081549935263, -17.339185970679388 ], [ 34.980937301020425, -17.339156605307025 ], [ 34.981060535925209, -17.339133402398442 ], [ 34.981184866300069, -17.339116425547651 ], [ 34.981309951376097, -17.339105721284096 ], [ 34.981435448316205, -17.339101318945044 ], [ 34.98156101315476, -17.339103230595249 ], [ 34.981679444479695, -17.339111001077271 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.4", "sub_field": "1.4B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.975949718590073, -17.343237636498074 ], [ 34.976297118077937, -17.340867839790402 ], [ 34.978926265695563, -17.341206981511462 ], [ 34.978913049912912, -17.341279639946706 ], [ 34.978882794231374, -17.341406422427387 ], [ 34.978845671151007, -17.341531510388236 ], [ 34.978801782409981, -17.341654560969456 ], [ 34.978751248291495, -17.341775236894811 ], [ 34.978694207294154, -17.341893207396165 ], [ 34.978630815752453, -17.342008149120165 ], [ 34.978561247408479, -17.342119747014557 ], [ 34.978485692935763, -17.342227695191749 ], [ 34.978404359416693, -17.342331697767307 ], [ 34.978317469775135, -17.342431469671059 ], [ 34.978225262165303, -17.342526737428432 ], [ 34.978127989319184, -17.342617239910208 ], [ 34.97802591785377, -17.342702729048238 ], [ 34.97791932754032, -17.34278297051554 ], [ 34.977808510537486, -17.342857744368597 ], [ 34.977693770590584, -17.34292684565029 ], [ 34.97757542219891, -17.342990084951776 ], [ 34.977453789753753, -17.343047288931722 ], [ 34.977329206649109, -17.343098300791471 ], [ 34.977202014367826, -17.343142980704894 ], [ 34.977072561545548, -17.343181206201727 ], [ 34.976941203014952, -17.343212872503312 ], [ 34.97680829883312, -17.343237892809821 ], [ 34.97667421329443, -17.343256198538253 ], [ 34.976539313931994, -17.343267739510448 ], [ 34.976403970510042, -17.34327248409063 ], [ 34.976268554010332, -17.343270419272212 ], [ 34.976133435615104, -17.343261550713425 ], [ 34.975998985689543, -17.343245902721812 ], [ 34.975949718590073, -17.343237636498074 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.2", "sub_field": "1.2D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.976816384818314, -17.338238223714697 ], [ 34.976762738159529, -17.338203588832894 ], [ 34.976658388456634, -17.338128315241242 ], [ 34.976558283695852, -17.3380478996406 ], [ 34.976462698261315, -17.337962562450763 ], [ 34.976371894148514, -17.33787253758145 ], [ 34.976286120246264, -17.337778071791206 ], [ 34.976205611654429, -17.337679424010897 ], [ 34.97613058903957, -17.337576864633927 ], [ 34.976061258030121, -17.337470674775066 ], [ 34.975997808652856, -17.337361145499887 ], [ 34.975940414812008, -17.337248577026841 ], [ 34.975889233812737, -17.337133277904332 ], [ 34.975844405930061, -17.337015564164975 ], [ 34.97580605402436, -17.336895758459242 ], [ 34.97577428320492, -17.336774189171134 ], [ 34.975749180541747, -17.336651189517951 ], [ 34.975730814827195, -17.336527096636967 ], [ 34.975719236387441, -17.336402250661358 ], [ 34.975714476944766, -17.336276993787809 ], [ 34.975716549530709, -17.336151669338609 ], [ 34.975725448450497, -17.336026620820551 ], [ 34.975741149298841, -17.33590219098344 ], [ 34.975763609026941, -17.335778720880647 ], [ 34.975792766060671, -17.335656548934217 ], [ 34.975828540469436, -17.335536010007385 ], [ 34.975870834185486, -17.335417434486722 ], [ 34.975919531272815, -17.335301147376583 ], [ 34.975974498245002, -17.335187467408328 ], [ 34.976035584431322, -17.335076706166753 ], [ 34.976102622389732, -17.334969167236075 ], [ 34.976145471591806, -17.334907946244652 ], [ 34.97820349148671, -17.336307953800048 ], [ 34.976973009965533, -17.338311269909727 ], [ 34.976816384818314, -17.338238223714697 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.7", "sub_field": "1.7C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.965141978743468, -17.34115131321493 ], [ 34.96697037363257, -17.340380748223968 ], [ 34.967862993493888, -17.342116599326115 ], [ 34.967767121182092, -17.342155850020827 ], [ 34.967665828053995, -17.342191426810356 ], [ 34.967562734991439, -17.342221863436112 ], [ 34.967458124572644, -17.342247076470123 ], [ 34.967352283535391, -17.342266996802234 ], [ 34.96724550199103, -17.342281569829609 ], [ 34.967138072629211, -17.342290755606424 ], [ 34.967030289915506, -17.342294528953374 ], [ 34.966922449284276, -17.342292879526671 ], [ 34.966814846328695, -17.342285811846477 ], [ 34.966707775990557, -17.342273345284447 ], [ 34.966601531751685, -17.342255514010727 ], [ 34.966496404829407, -17.342232366900181 ], [ 34.966392683378324, -17.342203967398536 ], [ 34.966290651700341, -17.342170393348415 ], [ 34.966190589465299, -17.342131736775904 ], [ 34.966092770944378, -17.342088103638435 ], [ 34.965997464258237, -17.342039613534183 ], [ 34.965904930642061, -17.341986399374331 ], [ 34.965815423729424, -17.341928607018694 ], [ 34.965729188857061, -17.34186639487589 ], [ 34.965646462392378, -17.341799933469115 ], [ 34.965567471085528, -17.341729404968714 ], [ 34.965492431447899, -17.341655002692814 ], [ 34.965421549158599, -17.341576930577368 ], [ 34.965355018500752, -17.34149540261722 ], [ 34.965293021828984, -17.34141064227941 ], [ 34.965235729069583, -17.341322881890694 ], [ 34.965183297254761, -17.341232362000632 ], [ 34.965141978743468, -17.34115131321493 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.3", "sub_field": "1.3B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.970773107481506, -17.335663616039604 ], [ 34.970354223381719, -17.338412629071641 ], [ 34.970344564689924, -17.338411504611443 ], [ 34.970194661483319, -17.338386347845049 ], [ 34.970046334609464, -17.338353690604716 ], [ 34.96989999063991, -17.338313622403852 ], [ 34.96975603071045, -17.338266253069662 ], [ 34.969614849421397, -17.338211712442192 ], [ 34.969476833755884, -17.338150150018279 ], [ 34.969342362018992, -17.338081734541827 ], [ 34.969211802800672, -17.338006653541218 ], [ 34.969085513965389, -17.337925112815231 ], [ 34.968963841671005, -17.337837335868841 ], [ 34.968847119420062, -17.337743563300634 ], [ 34.96873566714541, -17.337644052143105 ], [ 34.96862979033336, -17.337539075158222 ], [ 34.968529779186312, -17.337428920089575 ], [ 34.968435907827235, -17.337313888873631 ], [ 34.968348433548449, -17.3371942968121 ], [ 34.968267596106315, -17.337070471707587 ], [ 34.968193617064209, -17.336942752964909 ], [ 34.968126699185284, -17.336811490660896 ], [ 34.968067025876756, -17.336677044584633 ], [ 34.96801476068736, -17.336539783251222 ], [ 34.967970046859186, -17.33640008289165 ], [ 34.967933006935205, -17.336258326421515 ], [ 34.967903742423537, -17.336114902391344 ], [ 34.967882333519427, -17.335970203921576 ], [ 34.967868838885579, -17.335824627624998 ], [ 34.967863295491597, -17.335678572519573 ], [ 34.967865718512797, -17.335532438934763 ], [ 34.967876101288894, -17.335386627414191 ], [ 34.967894415342442, -17.335241537617812 ], [ 34.967900765229288, -17.335206638144747 ], [ 34.970773107481506, -17.335663616039604 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.1", "sub_field": "1.1D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.975967002903133, -17.329006613978088 ], [ 34.977225538507916, -17.327402417183986 ], [ 34.977276534416632, -17.327439206013974 ], [ 34.977359257505945, -17.327505662931092 ], [ 34.977438246093733, -17.327576187031788 ], [ 34.977513283679436, -17.327650585018809 ], [ 34.977584164590994, -17.327728652976958 ], [ 34.97765069454843, -17.327810176931923 ], [ 34.97771269119648, -17.327894933436831 ], [ 34.977769984604379, -17.327982690184498 ], [ 34.97782241773163, -17.328073206644273 ], [ 34.977869846858539, -17.328166234721127 ], [ 34.977912141980134, -17.328261519435703 ], [ 34.977949187162594, -17.328358799623132 ], [ 34.977980880861054, -17.328457808648796 ], [ 34.978007136197959, -17.328558275139187 ], [ 34.978027881201406, -17.328659923725571 ], [ 34.978043059002367, -17.328762475798857 ], [ 34.978052627990678, -17.328865650273102 ], [ 34.978056561929264, -17.328969164356007 ], [ 34.978054850026062, -17.329072734323933 ], [ 34.978047496963782, -17.329176076299639 ], [ 34.978034522887093, -17.329278907030307 ], [ 34.978015963347566, -17.329380944663903 ], [ 34.977991869206356, -17.329481909521785 ], [ 34.977962306494838, -17.329581524865198 ], [ 34.977927356233742, -17.329679517653869 ], [ 34.977887114211171, -17.329775619294388 ], [ 34.97784169072019, -17.329869566376452 ], [ 34.977791210256534, -17.32996110139484 ], [ 34.977735811177453, -17.330049973455242 ], [ 34.97768378767087, -17.330124305102657 ], [ 34.975967002903133, -17.329006613978088 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.9", "sub_field": "1.9D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.964728870983024, -17.336051561916904 ], [ 34.967428748672695, -17.335013069641263 ], [ 34.967469972171351, -17.335121335030792 ], [ 34.967514685216699, -17.335261035588786 ], [ 34.967551724543249, -17.335402792202579 ], [ 34.967580988613044, -17.335546216333061 ], [ 34.967602397198554, -17.335690914869808 ], [ 34.967615891602726, -17.335836491208521 ], [ 34.967621434820103, -17.335982546338016 ], [ 34.967619011638341, -17.336128679933918 ], [ 34.967608628680289, -17.336274491455832 ], [ 34.967590314385895, -17.336419581245202 ], [ 34.967564118934519, -17.33656355162071 ], [ 34.96753011410761, -17.336706007968349 ], [ 34.96748839309214, -17.336846559823023 ], [ 34.967439070225389, -17.336984821938778 ], [ 34.967382280681733, -17.337120415344799 ], [ 34.967318180102346, -17.337252968384213 ], [ 34.967246944168728, -17.337382117732766 ], [ 34.96716876812134, -17.337507509394726 ], [ 34.967083866224598, -17.33762879967334 ], [ 34.966992471179729, -17.337745656112833 ], [ 34.966894833487004, -17.337857758409786 ], [ 34.966791220759234, -17.337964799291147 ], [ 34.96668191698835, -17.338066485356531 ], [ 34.966567221766972, -17.338162537882528 ], [ 34.966447449467367, -17.338252693586735 ], [ 34.966322928379626, -17.338336705349501 ], [ 34.966193999811964, -17.338414342891376 ], [ 34.966061017155035, -17.33848539340439 ], [ 34.966003469280459, -17.338512454706869 ], [ 34.964728870983024, -17.336051561916904 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.10", "sub_field": "1.10D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.957010326871092, -17.336809457524954 ], [ 34.955242480798987, -17.334332157368866 ], [ 34.955350420930586, -17.334267165729322 ], [ 34.955492013053238, -17.334191523989539 ], [ 34.955637532828916, -17.334123103067494 ], [ 34.95578658140937, -17.3340620904909 ], [ 34.955938750276061, -17.334008653482151 ], [ 34.956093622359603, -17.333962938500228 ], [ 34.956250773182859, -17.333925070839225 ], [ 34.95640977202418, -17.333895154285123 ], [ 34.956570183097803, -17.33387327083128 ], [ 34.956731566748068, -17.333859480453818 ], [ 34.95689348065433, -17.333853820947308 ], [ 34.957055481043014, -17.333856307821161 ], [ 34.957217123903831, -17.333866934257205 ], [ 34.957377966206458, -17.333885671128321 ], [ 34.957537567114713, -17.333912467078409 ], [ 34.957695489194478, -17.333947248662994 ], [ 34.957851299612628, -17.333989920550646 ], [ 34.958004571322995, -17.334040365784176 ], [ 34.958154884236826, -17.334098446101205 ], [ 34.958301826373862, -17.33416400231307 ], [ 34.958444994991495, -17.334236854741064 ], [ 34.958583997688393, -17.334316803708923 ], [ 34.958718453479904, -17.334403630089952 ], [ 34.958847993842262, -17.334497095907654 ], [ 34.958972263722472, -17.334596944987808 ], [ 34.9590909225114, -17.33470290366062 ], [ 34.959203644977393, -17.33481468151064 ], [ 34.959310122157575, -17.33493197217275 ], [ 34.959410062204682, -17.335054454171708 ], [ 34.959503191187082, -17.335181791803198 ], [ 34.959540353269119, -17.335238722501 ], [ 34.957010326871092, -17.336809457524954 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.11", "sub_field": "1.11D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.960619385935466, -17.330692249633707 ], [ 34.961187884958441, -17.334400587778653 ], [ 34.961072028950504, -17.334416395801075 ], [ 34.960867969605161, -17.334433837419532 ], [ 34.960663239453304, -17.33444099772812 ], [ 34.960458399679979, -17.334437857096304 ], [ 34.96025401177112, -17.334424424129391 ], [ 34.960050635974234, -17.334400735644973 ], [ 34.959848829762414, -17.334366856572057 ], [ 34.959649146305892, -17.33432287977303 ], [ 34.959452132955583, -17.334268925789214 ], [ 34.959258329742404, -17.334205142510285 ], [ 34.959068267896775, -17.334131704768978 ], [ 34.958882468392225, -17.334048813861745 ], [ 34.958701440517132, -17.333956696996925 ], [ 34.958525680478566, -17.333855606671875 ], [ 34.958355670041925, -17.333745819980795 ], [ 34.958191875210261, -17.333627637855024 ], [ 34.958034744946829, -17.333501384238147 ], [ 34.957884709944366, -17.333367405197862 ], [ 34.957742181444438, -17.333226067977272 ], [ 34.957607550110296, -17.333077759988161 ], [ 34.957481184955945, -17.332922887748911 ], [ 34.957363432334795, -17.332761875770107 ], [ 34.957254614990347, -17.332595165390771 ], [ 34.957155031171695, -17.332423213568532 ], [ 34.957064953816122, -17.332246491626936 ], [ 34.956984629801269, -17.332065483963447 ], [ 34.956914279268503, -17.331880686721558 ], [ 34.956854095019878, -17.331692606430721 ], [ 34.956804241989921, -17.331501758617907 ], [ 34.956779145904193, -17.33137872104356 ], [ 34.960619385935466, -17.330692249633707 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.12", "sub_field": "1.12A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.955236330182871, -17.33431716598372 ], [ 34.953639390624843, -17.331963065750791 ], [ 34.956244881516085, -17.330425794331145 ], [ 34.956261331297341, -17.330450996469615 ], [ 34.956336256917439, -17.330580374246498 ], [ 34.956404030135602, -17.330713340954052 ], [ 34.956464465179891, -17.330849532148207 ], [ 34.956517396390062, -17.330988574546339 ], [ 34.956562678671851, -17.33113008705028 ], [ 34.95660018789475, -17.331273681790872 ], [ 34.956629821232468, -17.331418965190952 ], [ 34.956651497444895, -17.331565539044075 ], [ 34.95666515710095, -17.33171300160593 ], [ 34.956670762741744, -17.331860948695443 ], [ 34.956668298983416, -17.33200897480253 ], [ 34.956657772559488, -17.332156674199577 ], [ 34.956639212302683, -17.332303642053507 ], [ 34.956612669066011, -17.332449475535363 ], [ 34.956578215583725, -17.33259377492443 ], [ 34.956535946272076, -17.332736144703894 ], [ 34.95648597697074, -17.3328761946449 ], [ 34.95642844462553, -17.333013540876248 ], [ 34.956363506913185, -17.33314780693653 ], [ 34.956291341809404, -17.333278624806081 ], [ 34.956212147101148, -17.333405635915714 ], [ 34.956126139844635, -17.333528492129666 ], [ 34.956033555770553, -17.333646856699861 ], [ 34.955934648638085, -17.33376040518899 ], [ 34.955829689539371, -17.333868826359854 ], [ 34.955718966156532, -17.333971823028588 ], [ 34.95560278197329, -17.33406911287927 ], [ 34.955481455443014, -17.334160429237812 ], [ 34.955355319115959, -17.334245521803091 ], [ 34.955236330182871, -17.33431716598372 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.13", "sub_field": "1.13B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.954988844373986, -17.325074100205931 ], [ 34.955577723835631, -17.328974101137693 ], [ 34.955456933590519, -17.32899057899429 ], [ 34.955237672884792, -17.329009313455341 ], [ 34.955017691779858, -17.329017000371412 ], [ 34.95479759326777, -17.329013618667915 ], [ 34.954577980662954, -17.328999177610459 ], [ 34.954359455948037, -17.328973716779579 ], [ 34.95414261812347, -17.328937305962217 ], [ 34.9539280615653, -17.328890044960499 ], [ 34.953716374395555, -17.328832063318103 ], [ 34.953508136869821, -17.328763519965111 ], [ 34.953303919786507, -17.328684602782413 ], [ 34.953104282921842, -17.328595528086584 ], [ 34.952909773495264, -17.328496540036845 ], [ 34.952720924669258, -17.328387909965802 ], [ 34.952538254087635, -17.328269935635504 ], [ 34.95236226245656, -17.328142940421166 ], [ 34.952193432171818, -17.328007272424674 ], [ 34.952032225996575, -17.327863303520239 ], [ 34.95187908579274, -17.327711428334975 ], [ 34.951734431309859, -17.32755206316698 ], [ 34.951598659034502, -17.327385644844153 ], [ 34.951472141103615, -17.32721262952661 ], [ 34.951355224284541, -17.327033491456227 ], [ 34.951248229024607, -17.326848721656539 ], [ 34.951151448573079, -17.326658826586701 ], [ 34.951065148177435, -17.326464326753069 ], [ 34.950989564356654, -17.326265755282392 ], [ 34.950924904253185, -17.326063656460345 ], [ 34.95087134506548, -17.325858584239537 ], [ 34.950829033562627, -17.325651100720965 ], [ 34.950800585931511, -17.325458685865403 ], [ 34.954988844373986, -17.325074100205931 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.14", "sub_field": "1.14D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.959429692942393, -17.324632437480492 ], [ 34.96199524794266, -17.324405539234302 ], [ 34.962917097922038, -17.326827271055144 ], [ 34.9628057726948, -17.326860136943811 ], [ 34.962670123327506, -17.326892829017506 ], [ 34.962532878449238, -17.326918657395606 ], [ 34.962394414252785, -17.326937551279997 ], [ 34.962255110273766, -17.32694945888019 ], [ 34.96211534835011, -17.326954347555368 ], [ 34.961975511575361, -17.326952203903854 ], [ 34.961835983248399, -17.326943033799861 ], [ 34.961697145822697, -17.326926862377444 ], [ 34.961559379857924, -17.326903733961579 ], [ 34.961423062976593, -17.326873711946686 ], [ 34.961288568828891, -17.326836878622842 ], [ 34.961156266068393, -17.326793334950221 ], [ 34.961026517341416, -17.326743200282376 ], [ 34.96089967829289, -17.32668661203898 ], [ 34.960776096591395, -17.326623725329195 ], [ 34.960656110976146, -17.326554712526441 ], [ 34.960540050328412, -17.326479762795923 ], [ 34.960428232769921, -17.326399081575982 ], [ 34.960320964790888, -17.326312890015025 ], [ 34.960218540409869, -17.326221424365276 ], [ 34.960121240367819, -17.326124935335116 ], [ 34.960029331358555, -17.32602368740184 ], [ 34.959943065297743, -17.325917958086681 ], [ 34.959862678632533, -17.325808037194083 ], [ 34.959788391693294, -17.325694226017156 ], [ 34.959720408089943, -17.325576836511928 ], [ 34.959658914153827, -17.32545619044215 ], [ 34.959604078427063, -17.325332618497225 ], [ 34.959556051200735, -17.325206459385821 ], [ 34.959514964103008, -17.325078058907394 ], [ 34.959480929738518, -17.324947769004307 ], [ 34.9594540413798, -17.324815946797088 ], [ 34.959434372711804, -17.324682953605588 ], [ 34.959429692942393, -17.324632437480492 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.16", "sub_field": "1.16C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.955074672069358, -17.318897254404234 ], [ 34.953055168419084, -17.319242829332627 ], [ 34.953045135325517, -17.3191749687957 ], [ 34.953035406841693, -17.319069907036841 ], [ 34.953031416182405, -17.318964500208651 ], [ 34.953033174276065, -17.318859037225231 ], [ 34.953040676293959, -17.31875380715416 ], [ 34.953053901663694, -17.318649098424061 ], [ 34.953072814125669, -17.318545198034169 ], [ 34.953097361832498, -17.318442390767569 ], [ 34.953127477491357, -17.318340958410715 ], [ 34.9531630785484, -17.31824117898103 ], [ 34.953204067415207, -17.318143325964947 ], [ 34.953250331736328, -17.318047667568258 ], [ 34.953301744697342, -17.317954465981085 ], [ 34.953358165372507, -17.317863976659215 ], [ 34.953419439111137, -17.31777644762397 ], [ 34.953485397961508, -17.317692118782404 ], [ 34.953555861131257, -17.317611221269846 ], [ 34.953630635482966, -17.317533976816364 ], [ 34.953709516063618, -17.317460597139068 ], [ 34.953792286666292, -17.317391283361872 ], [ 34.953878720422843, -17.317326225464313 ], [ 34.953968580425695, -17.317265601760816 ], [ 34.954061620377189, -17.317209578412005 ], [ 34.954157585264667, -17.3171583089694 ], [ 34.954256212059335, -17.317111933954514 ], [ 34.954357230437225, -17.317070580473761 ], [ 34.954460363520035, -17.317034361870103 ], [ 34.954565328634033, -17.317003377412487 ], [ 34.954661651915764, -17.316980166566335 ], [ 34.955074672069358, -17.318897254404234 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.17", "sub_field": "1.17A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.954026875506962, -17.314612148368443 ], [ 34.95629602563065, -17.314112227198336 ], [ 34.956322154378064, -17.31421226865082 ], [ 34.956345251698423, -17.314325525156761 ], [ 34.95636214657646, -17.314439787577303 ], [ 34.956372792693529, -17.314554742729765 ], [ 34.956377160857997, -17.314670075532256 ], [ 34.956375239085375, -17.314785469867143 ], [ 34.956367032631277, -17.314900609447584 ], [ 34.956352563977227, -17.315015178684423 ], [ 34.956331872769042, -17.31512886355118 ], [ 34.956305015708395, -17.315241352444758 ], [ 34.956272066397489, -17.31535233703957 ], [ 34.956233115137429, -17.315461513132622 ], [ 34.956188268680854, -17.31556858147739 ], [ 34.956137649939436, -17.315673248603972 ], [ 34.956081397647068, -17.315775227623565 ], [ 34.95601966597976, -17.315874239014846 ], [ 34.955952624133047, -17.315970011390132 ], [ 34.955880455858406, -17.316062282239301 ], [ 34.955803358959564, -17.31615079864936 ], [ 34.955721544750475, -17.31623531799772 ], [ 34.955635237476123, -17.316315608617266 ], [ 34.955544673697872, -17.316391450431393 ], [ 34.955450101645134, -17.316462635557304 ], [ 34.955351780534926, -17.316528968875815 ], [ 34.955249979861421, -17.316590268566287 ], [ 34.955144978657209, -17.316646366605035 ], [ 34.955037064728437, -17.316697109225839 ], [ 34.954926533865958, -17.3167423573416 ], [ 34.954813689034438, -17.316781986925569 ], [ 34.954698839541891, -17.316815889351282 ], [ 34.95460479566993, -17.316838550983693 ], [ 34.954026875506962, -17.314612148368443 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.18", "sub_field": "1.18D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.959294104555354, -17.315720468707916 ], [ 34.961301964672387, -17.313838203379714 ], [ 34.963115666188799, -17.315822640664031 ], [ 34.963041894062059, -17.315884425490665 ], [ 34.962926783983661, -17.315971076915993 ], [ 34.962807110273779, -17.316051822842091 ], [ 34.962683200953926, -17.316126441941666 ], [ 34.96255539565685, -17.316194729680749 ], [ 34.962424044695666, -17.316256498879472 ], [ 34.962289508103467, -17.316311580225161 ], [ 34.962152154646418, -17.316359822736526 ], [ 34.962012360812984, -17.316401094177511 ], [ 34.961870509781747, -17.316435281419889 ], [ 34.961726990371027, -17.316462290753368 ], [ 34.961582195973008, -17.316482048142475 ], [ 34.961436523475342, -17.316494499429613 ], [ 34.961290372173067, -17.316499610483458 ], [ 34.961144142673987, -17.316497367292616 ], [ 34.96099823580051, -17.316487776003999 ], [ 34.960853051490787, -17.316470862906066 ], [ 34.960708987702283, -17.316446674356676 ], [ 34.960566439320871, -17.31641527665608 ], [ 34.960425797078265, -17.316376755865221 ], [ 34.960287446480869, -17.316331217569729 ], [ 34.960151766752965, -17.316278786590583 ], [ 34.960019129797111, -17.316219606641869 ], [ 34.959889899174662, -17.316153839936874 ], [ 34.959764429109072, -17.316081666743379 ], [ 34.959643063514939, -17.316003284889536 ], [ 34.959526135055206, -17.315918909221505 ], [ 34.959413964229249, -17.31582877101453 ], [ 34.959306858494422, -17.315733117339001 ], [ 34.959294104555354, -17.315720468707916 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "DL1.3", "sub_field": "DL1.3" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.947298927757849, -17.321663500672134 ], [ 34.947451385514611, -17.321753599029073 ], [ 34.947340653363916, -17.32175579751058 ], [ 34.947298927757849, -17.321663500672134 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.6", "sub_field": "1.6D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.978720327296884, -17.343280601630497 ], [ 34.978354668011164, -17.346604682049772 ], [ 34.974946239091686, -17.346248144213522 ], [ 34.974966677958683, -17.346086172049372 ], [ 34.974997699207229, -17.345915643565576 ], [ 34.975037970821617, -17.345746907989223 ], [ 34.975087382395003, -17.345580427809782 ], [ 34.97514579846969, -17.345416659333534 ], [ 34.975213058908558, -17.345256051432944 ], [ 34.97528897933433, -17.345099044316385 ], [ 34.97537335163512, -17.344946068321619 ], [ 34.975465944535074, -17.344797542736355 ], [ 34.975566504228468, -17.344653874649165 ], [ 34.975674755075552, -17.34451545783368 ], [ 34.975790400358186, -17.344382671669511 ], [ 34.975913123093221, -17.344255880102434 ], [ 34.976042586901428, -17.344135430647047 ], [ 34.976178436929551, -17.344021653434318 ], [ 34.976320300822913, -17.343914860306906 ], [ 34.976467789746074, -17.343815343964536 ], [ 34.97662049944848, -17.343723377161893 ], [ 34.976778011372502, -17.343639211961133 ], [ 34.976939893800484, -17.343563079041154 ], [ 34.977105703038013, -17.343495187065461 ], [ 34.977274984629851, -17.343435722110325 ], [ 34.977447274605353, -17.343384847154883 ], [ 34.977622100750068, -17.343342701634597 ], [ 34.97779898389976, -17.34330940105912 ], [ 34.977977439253515, -17.343285036695779 ], [ 34.978156977702334, -17.343269675319497 ], [ 34.978337107169416, -17.343263359029866 ], [ 34.978517333958685, -17.343266105135793 ], [ 34.978697164107672, -17.343277906108096 ], [ 34.978720327296884, -17.343280601630497 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.6", "sub_field": "1.6B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.977997053286906, -17.349855632180109 ], [ 34.978354668011164, -17.346604682049772 ], [ 34.981789841437561, -17.346964017488332 ], [ 34.981778585407078, -17.347053244850819 ], [ 34.981747570472095, -17.34722377374904 ], [ 34.981707304975053, -17.34739251006086 ], [ 34.981657899255794, -17.347558991288736 ], [ 34.981599488707879, -17.347722761114898 ], [ 34.981532233407833, -17.347883370652081 ], [ 34.981456317676674, -17.348040379673982 ], [ 34.981371949574829, -17.348193357821991 ], [ 34.981279360332159, -17.348341885784777 ], [ 34.981178803714307, -17.348485556447741 ], [ 34.981070555327335, -17.348623976009009 ], [ 34.980954911862447, -17.348756765058912 ], [ 34.980832190282847, -17.348883559620049 ], [ 34.980702726955109, -17.349004012144992 ], [ 34.980566876727238, -17.349117792469176 ], [ 34.980425011956086, -17.349224588715831 ], [ 34.98027752148672, -17.349324108151073 ], [ 34.980124809586627, -17.349416077986319 ], [ 34.979967294837536, -17.349500246126194 ], [ 34.979805408988021, -17.349576381859599 ], [ 34.979639595770003, -17.349644276492203 ], [ 34.979470309682334, -17.349703743918624 ], [ 34.979298014744906, -17.34975462113265 ], [ 34.979123183226534, -17.349796768674064 ], [ 34.978946294350337, -17.349830071011084 ], [ 34.978767832979948, -17.349854436857079 ], [ 34.978588288290318, -17.349869799420887 ], [ 34.978408152426617, -17.34987611658989 ], [ 34.978227919154975, -17.349873371045543 ], [ 34.97804808250892, -17.349861570310917 ], [ 34.977997053286906, -17.349855632180109 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.6", "sub_field": "1.6A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.978354668011164, -17.346604682049772 ], [ 34.978720327296884, -17.343280601630497 ], [ 34.978876104741104, -17.343298729600111 ], [ 34.979053665421596, -17.343328518536502 ], [ 34.979229359493594, -17.343367191269554 ], [ 34.979402705416973, -17.343414641803008 ], [ 34.979573228086664, -17.343470740082537 ], [ 34.979740460134565, -17.3435353323522 ], [ 34.979903943210367, -17.343608241575833 ], [ 34.980063229237601, -17.343689267922098 ], [ 34.980217881641593, -17.343778189312268 ], [ 34.980367476545865, -17.343874762028761 ], [ 34.98051160393382, -17.343978721383035 ], [ 34.980649868772417, -17.34408978244101 ], [ 34.980781892094825, -17.344207640803891 ], [ 34.980907312039015, -17.344331973442401 ], [ 34.981025784839666, -17.344462439582017 ], [ 34.981136985770199, -17.344598681636882 ], [ 34.981240610033026, -17.344740326189832 ], [ 34.9813363735949, -17.344886985015684 ], [ 34.981424013965515, -17.345038256145251 ], [ 34.981503290917125, -17.345193724966947 ], [ 34.981573987143072, -17.345352965363091 ], [ 34.981635908853576, -17.345515540877745 ], [ 34.981688886307055, -17.345681005912901 ], [ 34.981732774275621, -17.345848906949652 ], [ 34.981767452443343, -17.346018783791244 ], [ 34.981792825736257, -17.34619017082429 ], [ 34.981808824583204, -17.346362598294956 ], [ 34.981815405106829, -17.346535593596407 ], [ 34.981812549244125, -17.346708682564159 ], [ 34.981800264796163, -17.3468813907757 ], [ 34.981789841437561, -17.346964017488332 ], [ 34.978354668011164, -17.346604682049772 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.5", "sub_field": "1.5B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.981414279389675, -17.341507128753143 ], [ 34.981172574054533, -17.343691265597464 ], [ 34.981109475823907, -17.343683923580347 ], [ 34.980985765165556, -17.343663170309824 ], [ 34.980863355007244, -17.343636227310451 ], [ 34.980742580878861, -17.343603168432548 ], [ 34.980623773825592, -17.343564084290307 ], [ 34.980507259500335, -17.343519082013337 ], [ 34.980393357270984, -17.343468284952998 ], [ 34.980282379345013, -17.343411832344319 ], [ 34.980174629913549, -17.343349878924265 ], [ 34.980070404317615, -17.343282594507567 ], [ 34.979969988238452, -17.343210163521317 ], [ 34.979873656914428, -17.343132784499232 ], [ 34.979781674386686, -17.343050669537625 ], [ 34.9796942927752, -17.34296404371387 ], [ 34.979611751587868, -17.342873144469429 ], [ 34.979534277063955, -17.34277822095903 ], [ 34.979462081553976, -17.342679533367615 ], [ 34.979395362937744, -17.3425773521972 ], [ 34.979334304081959, -17.342471957525312 ], [ 34.979279072339089, -17.34236363823727 ], [ 34.979229819088637, -17.342252691234346 ], [ 34.979186679322453, -17.342139420619834 ], [ 34.979149771274642, -17.342024136865543 ], [ 34.979119196097734, -17.341907155960765 ], [ 34.979095037585431, -17.341788798546016 ], [ 34.979077361943133, -17.341669389034291 ], [ 34.979066217606537, -17.341549254721702 ], [ 34.97906163510909, -17.341428724890427 ], [ 34.979063626998311, -17.341308129906089 ], [ 34.979069772171925, -17.341221754105334 ], [ 34.981414279389675, -17.341507128753143 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.5", "sub_field": "1.5D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.981414279389675, -17.341507128753143 ], [ 34.981679444479695, -17.339111001077271 ], [ 34.981686301740211, -17.339111450993876 ], [ 34.981810970678175, -17.339125957608928 ], [ 34.981934678272594, -17.339146710678925 ], [ 34.982057085462138, -17.339173653321986 ], [ 34.982177856749416, -17.339206711691663 ], [ 34.98229666112038, -17.339245795179348 ], [ 34.982413172951517, -17.339290796662603 ], [ 34.982527072902251, -17.33934159279875 ], [ 34.982638048790086, -17.339398044362888 ], [ 34.982745796446203, -17.339459996629525 ], [ 34.98285002054908, -17.339527279796517 ], [ 34.982950435433793, -17.339599709450528 ], [ 34.983046765875045, -17.339677087072367 ], [ 34.983138747841409, -17.33975920058111 ], [ 34.983226129219041, -17.339845824915287 ], [ 34.983308670502595, -17.339936722649764 ], [ 34.983386145451767, -17.340031644646341 ], [ 34.983458341711369, -17.340130330736663 ], [ 34.983525061393408, -17.340232510435182 ], [ 34.98358612161946, -17.340337903680478 ], [ 34.983641355022087, -17.340446221602868 ], [ 34.983690610203553, -17.340557167316049 ], [ 34.983733752150904, -17.340670436730836 ], [ 34.983770662606105, -17.340785719388574 ], [ 34.98380124039025, -17.340902699312007 ], [ 34.983825401681088, -17.341021055871334 ], [ 34.983843080242778, -17.341140464662963 ], [ 34.983854227607615, -17.341260598398659 ], [ 34.983858813209004, -17.341381127802574 ], [ 34.983856824465377, -17.341501722513755 ], [ 34.983848266814846, -17.341622051991578 ], [ 34.983833163700361, -17.341741786421814 ], [ 34.983822529204822, -17.341800262183327 ], [ 34.981414279389675, -17.341507128753143 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.5", "sub_field": "1.5A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.981172574054533, -17.343691265597464 ], [ 34.981414279389675, -17.341507128753143 ], [ 34.983822529204822, -17.341800262183327 ], [ 34.983811556505685, -17.341860597620538 ], [ 34.98378350444203, -17.341978159933678 ], [ 34.983749084385991, -17.342094151129654 ], [ 34.983708390668845, -17.342208253282561 ], [ 34.983661534818218, -17.342320153643641 ], [ 34.983608645252517, -17.342429545498533 ], [ 34.983549866928996, -17.342536129007982 ], [ 34.983485360946574, -17.342639612029675 ], [ 34.983415304104362, -17.34273971091913 ], [ 34.983339888417177, -17.342836151307104 ], [ 34.983259320589269, -17.342928668851723 ], [ 34.983173821447792, -17.343017009963088 ], [ 34.983083625337699, -17.343100932498373 ], [ 34.982988979479238, -17.343180206425632 ], [ 34.982890143290554, -17.343254614454327 ], [ 34.982787387676503, -17.343323952631003 ], [ 34.982680994286177, -17.343388030898371 ], [ 34.982571254740826, -17.343446673616306 ], [ 34.982458469834533, -17.343499720043344 ], [ 34.982342948709736, -17.343547024777287 ], [ 34.982225008009713, -17.343588458153864 ], [ 34.982104971010692, -17.343623906602136 ], [ 34.981983166735645, -17.343653272955859 ], [ 34.981859929052334, -17.343676476719857 ], [ 34.981735595758096, -17.343693454290744 ], [ 34.981610507653819, -17.343704159131189 ], [ 34.981485007609791, -17.343708561897607 ], [ 34.981359439625614, -17.343706650520566 ], [ 34.981234147887363, -17.3436984302379 ], [ 34.981172574054533, -17.343691265597464 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.4", "sub_field": "1.4A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.977605005473734, -17.338606017387828 ], [ 34.977665672603734, -17.33863431107558 ], [ 34.977763772866389, -17.338690719975755 ], [ 34.977876169269216, -17.338763283345308 ], [ 34.977984457548132, -17.338841396753352 ], [ 34.978088340898189, -17.338924846102827 ], [ 34.978187534586567, -17.339013402671572 ], [ 34.978281766732948, -17.339106823739105 ], [ 34.978370779054657, -17.339204853251911 ], [ 34.978454327574674, -17.339307222525107 ], [ 34.97853218329027, -17.339413650978859 ], [ 34.978604132800747, -17.339523846907355 ], [ 34.978669978892427, -17.33963750827823 ], [ 34.978729541079169, -17.339754323560388 ], [ 34.978782656097266, -17.339873972577784 ], [ 34.97882917835291, -17.339996127386925 ], [ 34.978868980321394, -17.34012045317569 ], [ 34.978901952896805, -17.340246609181008 ], [ 34.978928005691152, -17.340374249622688 ], [ 34.978947067282256, -17.340503024651259 ], [ 34.978959085409684, -17.340632581306743 ], [ 34.978964027118096, -17.340762564486102 ], [ 34.978961878847812, -17.340892617916531 ], [ 34.978952646472038, -17.341022385131897 ], [ 34.97893635528105, -17.34115151044988 ], [ 34.978926265695563, -17.341206981511462 ], [ 34.976297118077937, -17.340867839790402 ], [ 34.976670537115623, -17.338320549892174 ], [ 34.976755638684374, -17.338330454697513 ], [ 34.976889048044576, -17.338352838997491 ], [ 34.977004841715654, -17.33837832923189 ], [ 34.977605005473734, -17.338606017387828 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.4", "sub_field": "1.4C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.976297118077937, -17.340867839790402 ], [ 34.975949718590073, -17.343237636498074 ], [ 34.97586557276648, -17.343223518187639 ], [ 34.975733562536121, -17.343194458466325 ], [ 34.975603316843497, -17.343158803210262 ], [ 34.975475192696571, -17.343116650150446 ], [ 34.975349541287549, -17.343068114828622 ], [ 34.97522670703011, -17.343013330280517 ], [ 34.975107026615298, -17.342952446671219 ], [ 34.974990828088544, -17.34288563088344 ], [ 34.974878429950408, -17.342813066060131 ], [ 34.974770140283518, -17.342734951102397 ], [ 34.974666255908012, -17.342651500124298 ], [ 34.974567061567939, -17.342562941865822 ], [ 34.974472829150777, -17.342469519065983 ], [ 34.974383816942158, -17.342371487797241 ], [ 34.974300268917865, -17.342269116763756 ], [ 34.974222414075207, -17.34216268656462 ], [ 34.974150465805295, -17.342052488924793 ], [ 34.974084621308229, -17.341938825895362 ], [ 34.974025061052657, -17.341822009025627 ], [ 34.973971948281175, -17.341702358509004 ], [ 34.973925428562943, -17.341580202305412 ], [ 34.973885629394871, -17.34145587524225 ], [ 34.973852659852184, -17.341329718096571 ], [ 34.973826610289663, -17.34120207666103 ], [ 34.973807552094073, -17.341073300796005 ], [ 34.973795537488641, -17.340943743470646 ], [ 34.973790599390107, -17.340813759795306 ], [ 34.973792751318612, -17.340683706048242 ], [ 34.973801987360879, -17.340553938699045 ], [ 34.973802974953465, -17.340546112708161 ], [ 34.976297118077937, -17.340867839790402 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.4", "sub_field": "1.4D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.976670537115623, -17.338320549892174 ], [ 34.976297118077937, -17.340867839790402 ], [ 34.973802974953465, -17.340546112708161 ], [ 34.973818282186457, -17.340424813431532 ], [ 34.973841591117456, -17.34029668416888 ], [ 34.973871850251086, -17.340169902103536 ], [ 34.973908976634981, -17.340044814734618 ], [ 34.973952868494727, -17.33992176491547 ], [ 34.974003405512995, -17.339801089913962 ], [ 34.974060449159396, -17.339683120488104 ], [ 34.974123843070416, -17.339568179979455 ], [ 34.974193413478005, -17.339456583426976 ], [ 34.97426896968603, -17.339348636703559 ], [ 34.974350304593074, -17.339244635677673 ], [ 34.974437195260094, -17.339144865402556 ], [ 34.974529403521622, -17.339049599334913 ], [ 34.974626676638515, -17.338959098585459 ], [ 34.974728747990838, -17.338873611203347 ], [ 34.974835337808571, -17.338793371496333 ], [ 34.974946153938468, -17.338718599388653 ], [ 34.975060892644805, -17.338649499818292 ], [ 34.975179239441857, -17.338586262175358 ], [ 34.975300869955831, -17.338529059782996 ], [ 34.975425450813887, -17.338478049422477 ], [ 34.975552640557751, -17.338433370903498 ], [ 34.975682090579618, -17.338395146681059 ], [ 34.975813446077481, -17.338363481519838 ], [ 34.975946347027502, -17.33833846220719 ], [ 34.976080429170672, -17.338320157315209 ], [ 34.976215325011133, -17.338308617012903 ], [ 34.976350664823187, -17.338303872928698 ], [ 34.976486077664653, -17.338305938063733 ], [ 34.976621192393402, -17.338314806756351 ], [ 34.976670537115623, -17.338320549892174 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.2", "sub_field": "1.2A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.979495810456342, -17.334203961691134 ], [ 34.97820349148671, -17.336307953800048 ], [ 34.976145471591806, -17.334907946244652 ], [ 34.976175428365991, -17.334865145367932 ], [ 34.976253802797373, -17.334764925673479 ], [ 34.976337530859709, -17.334668782842019 ], [ 34.976426383056278, -17.334576980388171 ], [ 34.976520115846917, -17.33448976992964 ], [ 34.976618472315508, -17.334407390497606 ], [ 34.976721182874279, -17.334330067881691 ], [ 34.976827966002574, -17.334258014011112 ], [ 34.976938529018604, -17.334191426373845 ], [ 34.977052568881554, -17.334130487475448 ], [ 34.977169773022176, -17.334075364338887 ], [ 34.977289820199417, -17.334026208046808 ], [ 34.977412381380873, -17.333983153327463 ], [ 34.977537120644556, -17.333946318185511 ], [ 34.977663696099462, -17.333915803578684 ], [ 34.977791760822647, -17.333891693141041 ], [ 34.977920963809922, -17.333874052953824 ], [ 34.978050950937828, -17.333862931364354 ], [ 34.978181365934098, -17.333858358853604 ], [ 34.978311851354057, -17.333860347952598 ], [ 34.978442049560201, -17.333868893208162 ], [ 34.978571603702264, -17.333883971197807 ], [ 34.978700158695197, -17.333905540594017 ], [ 34.978827362192298, -17.33393354227746 ], [ 34.978952865550774, -17.333967899499068 ], [ 34.97907632478725, -17.334008518090307 ], [ 34.979197401520416, -17.334055286721373 ], [ 34.979315763898441, -17.334108077206196 ], [ 34.979431087508353, -17.334166744853853 ], [ 34.979495810456342, -17.334203961691134 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.2", "sub_field": "1.2C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.976973009965533, -17.338311269909727 ], [ 34.97820349148671, -17.336307953800048 ], [ 34.980211685376219, -17.337674066258426 ], [ 34.980160358782953, -17.337739701106912 ], [ 34.98007663167305, -17.33783584545019 ], [ 34.979987780081963, -17.337927649472633 ], [ 34.979894047543212, -17.338014861539353 ], [ 34.979795690970128, -17.338097242601521 ], [ 34.979692979951686, -17.338174566851677 ], [ 34.979586196013528, -17.338246622342712 ], [ 34.979475631846341, -17.33831321156887 ], [ 34.979361590503522, -17.338374152007198 ], [ 34.979244384570571, -17.338429276617834 ], [ 34.979124335308164, -17.338478434302029 ], [ 34.979001771771522, -17.338521490316253 ], [ 34.978877029908503, -17.33855832664165 ], [ 34.978750451638518, -17.338588842307534 ], [ 34.978622383915415, -17.338612953668228 ], [ 34.978493177776194, -17.338630594632406 ], [ 34.978363187378896, -17.338641716844212 ], [ 34.978232769031571, -17.338646289815852 ], [ 34.978102280215623, -17.338644301011275 ], [ 34.977972078605781, -17.338635755880404 ], [ 34.97784252108957, -17.338620677844375 ], [ 34.977713962789039, -17.338599108231218 ], [ 34.977586756087099, -17.338571106162615 ], [ 34.97746124966168, -17.338536748391885 ], [ 34.977445054809721, -17.33853142025189 ], [ 34.976973009965533, -17.338311269909727 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.2", "sub_field": "1.2B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.97820349148671, -17.336307953800048 ], [ 34.979495810456342, -17.334203961691134 ], [ 34.979543056265214, -17.334231128865 ], [ 34.979651363278293, -17.334301052772663 ], [ 34.979755711692235, -17.334376324925834 ], [ 34.979855815500571, -17.334456739014655 ], [ 34.979951400329632, -17.334542074635905 ], [ 34.980042204190553, -17.334632097897025 ], [ 34.980127978197274, -17.334726562057128 ], [ 34.980208487248802, -17.334825208203203 ], [ 34.980283510673523, -17.334927765959726 ], [ 34.980352842834122, -17.33503395422969 ], [ 34.980416293691249, -17.335143481964927 ], [ 34.980473689324427, -17.335256048963895 ], [ 34.980524872408871, -17.335371346694291 ], [ 34.980569702646726, -17.335489059138776 ], [ 34.98060805715177, -17.335608863661058 ], [ 34.980639830786288, -17.33573043189012 ], [ 34.980664936449386, -17.335853430620244 ], [ 34.980683305315878, -17.335977522724271 ], [ 34.980694887025031, -17.336102368077537 ], [ 34.980699649818746, -17.336227624490192 ], [ 34.980697580628785, -17.336352948644983 ], [ 34.980688685112696, -17.336477997038344 ], [ 34.980672987638499, -17.336602426921839 ], [ 34.980650531218004, -17.336725897241585 ], [ 34.980621377389127, -17.336848069573168 ], [ 34.980585606047292, -17.336968609049123 ], [ 34.980543315226697, -17.337087185276864 ], [ 34.980494620831621, -17.337203473244294 ], [ 34.980439656318971, -17.337317154210655 ], [ 34.98037857233254, -17.337427916580229 ], [ 34.980311536290245, -17.337535456756427 ], [ 34.980238731925354, -17.337639479973969 ], [ 34.980211685376219, -17.337674066258426 ], [ 34.97820349148671, -17.336307953800048 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.1", "sub_field": "1.1A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.974747055587912, -17.330561624074441 ], [ 34.975967002903133, -17.329006613978088 ], [ 34.97768378767087, -17.330124305102657 ], [ 34.977675645322599, -17.330135938962055 ], [ 34.977610877597876, -17.330218762285984 ], [ 34.977541685523548, -17.330298216410046 ], [ 34.97746825874755, -17.330374083551767 ], [ 34.97739079852591, -17.330446155760175 ], [ 34.97730951717098, -17.330514235485847 ], [ 34.977224637469618, -17.330578136122426 ], [ 34.977136392072467, -17.330637682518109 ], [ 34.977045022856281, -17.330692711455839 ], [ 34.976950780260992, -17.33074307210066 ], [ 34.976853922603134, -17.330788626413213 ], [ 34.976754715367839, -17.330829249528179 ], [ 34.9766534304811, -17.330864830096498 ], [ 34.976550345564355, -17.330895270590695 ], [ 34.976445743173485, -17.330920487572168 ], [ 34.976339910024251, -17.330940411919936 ], [ 34.976233136206396, -17.330954989020189 ], [ 34.976125714388374, -17.330964178915902 ], [ 34.97601793901508, -17.33096795641648 ], [ 34.975910105500709, -17.330966311166762 ], [ 34.975802509418969, -17.330959247675441 ], [ 34.975695445692772, -17.330946785302704 ], [ 34.975589207785838, -17.330928958207167 ], [ 34.975484086898128, -17.330905815252294 ], [ 34.975380371167688, -17.330877419872376 ], [ 34.975278344880749, -17.330843849898734 ], [ 34.975178287692358, -17.330805197346297 ], [ 34.975080473859883, -17.33076156816146 ], [ 34.97498517149112, -17.330713081931542 ], [ 34.974892641809419, -17.330659871557128 ], [ 34.974803138437572, -17.330602082887651 ], [ 34.974747055587912, -17.330561624074441 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.1", "sub_field": "1.1C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.977225538507916, -17.327402417183986 ], [ 34.975967002903133, -17.329006613978088 ], [ 34.974274653035941, -17.327904830888109 ], [ 34.974317836853025, -17.327843131256433 ], [ 34.974382605457386, -17.327760308914549 ], [ 34.974451798181676, -17.327680855822567 ], [ 34.974525225370748, -17.327604989751688 ], [ 34.974602685764104, -17.327532918641111 ], [ 34.974683967047483, -17.327464840028238 ], [ 34.97476884643482, -17.327400940507246 ], [ 34.974857091278928, -17.327341395217715 ], [ 34.974948459709061, -17.327286367364568 ], [ 34.975042701293965, -17.327236007770892 ], [ 34.975139557728149, -17.327190454464535 ], [ 34.975238763539863, -17.327149832299764 ], [ 34.975340046818744, -17.32711425261525 ], [ 34.975443129960965, -17.327083812928787 ], [ 34.975547730430073, -17.327058596670124 ], [ 34.97565356153136, -17.32703867295232 ], [ 34.975760333197549, -17.327024096382285 ], [ 34.975867752783742, -17.32701490691122 ], [ 34.975975525869487, -17.327011129725086 ], [ 34.976083357065676, -17.327012775175607 ], [ 34.976190950823955, -17.327019838751866 ], [ 34.976298012246922, -17.327032301092764 ], [ 34.97640424789607, -17.327050128040007 ], [ 34.976509366596161, -17.327073270731798 ], [ 34.976613080233122, -17.327101665736684 ], [ 34.976715104543686, -17.327135235227491 ], [ 34.976815159894414, -17.327173887194547 ], [ 34.976912972048119, -17.327217515697914 ], [ 34.977008272915363, -17.327266001157675 ], [ 34.977100801289254, -17.327319210681736 ], [ 34.977190303561343, -17.32737699842998 ], [ 34.977225538507916, -17.327402417183986 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.1", "sub_field": "1.1B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.975967002903133, -17.329006613978088 ], [ 34.974747055587912, -17.330561624074441 ], [ 34.974716906702625, -17.330539874321609 ], [ 34.974634182963371, -17.330473416372392 ], [ 34.974555193962502, -17.330402891200858 ], [ 34.974480156205075, -17.330328492116003 ], [ 34.974409275365083, -17.330250423045062 ], [ 34.974342745721671, -17.330168897974517 ], [ 34.974280749626672, -17.330084140363468 ], [ 34.974223457004811, -17.329996382531196 ], [ 34.974171024887951, -17.329905865020287 ], [ 34.974123596984739, -17.329812835937251 ], [ 34.974081303286702, -17.329717550272466 ], [ 34.974044259712088, -17.329620269201229 ], [ 34.974012567788179, -17.329521259367823 ], [ 34.973986314373022, -17.329420792154632 ], [ 34.973965571417502, -17.329319142938278 ], [ 34.973950395768235, -17.329216590334781 ], [ 34.973940829011752, -17.329113415435877 ], [ 34.973936897360709, -17.329009901038535 ], [ 34.973938611582099, -17.328906330869806 ], [ 34.973945966967818, -17.328802988809159 ], [ 34.973958943347753, -17.328700158110344 ], [ 34.973977505145072, -17.328598120625017 ], [ 34.974001601473894, -17.328497156030238 ], [ 34.974031166278891, -17.328397541061847 ], [ 34.974066118516369, -17.328299548756014 ], [ 34.974106362376588, -17.328203447700862 ], [ 34.974151787546347, -17.328109501300297 ], [ 34.97420226951153, -17.328017967052048 ], [ 34.974257669898414, -17.327929095841995 ], [ 34.974274653035941, -17.327904830888109 ], [ 34.975967002903133, -17.329006613978088 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.3", "sub_field": "1.3C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.971195967655134, -17.332888509205493 ], [ 34.970773107481506, -17.335663616039604 ], [ 34.967900765229288, -17.335206638144747 ], [ 34.967920610457007, -17.335097567226367 ], [ 34.967954614815163, -17.334955110851492 ], [ 34.967996335195423, -17.334814558954029 ], [ 34.968045657228011, -17.33467629677391 ], [ 34.968102445708503, -17.334540703274193 ], [ 34.968166544968589, -17.334408150102437 ], [ 34.968237779302939, -17.334279000572078 ], [ 34.968315953450919, -17.334153608666739 ], [ 34.968400853131925, -17.334032318069912 ], [ 34.968492245632852, -17.333915461223164 ], [ 34.968589880446025, -17.333803358414983 ], [ 34.968693489955889, -17.333696316902909 ], [ 34.968802790172646, -17.333594630071527 ], [ 34.968917481510559, -17.333498576628386 ], [ 34.969037249609258, -17.333408419840225 ], [ 34.969161766195299, -17.333324406811389 ], [ 34.969290689981875, -17.333246767806713 ], [ 34.969423667604325, -17.333175715620431 ], [ 34.969560334588486, -17.333111444993076 ], [ 34.969700316349666, -17.333054132077738 ], [ 34.969843229219229, -17.333003933957372 ], [ 34.969988681496098, -17.332960988214293 ], [ 34.970136274520222, -17.332925412553223 ], [ 34.970285603765085, -17.33289730447866 ], [ 34.970436259946382, -17.332876741027736 ], [ 34.970587830143572, -17.332863778559105 ], [ 34.970739898931548, -17.332858452598554 ], [ 34.970892049519023, -17.332860777741558 ], [ 34.97104386489081, -17.332870747613462 ], [ 34.971194928950496, -17.332888334886835 ], [ 34.971195967655134, -17.332888509205493 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.3", "sub_field": "1.3A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.970354223381719, -17.338412629071641 ], [ 34.970773107481506, -17.335663616039604 ], [ 34.973634084154476, -17.336118785717325 ], [ 34.973618904232666, -17.336202230245593 ], [ 34.973584904230833, -17.33634468714585 ], [ 34.973543188019221, -17.336485239791532 ], [ 34.97349386992196, -17.336623502934522 ], [ 34.973437085100322, -17.336759097601192 ], [ 34.973372989182529, -17.336891652131232 ], [ 34.973301757837334, -17.337020803196278 ], [ 34.973223586292583, -17.337146196795945 ], [ 34.973138688800368, -17.33726748922814 ], [ 34.973047298049813, -17.337384348031179 ], [ 34.972949664529423, -17.337496452895188 ], [ 34.97284605584057, -17.337603496540073 ], [ 34.972736755964092, -17.337705185557869 ], [ 34.972622064481968, -17.337801241217083 ], [ 34.972502295756165, -17.337891400226717 ], [ 34.972377778066971, -17.337975415458054 ], [ 34.9722488527133, -17.338053056622162 ], [ 34.972115873076987, -17.338124110901102 ], [ 34.97197920365425, -17.338188383531453 ], [ 34.97183921905652, -17.338245698338167 ], [ 34.97169630298346, -17.338295898217574 ], [ 34.971550847171287, -17.338338845568071 ], [ 34.971403250318779, -17.338374422667343 ], [ 34.971253916994385, -17.338402531995111 ], [ 34.971103256527137, -17.338423096500538 ], [ 34.970951681884493, -17.3384360598134 ], [ 34.970799608540283, -17.338441386398674 ], [ 34.970647453335715, -17.338439061654018 ], [ 34.970495633336562, -17.338429091949749 ], [ 34.970354223381719, -17.338412629071641 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.3", "sub_field": "1.3D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.970773107481506, -17.335663616039604 ], [ 34.971195967655134, -17.332888509205493 ], [ 34.971344827660843, -17.33291349135645 ], [ 34.971493150178375, -17.332946148071397 ], [ 34.971639489979275, -17.332986215524052 ], [ 34.971783445973415, -17.333033583895354 ], [ 34.971924623603627, -17.333088123355868 ], [ 34.972062635926825, -17.333149684421517 ], [ 34.972197104674571, -17.333218098363261 ], [ 34.972327661289661, -17.333293177669553 ], [ 34.97245394793616, -17.333374716560179 ], [ 34.972575618480157, -17.333462491550247 ], [ 34.972692339438325, -17.333556262062647 ], [ 34.972803790891966, -17.333655771087365 ], [ 34.972909667363737, -17.333760745885836 ], [ 34.973009678655011, -17.333870898738422 ], [ 34.973103550641191, -17.33398592773289 ], [ 34.973191026023137, -17.334105517591908 ], [ 34.973271865032345, -17.334229340537018 ], [ 34.9733458460883, -17.33435705718701 ], [ 34.973412766405815, -17.334488317488017 ], [ 34.973472442550914, -17.334622761672907 ], [ 34.973524710943792, -17.334760021247295 ], [ 34.973569428307272, -17.334899719999441 ], [ 34.973606472059643, -17.335041475031371 ], [ 34.973635740650828, -17.335184897808336 ], [ 34.973657153840904, -17.335329595223612 ], [ 34.973670652920205, -17.335475170675963 ], [ 34.973676200870415, -17.335621225156661 ], [ 34.973673782466292, -17.335767358343112 ], [ 34.973663404317563, -17.335913169696045 ], [ 34.97364509485098, -17.336058259557369 ], [ 34.973634084154476, -17.336118785717325 ], [ 34.970773107481506, -17.335663616039604 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.7", "sub_field": "1.7B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.96697037363257, -17.340380748223968 ], [ 34.968913119447087, -17.339561990788184 ], [ 34.968924732557923, -17.339588154703865 ], [ 34.968961776131572, -17.339685436701803 ], [ 34.96899346779216, -17.339784447337809 ], [ 34.969019720667752, -17.339884915233949 ], [ 34.969040462793117, -17.33998656501765 ], [ 34.969055637307129, -17.340089118076534 ], [ 34.969065202608647, -17.340192293321937 ], [ 34.969069132470679, -17.34029580795945 ], [ 34.969067416112331, -17.340399378263918 ], [ 34.969060058228507, -17.340502720357161 ], [ 34.969047078977077, -17.340605550986041 ], [ 34.969028513923831, -17.340707588298791 ], [ 34.969004413945001, -17.340808552617649 ], [ 34.968974845087928, -17.340908167205317 ], [ 34.968939888390231, -17.341006159023614 ], [ 34.968899639657678, -17.341102259481772 ], [ 34.968854209201659, -17.341196205172718 ], [ 34.968803721537007, -17.341287738595042 ], [ 34.968748315040735, -17.341376608858798 ], [ 34.968688141572834, -17.341462572373302 ], [ 34.968623366060072, -17.341545393514739 ], [ 34.968554166044022, -17.341624845272079 ], [ 34.96848073119444, -17.341700709869322 ], [ 34.968403262789458, -17.341772779362497 ], [ 34.968321973163881, -17.3418408562096 ], [ 34.968237085127249, -17.341904753812166 ], [ 34.968148831353027, -17.341964297026713 ], [ 34.968057453740961, -17.342019322644887 ], [ 34.967963202754007, -17.34206967984084 ], [ 34.967866336731689, -17.342115230584671 ], [ 34.967862993493888, -17.342116599326115 ], [ 34.96697037363257, -17.340380748223968 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.7", "sub_field": "1.7D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.96697037363257, -17.340380748223968 ], [ 34.965141978743468, -17.34115131321493 ], [ 34.965135870092325, -17.341139330722289 ], [ 34.965093577571821, -17.341044043052051 ], [ 34.965056535608213, -17.340946760170706 ], [ 34.965024845724365, -17.340847748727509 ], [ 34.964998594772723, -17.340747280109223 ], [ 34.964977854697416, -17.340645629696343 ], [ 34.964962682337145, -17.340543076108169 ], [ 34.964953119269445, -17.340439900439126 ], [ 34.96494919169681, -17.340336385488293 ], [ 34.964950910375102, -17.340232814984212 ], [ 34.964958270583949, -17.340129472807298 ], [ 34.964971252140032, -17.340026642211601 ], [ 34.964989819452306, -17.339924605048481 ], [ 34.965013921619786, -17.339823640994112 ], [ 34.965043492571134, -17.339724026782864 ], [ 34.965078451245816, -17.339626035448813 ], [ 34.965118701816422, -17.339529935577406 ], [ 34.965164133951369, -17.339435990569296 ], [ 34.965214623117454, -17.339344457918411 ], [ 34.965270030921225, -17.339255588506234 ], [ 34.965330205488343, -17.339169625914124 ], [ 34.965394981880017, -17.339086805755809 ], [ 34.965464182545041, -17.33900735503153 ], [ 34.965537617806547, -17.338931491505967 ], [ 34.965615086381874, -17.338859423111348 ], [ 34.965696375934364, -17.338791347377654 ], [ 34.965781263655309, -17.338727450891149 ], [ 34.965869516874641, -17.338667908783069 ], [ 34.965960893698714, -17.338612884249663 ], [ 34.966039653327748, -17.338570804330793 ], [ 34.96697037363257, -17.340380748223968 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.7", "sub_field": "1.7A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.968913119447087, -17.339561990788184 ], [ 34.96697037363257, -17.340380748223968 ], [ 34.966039653327748, -17.338570804330793 ], [ 34.966055143673252, -17.338562528104891 ], [ 34.966152008469777, -17.338516978367149 ], [ 34.96625122259362, -17.338476359880929 ], [ 34.966352514111676, -17.338440783974821 ], [ 34.966455605397492, -17.338410348156252 ], [ 34.966560213892357, -17.33838513584438 ], [ 34.966666052879539, -17.338365216141444 ], [ 34.966772832270152, -17.338350643643359 ], [ 34.96688025939816, -17.338341458290188 ], [ 34.966988039822454, -17.338337685256583 ], [ 34.967095878133762, -17.338339334882878 ], [ 34.967203478764311, -17.338346402646742 ], [ 34.967310546797805, -17.338358869175604 ], [ 34.967416788777662, -17.33837670029968 ], [ 34.967521913511277, -17.338399847145745 ], [ 34.967625632868057, -17.338428246270951 ], [ 34.967727662569054, -17.338461819836844 ], [ 34.967827722966078, -17.338500475822592 ], [ 34.967925539808057, -17.338544108277233 ], [ 34.968020844992765, -17.338592597610052 ], [ 34.968113377301442, -17.338645810918319 ], [ 34.968202883114863, -17.338703602351565 ], [ 34.968289117108299, -17.338765813511245 ], [ 34.968371842924007, -17.338832273884954 ], [ 34.968450833818949, -17.338902801313676 ], [ 34.968525873286232, -17.33897720249103 ], [ 34.968596755648612, -17.339055273493063 ], [ 34.968663286622174, -17.339136800337148 ], [ 34.968725283848819, -17.339221559568486 ], [ 34.968782577396205, -17.339309318872438 ], [ 34.968835010223451, -17.339399837711309 ], [ 34.968882438611672, -17.339492867983591 ], [ 34.968913119447087, -17.339561990788184 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.8", "sub_field": "1.8C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.958846576270247, -17.339420347508153 ], [ 34.961161225337996, -17.342695153682683 ], [ 34.957665162047512, -17.344762020273741 ], [ 34.957619750775827, -17.344692455184319 ], [ 34.957515131525668, -17.344511824153546 ], [ 34.95742049925115, -17.344326182128594 ], [ 34.957336113312707, -17.344136037957821 ], [ 34.957262204982982, -17.343941912828704 ], [ 34.957198976813203, -17.343744338839166 ], [ 34.957146602078303, -17.343543857538954 ], [ 34.957105224302296, -17.34334101844518 ], [ 34.957074956865263, -17.343136377535984 ], [ 34.957055882692877, -17.342930495726488 ], [ 34.957048054029585, -17.342723937331343 ], [ 34.957051492295761, -17.342517268517859 ], [ 34.957066188029401, -17.342311055754124 ], [ 34.957092100912497, -17.342105864256347 ], [ 34.957129159881944, -17.341902256439582 ], [ 34.957177263324716, -17.341700790376251 ], [ 34.95723627935687, -17.341502018266478 ], [ 34.957306046185302, -17.341306484924623 ], [ 34.957386372551667, -17.341114726286037 ], [ 34.957477038256947, -17.34092726793822 ], [ 34.957577794765285, -17.340744623680305 ], [ 34.957688365885573, -17.340567294114859 ], [ 34.957808448528688, -17.340395765275964 ], [ 34.957937713538506, -17.340230507297168 ], [ 34.958075806594252, -17.340071973122999 ], [ 34.958222349181902, -17.339920597267728 ], [ 34.958376939631663, -17.339776794624512 ], [ 34.95853915421911, -17.339640959328449 ], [ 34.958708548326541, -17.339513463676425 ], [ 34.958846576270247, -17.339420347508153 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.8", "sub_field": "1.8A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.963463180156758, -17.345951999810669 ], [ 34.961161225337996, -17.342695153682683 ], [ 34.964664424622271, -17.340624068304397 ], [ 34.964697486842347, -17.340674713360091 ], [ 34.964802110127501, -17.34085534020641 ], [ 34.964896747286502, -17.341040978226815 ], [ 34.964981138905401, -17.341231118616804 ], [ 34.965055053649337, -17.34142524023035 ], [ 34.965118288896797, -17.34162281100804 ], [ 34.965170671295283, -17.3418232894353 ], [ 34.965212057236833, -17.342026126026514 ], [ 34.965242333251901, -17.342230764830955 ], [ 34.96526141632085, -17.342436644956557 ], [ 34.96526925410172, -17.342643202107119 ], [ 34.965265825074276, -17.342849870128966 ], [ 34.965251138599292, -17.34305608256269 ], [ 34.965225234893339, -17.343261274195687 ], [ 34.965188184918972, -17.343464882611432 ], [ 34.965140090190644, -17.343666349730935 ], [ 34.965081082496845, -17.343865123342518 ], [ 34.965011323539322, -17.344060658615334 ], [ 34.964931004490182, -17.344252419592877 ], [ 34.964840345468289, -17.344439880662051 ], [ 34.964739594936226, -17.344622527993895 ], [ 34.96462902901969, -17.344799860952165 ], [ 34.964508950750719, -17.344971393465599 ], [ 34.964379689237582, -17.345136655360388 ], [ 34.964241598762683, -17.345295193649122 ], [ 34.964095057811839, -17.345446573772414 ], [ 34.963940468036853, -17.34559038079038 ], [ 34.963778253154771, -17.345726220519964 ], [ 34.963608857786525, -17.345853720615708 ], [ 34.963463180156758, -17.345951999810669 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.8", "sub_field": "1.8D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.961161225337996, -17.342695153682683 ], [ 34.958846576270247, -17.339420347508153 ], [ 34.958884657661677, -17.339394657106936 ], [ 34.959066999530179, -17.339284865242441 ], [ 34.959255074158534, -17.339184388997051 ], [ 34.95944836606386, -17.339093503752036 ], [ 34.959646345466524, -17.339012458601051 ], [ 34.95984846974207, -17.33894147566772 ], [ 34.96005418490828, -17.338880749496898 ], [ 34.960262927143212, -17.338830446521602 ], [ 34.960474124330389, -17.338790704607014 ], [ 34.960687197626548, -17.338761632672661 ], [ 34.960901563047791, -17.338743310394069 ], [ 34.961116633069963, -17.338735787984415 ], [ 34.961331818238484, -17.338739086056979 ], [ 34.961546528783735, -17.338753195568739 ], [ 34.961760176237021, -17.338778077845117 ], [ 34.961972175043229, -17.3388136646861 ], [ 34.962181944165295, -17.338859858553089 ], [ 34.962388908676445, -17.338916532836219 ], [ 34.96259250133555, -17.338983532201397 ], [ 34.962792164141611, -17.33906067301595 ], [ 34.962987349862807, -17.339147743851825 ], [ 34.963177523536125, -17.33924450606515 ], [ 34.963362163933262, -17.33935069444999 ], [ 34.963540764989084, -17.339466017965307 ], [ 34.96371283718851, -17.339590160532456 ], [ 34.963877908907953, -17.339722781901354 ], [ 34.964035527707921, -17.339863518582955 ], [ 34.964185261572979, -17.340011984845287 ], [ 34.964326700095761, -17.340167773770666 ], [ 34.964459455601954, -17.340330458370634 ], [ 34.964583164212755, -17.340499592756277 ], [ 34.964664424622271, -17.340624068304397 ], [ 34.961161225337996, -17.342695153682683 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.9", "sub_field": "1.9A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.962034302490196, -17.337088012040809 ], [ 34.964728870983024, -17.336051561916904 ], [ 34.966003469280459, -17.338512454706869 ], [ 34.965924344913347, -17.338549662135424 ], [ 34.965784357706042, -17.338606972920125 ], [ 34.965641439239995, -17.338657168665854 ], [ 34.965495981257945, -17.338700111782341 ], [ 34.965348382464626, -17.338735684558884 ], [ 34.965199047433821, -17.338763789487064 ], [ 34.965048385499237, -17.33878434952808 ], [ 34.96489680963235, -17.338797308323986 ], [ 34.964744735310369, -17.338802630352134 ], [ 34.964592579377161, -17.338800301022673 ], [ 34.964440758900508, -17.338790326718492 ], [ 34.964289690028806, -17.338772734777791 ], [ 34.96413978685019, -17.338747573419131 ], [ 34.963991460257319, -17.338714911609266 ], [ 34.963845116820963, -17.338674838874116 ], [ 34.963701157675445, -17.338627465053349 ], [ 34.96355997741891, -17.338572919999283 ], [ 34.963421963031685, -17.338511353220905 ], [ 34.963287492815319, -17.338442933474067 ], [ 34.96315693535562, -17.338367848298851 ], [ 34.963030648512166, -17.338286303505473 ], [ 34.96290897843744, -17.338198522610085 ], [ 34.962792258627822, -17.338104746222072 ], [ 34.962680809009477, -17.338005231384418 ], [ 34.962574935061454, -17.337900250869161 ], [ 34.962474926978267, -17.337790092429572 ], [ 34.962381058874492, -17.337675058011381 ], [ 34.962293588033532, -17.337555462925053 ], [ 34.962212754202319, -17.337431634981467 ], [ 34.962138778934303, -17.337303913593271 ], [ 34.962071864982285, -17.337172648844497 ], [ 34.962034302490196, -17.337088012040809 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.9", "sub_field": "1.9C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.967428748672695, -17.335013069641263 ], [ 34.964728870983024, -17.336051561916904 ], [ 34.963415169170496, -17.333515171177073 ], [ 34.963505628599926, -17.333472634116486 ], [ 34.963645612969273, -17.33341532522212 ], [ 34.963788528230573, -17.33336513123426 ], [ 34.96393398267621, -17.33332218972393 ], [ 34.964081577640179, -17.333286618384236 ], [ 34.964230908590665, -17.333258514707836 ], [ 34.964381566238657, -17.333237955719817 ], [ 34.964533137659622, -17.333224997766614 ], [ 34.964685207425113, -17.333219676361633 ], [ 34.964837358741143, -17.333222006087869 ], [ 34.964989174590535, -17.333231980558111 ], [ 34.96514023887557, -17.333249572432333 ], [ 34.965290137558391, -17.333274733492722 ], [ 34.965438459795585, -17.333307394775765 ], [ 34.965584799064111, -17.333347466761317 ], [ 34.965728754275318, -17.333394839617938 ], [ 34.965869930874121, -17.333449383503872 ], [ 34.966007941920303, -17.333510948922914 ], [ 34.966142409148922, -17.333579367134121 ], [ 34.96627296400689, -17.333654450614219 ], [ 34.966399248663151, -17.333735993571572 ], [ 34.966520916989182, -17.333823772510151 ], [ 34.966637635507752, -17.333917546842027 ], [ 34.966749084306826, -17.334017059546696 ], [ 34.966854957916375, -17.334122037875517 ], [ 34.966954966145565, -17.334232194099176 ], [ 34.967048834878206, -17.334347226296227 ], [ 34.967136306824045, -17.334466819180523 ], [ 34.967217142224001, -17.334590644965317 ], [ 34.967291119507479, -17.334718364261622 ], [ 34.967358035899565, -17.334849627008314 ], [ 34.967417707977077, -17.334984073431553 ], [ 34.967428748672695, -17.335013069641263 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.9", "sub_field": "1.9B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.964728870983024, -17.336051561916904 ], [ 34.962034302490196, -17.337088012040809 ], [ 34.962012195742716, -17.337038200530916 ], [ 34.961959934753182, -17.336900937173795 ], [ 34.961915225244269, -17.33676123500965 ], [ 34.961878189747068, -17.336619476959022 ], [ 34.961848929757601, -17.33647605157676 ], [ 34.961827525458673, -17.336331351987006 ], [ 34.96181403550041, -17.336185774805603 ], [ 34.961808496839552, -17.336039719052945 ], [ 34.961810924638471, -17.335893585060266 ], [ 34.961821312223798, -17.33574777337228 ], [ 34.961839631104837, -17.335602683649384 ], [ 34.961865831052002, -17.335458713572155 ], [ 34.961899840234594, -17.335316257751341 ], [ 34.961941565417888, -17.335175706646258 ], [ 34.961990892218978, -17.335037445494642 ], [ 34.962047685420352, -17.33490185325676 ], [ 34.962111789340781, -17.334769301576685 ], [ 34.962183028262146, -17.334640153763782 ], [ 34.962261206911265, -17.334514763796925 ], [ 34.962346110995185, -17.334393475354325 ], [ 34.962437507788763, -17.334276620871645 ], [ 34.962535146772538, -17.334164520630836 ], [ 34.962638760319571, -17.334057481882457 ], [ 34.962748064428943, -17.333955798003494 ], [ 34.962862759504311, -17.333859747693428 ], [ 34.962982531175044, -17.333769594210374 ], [ 34.96310705115792, -17.333685584649643 ], [ 34.963235978156838, -17.33360794926655 ], [ 34.963368958798306, -17.333536900845434 ], [ 34.963415169170496, -17.333515171177073 ], [ 34.964728870983024, -17.336051561916904 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.10", "sub_field": "1.10A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.958739701646998, -17.339232847473824 ], [ 34.957010326871092, -17.336809457524954 ], [ 34.959540353269119, -17.335238722501 ], [ 34.959589253839567, -17.335313636053893 ], [ 34.959668014263052, -17.335449625557878 ], [ 34.959739256571318, -17.335589387587159 ], [ 34.959802785482793, -17.335732539073017 ], [ 34.959858426855881, -17.335878687655981 ], [ 34.959906028166579, -17.336027432761135 ], [ 34.959945458926505, -17.336178366695954 ], [ 34.959976611040844, -17.336331075767689 ], [ 34.959999399104831, -17.336485141417217 ], [ 34.960013760638006, -17.336640141366214 ], [ 34.960019656255689, -17.336795650774501 ], [ 34.960017069777173, -17.336951243404492 ], [ 34.960006008270348, -17.337106492789449 ], [ 34.959986502032443, -17.337260973402362 ], [ 34.959958604507399, -17.337414261822307 ], [ 34.959922392139397, -17.337565937894968 ], [ 34.959877964163766, -17.337715585884322 ], [ 34.959825442335102, -17.337862795612132 ], [ 34.959764970593746, -17.338007163582269 ], [ 34.959696714671516, -17.338148294086736 ], [ 34.959620861637603, -17.338285800290265 ], [ 34.959537619385998, -17.33841930529077 ], [ 34.959447216065776, -17.338548443152437 ], [ 34.959349899456001, -17.33867285990879 ], [ 34.959245936286599, -17.338792214533026 ], [ 34.959135611507378, -17.338906179872861 ], [ 34.959019227507071, -17.339014443547278 ], [ 34.958897103284585, -17.339116708802869 ], [ 34.9587695735746, -17.339212695327376 ], [ 34.958739701646998, -17.339232847473824 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.10", "sub_field": "1.10C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.955242480798987, -17.334332157368866 ], [ 34.957010326871092, -17.336809457524954 ], [ 34.954343541002665, -17.33846509794364 ], [ 34.954260784434837, -17.338338313495246 ], [ 34.95418202629854, -17.338202321620905 ], [ 34.954110786758697, -17.338062557323308 ], [ 34.954047261066549, -17.337919403696002 ], [ 34.953991623328406, -17.337773253121934 ], [ 34.953944026028459, -17.33762450619783 ], [ 34.95390459961115, -17.337473570636146 ], [ 34.953873452123695, -17.337320860147432 ], [ 34.95385066892019, -17.337166793306359 ], [ 34.953836312427846, -17.337011792404329 ], [ 34.953830421976093, -17.336856282291919 ], [ 34.953833013689021, -17.336700689214442 ], [ 34.953844080441442, -17.336545439643551 ], [ 34.953863591878616, -17.336390959108272 ], [ 34.953891494499679, -17.336237671028709 ], [ 34.953927711804546, -17.336085995555422 ], [ 34.953972144503872, -17.335936348417885 ], [ 34.954024670791277, -17.335789139784975 ], [ 34.954085146677556, -17.33564477314086 ], [ 34.954153406385494, -17.335503644178985 ], [ 34.954229262804425, -17.335366139717731 ], [ 34.954312508003291, -17.33523263664009 ], [ 34.954402913800664, -17.335103500860804 ], [ 34.954500232390394, -17.334979086323543 ], [ 34.954604197020849, -17.334859734030776 ], [ 34.954714522726206, -17.334745771109269 ], [ 34.954830907107592, -17.334637509913541 ], [ 34.954953031161914, -17.334535247169818 ], [ 34.95508056015634, -17.334439263162871 ], [ 34.955213144545709, -17.334349820967844 ], [ 34.955242480798987, -17.334332157368866 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.10", "sub_field": "1.10B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.957010326871092, -17.336809457524954 ], [ 34.958739701646998, -17.339232847473824 ], [ 34.958636987930127, -17.339302140018049 ], [ 34.95849970976434, -17.339384797702966 ], [ 34.9583581153545, -17.339460441813092 ], [ 34.958212592810412, -17.339528865003448 ], [ 34.958063541010617, -17.33958987972149 ], [ 34.957911368508981, -17.339643318721347 ], [ 34.957756492414681, -17.339689035522227 ], [ 34.957599337248816, -17.339726904810092 ], [ 34.957440333780596, -17.339756822781162 ], [ 34.957279917846499, -17.339778707426536 ], [ 34.957118529155416, -17.339792498757056 ], [ 34.956956610083246, -17.339798158967696 ], [ 34.956794604460114, -17.339795672541321 ], [ 34.956632956353708, -17.339785046291222 ], [ 34.956472108851798, -17.339766309342444 ], [ 34.95631250284756, -17.339739513051995 ], [ 34.956154575830887, -17.339704730868029 ], [ 34.95599876068907, -17.339662058128571 ], [ 34.955845484519955, -17.339611611800109 ], [ 34.955695167461201, -17.33955353015708 ], [ 34.955548221538436, -17.339487972402619 ], [ 34.955405049535798, -17.339415118232349 ], [ 34.955266043891676, -17.339335167341641 ], [ 34.955131585622986, -17.339248338878221 ], [ 34.955002043280672, -17.339154870841433 ], [ 34.954877771939422, -17.339055019429797 ], [ 34.954759112224359, -17.338949058338667 ], [ 34.954646389377288, -17.338837278009954 ], [ 34.95453991236527, -17.338719984835976 ], [ 34.954439973033701, -17.338597500319473 ], [ 34.954346845306404, -17.338470160192326 ], [ 34.954343541002665, -17.33846509794364 ], [ 34.957010326871092, -17.336809457524954 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.11", "sub_field": "1.11A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.959791281109077, -17.327010987988146 ], [ 34.960630932270384, -17.330690185640638 ], [ 34.956779145904193, -17.33137872104356 ], [ 34.956764856793804, -17.331308666394371 ], [ 34.956736047353225, -17.331113859021773 ], [ 34.95671789260097, -17.330917870461413 ], [ 34.956710442264828, -17.330721237910581 ], [ 34.956713716731777, -17.330524500330025 ], [ 34.956727706992346, -17.330328196966747 ], [ 34.956752374665776, -17.330132865875832 ], [ 34.956787652105561, -17.329939042445719 ], [ 34.956833442585214, -17.329747257930709 ], [ 34.956889620563857, -17.329558037994907 ], [ 34.956956032030497, -17.329371901271418 ], [ 34.957032494926693, -17.329189357940876 ], [ 34.957118799645741, -17.329010908333149 ], [ 34.957214709607541, -17.328837041556099 ], [ 34.957319961907359, -17.328668234155014 ], [ 34.957434268036607, -17.328504948806675 ], [ 34.957557314673871, -17.328347633051155 ], [ 34.957688764543875, -17.328196718065481 ], [ 34.957828257342058, -17.328052617481823 ], [ 34.957975410722298, -17.327915726254009 ], [ 34.958129821344905, -17.327786419575087 ], [ 34.958291065982159, -17.327665051849202 ], [ 34.958458702678442, -17.327551955720331 ], [ 34.958632271961456, -17.327447441160722 ], [ 34.958811298101537, -17.327351794621475 ], [ 34.958995290415487, -17.32726527824753 ], [ 34.959183744611337, -17.327188129159367 ], [ 34.959376144170342, -17.327120558803227 ], [ 34.959571961762535, -17.327062752371688 ], [ 34.959770660691824, -17.327014868296196 ], [ 34.959791281109077, -17.327010987988146 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.11", "sub_field": "1.11B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.960630932270384, -17.330690185640638 ], [ 34.959791281109077, -17.327010987988146 ], [ 34.959971696366765, -17.326977037813023 ], [ 34.960174517792879, -17.326949364603571 ], [ 34.960378569082671, -17.326931924510365 ], [ 34.960583290978782, -17.32692476532921 ], [ 34.960788122386646, -17.326927906678304 ], [ 34.960992501911946, -17.326941339944465 ], [ 34.961195869399035, -17.326965028306834 ], [ 34.96139766746591, -17.326998906837758 ], [ 34.961597343031521, -17.327042882680757 ], [ 34.96179434883144, -17.327096835305003 ], [ 34.96198814491747, -17.327160616835705 ], [ 34.962178200137267, -17.327234052459289 ], [ 34.962363993589896, -17.327316940902499 ], [ 34.962545016053291, -17.327409054984003 ], [ 34.962720771379686, -17.327510142236925 ], [ 34.962890777855286, -17.327619925600736 ], [ 34.963054569520445, -17.327738104180536 ], [ 34.963211697446603, -17.327864354071615 ], [ 34.963361730966632, -17.327998329247073 ], [ 34.96350425885521, -17.328139662506167 ], [ 34.963638890455776, -17.328287966480499 ], [ 34.963765256751422, -17.328442834695693 ], [ 34.963883011376232, -17.328603842685226 ], [ 34.963991831564719, -17.328770549153813 ], [ 34.96409141903667, -17.328942497186652 ], [ 34.964181500814739, -17.329119215501724 ], [ 34.964261829972877, -17.329300219741285 ], [ 34.964332186313328, -17.329485013799381 ], [ 34.964392376970423, -17.329673091181412 ], [ 34.964442236939426, -17.329863936392307 ], [ 34.964470767983819, -17.3300037865056 ], [ 34.960630932270384, -17.330690185640638 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.11", "sub_field": "1.11C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.961187884958441, -17.334400587778653 ], [ 34.960619385935466, -17.330692249633707 ], [ 34.964470767983819, -17.3300037865056 ], [ 34.964481629529139, -17.330057026349291 ], [ 34.964510446736824, -17.330251831815545 ], [ 34.964528609544658, -17.330447818850658 ], [ 34.964536068136539, -17.330644450274068 ], [ 34.964532802035059, -17.330841187137342 ], [ 34.964518820158055, -17.331037490201368 ], [ 34.964494160794402, -17.331232821414325 ], [ 34.964458891499589, -17.331426645386436 ], [ 34.964413108910819, -17.331618430857471 ], [ 34.964356938482574, -17.331807652152843 ], [ 34.964290534143089, -17.331993790624551 ], [ 34.964214077872739, -17.332176336072834 ], [ 34.964127779205612, -17.332354788144567 ], [ 34.964031874655497, -17.332528657704952 ], [ 34.963926627067856, -17.332697468178146 ], [ 34.963812324899642, -17.332860756853766 ], [ 34.963689281428891, -17.333018076155238 ], [ 34.963557833896161, -17.33316899486671 ], [ 34.963418342580404, -17.333313099315198 ], [ 34.963271189811508, -17.333449994504512 ], [ 34.96311677892249, -17.333579305198217 ], [ 34.962955533143941, -17.333700676948169 ], [ 34.962787894444006, -17.333813777066307 ], [ 34.962614322316931, -17.333918295536666 ], [ 34.962435292523566, -17.334013945865316 ], [ 34.962251295787105, -17.334100465865792 ], [ 34.962062836447977, -17.334177618377865 ], [ 34.961870431081294, -17.334245191917802 ], [ 34.961674607080617, -17.334303001258153 ], [ 34.961475901212289, -17.334350887935564 ], [ 34.961274858143774, -17.334388720685283 ], [ 34.961187884958441, -17.334400587778653 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.14", "sub_field": "1.14C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.96199524794266, -17.324405539234302 ], [ 34.964747977596112, -17.324162087220788 ], [ 34.964754107256297, -17.324228233923105 ], [ 34.964759198639079, -17.324362472761511 ], [ 34.964756968599289, -17.324496783565166 ], [ 34.964747423233277, -17.324630798198889 ], [ 34.964730588688205, -17.324764149338428 ], [ 34.964706511090462, -17.324896471477366 ], [ 34.964675256419525, -17.325027401928896 ], [ 34.964636910327144, -17.325156581819918 ], [ 34.96459157790283, -17.325283657074714 ], [ 34.964539383385976, -17.325408279385538 ], [ 34.964480469825446, -17.325530107167204 ], [ 34.964414998687616, -17.325648806493543 ], [ 34.964343149413992, -17.32576405201263 ], [ 34.964265118929369, -17.325875527838612 ], [ 34.964181121102321, -17.325982928417645 ], [ 34.964091386158998, -17.326085959365418 ], [ 34.963996160052147, -17.326184338274128 ], [ 34.963895703787074, -17.326277795486632 ], [ 34.963790292706257, -17.326366074835622 ], [ 34.96368021573462, -17.326448934345862 ], [ 34.963565774587678, -17.326526146897482 ], [ 34.9634472829445, -17.32659750084861 ], [ 34.963325065587895, -17.326662800615537 ], [ 34.963199457514115, -17.326721867208889 ], [ 34.963070803014624, -17.32677453872428 ], [ 34.962939454732314, -17.326820670786223 ], [ 34.962917097922038, -17.326827271055144 ], [ 34.96199524794266, -17.324405539234302 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.14", "sub_field": "1.14A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.96199524794266, -17.324405539234302 ], [ 34.959429692942393, -17.324632437480492 ], [ 34.959421977630143, -17.324549153958536 ], [ 34.959416890093451, -17.32441491459441 ], [ 34.959419124030532, -17.324280603456128 ], [ 34.959428673302277, -17.324146588682531 ], [ 34.959445511718769, -17.324013237599395 ], [ 34.959469593111187, -17.323880915712522 ], [ 34.9595008514585, -17.323749985705984 ], [ 34.959539201068687, -17.323620806447984 ], [ 34.959584536813644, -17.323493732007297 ], [ 34.959636734417636, -17.323369110682769 ], [ 34.959695650797968, -17.323247284048762 ], [ 34.95976112445738, -17.323128586018875 ], [ 34.95983297592673, -17.323013341930839 ], [ 34.959911008257158, -17.322901867654821 ], [ 34.959995007559861, -17.322794468727679 ], [ 34.960084743592546, -17.322691439515644 ], [ 34.96017997039052, -17.322593062407474 ], [ 34.960280426940905, -17.322499607040605 ], [ 34.960385837898102, -17.322411329562119 ], [ 34.960495914338523, -17.322328471926756 ], [ 34.960610354552486, -17.322251261233856 ], [ 34.960728844871134, -17.322179909104896 ], [ 34.960851060526181, -17.322114611103654 ], [ 34.960976666540034, -17.322055546200151 ], [ 34.96108398180769, -17.322011611539249 ], [ 34.96199524794266, -17.324405539234302 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.14", "sub_field": "1.14B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.964747977596112, -17.324162087220788 ], [ 34.96199524794266, -17.324405539234302 ], [ 34.96108398180769, -17.322011611539249 ], [ 34.961105318643774, -17.322002876280248 ], [ 34.96123666422077, -17.321956745701957 ], [ 34.961370343273025, -17.321917280899935 ], [ 34.961505989407819, -17.321884590038856 ], [ 34.961643230841815, -17.321858762717138 ], [ 34.961781691419915, -17.321839869721348 ], [ 34.961920991646146, -17.321827962832202 ], [ 34.962060749723705, -17.32182307468274 ], [ 34.962200582601191, -17.321825218668891 ], [ 34.96234010702242, -17.321834388912762 ], [ 34.962478940576688, -17.321850560278769 ], [ 34.962616702746779, -17.321873688442555 ], [ 34.962753015951741, -17.321903710012467 ], [ 34.962887506581652, -17.321940542703295 ], [ 34.963019806021485, -17.321984085561798 ], [ 34.963149551661324, -17.32203421924336 ], [ 34.963276387890048, -17.322090806339091 ], [ 34.963399967069982, -17.322153691752426 ], [ 34.963519950489498, -17.322222703124122 ], [ 34.963636009291434, -17.32229765130468 ], [ 34.963747825374263, -17.322378330872727 ], [ 34.963855092263934, -17.322464520697949 ], [ 34.963957515953815, -17.322555984547151 ], [ 34.964054815710554, -17.322652471731679 ], [ 34.964146724843467, -17.322753717794438 ], [ 34.96423299143548, -17.322859445234723 ], [ 34.964313379033676, -17.322969364268623 ], [ 34.96438766729743, -17.323083173623363 ], [ 34.964455652602332, -17.323200561362906 ], [ 34.964517148598397, -17.323321205742843 ], [ 34.964571986720919, -17.323444776092288 ], [ 34.964620016652553, -17.323570933720035 ], [ 34.964661106735534, -17.323699332842892 ], [ 34.964695144332502, -17.323829621533367 ], [ 34.96472203613552, -17.323961442684205 ], [ 34.964741708421862, -17.324094434987114 ], [ 34.964747977596112, -17.324162087220788 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.13", "sub_field": "1.13C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.954371207002062, -17.320983642849392 ], [ 34.954988844373986, -17.325074100205931 ], [ 34.950800585931511, -17.325458685865403 ], [ 34.950798085682457, -17.325441774613314 ], [ 34.95077858621412, -17.32523117967385 ], [ 34.950770588566108, -17.325019893135909 ], [ 34.950774114620295, -17.324808494126536 ], [ 34.9507891546723, -17.324597562079067 ], [ 34.950815667458698, -17.324387675145019 ], [ 34.950853580270376, -17.324179408609258 ], [ 34.950902789152352, -17.323973333313319 ], [ 34.950963159189094, -17.323770014090702 ], [ 34.951034524874757, -17.323570008218802 ], [ 34.951116690567204, -17.323373863891501 ], [ 34.951209431024573, -17.323182118716737 ], [ 34.95131249202305, -17.322995298242994 ], [ 34.951425591053983, -17.322813914518999 ], [ 34.951548418098469, -17.322638464690389 ], [ 34.951680636477363, -17.322469429637149 ], [ 34.951821883774279, -17.322307272655788 ], [ 34.951971772829168, -17.322152438189679 ], [ 34.952129892799526, -17.322005350611001 ], [ 34.952295810286643, -17.321866413057819 ], [ 34.952469070523492, -17.321736006329285 ], [ 34.952649198621224, -17.321614487842119 ], [ 34.952835700870786, -17.321502190651106 ], [ 34.95302806609596, -17.321399422536551 ], [ 34.953225767054377, -17.32130646516076 ], [ 34.953428261882415, -17.321223573296251 ], [ 34.953634995580217, -17.321150974127637 ], [ 34.953845401532618, -17.321088866629136 ], [ 34.95405890306192, -17.321037421019309 ], [ 34.954274915008149, -17.320996778294667 ], [ 34.954371207002062, -17.320983642849392 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.13", "sub_field": "1.13A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.955577723835631, -17.328974101137693 ], [ 34.954988844373986, -17.325074100205931 ], [ 34.959167806457437, -17.324690368182022 ], [ 34.959171141296942, -17.324726367099057 ], [ 34.959179148473709, -17.324937652338512 ], [ 34.959175632074597, -17.325149050522835 ], [ 34.959160601698166, -17.325359982227685 ], [ 34.95913409850187, -17.32556986930533 ], [ 34.959096195089728, -17.325778136469321 ], [ 34.95904699531377, -17.325984212871251 ], [ 34.958986633989731, -17.3261875336655 ], [ 34.958915276527989, -17.326387541557416 ], [ 34.958833118480612, -17.326583688331024 ], [ 34.958740385005683, -17.326775436351578 ], [ 34.958637330250511, -17.326962260039437 ], [ 34.958524236655357, -17.327143647310606 ], [ 34.958401414179555, -17.327319100980638 ], [ 34.958269199452211, -17.327488140127418 ], [ 34.958127954849679, -17.327650301409609 ], [ 34.957978067502509, -17.327805140336711 ], [ 34.957819948234501, -17.327952232487675 ], [ 34.957654030436665, -17.328091174674338 ], [ 34.957480768879407, -17.328221586046784 ], [ 34.957300638466052, -17.328343109137428 ], [ 34.957114132931054, -17.328455410841006 ], [ 34.956921763486584, -17.328558183327818 ], [ 34.956724057421276, -17.328651144887623 ], [ 34.956521556654678, -17.328734040702091 ], [ 34.956314816251719, -17.328806643543295 ], [ 34.95610440290104, -17.328868754396808 ], [ 34.955890893361392, -17.328920203007311 ], [ 34.955674872880472, -17.3289608483454 ], [ 34.955577723835631, -17.328974101137693 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.13", "sub_field": "1.13D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.954694649227697, -17.320949807844581 ], [ 34.954492845332638, -17.320967049843368 ], [ 34.954371207002062, -17.320983642849392 ], [ 34.954988844373986, -17.325074100205931 ], [ 34.959167806457437, -17.324690368182022 ], [ 34.959151632530556, -17.324515773917259 ], [ 34.9591206756849, -17.324306450007203 ], [ 34.959078355647378, -17.324098969102089 ], [ 34.959024788449319, -17.323893899881941 ], [ 34.958960120947573, -17.323691804414914 ], [ 34.958884530421642, -17.323493236616837 ], [ 34.958798224087445, -17.323298740733154 ], [ 34.958701438529012, -17.323108849847333 ], [ 34.958594439049804, -17.322924084419899 ], [ 34.958477518945266, -17.322744950862091 ], [ 34.958350998698776, -17.322571940148009 ], [ 34.958215225103011, -17.322405526469097 ], [ 34.958070570309403, -17.32224616593458 ], [ 34.957917430807903, -17.322094295321598 ], [ 34.957756226340365, -17.321950330878167 ], [ 34.957587398750036, -17.321814667182498 ], [ 34.957411410770504, -17.32168767606171 ], [ 34.957228744757572, -17.321569705572823 ], [ 34.957039901367345, -17.321461079048959 ], [ 34.956845398184079, -17.321362094213331 ], [ 34.956645768301847, -17.321273022363293 ], [ 34.956441558863588, -17.321194107626912 ], [ 34.956233329561812, -17.321125566293954 ], [ 34.956021651104756, -17.321067586223158 ], [ 34.955807103652589, -17.321020326327432 ], [ 34.955590275227564, -17.320983916138314 ], [ 34.955489834358993, -17.320972213087796 ], [ 34.955350371836573, -17.320966445369216 ], [ 34.954694649227697, -17.320949807844581 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.12", "sub_field": "1.12D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.953639390624843, -17.331963065750791 ], [ 34.952025040374643, -17.329583299853645 ], [ 34.952098160034545, -17.329533973616417 ], [ 34.952228758810662, -17.32945534029977 ], [ 34.952363463216741, -17.329383379493198 ], [ 34.952501904045853, -17.329318288427501 ], [ 34.952643701851599, -17.329260245504308 ], [ 34.952788467988015, -17.32920940980728 ], [ 34.952935805674699, -17.329165920666156 ], [ 34.953085311084202, -17.329129897274889 ], [ 34.953236574448724, -17.329101438365029 ], [ 34.953389181183084, -17.329080621935237 ], [ 34.953542713020894, -17.329067505037447 ], [ 34.953696749160791, -17.329062123620638 ], [ 34.953850867419632, -17.329064492432295 ], [ 34.954004645389389, -17.329074604977993 ], [ 34.954157661594884, -17.32909243353928 ], [ 34.954309496648627, -17.32911792924957 ], [ 34.954459734400203, -17.329151022228181 ], [ 34.954607963076747, -17.329191621771777 ], [ 34.954753776411302, -17.329239616603008 ], [ 34.954896774756236, -17.329294875175442 ], [ 34.955036566178386, -17.329357246034085 ], [ 34.955172767533284, -17.329426558230505 ], [ 34.955305005515051, -17.329502621791256 ], [ 34.955432917679516, -17.329585228238543 ], [ 34.955556153437534, -17.329674151161573 ], [ 34.955674375015825, -17.329769146837066 ], [ 34.955787258382678, -17.329869954897095 ], [ 34.955894494136068, -17.329976299042801 ], [ 34.955995788351686, -17.330087887801479 ], [ 34.956090863388482, -17.330204415325397 ], [ 34.956179458649743, -17.330325562230108 ], [ 34.956244881516085, -17.330425794331145 ], [ 34.953639390624843, -17.331963065750791 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.12", "sub_field": "1.12B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.953639390624843, -17.331963065750791 ], [ 34.955236330182871, -17.33431716598372 ], [ 34.955224718727678, -17.334324157333015 ], [ 34.955090012251418, -17.334396120283976 ], [ 34.954951568916741, -17.334461213401692 ], [ 34.954809768197514, -17.334519258262056 ], [ 34.954664998771598, -17.334570095760149 ], [ 34.954517657455462, -17.334613586546542 ], [ 34.954368148116309, -17.33464961140923 ], [ 34.954216880565006, -17.334678071600539 ], [ 34.954064269432592, -17.334698889107798 ], [ 34.953910733033638, -17.334712006867239 ], [ 34.953756692219486, -17.334717388920446 ], [ 34.9536025692245, -17.334715020512974 ], [ 34.953448786508559, -17.334704908134796 ], [ 34.953295765598902, -17.334687079502569 ], [ 34.953143925934526, -17.334661583483619 ], [ 34.952993683716358, -17.334628489962032 ], [ 34.952845450766205, -17.334587889647075 ], [ 34.952699633397849, -17.334539893824552 ], [ 34.952556631303125, -17.334484634051776 ], [ 34.952416836456244, -17.334422261796878 ], [ 34.952280632039226, -17.334352948023589 ], [ 34.952148391391503, -17.334276882722619 ], [ 34.952020476986448, -17.334194274390857 ], [ 34.951897239437749, -17.334105349459705 ], [ 34.95177901653831, -17.33401035167449 ], [ 34.951666132334324, -17.333909541426202 ], [ 34.951558896236968, -17.333803195037721 ], [ 34.951457602174337, -17.333691604006312 ], [ 34.951362527785797, -17.333575074204578 ], [ 34.951273933660957, -17.333453925041969 ], [ 34.951229068073914, -17.333385185468085 ], [ 34.953639390624843, -17.331963065750791 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.12", "sub_field": "1.12C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.952025040374643, -17.329583299853645 ], [ 34.953639390624843, -17.331963065750791 ], [ 34.951229068073914, -17.333385185468085 ], [ 34.951192062625495, -17.333328488589178 ], [ 34.951117139075599, -17.333199108667912 ], [ 34.951049368362995, -17.333066139908343 ], [ 34.9509889362322, -17.332929946777021 ], [ 34.950936008311515, -17.33279090257782 ], [ 34.950890729659179, -17.332649388428607 ], [ 34.950853224365929, -17.332505792216605 ], [ 34.950823595214999, -17.332360507535061 ], [ 34.95080192340064, -17.332213932604489 ], [ 34.950788268305715, -17.332066469180983 ], [ 34.950782667339162, -17.331918521455069 ], [ 34.950785135833648, -17.331770494943793 ], [ 34.950795667003739, -17.331622795379207 ], [ 34.950814231964728, -17.33147582759624 ], [ 34.950840779812026, -17.331329994423033 ], [ 34.950875237760897, -17.331185695576949 ], [ 34.950917511346177, -17.331043326568849 ], [ 34.950967484681314, -17.330903277619115 ], [ 34.951025020776335, -17.330765932588115 ], [ 34.951089961913382, -17.330631667924091 ], [ 34.951162130079283, -17.330500851631427 ], [ 34.951241327453488, -17.330373842262006 ], [ 34.951327336950605, -17.33025098793253 ], [ 34.951419922815354, -17.330132625370453 ], [ 34.951518831268977, -17.330019078991043 ], [ 34.951623791204831, -17.329910660008423 ], [ 34.95173451493163, -17.329807665582468 ], [ 34.951850698961884, -17.329710378004542 ], [ 34.951972024843911, -17.32961906392379 ], [ 34.952025040374643, -17.329583299853645 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.17", "sub_field": "1.17B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.951827023263888, -17.31509680237874 ], [ 34.954026875506962, -17.314612148368443 ], [ 34.95460479566993, -17.316838550983693 ], [ 34.954582300191881, -17.316843971690474 ], [ 34.954464390420419, -17.316866156967695 ], [ 34.954345433420492, -17.316882384371478 ], [ 34.954225755255891, -17.316892609420933 ], [ 34.954105683967548, -17.316896804087772 ], [ 34.953985548674133, -17.316894956873099 ], [ 34.953865678669999, -17.316887072839005 ], [ 34.953746402522292, -17.316873173594651 ], [ 34.953628047170405, -17.316853297237095 ], [ 34.953510937029606, -17.316827498246788 ], [ 34.953395393101793, -17.316795847338348 ], [ 34.953281732095512, -17.316758431266628 ], [ 34.953170265557723, -17.316715352588957 ], [ 34.953061299019829, -17.316666729384 ], [ 34.95295513116006, -17.316612694928025 ], [ 34.952852052984774, -17.316553397329692 ], [ 34.95275234703076, -17.316488999123926 ], [ 34.952656286590667, -17.316419676826428 ], [ 34.952564134963964, -17.316345620449805 ], [ 34.95247614473513, -17.316267032982701 ], [ 34.952392557081382, -17.316184129833367 ], [ 34.952313601111499, -17.316097138239147 ], [ 34.952239493237947, -17.31600629664365 ], [ 34.952170436583643, -17.315911854043023 ], [ 34.952106620425212, -17.315814069303528 ], [ 34.952048219674296, -17.31571321045185 ], [ 34.95199539439816, -17.315609553940433 ], [ 34.951948289380944, -17.315503383889688 ], [ 34.951907033726947, -17.315394991309187 ], [ 34.951871740506874, -17.315284673299935 ], [ 34.951842506447925, -17.315172732240043 ], [ 34.951827023263888, -17.31509680237874 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.17", "sub_field": "1.17D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.95629602563065, -17.314112227198336 ], [ 34.954026875506962, -17.314612148368443 ], [ 34.953494750340077, -17.312562168216999 ], [ 34.953582389133082, -17.312541050248115 ], [ 34.953700296363273, -17.312518865749148 ], [ 34.953819250693684, -17.312502638999813 ], [ 34.953938926089734, -17.312492414473798 ], [ 34.954058994540823, -17.312488220193675 ], [ 34.95417912695936, -17.312490067654078 ], [ 34.954298994082514, -17.312497951790249 ], [ 34.954418267374685, -17.312511850991893 ], [ 34.954536619927801, -17.312531727162444 ], [ 34.954653727357204, -17.312557525823461 ], [ 34.954769268690725, -17.312589176263966 ], [ 34.954882927248256, -17.312626591734215 ], [ 34.954994391509658, -17.312669669683476 ], [ 34.955103355968461, -17.312718292041072 ], [ 34.955209521969209, -17.312772325539996 ], [ 34.955312598525929, -17.312831622082093 ], [ 34.955412303119552, -17.312896019143999 ], [ 34.955508362472351, -17.312965340222579 ], [ 34.955600513296758, -17.313039395318516 ], [ 34.955688503017065, -17.313117981457257 ], [ 34.955772090461657, -17.313200883245095 ], [ 34.955851046524032, -17.313287873459569 ], [ 34.955925154790734, -17.313378713672218 ], [ 34.955994212134506, -17.313473154902031 ], [ 34.956058029271169, -17.313570938297769 ], [ 34.956116431278367, -17.313671795847476 ], [ 34.956169258075107, -17.313775451112978 ], [ 34.956216364860559, -17.31388161998748 ], [ 34.956257622511032, -17.313990011474338 ], [ 34.956292917933979, -17.3141003284845 ], [ 34.95629602563065, -17.314112227198336 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.17", "sub_field": "1.17C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.954026875506962, -17.314612148368443 ], [ 34.951827023263888, -17.31509680237874 ], [ 34.951819411668815, -17.315059474955834 ], [ 34.951802519460308, -17.314945211880861 ], [ 34.951791876111791, -17.314830256204942 ], [ 34.951787510784577, -17.314714923015753 ], [ 34.951789435432069, -17.314599528435117 ], [ 34.951797644767126, -17.314484388752604 ], [ 34.951812116276713, -17.314369819558468 ], [ 34.95183281028369, -17.314256134878732 ], [ 34.951859670055732, -17.314143646314449 ], [ 34.951892621960923, -17.314032662187568 ], [ 34.95193157566974, -17.31392348669598 ], [ 34.951976424402709, -17.313816419079632 ], [ 34.952027045223218, -17.313711752800415 ], [ 34.952083299374593, -17.313609774737873 ], [ 34.952145032660468, -17.313510764402817 ], [ 34.952212075867564, -17.313414993171374 ], [ 34.952284245229521, -17.313322723541155 ], [ 34.952361342930651, -17.31323420841176 ], [ 34.952443157648254, -17.313149690391761 ], [ 34.952529465131775, -17.313069401133724 ], [ 34.95262002881752, -17.312993560699343 ], [ 34.952714600477137, -17.312922376956276 ], [ 34.952812920897877, -17.312856045008573 ], [ 34.952914720593142, -17.312794746661851 ], [ 34.95301972054105, -17.312738649925091 ], [ 34.953127632949226, -17.31268790855022 ], [ 34.953238162043483, -17.312642661610678 ], [ 34.953351004878527, -17.312603033120357 ], [ 34.953465852168193, -17.312569131693717 ], [ 34.953494750340077, -17.312562168216999 ], [ 34.954026875506962, -17.314612148368443 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.16", "sub_field": "1.16D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.957182302821295, -17.318536599276882 ], [ 34.955074672069358, -17.318897254404234 ], [ 34.954661651915764, -17.316980166566335 ], [ 34.954671838084693, -17.316977712023718 ], [ 34.954779599945176, -17.316957436047787 ], [ 34.954888318856462, -17.316942605057037 ], [ 34.954997696836713, -17.316933259699976 ], [ 34.955107434097982, -17.316929425589755 ], [ 34.955217229867827, -17.316931113234073 ], [ 34.955326783213508, -17.316938318006393 ], [ 34.955435793866819, -17.316951020158577 ], [ 34.955543963046949, -17.316969184875028 ], [ 34.955650994279274, -17.316992762368198 ], [ 34.955756594207948, -17.317021688014929 ], [ 34.955860473399802, -17.31705588253363 ], [ 34.955962347137593, -17.3170952522016 ], [ 34.9560619362003, -17.317139689111766 ], [ 34.956158967628355, -17.317189071468594 ], [ 34.956253175471744, -17.317243263921736 ], [ 34.956344301518882, -17.317302117937068 ], [ 34.956432096004264, -17.317365472203772 ], [ 34.956516318293041, -17.317433153076365 ], [ 34.956596737540529, -17.317504975050671 ], [ 34.956673133324919, -17.317580741272227 ], [ 34.956745296251377, -17.317660244075764 ], [ 34.956813028526042, -17.317743265554331 ], [ 34.956876144498082, -17.31782957815658 ], [ 34.956934471168672, -17.317918945310385 ], [ 34.956987848665094, -17.318011122071191 ], [ 34.957036130679015, -17.318105855793377 ], [ 34.957079184867595, -17.318202886822661 ], [ 34.957116893216238, -17.31830194920779 ], [ 34.957149152362099, -17.318402771429408 ], [ 34.957175873877567, -17.318505077144206 ], [ 34.957182302821295, -17.318536599276882 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.16", "sub_field": "1.16B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.954859932451754, -17.320941430306988 ], [ 34.954821771081448, -17.320936983748155 ], [ 34.954713599562865, -17.320918818875519 ], [ 34.954606566065088, -17.320895241107298 ], [ 34.954500963968805, -17.320866315069665 ], [ 34.954397082730935, -17.320832120048479 ], [ 34.954295207091114, -17.320792749771989 ], [ 34.95419561629123, -17.320748312153899 ], [ 34.954098583309857, -17.320698928997505 ], [ 34.954004374114035, -17.320644735661915 ], [ 34.95391324693022, -17.320585880690935 ], [ 34.953825451536296, -17.320522525405838 ], [ 34.953741228577087, -17.320454843463274 ], [ 34.95366080890463, -17.320383020379118 ], [ 34.953584412945403, -17.320307253020022 ], [ 34.953512250096132, -17.320227749063722 ], [ 34.953444518149837, -17.320144726429767 ], [ 34.95338140275377, -17.320058412682187 ], [ 34.953323076900439, -17.319969044405653 ], [ 34.95326970045361, -17.319876866557024 ], [ 34.953221419710069, -17.319782131793826 ], [ 34.953178366998799, -17.319685099781751 ], [ 34.953140660318205, -17.319586036482843 ], [ 34.953108403012827, -17.31948521342645 ], [ 34.953081683490133, -17.319382906965 ], [ 34.953060574978281, -17.319279397516461 ], [ 34.953055168419084, -17.319242829332627 ], [ 34.955074672069358, -17.318897254404234 ], [ 34.955511377817565, -17.320924282071903 ], [ 34.955477979112175, -17.320930566296397 ], [ 34.955369257970489, -17.320945397833995 ], [ 34.95529630929034, -17.320951630797449 ], [ 34.954859932451754, -17.320941430306988 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.16", "sub_field": "1.16A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.955074672069358, -17.318897254404234 ], [ 34.957182302821295, -17.318536599276882 ], [ 34.95719698451262, -17.318608585942435 ], [ 34.957212426395785, -17.318713014116351 ], [ 34.95722215719276, -17.318818075437807 ], [ 34.957226150222589, -17.318923481942836 ], [ 34.957224394530947, -17.319028944720859 ], [ 34.957216894920172, -17.319134174706594 ], [ 34.957203671936284, -17.319238883472298 ], [ 34.957184761812783, -17.319342784018371 ], [ 34.957160216371385, -17.319445591560026 ], [ 34.957130102880129, -17.319547024307777 ], [ 34.957094503869122, -17.319646804239927 ], [ 34.957053516904331, -17.31974465786454 ], [ 34.957007254320388, -17.319840316969159 ], [ 34.956955842912699, -17.319933519355892 ], [ 34.956899423589938, -17.320024009560218 ], [ 34.956838150988013, -17.320111539551114 ], [ 34.956772193046199, -17.320195869411073 ], [ 34.956701730546861, -17.320276767993608 ], [ 34.956626956620106, -17.320354013556926 ], [ 34.956548076214318, -17.320427394371769 ], [ 34.956465305534444, -17.320496709301732 ], [ 34.95637887144953, -17.320561768354683 ], [ 34.956289010870705, -17.320622393203507 ], [ 34.956195970101952, -17.320678417675055 ], [ 34.956100004164895, -17.320729688205471 ], [ 34.956001376099834, -17.32077606426137 ], [ 34.955900356244626, -17.320817418724921 ], [ 34.955797221493775, -17.320853638242355 ], [ 34.955692254539301, -17.320884623534763 ], [ 34.955585743095874, -17.320910289670167 ], [ 34.955511377817565, -17.320924282071903 ], [ 34.955074672069358, -17.318897254404234 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "6.2", "sub_field": "6.2D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.933686532028823, -17.34741850738671 ], [ 34.930918907739319, -17.350619195960071 ], [ 34.930749371120577, -17.350482950059067 ], [ 34.9305831090279, -17.350334454986911 ], [ 34.930425167481324, -17.35017780671259 ], [ 34.930275979390657, -17.350013434618159 ], [ 34.930135953668632, -17.349841789256359 ], [ 34.930005474110168, -17.349663341115505 ], [ 34.929884898340468, -17.349478579329688 ], [ 34.929774556834893, -17.349288010337819 ], [ 34.929674752013298, -17.349092156495377 ], [ 34.929585757411353, -17.348891554642378 ], [ 34.92950781693105, -17.348686754631803 ], [ 34.929441144172401, -17.348478317822277 ], [ 34.9293859218484, -17.348266815539219 ], [ 34.929342301284514, -17.348052827508759 ], [ 34.929310402004319, -17.347836940268596 ], [ 34.92929031140222, -17.347619745560188 ], [ 34.929282084504514, -17.347401838706777 ], [ 34.929285743818809, -17.347183816981492 ], [ 34.92930127927297, -17.346966277970296 ], [ 34.929328648243064, -17.346749817933908 ], [ 34.929367775670698, -17.346535030173548 ], [ 34.929418554269198, -17.34632250340475 ], [ 34.929480844818194, -17.346112820143716 ], [ 34.929554476545505, -17.345906555110741 ], [ 34.929639247595759, -17.345704273655056 ], [ 34.929734925584, -17.345506530205324 ], [ 34.929841248233032, -17.345313866750129 ], [ 34.929957924092612, -17.34512681135249 ], [ 34.930084633338602, -17.344945876702717 ], [ 34.930221028649861, -17.34477155871328 ], [ 34.930343832257769, -17.344630621189719 ], [ 34.933686532028823, -17.34741850738671 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "6.2", "sub_field": "6.2A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.936458268544008, -17.344213063126752 ], [ 34.933686532028823, -17.34741850738671 ], [ 34.930343832257769, -17.344630621189719 ], [ 34.930366736160408, -17.344604335159747 ], [ 34.93052135648437, -17.344444664371434 ], [ 34.930684465810785, -17.344292983975372 ], [ 34.93085561706534, -17.344149709696961 ], [ 34.931034341135764, -17.344015234220802 ], [ 34.931220148157657, -17.343889926114542 ], [ 34.931412528857074, -17.343774128818861 ], [ 34.931610955946326, -17.343668159706397 ], [ 34.931814885569068, -17.343572309212028 ], [ 34.93202375879077, -17.343486840037041 ], [ 34.932237003130467, -17.34341198642927 ], [ 34.932454034129648, -17.343347953541233 ], [ 34.932674256953796, -17.343294916868011 ], [ 34.932897068022541, -17.343253021766351 ], [ 34.933121856663583, -17.343222383056439 ], [ 34.933348006786112, -17.343203084707323 ], [ 34.933574898569034, -17.343195179606763 ], [ 34.933801910159438, -17.343198689416489 ], [ 34.934028419376581, -17.343213604512808 ], [ 34.934253805416724, -17.343239884013023 ], [ 34.934477450554319, -17.343277455887517 ], [ 34.93469874183473, -17.343326217157209 ], [ 34.934917072753706, -17.343386034175726 ], [ 34.935131844919461, -17.343456742995702 ], [ 34.935342469692365, -17.343538149818031 ], [ 34.935548369797949, -17.343630031523016 ], [ 34.935748980908848, -17.343732136281769 ], [ 34.935943753191204, -17.343844184246329 ], [ 34.936132152811439, -17.343965868316541 ], [ 34.936313663399275, -17.34409685498165 ], [ 34.936458268544008, -17.344213063126752 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "6.1", "sub_field": "6.1C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.924014427740026, -17.344857153121342 ], [ 34.926975301324241, -17.341756277068452 ], [ 34.930206135800262, -17.344526394378821 ], [ 34.93019316385849, -17.344541281858941 ], [ 34.93003854325503, -17.344700952461203 ], [ 34.929875433121211, -17.344852632448269 ], [ 34.929704280527545, -17.344995906055839 ], [ 34.929525554592857, -17.345130380560647 ], [ 34.929339745198348, -17.345255687357142 ], [ 34.929147361644837, -17.345371482967963 ], [ 34.92894893125672, -17.34547744998568 ], [ 34.928744997936427, -17.345573297942956 ], [ 34.928536120673414, -17.345658764108965 ], [ 34.928322872011776, -17.345733614209617 ], [ 34.928105836480654, -17.345797643070011 ], [ 34.92788560899178, -17.345850675176816 ], [ 34.927662793208455, -17.345892565159698 ], [ 34.927437999890635, -17.345923198189734 ], [ 34.927211845220448, -17.345942490294355 ], [ 34.926984949112807, -17.345950388587646 ], [ 34.926757933515866, -17.345946871415304 ], [ 34.926531420705857, -17.345931948414133 ], [ 34.926306031580957, -17.345905660485595 ], [ 34.926082383959056, -17.345868079683793 ], [ 34.925861090883807, -17.345819309017926 ], [ 34.925642758943994, -17.345759482169868 ], [ 34.925427986610323, -17.345688763127814 ], [ 34.9252173625948, -17.345607345736642 ], [ 34.925011464236583, -17.345515453166517 ], [ 34.92481085591924, -17.345413337301096 ], [ 34.924616087523482, -17.345301278046954 ], [ 34.92442769291965, -17.345179582566296 ], [ 34.924246188504156, -17.34504858443481 ], [ 34.924072071783883, -17.344908642727177 ], [ 34.924014427740026, -17.344857153121342 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "6.1", "sub_field": "6.1A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.929874621707043, -17.3387198647094 ], [ 34.926975301324241, -17.341756277068452 ], [ 34.923737722930689, -17.338980377523537 ], [ 34.923844300080475, -17.338870324057513 ], [ 34.924007410942906, -17.33871864893819 ], [ 34.924178563186587, -17.338575380261723 ], [ 34.92435728769653, -17.338440910697358 ], [ 34.924543094607159, -17.338315608796531 ], [ 34.924735474644862, -17.338199817982947 ], [ 34.924933900523754, -17.338093855611472 ], [ 34.925137828390824, -17.337998012098549 ], [ 34.92534669931635, -17.33791255012633 ], [ 34.925559940825678, -17.337837703922908 ], [ 34.925776968468028, -17.33777367862054 ], [ 34.925997187418091, -17.337720649693534 ], [ 34.92621999410612, -17.337678762477399 ], [ 34.926444777871808, -17.337648131770756 ], [ 34.926670922637705, -17.337628841520697 ], [ 34.926897808597396, -17.337620944592842 ], [ 34.927124813913892, -17.337624462626497 ], [ 34.927351316423618, -17.337639385975457 ], [ 34.927576695341251, -17.337665673734445 ], [ 34.927800332960729, -17.337703253851203 ], [ 34.928021616347976, -17.33775202332404 ], [ 34.928239939020372, -17.337811848484161 ], [ 34.928454702608725, -17.337882565361848 ], [ 34.928665318496876, -17.337963980135974 ], [ 34.928871209434661, -17.338055869664995 ], [ 34.929071811119861, -17.338157982098618 ], [ 34.929266573744414, -17.338270037567831 ], [ 34.929454963501193, -17.338391728951922 ], [ 34.929636464046908, -17.338522722720082 ], [ 34.929810577917074, -17.338662659845419 ], [ 34.929874621707043, -17.3387198647094 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "6.1", "sub_field": "6.1D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.926975301324241, -17.341756277068452 ], [ 34.924014427740026, -17.344857153121342 ], [ 34.923905820012344, -17.344760141032754 ], [ 34.923747888881501, -17.344603486403877 ], [ 34.923598711272561, -17.344439108239992 ], [ 34.923458696069524, -17.344267457110487 ], [ 34.923328227038496, -17.344089003519457 ], [ 34.92320766177577, -17.343904236615927 ], [ 34.923097330727906, -17.343713662852821 ], [ 34.922997536286125, -17.343517804598687 ], [ 34.922908551957605, -17.343317198705623 ], [ 34.922830621616164, -17.343112395037664 ], [ 34.922763958834061, -17.342903954963422 ], [ 34.922708746296955, -17.342692449817278 ], [ 34.922665135303461, -17.342478459333183 ], [ 34.922633245350909, -17.342262570055521 ], [ 34.922613163808208, -17.342045373731359 ], [ 34.922604945676802, -17.341827465688315 ], [ 34.922608613440303, -17.341609443202778 ], [ 34.922624157003398, -17.341391903862778 ], [ 34.922651533719979, -17.341175443929924 ], [ 34.922690668510434, -17.34096065670515 ], [ 34.922741454067939, -17.340748130902483 ], [ 34.922803751153019, -17.340538449035467 ], [ 34.922877388975664, -17.340332185820596 ], [ 34.922962165663776, -17.34012990660208 ], [ 34.92305784881701, -17.339932165802466 ], [ 34.923164176144006, -17.339739505403038 ], [ 34.923280856181734, -17.33955245345847 ], [ 34.923407569094621, -17.339371522649586 ], [ 34.923543967551495, -17.339197208878335 ], [ 34.923689677677785, -17.339029989908727 ], [ 34.923737722930689, -17.338980377523537 ], [ 34.926975301324241, -17.341756277068452 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.5", "sub_field": "2.5C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.917570487117061, -17.339711118027701 ], [ 34.920240082361659, -17.336946735747041 ], [ 34.923199307657669, -17.339448856263918 ], [ 34.923071705624515, -17.33958061908611 ], [ 34.922923099879576, -17.339718804051945 ], [ 34.922767167458396, -17.339849329976413 ], [ 34.922604335762102, -17.339971839080217 ], [ 34.922435051105928, -17.340085995557583 ], [ 34.922259777495817, -17.340191486496835 ], [ 34.922078995356557, -17.340288022738303 ], [ 34.921893200214804, -17.340375339667009 ], [ 34.921702901340709, -17.340453197938189 ], [ 34.921508620351901, -17.340521384133421 ], [ 34.921310889783442, -17.340579711345818 ], [ 34.921110251627987, -17.340628019692403 ], [ 34.920907255849897, -17.340666176752553 ], [ 34.920702458877493, -17.34069407793098 ], [ 34.920496422077605, -17.340711646744563 ], [ 34.920289710216586, -17.340718835032121 ], [ 34.920082889911853, -17.3407156230864 ], [ 34.919876528078532, -17.340702019708203 ], [ 34.919671190375219, -17.34067806218227 ], [ 34.91946743965304, -17.340643816175103 ], [ 34.919265834412677, -17.340599375555005 ], [ 34.919066927273107, -17.340544862134671 ], [ 34.918871263456573, -17.340480425337375 ], [ 34.918679379293778, -17.340406241787306 ], [ 34.918491800753571, -17.34032251482536 ], [ 34.918309042000999, -17.340229473951673 ], [ 34.918131603987675, -17.340127374196545 ], [ 34.917959973078531, -17.340016495421217 ], [ 34.917794619718457, -17.33989714155069 ], [ 34.917635997142689, -17.339769639740499 ], [ 34.917570487117061, -17.339711118027701 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.5", "sub_field": "2.5A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.922942052048946, -17.33414882953203 ], [ 34.920240082361659, -17.336946735747041 ], [ 34.917269399022985, -17.334434927085208 ], [ 34.917287823506996, -17.334413783914261 ], [ 34.91742869674966, -17.334268322621668 ], [ 34.917577303095733, -17.334130141694978 ], [ 34.917733235223096, -17.33399961986192 ], [ 34.917896065733828, -17.333877114856964 ], [ 34.918065348325705, -17.333762962440996 ], [ 34.918240619015357, -17.333657475481164 ], [ 34.91842139741005, -17.333560943093559 ], [ 34.918607188024126, -17.333473629850932 ], [ 34.918797481637021, -17.33339577505766 ], [ 34.91899175668879, -17.333327592094061 ], [ 34.919189480709392, -17.333269267831611 ], [ 34.919390111777922, -17.333220962120961 ], [ 34.91959310000766, -17.333182807353882 ], [ 34.919797889052994, -17.333154908100525 ], [ 34.920003917633906, -17.333137340822887 ], [ 34.920210621074162, -17.333130153665365 ], [ 34.920417432848552, -17.333133366322823 ], [ 34.920623786135408, -17.33314696998664 ], [ 34.920829115369827, -17.333170927368968 ], [ 34.921032857793406, -17.333205172804849 ], [ 34.921234454996402, -17.333249612432297 ], [ 34.921433354447913, -17.333304124449441 ], [ 34.921629011009919, -17.333368559448381 ], [ 34.921820888431192, -17.333442740824655 ], [ 34.922008460816762, -17.333526465261187 ], [ 34.922191214068988, -17.333619503285469 ], [ 34.922368647296558, -17.333721599898446 ], [ 34.922540274186922, -17.333832475273329 ], [ 34.922705624339265, -17.333951825522362 ], [ 34.922864244553487, -17.334079323529647 ], [ 34.922942052048946, -17.33414882953203 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.5", "sub_field": "2.5D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.920240082361659, -17.336946735747041 ], [ 34.917570487117061, -17.339711118027701 ], [ 34.917484540134382, -17.339634339479865 ], [ 34.917340663832782, -17.339491611633623 ], [ 34.917204762595283, -17.339341847425441 ], [ 34.917077208916524, -17.339185457365442 ], [ 34.916958352407384, -17.339022870124747 ], [ 34.916848518836794, -17.338854531360347 ], [ 34.916748009238944, -17.338680902493451 ], [ 34.916657099088269, -17.338502459444559 ], [ 34.916576037544537, -17.338319691328788 ], [ 34.916505046770205, -17.338133099115122 ], [ 34.916444321321642, -17.337943194253128 ], [ 34.916394027616185, -17.337750497270942 ], [ 34.916354303476247, -17.337555536348415 ], [ 34.916325257751936, -17.337358845869311 ], [ 34.916306970023015, -17.337160964956428 ], [ 34.916299490381085, -17.336962435993936 ], [ 34.91630283929274, -17.336763803140538 ], [ 34.91631700754381, -17.33656561083799 ], [ 34.916341956265015, -17.336368402318797 ], [ 34.916377617038826, -17.33617271811719 ], [ 34.916423892087501, -17.335979094587614 ], [ 34.91648065454131, -17.335788062434609 ], [ 34.916547748786755, -17.335600145258304 ], [ 34.916624990893368, -17.33541585811918 ], [ 34.916712169118249, -17.335235706126554 ], [ 34.916809044486669, -17.335060183054143 ], [ 34.916915351447358, -17.334889769986802 ], [ 34.917030798600706, -17.334724934001962 ], [ 34.917155069497618, -17.334566126889655 ], [ 34.917269399022985, -17.334434927085208 ], [ 34.920240082361659, -17.336946735747041 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.1", "sub_field": "2.1A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.915191282440283, -17.337693243965902 ], [ 34.913379761071603, -17.335625170111705 ], [ 34.915518445366438, -17.33371081141938 ], [ 34.91556591333611, -17.333763124395745 ], [ 34.915660056119648, -17.333878556262661 ], [ 34.915747779977927, -17.333998562055793 ], [ 34.915828844461352, -17.334122812856673 ], [ 34.915903027371279, -17.33425096811143 ], [ 34.9159701253691, -17.334382676564005 ], [ 34.916029954533762, -17.33451757721895 ], [ 34.916082350865892, -17.334655300330706 ], [ 34.916127170737504, -17.334795468417038 ], [ 34.916164291285796, -17.334937697293565 ], [ 34.916193610750071, -17.335081597126699 ], [ 34.916215048750807, -17.335226773502143 ], [ 34.916228546510219, -17.33537282850585 ], [ 34.916234067013484, -17.335519361814619 ], [ 34.916231595110446, -17.335665971793379 ], [ 34.916221137557322, -17.335812256595954 ], [ 34.91620272299842, -17.335957815266493 ], [ 34.916176401887839, -17.336102248838476 ], [ 34.916142246351313, -17.336245161428248 ], [ 34.916100349988831, -17.336386161320107 ], [ 34.916050827618207, -17.33652486203999 ], [ 34.915993814960572, -17.336660883414858 ], [ 34.915929468268565, -17.336793852614697 ], [ 34.915857963898262, -17.336923405174531 ], [ 34.915779497825845, -17.337049185993408 ], [ 34.915694285110668, -17.337170850307807 ], [ 34.915602559305853, -17.337288064636702 ], [ 34.915504571818289, -17.337400507695637 ], [ 34.915400591219608, -17.337507871277509 ], [ 34.91529090251003, -17.337609861097384 ], [ 34.915191282440283, -17.337693243965902 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.1", "sub_field": "2.1C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.911437392245574, -17.333407717834426 ], [ 34.913379761071603, -17.335625170111705 ], [ 34.911255677582851, -17.337526459469718 ], [ 34.911170570838692, -17.337442025530336 ], [ 34.911070267333443, -17.337331481742687 ], [ 34.910976125197692, -17.337216047643373 ], [ 34.910888402465794, -17.337096039638435 ], [ 34.910807339575037, -17.336971786670553 ], [ 34.910733158706634, -17.336843629317364 ], [ 34.910666063176791, -17.336711918857905 ], [ 34.910606236879538, -17.336577016309619 ], [ 34.910553843782843, -17.336439291438793 ], [ 34.910509027479215, -17.336299121746951 ], [ 34.910471910792388, -17.336156891436062 ], [ 34.910442595440756, -17.336012990355417 ], [ 34.910421161758748, -17.335867812933003 ], [ 34.910407668476871, -17.335721757094326 ], [ 34.910402152560884, -17.335575223171723 ], [ 34.910404629110673, -17.335428612807021 ], [ 34.910415091319095, -17.335282327850589 ], [ 34.910433510490847, -17.335136769259989 ], [ 34.910459836121319, -17.334992336000848 ], [ 34.910493996035243, -17.334849423953397 ], [ 34.910535896584648, -17.334708424827415 ], [ 34.910585422905875, -17.334569725088556 ], [ 34.910642439234486, -17.334433704899109 ], [ 34.910706789277619, -17.334300737076106 ], [ 34.910778296642519, -17.334171186069447 ], [ 34.910856765320155, -17.334045406963057 ], [ 34.910941980222667, -17.333923744501693 ], [ 34.911033707772944, -17.33380653214607 ], [ 34.911131696544956, -17.333694091159053 ], [ 34.911235677953037, -17.333586729725003 ], [ 34.911345366988073, -17.33348474210538 ], [ 34.911437392245574, -17.333407717834426 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.1", "sub_field": "2.1B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.913379761071603, -17.335625170111705 ], [ 34.915191282440283, -17.337693243965902 ], [ 34.915175806337395, -17.337706197599196 ], [ 34.915055618172943, -17.337796616722148 ], [ 34.914930667446747, -17.337880870624524 ], [ 34.914801296644683, -17.337958728363152 ], [ 34.914667860369669, -17.338029976526471 ], [ 34.914530724369655, -17.338094419819619 ], [ 34.914390264535022, -17.338151881599728 ], [ 34.914246865868172, -17.338202204360293 ], [ 34.914100921428201, -17.338245250162895 ], [ 34.913952831253319, -17.338280901015388 ], [ 34.913803001264291, -17.33830905919536 ], [ 34.913651842151559, -17.338329647518076 ], [ 34.913499768249508, -17.338342609548064 ], [ 34.913347196400544, -17.338347909753889 ], [ 34.913194544812328, -17.338345533605516 ], [ 34.913042231911355, -17.33833548761422 ], [ 34.912890675195818, -17.338317799314741 ], [ 34.912740290091094, -17.338292517189796 ], [ 34.912591488810882, -17.33825971053723 ], [ 34.912444679227121, -17.33821946928002 ], [ 34.912300263751881, -17.338171903719815 ], [ 34.912158638234182, -17.338117144234534 ], [ 34.912020190874792, -17.338055340920988 ], [ 34.911885301162073, -17.337986663183429 ], [ 34.911754338831614, -17.33791129926913 ], [ 34.91162766285283, -17.337829455752399 ], [ 34.911505620444707, -17.337741356968213 ], [ 34.911388546124208, -17.337647244397367 ], [ 34.911276760789143, -17.337547376004352 ], [ 34.911255677582851, -17.337526459469718 ], [ 34.913379761071603, -17.335625170111705 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.2", "sub_field": "2.2B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.909113153334729, -17.34030905206204 ], [ 34.911578911920543, -17.342468032199982 ], [ 34.911483266003707, -17.342566783644735 ], [ 34.911354818022311, -17.342686208422425 ], [ 34.911220037986965, -17.342799013216435 ], [ 34.911079295321287, -17.342904888824119 ], [ 34.910932975794793, -17.343003545035192 ], [ 34.910781480465488, -17.343094711427394 ], [ 34.910625224580507, -17.343178138107756 ], [ 34.910464636437894, -17.343253596397744 ], [ 34.9103001562125, -17.343320879460098 ], [ 34.910132234749348, -17.343379802865964 ], [ 34.909961332327725, -17.343430205100489 ], [ 34.90978791739942, -17.343471948005583 ], [ 34.909612465304448, -17.343504917158779 ], [ 34.909435456967969, -17.343529022186893 ], [ 34.909257377581909, -17.343544197013831 ], [ 34.90907871527471, -17.343550400041732 ], [ 34.90889995977318, -17.343547614265091 ], [ 34.908721601059909, -17.343535847317359 ], [ 34.908544128029973, -17.343515131450051 ], [ 34.908368027150622, -17.343485523444404 ], [ 34.908193781127558, -17.343447104455645 ], [ 34.908021867581709, -17.343399979790647 ], [ 34.907852757739754, -17.343344278619131 ], [ 34.90768691514225, -17.343280153619709 ], [ 34.907524794372911, -17.343207780561201 ], [ 34.90736683981239, -17.343127357820926 ], [ 34.907213484420012, -17.343039105840777 ], [ 34.907065148546927, -17.342943266522941 ], [ 34.906922238783757, -17.342840102566772 ], [ 34.906860112803329, -17.342790160574449 ], [ 34.909113153334729, -17.34030905206204 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.2", "sub_field": "2.2D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.909113153334729, -17.34030905206204 ], [ 34.906533452371185, -17.338050305737823 ], [ 34.90660624542992, -17.337975151605978 ], [ 34.906734693855789, -17.337855729846009 ], [ 34.906869473666958, -17.337742928108351 ], [ 34.907010215442298, -17.337637055562148 ], [ 34.907156533422004, -17.337538402384133 ], [ 34.907308026565047, -17.33744723896336 ], [ 34.907464279648224, -17.337363815160227 ], [ 34.907624864404212, -17.337288359621752 ], [ 34.907789340695246, -17.337221079155054 ], [ 34.907957257719431, -17.337162158160531 ], [ 34.908128155246089, -17.33711175812665 ], [ 34.908301564877085, -17.337070017187351 ], [ 34.908477011330419, -17.337037049743579 ], [ 34.908654013742748, -17.337012946149805 ], [ 34.908832086987054, -17.336997772466383 ], [ 34.909010743002206, -17.336991570278656 ], [ 34.909189492130309, -17.33699435658291 ], [ 34.9093678444586, -17.337006123739954 ], [ 34.909545311161978, -17.337026839495923 ], [ 34.90972140584249, -17.337056447070864 ], [ 34.909895645862328, -17.337094865314214 ], [ 34.910067553666394, -17.3371419889273 ], [ 34.910236658090895, -17.337197688751889 ], [ 34.910402495654601, -17.337261812124172 ], [ 34.910564611828974, -17.33733418329313 ], [ 34.910722562283695, -17.337414603902154 ], [ 34.910875914104444, -17.337502853532694 ], [ 34.911024246979174, -17.337598690308319 ], [ 34.911167154350139, -17.337701851557512 ], [ 34.911304244527969, -17.337812054533547 ], [ 34.91134641261079, -17.337849727197245 ], [ 34.909113153334729, -17.34030905206204 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.2", "sub_field": "2.2A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.911578911920543, -17.342468032199982 ], [ 34.909113153334729, -17.34030905206204 ], [ 34.91134641261079, -17.337849727197245 ], [ 34.91143514176526, -17.337928997189451 ], [ 34.911559487286333, -17.338052359005587 ], [ 34.91167694027061, -17.338181801868199 ], [ 34.911787178786682, -17.338316970995947 ], [ 34.911889900674801, -17.33845749591223 ], [ 34.911984824375082, -17.338602991460462 ], [ 34.91207168969926, -17.33875305885968 ], [ 34.912150258544031, -17.338907286797372 ], [ 34.912220315543756, -17.339065252556818 ], [ 34.912281668660903, -17.339226523175476 ], [ 34.912334149712621, -17.339390656631728 ], [ 34.91237761483189, -17.339557203056199 ], [ 34.912411944862086, -17.339725705964806 ], [ 34.912437045683824, -17.339895703509814 ], [ 34.912452848473158, -17.340066729745672 ], [ 34.912459309890508, -17.340238315906035 ], [ 34.912456412199738, -17.340409991688645 ], [ 34.912444163317041, -17.340581286544278 ], [ 34.912422596789526, -17.340751730966492 ], [ 34.912391771703525, -17.340920857778553 ], [ 34.912351772523039, -17.341088203413818 ], [ 34.912302708858348, -17.34125330918652 ], [ 34.912244715165983, -17.341415722548859 ], [ 34.912177950380325, -17.341574998331588 ], [ 34.912102597478352, -17.341730699964163 ], [ 34.912018862978236, -17.341882400671473 ], [ 34.911926976373564, -17.342029684643691 ], [ 34.91182718950445, -17.342172148176019 ], [ 34.911719775867432, -17.34230940077537 ], [ 34.911605029865996, -17.34244106623078 ], [ 34.911578911920543, -17.342468032199982 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.3", "sub_field": "2.3B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.899977306418968, -17.342598676400389 ], [ 34.900577206073855, -17.344757029496293 ], [ 34.90053099248378, -17.344770656279508 ], [ 34.900412035589035, -17.344799284489611 ], [ 34.900291681601324, -17.344821894278759 ], [ 34.900170260413489, -17.344838423671792 ], [ 34.900048104844025, -17.344848827360014 ], [ 34.899925549724713, -17.344853076825434 ], [ 34.899802930982794, -17.344851160418948 ], [ 34.899680584720031, -17.344843083392274 ], [ 34.899558846291384, -17.344828867883589 ], [ 34.899438049385651, -17.34480855285684 ], [ 34.899318525110722, -17.344782193994938 ], [ 34.899200601085958, -17.344749863547143 ], [ 34.899084600543979, -17.344711650130986 ], [ 34.898970841444694, -17.344667658489421 ], [ 34.898859635603621, -17.344618009203618 ], [ 34.898751287837094, -17.344562838362481 ], [ 34.898646095126765, -17.344502297189571 ], [ 34.898544345805426, -17.344436551628583 ], [ 34.898446318766688, -17.344365781888438 ], [ 34.898352282700444, -17.34429018194934 ], [ 34.898262495356455, -17.344209959030934 ], [ 34.898177202837687, -17.344125333024376 ], [ 34.898096638925864, -17.344036535889487 ], [ 34.898021024440638, -17.343943811018971 ], [ 34.897950566634307, -17.343847412571144 ], [ 34.89788545862384, -17.343747604773341 ], [ 34.897825878861511, -17.343644661197509 ], [ 34.897771990645907, -17.343538864010416 ], [ 34.897723941674307, -17.343430503200111 ], [ 34.897681863638006, -17.343319875781045 ], [ 34.897645871861357, -17.343207284979929 ], [ 34.897632654475345, -17.343156624592137 ], [ 34.899977306418968, -17.342598676400389 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.3", "sub_field": "2.3D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.899977306418968, -17.342598676400389 ], [ 34.8993698935897, -17.3404132919784 ], [ 34.899392664965589, -17.340407811872627 ], [ 34.899513016297661, -17.340385202886463 ], [ 34.899634434697063, -17.340368674166911 ], [ 34.89975658737589, -17.340358271015273 ], [ 34.899879139534015, -17.340354021943661 ], [ 34.90000175527662, -17.340355938596865 ], [ 34.900124098534818, -17.340364015720475 ], [ 34.900245833986595, -17.340378231175233 ], [ 34.900366627975728, -17.340398545997807 ], [ 34.900486149426307, -17.340424904507497 ], [ 34.900604070749914, -17.340457234458924 ], [ 34.90072006874351, -17.340495447239984 ], [ 34.90083382547509, -17.340539438114668 ], [ 34.900945029155146, -17.340589086510239 ], [ 34.901053374990944, -17.340644256347534 ], [ 34.901158566022026, -17.340704796413981 ], [ 34.901260313933982, -17.34077054077801 ], [ 34.901358339848642, -17.340841309243807 ], [ 34.901452375088432, -17.340916907845159 ], [ 34.901542161912666, -17.340997129377051 ], [ 34.901627454224055, -17.34108175396354 ], [ 34.901708018243141, -17.34117054966033 ], [ 34.901783633149101, -17.341263273090505 ], [ 34.901854091685024, -17.341359670111498 ], [ 34.901919200725978, -17.341459476511659 ], [ 34.901978781808353, -17.341562418734288 ], [ 34.902032671619118, -17.341668214627489 ], [ 34.902080722443529, -17.341776574217395 ], [ 34.902122802569984, -17.341887200502885 ], [ 34.902158796651207, -17.341999790269696 ], [ 34.902178392629686, -17.3420748920176 ], [ 34.899977306418968, -17.342598676400389 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.3", "sub_field": "2.3A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.900577206073855, -17.344757029496293 ], [ 34.899977306418968, -17.342598676400389 ], [ 34.902178392629686, -17.3420748920176 ], [ 34.902188606020459, -17.342114034921313 ], [ 34.902212148962079, -17.342229621324904 ], [ 34.9022293609356, -17.342346232669474 ], [ 34.902240194752764, -17.342463549334198 ], [ 34.902244620706924, -17.34258124976445 ], [ 34.902242626654697, -17.342699011353165 ], [ 34.902234218049344, -17.342816511325012 ], [ 34.902219417925892, -17.342933427621141 ], [ 34.902198266838255, -17.343049439781904 ], [ 34.902170822748069, -17.343164229825184 ], [ 34.902137160866111, -17.343277483118008 ], [ 34.902097373446153, -17.343388889238934 ], [ 34.902051569532283, -17.343498142828913 ], [ 34.901999874660113, -17.343604944428311 ], [ 34.901942430512797, -17.343709001297707 ], [ 34.901879394532841, -17.343810028220332 ], [ 34.901810939490574, -17.343907748283865 ], [ 34.901737253010715, -17.344001893639497 ], [ 34.901658537058189, -17.344092206236112 ], [ 34.901575007384508, -17.344178438527649 ], [ 34.901486892936596, -17.344260354151714 ], [ 34.901394435229165, -17.344337728577433 ], [ 34.901297887682823, -17.344410349720999 ], [ 34.901197514929372, -17.344478018526939 ], [ 34.901093592086589, -17.344540549513926 ], [ 34.90098640400398, -17.344597771283127 ], [ 34.900876244482049, -17.344649526988103 ], [ 34.900763415466933, -17.344695674764722 ], [ 34.900648226222749, -17.344736088120147 ], [ 34.900577206073855, -17.344757029496293 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.4", "sub_field": "2.4A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.89784433134804, -17.347383688817565 ], [ 34.896270521422032, -17.345778057340958 ], [ 34.898018534309401, -17.34427437726421 ], [ 34.898049630555178, -17.344308651412607 ], [ 34.898125245197328, -17.344401376276529 ], [ 34.898195703304218, -17.344497774650247 ], [ 34.898260811752031, -17.344597582318404 ], [ 34.898320392078851, -17.344700525720842 ], [ 34.898374280973691, -17.344806322702414 ], [ 34.898422330724294, -17.344914683286273 ], [ 34.898464409622015, -17.345025310468575 ], [ 34.898500402322924, -17.345137901032597 ], [ 34.898530210164083, -17.345252146379647 ], [ 34.898553751434022, -17.345367733374978 ], [ 34.898570961596832, -17.345484345205968 ], [ 34.898581793469205, -17.345601662250449 ], [ 34.898586217349859, -17.345719362952764 ], [ 34.898584221101068, -17.345837124705092 ], [ 34.898575810182095, -17.345954624731661 ], [ 34.898561007634342, -17.346071540973472 ], [ 34.898539854018331, -17.34618755297101 ], [ 34.898512407302668, -17.346302342742597 ], [ 34.898478742705286, -17.346415595656016 ], [ 34.898438952487425, -17.346527001290848 ], [ 34.898393145700823, -17.346636254289407 ], [ 34.898341447888967, -17.346743055193635 ], [ 34.898284000743089, -17.34684711126604 ], [ 34.898220961713911, -17.346948137292031 ], [ 34.898152503580086, -17.347045856361728 ], [ 34.898078813974848, -17.347140000629064 ], [ 34.898000094871662, -17.347230312045884 ], [ 34.897916562030716, -17.347316543069386 ], [ 34.89784433134804, -17.347383688817565 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.4", "sub_field": "2.4C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.894633955948727, -17.344108401435214 ], [ 34.896270521422032, -17.345778057340958 ], [ 34.894530375270897, -17.347274970273929 ], [ 34.89451869393659, -17.347263380014812 ], [ 34.894438130123419, -17.347174581368925 ], [ 34.894362515902834, -17.347081855064534 ], [ 34.894292058526425, -17.346985455263962 ], [ 34.894226951109985, -17.346885646198167 ], [ 34.894167372104157, -17.346782701442645 ], [ 34.894113484805423, -17.346676903167346 ], [ 34.894065436908569, -17.346568541363308 ], [ 34.894023360101947, -17.346457913047725 ], [ 34.893987369706529, -17.346345321449771 ], [ 34.893957564360043, -17.346231075179425 ], [ 34.893934025746624, -17.346115487381585 ], [ 34.893916818373043, -17.345998874877647 ], [ 34.893905989392053, -17.345881557297147 ], [ 34.893901568473275, -17.345763856201621 ], [ 34.893903567721992, -17.3456460942032 ], [ 34.893911981646049, -17.34552859408036 ], [ 34.893926787171132, -17.345411677893143 ], [ 34.893947943704113, -17.345295666100494 ], [ 34.893975393244382, -17.345180876681777 ], [ 34.894009060543098, -17.345067624265408 ], [ 34.894048853309421, -17.344956219266344 ], [ 34.894094662463672, -17.344846967035298 ], [ 34.894146362436459, -17.344740167021918 ], [ 34.894203811512831, -17.34463611195395 ], [ 34.894266852220959, -17.344535087034966 ], [ 34.894335311763747, -17.344437369162709 ], [ 34.894409002492552, -17.344343226170164 ], [ 34.894487722421609, -17.344252916091463 ], [ 34.894571255781663, -17.344166686454802 ], [ 34.894633955948727, -17.344108401435214 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "2.4", "sub_field": "2.4B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.896270521422032, -17.345778057340958 ], [ 34.89784433134804, -17.347383688817565 ], [ 34.897828444407587, -17.347398457340631 ], [ 34.897735983525699, -17.347475830332453 ], [ 34.897639432814358, -17.347548449964961 ], [ 34.89753905691407, -17.347616117186867 ], [ 34.897435130951216, -17.347678646521146 ], [ 34.897327939783857, -17.347735866573512 ], [ 34.897217777220966, -17.347787620502217 ], [ 34.897104945217023, -17.347833766448016 ], [ 34.896989753044323, -17.347874177923067 ], [ 34.8968725164452, -17.347908744157682 ], [ 34.896753556766491, -17.347937370403951 ], [ 34.896633200078661, -17.347959978195529 ], [ 34.896511776281947, -17.347976505562741 ], [ 34.896389618201965, -17.347986907202465 ], [ 34.896267060677431, -17.347991154602312 ], [ 34.89614443964215, -17.347989236118849 ], [ 34.896022091204195, -17.347981157009478 ], [ 34.895900350724474, -17.347966939418107 ], [ 34.895779551897398, -17.347946622314364 ], [ 34.895660025836158, -17.347920261386893 ], [ 34.895542100164981, -17.347887928890579 ], [ 34.895426098121014, -17.347849713448607 ], [ 34.89531233766828, -17.347805719809461 ], [ 34.895201130625999, -17.347756068559807 ], [ 34.89509278181383, -17.347700895793917 ], [ 34.894987588216253, -17.347640352740644 ], [ 34.894885838168513, -17.347574605348829 ], [ 34.894787810566186, -17.347503833832434 ], [ 34.894693774100759, -17.347428232176501 ], [ 34.894603986523009, -17.347348007605405 ], [ 34.894530375270897, -17.347274970273929 ], [ 34.896270521422032, -17.345778057340958 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "3.1", "sub_field": "3.1A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.913636518942788, -17.329266359662952 ], [ 34.911721763444021, -17.327270799151147 ], [ 34.914031362474837, -17.325516182075784 ], [ 34.914117900913318, -17.325634575111025 ], [ 34.914198960029665, -17.325758827177712 ], [ 34.914273137824281, -17.325886983615426 ], [ 34.914340230972627, -17.326018693164876 ], [ 34.914400055566908, -17.326153594827613 ], [ 34.914452447620299, -17.326291318855283 ], [ 34.914497263516544, -17.326431487763152 ], [ 34.914534380403744, -17.326573717364585 ], [ 34.914563696531239, -17.326717617824006 ], [ 34.914585131528675, -17.326862794725379 ], [ 34.914598626626493, -17.327008850153227 ], [ 34.914604144817197, -17.327155383783179 ], [ 34.914601670956962, -17.327301993979287 ], [ 34.914591211807391, -17.327448278894774 ], [ 34.91457279601719, -17.327593837573485 ], [ 34.914546474043796, -17.327738271048865 ], [ 34.914512318015369, -17.327881183437523 ], [ 34.914470421533231, -17.328022183024327 ], [ 34.914420899415532, -17.328160883336039 ], [ 34.914363887382741, -17.328296904200737 ], [ 34.914299541685793, -17.328429872789819 ], [ 34.914228038678019, -17.328559424639966 ], [ 34.914149574331894, -17.328685204652192 ], [ 34.914064363702025, -17.328806868065175 ], [ 34.913972640335864, -17.328924081400377 ], [ 34.913874655633592, -17.329036523376065 ], [ 34.913770678159203, -17.32914388578811 ], [ 34.913660992904397, -17.329245874354772 ], [ 34.913636518942788, -17.329266359662952 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "3.1", "sub_field": "3.1C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.909685584378018, -17.325148690849765 ], [ 34.911721763444021, -17.327270799151147 ], [ 34.909460106454006, -17.328988994353146 ], [ 34.909440549923985, -17.328967439651969 ], [ 34.909346413613996, -17.328852004133012 ], [ 34.909258696485878, -17.328731994784103 ], [ 34.909177638961573, -17.328607740551625 ], [ 34.909103463207565, -17.328479582016655 ], [ 34.909036372526053, -17.328347870461474 ], [ 34.908976550797789, -17.32821296690652 ], [ 34.908924161978199, -17.32807524112085 ], [ 34.908879349648117, -17.327935070608461 ], [ 34.908842236620366, -17.327792839573583 ], [ 34.908812924603325, -17.327648937867501 ], [ 34.908791493922287, -17.327503759919889 ], [ 34.908778003299552, -17.327357703657697 ], [ 34.908772489693582, -17.327211169414433 ], [ 34.90877496819796, -17.327064558832792 ], [ 34.908785432000194, -17.326918273763734 ], [ 34.908803852400631, -17.326772715165127 ], [ 34.908830178891286, -17.32662828200262 ], [ 34.908864339294539, -17.326485370156192 ], [ 34.908906239961134, -17.326344371335054 ], [ 34.908955766027034, -17.326205672004026 ], [ 34.90901278172857, -17.3260696523243 ], [ 34.909077130774563, -17.325936685111486 ], [ 34.909148636775029, -17.325807134813829 ], [ 34.909227103724675, -17.325681356513325 ], [ 34.909312316540387, -17.325559694952492 ], [ 34.90940404165076, -17.325442483589615 ], [ 34.909502027636492, -17.325330043684811 ], [ 34.909606005919557, -17.325222683419522 ], [ 34.909685584378018, -17.325148690849765 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "3.1", "sub_field": "3.1B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.911721763444021, -17.327270799151147 ], [ 34.913636518942788, -17.329266359662952 ], [ 34.913545900507494, -17.329342209523453 ], [ 34.91342571642943, -17.329432627237008 ], [ 34.913300770089045, -17.329516879657564 ], [ 34.913171403960227, -17.329594735846065 ], [ 34.913037972633042, -17.329665982395142 ], [ 34.912900841841903, -17.3297304240144 ], [ 34.912760387462882, -17.329787884065563 ], [ 34.912616994483479, -17.329838205046872 ], [ 34.912471055947115, -17.329881249024769 ], [ 34.912322971875888, -17.329916898012115 ], [ 34.912173148173757, -17.329945054291645 ], [ 34.912021995513982, -17.329965640683806 ], [ 34.911869928213214, -17.329978600758434 ], [ 34.911717363095775, -17.329983898989447 ], [ 34.911564718350839, -17.329981520852233 ], [ 34.911412412386156, -17.329971472863519 ], [ 34.911260862680884, -17.329953782563518 ], [ 34.911110484641192, -17.329928498440431 ], [ 34.910961690461392, -17.329895689797588 ], [ 34.910814887993944, -17.32985544656341 ], [ 34.910670479631406, -17.329807879044964 ], [ 34.910528861203304, -17.329753117625536 ], [ 34.910390420890941, -17.329691312407235 ], [ 34.910255538163376, -17.329622632799548 ], [ 34.910124582737076, -17.329547267054856 ], [ 34.909997913562464, -17.329465421752481 ], [ 34.90987587783988, -17.3293773212323 ], [ 34.909758810067963, -17.329283206979852 ], [ 34.909647031126617, -17.329183336964235 ], [ 34.90954084739743, -17.329077984931061 ], [ 34.909460106454006, -17.328988994353146 ], [ 34.911721763444021, -17.327270799151147 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "3.2", "sub_field": "3.2B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.906021160934294, -17.32453810278799 ], [ 34.904885066790669, -17.326570895183487 ], [ 34.904806230599256, -17.326530749763023 ], [ 34.904701047182016, -17.326470210855359 ], [ 34.904599306688482, -17.326404467496388 ], [ 34.904501287988097, -17.326333699888984 ], [ 34.904407259747913, -17.32625810200749 ], [ 34.904317479696211, -17.32617788106592 ], [ 34.904232193915959, -17.326093256949981 ], [ 34.904151636170468, -17.326004461614346 ], [ 34.904076027262448, -17.325911738446724 ], [ 34.90400557442895, -17.325815341600823 ], [ 34.903940470773314, -17.32571553529953 ], [ 34.903880894735885, -17.325612593110719 ], [ 34.903827009605024, -17.325506797197328 ], [ 34.90377896306962, -17.325398437543917 ], [ 34.903736886814265, -17.32528781116179 ], [ 34.903700896158483, -17.325175221274797 ], [ 34.903671089740698, -17.325060976488256 ], [ 34.903647549248021, -17.324945389942979 ], [ 34.903630339192389, -17.324828778456951 ], [ 34.903619506733925, -17.324711461656918 ], [ 34.90361508155182, -17.324593761102271 ], [ 34.903617075763009, -17.324475999403674 ], [ 34.903625483889194, -17.324358499338782 ], [ 34.903640282871983, -17.324241582967488 ], [ 34.903661432136161, -17.324125570749231 ], [ 34.903688873701093, -17.324010780664615 ], [ 34.903722532339778, -17.323897527343828 ], [ 34.903762315785151, -17.323786121204328 ], [ 34.903808114983079, -17.323676867599978 ], [ 34.903859804391431, -17.323570065984185 ], [ 34.903896908377526, -17.323502846894009 ], [ 34.906021160934294, -17.32453810278799 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "3.2", "sub_field": "3.2D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.906021160934294, -17.32453810278799 ], [ 34.907099870341185, -17.322607987848503 ], [ 34.907107996990085, -17.322612126146115 ], [ 34.907213178728831, -17.322672663949191 ], [ 34.907314917813316, -17.322738406113043 ], [ 34.907412935389594, -17.322809172447901 ], [ 34.907506962802877, -17.322884768993358 ], [ 34.907596742334007, -17.322964988550062 ], [ 34.907682027905636, -17.323049611247448 ], [ 34.907762585756814, -17.323138405146423 ], [ 34.907838195083627, -17.323231126874965 ], [ 34.907908648644458, -17.32332752229518 ], [ 34.907973753328015, -17.323427327199781 ], [ 34.908033330682649, -17.323530268036201 ], [ 34.908087217405573, -17.323636062656309 ], [ 34.908135265790484, -17.323744421089724 ], [ 34.908177344132525, -17.323855046338515 ], [ 34.908213337089279, -17.323967635191241 ], [ 34.908243145997119, -17.324081879053907 ], [ 34.908266689141676, -17.324197464795841 ], [ 34.908283901981953, -17.324314075607841 ], [ 34.908294737327296, -17.324431391870569 ], [ 34.908299165466907, -17.324549092030502 ], [ 34.908297174251452, -17.324666853481297 ], [ 34.908288769126365, -17.324784353448031 ], [ 34.908273973117211, -17.32490126987188 ], [ 34.908252826766599, -17.325017282292816 ], [ 34.908225388023233, -17.325132072728039 ], [ 34.908191732083218, -17.325245326543502 ], [ 34.908151951184053, -17.32535673331634 ], [ 34.908106154351955, -17.325465987685721 ], [ 34.908071600970572, -17.325537386101097 ], [ 34.906021160934294, -17.32453810278799 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "3.2", "sub_field": "3.2A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.904885066790669, -17.326570895183487 ], [ 34.906021160934294, -17.32453810278799 ], [ 34.908071600970572, -17.325537386101097 ], [ 34.908054467103149, -17.325572790189899 ], [ 34.90799703109991, -17.325676848086957 ], [ 34.907934003762392, -17.325777876157318 ], [ 34.907865557837262, -17.325875597485545 ], [ 34.907791880924258, -17.325969744219353 ], [ 34.907713174962034, -17.326060058303852 ], [ 34.907629655674789, -17.326146292188934 ], [ 34.907541551980941, -17.326228209507818 ], [ 34.907449105365721, -17.326305585725002 ], [ 34.907352569219306, -17.326378208751755 ], [ 34.907252208142296, -17.32644587952749 ], [ 34.907148297220381, -17.326508412565456 ], [ 34.90704112127046, -17.326565636461215 ], [ 34.906930974059705, -17.326617394362458 ], [ 34.906818157500545, -17.326663544399111 ], [ 34.906702980822907, -17.326703960072088 ], [ 34.906585759726646, -17.326738530600174 ], [ 34.906466815516076, -17.326767161223724 ], [ 34.906346474219269, -17.326789773464377 ], [ 34.906225065694215, -17.326806305340277 ], [ 34.906102922724692, -17.326816711535912 ], [ 34.905980380107977, -17.326820963526419 ], [ 34.905857773736962, -17.326819049655761 ], [ 34.905735439679553, -17.326810975168716 ], [ 34.905613713257182, -17.326796762196473 ], [ 34.905492928125767, -17.326776449696034 ], [ 34.905373415360891, -17.326750093343406 ], [ 34.905255502550368, -17.326717765380913 ], [ 34.905139512896064, -17.326679554419297 ], [ 34.90502576432803, -17.326635565194774 ], [ 34.904914568632925, -17.326585918281896 ], [ 34.904885066790669, -17.326570895183487 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "3.3", "sub_field": "3.3B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.900551100160158, -17.32491517010353 ], [ 34.900776626466843, -17.327745490872061 ], [ 34.900656786239431, -17.327755697621463 ], [ 34.900504221756528, -17.327760987206652 ], [ 34.900351578120073, -17.327758600371943 ], [ 34.900199273734728, -17.327748543657901 ], [ 34.900047726075364, -17.327730844628654 ], [ 34.899897350542489, -17.327705551796367 ], [ 34.899748559323548, -17.327672734488232 ], [ 34.899601760262883, -17.327632482656444 ], [ 34.899457355743735, -17.327584906631635 ], [ 34.899315741585028, -17.327530136820421 ], [ 34.899177305956393, -17.327468323347929 ], [ 34.899042428314019, -17.327399635646241 ], [ 34.898911478360418, -17.327324261989983 ], [ 34.898784815031014, -17.327242408980108 ], [ 34.898662785510126, -17.327154300977668 ], [ 34.898545724279373, -17.32706017948869 ], [ 34.898433952200669, -17.326960302502119 ], [ 34.898327775636837, -17.326854943782664 ], [ 34.898227485611763, -17.326744392120304 ], [ 34.898133357012782, -17.326628950538552 ], [ 34.898045647837129, -17.326508935463988 ], [ 34.897964598484933, -17.32638467585863 ], [ 34.897890431100279, -17.32625651231832 ], [ 34.897823348962419, -17.326124796139013 ], [ 34.897763535928661, -17.325989888353835 ], [ 34.897711155930615, -17.325852158743412 ], [ 34.897666352524929, -17.325711984822245 ], [ 34.897629248499918, -17.325569750803872 ], [ 34.897599945539312, -17.325425846547741 ], [ 34.897578523943601, -17.325280666490528 ], [ 34.897565743794004, -17.325142207305205 ], [ 34.900551100160158, -17.32491517010353 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "3.3", "sub_field": "3.3D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.900551100160158, -17.32491517010353 ], [ 34.900331858463275, -17.322163720229909 ], [ 34.900446486600295, -17.322159746280438 ], [ 34.900599125593352, -17.322162133497788 ], [ 34.900751425327122, -17.322172190362547 ], [ 34.900902968377679, -17.322189889308945 ], [ 34.901053339395013, -17.322215181825705 ], [ 34.901202126241159, -17.322247998589095 ], [ 34.901348921119656, -17.322288249652903 ], [ 34.901493321693145, -17.3223358246949 ], [ 34.901634932185885, -17.322390593319241 ], [ 34.901773364468404, -17.322452405413809 ], [ 34.901908239121205, -17.322521091561601 ], [ 34.902039186474497, -17.322596463505029 ], [ 34.902165847621376, -17.322678314661896 ], [ 34.902287875401406, -17.322766420691451 ], [ 34.902404935352081, -17.322860540109318 ], [ 34.90251670662547, -17.322960414949193 ], [ 34.902622882867597, -17.323065771469913 ], [ 34.902723173058085, -17.323176320905603 ], [ 34.90281730230781, -17.323291760257099 ], [ 34.902905012612401, -17.323411773122313 ], [ 34.902986063559332, -17.323536030563393 ], [ 34.903060232987073, -17.323664192008231 ], [ 34.903127317593992, -17.323795906183808 ], [ 34.903187133495692, -17.323930812078967 ], [ 34.903239516729194, -17.324068539933798 ], [ 34.90328432370238, -17.324208712253018 ], [ 34.903321431587827, -17.324350944840624 ], [ 34.903350738659526, -17.32449484785289 ], [ 34.903372164571941, -17.324640026866742 ], [ 34.90337772102832, -17.324700204781678 ], [ 34.900551100160158, -17.32491517010353 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "3.3", "sub_field": "3.3A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.900776626466843, -17.327745490872061 ], [ 34.900551100160158, -17.32491517010353 ], [ 34.90337772102832, -17.324700204781678 ], [ 34.903385650580439, -17.324786083960905 ], [ 34.903391159702402, -17.324932618806443 ], [ 34.903388676818878, -17.325079229764025 ], [ 34.903378208716227, -17.325225514984815 ], [ 34.903359784067653, -17.325371073511793 ], [ 34.903333453354918, -17.325515506378824 ], [ 34.903299288730132, -17.325658417704162 ], [ 34.903257383818179, -17.325799415775553 ], [ 34.903207853460337, -17.325938114123943 ], [ 34.903150833399678, -17.326074132582772 ], [ 34.903086479909113, -17.326207098330002 ], [ 34.903014969363383, -17.326336646910139 ], [ 34.902936497755626, -17.326462423233099 ], [ 34.902851280160384, -17.326584082547722 ], [ 34.902759550144168, -17.326701291386676 ], [ 34.902661559125448, -17.326813728480538 ], [ 34.902557575685506, -17.326921085638585 ], [ 34.902447884832448, -17.327023068593451 ], [ 34.902332787219926, -17.327119397807941 ], [ 34.902212598323182, -17.327209809241211 ], [ 34.902087647574284, -17.327294055072645 ], [ 34.901958277459215, -17.327371904381195 ], [ 34.901824842578996, -17.327443143778424 ], [ 34.901687708677741, -17.327507577993487 ], [ 34.901547251640118, -17.327565030408447 ], [ 34.90140385646086, -17.327615343542455 ], [ 34.901257916189486, -17.327658379483513 ], [ 34.901109830852782, -17.327694020266488 ], [ 34.900960006358247, -17.327722168196619 ], [ 34.900808853381278, -17.327742746117259 ], [ 34.900776626466843, -17.327745490872061 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.1", "sub_field": "4.1B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.886782334569538, -17.326739205117487 ], [ 34.887703979851366, -17.329372879634846 ], [ 34.8876643191578, -17.329386792202165 ], [ 34.887518373215272, -17.329429818514686 ], [ 34.887370282716603, -17.329465449470245 ], [ 34.88722045358341, -17.329493587401011 ], [ 34.887069296503846, -17.329514155177758 ], [ 34.886917225806691, -17.329527096421376 ], [ 34.886764658325532, -17.329532375657408 ], [ 34.886612012256101, -17.329529978413341 ], [ 34.886459706009731, -17.329519911258302 ], [ 34.886308157066374, -17.329502201785065 ], [ 34.886157780830047, -17.329476898534416 ], [ 34.886008989490165, -17.32944407086211 ], [ 34.885862190891373, -17.32940380874879 ], [ 34.885717787415615, -17.329356222553258 ], [ 34.88557617487897, -17.329301442710033 ], [ 34.885437741446637, -17.32923961937173 ], [ 34.885302866568736, -17.329170921997534 ], [ 34.885171919940205, -17.32909553888852 ], [ 34.885045260487331, -17.329013676671618 ], [ 34.884923235383837, -17.328925559733097 ], [ 34.884806179099137, -17.328831429603483 ], [ 34.884694412481636, -17.328731544295415 ], [ 34.88458824187915, -17.328626177596401 ], [ 34.884487958299232, -17.328515618318296 ], [ 34.884393836611494, -17.328400169505525 ], [ 34.884306134794222, -17.328280147604413 ], [ 34.884225093227393, -17.328155881595727 ], [ 34.884150934033698, -17.328027712092847 ], [ 34.884083860469936, -17.327895990408074 ], [ 34.884024056369917, -17.327761077589614 ], [ 34.883971685640724, -17.327623343431874 ], [ 34.883964047061163, -17.327599439232444 ], [ 34.886782334569538, -17.326739205117487 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.1", "sub_field": "4.1D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.886782334569538, -17.326739205117487 ], [ 34.885846577829994, -17.324065205990067 ], [ 34.885953641947083, -17.324033642826574 ], [ 34.886101728582837, -17.323998013298716 ], [ 34.886251553602811, -17.323969876608579 ], [ 34.886402706363931, -17.323949309871807 ], [ 34.886554772584716, -17.32393636945611 ], [ 34.886707335480622, -17.323931090826889 ], [ 34.88685997690618, -17.323933488449978 ], [ 34.887012278500904, -17.323943555752123 ], [ 34.887163822835845, -17.323961265138948 ], [ 34.887314194557419, -17.323986568070588 ], [ 34.887462981525729, -17.324019395194775 ], [ 34.887609775944007, -17.324059656536921 ], [ 34.887754175476097, -17.324107241746617 ], [ 34.887895784349141, -17.324162020400173 ], [ 34.888034214438093, -17.324223842358005 ], [ 34.888169086329441, -17.324292538176074 ], [ 34.88830003036098, -17.324367919570307 ], [ 34.888426687634862, -17.324449779932586 ], [ 34.888548711001285, -17.324537894896967 ], [ 34.888665766009801, -17.324632022954553 ], [ 34.888777531826008, -17.324731906115399 ], [ 34.888883702110896, -17.324837270615532 ], [ 34.888983985860385, -17.324947827667181 ], [ 34.88907810820298, -17.325063274250319 ], [ 34.889165811153212, -17.325183293943041 ], [ 34.889246854318749, -17.325307557788776 ], [ 34.889321015559332, -17.325435725197845 ], [ 34.889388091595755, -17.325567444880889 ], [ 34.889447898567113, -17.325702355811647 ], [ 34.88950027253486, -17.325840088216371 ], [ 34.889520512970037, -17.325903422939145 ], [ 34.886782334569538, -17.326739205117487 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.1", "sub_field": "4.1A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.887703979851366, -17.329372879634846 ], [ 34.886782334569538, -17.326739205117487 ], [ 34.889520512970037, -17.325903422939145 ], [ 34.889545069932325, -17.325980264587329 ], [ 34.889582167958245, -17.326122500717403 ], [ 34.889611464913656, -17.326266406753103 ], [ 34.889632880480761, -17.326411588263088 ], [ 34.889646355943256, -17.326557647319234 ], [ 34.889651854347427, -17.326704183587182 ], [ 34.889649360603691, -17.326850795423734 ], [ 34.889638881528178, -17.326997080977577 ], [ 34.889620445824171, -17.327142639290791 ], [ 34.889594104003734, -17.327287071397805 ], [ 34.889559928249412, -17.327429981418941 ], [ 34.889518012216612, -17.327570977645536 ], [ 34.889468470777103, -17.327709673613544 ], [ 34.889411439704311, -17.32784568916291 ], [ 34.889347075301409, -17.327978651479597 ], [ 34.889275553972986, -17.328108196117469 ], [ 34.889197071741769, -17.328233967997264 ], [ 34.88911184371144, -17.328355622379977 ], [ 34.889020103477137, -17.32847282581178 ], [ 34.888922102485324, -17.328585257038089 ], [ 34.888818109344669, -17.328692607884239 ], [ 34.888708409089844, -17.328794584100173 ], [ 34.888593302400324, -17.328890906167157 ], [ 34.888473104776217, -17.328981310063959 ], [ 34.888348145673561, -17.329065547990599 ], [ 34.888218767601209, -17.329143389047712 ], [ 34.888085325182047, -17.329214619869479 ], [ 34.887948184180871, -17.329279045208576 ], [ 34.887807720501797, -17.329336488471384 ], [ 34.887703979851366, -17.329372879634846 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.2", "sub_field": "4.2A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.894318941931957, -17.326404205007993 ], [ 34.892592473294087, -17.324940604483569 ], [ 34.89423852225147, -17.323363284353789 ], [ 34.894279290997311, -17.323403741786901 ], [ 34.894359843150291, -17.323492541716824 ], [ 34.894435446447254, -17.323585269172604 ], [ 34.894505893663094, -17.323681670000639 ], [ 34.894570991703901, -17.323781479978805 ], [ 34.894630562136221, -17.323884425540623 ], [ 34.894684441676247, -17.32399022452503 ], [ 34.894732482637338, -17.32409858694966 ], [ 34.894774553334969, -17.324209215805645 ], [ 34.8948105384477, -17.324321807871623 ], [ 34.894840339333385, -17.324436054544822 ], [ 34.894863874299624, -17.324551642686835 ], [ 34.894881078827837, -17.324668255481892 ], [ 34.894891905750164, -17.324785573305192 ], [ 34.89489632537888, -17.32490327459891 ], [ 34.894894325587956, -17.325021036753622 ], [ 34.894885911846373, -17.325138536992469 ], [ 34.894871107203294, -17.325255453255821 ], [ 34.894849952224995, -17.325371465084146 ], [ 34.894822504883827, -17.325486254496205 ], [ 34.894788840399457, -17.32559950686079 ], [ 34.894749051032846, -17.325710911758964 ], [ 34.894703245833377, -17.325820163835068 ], [ 34.894651550340264, -17.325926963633588 ], [ 34.894594106238337, -17.326031018420061 ], [ 34.89453107096999, -17.326132042983406 ], [ 34.894462617303638, -17.326229760417807 ], [ 34.894388932860224, -17.326323902881626 ], [ 34.894318941931957, -17.326404205007993 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.2", "sub_field": "4.2C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.890807409243124, -17.32342733014784 ], [ 34.892592473294087, -17.324940604483569 ], [ 34.890923514244896, -17.32653987812137 ], [ 34.890914484047336, -17.32653180828056 ], [ 34.890829203620619, -17.3264471778465 ], [ 34.890748651575883, -17.326358376480158 ], [ 34.890673048700258, -17.326265647585799 ], [ 34.890602602214209, -17.326169245332796 ], [ 34.890537505203675, -17.326069433958875 ], [ 34.890477936090804, -17.325966487045811 ], [ 34.890424058144973, -17.325860686769481 ], [ 34.890376019035322, -17.325752323126384 ], [ 34.890333950426125, -17.325641693138767 ], [ 34.890297967615908, -17.325529100040367 ], [ 34.890268169221613, -17.32541485244527 ], [ 34.890244636908328, -17.325299263501982 ], [ 34.890227435165592, -17.325182650035078 ], [ 34.890216611130739, -17.325065331676697 ], [ 34.890212194459821, -17.324947629990511 ], [ 34.890214197246515, -17.324829867590271 ], [ 34.890222613988975, -17.324712367255533 ], [ 34.890237421605185, -17.324595451046935 ], [ 34.890258579496297, -17.324479439423452 ], [ 34.890286029658014, -17.324364650364053 ], [ 34.890319696839796, -17.324251398496109 ], [ 34.890359488751137, -17.324139994233082 ], [ 34.890405296314732, -17.324030742923696 ], [ 34.890456993965522, -17.323923944015053 ], [ 34.890514439994988, -17.323819890231881 ], [ 34.890577476939619, -17.32371886677425 ], [ 34.890645932012674, -17.323621150535875 ], [ 34.89071961757778, -17.323527009345277 ], [ 34.890798331663291, -17.323436701231689 ], [ 34.890807409243124, -17.32342733014784 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.2", "sub_field": "4.2B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.892592473294087, -17.324940604483569 ], [ 34.894318941931957, -17.326404205007993 ], [ 34.894310219599099, -17.326414212331692 ], [ 34.894226693264422, -17.326500441230607 ], [ 34.89413858279395, -17.326582353225231 ], [ 34.89404612969151, -17.32665972379467 ], [ 34.893949587365086, -17.326732340865671 ], [ 34.893849220432202, -17.326800005393974 ], [ 34.893745303994642, -17.32686253190996 ], [ 34.893638122884369, -17.326919749027052 ], [ 34.893527970882786, -17.326971499911568 ], [ 34.893415149915398, -17.327017642712615 ], [ 34.893299969224287, -17.327058050950988 ], [ 34.893182744520246, -17.327092613865844 ], [ 34.893063797117513, -17.327121236718394 ], [ 34.892943453052915, -17.327143841051626 ], [ 34.892822042192059, -17.327160364905325 ], [ 34.892699897325102, -17.327170762986 ], [ 34.892577353254502, -17.327175006791023 ], [ 34.892454745877195, -17.327173084686827 ], [ 34.892332411263794, -17.327165001940749 ], [ 34.892210684737286, -17.327150780706638 ], [ 34.892089899953852, -17.327130459964124 ], [ 34.891970387988138, -17.32710409541183 ], [ 34.89185247642574, -17.327071759314567 ], [ 34.891736488465099, -17.327033540305397 ], [ 34.89162274203165, -17.32698954314257 ], [ 34.891511548906152, -17.326939888422402 ], [ 34.891403213870092, -17.326884712248685 ], [ 34.891298033870221, -17.326824165859591 ], [ 34.891196297204459, -17.326758415213135 ], [ 34.891098282731733, -17.326687640532139 ], [ 34.891004259107483, -17.326612035810371 ], [ 34.890923514244896, -17.32653987812137 ], [ 34.892592473294087, -17.324940604483569 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.4", "sub_field": "4.4B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.901510659033576, -17.318761975282452 ], [ 34.90279052743562, -17.320898850353373 ], [ 34.902695969314003, -17.320949335742025 ], [ 34.902573880267774, -17.32100670306102 ], [ 34.90244883266864, -17.321057854335081 ], [ 34.902321169272682, -17.321102649355964 ], [ 34.902191240006921, -17.321140965337896 ], [ 34.902059401010106, -17.321172697254248 ], [ 34.901926013656272, -17.321197758125358 ], [ 34.901791443564306, -17.321216079257098 ], [ 34.90165605959551, -17.32122761042908 ], [ 34.901520232842493, -17.321232320032479 ], [ 34.901384335611887, -17.321230195156613 ], [ 34.901248740403666, -17.321221241624368 ], [ 34.901113818890003, -17.321205483976261 ], [ 34.900979940896406, -17.321182965403178 ], [ 34.900847473387834, -17.321153747627999 ], [ 34.900716779462755, -17.321117910736373 ], [ 34.900588217357772, -17.321075552957247 ], [ 34.900462139465517, -17.321026790393535 ], [ 34.900338891368634, -17.320971756703898 ], [ 34.900218810892511, -17.320910602736351 ], [ 34.900102227179111, -17.32084349611473 ], [ 34.899989459784749, -17.320770620779172 ], [ 34.899880817804124, -17.320692176481966 ], [ 34.899776599023014, -17.320608378239882 ], [ 34.899677089102028, -17.320519455744826 ], [ 34.899582560793547, -17.320425652734166 ], [ 34.899493273194146, -17.320327226322597 ], [ 34.89940947103441, -17.32022444629732 ], [ 34.899331384008093, -17.320117594378495 ], [ 34.899259226142632, -17.320006963446986 ], [ 34.89921991385566, -17.319939028684551 ], [ 34.901510659033576, -17.318761975282452 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.3", "sub_field": "4.3B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.896432140987109, -17.321371469783127 ], [ 34.89747695456991, -17.323348411891939 ], [ 34.897469679008267, -17.323352296088142 ], [ 34.897359530326881, -17.323404048785928 ], [ 34.897246712631812, -17.32345019345582 ], [ 34.897131535156085, -17.323490603613518 ], [ 34.897014313601751, -17.323525168492896 ], [ 34.896895369274397, -17.323553793349777 ], [ 34.896775028202391, -17.323576399721649 ], [ 34.896653620243129, -17.323592925642721 ], [ 34.896531478178844, -17.323603325813824 ], [ 34.896408936804313, -17.323607571726608 ], [ 34.896286332009041, -17.323605651741715 ], [ 34.896163999856569, -17.323597571120658 ], [ 34.89604227566317, -17.323583352011493 ], [ 34.895921493078589, -17.323563033388012 ], [ 34.895801983171467, -17.32353667094301 ], [ 34.895684073521728, -17.323504336935521 ], [ 34.895568087322616, -17.32346611999289 ], [ 34.895454342494695, -17.323422124867694 ], [ 34.895343150814341, -17.323372472150655 ], [ 34.895234817059112, -17.32331729794009 ], [ 34.895129638172165, -17.323256753468765 ], [ 34.895027902448454, -17.323191004689452 ], [ 34.894929888744272, -17.323120231819857 ], [ 34.894835865713006, -17.323044628848749 ], [ 34.894746091068598, -17.3229644030041 ], [ 34.894660810879273, -17.322879774185061 ], [ 34.894580258892958, -17.322790974359165 ], [ 34.894504655896569, -17.322698246926471 ], [ 34.894434209110955, -17.322601846052333 ], [ 34.894369111622865, -17.3225020359707 ], [ 34.894337657341168, -17.322447678138754 ], [ 34.896432140987109, -17.321371469783127 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.4", "sub_field": "4.4D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.901510659033576, -17.318761975282452 ], [ 34.900201998898332, -17.316577029381111 ], [ 34.900293113953509, -17.316528382885895 ], [ 34.900415201043664, -17.316471017154804 ], [ 34.900540246370653, -17.316419867378574 ], [ 34.900667907203236, -17.316375073749036 ], [ 34.900797833642493, -17.316336759036712 ], [ 34.900929669580677, -17.316305028254352 ], [ 34.90106305367722, -17.316279968369155 ], [ 34.90119762034896, -17.316261648064486 ], [ 34.901333000772034, -17.316250117551629 ], [ 34.901468823892699, -17.316245408432209 ], [ 34.901604717444123, -17.316247533611602 ], [ 34.90174030896663, -17.316256487263608 ], [ 34.901875226828437, -17.316272244846392 ], [ 34.90200910124404, -17.316294763169765 ], [ 34.902141565287693, -17.316323980513619 ], [ 34.902272255898858, -17.316359816796982 ], [ 34.902400814877282, -17.31640217379762 ], [ 34.902526889864582, -17.316450935421123 ], [ 34.902650135309905, -17.31650596801914 ], [ 34.902770213416957, -17.316567120755611 ], [ 34.902886795069712, -17.316634226020188 ], [ 34.902999560734415, -17.316707099887594 ], [ 34.903108201335357, -17.316785542621652 ], [ 34.903212419101848, -17.316869339222688 ], [ 34.903311928384397, -17.316958260016797 ], [ 34.903406456437629, -17.317052061285217 ], [ 34.903495744167735, -17.317150485932377 ], [ 34.903579546842757, -17.317253264190409 ], [ 34.903657634763242, -17.317360114358475 ], [ 34.903729793891969, -17.317470743574912 ], [ 34.903795826440607, -17.317584848619777 ], [ 34.903797012928912, -17.317587178252715 ], [ 34.901510659033576, -17.318761975282452 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.4", "sub_field": "4.4A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.90279052743562, -17.320898850353373 ], [ 34.901510659033576, -17.318761975282452 ], [ 34.903797012928912, -17.317587178252715 ], [ 34.903855551411873, -17.317702116745874 ], [ 34.903908805095746, -17.317822226535956 ], [ 34.903955441518242, -17.317944848783579 ], [ 34.90399533284166, -17.318069647395419 ], [ 34.904028369715036, -17.318196280312364 ], [ 34.904054461574034, -17.318324400447054 ], [ 34.904073536889292, -17.318453656635143 ], [ 34.904085543362676, -17.31858369459782 ], [ 34.904090448070683, -17.318714157912751 ], [ 34.90408823755498, -17.318844688991089 ], [ 34.904078917859259, -17.318974930057436 ], [ 34.904062514513072, -17.319104524130601 ], [ 34.904039072461806, -17.319233116001957 ], [ 34.904008655943819, -17.319360353209095 ], [ 34.90397134831445, -17.31948588700191 ], [ 34.903927251817677, -17.319609373298508 ], [ 34.903876487306093, -17.319730473628336 ], [ 34.903819193909769, -17.319848856059931 ], [ 34.903755528655026, -17.319964196110821 ], [ 34.903685666034164, -17.320076177636885 ], [ 34.903609797527295, -17.320184493698932 ], [ 34.903528131077636, -17.320288847404175 ], [ 34.903440890521551, -17.320388952719917 ], [ 34.903348314975204, -17.320484535257712 ], [ 34.903250658179061, -17.320575333025491 ], [ 34.903148187802621, -17.320661097145702 ], [ 34.903041184710538, -17.320741592537622 ], [ 34.902929942192998, -17.320816598561695 ], [ 34.902814765161672, -17.320885909624447 ], [ 34.90279052743562, -17.320898850353373 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.3", "sub_field": "4.3D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.896432140987109, -17.321371469783127 ], [ 34.895357321246472, -17.319337751577674 ], [ 34.895412100380852, -17.319312014200012 ], [ 34.895524916225504, -17.319265870748787 ], [ 34.895640091613821, -17.319225461723033 ], [ 34.895757310866635, -17.319190897876435 ], [ 34.89587625270358, -17.319162273941881 ], [ 34.895996591123556, -17.319139668371751 ], [ 34.896117996298237, -17.319123143123075 ], [ 34.8962401354759, -17.319112743487636 ], [ 34.8963626738934, -17.319108497967935 ], [ 34.896485275693607, -17.31911041819901 ], [ 34.896607604845812, -17.319118498916652 ], [ 34.896729326066612, -17.319132717971751 ], [ 34.896850105738828, -17.319153036391111 ], [ 34.896969612825721, -17.31917939848416 ], [ 34.897087519778275, -17.319211731995729 ], [ 34.897203503432799, -17.31924994830392 ], [ 34.897317245896588, -17.319293942663073 ], [ 34.897428435419187, -17.319343594490885 ], [ 34.897536767246699, -17.319398767698779 ], [ 34.897641944457035, -17.319459311064964 ], [ 34.897743678773672, -17.319525058648797 ], [ 34.897841691355659, -17.319595830245646 ], [ 34.897935713561985, -17.3196714318807 ], [ 34.898025487687661, -17.319751656340614 ], [ 34.898110767670225, -17.319836283741424 ], [ 34.898191319763967, -17.319925082131114 ], [ 34.89826692318082, -17.320017808125392 ], [ 34.898337370695316, -17.320114207574626 ], [ 34.898402469212712, -17.320214016260511 ], [ 34.898462040298284, -17.32031696062009 ], [ 34.898466676679575, -17.320326064479389 ], [ 34.896432140987109, -17.321371469783127 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.3", "sub_field": "4.3A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.89747695456991, -17.323348411891939 ], [ 34.896432140987109, -17.321371469783127 ], [ 34.898466676679575, -17.320326064479389 ], [ 34.898515920666433, -17.320422758495592 ], [ 34.898563962628245, -17.320531119907688 ], [ 34.898606034496453, -17.320641747850299 ], [ 34.898642020946426, -17.320754339104575 ], [ 34.898671823332343, -17.320868585069945 ], [ 34.898695359957713, -17.320984172609954 ], [ 34.898712566299402, -17.321100784910492 ], [ 34.898723395184547, -17.321218102348105 ], [ 34.898727816920108, -17.321335803366054 ], [ 34.89872581937427, -17.32145356535565 ], [ 34.898717408009844, -17.321571065540464 ], [ 34.898702605869474, -17.321687981861071 ], [ 34.898681453512594, -17.321803993857738 ], [ 34.898654008904387, -17.321918783548817 ], [ 34.898620347257008, -17.322032036302279 ], [ 34.898580560823632, -17.322143441698167 ], [ 34.898534758645638, -17.322252694379447 ], [ 34.898483066253917, -17.322359494888957 ], [ 34.89842562532484, -17.322463550490273 ], [ 34.898362593292084, -17.322564575970105 ], [ 34.898294142915205, -17.322662294420091 ], [ 34.89822046180609, -17.322756437995842 ], [ 34.898141751914984, -17.322846748651152 ], [ 34.89805822897678, -17.322932978845273 ], [ 34.897970121919982, -17.323014892221561 ], [ 34.897877672239005, -17.323092264255322 ], [ 34.897781133332444, -17.323164882869285 ], [ 34.897680769808474, -17.323232549014975 ], [ 34.897576856759528, -17.323295077218333 ], [ 34.89747695456991, -17.323348411891939 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.5", "sub_field": "4.5A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.895433867295488, -17.318895605322869 ], [ 34.893769553711472, -17.316586264213559 ], [ 34.896250865251233, -17.315043362242964 ], [ 34.896318457119754, -17.315146999772502 ], [ 34.896392618163802, -17.315275164603293 ], [ 34.896459694535324, -17.315406881910381 ], [ 34.896519502372527, -17.315541790673997 ], [ 34.896571877734488, -17.31567952112637 ], [ 34.896616677050702, -17.315819695765096 ], [ 34.896653777514736, -17.315961930387815 ], [ 34.896683077420903, -17.316105835145184 ], [ 34.896704496443341, -17.316251015609364 ], [ 34.896717975856284, -17.316397073855079 ], [ 34.896723478695229, -17.316543609550223 ], [ 34.896720989858451, -17.316690221053168 ], [ 34.896710516148602, -17.316836506513539 ], [ 34.896692086254312, -17.316982064973665 ], [ 34.896665750671708, -17.317126497467576 ], [ 34.896631581566275, -17.317269408114562 ], [ 34.896589672575168, -17.317410405204214 ], [ 34.89654013855089, -17.317549102270181 ], [ 34.896483115246532, -17.317685119149388 ], [ 34.896418758943973, -17.317818083024147 ], [ 34.89634724602562, -17.317947629444067 ], [ 34.896268772491148, -17.318073403325023 ], [ 34.896183553420386, -17.318195059922527 ], [ 34.896091822383909, -17.31831226577669 ], [ 34.895993830802993, -17.318424699626295 ], [ 34.89588984726047, -17.318532053289481 ], [ 34.895780156764722, -17.318634032508506 ], [ 34.895665059968458, -17.31873035775638 ], [ 34.895544872344658, -17.31882076500316 ], [ 34.895433867295488, -17.318895605322869 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.5", "sub_field": "4.5C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.892115685045027, -17.314291416092694 ], [ 34.893769553711472, -17.316586264213559 ], [ 34.891356570129695, -17.318086679241528 ], [ 34.891297083615598, -17.317995466139099 ], [ 34.891222924615022, -17.317867299214686 ], [ 34.891155850712593, -17.317735579905825 ], [ 34.891096045743055, -17.317600669254201 ], [ 34.891043673615911, -17.31746293704829 ], [ 34.890998877866231, -17.317322760809684 ], [ 34.890961781261367, -17.317180524758289 ], [ 34.890932485464695, -17.317036618759101 ], [ 34.890911070757085, -17.31689143725356 ], [ 34.890897595817023, -17.316745378178336 ], [ 34.890892097560027, -17.316598841874647 ], [ 34.890894591037593, -17.316452229990769 ], [ 34.890905069396219, -17.316305944381273 ], [ 34.890923503896353, -17.316160386005446 ], [ 34.8909498439914, -17.316015953828344 ], [ 34.890984017466458, -17.315873043727201 ], [ 34.891025930636467, -17.315732047406435 ], [ 34.891075468603169, -17.315593351324001 ], [ 34.89113249557024, -17.315457335632171 ], [ 34.891196855215718, -17.315324373135638 ], [ 34.891268371120539, -17.315194828269686 ], [ 34.891346847252301, -17.315069056101418 ], [ 34.891432068502752, -17.314947401356562 ], [ 34.891523801277408, -17.314830197474695 ], [ 34.891621794135965, -17.314717765695455 ], [ 34.891725778481565, -17.314610414177974 ], [ 34.891835469297064, -17.314508437156523 ], [ 34.891950565926237, -17.314412114133994 ], [ 34.892070752897929, -17.314321709115958 ], [ 34.892115685045027, -17.314291416092694 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.5", "sub_field": "4.5B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.893769553711472, -17.316586264213559 ], [ 34.895433867295488, -17.318895605322869 ], [ 34.895419923321938, -17.318905006439724 ], [ 34.895290555381528, -17.318982851157038 ], [ 34.895157123118501, -17.319054085779275 ], [ 34.895019992269809, -17.319118515048697 ], [ 34.894879538711734, -17.319175962360919 ], [ 34.894736147429491, -17.319226270249096 ], [ 34.894590211461896, -17.319269300815598 ], [ 34.894442130823954, -17.319304936110047 ], [ 34.894292311410268, -17.319333078452726 ], [ 34.894141163882288, -17.319353650702329 ], [ 34.893989102542648, -17.319366596467447 ], [ 34.89383654419926, -17.31937188026124 ], [ 34.893683907022783, -17.319369487598706 ], [ 34.893531609400171, -17.319359425036396 ], [ 34.893380068787728, -17.319341720154483 ], [ 34.893229700566629, -17.319316421481151 ], [ 34.893080916904303, -17.31928359835964 ], [ 34.892934125624336, -17.319243340758049 ], [ 34.892789729088619, -17.319195759022865 ], [ 34.892648123094212, -17.319140983576336 ], [ 34.892509695788348, -17.319079164559028 ], [ 34.892374826604382, -17.319010471418256 ], [ 34.892243885221639, -17.318935092443517 ], [ 34.892117230552039, -17.318853234250383 ], [ 34.891995209756132, -17.318765121214074 ], [ 34.891878157291615, -17.318670994854429 ], [ 34.891766393996384, -17.318571113173732 ], [ 34.891660226209176, -17.31846574994956 ], [ 34.89155994492981, -17.318355193984225 ], [ 34.891465825021619, -17.318239748313058 ], [ 34.891378124458043, -17.318119729373805 ], [ 34.891356570129695, -17.318086679241528 ], [ 34.893769553711472, -17.316586264213559 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.6", "sub_field": "4.6B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.899440097999843, -17.313485312578923 ], [ 34.897682148973075, -17.31561157677756 ], [ 34.89767166812657, -17.315604008707908 ], [ 34.897554615295256, -17.315509885726271 ], [ 34.897442851360346, -17.315410007298045 ], [ 34.897336682662356, -17.315304647191873 ], [ 34.897236400203575, -17.315194094201519 ], [ 34.897142278850595, -17.31507865135416 ], [ 34.897054576580814, -17.314958635079801 ], [ 34.896973533775437, -17.314834374343764 ], [ 34.89689937256059, -17.314706209744962 ], [ 34.896832296198639, -17.314574492582278 ], [ 34.89677248853107, -17.314439583891517 ], [ 34.896720113474764, -17.314301853455778 ], [ 34.89667531457286, -17.314161678791795 ], [ 34.896638214601346, -17.314019444115193 ], [ 34.896608915232846, -17.313875539287164 ], [ 34.896587496758009, -17.313730358745971 ], [ 34.89657401786566, -17.313584300425656 ], [ 34.896568515482123, -17.313437764665327 ], [ 34.896571004670271, -17.313291153111834 ], [ 34.896581478588317, -17.313144867618806 ], [ 34.896599908508904, -17.312999309145241 ], [ 34.896626243897977, -17.312854876656449 ], [ 34.896660412553551, -17.312711966030534 ], [ 34.896702320803762, -17.312570968973372 ], [ 34.896751853763831, -17.312432271944935 ], [ 34.896808875651196, -17.312296255100076 ], [ 34.896873230157759, -17.312163291246673 ], [ 34.896944740878574, -17.312033744823729 ], [ 34.897023211795457, -17.31190797090262 ], [ 34.897108427814409, -17.311786314213879 ], [ 34.897200155355293, -17.311669108202434 ], [ 34.8972189524941, -17.311647539775638 ], [ 34.899440097999843, -17.313485312578923 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.6", "sub_field": "4.6D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.899440097999843, -17.313485312578923 ], [ 34.90130835758503, -17.31122562620051 ], [ 34.901413639955251, -17.311310283537498 ], [ 34.901525403085479, -17.31141015982055 ], [ 34.901631571462033, -17.311515517729532 ], [ 34.901731854086115, -17.311626068494771 ], [ 34.901825976089349, -17.311741509113453 ], [ 34.901913679487194, -17.311861523179989 ], [ 34.901994723886119, -17.311985781753258 ], [ 34.902068887142498, -17.312113944258016 ], [ 34.90213596597161, -17.312245659418359 ], [ 34.902195776504954, -17.312380566220416 ], [ 34.902248154794236, -17.312518294901775 ], [ 34.902292957260912, -17.312658467964877 ], [ 34.902330061089906, -17.312800701211714 ], [ 34.902359364566351, -17.312944604796709 ], [ 34.902380787354609, -17.313089784295226 ], [ 34.902394270718574, -17.313235841784657 ], [ 34.902399777682902, -17.313382376934985 ], [ 34.902397293134591, -17.313528988106064 ], [ 34.902386823864568, -17.313675273448435 ], [ 34.902368398549257, -17.313820832004755 ], [ 34.902342067672237, -17.313965264808807 ], [ 34.902307903386109, -17.314108175979023 ], [ 34.902265999314849, -17.314249173803603 ], [ 34.902216470297404, -17.314387871814134 ], [ 34.902159452073207, -17.314523889844988 ], [ 34.902095100910188, -17.314656855075331 ], [ 34.902023593176651, -17.314786403051055 ], [ 34.901945124858074, -17.31491217868377 ], [ 34.901859911019962, -17.315033837224195 ], [ 34.901768185218579, -17.315151045207116 ], [ 34.90167019886082, -17.315263481365488 ], [ 34.901634167766389, -17.315300682952017 ], [ 34.899440097999843, -17.313485312578923 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "4.6", "sub_field": "4.6A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.897682148973075, -17.31561157677756 ], [ 34.899440097999843, -17.313485312578923 ], [ 34.901634167766389, -17.315300682952017 ], [ 34.901566220515207, -17.315370837511132 ], [ 34.901456535175853, -17.31547281937948 ], [ 34.901341443481343, -17.315569147436317 ], [ 34.901221260890679, -17.315659557643983 ], [ 34.901096316818645, -17.31574380218521 ], [ 34.900966953732905, -17.315821650142521 ], [ 34.900833526215209, -17.315892888131156 ], [ 34.900696399989521, -17.315957320884166 ], [ 34.900555950919419, -17.316014771787611 ], [ 34.900412563977817, -17.316065083364801 ], [ 34.900266632191638, -17.316108117707994 ], [ 34.900118555564397, -17.316143756856455 ], [ 34.899968739979705, -17.316171903119908 ], [ 34.899817596088525, -17.316192479346252 ], [ 34.899665538183491, -17.316205429133237 ], [ 34.899512983063111, -17.316210716982958 ], [ 34.899360348889211, -17.316208328399284 ], [ 34.8992080540405, -17.316198269927593 ], [ 34.899056515965661, -17.316180569136844 ], [ 34.898906150038954, -17.316155274543995 ], [ 34.898757368421464, -17.316122455481082 ], [ 34.898610578931248, -17.316082201905107 ], [ 34.898466183925223, -17.316034624151484 ], [ 34.898324579196277, -17.31597985263155 ], [ 34.898186152888186, -17.315918037475122 ], [ 34.898051284431546, -17.315849348118899 ], [ 34.897920343503721, -17.315773972842017 ], [ 34.897793689015366, -17.315692118249903 ], [ 34.897682148973075, -17.31561157677756 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "5.1", "sub_field": "5.1A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.889644280205616, -17.310113123629545 ], [ 34.893567231271753, -17.309696371939275 ], [ 34.894135431768206, -17.313461353424515 ], [ 34.894022909687017, -17.31347666856341 ], [ 34.893816897561351, -17.313494207208084 ], [ 34.893610212128692, -17.313501365053114 ], [ 34.893403419933982, -17.313498122474773 ], [ 34.893197087815246, -17.313484488357865 ], [ 34.892991781349544, -17.313460500071429 ], [ 34.892788063302312, -17.313426223366324 ], [ 34.892586492084611, -17.313381752195014 ], [ 34.892387620222046, -17.313327208454009 ], [ 34.892191992840104, -17.313262741649709 ], [ 34.892000146169543, -17.313188528488556 ], [ 34.891812606076378, -17.313104772392641 ], [ 34.891629886620137, -17.313011702941967 ], [ 34.891452488644688, -17.312909575245161 ], [ 34.891280898405093, -17.31279866924006 ], [ 34.89111558623474, -17.312679288926272 ], [ 34.890957005255849, -17.312551761531832 ], [ 34.890805590137525, -17.312416436616065 ], [ 34.890661755904112, -17.312273685111339 ], [ 34.890525896797627, -17.312123898306147 ], [ 34.890398385197194, -17.311967486772534 ], [ 34.890279570598331, -17.311804879240409 ], [ 34.890169778655036, -17.311636521422365 ], [ 34.89006931028738, -17.311462874791772 ], [ 34.889978440856758, -17.311284415317768 ], [ 34.889897419411362, -17.311101632160437 ], [ 34.889826468003683, -17.310915026329962 ], [ 34.889765781082247, -17.310725109313132 ], [ 34.889715524958767, -17.310532401671345 ], [ 34.889675837352712, -17.310337431613661 ], [ 34.889646827014069, -17.310140733548774 ], [ 34.889644280205616, -17.310113123629545 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "5.1", "sub_field": "5.1C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.897496240357995, -17.309278976679899 ], [ 34.893567231271753, -17.309696371939275 ], [ 34.893002323669023, -17.305953209660426 ], [ 34.893119668539669, -17.305937238579631 ], [ 34.893325672458758, -17.305919701457171 ], [ 34.893532349484737, -17.30591254472894 ], [ 34.893739133164758, -17.305915788006445 ], [ 34.893945456754139, -17.305929422397227 ], [ 34.894150754769228, -17.30595341052922 ], [ 34.894354464537166, -17.30598768665326 ], [ 34.894556027737657, -17.306032156823267 ], [ 34.894754891932848, -17.306086699153688 ], [ 34.89495051208133, -17.30615116415359 ], [ 34.895142352031577, -17.306225375136297 ], [ 34.895329885991188, -17.306309128703578 ], [ 34.895512599967851, -17.306402195303132 ], [ 34.895689993177726, -17.306504319857581 ], [ 34.895861579417975, -17.306615222463513 ], [ 34.896026888399007, -17.306734599158542 ], [ 34.896185467033547, -17.306862122754314 ], [ 34.896336880678206, -17.306997443733113 ], [ 34.896480714324767, -17.307140191205693 ], [ 34.896616573737617, -17.30728997392773 ], [ 34.896744086534312, -17.307446381371953 ], [ 34.89686290320622, -17.307608984853228 ], [ 34.89697269807661, -17.30777733870335 ], [ 34.897073170193366, -17.307950981492414 ], [ 34.897164044153996, -17.308129437293385 ], [ 34.897245070860698, -17.308312216986412 ], [ 34.897316028203313, -17.308498819599276 ], [ 34.897376721668323, -17.308688733680437 ], [ 34.897426984872283, -17.308881438700638 ], [ 34.897466680018177, -17.309076406479587 ], [ 34.897495698273403, -17.309273102633544 ], [ 34.897496240357995, -17.309278976679899 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "5.1", "sub_field": "5.1B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.893567231271753, -17.309696371939275 ], [ 34.889644280205616, -17.310113123629545 ], [ 34.889628573425647, -17.309942846620235 ], [ 34.889621126585517, -17.309744313228641 ], [ 34.889624506870462, -17.309545677544772 ], [ 34.889638704980335, -17.309347484018105 ], [ 34.889663681964102, -17.309150275884431 ], [ 34.88969936932687, -17.308954593676912 ], [ 34.889745669218065, -17.308760973744466 ], [ 34.889802454699968, -17.308569946781756 ], [ 34.889869570096032, -17.308382036374624 ], [ 34.889946831417923, -17.308197757564983 ], [ 34.890034026870126, -17.308017615439326 ], [ 34.890130917430803, -17.307842103744292 ], [ 34.890237237507151, -17.307671703533526 ], [ 34.890352695663665, -17.307506881849253 ], [ 34.890476975421151, -17.307348090442296 ], [ 34.890609736124361, -17.307195764533997 ], [ 34.890750613875788, -17.3070503216235 ], [ 34.890899222533278, -17.306912160343522 ], [ 34.891055154768431, -17.306781659367985 ], [ 34.891217983183068, -17.306659176374215 ], [ 34.891387261480766, -17.306545047062802 ], [ 34.891562525690006, -17.306439584237634 ], [ 34.891743295435781, -17.306343076948679 ], [ 34.891929075256186, -17.306255789699943 ], [ 34.892119355960247, -17.306177961724611 ], [ 34.892313616023394, -17.306109806329552 ], [ 34.892511323016663, -17.306051510310741 ], [ 34.892711935065812, -17.30600323344148 ], [ 34.892914902336223, -17.305965108034528 ], [ 34.893002323669023, -17.305953209660426 ], [ 34.893567231271753, -17.309696371939275 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "5.3", "sub_field": "5.3C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.88655044353051, -17.30082027669599 ], [ 34.887065228066767, -17.304559537671949 ], [ 34.883138407553353, -17.304967747142655 ], [ 34.883124499512981, -17.304816903162319 ], [ 34.883117060422094, -17.30461836873225 ], [ 34.883120448196905, -17.304419732390357 ], [ 34.883134653516784, -17.304221538587896 ], [ 34.883159637410927, -17.304024330561418 ], [ 34.883195331365513, -17.303828648843769 ], [ 34.883241637511922, -17.303635029782509 ], [ 34.883298428895301, -17.303444004069913 ], [ 34.883365549822955, -17.303256095288376 ], [ 34.883442816291399, -17.30307181847537 ], [ 34.883530016491029, -17.302891678711884 ], [ 34.883626911387026, -17.302716169738098 ], [ 34.883733235374699, -17.30254577260019 ], [ 34.883848697007878, -17.302380954331959 ], [ 34.883972979797818, -17.302222166674863 ], [ 34.884105743080994, -17.302069844839973 ], [ 34.884246622952844, -17.301924406315258 ], [ 34.88439523326543, -17.301786249721427 ], [ 34.88455116668581, -17.301655753719565 ], [ 34.884713995812575, -17.301533275973377 ], [ 34.884883274347381, -17.301419152169071 ], [ 34.885058538318042, -17.301313695095438 ], [ 34.885239307350204, -17.301217193786709 ], [ 34.885425085983954, -17.301129912730506 ], [ 34.885615365031562, -17.301052091143053 ], [ 34.885809622973014, -17.30098394231371 ], [ 34.886007327385144, -17.300925653020485 ], [ 34.88620793640078, -17.30087738301825 ], [ 34.886410900193646, -17.30083926460097 ], [ 34.88655044353051, -17.30082027669599 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "5.3", "sub_field": "5.3A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.887585923201613, -17.308341731698718 ], [ 34.887065228066767, -17.304559537671949 ], [ 34.890991820206963, -17.304151351941599 ], [ 34.891009712544758, -17.30434532014915 ], [ 34.891017160039745, -17.304543853465603 ], [ 34.891013780776646, -17.304742489111405 ], [ 34.890999583982811, -17.304940682642922 ], [ 34.890974608535664, -17.305137890826575 ], [ 34.890938922856463, -17.305333573127797 ], [ 34.890892624723172, -17.305527193192621 ], [ 34.89083584100284, -17.305718220317804 ], [ 34.890768727304206, -17.305906130905438 ], [ 34.890691467551548, -17.306090409898285 ], [ 34.890604273480889, -17.306270552191478 ], [ 34.890507384059916, -17.306446064017113 ], [ 34.890401064833291, -17.306616464297758 ], [ 34.890285607195082, -17.306781285965204 ], [ 34.890161327590214, -17.306940077240721 ], [ 34.890028566647352, -17.307092402873621 ], [ 34.889887688245388, -17.307237845334313 ], [ 34.889739078516179, -17.307376005958961 ], [ 34.889583144786272, -17.307506506042298 ], [ 34.889420314460438, -17.307628987875948 ], [ 34.889251033850186, -17.307743115728897 ], [ 34.889075766950455, -17.307848576767977 ], [ 34.888894994167643, -17.307945081915541 ], [ 34.888709211002805, -17.3080323666419 ], [ 34.888518926693266, -17.308110191690613 ], [ 34.888324662816736, -17.308178343734379 ], [ 34.888126951861395, -17.308236635960007 ], [ 34.887926335766082, -17.308284908580482 ], [ 34.887723364434663, -17.308323029273154 ], [ 34.887585923201613, -17.308341731698718 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "5.3", "sub_field": "5.3D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.887065228066767, -17.304559537671949 ], [ 34.88655044353051, -17.30082027669599 ], [ 34.886615662485013, -17.300811402239216 ], [ 34.886821662068201, -17.300793872293969 ], [ 34.88702833434634, -17.300786722807359 ], [ 34.887235112879623, -17.30078997337106 ], [ 34.887441430937386, -17.300803615072628 ], [ 34.887646723051198, -17.300827610519995 ], [ 34.88785042656432, -17.30086189394396 ], [ 34.888051983173575, -17.300906371378414 ], [ 34.888250840459207, -17.300960920917966 ], [ 34.888446453398686, -17.301025393051869 ], [ 34.888638285860232, -17.301099611073905 ], [ 34.888825812071993, -17.301183371566594 ], [ 34.889008518062795, -17.301276444958631 ], [ 34.889185903070754, -17.301378576154015 ], [ 34.889357480915407, -17.301489485231148 ], [ 34.889522781330243, -17.301608868209907 ], [ 34.889681351251404, -17.301736397884714 ], [ 34.889832756059356, -17.301871724721188 ], [ 34.88997658077011, -17.302014477814012 ], [ 34.89011243117254, -17.302164265903457 ], [ 34.890239934908863, -17.302320678447551 ], [ 34.890358742495273, -17.302483286747144 ], [ 34.890468528279946, -17.302651645120829 ], [ 34.890568991335634, -17.3028252921263 ], [ 34.890659856284707, -17.303003751824949 ], [ 34.890740874054046, -17.303186535086216 ], [ 34.890811822557978, -17.303373140928109 ], [ 34.890872507307208, -17.303563057890219 ], [ 34.890922761942207, -17.303755765435376 ], [ 34.890962448689429, -17.303950735376358 ], [ 34.890991458739279, -17.304147433323465 ], [ 34.890991820206963, -17.304151351941599 ], [ 34.887065228066767, -17.304559537671949 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "5.2", "sub_field": "5.2A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.8969126991338, -17.302747852699646 ], [ 34.900876176680299, -17.302893796550027 ], [ 34.900862215681485, -17.303088843323266 ], [ 34.900837250927268, -17.303286051715396 ], [ 34.900801576024612, -17.303481734749649 ], [ 34.900755288721975, -17.303675356070045 ], [ 34.900698515856696, -17.303866384969915 ], [ 34.900631413007766, -17.30405429784653 ], [ 34.900554164069703, -17.304238579636362 ], [ 34.900466980748888, -17.30441872522692 ], [ 34.900370101983533, -17.304594240841258 ], [ 34.900263793289071, -17.304764645391586 ], [ 34.900148346030669, -17.30492947179798 ], [ 34.900024076624817, -17.305088268268765 ], [ 34.899891325672165, -17.305240599538983 ], [ 34.899750457024226, -17.305386048063617 ], [ 34.899601856786099, -17.305524215162226 ], [ 34.899445932258295, -17.305654722111793 ], [ 34.899283110820335, -17.305777211185095 ], [ 34.899113838759355, -17.305891346631288 ], [ 34.898938580046824, -17.305996815596405 ], [ 34.898757815066695, -17.306093328981046 ], [ 34.898572039298578, -17.30618062223294 ], [ 34.898381761959563, -17.306258456072264 ], [ 34.898187504608195, -17.30632661714764 ], [ 34.89798979971475, -17.306384918621077 ], [ 34.89778918920144, -17.306433200680189 ], [ 34.897586222956839, -17.306471330976475 ], [ 34.897381457328272, -17.306499204988068 ], [ 34.89717545359661, -17.306516746306354 ], [ 34.896968776437447, -17.306523906845566 ], [ 34.896848733623173, -17.306522026027363 ], [ 34.8969126991338, -17.302747852699646 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "5.2", "sub_field": "5.2C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.8969126991338, -17.302747852699646 ], [ 34.892982579947684, -17.30260313717654 ], [ 34.892983172682968, -17.302568275889897 ], [ 34.892997367422488, -17.302370082399289 ], [ 34.893022340623894, -17.302172874159623 ], [ 34.893058023802681, -17.301977191704324 ], [ 34.89310431911975, -17.301783571383027 ], [ 34.893161099649916, -17.301592543891452 ], [ 34.893228209730253, -17.301404632816908 ], [ 34.89330546538698, -17.301220353203167 ], [ 34.893392654840142, -17.301040210138897 ], [ 34.8934895390843, -17.300864697373338 ], [ 34.893595852544017, -17.300694295963083 ], [ 34.893711303801894, -17.300529472953652 ], [ 34.893835576397656, -17.300370680099508 ], [ 34.893968329695682, -17.300218352625976 ], [ 34.894109199818764, -17.30007290803648 ], [ 34.894257800645626, -17.299934744968354 ], [ 34.894413724869359, -17.299804242100432 ], [ 34.894576545113729, -17.299681757115238 ], [ 34.894745815104677, -17.299567625718822 ], [ 34.894921070893432, -17.299462160720779 ], [ 34.89510183212807, -17.299365651177041 ], [ 34.895287603369994, -17.299278361597814 ], [ 34.895477875451739, -17.29920053122266 ], [ 34.895672126872348, -17.299132373365019 ], [ 34.895869825226519, -17.299074074827637 ], [ 34.896070428663677, -17.299025795390687 ], [ 34.896273387372787, -17.298987667373979 ], [ 34.896478145089084, -17.298959795274413 ], [ 34.89668414061839, -17.29894225547962 ], [ 34.896890809374973, -17.298935096058699 ], [ 34.8969772955911, -17.298936451464556 ], [ 34.8969126991338, -17.302747852699646 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "5.2", "sub_field": "5.2D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.900876176680299, -17.302893796550027 ], [ 34.8969126991338, -17.302747852699646 ], [ 34.8969772955911, -17.298936451464556 ], [ 34.897097584928609, -17.298938336630535 ], [ 34.897303900556857, -17.298951968310067 ], [ 34.897509190797912, -17.298975953732672 ], [ 34.89771289300019, -17.299010227156604 ], [ 34.897914448864078, -17.299054694643154 ], [ 34.898113305971897, -17.299109234314148 ], [ 34.898308919301599, -17.299173696685894 ], [ 34.898500752720359, -17.299247905078936 ], [ 34.898688280453683, -17.299331656102137 ], [ 34.898870988526284, -17.299424720210141 ], [ 34.899048376170541, -17.299526842332384 ], [ 34.899219957198824, -17.299637742572131 ], [ 34.899385261335887, -17.299757116973481 ], [ 34.899543835507686, -17.299884638354321 ], [ 34.899695245083088, -17.300019957203023 ], [ 34.899839075065046, -17.300162702636175 ], [ 34.899974931227973, -17.300312483415023 ], [ 34.900102441198349, -17.30046888901763 ], [ 34.900221255475309, -17.300631490763884 ], [ 34.900331048388715, -17.300799842990351 ], [ 34.900431518991795, -17.300973484271562 ], [ 34.900522391886227, -17.301151938684619 ], [ 34.900603417977173, -17.301334717113466 ], [ 34.90067437515615, -17.301521318589351 ], [ 34.900735068910123, -17.301711231663859 ], [ 34.900785332854859, -17.301903935810486 ], [ 34.900825029191338, -17.302098902851345 ], [ 34.900854049083712, -17.302295598404701 ], [ 34.900872312957986, -17.302493483349586 ], [ 34.900879770720429, -17.302692015303364 ], [ 34.900876401895346, -17.302890650108349 ], [ 34.900876176680299, -17.302893796550027 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "5.4", "sub_field": "5.4B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.894017157017068, -17.294410311809727 ], [ 34.89693731592709, -17.294573580994058 ], [ 34.896864188941137, -17.297331505303269 ], [ 34.896805931020118, -17.297330592199042 ], [ 34.896653651768069, -17.297320530228568 ], [ 34.896502129402165, -17.297302825932473 ], [ 34.896351779253607, -17.297277527837316 ], [ 34.896203013440193, -17.297244705284744 ], [ 34.896056239736616, -17.297204448241324 ], [ 34.89591186045655, -17.297156867051939 ], [ 34.895770271349726, -17.297102092137393 ], [ 34.895631860517078, -17.29704027363676 ], [ 34.895497007346798, -17.296971580995908 ], [ 34.895366081474315, -17.296896202502978 ], [ 34.895239441768993, -17.296814344772198 ], [ 34.8951174353504, -17.296726232177512 ], [ 34.895000396636796, -17.296632106237524 ], [ 34.894888646428399, -17.296532224953381 ], [ 34.894782491028053, -17.296426862101541 ], [ 34.894682221401673, -17.296316306483291 ], [ 34.894588112380617, -17.296200861132984 ], [ 34.894500421908518, -17.296080842487484 ], [ 34.894419390334207, -17.295956579518617 ], [ 34.894345239753093, -17.295828412831394 ], [ 34.894278173398355, -17.295696693730406 ], [ 34.894218375084108, -17.295561783256755 ], [ 34.894166008701575, -17.295424051198424 ], [ 34.89412121777012, -17.295283875076617 ], [ 34.89408412504396, -17.295141639110881 ], [ 34.894054832175833, -17.294997733166007 ], [ 34.894033419438607, -17.294852551683256 ], [ 34.894019945505391, -17.294706492599275 ], [ 34.894014447288981, -17.294559956255252 ], [ 34.894016939840789, -17.294413344299628 ], [ 34.894017157017068, -17.294410311809727 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "5.4", "sub_field": "5.4A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.89693731592709, -17.294573580994058 ], [ 34.89983659573803, -17.294735682805793 ], [ 34.899832163531997, -17.294797598243601 ], [ 34.899822899654588, -17.294870775272294 ], [ 34.899813736479395, -17.294943156836158 ], [ 34.899787404689512, -17.295087589492788 ], [ 34.899753240317445, -17.295230500332377 ], [ 34.899711336987437, -17.29537149764397 ], [ 34.89966180953644, -17.29551019496061 ], [ 34.899604793699602, -17.295646212118569 ], [ 34.89954044573831, -17.295779176299387 ], [ 34.89946894201217, -17.295908723051863 ], [ 34.899390478495619, -17.296034497290957 ], [ 34.899305270241072, -17.296156154271213 ], [ 34.899213550789469, -17.29627336053171 ], [ 34.899115571530331, -17.296385794810135 ], [ 34.899011601012702, -17.296493148923457 ], [ 34.898901924209291, -17.296595128612708 ], [ 34.898786841735244, -17.296691454349638 ], [ 34.898666669024301, -17.296781862102957 ], [ 34.898541735464178, -17.296866104062158 ], [ 34.898412383493714, -17.296943949316809 ], [ 34.898278967664204, -17.297015184489627 ], [ 34.898141853667532, -17.297079614321344 ], [ 34.898001417333766, -17.297137062206048 ], [ 34.89785804360087, -17.297187370675346 ], [ 34.897712125459591, -17.297230401829999 ], [ 34.897564062875993, -17.297266037718053 ], [ 34.897414261695189, -17.297294180658142 ], [ 34.89726313252865, -17.297314753507322 ], [ 34.897111089628602, -17.29732769987255 ], [ 34.896958549752426, -17.297332984265342 ], [ 34.896864188941137, -17.297331505303269 ], [ 34.89693731592709, -17.294573580994058 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "5.4", "sub_field": "5.4C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.89693731592709, -17.294573580994058 ], [ 34.894017157017068, -17.294410311809727 ], [ 34.894027416309868, -17.294267058587142 ], [ 34.894045847961912, -17.294121500077392 ], [ 34.894072184258114, -17.293977067735781 ], [ 34.894089624499351, -17.293904124152515 ], [ 34.894106352994037, -17.293834157440013 ], [ 34.894148260497595, -17.293693160895057 ], [ 34.894197791886107, -17.293554464559477 ], [ 34.89425481138133, -17.293418448586269 ], [ 34.894319162681754, -17.293285485780885 ], [ 34.894390669391221, -17.293155940579478 ], [ 34.894469135502568, -17.293030168050077 ], [ 34.894554345934992, -17.292908512919421 ], [ 34.894646067123652, -17.292791308628154 ], [ 34.894744047660041, -17.292678876417035 ], [ 34.894848018981065, -17.292571524446419 ], [ 34.894957696105273, -17.292469546951811 ], [ 34.895072778413969, -17.292373223437412 ], [ 34.89519295047522, -17.292282817910166 ], [ 34.895317882908451, -17.292198578156203 ], [ 34.89544723328715, -17.292120735061754 ], [ 34.89558064707748, -17.292049501980475 ], [ 34.895717758609869, -17.291985074148698 ], [ 34.89585819208127, -17.291927628150439 ], [ 34.896001562585077, -17.291877321433407 ], [ 34.89614747716594, -17.291834291877638 ], [ 34.896295535896762, -17.291798657417591 ], [ 34.896445332974679, -17.29177051571898 ], [ 34.896596457833176, -17.291749943911135 ], [ 34.896748496267172, -17.291736998375693 ], [ 34.896901031568262, -17.291731714592061 ], [ 34.897012622272264, -17.291733463938773 ], [ 34.896942313597627, -17.29438509794787 ], [ 34.89693731592709, -17.294573580994058 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "5.4", "sub_field": "5.4D" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.89983659573803, -17.294735682805793 ], [ 34.89693731592709, -17.294573580994058 ], [ 34.896942313597627, -17.29438509794787 ], [ 34.897012622272264, -17.291733463938773 ], [ 34.897053645666574, -17.291734107040188 ], [ 34.897205920276505, -17.291744169161014 ], [ 34.897357438042967, -17.29176187337432 ], [ 34.897507783685214, -17.291787171154436 ], [ 34.89765654513478, -17.291819993163191 ], [ 34.897803314664799, -17.291860249439978 ], [ 34.897947690007349, -17.291907829648292 ], [ 34.898089275455796, -17.291962603378103 ], [ 34.89822768294929, -17.292024420503296 ], [ 34.898362533136257, -17.292093111593083 ], [ 34.898493456413945, -17.292168488376308 ], [ 34.898620093941389, -17.2922503442575 ], [ 34.898742098622918, -17.292338454882941 ], [ 34.898859136059286, -17.292432578755633 ], [ 34.898970885464273, -17.292532457897078 ], [ 34.899077040543823, -17.292637818554272 ], [ 34.899177310335574, -17.29274837194999 ], [ 34.899271420006308, -17.29286381507422 ], [ 34.8993591116053, -17.292983831514487 ], [ 34.899440144771376, -17.293108092323138 ], [ 34.899514297391676, -17.293236256918828 ], [ 34.899581366210683, -17.293367974019876 ], [ 34.899641167387308, -17.29350288260709 ], [ 34.899693536998917, -17.293640612913151 ], [ 34.899738331490795, -17.293780787436059 ], [ 34.899775428069709, -17.293923021973789 ], [ 34.899804725040731, -17.294066926677193 ], [ 34.899826142086027, -17.294212107118611 ], [ 34.899839620485281, -17.294358165372817 ], [ 34.899845123276762, -17.29450470110768 ], [ 34.899842635358958, -17.294651312681466 ], [ 34.89983659573803, -17.294735682805793 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "DL1.3", "sub_field": "DL1.3" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.947451385514611, -17.321753599029073 ], [ 34.947298927757849, -17.321663500672134 ], [ 34.947001852567226, -17.32100637178991 ], [ 34.947092750341952, -17.318710743453458 ], [ 34.94828268484742, -17.317606307231124 ], [ 34.948861125232021, -17.317077753261884 ], [ 34.949299087237506, -17.316123196719229 ], [ 34.950715487823011, -17.316226521400374 ], [ 34.951786380891292, -17.316304641732966 ], [ 34.953480384874766, -17.317416974640771 ], [ 34.953265535589061, -17.317724640001025 ], [ 34.953133320644007, -17.318048082516636 ], [ 34.953034159435212, -17.318442523838343 ], [ 34.952968051962692, -17.318884297113428 ], [ 34.952968051962692, -17.319373402000465 ], [ 34.953092003473678, -17.319909838121443 ], [ 34.952794519847309, -17.321448138900415 ], [ 34.952108654819853, -17.321661133377713 ], [ 34.950066988602522, -17.321701668718116 ], [ 34.947451385514611, -17.321753599029073 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.18", "sub_field": "1.18C" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.961301964672387, -17.313838203379714 ], [ 34.963291039141012, -17.311973548628245 ], [ 34.963318548862773, -17.312000830743248 ], [ 34.963414658522524, -17.312106712348005 ], [ 34.963504867652226, -17.312217280413027 ], [ 34.963588928992536, -17.312332231887332 ], [ 34.963666612132982, -17.31245125170528 ], [ 34.963737704143533, -17.312574013649993 ], [ 34.963802010158297, -17.312700181247447 ], [ 34.963859353909704, -17.312829408688614 ], [ 34.963909578211776, -17.312961341777211 ], [ 34.963952545391066, -17.313095618900494 ], [ 34.963988137664089, -17.313231872020292 ], [ 34.964016257460422, -17.3133697276817 ], [ 34.964036827690187, -17.313508808036687 ], [ 34.964049791955574, -17.313648731879656 ], [ 34.964055114705573, -17.313789115692281 ], [ 34.964052781333649, -17.313929574694654 ], [ 34.964042798217903, -17.314069723899909 ], [ 34.964025192703858, -17.314209179169463 ], [ 34.964000013029583, -17.314347558265844 ], [ 34.963967328193803, -17.314484481900458 ], [ 34.963927227766852, -17.314619574773129 ], [ 34.963879821645449, -17.314752466600854 ], [ 34.96382523975155, -17.314882793132711 ], [ 34.963763631676478, -17.315010197148315 ], [ 34.963695166271002, -17.315134329436955 ], [ 34.963620031182742, -17.315254849754851 ], [ 34.963538432341863, -17.315371427757743 ], [ 34.963450593396857, -17.315483743906491 ], [ 34.963356755101515, -17.3155914903429 ], [ 34.96325717465519, -17.315694371733709 ], [ 34.963152124997862, -17.315792106080078 ], [ 34.963115666188799, -17.315822640664031 ], [ 34.961301964672387, -17.313838203379714 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.18", "sub_field": "1.18A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.961301964672387, -17.313838203379714 ], [ 34.959294104555354, -17.315720468707916 ], [ 34.959205111423216, -17.315632210383061 ], [ 34.959109001898497, -17.315526326733945 ], [ 34.959018793349316, -17.315415756619789 ], [ 34.95893473302857, -17.315300803113992 ], [ 34.958857051335606, -17.315181781304474 ], [ 34.958785961184603, -17.315059017429945 ], [ 34.958721657421044, -17.314932847985528 ], [ 34.958664316287852, -17.314803618800539 ], [ 34.958614094942313, -17.314671684090325 ], [ 34.958571131025515, -17.314537405485428 ], [ 34.958535542285148, -17.314401151040233 ], [ 34.958507426252964, -17.314263294224176 ], [ 34.958486859977597, -17.31412421289793 ], [ 34.958473899813526, -17.313984288277727 ], [ 34.958468581266821, -17.313843903890437 ], [ 34.958470918897959, -17.313703444522226 ], [ 34.958480906282176, -17.313563295164005 ], [ 34.958498516027234, -17.31342383995602 ], [ 34.95852369984862, -17.313285461135049 ], [ 34.958556388702249, -17.313148537986695 ], [ 34.958596492973705, -17.31301344580578 ], [ 34.958643902724212, -17.312880554867768 ], [ 34.958698487991995, -17.31275022941379 ], [ 34.958760099148805, -17.312622826652447 ], [ 34.958828567310086, -17.312498695780704 ], [ 34.958903704798047, -17.312378177026815 ], [ 34.958985305656192, -17.3122616007179 ], [ 34.959073146213981, -17.312149286374552 ], [ 34.959166985699902, -17.31204154183515 ], [ 34.95926656690159, -17.311938662412228 ], [ 34.959371616870783, -17.311840930083029 ], [ 34.959431043918748, -17.311791160431401 ], [ 34.961301964672387, -17.313838203379714 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "1.18", "sub_field": "1.18B" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.963291039141012, -17.311973548628245 ], [ 34.961301964672387, -17.313838203379714 ], [ 34.959431043918748, -17.311791160431401 ], [ 34.959481847671512, -17.311748612716794 ], [ 34.95959695716941, -17.311661963340608 ], [ 34.959716629859727, -17.311581219445991 ], [ 34.959840537732092, -17.311506602337992 ], [ 34.95996834116962, -17.311438316528729 ], [ 34.960099689879613, -17.311376549176956 ], [ 34.960234223853597, -17.311321469575088 ], [ 34.960371574354085, -17.311273228685337 ], [ 34.960511364925047, -17.311231958725969 ], [ 34.960653212423622, -17.311197772808992 ], [ 34.9607967280702, -17.31117076463017 ], [ 34.960941518513806, -17.311151008212327 ], [ 34.961087186910177, -17.311138557702407 ], [ 34.961233334009165, -17.311133447223234 ], [ 34.961379559249025, -17.31113569077991 ], [ 34.961525461854002, -17.311145282221496 ], [ 34.961670641932749, -17.311162195257861 ], [ 34.96181470157412, -17.311186383531847 ], [ 34.96195724593769, -17.311217780746176 ], [ 34.962097884335776, -17.311256300845265 ], [ 34.962236231304082, -17.311301838251062 ], [ 34.962371907658095, -17.311354268152318 ], [ 34.962504541532205, -17.311413446846771 ], [ 34.962633769398863, -17.311479212134888 ], [ 34.962759237064759, -17.311551383764364 ], [ 34.962880600641611, -17.31162976392423 ], [ 34.962997527488582, -17.311714137786886 ], [ 34.96310969712394, -17.311804274096819 ], [ 34.96321680210341, -17.311899925804482 ], [ 34.963291039141012, -17.311973548628245 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "DL1.1", "sub_field": "DL1.1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.973316494728564, -17.333251794475164 ], [ 34.973346256561562, -17.333419673334269 ], [ 34.973237698645903, -17.333332450175927 ], [ 34.973316494728564, -17.333251794475164 ] ] ] } },
+{ "type": "Feature", "properties": { "field": "DL1.1", "sub_field": "DL1.1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 34.973346256561562, -17.333419673334269 ], [ 34.973316494728564, -17.333251794475164 ], [ 34.974847423373653, -17.331684735320394 ], [ 34.975486037377962, -17.331031049777948 ], [ 34.976506367806572, -17.330987952117045 ], [ 34.97768922874593, -17.331936098320032 ], [ 34.978068466604348, -17.332608417206025 ], [ 34.977245857001407, -17.333467640564528 ], [ 34.975576332106158, -17.335211474799472 ], [ 34.973346256561562, -17.333419673334269 ] ] ] } }
+]
+}
diff --git a/r_app/experiments/plot_testing.R b/r_app/experiments/plot_testing.R
new file mode 100644
index 0000000..3dc5b97
--- /dev/null
+++ b/r_app/experiments/plot_testing.R
@@ -0,0 +1,117 @@
+library(here)
+library(sf)
+library(tidyverse)
+library(tmap)
+library(lubridate)
+library(exactextractr)
+library(zoo)
+library(raster)
+library(terra)
+library(rsample)
+library(caret)
+library(randomForest)
+library(CAST)
+
+project_dir <- "chemba"
+source(here("r_app", "parameters_project.R"))
+cumulative_CI_vals_dir <- "C:/Users/timon/Resilience BV/4020 SCane ESA DEMO - Documenten/General/4020 SCDEMO Team/4020 TechnicalData/WP3/smartcane/laravel_app/storage/app/chemba/Data/extracted_ci/cumulative_vals"
+CI_quadrant <- readRDS(here(cumulative_CI_vals_dir,"All_pivots_Cumulative_CI_quadrant_year_v2.rds"))# %>%
+
+cum_ci_plot <- function(pivotName, plot_type = "value", facet_on = TRUE, x_axis = "days") {
+
+ # pivotName = "3a13"
+ data_ci <- CI_quadrant %>% filter(field == pivotName)
+
+ if (nrow(data_ci) == 0) {
+ return(cum_ci_plot2(pivotName)) # Return an empty data frame if no data is found
+ }
+
+ data_ci2 <- data_ci %>%
+ mutate(CI_rate = cumulative_CI / DOY,
+ week = week(Date),
+ week_from_doy = DOY / 7) %>%
+ group_by(field) %>%
+ mutate(mean_CIrate_rolling_10_days = rollapplyr(CI_rate, width = 10, FUN = mean, partial = TRUE),
+ mean_rolling_10_days = rollapplyr(value, width = 10, FUN = mean, partial = TRUE))
+
+ data_ci2 <- data_ci2 %>% mutate(season = as.factor(season))
+
+ date_preperation_perfect_pivot <- data_ci2 %>%
+ group_by(season) %>%
+ summarise(min_date = min(Date),
+ max_date = max(Date),
+ days = max_date - min_date)
+
+ unique_seasons <- sort(unique(date_preperation_perfect_pivot$season), decreasing = TRUE)[1:3]
+
+ # Determine the y aesthetic based on the plot type
+ y_aesthetic <- switch(plot_type,
+ "CI_rate" = "mean_CIrate_rolling_10_days",
+ "cumulative_CI" = "cumulative_CI",
+ "value" = "mean_rolling_10_days")
+
+ y_label <- switch(plot_type,
+ "CI_rate" = "10-Day Rolling Mean CI Rate (cumulative CI / age)",
+ "cumulative_CI" = "Cumulative CI",
+ "value" = "10-Day Rolling Mean CI")
+
+ if (facet_on) {
+ g <- ggplot(data = data_ci2 %>% filter(season %in% unique_seasons)) +
+ facet_wrap(~season, scales = "free_x") +
+ geom_line(aes_string(x = "Date", y = y_aesthetic, col = "sub_field", group = "sub_field")) +
+ labs(title = paste("Plot of", y_label, "for Pivot", pivotName),
+ color = "Field Name",
+ y = y_label) +
+ scale_x_date(date_breaks = "1 month", date_labels = "%m-%Y") +
+ theme_minimal() +
+ theme(axis.text.x = element_text(angle = 60, hjust = 1),
+ legend.justification = c(1, 0), legend.position = c(1, 0),
+ legend.title = element_text(size = 8),
+ legend.text = element_text(size = 8)) +
+ guides(color = guide_legend(nrow = 2, byrow = TRUE))
+ } else if (x_axis == "weeks") {
+ g <- ggplot(data = data_ci2 %>% filter(season %in% unique_seasons)) +
+ facet_wrap(~sub_field, nrow=1) +
+ geom_line(aes_string(x = "week_from_doy", y = y_aesthetic, col = "season", group = "season")) +
+ labs(title = paste("Plot of", y_label, "for Pivot", pivotName),
+ color = "Season",
+ y = y_label,
+ x = "Week of Year") +
+ theme_minimal() +
+ theme(axis.text.x = element_text(angle = 60, hjust = 1),
+ legend.justification = c(1, 0), legend.position = c(1, 0),
+ legend.title = element_text(size = 8),
+ legend.text = element_text(size = 8)) +
+ guides(color = guide_legend(nrow = 2, byrow = TRUE))
+ } else {
+ g <- ggplot(data = data_ci2 %>% filter(season %in% unique_seasons)) +
+ facet_wrap(~sub_field, nrow=1) +
+ geom_line(aes_string(x = "DOY", y = y_aesthetic, col = "season", group = "season")) +
+ labs(title = paste("Plot of", y_label, "for Pivot", pivotName),
+ color = "Season",
+ y = y_label,
+ x = "Age of Crop (Days)") +
+ theme_minimal() +
+ theme(axis.text.x = element_text(angle = 60, hjust = 1),
+ legend.justification = c(1, 0), legend.position = c(1, 0),
+ legend.title = element_text(size = 8),
+ legend.text = element_text(size = 8)) +
+ guides(color = guide_legend(nrow = 2, byrow = TRUE))
+ }
+
+ return(g)
+}
+
+# facet_on FALSE
+g1 <- cum_ci_plot("1.16", "value", FALSE, "days")
+g2 <- cum_ci_plot("3a13", "cumulative_CI", FALSE, "days")
+g3 <- cum_ci_plot("3a13", "CI_rate", FALSE, "days")
+g7 <- cum_ci_plot("3a13", "value", FALSE, "weeks")
+g8 <- cum_ci_plot("3a13", "cumulative_CI", FALSE, "weeks")
+g9 <- cum_ci_plot("3a13", "CI_rate", FALSE, "weeks")
+
+# facet_on TRUE
+g4 <- cum_ci_plot("3a13", "value", TRUE, "days")
+g5 <- cum_ci_plot("3a13", "cumulative_CI", TRUE, "days")
+g6 <- cum_ci_plot("3a13", "CI_rate", TRUE, "days")
+
diff --git a/r_app/experiments/plotting_ci_all_field_dates.R b/r_app/experiments/plotting_ci_all_field_dates.R
new file mode 100644
index 0000000..8e7259e
--- /dev/null
+++ b/r_app/experiments/plotting_ci_all_field_dates.R
@@ -0,0 +1,83 @@
+library(ggplot2)
+library(dplyr)
+library(sf)
+library(terra)
+
+# Define the file paths
+raster_dir <- "C:/Users/timon/Resilience BV/4020 SCane ESA DEMO - Documenten/General/4020 SCDEMO Team/4020 TechnicalData/WP3/smartcane/laravel_app/storage/app/chemba/merged_final_tif"
+polygon_path <- "C:/Users/timon/Resilience BV/4020 SCane ESA DEMO - Documenten/General/4020 SCDEMO Team/4020 TechnicalData/WP3/smartcane/r_app/experiments/pivot.geojson"
+
+# Load the polygon
+polygon <- st_read(polygon_path)
+
+# Filter the polygon for the specific field
+polygon_filtered <- subset(polygon, field == "1.1")
+
+# List all raster files in the directory
+raster_files <- list.files(raster_dir, pattern = "\\.tif$", full.names = FALSE)
+raster_file <- raster_files[1]
+
+# Initialize an empty list to store CI values
+ci_values <- list()
+
+# Define a function to process each raster file
+process_raster_file <- function(raster_file) {
+ # Extract the date from the file name (assuming the date is in YYYYMMDD format)
+ date <- as.Date(substr(raster_file, 1, 10), format = "%Y-%m-%d")
+
+ # Load the raster
+ raster_data <- terra::rast(file.path(raster_dir, raster_file))
+
+ # Crop the raster using the filtered polygon
+ cropped_raster <- terra::crop(raster_data, polygon_filtered)
+
+ # Extract the CI band (assuming the CI band is the first band)
+ ci_band <- cropped_raster$CI
+
+ # Extract the CI values from the CI band
+ return(data.frame(Date = date, CI = terra::values(ci_band)) %>%
+ filter(!is.na(CI)) %>%
+ mutate(Polygon = unique(polygon_filtered$field)))
+}
+
+# Use walk to apply the function to each raster file
+ci_values <- purrr::map(raster_files, process_raster_file)
+
+# Combine all CI values into a single data frame
+ci_values_df <- do.call(rbind, ci_values)
+
+head(ci_values_df)
+
+# Plot the first raster
+plot(terra::rast(file.path(raster_dir, raster_files[1]))[[1]])
+
+# Calculate the mean CI value
+ci_stats <- ci_values_df %>%
+ group_by(Date, Polygon) %>%
+ summarize(
+ mean_ci = mean(CI, na.rm = TRUE),
+ q5 = quantile(CI, 0.05, na.rm = TRUE),
+ q25 = quantile(CI, 0.25, na.rm = TRUE),
+ q50 = quantile(CI, 0.50, na.rm = TRUE),
+ q75 = quantile(CI, 0.75, na.rm = TRUE),
+ q95 = quantile(CI, 0.95, na.rm = TRUE),
+ cv = sd(CI, na.rm = TRUE) / mean(CI, na.rm = TRUE) * 100,
+ .groups = "drop"
+ )
+
+# Plot the mean CI value over time
+ggplot(ci_stats, aes(x = Date, y = mean_ci)) +
+ geom_line() +
+ geom_ribbon(aes(ymin = q25, ymax = q75), alpha = 0.2) +
+ labs(title = "Mean CI value over time",
+ x = "Date",
+ y = "Mean CI") +
+ theme_minimal()
+
+# Plot the coefficient of variation over time
+ggplot(ci_stats, aes(x = Date, y = cv)) +
+ geom_line() +
+ labs(title = "Coefficient of variation over time",
+ x = "Date",
+ y = "CV (%)") +
+ theme_minimal()
diff --git a/r_app/experiments/run_tests.R b/r_app/experiments/run_tests.R
new file mode 100644
index 0000000..65a776d
--- /dev/null
+++ b/r_app/experiments/run_tests.R
@@ -0,0 +1,38 @@
+# run_tests.R
+#
+# TEST RUNNER FOR SMARTCANE
+# =======================
+# This script runs all tests for the SmartCane project.
+# Usage: Rscript run_tests.R [test_pattern]
+# - test_pattern: Optional regex pattern to match test files (default: all test_*.R files)
+#
+
+# Process command line arguments
+args <- commandArgs(trailingOnly = TRUE)
+test_pattern <- if (length(args) > 0) args[1] else "^test_.+\\.R$"
+
+# Set working directory to script location
+script_dir <- dirname(normalizePath(sys.frame(1)$ofile))
+setwd(script_dir)
+
+# Source test framework
+source("tests/test_framework.R")
+
+# Set up test environment
+env <- setup_test_env()
+
+# Run tests
+cat("Running tests with pattern:", test_pattern, "\n\n")
+success <- run_tests(test_pattern)
+
+# Clean up
+teardown_test_env()
+
+# Exit with appropriate status code
+if (success) {
+ cat("\n✓ All tests passed successfully!\n")
+ quit(save = "no", status = 0)
+} else {
+ cat("\n✗ Some tests failed.\n")
+ quit(save = "no", status = 1)
+}
\ No newline at end of file
diff --git a/r_app/utils_3.R b/r_app/experiments/utils_3.R
similarity index 100%
rename from r_app/utils_3.R
rename to r_app/experiments/utils_3.R
diff --git a/r_app/growth_model_utils.R b/r_app/growth_model_utils.R
new file mode 100644
index 0000000..e297e36
--- /dev/null
+++ b/r_app/growth_model_utils.R
@@ -0,0 +1,233 @@
+# filepath: c:\Users\timon\Resilience BV\4020 SCane ESA DEMO - Documenten\General\4020 SCDEMO Team\4020 TechnicalData\WP3\smartcane\r_app\growth_model_utils.R
+#
+# GROWTH_MODEL_UTILS.R
+# ===================
+# Utility functions for growth model interpolation and manipulation.
+# These functions support the creation of continuous growth models from point measurements.
+
+#' Safe logging function that works whether log_message exists or not
+#'
+#' @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)
+ }
+ }
+}
+
+#' Load and prepare the combined CI data
+#'
+#' @param data_dir Directory containing the combined CI data
+#' @return Long-format dataframe with CI values by date
+#'
+load_combined_ci_data <- function(data_dir) {
+ file_path <- here::here(data_dir, "combined_CI_data.rds")
+
+ if (!file.exists(file_path)) {
+ stop(paste("Combined CI data file not found:", file_path))
+ }
+
+ safe_log(paste("Loading CI data from:", file_path))
+
+ # Load and transform the data to long format
+ pivot_stats <- readRDS(file_path) %>%
+ dplyr::ungroup() %>%
+ dplyr::group_by(field, sub_field) %>%
+ dplyr::summarise(dplyr::across(everything(), ~ first(stats::na.omit(.))), .groups = "drop")
+
+ pivot_stats_long <- pivot_stats %>%
+ tidyr::gather("Date", value, -field, -sub_field) %>%
+ dplyr::mutate(Date = lubridate::ymd(Date)) %>%
+ tidyr::drop_na(c("value", "Date")) %>%
+ dplyr::mutate(value = as.numeric(value)) %>%
+ dplyr::filter_all(dplyr::all_vars(!is.infinite(.))) %>%
+ dplyr::distinct()
+
+ safe_log(paste("Loaded", nrow(pivot_stats_long), "CI data points"))
+
+ return(pivot_stats_long)
+}
+
+#' Extract and interpolate CI data for a specific field and season
+#'
+#' @param field_name Name of the field or sub-field
+#' @param harvesting_data Dataframe with harvesting information
+#' @param field_CI_data Dataframe with CI measurements
+#' @param season Year of the growing season
+#' @return Dataframe with interpolated daily CI values
+#'
+extract_CI_data <- function(field_name, harvesting_data, field_CI_data, season) {
+ # Filter harvesting data for the given season and field name
+ filtered_harvesting_data <- harvesting_data %>%
+ dplyr::filter(year == season, sub_field == field_name)
+
+ if (nrow(filtered_harvesting_data) == 0) {
+ safe_log(paste("No harvesting data found for field:", field_name, "in season:", season), "WARNING")
+ return(data.frame())
+ }
+
+ # Filter field CI data for the given field name
+ filtered_field_CI_data <- field_CI_data %>%
+ dplyr::filter(sub_field == field_name)
+
+ # Return an empty data frame if no CI data is found
+ if (nrow(filtered_field_CI_data) == 0) {
+ safe_log(paste("No CI data found for field:", field_name, "in season:", season), "WARNING")
+ return(data.frame())
+ }
+
+ # Log season dates
+ season_start <- filtered_harvesting_data$season_start[1]
+ season_end <- filtered_harvesting_data$season_end[1]
+ ci_date_range <- paste(format(min(filtered_field_CI_data$Date), "%Y-%m-%d"),
+ "to",
+ format(max(filtered_field_CI_data$Date), "%Y-%m-%d"))
+
+ # Create a linear interpolation function for the CI data
+ tryCatch({
+ ApproxFun <- stats::approxfun(x = filtered_field_CI_data$Date, y = filtered_field_CI_data$value)
+ Dates <- seq.Date(min(filtered_field_CI_data$Date), max(filtered_field_CI_data$Date), by = 1)
+ LinearFit <- ApproxFun(Dates)
+
+ # Combine interpolated data with the original CI data
+ CI <- data.frame(Date = Dates, FitData = LinearFit) %>%
+ dplyr::left_join(filtered_field_CI_data, by = "Date") %>%
+ dplyr::filter(Date > filtered_harvesting_data$season_start & Date < filtered_harvesting_data$season_end)
+
+ # If CI is empty after filtering, return an empty dataframe
+ if (nrow(CI) == 0) {
+ safe_log(paste0("No CI data within season dates for field: ", field_name,
+ " (Season: ", season, ", dates: ",
+ format(season_start, "%Y-%m-%d"), " to ",
+ format(season_end, "%Y-%m-%d"),
+ "). Available CI data range: ", ci_date_range),
+ "WARNING")
+ return(data.frame())
+ }
+
+ # Add additional columns
+ CI <- CI %>%
+ dplyr::mutate(
+ DOY = seq(1, n(), 1),
+ model = paste0("Data", season, " : ", field_name),
+ season = season,
+ subField = field_name
+ )
+
+ # Log successful interpolation
+ safe_log(paste0("Successfully interpolated CI data for field: ", field_name,
+ " (Season: ", season, ", dates: ",
+ format(season_start, "%Y-%m-%d"), " to ",
+ format(season_end, "%Y-%m-%d"),
+ "). ", nrow(CI), " data points created."))
+
+ return(CI)
+ }, error = function(e) {
+ safe_log(paste0("Error interpolating CI data for field ", field_name,
+ " in season ", season,
+ " (", format(season_start, "%Y-%m-%d"), " to ",
+ format(season_end, "%Y-%m-%d"),
+ "): ", e$message), "ERROR")
+ return(data.frame())
+ })
+}
+
+#' Generate interpolated CI data for all fields and seasons
+#'
+#' @param years Vector of years to process
+#' @param harvesting_data Dataframe with harvesting information
+#' @param ci_data Long-format dataframe with CI measurements
+#' @return Dataframe with interpolated daily CI values for all fields/seasons
+#'
+generate_interpolated_ci_data <- function(years, harvesting_data, ci_data) {
+ safe_log("Starting CI data interpolation for all fields")
+
+ # 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())
+ }
+
+ # 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())
+ }
+
+ # 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)
+ })
+
+ safe_log(paste("Total interpolated data points:", nrow(result)))
+ return(result)
+}
+
+#' Calculate growth metrics for interpolated CI data
+#'
+#' @param interpolated_data Dataframe with interpolated CI values
+#' @return Dataframe with added growth metrics (CI_per_day and cumulative_CI)
+#'
+calculate_growth_metrics <- function(interpolated_data) {
+ if (nrow(interpolated_data) == 0) {
+ safe_log("No data provided to calculate growth metrics", "WARNING")
+ return(interpolated_data)
+ }
+
+ result <- interpolated_data %>%
+ dplyr::group_by(model) %>%
+ dplyr::mutate(
+ CI_per_day = FitData - dplyr::lag(FitData),
+ cumulative_CI = cumsum(FitData)
+ )
+
+ return(result)
+}
+
+#' Save interpolated growth model data
+#'
+#' @param data Dataframe with interpolated growth data
+#' @param output_dir Directory to save the output
+#' @param file_name Filename for the output (default: "All_pivots_Cumulative_CI_quadrant_year_v2.rds")
+#' @return Path to the saved file
+#'
+save_growth_model <- function(data, output_dir, file_name = "All_pivots_Cumulative_CI_quadrant_year_v2.rds") {
+ # Create output directory if it doesn't exist
+ dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
+
+ # Create full file path
+ file_path <- here::here(output_dir, file_name)
+
+ # Save the data
+ saveRDS(data, file_path)
+
+ safe_log(paste("Interpolated CI data saved to:", file_path))
+ return(file_path)
+}
\ No newline at end of file
diff --git a/r_app/installPackages.R b/r_app/installPackages.R
index 75b5e9e..c8d1a2f 100644
--- a/r_app/installPackages.R
+++ b/r_app/installPackages.R
@@ -1,15 +1,40 @@
-install.packages('CAST')
-install.packages("packages/CIprep_0.1.4.tar.gz",repos=NULL, type="source")
-install.packages('caret')
-install.packages('exactextractr')
-install.packages('googledrive')
-install.packages('here')
-install.packages('lubridate')
-install.packages('raster')
-install.packages('readxl')
-install.packages('rsample')
-install.packages('sf')
-install.packages('terra')
-install.packages('tidyverse')
-install.packages('tmap')
-install.packages('zoo')
+# 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
index 2813f1e..189f4f4 100644
--- a/r_app/interpolate_growth_model.R
+++ b/r_app/interpolate_growth_model.R
@@ -1,124 +1,102 @@
-library(sf)
-library(terra)
-library(tidyverse)
-library(lubridate)
-library(exactextractr)
+# 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")
+#
-# Vang alle command line argumenten op
-args <- commandArgs(trailingOnly = TRUE)
-
-# Converteer het tweede argument naar een string waarde
-project_dir <- as.character(args[1])
-
-# Controleer of data_dir een geldige waarde is
-if (!is.character(project_dir)) {
- project_dir <- "chemba"
-}
-
-source("parameters_project.R")
-source("ci_extraction_utils.R")
-# Check if the file exists
-file_path <- here(cumulative_CI_vals_dir, "combined_CI_data.rds")
-pivot_stats2 <- data.frame()
-if (file.exists(file_path)) {
- pivot_stats2 <- readRDS(file_path) %>%
- ungroup() %>%
- group_by(field, sub_field) %>%
- summarise(across(everything(), ~ first(na.omit(.))), .groups = "drop")
-}
-head(pivot_stats2)
- #%>% drop_na(pivot_quadrant)
-
-# gather data into long format for easier calculation and visualisation
-pivot_stats_long <- pivot_stats2 %>%
- tidyr::gather("Date", value, -field, -sub_field ) %>%
- mutate(#Date = right(Date, 8),
- Date = lubridate::ymd(Date)
- ) %>%
- drop_na(c("value","Date")) %>%
- mutate(value = as.numeric(value))%>%
- filter_all(all_vars(!is.infinite(.))) %>%
- # rename(field = pivot_quadrant,
- # sub_field = field) %>%
- unique()
-
-years <- harvesting_data %>%
- filter(!is.na(season_start)) %>%
- distinct(year) %>%
- pull(year)
-
-extract_CI_data <- function(field_names, harvesting_data, field_CI_data, season) {
- # Filter harvesting data for the given season and field names
- filtered_harvesting_data <- harvesting_data %>%
- filter(year == season, sub_field %in% field_names)
-
- # Filter field CI data for the given field names
- filtered_field_CI_data <- field_CI_data %>%
- filter(sub_field %in% field_names)
-
- # Return an empty data frame if no CI data is found
- if (nrow(filtered_field_CI_data) == 0) {
- return(data.frame())
- }
-
-
- # Create a linear interpolation function for the CI data
- ApproxFun <- approxfun(x = filtered_field_CI_data$Date, y = filtered_field_CI_data$value)
- Dates <- seq.Date(ymd(min(filtered_field_CI_data$Date)), ymd(max(filtered_field_CI_data$Date)), by = 1)
- LinearFit <- ApproxFun(Dates)
- # Combine interpolated data with the original CI data
-
- CI <- data.frame(Date = Dates, FitData = LinearFit) %>%
- left_join(., filtered_field_CI_data, by = "Date") %>%
- filter(Date > filtered_harvesting_data$season_start & Date < filtered_harvesting_data$season_end)
-
- # If CI is empty after filtering, return an empty dataframe
- if (nrow(CI) == 0) {
- message ('CI empty after filtering')
- return(data.frame())
- }
-
- # Add additional columns if data exists
- CI <- CI %>%
- mutate(DOY = seq(1, n(), 1),
- model = paste0("Data", season, " : ", field_names),
- season = season,
- subField = field_names)
-
- return(CI)
-}
-
-message(harvesting_data)
-
-CI_all <- map_df(years, function(yr) {
- # yr = 2021
- message(yr)
-
- # Get the fields harvested in this year
- sub_fields <- harvesting_data %>%
- filter(year == yr) %>%
- filter(!is.na(season_start)) %>%
- pull(sub_field)
-
-
- # Filter sub_fields to only include those with value data in pivot_stats_long
- valid_sub_fields <- sub_fields %>%
- keep(~ any(pivot_stats_long$sub_field == .x))
-
- # Extract data for each valid field
- map(valid_sub_fields, ~ extract_CI_data(.x, harvesting_data = harvesting_data, field_CI_data = pivot_stats_long, season = yr)) %>%
- list_rbind()
+# 1. Load required packages
+# -----------------------
+suppressPackageStartupMessages({
+ library(tidyverse)
+ library(lubridate)
+ library(here)
})
-# it will crash here if CI_all is empty and will not overwrite the rds rendering growth_model.R useless
-# if(nrow(CI_all) > 0){
- CI_all <- CI_all %>%
- group_by(model) %>%
- mutate(CI_per_day = FitData - lag(FitData), cumulative_CI = cumsum(FitData))
-# }
+# 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)
+ })
+}
-
-saveRDS(CI_all, here(cumulative_CI_vals_dir,"All_pivots_Cumulative_CI_quadrant_year_v2.rds"))
-message('rds saved')
-
+if (sys.nframe() == 0) {
+ main()
+}
diff --git a/r_app/mosaic_creation.R b/r_app/mosaic_creation.R
index 85a3d18..7f92d9f 100644
--- a/r_app/mosaic_creation.R
+++ b/r_app/mosaic_creation.R
@@ -1,137 +1,119 @@
-library(sf)
-library(terra)
-library(tidyverse)
-library(lubridate)
+# 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
+#
-# Vang alle command line argumenten op
-args <- commandArgs(trailingOnly = TRUE)
+# 1. Load required packages
+# -----------------------
+suppressPackageStartupMessages({
+ library(sf)
+ library(terra)
+ library(tidyverse)
+ library(lubridate)
+ library(here)
+})
-# Controleer of er ten minste één argument is doorgegeven
-if (length(args) == 0) {
- stop("Geen argumenten doorgegeven aan het script")
-}
-
-# Converteer het eerste argument naar een numerieke waarde
-end_date <- as.Date(args[1])
-
-
-offset <- as.numeric(args[2])
-# Controleer of weeks_ago een geldig getal is
-if (is.na(offset)) {
- # stop("Het argument is geen geldig getal")
- offset <- 7
-}
-
-# Converteer het tweede argument naar een string waarde
-project_dir <- as.character(args[3])
-
-# Controleer of data_dir een geldige waarde is
-if (!is.character(project_dir)) {
- project_dir <- "chemba"
-}
-
-source("parameters_project.R")
-source("mosaic_creation_utils.R")
-
-week <- week(end_date)
-
-dates <- date_list(end_date, offset)
-
-file_name_tif <- as.character(args[4])
-if (is.na(file_name_tif)) {
- file_name_tif <- paste0("week_", sprintf("%02d", dates$week), "_", dates$year, ".tif")
-}
-
-print(dates)
-print(file_name_tif)
-#load pivot geojson
-# pivot_sf_q <- st_read(here(data_dir, "pivot.geojson")) %>% dplyr::select(pivot, pivot_quadrant) %>% vect()
-
-vrt_files <- list.files(here(daily_vrt),full.names = T)
-vrt_list <- map(dates$days_filter, ~ vrt_files[grepl(pattern = .x, x = vrt_files)]) %>%
- compact() %>%
- flatten_chr()
-
-raster_files_final <- list.files(merged_final,full.names = T, pattern = ".tif")
-
-if (length(vrt_list) > 0) {
- print("vrt list made, preparing mosaic creation by counting cloud cover")
+# 2. Process command line arguments and run mosaic creation
+# ------------------------------------------------------
+main <- function() {
+ # Capture command line arguments
+ args <- commandArgs(trailingOnly = TRUE)
- total_pix_area <-
- rast(vrt_list[1]) %>% terra::subset(1) %>% setValues(1) %>%
- crop(field_boundaries, mask = TRUE) %>%
- global(., fun = "notNA") #%>%
-
- layer_5_list <- purrr::map(vrt_list, function(vrt_list) {
- rast(vrt_list[1]) %>% terra::subset(1)
- }) %>% rast()
-
- missing_pixels_count <-
- layer_5_list %>% global(., fun = "notNA") %>%
- mutate(
- total_pixels = total_pix_area$notNA,
- missing_pixels_percentage = round(100 - ((
- notNA / total_pix_area$notNA
- ) * 100)),
- thres_5perc = as.integer(missing_pixels_percentage < 5),
- thres_40perc = as.integer(missing_pixels_percentage < 45)
- )
-
- index_5perc <- which(missing_pixels_count$thres_5perc == max(missing_pixels_count$thres_5perc))
- index_40perc <- which(missing_pixels_count$thres_40perc == max(missing_pixels_count$thres_40perc))
-
- ## Create mosaic
-
- if (sum(missing_pixels_count$thres_5perc) > 1) {
- message("More than 1 raster without clouds (<5%), max composite made")
-
- cloudy_rasters_list <- vrt_list[index_5perc]
-
- rsrc <- sprc(cloudy_rasters_list)
- x <- terra::mosaic(rsrc, fun = "max")
- names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
-
- } else if (sum(missing_pixels_count$thres_5perc) == 1) {
- message("Only 1 raster without clouds (<5%)")
-
- x <- rast(vrt_list[index_5perc[1]])
- names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
-
- } else if (sum(missing_pixels_count$thres_40perc) > 1) {
- message("More than 1 image contains clouds, composite made of <40% cloud cover images")
-
- cloudy_rasters_list <- vrt_list[index_40perc]
-
- rsrc <- sprc(cloudy_rasters_list)
- x <- mosaic(rsrc, fun = "max")
- names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
-
- } else if (sum(missing_pixels_count$thres_40perc) == 1) {
- message("Only 1 image available but contains clouds")
-
- x <- rast(vrt_list[index_40perc[1]])
- names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
-
- } else if (sum(missing_pixels_count$thres_40perc) == 0) {
- message("No cloud free images available, all images combined")
-
- rsrc <- sprc(vrt_list)
- x <- mosaic(rsrc, fun = "max")
- names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
-
+ # 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 <- "chemba"
+ message("No project_dir provided. Using default:", project_dir)
}
-} else{
- message("No images available this week, empty mosaic created")
+ # Make project_dir available globally so parameters_project.R can use it
+ assign("project_dir", project_dir, envir = .GlobalEnv)
- x <- rast(raster_files_final[1]) %>% setValues(0) %>%
- crop(field_boundaries, mask = TRUE)
+ # 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 <- "2024-08-25" # Default date for testing
+ }
+ } else {
+ # Default to current date if no argument is provided
+ end_date <- Sys.Date()
+ #end_date <- "2024-08-25" # 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")
+ }
- names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
-}
-plot(x$CI, main = paste("CI map ", dates$week))
-plotRGB(x, main = paste("RGB map ", dates$week))
-file_path_tif <- here(weekly_CI_mosaic ,file_name_tif)
-writeRaster(x, file_path_tif, overwrite=TRUE)
-message("Raster written/made at: ", file_path_tif)
+
+ # 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 68aadb8..5891cc6 100644
--- a/r_app/mosaic_creation_utils.R
+++ b/r_app/mosaic_creation_utils.R
@@ -1,13 +1,424 @@
-# utils for mosaic creation
+# MOSAIC_CREATION_UTILS.R
+# ======================
+# Utility functions for creating weekly mosaics from daily satellite imagery.
+# These functions support cloud cover assessment, date handling, and mosaic creation.
-date_list <- function(end_date, offset){
- offset <- as.numeric(offset) - 1
- end_date <- as.Date(end_date)
+#' Safe logging function
+#' @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)
+ }
+ }
+}
+
+#' Generate a sequence of dates for processing
+#'
+#' @param end_date The end date for the sequence (Date object)
+#' @param offset Number of days to look back from end_date
+#' @return A list containing week number, year, and a sequence of dates for filtering
+#'
+date_list <- function(end_date, offset) {
+ # Input validation
+ if (!lubridate::is.Date(end_date)) {
+ end_date <- as.Date(end_date)
+ if (is.na(end_date)) {
+ stop("Invalid end_date provided. Expected a Date object or a string convertible to Date.")
+ }
+ }
+
+ offset <- as.numeric(offset)
+ if (is.na(offset) || offset < 1) {
+ stop("Invalid offset provided. Expected a positive number.")
+ }
+
+ # Calculate date range
+ offset <- offset - 1 # Adjust offset to include end_date
start_date <- end_date - lubridate::days(offset)
- week <- week(start_date)
- year <- year(start_date)
- days_filter <- seq(from = start_date, to = end_date, by = "day")
+ # Extract week and year information
+ week <- lubridate::week(start_date)
+ year <- lubridate::year(start_date)
- return(list("week" = week, "year" = year, "days_filter" = days_filter))
+ # Generate sequence of dates
+ days_filter <- seq(from = start_date, to = end_date, by = "day")
+ days_filter <- format(days_filter, "%Y-%m-%d") # Format for consistent filtering
+
+ # Log the date range
+ safe_log(paste("Date range generated from", start_date, "to", end_date))
+
+ return(list(
+ "week" = week,
+ "year" = year,
+ "days_filter" = days_filter,
+ "start_date" = start_date,
+ "end_date" = end_date
+ ))
+}
+
+#' Create a weekly mosaic from available VRT files
+#'
+#' @param dates List from date_list() with date range info
+#' @param field_boundaries Field boundaries for image cropping
+#' @param daily_vrt_dir Directory containing VRT files
+#' @param merged_final_dir Directory with merged final rasters
+#' @param output_dir Output directory for weekly mosaics
+#' @param file_name_tif Output filename for the mosaic
+#' @param create_plots Whether to create visualization plots (default: TRUE)
+#' @return The file path of the saved mosaic
+#'
+create_weekly_mosaic <- function(dates, field_boundaries, daily_vrt_dir,
+ merged_final_dir, output_dir, file_name_tif,
+ create_plots = TRUE) {
+ # Find VRT files for the specified date range
+ vrt_list <- find_vrt_files(daily_vrt_dir, dates)
+
+ # Find final raster files for fallback
+ raster_files_final <- list.files(merged_final_dir, full.names = TRUE, pattern = "\\.tif$")
+
+ # Process the mosaic if VRT files are available
+ if (length(vrt_list) > 0) {
+ safe_log("VRT list created, assessing cloud cover for mosaic creation")
+
+ # Calculate cloud cover statistics
+ missing_pixels_count <- count_cloud_coverage(vrt_list, field_boundaries)
+
+ # Create mosaic based on cloud cover assessment
+ 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")
+
+ # Create empty mosaic if no files are available
+ if (length(raster_files_final) == 0) {
+ stop("No VRT files or final raster files available to create mosaic")
+ }
+
+ mosaic <- terra::rast(raster_files_final[1]) %>%
+ terra::setValues(0) %>%
+ terra::crop(field_boundaries, mask = TRUE)
+
+ names(mosaic) <- c("Red", "Green", "Blue", "NIR", "CI")
+ }
+
+ # Save the mosaic
+ file_path <- save_mosaic(mosaic, output_dir, file_name_tif, create_plots)
+
+ safe_log(paste("Weekly mosaic processing completed for week", dates$week))
+
+ return(file_path)
+}
+
+#' Find VRT files within a date range
+#'
+#' @param vrt_directory Directory containing VRT files
+#' @param dates List from date_list() function containing days_filter
+#' @return Character vector of VRT file paths
+#'
+find_vrt_files <- function(vrt_directory, dates) {
+ # Get all VRT files in directory
+ vrt_files <- list.files(here::here(vrt_directory), full.names = TRUE)
+
+ if (length(vrt_files) == 0) {
+ warning("No VRT files found in directory: ", vrt_directory)
+ return(character(0))
+ }
+
+ # Filter files by dates
+ vrt_list <- purrr::map(dates$days_filter, ~ vrt_files[grepl(pattern = .x, x = vrt_files)]) %>%
+ purrr::compact() %>%
+ purrr::flatten_chr()
+
+ # Log results
+ safe_log(paste("Found", length(vrt_list), "VRT files for the date range"))
+
+ return(vrt_list)
+}
+
+#' Count missing pixels (clouds) in rasters
+#'
+#' @param vrt_list List of VRT files to analyze
+#' @param field_boundaries Field boundaries vector for masking
+#' @return Data frame with cloud coverage statistics
+#'
+count_cloud_coverage <- function(vrt_list, field_boundaries) {
+ if (length(vrt_list) == 0) {
+ warning("No VRT files provided for cloud coverage calculation")
+ return(NULL)
+ }
+
+ tryCatch({
+ # Calculate total pixel area using the first VRT file
+ total_pix_area <- terra::rast(vrt_list[1]) |>
+ terra::subset(1) |>
+ terra::setValues(1) |>
+ terra::crop(field_boundaries, mask = TRUE) |>
+ terra::global(fun = "notNA")
+
+ # Process each raster to detect clouds and shadows
+ processed_rasters <- list()
+ cloud_masks <- list()
+
+ # Create data frame for missing pixels count
+ missing_pixels_df <- data.frame(
+ filename = vrt_list,
+ notNA = numeric(length(vrt_list)),
+ total_pixels = numeric(length(vrt_list)),
+ missing_pixels_percentage = numeric(length(vrt_list)),
+ thres_5perc = numeric(length(vrt_list)),
+ thres_40perc = numeric(length(vrt_list))
+ )
+ # Fill in the data frame with missing pixel statistics
+ for (i in seq_along(processed_rasters)) {
+ notna_count <- terra::global(processed_rasters[[i]][[1]], fun = "notNA")$notNA
+ missing_pixels_df$notNA[i] <- notna_count
+ missing_pixels_df$total_pixels[i] <- total_pix_area$notNA
+ missing_pixels_df$missing_pixels_percentage[i] <- round(100 - ((notna_count / total_pix_area$notNA) * 100))
+ missing_pixels_df$thres_5perc[i] <- as.integer(missing_pixels_df$missing_pixels_percentage[i] < 5)
+ missing_pixels_df$thres_40perc[i] <- as.integer(missing_pixels_df$missing_pixels_percentage[i] < 45)
+ }
+
+ # Store processed rasters and cloud masks as attributes
+ attr(missing_pixels_df, "cloud_masks") <- cloud_masks
+ attr(missing_pixels_df, "processed_rasters") <- processed_rasters
+
+ # Log results
+ safe_log(paste(
+ "Cloud cover assessment completed for", length(vrt_list), "files.",
+ sum(missing_pixels_df$thres_5perc), "files with <5% cloud cover,",
+ sum(missing_pixels_df$thres_40perc), "files with <45% cloud cover"
+ ))
+ return(missing_pixels_df)
+ }, error = function(e) {
+ warning("Error in cloud coverage calculation: ", e$message)
+ return(NULL)
+ })
+}
+
+#' Create a mosaic from VRT files based on cloud coverage
+#'
+#' @param vrt_list List of VRT files to create mosaic from
+#' @param missing_pixels_count Cloud coverage statistics from count_cloud_coverage()
+#' @param field_boundaries Field boundaries vector for masking (optional)
+#' @param raster_files_final List of processed raster files to use as fallback
+#' @return A SpatRaster object with the mosaic
+#'
+create_mosaic <- function(vrt_list, missing_pixels_count, field_boundaries = NULL, raster_files_final = NULL) {
+ # If no VRT files, create an empty mosaic
+ if (length(vrt_list) == 0) {
+ if (length(raster_files_final) == 0 || is.null(field_boundaries)) {
+ 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")
+
+ x <- terra::rast(raster_files_final[1]) |>
+ terra::setValues(0) |>
+ terra::crop(field_boundaries, mask = TRUE)
+
+ names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
+ return(x)
+ }
+
+ # If missing pixel count was not calculated, use all files
+ if (is.null(missing_pixels_count)) {
+ safe_log("No cloud coverage data available, using all images", "WARNING")
+
+ rsrc <- terra::sprc(vrt_list)
+ x <- terra::mosaic(rsrc, fun = "max")
+ names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
+ return(x)
+ }
+
+ # Check if we have processed rasters from cloud detection
+ processed_rasters <- attr(missing_pixels_count, "processed_rasters")
+ cloud_masks <- attr(missing_pixels_count, "cloud_masks")
+
+ if (!is.null(processed_rasters) && length(processed_rasters) > 0) {
+ safe_log("Using cloud-masked rasters for mosaic creation")
+
+ # Determine best rasters to use based on cloud coverage
+ index_5perc <- which(missing_pixels_count$thres_5perc == max(missing_pixels_count$thres_5perc))
+ index_40perc <- which(missing_pixels_count$thres_40perc == max(missing_pixels_count$thres_40perc))
+
+ # Create mosaic based on available cloud-free images
+ if (sum(missing_pixels_count$thres_5perc) > 1) {
+ safe_log("Creating max composite from multiple cloud-free images (<5% clouds)")
+
+ # Use the cloud-masked rasters instead of original files
+ cloudy_rasters_list <- processed_rasters[index_5perc]
+ rsrc <- terra::sprc(cloudy_rasters_list)
+ x <- terra::mosaic(rsrc, fun = "max")
+
+ # Also create a composite mask showing where data is valid
+ mask_list <- cloud_masks[index_5perc]
+ mask_rsrc <- terra::sprc(mask_list)
+ mask_composite <- terra::mosaic(mask_rsrc, fun = "max")
+ attr(x, "cloud_mask") <- mask_composite
+
+ } else if (sum(missing_pixels_count$thres_5perc) == 1) {
+ safe_log("Using single cloud-free image (<5% clouds)")
+
+ # Use the cloud-masked raster
+ x <- processed_rasters[[index_5perc[1]]]
+ attr(x, "cloud_mask") <- cloud_masks[[index_5perc[1]]]
+
+ } else if (sum(missing_pixels_count$thres_40perc) > 1) {
+ safe_log("Creating max composite from partially cloudy images (<40% clouds)", "WARNING")
+
+ # Use the cloud-masked rasters
+ cloudy_rasters_list <- processed_rasters[index_40perc]
+ rsrc <- terra::sprc(cloudy_rasters_list)
+ x <- terra::mosaic(rsrc, fun = "max")
+
+ # Also create a composite mask
+ mask_list <- cloud_masks[index_40perc]
+ mask_rsrc <- terra::sprc(mask_list)
+ mask_composite <- terra::mosaic(mask_rsrc, fun = "max")
+ attr(x, "cloud_mask") <- mask_composite
+
+ } else if (sum(missing_pixels_count$thres_40perc) == 1) {
+ safe_log("Using single partially cloudy image (<40% clouds)", "WARNING")
+
+ # Use the cloud-masked raster
+ x <- processed_rasters[[index_40perc[1]]]
+ attr(x, "cloud_mask") <- cloud_masks[[index_40perc[1]]]
+
+ } else {
+ safe_log("No cloud-free images available, using all cloud-masked images", "WARNING")
+
+ # Use all cloud-masked rasters
+ rsrc <- terra::sprc(processed_rasters)
+ x <- terra::mosaic(rsrc, fun = "max")
+
+ # Also create a composite mask
+ mask_rsrc <- terra::sprc(cloud_masks)
+ mask_composite <- terra::mosaic(mask_rsrc, fun = "max")
+ attr(x, "cloud_mask") <- mask_composite
+ }
+ } else {
+ # Fall back to original behavior if no cloud-masked rasters available
+ safe_log("No cloud-masked rasters available, using original images", "WARNING")
+
+ # Determine best rasters to use based on cloud coverage
+ index_5perc <- which(missing_pixels_count$thres_5perc == max(missing_pixels_count$thres_5perc))
+ index_40perc <- which(missing_pixels_count$thres_40perc == max(missing_pixels_count$thres_40perc))
+
+ # Create mosaic based on available cloud-free images
+ if (sum(missing_pixels_count$thres_5perc) > 1) {
+ safe_log("Creating max composite from multiple cloud-free images (<5% clouds)")
+
+ cloudy_rasters_list <- vrt_list[index_5perc]
+ rsrc <- terra::sprc(cloudy_rasters_list)
+ x <- terra::mosaic(rsrc, fun = "max")
+
+ } else if (sum(missing_pixels_count$thres_5perc) == 1) {
+ safe_log("Using single cloud-free image (<5% clouds)")
+
+ x <- terra::rast(vrt_list[index_5perc[1]])
+
+ } else if (sum(missing_pixels_count$thres_40perc) > 1) {
+ safe_log("Creating max composite from partially cloudy images (<40% clouds)", "WARNING")
+
+ cloudy_rasters_list <- vrt_list[index_40perc]
+ rsrc <- terra::sprc(cloudy_rasters_list)
+ x <- terra::mosaic(rsrc, fun = "max")
+
+ } else if (sum(missing_pixels_count$thres_40perc) == 1) {
+ safe_log("Using single partially cloudy image (<40% clouds)", "WARNING")
+
+ x <- terra::rast(vrt_list[index_40perc[1]])
+
+ } else {
+ safe_log("No cloud-free images available, using all images", "WARNING")
+
+ rsrc <- terra::sprc(vrt_list)
+ x <- terra::mosaic(rsrc, fun = "max")
+ }
+ }
+
+ # Set consistent layer names
+ names(x) <- c("Red", "Green", "Blue", "NIR", "CI")
+ return(x)
+}
+
+#' Save a mosaic raster to disk
+#'
+#' @param mosaic_raster A SpatRaster object to save
+#' @param output_dir Directory to save the output
+#' @param file_name Filename for the output raster
+#' @param plot_result Whether to create visualizations (default: FALSE)
+#' @return The file path of the saved raster
+#'
+save_mosaic <- function(mosaic_raster, output_dir, file_name, plot_result = FALSE) {
+ # Validate input
+ if (is.null(mosaic_raster)) {
+ stop("No mosaic raster provided to save")
+ }
+
+ # Create output directory if it doesn't exist
+ dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
+
+ # Create full file path
+ file_path <- here::here(output_dir, file_name)
+
+ # Get cloud mask if it exists
+ cloud_mask <- attr(mosaic_raster, "cloud_mask")
+
+ # Save raster
+ terra::writeRaster(mosaic_raster, file_path, overwrite = TRUE)
+
+ # Save cloud mask if available
+ if (!is.null(cloud_mask)) {
+ # Create mask filename by adding _mask before extension
+ mask_file_name <- gsub("\\.(tif|TIF)$", "_mask.\\1", file_name)
+ mask_file_path <- here::here(output_dir, mask_file_name)
+
+ # Save the mask
+ terra::writeRaster(cloud_mask, mask_file_path, overwrite = TRUE)
+ safe_log(paste("Cloud/shadow mask saved to:", mask_file_path))
+ }
+
+ # Create plots if requested
+ if (plot_result) {
+ # Plot the CI band
+ if ("CI" %in% names(mosaic_raster)) {
+ terra::plot(mosaic_raster$CI, main = paste("CI map", file_name))
+ }
+
+ # Plot RGB image
+ if (all(c("Red", "Green", "Blue") %in% names(mosaic_raster))) {
+ terra::plotRGB(mosaic_raster, main = paste("RGB map", file_name))
+ }
+
+ # Plot cloud mask if available
+ if (!is.null(cloud_mask)) {
+ terra::plot(cloud_mask, main = paste("Cloud/shadow mask", file_name),
+ col = c("red", "green"))
+ }
+
+ # If we have both RGB and cloud mask, create a side-by-side comparison
+ if (all(c("Red", "Green", "Blue") %in% names(mosaic_raster)) && !is.null(cloud_mask)) {
+ old_par <- par(mfrow = c(1, 2))
+ terra::plotRGB(mosaic_raster, main = "RGB Image")
+
+ # Create a colored mask for visualization (red = cloud/shadow, green = clear)
+ mask_plot <- cloud_mask
+ terra::plot(mask_plot, main = "Cloud/Shadow Mask", col = c("red", "green"))
+ par(old_par)
+ }
+ }
+
+ # Log save completion
+ safe_log(paste("Mosaic saved to:", file_path))
+
+ return(file_path)
}
diff --git a/r_app/packages.R b/r_app/packages.R
new file mode 100644
index 0000000..d6534fa
--- /dev/null
+++ b/r_app/packages.R
@@ -0,0 +1,117 @@
+# 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 6751c9b..1db88f5 100644
--- a/r_app/parameters_project.R
+++ b/r_app/parameters_project.R
@@ -1,82 +1,225 @@
-library(here)
-library('readxl')
-#chemba
+# filepath: c:\Users\timon\Resilience BV\4020 SCane ESA DEMO - Documenten\General\4020 SCDEMO Team\4020 TechnicalData\WP3\smartcane\r_app\parameters_project.R
+#
+# PARAMETERS_PROJECT.R
+# ====================
+# This script defines project parameters, directory structures, and loads field boundaries.
+# It establishes all the necessary paths and creates required directories for the SmartCane project.
-laravel_storage_dir <- here("laravel_app/storage/app", project_dir)
-reports_dir <- here(laravel_storage_dir, "reports")
-log_dir <- here(laravel_storage_dir, "logs")
-planet_tif_folder <- here(laravel_storage_dir, "merged_tif")
-merged_final <- here(laravel_storage_dir, "merged_final_tif")
-planet_tif_folder <- here(laravel_storage_dir, "merged_tif")
-merged_final <- here(laravel_storage_dir, "merged_final_tif")
-data_dir <- here(laravel_storage_dir, "Data")
-extracted_CI_dir <- here(data_dir, "extracted_ci")
-daily_CI_vals_dir <- here(extracted_CI_dir, "daily_vals")
-cumulative_CI_vals_dir <- here(extracted_CI_dir, "cumulative_vals")
-weekly_CI_mosaic <- here(laravel_storage_dir, "weekly_mosaic")
-daily_vrt <- here(data_dir, "vrt")
-harvest_dir <- here(data_dir, "HarvestData")
+# 1. Load required libraries
+# -------------------------
+suppressPackageStartupMessages({
+ library(here)
+ library(readxl)
+ library(sf)
+ library(dplyr)
+ library(tidyr)
+})
-dir.create(here(laravel_storage_dir), showWarnings = FALSE)
-dir.create(here(reports_dir), showWarnings = FALSE)
-dir.create(here(data_dir), showWarnings = FALSE)
-dir.create(here(log_dir), showWarnings = FALSE)
-dir.create(here(extracted_CI_dir), showWarnings = FALSE)
-dir.create(here(daily_CI_vals_dir), showWarnings = FALSE)
-dir.create(here(cumulative_CI_vals_dir), showWarnings = FALSE)
-dir.create(here(weekly_CI_mosaic),showWarnings = FALSE)
-dir.create(here(daily_vrt), showWarnings = FALSE)
-dir.create(merged_final,showWarnings = FALSE)
-dir.create(harvest_dir,showWarnings = FALSE)
-
-field_boundaries_sf <- st_read(here(data_dir, "pivot.geojson"), crs = 4326)
-
-names(field_boundaries_sf) <- c("field", "sub_field", "geometry")
-
-field_boundaries <- field_boundaries_sf %>% terra::vect()
-
-harvesting_data <- read_excel(here(data_dir, "harvest.xlsx")) %>%
- dplyr::select(
- c(
- "field",
- "sub_field",
- "year",
- "season_start",
- "season_end",
- "age",
- "sub_area",
- "tonnage_ha"
- )
- ) %>%
- mutate(
- field = as.character(field),
- sub_field = as.character(sub_field),
- year = as.numeric(year),
- season_start = as.Date(season_start, format="%d/%m/%Y"),
- season_end = as.Date(season_end, format="%d/%m/%Y"),
- age = as.numeric(age),
- sub_area = as.character(sub_area),
- tonnage_ha = as.numeric(tonnage_ha)
- ) %>%
- mutate(
- season_end = case_when(season_end > Sys.Date() ~ Sys.Date(),
- is.na(season_end) ~ Sys.Date(),
- TRUE ~ season_end),
- age = round(as.numeric(season_end - season_start) / 7, 0)
+# 2. Define project directory structure
+# -----------------------------------
+setup_project_directories <- function(project_dir) {
+ # Base directories
+ laravel_storage_dir <- here("laravel_app/storage/app", project_dir)
+
+ # Main subdirectories
+ dirs <- list(
+ reports = here(laravel_storage_dir, "reports"),
+ logs = here(laravel_storage_dir, "logs"),
+ data = here(laravel_storage_dir, "Data"),
+ tif = list(
+ merged = here(laravel_storage_dir, "merged_tif"),
+ final = here(laravel_storage_dir, "merged_final_tif")
+ ),
+ weekly_mosaic = here(laravel_storage_dir, "weekly_mosaic"),
+ extracted_ci = list(
+ base = here(laravel_storage_dir, "Data/extracted_ci"),
+ daily = here(laravel_storage_dir, "Data/extracted_ci/daily_vals"),
+ cumulative = here(laravel_storage_dir, "Data/extracted_ci/cumulative_vals")
+ ),
+ vrt = here(laravel_storage_dir, "Data/vrt"),
+ harvest = here(laravel_storage_dir, "Data/HarvestData")
)
-
-log_file <- here(log_dir, paste0(format(Sys.Date(), "%Y%m%d"), ".log"))
-
-
-
-
-
-# Create a logging function
-log_message <- function(message) {
- cat(message, "\n", file = log_file, append = TRUE)
+
+ # Create all directories
+ for (dir_path in unlist(dirs)) {
+ dir.create(dir_path, showWarnings = FALSE, recursive = TRUE)
+ }
+
+ # Return directory structure for use in other functions
+ return(list(
+ laravel_storage_dir = laravel_storage_dir,
+ reports_dir = dirs$reports,
+ log_dir = dirs$logs,
+ data_dir = dirs$data,
+ planet_tif_folder = dirs$tif$merged,
+ merged_final = dirs$tif$final,
+ daily_CI_vals_dir = dirs$extracted_ci$daily,
+ cumulative_CI_vals_dir = dirs$extracted_ci$cumulative,
+ weekly_CI_mosaic = dirs$weekly_mosaic,
+ daily_vrt = dirs$vrt,
+ harvest_dir = dirs$harvest,
+ extracted_CI_dir = dirs$extracted_ci$base
+ ))
}
-
-log_head <- function(list) {
- log_message(paste(capture.output(str(head(list))), collapse = "\n"))
+# 3. Load field boundaries
+# ----------------------
+load_field_boundaries <- function(data_dir) {
+ 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))
+ }
+
+ tryCatch({
+ field_boundaries_sf <- st_read(field_boundaries_path, crs = 4326, quiet = TRUE)
+ names(field_boundaries_sf) <- c("field", "sub_field", "geometry")
+ field_boundaries <- terra::vect(field_boundaries_sf)
+
+ return(list(
+ field_boundaries_sf = field_boundaries_sf,
+ field_boundaries = field_boundaries
+ ))
+ }, error = function(e) {
+ stop(paste("Error loading field boundaries:", e$message))
+ })
+}
+
+# 4. Load harvesting data
+# ---------------------
+load_harvesting_data <- function(data_dir) {
+ harvest_file <- here(data_dir, "harvest.xlsx")
+
+ if (!file.exists(harvest_file)) {
+ warning(paste("Harvest data file not found at path:", harvest_file))
+ return(NULL)
+ }
+
+ tryCatch({
+ harvesting_data <- read_excel(harvest_file) %>%
+ dplyr::select(
+ c(
+ "field",
+ "sub_field",
+ "year",
+ "season_start",
+ "season_end",
+ "age",
+ "sub_area",
+ "tonnage_ha"
+ )
+ ) %>%
+ mutate(
+ field = as.character(field),
+ sub_field = as.character(sub_field),
+ year = as.numeric(year),
+ season_start = as.Date(season_start, format="%d/%m/%Y"),
+ season_end = as.Date(season_end, format="%d/%m/%Y"),
+ age = as.numeric(age),
+ sub_area = as.character(sub_area),
+ tonnage_ha = as.numeric(tonnage_ha)
+ ) %>%
+ mutate(
+ season_end = case_when(
+ season_end > Sys.Date() ~ Sys.Date(),
+ is.na(season_end) ~ Sys.Date(),
+ TRUE ~ season_end
+ ),
+ age = round(as.numeric(season_end - season_start) / 7, 0)
+ )
+
+ return(harvesting_data)
+ }, error = function(e) {
+ warning(paste("Error loading harvesting data:", e$message))
+ return(NULL)
+ })
+}
+
+# 5. Define logging functions globally first
+# ---------------------------------------
+# Create a simple default log function in case setup_logging hasn't been called yet
+log_message <- function(message, level = "INFO") {
+ timestamp <- format(Sys.time(), "%Y-%m-%d %H:%M:%S")
+ formatted_message <- paste0("[", level, "] ", timestamp, " - ", message)
+ cat(formatted_message, "\n")
+}
+
+log_head <- function(list, level = "INFO") {
+ log_message(paste(capture.output(str(head(list))), collapse = "\n"), level)
+}
+
+# 6. Set up full logging system with file output
+# -------------------------------------------
+setup_logging <- function(log_dir) {
+ log_file <- here(log_dir, paste0(format(Sys.Date(), "%Y%m%d"), ".log"))
+
+ # Create enhanced log functions
+ log_message <- function(message, level = "INFO") {
+ timestamp <- format(Sys.time(), "%Y-%m-%d %H:%M:%S")
+ formatted_message <- paste0("[", level, "] ", timestamp, " - ", message)
+ cat(formatted_message, "\n", file = log_file, append = TRUE)
+
+ # Also print to console for debugging
+ if (level %in% c("ERROR", "WARNING")) {
+ cat(formatted_message, "\n")
+ }
+ }
+
+ log_head <- function(list, level = "INFO") {
+ log_message(paste(capture.output(str(head(list))), collapse = "\n"), level)
+ }
+
+ # Update the global functions with the enhanced versions
+ assign("log_message", log_message, envir = .GlobalEnv)
+ assign("log_head", log_head, envir = .GlobalEnv)
+
+ return(list(
+ log_file = log_file,
+ log_message = log_message,
+ log_head = log_head
+ ))
+}
+
+# 7. Initialize the project
+# ----------------------
+# Export project directories and settings
+initialize_project <- function(project_dir) {
+ # Set up directory structure
+ dirs <- setup_project_directories(project_dir)
+
+ # Set up logging
+ logging <- setup_logging(dirs$log_dir)
+
+ # Load field boundaries
+ boundaries <- load_field_boundaries(dirs$data_dir)
+
+ # Load harvesting data
+ harvesting_data <- load_harvesting_data(dirs$data_dir)
+
+ # Return all initialized components
+ return(c(
+ dirs,
+ list(
+ logging = logging,
+ field_boundaries = boundaries$field_boundaries,
+ field_boundaries_sf = boundaries$field_boundaries_sf,
+ harvesting_data = harvesting_data
+ )
+ ))
+}
+
+# When script is sourced, initialize with the global project_dir variable if it exists
+if (exists("project_dir")) {
+ # Now we can safely log before initialization
+ log_message(paste("Initializing project with directory:", project_dir))
+
+ project_config <- initialize_project(project_dir)
+
+ # Expose all variables to the global environment
+ list2env(project_config, envir = .GlobalEnv)
+
+ # Log project initialization completion
+ log_message(paste("Project initialized with directory:", project_dir))
+} else {
+ warning("project_dir variable not found. Please set project_dir before sourcing parameters_project.R")
}
diff --git a/r_app/report_utils.R b/r_app/report_utils.R
index 065d782..930ec3c 100644
--- a/r_app/report_utils.R
+++ b/r_app/report_utils.R
@@ -1,3 +1,34 @@
+# REPORT_UTILS.R
+# =============
+# Utility functions for generating SmartCane reports with visualizations.
+# These functions support the creation of maps, charts and report elements
+# for the CI_report_dashboard_planet.Rmd document.
+
+#' Safe logging function that works whether log_message exists or not
+#'
+#' @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)
+ }
+ }
+}
+
+#' Creates a sub-chunk for use within RMarkdown documents
+#'
+#' @param g A ggplot object to render in the sub-chunk
+#' @param fig_height Height of the figure in inches
+#' @param fig_width Width of the figure in inches
+#' @return NULL (writes chunk directly to output)
+#'
subchunkify <- function(g, fig_height=7, fig_width=5) {
g_deparsed <- paste0(deparse(
function() {g}
@@ -14,12 +45,48 @@ subchunkify <- function(g, fig_height=7, fig_width=5) {
cat(knitr::knit(text = knitr::knit_expand(text = sub_chunk), quiet = TRUE))
}
+#' Creates a Chlorophyll Index map for a pivot
+#'
+#' @param pivot_raster The raster data containing CI values
+#' @param pivot_shape The shape of the pivot field
+#' @param pivot_spans Additional boundary data for the field
+#' @param show_legend Whether to show the legend (default: FALSE)
+#' @param legend_is_portrait Whether to show the legend in portrait orientation (default: FALSE)
+#' @param week Week number to display in the title
+#' @param age Age of the crop in weeks
+#' @param borders Whether to display field borders (default: FALSE)
+#' @return A tmap object with the CI map
+#'
create_CI_map <- function(pivot_raster, pivot_shape, pivot_spans, show_legend = F, legend_is_portrait = F, week, age, borders = FALSE){
- map <- tm_shape(pivot_raster, unit = "m") +
- tm_raster(breaks = c(0,0.5,1,2,3,4,5,6,7,Inf), palette = "RdYlGn",legend.is.portrait = legend_is_portrait ,midpoint = NA) +
- tm_layout(main.title = paste0("\nMax CI week ", week,"\n", age, " weeks old"),
- main.title.size = 0.7, legend.show = show_legend)
+ # Input validation
+ if (missing(pivot_raster) || is.null(pivot_raster)) {
+ stop("pivot_raster is required")
+ }
+ if (missing(pivot_shape) || is.null(pivot_shape)) {
+ stop("pivot_shape is required")
+ }
+ if (missing(pivot_spans) || is.null(pivot_spans)) {
+ stop("pivot_spans is required")
+ }
+ if (missing(week) || is.null(week)) {
+ stop("week parameter is required")
+ }
+ if (missing(age) || is.null(age)) {
+ stop("age parameter is required")
+ }
+ # Create the base map
+ map <- tm_shape(pivot_raster, unit = "m") # Add raster with continuous spectrum (fixed scale 1-8 for consistent comparison)
+ map <- map + tm_raster(col.scale = tm_scale_continuous(values = "brewer.rd_yl_gn",
+ limits = c(1, 8)),
+ col.legend = tm_legend(title = "CI",
+ orientation = if(legend_is_portrait) "portrait" else "landscape",
+ show = show_legend,
+ position = c("left", "bottom")))
+ # Add layout elements
+ map <- map + tm_layout(main.title = paste0("Max CI week ", week,"\n", age, " weeks old"),
+ main.title.size = 0.7)
+ # Add borders if requested
if (borders) {
map <- map +
tm_shape(pivot_shape) +
@@ -32,12 +99,50 @@ create_CI_map <- function(pivot_raster, pivot_shape, pivot_spans, show_legend =
return(map)
}
+#' Creates a Chlorophyll Index difference map between two weeks
+#'
+#' @param pivot_raster The raster data containing CI difference values
+#' @param pivot_shape The shape of the pivot field
+#' @param pivot_spans Additional boundary data for the field
+#' @param show_legend Whether to show the legend (default: FALSE)
+#' @param legend_is_portrait Whether to show the legend in portrait orientation (default: FALSE)
+#' @param week_1 First week number for comparison
+#' @param week_2 Second week number for comparison
+#' @param age Age of the crop in weeks
+#' @param borders Whether to display field borders (default: TRUE)
+#' @return A tmap object with the CI difference map
+#'
create_CI_diff_map <- function(pivot_raster, pivot_shape, pivot_spans, show_legend = F, legend_is_portrait = F, week_1, week_2, age, borders = TRUE){
- map <- tm_shape(pivot_raster, unit = "m") +
- tm_raster(breaks = c(-3,-2,-1,0,1,2,3), palette = "RdYlGn",legend.is.portrait = legend_is_portrait, midpoint = 0, title = "CI difference") +
- tm_layout(main.title = paste0("CI change week ", week_1, " - week ", week_2, "\n", age, " weeks old"),
- main.title.size = 0.7, legend.show = show_legend)
+ # Input validation
+ if (missing(pivot_raster) || is.null(pivot_raster)) {
+ stop("pivot_raster is required")
+ }
+ if (missing(pivot_shape) || is.null(pivot_shape)) {
+ stop("pivot_shape is required")
+ }
+ if (missing(pivot_spans) || is.null(pivot_spans)) {
+ stop("pivot_spans is required")
+ }
+ if (missing(week_1) || is.null(week_1) || missing(week_2) || is.null(week_2)) {
+ stop("week_1 and week_2 parameters are required")
+ }
+ if (missing(age) || is.null(age)) {
+ stop("age parameter is required")
+ }
+ # Create the base map
+ map <- tm_shape(pivot_raster, unit = "m") # Add raster with continuous spectrum (centered at 0 for difference maps, fixed scale)
+ map <- map + tm_raster(col.scale = tm_scale_continuous(values = "brewer.rd_yl_gn",
+ midpoint = 0,
+ limits = c(-3, 3)),
+ col.legend = tm_legend(title = "CI difference",
+ orientation = if(legend_is_portrait) "portrait" else "landscape",
+ show = show_legend,
+ position = c("left", "bottom")))
+ # Add layout elements
+ map <- map + tm_layout(main.title = paste0("CI change week ", week_1, " - week ", week_2, "\n", age, " weeks old"),
+ main.title.size = 0.7)
+ # Add borders if requested
if (borders) {
map <- map +
tm_shape(pivot_shape) +
@@ -50,186 +155,336 @@ create_CI_diff_map <- function(pivot_raster, pivot_shape, pivot_spans, show_lege
return(map)
}
-ci_plot <- function(pivotName){
- # pivotName = "1.1"
- pivotShape <- AllPivots0 %>% terra::subset(field %in% pivotName) %>% st_transform(crs(CI))
- age <- harvesting_data %>% dplyr::filter(field %in% pivotName) %>% sort("year") %>% tail(., 1) %>% dplyr::select(age) %>% unique() %>% pull() %>% round()
-
- AllPivots2 <- AllPivots0 %>% dplyr::filter(field %in% pivotName)
-
- singlePivot <- CI %>% crop(., pivotShape) %>% mask(., pivotShape)
-
- singlePivot_m1 <- CI_m1 %>% crop(., pivotShape) %>% mask(., pivotShape)
- singlePivot_m2 <- CI_m2 %>% crop(., pivotShape) %>% mask(., pivotShape)
- # singlePivot_m3 <- CI_m3 %>% crop(., pivotShape) %>% mask(., pivotShape)
-
- abs_CI_last_week <- last_week_dif_raster_abs %>% crop(., pivotShape) %>% mask(., pivotShape)
- abs_CI_three_week <- three_week_dif_raster_abs %>% crop(., pivotShape) %>% mask(., pivotShape)
-
- planting_date <- harvesting_data %>% dplyr::filter(field %in% pivotName) %>% ungroup() %>% dplyr::select(season_start) %>% unique()
-
- joined_spans2 <- AllPivots0 %>% st_transform(crs(pivotShape)) %>% dplyr::filter(field %in% pivotName) #%>% unique() %>% st_crop(., pivotShape)
-
- CImap_m2 <- create_CI_map(singlePivot_m2, AllPivots2, joined_spans2, show_legend= T, legend_is_portrait = T, week = week_minus_2, age = age -2, borders = borders)
- CImap_m1 <- create_CI_map(singlePivot_m1, AllPivots2, joined_spans2, show_legend= F, legend_is_portrait = F, week = week_minus_1, age = age -1, borders = borders)
- CImap <- create_CI_map(singlePivot, AllPivots2, joined_spans2, show_legend= F, legend_is_portrait = F, week = week, age = age, borders = borders)
-
-
- CI_max_abs_last_week <- create_CI_diff_map(abs_CI_last_week,AllPivots2, joined_spans2, show_legend = T, legend_is_portrait = T, week_1 = week, week_2 = week_minus_1, age = age, borders = borders)
- CI_max_abs_three_week <- create_CI_diff_map(abs_CI_three_week, AllPivots2, joined_spans2, show_legend = T, legend_is_portrait = T, week_1 = week, week_2 = week_minus_3, age = age, borders = borders)
-
- tst <- tmap_arrange(CImap_m2, CImap_m1, CImap,CI_max_abs_last_week, CI_max_abs_three_week, nrow = 1)
-
- cat(paste("## Field", pivotName, "-", age, "weeks after planting/harvest", "\n"))
- # cat("\n")
- # cat(' Pivot', pivotName, '- week', week, '-', age$Age, 'weeks after planting/harvest ')
- # cat(paste("# Pivot",pivots$pivot[i],"\n"))
- print(tst)
-
-}
-
-cum_ci_plot <- function(pivotName){
-
- # pivotName = "3a13"
- data_ci <- CI_quadrant %>% filter(field == pivotName)
-
- if (nrow(data_ci) == 0) {
- return(cum_ci_plot2(pivotName)) # Return an empty data frame if no data is found
+#' Creates a visualization of CI data for a specific pivot field
+#'
+#' @param pivotName The name or ID of the pivot field to visualize
+#' @param field_boundaries Field boundaries spatial data (sf object)
+#' @param current_ci Current week's Chlorophyll Index raster
+#' @param ci_minus_1 Previous week's Chlorophyll Index raster
+#' @param ci_minus_2 Two weeks ago Chlorophyll Index raster
+#' @param last_week_diff Difference raster between current and last week
+#' @param three_week_diff Difference raster between current and three weeks ago
+#' @param harvesting_data Data frame containing field harvesting/planting information
+#' @param week Current week number
+#' @param week_minus_1 Previous week number
+#' @param week_minus_2 Two weeks ago week number
+#' @param week_minus_3 Three weeks ago week number
+#' @param borders Whether to display field borders (default: TRUE)
+#' @return NULL (adds output directly to R Markdown document)
+#'
+ci_plot <- function(pivotName,
+ 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 = TRUE){
+ # Input validation
+ if (missing(pivotName) || is.null(pivotName) || pivotName == "") {
+ stop("pivotName is required")
+ }
+ if (missing(field_boundaries) || is.null(field_boundaries)) {
+ stop("field_boundaries is required")
+ }
+ if (missing(current_ci) || is.null(current_ci)) {
+ stop("current_ci is required")
+ }
+ if (missing(ci_minus_1) || is.null(ci_minus_1)) {
+ stop("ci_minus_1 is required")
+ }
+ if (missing(ci_minus_2) || is.null(ci_minus_2)) {
+ stop("ci_minus_2 is required")
+ }
+ if (missing(last_week_diff) || is.null(last_week_diff)) {
+ stop("last_week_diff is required")
+ }
+ if (missing(three_week_diff) || is.null(three_week_diff)) {
+ stop("three_week_diff is required")
+ }
+ if (missing(harvesting_data) || is.null(harvesting_data)) {
+ stop("harvesting_data is required")
}
- data_ci2 <- data_ci %>% mutate(CI_rate = cumulative_CI/DOY,
- week = week(Date))%>% group_by(field) %>%
- mutate(mean_rolling10 = rollapplyr(CI_rate , width = 10, FUN = mean, partial = TRUE))
-
- date_preperation_perfect_pivot <- data_ci2 %>% group_by(season) %>% summarise(min_date = min(Date),
- max_date = max(Date),
- days = max_date - min_date)
-
-
- unique_seasons <- unique(date_preperation_perfect_pivot$season)
-
- g <- ggplot(data= data_ci2 %>% filter(season %in% unique_seasons)) +
- facet_wrap(~season, scales = "free_x") +
- geom_line( aes(Date, mean_rolling10, col = sub_field, group = sub_field)) +
- labs(title = paste("14 day rolling MEAN CI rate - Pivot ", pivotName),
- color = "Field name")+
- scale_x_date(date_breaks = "1 month", date_labels = "%m-%Y") +
- theme_minimal() +
- theme(axis.text.x = element_text(angle = 60, hjust = 1),
- legend.justification=c(1,0), legend.position = c(1, 0),
- legend.title = element_text(size = 8),
- legend.text = element_text(size = 8)) +
- guides(color = guide_legend(nrow = 2, byrow = TRUE))
-
- subchunkify(g, 3.2, 10)
-
+ # Extract pivot shape and age data
+ tryCatch({
+ pivotShape <- field_boundaries %>% terra::subset(field %in% pivotName) %>% sf::st_transform(terra::crs(current_ci))
+ age <- harvesting_data %>%
+ dplyr::filter(field %in% pivotName) %>%
+ sort("year") %>%
+ tail(., 1) %>%
+ dplyr::select(age) %>%
+ unique() %>%
+ pull() %>%
+ round()
+
+ # Filter for the specific pivot
+ AllPivots2 <- field_boundaries %>% dplyr::filter(field %in% pivotName)
+
+ # Create crop masks for different timepoints using terra functions
+ singlePivot <- terra::crop(current_ci, pivotShape) %>% terra::mask(., pivotShape)
+ singlePivot_m1 <- terra::crop(ci_minus_1, pivotShape) %>% terra::mask(., pivotShape)
+ singlePivot_m2 <- terra::crop(ci_minus_2, pivotShape) %>% terra::mask(., pivotShape)
+
+ # Create difference maps
+ abs_CI_last_week <- terra::crop(last_week_diff, pivotShape) %>% terra::mask(., pivotShape)
+ abs_CI_three_week <- terra::crop(three_week_diff, pivotShape) %>% terra::mask(., pivotShape)
+
+ # Get planting date
+ planting_date <- harvesting_data %>%
+ dplyr::filter(field %in% pivotName) %>%
+ ungroup() %>%
+ dplyr::select(season_start) %>%
+ unique()
+
+ # Create spans for borders
+ joined_spans2 <- field_boundaries %>%
+ sf::st_transform(sf::st_crs(pivotShape)) %>% dplyr::filter(field %in% pivotName)
+
+ # Create the maps for different timepoints
+ CImap_m2 <- create_CI_map(singlePivot_m2, AllPivots2, joined_spans2,
+ show_legend = TRUE, legend_is_portrait = TRUE,
+ week = week_minus_2, age = age - 2, borders = borders)
+
+ CImap_m1 <- create_CI_map(singlePivot_m1, AllPivots2, joined_spans2,
+ show_legend = FALSE, legend_is_portrait = FALSE,
+ week = week_minus_1, age = age - 1, borders = borders)
+
+ CImap <- create_CI_map(singlePivot, AllPivots2, joined_spans2,
+ show_legend = FALSE, legend_is_portrait = FALSE,
+ week = week, age = age, borders = borders)
+ # Create difference maps - only show legend on the second one to avoid redundancy
+ CI_max_abs_last_week <- create_CI_diff_map(abs_CI_last_week, AllPivots2, joined_spans2,
+ show_legend = FALSE, legend_is_portrait = FALSE,
+ week_1 = week, week_2 = week_minus_1, age = age, borders = borders)
+
+ CI_max_abs_three_week <- create_CI_diff_map(abs_CI_three_week, AllPivots2, joined_spans2,
+ show_legend = TRUE, legend_is_portrait = TRUE,
+ week_1 = week, week_2 = week_minus_3, age = age, borders = borders)
+
+ # Arrange the maps
+ tst <- tmap_arrange(CImap_m2, CImap_m1, CImap, CI_max_abs_last_week, CI_max_abs_three_week, nrow = 1)
+
+ # Output heading and map to R Markdown
+ cat(paste("## Field", pivotName, "-", age, "weeks after planting/harvest", "\n"))
+ print(tst)
+
+ }, error = function(e) {
+ safe_log(paste("Error creating CI plot for pivot", pivotName, ":", e$message), "ERROR")
+ cat(paste("## Field", pivotName, "- Error creating visualization", "\n"))
+ cat(paste("Error:", e$message, "\n"))
+ })
}
-cum_ci_plot <- function(pivotName, plot_type = "value", facet_on = FALSE) {
-
- # pivotName = "3a13"
- data_ci <- CI_quadrant %>% filter(field == pivotName)
-
- if (nrow(data_ci) == 0) {
- return(cum_ci_plot2(pivotName)) # Return an empty data frame if no data is found
+#' Creates a plot showing Chlorophyll Index data over time for a pivot field
+#'
+#' @param pivotName The name or ID of the pivot field to visualize
+#' @param ci_quadrant_data Data frame containing CI quadrant data with field, sub_field, Date, DOY, cumulative_CI, value and season columns
+#' @param plot_type Type of plot to generate: "value", "CI_rate", or "cumulative_CI"
+#' @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")
+#' @return NULL (adds output directly to R Markdown document)
+#'
+cum_ci_plot <- function(pivotName, ci_quadrant_data = CI_quadrant, plot_type = "value", facet_on = FALSE, x_unit = "days") {
+ # Input validation
+ if (missing(pivotName) || is.null(pivotName) || pivotName == "") {
+ stop("pivotName is required")
+ }
+ if (missing(ci_quadrant_data) || is.null(ci_quadrant_data)) {
+ stop("ci_quadrant_data is required")
+ }
+ if (!plot_type %in% c("value", "CI_rate", "cumulative_CI")) {
+ stop("plot_type must be one of: 'value', 'CI_rate', or 'cumulative_CI'")
+ }
+ if (!x_unit %in% c("days", "weeks")) {
+ stop("x_unit must be either 'days' or 'weeks'")
}
- data_ci2 <- data_ci %>%
- mutate(CI_rate = cumulative_CI / DOY,
- week = week(Date)) %>%
- group_by(field) %>%
- mutate(mean_CIrate_rolling_10_days = rollapplyr(CI_rate, width = 10, FUN = mean, partial = TRUE),
- mean_rolling_10_days = rollapplyr(value, width = 10, FUN = mean, partial = TRUE))
-
- data_ci2 <- data_ci2 %>% mutate(season = as.factor(season))
-
- date_preperation_perfect_pivot <- data_ci2 %>%
- group_by(season) %>%
- summarise(min_date = min(Date),
- max_date = max(Date),
- days = max_date - min_date)
-
- unique_seasons <- sort(unique(date_preperation_perfect_pivot$season), decreasing = TRUE)[1:3]
-
- # Determine the y aesthetic based on the plot type
- y_aesthetic <- switch(plot_type,
- "CI_rate" = "mean_CIrate_rolling_10_days",
- "cumulative_CI" = "cumulative_CI",
- "value" = "mean_rolling_10_days")
-
- y_label <- switch(plot_type,
- "CI_rate" = "10-Day Rolling Mean CI Rate (cumulative CI / age)",
- "cumulative_CI" = "Cumulative CI",
- "value" = "10-Day Rolling Mean CI")
-
- if (facet_on) {
- g <- ggplot(data = data_ci2 %>% filter(season %in% unique_seasons)) +
- facet_wrap(~season, scales = "free_x") +
- geom_line(aes_string(x = "Date", y = y_aesthetic, col = "sub_field", group = "sub_field")) +
- labs(title = paste("Plot of", y_label, "for Pivot", pivotName),
- color = "Field Name",
- y = y_label) +
- scale_x_date(date_breaks = "1 month", date_labels = "%m-%Y") +
- theme_minimal() +
- theme(axis.text.x = element_text(angle = 60, hjust = 1),
- legend.justification = c(1, 0), legend.position = c(1, 0),
- legend.title = element_text(size = 8),
- legend.text = element_text(size = 8)) +
- guides(color = guide_legend(nrow = 2, byrow = TRUE))
- } else {
- g <- ggplot(data = data_ci2 %>% filter(season %in% unique_seasons)) +
- geom_line(aes_string(x = "DOY", y = y_aesthetic, col = "season", group = "season")) +
- labs(title = paste("Plot of", y_label, "for Pivot", pivotName),
- color = "Season",
- y = y_label,
- x = "Age of Crop (Days)") +
- theme_minimal() +
- theme(axis.text.x = element_text(angle = 60, hjust = 1),
- legend.justification = c(1, 0), legend.position = c(1, 0),
- legend.title = element_text(size = 8),
- legend.text = element_text(size = 8)) +
- guides(color = guide_legend(nrow = 2, byrow = TRUE))
- }
-
- subchunkify(g, 3.2, 10)
+ # Filter data for the specified pivot
+ tryCatch({
+ data_ci <- ci_quadrant_data %>% dplyr::filter(field == pivotName)
+
+ if (nrow(data_ci) == 0) {
+ safe_log(paste("No CI data found for pivot", pivotName), "WARNING")
+ return(cum_ci_plot2(pivotName)) # Use fallback function when no data is available
+ }
+
+ # Process data
+ data_ci2 <- data_ci %>%
+ dplyr::mutate(CI_rate = cumulative_CI / DOY,
+ week = lubridate::week(Date)) %>%
+ dplyr::group_by(field) %>%
+ dplyr::mutate(mean_CIrate_rolling_10_days = zoo::rollapplyr(CI_rate, width = 10, FUN = mean, partial = TRUE),
+ mean_rolling_10_days = zoo::rollapplyr(value, width = 10, FUN = mean, partial = TRUE))
+
+ data_ci2 <- data_ci2 %>% dplyr::mutate(season = as.factor(season))
+
+ # Prepare date information by season
+ date_preparation_perfect_pivot <- data_ci2 %>%
+ dplyr::group_by(season) %>%
+ dplyr::summarise(min_date = min(Date),
+ max_date = max(Date),
+ days = max_date - min_date)
+
+ # Get the 3 most recent seasons
+ unique_seasons <- sort(unique(date_preparation_perfect_pivot$season), decreasing = TRUE)[1:3]
+
+ # Determine the y aesthetic based on the plot type
+ y_aesthetic <- switch(plot_type,
+ "CI_rate" = "mean_CIrate_rolling_10_days",
+ "cumulative_CI" = "cumulative_CI",
+ "value" = "mean_rolling_10_days")
+
+ y_label <- switch(plot_type,
+ "CI_rate" = "10-Day Rolling Mean CI Rate (cumulative CI / age)",
+ "cumulative_CI" = "Cumulative CI",
+ "value" = "10-Day Rolling Mean CI")
+
+ # 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")
+
+ # 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)) +
+ ggplot2::facet_wrap(~season, scales = "free_x") +
+ ggplot2::geom_line(ggplot2::aes_string(x = x_var, y = y_aesthetic, col = "sub_field", group = "sub_field")) +
+ ggplot2::labs(title = paste("Plot of", y_label, "for Pivot", pivotName),
+ color = "Field Name",
+ y = y_label,
+ x = x_label) +
+ ggplot2::scale_x_date(date_breaks = "1 month", date_labels = "%m-%Y") +
+ ggplot2::theme_minimal() +
+ ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 60, hjust = 1),
+ 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_aesthetic, col = "season", group = "season")) +
+ ggplot2::labs(title = paste("Plot of", y_label, "for Pivot", pivotName),
+ color = "Season",
+ y = y_label,
+ x = x_label) +
+ ggplot2::theme_minimal() +
+ ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 60, hjust = 1),
+ 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))
+ }
+
+ # Output plot to R Markdown with reduced height
+ subchunkify(g, 3.2, 10) # Reduced from 3.2 to 2.8
+
+ }, error = function(e) {
+ safe_log(paste("Error creating CI trend plot for pivot", pivotName, ":", e$message), "ERROR")
+ cum_ci_plot2(pivotName) # Use fallback function in case of error
+ })
}
+#' Fallback function for creating CI visualization when data is missing
+#'
+#' @param pivotName The name or ID of the pivot field to visualize
+#' @return NULL (adds output directly to R Markdown document)
+#'
cum_ci_plot2 <- function(pivotName){
- end_date <- Sys.Date()
- start_date <- end_date %m-% months(11) # 11 months ago from end_date
- date_seq <- seq.Date(from = start_date, to = end_date, by = "month")
- midpoint_date <- start_date + (end_date - start_date) / 2
- g <- ggplot() +
- scale_x_date(limits = c(start_date, end_date), date_breaks = "1 month", date_labels = "%m-%Y") +
- scale_y_continuous(limits = c(0, 4)) +
- labs(title = paste("14 day rolling MEAN CI rate - Field ", pivotName),
- x = "Date", y = "CI Rate") +
- theme(axis.text.x = element_text(angle = 60, hjust = 1),
- legend.justification = c(1, 0), legend.position = c(1, 0),
- legend.title = element_text(size = 8),
- legend.text = element_text(size = 8)) +
- annotate("text", x = midpoint_date, y = 2, label = "No data available", size = 6, hjust = 0.5)
-
- subchunkify(g, 3.2, 10)
+ # Input validation
+ if (missing(pivotName) || is.null(pivotName) || pivotName == "") {
+ stop("pivotName is required")
+ }
+
+ # Create a simple plot showing "No data available"
+ tryCatch({
+ end_date <- Sys.Date()
+ start_date <- end_date %m-% months(11) # 11 months ago from end_date
+ date_seq <- seq.Date(from = start_date, to = end_date, by = "month")
+ midpoint_date <- start_date + (end_date - start_date) / 2
+
+ g <- ggplot() +
+ scale_x_date(limits = c(start_date, end_date), date_breaks = "1 month", date_labels = "%m-%Y") +
+ scale_y_continuous(limits = c(0, 4)) +
+ 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),
+ legend.justification = c(1, 0), legend.position = c(1, 0),
+ legend.title = element_text(size = 8),
+ legend.text = element_text(size = 8)) +
+ annotate("text", x = midpoint_date, y = 2, label = "No data available", size = 6, hjust = 0.5)
+
+ subchunkify(g, 3.2, 10)
+
+ }, error = function(e) {
+ safe_log(paste("Error creating fallback CI plot for pivot", pivotName, ":", e$message), "ERROR")
+ cat(paste("No data available for field", pivotName, "\n"))
+ })
}
+#' Gets the file path for a specific week's mosaic
+#'
+#' @param mosaic_path Base directory containing mosaic files
+#' @param input_date Reference date to calculate from
+#' @param week_offset Number of weeks to offset from input date (positive or negative)
+#' @return File path to the requested week's mosaic TIF file
+#'
get_week_path <- function(mosaic_path, input_date, week_offset) {
- # Convert input_date to Date object (in case it's a string)
- input_date <- as.Date(input_date)
+ # Input validation
+ if (missing(mosaic_path) || is.null(mosaic_path) || mosaic_path == "") {
+ stop("mosaic_path is required")
+ }
+ if (missing(input_date)) {
+ stop("input_date is required")
+ }
- # Get the start of the week for the input date (adjust to Monday as the start of the week)
- start_of_week <- floor_date(input_date, unit = "week", week_start = 1)
- # Calculate the new date after applying the week offset
- target_date <- start_of_week + weeks(week_offset)
+ tryCatch({
+ # Convert input_date to Date object (in case it's a string)
+ input_date <- as.Date(input_date)
+ if (is.na(input_date)) {
+ stop("Invalid input_date. Expected a Date object or a string convertible to Date.")
+ }
- # Get the week number and year of the target date
- target_week <- sprintf("%02d", isoweek(target_date)) # Left-pad week number with a zero if needed
- target_year <- isoyear(target_date)
+ # Validate week_offset
+ week_offset <- as.integer(week_offset)
+ if (is.na(week_offset)) {
+ stop("Invalid week_offset. Expected an integer value.")
+ }
- # Generate the file path for the target week
- path_to_week <- here(mosaic_path, paste0("week_", target_week, "_", target_year, ".tif"))
+ # Get the start of the week for the input date (adjust to Monday as the start of the week)
+ start_of_week <- lubridate::floor_date(input_date, unit = "week", week_start = 1)
- # Return the path
- return(path_to_week)
+ # Calculate the new date after applying the week offset
+ target_date <- start_of_week + lubridate::weeks(week_offset)
+
+ # Get the week number and year of the target date
+ target_week <- sprintf("%02d", lubridate::isoweek(target_date)) # Left-pad week number with a zero if needed
+ target_year <- lubridate::isoyear(target_date)
+
+ # Generate the file path for the target week
+ path_to_week <- here::here(mosaic_path, paste0("week_", target_week, "_", target_year, ".tif"))
+
+ # Log the path calculation
+ safe_log(paste("Calculated path for week", target_week, "of year", target_year, ":", path_to_week), "INFO")
+
+ # Return the path
+ return(path_to_week)
+
+ }, error = function(e) {
+ safe_log(paste("Error calculating week path:", e$message), "ERROR")
+ stop(e$message)
+ })
}
diff --git a/r_app/system_architecture.md b/r_app/system_architecture.md
new file mode 100644
index 0000000..cf4775b
--- /dev/null
+++ b/r_app/system_architecture.md
@@ -0,0 +1,409 @@
+
+# SmartCane System Architecture
+
+## Overview
+
+The SmartCane system is a comprehensive agricultural intelligence platform that processes satellite imagery and farm data to provide agronomic insights for sugarcane farmers. The system architecture follows a modular, layered approach with clear separation of concerns between data acquisition, processing, and presentation.
+
+## Architectural Layers
+
+The SmartCane system follows a layered architecture pattern, which is a standard approach in software engineering for organizing complex systems. This architecture divides the system into distinct functional layers, each with specific responsibilities. While these layers aren't explicitly shown as separate visual elements in the diagrams, they help conceptualize how components are organized by their function:
+
+
+
+### 1. Data Acquisition Layer
+- **Role**: Responsible for fetching raw data from external sources and user inputs
+- **Components**: Manual Sentinel Hub Requests, Python API Downloader, User Input Interface
+- **Functions**: Manual request setup on Sentinel Hub Requests Builder for specific client fields, connects to satellite data providers, downloads imagery, manages API credentials, performs preliminary data validation
+
+### 2. Processing Layer (SmartCane Engine)
+- **Role**: Core analytical engine that transforms raw data into actionable insights
+- **Components**: Python API Downloader (pre-processing), R Processing Engine (analytics)
+- **Functions**: Image processing, cloud masking, crop index calculation, field boundary processing, statistical analysis, report generation
+
+### 3. Presentation Layer
+- **Role**: Delivers insights to end users in accessible formats
+- **Components**: Laravel Web App, Email Delivery System
+- **Functions**: Interactive dashboards, visualization, report delivery, user management, project scheduling
+
+### 4. Data Storage Layer
+- **Role**: Persists system data across processing cycles
+- **Components**: File System, Database
+- **Functions**: Stores raw imagery, processed rasters, analytical results, user data, configuration
+
+## Key Subsystems
+
+### 1. Python API Downloader
+- **Role**: Acquires and pre-processes satellite imagery
+- **Inputs**: API credentials, field boundaries, date parameters, evaluation scripts
+- **Outputs**: Raw satellite images, merged GeoTIFFs, virtual rasters
+- **Interfaces**: External satellite APIs (Planet via Sentinel Hub), file system
+- **Orchestration**: Triggered by shell scripts from the Laravel application
+
+### 2. R Processing Engine
+- **Role**: Performs advanced analytics and generates insights
+- **Inputs**: Processed satellite imagery, field boundaries, harvest data, project parameters
+- **Outputs**: Crop indices, mosaics, RDS data files, agronomic reports
+- **Interfaces**: File system, report templates
+- **Orchestration**: Triggered by shell scripts from the Laravel application
+
+### 3. Laravel Web Application
+- **Role**: Provides operator interface and orchestrates the overall system
+- **Inputs**: User data, configuration settings
+- **Outputs**: Web interface, scheduling, report delivery
+- **Interfaces**: Users, database, file system
+- **Orchestration**: Controls execution of the SmartCane Engine via shell scripts
+
+### 4. Shell Script Orchestration
+- **Role**: Bridges between web application and processing components
+- **Functions**: Triggers processing workflows, manages execution environment, handles errors
+- **Examples**: runcane.sh, runpython.sh, build_mosaic.sh, build_report.sh
+
+## Data Flow
+
+1. **Input Stage**:
+ - Operators (internal team) manually prepare and submit requests on Sentinel Hub Requests Builder for the specific fields of a client.
+ - Operators (internal team) provide farm data (field boundaries, harvest data) via the Laravel Web App.
+ - System schedules data acquisition for specific dates/regions
+
+2. **Acquisition Stage**:
+ - Laravel triggers Python API Downloader via shell scripts
+ - Python connects to satellite data providers and downloads raw imagery
+ - Downloaded data is stored in the file system
+
+3. **Processing Stage**:
+ - Laravel triggers R Processing Engine via shell scripts
+ - R scripts read satellite imagery and farm data
+ - Processing produces crop indices, analytics, and reports
+ - Results are stored in the file system
+
+4. **Output Stage**:
+ - Laravel Web App accesses processed results
+ - Reports are delivered to users via email
+
+## System Integration Points
+
+- **Python-R Integration**: Data handover via file system (GeoTIFF, virtual rasters)
+- **Engine-Laravel Integration**: Orchestration via shell scripts, data exchange via file system and database
+- **User-System Integration**: Web interface, file uploads, email notifications
+
+## Developed/Customized Elements
+
+- **Custom Cloud Masking Algorithm**: Specialized for agricultural applications in tropical regions
+- **Crop Index Extraction Pipeline**: Tailored to sugarcane spectral characteristics
+- **Reporting Templates**: Designed for agronomic decision support
+- **Shell Script Orchestration**: Custom workflow management for the system's components
+
+## Strategic Role of Satellite Data
+
+Satellite data is central to the SmartCane system, providing:
+- Regular, non-invasive field monitoring
+- Detection of spatial patterns not visible from ground level
+- Historical analysis of crop performance
+- Early warning of crop stress or disease
+- Quantification of field variability for precision agriculture
+
+## Pilot Utilization Sites
+
+The SmartCane system is currently operational in Mozambique, Kenya, and Tanzania. Future pilot deployments and expansions are planned for Uganda, Colombia, Mexico, Guatemala, South Africa, and Zambia.
+
+---
+
+## System Architecture Diagrams
+
+Below are diagrams illustrating the system architecture from different perspectives.
+
+### Overall System Architecture
+
+This diagram provides a high-level overview of the complete SmartCane system, showing how major components interact. It focuses on the system boundaries and main data flows between the Python API Downloader, R Processing Engine, Laravel Web App, and data storage components. This view helps understand how the system works as a whole.
+
+```mermaid
+graph TD
+ A["fa:fa-satellite External Satellite Data Providers API"] --> PyDL["fa:fa-download Python API Downloader"];
+ C["fa:fa-users Users: Farm Data Input e.g., GeoJSON, Excel"] --> D{"fa:fa-laptop-code Laravel Web App"};
+
+ subgraph SmartCane System
+ PyDL --> G["fa:fa-folder-open File System: Raw Satellite Imagery, Rasters, RDS, Reports, Boundaries"];
+ E["fa:fa-cogs R Processing Engine"] -- Reads --> G;
+ E -- Writes --> G;
+
+ D -- Manages/Triggers --> F["fa:fa-terminal Shell Script Orchestration"];
+ F -- Executes --> PyDL;
+ F -- Executes --> E;
+
+ D -- Manages/Accesses --> G;
+ D -- Reads/Writes --> H["fa:fa-database Database: Project Metadata, Users, Schedules"];
+
+ E -- Generates --> I["fa:fa-file-alt Agronomic Reports: DOCX, HTML"];
+ D -- Accesses/Delivers --> I;
+ end
+
+ D --> J["fa:fa-desktop Users: Web Interface (future)"];
+ I -- Via Email (SMTP) --> K["fa:fa-envelope Users: Email Reports"];
+
+ style E fill:#f9f,stroke:#333,stroke-width:2px
+ style D fill:#bbf,stroke:#333,stroke-width:2px
+ style PyDL fill:#ffdd57,stroke:#333,stroke-width:2px
+```
+
+### R Processing Engine Detail
+
+This diagram zooms in on the R Processing Engine subsystem, detailing the internal components and data flow. It shows how raw satellite imagery and field data progress through various R scripts to produce crop indices and reports. The diagram highlights the data transformation pipeline within this analytical core of the SmartCane system.
+
+```mermaid
+graph TD
+ subgraph R Processing Engine
+
+ direction TB
+
+ subgraph Inputs
+ SatelliteImages["fa:fa-image Raw Satellite Imagery"]
+ FieldBoundaries["fa:fa-map-marker-alt Field Boundaries .geojson"]
+ HarvestData["fa:fa-file-excel Harvest Data .xlsx"]
+ ProjectParams["fa:fa-file-code Project Parameters .R"]
+ end
+
+ subgraph Core R Scripts & Processes
+ ParamConfig("fa:fa-cogs parameters_project.R")
+ MosaicScript("fa:fa-images mosaic_creation.R")
+ CIExtractionScript("fa:fa-microscope ci_extraction.R")
+ ReportUtils("fa:fa-tools executive_report_utils.R")
+ DashboardRmd("fa:fa-tachometer-alt CI_report_dashboard_planet_enhanced.Rmd")
+ SummaryRmd("fa:fa-list-alt CI_report_executive_summary.Rmd")
+ end
+
+ subgraph Outputs
+ WeeklyMosaics["fa:fa-file-image Weekly Mosaics .tif"]
+ CIDataRDS["fa:fa-database CI Data .rds"]
+ CIRasters["fa:fa-layer-group CI Rasters .tif"]
+ DashboardReport["fa:fa-chart-bar Dashboard Report .docx/.html"]
+ SummaryReport["fa:fa-file-invoice Executive Summary .docx/.html"]
+ end
+
+ %% Data Flow
+ ProjectParams --> ParamConfig;
+
+ SatelliteImages --> MosaicScript;
+ FieldBoundaries --> MosaicScript;
+ ParamConfig --> MosaicScript;
+ MosaicScript --> WeeklyMosaics;
+
+ WeeklyMosaics --> CIExtractionScript;
+ FieldBoundaries --> CIExtractionScript;
+ ParamConfig --> CIExtractionScript;
+ CIExtractionScript --> CIDataRDS;
+ CIExtractionScript --> CIRasters;
+
+ CIDataRDS --> ReportUtils;
+ CIRasters --> ReportUtils;
+ HarvestData --> ReportUtils;
+ ParamConfig --> ReportUtils;
+
+ ReportUtils --> DashboardRmd;
+ ReportUtils --> SummaryRmd;
+ ParamConfig --> DashboardRmd;
+ ParamConfig --> SummaryRmd;
+
+ DashboardRmd --> DashboardReport;
+ SummaryRmd --> SummaryReport;
+
+ end
+
+ ShellOrchestration["fa:fa-terminal Shell Scripts e.g., build_mosaic.sh, build_report.sh"] -->|Triggers| R_Processing_Engine["fa:fa-cogs R Processing Engine"]
+
+ style R_Processing_Engine fill:#f9f,stroke:#333,stroke-width:2px
+ style Inputs fill:#ccf,stroke:#333,stroke-width:1px
+ style Outputs fill:#cfc,stroke:#333,stroke-width:1px
+ style Core_R_Scripts_Processes fill:#ffc,stroke:#333,stroke-width:1px
+```
+
+### Python API Downloader Detail
+
+This diagram focuses on the Python API Downloader subsystem, showing its internal components and workflow. It illustrates how API credentials, field boundaries, and other inputs are processed through various Python functions to download, process, and prepare satellite imagery. This view reveals the technical implementation details of the data acquisition layer.
+
+```mermaid
+graph TD
+ subgraph Python API Downloader
+
+ direction TB
+
+ subgraph Inputs_Py [Inputs]
+ APICreds["fa:fa-key API Credentials (SH_CLIENT_ID, SH_CLIENT_SECRET)"]
+ DateRangeParams["fa:fa-calendar-alt Date Range Parameters (days_needed, specific_date)"]
+ GeoJSONInput["fa:fa-map-marker-alt Field Boundaries (pivot.geojson)"]
+ ProjectConfig["fa:fa-cogs Project Configuration (project_name, paths)"]
+ EvalScripts["fa:fa-file-code Evalscripts (JS for cloud masking & band selection)"]
+ end
+
+ subgraph Core_Python_Logic_Py [Core Python Logic & Libraries]
+ SetupConfig["fa:fa-cog SentinelHubConfig & BYOC Definition"]
+ DateSlotGen["fa:fa-calendar-check Date Slot Generation (slots)"]
+ GeoProcessing["fa:fa-map GeoJSON Parsing & BBox Splitting (geopandas, BBoxSplitter)"]
+ AvailabilityCheck["fa:fa-search-location Image Availability Check (SentinelHubCatalog)"]
+ RequestHandler["fa:fa-paper-plane Request Generation (SentinelHubRequest, get_true_color_request_day)"]
+ DownloadClient["fa:fa-cloud-download-alt Image Download (SentinelHubDownloadClient, download_function)"]
+ MergeUtility["fa:fa-object-group Tile Merging (gdal.BuildVRT, gdal.Translate, merge_files)"]
+ CleanupUtility["fa:fa-trash-alt Intermediate File Cleanup (empty_folders)"]
+ end
+
+ subgraph Outputs_Py [Outputs]
+ RawSatImages["fa:fa-file-image Raw Downloaded Satellite Imagery Tiles (response.tiff in dated subfolders)"]
+ MergedTifs["fa:fa-images Merged TIFs (merged_tif/{slot}.tif)"]
+ VirtualRasters["fa:fa-layer-group Virtual Rasters (merged_virtual/merged{slot}.vrt)"]
+ DownloadLogs["fa:fa-file-alt Console Output Logs (print statements)"]
+ end
+
+ ExternalSatAPI["fa:fa-satellite External Satellite Data Providers API (Planet via Sentinel Hub)"]
+
+ %% Data Flow for Python Downloader
+ APICreds --> SetupConfig;
+ DateRangeParams --> DateSlotGen;
+ GeoJSONInput --> GeoProcessing;
+ ProjectConfig --> SetupConfig;
+ ProjectConfig --> GeoProcessing;
+ ProjectConfig --> MergeUtility;
+ ProjectConfig --> CleanupUtility;
+ EvalScripts --> RequestHandler;
+
+ DateSlotGen -- Available Slots --> AvailabilityCheck;
+ GeoProcessing -- BBox List --> AvailabilityCheck;
+ SetupConfig --> AvailabilityCheck;
+ AvailabilityCheck -- Filtered Slots & BBoxes --> RequestHandler;
+
+ RequestHandler -- Download Requests --> DownloadClient;
+ SetupConfig --> DownloadClient;
+ DownloadClient -- Downloads Data From --> ExternalSatAPI;
+ ExternalSatAPI -- Returns Image Data --> DownloadClient;
+ DownloadClient -- Writes --> RawSatImages;
+ DownloadClient -- Generates --> DownloadLogs;
+
+ RawSatImages --> MergeUtility;
+ MergeUtility -- Writes --> MergedTifs;
+ MergeUtility -- Writes --> VirtualRasters;
+
+ end
+
+ ShellOrchestratorPy["fa:fa-terminal Shell Scripts (e.g., runpython.sh triggering planet_download.ipynb)"] -->|Triggers| Python_API_Downloader["fa:fa-download Python API Downloader"];
+
+ style Python_API_Downloader fill:#ffdd57,stroke:#333,stroke-width:2px
+ style Inputs_Py fill:#cdeeff,stroke:#333,stroke-width:1px
+ style Outputs_Py fill:#d4efdf,stroke:#333,stroke-width:1px
+ style Core_Python_Logic_Py fill:#fff5cc,stroke:#333,stroke-width:1px
+ style ExternalSatAPI fill:#f5b7b1,stroke:#333,stroke-width:2px
+```
+
+### SmartCane Engine Integration Diagram
+
+This diagram illustrates the integration of Python and R components within the SmartCane Engine. Unlike the first diagram that shows the overall system, this one specifically focuses on how the two processing components interact with each other and the rest of the system. It emphasizes the orchestration layer and data flows between the core processing components and external systems.
+
+```mermaid
+graph TD
+ %% External Systems & Users
+ Users_DataInput["fa:fa-user Users: Farm Data Input (GeoJSON, Excel, etc.)"] --> Laravel_WebApp;
+ ExternalSatAPI["fa:fa-satellite External Satellite Data Providers API"];
+
+ %% Main Application Components
+ Laravel_WebApp["fa:fa-globe Laravel Web App (Frontend & Control Plane)"];
+ Shell_Orchestration["fa:fa-terminal Shell Script Orchestration (e.g., runcane.sh, runpython.sh, build_mosaic.sh)"]; subgraph SmartCane_Engine ["SmartCane Engine (Data Processing Core)"]
+ direction TB
+ Python_Downloader["fa:fa-download Python API Downloader"];
+ R_Engine["fa:fa-chart-line R Processing Engine"];
+ end
+ %% Data Storage
+ FileSystem["fa:fa-folder File System (Raw Imagery, Rasters, RDS, Reports, Boundaries)"];
+ Database["fa:fa-database Database (Project Metadata, Users, Schedules)"];
+
+ %% User Outputs
+ Users_WebView["fa:fa-desktop Users: Web Interface (future)"];
+ Users_EmailReports["fa:fa-envelope Users: Email Reports (Agronomic Reports)"];
+ AgronomicReports["fa:fa-file-alt Agronomic Reports (DOCX, HTML)"];
+
+ %% --- Data Flows & Interactions ---
+
+ %% Laravel to Orchestration & Engine
+ Laravel_WebApp -- Manages/Triggers --> Shell_Orchestration;
+ Shell_Orchestration -- Executes --> Python_Downloader;
+ Shell_Orchestration -- Executes --> R_Engine;
+
+ %% Python Downloader within Engine
+ ExternalSatAPI -- Satellite Data --> Python_Downloader;
+ Python_Downloader -- Writes Raw Data --> FileSystem;
+ %% Inputs to Python (simplified for this view - details in Python-specific diagram)
+ %% Laravel_WebApp -- Provides Config/Boundaries --> Python_Downloader;
+
+
+ %% R Engine within Engine
+ %% Inputs to R (simplified - details in R-specific diagram)
+ %% Laravel_WebApp -- Provides Config/Boundaries --> R_Engine;
+ R_Engine -- Reads Processed Data/Imagery --> FileSystem;
+ R_Engine -- Writes Derived Products --> FileSystem;
+ R_Engine -- Generates --> AgronomicReports;
+
+ %% Laravel interaction with Data Storage
+ Laravel_WebApp -- Manages/Accesses --> FileSystem;
+ Laravel_WebApp -- Reads/Writes --> Database;
+
+ %% Output Delivery
+ Laravel_WebApp --> Users_WebView;
+ AgronomicReports --> Users_EmailReports;
+ %% Assuming a mechanism like SMTP, potentially triggered by Laravel or R-Engine completion
+ Laravel_WebApp -- Delivers/Displays --> AgronomicReports;
+
+
+ %% Styling
+ style SmartCane_Engine fill:#e6ffe6,stroke:#333,stroke-width:2px
+ style Python_Downloader fill:#ffdd57,stroke:#333,stroke-width:2px
+ style R_Engine fill:#f9f,stroke:#333,stroke-width:2px
+ style Laravel_WebApp fill:#bbf,stroke:#333,stroke-width:2px
+ style Shell_Orchestration fill:#f0ad4e,stroke:#333,stroke-width:2px
+ style FileSystem fill:#d1e0e0,stroke:#333,stroke-width:1px
+ style Database fill:#d1e0e0,stroke:#333,stroke-width:1px
+ style ExternalSatAPI fill:#f5b7b1,stroke:#333,stroke-width:2px
+ style AgronomicReports fill:#d4efdf,stroke:#333,stroke-width:1px
+```
+
+## Future Directions
+
+The SmartCane platform is poised for significant evolution, with several key enhancements and new capabilities planned to further empower users and expand its utility:
+
+- **Advanced Management Dashboard**: Development of a more comprehensive and interactive management dashboard to provide users with deeper insights and greater control over their operations.
+- **Enhanced Yield Prediction Models**: Improving the accuracy and granularity of yield predictions by incorporating more variables and advanced machine learning techniques.
+- **Integrated Weather and Irrigation Advice**: Leveraging weather forecast data and soil moisture information (potentially from new data sources) to provide precise irrigation scheduling and weather-related agronomic advice.
+- **AI-Guided Agronomic Advice**: Implementing sophisticated AI algorithms to analyze integrated data (satellite, weather, soil, farm practices) and offer tailored, actionable agronomic recommendations.
+- **Automated Advice Generation**: Developing capabilities for the system to automatically generate and disseminate critical advice and alerts to users based on real-time data analysis.
+- **Expanded Data Source Integration**:
+ - **Radar Data**: Incorporating radar satellite imagery (e.g., Sentinel-1) for all-weather monitoring capabilities, particularly useful during cloudy seasons for assessing crop structure, soil moisture, and biomass.
+ - **IoT and Ground Sensors**: Integrating data from in-field IoT devices and soil sensors for highly localized and continuous monitoring of environmental and soil conditions.
+- **Client-Facing Portal**: Exploration and potential development of a client-facing portal to allow end-users direct access to their data, dashboards, and reports, complementing the current internal management interface.
+
+These future developments aim to transform SmartCane into an even more powerful decision support system, fostering sustainable and efficient agricultural practices.
+
+## Conclusion and Integration Summary
+
+The SmartCane system architecture demonstrates a well-integrated solution that combines different technologies and subsystems to solve complex agricultural challenges. Here is a summary of how the key subsystems work together:
+
+### Subsystem Integration
+
+1. **Data Flow Sequence**
+ - The Laravel Web App initiates the workflow and manages user interactions
+ - Shell scripts orchestrate the execution sequence of the processing subsystems
+ - The Python API Downloader acquires raw data from external sources
+ - The R Processing Engine transforms this data into actionable insights
+ - Results flow back to users through the web interface and email reports
+
+2. **Technology Integration**
+ - **Python + R**: Different programming languages are leveraged for their respective strengths—Python for API communication and data acquisition, R for statistical analysis and report generation
+ - **Laravel + Processing Engine**: Clear separation between web presentation layer and computational backend
+ - **File System + Database**: Hybrid data storage approach with file system for imagery and reports, database for metadata and user information
+
+3. **Key Integration Mechanisms**
+ - **File System Bridge**: The different subsystems primarily communicate through standardized file formats (GeoTIFF, GeoJSON, RDS, DOCX)
+ - **Shell Script Orchestration**: Acts as the "glue" between subsystems, ensuring proper execution sequence and environment setup
+ - **Standardized Data Formats**: Use of widely-accepted geospatial and data formats enables interoperability
+
+4. **Extensibility and Scalability**
+ - The modular architecture allows for replacement or enhancement of individual components
+ - The clear subsystem boundaries enable parallel development and testing
+ - Standard interfaces simplify integration of new data sources, algorithms, or output methods
+
+The SmartCane architecture balances complexity with maintainability by using well-established technologies and clear boundaries between subsystems. The separation of concerns between data acquisition, processing, and presentation layers ensures that changes in one area minimally impact others, while the consistent data flow pathways ensure that information moves smoothly through the system.