Skip to content

Commit 7a04d39

Browse files
authored
Allow make_xarray_grid to take horizontal coordinates as 1d arrays (#300)
Now make_xarray_grid could take 1d arrays for horizontal coordinates, such as how xr.DataArray stores the single dimension coordinates. Add a new non-public meshgrid_to_1d function to convert 2d meshgrids (like the ones generated by vd.grid_coordinates) to 1d arrays containing a single coordinate of the corresponding axis. This function checks if all elements inside the 2d array are equal along the right axis, i.e. a vaild meshgrid.
1 parent 0881c25 commit 7a04d39

File tree

3 files changed

+171
-3
lines changed

3 files changed

+171
-3
lines changed

verde/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@
2121
longitude_continuity,
2222
)
2323
from .mask import distance_mask, convexhull_mask
24-
from .utils import variance_to_weights, maxabs, grid_to_table, make_xarray_grid
24+
from .utils import (
25+
variance_to_weights,
26+
maxabs,
27+
grid_to_table,
28+
make_xarray_grid,
29+
)
2530
from .io import load_surfer
2631
from .distances import median_distance
2732
from .blockreduce import BlockReduce, BlockMean

verde/tests/test_utils.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
grid_to_table,
2424
partition_by_sum,
2525
make_xarray_grid,
26+
meshgrid_to_1d,
2627
)
2728
from .. import utils
2829

@@ -218,3 +219,75 @@ def test_make_xarray_grid_invalid_extra_coords():
218219
make_xarray_grid(
219220
coordinates, data, data_names="dummy", extra_coords_names=["upward", "time"]
220221
)
222+
223+
224+
def test_make_xarray_grid_invalid_2d_coordinates():
225+
"""
226+
Check if error is raised if invaild 2d coordinates array are passed
227+
"""
228+
region = (-10, -5, 6, 10)
229+
spacing = 1
230+
easting, northing = grid_coordinates(region, spacing=spacing)
231+
# Change only one element of the easting array
232+
easting[2, 2] = -1000
233+
data = np.ones_like(easting)
234+
with pytest.raises(ValueError):
235+
make_xarray_grid((easting, northing), data, data_names="dummy")
236+
237+
238+
def test_make_xarray_grid_coordinates_as_1d_arrays():
239+
"""
240+
Check if it can handle coordinates as 1d-arrays
241+
"""
242+
region = (-10, -5, 6, 10)
243+
easting = np.linspace(*region[:2], 6, dtype=float)
244+
northing = np.linspace(*region[2:], 5, dtype=float)
245+
data = np.ones((northing.size, easting.size))
246+
grid = make_xarray_grid((easting, northing), data, data_names="dummy")
247+
npt.assert_allclose(grid.easting, [-10, -9, -8, -7, -6, -5])
248+
npt.assert_allclose(grid.northing, [6, 7, 8, 9, 10])
249+
npt.assert_allclose(grid.dummy, 1)
250+
assert grid.dummy.shape == (5, 6)
251+
252+
253+
def test_make_xarray_grid_invalid_mixed_coordinates():
254+
"""
255+
Check if error is raised when horizontal coordinates have mixed dimensions
256+
"""
257+
region = (-10, -5, 6, 10)
258+
spacing = 1
259+
easting, northing = grid_coordinates(region, spacing=spacing)
260+
data = np.ones_like(easting)
261+
# easting is 1d, but northing is 2d
262+
with pytest.raises(ValueError):
263+
make_xarray_grid((easting[0, :], northing), data, data_names="dummy")
264+
# northing is 1d, but easting is 2d
265+
with pytest.raises(ValueError):
266+
make_xarray_grid((easting, northing[:, 0]), data, data_names="dummy")
267+
268+
269+
def test_meshgrid_to_1d_invalid():
270+
"""
271+
Check if error is raised after invalid meshgrid
272+
"""
273+
region = (-10, -5, 6, 10)
274+
# Modify one element of easting
275+
easting, northing = grid_coordinates(region=region, spacing=1)
276+
easting[2, 2] = -9999
277+
with pytest.raises(ValueError):
278+
meshgrid_to_1d((easting, northing))
279+
# Modify one element of northing
280+
easting, northing = grid_coordinates(region=region, spacing=1)
281+
northing[2, 3] = -9999
282+
with pytest.raises(ValueError):
283+
meshgrid_to_1d((easting, northing))
284+
# Pass invalid shapes
285+
easting = np.arange(16).reshape(4, 4)
286+
northing = np.arange(9).reshape(3, 3)
287+
with pytest.raises(ValueError):
288+
meshgrid_to_1d((easting, northing))
289+
# Pass 1d arrays
290+
easting = np.linspace(0, 10, 11)
291+
northing = np.linspace(-4, -4, 9)
292+
with pytest.raises(ValueError):
293+
meshgrid_to_1d((easting, northing))

