transformez 0.1.0__py3-none-any.whl
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/__init__.py +125 -0
- transformez/cli.py +228 -0
- transformez/definitions.py +187 -0
- transformez/grid_engine.py +299 -0
- transformez/hooks.py +140 -0
- transformez/htdp.py +282 -0
- transformez/spatial.py +319 -0
- transformez/srs.py +169 -0
- transformez/transform.py +278 -0
- transformez/utils.py +43 -0
- transformez/vdatum.py +131 -0
- transformez-0.1.0.dist-info/METADATA +210 -0
- transformez-0.1.0.dist-info/RECORD +18 -0
- transformez-0.1.0.dist-info/WHEEL +5 -0
- transformez-0.1.0.dist-info/entry_points.txt +5 -0
- transformez-0.1.0.dist-info/licenses/AUTHORS.md +10 -0
- transformez-0.1.0.dist-info/licenses/LICENSE +21 -0
- transformez-0.1.0.dist-info/top_level.txt +1 -0
transformez/__init__.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
__author__ = "Matthew Love"
|
|
5
|
+
__credits__ = "CIRES"
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
# --- fix the PROJ_LIB path to work with rasterio/pyproj
|
|
10
|
+
def _find_proj_lib():
|
|
11
|
+
"""Locate the best available PROJ_LIB path."""
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import rasterio
|
|
15
|
+
# Common path in wheels: site-packages/rasterio/proj_data
|
|
16
|
+
r_path = os.path.join(os.path.dirname(rasterio.__file__), 'proj_data')
|
|
17
|
+
if os.path.exists(os.path.join(r_path, 'proj.db')):
|
|
18
|
+
return r_path
|
|
19
|
+
|
|
20
|
+
# Linux wheels often put it in .libs adjacent to the package
|
|
21
|
+
# e.g. site-packages/rasterio.libs/proj.db
|
|
22
|
+
parent = os.path.dirname(os.path.dirname(rasterio.__file__))
|
|
23
|
+
libs = glob.glob(os.path.join(parent, 'rasterio.libs*'))
|
|
24
|
+
if libs:
|
|
25
|
+
# Look inside the libs folder
|
|
26
|
+
for root, _, files in os.walk(libs[0]):
|
|
27
|
+
if 'proj.db' in files:
|
|
28
|
+
return root
|
|
29
|
+
except ImportError:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
# Try PyProj's bundled data (Fallback)
|
|
33
|
+
try:
|
|
34
|
+
import pyproj
|
|
35
|
+
p_path = pyproj.datadir.get_data_dir()
|
|
36
|
+
if os.path.exists(os.path.join(p_path, 'proj.db')):
|
|
37
|
+
return p_path
|
|
38
|
+
except ImportError:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
target_proj_lib = _find_proj_lib()
|
|
44
|
+
|
|
45
|
+
# Unset conflicting system/conda vars if we found a better one
|
|
46
|
+
if 'PROJ_LIB' in os.environ:
|
|
47
|
+
del os.environ['PROJ_LIB']
|
|
48
|
+
|
|
49
|
+
if target_proj_lib:
|
|
50
|
+
os.environ['PROJ_LIB'] = target_proj_lib
|
|
51
|
+
# print(f"DEBUG: PROJ_LIB set to {target_proj_lib}")
|
|
52
|
+
|
|
53
|
+
# # --- PROJ_LIB Fix (Run before any geo-imports) ---
|
|
54
|
+
# # This prevents "PROJ: proj_create_from_database: Cannot find proj.db" errors
|
|
55
|
+
# # when conflicting Conda/System PROJ installations exist.
|
|
56
|
+
# try:
|
|
57
|
+
# # 1. We must import pyproj first to let it find its own bundled data
|
|
58
|
+
# import os
|
|
59
|
+
# import pyproj
|
|
60
|
+
|
|
61
|
+
# # 2. Get the valid data directory from the wheel
|
|
62
|
+
# proj_lib = pyproj.datadir.get_data_dir()
|
|
63
|
+
# #print(proj_lib)
|
|
64
|
+
# # 3. Force the environment variable to use this valid path
|
|
65
|
+
# # (Overriding any bad global/Conda defaults)
|
|
66
|
+
# os.environ['PROJ_LIB'] = proj_lib
|
|
67
|
+
|
|
68
|
+
# except ImportError:
|
|
69
|
+
# # If pyproj isn't installed yet (e.g. during pip install), skip this.
|
|
70
|
+
# pass
|
|
71
|
+
|
|
72
|
+
# --- End PROJ_LIB Fix ---
|
|
73
|
+
|
|
74
|
+
from .hooks import TransformezHook
|
|
75
|
+
#from .modules import DatumGridFetcher
|
|
76
|
+
from fetchez.hooks.registry import HookRegistry
|
|
77
|
+
# form fetchez.registry import FetchezRegistry # Implicitly handled via setup_fetchez passing cls
|
|
78
|
+
|
|
79
|
+
def setup_fetchez(registry_cls):
|
|
80
|
+
"""Called by fetchez when loading plugins.
|
|
81
|
+
Registers modules, hooks, and presets.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
# module should gather necessary grids to do the transformation
|
|
85
|
+
# Register the Module with Fetchez
|
|
86
|
+
# registry_cls.register_module(
|
|
87
|
+
# mod_key='datum_grids',
|
|
88
|
+
# mod_cls=DatumGridFetcher,
|
|
89
|
+
# metadata={
|
|
90
|
+
# 'desc': 'Fetch NOAA VDatum and PROJ-CDN grids for a region',
|
|
91
|
+
# 'tags': ['vdatum', 'geoid', 'transformation']
|
|
92
|
+
# }
|
|
93
|
+
# )
|
|
94
|
+
|
|
95
|
+
# Register Fetchez Hooks
|
|
96
|
+
HookRegistry.register_hook(TransformezHook)
|
|
97
|
+
|
|
98
|
+
# Register Global Presets
|
|
99
|
+
from fetchez.presets import register_global_preset
|
|
100
|
+
|
|
101
|
+
register_global_preset(
|
|
102
|
+
name="make-shift-grid",
|
|
103
|
+
help_text="Download datum grids and composite them into a single GTX shift grid.",
|
|
104
|
+
hooks=[
|
|
105
|
+
{"name": "transformez", "args": {}}
|
|
106
|
+
]
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# "transform-pipeline": {
|
|
111
|
+
# "help_text": "Generate shift grid based on region, then apply it to files.",
|
|
112
|
+
# "hooks": [
|
|
113
|
+
# {
|
|
114
|
+
# "name": "transformez",
|
|
115
|
+
# "args": {"stage": "pre", "datum_in": "5703", "output_grid": "/tmp/shift.gtx"}
|
|
116
|
+
# },
|
|
117
|
+
# {
|
|
118
|
+
# "name": "transformez",
|
|
119
|
+
# "args": {"stage": "file", "apply": "True", "output_grid": "/tmp/shift.gtx"}
|
|
120
|
+
# },
|
|
121
|
+
# {
|
|
122
|
+
# "name": "audit"
|
|
123
|
+
# }
|
|
124
|
+
# ]
|
|
125
|
+
# }
|
transformez/cli.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
transformez.cli
|
|
6
|
+
~~~~~~~~~~~~~
|
|
7
|
+
|
|
8
|
+
The transformez CLI.
|
|
9
|
+
Generates vertical transformation grids using the Fetchez-DLIM ecosystem.
|
|
10
|
+
|
|
11
|
+
:copyright: (c) 2010-2026 Regents of the University of Colorado
|
|
12
|
+
:license: MIT, see LICENSE for more details.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
import argparse
|
|
18
|
+
import logging
|
|
19
|
+
|
|
20
|
+
import pyproj
|
|
21
|
+
import rasterio
|
|
22
|
+
|
|
23
|
+
from . import __version__
|
|
24
|
+
from .transform import VerticalTransform
|
|
25
|
+
from .definitions import Datums
|
|
26
|
+
from .grid_engine import plot_grid, GridWriter, GridEngine
|
|
27
|
+
|
|
28
|
+
import fetchez
|
|
29
|
+
from fetchez import spatial
|
|
30
|
+
from fetchez import utils
|
|
31
|
+
from fetchez.spatial import parse_region, Region
|
|
32
|
+
|
|
33
|
+
logging.basicConfig(level=logging.INFO, format='[ %(levelname)s ] %(name)s: %(message)s', stream=sys.stderr)
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
logging.getLogger('fetchez').setLevel(logging.WARNING)
|
|
37
|
+
|
|
38
|
+
def parse_compound_datum(datum_arg):
|
|
39
|
+
"""Parse a datum string that might contain a geoid override.
|
|
40
|
+
Format: "EPSG" or "EPSG:GEOID" or "NAME:GEOID"
|
|
41
|
+
"""
|
|
42
|
+
if ':' in str(datum_arg):
|
|
43
|
+
parts = str(datum_arg).split(':')
|
|
44
|
+
datum = Datums.get_vdatum_by_name(parts[0])
|
|
45
|
+
geoid = parts[1]
|
|
46
|
+
return datum, geoid
|
|
47
|
+
else:
|
|
48
|
+
return Datums.get_vdatum_by_name(datum_arg), None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_grid_info(filename):
|
|
52
|
+
"""Extract region, resolution, and SRS from a raster using Rasterio."""
|
|
53
|
+
|
|
54
|
+
with rasterio.open(filename) as ds:
|
|
55
|
+
bounds = ds.bounds # left, bottom, right, top
|
|
56
|
+
width = ds.width
|
|
57
|
+
height = ds.height
|
|
58
|
+
gt = ds.transform.to_gdal() # (c, a, b, f, d, e)
|
|
59
|
+
srs_wkt = ds.crs.to_wkt() if ds.crs else None
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
'te': (bounds.left, bounds.bottom, bounds.right, bounds.top),
|
|
63
|
+
'region': (bounds.left, bounds.right, bounds.bottom, bounds.top),
|
|
64
|
+
'nx': width,
|
|
65
|
+
'ny': height,
|
|
66
|
+
'gt': gt,
|
|
67
|
+
'srs_wkt': srs_wkt
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def transformez_cli():
|
|
72
|
+
parser = argparse.ArgumentParser(
|
|
73
|
+
description=f'%(prog)s ({__version__}): Generate a vertical transformation grid',
|
|
74
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
75
|
+
epilog="CUDEM home page: <http://cudem.colorado.edu>"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
input_grp = parser.add_mutually_exclusive_group(required=True)
|
|
79
|
+
input_grp.add_argument('-R', '--region', help=spatial.region_help_msg())
|
|
80
|
+
input_grp.add_argument('--dem', help='Input DEM to transform. Automatically sets Region and Resolution.')
|
|
81
|
+
|
|
82
|
+
sel_grp = parser.add_argument_group('Geospatial Selection')
|
|
83
|
+
sel_grp.add_argument('-E', '--increment', help='Grid resolution (e.g. 0.0001 or 1s) (Required if not using --dem).')
|
|
84
|
+
|
|
85
|
+
datum_group = parser.add_argument_group('Datum Configuration')
|
|
86
|
+
datum_group.add_argument('-I', '--vdatum_in', default='5703',
|
|
87
|
+
help='Input vertical datum. Format: "EPSG" or "EPSG:GEOID" (e.g. "5703:g2012a")')
|
|
88
|
+
datum_group.add_argument('-O', '--vdatum_out', default='7662',
|
|
89
|
+
help='Output vertical datum. Format: "EPSG" or "EPSG:GEOID"')
|
|
90
|
+
datum_group.add_argument('--epoch-in', type=float, default=1997.0,
|
|
91
|
+
help='Input coordinate epoch (decimal year).')
|
|
92
|
+
datum_group.add_argument('--epoch-out', type=float, default=1997.0,
|
|
93
|
+
help='Output coordinate epoch (decimal year).')
|
|
94
|
+
|
|
95
|
+
proc_group = parser.add_argument_group('Processing Options')
|
|
96
|
+
proc_group.add_argument('--preview', action='store_true', help='Plot the transformation grid (matplotlib) before processing.')
|
|
97
|
+
proc_group.add_argument('--output', help='Output filename (default auto-generated).')
|
|
98
|
+
|
|
99
|
+
sys_group = parser.add_argument_group('System & Logging')
|
|
100
|
+
sys_group.add_argument('-D', '--cache-dir', help='Directory for storing temporary grids.')
|
|
101
|
+
sys_group.add_argument('-k', '--keep-cache', action='store_true', help='Do not delete temporary files after run.')
|
|
102
|
+
sys_group.add_argument('-l', '--list-epsg', action='store_true', help='List supported EPSG codes/names and exit.')
|
|
103
|
+
sys_group.add_argument('-q', '--quiet', action='store_true', help='Suppress log output.')
|
|
104
|
+
sys_group.add_argument('--version', action='version', version=f'%(prog)s {__version__}')
|
|
105
|
+
|
|
106
|
+
# Handle negative coordinates in arguments
|
|
107
|
+
fixed_argv = spatial.fix_argparse_region(sys.argv[1:])
|
|
108
|
+
args = parser.parse_args(fixed_argv)
|
|
109
|
+
|
|
110
|
+
if args.quiet:
|
|
111
|
+
logger.setLevel(logging.WARNING)
|
|
112
|
+
|
|
113
|
+
if args.list_epsg:
|
|
114
|
+
def _print_epsg(title, data):
|
|
115
|
+
print(f'{title}:')
|
|
116
|
+
for key, val in data.items():
|
|
117
|
+
print(f' {key}\t{val["name"]}')
|
|
118
|
+
|
|
119
|
+
_print_epsg('HTDP EPSG', Datums.HTDP)
|
|
120
|
+
_print_epsg('CDN EPSG', Datums.CDN)
|
|
121
|
+
_print_epsg('Tidal EPSG', Datums.TIDAL)
|
|
122
|
+
sys.exit(0)
|
|
123
|
+
|
|
124
|
+
cache_dir = args.cache_dir or os.path.join(os.path.expanduser('~'), '.transformez')
|
|
125
|
+
if not os.path.exists(cache_dir):
|
|
126
|
+
os.makedirs(cache_dir)
|
|
127
|
+
|
|
128
|
+
epsg_in, geoid_in = parse_compound_datum(args.vdatum_in)
|
|
129
|
+
epsg_out, geoid_out = parse_compound_datum(args.vdatum_out)
|
|
130
|
+
|
|
131
|
+
if args.dem:
|
|
132
|
+
if not os.path.exists(args.dem):
|
|
133
|
+
logger.error(f'Input DEM not found: {args.dem}')
|
|
134
|
+
sys.exit(1)
|
|
135
|
+
|
|
136
|
+
logger.info(f'Extracting grid info from DEM: {args.dem}')
|
|
137
|
+
info = get_grid_info(args.dem)
|
|
138
|
+
logger.info(f'Grid info is: {info}')
|
|
139
|
+
|
|
140
|
+
# Override region and dimensions from DEM
|
|
141
|
+
# info['te'] is (w, s, e, n)
|
|
142
|
+
region_obj = spatial.Region.from_list(info['region'])
|
|
143
|
+
nx = info['nx']
|
|
144
|
+
ny = info['ny']
|
|
145
|
+
|
|
146
|
+
if info.get('srs_wkt'):
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
# Default Output Name for DEM
|
|
150
|
+
if not args.output:
|
|
151
|
+
base, ext = os.path.splitext(args.dem)
|
|
152
|
+
dst_grid = f'{base}_trans_{epsg_out}{ext}'
|
|
153
|
+
else:
|
|
154
|
+
dst_grid = args.output
|
|
155
|
+
|
|
156
|
+
elif args.region:
|
|
157
|
+
# parse_region returns a list of Region objects
|
|
158
|
+
# we're only using one for now.
|
|
159
|
+
these_regions = parse_region(args.region)
|
|
160
|
+
region_obj = these_regions[0]
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
if '/' in args.increment:
|
|
164
|
+
inc_x, inc_y = [float(x) for x in args.increment.split('/')]
|
|
165
|
+
else:
|
|
166
|
+
inc_val = utils.str2inc(args.increment)
|
|
167
|
+
inc_x, inc_y = inc_val, inc_val
|
|
168
|
+
|
|
169
|
+
width = region_obj.width
|
|
170
|
+
height = region_obj.height
|
|
171
|
+
nx = int(width / inc_x)
|
|
172
|
+
ny = int(height / inc_y)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.error(f'Invalid increment: {args.increment}. {e}')
|
|
175
|
+
sys.exit(1)
|
|
176
|
+
|
|
177
|
+
if not args.output:
|
|
178
|
+
base, ext = 'transformez_trans', 'tif' # Default to TIF now
|
|
179
|
+
v_out_str = str(epsg_out)
|
|
180
|
+
if geoid_out:
|
|
181
|
+
v_out_str += f"_{geoid_out}"
|
|
182
|
+
dst_grid = f"{base}_{epsg_in}_{v_out_str}.{ext}"
|
|
183
|
+
else:
|
|
184
|
+
dst_grid = args.output
|
|
185
|
+
else:
|
|
186
|
+
logger.error("Region or DEM is required.")
|
|
187
|
+
sys.exit(1)
|
|
188
|
+
|
|
189
|
+
# Initialize Vertical Transform
|
|
190
|
+
vt = VerticalTransform(
|
|
191
|
+
region=region_obj,
|
|
192
|
+
nx=nx,
|
|
193
|
+
ny=ny,
|
|
194
|
+
epsg_in=epsg_in,
|
|
195
|
+
epsg_out=epsg_out,
|
|
196
|
+
geoid_in=geoid_in,
|
|
197
|
+
geoid_out=geoid_out,
|
|
198
|
+
epoch_in=args.epoch_in,
|
|
199
|
+
epoch_out=args.epoch_out,
|
|
200
|
+
cache_dir=cache_dir,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
logger.info(f"Generating shift grid: {epsg_in} -> {epsg_out}")
|
|
204
|
+
shift_array, _ = vt._vertical_transform(vt.epsg_in, vt.epsg_out)
|
|
205
|
+
|
|
206
|
+
if shift_array is not None:
|
|
207
|
+
if args.preview:
|
|
208
|
+
plot_grid(
|
|
209
|
+
shift_array,
|
|
210
|
+
region=region_obj,
|
|
211
|
+
title=f"Shift: {epsg_in} -> {epsg_out}"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if args.dem:
|
|
215
|
+
# Apply to DEM
|
|
216
|
+
logger.info(f"Applying transformation to DEM...")
|
|
217
|
+
GridEngine.apply_vertical_shift(args.dem, shift_array, dst_grid)
|
|
218
|
+
else:
|
|
219
|
+
# Just write the grid
|
|
220
|
+
logger.info(f"Saving transformation grid to: {dst_grid}")
|
|
221
|
+
GridWriter.write(dst_grid, shift_array, region_obj)
|
|
222
|
+
return
|
|
223
|
+
else:
|
|
224
|
+
logger.error("Failed to generate transformation grid.")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
if __name__ == '__main__':
|
|
228
|
+
transformez_cli()
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
transformez.definitions
|
|
6
|
+
~~~~~~~~~~~~~
|
|
7
|
+
|
|
8
|
+
This file contains the various vertical datum transformation references
|
|
9
|
+
and definitions.
|
|
10
|
+
|
|
11
|
+
:copyright: (c) 2010-2026 Regents of the University of Colorado
|
|
12
|
+
:license: MIT, see LICENSE for more details.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
class Datums:
|
|
20
|
+
"""Class to manage vertical datum definitions and lookups."""
|
|
21
|
+
|
|
22
|
+
# =========================================================================
|
|
23
|
+
# Vertical Datum References
|
|
24
|
+
# =========================================================================
|
|
25
|
+
SURFACES = {
|
|
26
|
+
# --- Tidal Datums ---
|
|
27
|
+
1089: {'name': 'mllw', 'description': 'Mean Lower Low Water', 'uncertainty': 0, 'epsg': 5866},
|
|
28
|
+
5866: {'name': 'mllw', 'description': 'Mean Lower Low Water', 'uncertainty': 0, 'epsg': 5866},
|
|
29
|
+
1091: {'name': 'mlw', 'description': 'Mean Low Water', 'uncertainty': 0, 'epsg': 1091},
|
|
30
|
+
5869: {'name': 'mhhw', 'description': 'Mean Higher High Water', 'uncertainty': 0, 'epsg': 5869},
|
|
31
|
+
5868: {'name': 'mhw', 'description': 'Mean High Water', 'uncertainty': 0, 'epsg': 5868},
|
|
32
|
+
5714: {'name': 'msl', 'description': 'Mean Sea Level', 'uncertainty': 0, 'epsg': 5714},
|
|
33
|
+
5713: {'name': 'mtl', 'description': 'Mean Tide Level', 'uncertainty': 0, 'epsg': 5713},
|
|
34
|
+
|
|
35
|
+
# --- Hydraulic / River Datums ---
|
|
36
|
+
# Columbia River Datum (No standard EPSG, using 0 placeholder or custom)
|
|
37
|
+
0: {'name': 'crd', 'description': 'Columbia River Datum', 'uncertainty': 0, 'epsg': 0},
|
|
38
|
+
|
|
39
|
+
# IGLD 1985 (Dynamic Height)
|
|
40
|
+
5609: {'name': 'IGLD85', 'description': 'International Great Lakes Datum 1985', 'uncertainty': 0, 'epsg': 5609},
|
|
41
|
+
|
|
42
|
+
# IGLD Low Water Datum (Chart Datum for Lakes)
|
|
43
|
+
# VDatum uses 'LWD_IGLD85' string
|
|
44
|
+
9000: {'name': 'LWD_IGLD85', 'description': 'IGLD85 Low Water Datum', 'uncertainty': 0, 'epsg': 5609},
|
|
45
|
+
|
|
46
|
+
# --- Legacy Vertical ---
|
|
47
|
+
# NGVD29 is often best handled via VDatum (VERTCON) if PROJ isn't configured
|
|
48
|
+
5702: {'name': 'NGVD29', 'description': 'National Geodetic Vertical Datum 1929', 'uncertainty': 0.05, 'epsg': 5702},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
HTDP = {
|
|
52
|
+
4269: {'name': 'NAD_83(2011/CORS96/2007)', 'description': '(North American plate fixed)', 'htdp_id': 1, 'uncertainty': .02, 'epoch': 1997.0},
|
|
53
|
+
6781: {'name': 'NAD_83(2011/CORS96/2007)', 'description': '(North American plate fixed)', 'htdp_id': 1, 'uncertainty': .02, 'epoch': 1997.0},
|
|
54
|
+
6319: {'name': 'NAD_83(2011/CORS96/2007)', 'description': '(North American plate fixed)', 'htdp_id': 1, 'uncertainty': .02, 'epoch': 1997.0},
|
|
55
|
+
6321: {'name': 'NAD_83(PA11/PACP00)', 'description': '(Pacific plate fixed)', 'htdp_id': 2, 'uncertainty': .02, 'epoch': 1997.0},
|
|
56
|
+
6324: {'name': 'NAD_83(MA11/MARP00)', 'description': '(Mariana plate fixed)', 'htdp_id': 3, 'uncertainty': .02, 'epoch': 1997.0},
|
|
57
|
+
4979: {'name': 'WGS_84(original)', 'description': '(NAD_83(2011) used)', 'htdp_id': 4, 'uncertainty': 0, 'epoch': 1997.0},
|
|
58
|
+
7815: {'name': 'WGS_84(original)', 'description': '(NAD_83(2011) used)', 'htdp_id': 4, 'uncertainty': 0, 'epoch': 1997.0},
|
|
59
|
+
7816: {'name': 'WGS_84(original)', 'description': '(NAD_83(2011) used)', 'htdp_id': 4, 'uncertainty': 0, 'epoch': 1997.0},
|
|
60
|
+
7656: {'name': 'WGS_84(G730)', 'description': '(ITRF91 used)', 'htdp_id': 5, 'uncertainty': 0, 'epoch': 1997.0},
|
|
61
|
+
7657: {'name': 'WGS_84(G730)', 'description': '(ITRF91 used)', 'htdp_id': 5, 'uncertainty': 0, 'epoch': 1997.0},
|
|
62
|
+
7658: {'name': 'WGS_84(G873)', 'description': '(ITRF94 used)', 'htdp_id': 6, 'uncertainty': 0, 'epoch': 1997.0},
|
|
63
|
+
7659: {'name': 'WGS_84(G873)', 'description': '(ITRF94 used)', 'htdp_id': 6, 'uncertainty': 0, 'epoch': 1997.0},
|
|
64
|
+
7660: {'name': 'WGS_84(G1150)', 'description': '(ITRF2000 used)', 'htdp_id': 7, 'uncertainty': 0, 'epoch': 1997.0},
|
|
65
|
+
7661: {'name': 'WGS_84(G1150)', 'description': '(ITRF2000 used)', 'htdp_id': 7, 'uncertainty': 0, 'epoch': 1997.0},
|
|
66
|
+
7662: {'name': 'WGS_84(G1674)', 'description': '(ITRF2008 used)', 'htdp_id': 8, 'uncertainty': 0, 'epoch': 2000.0},
|
|
67
|
+
7663: {'name': 'WGS_84(G1674)', 'description': '(ITRF2008 used)', 'htdp_id': 8, 'uncertainty': 0, 'epoch': 2000.0},
|
|
68
|
+
7664: {'name': 'WGS_84(G1762)', 'description': '(IGb08 used)', 'htdp_id': 9, 'uncertainty': 0, 'epoch': 2000.0},
|
|
69
|
+
7665: {'name': 'WGS_84(G1762)', 'description': '(IGb08 used)', 'htdp_id': 9, 'uncertainty': 0, 'epoch': 2000.0},
|
|
70
|
+
7666: {'name': 'WGS_84(G2139)', 'description': '(ITRF2014=IGS14=IGb14 used)', 'htdp_id': 10, 'uncertainty': 0, 'epoch': 1997.0},
|
|
71
|
+
7667: {'name': 'WGS_84(G2139)', 'description': '(ITRF2014=IGS14=IGb14 used)', 'htdp_id': 10, 'uncertainty': 0, 'epoch': 1997.0},
|
|
72
|
+
4910: {'name': 'ITRF88', 'description': '', 'htdp_id': 11, 'uncertainty': 0, 'epoch': 1988.0},
|
|
73
|
+
4911: {'name': 'ITRF89', 'description': '', 'htdp_id': 12, 'uncertainty': 0, 'epoch': 1988.0},
|
|
74
|
+
7901: {'name': 'ITRF89', 'description': '', 'htdp_id': 12, 'uncertainty': 0, 'epoch': 1988.0},
|
|
75
|
+
7902: {'name': 'ITRF90', 'description': '(PNEOS90/NEOS90)', 'htdp_id': 13, 'uncertainty': 0, 'epoch': 1988.0},
|
|
76
|
+
7903: {'name': 'ITRF91', 'description': '', 'htdp_id': 14, 'uncertainty': 0, 'epoch': 1988.0},
|
|
77
|
+
7904: {'name': 'ITRF92', 'description': '', 'htdp_id': 15, 'uncertainty': 0, 'epoch': 1988.0},
|
|
78
|
+
7905: {'name': 'ITRF93', 'description': '', 'htdp_id': 16, 'uncertainty': 0, 'epoch': 1988.0},
|
|
79
|
+
7906: {'name': 'ITRF94', 'description': '', 'htdp_id': 17, 'uncertainty': 0, 'epoch': 1988.0},
|
|
80
|
+
7907: {'name': 'ITRF96', 'description': '', 'htdp_id': 18, 'uncertainty': 0, 'epoch': 1996.0},
|
|
81
|
+
7908: {'name': 'ITRF97', 'description': 'IGS97', 'htdp_id': 19, 'uncertainty': 0, 'epoch': 1997.0},
|
|
82
|
+
7909: {'name': 'ITRF2000', 'description': 'IGS00/IGb00', 'htdp_id': 20, 'uncertainty': 0, 'epoch': 2000.0},
|
|
83
|
+
7910: {'name': 'ITRF2005', 'description': 'IGS05', 'htdp_id': 21, 'uncertainty': 0, 'epoch': 2000.0},
|
|
84
|
+
7911: {'name': 'ITRF2008', 'description': 'IGS08/IGb08', 'htdp_id': 22, 'uncertainty': 0, 'epoch': 2000.0},
|
|
85
|
+
7912: {'name': 'ELLIPSOID', 'description': 'IGS14/IGb14/WGS84/ITRF2014 Ellipsoid', 'htdp_id': 23, 'uncertainty': 0, 'epoch': 2000.0},
|
|
86
|
+
1322: {'name': 'ITRF2020', 'description': 'IGS20', 'htdp_id': 24, 'uncertainty': 0, 'epoch': 2000.0},
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
CDN = {
|
|
91
|
+
# CONUS / Alaska / Hawaii / PR / VI
|
|
92
|
+
5703: {'name': 'NAVD88 height', 'vdatum_id': 'navd88:m:height', 'default_geoid': 'g2018', 'ellipsoid': 6319},
|
|
93
|
+
6360: {'name': 'NAVD88 height (usFt)', 'default_geoid': 'g2018'},
|
|
94
|
+
8228: {'name': 'NAVD88 height (Ft)', 'default_geoid': 'g2018'},
|
|
95
|
+
|
|
96
|
+
# Puerto Rico
|
|
97
|
+
6641: {'name': 'PRVD02 height', 'vdatum_id': 'prvd02:m:height', 'default_geoid': 'g2018', 'ellipsoid': 6319},
|
|
98
|
+
|
|
99
|
+
# Virgin Islands
|
|
100
|
+
6642: {'name': 'VIVD09 height', 'vdatum_id': 'vivd09:m:height', 'default_geoid': 'g2018', 'ellipsoid': 6319},
|
|
101
|
+
|
|
102
|
+
# Canada (CGVD2013 uses CGG2013 geoid)
|
|
103
|
+
# Note: You need to ensure 'CGG2013' is fetchable via your fetcher or map it to a filename
|
|
104
|
+
6647: {'name': 'CGVD2013(CGG2013)', 'vdatum_id': 'cgvd2013:m:height', 'default_geoid': 'CGG2013'},
|
|
105
|
+
|
|
106
|
+
# Global EGM
|
|
107
|
+
3855: {'name': 'EGM2008 height', 'vdatum_id': 'egm2008:m:height', 'default_geoid': 'egm2008'},
|
|
108
|
+
5773: {'name': 'EGM96 height', 'vdatum_id': 'egm96:m:height', 'default_geoid': 'egm96'},
|
|
109
|
+
|
|
110
|
+
# # Ellipsoidal (Hubs) - No Geoid needed
|
|
111
|
+
# 6319: {'name': 'NAD83(2011)', 'vdatum_id': 'nad83_2011:m:height'},
|
|
112
|
+
# 4979: {'name': 'WGS84', 'vdatum_id': 'wgs84:m:height'},
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
GEOIDS = {
|
|
116
|
+
# Standard PROJ-CDN Geoids (Default provider is 'proj')
|
|
117
|
+
'g2018': {'name': 'geoid 2018', 'uncertainty': .0127, 'provider': 'proj'},
|
|
118
|
+
'g2012b': {'name': 'geoid 2012b', 'uncertainty': .017, 'provider': 'proj'},
|
|
119
|
+
'geoid09': {'name': 'geoid 2009', 'uncertainty': .05, 'provider': 'proj'},
|
|
120
|
+
|
|
121
|
+
# New XGEOIDs via VDatum (Provider is 'vdatum')
|
|
122
|
+
'xgeoid20b': {'name': 'xgeoid20b', 'uncertainty': .02, 'provider': 'vdatum'},
|
|
123
|
+
'xgeoid19b': {'name': 'xgeoid19b', 'uncertainty': .02, 'provider': 'vdatum'},
|
|
124
|
+
|
|
125
|
+
'egm2008': {'name': 'EGM2008', 'uncertainty': 0, 'provider': 'proj'},
|
|
126
|
+
'egm96': {'name': 'EGM96', 'uncertainty': 0, 'provider': 'proj'},
|
|
127
|
+
|
|
128
|
+
'CGG2013': {'name': 'CGG2013', 'uncertainty': 0.01, 'provider': 'proj'},
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def get_default_geoid(cls, epsg):
|
|
133
|
+
"""Return default geoid for a generic CDN EPSG, or None."""
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
e_int = int(epsg)
|
|
137
|
+
except:
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
if e_int in cls.CDN:
|
|
141
|
+
return cls.CDN[e_int].get('default_geoid')
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def get_vdatum_by_name(cls, datum_name):
|
|
147
|
+
"""Return the vertical datum EPSG based on the vertical datum name."""
|
|
148
|
+
|
|
149
|
+
if datum_name is None:
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
datum_int = int(datum_name)
|
|
154
|
+
except (ValueError, TypeError):
|
|
155
|
+
datum_int = None
|
|
156
|
+
|
|
157
|
+
for frame_set in [cls.SURFACES, cls.HTDP, cls.CDN]:
|
|
158
|
+
if datum_int in frame_set:
|
|
159
|
+
return datum_int
|
|
160
|
+
|
|
161
|
+
for epsg, info in frame_set.items():
|
|
162
|
+
if str(datum_name).lower() in info['name'].lower():
|
|
163
|
+
return epsg
|
|
164
|
+
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def get_vdatum_id(cls, epsg):
|
|
170
|
+
"""Retrieve the NOAA VDatum CLI string for an EPSG."""
|
|
171
|
+
if epsg in cls.SURFACES: return cls.SURFACES[epsg].get('vdatum_id')
|
|
172
|
+
if epsg in cls.CDN: return cls.CDN[epsg].get('vdatum_id')
|
|
173
|
+
if epsg == 6319: return 'nad83_2011:m:height'
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def get_frame_type(cls, epsg):
|
|
179
|
+
"""Identify which frame set an EPSG belongs to."""
|
|
180
|
+
|
|
181
|
+
if epsg in cls.SURFACES:
|
|
182
|
+
return 'surface'
|
|
183
|
+
if epsg in cls.HTDP:
|
|
184
|
+
return 'htdp'
|
|
185
|
+
if epsg in cls.CDN:
|
|
186
|
+
return 'cdn'
|
|
187
|
+
return None
|