ecological-agent-skills 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_CONTEXT.md +191 -0
- package/CATALOG.md +329 -0
- package/LICENSE +692 -0
- package/README.md +347 -0
- package/bin/install.mjs +168 -0
- package/docs/comparison-with-alternatives.md +38 -0
- package/docs/global-examples-index.md +103 -0
- package/docs/repository-statistics.md +101 -0
- package/docs/theoretical-foundations.md +188 -0
- package/environment.yaml +106 -0
- package/examples/community/arctic_tundra_vegetation_example.md +247 -0
- package/examples/community/bird_landuse_example.md +63 -0
- package/examples/community/phytoplankton_reservoir_example.md +60 -0
- package/examples/community/reef_fish_indopacific_example.md +221 -0
- package/examples/impact/baci_road_example.md +57 -0
- package/examples/impact/ecosystem_services_atlantic_forest.md +83 -0
- package/examples/impact/forest_loss_borneo_timeseries_example.md +225 -0
- package/examples/occupancy/puma_camera_example.md +61 -0
- package/examples/occupancy/snow_leopard_himalayas_example.md +204 -0
- package/examples/reproducible/whittaker_biome_sdm_example.md +406 -0
- package/examples/sdm/anteater_cerrado_example.md +69 -0
- package/examples/sdm/jaguar_amazon_example.md +80 -0
- package/examples/sdm/koala_climate_change_example.md +170 -0
- package/examples/sdm/wolf_recolonization_europe_example.md +193 -0
- package/package.json +43 -0
- package/renv.lock +194 -0
- package/skills/SKILL_INDEX.json +1020 -0
- package/skills/acoustic-monitoring/SKILL.md +163 -0
- package/skills/acoustic-monitoring/examples/example-prompts.md +100 -0
- package/skills/acoustic-monitoring/examples/temperate_forest_birds_example.md +285 -0
- package/skills/acoustic-monitoring/resources/acoustic-indices-reference.md +93 -0
- package/skills/acoustic-monitoring/resources/soundscape-ecology-guide.md +90 -0
- package/skills/acoustic-monitoring/resources/species-id-tools-comparison.md +89 -0
- package/skills/acoustic-monitoring/scripts/batch_species_detection.py +360 -0
- package/skills/acoustic-monitoring/scripts/compute_acoustic_indices.R +235 -0
- package/skills/acoustic-monitoring/scripts/compute_acoustic_indices.py +374 -0
- package/skills/biostatistics-workbench/SKILL.md +140 -0
- package/skills/biostatistics-workbench/examples/example-prompts.md +39 -0
- package/skills/biostatistics-workbench/resources/effect-size-reference.md +81 -0
- package/skills/biostatistics-workbench/resources/glm-family-link-reference.md +47 -0
- package/skills/biostatistics-workbench/resources/test-selection-guide.md +93 -0
- package/skills/biostatistics-workbench/scripts/glm_pipeline.R +78 -0
- package/skills/biostatistics-workbench/scripts/glm_pipeline.py +210 -0
- package/skills/camera-trap-processing/SKILL.md +159 -0
- package/skills/camera-trap-processing/examples/example-prompts.md +103 -0
- package/skills/camera-trap-processing/examples/leopard_serengeti_example.md +231 -0
- package/skills/camera-trap-processing/resources/activity-patterns-reference.md +113 -0
- package/skills/camera-trap-processing/resources/camtrapR-workflow-guide.md +130 -0
- package/skills/camera-trap-processing/resources/detection-event-definition-guide.md +89 -0
- package/skills/camera-trap-processing/scripts/estimate_activity.R +169 -0
- package/skills/camera-trap-processing/scripts/process_camtrap_data.R +179 -0
- package/skills/camera-trap-processing/scripts/process_camtrap_data.py +192 -0
- package/skills/community-ecology-ordination/SKILL.md +133 -0
- package/skills/community-ecology-ordination/examples/example-prompts.md +35 -0
- package/skills/community-ecology-ordination/resources/dissimilarity-metric-guide.md +53 -0
- package/skills/community-ecology-ordination/resources/nmds-interpretation-guide.md +104 -0
- package/skills/community-ecology-ordination/scripts/__pycache__/community_analysis.cpython-311.pyc +0 -0
- package/skills/community-ecology-ordination/scripts/community_analysis.R +143 -0
- package/skills/community-ecology-ordination/scripts/community_analysis.py +231 -0
- package/skills/ecological-data-foundation/SKILL.md +129 -0
- package/skills/ecological-data-foundation/examples/example-prompts.md +40 -0
- package/skills/ecological-data-foundation/resources/coordinate-cleaning-flags.md +66 -0
- package/skills/ecological-data-foundation/resources/darwin-core-glossary.md +91 -0
- package/skills/ecological-data-foundation/resources/data-citation-guide.md +265 -0
- package/skills/ecological-data-foundation/resources/gbif-data-citation-guide.md +193 -0
- package/skills/ecological-data-foundation/resources/qa-checklist.md +83 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/clean_occurrences.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_ebird.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_inat.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_iucn.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_obis.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/clean_occurrences.R +230 -0
- package/skills/ecological-data-foundation/scripts/clean_occurrences.py +268 -0
- package/skills/ecological-data-foundation/scripts/download_from_ebird.R +251 -0
- package/skills/ecological-data-foundation/scripts/download_from_ebird.py +364 -0
- package/skills/ecological-data-foundation/scripts/download_from_gbif.R +315 -0
- package/skills/ecological-data-foundation/scripts/download_from_gbif.py +407 -0
- package/skills/ecological-data-foundation/scripts/download_from_inat.R +238 -0
- package/skills/ecological-data-foundation/scripts/download_from_inat.py +304 -0
- package/skills/ecological-data-foundation/scripts/download_from_iucn.R +273 -0
- package/skills/ecological-data-foundation/scripts/download_from_iucn.py +344 -0
- package/skills/ecological-data-foundation/scripts/download_from_obis.R +248 -0
- package/skills/ecological-data-foundation/scripts/download_from_obis.py +318 -0
- package/skills/ecological-impact-assessment/SKILL.md +123 -0
- package/skills/ecological-impact-assessment/examples/example-prompts.md +32 -0
- package/skills/ecological-impact-assessment/resources/baci-design-guide.md +55 -0
- package/skills/ecological-impact-assessment/resources/fragmentation-metrics-reference.md +86 -0
- package/skills/ecological-impact-assessment/resources/pressure-index-template.md +78 -0
- package/skills/ecological-impact-assessment/resources/study-design-guide.md +168 -0
- package/skills/ecological-impact-assessment/scripts/baci_analysis.R +161 -0
- package/skills/ecological-impact-assessment/scripts/fragmentation_analysis.py +141 -0
- package/skills/ecological-impact-assessment/scripts/power_analysis_baci.R +274 -0
- package/skills/ecosystem-services-assessment/SKILL.md +125 -0
- package/skills/ecosystem-services-assessment/examples/example-prompts.md +24 -0
- package/skills/ecosystem-services-assessment/resources/es-indicator-reference.md +45 -0
- package/skills/ecosystem-services-assessment/resources/invest-parameter-guide.md +86 -0
- package/skills/ecosystem-services-assessment/resources/rusle-coefficients.md +88 -0
- package/skills/ecosystem-services-assessment/scripts/__pycache__/compute_es.cpython-311.pyc +0 -0
- package/skills/ecosystem-services-assessment/scripts/compute_es.py +189 -0
- package/skills/ecosystem-services-assessment/scripts/tradeoff_analysis.R +161 -0
- package/skills/environmental-time-series/SKILL.md +125 -0
- package/skills/environmental-time-series/examples/example-prompts.md +33 -0
- package/skills/environmental-time-series/resources/anomaly-indices-reference.md +88 -0
- package/skills/environmental-time-series/resources/bfast-parameter-guide.md +69 -0
- package/skills/environmental-time-series/scripts/__pycache__/recovery_trajectory.cpython-311.pyc +0 -0
- package/skills/environmental-time-series/scripts/__pycache__/trend_analysis.cpython-311.pyc +0 -0
- package/skills/environmental-time-series/scripts/recovery_trajectory.R +305 -0
- package/skills/environmental-time-series/scripts/recovery_trajectory.py +178 -0
- package/skills/environmental-time-series/scripts/trend_analysis.R +192 -0
- package/skills/environmental-time-series/scripts/trend_analysis.py +184 -0
- package/skills/geoprocessing-for-ecology/SKILL.md +123 -0
- package/skills/geoprocessing-for-ecology/examples/example-prompts.md +32 -0
- package/skills/geoprocessing-for-ecology/resources/crs-reference.md +62 -0
- package/skills/geoprocessing-for-ecology/resources/global-predictor-sources.md +331 -0
- package/skills/geoprocessing-for-ecology/resources/resampling-methods.md +57 -0
- package/skills/geoprocessing-for-ecology/scripts/__pycache__/download_predictors.cpython-311.pyc +0 -0
- package/skills/geoprocessing-for-ecology/scripts/download_predictors.R +239 -0
- package/skills/geoprocessing-for-ecology/scripts/download_predictors.py +379 -0
- package/skills/geoprocessing-for-ecology/scripts/stack_and_extract.R +224 -0
- package/skills/geoprocessing-for-ecology/scripts/stack_and_extract.py +172 -0
- package/skills/landscape-connectivity/SKILL.md +170 -0
- package/skills/landscape-connectivity/examples/example-prompts.md +96 -0
- package/skills/landscape-connectivity/examples/jaguar_mesoamerica_corridor_example.md +271 -0
- package/skills/landscape-connectivity/resources/circuitscape-parameter-guide.md +155 -0
- package/skills/landscape-connectivity/resources/graph-theory-for-ecology.md +134 -0
- package/skills/landscape-connectivity/resources/resistance-surface-guide.md +141 -0
- package/skills/landscape-connectivity/scripts/connectivity_analysis.py +387 -0
- package/skills/landscape-connectivity/scripts/connectivity_metrics.R +274 -0
- package/skills/landscape-connectivity/scripts/resistance_surface.R +239 -0
- package/skills/model-validation-and-uncertainty/SKILL.md +131 -0
- package/skills/model-validation-and-uncertainty/examples/example-prompts.md +30 -0
- package/skills/model-validation-and-uncertainty/resources/extrapolation-risk-guide.md +236 -0
- package/skills/model-validation-and-uncertainty/resources/metric-selection-guide.md +52 -0
- package/skills/model-validation-and-uncertainty/resources/threshold-selection-guide.md +64 -0
- package/skills/model-validation-and-uncertainty/scripts/__pycache__/validate_model.cpython-311.pyc +0 -0
- package/skills/model-validation-and-uncertainty/scripts/extrapolation_risk.R +315 -0
- package/skills/model-validation-and-uncertainty/scripts/validate_model.py +226 -0
- package/skills/model-validation-and-uncertainty/scripts/validate_sdm.R +162 -0
- package/skills/occupancy-and-detection/SKILL.md +126 -0
- package/skills/occupancy-and-detection/examples/example-prompts.md +33 -0
- package/skills/occupancy-and-detection/resources/detection-history-format.md +100 -0
- package/skills/occupancy-and-detection/resources/occupancy-study-design.md +47 -0
- package/skills/occupancy-and-detection/scripts/__pycache__/occupancy_analysis.cpython-311.pyc +0 -0
- package/skills/occupancy-and-detection/scripts/occupancy_analysis.R +160 -0
- package/skills/occupancy-and-detection/scripts/occupancy_analysis.py +159 -0
- package/skills/population-viability-analysis/SKILL.md +161 -0
- package/skills/population-viability-analysis/examples/african_elephant_pva_example.md +266 -0
- package/skills/population-viability-analysis/examples/example-prompts.md +95 -0
- package/skills/population-viability-analysis/resources/extinction-risk-thresholds.md +128 -0
- package/skills/population-viability-analysis/resources/matrix-model-guide.md +139 -0
- package/skills/population-viability-analysis/resources/sensitivity-elasticity-reference.md +182 -0
- package/skills/population-viability-analysis/scripts/matrix_pva.R +258 -0
- package/skills/population-viability-analysis/scripts/pva_analysis.py +442 -0
- package/skills/population-viability-analysis/scripts/stochastic_pva.R +353 -0
- package/skills/predictive-modeling-best-practices/SKILL.md +136 -0
- package/skills/predictive-modeling-best-practices/examples/example-prompts.md +58 -0
- package/skills/predictive-modeling-best-practices/resources/collinearity-decision-tree.md +65 -0
- package/skills/predictive-modeling-best-practices/resources/sampling-bias-correction.md +267 -0
- package/skills/predictive-modeling-best-practices/resources/spatial-cv-guide.md +73 -0
- package/skills/predictive-modeling-best-practices/scripts/__pycache__/spatial_cv.cpython-311.pyc +0 -0
- package/skills/predictive-modeling-best-practices/scripts/collinearity_check.R +112 -0
- package/skills/predictive-modeling-best-practices/scripts/spatial_cv.py +182 -0
- package/skills/reproducible-ecology-pipeline/SKILL.md +139 -0
- package/skills/reproducible-ecology-pipeline/examples/example-prompts.md +35 -0
- package/skills/reproducible-ecology-pipeline/resources/directory-structure-template.md +94 -0
- package/skills/reproducible-ecology-pipeline/resources/params-yaml-template.yaml +84 -0
- package/skills/reproducible-ecology-pipeline/resources/reproducibility-checklist-template.md +66 -0
- package/skills/reproducible-ecology-pipeline/scripts/generate_file_manifest.py +110 -0
- package/skills/reproducible-ecology-pipeline/scripts/init_project.sh +53 -0
- package/skills/spatial-prioritization/SKILL.md +162 -0
- package/skills/spatial-prioritization/examples/biodiversity_hotspot_prioritization_example.md +289 -0
- package/skills/spatial-prioritization/examples/example-prompts.md +93 -0
- package/skills/spatial-prioritization/resources/cost-surface-reference.md +130 -0
- package/skills/spatial-prioritization/resources/marxan-vs-prioritizr-comparison.md +125 -0
- package/skills/spatial-prioritization/resources/prioritizr-formulation-guide.md +188 -0
- package/skills/spatial-prioritization/resources/representation-targets-guide.md +186 -0
- package/skills/spatial-prioritization/scripts/prioritization_sensitivity.R +320 -0
- package/skills/spatial-prioritization/scripts/run_prioritization.R +336 -0
- package/skills/species-distribution-modeling/SKILL.md +139 -0
- package/skills/species-distribution-modeling/examples/example-prompts.md +36 -0
- package/skills/species-distribution-modeling/resources/algorithm-comparison.md +25 -0
- package/skills/species-distribution-modeling/resources/calibration-area-guide.md +71 -0
- package/skills/species-distribution-modeling/resources/climate-scenario-preparation.md +170 -0
- package/skills/species-distribution-modeling/resources/maxent-calibration-guide.md +211 -0
- package/skills/species-distribution-modeling/resources/sdm-checklist.md +37 -0
- package/skills/species-distribution-modeling/scripts/predict_distribution.R +236 -0
- package/skills/species-distribution-modeling/scripts/predict_distribution.py +286 -0
- package/skills/species-distribution-modeling/scripts/prepare_future_layers.R +351 -0
- package/skills/species-distribution-modeling/scripts/project_scenarios.R +220 -0
- package/skills/species-distribution-modeling/scripts/run_ensemble_sdm.R +99 -0
- package/skills/species-distribution-modeling/scripts/sdm_pipeline.py +318 -0
- package/skills/species-distribution-modeling/scripts/tune_maxnet.R +344 -0
- package/templates/SKILL_TEMPLATE.md +225 -0
- package/templates/checklists/data-submission-checklist.md +38 -0
- package/templates/checklists/post-analysis-checklist.md +55 -0
- package/templates/checklists/pre-analysis-checklist.md +31 -0
- package/templates/prompts/debug-skill.md +47 -0
- package/templates/prompts/invoke-skill.md +34 -0
- package/templates/prompts/invoke-workflow.md +45 -0
- package/templates/reports/technical-report-template.md +80 -0
- package/templates/scripts/logger_setup.R +79 -0
- package/templates/scripts/logger_setup.py +119 -0
- package/templates/scripts/params_loader.R +28 -0
- package/templates/scripts/params_loader.py +38 -0
- package/workflows/analyze-community-structure/WORKFLOW.md +72 -0
- package/workflows/analyze-environmental-change/WORKFLOW.md +73 -0
- package/workflows/assess-ecological-impact/WORKFLOW.md +75 -0
- package/workflows/assess-ecosystem-services/WORKFLOW.md +68 -0
- package/workflows/assess-landscape-connectivity/WORKFLOW.md +84 -0
- package/workflows/build-fire-risk-map/WORKFLOW.md +79 -0
- package/workflows/produce-technical-report/WORKFLOW.md +113 -0
- package/workflows/run-camera-trap-occupancy/WORKFLOW.md +87 -0
- package/workflows/run-conservation-prioritization/WORKFLOW.md +89 -0
- package/workflows/run-multispecies-screening/WORKFLOW.md +197 -0
- package/workflows/run-occupancy-analysis/WORKFLOW.md +74 -0
- package/workflows/run-population-viability/WORKFLOW.md +90 -0
- package/workflows/run-sdm-study/WORKFLOW.md +99 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
# Usage: Rscript connectivity_metrics.R <patches_shp> <output_dir> [dmax_m] [area_col]
|
|
5
|
+
#
|
|
6
|
+
# Computes graph-based landscape connectivity metrics (IIC, PC, dIIC, dPC,
|
|
7
|
+
# betweenness centrality) for a set of habitat patches.
|
|
8
|
+
#
|
|
9
|
+
# Arguments:
|
|
10
|
+
# patches_shp — Shapefile or GeoPackage of habitat patches (.shp or .gpkg)
|
|
11
|
+
# output_dir — Directory for output files
|
|
12
|
+
# dmax_m — Maximum dispersal distance in metres (default: 1000)
|
|
13
|
+
# area_col — Column name for patch area in patches layer (default: "area_ha")
|
|
14
|
+
#
|
|
15
|
+
# Outputs:
|
|
16
|
+
# patch_metrics.csv — IIC, PC, dIIC, dPC, BC per patch
|
|
17
|
+
# landscape_summary.csv — IIC, PC, number of components, largest component
|
|
18
|
+
# connectivity_graph.png — Network visualisation coloured by dPC
|
|
19
|
+
|
|
20
|
+
# ── Inline logger ─────────────────────────────────────────────────────────────
|
|
21
|
+
SKILL_NAME <- "landscape-connectivity"
|
|
22
|
+
.log_ts <- function() format(Sys.time(), "[%Y-%m-%d %H:%M:%S]")
|
|
23
|
+
log_info <- function(...) message(.log_ts(), " [INFO] ", sprintf(...))
|
|
24
|
+
log_warn <- function(...) message(.log_ts(), " [WARN] ", sprintf(...))
|
|
25
|
+
log_error<- function(...) message(.log_ts(), " [ERROR] ", sprintf(...))
|
|
26
|
+
log_step <- function(n, d) log_info("-- STEP %d: %s", n, d)
|
|
27
|
+
log_decision <- function(v, val, why) log_info("DECISION | %s = %s | %s", v, val, why)
|
|
28
|
+
dir.create("logs", recursive=TRUE, showWarnings=FALSE)
|
|
29
|
+
|
|
30
|
+
suppressPackageStartupMessages(library(sf))
|
|
31
|
+
suppressPackageStartupMessages(library(igraph))
|
|
32
|
+
suppressPackageStartupMessages(library(dplyr))
|
|
33
|
+
suppressPackageStartupMessages(library(ggplot2))
|
|
34
|
+
|
|
35
|
+
args <- commandArgs(trailingOnly = TRUE)
|
|
36
|
+
if (length(args) < 2) {
|
|
37
|
+
log_error("Argumentos insuficientes. Uso: Rscript connectivity_metrics.R <patches_shp> <output_dir> [dmax_m] [area_col]")
|
|
38
|
+
cat("Usage: Rscript connectivity_metrics.R <patches_shp> <output_dir>",
|
|
39
|
+
"[dmax_m] [area_col]\n")
|
|
40
|
+
quit(status = 1)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
patches_path <- args[1]
|
|
44
|
+
output_dir <- args[2]
|
|
45
|
+
dmax_m <- if (length(args) >= 3) as.numeric(args[3]) else 1000
|
|
46
|
+
area_col <- if (length(args) >= 4) args[4] else "area_ha"
|
|
47
|
+
|
|
48
|
+
# ── Input precondition checks ────────────────────────────────────────────────
|
|
49
|
+
if (!file.exists(patches_path)) {
|
|
50
|
+
log_error("Input nao encontrado: %s\nCausa provavel: arquivo shapefile/GeoPackage nao existe ou caminho incorreto\nVerifique: se o arquivo .shp ou .gpkg existe no caminho especificado\nSkill anterior: [nenhuma — etapa inicial ou saida de processamento GIS]", patches_path)
|
|
51
|
+
stop("Missing patches file: ", patches_path)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
log_decision("dmax_m", dmax_m,
|
|
55
|
+
"distancia maxima de dispersao em metros; define conectividade estrutural entre manchas")
|
|
56
|
+
log_decision("area_col", area_col,
|
|
57
|
+
"coluna de area das manchas; usada para calculo de IIC e PC ponderados por area")
|
|
58
|
+
|
|
59
|
+
dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
|
|
60
|
+
|
|
61
|
+
log_step(1, "Carregando camada de manchas de habitat")
|
|
62
|
+
# ── Load patches ────────────────────────────────────────────────────────────
|
|
63
|
+
patches <- tryCatch({
|
|
64
|
+
sf::st_read(patches_path, quiet = TRUE)
|
|
65
|
+
}, error = function(e) {
|
|
66
|
+
log_error("Falha ao ler shapefile de manchas: %s\nCausa provavel: arquivo corrompido ou CRS invalido\nVerifique: integridade do shapefile\nSkill anterior: [nenhuma]", conditionMessage(e))
|
|
67
|
+
stop(e)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
if (!area_col %in% names(patches)) {
|
|
71
|
+
# Compute area from geometry if column missing
|
|
72
|
+
log_warn("Coluna '%s' nao encontrada; calculando area a partir da geometria", area_col)
|
|
73
|
+
patches[[area_col]] <- as.numeric(st_area(patches)) / 10000 # m² → ha
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
n <- nrow(patches)
|
|
77
|
+
A <- sum(patches[[area_col]]) # total landscape area proxy (sum of patch areas)
|
|
78
|
+
log_info("Manchas carregadas: %d. Area total das manchas: %.1f ha", n, A)
|
|
79
|
+
|
|
80
|
+
if (n < 3) {
|
|
81
|
+
log_error("Minimo de 3 manchas necessario para analise de conectividade. Encontradas: %d\nCausa provavel: dado de entrada com poucas feicoes\nVerifique: arquivo de manchas e filtros de area minima\nSkill anterior: [nenhuma]", n)
|
|
82
|
+
stop("At least 3 patches required for connectivity analysis. ",
|
|
83
|
+
"Found ", n, " patches.")
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
log_step(2, "Calculando distancias entre centroides das manchas")
|
|
87
|
+
# ── Compute pairwise distances between patch centroids ──────────────────────
|
|
88
|
+
centroids <- st_centroid(patches)
|
|
89
|
+
# Reproject to projected CRS if needed
|
|
90
|
+
if (st_is_longlat(centroids)) {
|
|
91
|
+
log_warn("Entrada em CRS geografico. Reprojetando para UTM para calculo de distancias")
|
|
92
|
+
utm_crs <- 32700 + round((mean(st_coordinates(centroids)[, 1]) + 180) / 6) + 1
|
|
93
|
+
centroids <- st_transform(centroids, crs = utm_crs)
|
|
94
|
+
log_info("CRS UTM usado: EPSG:%d", utm_crs)
|
|
95
|
+
}
|
|
96
|
+
coords <- st_coordinates(centroids)
|
|
97
|
+
dist_mat <- as.matrix(dist(coords)) # metres
|
|
98
|
+
|
|
99
|
+
log_step(3, "Construindo grafo de adjacencia binaria")
|
|
100
|
+
# ── Build adjacency matrix (binary, distance < dmax) ───────────────────────
|
|
101
|
+
adj_binary <- (dist_mat < dmax_m) * 1
|
|
102
|
+
diag(adj_binary) <- 0
|
|
103
|
+
|
|
104
|
+
g_binary <- graph_from_adjacency_matrix(adj_binary, mode = "undirected",
|
|
105
|
+
weighted = FALSE)
|
|
106
|
+
sp_hops <- distances(g_binary, algorithm = "bfs")
|
|
107
|
+
|
|
108
|
+
n_edges <- sum(adj_binary) / 2
|
|
109
|
+
log_info("Grafo construido: %d nos, %d arestas (dmax = %g m)", n, n_edges, dmax_m)
|
|
110
|
+
if (n_edges == 0) {
|
|
111
|
+
log_warn("Nenhuma mancha conectada dentro de dmax = %g m; aumente dmax_m ou verifique CRS", dmax_m)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
log_step(4, "Calculando IIC — Integral Index of Connectivity")
|
|
115
|
+
# ── IIC (Integral Index of Connectivity) ────────────────────────────────────
|
|
116
|
+
areas <- patches[[area_col]]
|
|
117
|
+
|
|
118
|
+
compute_iic_internal <- function(areas_vec, sp_hops_mat) {
|
|
119
|
+
n <- length(areas_vec)
|
|
120
|
+
num <- 0
|
|
121
|
+
for (i in seq_len(n)) {
|
|
122
|
+
for (j in seq_len(n)) {
|
|
123
|
+
nij <- sp_hops_mat[i, j]
|
|
124
|
+
if (is.finite(nij)) {
|
|
125
|
+
num <- num + (areas_vec[i] * areas_vec[j]) / (1 + nij)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
num / sum(areas_vec)^2
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
IIC_full <- compute_iic_internal(areas, sp_hops)
|
|
133
|
+
log_info("IIC (paisagem completa) = %.6f", IIC_full)
|
|
134
|
+
|
|
135
|
+
log_step(5, "Calculando dIIC por mancha (leave-one-out)")
|
|
136
|
+
# ── dIIC per patch ──────────────────────────────────────────────────────────
|
|
137
|
+
log_info("Calculando dIIC para cada mancha (pode levar um momento)...")
|
|
138
|
+
dIIC_vec <- numeric(n)
|
|
139
|
+
for (i in seq_len(n)) {
|
|
140
|
+
areas_i <- areas[-i]
|
|
141
|
+
adj_i <- adj_binary[-i, -i]
|
|
142
|
+
g_i <- graph_from_adjacency_matrix(adj_i, mode = "undirected")
|
|
143
|
+
sp_i <- distances(g_i, algorithm = "bfs")
|
|
144
|
+
IIC_i <- compute_iic_internal(areas_i, sp_i)
|
|
145
|
+
dIIC_vec[i] <- (IIC_full - IIC_i) / IIC_full * 100
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
log_step(6, "Calculando PC — Probability of Connectivity")
|
|
149
|
+
# ── PC (Probability of Connectivity) ────────────────────────────────────────
|
|
150
|
+
# Dispersal probability: p = exp(-d / dmax) (negative exponential kernel)
|
|
151
|
+
# dmax serves as mean dispersal distance parameter
|
|
152
|
+
p_mat <- exp(-dist_mat / dmax_m)
|
|
153
|
+
diag(p_mat) <- 0
|
|
154
|
+
|
|
155
|
+
log_decision("dispersal_kernel", "exponencial negativo exp(-d/dmax)",
|
|
156
|
+
"nucleo padrao para probabilidade de colonizacao; ajuste se dados de telemetria disponíveis")
|
|
157
|
+
|
|
158
|
+
# Build weighted graph for shortest path probabilities
|
|
159
|
+
p_weight <- ifelse(p_mat > 0.001, -log(p_mat), Inf)
|
|
160
|
+
diag(p_weight) <- 0
|
|
161
|
+
g_prob <- graph_from_adjacency_matrix(p_weight, mode = "undirected",
|
|
162
|
+
weighted = TRUE)
|
|
163
|
+
sp_prob_dist <- distances(g_prob, algorithm = "dijkstra")
|
|
164
|
+
pij_star <- exp(-sp_prob_dist) # convert back to probability
|
|
165
|
+
|
|
166
|
+
PC_full <- sum(outer(areas, areas) * pij_star) / sum(areas)^2
|
|
167
|
+
log_info("PC (paisagem completa) = %.6f", PC_full)
|
|
168
|
+
|
|
169
|
+
log_step(7, "Calculando dPC por mancha (leave-one-out)")
|
|
170
|
+
# ── dPC per patch ────────────────────────────────────────────────────────────
|
|
171
|
+
log_info("Calculando dPC para cada mancha...")
|
|
172
|
+
dPC_vec <- numeric(n)
|
|
173
|
+
for (i in seq_len(n)) {
|
|
174
|
+
areas_i <- areas[-i]
|
|
175
|
+
p_mat_i <- p_mat[-i, -i]
|
|
176
|
+
pw_i <- ifelse(p_mat_i > 0.001, -log(p_mat_i), Inf)
|
|
177
|
+
g_i <- graph_from_adjacency_matrix(pw_i, mode = "undirected",
|
|
178
|
+
weighted = TRUE)
|
|
179
|
+
sp_i <- distances(g_i, algorithm = "dijkstra")
|
|
180
|
+
pij_i <- exp(-sp_i)
|
|
181
|
+
PC_i <- sum(outer(areas_i, areas_i) * pij_i) / sum(areas_i)^2
|
|
182
|
+
dPC_vec[i] <- (PC_full - PC_i) / PC_full * 100
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# ── Betweenness centrality ───────────────────────────────────────────────────
|
|
186
|
+
BC <- betweenness(g_binary, normalized = TRUE)
|
|
187
|
+
|
|
188
|
+
log_step(8, "Escrevendo resumo da paisagem e metricas por mancha")
|
|
189
|
+
# ── Landscape summary ────────────────────────────────────────────────────────
|
|
190
|
+
components_g <- components(g_binary)
|
|
191
|
+
largest_comp <- max(components_g$csize)
|
|
192
|
+
|
|
193
|
+
summary_df <- data.frame(
|
|
194
|
+
metric = c("IIC", "PC", "n_patches", "n_components",
|
|
195
|
+
"largest_component_size", "dmax_m"),
|
|
196
|
+
value = c(IIC_full, PC_full, n, components_g$no,
|
|
197
|
+
largest_comp, dmax_m)
|
|
198
|
+
)
|
|
199
|
+
sum_path <- file.path(output_dir, "landscape_summary.csv")
|
|
200
|
+
write.csv(summary_df, sum_path, row.names = FALSE)
|
|
201
|
+
log_info("Resumo da paisagem gravado: %s", sum_path)
|
|
202
|
+
|
|
203
|
+
if (components_g$no > n / 2) {
|
|
204
|
+
log_warn("Paisagem altamente fragmentada: %d componentes para %d manchas; considere aumentar dmax_m",
|
|
205
|
+
components_g$no, n)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# ── Patch metrics ────────────────────────────────────────────────────────────
|
|
209
|
+
patch_id <- if ("id" %in% names(patches)) patches$id else seq_len(n)
|
|
210
|
+
patch_metrics <- data.frame(
|
|
211
|
+
patch_id = patch_id,
|
|
212
|
+
area_ha = areas,
|
|
213
|
+
dIIC_pct = round(dIIC_vec, 4),
|
|
214
|
+
dPC_pct = round(dPC_vec, 4),
|
|
215
|
+
BC_norm = round(BC, 4),
|
|
216
|
+
component = components_g$membership
|
|
217
|
+
)
|
|
218
|
+
patch_metrics <- patch_metrics[order(-patch_metrics$dPC_pct), ]
|
|
219
|
+
|
|
220
|
+
patch_path <- file.path(output_dir, "patch_metrics.csv")
|
|
221
|
+
write.csv(patch_metrics, patch_path, row.names = FALSE)
|
|
222
|
+
log_info("Metricas por mancha gravadas: %s (%d manchas)", patch_path, n)
|
|
223
|
+
|
|
224
|
+
# Report top patches
|
|
225
|
+
log_info("Top 5 manchas por dPC:")
|
|
226
|
+
top5 <- head(patch_metrics[, c("patch_id", "area_ha", "dPC_pct", "BC_norm")], 5)
|
|
227
|
+
for (r in seq_len(nrow(top5))) {
|
|
228
|
+
log_info(" patch_id=%s | area=%.1f ha | dPC=%.2f%% | BC=%.4f",
|
|
229
|
+
top5$patch_id[r], top5$area_ha[r], top5$dPC_pct[r], top5$BC_norm[r])
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
log_step(9, "Gerando visualizacao do grafo de conectividade")
|
|
233
|
+
# ── Network visualisation ────────────────────────────────────────────────────
|
|
234
|
+
V(g_binary)$dPC <- dPC_vec
|
|
235
|
+
V(g_binary)$area <- areas
|
|
236
|
+
layout_coords <- coords[, 1:2]
|
|
237
|
+
|
|
238
|
+
patch_df <- data.frame(
|
|
239
|
+
x = coords[, 1],
|
|
240
|
+
y = coords[, 2],
|
|
241
|
+
dPC = dPC_vec,
|
|
242
|
+
area = areas / max(areas) * 3 + 0.5 # size scaled
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
edges_df <- as.data.frame(get.edgelist(g_binary))
|
|
246
|
+
edges_df$x_from <- coords[as.integer(edges_df$V1), 1]
|
|
247
|
+
edges_df$y_from <- coords[as.integer(edges_df$V1), 2]
|
|
248
|
+
edges_df$x_to <- coords[as.integer(edges_df$V2), 1]
|
|
249
|
+
edges_df$y_to <- coords[as.integer(edges_df$V2), 2]
|
|
250
|
+
|
|
251
|
+
p <- ggplot() +
|
|
252
|
+
geom_segment(data = edges_df,
|
|
253
|
+
aes(x = x_from, y = y_from, xend = x_to, yend = y_to),
|
|
254
|
+
colour = "grey60", linewidth = 0.5, alpha = 0.7) +
|
|
255
|
+
geom_point(data = patch_df,
|
|
256
|
+
aes(x = x, y = y, size = area, fill = dPC),
|
|
257
|
+
shape = 21, colour = "grey30") +
|
|
258
|
+
scale_fill_viridis_c(option = "plasma", name = "dPC (%)") +
|
|
259
|
+
scale_size_continuous(range = c(2, 8), guide = "none") +
|
|
260
|
+
labs(x = "Easting", y = "Northing",
|
|
261
|
+
title = sprintf("Connectivity graph (dmax = %g m, IIC = %.4f)",
|
|
262
|
+
dmax_m, IIC_full)) +
|
|
263
|
+
theme_minimal(base_size = 10) +
|
|
264
|
+
coord_equal()
|
|
265
|
+
|
|
266
|
+
plot_path <- file.path(output_dir, "connectivity_graph.png")
|
|
267
|
+
tryCatch({
|
|
268
|
+
ggsave(plot_path, p, width = 8, height = 7, dpi = 150)
|
|
269
|
+
log_info("Visualizacao do grafo salva: %s", plot_path)
|
|
270
|
+
}, error = function(e) {
|
|
271
|
+
log_warn("Nao foi possivel salvar visualizacao do grafo: %s", conditionMessage(e))
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
log_info("Analise de conectividade concluida")
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
# Usage: Rscript resistance_surface.R <landcover_tif> <resistance_csv> <output_dir>
|
|
5
|
+
# [dem_tif] [road_shp]
|
|
6
|
+
#
|
|
7
|
+
# Constructs a resistance surface from a land cover raster and resistance
|
|
8
|
+
# lookup table. Optionally adds slope-based and road-proximity penalties.
|
|
9
|
+
#
|
|
10
|
+
# Arguments:
|
|
11
|
+
# landcover_tif — Land cover raster (.tif) with integer class codes
|
|
12
|
+
# resistance_csv — CSV with columns: lc_code (int), resistance (num), description (char)
|
|
13
|
+
# output_dir — Directory for output files
|
|
14
|
+
# dem_tif — (optional) DEM raster for slope penalty
|
|
15
|
+
# road_shp — (optional) Road vector layer for proximity penalty
|
|
16
|
+
#
|
|
17
|
+
# Outputs:
|
|
18
|
+
# resistance_lc.tif — Land cover resistance only
|
|
19
|
+
# resistance_combined.tif — Combined resistance (lc + optional slope + road)
|
|
20
|
+
# resistance_stats.csv — Summary statistics of resistance surface
|
|
21
|
+
# resistance_map.png — Visualisation of combined resistance
|
|
22
|
+
|
|
23
|
+
# ── Inline logger ─────────────────────────────────────────────────────────────
|
|
24
|
+
SKILL_NAME <- "landscape-connectivity"
|
|
25
|
+
.log_ts <- function() format(Sys.time(), "[%Y-%m-%d %H:%M:%S]")
|
|
26
|
+
log_info <- function(...) message(.log_ts(), " [INFO] ", sprintf(...))
|
|
27
|
+
log_warn <- function(...) message(.log_ts(), " [WARN] ", sprintf(...))
|
|
28
|
+
log_error<- function(...) message(.log_ts(), " [ERROR] ", sprintf(...))
|
|
29
|
+
log_step <- function(n, d) log_info("-- STEP %d: %s", n, d)
|
|
30
|
+
log_decision <- function(v, val, why) log_info("DECISION | %s = %s | %s", v, val, why)
|
|
31
|
+
dir.create("logs", recursive=TRUE, showWarnings=FALSE)
|
|
32
|
+
|
|
33
|
+
suppressPackageStartupMessages(library(terra))
|
|
34
|
+
suppressPackageStartupMessages(library(sf))
|
|
35
|
+
suppressPackageStartupMessages(library(dplyr))
|
|
36
|
+
suppressPackageStartupMessages(library(ggplot2))
|
|
37
|
+
|
|
38
|
+
args <- commandArgs(trailingOnly = TRUE)
|
|
39
|
+
if (length(args) < 3) {
|
|
40
|
+
log_error("Argumentos insuficientes. Uso: Rscript resistance_surface.R <landcover_tif> <resistance_csv> <output_dir> [dem_tif] [road_shp]")
|
|
41
|
+
cat("Usage: Rscript resistance_surface.R <landcover_tif> <resistance_csv>",
|
|
42
|
+
"<output_dir> [dem_tif] [road_shp]\n")
|
|
43
|
+
quit(status = 1)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
lc_path <- args[1]
|
|
47
|
+
res_csv <- args[2]
|
|
48
|
+
output_dir <- args[3]
|
|
49
|
+
dem_path <- if (length(args) >= 4 && args[4] != "NA") args[4] else NULL
|
|
50
|
+
road_path <- if (length(args) >= 5 && args[5] != "NA") args[5] else NULL
|
|
51
|
+
|
|
52
|
+
# ── Input precondition checks ────────────────────────────────────────────────
|
|
53
|
+
if (!file.exists(lc_path)) {
|
|
54
|
+
log_error("Input nao encontrado: %s\nCausa provavel: raster de cobertura de terra nao existe\nVerifique: caminho e existencia do arquivo .tif\nSkill anterior: [nenhuma — entrada do usuario]", lc_path)
|
|
55
|
+
stop("Missing landcover_tif: ", lc_path)
|
|
56
|
+
}
|
|
57
|
+
if (!file.exists(res_csv)) {
|
|
58
|
+
log_error("Input nao encontrado: %s\nCausa provavel: CSV de resistencia nao existe\nVerifique: se o arquivo contem colunas lc_code e resistance\nSkill anterior: [nenhuma — entrada do usuario]", res_csv)
|
|
59
|
+
stop("Missing resistance_csv: ", res_csv)
|
|
60
|
+
}
|
|
61
|
+
if (!is.null(dem_path) && !file.exists(dem_path)) {
|
|
62
|
+
log_error("DEM opcional nao encontrado: %s\nCausa provavel: caminho incorreto para DEM\nVerifique: existencia do arquivo .tif do DEM\nSkill anterior: [nenhuma]", dem_path)
|
|
63
|
+
stop("Missing dem_tif: ", dem_path)
|
|
64
|
+
}
|
|
65
|
+
if (!is.null(road_path) && !file.exists(road_path)) {
|
|
66
|
+
log_error("Shapefile de estradas opcional nao encontrado: %s\nCausa provavel: caminho incorreto\nVerifique: existencia do shapefile de estradas\nSkill anterior: [nenhuma]", road_path)
|
|
67
|
+
stop("Missing road_shp: ", road_path)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
log_decision("dem_path", ifelse(is.null(dem_path), "NULL", dem_path),
|
|
71
|
+
"DEM incluido na penalidade de declividade; NULL = sem penalidade de relevo")
|
|
72
|
+
log_decision("road_path", ifelse(is.null(road_path), "NULL", road_path),
|
|
73
|
+
"shapefile de estradas para penalidade de proximidade; NULL = sem penalidade viaria")
|
|
74
|
+
|
|
75
|
+
dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
|
|
76
|
+
|
|
77
|
+
log_step(1, "Carregando raster de cobertura de terra e tabela de resistencia")
|
|
78
|
+
# ── Load inputs ──────────────────────────────────────────────────────────────
|
|
79
|
+
lc <- tryCatch({
|
|
80
|
+
rast(lc_path)
|
|
81
|
+
}, error = function(e) {
|
|
82
|
+
log_error("Falha ao carregar raster de cobertura de terra: %s\nCausa provavel: arquivo .tif corrompido ou formato invalido\nVerifique: integridade do raster\nSkill anterior: [nenhuma]", conditionMessage(e))
|
|
83
|
+
stop(e)
|
|
84
|
+
})
|
|
85
|
+
rt <- tryCatch({
|
|
86
|
+
read.csv(res_csv)
|
|
87
|
+
}, error = function(e) {
|
|
88
|
+
log_error("Falha ao ler CSV de resistencia: %s\nCausa provavel: formato incorreto\nVerifique: colunas lc_code e resistance\nSkill anterior: [nenhuma]", conditionMessage(e))
|
|
89
|
+
stop(e)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
required_cols <- c("lc_code", "resistance")
|
|
93
|
+
if (!all(required_cols %in% names(rt))) {
|
|
94
|
+
log_error("CSV de resistencia deve conter colunas: lc_code, resistance. Encontradas: %s\nCausa provavel: cabecalho do CSV incorreto\nVerifique: estrutura do arquivo resistance_csv\nSkill anterior: [nenhuma]",
|
|
95
|
+
paste(names(rt), collapse = ", "))
|
|
96
|
+
stop("resistance_csv must contain columns: lc_code, resistance. Found: ",
|
|
97
|
+
paste(names(rt), collapse = ", "))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
log_info("Raster de cobertura: %d linhas x %d colunas, CRS: %s",
|
|
101
|
+
nrow(lc), ncol(lc), crs(lc, describe = TRUE)$name)
|
|
102
|
+
log_info("Tabela de resistencia: %d classes", nrow(rt))
|
|
103
|
+
|
|
104
|
+
log_step(2, "Reclassificando cobertura de terra para resistencia")
|
|
105
|
+
# ── Reclassify land cover to resistance ──────────────────────────────────────
|
|
106
|
+
rcl_mat <- as.matrix(rt %>%
|
|
107
|
+
arrange(lc_code) %>%
|
|
108
|
+
mutate(from = lc_code - 0.5, to = lc_code + 0.5) %>%
|
|
109
|
+
select(from, to, resistance))
|
|
110
|
+
|
|
111
|
+
res_lc <- tryCatch({
|
|
112
|
+
classify(lc, rcl_mat, include.lowest = TRUE, right = FALSE)
|
|
113
|
+
}, error = function(e) {
|
|
114
|
+
log_error("Falha em reclassificacao: %s\nCausa provavel: codigos de classe no raster fora do intervalo da tabela\nVerifique: consistencia entre lc_code e valores do raster\nSkill anterior: [nenhuma]", conditionMessage(e))
|
|
115
|
+
stop(e)
|
|
116
|
+
})
|
|
117
|
+
names(res_lc) <- "resistance"
|
|
118
|
+
|
|
119
|
+
# Check for unmatched codes
|
|
120
|
+
lc_vals <- unique(values(lc, na.rm = TRUE))
|
|
121
|
+
unmatched <- setdiff(lc_vals, rt$lc_code)
|
|
122
|
+
if (length(unmatched) > 0) {
|
|
123
|
+
log_warn("%d codigos de cobertura de terra sem atribuicao de resistencia: %s",
|
|
124
|
+
length(unmatched), paste(unmatched, collapse = ", "))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
lc_path_out <- file.path(output_dir, "resistance_lc.tif")
|
|
128
|
+
writeRaster(res_lc, lc_path_out, overwrite = TRUE)
|
|
129
|
+
log_info("Resistencia LC gravada: %s", lc_path_out)
|
|
130
|
+
|
|
131
|
+
log_step(3, "Adicionando penalidades opcionais (declividade, estradas)")
|
|
132
|
+
# ── Optional: slope penalty ──────────────────────────────────────────────────
|
|
133
|
+
if (!is.null(dem_path)) {
|
|
134
|
+
log_info("Adicionando resistencia baseada em declividade...")
|
|
135
|
+
tryCatch({
|
|
136
|
+
dem <- rast(dem_path)
|
|
137
|
+
dem_proj <- project(dem, lc, method = "bilinear")
|
|
138
|
+
slope_deg <- terrain(dem_proj, v = "slope", unit = "degrees")
|
|
139
|
+
# Exponential cost: doubles every ~10° of slope
|
|
140
|
+
slope_res <- exp(slope_deg / 15)
|
|
141
|
+
slope_res <- slope_res / global(slope_res, "min", na.rm = TRUE)[[1]]
|
|
142
|
+
names(slope_res) <- "slope_resistance"
|
|
143
|
+
writeRaster(slope_res, file.path(output_dir, "slope_resistance.tif"),
|
|
144
|
+
overwrite = TRUE)
|
|
145
|
+
log_info("Resistencia de declividade gravada")
|
|
146
|
+
log_decision("slope_decay_param", 15,
|
|
147
|
+
"parametro de decaimento exponencial; resistencia dobra a cada 15 graus de declividade")
|
|
148
|
+
}, error = function(e) {
|
|
149
|
+
log_error("Falha ao processar DEM para declividade: %s\nCausa provavel: DEM com CRS incompativel ou valores invalidos\nVerifique: CRS e extensao do DEM\nSkill anterior: [nenhuma]", conditionMessage(e))
|
|
150
|
+
stop(e)
|
|
151
|
+
})
|
|
152
|
+
} else {
|
|
153
|
+
slope_res <- NULL
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# ── Optional: road proximity penalty ─────────────────────────────────────────
|
|
157
|
+
if (!is.null(road_path)) {
|
|
158
|
+
log_info("Adicionando resistencia de proximidade a estradas...")
|
|
159
|
+
tryCatch({
|
|
160
|
+
roads <- st_read(road_path, quiet = TRUE)
|
|
161
|
+
roads_v <- vect(roads)
|
|
162
|
+
# Compute distance to nearest road
|
|
163
|
+
road_dist <- distance(res_lc, roads_v)
|
|
164
|
+
road_dist <- project(road_dist, lc, method = "bilinear")
|
|
165
|
+
# Resistance peaks at road (dist=0) and decays with distance
|
|
166
|
+
# max_penalty = 10× at road edge, decays to 1 at 500 m
|
|
167
|
+
road_res <- pmax(1, 10 * exp(-road_dist / 200))
|
|
168
|
+
names(road_res) <- "road_resistance"
|
|
169
|
+
writeRaster(road_res, file.path(output_dir, "road_resistance.tif"),
|
|
170
|
+
overwrite = TRUE)
|
|
171
|
+
log_info("Resistencia de estradas gravada")
|
|
172
|
+
log_decision("road_max_penalty", 10,
|
|
173
|
+
"penalidade maxima na beira da estrada (10x); decai a 1 a 500 m")
|
|
174
|
+
}, error = function(e) {
|
|
175
|
+
log_error("Falha ao processar shapefile de estradas: %s\nCausa provavel: shapefile invalido ou CRS incompativel\nVerifique: integridade do shapefile de estradas\nSkill anterior: [nenhuma]", conditionMessage(e))
|
|
176
|
+
stop(e)
|
|
177
|
+
})
|
|
178
|
+
} else {
|
|
179
|
+
road_res <- NULL
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
log_step(4, "Combinando camadas de resistencia e normalizando")
|
|
183
|
+
# ── Combine resistance layers ─────────────────────────────────────────────────
|
|
184
|
+
combined <- res_lc
|
|
185
|
+
if (!is.null(slope_res)) combined <- combined * slope_res
|
|
186
|
+
if (!is.null(road_res)) combined <- combined * road_res
|
|
187
|
+
|
|
188
|
+
# Cap at maximum to avoid instability
|
|
189
|
+
max_cap <- 1000
|
|
190
|
+
combined[combined > max_cap] <- max_cap
|
|
191
|
+
log_decision("max_cap_resistance", max_cap,
|
|
192
|
+
"resistencia maxima para evitar instabilidade numerica nos algoritmos de menor custo")
|
|
193
|
+
|
|
194
|
+
# Rescale so minimum = 1
|
|
195
|
+
min_val <- global(combined, "min", na.rm = TRUE)[[1]]
|
|
196
|
+
combined <- combined / min_val
|
|
197
|
+
combined_path <- file.path(output_dir, "resistance_combined.tif")
|
|
198
|
+
writeRaster(combined, combined_path, overwrite = TRUE)
|
|
199
|
+
log_info("Resistencia combinada gravada: %s", combined_path)
|
|
200
|
+
|
|
201
|
+
log_step(5, "Calculando estatisticas da superficie de resistencia")
|
|
202
|
+
# ── Statistics ────────────────────────────────────────────────────────────────
|
|
203
|
+
vals <- values(combined, na.rm = TRUE)
|
|
204
|
+
stats_df <- data.frame(
|
|
205
|
+
statistic = c("min", "q25", "median", "mean", "q75", "q95", "max"),
|
|
206
|
+
value = round(quantile(vals, c(0, 0.25, 0.5, NA, 0.75, 0.95, 1),
|
|
207
|
+
na.rm = TRUE), 3)
|
|
208
|
+
)
|
|
209
|
+
stats_df$value[4] <- round(mean(vals, na.rm = TRUE), 3)
|
|
210
|
+
stats_path <- file.path(output_dir, "resistance_stats.csv")
|
|
211
|
+
write.csv(stats_df, stats_path, row.names = FALSE)
|
|
212
|
+
log_info("Estatisticas da superficie de resistencia:")
|
|
213
|
+
for (r in seq_len(nrow(stats_df))) {
|
|
214
|
+
log_info(" %s = %.3f", stats_df$statistic[r], stats_df$value[r])
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
log_step(6, "Gerando visualizacao do mapa de resistencia")
|
|
218
|
+
# ── Visualisation ─────────────────────────────────────────────────────────────
|
|
219
|
+
tryCatch({
|
|
220
|
+
plot_r <- aggregate(combined, fact = max(1, floor(nrow(combined) / 500)))
|
|
221
|
+
plot_df <- as.data.frame(plot_r, xy = TRUE)
|
|
222
|
+
names(plot_df)[3] <- "resistance"
|
|
223
|
+
|
|
224
|
+
p <- ggplot(plot_df, aes(x = x, y = y, fill = log1p(resistance))) +
|
|
225
|
+
geom_raster() +
|
|
226
|
+
scale_fill_viridis_c(option = "magma", name = "log(resistance + 1)") +
|
|
227
|
+
coord_equal() +
|
|
228
|
+
labs(x = "Easting", y = "Northing",
|
|
229
|
+
title = "Combined resistance surface") +
|
|
230
|
+
theme_minimal(base_size = 10)
|
|
231
|
+
|
|
232
|
+
map_path <- file.path(output_dir, "resistance_map.png")
|
|
233
|
+
ggsave(map_path, p, width = 8, height = 7, dpi = 150)
|
|
234
|
+
log_info("Mapa de resistencia salvo: %s", map_path)
|
|
235
|
+
}, error = function(e) {
|
|
236
|
+
log_warn("Nao foi possivel gerar mapa de resistencia: %s", conditionMessage(e))
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
log_info("Construcao da superficie de resistencia concluida")
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: model-validation-and-uncertainty
|
|
3
|
+
description: "Validates predictive models and quantifies uncertainty including AUC/TSS metrics, calibration, extrapolation risk (MOP/MESS/ExDet), and ensemble uncertainty maps. Use this skill when the user needs model performance evaluation, ROC curves, cross-validation results, calibration curves, overfitting diagnostics, prediction intervals, bootstrap uncertainty, sensitivity/specificity assessment, or extrapolation risk analysis."
|
|
4
|
+
skill_version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Skill: model-validation-and-uncertainty
|
|
8
|
+
|
|
9
|
+
**Domain:** Metrics · Calibration · Sensitivity · External validation · Uncertainty
|
|
10
|
+
**Phase:** 2 — Modeling
|
|
11
|
+
**Used by:** run-sdm-study, assess-ecological-impact, analyze-community-structure, build-fire-risk-map, run-occupancy-analysis
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Purpose
|
|
16
|
+
|
|
17
|
+
Guides the agent through rigorous evaluation of any fitted model: computing performance metrics on held-out data, assessing calibration, running sensitivity analyses, performing external validation, and quantifying and visualising prediction uncertainty.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## When to Invoke
|
|
22
|
+
|
|
23
|
+
- After any model is fitted, before results are reported
|
|
24
|
+
- When the user asks about model performance, reliability, or uncertainty
|
|
25
|
+
- When preparing results for publication or decision-making
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Inputs
|
|
30
|
+
|
|
31
|
+
| Input | Format | Required |
|
|
32
|
+
|-------|--------|----------|
|
|
33
|
+
| Fitted model object | RData, pkl, ONNX | Yes |
|
|
34
|
+
| Validation dataset (independent) | CSV | Yes |
|
|
35
|
+
| Training/CV predictions | CSV | Yes |
|
|
36
|
+
| Prediction surface/map | GeoTIFF | Conditional |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Outputs
|
|
41
|
+
|
|
42
|
+
| Output | Description |
|
|
43
|
+
|--------|-------------|
|
|
44
|
+
| `performance_metrics.csv` | Full metric table (train, CV, test) |
|
|
45
|
+
| `calibration_plot.png` | Observed vs predicted calibration curve |
|
|
46
|
+
| `roc_curve.png` | ROC curve with AUC (for classifiers) |
|
|
47
|
+
| `sensitivity_report.md` | Effect of parameter/predictor perturbation |
|
|
48
|
+
| `uncertainty_map.tif` | Spatial uncertainty (SD across ensemble or bootstrap) |
|
|
49
|
+
| `validation_report.md` | Comprehensive validation narrative |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Steps
|
|
54
|
+
|
|
55
|
+
### 1. Select Appropriate Metrics
|
|
56
|
+
|
|
57
|
+
| Task | Primary metrics | Secondary |
|
|
58
|
+
|------|----------------|-----------|
|
|
59
|
+
| Binary classification (SDM) | AUC-ROC, TSS, Boyce index | Sensitivity, specificity |
|
|
60
|
+
| Regression (abundance, biomass) | RMSE, MAE, R² | Bias, MAPE |
|
|
61
|
+
| Occupancy | AUC, WAIC, posterior predictive checks | |
|
|
62
|
+
| Community ordination | Stress (NMDS), R² (RDA/CCA) | Procrustes |
|
|
63
|
+
| Count / Poisson | RMSE, Pseudo-R², dispersion | |
|
|
64
|
+
|
|
65
|
+
### 2. Compute Metrics on Train, CV, and Test Sets
|
|
66
|
+
- Report all three to diagnose overfitting (train >> CV/test gap)
|
|
67
|
+
- Report metric mean ± SD across CV folds
|
|
68
|
+
|
|
69
|
+
### 3. Assess Calibration
|
|
70
|
+
- For classifiers: plot mean predicted probability vs. observed occurrence rate across bins
|
|
71
|
+
- For regression: plot predicted vs. observed scatter
|
|
72
|
+
- Compute calibration slope and intercept; values near 1 and 0 are ideal
|
|
73
|
+
|
|
74
|
+
### 4. Threshold Selection (for binary predictions)
|
|
75
|
+
- Maximise TSS (Youden's J)
|
|
76
|
+
- Maximise Sensitivity + Specificity
|
|
77
|
+
- Fixed prevalence-based threshold
|
|
78
|
+
- Document chosen threshold and rationale
|
|
79
|
+
|
|
80
|
+
### 5. Variable Importance and Response Curves
|
|
81
|
+
- Compute permutation importance for each predictor
|
|
82
|
+
- Plot partial dependence / marginal effect curves for top predictors
|
|
83
|
+
- Flag predictors with response curves showing ecologically implausible patterns
|
|
84
|
+
|
|
85
|
+
### 6. Sensitivity Analysis
|
|
86
|
+
- Perturb each hyperparameter by ±10%; measure effect on primary metric
|
|
87
|
+
- Remove each predictor in turn; measure effect (jackknife importance)
|
|
88
|
+
- Assess sensitivity to background/pseudo-absence sampling strategy (for SDMs)
|
|
89
|
+
|
|
90
|
+
### 7. External Validation
|
|
91
|
+
- Apply model to an independent dataset (different time period, different region)
|
|
92
|
+
- Report metric degradation; quantify transferability
|
|
93
|
+
- Flag models with poor transferability before applying to novel conditions
|
|
94
|
+
|
|
95
|
+
### 8. Uncertainty Quantification
|
|
96
|
+
- Ensemble SD: standard deviation across ensemble members or bootstrap replicates
|
|
97
|
+
- Bayesian credible intervals: for Bayesian models, report posterior predictive intervals
|
|
98
|
+
- Map uncertainty spatially where relevant
|
|
99
|
+
- Report regions of high uncertainty explicitly
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Key Decisions to Document
|
|
104
|
+
|
|
105
|
+
- Primary and secondary metrics chosen and rationale
|
|
106
|
+
- Threshold selection method
|
|
107
|
+
- External validation dataset description
|
|
108
|
+
- Uncertainty quantification method
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Tools and Libraries
|
|
113
|
+
|
|
114
|
+
**R:** `ROCR`, `PresenceAbsence`, `dismo`, `gbm`, `boot`, `brms`
|
|
115
|
+
**Python:** `sklearn.metrics`, `scikit-learn`, `shap`, `pysdm`
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Resources
|
|
120
|
+
|
|
121
|
+
- `resources/metric-selection-guide.md` — which metrics for which task
|
|
122
|
+
- `resources/threshold-selection-guide.md` — threshold methods compared
|
|
123
|
+
- `examples/` — calibration plot and uncertainty map examples
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Notes
|
|
128
|
+
|
|
129
|
+
- Never report only training performance
|
|
130
|
+
- Boyce index is preferred over AUC for presence-only SDMs
|
|
131
|
+
- For small samples, report bootstrapped CIs around all metrics
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Example Invocation Prompts — model-validation-and-uncertainty
|
|
2
|
+
|
|
3
|
+
## Full Validation Suite
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Load skill: model-validation-and-uncertainty
|
|
7
|
+
Task: Validate the SDM ensemble for Chrysocyon brachyurus.
|
|
8
|
+
Files:
|
|
9
|
+
- models/ensemble_predictions.csv (columns: site_id, observed, predicted_prob)
|
|
10
|
+
- models/cv_fold_predictions.csv (columns: fold, observed, predicted_prob)
|
|
11
|
+
- outputs/suitability_ensemble.tif
|
|
12
|
+
- outputs/suitability_sd.tif (uncertainty)
|
|
13
|
+
|
|
14
|
+
Run:
|
|
15
|
+
1. AUC-ROC and TSS on CV folds and independent test set.
|
|
16
|
+
2. Boyce index on test set.
|
|
17
|
+
3. Calibration plot (10 bins).
|
|
18
|
+
4. Threshold selection: MaxTSS and P10.
|
|
19
|
+
5. Report: performance_metrics.csv, calibration_plot.png, validation_report.md
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Calibration Check Only
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Load skill: model-validation-and-uncertainty
|
|
26
|
+
Task: Calibration assessment only.
|
|
27
|
+
Predictions: outputs/glm_predictions.csv (obs: 0/1, pred: probability 0-1).
|
|
28
|
+
Generate calibration plot with 10 bins. Report calibration slope and intercept.
|
|
29
|
+
A well-calibrated model should have slope ≈ 1 and intercept ≈ 0.
|
|
30
|
+
```
|