123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- import numpy as np
- class Triangulation:
- """
- An unstructured triangular grid consisting of npoints points and
- ntri triangles. The triangles can either be specified by the user
- or automatically generated using a Delaunay triangulation.
- Parameters
- ----------
- x, y : array-like of shape (npoints)
- Coordinates of grid points.
- triangles : integer array-like of shape (ntri, 3), optional
- For each triangle, the indices of the three points that make
- up the triangle, ordered in an anticlockwise manner. If not
- specified, the Delaunay triangulation is calculated.
- mask : boolean array-like of shape (ntri), optional
- Which triangles are masked out.
- Attributes
- ----------
- edges : int array of shape (nedges, 2)
- See `~.Triangulation.edges`
- neighbors : int array of shape (ntri, 3)
- See `~.Triangulation.neighbors`
- mask : bool array of shape (ntri, 3)
- Masked out triangles.
- is_delaunay : bool
- Whether the Triangulation is a calculated Delaunay
- triangulation (where *triangles* was not specified) or not.
- Notes
- -----
- For a Triangulation to be valid it must not have duplicate points,
- triangles formed from colinear points, or overlapping triangles.
- """
- def __init__(self, x, y, triangles=None, mask=None):
- from matplotlib import _qhull
- self.x = np.asarray(x, dtype=np.float64)
- self.y = np.asarray(y, dtype=np.float64)
- if self.x.shape != self.y.shape or self.x.ndim != 1:
- raise ValueError("x and y must be equal-length 1-D arrays")
- self.mask = None
- self._edges = None
- self._neighbors = None
- self.is_delaunay = False
- if triangles is None:
- # No triangulation specified, so use matplotlib._qhull to obtain
- # Delaunay triangulation.
- self.triangles, self._neighbors = _qhull.delaunay(x, y)
- self.is_delaunay = True
- else:
- # Triangulation specified. Copy, since we may correct triangle
- # orientation.
- self.triangles = np.array(triangles, dtype=np.int32, order='C')
- if self.triangles.ndim != 2 or self.triangles.shape[1] != 3:
- raise ValueError('triangles must be a (?,3) array')
- if self.triangles.max() >= len(self.x):
- raise ValueError('triangles max element is out of bounds')
- if self.triangles.min() < 0:
- raise ValueError('triangles min element is out of bounds')
- if mask is not None:
- self.mask = np.asarray(mask, dtype=bool)
- if self.mask.shape != (self.triangles.shape[0],):
- raise ValueError('mask array must have same length as '
- 'triangles array')
- # Underlying C++ object is not created until first needed.
- self._cpp_triangulation = None
- # Default TriFinder not created until needed.
- self._trifinder = None
- def calculate_plane_coefficients(self, z):
- """
- Calculate plane equation coefficients for all unmasked triangles from
- the point (x, y) coordinates and specified z-array of shape (npoints).
- The returned array has shape (npoints, 3) and allows z-value at (x, y)
- position in triangle tri to be calculated using
- ``z = array[tri, 0] * x + array[tri, 1] * y + array[tri, 2]``.
- """
- return self.get_cpp_triangulation().calculate_plane_coefficients(z)
- @property
- def edges(self):
- """
- Return integer array of shape (nedges, 2) containing all edges of
- non-masked triangles.
- Each row defines an edge by it's start point index and end point
- index. Each edge appears only once, i.e. for an edge between points
- *i* and *j*, there will only be either *(i, j)* or *(j, i)*.
- """
- if self._edges is None:
- self._edges = self.get_cpp_triangulation().get_edges()
- return self._edges
- def get_cpp_triangulation(self):
- """
- Return the underlying C++ Triangulation object, creating it
- if necessary.
- """
- from matplotlib import _tri
- if self._cpp_triangulation is None:
- self._cpp_triangulation = _tri.Triangulation(
- self.x, self.y, self.triangles, self.mask, self._edges,
- self._neighbors, not self.is_delaunay)
- return self._cpp_triangulation
- def get_masked_triangles(self):
- """
- Return an array of triangles that are not masked.
- """
- if self.mask is not None:
- return self.triangles[~self.mask]
- else:
- return self.triangles
- @staticmethod
- def get_from_args_and_kwargs(*args, **kwargs):
- """
- Return a Triangulation object from the args and kwargs, and
- the remaining args and kwargs with the consumed values removed.
- There are two alternatives: either the first argument is a
- Triangulation object, in which case it is returned, or the args
- and kwargs are sufficient to create a new Triangulation to
- return. In the latter case, see Triangulation.__init__ for
- the possible args and kwargs.
- """
- if isinstance(args[0], Triangulation):
- triangulation, *args = args
- else:
- x, y, *args = args
- # Check triangles in kwargs then args.
- triangles = kwargs.pop('triangles', None)
- from_args = False
- if triangles is None and args:
- triangles = args[0]
- from_args = True
- if triangles is not None:
- try:
- triangles = np.asarray(triangles, dtype=np.int32)
- except ValueError:
- triangles = None
- if triangles is not None and (triangles.ndim != 2 or
- triangles.shape[1] != 3):
- triangles = None
- if triangles is not None and from_args:
- args = args[1:] # Consumed first item in args.
- # Check for mask in kwargs.
- mask = kwargs.pop('mask', None)
- triangulation = Triangulation(x, y, triangles, mask)
- return triangulation, args, kwargs
- def get_trifinder(self):
- """
- Return the default :class:`matplotlib.tri.TriFinder` of this
- triangulation, creating it if necessary. This allows the same
- TriFinder object to be easily shared.
- """
- if self._trifinder is None:
- # Default TriFinder class.
- from matplotlib.tri.trifinder import TrapezoidMapTriFinder
- self._trifinder = TrapezoidMapTriFinder(self)
- return self._trifinder
- @property
- def neighbors(self):
- """
- Return integer array of shape (ntri, 3) containing neighbor triangles.
- For each triangle, the indices of the three triangles that
- share the same edges, or -1 if there is no such neighboring
- triangle. ``neighbors[i, j]`` is the triangle that is the neighbor
- to the edge from point index ``triangles[i,j]`` to point index
- ``triangles[i,(j+1)%3]``.
- """
- if self._neighbors is None:
- self._neighbors = self.get_cpp_triangulation().get_neighbors()
- return self._neighbors
- def set_mask(self, mask):
- """
- Set or clear the mask array. This is either None, or a boolean
- array of shape (ntri).
- """
- if mask is None:
- self.mask = None
- else:
- self.mask = np.asarray(mask, dtype=bool)
- if self.mask.shape != (self.triangles.shape[0],):
- raise ValueError('mask array must have same length as '
- 'triangles array')
- # Set mask in C++ Triangulation.
- if self._cpp_triangulation is not None:
- self._cpp_triangulation.set_mask(self.mask)
- # Clear derived fields so they are recalculated when needed.
- self._edges = None
- self._neighbors = None
- # Recalculate TriFinder if it exists.
- if self._trifinder is not None:
- self._trifinder._initialize()
|