transformez 0.5.0__tar.gz → 0.5.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.
Files changed (23) hide show
  1. {transformez-0.5.0 → transformez-0.5.2}/PKG-INFO +5 -3
  2. {transformez-0.5.0 → transformez-0.5.2}/README.md +2 -1
  3. {transformez-0.5.0 → transformez-0.5.2}/pyproject.toml +2 -1
  4. {transformez-0.5.0 → transformez-0.5.2}/src/transformez/_version.py +1 -1
  5. {transformez-0.5.0 → transformez-0.5.2}/src/transformez/cli.py +12 -11
  6. {transformez-0.5.0 → transformez-0.5.2}/src/transformez/grid_engine.py +20 -11
  7. {transformez-0.5.0 → transformez-0.5.2}/src/transformez/srs.py +80 -17
  8. {transformez-0.5.0 → transformez-0.5.2}/AUTHORS.md +0 -0
  9. {transformez-0.5.0 → transformez-0.5.2}/CHANGELOG.md +0 -0
  10. {transformez-0.5.0 → transformez-0.5.2}/CITATION.cff +0 -0
  11. {transformez-0.5.0 → transformez-0.5.2}/CONTRIBUTING.md +0 -0
  12. {transformez-0.5.0 → transformez-0.5.2}/LICENSE +0 -0
  13. {transformez-0.5.0 → transformez-0.5.2}/src/transformez/__init__.py +0 -0
  14. {transformez-0.5.0 → transformez-0.5.2}/src/transformez/_spatial.py +0 -0
  15. {transformez-0.5.0 → transformez-0.5.2}/src/transformez/api.py +0 -0
  16. {transformez-0.5.0 → transformez-0.5.2}/src/transformez/definitions.py +0 -0
  17. {transformez-0.5.0 → transformez-0.5.2}/src/transformez/hooks.py +0 -0
  18. {transformez-0.5.0 → transformez-0.5.2}/src/transformez/htdp.py +0 -0
  19. {transformez-0.5.0 → transformez-0.5.2}/src/transformez/modules.py +0 -0
  20. {transformez-0.5.0 → transformez-0.5.2}/src/transformez/transform.py +0 -0
  21. {transformez-0.5.0 → transformez-0.5.2}/src/transformez/utils.py +0 -0
  22. {transformez-0.5.0 → transformez-0.5.2}/src/transformez/vdatum.py +0 -0
  23. {transformez-0.5.0 → transformez-0.5.2}/tests/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: transformez
3
- Version: 0.5.0
3
+ Version: 0.5.2
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
@@ -36,11 +36,12 @@ Classifier: Topic :: Scientific/Engineering :: GIS
36
36
  Requires-Python: >=3.12
37
37
  Requires-Dist: click
38
38
  Requires-Dist: fetchez>=0.6.0
39
- Requires-Dist: fiona
40
39
  Requires-Dist: numpy>1.24
40
+ Requires-Dist: pyogrio>=0.7.0
41
41
  Requires-Dist: pyproj>3.6.1
42
42
  Requires-Dist: rasterio>1.4.0
43
43
  Requires-Dist: scipy
44
+ Requires-Dist: shapely>=2.0.0
44
45
  Provides-Extra: full
45
46
  Requires-Dist: matplotlib; extra == 'full'
46
47
  Provides-Extra: preview
@@ -56,10 +57,11 @@ Description-Content-Type: text/markdown
56
57
  <p align="center"><strong>Global vertical datum transformations, simplified.</strong></p>
57
58
 
58
59
  <p align="center">
59
- <a href="https://github.com/continuous-dems/transformez"><img src="https://img.shields.io/badge/version-0.5.0-blue.svg" alt="Version"></a>
60
+ <a href="https://github.com/continuous-dems/transformez"><img src="https://img.shields.io/badge/version-0.5.1-blue.svg" alt="Version"></a>
60
61
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
61
62
  <a href="https://www.python.org/"><img src="https://img.shields.io/badge/python-3.12+-yellow.svg" alt="Python"></a>
62
63
  <a href="https://badge.fury.io/py/transformez"><img src="https://badge.fury.io/py/transformez.svg" alt="PyPI version"></a>
