Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [ Unreleased]

### Added
- support for FES2014
- Coastal blend where vdatum cuts off
- decay extrapolation to 0 inland from tidal datums
- Add API

### Changed
- cli now uses click

## [0.1.0] - 2026-02-10
### Added

Expand Down
63 changes: 47 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
![Shift Grid Example](docs/images/mllw2nvd.png)
*(Above: A generated vertical shift grid transforming MLLW to NAVD88)*

```transformez run -R loc:"new orleans" -E 3s -I mllw -O 5703```

## Installation

### Prerequisites: HTDP
Expand Down Expand Up @@ -59,20 +61,14 @@ pip install transformez

```bash
# Transform MLLW to WGS84 Ellipsoid in Norton Sound, AK
# (Where NOAA has no coverage!)
transformez -R -166/-164/63/64 -E 3s \
--input-datum mllw \
--output-datum 4979 \
--output shift_ak.tif

transformez run -R -166/-164/63/64 -E 1s -I mllw -O 4979
```

**Transform a raster directly.** Transformez reads the bounds/resolution from the file.

```bash
transformez --dem input_bathymetry.tif \
--input-datum "mllw" \
--output-datum "5703:geoid=geoid12b" \
--output output_navd88.tif
transformez run my_dem.tif -I mllw -O 5703
```

