feat: wire shell placeholders to backend actions #12
@@ -34,6 +34,9 @@ Importer status:
|
|||||||
|
|
||||||
- `ovp-text`: project-owned plain-text heightfield fixture format used for tests.
|
- `ovp-text`: project-owned plain-text heightfield fixture format used for tests.
|
||||||
- `hgt`: enabled by the optional `hgt` Cargo feature; parses SRTM HGT payloads as square grids of big-endian signed 16-bit metre samples. The implementation and tests use open specifications and synthetic/tiny fixtures only.
|
- `hgt`: enabled by the optional `hgt` Cargo feature; parses SRTM HGT payloads as square grids of big-endian signed 16-bit metre samples. The implementation and tests use open specifications and synthetic/tiny fixtures only.
|
||||||
|
- `geotiff`: enabled by the optional `import-geotiff` Cargo feature; parses single-band GeoTIFF elevation tiles in memory via the pure-Rust `geotiff-reader` crate (no GDAL, no native dependency). It supports a deliberately narrow subset — a single-band raster decoded as `f32` — and is reported by `openvistapro info` only when the feature is built.
|
||||||
|
|
||||||
|
All importer tests use tiny synthetic, project-owned fixture data: HGT uses inline synthetic byte arrays, and the GeoTIFF tests generate a tiny single-band tile in memory rather than reading committed binaries or real geodata.
|
||||||
|
|
||||||
To verify the importer feature surface:
|
To verify the importer feature surface:
|
||||||
|
|
||||||
@@ -42,6 +45,9 @@ cargo test hgt
|
|||||||
cargo test hgt --features hgt
|
cargo test hgt --features hgt
|
||||||
cargo run --features hgt --bin openvistapro -- info
|
cargo run --features hgt --bin openvistapro -- info
|
||||||
cargo test --no-default-features
|
cargo test --no-default-features
|
||||||
|
cargo test geotiff --features import-geotiff
|
||||||
|
cargo run --features import-geotiff --bin openvistapro -- info
|
||||||
|
cargo test --all-features
|
||||||
```
|
```
|
||||||
|
|
||||||
The default `render` mode writes a deterministic top-down elevation preview.
|
The default `render` mode writes a deterministic top-down elevation preview.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
Start simple, then split into crates when module boundaries stabilize.
|
Start simple, then split into crates when module boundaries stabilize.
|
||||||
|
|
||||||
- `src/terrain.rs`: height grid, bounds, sampling, normals, terrain transforms.
|
- `src/terrain.rs`: height grid, bounds, sampling, normals, terrain transforms.
|
||||||
- `src/import/`: importers for open/safe formats; historical compatibility later.
|
- `src/import/`: importers for open/safe formats; historical compatibility later. Implemented so far: the project-owned `ovp-text` heightfield, an SRTM/HGT byte parser behind the `hgt` feature, and an optional single-band GeoTIFF importer (`src/import/geotiff.rs`) behind the `import-geotiff` feature. Each importer yields the same internal `HeightGrid` plus `TerrainSourceMetadata`, keeping source formats out of renderer code.
|
||||||
- `src/scene.rs`: camera, target, light, atmosphere, water, vegetation parameters.
|
- `src/scene.rs`: camera, target, light, atmosphere, water, vegetation parameters.
|
||||||
- `src/render/`: CPU reference renderer first, then WGPU renderer.
|
- `src/render/`: CPU reference renderer first, then WGPU renderer.
|
||||||
- `src/script.rs`: parse and execute OpenVistaPro script commands.
|
- `src/script.rs`: parse and execute OpenVistaPro script commands.
|
||||||
@@ -20,7 +20,7 @@ Start simple, then split into crates when module boundaries stabilize.
|
|||||||
- `image` for PNG output and texture loading.
|
- `image` for PNG output and texture loading.
|
||||||
- `nalgebra` or `glam` for vector/matrix math.
|
- `nalgebra` or `glam` for vector/matrix math.
|
||||||
- `wgpu` plus `winit`/`egui` for the eventual interactive app.
|
- `wgpu` plus `winit`/`egui` for the eventual interactive app.
|
||||||
- `gdal` support should be optional because native GDAL dependency setup can be heavy.
|
- GeoTIFF support uses the pure-Rust `geotiff-reader` crate behind the optional `import-geotiff` feature, so default builds stay free of native dependencies. `gdal` is intentionally not used; any future GDAL-backed path would be a separate, opt-in feature because native GDAL setup can be heavy.
|
||||||
|
|
||||||
## Development strategy
|
## Development strategy
|
||||||
|
|
||||||
|
|||||||
@@ -344,35 +344,52 @@ Run: `cargo test dem --features import-dem`
|
|||||||
|
|
||||||
Expected: PASS.
|
Expected: PASS.
|
||||||
|
|
||||||
### Task C3: Add GeoTIFF spike behind an optional feature
|
### Task C3: GeoTIFF importer behind an optional feature (implemented)
|
||||||
|
|
||||||
**Objective:** Decide whether to use a pure-Rust TIFF path or optional GDAL without making default builds fragile.
|
**Status:** Done. Landed as the optional `import-geotiff` feature; this section
|
||||||
|
records the implemented design. The crate survey behind it is
|
||||||
|
[`docs/research/geotiff-import-strategy.md`](../research/geotiff-import-strategy.md).
|
||||||
|
|
||||||
|
**Objective:** Parse single-band GeoTIFF elevation tiles into the internal
|
||||||
|
`HeightGrid` without making default builds fragile or pulling a native
|
||||||
|
dependency.
|
||||||
|
|
||||||
|
**Outcome:** A pure-Rust path was chosen over GDAL. The importer uses the
|
||||||
|
`geotiff-reader` crate (`local` feature, `cog`/network feature off), declared as
|
||||||
|
an optional dependency gated by the `import-geotiff` Cargo feature. There is no
|
||||||
|
GDAL dependency and no native toolchain requirement, so `import-geotiff` builds
|
||||||
|
and tests run anywhere `cargo` runs — the "documented skip if native GDAL is not
|
||||||
|
installed" escape hatch is not needed and is reserved for any future,
|
||||||
|
separately reviewed GDAL-backed feature.
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `src/import/geotiff.rs`
|
- `src/import/geotiff.rs`: `cfg(feature = "import-geotiff")` module exposing
|
||||||
- Modify: `Cargo.toml`
|
`parse_geotiff_bytes(&[u8]) -> Result<ImportedTerrain, ImportError>`. It reads
|
||||||
- Modify: `docs/plans/phase-4-formats-scripts-ui.md` or follow-up notes if the spike changes direction
|
the payload in memory, rejects non-single-band rasters, decodes the raster as
|
||||||
- Test: tiny openly licensed or generated GeoTIFF fixture only if licensing is clear
|
`f32`, and builds a `HeightGrid` plus `TerrainSourceMetadata` (format
|
||||||
|
`"geotiff"`). Reader/decode failures map to `ImportError::MalformedSource`.
|
||||||
|
- `src/import.rs`: declares the `geotiff` submodule under the feature cfg.
|
||||||
|
- `Cargo.toml`: `import-geotiff = ["dep:geotiff-reader"]`; `geotiff-writer` and
|
||||||
|
`ndarray` are dev-dependencies used only to generate the test fixture.
|
||||||
|
- `src/cli.rs`: `supported_importers()` and `info_text()` list `geotiff` only
|
||||||
|
when the feature is built. (A `render --import-geotiff` flag mirroring the HGT
|
||||||
|
path remains future work.)
|
||||||
|
|
||||||
**Step 1: Write failing test or spike acceptance check**
|
**Fixture hygiene:** Tests do not commit any GeoTIFF binary. They generate a
|
||||||
|
tiny single-band elevation tile in memory with `geotiff-writer` (dev-dependency)
|
||||||
|
and parse it back, mirroring the synthetic-fixture approach used for HGT. No
|
||||||
|
proprietary data and no large real-world DEMs enter the repository; real tiles
|
||||||
|
may be used only for local manual verification under the already-ignored
|
||||||
|
`reference/` and `.work/` directories.
|
||||||
|
|
||||||
Prefer a generated fixture committed under an explicit license. If that is not practical, write parser-interface tests and keep the full fixture local until license is resolved.
|
**Validation**
|
||||||
|
|
||||||
**Step 2: Verify RED**
|
Run: `cargo test --no-default-features`, `cargo test geotiff --features import-geotiff`,
|
||||||
|
and `cargo test --all-features`.
|
||||||
|
|
||||||
Run: `cargo test geotiff --features import-geotiff`
|
Expected: default and `--no-default-features` builds stay GeoTIFF-free; the
|
||||||
|
feature build and `--all-features` exercise the parser and pass without any
|
||||||
Expected: FAIL until the selected crate and parser stub exist.
|
native dependency.
|
||||||
|
|
||||||
**Step 3: Implement minimal code**
|
|
||||||
|
|
||||||
Add only enough to detect/parse a tiny supported subset. If GDAL is required, keep the feature opt-in and document native setup.
|
|
||||||
|
|
||||||
**Step 4: Verify GREEN**
|
|
||||||
|
|
||||||
Run: `cargo test --features import-geotiff`
|
|
||||||
|
|
||||||
Expected: PASS on systems with the optional dependency available, or a documented skip if native GDAL is not installed.
|
|
||||||
|
|
||||||
## Milestone D: Scene/project metadata evolution
|
## Milestone D: Scene/project metadata evolution
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
# GeoTIFF Import Strategy Research Note
|
# GeoTIFF Import Strategy Research Note
|
||||||
|
|
||||||
> **Status:** Spike / decision note. Documentation-only — no code or `Cargo.toml`
|
> **Status:** Decision implemented. The recommended pure-Rust path has landed
|
||||||
> changes accompany this note.
|
> as the optional `import-geotiff` feature; the crate survey below is kept for
|
||||||
|
> provenance.
|
||||||
>
|
>
|
||||||
> **Context:** Phase 4 Milestone C, Task C3 ("Add GeoTIFF spike behind an
|
> **Context:** Phase 4 Milestone C, Task C3 ("GeoTIFF importer behind an
|
||||||
> optional feature"). This note records the crate survey and the recommended
|
> optional feature"). This note recorded the crate survey and recommended
|
||||||
> direction so the implementation slice can start from a settled decision.
|
> direction; the "Implementation notes" section below describes what was
|
||||||
|
> actually built.
|
||||||
|
|
||||||
## Recommendation
|
## Recommendation
|
||||||
|
|
||||||
@@ -136,44 +138,48 @@ Consistent with Task C1's existing feature scaffold (`import-dem`,
|
|||||||
- The committed fixture should be only as large as the test needs (target:
|
- The committed fixture should be only as large as the test needs (target:
|
||||||
well under a kilobyte).
|
well under a kilobyte).
|
||||||
|
|
||||||
## Follow-up implementation slice
|
## Implementation notes
|
||||||
|
|
||||||
Concrete next slice for Task C3, to be done TDD (RED → minimal GREEN →
|
The recommended pure-Rust path has landed. What was built:
|
||||||
validation) on a separate implementation branch:
|
|
||||||
|
|
||||||
1. **Add the feature + dependency.** In `Cargo.toml`, make `geotiff-reader` an
|
1. **Feature + dependency.** `Cargo.toml` declares
|
||||||
optional dependency gated by `import-geotiff` (default `cog` feature
|
`import-geotiff = ["dep:geotiff-reader"]`; `geotiff-reader` is an optional
|
||||||
disabled). Confirm `cargo test --no-default-features` and the default build
|
dependency built with its `local` feature and `default-features = false`, so
|
||||||
stay GeoTIFF-free.
|
the network-oriented `cog` feature stays off. Default and
|
||||||
2. **Create `src/import/geotiff.rs`.** A `cfg(feature = "import-geotiff")`
|
`--no-default-features` builds remain GeoTIFF-free.
|
||||||
module exposing something like
|
2. **`src/import/geotiff.rs`.** A `cfg(feature = "import-geotiff")` module
|
||||||
`parse_geotiff_bytes(&[u8]) -> Result<ImportedTerrain, ImportError>`,
|
exposing `parse_geotiff_bytes(&[u8]) -> Result<ImportedTerrain, ImportError>`,
|
||||||
reusing the `ImportedTerrain` / `ImportError` / `TerrainSourceMetadata`
|
reusing the `ImportedTerrain` / `ImportError` / `TerrainSourceMetadata` types
|
||||||
types from Milestone A.
|
from Milestone A. It reads the payload in memory, rejects rasters that are
|
||||||
3. **Generate the synthetic fixture.** Add a tiny generated single-band
|
not single-band, decodes the raster as `f32` into a row-major `HeightGrid`,
|
||||||
elevation GeoTIFF as a project-owned fixture (separate commit from the
|
and records `TerrainSourceMetadata` with format `"geotiff"`. Reader and
|
||||||
parser, per the plan's commit strategy).
|
decode failures map to `ImportError::MalformedSource`.
|
||||||
4. **RED test.** `cargo test geotiff --features import-geotiff` — assert
|
3. **Synthetic fixtures only.** No GeoTIFF binary is committed. Tests generate a
|
||||||
dimensions, sample values, and metadata; assert malformed input yields a
|
tiny single-band elevation tile in memory with the `geotiff-writer`
|
||||||
typed error.
|
dev-dependency (alongside `ndarray`) and parse it back — mirroring the
|
||||||
5. **Minimal GREEN.** Parse only the tiny supported subset (single band,
|
inline-bytes approach used for HGT. Real USGS/NASA tiles may be used for
|
||||||
uncompressed, known sample type). Reject everything else explicitly;
|
local manual verification only, under the already-ignored `reference/` and
|
||||||
document that broad real-world GeoTIFF coverage is future work.
|
`.work/` directories.
|
||||||
6. **CLI plumbing (optional, can be a later slice).** Mirror the HGT pattern
|
4. **Tests.** `cargo test geotiff --features import-geotiff` asserts dimensions,
|
||||||
(Task B3): an `--import-geotiff <path>` render input, gated so it only
|
sample values, and metadata, and checks that non-GeoTIFF input yields a typed
|
||||||
appears when the feature is built.
|
`ImportError::MalformedSource`.
|
||||||
7. **Validation.** `cargo test --no-default-features`,
|
5. **Supported subset.** Only the narrow single-band `f32` case is parsed;
|
||||||
`cargo test --features import-geotiff`, `cargo test --all-features`,
|
broader real-world GeoTIFF coverage (multi-band, exotic compression, full
|
||||||
`cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`,
|
geo-tag interpretation) remains future work.
|
||||||
`git diff --check`.
|
6. **`openvistapro info`.** `supported_importers()` and `info_text()` list
|
||||||
8. **Update `openvistapro info`** to report GeoTIFF support honestly — only
|
`geotiff` only when the feature is built, keeping `info` honest.
|
||||||
once a real parser and fixture exist (consistent with Task A3).
|
7. **GDAL is not part of the landed feature.** The importer has zero native
|
||||||
|
dependencies; a GDAL-backed path stays out of scope until a separate review
|
||||||
|
approves the native-dependency cost, and would use its own opt-in feature
|
||||||
|
name rather than `import-geotiff`.
|
||||||
|
|
||||||
If `geotiff-reader` proves insufficient during the spike (missing geo-tag
|
**Not yet done:** CLI render plumbing. A `render --import-geotiff <path>` input
|
||||||
coverage, API gaps), fall back in this order: `georaster` → `wbgeotiff` →
|
mirroring the HGT pattern (Task B3) is still future work; the parser is reachable
|
||||||
hand-rolled geo-tag reading on top of `image`'s TIFF decoder. A GDAL-backed
|
through the library API and reported by `info`, but not yet wired into `render`.
|
||||||
path stays out of scope until a separate review approves the native-dependency
|
|
||||||
cost.
|
If `geotiff-reader` later proves insufficient (missing geo-tag coverage, API
|
||||||
|
gaps), the documented fallback order remains `georaster` → `wbgeotiff` →
|
||||||
|
hand-rolled geo-tag reading on top of `image`'s TIFF decoder.
|
||||||
|
|
||||||
## Sources
|
## Sources
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user