64
+ <a href="https://anaconda.org/conda-forge/transformez"><img src="https://img.shields.io/conda/vn/conda-forge/transformez.svg" alt="Conda Version"></a>
63
65
  <a href="https://cudem.zulip.org"><img src="https://img.shields.io/badge/zulip-join_chat-brightgreen.svg" alt="Project Chat"></a>
64
66
  </p>
65
67
 
@@ -7,10 +7,11 @@
7
7
  <p align="center"><strong>Global vertical datum transformations, simplified.</strong></p>
8
8
 
9
9
  <p align="center">
10
- <a href="https://github.com/continuous-dems/transformez"><img src="https://img.shields.io/badge/version-0.5.0-blue.svg" alt="Version"></a>
10
+ <a href="https://github.com/continuous-dems/transformez"><img src="https://img.shields.io/badge/version-0.5.1-blue.svg" alt="Version"></a>
11
11
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
12
12
  <a href="https://www.python.org/"><img src="https://img.shields.io/badge/python-3.12+-yellow.svg" alt="Python"></a>
13
13
  <a href="https://badge.fury.io/py/transformez"><img src="https://badge.fury.io/py/transformez.svg" alt="PyPI version"></a>
14
+ <a href="https://anaconda.org/conda-forge/transformez"><img src="https://img.shields.io/conda/vn/conda-forge/transformez.svg" alt="Conda Version"></a>
14
15
  <a href="https://cudem.zulip.org"><img src="https://img.shields.io/badge/zulip-join_chat-brightgreen.svg" alt="Project Chat"></a>
15
16
  </p>
16
17
 
