Architecture Overview¶
Module Map¶
ras2cng/
├── cli.py — Typer CLI for archive, extraction, mapping, MapLibre packaging, and validation
├── project.py — Full-project orchestration: inspect, archive, metadata export
├── catalog.py — Manifest schema v2.5 for archive catalogs (manifest.json)
├── geometry.py — HDF + text geometry export via ras-commander (20 HDF + 3 text layers)
├── results.py — Plan HDF results export with polygon/point/geometryless modes and cell/face join keys
├── precipitation.py — Gridded precipitation GeoTIFF export from HDF meteorology results
├── mapping.py — Result raster generation via RasStoreMapHelper.exe (WSE, Depth, Velocity, etc.)
├── scaffold.py — Barebones project synthesis from a plan HDF (powers map-hdf)
├── terrain.py — Terrain discovery, consolidation, and downsampling
├── duckdb_session.py — DuckDB wrapper with auto-loaded spatial extension
├── pmtiles.py — Vector/raster PMTiles pipeline
└── postgis_sync.py — GeoParquet → PostGIS via SQLAlchemy/GeoAlchemy2
CLI Layer¶
cli.py uses lazy imports — heavy dependencies (geopandas, duckdb, sqlalchemy) are imported
inside each command function, not at module level. This keeps ras2cng --help fast even when
optional extras are not installed.
Data Flow¶
HEC-RAS project directory
│
▼
project.py (archive_project / inspect_project)
│ Discovers all geometry, plan, and terrain files
│ Orchestrates extraction via geometry.py / results.py / mapping.py / terrain.py
▼
geometry.py / results.py precipitation.py / mapping.py / terrain.py
│ ras-commander parses │ RasStoreMapHelper.exe generates
│ HDF/text → GeoDataFrame │ precipitation/result rasters + terrain HDFs
▼ ▼
GeoParquet/Parquet (ZSTD, bbox, indexes) GeoTIFF rasters + terrain HDFs
│
├── catalog.py → manifest.json (schema v2.5)
├── duckdb_session.py → SQL analytics
├── pmtiles.py → GeoJSON → tippecanoe → PMTiles
└── postgis_sync.py → SQLAlchemy → PostGIS + GIST index
Maps Without a Project (map-hdf)¶
ras2cng map needs a full HEC-RAS project on disk. When only deliverables are
available — a computed plan HDF and a terrain raster, the typical LWI handoff —
use ras2cng map-hdf instead. scaffold.py synthesizes a barebones project
around the HDF: the projection WKT, unit system, plan titles, and Plan ShortID
are all read from the HDF itself (which also carries a full geometry copy), the
HEC-RAS terrain is built headlessly from the raw TIFF via RasProcess.exe
CreateTerrain, and a minimal .rasmap is generated. The scaffold then flows
through the same generate_result_maps() pipeline as map.
The scaffold directory (default OUTPUT/_scaffold) is kept between runs so the
expensive terrain build is reused; pass --rm-scaffold to delete it. The ESRI
projection file is written to Terrain\Projection.prj inside the scaffold —
never next to the HEC-RAS .prj, which would break project resolution.
Key Design Decisions¶
- Spatial post-processing by default:
archive_project()runs the archive index pass unlesssort=Falseor--no-sortis used. The CLI commandras2cng spatial-index ARCHIVE_DIRruns the same pass later for extraction-only archives. - ZSTD compression for archives:
archive_project()uses ZSTD compression with per-row bbox columns and GeoParquetcoveringmetadata for spatial predicate pushdown. Post-processing preserves that metadata while addinghilbert_indexto GeoParquet geometry and sorting bylayer,hilbert_index. Legacy single-file exports still use snappy for maximum read performance - snake_case columns: ras-commander normalizes all column names (e.g., "Maximum Depth" →
maximum_depth) - Table alias
_: DuckDB queries always use_as the table name — consistent, short, collision-free - Suffix-based detection: File type is determined from the file extension, not file inspection — fast and predictable
- Result join keys: Cell variables join geometry by
(mesh_name, cell_id), face variables join nativemesh_facesby(mesh_name, face_id). Geometryless archive result tables getjoin_indexand, when matching mesh geometry exists, inherithilbert_indexwithindex_status=spatial_join.