cfasig 0.1.0__tar.gz
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.
- cfasig-0.1.0/LICENSE +21 -0
- cfasig-0.1.0/PKG-INFO +242 -0
- cfasig-0.1.0/README.md +209 -0
- cfasig-0.1.0/pyproject.toml +55 -0
- cfasig-0.1.0/setup.cfg +4 -0
- cfasig-0.1.0/src/cfasig/__init__.py +122 -0
- cfasig-0.1.0/src/cfasig/archivo.py +375 -0
- cfasig-0.1.0/src/cfasig/campos.py +18 -0
- cfasig-0.1.0/src/cfasig/cli.py +107 -0
- cfasig-0.1.0/src/cfasig/geometria.py +448 -0
- cfasig-0.1.0/src/cfasig/proyeccion.py +34 -0
- cfasig-0.1.0/src/cfasig/py.typed +0 -0
- cfasig-0.1.0/src/cfasig/raster.py +507 -0
- cfasig-0.1.0/src/cfasig.egg-info/PKG-INFO +242 -0
- cfasig-0.1.0/src/cfasig.egg-info/SOURCES.txt +26 -0
- cfasig-0.1.0/src/cfasig.egg-info/dependency_links.txt +1 -0
- cfasig-0.1.0/src/cfasig.egg-info/entry_points.txt +2 -0
- cfasig-0.1.0/src/cfasig.egg-info/requires.txt +12 -0
- cfasig-0.1.0/src/cfasig.egg-info/top_level.txt +1 -0
- cfasig-0.1.0/tests/test_archivo.py +27 -0
- cfasig-0.1.0/tests/test_campos.py +14 -0
- cfasig-0.1.0/tests/test_cli.py +107 -0
- cfasig-0.1.0/tests/test_conversion.py +79 -0
- cfasig-0.1.0/tests/test_geometria.py +163 -0
- cfasig-0.1.0/tests/test_gpx.py +103 -0
- cfasig-0.1.0/tests/test_proyeccion.py +31 -0
- cfasig-0.1.0/tests/test_puntos.py +121 -0
- cfasig-0.1.0/tests/test_raster.py +240 -0
cfasig-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 antruc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
cfasig-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cfasig
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Wrapper simple en español sobre geopandas/shapely/rasterio para tareas de SIG en CONSAEFA.
|
|
5
|
+
Author: CONSAEFA S.C.
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/CONSAEFA/cfasig
|
|
8
|
+
Project-URL: Repository, https://github.com/CONSAEFA/cfasig
|
|
9
|
+
Project-URL: Issues, https://github.com/CONSAEFA/cfasig/issues
|
|
10
|
+
Keywords: gis,sig,geopandas,shapely,rasterio,forestal
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Natural Language :: Spanish
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: geopandas>=0.13
|
|
22
|
+
Requires-Dist: shapely>=2.0
|
|
23
|
+
Requires-Dist: pandas>=1.5
|
|
24
|
+
Requires-Dist: numpy>=1.24
|
|
25
|
+
Requires-Dist: rasterio>=1.3
|
|
26
|
+
Requires-Dist: scipy>=1.10
|
|
27
|
+
Requires-Dist: pysheds>=0.3
|
|
28
|
+
Requires-Dist: openpyxl>=3.1
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# cfasig — Librería SIG, CONSAEFA S.C.
|
|
35
|
+
|
|
36
|
+
[](https://github.com/psf/black)
|
|
37
|
+
|
|
38
|
+
Wrapper en español sobre geopandas/shapely (vector) y rasterio/pysheds/scipy (raster) que simplifica tareas repetidas de SIG (cargar de shapefile o de puntos CSV/Excel, recortar, disolver, buffer, combinar, convertir entre puntos/líneas/polígonos, guardar; remuestrear, hidrología, aspecto). La idea es tener una sola función clara por operación en lugar de repetir la sintaxis de cada librería en cada script.
|
|
39
|
+
|
|
40
|
+
**Versión:** 0.1.0 | **Fecha:** Julio 2026
|
|
41
|
+
**Paquete:** `cfasig` (Python ≥ 3.10; probado en 3.14)
|
|
42
|
+
**Ruta local:** `%USERPROFILE%\Downloads\cfasig\`
|
|
43
|
+
|
|
44
|
+
## Stack
|
|
45
|
+
|
|
46
|
+
- **Base vector:** geopandas ≥ 0.13 (GeoDataFrame como estructura central).
|
|
47
|
+
- **Geometría:** shapely ≥ 2.0 (`unary_union`, `box`, `buffer`, `difference`, `split`).
|
|
48
|
+
- **Tablas:** pandas ≥ 1.5 (concatenación de capas), openpyxl ≥ 3.1 (lectura de .xlsx en `cargar_puntos`).
|
|
49
|
+
- **Raster:** rasterio ≥ 1.3 (recorte, remuestreo, rasterización, poligonización), numpy ≥ 1.24 (arrays), scipy ≥ 1.10 (`ndimage`: etiquetado, relleno, suavizado; `interpolate`/`spatial`: interpolación de MDE por TIN/IDW), pysheds ≥ 0.3 (dirección/acumulación de flujo).
|
|
50
|
+
- **Empaquetado:** setuptools + pyproject.toml. Instalable en modo editable con `pip install -e .`. Licencia MIT. Expone el comando de consola `cfasig` (ver sección CLI). Dev: pytest ≥ 7, ruff ≥ 0.1.
|
|
51
|
+
- **Idioma:** API, funciones y docstrings en español; distancias en las unidades del CRS (metros si es UTM).
|
|
52
|
+
- **Filosofía:** wrapper delgado. Cada función es una operación conocida de las librerías base con nombre simple, valores por defecto sensatos y mensajes de aviso en consola. No reinventa; ordena. No está limitado a geopandas/shapely: cubre SIG en general (vector y raster).
|
|
53
|
+
|
|
54
|
+
## Estructura de archivos
|
|
55
|
+
|
|
56
|
+
Layout `src/`: el paquete vive bajo `src/cfasig/`, así que hay que instalarlo (`pip install -e .`) para importarlo; evita que los tests importen el código desde la carpeta en vez del instalado.
|
|
57
|
+
|
|
58
|
+
- **Raíz:** pyproject.toml / README.md / CLAUDE.md (convenciones de código, estilo "ponytail")
|
|
59
|
+
- **ejemplos/** — scripts de referencia: `caminos.py`, `hidrologia.py`, `rodalizacion.py`
|
|
60
|
+
- **src/cfasig/**
|
|
61
|
+
- `__init__.py` — expone toda la API pública (`import cfasig as sig`)
|
|
62
|
+
- `archivo.py` — entrada/salida de capas
|
|
63
|
+
- `proyeccion.py` — sistemas de coordenadas (CRS)
|
|
64
|
+
- `geometria.py` — operaciones geométricas (vector)
|
|
65
|
+
- `campos.py` — utilidades de columnas de atributos
|
|
66
|
+
- `raster.py` — operaciones raster (recorte, remuestreo, hidrología, aspecto)
|
|
67
|
+
- `cli.py` — comando de consola `cfasig` (convierte entre formatos)
|
|
68
|
+
- **tests/** — suite pytest (`conftest.py` con fixtures + un `test_*.py` por módulo)
|
|
69
|
+
|
|
70
|
+
## API pública (`import cfasig as sig`)
|
|
71
|
+
|
|
72
|
+
**archivo.py**
|
|
73
|
+
|
|
74
|
+
- `cargar(ruta, capa=None, mostrar=True)` — lee una capa vectorial (.shp/.gpkg/.geojson/.gpx) como GeoDataFrame; `capa` elige la capa en formatos multicapa (GPX: waypoints/routes/tracks; GeoPackage). Opcionalmente imprime nº de entidades y CRS.
|
|
75
|
+
- `cargar_puntos(ruta, x="x", y="y", epsg=None, orden=None, mostrar=True)` — arma una capa de puntos desde un CSV o Excel (.csv/.xlsx/.xls) con columnas de coordenadas. `x`/`y` nombran las columnas de coordenada; `epsg` fija el CRS (el archivo de texto no lo trae, sin él no se puede reproyectar ni medir áreas); `orden` ordena las filas por una columna de secuencia antes de armar la capa (importa si luego conviertes a línea/polígono). Todas las columnas del archivo quedan como atributos.
|
|
76
|
+
- `convertir(entrada, salida, capa=None, gpx_como="track", mostrar=True)` — convierte de un formato a otro (`cargar` + `guardar`) en una línea; útil para bucles de lote. Si la entrada es GPX y no se da `capa`, autodetecta la capa con datos (un GPX de solo tracks leído a secas sale vacío porque la capa por defecto es waypoints).
|
|
77
|
+
- `guardar(gdf, ruta, gpx_como="track", mostrar=True)` — escribe la capa; el formato se deduce de la extensión. Para .gpx/.kml/.kmz reproyecta automáticamente a EPSG:4326 (esos formatos solo aceptan lon/lat) y usa el driver adecuado (GPX / LIBKML). Convertir = `cargar` + `guardar`: p. ej. `guardar(cargar("predio.shp"), "predio.kmz")`. GPX no admite polígonos: se exporta su contorno como línea. `gpx_como` decide cómo se escriben las líneas en GPX: `"track"` (por defecto, como trabaja el GPS aquí) o `"route"`.
|
|
78
|
+
- `cargar_waypoints(ruta, mostrar=True)` — lee los waypoints de un GPX con la tabla de atributos limpia (columnas NAME, LAYER, ELEVATION, time en ISO UTC, sym), descartando las ~18 columnas vacías del esquema fijo de GPX. Estilo Global Mapper.
|
|
79
|
+
- `cargar_tracks(ruta, utc_offset=-6, mostrar=True)` — lee los tracks de un GPX, una línea por tramo (`<trkseg>`), con columnas limpias (NAME, LAYER, gpxx_DisplayColor, START_TIME, END_TIME). Los tiempos salen del primer/último punto del tramo convertidos a hora local (`utc_offset`, por defecto -6 = Jalisco) con formato español. `convertir` usa estos dos lectores automáticamente para GPX (waypoints/tracks); las routes u otras capas caen al lector genérico.
|
|
80
|
+
|
|
81
|
+
**proyeccion.py**
|
|
82
|
+
|
|
83
|
+
- `reproyectar(gdf, epsg)` — reproyecta siempre al EPSG indicado (`to_crs`).
|
|
84
|
+
- `asegurar_crs(gdf, epsg, nombre="capa")` — reproyecta solo si la capa no está ya en ese EPSG; avisa cuando lo hace. Evita reproyecciones innecesarias.
|
|
85
|
+
|
|
86
|
+
**geometria.py**
|
|
87
|
+
|
|
88
|
+
- `recortar(gdf, mascara)` — recorta (`clip`) por otra capa; reproyecta la máscara al CRS de `gdf` si difieren.
|
|
89
|
+
- `disolver(gdf, campo=None, valor=None)` — une todas las geometrías en una sola (`unary_union`); opcionalmente etiqueta el resultado con `campo=valor`.
|
|
90
|
+
- `buffer(gdf, distancia)` — área de influencia por geometría (unidades del CRS); devuelve copia, no muta el original.
|
|
91
|
+
- `quitar_solape(gdf, otro, limpiar=True)` — resta de `gdf` lo que pise `otro` (`difference`), dando prioridad a `otro`; limpia vacías por defecto.
|
|
92
|
+
- `combinar(capas)` — concatena una lista de capas en una sola (toma el CRS de la primera).
|
|
93
|
+
- `puntos_a_linea(gdf, campo=None)` — une los puntos en una línea siguiendo el orden de las filas; con `campo` genera una línea por cada valor distinto (agrupación). Requiere ≥2 puntos por línea.
|
|
94
|
+
- `puntos_a_poligono(gdf, campo=None, envolvente=False)` — une los puntos en un polígono usándolos como vértices en orden de filas; con `envolvente=True` usa la envolvente convexa (útil si no vienen ordenados por el contorno); con `campo`, un polígono por grupo. Requiere ≥3 puntos.
|
|
95
|
+
- `linea_a_poligono(gdf)` — cierra cada línea en un polígono (sus vértices como contorno); si la línea está abierta, une el último punto con el primero. Conserva atributos.
|
|
96
|
+
- `crear_cuadro(gdf, margen=0)` — rectángulo (bounding box) alrededor de la capa, ampliado `margen`; útil como máscara de recorte previo rápido.
|
|
97
|
+
- `limpiar_vacias(gdf)` — elimina entidades con geometría vacía o nula.
|
|
98
|
+
- `reparar_geometrias(gdf)` — `buffer(0)` + descarta inválidas/vacías; el patrón de reparación que se repite tras cada operación pesada.
|
|
99
|
+
- `calcular_superficie(gdf, campo="superficie_ha", en_hectareas=True, decimales=4)` — añade una columna de área (m² del CRS, o ha si `en_hectareas`).
|
|
100
|
+
- `intersectar(gdf, otro)` — intersección con `overlay` conservando atributos de ambas capas (distinto de `recortar`, que solo recorta).
|
|
101
|
+
- `unir_atributos(gdf, otro, como="left", predicado="intersects")` — spatial join: pega las columnas de `otro` a `gdf` según su posición sin cortar geometrías (a diferencia de `intersectar`). `predicado` = 'intersects'/'within'/'contains'... Reproyecta `otro` si difiere el CRS.
|
|
102
|
+
- `calcular_longitud(gdf, campo="longitud_m", en_km=False, decimales=4)` — añade una columna con la longitud de cada geometría (unidades del CRS, o km si `en_km`); gemelo de `calcular_superficie` para líneas.
|
|
103
|
+
- `simplificar(gdf, tolerancia, conservar_topologia=True)` — reduce vértices (Douglas-Peucker); `tolerancia` en unidades del CRS. Con `conservar_topologia` (por defecto) evita auto-cruces y no rompe bordes compartidos entre polígonos vecinos.
|
|
104
|
+
- `cerrar_microhuecos(gdf, distancia, estilo_junta=2)` — closing morfológico (expandir/contraer) que cierra huecos menores a `distancia`.
|
|
105
|
+
- `subdividir_por_area(gdf, area_max_ha, campo_area="superficie_ha", min_esquirla=100, max_prof=8)` — parte los polígonos que superen `area_max_ha` por bisección recursiva del eje más largo.
|
|
106
|
+
- `fusionar_menores(gdf, area_min_ha, area_max_ha, campo_area="superficie_ha", max_iter=30)` — fusiona iterativamente los polígonos bajo el mínimo con su mejor vecino, sin superar el máximo.
|
|
107
|
+
|
|
108
|
+
**campos.py**
|
|
109
|
+
|
|
110
|
+
- `campo_seguro(gdf, campo)` — devuelve un nombre de columna que no choque con los existentes (si existe, genera una variante única `campo_ab12`).
|
|
111
|
+
|
|
112
|
+
**raster.py**
|
|
113
|
+
|
|
114
|
+
- `cargar_raster(ruta, mostrar=True)` — abre un raster de una banda; devuelve `(array, perfil, transform, crs, resolucion_m)`.
|
|
115
|
+
- `guardar_raster(arr, ruta, perfil, mostrar=True)` — escribe un array 2D a raster (float32).
|
|
116
|
+
- `recortar_raster(ruta, mascara, nodata=nan)` — recorta un raster al contorno de una capa vector (reproyecta la máscara); devuelve `(array, perfil, transform, crs, resolucion_m)`.
|
|
117
|
+
- `rellenar_nodata(arr)` — rellena NaN con el valor del píxel válido más cercano (sin interpolar).
|
|
118
|
+
- `remuestrear(arr, transform, crs, res_destino, metodo=bilinear)` — cambia la resolución; devuelve `(array, transform)`.
|
|
119
|
+
- `interpolar_mde(curvas, campo_elevacion, resolucion=None, equidistancia=None, intervalo_muestreo=None, cuadro=None, metodo="tin", mostrar=True)` — genera un MDE continuo interpolando curvas de nivel vectoriales. Muestrea puntos a intervalos regulares sobre cada curva (evita el sesgo de densidad de vértices) e interpola sobre una malla regular: `"tin"` (triangulación de Delaunay, respeta quiebres de pendiente) o `"idw"` (distancia inversa, respaldo si el TIN falla por geometría degenerada). Fuera de la envolvente convexa devuelve NaN (no extrapola; rellena luego con `rellenar_nodata`). `resolucion` por defecto = `equidistancia/4` redondeada a un valor limpio; `equidistancia` se autodetecta (moda de las diferencias entre cotas). Devuelve `(array, transform)`; encaja entre el flujo vector (`recortar`/`disolver`) y `acondicionar_mde`/`quemar_cauces`.
|
|
120
|
+
- `rasterizar(gdf, forma, transform, valor=1, relleno=0, tipo=uint8)` — vector → máscara raster.
|
|
121
|
+
- `quemar_cauces(mde, cauces, transform, profundidad)` — *stream burning*: baja la elevación del MDE en los cauces; devuelve `(array, nº_píxeles_quemados)`.
|
|
122
|
+
- `combinar_categorias(a, b, factor=10)` — empaqueta dos rasters categóricos en un ID único (`a*factor+b`); recuperar con `//factor` y `%factor`.
|
|
123
|
+
- `poligonizar(arr, transform, crs, mascara=None, campo="valor")` — raster → polígonos vector (acepta máscara booleana o entera).
|
|
124
|
+
- `acondicionar_mde(mde_path)` — pysheds: rellena pits, depresiones y zonas planas; devuelve `(grid, mde_acondicionado)`.
|
|
125
|
+
- `direccion_flujo(grid, dem)` — dirección de flujo D8.
|
|
126
|
+
- `acumulacion_flujo(grid, fdir)` — acumulación de flujo (array numpy).
|
|
127
|
+
- `etiquetar_cuencas(acumulacion, umbral)` — laderas de no-cauce (acumulación ≤ umbral) etiquetadas como cuencas; devuelve `(array, n_cuencas)`.
|
|
128
|
+
- `calcular_aspecto(mde, res, suavizar=3, nodata=-9999)` — orientación cardinal por píxel (0=plano/sin orientación, 1=N, 2=E, 3=S, 4=O).
|
|
129
|
+
|
|
130
|
+
## Ejemplos de uso (`ejemplos/`)
|
|
131
|
+
|
|
132
|
+
`ejemplos/caminos.py` como referencia. Los otros dos (`ejemplos/hidrologia.py`, `ejemplos/rodalizacion.py`) siguen el mismo patrón.
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
"""
|
|
136
|
+
Ejemplo: buffer jerárquico de caminos (primario > secundario > saca).
|
|
137
|
+
|
|
138
|
+
Cada nivel recorta al inferior para que no se solapen. Muestra el uso
|
|
139
|
+
de cfasig con un patrón repetido resuelto en un bucle.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
import cfasig as sig
|
|
143
|
+
|
|
144
|
+
# ── CONFIG ───────────────────────────────────────────────────
|
|
145
|
+
EPSG_UTM = 32613
|
|
146
|
+
|
|
147
|
+
# nombre, ruta, buffer (en metros). Orden = prioridad (mayor a menor).
|
|
148
|
+
NIVELES = [
|
|
149
|
+
("PRIMARIO", "/home/user/Downloads/es/EJ_ESTANCIA_F13_D51_CAMINO_PRIM_1.shp", 5.0),
|
|
150
|
+
("SECUNDARIO", "/home/user/Downloads/es/EJ_ESTANCIA_F13_D51_CAMINO_SEC_1.shp", 3.0),
|
|
151
|
+
("SACA", "/home/user/Downloads/es/EJ_ESTANCIA_F13_D51_CAMINO_SACA_1.shp", 1.75),
|
|
152
|
+
]
|
|
153
|
+
CAMPO_TIPO = "camino"
|
|
154
|
+
|
|
155
|
+
SALIDA_COMBINADO = "/home/user/Downloads/CAMINOS_BUFFER_COMBINADO.shp"
|
|
156
|
+
SALIDA_DISUELTO = "/home/user/Downloads/CAMINOS_BUFFER_DISUELTO.shp"
|
|
157
|
+
|
|
158
|
+
# ── 1. CARGAR, ASEGURAR CRS, DISOLVER Y BUFFER CADA NIVEL ────
|
|
159
|
+
capas = []
|
|
160
|
+
for nombre, ruta, dist in NIVELES:
|
|
161
|
+
gdf = sig.cargar(ruta)
|
|
162
|
+
gdf = sig.asegurar_crs(gdf, EPSG_UTM, nombre)
|
|
163
|
+
gdf = sig.disolver(gdf, campo=CAMPO_TIPO, valor=nombre)
|
|
164
|
+
gdf = sig.buffer(gdf, dist)
|
|
165
|
+
capas.append(gdf)
|
|
166
|
+
|
|
167
|
+
# ── 2. QUITAR SOLAPE: cada nivel pierde contra el inmediato superior ──
|
|
168
|
+
for i in range(1, len(capas)):
|
|
169
|
+
capas[i] = sig.quitar_solape(capas[i], capas[i - 1])
|
|
170
|
+
|
|
171
|
+
# ── 3. COMBINAR Y GUARDAR ────────────────────────────────────
|
|
172
|
+
combinado = sig.combinar(capas)
|
|
173
|
+
sig.guardar(combinado, SALIDA_COMBINADO)
|
|
174
|
+
|
|
175
|
+
# ── 4. VERSIÓN DISUELTA (una sola geometría) ─────────────────
|
|
176
|
+
sig.guardar(sig.disolver(combinado, campo="caminos", valor="caminos"), SALIDA_DISUELTO)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Estilo y estructura de un script** (confirmado contra los tres scripts de `ejemplos/`):
|
|
180
|
+
|
|
181
|
+
- Docstring corto al inicio: qué hace y por qué (una frase de "qué", una de "por qué" si no es obvio).
|
|
182
|
+
- Bloque `CONFIG` arriba de todo, en mayúsculas: rutas, EPSG, distancias, campos. Nada de esto va suelto en medio de la lógica; si hay que tocar un valor para correrlo en otro predio, se toca aquí y solo aquí.
|
|
183
|
+
- Cuando hay una lista de "cosas parecidas" y vale la pena (3+ niveles/casos), se modela como lista de tuplas (`NIVELES` en `caminos.py`) y se recorre con un `for`, en vez de repetir el bloque una vez por elemento; el orden puede codificar prioridad. Con solo 2 casos (perenne/intermitente en `hidrologia.py`) no se generaliza: se escriben los dos bloques directo, sin loop ni lista, porque el loop no ahorra nada con dos ramas. Loop si repites de verdad, directo si no (YAGNI).
|
|
184
|
+
- Pasos numerados con comentarios `# ── N. VERBO EN MAYÚSCULAS ──`: cada bloque es una etapa clara del flujo (cargar, quitar solape, combinar, guardar). Sirve para ubicarse en scripts de 40-100 líneas sin funciones propias.
|
|
185
|
+
- El script encadena funciones de `cfasig` (`sig.cargar`, `sig.buffer`, ...); no reimplementa nada que la librería ya resuelva. La única lógica que vive en el script es la específica del caso (qué niveles hay, qué prioridad tienen, claves de dominio como UMM).
|
|
186
|
+
- `print()` como reporte, no logging: además de los avisos que ya imprimen funciones como `cargar` (nº de entidades, CRS), el script puede imprimir sus propios diagnósticos a media ejecución (valores únicos de un campo, tamaño de un raster, píxeles quemados) y, si el resultado tiene métricas que valen la pena, un resumen al final (conteos, sumas, rangos) — ver `rodalizacion.py`. Si el script ya va a imprimir lo suyo, usa `mostrar=False` en `cargar`/`cargar_raster` para no duplicar el aviso.
|
|
187
|
+
- Sin funciones ni clases propias salvo que el script se vuelva a llamar con distintos parámetros; un script de un solo uso es lineal de arriba a abajo, reutilizando el mismo nombre de variable al reasignar (`gdf = sig.algo(gdf)`) en vez de encadenar nombres nuevos por paso.
|
|
188
|
+
- Nombres de variable en español, cortos y descriptivos (`gdf`, `capas`, `combinado`); constantes de config en mayúsculas.
|
|
189
|
+
|
|
190
|
+
## Instalación / uso
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# desde la carpeta que contiene pyproject.toml
|
|
194
|
+
pip install -e .
|
|
195
|
+
|
|
196
|
+
# luego, desde cualquier script:
|
|
197
|
+
import cfasig as sig
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Con layout `src/`, la instalación (`pip install -e .`) es obligatoria: sin ella `import cfasig` no encuentra el paquete.
|
|
201
|
+
|
|
202
|
+
## CLI (`cfasig`)
|
|
203
|
+
|
|
204
|
+
La instalación registra el comando de consola `cfasig` (`project.scripts` → `cli.main`). Hoy hace una sola cosa: convertir archivos entre formatos, uno o varios en la misma llamada (proceso por lote). La salida se autonombra (misma carpeta y nombre, extensión nueva).
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
cfasig predio.shp kmz # crea predio.kmz
|
|
208
|
+
cfasig ruta.gpx shp # crea ruta.shp
|
|
209
|
+
cfasig a.shp b.shp c.shp kmz # lote: crea a.kmz, b.kmz, c.kmz
|
|
210
|
+
cfasig ayuda # muestra la ayuda
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Uso: `cfasig ARCHIVO [ARCHIVO ...] TIPO`. Para el lote se pasan varios archivos y el TIPO al final; pensado para arrastrar los archivos a la terminal (quedan separados por espacios), sin glob ni comodines porque el usuario no técnico los arrastra. Con varios archivos imprime un resumen final (`Listo: N convertidos, M con error`) y sigue con los demás si uno falla. Un archivo que ya está en el formato destino se salta.
|
|
214
|
+
|
|
215
|
+
Tipos válidos: `shp`, `gpkg`, `geojson`, `gpx`, `kml`, `kmz`. Opciones avanzadas: `--capa NOMBRE` (capa a leer en archivos multicapa) y `--gpx-como {track,route}`. Pide confirmación antes de sobrescribir cada archivo. Ayuda y errores en español; la ayuda no carga geopandas (import perezoso). `# ponytail:` sin subcomandos porque solo convierte; migra a subparsers si crecen las operaciones.
|
|
216
|
+
|
|
217
|
+
## Tests
|
|
218
|
+
|
|
219
|
+
Suite en `tests/`, un archivo por módulo/tema (`test_archivo`, `test_campos`, `test_proyeccion`, `test_geometria`, `test_raster`, más `test_conversion` para el ida y vuelta entre formatos, `test_puntos` para carga de puntos CSV/Excel y su conversión a línea/polígono, `test_gpx` para los lectores limpios de waypoints/tracks, y `test_cli` para el comando de consola), con fixtures compartidas en `conftest.py`. Cubre la lógica determinista de vector y raster; los helpers de flujo de pysheds (`acondicionar_mde`, `direccion_flujo`, `acumulacion_flujo`) quedan pendientes hasta tener un MDE de prueba.
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
pip install -e ".[dev]" # instala pytest
|
|
223
|
+
pytest
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Convenciones y decisiones
|
|
227
|
+
|
|
228
|
+
- Wrapper delgado: una función = una operación de la librería base (vector o raster), con nombre simple en español.
|
|
229
|
+
- La lógica de dominio (claves UMM, valores calibrados) se queda en los scripts; la librería solo aporta operaciones SIG genéricas.
|
|
230
|
+
- Raster: las funciones trabajan sobre arrays 2D (numpy) más su `transform`/`crs`, que es como rasterio maneja los datos. Los scripts no necesitan importar numpy para el flujo normal.
|
|
231
|
+
- Las funciones que devuelven capa (`buffer`, `quitar_solape`, `limpiar_vacias`) trabajan sobre una copia; no mutan la entrada.
|
|
232
|
+
- Distancias y márgenes en unidades del CRS activo (usar UTM para metros).
|
|
233
|
+
- `disolver` y `combinar` conservan el CRS de origen; `recortar` alinea CRS automáticamente.
|
|
234
|
+
- Prioridad entre capas modelada con `quitar_solape` (la capa `otro` gana el solape).
|
|
235
|
+
- Mensajes informativos por consola (`print`); sin logging formal todavía.
|
|
236
|
+
- Seguros implementados dentro de cada función: un helper compartido, `_exigir_crs(gdf, nombre)` (proyeccion.py, usado en proyeccion/geometria/raster), más chequeos en línea (campo inexistente, ruta inexistente, lista/capa vacía, factor que colisiona, máscara que no intersecta). Fallan con `raise ValueError` de mensaje claro.
|
|
237
|
+
- EPSG de trabajo típico: 32613 (UTM 13N, WGS84) para la zona de operación.
|
|
238
|
+
|
|
239
|
+
## Pendientes / ideas para crecer
|
|
240
|
+
|
|
241
|
+
- Completar los tests de flujo hidrológico (pysheds) con un MDE pequeño de prueba: es lo único de la API que aún no tiene cobertura.
|
|
242
|
+
- `validar_mde(mde, curvas, campo_elevacion)`: comparar el MDE interpolado contra las cotas originales de las curvas (percentiles/residuales) para medir la calidad. Quedó fuera de `interpolar_mde`; sería función aparte.
|
cfasig-0.1.0/README.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# cfasig — Librería SIG, CONSAEFA S.C.
|
|
2
|
+
|
|
3
|
+
[](https://github.com/psf/black)
|
|
4
|
+
|
|
5
|
+
Wrapper en español sobre geopandas/shapely (vector) y rasterio/pysheds/scipy (raster) que simplifica tareas repetidas de SIG (cargar de shapefile o de puntos CSV/Excel, recortar, disolver, buffer, combinar, convertir entre puntos/líneas/polígonos, guardar; remuestrear, hidrología, aspecto). La idea es tener una sola función clara por operación en lugar de repetir la sintaxis de cada librería en cada script.
|
|
6
|
+
|
|
7
|
+
**Versión:** 0.1.0 | **Fecha:** Julio 2026
|
|
8
|
+
**Paquete:** `cfasig` (Python ≥ 3.10; probado en 3.14)
|
|
9
|
+
**Ruta local:** `%USERPROFILE%\Downloads\cfasig\`
|
|
10
|
+
|
|
11
|
+
## Stack
|
|
12
|
+
|
|
13
|
+
- **Base vector:** geopandas ≥ 0.13 (GeoDataFrame como estructura central).
|
|
14
|
+
- **Geometría:** shapely ≥ 2.0 (`unary_union`, `box`, `buffer`, `difference`, `split`).
|
|
15
|
+
- **Tablas:** pandas ≥ 1.5 (concatenación de capas), openpyxl ≥ 3.1 (lectura de .xlsx en `cargar_puntos`).
|
|
16
|
+
- **Raster:** rasterio ≥ 1.3 (recorte, remuestreo, rasterización, poligonización), numpy ≥ 1.24 (arrays), scipy ≥ 1.10 (`ndimage`: etiquetado, relleno, suavizado; `interpolate`/`spatial`: interpolación de MDE por TIN/IDW), pysheds ≥ 0.3 (dirección/acumulación de flujo).
|
|
17
|
+
- **Empaquetado:** setuptools + pyproject.toml. Instalable en modo editable con `pip install -e .`. Licencia MIT. Expone el comando de consola `cfasig` (ver sección CLI). Dev: pytest ≥ 7, ruff ≥ 0.1.
|
|
18
|
+
- **Idioma:** API, funciones y docstrings en español; distancias en las unidades del CRS (metros si es UTM).
|
|
19
|
+
- **Filosofía:** wrapper delgado. Cada función es una operación conocida de las librerías base con nombre simple, valores por defecto sensatos y mensajes de aviso en consola. No reinventa; ordena. No está limitado a geopandas/shapely: cubre SIG en general (vector y raster).
|
|
20
|
+
|
|
21
|
+
## Estructura de archivos
|
|
22
|
+
|
|
23
|
+
Layout `src/`: el paquete vive bajo `src/cfasig/`, así que hay que instalarlo (`pip install -e .`) para importarlo; evita que los tests importen el código desde la carpeta en vez del instalado.
|
|
24
|
+
|
|
25
|
+
- **Raíz:** pyproject.toml / README.md / CLAUDE.md (convenciones de código, estilo "ponytail")
|
|
26
|
+
- **ejemplos/** — scripts de referencia: `caminos.py`, `hidrologia.py`, `rodalizacion.py`
|
|
27
|
+
- **src/cfasig/**
|
|
28
|
+
- `__init__.py` — expone toda la API pública (`import cfasig as sig`)
|
|
29
|
+
- `archivo.py` — entrada/salida de capas
|
|
30
|
+
- `proyeccion.py` — sistemas de coordenadas (CRS)
|
|
31
|
+
- `geometria.py` — operaciones geométricas (vector)
|
|
32
|
+
- `campos.py` — utilidades de columnas de atributos
|
|
33
|
+
- `raster.py` — operaciones raster (recorte, remuestreo, hidrología, aspecto)
|
|
34
|
+
- `cli.py` — comando de consola `cfasig` (convierte entre formatos)
|
|
35
|
+
- **tests/** — suite pytest (`conftest.py` con fixtures + un `test_*.py` por módulo)
|
|
36
|
+
|
|
37
|
+
## API pública (`import cfasig as sig`)
|
|
38
|
+
|
|
39
|
+
**archivo.py**
|
|
40
|
+
|
|
41
|
+
- `cargar(ruta, capa=None, mostrar=True)` — lee una capa vectorial (.shp/.gpkg/.geojson/.gpx) como GeoDataFrame; `capa` elige la capa en formatos multicapa (GPX: waypoints/routes/tracks; GeoPackage). Opcionalmente imprime nº de entidades y CRS.
|
|
42
|
+
- `cargar_puntos(ruta, x="x", y="y", epsg=None, orden=None, mostrar=True)` — arma una capa de puntos desde un CSV o Excel (.csv/.xlsx/.xls) con columnas de coordenadas. `x`/`y` nombran las columnas de coordenada; `epsg` fija el CRS (el archivo de texto no lo trae, sin él no se puede reproyectar ni medir áreas); `orden` ordena las filas por una columna de secuencia antes de armar la capa (importa si luego conviertes a línea/polígono). Todas las columnas del archivo quedan como atributos.
|
|
43
|
+
- `convertir(entrada, salida, capa=None, gpx_como="track", mostrar=True)` — convierte de un formato a otro (`cargar` + `guardar`) en una línea; útil para bucles de lote. Si la entrada es GPX y no se da `capa`, autodetecta la capa con datos (un GPX de solo tracks leído a secas sale vacío porque la capa por defecto es waypoints).
|
|
44
|
+
- `guardar(gdf, ruta, gpx_como="track", mostrar=True)` — escribe la capa; el formato se deduce de la extensión. Para .gpx/.kml/.kmz reproyecta automáticamente a EPSG:4326 (esos formatos solo aceptan lon/lat) y usa el driver adecuado (GPX / LIBKML). Convertir = `cargar` + `guardar`: p. ej. `guardar(cargar("predio.shp"), "predio.kmz")`. GPX no admite polígonos: se exporta su contorno como línea. `gpx_como` decide cómo se escriben las líneas en GPX: `"track"` (por defecto, como trabaja el GPS aquí) o `"route"`.
|
|
45
|
+
- `cargar_waypoints(ruta, mostrar=True)` — lee los waypoints de un GPX con la tabla de atributos limpia (columnas NAME, LAYER, ELEVATION, time en ISO UTC, sym), descartando las ~18 columnas vacías del esquema fijo de GPX. Estilo Global Mapper.
|
|
46
|
+
- `cargar_tracks(ruta, utc_offset=-6, mostrar=True)` — lee los tracks de un GPX, una línea por tramo (`<trkseg>`), con columnas limpias (NAME, LAYER, gpxx_DisplayColor, START_TIME, END_TIME). Los tiempos salen del primer/último punto del tramo convertidos a hora local (`utc_offset`, por defecto -6 = Jalisco) con formato español. `convertir` usa estos dos lectores automáticamente para GPX (waypoints/tracks); las routes u otras capas caen al lector genérico.
|
|
47
|
+
|
|
48
|
+
**proyeccion.py**
|
|
49
|
+
|
|
50
|
+
- `reproyectar(gdf, epsg)` — reproyecta siempre al EPSG indicado (`to_crs`).
|
|
51
|
+
- `asegurar_crs(gdf, epsg, nombre="capa")` — reproyecta solo si la capa no está ya en ese EPSG; avisa cuando lo hace. Evita reproyecciones innecesarias.
|
|
52
|
+
|
|
53
|
+
**geometria.py**
|
|
54
|
+
|
|
55
|
+
- `recortar(gdf, mascara)` — recorta (`clip`) por otra capa; reproyecta la máscara al CRS de `gdf` si difieren.
|
|
56
|
+
- `disolver(gdf, campo=None, valor=None)` — une todas las geometrías en una sola (`unary_union`); opcionalmente etiqueta el resultado con `campo=valor`.
|
|
57
|
+
- `buffer(gdf, distancia)` — área de influencia por geometría (unidades del CRS); devuelve copia, no muta el original.
|
|
58
|
+
- `quitar_solape(gdf, otro, limpiar=True)` — resta de `gdf` lo que pise `otro` (`difference`), dando prioridad a `otro`; limpia vacías por defecto.
|
|
59
|
+
- `combinar(capas)` — concatena una lista de capas en una sola (toma el CRS de la primera).
|
|
60
|
+
- `puntos_a_linea(gdf, campo=None)` — une los puntos en una línea siguiendo el orden de las filas; con `campo` genera una línea por cada valor distinto (agrupación). Requiere ≥2 puntos por línea.
|
|
61
|
+
- `puntos_a_poligono(gdf, campo=None, envolvente=False)` — une los puntos en un polígono usándolos como vértices en orden de filas; con `envolvente=True` usa la envolvente convexa (útil si no vienen ordenados por el contorno); con `campo`, un polígono por grupo. Requiere ≥3 puntos.
|
|
62
|
+
- `linea_a_poligono(gdf)` — cierra cada línea en un polígono (sus vértices como contorno); si la línea está abierta, une el último punto con el primero. Conserva atributos.
|
|
63
|
+
- `crear_cuadro(gdf, margen=0)` — rectángulo (bounding box) alrededor de la capa, ampliado `margen`; útil como máscara de recorte previo rápido.
|
|
64
|
+
- `limpiar_vacias(gdf)` — elimina entidades con geometría vacía o nula.
|
|
65
|
+
- `reparar_geometrias(gdf)` — `buffer(0)` + descarta inválidas/vacías; el patrón de reparación que se repite tras cada operación pesada.
|
|
66
|
+
- `calcular_superficie(gdf, campo="superficie_ha", en_hectareas=True, decimales=4)` — añade una columna de área (m² del CRS, o ha si `en_hectareas`).
|
|
67
|
+
- `intersectar(gdf, otro)` — intersección con `overlay` conservando atributos de ambas capas (distinto de `recortar`, que solo recorta).
|
|
68
|
+
- `unir_atributos(gdf, otro, como="left", predicado="intersects")` — spatial join: pega las columnas de `otro` a `gdf` según su posición sin cortar geometrías (a diferencia de `intersectar`). `predicado` = 'intersects'/'within'/'contains'... Reproyecta `otro` si difiere el CRS.
|
|
69
|
+
- `calcular_longitud(gdf, campo="longitud_m", en_km=False, decimales=4)` — añade una columna con la longitud de cada geometría (unidades del CRS, o km si `en_km`); gemelo de `calcular_superficie` para líneas.
|
|
70
|
+
- `simplificar(gdf, tolerancia, conservar_topologia=True)` — reduce vértices (Douglas-Peucker); `tolerancia` en unidades del CRS. Con `conservar_topologia` (por defecto) evita auto-cruces y no rompe bordes compartidos entre polígonos vecinos.
|
|
71
|
+
- `cerrar_microhuecos(gdf, distancia, estilo_junta=2)` — closing morfológico (expandir/contraer) que cierra huecos menores a `distancia`.
|
|
72
|
+
- `subdividir_por_area(gdf, area_max_ha, campo_area="superficie_ha", min_esquirla=100, max_prof=8)` — parte los polígonos que superen `area_max_ha` por bisección recursiva del eje más largo.
|
|
73
|
+
- `fusionar_menores(gdf, area_min_ha, area_max_ha, campo_area="superficie_ha", max_iter=30)` — fusiona iterativamente los polígonos bajo el mínimo con su mejor vecino, sin superar el máximo.
|
|
74
|
+
|
|
75
|
+
**campos.py**
|
|
76
|
+
|
|
77
|
+
- `campo_seguro(gdf, campo)` — devuelve un nombre de columna que no choque con los existentes (si existe, genera una variante única `campo_ab12`).
|
|
78
|
+
|
|
79
|
+
**raster.py**
|
|
80
|
+
|
|
81
|
+
- `cargar_raster(ruta, mostrar=True)` — abre un raster de una banda; devuelve `(array, perfil, transform, crs, resolucion_m)`.
|
|
82
|
+
- `guardar_raster(arr, ruta, perfil, mostrar=True)` — escribe un array 2D a raster (float32).
|
|
83
|
+
- `recortar_raster(ruta, mascara, nodata=nan)` — recorta un raster al contorno de una capa vector (reproyecta la máscara); devuelve `(array, perfil, transform, crs, resolucion_m)`.
|
|
84
|
+
- `rellenar_nodata(arr)` — rellena NaN con el valor del píxel válido más cercano (sin interpolar).
|
|
85
|
+
- `remuestrear(arr, transform, crs, res_destino, metodo=bilinear)` — cambia la resolución; devuelve `(array, transform)`.
|
|
86
|
+
- `interpolar_mde(curvas, campo_elevacion, resolucion=None, equidistancia=None, intervalo_muestreo=None, cuadro=None, metodo="tin", mostrar=True)` — genera un MDE continuo interpolando curvas de nivel vectoriales. Muestrea puntos a intervalos regulares sobre cada curva (evita el sesgo de densidad de vértices) e interpola sobre una malla regular: `"tin"` (triangulación de Delaunay, respeta quiebres de pendiente) o `"idw"` (distancia inversa, respaldo si el TIN falla por geometría degenerada). Fuera de la envolvente convexa devuelve NaN (no extrapola; rellena luego con `rellenar_nodata`). `resolucion` por defecto = `equidistancia/4` redondeada a un valor limpio; `equidistancia` se autodetecta (moda de las diferencias entre cotas). Devuelve `(array, transform)`; encaja entre el flujo vector (`recortar`/`disolver`) y `acondicionar_mde`/`quemar_cauces`.
|
|
87
|
+
- `rasterizar(gdf, forma, transform, valor=1, relleno=0, tipo=uint8)` — vector → máscara raster.
|
|
88
|
+
- `quemar_cauces(mde, cauces, transform, profundidad)` — *stream burning*: baja la elevación del MDE en los cauces; devuelve `(array, nº_píxeles_quemados)`.
|
|
89
|
+
- `combinar_categorias(a, b, factor=10)` — empaqueta dos rasters categóricos en un ID único (`a*factor+b`); recuperar con `//factor` y `%factor`.
|
|
90
|
+
- `poligonizar(arr, transform, crs, mascara=None, campo="valor")` — raster → polígonos vector (acepta máscara booleana o entera).
|
|
91
|
+
- `acondicionar_mde(mde_path)` — pysheds: rellena pits, depresiones y zonas planas; devuelve `(grid, mde_acondicionado)`.
|
|
92
|
+
- `direccion_flujo(grid, dem)` — dirección de flujo D8.
|
|
93
|
+
- `acumulacion_flujo(grid, fdir)` — acumulación de flujo (array numpy).
|
|
94
|
+
- `etiquetar_cuencas(acumulacion, umbral)` — laderas de no-cauce (acumulación ≤ umbral) etiquetadas como cuencas; devuelve `(array, n_cuencas)`.
|
|
95
|
+
- `calcular_aspecto(mde, res, suavizar=3, nodata=-9999)` — orientación cardinal por píxel (0=plano/sin orientación, 1=N, 2=E, 3=S, 4=O).
|
|
96
|
+
|
|
97
|
+
## Ejemplos de uso (`ejemplos/`)
|
|
98
|
+
|
|
99
|
+
`ejemplos/caminos.py` como referencia. Los otros dos (`ejemplos/hidrologia.py`, `ejemplos/rodalizacion.py`) siguen el mismo patrón.
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
"""
|
|
103
|
+
Ejemplo: buffer jerárquico de caminos (primario > secundario > saca).
|
|
104
|
+
|
|
105
|
+
Cada nivel recorta al inferior para que no se solapen. Muestra el uso
|
|
106
|
+
de cfasig con un patrón repetido resuelto en un bucle.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
import cfasig as sig
|
|
110
|
+
|
|
111
|
+
# ── CONFIG ───────────────────────────────────────────────────
|
|
112
|
+
EPSG_UTM = 32613
|
|
113
|
+
|
|
114
|
+
# nombre, ruta, buffer (en metros). Orden = prioridad (mayor a menor).
|
|
115
|
+
NIVELES = [
|
|
116
|
+
("PRIMARIO", "/home/user/Downloads/es/EJ_ESTANCIA_F13_D51_CAMINO_PRIM_1.shp", 5.0),
|
|
117
|
+
("SECUNDARIO", "/home/user/Downloads/es/EJ_ESTANCIA_F13_D51_CAMINO_SEC_1.shp", 3.0),
|
|
118
|
+
("SACA", "/home/user/Downloads/es/EJ_ESTANCIA_F13_D51_CAMINO_SACA_1.shp", 1.75),
|
|
119
|
+
]
|
|
120
|
+
CAMPO_TIPO = "camino"
|
|
121
|
+
|
|
122
|
+
SALIDA_COMBINADO = "/home/user/Downloads/CAMINOS_BUFFER_COMBINADO.shp"
|
|
123
|
+
SALIDA_DISUELTO = "/home/user/Downloads/CAMINOS_BUFFER_DISUELTO.shp"
|
|
124
|
+
|
|
125
|
+
# ── 1. CARGAR, ASEGURAR CRS, DISOLVER Y BUFFER CADA NIVEL ────
|
|
126
|
+
capas = []
|
|
127
|
+
for nombre, ruta, dist in NIVELES:
|
|
128
|
+
gdf = sig.cargar(ruta)
|
|
129
|
+
gdf = sig.asegurar_crs(gdf, EPSG_UTM, nombre)
|
|
130
|
+
gdf = sig.disolver(gdf, campo=CAMPO_TIPO, valor=nombre)
|
|
131
|
+
gdf = sig.buffer(gdf, dist)
|
|
132
|
+
capas.append(gdf)
|
|
133
|
+
|
|
134
|
+
# ── 2. QUITAR SOLAPE: cada nivel pierde contra el inmediato superior ──
|
|
135
|
+
for i in range(1, len(capas)):
|
|
136
|
+
capas[i] = sig.quitar_solape(capas[i], capas[i - 1])
|
|
137
|
+
|
|
138
|
+
# ── 3. COMBINAR Y GUARDAR ────────────────────────────────────
|
|
139
|
+
combinado = sig.combinar(capas)
|
|
140
|
+
sig.guardar(combinado, SALIDA_COMBINADO)
|
|
141
|
+
|
|
142
|
+
# ── 4. VERSIÓN DISUELTA (una sola geometría) ─────────────────
|
|
143
|
+
sig.guardar(sig.disolver(combinado, campo="caminos", valor="caminos"), SALIDA_DISUELTO)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Estilo y estructura de un script** (confirmado contra los tres scripts de `ejemplos/`):
|
|
147
|
+
|
|
148
|
+
- Docstring corto al inicio: qué hace y por qué (una frase de "qué", una de "por qué" si no es obvio).
|
|
149
|
+
- Bloque `CONFIG` arriba de todo, en mayúsculas: rutas, EPSG, distancias, campos. Nada de esto va suelto en medio de la lógica; si hay que tocar un valor para correrlo en otro predio, se toca aquí y solo aquí.
|
|
150
|
+
- Cuando hay una lista de "cosas parecidas" y vale la pena (3+ niveles/casos), se modela como lista de tuplas (`NIVELES` en `caminos.py`) y se recorre con un `for`, en vez de repetir el bloque una vez por elemento; el orden puede codificar prioridad. Con solo 2 casos (perenne/intermitente en `hidrologia.py`) no se generaliza: se escriben los dos bloques directo, sin loop ni lista, porque el loop no ahorra nada con dos ramas. Loop si repites de verdad, directo si no (YAGNI).
|
|
151
|
+
- Pasos numerados con comentarios `# ── N. VERBO EN MAYÚSCULAS ──`: cada bloque es una etapa clara del flujo (cargar, quitar solape, combinar, guardar). Sirve para ubicarse en scripts de 40-100 líneas sin funciones propias.
|
|
152
|
+
- El script encadena funciones de `cfasig` (`sig.cargar`, `sig.buffer`, ...); no reimplementa nada que la librería ya resuelva. La única lógica que vive en el script es la específica del caso (qué niveles hay, qué prioridad tienen, claves de dominio como UMM).
|
|
153
|
+
- `print()` como reporte, no logging: además de los avisos que ya imprimen funciones como `cargar` (nº de entidades, CRS), el script puede imprimir sus propios diagnósticos a media ejecución (valores únicos de un campo, tamaño de un raster, píxeles quemados) y, si el resultado tiene métricas que valen la pena, un resumen al final (conteos, sumas, rangos) — ver `rodalizacion.py`. Si el script ya va a imprimir lo suyo, usa `mostrar=False` en `cargar`/`cargar_raster` para no duplicar el aviso.
|
|
154
|
+
- Sin funciones ni clases propias salvo que el script se vuelva a llamar con distintos parámetros; un script de un solo uso es lineal de arriba a abajo, reutilizando el mismo nombre de variable al reasignar (`gdf = sig.algo(gdf)`) en vez de encadenar nombres nuevos por paso.
|
|
155
|
+
- Nombres de variable en español, cortos y descriptivos (`gdf`, `capas`, `combinado`); constantes de config en mayúsculas.
|
|
156
|
+
|
|
157
|
+
## Instalación / uso
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# desde la carpeta que contiene pyproject.toml
|
|
161
|
+
pip install -e .
|
|
162
|
+
|
|
163
|
+
# luego, desde cualquier script:
|
|
164
|
+
import cfasig as sig
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Con layout `src/`, la instalación (`pip install -e .`) es obligatoria: sin ella `import cfasig` no encuentra el paquete.
|
|
168
|
+
|
|
169
|
+
## CLI (`cfasig`)
|
|
170
|
+
|
|
171
|
+
La instalación registra el comando de consola `cfasig` (`project.scripts` → `cli.main`). Hoy hace una sola cosa: convertir archivos entre formatos, uno o varios en la misma llamada (proceso por lote). La salida se autonombra (misma carpeta y nombre, extensión nueva).
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
cfasig predio.shp kmz # crea predio.kmz
|
|
175
|
+
cfasig ruta.gpx shp # crea ruta.shp
|
|
176
|
+
cfasig a.shp b.shp c.shp kmz # lote: crea a.kmz, b.kmz, c.kmz
|
|
177
|
+
cfasig ayuda # muestra la ayuda
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Uso: `cfasig ARCHIVO [ARCHIVO ...] TIPO`. Para el lote se pasan varios archivos y el TIPO al final; pensado para arrastrar los archivos a la terminal (quedan separados por espacios), sin glob ni comodines porque el usuario no técnico los arrastra. Con varios archivos imprime un resumen final (`Listo: N convertidos, M con error`) y sigue con los demás si uno falla. Un archivo que ya está en el formato destino se salta.
|
|
181
|
+
|
|
182
|
+
Tipos válidos: `shp`, `gpkg`, `geojson`, `gpx`, `kml`, `kmz`. Opciones avanzadas: `--capa NOMBRE` (capa a leer en archivos multicapa) y `--gpx-como {track,route}`. Pide confirmación antes de sobrescribir cada archivo. Ayuda y errores en español; la ayuda no carga geopandas (import perezoso). `# ponytail:` sin subcomandos porque solo convierte; migra a subparsers si crecen las operaciones.
|
|
183
|
+
|
|
184
|
+
## Tests
|
|
185
|
+
|
|
186
|
+
Suite en `tests/`, un archivo por módulo/tema (`test_archivo`, `test_campos`, `test_proyeccion`, `test_geometria`, `test_raster`, más `test_conversion` para el ida y vuelta entre formatos, `test_puntos` para carga de puntos CSV/Excel y su conversión a línea/polígono, `test_gpx` para los lectores limpios de waypoints/tracks, y `test_cli` para el comando de consola), con fixtures compartidas en `conftest.py`. Cubre la lógica determinista de vector y raster; los helpers de flujo de pysheds (`acondicionar_mde`, `direccion_flujo`, `acumulacion_flujo`) quedan pendientes hasta tener un MDE de prueba.
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
pip install -e ".[dev]" # instala pytest
|
|
190
|
+
pytest
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Convenciones y decisiones
|
|
194
|
+
|
|
195
|
+
- Wrapper delgado: una función = una operación de la librería base (vector o raster), con nombre simple en español.
|
|
196
|
+
- La lógica de dominio (claves UMM, valores calibrados) se queda en los scripts; la librería solo aporta operaciones SIG genéricas.
|
|
197
|
+
- Raster: las funciones trabajan sobre arrays 2D (numpy) más su `transform`/`crs`, que es como rasterio maneja los datos. Los scripts no necesitan importar numpy para el flujo normal.
|
|
198
|
+
- Las funciones que devuelven capa (`buffer`, `quitar_solape`, `limpiar_vacias`) trabajan sobre una copia; no mutan la entrada.
|
|
199
|
+
- Distancias y márgenes en unidades del CRS activo (usar UTM para metros).
|
|
200
|
+
- `disolver` y `combinar` conservan el CRS de origen; `recortar` alinea CRS automáticamente.
|
|
201
|
+
- Prioridad entre capas modelada con `quitar_solape` (la capa `otro` gana el solape).
|
|
202
|
+
- Mensajes informativos por consola (`print`); sin logging formal todavía.
|
|
203
|
+
- Seguros implementados dentro de cada función: un helper compartido, `_exigir_crs(gdf, nombre)` (proyeccion.py, usado en proyeccion/geometria/raster), más chequeos en línea (campo inexistente, ruta inexistente, lista/capa vacía, factor que colisiona, máscara que no intersecta). Fallan con `raise ValueError` de mensaje claro.
|
|
204
|
+
- EPSG de trabajo típico: 32613 (UTM 13N, WGS84) para la zona de operación.
|
|
205
|
+
|
|
206
|
+
## Pendientes / ideas para crecer
|
|
207
|
+
|
|
208
|
+
- Completar los tests de flujo hidrológico (pysheds) con un MDE pequeño de prueba: es lo único de la API que aún no tiene cobertura.
|
|
209
|
+
- `validar_mde(mde, curvas, campo_elevacion)`: comparar el MDE interpolado contra las cotas originales de las curvas (percentiles/residuales) para medir la calidad. Quedó fuera de `interpolar_mde`; sería función aparte.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cfasig"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Wrapper simple en español sobre geopandas/shapely/rasterio para tareas de SIG en CONSAEFA."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [{ name = "CONSAEFA S.C." }]
|
|
12
|
+
license = "MIT"
|
|
13
|
+
license-files = ["LICENSE"]
|
|
14
|
+
keywords = ["gis", "sig", "geopandas", "shapely", "rasterio", "forestal"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"Topic :: Scientific/Engineering :: GIS",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Natural Language :: Spanish",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
dependencies = [
|
|
26
|
+
"geopandas>=0.13",
|
|
27
|
+
"shapely>=2.0",
|
|
28
|
+
"pandas>=1.5",
|
|
29
|
+
"numpy>=1.24",
|
|
30
|
+
"rasterio>=1.3",
|
|
31
|
+
"scipy>=1.10",
|
|
32
|
+
"pysheds>=0.3",
|
|
33
|
+
"openpyxl>=3.1",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
cfasig = "cfasig.cli:main"
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
dev = ["pytest>=7", "ruff>=0.1"]
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Homepage = "https://github.com/CONSAEFA/cfasig"
|
|
44
|
+
Repository = "https://github.com/CONSAEFA/cfasig"
|
|
45
|
+
Issues = "https://github.com/CONSAEFA/cfasig/issues"
|
|
46
|
+
|
|
47
|
+
[tool.setuptools]
|
|
48
|
+
package-dir = { "" = "src" }
|
|
49
|
+
packages = ["cfasig"]
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.package-data]
|
|
52
|
+
cfasig = ["py.typed"]
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|
cfasig-0.1.0/setup.cfg
ADDED