transformez 0.2.0__tar.gz → 0.2.2__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.2.2/CHANGELOG.md +14 -0
- transformez-0.2.2/CITATION.cff +19 -0
- transformez-0.2.2/CONTRIBUTING.md +0 -0
- {transformez-0.2.0/src/transformez.egg-info → transformez-0.2.2}/PKG-INFO +13 -12
- transformez-0.2.2/pyproject.toml +75 -0
- transformez-0.2.2/src/transformez/__init__.py +101 -0
- transformez-0.2.2/src/transformez/_version.py +4 -0
- {transformez-0.2.0 → transformez-0.2.2}/src/transformez/cli.py +4 -1
- {transformez-0.2.0 → transformez-0.2.2}/src/transformez/grid_engine.py +4 -0
- transformez-0.2.2/src/transformez/modules.py +108 -0
- {transformez-0.2.0 → transformez-0.2.2}/src/transformez/transform.py +68 -54
- transformez-0.2.2/src/transformez/utils.py +96 -0
- transformez-0.2.2/tests/test_cli.py +36 -0
- transformez-0.2.0/PKG-INFO +0 -127
- transformez-0.2.0/pyproject.toml +0 -41
- transformez-0.2.0/setup.cfg +0 -4
- transformez-0.2.0/setup.py +0 -2
- transformez-0.2.0/src/transformez/__init__.py +0 -75
- transformez-0.2.0/src/transformez/utils.py +0 -43
- transformez-0.2.0/src/transformez.egg-info/SOURCES.txt +0 -22
- transformez-0.2.0/src/transformez.egg-info/dependency_links.txt +0 -1
- transformez-0.2.0/src/transformez.egg-info/entry_points.txt +0 -5
- transformez-0.2.0/src/transformez.egg-info/requires.txt +0 -4
- transformez-0.2.0/src/transformez.egg-info/top_level.txt +0 -1
- {transformez-0.2.0 → transformez-0.2.2}/AUTHORS.md +0 -0
- {transformez-0.2.0 → transformez-0.2.2}/LICENSE +0 -0
- {transformez-0.2.0 → transformez-0.2.2}/README.md +0 -0
- {transformez-0.2.0 → transformez-0.2.2}/src/transformez/definitions.py +0 -0
- {transformez-0.2.0 → transformez-0.2.2}/src/transformez/hooks.py +0 -0
- {transformez-0.2.0 → transformez-0.2.2}/src/transformez/htdp.py +0 -0
- {transformez-0.2.0 → transformez-0.2.2}/src/transformez/spatial.py +0 -0
- {transformez-0.2.0 → transformez-0.2.2}/src/transformez/srs.py +0 -0
- {transformez-0.2.0 → transformez-0.2.2}/src/transformez/vdatum.py +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-02-10
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Now uses raserio
|
|
13
|
+
- Renamed project to `transformez`.
|
|
14
|
+
- Refactored and decoupled from old cudem.vdatums
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use this software, please cite it as below."
|
|
3
|
+
authors:
|
|
4
|
+
- family-names: Love
|
|
5
|
+
given-names: Matthew
|
|
6
|
+
- family-names: Amante
|
|
7
|
+
given-names: Christopher
|
|
8
|
+
- family-names: MacFerrin
|
|
9
|
+
given-names: Michael
|
|
10
|
+
- family-names: Lim
|
|
11
|
+
given-names: Elliot
|
|
12
|
+
- family-names: Fisher
|
|
13
|
+
given-names: Matt
|
|
14
|
+
|
|
15
|
+
website: https://ciresdem.github.io/transformez/
|
|
16
|
+
title: "Transformez"
|
|
17
|
+
version: 0.1.0
|
|
18
|
+
date-released: 2026-02-10
|
|
19
|
+
url: "https://github.com/ciresdem/transformez"
|
|
File without changes
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: transformez
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A standalone utility for vertical elevation datum transformations.
|
|
5
|
-
|
|
5
|
+
Project-URL: Homepage, https://github.com/continuous-dems/transformez
|
|
6
|
+
Project-URL: Issues, https://github.com/continuous-dems/transformez/issues
|
|
7
|
+
Author-email: Matthew Love <matthew.love@colorado.edu>, Christopher Amante <christopher.amante@colorado.edu>, Elliot Lim <elliot.lim@colorado.edu>, Michael MacFerrin <michael.macferrin@colorado.edu>
|
|
8
|
+
Maintainer-email: Matthew Love <matthew.love@colorado.edu>
|
|
6
9
|
License: MIT License
|
|
7
10
|
|
|
8
11
|
Copyright (c) 2010-2026 Regents of the University of Colorado
|
|
@@ -24,21 +27,19 @@ License: MIT License
|
|
|
24
27
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
28
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
29
|
SOFTWARE.
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
License-File: AUTHORS.md
|
|
31
|
+
License-File: LICENSE
|
|
29
32
|
Keywords: Geospatial
|
|
30
|
-
Classifier: Programming Language :: Python :: 3
|
|
31
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
32
33
|
Classifier: Operating System :: OS Independent
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
33
35
|
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
License-File: AUTHORS.md
|
|
36
|
+
Requires-Python: >=3.8
|
|
37
|
+
Requires-Dist: fetchez>0.3.3
|
|
37
38
|
Requires-Dist: numpy<2.0.0
|
|
38
39
|
Requires-Dist: pyproj
|
|
39
40
|
Requires-Dist: rasterio
|
|
40
|
-
Requires-Dist:
|
|
41
|
-
|
|
41
|
+
Requires-Dist: scipy
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
42
43
|
|
|
43
44
|
# 🌍 Transformez ↕
|
|
44
45
|
|
|
@@ -124,4 +125,4 @@ shift, unc = vt._vertical_transform(vt.epsg_in, vt.epsg_out)
|
|
|
124
125
|
## License
|
|
125
126
|
|
|
126
127
|
This project is licensed under the MIT License - see the [LICENSE](https://github.com/ciresdem/transformez/blob/main/LICENSE) file for details.
|
|
127
|
-
Copyright (c) 2010-2026 Regents of the University of Colorado
|
|
128
|
+
Copyright (c) 2010-2026 Regents of the University of Colorado
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.5.0", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = 'transformez'
|
|
7
|
+
description = 'A standalone utility for vertical elevation datum transformations.'
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.8"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
dynamic = ["version"]
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Matthew Love", email = "matthew.love@colorado.edu"},
|
|
14
|
+
{name = "Christopher Amante", email = "christopher.amante@colorado.edu"},
|
|
15
|
+
{name = "Elliot Lim", email = "elliot.lim@colorado.edu"},
|
|
16
|
+
{name = "Michael MacFerrin", email = "michael.macferrin@colorado.edu"},
|
|
17
|
+
]
|
|
18
|
+
maintainers = [
|
|
19
|
+
{name = "Matthew Love", email = "matthew.love@colorado.edu"},
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
'Programming Language :: Python :: 3',
|
|
23
|
+
'Operating System :: OS Independent',
|
|
24
|
+
'Topic :: Scientific/Engineering :: GIS',
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
'numpy<2.0.0',
|
|
28
|
+
'pyproj',
|
|
29
|
+
'rasterio',
|
|
30
|
+
'fetchez>0.3.3',
|
|
31
|
+
'scipy',
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
keywords = ["Geospatial"]
|
|
35
|
+
|
|
36
|
+
[dependency-groups]
|
|
37
|
+
test = [
|
|
38
|
+
"pytest",
|
|
39
|
+
"pytest-cov",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.entry-points."fetchez.plugins"]
|
|
43
|
+
transformez = "transformez"
|
|
44
|
+
|
|
45
|
+
[project.scripts]
|
|
46
|
+
transformez = 'transformez.cli:transformez_cli'
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
Homepage = "https://github.com/continuous-dems/transformez"
|
|
50
|
+
Issues = "https://github.com/continuous-dems/transformez/issues"
|
|
51
|
+
|
|
52
|
+
# --- Build configuration ---
|
|
53
|
+
|
|
54
|
+
[tool.hatch.version]
|
|
55
|
+
source = "vcs"
|
|
56
|
+
|
|
57
|
+
[tool.hatch.build.hooks.version]
|
|
58
|
+
path = "src/transformez/_version.py"
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.targets.wheel]
|
|
61
|
+
packages = ["src/transformez"]
|
|
62
|
+
|
|
63
|
+
[tool.hatch.build.targets.sdist]
|
|
64
|
+
include = [
|
|
65
|
+
"src/transformez",
|
|
66
|
+
"tests",
|
|
67
|
+
"CHANGELOG.md",
|
|
68
|
+
"CITATION.cff",
|
|
69
|
+
"CONTRIBUTING.md",
|
|
70
|
+
"AUTHORS.md",
|
|
71
|
+
]
|
|
72
|
+
exclude = [
|
|
73
|
+
".github",
|
|
74
|
+
]
|
|
75
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
transformez
|
|
6
|
+
~~~~~~~~~~~~~
|
|
7
|
+
|
|
8
|
+
:copyright: (c) 2010-2026 Regents of the University of Colorado
|
|
9
|
+
:license: MIT, see LICENSE for more details.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__author__ = "Matthew Love"
|
|
13
|
+
__credits__ = "CIRES"
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from transformez._version import __version__
|
|
17
|
+
except ImportError:
|
|
18
|
+
# Fallback when using the package from source without installing
|
|
19
|
+
# in editable mode with pip (nobody should do this):
|
|
20
|
+
# <https://pip.pypa.io/en/stable/topics/local-project-installs/#editable-installs>
|
|
21
|
+
import warnings
|
|
22
|
+
|
|
23
|
+
warnings.warn(
|
|
24
|
+
"Importing 'transformez' outside a proper installation."
|
|
25
|
+
" It's highly recommended to install the package from a stable release or"
|
|
26
|
+
" in editable mode.",
|
|
27
|
+
stacklevel=2,
|
|
28
|
+
)
|
|
29
|
+
__version__ = "dev"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
import os
|
|
33
|
+
import glob
|
|
34
|
+
# from .hooks import TransformezHook
|
|
35
|
+
# from fetchez.hooks.registry import HookRegistry
|
|
36
|
+
from fetchez.registry import FetchezRegistry
|
|
37
|
+
|
|
38
|
+
from .modules import TransformezMod
|
|
39
|
+
|
|
40
|
+
def _find_proj_lib():
|
|
41
|
+
"""Locate the best available PROJ_LIB path."""
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
import rasterio
|
|
45
|
+
r_path = os.path.join(os.path.dirname(rasterio.__file__), "proj_data")
|
|
46
|
+
if os.path.exists(os.path.join(r_path, "proj.db")):
|
|
47
|
+
return r_path
|
|
48
|
+
|
|
49
|
+
parent = os.path.dirname(os.path.dirname(rasterio.__file__))
|
|
50
|
+
libs = glob.glob(os.path.join(parent, "rasterio.libs*"))
|
|
51
|
+
if libs:
|
|
52
|
+
for root, _, files in os.walk(libs[0]):
|
|
53
|
+
if "proj.db" in files:
|
|
54
|
+
return root
|
|
55
|
+
except ImportError:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
import pyproj
|
|
60
|
+
p_path = pyproj.datadir.get_data_dir()
|
|
61
|
+
if os.path.exists(os.path.join(p_path, "proj.db")):
|
|
62
|
+
return p_path
|
|
63
|
+
except ImportError:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
target_proj_lib = _find_proj_lib()
|
|
69
|
+
|
|
70
|
+
if "PROJ_LIB" in os.environ:
|
|
71
|
+
del os.environ["PROJ_LIB"]
|
|
72
|
+
|
|
73
|
+
if target_proj_lib:
|
|
74
|
+
os.environ["PROJ_LIB"] = target_proj_lib
|
|
75
|
+
|
|
76
|
+
def setup_fetchez(registry_cls):
|
|
77
|
+
"""Called by fetchez when loading plugins.
|
|
78
|
+
|
|
79
|
+
Registers modules, hooks, and presets.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
registry_cls.register_module(
|
|
83
|
+
'transformez',
|
|
84
|
+
TransformezMod,
|
|
85
|
+
metadata={
|
|
86
|
+
'desc': 'Generate vertical datum shift grids on-demand.',
|
|
87
|
+
"tags": ["vdatum", "transformation", "shift-grid"],
|
|
88
|
+
"category": "Tools"
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# HookRegistry.register_hook(TransformezHook)
|
|
93
|
+
# from fetchez.presets import register_global_preset
|
|
94
|
+
# register_global_preset(
|
|
95
|
+
# name="make-shift-grid",
|
|
96
|
+
# help_text="Download datum grids and composite them into a single shift grid.",
|
|
97
|
+
# hooks=[
|
|
98
|
+
# {"name": "transformez", "args": {}}
|
|
99
|
+
# ]
|
|
100
|
+
# )
|
|
101
|
+
setup_fetchez(FetchezRegistry)
|
|
@@ -73,7 +73,7 @@ def list_supported_datums():
|
|
|
73
73
|
|
|
74
74
|
def transformez_cli():
|
|
75
75
|
parser = argparse.ArgumentParser(
|
|
76
|
-
description="Global Vertical Datum Transformer",
|
|
76
|
+
description=f"{utils.CYAN}%(prog)s{utils.RESET} ({__version__}) :: Global Vertical Datum Transformer",
|
|
77
77
|
epilog="Examples:\n"
|
|
78
78
|
" transformez -R -166/-164/63/64 -I mllw -O 4979\n"
|
|
79
79
|
" transformez input_dem.tif -I mllw -O 5703:geoid=g2012b",
|
|
@@ -100,6 +100,9 @@ def transformez_cli():
|
|
|
100
100
|
grp_sys.add_argument('--list-datums', action='store_true', help='List supported datums.')
|
|
101
101
|
grp_sys.add_argument('--cache-dir', help='Override cache directory.')
|
|
102
102
|
grp_sys.add_argument('--verbose', action='store_true', help='Enable debug logging.')
|
|
103
|
+
grp_sys.add_argument(
|
|
104
|
+
"-v", "--version", action="version", version=f"%(prog)s {__version__}"
|
|
105
|
+
)
|
|
103
106
|
|
|
104
107
|
args = parser.parse_args()
|
|
105
108
|
|
|
@@ -165,6 +165,10 @@ class GridWriter:
|
|
|
165
165
|
def write(filename, data, region):
|
|
166
166
|
"""Write a vertical shift grid using Rasterio."""
|
|
167
167
|
|
|
168
|
+
dirname = os.path.dirname(filename)
|
|
169
|
+
if dirname and not os.path.exists(dirname):
|
|
170
|
+
os.makedirs(dirname)
|
|
171
|
+
|
|
168
172
|
if not filename.endswith('.tif'):
|
|
169
173
|
filename = os.path.splitext(filename)[0] + '.tif'
|
|
170
174
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
transformez.modules
|
|
6
|
+
~~~~~~~~~~~~~
|
|
7
|
+
|
|
8
|
+
Some modules for `fetchez`
|
|
9
|
+
|
|
10
|
+
:copyright: (c) 2010-2026 Regents of the University of Colorado
|
|
11
|
+
:license: MIT, see LICENSE for more details.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import logging
|
|
16
|
+
from fetchez import core, cli
|
|
17
|
+
from transformez.transform import VerticalTransform
|
|
18
|
+
from transformez.grid_engine import GridWriter
|
|
19
|
+
from transformez.definitions import Datums
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
#!/usr/bin/env python
|
|
25
|
+
# -*- coding: utf-8 -*-
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
transformez.modules.transformez_mod
|
|
29
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
@cli.cli_opts(
|
|
36
|
+
help_text="Generate a vertical shift grid (e.g. MLLW to NAVD88).",
|
|
37
|
+
src_datum="Source Datum (e.g. 'mllw', '5703', '4979').",
|
|
38
|
+
dst_datum="Destination Datum (e.g. '5703:geoid=g2012b').",
|
|
39
|
+
increment="Grid resolution (default: 3s).",
|
|
40
|
+
output_name="Optional output filename override."
|
|
41
|
+
)
|
|
42
|
+
class TransformezMod(core.FetchModule):
|
|
43
|
+
"""A dynamic Fetchez module that generates vertical shift grids on demand.
|
|
44
|
+
|
|
45
|
+
Usage:
|
|
46
|
+
... transformez --src-datum mllw --dst-datum 5703
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, src_datum='5703', dst_datum='4979', increment='3s', output_name=None, **kwargs):
|
|
50
|
+
super().__init__(name="transformez", **kwargs)
|
|
51
|
+
self.src_datum = src_datum
|
|
52
|
+
self.dst_datum = dst_datum
|
|
53
|
+
self.increment = increment
|
|
54
|
+
self.output_name = output_name
|
|
55
|
+
|
|
56
|
+
s_name = str(self.src_datum).replace(':', '_')
|
|
57
|
+
d_name = str(self.dst_datum).replace(':', '_')
|
|
58
|
+
w, e, s, n = self.region
|
|
59
|
+
self.dst_fn = os.path.join(self._outdir, f"shift_{s_name}_to_{d_name}_{w}_{s}.tif")
|
|
60
|
+
|
|
61
|
+
def run(self):
|
|
62
|
+
from fetchez import utils
|
|
63
|
+
try:
|
|
64
|
+
inc_val = utils.str2inc(self.increment)
|
|
65
|
+
nx = int(self.region.width / inc_val)
|
|
66
|
+
ny = int(self.region.height / inc_val)
|
|
67
|
+
except Exception:
|
|
68
|
+
logger.warning(f"Invalid increment '{self.increment}', defaulting to 3s (~0.000833).")
|
|
69
|
+
# Default roughly 3 arc-seconds (approx 90m)
|
|
70
|
+
nx = int(self.region.width / 0.00083333333)
|
|
71
|
+
ny = int(self.region.height / 0.00083333333)
|
|
72
|
+
|
|
73
|
+
def parse_d(d_str):
|
|
74
|
+
if ':' in str(d_str):
|
|
75
|
+
parts = d_str.split(':')
|
|
76
|
+
geoid = parts[1].split('=')[1] if 'geoid=' in parts[1] else parts[1]
|
|
77
|
+
return parts[0], geoid
|
|
78
|
+
return d_str, None
|
|
79
|
+
|
|
80
|
+
epsg_in, geoid_in = parse_d(self.src_datum)
|
|
81
|
+
epsg_out, geoid_out = parse_d(self.dst_datum)
|
|
82
|
+
|
|
83
|
+
vt = VerticalTransform(
|
|
84
|
+
region=self.region,
|
|
85
|
+
nx=nx, ny=ny,
|
|
86
|
+
epsg_in=epsg_in, epsg_out=epsg_out,
|
|
87
|
+
geoid_in=geoid_in, geoid_out=geoid_out,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
logger.info(f"Generating shift grid: {self.src_datum} -> {self.dst_datum}...")
|
|
91
|
+
shift_array, _ = vt._vertical_transform(vt.epsg_in, vt.epsg_out)
|
|
92
|
+
|
|
93
|
+
if shift_array is None:
|
|
94
|
+
logger.error("Transformation failed (No coverage or invalid datums).")
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
GridWriter.write(self.dst_fn, shift_array, self.region)
|
|
98
|
+
|
|
99
|
+
self.add_entry_to_results(
|
|
100
|
+
url=f"file://{self.dst_fn}",
|
|
101
|
+
dst_fn=self.dst_fn,
|
|
102
|
+
data_type="gtiff",
|
|
103
|
+
meta={
|
|
104
|
+
"src_datum": self.src_datum,
|
|
105
|
+
"dst_datum": self.dst_datum,
|
|
106
|
+
"generator": "transformez"
|
|
107
|
+
}
|
|
108
|
+
)
|
|
@@ -70,6 +70,7 @@ class VerticalTransform:
|
|
|
70
70
|
|
|
71
71
|
def _get_native_ellipsoid(self, epsg, ref_type):
|
|
72
72
|
"""Helper to identify the native frame of a datum."""
|
|
73
|
+
|
|
73
74
|
if ref_type == 'surface':
|
|
74
75
|
# NOAA VDatum = NAD83, Global = WGS84
|
|
75
76
|
region = Datums.SURFACES[epsg].get('region')
|
|
@@ -80,7 +81,7 @@ class VerticalTransform:
|
|
|
80
81
|
elif ref_type == 'htdp':
|
|
81
82
|
# If it's a Frame, it is its own native ellipsoid
|
|
82
83
|
return epsg
|
|
83
|
-
return WGS84_EPSG # Default
|
|
84
|
+
return WGS84_EPSG # Default
|
|
84
85
|
|
|
85
86
|
def fetch_grid(self, module_name, **kwargs):
|
|
86
87
|
"""Generic fetcher wrapper."""
|
|
@@ -158,34 +159,58 @@ class VerticalTransform:
|
|
|
158
159
|
logger.error(f" [HTDP] Failed: {e}")
|
|
159
160
|
return np.zeros((self.ny, self.nx))
|
|
160
161
|
|
|
162
|
+
def _fetch_geoid_with_fallback(self, target_geoid):
|
|
163
|
+
"""Fetches a geoid grid. If the primary geoid lacks coverage (e.g., GEOID18 in AK),
|
|
164
|
+
it automatically falls back to older, compatible models.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
# Ordered list of preferred US geoids (Newest to Oldest)
|
|
168
|
+
us_geoids = ['g2018', 'g2012b', 'geoid09']
|
|
169
|
+
|
|
170
|
+
if target_geoid in us_geoids:
|
|
171
|
+
start_idx = us_geoids.index(target_geoid)
|
|
172
|
+
geoids_to_try = us_geoids[start_idx:]
|
|
173
|
+
else:
|
|
174
|
+
geoids_to_try = [target_geoid]
|
|
175
|
+
|
|
176
|
+
for g in geoids_to_try:
|
|
177
|
+
geoid_def = Datums.GEOIDS.get(g, {})
|
|
178
|
+
provider = geoid_def.get('provider', 'proj')
|
|
179
|
+
grid = self._get_grid(provider, g)
|
|
180
|
+
|
|
181
|
+
if np.any(grid):
|
|
182
|
+
if g != target_geoid and self.verbose:
|
|
183
|
+
logger.info(f" [Geoid Fallback] '{target_geoid}' lacks coverage here. Falling back to '{g}'.")
|
|
184
|
+
return grid, g
|
|
185
|
+
|
|
186
|
+
return np.zeros((self.ny, self.nx)), target_geoid
|
|
187
|
+
|
|
161
188
|
# =========================================================================
|
|
162
189
|
# Chains
|
|
163
190
|
# =========================================================================
|
|
164
191
|
def _get_vdatum_chain(self, datum_name, geoid_name):
|
|
165
192
|
"""Builds shift: Tidal -> [NAD83 Native]."""
|
|
166
|
-
|
|
167
193
|
total_shift = np.zeros((self.ny, self.nx))
|
|
168
194
|
desc = []
|
|
169
195
|
|
|
170
196
|
# Tidal -> LMSL
|
|
171
|
-
if datum_name not in [
|
|
172
|
-
grid = self._get_grid(
|
|
173
|
-
if not np.any(grid):
|
|
174
|
-
return None, f"Missing Tidal Grid: {datum_name}"
|
|
175
|
-
|
|
197
|
+
if datum_name not in ['msl', '5714', 'lmsl']:
|
|
198
|
+
grid = self._get_grid('vdatum', datum_name)
|
|
199
|
+
if not np.any(grid): return None, f"Missing Tidal Grid: {datum_name}"
|
|
176
200
|
total_shift += grid
|
|
177
201
|
desc.append(f"({datum_name}->LMSL)")
|
|
178
202
|
|
|
179
203
|
# LMSL -> Ortho (TSS)
|
|
180
|
-
tss = self._get_grid(
|
|
204
|
+
tss = self._get_grid('vdatum', 'tss')
|
|
181
205
|
total_shift += tss
|
|
182
206
|
desc.append("TSS(LMSL->NAVD88)")
|
|
183
207
|
|
|
184
|
-
# Ortho -> NAD83 (Geoid)
|
|
185
|
-
actual_geoid = geoid_name if geoid_name else
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
208
|
+
# Ortho -> NAD83 (Smart Geoid Fallback)
|
|
209
|
+
actual_geoid = geoid_name if geoid_name else 'g2018'
|
|
210
|
+
geoid_grid, used_geoid = self._fetch_geoid_with_fallback(actual_geoid)
|
|
211
|
+
|
|
212
|
+
total_shift += geoid_grid
|
|
213
|
+
desc.append(f"Geoid({used_geoid}->NAD83)")
|
|
189
214
|
|
|
190
215
|
return total_shift, " + ".join(desc)
|
|
191
216
|
|
|
@@ -219,8 +244,13 @@ class VerticalTransform:
|
|
|
219
244
|
mss_name = model_def["grids"].get("mss")
|
|
220
245
|
if mss_name:
|
|
221
246
|
mss_grid = self._get_grid(provider, mss_name)
|
|
247
|
+
if provider == 'seanoe' or 'fes' in model.lower():
|
|
248
|
+
mss_grid -= 0.70
|
|
249
|
+
desc.append("MSS->WGS84(TP_Corr)")
|
|
250
|
+
else:
|
|
251
|
+
desc.append("MSS->Ellipsoid")
|
|
252
|
+
|
|
222
253
|
total_shift += mss_grid
|
|
223
|
-
desc.append("MSS->Ellipsoid")
|
|
224
254
|
|
|
225
255
|
if not desc:
|
|
226
256
|
return total_shift, "Global Chain (Empty)"
|
|
@@ -232,50 +262,38 @@ class VerticalTransform:
|
|
|
232
262
|
# =========================================================================
|
|
233
263
|
def _step_to_hub(self, epsg, ref_type, geoid=None, epoch=None):
|
|
234
264
|
shift = np.zeros((self.ny, self.nx))
|
|
235
|
-
if epsg == self.hub_epsg:
|
|
236
|
-
return shift, "Already at Hub"
|
|
265
|
+
if epsg == self.hub_epsg: return shift, "Already at Hub"
|
|
237
266
|
|
|
238
|
-
# Determine the Native Ellipsoid of this specific Input
|
|
239
267
|
native_epsg = self._get_native_ellipsoid(epsg, ref_type)
|
|
240
|
-
|
|
241
|
-
# Calculate Input -> Native
|
|
242
268
|
chain_shift = None
|
|
243
269
|
chain_desc = ""
|
|
244
270
|
|
|
245
|
-
if ref_type ==
|
|
246
|
-
datum_name = Datums.SURFACES[epsg][
|
|
247
|
-
region_tag = Datums.SURFACES[epsg].get(
|
|
271
|
+
if ref_type == 'surface':
|
|
272
|
+
datum_name = Datums.SURFACES[epsg]['name']
|
|
273
|
+
region_tag = Datums.SURFACES[epsg].get('region')
|
|
248
274
|
|
|
249
|
-
if region_tag ==
|
|
275
|
+
if region_tag == 'usa':
|
|
250
276
|
s, d = self._get_vdatum_chain(datum_name, geoid)
|
|
251
|
-
# Fallback
|
|
252
277
|
if s is None:
|
|
253
|
-
# Switch to Global Chain (Native=WGS84)
|
|
254
278
|
native_epsg = WGS84_EPSG
|
|
255
279
|
proxy_name = Datums.get_global_proxy(datum_name)
|
|
256
280
|
if proxy_name:
|
|
257
|
-
s, d = self._get_global_chain(proxy_name, model=
|
|
258
|
-
d = f"Global({proxy_name}) [Proxy] -> WGS84"
|
|
259
|
-
|
|
281
|
+
s, d = self._get_global_chain(proxy_name, model='fes2014')
|
|
282
|
+
# d = f"Global({proxy_name}) [Proxy] -> WGS84"
|
|
260
283
|
chain_shift, chain_desc = s, d
|
|
261
284
|
|
|
262
|
-
elif region_tag ==
|
|
285
|
+
elif region_tag == 'global':
|
|
263
286
|
chain_shift, chain_desc = self._get_global_chain(datum_name)
|
|
264
287
|
|
|
265
|
-
elif ref_type ==
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
provider = geoid_def.get("provider", "proj")
|
|
270
|
-
chain_shift = self._get_grid(provider, target_geoid)
|
|
271
|
-
chain_desc = f"Ortho(via {target_geoid}) -> Frame({native_epsg})"
|
|
288
|
+
elif ref_type == 'cdn':
|
|
289
|
+
target_geoid = geoid if geoid else 'g2018'
|
|
290
|
+
chain_shift, used_geoid = self._fetch_geoid_with_fallback(target_geoid)
|
|
291
|
+
chain_desc = f"Ortho(via {used_geoid}) -> Frame({native_epsg})"
|
|
272
292
|
|
|
273
|
-
elif ref_type ==
|
|
274
|
-
# Frame is already Native
|
|
293
|
+
elif ref_type == 'htdp':
|
|
275
294
|
chain_shift = np.zeros((self.ny, self.nx))
|
|
276
295
|
chain_desc = f"Frame({epsg})"
|
|
277
296
|
|
|
278
|
-
# --- Native -> Hub ---
|
|
279
297
|
if chain_shift is not None:
|
|
280
298
|
if native_epsg != self.hub_epsg:
|
|
281
299
|
htdp_shift = self._get_htdp_shift(native_epsg, self.hub_epsg, epoch, self.epoch_out)
|
|
@@ -287,12 +305,9 @@ class VerticalTransform:
|
|
|
287
305
|
|
|
288
306
|
def _step_from_hub(self, epsg, ref_type, geoid=None, epoch=None):
|
|
289
307
|
shift = np.zeros((self.ny, self.nx))
|
|
290
|
-
if epsg == self.hub_epsg:
|
|
291
|
-
return shift, "Remain at Hub"
|
|
308
|
+
if epsg == self.hub_epsg: return shift, "Remain at Hub"
|
|
292
309
|
|
|
293
310
|
native_epsg = self._get_native_ellipsoid(epsg, ref_type)
|
|
294
|
-
|
|
295
|
-
# --- Hub -> Native ---
|
|
296
311
|
total_out = np.zeros((self.ny, self.nx))
|
|
297
312
|
desc_parts = []
|
|
298
313
|
|
|
@@ -301,26 +316,25 @@ class VerticalTransform:
|
|
|
301
316
|
total_out += htdp_shift
|
|
302
317
|
desc_parts.append(f"Hub({self.hub_epsg}->{native_epsg})")
|
|
303
318
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
chain_geoid = geoid if geoid else "g2018"
|
|
319
|
+
if ref_type == 'surface':
|
|
320
|
+
datum_name = Datums.SURFACES[epsg]['name']
|
|
321
|
+
chain_geoid = geoid if geoid else 'g2018'
|
|
308
322
|
s, d = self._get_vdatum_chain(datum_name, chain_geoid)
|
|
309
323
|
if s is None:
|
|
310
324
|
return np.zeros((self.ny, self.nx)), "FAILED Output Chain"
|
|
311
325
|
|
|
312
|
-
# Subtract because Chain is Input->Native
|
|
313
326
|
total_out -= s
|
|
314
327
|
desc_parts.append(f"Native -> VDatum({datum_name})")
|
|
315
328
|
|
|
316
|
-
elif ref_type ==
|
|
317
|
-
target_geoid = geoid if geoid else
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
329
|
+
elif ref_type == 'cdn':
|
|
330
|
+
target_geoid = geoid if geoid else 'g2018'
|
|
331
|
+
geoid_grid, used_geoid = self._fetch_geoid_with_fallback(target_geoid)
|
|
332
|
+
|
|
333
|
+
if not np.any(geoid_grid):
|
|
334
|
+
logger.warning(f"Geoid {target_geoid} (and fallbacks) not found/covered.")
|
|
321
335
|
|
|
322
336
|
total_out -= geoid_grid
|
|
323
|
-
desc_parts.append(f"Native -> Ortho(via {
|
|
337
|
+
desc_parts.append(f"Native -> Ortho(via {used_geoid})")
|
|
324
338
|
|
|
325
339
|
return total_out, " + ".join(desc_parts)
|
|
326
340
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
transformez.utils
|
|
6
|
+
~~~~~~~~~~~~~
|
|
7
|
+
|
|
8
|
+
This holds various utility functions.
|
|
9
|
+
|
|
10
|
+
:copyright: (c) 2010-2026 Regents of the University of Colorado
|
|
11
|
+
:license: MIT, see LICENSE for more details.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import subprocess
|
|
16
|
+
import logging
|
|
17
|
+
import numpy as np
|
|
18
|
+
import rasterio
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
cmd_exists = lambda x: any(os.access(os.path.join(path, x), os.X_OK)
|
|
22
|
+
for path in os.environ['PATH'].split(os.pathsep))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def run_cmd(args):
|
|
26
|
+
"""Standalone replacement for utils.run_cmd using subprocess."""
|
|
27
|
+
|
|
28
|
+
logger.info(f"Running: {' '.join(args) if isinstance(args, list) else args}")
|
|
29
|
+
|
|
30
|
+
result = subprocess.run(
|
|
31
|
+
args,
|
|
32
|
+
shell=False if isinstance(args, list) else True,
|
|
33
|
+
capture_output=True,
|
|
34
|
+
text=True
|
|
35
|
+
)
|
|
36
|
+
return result.stdout, result.returncode
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def cmd_check(cmd_str, cmd_vers_str):
|
|
40
|
+
"""check system for availability of 'cmd_str'"""
|
|
41
|
+
|
|
42
|
+
if cmd_exists(cmd_str):
|
|
43
|
+
cmd_vers, status = run_cmd(f"{cmd_vers_str}")
|
|
44
|
+
return cmd_vers.rstrip()
|
|
45
|
+
return b"0"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class RasterQuery:
|
|
49
|
+
"""Raster query for point clouds.
|
|
50
|
+
Pre-loads raster data and inverse transform to rapidly query (X, Y) arrays.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self, filename, default_nodata=0.0):
|
|
54
|
+
if not filename or not os.path.exists(filename):
|
|
55
|
+
raise FileNotFoundError(f"Raster not found: {filename}")
|
|
56
|
+
|
|
57
|
+
self.default_nodata = default_nodata
|
|
58
|
+
|
|
59
|
+
with rasterio.open(filename) as src:
|
|
60
|
+
self.data = src.read(1)
|
|
61
|
+
self.transform = src.transform
|
|
62
|
+
self.inv_transform = ~src.transform
|
|
63
|
+
self.bounds = src.bounds
|
|
64
|
+
self.width = src.width
|
|
65
|
+
self.height = src.height
|
|
66
|
+
|
|
67
|
+
if src.nodata is not None:
|
|
68
|
+
self.data[self.data == src.nodata] = self.default_nodata
|
|
69
|
+
self.data = np.nan_to_num(self.data, nan=self.default_nodata)
|
|
70
|
+
|
|
71
|
+
def query(self, x, y):
|
|
72
|
+
"""query the raster at given X, Y numpy arrays using pure vectorized affine math."""
|
|
73
|
+
|
|
74
|
+
q_x = np.asarray(x).copy()
|
|
75
|
+
q_y = np.asarray(y)
|
|
76
|
+
|
|
77
|
+
if self.bounds.left < 0 and np.any(q_x > 180):
|
|
78
|
+
q_x = np.where(q_x > 180, q_x - 360, q_x)
|
|
79
|
+
elif self.bounds.left >= 0 and np.any(q_x < 0):
|
|
80
|
+
q_x = np.where(q_x < 0, q_x + 360, q_x)
|
|
81
|
+
|
|
82
|
+
cols_f, rows_f = self.inv_transform * (q_x, q_y)
|
|
83
|
+
|
|
84
|
+
cols = np.floor(cols_f).astype(int)
|
|
85
|
+
rows = np.floor(rows_f).astype(int)
|
|
86
|
+
|
|
87
|
+
valid = (
|
|
88
|
+
(rows >= 0) & (rows < self.height) &
|
|
89
|
+
(cols >= 0) & (cols < self.width)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
results = np.full_like(q_x, self.default_nodata, dtype=self.data.dtype)
|
|
93
|
+
if np.any(valid):
|
|
94
|
+
results[valid] = self.data[rows[valid], cols[valid]]
|
|
95
|
+
|
|
96
|
+
return results
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
# CMD will run Transformez
|
|
5
|
+
CMD = [sys.executable, "-m", "transformez.cli"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def run_transformez(args):
|
|
9
|
+
"""Run transformez and return result."""
|
|
10
|
+
|
|
11
|
+
return subprocess.run(CMD + args, capture_output=True, text=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_help():
|
|
15
|
+
"""Does the help menu work?"""
|
|
16
|
+
|
|
17
|
+
result = run_transformez(["--help"])
|
|
18
|
+
assert result.returncode == 0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_version():
|
|
22
|
+
"""Does version print?"""
|
|
23
|
+
|
|
24
|
+
result = run_transformez(["--version"])
|
|
25
|
+
assert result.returncode == 0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_list_modules():
|
|
29
|
+
"""Can we list datums without crashing?"""
|
|
30
|
+
|
|
31
|
+
result = run_transformez(["--list-datums"])
|
|
32
|
+
assert result.returncode == 0
|
|
33
|
+
assert "lat" in result.stdout
|
|
34
|
+
assert "mllw" in result.stdout
|
|
35
|
+
assert "NAVD88" in result.stdout
|
|
36
|
+
assert "g2018" in result.stdout
|
transformez-0.2.0/PKG-INFO
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: transformez
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: A standalone utility for vertical elevation datum transformations.
|
|
5
|
-
Author: Matthew Love
|
|
6
|
-
License: MIT License
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2010-2026 Regents of the University of Colorado
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
|
18
|
-
copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
-
SOFTWARE.
|
|
27
|
-
Project-URL: Homepage, https://github.com/ciresdem/transformez
|
|
28
|
-
Project-URL: Issues, https://github.com/ciresdem/transformez/issues
|
|
29
|
-
Keywords: Geospatial
|
|
30
|
-
Classifier: Programming Language :: Python :: 3
|
|
31
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
-
Classifier: Operating System :: OS Independent
|
|
33
|
-
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
34
|
-
Description-Content-Type: text/markdown
|
|
35
|
-
License-File: LICENSE
|
|
36
|
-
License-File: AUTHORS.md
|
|
37
|
-
Requires-Dist: numpy<2.0.0
|
|
38
|
-
Requires-Dist: pyproj
|
|
39
|
-
Requires-Dist: rasterio
|
|
40
|
-
Requires-Dist: fetchez>0.3.3
|
|
41
|
-
Dynamic: license-file
|
|
42
|
-
|
|
43
|
-
# 🌍 Transformez ↕
|
|
44
|
-
|
|
45
|
-
**Global vertical datum transformations, simplified.**
|
|
46
|
-
|
|
47
|
-
*Transformez Les Données*
|
|
48
|
-
|
|
49
|
-
> 🚀 **v0.2.0:** Now supporting global tidal transformations via FES2014 & SEANOE.
|
|
50
|
-
|
|
51
|
-
**Transformez** is a standalone Python engine for converting geospatial data between vertical datums (e.g., `MLLW` ↔ `NAVD88` ↔ `Ellipsoid`).
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## Installation
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
pip install transformez
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
*Requires [htdp](https://geodesy.noaa.gov/TOOLS/Htdp/Htdp.shtml) to be in your system PATH for frame transformations.*
|
|
62
|
-
|
|
63
|
-
## Usage
|
|
64
|
-
|
|
65
|
-
**Generate a vertical shift grid for anywhere on Earth.**
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
# Transform MLLW to WGS84 Ellipsoid in Norton Sound, AK
|
|
69
|
-
# (Where NOAA has no coverage!)
|
|
70
|
-
transformez -R -166/-164/63/64 -E 3s \
|
|
71
|
-
--input-datum mllw \
|
|
72
|
-
--output-datum 4979 \
|
|
73
|
-
--output shift_ak.tif
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
**Transform a raster directly.** Transformez reads the bounds/resolution from the file.
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
transformez --dem input_bathymetry.tif \
|
|
80
|
-
--input-datum "mllw" \
|
|
81
|
-
--output-datum "5703:geoid=geoid12b" \
|
|
82
|
-
--output output_navd88.tif
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
**Integrate directly into your download pipeline.**
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
# Download GEBCO and shift EGM96 to WGS84 on the fly
|
|
89
|
-
fetchez gebco ... --hook transformez:datum_in=5773,datum_out=4979
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Python API
|
|
93
|
-
|
|
94
|
-
```python
|
|
95
|
-
from transformez.transform import VerticalTransform
|
|
96
|
-
from fetchez.spatial import Region
|
|
97
|
-
|
|
98
|
-
# Define a region in India (Bay of Bengal)
|
|
99
|
-
region = Region(80, 85, 10, 15)
|
|
100
|
-
|
|
101
|
-
# Initialize Transformer
|
|
102
|
-
# Requesting "MLLW" in India triggers the Global Fallback automatically
|
|
103
|
-
vt = VerticalTransform(
|
|
104
|
-
region=region,
|
|
105
|
-
nx=1000, ny=1000,
|
|
106
|
-
epsg_in="mllw", # Will resolve to FES2014 LAT
|
|
107
|
-
epsg_out="epsg:4979" # WGS84 Ellipsoid
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
# Generate Shift
|
|
111
|
-
shift, unc = vt._vertical_transform(vt.epsg_in, vt.epsg_out)
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
## Supported Datums
|
|
115
|
-
|
|
116
|
-
* **Tidal**: mllw, mhhw, msl, lat
|
|
117
|
-
|
|
118
|
-
* **Ellipsoidal**: 4979 (WGS84), 6319 (NAD83 2011)
|
|
119
|
-
|
|
120
|
-
* **Orthometric**: 5703 (NAVD88), egm2008, egm96
|
|
121
|
-
|
|
122
|
-
* **Geoids**: g2018, g2012b, geoid09, xgeoid20b
|
|
123
|
-
|
|
124
|
-
## License
|
|
125
|
-
|
|
126
|
-
This project is licensed under the MIT License - see the [LICENSE](https://github.com/ciresdem/transformez/blob/main/LICENSE) file for details.
|
|
127
|
-
Copyright (c) 2010-2026 Regents of the University of Colorado
|
transformez-0.2.0/pyproject.toml
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ['setuptools>=61.0']
|
|
3
|
-
build-backend = 'setuptools.build_meta'
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = 'transformez'
|
|
7
|
-
version = '0.2.0'
|
|
8
|
-
description = 'A standalone utility for vertical elevation datum transformations.'
|
|
9
|
-
readme = "README.md"
|
|
10
|
-
authors = [{ name = 'Matthew Love' }]
|
|
11
|
-
license = { file = 'LICENSE' }
|
|
12
|
-
classifiers = [
|
|
13
|
-
'Programming Language :: Python :: 3',
|
|
14
|
-
'License :: OSI Approved :: MIT License',
|
|
15
|
-
'Operating System :: OS Independent',
|
|
16
|
-
'Topic :: Scientific/Engineering :: GIS',
|
|
17
|
-
]
|
|
18
|
-
dependencies = [
|
|
19
|
-
'numpy<2.0.0',
|
|
20
|
-
'pyproj',
|
|
21
|
-
'rasterio',
|
|
22
|
-
'fetchez>0.3.3',
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
keywords = ["Geospatial"]
|
|
26
|
-
|
|
27
|
-
[project.entry-points."fetchez.plugins"]
|
|
28
|
-
transformez = "transformez"
|
|
29
|
-
|
|
30
|
-
[project.scripts]
|
|
31
|
-
transformez = 'transformez.cli:transformez_cli'
|
|
32
|
-
|
|
33
|
-
[project.urls]
|
|
34
|
-
Homepage = "https://github.com/ciresdem/transformez"
|
|
35
|
-
Issues = "https://github.com/ciresdem/transformez/issues"
|
|
36
|
-
|
|
37
|
-
[tool.setuptools.packages.find]
|
|
38
|
-
where = ["src"] # Look for packages inside the 'src' folder
|
|
39
|
-
include = ["transformez*"] # Include the package named 'transformez'
|
|
40
|
-
exclude = ["tests*", "docs*", "scripts*", "examples*"]
|
|
41
|
-
namespaces = false
|
transformez-0.2.0/setup.cfg
DELETED
transformez-0.2.0/setup.py
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
|
|
4
|
-
"""
|
|
5
|
-
transformez
|
|
6
|
-
~~~~~~~~~~~~~
|
|
7
|
-
|
|
8
|
-
:copyright: (c) 2010-2026 Regents of the University of Colorado
|
|
9
|
-
:license: MIT, see LICENSE for more details.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
__version__ = "0.2.0"
|
|
13
|
-
__author__ = "Matthew Love"
|
|
14
|
-
__credits__ = "CIRES"
|
|
15
|
-
|
|
16
|
-
import os
|
|
17
|
-
import glob
|
|
18
|
-
from .hooks import TransformezHook
|
|
19
|
-
from fetchez.hooks.registry import HookRegistry
|
|
20
|
-
# from fetchez.registry import FetchezRegistry
|
|
21
|
-
|
|
22
|
-
def _find_proj_lib():
|
|
23
|
-
"""Locate the best available PROJ_LIB path."""
|
|
24
|
-
|
|
25
|
-
try:
|
|
26
|
-
import rasterio
|
|
27
|
-
r_path = os.path.join(os.path.dirname(rasterio.__file__), "proj_data")
|
|
28
|
-
if os.path.exists(os.path.join(r_path, "proj.db")):
|
|
29
|
-
return r_path
|
|
30
|
-
|
|
31
|
-
parent = os.path.dirname(os.path.dirname(rasterio.__file__))
|
|
32
|
-
libs = glob.glob(os.path.join(parent, "rasterio.libs*"))
|
|
33
|
-
if libs:
|
|
34
|
-
for root, _, files in os.walk(libs[0]):
|
|
35
|
-
if "proj.db" in files:
|
|
36
|
-
return root
|
|
37
|
-
except ImportError:
|
|
38
|
-
pass
|
|
39
|
-
|
|
40
|
-
try:
|
|
41
|
-
import pyproj
|
|
42
|
-
p_path = pyproj.datadir.get_data_dir()
|
|
43
|
-
if os.path.exists(os.path.join(p_path, "proj.db")):
|
|
44
|
-
return p_path
|
|
45
|
-
except ImportError:
|
|
46
|
-
pass
|
|
47
|
-
|
|
48
|
-
return None
|
|
49
|
-
|
|
50
|
-
target_proj_lib = _find_proj_lib()
|
|
51
|
-
|
|
52
|
-
if "PROJ_LIB" in os.environ:
|
|
53
|
-
del os.environ["PROJ_LIB"]
|
|
54
|
-
|
|
55
|
-
if target_proj_lib:
|
|
56
|
-
os.environ["PROJ_LIB"] = target_proj_lib
|
|
57
|
-
|
|
58
|
-
def setup_fetchez(registry_cls):
|
|
59
|
-
"""Called by fetchez when loading plugins.
|
|
60
|
-
|
|
61
|
-
Registers modules, hooks, and presets.
|
|
62
|
-
"""
|
|
63
|
-
|
|
64
|
-
HookRegistry.register_hook(TransformezHook)
|
|
65
|
-
|
|
66
|
-
from fetchez.presets import register_global_preset
|
|
67
|
-
register_global_preset(
|
|
68
|
-
name="make-shift-grid",
|
|
69
|
-
help_text="Download datum grids and composite them into a single shift grid.",
|
|
70
|
-
hooks=[
|
|
71
|
-
{"name": "transformez", "args": {}}
|
|
72
|
-
]
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
# setup_fetchez(FetchezRegistry)
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
|
|
4
|
-
"""
|
|
5
|
-
transformez.utils
|
|
6
|
-
~~~~~~~~~~~~~
|
|
7
|
-
|
|
8
|
-
This holds various utility functions.
|
|
9
|
-
|
|
10
|
-
:copyright: (c) 2010-2026 Regents of the University of Colorado
|
|
11
|
-
:license: MIT, see LICENSE for more details.
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
import os
|
|
15
|
-
import subprocess
|
|
16
|
-
import logging
|
|
17
|
-
|
|
18
|
-
logger = logging.getLogger(__name__)
|
|
19
|
-
cmd_exists = lambda x: any(os.access(os.path.join(path, x), os.X_OK)
|
|
20
|
-
for path in os.environ['PATH'].split(os.pathsep))
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def run_cmd(args):
|
|
24
|
-
"""Standalone replacement for utils.run_cmd using subprocess."""
|
|
25
|
-
|
|
26
|
-
logger.info(f"Running: {' '.join(args) if isinstance(args, list) else args}")
|
|
27
|
-
|
|
28
|
-
result = subprocess.run(
|
|
29
|
-
args,
|
|
30
|
-
shell=False if isinstance(args, list) else True,
|
|
31
|
-
capture_output=True,
|
|
32
|
-
text=True
|
|
33
|
-
)
|
|
34
|
-
return result.stdout, result.returncode
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def cmd_check(cmd_str, cmd_vers_str):
|
|
38
|
-
"""check system for availability of 'cmd_str'"""
|
|
39
|
-
|
|
40
|
-
if cmd_exists(cmd_str):
|
|
41
|
-
cmd_vers, status = run_cmd(f"{cmd_vers_str}")
|
|
42
|
-
return cmd_vers.rstrip()
|
|
43
|
-
return b"0"
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
AUTHORS.md
|
|
2
|
-
LICENSE
|
|
3
|
-
README.md
|
|
4
|
-
pyproject.toml
|
|
5
|
-
setup.py
|
|
6
|
-
src/transformez/__init__.py
|
|
7
|
-
src/transformez/cli.py
|
|
8
|
-
src/transformez/definitions.py
|
|
9
|
-
src/transformez/grid_engine.py
|
|
10
|
-
src/transformez/hooks.py
|
|
11
|
-
src/transformez/htdp.py
|
|
12
|
-
src/transformez/spatial.py
|
|
13
|
-
src/transformez/srs.py
|
|
14
|
-
src/transformez/transform.py
|
|
15
|
-
src/transformez/utils.py
|
|
16
|
-
src/transformez/vdatum.py
|
|
17
|
-
src/transformez.egg-info/PKG-INFO
|
|
18
|
-
src/transformez.egg-info/SOURCES.txt
|
|
19
|
-
src/transformez.egg-info/dependency_links.txt
|
|
20
|
-
src/transformez.egg-info/entry_points.txt
|
|
21
|
-
src/transformez.egg-info/requires.txt
|
|
22
|
-
src/transformez.egg-info/top_level.txt
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
transformez
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|