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,379 @@
|
|
|
1
|
+
# ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
"""Download global environmental predictor layers for ecological modelling.
|
|
5
|
+
|
|
6
|
+
Usage: python download_predictors.py <output_dir> [resolution] [extent_wkt] [source]
|
|
7
|
+
|
|
8
|
+
Arguments:
|
|
9
|
+
output_dir : Directory for outputs (created if absent)
|
|
10
|
+
resolution : WorldClim resolution in arc-minutes: 2.5, 5, or 10 (default: 2.5)
|
|
11
|
+
extent_wkt : WKT bounding box for clipping (optional).
|
|
12
|
+
Example: "POLYGON((-80 -30,-80 10,-30 10,-30 -30,-80 -30))"
|
|
13
|
+
source : Comma-separated list: worldclim,chelsa,era5 (default: worldclim)
|
|
14
|
+
|
|
15
|
+
Outputs:
|
|
16
|
+
output_dir/worldclim/wc2.1_{res}m_bio_{1..19}.tif — WorldClim bioclimatic variables
|
|
17
|
+
output_dir/chelsa/CHELSA_bio{1..19}.tif — CHELSA bioclimatic variables
|
|
18
|
+
output_dir/predictor_metadata.csv — layer provenance
|
|
19
|
+
|
|
20
|
+
References:
|
|
21
|
+
WorldClim: Fick & Hijmans (2017) doi:10.1002/joc.5086
|
|
22
|
+
CHELSA: Karger et al. (2021) doi:10.1038/s41597-021-01084-7
|
|
23
|
+
ERA5-Land: https://doi.org/10.24381/cds.68d2bb30
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
import sys
|
|
28
|
+
from datetime import datetime
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
SKILL_NAME = "geoprocessing-for-ecology"
|
|
32
|
+
_LOG_DIR = Path("logs")
|
|
33
|
+
_LOG_DIR.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
_log_file = _LOG_DIR / f"skill_{SKILL_NAME}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
|
|
35
|
+
logging.basicConfig(
|
|
36
|
+
level=logging.INFO,
|
|
37
|
+
format="[%(asctime)s] [%(levelname)s] [" + SKILL_NAME + "] %(message)s",
|
|
38
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
39
|
+
handlers=[
|
|
40
|
+
logging.StreamHandler(sys.stdout),
|
|
41
|
+
logging.FileHandler(_log_file, encoding="utf-8"),
|
|
42
|
+
],
|
|
43
|
+
)
|
|
44
|
+
logger = logging.getLogger(SKILL_NAME)
|
|
45
|
+
|
|
46
|
+
def log_step(n: int, desc: str) -> None:
|
|
47
|
+
logger.info("-- STEP %d: %s", n, desc)
|
|
48
|
+
|
|
49
|
+
def log_decision(var: str, val, why: str) -> None:
|
|
50
|
+
logger.info("DECISION | %s = %s | %s", var, val, why)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
import csv
|
|
54
|
+
import hashlib
|
|
55
|
+
from datetime import date
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
import requests
|
|
59
|
+
except ImportError as e:
|
|
60
|
+
logger.error(
|
|
61
|
+
"Dependencia ausente: %s\n Instale com: pip install requests\n Skill anterior: geoprocessing-for-ecology",
|
|
62
|
+
e,
|
|
63
|
+
)
|
|
64
|
+
sys.exit(1)
|
|
65
|
+
|
|
66
|
+
# Optional rasterio for clipping
|
|
67
|
+
try:
|
|
68
|
+
import rasterio
|
|
69
|
+
from rasterio.mask import mask as rio_mask
|
|
70
|
+
from shapely import wkt as shapely_wkt
|
|
71
|
+
HAS_RASTERIO = True
|
|
72
|
+
except ImportError:
|
|
73
|
+
HAS_RASTERIO = False
|
|
74
|
+
logger.warning(
|
|
75
|
+
"rasterio/shapely nao instalados. Corte de camadas por extensao nao disponivel. "
|
|
76
|
+
"Instale com: pip install rasterio shapely"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ── Constants ─────────────────────────────────────────────────────────────────
|
|
81
|
+
WORLDCLIM_BASE = "https://biogeo.ucdavis.edu/data/worldclim/v2.1/base"
|
|
82
|
+
CHELSA_BASE = "https://os.zhdk.cloud.switch.ch/envicloud/chelsa/chelsa_V2/GLOBAL/climatologies/1981-2010/bio"
|
|
83
|
+
|
|
84
|
+
VALID_RESOLUTIONS = {0.5: "30s", 2.5: "2.5m", 5: "5m", 10: "10m"}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ── Helper functions ──────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
def sha256_file(path: Path) -> str:
|
|
90
|
+
"""Compute SHA256 checksum of a file."""
|
|
91
|
+
h = hashlib.sha256()
|
|
92
|
+
try:
|
|
93
|
+
with open(path, "rb") as f:
|
|
94
|
+
for chunk in iter(lambda: f.read(65536), b""):
|
|
95
|
+
h.update(chunk)
|
|
96
|
+
return h.hexdigest()
|
|
97
|
+
except Exception:
|
|
98
|
+
return "N/A"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def download_file(url: str, out_path: Path, description: str = "") -> bool:
|
|
102
|
+
"""Download a file with progress logging. Returns True on success."""
|
|
103
|
+
logger.info("Baixando %s de: %s", description or out_path.name, url)
|
|
104
|
+
try:
|
|
105
|
+
resp = requests.get(url, stream=True, timeout=600)
|
|
106
|
+
resp.raise_for_status()
|
|
107
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
with open(out_path, "wb") as f:
|
|
109
|
+
for chunk in resp.iter_content(chunk_size=65536):
|
|
110
|
+
f.write(chunk)
|
|
111
|
+
size_mb = out_path.stat().st_size / (1024 * 1024)
|
|
112
|
+
logger.info("Salvo: %s (%.1f MB)", out_path, size_mb)
|
|
113
|
+
return True
|
|
114
|
+
except requests.RequestException as e:
|
|
115
|
+
logger.error(
|
|
116
|
+
"Falha ao baixar '%s': %s\n Causa provavel: sem conexao ou servidor indisponivel.\n Skill anterior: geoprocessing-for-ecology",
|
|
117
|
+
url, e,
|
|
118
|
+
)
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def clip_raster(in_path: Path, geom_wkt: str, out_path: Path) -> bool:
|
|
123
|
+
"""Clip a raster to a WKT geometry using rasterio. Returns True on success."""
|
|
124
|
+
if not HAS_RASTERIO:
|
|
125
|
+
logger.warning("rasterio nao disponivel; sem corte de extensao.")
|
|
126
|
+
return False
|
|
127
|
+
try:
|
|
128
|
+
geom = shapely_wkt.loads(geom_wkt)
|
|
129
|
+
shapes = [{"type": "Polygon", "coordinates": [list(geom.exterior.coords)]}]
|
|
130
|
+
with rasterio.open(in_path) as src:
|
|
131
|
+
out_image, out_transform = rio_mask(src, shapes, crop=True, nodata=src.nodata)
|
|
132
|
+
out_meta = src.meta.copy()
|
|
133
|
+
out_meta.update({"height": out_image.shape[1], "width": out_image.shape[2],
|
|
134
|
+
"transform": out_transform})
|
|
135
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
136
|
+
with rasterio.open(out_path, "w", **out_meta) as dst:
|
|
137
|
+
dst.write(out_image)
|
|
138
|
+
logger.info("Cortado: %s", out_path)
|
|
139
|
+
return True
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.warning("Falha ao cortar '%s': %s. Mantendo versao global.", in_path.name, e)
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ── WorldClim download ────────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
def download_worldclim(output_dir: Path, resolution: float, extent_wkt: str | None,
|
|
148
|
+
meta_rows: list[dict]) -> None:
|
|
149
|
+
"""Download WorldClim v2.1 bioclimatic variables."""
|
|
150
|
+
res_str = VALID_RESOLUTIONS.get(resolution, f"{resolution}m")
|
|
151
|
+
wc_dir = output_dir / "worldclim"
|
|
152
|
+
wc_dir.mkdir(parents=True, exist_ok=True)
|
|
153
|
+
|
|
154
|
+
# WorldClim distributes all 19 BIO vars in a single zip
|
|
155
|
+
zip_name = f"wc2.1_{res_str}_bio.zip"
|
|
156
|
+
zip_url = f"{WORLDCLIM_BASE}/{zip_name}"
|
|
157
|
+
zip_path = wc_dir / zip_name
|
|
158
|
+
|
|
159
|
+
if not download_file(zip_url, zip_path, f"WorldClim {res_str} BIO"):
|
|
160
|
+
logger.error(
|
|
161
|
+
"Falha ao baixar WorldClim. Verifique conexao e resolucao: %g\n Skill anterior: geoprocessing-for-ecology",
|
|
162
|
+
resolution,
|
|
163
|
+
)
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
# Extract zip
|
|
167
|
+
import zipfile
|
|
168
|
+
try:
|
|
169
|
+
with zipfile.ZipFile(zip_path, "r") as z:
|
|
170
|
+
z.extractall(wc_dir)
|
|
171
|
+
zip_path.unlink() # remove zip after extraction
|
|
172
|
+
logger.info("Arquivo WorldClim extraido em: %s", wc_dir)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.error(
|
|
175
|
+
"Falha ao extrair ZIP WorldClim: %s\n Skill anterior: geoprocessing-for-ecology", e,
|
|
176
|
+
)
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
# Clip and register each BIO layer
|
|
180
|
+
for i in range(1, 20):
|
|
181
|
+
tif_name = f"wc2.1_{res_str}_bio_{i}.tif"
|
|
182
|
+
tif_path = wc_dir / tif_name
|
|
183
|
+
if not tif_path.exists():
|
|
184
|
+
logger.warning("Arquivo nao encontrado apos extracao: %s", tif_name)
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
if extent_wkt:
|
|
188
|
+
clip_raster(tif_path, extent_wkt, tif_path) # overwrite in place
|
|
189
|
+
|
|
190
|
+
meta_rows.append({
|
|
191
|
+
"source": "WorldClim_v2.1",
|
|
192
|
+
"variable": f"BIO{i}",
|
|
193
|
+
"resolution": f"{res_str}",
|
|
194
|
+
"file": str(tif_path),
|
|
195
|
+
"citation": "Fick & Hijmans (2017) doi:10.1002/joc.5086",
|
|
196
|
+
"licence": "CC BY 4.0",
|
|
197
|
+
"download_date": date.today().isoformat(),
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
logger.info("WorldClim: 19 variaveis BIO processadas em %s", wc_dir)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# ── CHELSA download ───────────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
def download_chelsa(output_dir: Path, extent_wkt: str | None,
|
|
206
|
+
meta_rows: list[dict]) -> None:
|
|
207
|
+
"""Download CHELSA v2.1 bioclimatic variables."""
|
|
208
|
+
ch_dir = output_dir / "chelsa"
|
|
209
|
+
ch_dir.mkdir(parents=True, exist_ok=True)
|
|
210
|
+
|
|
211
|
+
for i in range(1, 20):
|
|
212
|
+
fname = f"CHELSA_bio{i}_1981-2010_V.2.1.tif"
|
|
213
|
+
url = f"{CHELSA_BASE}/{fname}"
|
|
214
|
+
out_path = ch_dir / f"CHELSA_bio{i}.tif"
|
|
215
|
+
|
|
216
|
+
ok = download_file(url, out_path, f"CHELSA BIO{i}")
|
|
217
|
+
if not ok:
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
if extent_wkt:
|
|
221
|
+
clip_raster(out_path, extent_wkt, out_path)
|
|
222
|
+
|
|
223
|
+
meta_rows.append({
|
|
224
|
+
"source": "CHELSA_v2.1",
|
|
225
|
+
"variable": f"BIO{i}",
|
|
226
|
+
"resolution": "30 arc-sec (~1 km)",
|
|
227
|
+
"file": str(out_path),
|
|
228
|
+
"citation": "Karger et al. (2021) doi:10.1038/s41597-021-01084-7",
|
|
229
|
+
"licence": "CC BY 4.0",
|
|
230
|
+
"download_date": date.today().isoformat(),
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
logger.info("CHELSA: 19 variaveis BIO processadas em %s", ch_dir)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# ── ERA5 placeholder ──────────────────────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
def download_era5(output_dir: Path, meta_rows: list[dict]) -> None:
|
|
239
|
+
"""ERA5-Land download via cdsapi (requires CDS account and ~/.cdsapirc)."""
|
|
240
|
+
logger.info("ERA5-Land: verificando disponibilidade de cdsapi...")
|
|
241
|
+
try:
|
|
242
|
+
import cdsapi
|
|
243
|
+
except ImportError:
|
|
244
|
+
logger.warning(
|
|
245
|
+
"cdsapi nao instalado. Instale com: pip install cdsapi\n Tambem configure ~/.cdsapirc com sua chave CDS.\n Instruções: https://cds.climate.copernicus.eu/api-how-to"
|
|
246
|
+
)
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
era5_dir = output_dir / "era5"
|
|
250
|
+
era5_dir.mkdir(parents=True, exist_ok=True)
|
|
251
|
+
out_path = era5_dir / "era5_land_temp_precip_monthly.nc"
|
|
252
|
+
|
|
253
|
+
log_decision("era5_variables", "2m_temperature,total_precipitation",
|
|
254
|
+
"temperatura e precipitacao sao os preditores climaticos mais comuns em SDM")
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
c = cdsapi.Client()
|
|
258
|
+
c.retrieve(
|
|
259
|
+
"reanalysis-era5-land-monthly-means",
|
|
260
|
+
{
|
|
261
|
+
"variable": ["2m_temperature", "total_precipitation"],
|
|
262
|
+
"product_type": "monthly_averaged_reanalysis",
|
|
263
|
+
"year": [str(y) for y in range(1990, 2021)],
|
|
264
|
+
"month": [f"{m:02d}" for m in range(1, 13)],
|
|
265
|
+
"time": "00:00",
|
|
266
|
+
"format": "netcdf",
|
|
267
|
+
},
|
|
268
|
+
str(out_path),
|
|
269
|
+
)
|
|
270
|
+
logger.info("ERA5-Land salvo: %s", out_path)
|
|
271
|
+
meta_rows.append({
|
|
272
|
+
"source": "ERA5-Land",
|
|
273
|
+
"variable": "2m_temperature,total_precipitation (monthly 1990-2020)",
|
|
274
|
+
"resolution": "~9 km (0.1 degree)",
|
|
275
|
+
"file": str(out_path),
|
|
276
|
+
"citation": "Munoz-Sabater et al. (2021) doi:10.24381/cds.68d2bb30",
|
|
277
|
+
"licence": "CC BY 4.0",
|
|
278
|
+
"download_date": date.today().isoformat(),
|
|
279
|
+
})
|
|
280
|
+
except Exception as e:
|
|
281
|
+
logger.error(
|
|
282
|
+
"Falha ao baixar ERA5-Land: %s\n Causa provavel: cdsapi nao configurado ou chave CDS invalida.\n Verifique: https://cds.climate.copernicus.eu/api-how-to\n Skill anterior: geoprocessing-for-ecology",
|
|
283
|
+
e,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# ── Save metadata CSV ─────────────────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
def save_metadata(output_dir: Path, meta_rows: list[dict]) -> None:
|
|
290
|
+
if not meta_rows:
|
|
291
|
+
logger.warning("Nenhuma camada baixada com sucesso. Arquivo de metadata nao criado.")
|
|
292
|
+
return
|
|
293
|
+
meta_path = output_dir / "predictor_metadata.csv"
|
|
294
|
+
fieldnames = ["source", "variable", "resolution", "file", "citation", "licence", "download_date"]
|
|
295
|
+
try:
|
|
296
|
+
with open(meta_path, "w", newline="", encoding="utf-8") as f:
|
|
297
|
+
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
|
298
|
+
writer.writeheader()
|
|
299
|
+
writer.writerows(meta_rows)
|
|
300
|
+
logger.info("Metadata gravado: %s (%d camadas)", meta_path, len(meta_rows))
|
|
301
|
+
except OSError as e:
|
|
302
|
+
logger.error(
|
|
303
|
+
"Falha ao gravar metadata: %s\n Skill anterior: geoprocessing-for-ecology", e,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# ── Entry point ────────────────────────────────────────────────────────────────
|
|
308
|
+
|
|
309
|
+
def main():
|
|
310
|
+
logger.info("Script: download_predictors.py | Skill: %s", SKILL_NAME)
|
|
311
|
+
|
|
312
|
+
argv = sys.argv[1:]
|
|
313
|
+
|
|
314
|
+
if len(argv) < 1:
|
|
315
|
+
output_dir = Path("data/predictors")
|
|
316
|
+
resolution = 2.5
|
|
317
|
+
extent_wkt = None
|
|
318
|
+
sources_str = "worldclim"
|
|
319
|
+
logger.warning("Nenhum argumento fornecido. Usando valores padrao.")
|
|
320
|
+
else:
|
|
321
|
+
output_dir = Path(argv[0])
|
|
322
|
+
resolution = float(argv[1]) if len(argv) >= 2 else 2.5
|
|
323
|
+
extent_wkt = argv[2] if len(argv) >= 3 and argv[2] else None
|
|
324
|
+
sources_str = argv[3] if len(argv) >= 4 else "worldclim"
|
|
325
|
+
|
|
326
|
+
sources = [s.strip().lower() for s in sources_str.split(",")]
|
|
327
|
+
|
|
328
|
+
logger.info("Output dir : %s", output_dir)
|
|
329
|
+
logger.info("Resolution : %g arc-minutes", resolution)
|
|
330
|
+
logger.info("Extent WKT : %s", extent_wkt or "global (sem corte)")
|
|
331
|
+
logger.info("Sources : %s", ", ".join(sources))
|
|
332
|
+
|
|
333
|
+
log_decision("resolution", resolution,
|
|
334
|
+
"2.5 arc-min (~4.5 km) e o equilibrio padrao entre detalhe e tamanho de arquivo")
|
|
335
|
+
|
|
336
|
+
if resolution not in VALID_RESOLUTIONS:
|
|
337
|
+
logger.warning("Resolucao %g nao e padrao WorldClim. Valores validos: 0.5, 2.5, 5, 10.", resolution)
|
|
338
|
+
|
|
339
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
340
|
+
meta_rows: list[dict] = []
|
|
341
|
+
|
|
342
|
+
# Download WorldClim
|
|
343
|
+
if "worldclim" in sources:
|
|
344
|
+
log_step(1, "Baixar WorldClim v2.1")
|
|
345
|
+
try:
|
|
346
|
+
download_worldclim(output_dir, resolution, extent_wkt, meta_rows)
|
|
347
|
+
except Exception as e:
|
|
348
|
+
logger.error(
|
|
349
|
+
"Erro ao baixar WorldClim: %s\n Skill anterior: geoprocessing-for-ecology", e,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
# Download CHELSA
|
|
353
|
+
if "chelsa" in sources:
|
|
354
|
+
log_step(2, "Baixar CHELSA v2.1")
|
|
355
|
+
try:
|
|
356
|
+
download_chelsa(output_dir, extent_wkt, meta_rows)
|
|
357
|
+
except Exception as e:
|
|
358
|
+
logger.error(
|
|
359
|
+
"Erro ao baixar CHELSA: %s\n Skill anterior: geoprocessing-for-ecology", e,
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# Download ERA5
|
|
363
|
+
if "era5" in sources:
|
|
364
|
+
log_step(3, "Baixar ERA5-Land via cdsapi")
|
|
365
|
+
try:
|
|
366
|
+
download_era5(output_dir, meta_rows)
|
|
367
|
+
except Exception as e:
|
|
368
|
+
logger.error(
|
|
369
|
+
"Erro ao baixar ERA5: %s\n Skill anterior: geoprocessing-for-ecology", e,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
log_step(4 if len(sources) < 4 else len(sources) + 1, "Gravar metadata de preditores")
|
|
373
|
+
save_metadata(output_dir, meta_rows)
|
|
374
|
+
|
|
375
|
+
logger.info("Download de preditores concluido. Verifique: %s", output_dir)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
if __name__ == "__main__":
|
|
379
|
+
main()
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
# Usage: Rscript stack_and_extract.R <raster_dir> <points.csv> <study_area.shp> <output_dir>
|
|
5
|
+
# Clip rasters to study area and extract values at points
|
|
6
|
+
# Usage: Rscript stack_and_extract.R <raster_dir> <points_csv> <studyarea_shp> <output_dir>
|
|
7
|
+
# Requires: terra, sf
|
|
8
|
+
|
|
9
|
+
# ── Inline logger ─────────────────────────────────────────────────────────────
|
|
10
|
+
SKILL_NAME <- "geoprocessing-for-ecology"
|
|
11
|
+
.log_ts <- function() format(Sys.time(), "[%Y-%m-%d %H:%M:%S]")
|
|
12
|
+
log_info <- function(...) message(.log_ts(), " [INFO] ", sprintf(...))
|
|
13
|
+
log_warn <- function(...) message(.log_ts(), " [WARN] ", sprintf(...))
|
|
14
|
+
log_error<- function(...) message(.log_ts(), " [ERROR] ", sprintf(...))
|
|
15
|
+
log_step <- function(n, d) log_info("-- STEP %d: %s", n, d)
|
|
16
|
+
log_decision <- function(v, val, why) log_info("DECISION | %s = %s | %s", v, val, why)
|
|
17
|
+
dir.create("logs", recursive=TRUE, showWarnings=FALSE)
|
|
18
|
+
|
|
19
|
+
suppressPackageStartupMessages({
|
|
20
|
+
library(terra)
|
|
21
|
+
library(sf)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
args <- commandArgs(trailingOnly = TRUE)
|
|
25
|
+
raster_dir <- ifelse(length(args) >= 1, args[1], "data/predictors/raw")
|
|
26
|
+
points_file <- ifelse(length(args) >= 2, args[2], "data/processed/data_clean.csv")
|
|
27
|
+
area_file <- ifelse(length(args) >= 3, args[3], "data/spatial/study_area.shp")
|
|
28
|
+
output_dir <- ifelse(length(args) >= 4, args[4], "data/processed")
|
|
29
|
+
dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
|
|
30
|
+
|
|
31
|
+
log_info("Skill: %s | raster_dir=%s | points_file=%s | area_file=%s | output_dir=%s",
|
|
32
|
+
SKILL_NAME, raster_dir, points_file, area_file, output_dir)
|
|
33
|
+
|
|
34
|
+
# ── Input precondition checks ─────────────────────────────────────────────────
|
|
35
|
+
if (!dir.exists(raster_dir)) {
|
|
36
|
+
log_error(
|
|
37
|
+
"Diretorio de rasters nao encontrado: %s\nCausa provavel: caminho errado ou rasters ainda nao baixados/preparados.\nVerifique: se o diretorio existe e contem arquivos .tif.\nSkill anterior: download-predictors ou remote-sensing-analysis.",
|
|
38
|
+
raster_dir
|
|
39
|
+
)
|
|
40
|
+
stop("Missing raster directory: ", raster_dir)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!file.exists(points_file)) {
|
|
44
|
+
log_error(
|
|
45
|
+
"Input nao encontrado: %s\nCausa provavel: CSV de pontos nao gerado ou caminho incorreto.\nVerifique: execute primeiro o script de limpeza de ocorrencias.\nSkill anterior: species-distribution-modeling (data cleaning step).",
|
|
46
|
+
points_file
|
|
47
|
+
)
|
|
48
|
+
stop("Missing: ", points_file)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!file.exists(area_file)) {
|
|
52
|
+
log_error(
|
|
53
|
+
"Input nao encontrado: %s\nCausa provavel: shapefile da area de estudo ausente ou caminho incorreto.\nVerifique: se o arquivo .shp existe e se os arquivos auxiliares (.dbf, .shx, .prj) estao no mesmo diretorio.\nSkill anterior: geoprocessing-for-ecology (study area definition step).",
|
|
54
|
+
area_file
|
|
55
|
+
)
|
|
56
|
+
stop("Missing: ", area_file)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# ── 1. Load study area ─────────────────────────────────────────────────────────
|
|
60
|
+
log_step(1, "Carregar area de estudo (shapefile)")
|
|
61
|
+
study_area <- tryCatch({
|
|
62
|
+
vect(area_file)
|
|
63
|
+
}, error = function(e) {
|
|
64
|
+
log_error(
|
|
65
|
+
"Falha ao carregar shapefile da area de estudo: %s\nCausa provavel: arquivo corrompido, CRS ausente (.prj faltando) ou formato invalido.\nVerifique: se todos os arquivos do shapefile (.shp, .dbf, .shx, .prj) estao presentes.\nSkill anterior: geoprocessing-for-ecology (study area definition step).",
|
|
66
|
+
conditionMessage(e)
|
|
67
|
+
)
|
|
68
|
+
stop(e)
|
|
69
|
+
})
|
|
70
|
+
log_info("Area de estudo carregada: CRS=%s | Feicoes=%d", crs(study_area, describe=TRUE)$code, nrow(study_area))
|
|
71
|
+
|
|
72
|
+
# ── 2. Load and stack rasters ──────────────────────────────────────────────────
|
|
73
|
+
log_step(2, "Listar e empilhar rasters .tif do diretorio de entrada")
|
|
74
|
+
tif_files <- list.files(raster_dir, pattern = "\\.tif$", full.names = TRUE)
|
|
75
|
+
log_info("Rasters encontrados: %d", length(tif_files))
|
|
76
|
+
|
|
77
|
+
if (length(tif_files) == 0) {
|
|
78
|
+
log_error(
|
|
79
|
+
"Nenhum arquivo .tif encontrado em: %s\nCausa provavel: diretorio vazio, rasters em subpasta nao listada ou extensao diferente (.tiff).\nVerifique: o conteudo do diretorio e considere usar pattern='\\\\.tiff?$' se necessario.\nSkill anterior: download-predictors.",
|
|
80
|
+
raster_dir
|
|
81
|
+
)
|
|
82
|
+
stop("No .tif files found in ", raster_dir)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
log_decision("raster_pattern", "\\.tif$",
|
|
86
|
+
"apenas arquivos com extensao .tif sao carregados; arquivos .tiff devem ser renomeados ou o padrao ajustado")
|
|
87
|
+
|
|
88
|
+
stack_raw <- tryCatch({
|
|
89
|
+
rast(tif_files)
|
|
90
|
+
}, error = function(e) {
|
|
91
|
+
log_error(
|
|
92
|
+
"Falha ao empilhar rasters: %s\nCausa provavel: rasters com resolucoes, extensoes ou CRSs incompativeis.\nVerifique: se todos os rasters tem a mesma resolucao e CRS antes de empilhar.\nSkill anterior: download-predictors.",
|
|
93
|
+
conditionMessage(e)
|
|
94
|
+
)
|
|
95
|
+
stop(e)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
log_info("Stack criado: %d camadas | resolucao=%.4f x %.4f | CRS=%s",
|
|
99
|
+
nlyr(stack_raw), res(stack_raw)[1], res(stack_raw)[2],
|
|
100
|
+
crs(stack_raw, describe=TRUE)$code)
|
|
101
|
+
|
|
102
|
+
# ── 3. Reproject study area to raster CRS, then crop and mask ─────────────────
|
|
103
|
+
log_step(3, "Reprojetar area de estudo e recortar stack de rasters")
|
|
104
|
+
log_decision("reproject_target", "CRS do stack de rasters",
|
|
105
|
+
"reprojetar o vetor (leve) e nao o raster (pesado) minimiza tempo de processamento e artefatos de interpolacao")
|
|
106
|
+
|
|
107
|
+
stack_crop <- tryCatch({
|
|
108
|
+
study_proj <- project(study_area, crs(stack_raw))
|
|
109
|
+
crop_result <- crop(stack_raw, study_proj)
|
|
110
|
+
mask(crop_result, study_proj)
|
|
111
|
+
}, error = function(e) {
|
|
112
|
+
log_error(
|
|
113
|
+
"Falha ao recortar/mascarar rasters com a area de estudo: %s\nCausa provavel: area de estudo fora da extensao dos rasters ou CRS incompativel.\nVerifique: se a area de estudo e os rasters se sobrepoem geograficamente.\nSkill anterior: download-predictors.",
|
|
114
|
+
conditionMessage(e)
|
|
115
|
+
)
|
|
116
|
+
stop(e)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
log_info("Stack recortado: extensao=%.4f,%.4f,%.4f,%.4f (xmin,xmax,ymin,ymax)",
|
|
120
|
+
ext(stack_crop)[1], ext(stack_crop)[2], ext(stack_crop)[3], ext(stack_crop)[4])
|
|
121
|
+
|
|
122
|
+
n_valid_cells <- sum(!is.na(values(stack_crop[[1]])))
|
|
123
|
+
if (n_valid_cells == 0) {
|
|
124
|
+
log_warn("Stack mascarado nao contem celulas validas — a area de estudo pode nao sobrepor os rasters.")
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# ── 4. Write stack ─────────────────────────────────────────────────────────────
|
|
128
|
+
log_step(4, "Salvar stack de rasters processado")
|
|
129
|
+
stack_out <- file.path(output_dir, "predictors_stack.tif")
|
|
130
|
+
tryCatch({
|
|
131
|
+
writeRaster(stack_crop, stack_out, overwrite = TRUE)
|
|
132
|
+
log_info("Stack salvo: %s", stack_out)
|
|
133
|
+
}, error = function(e) {
|
|
134
|
+
log_error(
|
|
135
|
+
"Falha ao salvar stack de rasters: %s\nCausa provavel: disco cheio, permissao negada ou caminho invalido.\nVerifique: espaco em disco e permissoes do diretorio de saida.\nSkill anterior: nenhuma.",
|
|
136
|
+
conditionMessage(e)
|
|
137
|
+
)
|
|
138
|
+
stop(e)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
# ── 5. Load points and extract ─────────────────────────────────────────────────
|
|
142
|
+
log_step(5, "Carregar pontos de ocorrencia e extrair valores ambientais")
|
|
143
|
+
pts_df <- tryCatch({
|
|
144
|
+
read.csv(points_file)
|
|
145
|
+
}, error = function(e) {
|
|
146
|
+
log_error(
|
|
147
|
+
"Falha ao ler CSV de pontos: %s\nCausa provavel: arquivo corrompido ou separador incorreto.\nVerifique: formato do CSV de ocorrencias.\nSkill anterior: species-distribution-modeling (data cleaning step).",
|
|
148
|
+
conditionMessage(e)
|
|
149
|
+
)
|
|
150
|
+
stop(e)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
if (!all(c("decimalLongitude", "decimalLatitude") %in% names(pts_df))) {
|
|
154
|
+
log_error(
|
|
155
|
+
"Colunas de coordenadas ausentes no CSV de pontos (colunas presentes: %s).\nCausa provavel: CSV exportado com nomes de colunas diferentes (ex.: 'lon'/'lat' ou 'x'/'y').\nVerifique: renomeie as colunas para 'decimalLongitude' e 'decimalLatitude'.\nSkill anterior: species-distribution-modeling (data cleaning step).",
|
|
156
|
+
paste(names(pts_df), collapse = ", ")
|
|
157
|
+
)
|
|
158
|
+
stop("Points CSV must have columns: decimalLongitude, decimalLatitude")
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
log_info("Pontos carregados: %d registros", nrow(pts_df))
|
|
162
|
+
log_decision("points_crs", "EPSG:4326",
|
|
163
|
+
"assume coordenadas geograficas WGS84 (graus decimais); ajuste se os pontos estiverem em outro CRS")
|
|
164
|
+
|
|
165
|
+
n_na_coords <- sum(is.na(pts_df$decimalLongitude) | is.na(pts_df$decimalLatitude))
|
|
166
|
+
if (n_na_coords > 0) {
|
|
167
|
+
log_warn("%d ponto(s) com coordenadas NA — serao excluidos durante a vetorizacao.", n_na_coords)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
pts_vect <- tryCatch({
|
|
171
|
+
vect(pts_df, geom = c("decimalLongitude", "decimalLatitude"), crs = "EPSG:4326")
|
|
172
|
+
}, error = function(e) {
|
|
173
|
+
log_error(
|
|
174
|
+
"Falha ao criar SpatVector de pontos: %s\nCausa provavel: coordenadas fora do intervalo valido ou valores NA nao removidos.\nVerifique: se longitude esta entre -180 e 180 e latitude entre -90 e 90.\nSkill anterior: species-distribution-modeling (data cleaning step).",
|
|
175
|
+
conditionMessage(e)
|
|
176
|
+
)
|
|
177
|
+
stop(e)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
pts_proj <- tryCatch({
|
|
181
|
+
project(pts_vect, crs(stack_crop))
|
|
182
|
+
}, error = function(e) {
|
|
183
|
+
log_error(
|
|
184
|
+
"Falha ao reprojetar pontos para CRS do stack: %s\nCausa provavel: CRS do stack invalido ou nao suportado pela transformacao.\nVerifique: CRS do stack de rasters.\nSkill anterior: nenhuma.",
|
|
185
|
+
conditionMessage(e)
|
|
186
|
+
)
|
|
187
|
+
stop(e)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
extracted <- tryCatch({
|
|
191
|
+
terra::extract(stack_crop, pts_proj, ID = FALSE)
|
|
192
|
+
}, error = function(e) {
|
|
193
|
+
log_error(
|
|
194
|
+
"Falha ao extrair valores do raster nos pontos: %s\nCausa provavel: stack ou pontos invalidos, ou nenhum ponto dentro da extensao do raster.\nVerifique: se os pontos estao dentro da area de estudo.\nSkill anterior: nenhuma.",
|
|
195
|
+
conditionMessage(e)
|
|
196
|
+
)
|
|
197
|
+
stop(e)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
n_complete <- sum(complete.cases(extracted))
|
|
201
|
+
n_total <- nrow(extracted)
|
|
202
|
+
pct_complete <- round(100 * n_complete / n_total, 1)
|
|
203
|
+
|
|
204
|
+
if (pct_complete < 80) {
|
|
205
|
+
log_warn("Apenas %.1f%% dos pontos (%d/%d) possuem dados ambientais completos — muitos pontos fora da area mascarada.", pct_complete, n_complete, n_total)
|
|
206
|
+
} else {
|
|
207
|
+
log_info("Pontos com dados ambientais completos: %d/%d (%.1f%%)", n_complete, n_total, pct_complete)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
pts_env <- cbind(pts_df, extracted)
|
|
211
|
+
|
|
212
|
+
env_out <- file.path(output_dir, "points_with_env.csv")
|
|
213
|
+
tryCatch({
|
|
214
|
+
write.csv(pts_env, env_out, row.names = FALSE)
|
|
215
|
+
log_info("Valores extraidos salvos: %s", env_out)
|
|
216
|
+
}, error = function(e) {
|
|
217
|
+
log_error(
|
|
218
|
+
"Falha ao salvar points_with_env.csv: %s\nCausa provavel: permissao negada ou disco cheio.\nVerifique: permissoes do diretorio de saida.\nSkill anterior: nenhuma.",
|
|
219
|
+
conditionMessage(e)
|
|
220
|
+
)
|
|
221
|
+
stop(e)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
log_info("Geoprocessamento concluido. Saidas em: %s", output_dir)
|