Metadata-Version: 2.4
Name: ras-commander-fast-map
Version: 0.12.0
License-File: LICENSE
Summary: Optional Rust Step05, horizontal and configurable sloping FIM mapping engine
Home-Page: https://rascommander.info/ras/native-mapping-engine/
License-Expression: MIT AND BSD-3-Clause
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Documentation, https://rascommander.info/ras/native-mapping-engine/
Project-URL: Issues, https://github.com/gpt-cmdr/ras-commander-fast-map/issues
Project-URL: Repository, https://github.com/gpt-cmdr/ras-commander-fast-map

# RAS Commander Fast-Map

<p align="center"><img src="docs/images/logo/ras-commander-fast-map_logo.svg" alt="RAS Commander Fast-Map: Native Rust Mapping Engine" width="450"></p>

Native Rust 2D flood inundation mapping, distributed as the
`ras-commander-fast-map` Rust crate and Python wheel. It maps computed hydraulic
results; it does not run the HEC-RAS hydraulic solver.

## Install

```bash
pip install ras-commander-fast-map
ras2fim-batch method
```

Python wheels provide the compatible `ras2fim-batch` executable on Windows x64
and Linux x64 (glibc 2.28+). A wheel requires no Rust compiler or system GDAL.
Python consumers normally install `ras-commander[fim]`, which adds the public
`RasFim` APIs and Python preparation/GeoTIFF delivery dependencies. Remove the
former `ras2fim-engine` package before installing this package if both would
own the same executable. The command name and JSON contracts remain compatible.

Rust users can install the CLI or use the library independently of Python:

```bash
cargo install ras-commander-fast-map --locked
cargo add ras-commander-fast-map
```

```rust
let methods = ras_commander_fast_map::method_record();
assert!(methods.is_object());
```

