diff --git a/CHANGELOG.md b/CHANGELOG.md index 8629ecb..640c8a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 8e29b65..6508047 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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.** @@ -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 diff --git a/docs/images/mllw2nvd.png b/docs/images/mllw2nvd.png index 87e5d53..126dd09 100644 Binary files a/docs/images/mllw2nvd.png and b/docs/images/mllw2nvd.png differ diff --git a/src/transformez/api.py b/src/transformez/api.py index 093dd96..fc55db6 100644 --- a/src/transformez/api.py +++ b/src/transformez/api.py @@ -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], diff --git a/src/transformez/cli.py b/src/transformez/cli.py index 92e91b6..921f46c 100644 --- a/src/transformez/cli.py +++ b/src/transformez/cli.py @@ -15,6 +15,7 @@ import click import logging from transformez import api +from transformez import grid_engine logger = logging.getLogger(__name__) @@ -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 """ @@ -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 @@ -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: