transformez 0.3.2__tar.gz → 0.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {transformez-0.3.2 → transformez-0.3.4}/CHANGELOG.md +9 -1
- {transformez-0.3.2 → transformez-0.3.4}/PKG-INFO +1 -1
- {transformez-0.3.2 → transformez-0.3.4}/src/transformez/__init__.py +4 -0
- {transformez-0.3.2 → transformez-0.3.4}/src/transformez/_version.py +1 -1
- {transformez-0.3.2 → transformez-0.3.4}/src/transformez/api.py +32 -25
- {transformez-0.3.2 → transformez-0.3.4}/src/transformez/cli.py +54 -21
- transformez-0.3.4/src/transformez/definitions.py +499 -0
- {transformez-0.3.2 → transformez-0.3.4}/src/transformez/grid_engine.py +64 -27
- {transformez-0.3.2 → transformez-0.3.4}/src/transformez/hooks.py +20 -10
- {transformez-0.3.2 → transformez-0.3.4}/src/transformez/htdp.py +27 -29
- {transformez-0.3.2 → transformez-0.3.4}/src/transformez/modules.py +30 -15
- {transformez-0.3.2 → transformez-0.3.4}/src/transformez/spatial.py +53 -30
- {transformez-0.3.2 → transformez-0.3.4}/src/transformez/srs.py +67 -57
- {transformez-0.3.2 → transformez-0.3.4}/src/transformez/transform.py +198 -84
- {transformez-0.3.2 → transformez-0.3.4}/src/transformez/utils.py +6 -7
- {transformez-0.3.2 → transformez-0.3.4}/src/transformez/vdatum.py +27 -13
- transformez-0.3.2/src/transformez/definitions.py +0 -256
- {transformez-0.3.2 → transformez-0.3.4}/AUTHORS.md +0 -0
- {transformez-0.3.2 → transformez-0.3.4}/CITATION.cff +0 -0
- {transformez-0.3.2 → transformez-0.3.4}/CONTRIBUTING.md +0 -0
- {transformez-0.3.2 → transformez-0.3.4}/LICENSE +0 -0
- {transformez-0.3.2 → transformez-0.3.4}/README.md +0 -0
- {transformez-0.3.2 → transformez-0.3.4}/pyproject.toml +0 -0
- {transformez-0.3.2 → transformez-0.3.4}/tests/test_cli.py +0 -0
|
@@ -5,7 +5,13 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
## [0.3.2] - 2026-05-27
|
|
9
15
|
|
|
10
16
|
### Added
|
|
11
17
|
- support for FES2014
|
|
@@ -16,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
16
22
|
### Changed
|
|
17
23
|
- cli now uses click
|
|
18
24
|
|
|
25
|
+
<...missed...>
|
|
26
|
+
|
|
19
27
|
## [0.1.0] - 2026-02-10
|
|
20
28
|
### Added
|
|
21
29
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: transformez
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: A standalone utility for vertical elevation datum transformations.
|
|
5
5
|
Project-URL: Homepage, https://github.com/continuous-dems/transformez
|
|
6
6
|
Project-URL: Issues, https://github.com/continuous-dems/transformez/issues
|
|
@@ -33,11 +33,13 @@ import glob
|
|
|
33
33
|
# Expose the module for fetchez
|
|
34
34
|
from .modules import TransformezMod
|
|
35
35
|
|
|
36
|
+
|
|
36
37
|
def _find_proj_lib():
|
|
37
38
|
"""Locate the best available PROJ_LIB path."""
|
|
38
39
|
|
|
39
40
|
try:
|
|
40
41
|
import rasterio
|
|
42
|
+
|
|
41
43
|
r_path = os.path.join(os.path.dirname(rasterio.__file__), "proj_data")
|
|
42
44
|
if os.path.exists(os.path.join(r_path, "proj.db")):
|
|
43
45
|
return r_path
|
|
@@ -53,6 +55,7 @@ def _find_proj_lib():
|
|
|
53
55
|
|
|
54
56
|
try:
|
|
55
57
|
import pyproj
|
|
58
|
+
|
|
56
59
|
p_path = pyproj.datadir.get_data_dir()
|
|
57
60
|
if os.path.exists(os.path.join(p_path, "proj.db")):
|
|
58
61
|
return p_path
|
|
@@ -61,6 +64,7 @@ def _find_proj_lib():
|
|
|
61
64
|
|
|
62
65
|
return None
|
|
63
66
|
|
|
67
|
+
|
|
64
68
|
target_proj_lib = _find_proj_lib()
|
|
65
69
|
|
|
66
70
|
if "PROJ_LIB" in os.environ:
|
|
@@ -46,12 +46,10 @@ def _parse_datum(datum_arg: str) -> Tuple[Optional[int], Optional[str]]:
|
|
|
46
46
|
if not datum_arg:
|
|
47
47
|
return None, None
|
|
48
48
|
s = str(datum_arg)
|
|
49
|
-
if
|
|
50
|
-
parts = s.split(
|
|
49
|
+
if ":" in s:
|
|
50
|
+
parts = s.split(":")
|
|
51
51
|
geoid_part = parts[1]
|
|
52
|
-
geoid = (
|
|
53
|
-
geoid_part.split('=')[1] if 'geoid=' in geoid_part else geoid_part
|
|
54
|
-
)
|
|
52
|
+
geoid = geoid_part.split("=")[1] if "geoid=" in geoid_part else geoid_part
|
|
55
53
|
return Datums.get_vdatum_by_name(parts[0]), geoid
|
|
56
54
|
return Datums.get_vdatum_by_name(s), None
|
|
57
55
|
|
|
@@ -75,8 +73,7 @@ def plot_grid(grid_array, region, title="Vertical Shift Preview"):
|
|
|
75
73
|
region_obj = regions[0]
|
|
76
74
|
|
|
77
75
|
masked_data = np.ma.masked_where(
|
|
78
|
-
(np.isnan(grid_array)) | (grid_array == -9999) | (grid_array == 0),
|
|
79
|
-
grid_array
|
|
76
|
+
(np.isnan(grid_array)) | (grid_array == -9999) | (grid_array == 0), grid_array
|
|
80
77
|
)
|
|
81
78
|
|
|
82
79
|
if masked_data.count() == 0:
|
|
@@ -93,13 +90,19 @@ def plot_grid(grid_array, region, title="Vertical Shift Preview"):
|
|
|
93
90
|
plt.title(title)
|
|
94
91
|
plt.xlabel("Longitude")
|
|
95
92
|
plt.ylabel("Latitude")
|
|
96
|
-
plt.grid(True, linestyle=
|
|
93
|
+
plt.grid(True, linestyle=":", alpha=0.6)
|
|
97
94
|
|
|
98
|
-
stats = (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
95
|
+
stats = (
|
|
96
|
+
f"Min: {masked_data.min():.3f} m\n"
|
|
97
|
+
f"Max: {masked_data.max():.3f} m\n"
|
|
98
|
+
f"Mean: {masked_data.mean():.3f} m"
|
|
99
|
+
)
|
|
100
|
+
plt.annotate(
|
|
101
|
+
stats,
|
|
102
|
+
xy=(0.02, 0.02),
|
|
103
|
+
xycoords="axes fraction",
|
|
104
|
+
bbox=dict(boxstyle="round,pad=0.3", fc="white", ec="gray", alpha=0.8),
|
|
105
|
+
)
|
|
103
106
|
logger.info("Displaying preview... Close the plot window to continue.")
|
|
104
107
|
plt.show()
|
|
105
108
|
|
|
@@ -156,12 +159,15 @@ def generate_grid(
|
|
|
156
159
|
|
|
157
160
|
vt = VerticalTransform(
|
|
158
161
|
region=region_obj,
|
|
159
|
-
nx=nx,
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
nx=nx,
|
|
163
|
+
ny=ny,
|
|
164
|
+
epsg_in=epsg_in,
|
|
165
|
+
epsg_out=epsg_out,
|
|
166
|
+
geoid_in=geoid_in,
|
|
167
|
+
geoid_out=geoid_out,
|
|
162
168
|
decay_pixels=decay_pixels,
|
|
163
169
|
cache_dir=cache_dir,
|
|
164
|
-
verbose=verbose
|
|
170
|
+
verbose=verbose,
|
|
165
171
|
)
|
|
166
172
|
|
|
167
173
|
shift_array, _ = vt._vertical_transform(vt.epsg_in, vt.epsg_out)
|
|
@@ -184,7 +190,7 @@ def transform_raster(
|
|
|
184
190
|
decay_pixels: int = 100,
|
|
185
191
|
output_raster: Optional[str] = None,
|
|
186
192
|
cache_dir: Optional[str] = None,
|
|
187
|
-
verbose: bool = False
|
|
193
|
+
verbose: bool = False,
|
|
188
194
|
) -> Optional[str]:
|
|
189
195
|
"""Apply a vertical datum transformation directly to an existing raster file.
|
|
190
196
|
|
|
@@ -209,9 +215,7 @@ def transform_raster(
|
|
|
209
215
|
|
|
210
216
|
with rasterio.open(input_raster) as src:
|
|
211
217
|
bounds = src.bounds
|
|
212
|
-
region_obj = (
|
|
213
|
-
Region(bounds.left, bounds.right, bounds.bottom, bounds.top)
|
|
214
|
-
)
|
|
218
|
+
region_obj = Region(bounds.left, bounds.right, bounds.bottom, bounds.top)
|
|
215
219
|
nx, ny = src.width, src.height
|
|
216
220
|
|
|
217
221
|
epsg_in, geoid_in = _parse_datum(datum_in)
|
|
@@ -227,12 +231,15 @@ def transform_raster(
|
|
|
227
231
|
|
|
228
232
|
vt = VerticalTransform(
|
|
229
233
|
region=region_obj,
|
|
230
|
-
nx=nx,
|
|
231
|
-
|
|
232
|
-
|
|
234
|
+
nx=nx,
|
|
235
|
+
ny=ny,
|
|
236
|
+
epsg_in=epsg_in,
|
|
237
|
+
epsg_out=epsg_out,
|
|
238
|
+
geoid_in=geoid_in,
|
|
239
|
+
geoid_out=geoid_out,
|
|
233
240
|
decay_pixels=decay_pixels,
|
|
234
241
|
cache_dir=cache_dir,
|
|
235
|
-
verbose=verbose
|
|
242
|
+
verbose=verbose,
|
|
236
243
|
)
|
|
237
244
|
|
|
238
245
|
shift_array, _ = vt._vertical_transform(vt.epsg_in, vt.epsg_out)
|
|
@@ -15,29 +15,47 @@ import sys
|
|
|
15
15
|
import click
|
|
16
16
|
import logging
|
|
17
17
|
from transformez import api
|
|
18
|
-
from transformez import grid_engine
|
|
19
18
|
|
|
20
|
-
logger = logging.getLogger(__name__)
|
|
19
|
+
# logger = logging.getLogger(__name__)
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
@click.group(name="transform")
|
|
24
|
-
@click.version_option(package_name=
|
|
23
|
+
@click.version_option(package_name="transformez")
|
|
25
24
|
def transformez_cli():
|
|
26
25
|
"""Apply vertical datum transformations and generate shift grids."""
|
|
27
26
|
|
|
27
|
+
logging.basicConfig(level=logging.INFO, format='[%(levelname)s] %(message)s')
|
|
28
28
|
pass
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
@transformez_cli.command("run")
|
|
32
32
|
@click.argument("input_file", required=False)
|
|
33
|
-
@click.option(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@click.option(
|
|
33
|
+
@click.option(
|
|
34
|
+
"-R", "--region", help="Bounding box or location string (if no input file)."
|
|
35
|
+
)
|
|
36
|
+
@click.option(
|
|
37
|
+
"-E", "--increment", help="Resolution (e.g., 1s, 30m) (if no input file)."
|
|
38
|
+
)
|
|
39
|
+
@click.option(
|
|
40
|
+
"-I", "--input-datum", required=True, help="Source Datum (e.g., 'mllw', '5703')."
|
|
41
|
+
)
|
|
42
|
+
@click.option(
|
|
43
|
+
"-O",
|
|
44
|
+
"--output-datum",
|
|
45
|
+
required=True,
|
|
46
|
+
help="Target Datum (e.g., '4979', '5703:g2012b').",
|
|
47
|
+
)
|
|
37
48
|
@click.option("--out", "-o", help="Output filename (default: auto-named).")
|
|
38
|
-
@click.option(
|
|
49
|
+
@click.option(
|
|
50
|
+
"--decay-pixels",
|
|
51
|
+
type=int,
|
|
52
|
+
default=100,
|
|
53
|
+
help="Number of pixels to decay tidal shifts inland.",
|
|
54
|
+
)
|
|
39
55
|
@click.option("--preview", is_flag=True, help="Preview the transformation output.")
|
|
40
|
-
def transform_run(
|
|
56
|
+
def transform_run(
|
|
57
|
+
input_file, region, increment, input_datum, output_datum, out, decay_pixels, preview
|
|
58
|
+
):
|
|
41
59
|
"""Transform a raster's vertical datum or generate a standalone shift grid.
|
|
42
60
|
|
|
43
61
|
If an INPUT_FILE is provided, that specific raster is transformed in place.
|
|
@@ -58,17 +76,23 @@ def transform_run(input_file, region, increment, input_datum, output_datum, out,
|
|
|
58
76
|
datum_out=output_datum,
|
|
59
77
|
decay_pixels=decay_pixels,
|
|
60
78
|
output_raster=out,
|
|
61
|
-
verbose=True
|
|
79
|
+
verbose=True,
|
|
62
80
|
)
|
|
63
81
|
|
|
64
82
|
if result:
|
|
65
|
-
click.secho(
|
|
83
|
+
click.secho(
|
|
84
|
+
f"Successfully transformed raster: {result}", fg="green", bold=True
|
|
85
|
+
)
|
|
66
86
|
else:
|
|
67
87
|
click.secho("Failed to transform raster.", fg="red")
|
|
68
88
|
sys.exit(1)
|
|
69
89
|
|
|
70
90
|
elif region and increment:
|
|
71
|
-
click.secho(
|
|
91
|
+
click.secho(
|
|
92
|
+
f"Generating vertical shift grid for region: {region}...",
|
|
93
|
+
fg="cyan",
|
|
94
|
+
bold=True,
|
|
95
|
+
)
|
|
72
96
|
click.echo(f" Shift: {input_datum} ➔ {output_datum} @ {increment}")
|
|
73
97
|
|
|
74
98
|
# Auto-generate an output name if one wasn't provided
|
|
@@ -81,20 +105,25 @@ def transform_run(input_file, region, increment, input_datum, output_datum, out,
|
|
|
81
105
|
datum_out=output_datum,
|
|
82
106
|
decay_pixels=decay_pixels,
|
|
83
107
|
out_fn=out_fn,
|
|
84
|
-
verbose=True
|
|
108
|
+
verbose=True,
|
|
85
109
|
)
|
|
86
110
|
|
|
87
111
|
if preview:
|
|
88
112
|
api.plot_grid(result, region)
|
|
89
113
|
|
|
90
114
|
if result is not None:
|
|
91
|
-
click.secho(
|
|
115
|
+
click.secho(
|
|
116
|
+
f"Successfully generated shift grid: {out_fn}", fg="green", bold=True
|
|
117
|
+
)
|
|
92
118
|
else:
|
|
93
119
|
click.secho("Failed to generate shift grid.", fg="red")
|
|
94
120
|
sys.exit(1)
|
|
95
121
|
|
|
96
122
|
else:
|
|
97
|
-
click.secho(
|
|
123
|
+
click.secho(
|
|
124
|
+
"Error: You must provide either an INPUT_FILE or both --region and --increment.",
|
|
125
|
+
fg="red",
|
|
126
|
+
)
|
|
98
127
|
sys.exit(1)
|
|
99
128
|
|
|
100
129
|
|
|
@@ -107,7 +136,7 @@ def transform_list():
|
|
|
107
136
|
click.secho("\n🌊 Supported Tidal Surfaces:", fg="cyan", bold=True)
|
|
108
137
|
# For tidal datums, the user types the dictionary key (e.g., 'mllw')
|
|
109
138
|
for key, v in Datums.SURFACES.items():
|
|
110
|
-
region_str = v.get(
|
|
139
|
+
region_str = v.get("region", "global").upper()
|
|
111
140
|
click.echo(f" {key:<12} : {v.get('name', key):<30} [{region_str}]")
|
|
112
141
|
|
|
113
142
|
click.secho("\n🌐 Ellipsoidal / Frame Datums (EPSG):", fg="cyan", bold=True)
|
|
@@ -119,19 +148,23 @@ def transform_list():
|
|
|
119
148
|
# For orthometric, the key in Datums.CDN is typically the EPSG code (e.g., '5703')
|
|
120
149
|
for epsg_key, v in Datums.CDN.items():
|
|
121
150
|
# Fallback to the key if 'epsg' isn't explicitly defined in the dict
|
|
122
|
-
epsg_code = v.get(
|
|
123
|
-
geoid_str = v.get(
|
|
124
|
-
click.echo(
|
|
151
|
+
epsg_code = v.get("epsg", epsg_key)
|
|
152
|
+
geoid_str = v.get("default_geoid", "None")
|
|
153
|
+
click.echo(
|
|
154
|
+
f" {str(epsg_code):<12} : {v.get('name', 'Unknown'):<30} (Default Geoid: {geoid_str})"
|
|
155
|
+
)
|
|
125
156
|
|
|
126
157
|
click.secho("\n🌍 Available Geoids:", fg="cyan", bold=True)
|
|
127
158
|
click.echo(f" {', '.join(Datums.GEOIDS.keys())}")
|
|
128
159
|
|
|
129
160
|
click.secho("\n💡 Pro-Tip:", fg="yellow", bold=True, nl=False)
|
|
130
|
-
click.echo(
|
|
161
|
+
click.echo(
|
|
162
|
+
" Combine an EPSG and a specific Geoid using a colon (e.g., -O 5703:g2012b)\n"
|
|
163
|
+
)
|
|
131
164
|
|
|
132
165
|
except ImportError:
|
|
133
166
|
click.secho("Error: Could not load Transformez datum definitions.", fg="red")
|
|
134
167
|
|
|
135
168
|
|
|
136
|
-
if __name__ ==
|
|
169
|
+
if __name__ == "__main__":
|
|
137
170
|
transformez_cli()
|