Skip to content

Commit 9b5da2f

Browse files
authored
Merge pull request #214 from csiro-coasts/centroid_coordinates
Add Grid.centroid_coordinates attribute
2 parents 183cbb1 + d30f461 commit 9b5da2f

6 files changed

Lines changed: 64 additions & 23 deletions

File tree

docs/releases/development.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ Next release (in development)
1111
* Add example showing
1212
:ref:`multiple ways of plotting vector data <sphx_glr_examples_plot-vector-methods.py>`
1313
(:pr:`213`).
14+
* Add :attr:`.Grid.centroid_coordinates` attribute
15+
(:pr:`214`).

src/emsarray/conventions/_base.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,36 @@ def centroid(self) -> numpy.ndarray:
146146
The centres of the geometry of this grid as a :class:`numpy.ndarray` of Shapely points.
147147
Defaults to the :func:`shapely.centroid` of :attr:`Grid.geometry`,
148148
but some conventions might have more specific ways of finding the centres.
149+
If geometry is empty the corresponding index in this array will be None.
150+
151+
See also
152+
========
153+
Grid.centroid
154+
The same data represented as a two-dimensional array of coordinates.
155+
Grid.mask
156+
A boolean array indicating where the grid has geometry
149157
"""
150158
return self.convention.make_geometry_centroid(self.grid_kind)
151159

160+
@cached_property
161+
def centroid_coordinates(self) -> numpy.ndarray:
162+
"""
163+
The centres of the geometry of this grid as a 2-dimensional :class:`numpy.ndarray`
164+
with shape (:attr:`Grid.size`, 2).
165+
If geometry is empty the corresponding row in this array will be `[numpy.nan, numpy.nan]`.
166+
167+
See also
168+
========
169+
Grid.centroid
170+
The same data represented as an array of :class:`shapely.Point`.
171+
Grid.mask
172+
A boolean array indicating where the grid has geometry
173+
"""
174+
coordinates = numpy.full(fill_value=numpy.nan, shape=(self.size, 2))
175+
_coordinates, indexes = shapely.get_coordinates(self.centroid, return_index=True)
176+
coordinates[indexes] = _coordinates
177+
return coordinates
178+
152179
@abc.abstractmethod
153180
def ravel_index(self, index: Index) -> int:
154181
"""
@@ -1492,15 +1519,12 @@ def polygons(self) -> numpy.ndarray:
14921519
@cached_property
14931520
@utils.deprecated(
14941521
"dataset.ems.face_centres is deprecated. "
1495-
"Use dataset.ems.get_grid(data_array).centroid instead. "
1522+
"Use dataset.ems.get_grid(data_array).centroid_coordinates instead. "
14961523
"For a list of coordinate pairs use shapely.get_coordinates(grid.centroid)."
14971524
)
14981525
def face_centres(self) -> numpy.ndarray:
14991526
grid = self.grids[self.default_grid_kind]
1500-
centroid = grid.centroid
1501-
coords = numpy.full(fill_value=numpy.nan, shape=(grid.size, 2))
1502-
coords[centroid != None] = shapely.get_coordinates(centroid) # noqa: E711
1503-
return cast(numpy.ndarray, coords)
1527+
return grid.centroid_coordinates
15041528