verde/utils.py

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,12 +317,22 @@ def make_xarray_grid(
317317
dummy (northing, easting) float64 1.0 1.0 1.0 1.0 1.0 1.0
318318
319319
"""
320-
coordinates = check_coordinates(coordinates)
320+
# Check dimensions of the horizontal coordinates of the regular grid
321+
ndim = np.ndim(coordinates[0])
322+
if ndim != np.ndim(coordinates[1]):
323+
raise ValueError(
324+
"Incompatible dimensions between horizontal coordinates of the regular grid: "
325+
+ f"'{ndim}' and '{np.ndim(coordinates[1])}'. "
326+
+ "Both coordinates must have the same number of dimensions."
327+
)
328+
# Convert 2d horizontal coordinates to 1d arrays if needed
329+
if ndim == 2:
330+
coordinates = meshgrid_to_1d(coordinates)
321331
data = check_data(data)
322332
data_names = check_data_names(data, data_names)
323333
# dims is like shape with order (rows, cols) for the array
324334
# so the first element is northing and second is easting
325-
coords = {dims[1]: coordinates[0][0, :], dims[0]: coordinates[1][:, 0]}
335+
coords = {dims[1]: coordinates[0], dims[0]: coordinates[1]}
326336
# Extra coordinates are handled like 2D data arrays with
327337
# the same dims and the data.
328338
if coordinates[2:]:
@@ -333,6 +343,86 @@ def make_xarray_grid(
333343
return xr.Dataset(data_vars, coords)
334344

335345

346+
def meshgrid_to_1d(coordinates):
347+
"""
348+
Convert horizontal coordinates of 2d grids into 1d-arrays
349+
350+
Parameters
351+
----------
352+
coordinates : tuple of arrays
353+
Arrays with coordinates of each point in the grid. Each array must
354+
contain values for a dimension in the order: easting, northing,
355+
vertical, etc. All arrays must be 2d and need to have the same
356+
*shape*. The horizontal coordinates should be actual meshgrids.
357+
358+
Returns
359+
-------
360+
coordinates : tuple of arrays
361+
Arrays with coordinates of each point in the grid. The horizontal
362+
coordinates have been converted to 1d-arrays, having only a single
363+
coordinate point per its corresponding axis.
364+
All extra coordinates have not been modified.
365+
366+
Examples
367+
--------
368+
369+
>>> import verde as vd
370+
>>> coordinates = vd.grid_coordinates(
371+
... region=(0, 4, -3, 3), spacing=1, extra_coords=2
372+
... )
373+
>>> easting, northing, height = meshgrid_to_1d(coordinates)
374+
>>> print(easting)
375+
[0. 1. 2. 3. 4.]
376+
>>> print(northing)
377+
[-3. -2. -1. 0. 1. 2. 3.]
378+
>>> print(height)
379+
[[2. 2. 2. 2. 2.]
380+
[2. 2. 2. 2. 2.]
381+
[2. 2. 2. 2. 2.]
382+
[2. 2. 2. 2. 2.]
383+
[2. 2. 2. 2. 2.]
384+
[2. 2. 2. 2. 2.]
385+
[2. 2. 2. 2. 2.]]
386+
"""
387+
check_coordinates(coordinates)
388+
check_meshgrid(coordinates)
389+
easting, northing = coordinates[0][0, :], coordinates[1][:, 0]
390+
coordinates = (easting, northing, *coordinates[2:])
391+
return coordinates
392+
393+
394+
def check_meshgrid(coordinates):
395+
"""
396+
Check if the given horizontal coordinates define a meshgrid
397+
398+
Check if the rows of the easting 2d-array are identical. Check if the
399+
columns of the northing 2d-array are identical. This function does not
400+
check if the easting and northing coordinates are evenly spaced.
401+
402+
Parameters
403+
----------
404+
coordinates : tuple of arrays
405+
Arrays with coordinates of each point in the grid. Each array must
406+
contain values for a dimension in the order: easting, northing,
407+
vertical, etc. Only easting and northing will be checked, the other
408+
ones will be ignored. All arrays must be 2d and need to have the same
409+
*shape*.
410+
"""
411+
# Get the two first arrays as easting and northing
412+
easting, northing = coordinates[:2]
413+
# Check if all elements of easting along the zeroth axis are equal
414+
msg = (
415+
"Invalid coordinate array. The arrays for the horizontal "
416+
+ "coordinates of a regular grid must be meshgrids."
417+
)
418+
if not np.allclose(easting[0, :], easting):
419+
raise ValueError(msg)
420+
# Check if all elements of northing along the first axis are equal
421+
# (need to make northing[:, 0] a vertical array so numpy can compare)
422+
if not np.allclose(northing[:, 0][:, None], northing):
423+
raise ValueError(msg)
424+
425+
336426
def grid_to_table(grid):
337427
"""
338428
Convert a grid to a table with the values and coordinates of each point.

0 commit comments

Comments
 (0)