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,351 @@
|
|
|
1
|
+
# ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
# Usage: Rscript prepare_future_layers.R <current_stack.tif> <future_layers_dir> <study_area.shp> <output_dir> [ssp_label] [year_label]
|
|
5
|
+
|
|
6
|
+
# ── Inline logger ─────────────────────────────────────────────────────────────
|
|
7
|
+
SKILL_NAME <- "species-distribution-modeling"
|
|
8
|
+
.log_ts <- function() format(Sys.time(), "[%Y-%m-%d %H:%M:%S]")
|
|
9
|
+
log_info <- function(...) message(.log_ts(), " [INFO] ", sprintf(...))
|
|
10
|
+
log_warn <- function(...) message(.log_ts(), " [WARN] ", sprintf(...))
|
|
11
|
+
log_error<- function(...) message(.log_ts(), " [ERROR] ", sprintf(...))
|
|
12
|
+
log_step <- function(n, d) log_info("-- STEP %d: %s", n, d)
|
|
13
|
+
log_decision <- function(v, val, why) log_info("DECISION | %s = %s | %s", v, val, why)
|
|
14
|
+
dir.create("logs", recursive=TRUE, showWarnings=FALSE)
|
|
15
|
+
|
|
16
|
+
#
|
|
17
|
+
# Arguments:
|
|
18
|
+
# current_stack.tif : Reference calibration raster stack (sets CRS, resolution, extent)
|
|
19
|
+
# future_layers_dir : Directory containing future climate .tif files (one per variable)
|
|
20
|
+
# study_area.shp : Shapefile / GeoPackage defining the projection area (G area)
|
|
21
|
+
# output_dir : Directory to write the prepared future stack (created if absent)
|
|
22
|
+
# ssp_label : Optional SSP label for output filename (default: "ssp245")
|
|
23
|
+
# year_label : Optional time horizon label for output filename (default: "2050")
|
|
24
|
+
#
|
|
25
|
+
# Output:
|
|
26
|
+
# future_stack_{ssp_label}_{year_label}.tif — prepared stack ready for model projection
|
|
27
|
+
|
|
28
|
+
suppressPackageStartupMessages(library(terra))
|
|
29
|
+
suppressPackageStartupMessages(library(sf))
|
|
30
|
+
|
|
31
|
+
# ── 1. Parse arguments ──────────────────────────────────────────────────────
|
|
32
|
+
log_step(1, "Analisar argumentos da linha de comando")
|
|
33
|
+
args <- commandArgs(trailingOnly = TRUE)
|
|
34
|
+
|
|
35
|
+
if (length(args) < 4) {
|
|
36
|
+
log_warn("Menos de 4 argumentos. Usando caminhos padrao para teste.")
|
|
37
|
+
current_tif <- "data/predictors/env_train.tif"
|
|
38
|
+
future_dir <- "data/chelsa_future/ssp245_2050/"
|
|
39
|
+
study_area_path <- "data/study_area/g_area.shp"
|
|
40
|
+
output_dir <- "output/future_layers"
|
|
41
|
+
ssp_label <- "ssp245"
|
|
42
|
+
year_label <- "2050"
|
|
43
|
+
} else {
|
|
44
|
+
current_tif <- args[1]
|
|
45
|
+
future_dir <- args[2]
|
|
46
|
+
study_area_path <- args[3]
|
|
47
|
+
output_dir <- args[4]
|
|
48
|
+
ssp_label <- if (length(args) >= 5) args[5] else "ssp245"
|
|
49
|
+
year_label <- if (length(args) >= 6) args[6] else "2050"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
log_info("Script: prepare_future_layers.R | Skill: %s", SKILL_NAME)
|
|
53
|
+
log_info("Stack de calibracao : %s", current_tif)
|
|
54
|
+
log_info("Diretorio futuro : %s", future_dir)
|
|
55
|
+
log_info("Area de estudo : %s", study_area_path)
|
|
56
|
+
log_info("Output dir : %s", output_dir)
|
|
57
|
+
log_info("SSP label : %s", ssp_label)
|
|
58
|
+
log_info("Year label : %s", year_label)
|
|
59
|
+
|
|
60
|
+
log_decision("ssp_label", ssp_label, "cenario SSP para rotular o arquivo de saida")
|
|
61
|
+
log_decision("year_label", year_label, "horizonte temporal para rotular o arquivo de saida")
|
|
62
|
+
|
|
63
|
+
# ── Input precondition checks ─────────────────────────────────────────────────
|
|
64
|
+
if (!file.exists(current_tif)) {
|
|
65
|
+
log_error(
|
|
66
|
+
"Input nao encontrado: %s\nCausa provavel: arquivo nao gerado pelo passo anterior.\nVerifique a saida de: species-distribution-modeling (prepare_predictors ou similar)\nSkill anterior: species-distribution-modeling",
|
|
67
|
+
current_tif
|
|
68
|
+
)
|
|
69
|
+
stop("Calibration stack not found: ", current_tif)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!file.exists(study_area_path)) {
|
|
73
|
+
log_error(
|
|
74
|
+
"Input nao encontrado: %s\nCausa provavel: shapefile de area de estudo ausente.\nVerifique a saida de: ecological-data-foundation ou etapa de definicao da G area.\nSkill anterior: species-distribution-modeling",
|
|
75
|
+
study_area_path
|
|
76
|
+
)
|
|
77
|
+
stop("Study area file not found: ", study_area_path)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!dir.exists(future_dir)) {
|
|
81
|
+
log_error(
|
|
82
|
+
"Diretorio de camadas futuras nao encontrado: %s\nCausa provavel: camadas CHELSA/WorldClim futuras nao baixadas.\nBaixe os GeoTIFFs futuros e coloque em: %s\nSkill anterior: species-distribution-modeling",
|
|
83
|
+
future_dir, future_dir
|
|
84
|
+
)
|
|
85
|
+
stop("Future layers directory not found: ", future_dir)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# ── 2. Create output directory ───────────────────────────────────────────────
|
|
89
|
+
log_step(2, "Criar diretorio de saida")
|
|
90
|
+
dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
|
|
91
|
+
log_info("Diretorio de saida pronto: %s", output_dir)
|
|
92
|
+
|
|
93
|
+
# ── 3. Load reference calibration stack ─────────────────────────────────────
|
|
94
|
+
log_step(3, "Carregar stack de calibracao de referencia")
|
|
95
|
+
ref_stack <- tryCatch({
|
|
96
|
+
rast(current_tif)
|
|
97
|
+
}, error = function(e) {
|
|
98
|
+
log_error(
|
|
99
|
+
"Falha ao carregar stack de calibracao '%s': %s\nCausa provavel: arquivo GeoTIFF corrompido ou formato nao suportado pelo terra.\nSkill anterior: species-distribution-modeling",
|
|
100
|
+
current_tif, conditionMessage(e)
|
|
101
|
+
)
|
|
102
|
+
stop(e)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
log_info("CRS : %s", crs(ref_stack, describe = TRUE)$name)
|
|
106
|
+
log_info("Extent : %s", paste(round(as.vector(ext(ref_stack)), 3), collapse = ", "))
|
|
107
|
+
log_info("Res : %s", paste(res(ref_stack), collapse = " x "))
|
|
108
|
+
log_info("Layers : %s", paste(names(ref_stack), collapse = ", "))
|
|
109
|
+
|
|
110
|
+
# ── 4. Load study area (G area) ──────────────────────────────────────────────
|
|
111
|
+
log_step(4, "Carregar area de estudo (G area)")
|
|
112
|
+
study_area <- tryCatch({
|
|
113
|
+
vect(study_area_path)
|
|
114
|
+
}, error = function(e) {
|
|
115
|
+
log_error(
|
|
116
|
+
"Falha ao carregar area de estudo '%s': %s\nCausa provavel: shapefile corrompido, projecao invalida ou formato nao suportado.\nVerifique: ogrinfo '%s'\nSkill anterior: species-distribution-modeling",
|
|
117
|
+
study_area_path, conditionMessage(e), study_area_path
|
|
118
|
+
)
|
|
119
|
+
stop(e)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
# Reproject study area to match calibration CRS if needed
|
|
123
|
+
if (!identical(crs(study_area), crs(ref_stack))) {
|
|
124
|
+
log_info("Reprojetando area de estudo para o CRS de calibracao...")
|
|
125
|
+
study_area <- tryCatch(
|
|
126
|
+
project(study_area, crs(ref_stack)),
|
|
127
|
+
error = function(e) {
|
|
128
|
+
log_error(
|
|
129
|
+
"Falha ao reprojetar area de estudo: %s\nCausa provavel: CRS invalido ou incompativel.\nSkill anterior: species-distribution-modeling",
|
|
130
|
+
conditionMessage(e)
|
|
131
|
+
)
|
|
132
|
+
stop(e)
|
|
133
|
+
}
|
|
134
|
+
)
|
|
135
|
+
log_info("Reprojecao concluida.")
|
|
136
|
+
} else {
|
|
137
|
+
log_info("CRS da area de estudo ja coincide com o de calibracao. Sem reprojecao necessaria.")
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# ── 5. Load future climate layers ────────────────────────────────────────────
|
|
141
|
+
log_step(5, "Carregar camadas climaticas futuras")
|
|
142
|
+
future_files <- list.files(future_dir, pattern = "\\.tif$", full.names = TRUE,
|
|
143
|
+
recursive = FALSE)
|
|
144
|
+
if (length(future_files) == 0) {
|
|
145
|
+
log_error(
|
|
146
|
+
"Nenhum arquivo .tif encontrado em: %s\nCausa provavel: camadas futuras nao baixadas ou extensao diferente de .tif.\nVerifique o conteudo do diretorio.\nSkill anterior: species-distribution-modeling",
|
|
147
|
+
future_dir
|
|
148
|
+
)
|
|
149
|
+
stop("No .tif files found in: ", future_dir)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
log_info("Arquivos de camada futura encontrados: %d", length(future_files))
|
|
153
|
+
|
|
154
|
+
# Stack all future layers
|
|
155
|
+
future_raw <- tryCatch({
|
|
156
|
+
rast(future_files)
|
|
157
|
+
}, error = function(e) {
|
|
158
|
+
log_error(
|
|
159
|
+
"Falha ao empilhar camadas futuras: %s\nCausa provavel: GeoTIFFs corrompidos ou com extents incompativeis.\nVerifique: gdalinfo nos arquivos em %s\nSkill anterior: species-distribution-modeling",
|
|
160
|
+
conditionMessage(e), future_dir
|
|
161
|
+
)
|
|
162
|
+
stop(e)
|
|
163
|
+
})
|
|
164
|
+
log_info("Nomes das camadas futuras (brutos): %s", paste(names(future_raw), collapse = ", "))
|
|
165
|
+
|
|
166
|
+
# ── 6. Rename future layers to match calibration ─────────────────────────────
|
|
167
|
+
log_step(6, "Renomear camadas futuras para coincidir com a calibracao")
|
|
168
|
+
# Strategy: if layer names differ but count matches, rename by position.
|
|
169
|
+
# If counts differ, attempt name matching. Fail clearly if neither works.
|
|
170
|
+
|
|
171
|
+
ref_names <- names(ref_stack)
|
|
172
|
+
future_names <- names(future_raw)
|
|
173
|
+
|
|
174
|
+
if (setequal(ref_names, future_names)) {
|
|
175
|
+
# Names match already — reorder to calibration order
|
|
176
|
+
future_raw <- future_raw[[ref_names]]
|
|
177
|
+
log_info("Nomes das camadas coincidentes. Reordenados conforme calibracao.")
|
|
178
|
+
log_decision("rename_strategy", "reorder", "nomes identicos, apenas reordenados")
|
|
179
|
+
|
|
180
|
+
} else if (length(future_names) == length(ref_names) &&
|
|
181
|
+
!setequal(ref_names, future_names)) {
|
|
182
|
+
# Same count but different names — rename by position (common with CHELSA long names)
|
|
183
|
+
log_warn(
|
|
184
|
+
"Nomes das camadas diferem da calibracao. Renomeando por posicao (%d camadas).",
|
|
185
|
+
length(ref_names)
|
|
186
|
+
)
|
|
187
|
+
log_info("Nomes antigos: %s", paste(future_names, collapse = ", "))
|
|
188
|
+
log_info("Novos nomes : %s", paste(ref_names, collapse = ", "))
|
|
189
|
+
log_decision("rename_strategy", "by_position", "mesmo numero de camadas mas nomes diferentes (comum com CHELSA)")
|
|
190
|
+
names(future_raw) <- ref_names
|
|
191
|
+
|
|
192
|
+
} else {
|
|
193
|
+
# Different count — try to find matching layers by partial name
|
|
194
|
+
log_warn("Numero de camadas difere. Tentando correspondencia por nome parcial...")
|
|
195
|
+
matched <- sapply(ref_names, function(rn) {
|
|
196
|
+
idx <- which(grepl(rn, future_names, fixed = TRUE))
|
|
197
|
+
if (length(idx) == 1) idx else NA_integer_
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
if (any(is.na(matched))) {
|
|
201
|
+
missing_layers <- ref_names[is.na(matched)]
|
|
202
|
+
log_error(
|
|
203
|
+
"Nao e possivel associar camadas futuras as de calibracao.\nCalibracao espera: %s\nCamadas futuras: %s\nSem correspondencia para: %s\nAcao: renomeie os .tif futuros para coincidir exatamente com os nomes de calibracao.\nSkill anterior: species-distribution-modeling",
|
|
204
|
+
paste(ref_names, collapse = ", "),
|
|
205
|
+
paste(future_names, collapse = ", "),
|
|
206
|
+
paste(missing_layers, collapse = ", ")
|
|
207
|
+
)
|
|
208
|
+
stop(
|
|
209
|
+
"Cannot match future layers to calibration layers.\n",
|
|
210
|
+
" Calibration expects: ", paste(ref_names, collapse = ", "), "\n",
|
|
211
|
+
" Future layers found: ", paste(future_names, collapse = ", "), "\n",
|
|
212
|
+
" Could not find match for: ", paste(missing_layers, collapse = ", "), "\n",
|
|
213
|
+
" Action: rename future .tif files to match calibration layer names exactly."
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
future_raw <- future_raw[[matched]]
|
|
218
|
+
names(future_raw) <- ref_names
|
|
219
|
+
log_info("Camadas associadas por nome parcial. Reordenadas conforme calibracao.")
|
|
220
|
+
log_decision("rename_strategy", "partial_name_match", "contagem diferente; correspondencia por substring")
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# ── 7. Reproject to calibration CRS ──────────────────────────────────────────
|
|
224
|
+
log_step(7, "Reprojetar stack futuro para o CRS de calibracao")
|
|
225
|
+
if (!identical(crs(future_raw), crs(ref_stack))) {
|
|
226
|
+
log_info("Reprojetando stack futuro para CRS de calibracao...")
|
|
227
|
+
log_decision("resample_method_reproj", "bilinear", "interpolacao bilinear para dados continuos de clima")
|
|
228
|
+
future_raw <- tryCatch(
|
|
229
|
+
project(future_raw, crs(ref_stack), method = "bilinear"),
|
|
230
|
+
error = function(e) {
|
|
231
|
+
log_error(
|
|
232
|
+
"Falha ao reprojetar stack futuro: %s\nCausa provavel: CRS invalido ou falta de memoria para o raster.\nSkill anterior: species-distribution-modeling",
|
|
233
|
+
conditionMessage(e)
|
|
234
|
+
)
|
|
235
|
+
stop(e)
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
log_info("Reprojecao concluida.")
|
|
239
|
+
} else {
|
|
240
|
+
log_info("CRS ja coincide com calibracao. Sem reprojecao necessaria.")
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
# ── 8. Crop and mask to study area (G area) ───────────────────────────────────
|
|
244
|
+
log_step(8, "Recortar e mascarar para a area de estudo")
|
|
245
|
+
tryCatch({
|
|
246
|
+
future_cropped <- crop(future_raw, study_area)
|
|
247
|
+
future_masked <- mask(future_cropped, study_area)
|
|
248
|
+
log_info("Recorte e mascara aplicados. Celulas validas apos mascara: nao calculado (use global(future_masked, 'notNA')).")
|
|
249
|
+
}, error = function(e) {
|
|
250
|
+
log_error(
|
|
251
|
+
"Falha ao recortar/mascarar o stack futuro: %s\nCausa provavel: extent da area de estudo fora do extent do raster futuro.\nVerifique a projecao e o extent dos arquivos.\nSkill anterior: species-distribution-modeling",
|
|
252
|
+
conditionMessage(e)
|
|
253
|
+
)
|
|
254
|
+
stop(e)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
# ── 9. Resample to exactly match calibration grid ────────────────────────────
|
|
258
|
+
log_step(9, "Reamostrar para coincidir exatamente com o grid de calibracao")
|
|
259
|
+
log_decision("resample_method", "bilinear", "interpolacao bilinear para dados continuos de clima")
|
|
260
|
+
future_resampled <- tryCatch(
|
|
261
|
+
resample(future_masked, ref_stack, method = "bilinear"),
|
|
262
|
+
error = function(e) {
|
|
263
|
+
log_error(
|
|
264
|
+
"Falha na reamostragem do stack futuro: %s\nCausa provavel: incompatibilidade de CRS ou extent entre stack futuro e de calibracao.\nVerifique os passos 7 e 8.\nSkill anterior: species-distribution-modeling",
|
|
265
|
+
conditionMessage(e)
|
|
266
|
+
)
|
|
267
|
+
stop(e)
|
|
268
|
+
}
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# ── 10. Geometry verification ────────────────────────────────────────────────
|
|
272
|
+
log_step(10, "Verificar geometria do stack futuro contra o de calibracao")
|
|
273
|
+
geom_ok <- tryCatch(
|
|
274
|
+
compareGeom(ref_stack, future_resampled, stopOnError = FALSE,
|
|
275
|
+
res = TRUE, orig = TRUE, crs = TRUE),
|
|
276
|
+
error = function(e) {
|
|
277
|
+
log_warn("compareGeom retornou erro: %s. Prosseguindo com cautela.", conditionMessage(e))
|
|
278
|
+
FALSE
|
|
279
|
+
}
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
if (!geom_ok) {
|
|
283
|
+
log_error(
|
|
284
|
+
"Verificacao de geometria FALHOU apos reamostragem.\nCalibracao: ext=%s res=%s crs=%s\nFuturo : ext=%s res=%s crs=%s\nVerifique incompatibilidades de extent ou datum e re-execute.\nSkill anterior: species-distribution-modeling",
|
|
285
|
+
as.character(ext(ref_stack)),
|
|
286
|
+
paste(res(ref_stack), collapse = "x"),
|
|
287
|
+
crs(ref_stack, describe = TRUE)$name,
|
|
288
|
+
as.character(ext(future_resampled)),
|
|
289
|
+
paste(res(future_resampled), collapse = "x"),
|
|
290
|
+
crs(future_resampled, describe = TRUE)$name
|
|
291
|
+
)
|
|
292
|
+
stop(
|
|
293
|
+
"Geometry verification FAILED after resampling.\n",
|
|
294
|
+
" Calibration: ext=", as.character(ext(ref_stack)),
|
|
295
|
+
" res=", paste(res(ref_stack), collapse="x"),
|
|
296
|
+
" crs=", crs(ref_stack, describe=TRUE)$name, "\n",
|
|
297
|
+
" Future: ext=", as.character(ext(future_resampled)),
|
|
298
|
+
" res=", paste(res(future_resampled), collapse="x"),
|
|
299
|
+
" crs=", crs(future_resampled, describe=TRUE)$name, "\n",
|
|
300
|
+
" Check for extent or datum mismatches and re-run."
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
log_info("Verificacao de geometria PASSOU.")
|
|
304
|
+
|
|
305
|
+
# ── 11. Final layer name verification ────────────────────────────────────────
|
|
306
|
+
log_step(11, "Verificar nomes finais das camadas")
|
|
307
|
+
if (!identical(names(future_resampled), names(ref_stack))) {
|
|
308
|
+
name_diff <- setdiff(names(future_resampled), names(ref_stack))
|
|
309
|
+
log_error(
|
|
310
|
+
"Discrepancia de nomes de camadas no stack final.\nEsperado: %s\nObtido : %s\nDivergentes: %s\nSkill anterior: species-distribution-modeling",
|
|
311
|
+
paste(names(ref_stack), collapse = ", "),
|
|
312
|
+
paste(names(future_resampled), collapse = ", "),
|
|
313
|
+
paste(name_diff, collapse = ", ")
|
|
314
|
+
)
|
|
315
|
+
stop(
|
|
316
|
+
"Layer name mismatch in final stack.\n",
|
|
317
|
+
" Expected: ", paste(names(ref_stack), collapse = ", "), "\n",
|
|
318
|
+
" Got: ", paste(names(future_resampled), collapse = ", "), "\n",
|
|
319
|
+
" Differing layers: ", paste(name_diff, collapse = ", ")
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
log_info("Nomes das camadas verificados. Camadas: %s", paste(names(future_resampled), collapse = ", "))
|
|
323
|
+
|
|
324
|
+
# ── 12. Save output ───────────────────────────────────────────────────────────
|
|
325
|
+
log_step(12, "Gravar stack futuro preparado")
|
|
326
|
+
out_filename <- paste0("future_stack_", ssp_label, "_", year_label, ".tif")
|
|
327
|
+
out_path <- file.path(output_dir, out_filename)
|
|
328
|
+
|
|
329
|
+
tryCatch({
|
|
330
|
+
writeRaster(future_resampled, out_path, overwrite = TRUE)
|
|
331
|
+
log_info("Gravado: %s", out_path)
|
|
332
|
+
}, error = function(e) {
|
|
333
|
+
log_error(
|
|
334
|
+
"Falha ao gravar raster de saida '%s': %s\nCausa provavel: sem permissao de escrita ou espaco em disco insuficiente.\nSkill anterior: species-distribution-modeling",
|
|
335
|
+
out_path, conditionMessage(e)
|
|
336
|
+
)
|
|
337
|
+
stop(e)
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
# ── 13. Summary ───────────────────────────────────────────────────────────────
|
|
341
|
+
log_step(13, "Exibir resumo das camadas futuras preparadas")
|
|
342
|
+
log_info("========== RESUMO DAS CAMADAS FUTURAS ==========")
|
|
343
|
+
log_info("SSP : %s", ssp_label)
|
|
344
|
+
log_info("Horizonte temporal : %s", year_label)
|
|
345
|
+
log_info("Camadas preparadas : %d", nlyr(future_resampled))
|
|
346
|
+
log_info("Nomes das camadas : %s", paste(names(future_resampled), collapse = ", "))
|
|
347
|
+
log_info("CRS de saida : %s", crs(future_resampled, describe = TRUE)$name)
|
|
348
|
+
log_info("Resolucao de saida : %s unidades", paste(res(future_resampled), collapse = " x "))
|
|
349
|
+
log_info("Arquivo de saida : %s", out_path)
|
|
350
|
+
log_info("=================================================")
|
|
351
|
+
log_info("Pronto para: maxnet::predict(), biomod2::BIOMOD_Projection() ou sdm_pipeline.py")
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
# Usage: Rscript project_scenarios.R <model_rds> <scenarios_dir> <output_dir> [threshold_from_csv]
|
|
5
|
+
# Project a fitted SDM across multiple future climate scenario stacks.
|
|
6
|
+
# scenarios_dir must contain .tif files named: <ssp>_<year>.tif (e.g. ssp245_2050.tif)
|
|
7
|
+
|
|
8
|
+
# ── Inline logger ─────────────────────────────────────────────────────────────
|
|
9
|
+
SKILL_NAME <- "species-distribution-modeling"
|
|
10
|
+
.log_ts <- function() format(Sys.time(), "[%Y-%m-%d %H:%M:%S]")
|
|
11
|
+
log_info <- function(...) message(.log_ts(), " [INFO] ", sprintf(...))
|
|
12
|
+
log_warn <- function(...) message(.log_ts(), " [WARN] ", sprintf(...))
|
|
13
|
+
log_error<- function(...) message(.log_ts(), " [ERROR] ", sprintf(...))
|
|
14
|
+
log_step <- function(n, d) log_info("-- STEP %d: %s", n, d)
|
|
15
|
+
log_decision <- function(v, val, why) log_info("DECISION | %s = %s | %s", v, val, why)
|
|
16
|
+
dir.create("logs", recursive=TRUE, showWarnings=FALSE)
|
|
17
|
+
|
|
18
|
+
suppressPackageStartupMessages({
|
|
19
|
+
library(terra)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
# ── Arguments ─────────────────────────────────────────────────────────────────
|
|
23
|
+
args <- commandArgs(trailingOnly = TRUE)
|
|
24
|
+
model_rds <- if (length(args) >= 1) args[1] else stop("model_rds required")
|
|
25
|
+
scenarios_dir <- if (length(args) >= 2) args[2] else stop("scenarios_dir required")
|
|
26
|
+
output_dir <- if (length(args) >= 3) args[3] else "outputs/projections"
|
|
27
|
+
threshold_from_csv<- if (length(args) >= 4) args[4] else NULL # path to prediction_summary.csv
|
|
28
|
+
|
|
29
|
+
log_decision("scenarios_dir", scenarios_dir, "Directory containing SSP scenario stacks")
|
|
30
|
+
log_decision("threshold_from_csv", ifelse(is.null(threshold_from_csv), "NULL", threshold_from_csv),
|
|
31
|
+
"If provided, reuse threshold from predict_distribution.R output")
|
|
32
|
+
|
|
33
|
+
# ── Precondition checks ───────────────────────────────────────────────────────
|
|
34
|
+
if (!file.exists(model_rds)) {
|
|
35
|
+
log_error("Model RDS nao encontrado: %s\nCausa provavel: run_ensemble_sdm.R nao concluiu.\nVerifique: a saida de skills/species-distribution-modeling.\nSkill anterior: species-distribution-modeling", model_rds)
|
|
36
|
+
stop("Missing model: ", model_rds)
|
|
37
|
+
}
|
|
38
|
+
if (!dir.exists(scenarios_dir)) {
|
|
39
|
+
log_error("Scenarios dir nao encontrado: %s\nCausa provavel: prepare_future_layers.R nao foi executado.\nVerifique: CMIP6 stacks foram baixados e preparados.\nSkill anterior: geoprocessing-for-ecology", scenarios_dir)
|
|
40
|
+
stop("Missing scenarios dir: ", scenarios_dir)
|
|
41
|
+
}
|
|
42
|
+
dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
|
|
43
|
+
|
|
44
|
+
# ── Step 1: Load model ────────────────────────────────────────────────────────
|
|
45
|
+
log_step(1, "Loading model")
|
|
46
|
+
model_obj <- tryCatch(readRDS(model_rds), error = function(e) {
|
|
47
|
+
log_error("Falha ao ler model RDS: %s\nCausa provavel: arquivo corrompido.\nVerifique: Rscript gerou o modelo com saveRDS().", conditionMessage(e))
|
|
48
|
+
stop(e)
|
|
49
|
+
})
|
|
50
|
+
is_ensemble <- is.list(model_obj) && !inherits(model_obj, "MaxEnt")
|
|
51
|
+
log_info("Model class: %s | ensemble: %s", class(model_obj)[1], is_ensemble)
|
|
52
|
+
|
|
53
|
+
# ── Step 2: Get threshold ─────────────────────────────────────────────────────
|
|
54
|
+
log_step(2, "Resolving binary threshold")
|
|
55
|
+
threshold_val <- NA_real_
|
|
56
|
+
if (!is.null(threshold_from_csv) && file.exists(threshold_from_csv)) {
|
|
57
|
+
sum_df <- read.csv(threshold_from_csv, stringsAsFactors = FALSE)
|
|
58
|
+
threshold_val <- sum_df$threshold_value[1]
|
|
59
|
+
log_info("Threshold loaded from CSV: %.4f", threshold_val)
|
|
60
|
+
} else if (!is.null(model_obj$threshold_values)) {
|
|
61
|
+
threshold_val <- model_obj$threshold_values["MaxTSS"]
|
|
62
|
+
log_info("Threshold from model (MaxTSS): %.4f", threshold_val)
|
|
63
|
+
} else {
|
|
64
|
+
log_warn("No threshold source available; binary maps will use P10 of each scenario map")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# ── Helper: predict from one stack ────────────────────────────────────────────
|
|
68
|
+
predict_stack <- function(mod, preds) {
|
|
69
|
+
cls <- class(mod)[1]
|
|
70
|
+
if (cls == "maxnet") {
|
|
71
|
+
suppressPackageStartupMessages(library(maxnet))
|
|
72
|
+
pred_df <- as.data.frame(preds, na.rm = FALSE)
|
|
73
|
+
p <- predict(mod, pred_df, type = "cloglog")
|
|
74
|
+
r <- preds[[1]]; values(r) <- p; return(r)
|
|
75
|
+
} else if (cls %in% c("gbm", "BRT")) {
|
|
76
|
+
suppressPackageStartupMessages(library(gbm))
|
|
77
|
+
pred_df <- as.data.frame(preds, na.rm = FALSE)
|
|
78
|
+
p <- predict.gbm(mod, pred_df, n.trees = mod$n.trees, type = "response")
|
|
79
|
+
r <- preds[[1]]; values(r) <- p; return(r)
|
|
80
|
+
} else if (cls == "randomForest") {
|
|
81
|
+
suppressPackageStartupMessages(library(randomForest))
|
|
82
|
+
pred_df <- as.data.frame(preds, na.rm = FALSE)
|
|
83
|
+
p <- predict(mod, pred_df, type = "prob")[, 2]
|
|
84
|
+
r <- preds[[1]]; values(r) <- p; return(r)
|
|
85
|
+
} else {
|
|
86
|
+
return(predict(mod, preds))
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# ── Step 3: Find scenario stacks ──────────────────────────────────────────────
|
|
91
|
+
log_step(3, "Scanning scenario stacks")
|
|
92
|
+
tif_files <- list.files(scenarios_dir, pattern = "\\.tif$", full.names = TRUE)
|
|
93
|
+
if (length(tif_files) == 0) {
|
|
94
|
+
log_error("Nenhum .tif encontrado em: %s\nCausa provavel: prepare_future_layers.R nao gerou os stacks.\nVerifique: arquivos .tif existem em scenarios_dir.", scenarios_dir)
|
|
95
|
+
stop("No .tif files in scenarios_dir")
|
|
96
|
+
}
|
|
97
|
+
log_info("Found %d scenario stack(s)", length(tif_files))
|
|
98
|
+
|
|
99
|
+
# Parse scenario labels from filenames (e.g. ssp245_2050.tif → ssp245_2050)
|
|
100
|
+
scenario_labels <- tools::file_path_sans_ext(basename(tif_files))
|
|
101
|
+
log_decision("scenario_labels", paste(scenario_labels, collapse=", "),
|
|
102
|
+
"Derived from .tif filenames in scenarios_dir")
|
|
103
|
+
|
|
104
|
+
# ── Step 4: Project each scenario ────────────────────────────────────────────
|
|
105
|
+
log_step(4, "Projecting across all scenarios")
|
|
106
|
+
|
|
107
|
+
results <- vector("list", length(tif_files))
|
|
108
|
+
current_suit <- NULL # will be set from first file for change map
|
|
109
|
+
|
|
110
|
+
for (i in seq_along(tif_files)) {
|
|
111
|
+
lbl <- scenario_labels[i]
|
|
112
|
+
tif <- tif_files[i]
|
|
113
|
+
log_info("Projecting scenario %d/%d: %s", i, length(tif_files), lbl)
|
|
114
|
+
|
|
115
|
+
preds <- tryCatch(rast(tif), error = function(e) {
|
|
116
|
+
log_error("Falha ao ler stack %s: %s\nCausa provavel: arquivo corrompido ou caminho errado.", lbl, conditionMessage(e))
|
|
117
|
+
return(NULL)
|
|
118
|
+
})
|
|
119
|
+
if (is.null(preds)) { results[[i]] <- NULL; next }
|
|
120
|
+
|
|
121
|
+
suit <- tryCatch({
|
|
122
|
+
if (is_ensemble) {
|
|
123
|
+
preds_list <- lapply(model_obj$models, predict_stack, preds = preds)
|
|
124
|
+
suit_stack <- rast(preds_list)
|
|
125
|
+
w <- if (!is.null(model_obj$auc_weights)) model_obj$auc_weights else
|
|
126
|
+
rep(1 / nlyr(suit_stack), nlyr(suit_stack))
|
|
127
|
+
app(suit_stack, function(x) weighted.mean(x, w, na.rm = TRUE))
|
|
128
|
+
} else {
|
|
129
|
+
predict_stack(model_obj, preds)
|
|
130
|
+
}
|
|
131
|
+
}, error = function(e) {
|
|
132
|
+
log_error("Falha na projecao do cenario %s: %s\nVerifique: stack tem os mesmos preditores do modelo.", lbl, conditionMessage(e))
|
|
133
|
+
NULL
|
|
134
|
+
})
|
|
135
|
+
if (is.null(suit)) { results[[i]] <- NULL; next }
|
|
136
|
+
|
|
137
|
+
# Save suitability raster
|
|
138
|
+
suit_file <- file.path(output_dir, paste0("suitability_", lbl, ".tif"))
|
|
139
|
+
writeRaster(suit, suit_file, overwrite = TRUE)
|
|
140
|
+
|
|
141
|
+
# Binary map
|
|
142
|
+
thr_use <- if (!is.na(threshold_val)) threshold_val else
|
|
143
|
+
quantile(values(suit), 0.10, na.rm = TRUE)
|
|
144
|
+
binary <- suit >= thr_use
|
|
145
|
+
bin_file <- file.path(output_dir, paste0("binary_", lbl, ".tif"))
|
|
146
|
+
writeRaster(binary, bin_file, overwrite = TRUE, datatype = "INT1U")
|
|
147
|
+
|
|
148
|
+
# Area stats
|
|
149
|
+
suitable_cells <- sum(values(binary) == 1, na.rm = TRUE)
|
|
150
|
+
total_cells <- sum(!is.na(values(suit)))
|
|
151
|
+
cell_area_km2 <- prod(res(suit)) / 1e6
|
|
152
|
+
|
|
153
|
+
results[[i]] <- data.frame(
|
|
154
|
+
scenario = lbl,
|
|
155
|
+
threshold_used = round(thr_use, 4),
|
|
156
|
+
suitable_area_km2 = round(suitable_cells * cell_area_km2, 1),
|
|
157
|
+
total_area_km2 = round(total_cells * cell_area_km2, 1),
|
|
158
|
+
pct_suitable = round(100 * suitable_cells / total_cells, 2),
|
|
159
|
+
mean_suitability = round(mean(values(suit), na.rm = TRUE), 4),
|
|
160
|
+
stringsAsFactors = FALSE
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Keep first scenario as "current" reference for change map
|
|
164
|
+
if (i == 1) current_suit <- suit
|
|
165
|
+
|
|
166
|
+
log_info(" %s: suitable = %.1f km2 (%.1f%%)",
|
|
167
|
+
lbl, results[[i]]$suitable_area_km2, results[[i]]$pct_suitable)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# ── Step 5: Scenario comparison CSV ───────────────────────────────────────────
|
|
171
|
+
log_step(5, "Writing scenario comparison table")
|
|
172
|
+
valid_results <- Filter(Negate(is.null), results)
|
|
173
|
+
comparison_df <- do.call(rbind, valid_results)
|
|
174
|
+
comp_file <- file.path(output_dir, "scenario_comparison.csv")
|
|
175
|
+
write.csv(comparison_df, comp_file, row.names = FALSE)
|
|
176
|
+
log_info("Scenario comparison saved: %s", comp_file)
|
|
177
|
+
|
|
178
|
+
# ── Step 6: Change map (vs. first scenario) ────────────────────────────────────
|
|
179
|
+
log_step(6, "Computing change map relative to first scenario")
|
|
180
|
+
if (!is.null(current_suit) && length(tif_files) > 1) {
|
|
181
|
+
tryCatch({
|
|
182
|
+
# Load and stack all suitability rasters
|
|
183
|
+
suit_files <- file.path(output_dir, paste0("suitability_", scenario_labels, ".tif"))
|
|
184
|
+
suit_files <- suit_files[file.exists(suit_files)]
|
|
185
|
+
if (length(suit_files) >= 2) {
|
|
186
|
+
all_suits <- rast(suit_files)
|
|
187
|
+
mean_future <- app(all_suits[[-1]], mean, na.rm = TRUE)
|
|
188
|
+
change_map <- (mean_future - current_suit) / (current_suit + 1e-6)
|
|
189
|
+
change_file <- file.path(output_dir, "scenario_change_map.tif")
|
|
190
|
+
writeRaster(change_map, change_file, overwrite = TRUE)
|
|
191
|
+
log_info("Change map saved (relative to first scenario): %s", change_file)
|
|
192
|
+
}
|
|
193
|
+
}, error = function(e) {
|
|
194
|
+
log_warn("Change map computation failed: %s. Skipping.", conditionMessage(e))
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# ── Step 7: Summary plot ───────────────────────────────────────────────────────
|
|
199
|
+
log_step(7, "Generating scenario summary plot")
|
|
200
|
+
tryCatch({
|
|
201
|
+
suppressPackageStartupMessages(library(ggplot2))
|
|
202
|
+
p <- ggplot(comparison_df, aes(x = scenario, y = suitable_area_km2,
|
|
203
|
+
fill = pct_suitable)) +
|
|
204
|
+
geom_col() +
|
|
205
|
+
geom_text(aes(label = paste0(pct_suitable, "%")), vjust = -0.3, size = 3) +
|
|
206
|
+
scale_fill_gradient(low = "#d4e6f1", high = "#1a5276",
|
|
207
|
+
name = "% Suitable") +
|
|
208
|
+
labs(title = "Suitable Area by Scenario",
|
|
209
|
+
x = "Scenario", y = "Suitable Area (km²)") +
|
|
210
|
+
theme_bw() +
|
|
211
|
+
theme(axis.text.x = element_text(angle = 45, hjust = 1))
|
|
212
|
+
|
|
213
|
+
plot_file <- file.path(output_dir, "scenario_summary_plot.png")
|
|
214
|
+
ggsave(plot_file, p, width = max(6, length(tif_files) * 1.5), height = 5, dpi = 150)
|
|
215
|
+
log_info("Summary plot saved: %s", plot_file)
|
|
216
|
+
}, error = function(e) {
|
|
217
|
+
log_warn("ggplot2 summary plot failed: %s. Skipping.", conditionMessage(e))
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
log_step(8, "Done — all scenario projections in: %s", output_dir)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
# Usage: Rscript run_ensemble_sdm.R <occurrences.csv> <predictors_stack.tif> <study_area.shp> <output_dir>
|
|
5
|
+
|
|
6
|
+
# ── Inline logger ─────────────────────────────────────────────────────────────
|
|
7
|
+
SKILL_NAME <- "species-distribution-modeling"
|
|
8
|
+
.log_ts <- function() format(Sys.time(), "[%Y-%m-%d %H:%M:%S]")
|
|
9
|
+
log_info <- function(...) message(.log_ts(), " [INFO] ", sprintf(...))
|
|
10
|
+
log_warn <- function(...) message(.log_ts(), " [WARN] ", sprintf(...))
|
|
11
|
+
log_error<- function(...) message(.log_ts(), " [ERROR] ", sprintf(...))
|
|
12
|
+
log_step <- function(n, d) log_info("-- STEP %d: %s", n, d)
|
|
13
|
+
log_decision <- function(v, val, why) log_info("DECISION | %s = %s | %s", v, val, why)
|
|
14
|
+
dir.create("logs", recursive=TRUE, showWarnings=FALSE)
|
|
15
|
+
|
|
16
|
+
# Fit MaxEnt + BRT + RF ensemble SDM
|
|
17
|
+
# Usage: Rscript run_ensemble_sdm.R <params_yaml> <output_dir>
|
|
18
|
+
# Requires: terra, sf, maxnet, gbm, randomForest, dismo, blockCV, yaml
|
|
19
|
+
|
|
20
|
+
suppressPackageStartupMessages({
|
|
21
|
+
library(terra); library(sf); library(maxnet)
|
|
22
|
+
library(gbm); library(randomForest); library(yaml)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
log_step(1, "Analisar argumentos e carregar parametros YAML")
|
|
26
|
+
args <- commandArgs(trailingOnly = TRUE)
|
|
27
|
+
params_f <- ifelse(length(args) >= 1, args[1], "params.yaml")
|
|
28
|
+
output_dir <- ifelse(length(args) >= 2, args[2], "outputs/sdm")
|
|
29
|
+
|
|
30
|
+
log_info("Script: run_ensemble_sdm.R | Skill: %s", SKILL_NAME)
|
|
31
|
+
log_info("Params file : %s", params_f)
|
|
32
|
+
log_info("Output dir : %s", output_dir)
|
|
33
|
+
|
|
34
|
+
# ── Input precondition check ──────────────────────────────────────────────────
|
|
35
|
+
if (!file.exists(params_f)) {
|
|
36
|
+
log_error(
|
|
37
|
+
"Input nao encontrado: %s\nCausa provavel: arquivo nao gerado pelo passo anterior.\nVerifique a saida de: species-distribution-modeling (tune_maxnet ou prepare_future_layers)\nSkill anterior: species-distribution-modeling",
|
|
38
|
+
params_f
|
|
39
|
+
)
|
|
40
|
+
stop("Missing: ", params_f)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
|
|
44
|
+
log_info("Diretorio de saida pronto: %s", output_dir)
|
|
45
|
+
|
|
46
|
+
log_step(2, "Ler parametros do arquivo YAML")
|
|
47
|
+
p <- tryCatch({
|
|
48
|
+
yaml::read_yaml(params_f)
|
|
49
|
+
}, error = function(e) {
|
|
50
|
+
log_error(
|
|
51
|
+
"Falha ao ler arquivo YAML '%s': %s\nCausa provavel: YAML malformado ou encoding incorreto.\nVerifique a sintaxe do arquivo.\nSkill anterior: species-distribution-modeling",
|
|
52
|
+
params_f, conditionMessage(e)
|
|
53
|
+
)
|
|
54
|
+
stop(e)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
set.seed(p$random_seeds$global)
|
|
58
|
+
log_decision("random_seed", p$random_seeds$global, "semente global definida no params.yaml para reprodutibilidade")
|
|
59
|
+
log_decision("algorithms", paste(p$modeling$algorithms, collapse = ","), "algoritmos definidos em params.yaml$modeling$algorithms")
|
|
60
|
+
log_decision("cv_method", p$modeling$cv_method, "metodo de validacao cruzada definido em params.yaml")
|
|
61
|
+
log_decision("cv_folds", p$modeling$cv_folds, "numero de folds definido em params.yaml")
|
|
62
|
+
|
|
63
|
+
log_info("=== SDM Ensemble Pipeline ===")
|
|
64
|
+
log_info("Output : %s", output_dir)
|
|
65
|
+
log_info("Algoritmos: %s", paste(p$modeling$algorithms, collapse = ", "))
|
|
66
|
+
log_info("CV method : %s | Folds: %d", p$modeling$cv_method, p$modeling$cv_folds)
|
|
67
|
+
|
|
68
|
+
# NOTE: This is a scaffold. Load your data and call your modeling functions below.
|
|
69
|
+
# Example structure:
|
|
70
|
+
#
|
|
71
|
+
# occ <- read.csv("data/processed/occ_thinned.csv")
|
|
72
|
+
# bg <- read.csv("data/processed/background.csv")
|
|
73
|
+
# stack <- rast("data/predictors_stack.tif")
|
|
74
|
+
# predictors <- readLines("outputs/selected_predictors.txt")
|
|
75
|
+
#
|
|
76
|
+
# occ_env <- extract(stack[[predictors]], occ[, c("decimalLongitude","decimalLatitude")])
|
|
77
|
+
# bg_env <- extract(stack[[predictors]], bg[, c("lon","lat")])
|
|
78
|
+
#
|
|
79
|
+
# train_df <- rbind(
|
|
80
|
+
# cbind(pa = 1, occ_env),
|
|
81
|
+
# cbind(pa = 0, bg_env)
|
|
82
|
+
# ) |> na.omit()
|
|
83
|
+
#
|
|
84
|
+
# # MaxEnt
|
|
85
|
+
# mx <- maxnet(p = train_df$pa, data = train_df[,-1],
|
|
86
|
+
# regmult = p$hyperparameters$maxnet$regularization_multiplier[2])
|
|
87
|
+
#
|
|
88
|
+
# # Predict and ensemble — see biomod2 for full ensemble workflow
|
|
89
|
+
|
|
90
|
+
log_step(3, "Scaffold carregado — preencher com carregamento de dados e chamadas de modelo")
|
|
91
|
+
tryCatch({
|
|
92
|
+
log_info("Scaffold carregado. Adicione o carregamento de dados e as chamadas de modelo para seu estudo.")
|
|
93
|
+
}, error = function(e) {
|
|
94
|
+
log_error(
|
|
95
|
+
"Falha no bloco principal do scaffold: %s\nCausa provavel: erro de configuracao ou dados ausentes.\nVerifique os arquivos de dados e o params.yaml.\nSkill anterior: species-distribution-modeling",
|
|
96
|
+
conditionMessage(e)
|
|
97
|
+
)
|
|
98
|
+
stop(e)
|
|
99
|
+
})
|