**Integrate directly into your download pipeline.**
Expand Down Expand Up @@ -118,13 +114,48 @@ out_file = transformez.transform_raster(

## Supported Datums

* **Tidal**: mllw, mhhw, msl, lat

* **Ellipsoidal**: 4979 (WGS84), 6319 (NAD83 2011)

* **Orthometric**: 5703 (NAVD88), egm2008, egm96

* **Geoids**: g2018, g2012b, geoid09, xgeoid20b
🌊 **Supported Tidal Surfaces:**

| EPSG | NAME |DESC |
| --- | --- | --- |
| 1089 | mllw | [USA] |
| 5866 | mllw | [USA]|
| 1091 | mlw | [USA]|
| 5869 | mhhw | [USA]|
| 5868 | mhw | [USA]|
| 5714 | msl | [USA]|
| 5713 | mtl | [USA]|
| 0 | crd | [USA]|
| 5609 | IGLD85 | [USA]|
| 9000 | LWD_IGLD85 | [USA]|
| 5702 | NGVD29 | [GLOBAL]|
| 9001 | lat | [GLOBAL]|
| 9002 | hat | [GLOBAL]|
| 9003 | mss | [GLOBAL]|

🌐 **Ellipsoidal / Frame Datums (EPSG)**:

| EPSG | NAME |DESC |
| --- | --- | --- |
| 4979 | WGS84 | World Geodetic System 1984 |
| 6319 | NAD83 | North American Datum 1983 |

🏔️ **Orthometric / Geoid-Based (EPSG)**:

| EPSG | NAME |DESC |
| --- | --- | --- |
| 5703 | NAVD88 height | (Default Geoid: g2018)|
| 6360 | NAVD88 height (usFt) | (Default Geoid: g2018)|
| 8228 | NAVD88 height (Ft) | (Default Geoid: g2018)|
| 6641 | PRVD02 height | (Default Geoid: g2018)|
| 6642 | VIVD09 height | (Default Geoid: g2018)|
| 6647 | CGVD2013(CGG2013) | (Default Geoid: CGG2013)|
| 3855 | EGM2008 height | (Default Geoid: egm2008)|
| 5773 | EGM96 height | (Default Geoid: egm96)|

🌍 **Available Geoids**:

g2018, g2012b, geoid09, xgeoid20b, xgeoid19b, egm2008, egm96, CGG2013

## License

Expand Down
Binary file modified docs/images/mllw2nvd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions src/transformez/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,54 @@ def _parse_datum(datum_arg: str) -> Tuple[Optional[int], Optional[str]]:
return Datums.get_vdatum_by_name(s), None


def plot_grid(grid_array, region, title="Vertical Shift Preview"):
"""Plot the transformation grid using Matplotlib."""

try:
import matplotlib.pyplot as plt
except ImportError:
logger.warning("Matplotlib is not installed. Cannot generate preview.")
return

if isinstance(region, Region):
region_obj = region
else:
regions = parse_region(region)
if not regions:
logger.error(f"Could not parse region: {region}")
return None
region_obj = regions[0]

masked_data = np.ma.masked_where(
(np.isnan(grid_array)) | (grid_array == -9999) | (grid_array == 0),
grid_array
)

if masked_data.count() == 0:
logger.warning("Preview skipped: Grid contains no valid data.")
return

plt.figure(figsize=(10, 6))
plot_region = [region_obj.xmin, region_obj.xmax, region_obj.ymin, region_obj.ymax]

# im = plt.imshow(masked_data, extent=plot_region, cmap="RdBu_r", origin="upper")
im = plt.imshow(masked_data, extent=plot_region, cmap="viridis", origin="upper")
cbar = plt.colorbar(im)
cbar.set_label("Vertical Shift (meters)")
plt.title(title)
plt.xlabel("Longitude")
plt.ylabel("Latitude")
plt.grid(True, linestyle=':', alpha=0.6)

stats = (f"Min: {masked_data.min():.3f} m\n"
f"Max: {masked_data.max():.3f} m\n"
f"Mean: {masked_data.mean():.3f} m")
plt.annotate(stats, xy=(0.02, 0.02), xycoords='axes fraction',
bbox=dict(boxstyle="round,pad=0.3", fc="white", ec="gray", alpha=0.8))
logger.info("Displaying preview... Close the plot window to continue.")
plt.show()


def generate_grid(
region: Union[List[float], str, Region],
increment: Union[str, float],
Expand Down
11 changes: 8 additions & 3 deletions src/transformez/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import click
import logging
from transformez import api
from transformez import grid_engine

logger = logging.getLogger(__name__)

Expand All @@ -35,13 +36,14 @@ def transformez_cli():
@click.option("-O", "--output-datum", required=True, help="Target Datum (e.g., '4979', '5703:g2012b').")
@click.option("--out", "-o", help="Output filename (default: auto-named).")
@click.option("--decay-pixels", type=int, default=100, help="Number of pixels to decay tidal shifts inland.")
def transform_run(input_file, region, increment, input_datum, output_datum, out, decay_pixels):
@click.option("--preview", is_flag=True, help="Preview the transformation output.")
def transform_run(input_file, region, increment, input_datum, output_datum, out, decay_pixels, preview):
"""Transform a raster's vertical datum or generate a standalone shift grid.

If an INPUT_FILE is provided, that specific raster is transformed in place.
If no INPUT_FILE is provided, -R and -E must be used to generate a shift grid.

Examples:
Examples:\n
Transform a DEM : transformez run my_dem.tif -I mllw -O 5703
Generate a Grid : transformez run -R loc:"Miami" -E 1s -I mllw -O 4979
"""
Expand All @@ -66,7 +68,7 @@ def transform_run(input_file, region, increment, input_datum, output_datum, out,
sys.exit(1)

elif region and increment:
click.secho(f"Generating vertical shift grid for region...", fg="cyan", bold=True)
click.secho(f"Generating vertical shift grid for region: {region}...", fg="cyan", bold=True)
click.echo(f" Shift: {input_datum} ➔ {output_datum} @ {increment}")

# Auto-generate an output name if one wasn't provided
Expand All @@ -82,6 +84,9 @@ def transform_run(input_file, region, increment, input_datum, output_datum, out,
verbose=True
)

if preview:
api.plot_grid(result, region)

if result is not None:
click.secho(f"Successfully generated shift grid: {out_fn}", fg="green", bold=True)
else:
Expand Down
Loading