@@ -28,7 +28,8 @@ dependencies = [
28
28
  'numpy>1.24',
29
29
  'pyproj>3.6.1',
30
30
  'rasterio>1.4.0',
31
- 'fiona',
31
+ 'pyogrio>=0.7.0',
32
+ 'shapely>=2.0.0',
32
33
  'fetchez>=0.6.0',
33
34
  'scipy',
34
35
  'click',
@@ -1,4 +1,4 @@
1
1
  # This file is auto-generated by Hatchling. As such, do not:
2
2
  # - modify
3
3
  # - track in version control e.g. be sure to add to .gitignore
4
- __version__ = VERSION = '0.5.0'
4
+ __version__ = VERSION = '0.5.2'
@@ -29,17 +29,18 @@ TRANSFORMEZ_COMMANDS = {
29
29
  class TransformezMainGroup(FetchezMainGroup):
30
30
  """A custom Click Group that handles deprecated aliases."""
31
31
 
32
- def get_command(self, ctx, cmd_name):
33
- if cmd_name == "run":
34
- click.secho(
35
- " DEPRECATION WARNING: 'transformez run' is deprecated and will be removed in a future release.\n"
36
- "Please use 'transformez grid' to generate shift grids or 'transformez raster' to shift a raster.",
37
- fg="yellow",
38
- err=True,
39
- )
40
- return click.Group.get_command(self, ctx, "run")
41
-
42
- return click.Group.get_command(self, ctx, cmd_name)
32
+ pass
33
+ # def get_command(self, ctx, cmd_name):
34
+ # if cmd_name == "run":
35
+ # click.secho(
36
+ # " DEPRECATION WARNING: 'transformez run' is deprecated and will be removed in a future release.\n"
37
+ # "Please use 'transformez grid' to generate shift grids or 'transformez raster' to shift a raster.",
38
+ # fg="yellow",
39
+ # err=True,
40
+ # )
41
+ # return click.Group.get_command(self, ctx, "run")
42
+
43
+ # return click.Group.get_command(self, ctx, cmd_name)
43
44
 
44
45
  # def format_commands(self, ctx, formatter):
45
46
  # commands = []
@@ -79,18 +79,19 @@ def plot_grid(grid_array, region, title="Vertical Shift Preview"):
79
79
 
80
80
  class GridEngine:
81
81
  @staticmethod
82
- def create_land_mask(region, nx, ny, shapefiles):
83
- """Reads a list of vector shapefiles and rasterizes them into a boolean land mask.
82
+ def create_land_mask(region, nx, ny, vector_files):
83
+ """Reads a list of vector files and rasterizes them into a boolean land mask.
84
84
  Returns a numpy boolean array where True = Land, False = Ocean.
85
85
  """
86
86
 
87
87
  try:
88
- import fiona
88
+ from pyogrio.raw import read
89
+ import shapely
89
90
  from rasterio.features import rasterize
90
91
 
91
92
  except ImportError:
92
93
  logger.warning(
93
- "fiona and rasterio are required for vector coastline masking."
94
+ "pyogrio and shapely are required for vector coastline masking."
94
95
  )
95
96
  return None
96
97
 
@@ -99,16 +100,24 @@ class GridEngine:
99
100
  )
100
101
 
101
102
  geoms = []
102
- for shp in shapefiles:
103
+ for vec_path in vector_files:
103
104
  try:
104
- # Use bbox filtering in Fiona to dramatically speed up reading massive global shapefiles
105
+ # Use bbox filtering in pyogrio to speed up reading vectors
105
106
  bbox = (region.xmin, region.ymin, region.xmax, region.ymax)
106
- with fiona.open(shp, bbox=bbox) as src:
107
- for feature in src:
108
- if feature.get("geometry") is not None:
109
- geoms.append(feature["geometry"])
107
+
108
+ # pyogrio raw read returns a tuple: (meta, geometry_wkb, field_data)
109
+ meta, fids, geometry_wkb, fields = read(vec_path, bbox=bbox)
110
+
111
+ if len(geometry_wkb) > 0:
112
+ # Convert WKB directly to shapely geometries
113
+ # We also filter out any None/Null geometries
114
+ valid_geoms = [
115
+ g for g in shapely.from_wkb(geometry_wkb) if g is not None
116
+ ]
117
+ geoms.extend(valid_geoms)
118
+
110
119
  except Exception as e:
111
- logger.warning(f"Failed reading coastline shapefile {shp}: {e}")
120
+ logger.warning(f"Failed reading coastline vector {vec_path}: {e}")
112
121
 
113
122
  if not geoms:
114
123
  return None
@@ -16,6 +16,10 @@ import os
16
16
  import logging
17
17
  from pyproj import CRS, Transformer
18
18
 
19
+ import numpy as np
20
+ from rasterio.warp import transform_bounds, reproject, Resampling
21
+ from rasterio.transform import from_bounds
22
+
19
23
  from fetchez.spatial import Region
20
24
  from .definitions import Datums
21
25
  from .transform import VerticalTransform
@@ -145,7 +149,7 @@ class SRSParser:
145
149
  self.tc["want_vertical"] = has_src_vert or has_dst_vert
146
150
 
147
151
  def set_vertical_transform(self):
148
- """Generates the vertical shift grid using VerticalTransform."""
152
+ """Generates the vertical shift grid using VerticalTransform, aligned to src_crs."""
149
153
 
150
154
  if not self.region or not self.tc["want_vertical"]:
151
155
  return
@@ -162,7 +166,6 @@ class SRSParser:
162
166
 
163
167
  if not s_ident and self.tc["src_geoid"]:
164
168
  s_ident = 6319
165
-
166
169
  if not d_ident and self.tc["dst_geoid"]:
167
170
  d_ident = 6319
168
171
 
@@ -178,10 +181,32 @@ class SRSParser:
178
181
  logger.info(
179
182
  f"Generating vertical grid: {s_ident} -> {d_ident} : {self.tc['trans_fn']} :"
180
183
  )
184
+
185
+ # Determine Native WGS84 Region for Transformez
186
+ src_is_projected = self.tc["src_crs"].is_projected
187
+ if src_is_projected:
188
+ # Transform the native bounding box to WGS84 to query the shift models
189
+ w, s, e, n = transform_bounds(
190
+ self.tc["src_crs"],
191
+ "EPSG:4326",
192
+ proc_region.xmin,
193
+ proc_region.ymin,
194
+ proc_region.xmax,
195
+ proc_region.ymax,
196
+ )
197
+ vt_region = Region(w, e, s, n)
198
+ else:
199
+ vt_region = proc_region
200
+
201
+ # Generate grid resolution based on WGS84 bounds (approx 3 arc-seconds)
202
+ inc_deg = 3.0 / 3600.0
203
+ vt_nx = max(10, int(vt_region.width / inc_deg))
204
+ vt_ny = max(10, int(vt_region.height / inc_deg))
205
+
181
206
  vt = VerticalTransform(
182
- proc_region,
183
- nx=max(10, int(proc_region.width / 0.0008333)),
184
- ny=max(10, int(proc_region.height / 0.0008333)),
207
+ vt_region,
208
+ nx=vt_nx,
209
+ ny=vt_ny,
185
210
  epsg_in=s_ident,
186
211
  epsg_out=d_ident,
187
212
  geoid_in=self.tc["src_geoid"],
@@ -189,27 +214,65 @@ class SRSParser:
189
214
  cache_dir=self.cache_dir,
190
215
  )
191
216
  shift_arr, _ = vt._vertical_transform(vt.epsg_in, vt.epsg_out)
217
+
218
+ # Warp the Grid Back to the Source CRS
219
+ if src_is_projected:
220
+ logger.info(
221
+ f"Warping shift grid to native input CRS ({self.tc['src_crs'].name})..."
222
+ )
223
+ wgs_transform = from_bounds(
224
+ vt_region.xmin,
225
+ vt_region.ymin,
226
+ vt_region.xmax,
227
+ vt_region.ymax,
228
+ vt_nx,
229
+ vt_ny,
230
+ )
231
+
232
+ # Output grid dimensions for the native projection
233
+ out_nx = max(10, int(proc_region.width / (proc_region.width / vt_nx)))
234
+ out_ny = max(10, int(proc_region.height / (proc_region.height / vt_ny)))
235
+ native_transform = from_bounds(
236
+ proc_region.xmin,
237
+ proc_region.ymin,
238
+ proc_region.xmax,
239
+ proc_region.ymax,
240
+ out_nx,
241
+ out_ny,
242
+ )
243
+
244
+ native_shift_array = np.zeros((out_ny, out_nx), dtype=np.float32)
245
+
246
+ reproject(
247
+ source=shift_arr,
248
+ destination=native_shift_array,
249
+ src_transform=wgs_transform,
250
+ src_crs="EPSG:4326",
251
+ dst_transform=native_transform,
252
+ dst_crs=self.tc["src_crs"],
253
+ resampling=Resampling.bilinear,
254
+ )
255
+ shift_arr = native_shift_array
256
+
257
+ # We artificially inject the native transform into proc_region to satisfy GridWriter
258
+ proc_region.transform = native_transform
259
+
260
+ # Write the natively-aligned grid to disk!
192
261
  GridWriter.write(self.tc["trans_fn"], shift_arr, proc_region)
193
262
 
194
263
  self.manual_vert_grid = self.tc["trans_fn"]
195
264
 
196
265
  def get_components(self):
197
266
  """Returns the components:
198
-
199
- - Transformer: Source -> Hub (NAD83 2D)
200
- - Transformer: Hub -> Dest (2D)
201
- - Grid Path (str) or None
267
+ - Transformer: Source -> Dest (Single Pass Horizontal)
268
+ - Grid Path (str) or None (Aligned to Source)
202
269
  """
203
270
 
204
271
  if self.tc["want_vertical"] and not self.manual_vert_grid:
205
272
  self.set_vertical_transform()
206
273
 
207
- # Define Hub: NAD83(2011) 2D - maybe this should be wgs(transit)
208
- hub_crs = CRS.from_epsg(4269)
209
-
210
- # only build 2D transformer (for proj). We'll apply the vertical grid ourselves,
211
- # as pyproj seems finicky when it comes to this.
212
- t_to_hub = Transformer.from_crs(self.tc["src_crs"], hub_crs, always_xy=True)
213
- t_from_hub = Transformer.from_crs(hub_crs, self.tc["dst_crs"], always_xy=True)
274
+ horz_transformer = Transformer.from_crs(
275
+ self.tc["src_crs"], self.tc["dst_crs"], always_xy=True
276
+ )
214
277
 
215
- return t_to_hub, t_from_hub, self.manual_vert_grid
278
+ return horz_transformer, self.manual_vert_grid
File without changes
File without changes
File without changes
File without changes
File without changes