The software behind Rana’s hydrodynamic solver (internally called RanaHCC, the Hydrodynamic Computational Core) is a serious piece of engineering: a Fortran finite-volume solver on a quadtree grid, with Python bindings generated by f90wrap, wrapped in a cloud API that normally hides all the complexity from you.
The question I wanted to answer: can you run the whole pipeline locally, from a GeoPackage schematisation to a max water depth raster, without ever touching the API?
The answer is yes. Here’s how.
What the stack looks like
The RanaHCC stack is split across several repositories. Reading through them, the pipeline becomes clear:
model.gpkg + dem.tif
│
▼ threedigrid-builder → gridadmin.h5 (quadtree grid)
▼ threedi-tables → tables.h5 (volume/friction/infiltration lookups)
│
▼ py3di / Core3Di → results_3di.nc (water level timeseries)
│
▼ threedigrid + rasterio → max_waterdepth.tif
The GeoPackage is the schematisation. It contains channel geometry, boundary conditions, numerical settings, everything. The DEM drives the quadtree cell refinement and the subgrid volume tables. Once those two inputs exist, the solver takes over.
The solver itself is Fortran, compiled to shared libraries (libflow3di.so, libutils.so, and others) and wrapped at Python level by f90wrap + f2py. This means pip install alone gets you nowhere. You need CMake, gfortran, libnetcdf, libgdal, and a working f90wrap build. Docker is the pragmatic answer.
Building the CLI
I built a small Python CLI called hccexe with three commands:
hccexe validate model.gpkg # check schema for errors
hccexe run model.gpkg dem.tif # build grid + run simulation
hccexe postproc gridadmin.h5 results_3di.nc dem.tif # max depth raster
The run command inlines the logic from py3di/run_local_simulation.py. The existing entry point inside the calculationcore repo, but with a friendlier interface. Rasters are discovered in the model’s parent directory automatically. Boundary conditions are read directly from the GeoPackage’s SimulationTemplateSettings table. Uniform rain is applied as a two-point timeseries in m/s.
The Dockerfile is a multi-stage build: the build stage compiles all four Fortran libraries in order (utils → forcings → water_quality → core), generates the Python bindings with f90wrap, and installs everything. The runtime stage copies only the compiled .so files and the Python site-packages. No build tools, no source code.
Running it
After a one-time Docker build (around ten minutes, mostly Fortran), the workflow is:
docker compose run hccexe run \
maaiveldmodel.gpkg dem.tif \
--rain-intensity 25 \
--duration 3600
Output on a real Dutch polder model (40,041 2D cells + 106 1D nodes):
Building computational grid → gridadmin.h5 ...
[ 70%] Constructing 2D computational grid...
** INFO: No. active 2D computational cells: 40041
Building tables → tables.h5 ...
Extracting simulation settings from model ...
Applying rain: 25.0 mm/h (6.9444e-06 m/s) for 3600 s
** INFO: Created file: results_3di.nc
Running simulation: 3600 s ...
Simulation complete in 275.0 s.
275 seconds of wall time for 3600 seconds of simulated flood propagation. The adaptive timestep started at 30 s, dropped to roughly 0.8 s during peak inundation, then recovered. The result: a 19 MB NetCDF file with 13 snapshots of water level across 40,000 cells.
The postprocessing step:
docker compose run hccexe postproc \
maaiveldmodel/preprocessed/gridadmin.h5 \
maaiveldmodel/results/results_3di.nc \
dem.tif
This reads all 13 timesteps, takes the maximum water level per computational cell, maps each DEM pixel to its containing cell via GridH5Admin.cells.get_nodgrid(), subtracts the DEM elevation, and writes a LZW-compressed GeoTIFF. The whole postproc step takes a few seconds.
What the results look like
The results_3di.nc file contains per-timestep arrays for every node and flowline:
Mesh2D_s1— water surface elevation (m, absolute)Mesh2D_vol— water volume (m³)Mesh2D_ucx/Mesh2D_ucy— depth-averaged velocity componentsMesh2D_rain— rain flux actually appliedMesh1D_s1/Mesh1D_vol— same for the 1D channel network
A flow_summary.json gives a global volume balance, useful for checking mass conservation and spotting boundary condition issues.
Why this matters
The normal workflow requires uploading your schematisation to the Rana cloud, paying for compute time, and downloading results. For development iterations, testing a schematisation change, calibrating a rain event, validating boundary conditions, that round-trip is slow and expensive.
With a local CLI and Docker, the iteration cycle becomes: edit GeoPackage → hccexe run → inspect raster. No API keys, no upload wait, no credits consumed. The same solver that runs in the cloud now runs on your laptop.
The full pipeline, from raw GeoPackage to flood depth map, in a single command.
