Skip to content

Commit 184062a

Browse files
committed
Add Convention.triangulate() function
1 parent e58e5a0 commit 184062a

File tree

5 files changed

+370
-184
lines changed

5 files changed

+370
-184
lines changed

src/emsarray/conventions/_base.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from emsarray import plot as _plot
2020
from emsarray import utils
2121
from emsarray.exceptions import InvalidGeometryWarning, NoSuchCoordinateError
22-
from emsarray.operations import depth, point_extraction
22+
from emsarray.operations import depth, point_extraction, triangulate
2323
from emsarray.operations.cache import hash_attributes, hash_int, hash_string
2424
from emsarray.state import State
2525
from emsarray.types import Bounds, DataArrayOrName, Pathish
@@ -1949,6 +1949,51 @@ def hash_geometry(self, hash: "hashlib._Hash") -> None:
19491949
# Hash dataset attributes
19501950
hash_attributes(hash, data_array.attrs)
19511951

1952+
def make_triangulation(self) -> triangulate.Triangulation[GridKind]:
1953+
"""
1954+
Triangulates the polygons in the dataset.
1955+
Subclasses may have improved implementations.
1956+
1957+
This requires the dataset to have a grid with polygonal geometry.
1958+
If there is an additional grid corresponding to the vertices of the polygons
1959+
then subclasses should use this information to inform the triangulation.
1960+
1961+
Returns
1962+
-------
1963+
tuple of vertices, triangles, and `cell_indexes`
1964+
A tuple of three numpy arrays is returned,
1965+
containing vertices, triangles, and cell indexes respectively.
1966+
1967+
`vertices` is a numpy array of shape (V, 2)
1968+
where V is the number of unique vertices in the dataset.
1969+
The vertex coordinates are in (x, y) or (lon, lat) order.
1970+
If the dataset has a vertex grid associated with the polygons
1971+
then this array replicates that data.
1972+
1973+
`triangles` is a numpy array of shape (T, 3)
1974+
where T is the number of triangles in the dataset.
1975+
Each triangle is a set of three vertex indexes.
1976+
1977+
`cell_indexes` is a numpy list of length T.
1978+
Each entry indicates which polygon from the dataset a triangle is a part of.
1979+
1980+
See also
1981+
--------
1982+
:func:`~emsarray.operations.triangulate.triangulate`
1983+
"""
1984+
grid = self.default_grid
1985+
if not issubclass(grid.geometry_type, shapely.Polygon):
1986+
raise ValueError("Can not triangulate a dataset that does not have polygonal geometry")
1987+
polygons = grid.geometry
1988+
vertices = triangulate.find_unique_vertices(polygons)
1989+
polygon_vertex_indexes = triangulate.polygons_to_vertex_indexes(polygons, vertices)
1990+
vertex_coordinates, triangles, face_indexes = triangulate.triangulate(vertices, polygons, polygon_vertex_indexes)
1991+
return triangulate.Triangulation(
1992+
vertices=vertex_coordinates,
1993+
triangles=triangles,
1994+
face_indexes=face_indexes,
1995+
face_grid_kind=grid.grid_kind)
1996+
19521997

19531998
type DimensionIndex[GridKind] = tuple[GridKind, *tuple[int, ...]]
19541999

src/emsarray/conventions/arakawa_c.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from xarray.core.dataset import DatasetCoordinates
2020

2121
from emsarray import masking, utils
22+
from emsarray.operations import triangulate
2223
from emsarray.types import Pathish
2324

2425
from ._base import DimensionConvention, Specificity
@@ -405,6 +406,24 @@ def make_clip_mask(
405406
def apply_clip_mask(self, clip_mask: xarray.Dataset, work_dir: Pathish) -> xarray.Dataset:
406407
return masking.mask_grid_dataset(self.dataset, clip_mask, work_dir)
407408

409+
def make_triangulation(self) -> triangulate.Triangulation:
410+
vertices = self.grids[ArakawaCGridKind.node].geometry
411+
polygons = self.grids[ArakawaCGridKind.face].geometry
412+
413+
j_size, i_size = self.node.shape
414+
xx, yy = numpy.meshgrid(numpy.arange(i_size - 1), numpy.arange(j_size - 1) * i_size)
415+
bottom_left_indexes = (xx + yy).flatten()
416+
offsets = numpy.array([0, 1, i_size + 1, i_size])
417+
polygon_vertex_indexes = bottom_left_indexes[:, None].repeat(4, axis=1) + offsets
418+
419+
vertex_coordinates, triangles, face_indexes = triangulate.triangulate(vertices, polygons, polygon_vertex_indexes)
420+
return triangulate.Triangulation[ArakawaCGridKind](
421+
vertices=vertex_coordinates,
422+
triangles=triangles,
423+
face_indexes=face_indexes,
424+
face_grid_kind=ArakawaCGridKind.face,
425+
vertex_grid_kind=ArakawaCGridKind.node)
426+
408427

409428
def c_mask_from_centres(
410429
face_mask: numpy.ndarray,

src/emsarray/conventions/ugrid.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from emsarray.exceptions import (
2727
ConventionViolationError, ConventionViolationWarning
2828
)
29+
from emsarray.operations import triangulate
2930
from emsarray.types import Bounds, Pathish
3031

3132
from ._base import DimensionConvention, Specificity
@@ -1407,3 +1408,15 @@ def drop_geometry(self) -> xarray.Dataset:
14071408
dataset = super().drop_geometry()
14081409
dataset.attrs.pop('Conventions', None)
14091410
return dataset
1411+
1412+
def make_triangulation(self) -> triangulate.Triangulation:
1413+
vertices = self.grids[UGridKind.node].geometry
1414+
polygons = self.grids[UGridKind.face].geometry
1415+
polygon_vertex_indexes = self.topology.face_node_array
1416+
vertex_coordinates, triangles, face_indexes = triangulate.triangulate(vertices, polygons, polygon_vertex_indexes)
1417+
return triangulate.Triangulation[UGridKind](
1418+
vertices=vertex_coordinates,
1419+
triangles=triangles,
1420+
face_indexes=face_indexes,
1421+
face_grid_kind=UGridKind.face,
1422+
vertex_grid_kind=UGridKind.node)

0 commit comments

Comments
 (0)