bluemesh2d 0.1.1.dev0__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.
- bluemesh2d/_version.py +24 -0
- bluemesh2d/aabb_tree/findball.py +121 -0
- bluemesh2d/aabb_tree/findtria.py +299 -0
- bluemesh2d/aabb_tree/maketree.py +147 -0
- bluemesh2d/aabb_tree/mapvert.py +72 -0
- bluemesh2d/aabb_tree/queryset.py +83 -0
- bluemesh2d/aabb_tree/scantree.py +124 -0
- bluemesh2d/dependencies.py +83 -0
- bluemesh2d/feedback.py +142 -0
- bluemesh2d/geom_util/boundary_util.py +216 -0
- bluemesh2d/geom_util/getiso.py +194 -0
- bluemesh2d/geom_util/poly_util.py +795 -0
- bluemesh2d/geom_util/proj_util.py +250 -0
- bluemesh2d/geomesh_util/border_util.py +283 -0
- bluemesh2d/geomesh_util/depth_field.py +333 -0
- bluemesh2d/geomesh_util/grd_util.py +1000 -0
- bluemesh2d/geomesh_util/interpolation_mesh.py +318 -0
- bluemesh2d/geomesh_util/merge_circumcenters.py +268 -0
- bluemesh2d/geomesh_util/water_polygon.py +406 -0
- bluemesh2d/hfun_util/build_hfun.py +589 -0
- bluemesh2d/hfun_util/hfun_dispersion.py +96 -0
- bluemesh2d/hfun_util/lfshfn.py +110 -0
- bluemesh2d/hfun_util/limhfn.py +65 -0
- bluemesh2d/hfun_util/make_constant_hfun.py +32 -0
- bluemesh2d/hfun_util/make_depth_hfun.py +51 -0
- bluemesh2d/hfun_util/smooth_and_precomput.py +188 -0
- bluemesh2d/hfun_util/trihfn.py +84 -0
- bluemesh2d/hjac_util/limgrad.py +105 -0
- bluemesh2d/mesh_ball/cdtbal1.py +38 -0
- bluemesh2d/mesh_ball/cdtbal2.py +104 -0
- bluemesh2d/mesh_ball/inv_2x2.py +61 -0
- bluemesh2d/mesh_ball/inv_3x3.py +72 -0
- bluemesh2d/mesh_ball/pwrbal2.py +134 -0
- bluemesh2d/mesh_ball/tribal2.py +27 -0
- bluemesh2d/mesh_cost/relhfn.py +62 -0
- bluemesh2d/mesh_cost/triang.py +59 -0
- bluemesh2d/mesh_cost/triarea.py +51 -0
- bluemesh2d/mesh_cost/trideg.py +44 -0
- bluemesh2d/mesh_cost/triscr.py +43 -0
- bluemesh2d/mesh_file/bnd_util.py +664 -0
- bluemesh2d/mesh_file/loadmsh.py +165 -0
- bluemesh2d/mesh_file/ugrid.py +308 -0
- bluemesh2d/mesh_util/cfmtri.py +118 -0
- bluemesh2d/mesh_util/deltri.py +131 -0
- bluemesh2d/mesh_util/idxtri.py +53 -0
- bluemesh2d/mesh_util/isfeat.py +90 -0
- bluemesh2d/mesh_util/minlen.py +46 -0
- bluemesh2d/mesh_util/setset.py +49 -0
- bluemesh2d/mesh_util/tricon.py +90 -0
- bluemesh2d/mesh_util/tridiv.py +187 -0
- bluemesh2d/meshgen.py +202 -0
- bluemesh2d/ortho_merge/constants.py +27 -0
- bluemesh2d/ortho_merge/geometry.py +64 -0
- bluemesh2d/ortho_merge/ortho_merge_iter.py +812 -0
- bluemesh2d/ortho_merge/orthogonalize.py +2989 -0
- bluemesh2d/pipeline.py +277 -0
- bluemesh2d/poly_data/airfoil.msh +958 -0
- bluemesh2d/poly_data/channel.msh +212 -0
- bluemesh2d/poly_data/islands.msh +13819 -0
- bluemesh2d/poly_data/lake.msh +612 -0
- bluemesh2d/poly_data/river.msh +690 -0
- bluemesh2d/poly_test/inpoly.py +105 -0
- bluemesh2d/poly_test/inpoly_mat.py +104 -0
- bluemesh2d/refine.py +972 -0
- bluemesh2d/smood.py +623 -0
- bluemesh2d/smooth.py +522 -0
- bluemesh2d/tricost.py +426 -0
- bluemesh2d/tridemo.py +723 -0
- bluemesh2d/triread.py +53 -0
- bluemesh2d-0.1.1.dev0.dist-info/METADATA +139 -0
- bluemesh2d-0.1.1.dev0.dist-info/RECORD +74 -0
- bluemesh2d-0.1.1.dev0.dist-info/WHEEL +5 -0
- bluemesh2d-0.1.1.dev0.dist-info/licenses/LICENSE +674 -0
- bluemesh2d-0.1.1.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pyproj
|
|
3
|
+
import rasterio
|
|
4
|
+
from rasterio.transform import rowcol
|
|
5
|
+
from rasterio.warp import transform_bounds
|
|
6
|
+
from scipy.interpolate import (
|
|
7
|
+
LinearNDInterpolator,
|
|
8
|
+
NearestNDInterpolator,
|
|
9
|
+
RegularGridInterpolator,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _check_method(method):
|
|
14
|
+
"""Validate the interpolation ``method`` shared by all depth-field readers."""
|
|
15
|
+
if method not in ("linear", "nearest"):
|
|
16
|
+
raise ValueError("method must be 'linear' or 'nearest'")
|
|
17
|
+
return method
|
|
18
|
+
|
|
19
|
+
def depth_field_from_dat(x, y, z, input_crs, output_crs, method="linear"):
|
|
20
|
+
"""Build a callable depth field from scattered x-y-z points.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
x, y, z : ndarray of shape (N,)
|
|
25
|
+
Point coordinates and depth/elevation values.
|
|
26
|
+
input_crs : str or pyproj.CRS
|
|
27
|
+
CRS of the input coordinates.
|
|
28
|
+
output_crs : str or pyproj.CRS
|
|
29
|
+
CRS in which the returned depth field is queried.
|
|
30
|
+
method : {'linear', 'nearest'}, optional
|
|
31
|
+
Interpolation method. Default is ``'linear'``.
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
depth_field : callable
|
|
36
|
+
``depth_field(xy)`` returns interpolated depth values (m) for ``xy`` of
|
|
37
|
+
shape ``(M, 2)`` in ``output_crs``. Exposes ``.bounds`` in
|
|
38
|
+
``output_crs``.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
_check_method(method)
|
|
42
|
+
|
|
43
|
+
# Clean invalid values
|
|
44
|
+
mask = np.isfinite(x) & np.isfinite(y) & np.isfinite(z)
|
|
45
|
+
x, y, z = x[mask], y[mask], z[mask]
|
|
46
|
+
|
|
47
|
+
# Create interpolator
|
|
48
|
+
if method == "linear":
|
|
49
|
+
interp = LinearNDInterpolator(list(zip(x, y)), z, fill_value=np.nan)
|
|
50
|
+
else: # nearest
|
|
51
|
+
interp = NearestNDInterpolator(list(zip(x, y)), z)
|
|
52
|
+
|
|
53
|
+
input_crs = pyproj.CRS.from_user_input(input_crs)
|
|
54
|
+
output_crs = pyproj.CRS.from_user_input(output_crs)
|
|
55
|
+
transformer = pyproj.Transformer.from_crs(output_crs, input_crs, always_xy=True)
|
|
56
|
+
|
|
57
|
+
# Closure function
|
|
58
|
+
def depth_field(xy):
|
|
59
|
+
"""Evaluate interpolated depth at query coordinates."""
|
|
60
|
+
xs, ys = xy[:, 0], xy[:, 1]
|
|
61
|
+
xs, ys = transformer.transform(xs, ys)
|
|
62
|
+
depth = -interp(xs, ys)
|
|
63
|
+
depth[np.isnan(depth)] = 0.0
|
|
64
|
+
return np.asarray(depth, dtype=float)
|
|
65
|
+
|
|
66
|
+
# Expose the data extent in the query (output) CRS, so callers can size a
|
|
67
|
+
# sampling grid without a separate domain (same contract as the other readers).
|
|
68
|
+
depth_field.bounds = transform_bounds(
|
|
69
|
+
input_crs, output_crs, float(x.min()), float(y.min()), float(x.max()), float(y.max())
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return depth_field
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _cell_center_offset(dataset):
|
|
76
|
+
"""Half-cell offset (in pixels) to reach cell centres from the transform.
|
|
77
|
+
|
|
78
|
+
The rasterio/GDAL affine is corner-referenced, but a raster value's
|
|
79
|
+
location depends on the GeoTIFF ``GTRasterTypeGeoKey`` -- surfaced as the
|
|
80
|
+
GDAL/rasterio ``AREA_OR_POINT`` tag:
|
|
81
|
+
|
|
82
|
+
- ``Area`` (default, and what a missing tag means): the value represents
|
|
83
|
+
the whole cell, so its location is the cell *centre* -> offset 0.5.
|
|
84
|
+
- ``Point``: the value is a point sample at the grid node the transform
|
|
85
|
+
already indexes -> no offset.
|
|
86
|
+
|
|
87
|
+
Callers add this to grid-building indices, or subtract it when converting
|
|
88
|
+
world coordinates to fractional indices, so sampling lands on the true
|
|
89
|
+
data location for either convention.
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
tag = str(dataset.tags().get("AREA_OR_POINT", "Area")).strip().lower()
|
|
93
|
+
except Exception:
|
|
94
|
+
tag = "area"
|
|
95
|
+
return 0.0 if tag.startswith("point") else 0.5
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _read_window_decimated(dataset, bbox, max_cells):
|
|
99
|
+
"""Read band 1 clipped to ``bbox`` (raster CRS) and decimated to fit.
|
|
100
|
+
|
|
101
|
+
Only the requested window is read (never the whole raster), down-sampled
|
|
102
|
+
on read when still larger than ``max_cells`` -- so an oversized bathymetry
|
|
103
|
+
raster is turned into a depth field without materialising it in memory.
|
|
104
|
+
|
|
105
|
+
Returns ``(band, transform, step)`` where ``transform`` is the affine of
|
|
106
|
+
the decimated window (maps local col/row -> raster-CRS coordinates).
|
|
107
|
+
"""
|
|
108
|
+
from rasterio.transform import Affine
|
|
109
|
+
from rasterio.windows import Window
|
|
110
|
+
|
|
111
|
+
W, H = dataset.width, dataset.height
|
|
112
|
+
if bbox is not None:
|
|
113
|
+
xmin, ymin, xmax, ymax = bbox
|
|
114
|
+
inv = ~dataset.transform
|
|
115
|
+
cols, rows = [], []
|
|
116
|
+
for x in (xmin, xmax):
|
|
117
|
+
for y in (ymin, ymax):
|
|
118
|
+
c, r = inv * (x, y)
|
|
119
|
+
cols.append(c)
|
|
120
|
+
rows.append(r)
|
|
121
|
+
col_off = max(0, int(np.floor(min(cols))) - 2)
|
|
122
|
+
row_off = max(0, int(np.floor(min(rows))) - 2)
|
|
123
|
+
col_end = min(W, int(np.ceil(max(cols))) + 2)
|
|
124
|
+
row_end = min(H, int(np.ceil(max(rows))) + 2)
|
|
125
|
+
if col_end <= col_off or row_end <= row_off:
|
|
126
|
+
raise RuntimeError(
|
|
127
|
+
"The domain does not overlap the bathymetry raster.")
|
|
128
|
+
else:
|
|
129
|
+
col_off, row_off, col_end, row_end = 0, 0, W, H
|
|
130
|
+
|
|
131
|
+
win = Window(col_off, row_off, col_end - col_off, row_end - row_off)
|
|
132
|
+
win_w, win_h = int(win.width), int(win.height)
|
|
133
|
+
step = 1
|
|
134
|
+
if max_cells and win_w * win_h > max_cells:
|
|
135
|
+
step = int(np.ceil((win_w * win_h / float(max_cells)) ** 0.5))
|
|
136
|
+
out_w = max(1, win_w // step)
|
|
137
|
+
out_h = max(1, win_h // step)
|
|
138
|
+
|
|
139
|
+
band = dataset.read(1, window=win, out_shape=(out_h, out_w))
|
|
140
|
+
t = dataset.window_transform(win) * Affine.scale(win_w / out_w, win_h / out_h)
|
|
141
|
+
return band, t, step
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def depth_field_from_tif(tiff_path, output_crs, raster_crs=None, method="linear",
|
|
145
|
+
bbox=None, max_cells=None, invert_z=False,
|
|
146
|
+
nodata_value=None):
|
|
147
|
+
"""Build a callable depth field from a bathymetry GeoTIFF.
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
tiff_path : str
|
|
152
|
+
Path to the bathymetry GeoTIFF file.
|
|
153
|
+
output_crs : str or pyproj.CRS
|
|
154
|
+
CRS of coordinates passed to the returned depth field.
|
|
155
|
+
raster_crs : str or pyproj.CRS, optional
|
|
156
|
+
CRS of the raster. If ``None``, taken from the GeoTIFF metadata.
|
|
157
|
+
method : {'linear', 'nearest'}, optional
|
|
158
|
+
``'linear'`` for bilinear interpolation; ``'nearest'`` for nearest pixel.
|
|
159
|
+
Default is ``'linear'``.
|
|
160
|
+
bbox : tuple or None, optional
|
|
161
|
+
``(xmin, ymin, xmax, ymax)`` in the *raster* CRS. When given, only that
|
|
162
|
+
window of the raster is read (much less memory on large rasters). The
|
|
163
|
+
depth field returns 0 outside it, so the window must cover the full
|
|
164
|
+
query extent. Default ``None`` (whole raster).
|
|
165
|
+
max_cells : int or None, optional
|
|
166
|
+
If the window still exceeds this many cells it is down-sampled on read.
|
|
167
|
+
Default ``None`` (no decimation).
|
|
168
|
+
invert_z : bool, optional
|
|
169
|
+
The raster is assumed to store elevation (positive up), so depth is
|
|
170
|
+
``-value``. Set ``True`` when the raster already stores depth
|
|
171
|
+
(positive down) or has an inverted Z sign, giving depth ``+value``.
|
|
172
|
+
Default ``False``.
|
|
173
|
+
nodata_value : float or None, optional
|
|
174
|
+
Elevation (positive up) assigned to nodata / non-finite pixels.
|
|
175
|
+
``None`` (default) uses 0 (sea level -> depth 0).
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
depth_field : callable
|
|
180
|
+
``depth_field(xy)`` returns depth (m) for ``xy`` of shape ``(N, 2)`` in
|
|
181
|
+
``output_crs``. Exposes ``.bounds`` in ``output_crs``.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
_check_method(method)
|
|
185
|
+
|
|
186
|
+
from bluemesh2d.geom_util.proj_util import (
|
|
187
|
+
bundled_raster_data_env, require_georeferenced)
|
|
188
|
+
with bundled_raster_data_env(), rasterio.open(tiff_path) as dataset:
|
|
189
|
+
if raster_crs is None:
|
|
190
|
+
require_georeferenced(dataset)
|
|
191
|
+
raster_crs = dataset.crs
|
|
192
|
+
nodata = dataset.nodata
|
|
193
|
+
center_off = _cell_center_offset(dataset) # 0.5 Area / 0.0 Point
|
|
194
|
+
band, transform, _ = _read_window_decimated(dataset, bbox, max_cells)
|
|
195
|
+
# window bounds in raster CRS (from the decimated transform)
|
|
196
|
+
x0, y0 = transform * (0, 0)
|
|
197
|
+
x1, y1 = transform * (band.shape[1], band.shape[0])
|
|
198
|
+
rxmin, rxmax = sorted((x0, x1))
|
|
199
|
+
rymin, rymax = sorted((y0, y1))
|
|
200
|
+
|
|
201
|
+
output_crs = pyproj.CRS.from_user_input(output_crs)
|
|
202
|
+
raster_crs = pyproj.CRS.from_user_input(raster_crs) if raster_crs else output_crs
|
|
203
|
+
if raster_crs != output_crs:
|
|
204
|
+
transformer = pyproj.Transformer.from_crs(
|
|
205
|
+
output_crs, raster_crs, always_xy=True
|
|
206
|
+
)
|
|
207
|
+
else:
|
|
208
|
+
transformer = None
|
|
209
|
+
|
|
210
|
+
# Depth from elevation (-value), or +value when the Z sign is inverted.
|
|
211
|
+
# nodata / non-finite pixels are replaced by 0 (sea level -> depth 0), so
|
|
212
|
+
# a NaN can never propagate into a nodata hole in the hfun raster.
|
|
213
|
+
sign = 1.0 if invert_z else -1.0
|
|
214
|
+
depth_grid = np.asarray(band, dtype=np.float64) * sign
|
|
215
|
+
bad = ~np.isfinite(depth_grid)
|
|
216
|
+
if nodata is not None:
|
|
217
|
+
bad |= (np.asarray(band) == nodata)
|
|
218
|
+
if bad.any():
|
|
219
|
+
nv = 0.0 if nodata_value is None else float(nodata_value)
|
|
220
|
+
depth_grid[bad] = sign * nv
|
|
221
|
+
|
|
222
|
+
if method == "linear":
|
|
223
|
+
# Build interpolator in pixel (row, col) space
|
|
224
|
+
inv_transform = ~transform
|
|
225
|
+
rows = np.arange(band.shape[0])
|
|
226
|
+
cols = np.arange(band.shape[1])
|
|
227
|
+
interp = RegularGridInterpolator(
|
|
228
|
+
(rows, cols),
|
|
229
|
+
depth_grid,
|
|
230
|
+
method="linear",
|
|
231
|
+
bounds_error=False,
|
|
232
|
+
fill_value=np.nan,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def depth_field(xy):
|
|
236
|
+
xs, ys = xy[:, 0], xy[:, 1]
|
|
237
|
+
if transformer is not None:
|
|
238
|
+
xs, ys = transformer.transform(xs, ys)
|
|
239
|
+
xs, ys = np.asarray(xs), np.asarray(ys)
|
|
240
|
+
|
|
241
|
+
if method == "nearest":
|
|
242
|
+
rows, cols = rowcol(transform, xs, ys)
|
|
243
|
+
rows = np.clip(rows, 0, band.shape[0] - 1)
|
|
244
|
+
cols = np.clip(cols, 0, band.shape[1] - 1)
|
|
245
|
+
depth = depth_grid[rows, cols]
|
|
246
|
+
else:
|
|
247
|
+
# method == "linear": continuous (col, row) from inverse transform
|
|
248
|
+
col_row = np.column_stack(inv_transform * (xs, ys))
|
|
249
|
+
# RegularGridInterpolator expects (row, col) for array [rows, cols];
|
|
250
|
+
# subtract the half-cell offset (Area rasters) because the transform
|
|
251
|
+
# is corner-referenced while grid values sit at cell centres --
|
|
252
|
+
# avoids a half-cell shift. Point rasters use offset 0.
|
|
253
|
+
row_col = col_row[:, [1, 0]] - center_off
|
|
254
|
+
depth = interp(row_col)
|
|
255
|
+
# queries outside the read window return NaN; treat as depth 0 so the
|
|
256
|
+
# size function never produces nodata
|
|
257
|
+
depth = np.asarray(depth, dtype=np.float64)
|
|
258
|
+
depth[~np.isfinite(depth)] = 0.0
|
|
259
|
+
return depth
|
|
260
|
+
|
|
261
|
+
# Expose the (windowed) raster extent in the query (output) CRS as
|
|
262
|
+
# (xmin, ymin, xmax, ymax), so callers can size a sampling grid.
|
|
263
|
+
if raster_crs != output_crs:
|
|
264
|
+
depth_field.bounds = transform_bounds(
|
|
265
|
+
raster_crs, output_crs, rxmin, rymin, rxmax, rymax)
|
|
266
|
+
else:
|
|
267
|
+
depth_field.bounds = (rxmin, rymin, rxmax, rymax)
|
|
268
|
+
|
|
269
|
+
return depth_field
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def depth_field_from_xr(ds, input_crs, output_crs, x_name='lon', y_name='lat',
|
|
273
|
+
z_name="elevation", method="linear"):
|
|
274
|
+
"""Build a callable depth field from an xarray bathymetry dataset.
|
|
275
|
+
|
|
276
|
+
Parameters
|
|
277
|
+
----------
|
|
278
|
+
ds : xarray.Dataset
|
|
279
|
+
Bathymetry dataset (e.g. GEBCO subset).
|
|
280
|
+
input_crs : str or pyproj.CRS
|
|
281
|
+
CRS of the dataset coordinates.
|
|
282
|
+
output_crs : str or pyproj.CRS
|
|
283
|
+
CRS in which the depth field is queried.
|
|
284
|
+
x_name, y_name, z_name : str, optional
|
|
285
|
+
Names of longitude, latitude, and elevation variables in ``ds``.
|
|
286
|
+
method : {'linear', 'nearest'}, optional
|
|
287
|
+
Grid interpolation method. Default is ``'linear'``.
|
|
288
|
+
|
|
289
|
+
Returns
|
|
290
|
+
-------
|
|
291
|
+
depth_field : callable
|
|
292
|
+
``depth_field(xy)`` returns depth values (m) for ``xy`` of shape
|
|
293
|
+
``(N, 2)`` in ``output_crs``. Exposes ``.bounds`` in ``output_crs``.
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
_check_method(method)
|
|
297
|
+
|
|
298
|
+
# Extract lon/lat grid and data (z assumed [lat, lon])
|
|
299
|
+
lon = np.asarray(ds[x_name].values, dtype=float)
|
|
300
|
+
lat = np.asarray(ds[y_name].values, dtype=float)
|
|
301
|
+
z = np.asarray(ds[z_name].values, dtype=float)
|
|
302
|
+
if z.ndim != 2:
|
|
303
|
+
raise ValueError(f"z must be 2D, got shape {z.shape}")
|
|
304
|
+
|
|
305
|
+
# RegularGridInterpolator needs strictly increasing axes; flip if needed
|
|
306
|
+
if lat[0] > lat[-1]:
|
|
307
|
+
lat, z = lat[::-1], z[::-1, :]
|
|
308
|
+
if lon[0] > lon[-1]:
|
|
309
|
+
lon, z = lon[::-1], z[:, ::-1]
|
|
310
|
+
|
|
311
|
+
interp = RegularGridInterpolator(
|
|
312
|
+
(lat, lon), z, method=method, bounds_error=False, fill_value=np.nan
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
input_crs = pyproj.CRS.from_user_input(input_crs)
|
|
316
|
+
output_crs = pyproj.CRS.from_user_input(output_crs)
|
|
317
|
+
to_ds = pyproj.Transformer.from_crs(output_crs, input_crs, always_xy=True)
|
|
318
|
+
|
|
319
|
+
def depth_field(xy):
|
|
320
|
+
"""Evaluate interpolated depth at query coordinates."""
|
|
321
|
+
xs, ys = xy[:, 0], xy[:, 1]
|
|
322
|
+
lon_q, lat_q = to_ds.transform(xs, ys)
|
|
323
|
+
# depth = -elevation; interpolator expects (lat, lon) order
|
|
324
|
+
depth = -interp(np.column_stack([np.asarray(lat_q), np.asarray(lon_q)]))
|
|
325
|
+
return np.asarray(depth, dtype=np.float64)
|
|
326
|
+
|
|
327
|
+
# Expose the data extent in the query (output) CRS (same contract as the others).
|
|
328
|
+
depth_field.bounds = transform_bounds(
|
|
329
|
+
input_crs, output_crs, float(lon.min()), float(lat.min()),
|
|
330
|
+
float(lon.max()), float(lat.max())
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
return depth_field
|