See [Rust API documentation](https://docs.rs/ras-commander-fast-map) and the
[RasFim guide](https://rascommander.info/ras/api/fim/) for request contracts and
end-to-end model preparation. The Rust library consumes prepared geometry,
profiles and terrain; HDF extraction belongs to `ras-commander-fast-hdf` and
Commander. The CLI does not read a plan HDF directly.

## Methods and resource controls

### Polygon AOI edge rule (0.10.1)

Horizontal/sloping method records advertise `aoi_mask="global-terrain-lattice/1"`.
AOI coverage is computed in source terrain pixel units, at global pixel centers,
with y increasing northward. Even-odd scanlines include lower-y/exclude upper-y
endpoints; filled spans include the left endpoint and exclude the right.
For a rectangle this includes its minimum x/y edges and excludes its maximum
x/y edges. Holes and disjoint polygon parts retain the same rule. No epsilon
is added, and ring reversal does not change coverage. A cropped mask equals
the corresponding crop of a full-grid mask, including pixel-center edge ties.

The rule deliberately replaces origin-dependent legacy AOI masking. It need
not reproduce legacy GDAL edge ties. Cell ownership and hydraulic interpolation
are unchanged. AOI rows use the request's bounded Rayon pool; planned geometry
and per-worker crossing scratch are admitted before payload reads. The pure
Rust `aoi::mask` function accepts a framed Polygon/MultiPolygon WKB path, full
source geotransform and integer source window.

- Horizontal: cell-constant WSE with native float32 visibility.
- Sloping: reconstructed cell corners; optional cell faces, depth weighting,
  and shallow-to-horizontal selection.
- ras2fim-2D Step05: the separate globally prepared SciPy triangulation method.

Terrain decoding and ownership rasterization use terrio with GDAL disabled.
Rayon workers share immutable terrain/geometry across bounded in-flight profiles.
Bbox/polygon output masks retain global hydraulic topology. Planned allocation
admission is not a hard RSS limit. Python delivery may use Rasterio/GDAL;
the Rust mapping executable does not link it.

### Multi-polygon/timestep catalogs

Use RAS Commander's `RasFim.map_catalog()` to map multiple watershed/time
groups from computed 2D models. Give each job one selected polygon or bbox,
the complete source HDF and qualified mapping TIFF, and an ordered list of
saved timestep indices or `"Max"`. The Python supervisor stages native HDF
inputs and schedules this Rust engine; the Rust crate does not read model HDF
directly. See the [agent-ready catalog recipe](https://rascommander.info/ras/api/fim/#build-jobs-from-a-watershed-layer)
and [throughput policy](https://rascommander.info/ras/api/fim/#choose-a-throughput-policy).

Start with one reuse worker for overlapping time lists on one model/terrain.
Bound preparation waves with `max_profiles_per_batch`. Test independent
workers for different models or low-sharing workloads within one global
CPU/RAM allowance. Native in-flight timesteps share the same Rayon pool;
additional processes can duplicate geometry, staged data and caches.

Fast-Map 0.11.0 provides retained hydraulic/multiple-window caches and optional
native terrio LZW output. Commander retains Rasterio as its default writer;
select `product_writer="native"` for supported sloping catalogs and inspect
`RasFim.get_engine_info()` capability flags. Native cache ceilings count inside
working-memory admission. The supervisor's per-worker prepared/headroom
reservations and sampled memory guard also apply. Inspect total catalog wall
time, cache reports and peak tree memory before increasing concurrency/caches.

AOIs constrain raster reads and output pixels, while hydraulic dependencies
remain global. A watershed occupying 1% of the terrain does not imply a 100x
speedup. Keep the qualified source TIFF layout; do not crop hydraulic arrays
or retile cell-face terrain per reach. Inspect both overall and per-row
statuses, preserve partial receipts and use a fresh output folder for retries.

### Retained reach state (0.11)

The persistent sloping server additionally advertises `hydraulic_cache`,
`multi_window_cache` and `native_geotiff_outputs`. Requests can set
`hydraulic_cache_bytes` (default 0) and `window_cache_bytes` (default 64 MiB).
Their ceilings are included in planned working-memory admission, independently
of the terrain block cache. Zero disables retention. Both caches use LRU
eviction and include retained input keys and allocation metadata in their plans.

Hydraulic entries bind complete staged WSE file identities and rendering
options to reconstructed global cell/corner/face state. Geometry changes clear
them. Window entries bind terrain/geometry identity, the exact integer source
window, cell addresses, polygon WKB and AOI WKB. They retain ownership, terrain
and up to 32 MiB of plain-sloping weights per window when the budget permits.
Cell-face rendering retains its original donor-history path. Reports include
cache hit flags, retained byte plans and entry counts. No spatial clipping of
hydraulic dependencies is introduced.

With `stream_outputs=true, geotiff_outputs=true`, sloping requests write
lossless LZW `depth.tif` and `wse.tif` beneath each unit folder and stream JSON
completion records instead of arrays. Records include pixel counts and native
write timing. terrio owns encoding; no GDAL/Python dependency is added to the
engine. This route requires an EPSG-identifiable CRS and excludes velocity.
Commander validates output metadata before delivery. Ordinary array streams
and binary-file output remain compatible. These capabilities are included
in 0.11.0; inspect advertised flags when integrating older binaries.

### Fast-Map 0.12

0.12.0 adds storage-area level pools (`storage_area_surfaces`
`storage-area-level-pool/1` on the `sloping`, `horizontal` and
`whole_simulation` methods), `ras2fim-batch effective-terrain` for unmodified
multi-source terrains (`effective_terrain` `rasmapper-effective-terrain/1`),
the qualified duration rule (`duration_rule`
`rasmapper-backward-window-recession/1`), velocity clipping by the rendered or
horizontal depth (`velocity_depth`), and D x V, D x V^2 and Froude with their
Max over output times (`derived_products`, `derived_max` `reduce_max/1`).
Existing method IDs and request schemas are unchanged; inspect the advertised
flags before using these fields.

Binary32 operation order and method identifiers are preserved. `MIGRATION.json`
retains the immutable 0.8.0 extraction receipt; `SOURCE_MANIFEST.json` binds this
version's current sources. `ras2fim-batch method` reports package/build identity
and capabilities.

## Persistent reach sessions (0.9)

`sloping-server` accepts serialized JSON-line commands with absolute `request`
and `output` paths. Each request uses the existing sloping schema and streams
the same profile headers/binary arrays, followed by a single
`{"command_status":"complete"}` line. The process retains terrain blocks,
mesh/stencils and one Rayon pool. Send `{"close":true}` or close stdin to release
the session. Failed commands terminate the process; callers must reject partial
products. Commander owns process lifetime and timeouts through prepared models.

At most the last small window's owner/terrain arrays, exact geometry/AOI key
and plain-sloping weights are retained within a separate 64 MiB payload bound.
Weights use at most 32 MiB; pixels beyond the bounded prefix keep their original
evaluation path. Cell-face/donor-history weights are not cached. Large windows
still share terrain blocks and geometry, without retaining their output arrays.
Plans include the retained payload budget and the original 64 MiB decoded tile
cache; this is allocation admission, not a hard process RSS limit. Native
`PreparedSession` objects provide the same reuse without the CLI.

## Deterministic inundation boundaries (0.10)

Generate projected-CRS inundation polygons from signed depths with the separate
`deterministic-zero-depth-contours/1` method:

```bash
ras2fim-batch boundary boundary-request.json new-boundary-output
```

Commander prepares the model and terrain metadata for this command. Rust callers
can use `boundary_session::Request` and `boundary_session::run` directly. The
request schema is `ras-commander-boundary-request/1`:

| Field | Contract |
|---|---|
| `schema` | `ras-commander-boundary-request/1` |
| `interpolation` | `horizontal` or `sloping` |
| `model` | Existing `ras-commander-sloping-request/1` model, global physical-cell polygons, full hydraulic meshes, profile files, CRS, AOI and resource controls |
| `terrain_metadata` | Original terrain shape/transform, 256 × 256 tile grid, selected global tile IDs, mask/perimeter numeric descriptors, and optional paired stitch-point/triangle descriptors |

Boundary generation supports physical 2D cells with horizontal or plain corner
sloping surfaces. Cell-face interpolation, shallow-to-horizontal controls and
face depth weighting are rejected. Supply one original, unmodified float32
terrain TIFF and its matching terrain HDF metadata. Retiled/cropped TIFFs,
terrain modifications and multi-source mosaics are outside this boundary scope.
Commander rejects models containing 1D elements; boundary requests refuse
storage-area surfaces.

The method retains valid negative depths to locate the zero-depth crossings.
The intrinsic cell-wet gate uses float32 `WSE > minimum_elevation + 0.001`;
there is no added pixel-depth cutoff or 0.5-foot postprocessing. Masks/perimeters
and stitch triangles come from the terrain HDF; the TIFF alone is insufficient.
Bbox/polygon selection limits terrain tiles, while hydraulic topology stays
global. AOI clipping follows contour construction, preserving edge samples.

Quantized integer clipping produces coordinates at 0.001 **projected CRS units**.
Tile/profile results are combined in stable order, independent of worker order.
Each profile produces one GeoJSON FeatureCollection containing a MultiPolygon;
its explicit legacy GeoJSON `crs` member identifies projected coordinates. These
coordinates are not reprojected to RFC 7946 longitude/latitude. The report uses
`ras-commander-boundary-report/1` and includes profile status, polygon/hole/vertex
counts, area in squared CRS units, timings and effective concurrency. Empty
boundaries are explicit. Output requires a new or empty directory; a failed
batch removes delivered profile polygons and records an aborted report.

Profiles share geometry, terrain blocks, ownership and stencils. `model.threads`
and `model.max_inflight_profiles` bound work in one Rayon pool; tile workers
also share that pool. Planned admission covers prepared data and per-profile
contour allowances before payload reads. It is not a hard process RSS limit.

These deterministic polygons are **not an exact reproduction of RASMapper
vectors**. Stable all-subject integer union and quantized intersections can
produce different holes and boundary vertices. Repeated native Max exports can
also vary with unchanged inputs. Use the native stored-map API when its vector
output is required, and retain comparison evidence for the intended model.
This separate polygon layer does not change the existing Depth/WSE raster
methods, their float32 arithmetic or their qualification claims.

## Velocity (sloping requests)

Add per-mesh `velocity_normals_files` (HDF `Faces NormalUnitVector and Length`
as float32 x, y, length, with **length replaced by the face polyline length**)
and per-profile `face_velocity_files` (float32 face-normal velocities; for Max,
the summary `Maximum Face Velocity` with Max WSE as the profile values) to a
`sloping` request. Velocity magnitude is written as `velocity.bin`, or as a
third streamed array when the stream header has `"velocity": true`. Its values
do not depend on the WSE render mode; RAS Mapper clips them with the active
mode's depth, so velocity is drawn where the clipping depth > 0 (method
`rasmapper-sloping-velocity/0`). `velocity_depth` selects that depth:
`rendered` (default, this request's `sloping` or `slopingPretty` depth) or
`horizontal` (RAS Mapper's horizontal render: the owning cell's WSE over
terrain, skipping cells with `wse <= minimum + 0.001`, float32).

`derived_products` (`depth_x_velocity`, `depth_x_velocity_squared`, `froude`;
method `rasmapper-depth-velocity-products/1`) adds per-profile arrays from the
clipping depth `d` and velocity `v`, in RAS Mapper's operation order: `d * v`,
`(d * v) * v`, and `float32(v / sqrt(float32(g * d)))` with the request's
float32 `gravity` (32.174 or 9.80665). They are written as `<product>.bin` or
streamed after velocity in the listed order (header `"derived"`).
`reduce_max: true` keeps the pixelwise maximum of each derived product over
every profile in the request and writes only `max/<product>.bin`: RAS Mapper's
Max D x V, D x V^2 and Froude are per-output-time products reduced this way
(the valid area is the union over times), not Max depth times Max velocity.
The sloping method record advertises `velocity_depth`, `velocity_render_modes`,
`derived_products`, `derived_method_id` and `derived_max` (`reduce_max/1`).

Native HEC-RAS 7.0.1 comparison: BaldEagleCrkMulti2D bridges p05 (973,532 Max
and 402,500 output-time pixels), dam-break p07 2D cells (Max and one output
time, valid masks equal to depth) and a 2,500 x 2,500 Spring Creek eBFE AOI
(1,101,529 pixels) match bit for bit; Davis p02 Max differs in 5 of 20,560
pixels by at most 2 ulp. A true sloping native run shows the valid mask equals
the sloping depth mask on bridges Max (973,902 pixels, values and mask
identical); the 973,532-pixel figure above came from a horizontal-mode
reference. Horizontal clipping matches native horizontal velocity on bridges
Max (973,532 pixels) and 03JAN1999 00:00 (402,500), and slopingPretty velocity
values match natively on every common pixel (its mask follows the rendered
slopingPretty depth). Derived products, native 7.0.1: bridges 03JAN1999 00:00
sloping (402,616 pixels) and horizontal (402,500) bit for bit for all three;
`reduce_max` over all 433 output times bridges Max sloping (973,956) and
horizontal (973,531) bit for bit; Davis p02 (horizontal Max over 217 times and
one sloping/horizontal output time) equal masks, at most 5 pixels within 2 ulp
(the Davis velocity residual). Storage areas, boundary requests and shear
stress are outside this method.

## Whole-simulation products (arrival time, duration, percent inundated)

```bash
ras2fim-batch whole-simulation whole-request.json new-output-dir
```

Schema `ras-commander-whole-simulation-request/1` embeds a non-streaming
horizontal request whose profiles are the stored output times in order, plus
`times_hours` (one per profile, strictly increasing), `threshold`,
`cell_minimum_file` (float32 cell minimum elevations in cell order), optional
`products` (default `arrival_time`, `percent_inundated`) and
`allow_unqualified` (required for irregular intervals). Output times are evaluated sequentially over
the mapped 2D-owned pixels only, in parallel, with 32
bytes per mapped pixel. Products are written to `whole_simulation/<product>.bin`
(float32, NaN = never wet).

A pixel is wet when its horizontal depth is `> threshold` and its cell passes
RAS Mapper's thresholded gate `wse > minimum + threshold`. Storage areas are
rendered as level pools (see Storage-area surfaces) for arrival and percent;
duration with storage areas is refused. Arrival is the first wet time and percent is
`100 * wet / n`. Duration runs from arrival to RAS Mapper's recession time
(`duration_rule` `rasmapper-backward-window-recession/1`): each cell's WSE
series is split into monotone windows at its local extrema (PostProcessing's
`Increasing Regions` rule, `v[k] >= v[k-1]` rising), and the windows are searched
from the last. With `L` the last wet time, the recession is `L` when it ends a
window, is one step before a falling window's end, or starts that falling
window (the native round-half-even bisection gives up there), and `L + 1`
otherwise. Duration keeps 8 bytes per cell extremum, admitted against the
budget. Times are float32 hours. Irregular output intervals are
refused unless `allow_unqualified` is set. Initially wet pixels, multi-period
pixels and threshold-equal samples are reported.

Qualification (native HEC-RAS 7.0.1, threshold 0.5 ft, storage-area-free
models): Davis pipes/conduits p02 (217 output times, 4,879 valid pixels) and
BaldEagleCrkMulti2D bridges p05 (433 times, 959,043 pixels) match in every
valid-mask pixel and every arrival and percent value bit for bit, including
initially wet, multi-period and threshold-equal samples. RAS Mapper gives the
same products in horizontal and sloping render modes on both models, so this
horizontal pass covers sloping maps too. Duration matches the same references
in every pixel (the earlier first-dry-time rule differed on 103 Davis and 11
bridges pixels).
Bridges runs in 8.9 s at 383 MiB peak against about 25 s for the native call.

## Top-priority source terrain (no RAS Mapper export)

RAS Mapper writes stored maps per terrain source, on each source's own grid.
`sloping` (plain) and `horizontal` requests may map directly on the original
TIFF of the highest-priority source of a multi-source terrain, instead of a
RAS Mapper terrain export, by adding:

```json
"terrain_scope": {"kind": "top_priority_source", "source_count": 2,
  "mask": {"path": "mask.u1", "dtype": "|u1", "shape": [513, 65536]}}
```

`mask` is the source's full level-0 tile mask from the terrain HDF
(`Terrain/<source>/0/Mask`, tiles row-major); bit 0 is the source footprint.
Owned pixels outside the footprint are refused unless the result cannot
depend on their terrain: sloping pixels of cells that fail the native wet gate
are never drawn. Foreign pixels of wet cells, foreign pixels in horizontal
rendering and cell-face sloping are refused (method
`top-priority-source-footprint/1`). Boundary requests refuse it.

Qualification (plain sloping, native HEC-RAS 7.0.1, two-source Terrain50):
BaldEagleCrkMulti2D bridges p05 Max on the priority-1 `dtm_20ft` TIFF matches
in all 973,902 valid pixels and float32 bits, with 28 foreign 2D pixels all in
dry cells; the dam-break p07 2D cells also match for Max and one output time.
Lower-priority sources are not mosaicked: RAS Mapper's export resamples them
with a rule close to, but not, pixel-centre bilinear (84% of float32 values
reproduced), so a native mosaic is unqualified.

## Storage-area surfaces

`sloping`, `horizontal` and `whole-simulation` requests can render standalone
storage areas as level pools (method `storage-area-level-pool/1`, advertised as
`storage_area_surfaces` in `ras2fim-batch method`). Supply all three together:

| Field | Contract |
|---|---|
| `storage_areas_file` | Framed little-endian WKB Polygons/MultiPolygons, one per storage area (same framing as `geometry_file`) |
| `storage_area_count` | Number of storage areas in that file |
| `profiles[].storage_area_values_file` | Little-endian float32 storage-area WSE for that profile (Max or one output time), one per area, finite |

Each storage-area pixel (pixel centre inside the polygon, on the request window,
clipped by the AOI mask) gets the area's WSE; it is visible when float32
`wse >= terrain + 0.001`, with depth `wse - terrain`, in every render mode. The
caller sizes `window` to cover the storage areas as well as the 2D cells. In
whole-simulation requests a pool pixel is wet when its depth is strictly greater
than the threshold; there is no cell gate. Pixels owned by both a 2D cell and a
storage area, or by two storage areas, are refused (RAS Mapper's ordering for
those layouts is not qualified), as are velocity with storage areas, duration
with storage areas, boundary requests with storage areas and storage-area pixels outside a top-priority
source's footprint.

Qualification against native HEC-RAS 7.0.1 stored maps (RAS Mapper terrain
export, valid masks and float32 bits):

| Model | Products | Storage-area pixels | Result |
|---|---|---|---|
| BaldEagleCrkMulti2D p07 | Max Depth/WSE: horizontal, sloping, slopingPretty (with and without shallow reduction); 02JAN1999 13:00 horizontal and sloping | 466,912 (Max), 422,515 (13:00) | identical |
| BaldEagleCrkMulti2D p04 (15,503 cells, 2D levee structures) | Max Depth/WSE: horizontal, sloping, slopingPretty (both); 13:00 sloping | 461,639 (Max), 421,927 (13:00) | identical |
| p07 and p04 | Arrival time and percent inundated, 0.5 ft (865 output times) | 464,106 / 458,764 | identical |

Duration with storage areas is refused: the recession search indexes cells,
and level-pool pixels have no cell position. (A pre-recession-rule prototype
matched every storage-area duration; its 8 (p07) and 46 (p04) one-interval
differences were all 2D pixels.) Both models have one
storage area connected to the 2D area; multiple storage areas and
storage-area-to-storage-area connections are not covered by this evidence.

## Qualification and limits

The retained [five-model comparisons](https://rascommander.info/ras/development/fim-mapping-performance/)
cover raw Depth/WSE validity masks and float32 bits against native HEC-RAS 7.0.1,
without an added depth cutoff. Those published timing observations predate this
packaging extraction; no new speedup is claimed for renaming/distribution.

Mapping covers physical 2D cells. Commander rejects models with 1D river/cross-
section geometry. Storage areas are mapped only when the request supplies them. Cell-face
matching preserves the original source TIFF grid and 256-square tile layout;
arbitrary retiled/cropped terrain and multi-source mosaics are unqualified.
Depth weighting needs stored corner ground elevations. When absent, explicit
corner preparation still uses Windows/HEC-RAS 7.0.1 through Commander and
pythonnet; it has not been implemented in Rust. Once prepared, ordinary mapping
runs on Windows/Linux without HEC-RAS. Source terrain must match the effective
terrain used by the native reference.

## Build and verify

```bash
cargo fmt --check
cargo test --locked --all-targets
cargo clippy --locked --all-targets -- -D warnings -A clippy::chunks_exact_to_as_chunks
cargo publish --locked --dry-run
python scripts/verify_sources.py
```

CI builds and smoke-tests Windows/Linux wheels. Tagged releases attach the
wheels, source distribution, provenance and checksums to GitHub Releases.
See [release instructions](docs/releasing.md) for PyPI/crates.io publishing.

## License and attribution

Our code retains its MIT license. Step05/SciPy reuse retains BSD-3-Clause notices;
see `LICENSE`, `THIRD_PARTY_NOTICES.md` and `licenses/`. The crate license
expression is `MIT AND BSD-3-Clause`. The unmodified Clipper2 dependency retains
its Boost Software License 1.0 notice. Dependency notices accompany distributions.
HEC-RAS assemblies and model data are not distributed.
This independent project is not affiliated with or endorsed by HEC/USACE.