15051529
@cached_property
15061530
@utils.deprecated(

src/emsarray/plot/artists.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,8 @@ def from_grid(
255255
def make_triangulation(grid: 'conventions.Grid') -> Triangulation:
256256
convention = grid.convention
257257
# Compute the Delaunay triangulation of the face centres
258-
face_centres = grid.centroid
259-
coords = numpy.full(fill_value=numpy.nan, shape=(grid.size, 2))
260-
coords[face_centres != None] = shapely.get_coordinates(face_centres) # noqa: E711
261-
triangulation = Triangulation(coords[:, 0], coords[:, 1])
258+
centres = grid.centroid_coordinates
259+
triangulation = Triangulation(centres[:, 0], centres[:, 1])
262260

263261
# Mask out any Triangles that are not contained within the dataset geometry.
264262
# These are either in concave areas of the geometry (e.g. an inlet or bay)
@@ -393,8 +391,7 @@ def from_grid(
393391
if not issubclass(grid.geometry_type, shapely.Polygon):
394392
raise ValueError("Grid must have polygon geometry")
395393

396-
coords = numpy.full(fill_value=numpy.nan, shape=(grid.size, 2))
397-
coords[grid.centroid != None] = shapely.get_coordinates(grid.centroid) # noqa: E711
394+
coords = grid.centroid_coordinates
398395

399396
# A Quiver needs some values when being initialized.
400397
# We don't always want to provide values to the quiver,

tests/conventions/test_base.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,3 +536,23 @@ def test_centroid():
536536
assert len(face_centres) == len(polygons)
537537
assert polygons[i] is None
538538
assert face_centres[i] is None
539+
540+
541+
def test_centroid_coordinates():
542+
y_size, x_size = 10, 20
543+
dataset = xarray.Dataset({
544+
'temp': (['t', 'z', 'y', 'x'], numpy.random.standard_normal((5, 5, y_size, x_size))),
545+
'botz': (['y', 'x'], numpy.random.standard_normal((y_size, x_size)) - 10),
546+
})
547+
convention = SimpleConvention(dataset)
548+
549+
grid = convention.grids['face']
550+
xx, yy = numpy.meshgrid(
551+
numpy.arange(x_size) + 0.5,
552+
numpy.arange(y_size) + 0.5,
553+
)
554+
expected = numpy.c_[xx.flatten(), yy.flatten()]
555+
expected[~grid.mask] = [numpy.nan, numpy.nan]
556+
actual = grid.centroid_coordinates
557+
558+
numpy.testing.assert_equal(expected, actual)

tests/conventions/test_ugrid.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -463,34 +463,32 @@ def test_node_grid() -> None:
463463
assert node.equals_exact(shapely.Point([node_x, node_y]), 1e-6)
464464

465465

466-
def test_face_centres_from_variables():
466+
def test_face_centres_from_variables() -> None:
467467
dataset = make_dataset(width=3, make_face_coordinates=True)
468468
convention: UGrid = dataset.ems
469-
face_grid = dataset.ems.grids['face']
469+
face_grid = convention.grids['face']
470470

471-
face_centres = convention.default_grid.centroid
471+
coordinates = face_grid.centroid_coordinates
472472
lons = dataset['Mesh2_face_x'].values
473473
lats = dataset['Mesh2_face_y'].values
474474
for face in range(dataset.sizes['nMesh2_face']):
475475
lon = lons[face]
476476
lat = lats[face]
477477
linear_index = face_grid.ravel_index((UGridKind.face, face))
478-
point = face_centres[linear_index]
479-
numpy.testing.assert_equal([point.x, point.y], [lon, lat])
478+
numpy.testing.assert_equal(coordinates[linear_index], [lon, lat])
480479

481480

482-
def test_face_centres_from_centroids():
481+
def test_face_centres_from_centroids() -> None:
483482
dataset = make_dataset(width=3, make_face_coordinates=False)
484483
convention: UGrid = dataset.ems
485-
face_grid = dataset.ems.grids['face']
486-
face_centres = face_grid.centroid
484+
face_grid = convention.grids['face']
485+
coordinates = face_grid.centroid_coordinates
487486

488487
for face in range(dataset.sizes['nMesh2_face']):
489488
linear_index = convention.ravel_index((UGridKind.face, face))
490489
polygon = face_grid.geometry[linear_index]
491490
lon, lat = polygon.centroid.coords[0]
492-
point = face_centres[linear_index]
493-
numpy.testing.assert_equal([point.x, point.y], [lon, lat])
491+
numpy.testing.assert_equal(coordinates[linear_index], [lon, lat])
494492

495493

496494
def test_bounds(datasets: pathlib.Path):

tests/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,8 +456,8 @@ def plot_geometry(
456456

457457
dataset.ems.plot_geometry(axes)
458458
grid = dataset.ems.default_grid
459-
centroid = shapely.get_coordinates(grid.centroid)
460-
axes.scatter(centroid[:, 0], centroid[:, 1], c='red')
459+
x, y = grid.centroid_coordinates.T
460+
axes.scatter(x, y, c='red')
461461

462462
if title is not None:
463463
axes.set_title(title)

0 commit comments

Comments
 (0)