818 lines
24 KiB
Python
818 lines
24 KiB
Python
import numpy as np
|
|
|
|
try:
|
|
import matplotlib.pyplot as plt
|
|
except:
|
|
plt = None
|
|
from flopy.plot import plotutil
|
|
from flopy.utils import geometry
|
|
from flopy.plot.crosssection import _CrossSection
|
|
import warnings
|
|
|
|
warnings.filterwarnings("ignore", category=RuntimeWarning)
|
|
|
|
|
|
class _VertexCrossSection(_CrossSection):
|
|
"""
|
|
Class to create a cross section of the model from a vertex
|
|
discretization.
|
|
|
|
Class is not to be instantiated by the user!
|
|
|
|
Parameters
|
|
----------
|
|
ax : matplotlib.pyplot axis
|
|
The plot axis. If not provided it, plt.gca() will be used.
|
|
model : flopy.modflow object
|
|
flopy model object. (Default is None)
|
|
modelgrid : flopy.discretization.VertexGrid
|
|
Vertex model grid object
|
|
line : dict
|
|
Dictionary with either "row", "column", or "line" key. If key
|
|
is "row" or "column" key value should be the zero-based row or
|
|
column index for cross-section. If key is "line" value should
|
|
be an array of (x, y) tuples with vertices of cross-section.
|
|
Vertices should be in map coordinates consistent with xul,
|
|
yul, and rotation.
|
|
extent : tuple of floats
|
|
(xmin, xmax, ymin, ymax) will be used to specify axes limits. If None
|
|
then these will be calculated based on grid, coordinates, and rotation.
|
|
geographic_coords : bool
|
|
boolean flag to allow the user to plot cross section lines in
|
|
geographic coordinates. If False (default), cross section is plotted
|
|
as the distance along the cross section line.
|
|
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
ax=None,
|
|
model=None,
|
|
modelgrid=None,
|
|
line=None,
|
|
extent=None,
|
|
geographic_coords=False,
|
|
):
|
|
super(_VertexCrossSection, self).__init__(
|
|
ax=ax,
|
|
model=model,
|
|
modelgrid=modelgrid,
|
|
geographic_coords=geographic_coords,
|
|
)
|
|
|
|
if line is None:
|
|
err_msg = "line must be specified."
|
|
raise Exception(err_msg)
|
|
|
|
linekeys = [linekeys.lower() for linekeys in list(line.keys())]
|
|
|
|
if len(linekeys) != 1:
|
|
err_msg = (
|
|
"Either row, column, or line must be specified "
|
|
"in line dictionary.\nkeys specified: "
|
|
)
|
|
for k in linekeys:
|
|
err_msg += "{} ".format(k)
|
|
raise Exception(err_msg)
|
|
|
|
elif "line" not in linekeys:
|
|
err_msg = (
|
|
"only line can be specified in line dictionary "
|
|
"for vertex Discretization"
|
|
)
|
|
raise AssertionError(err_msg)
|
|
|
|
onkey = linekeys[0]
|
|
|
|
if ax is None:
|
|
self.ax = plt.gca()
|
|
else:
|
|
self.ax = ax
|
|
|
|
self.direction = "xy"
|
|
# convert pts list to a numpy array
|
|
verts = line[onkey]
|
|
xp = []
|
|
yp = []
|
|
for [v1, v2] in verts:
|
|
xp.append(v1)
|
|
yp.append(v2)
|
|
|
|
# unrotate and untransform modelgrid into modflow coordinates!
|
|
xp, yp = geometry.transform(
|
|
xp,
|
|
yp,
|
|
self.mg.xoffset,
|
|
self.mg.yoffset,
|
|
self.mg.angrot_radians,
|
|
inverse=True,
|
|
)
|
|
|
|
self.xcellcenters, self.ycellcenters = geometry.transform(
|
|
self.mg.xcellcenters,
|
|
self.mg.ycellcenters,
|
|
self.mg.xoffset,
|
|
self.mg.yoffset,
|
|
self.mg.angrot_radians,
|
|
inverse=True,
|
|
)
|
|
|
|
try:
|
|
self.xvertices, self.yvertices = geometry.transform(
|
|
self.mg.xvertices,
|
|
self.mg.yvertices,
|
|
self.mg.xoffset,
|
|
self.mg.yoffset,
|
|
self.mg.angrot_radians,
|
|
inverse=True,
|
|
)
|
|
except ValueError:
|
|
# irregular shapes in vertex grid ie. squares and triangles
|
|
(
|
|
xverts,
|
|
yverts,
|
|
) = plotutil.UnstructuredPlotUtilities.irregular_shape_patch(
|
|
self.mg.xvertices, self.mg.yvertices
|
|
)
|
|
|
|
self.xvertices, self.yvertices = geometry.transform(
|
|
xverts,
|
|
yverts,
|
|
self.mg.xoffset,
|
|
self.mg.yoffset,
|
|
self.mg.angrot_radians,
|
|
inverse=True,
|
|
)
|
|
|
|
pts = [(xt, yt) for xt, yt in zip(xp, yp)]
|
|
self.pts = np.array(pts)
|
|
|
|
# get points along the line
|
|
|
|
self.xypts = plotutil.UnstructuredPlotUtilities.line_intersect_grid(
|
|
self.pts, self.xvertices, self.yvertices
|
|
)
|
|
|
|
if len(self.xypts) < 2:
|
|
s = "cross-section cannot be created\n."
|
|
s += " less than 2 points intersect the model grid\n"
|
|
s += " {} points intersect the grid.".format(len(self.xypts))
|
|
raise Exception(s)
|
|
|
|
if self.geographic_coords:
|
|
# transform back to geographic coordinates
|
|
xypts = {}
|
|
for nn, pt in self.xypts.items():
|
|
xp = [t[0] for t in pt]
|
|
yp = [t[1] for t in pt]
|
|
xp, yp = geometry.transform(
|
|
xp,
|
|
yp,
|
|
self.mg.xoffset,
|
|
self.mg.yoffset,
|
|
self.mg.angrot_radians,
|
|
)
|
|
xypts[nn] = [(xt, yt) for xt, yt in zip(xp, yp)]
|
|
|
|
self.xypts = xypts
|
|
|
|
top = self.mg.top
|
|
top.shape = (1, -1)
|
|
botm = self.mg.botm
|
|
nlay = len(botm)
|
|
ncpl = self.mg.ncpl
|
|
|
|
elev = list(top.copy())
|
|
for k in range(nlay):
|
|
elev.append(botm[k, :])
|
|
|
|
self.elev = np.array(elev)
|
|
|
|
self.idomain = self.mg.idomain
|
|
if self.mg.idomain is None:
|
|
self.idomain = np.ones((nlay, ncpl), dtype=int)
|
|
|
|
# choose a projection direction based on maximum information
|
|
xpts = []
|
|
ypts = []
|
|
for nn, verts in self.xypts.items():
|
|
for v in verts:
|
|
xpts.append(v[0])
|
|
ypts.append(v[1])
|
|
|
|
if np.max(xpts) - np.min(xpts) > np.max(ypts) - np.min(ypts):
|
|
self.direction = "x"
|
|
else:
|
|
self.direction = "y"
|
|
|
|
# make vertex array based on projection direction
|
|
self.projpts = self.set_zpts(None)
|
|
|
|
# Create cross-section extent
|
|
if extent is None:
|
|
self.extent = self.get_extent()
|
|
else:
|
|
self.extent = extent
|
|
|
|
self.layer0 = None
|
|
self.layer1 = None
|
|
|
|
self.d = {
|
|
i: (np.min(np.array(v).T[0]), np.max(np.array(v).T[0]))
|
|
for i, v in sorted(self.projpts.items())
|
|
}
|
|
|
|
self.xpts = None
|
|
self.active = None
|
|
self.ncb = None
|
|
self.laycbd = None
|
|
self.zpts = None
|
|
self.xcentergrid = None
|
|
self.zcentergrid = None
|
|
self.geographic_xcentergrid = None
|
|
self.geographic_xpts = None
|
|
|
|
# Set axis limits
|
|
self.ax.set_xlim(self.extent[0], self.extent[1])
|
|
self.ax.set_ylim(self.extent[2], self.extent[3])
|
|
|
|
def plot_array(self, a, masked_values=None, head=None, **kwargs):
|
|
"""
|
|
Plot a three-dimensional array as a patch collection.
|
|
|
|
Parameters
|
|
----------
|
|
a : numpy.ndarray
|
|
Three-dimensional array to plot.
|
|
masked_values : iterable of floats, ints
|
|
Values to mask.
|
|
head : numpy.ndarray
|
|
Three-dimensional array to set top of patches to the minimum
|
|
of the top of a layer or the head value. Used to create
|
|
patches that conform to water-level elevations.
|
|
**kwargs : dictionary
|
|
keyword arguments passed to matplotlib.collections.PatchCollection
|
|
|
|
Returns
|
|
-------
|
|
patches : matplotlib.collections.PatchCollection
|
|
|
|
"""
|
|
if "ax" in kwargs:
|
|
ax = kwargs.pop("ax")
|
|
else:
|
|
ax = self.ax
|
|
|
|
if not isinstance(a, np.ndarray):
|
|
a = np.array(a)
|
|
|
|
if a.ndim > 1:
|
|
a = np.ravel(a)
|
|
|
|
if masked_values is not None:
|
|
for mval in masked_values:
|
|
a = np.ma.masked_values(a, mval)
|
|
|
|
if isinstance(head, np.ndarray):
|
|
projpts = self.set_zpts(np.ravel(head))
|
|
else:
|
|
projpts = self.projpts
|
|
|
|
pc = self.get_grid_patch_collection(projpts, a, **kwargs)
|
|
if pc is not None:
|
|
ax.add_collection(pc)
|
|
ax.set_xlim(self.extent[0], self.extent[1])
|
|
ax.set_ylim(self.extent[2], self.extent[3])
|
|
|
|
return pc
|
|
|
|
def plot_surface(self, a, masked_values=None, **kwargs):
|
|
"""
|
|
Plot a two- or three-dimensional array as line(s).
|
|
|
|
Parameters
|
|
----------
|
|
a : numpy.ndarray
|
|
Two- or three-dimensional array to plot.
|
|
masked_values : iterable of floats, ints
|
|
Values to mask.
|
|
**kwargs : dictionary
|
|
keyword arguments passed to matplotlib.pyplot.plot
|
|
|
|
Returns
|
|
-------
|
|
plot : list containing matplotlib.plot objects
|
|
"""
|
|
if "ax" in kwargs:
|
|
ax = kwargs.pop("ax")
|
|
else:
|
|
ax = self.ax
|
|
|
|
if "color" in kwargs:
|
|
color = kwargs.pop("color")
|
|
elif "c" in kwargs:
|
|
color = kwargs.pop("c")
|
|
else:
|
|
color = "b"
|
|
|
|
if not isinstance(a, np.ndarray):
|
|
a = np.array(a)
|
|
|
|
if a.ndim > 1:
|
|
a = np.ravel(a)
|
|
|
|
if a.size % self.mg.ncpl != 0:
|
|
raise AssertionError("Array size must be a multiple of ncpl")
|
|
|
|
if masked_values is not None:
|
|
for mval in masked_values:
|
|
a = np.ma.masked_values(a, mval)
|
|
|
|
data = []
|
|
lay_data = []
|
|
d = []
|
|
lay_d = []
|
|
dim = self.mg.ncpl
|
|
for cell, verts in sorted(self.projpts.items()):
|
|
|
|
if cell >= a.size:
|
|
continue
|
|
elif np.isnan(a[cell]):
|
|
continue
|
|
elif a[cell] is np.ma.masked:
|
|
continue
|
|
|
|
if cell >= dim:
|
|
data.append(lay_data)
|
|
d.append(lay_d)
|
|
dim += self.mg.ncpl
|
|
lay_data = [(a[cell], a[cell])]
|
|
lay_d = [self.d[cell]]
|
|
else:
|
|
lay_data.append((a[cell], a[cell]))
|
|
lay_d.append(self.d[cell])
|
|
|
|
if lay_data:
|
|
data.append(lay_data)
|
|
d.append(lay_d)
|
|
|
|
data = np.array(data)
|
|
d = np.array(d)
|
|
|
|
plot = []
|
|
for k in range(data.shape[0]):
|
|
if ax is None:
|
|
ax = plt.gca()
|
|
for ix, _ in enumerate(data[k]):
|
|
ax.plot(d[k, ix], data[k, ix], color=color, **kwargs)
|
|
|
|
ax.set_xlim(self.extent[0], self.extent[1])
|
|
ax.set_ylim(self.extent[2], self.extent[3])
|
|
plot.append(ax)
|
|
|
|
return plot
|
|
|
|
def plot_fill_between(
|
|
self,
|
|
a,
|
|
colors=("blue", "red"),
|
|
masked_values=None,
|
|
head=None,
|
|
**kwargs
|
|
):
|
|
"""
|
|
Plot a three-dimensional array as lines.
|
|
|
|
Parameters
|
|
----------
|
|
a : numpy.ndarray
|
|
Three-dimensional array to plot.
|
|
colors: list
|
|
matplotlib fill colors, two required
|
|
masked_values : iterable of floats, ints
|
|
Values to mask.
|
|
head : numpy.ndarray
|
|
Three-dimensional array to set top of patches to the minimum
|
|
of the top of a layer or the head value. Used to create
|
|
patches that conform to water-level elevations.
|
|
**kwargs : dictionary
|
|
keyword arguments passed to matplotlib.pyplot.plot
|
|
|
|
Returns
|
|
-------
|
|
plot : list containing matplotlib.fillbetween objects
|
|
|
|
"""
|
|
if "ax" in kwargs:
|
|
ax = kwargs.pop("ax")
|
|
else:
|
|
ax = self.ax
|
|
|
|
if not isinstance(a, np.ndarray):
|
|
a = np.array(a)
|
|
|
|
a = np.ravel(a)
|
|
|
|
if masked_values is not None:
|
|
for mval in masked_values:
|
|
a = np.ma.masked_values(a, mval)
|
|
|
|
if isinstance(head, np.ndarray):
|
|
projpts = self.set_zpts(head)
|
|
else:
|
|
projpts = self.projpts
|
|
|
|
plot = []
|
|
for cell, verts in sorted(projpts.items()):
|
|
if cell >= a.size:
|
|
continue
|
|
elif np.isnan(a[cell]):
|
|
continue
|
|
elif a[cell] is np.ma.masked:
|
|
continue
|
|
|
|
x = list(set(np.array(verts.T[0])))
|
|
y1 = np.max(np.array(verts.T[1]))
|
|
y2 = np.min(np.array(verts.T[1]))
|
|
v = a[cell]
|
|
|
|
if v > y1:
|
|
v = y1
|
|
|
|
elif v < y2:
|
|
v = y2
|
|
|
|
v = [v] * len(x)
|
|
|
|
x = np.array(x)
|
|
plot.append(ax.fill_between(x, y1, v, color=colors[0], **kwargs))
|
|
plot.append(ax.fill_between(x, v, y2, color=colors[1], **kwargs))
|
|
|
|
return plot
|
|
|
|
def contour_array(self, a, masked_values=None, head=None, **kwargs):
|
|
"""
|
|
Contour a two-dimensional array.
|
|
|
|
Parameters
|
|
----------
|
|
a : numpy.ndarray
|
|
Three-dimensional array to plot.
|
|
masked_values : iterable of floats, ints
|
|
Values to mask.
|
|
head : numpy.ndarray
|
|
Three-dimensional array to set top of patches to the minimum
|
|
of the top of a layer or the head value. Used to create
|
|
patches that conform to water-level elevations.
|
|
**kwargs : dictionary
|
|
keyword arguments passed to matplotlib.pyplot.contour
|
|
|
|
Returns
|
|
-------
|
|
contour_set : matplotlib.pyplot.contour
|
|
|
|
"""
|
|
if plt is None:
|
|
err_msg = (
|
|
"matplotlib must be installed to " + "use contour_array()"
|
|
)
|
|
raise ImportError(err_msg)
|
|
else:
|
|
import matplotlib.tri as tri
|
|
|
|
if not isinstance(a, np.ndarray):
|
|
a = np.array(a)
|
|
|
|
if a.ndim > 1:
|
|
a = np.ravel(a)
|
|
|
|
if "ax" in kwargs:
|
|
ax = kwargs.pop("ax")
|
|
else:
|
|
ax = self.ax
|
|
|
|
xcenters = [
|
|
np.mean(np.array(v).T[0]) for i, v in sorted(self.projpts.items())
|
|
]
|
|
|
|
plotarray = np.array([a[cell] for cell in sorted(self.projpts)])
|
|
|
|
# work around for tri-contour ignore vmin & vmax
|
|
# necessary for the tri-contour NaN issue fix
|
|
if "levels" not in kwargs:
|
|
if "vmin" not in kwargs:
|
|
vmin = np.nanmin(plotarray)
|
|
else:
|
|
vmin = kwargs.pop("vmin")
|
|
if "vmax" not in kwargs:
|
|
vmax = np.nanmax(plotarray)
|
|
else:
|
|
vmax = kwargs.pop("vmax")
|
|
|
|
levels = np.linspace(vmin, vmax, 7)
|
|
kwargs["levels"] = levels
|
|
|
|
# workaround for tri-contour nan issue
|
|
plotarray[np.isnan(plotarray)] = -(2 ** 31)
|
|
if masked_values is None:
|
|
masked_values = [-(2 ** 31)]
|
|
else:
|
|
masked_values = list(masked_values)
|
|
if -(2 ** 31) not in masked_values:
|
|
masked_values.append(-(2 ** 31))
|
|
|
|
ismasked = None
|
|
if masked_values is not None:
|
|
for mval in masked_values:
|
|
if ismasked is None:
|
|
ismasked = np.isclose(plotarray, mval)
|
|
else:
|
|
t = np.isclose(plotarray, mval)
|
|
ismasked += t
|
|
|
|
if isinstance(head, np.ndarray):
|
|
zcenters = self.set_zcentergrid(np.ravel(head))
|
|
else:
|
|
zcenters = [
|
|
np.mean(np.array(v).T[1])
|
|
for i, v in sorted(self.projpts.items())
|
|
]
|
|
|
|
plot_triplot = False
|
|
if "plot_triplot" in kwargs:
|
|
plot_triplot = kwargs.pop("plot_triplot")
|
|
|
|
if "extent" in kwargs:
|
|
extent = kwargs.pop("extent")
|
|
|
|
idx = (
|
|
(xcenters >= extent[0])
|
|
& (xcenters <= extent[1])
|
|
& (zcenters >= extent[2])
|
|
& (zcenters <= extent[3])
|
|
)
|
|
plotarray = plotarray[idx].flatten()
|
|
xcenters = xcenters[idx].flatten()
|
|
zcenters = zcenters[idx].flatten()
|
|
|
|
triang = tri.Triangulation(xcenters, zcenters)
|
|
|
|
if ismasked is not None:
|
|
ismasked = ismasked.flatten()
|
|
mask = np.any(
|
|
np.where(ismasked[triang.triangles], True, False), axis=1
|
|
)
|
|
triang.set_mask(mask)
|
|
|
|
contour_set = ax.tricontour(triang, plotarray, **kwargs)
|
|
|
|
if plot_triplot:
|
|
ax.triplot(triang, color="black", marker="o", lw=0.75)
|
|
|
|
ax.set_xlim(self.extent[0], self.extent[1])
|
|
ax.set_ylim(self.extent[2], self.extent[3])
|
|
|
|
return contour_set
|
|
|
|
def plot_inactive(self):
|
|
raise NotImplementedError(
|
|
"Function must be called in PlotCrossSection"
|
|
)
|
|
|
|
def plot_ibound(self):
|
|
raise NotImplementedError(
|
|
"Function must be called in PlotCrossSection"
|
|
)
|
|
|
|
def plot_grid(self):
|
|
raise NotImplementedError(
|
|
"Function must be called in PlotCrossSection"
|
|
)
|
|
|
|
def plot_bc(self):
|
|
raise NotImplementedError(
|
|
"Function must be called in PlotCrossSection"
|
|
)
|
|
|
|
def plot_specific_discharge(self):
|
|
raise NotImplementedError(
|
|
"Function must be called in PlotCrossSection"
|
|
)
|
|
|
|
def plot_discharge(self):
|
|
raise NotImplementedError(
|
|
"plot_specific_discharge must be " "used for VertexGrid models"
|
|
)
|
|
|
|
@classmethod
|
|
def get_grid_patch_collection(cls, projpts, plotarray, **kwargs):
|
|
"""
|
|
Get a PatchCollection of plotarray in unmasked cells
|
|
|
|
Parameters
|
|
----------
|
|
projpts : dict
|
|
dictionary defined by node number which contains model patch vertices.
|
|
plotarray : numpy.ndarray
|
|
One-dimensional array to attach to the Patch Collection.
|
|
**kwargs : dictionary
|
|
keyword arguments passed to matplotlib.collections.PatchCollection
|
|
|
|
Returns
|
|
-------
|
|
patches : matplotlib.collections.PatchCollection
|
|
|
|
"""
|
|
if plt is None:
|
|
err_msg = (
|
|
"matplotlib must be installed to "
|
|
+ "use get_grid_patch_collection()"
|
|
)
|
|
raise ImportError(err_msg)
|
|
else:
|
|
from matplotlib.patches import Polygon
|
|
from matplotlib.collections import PatchCollection
|
|
|
|
if "vmin" in kwargs:
|
|
vmin = kwargs.pop("vmin")
|
|
else:
|
|
vmin = None
|
|
if "vmax" in kwargs:
|
|
vmax = kwargs.pop("vmax")
|
|
else:
|
|
vmax = None
|
|
|
|
rectcol = []
|
|
data = []
|
|
for cell, verts in sorted(projpts.items()):
|
|
verts = plotutil.UnstructuredPlotUtilities.arctan2(np.array(verts))
|
|
|
|
if np.isnan(plotarray[cell]):
|
|
continue
|
|
elif plotarray[cell] is np.ma.masked:
|
|
continue
|
|
|
|
rectcol.append(Polygon(verts, closed=True))
|
|
data.append(plotarray[cell])
|
|
|
|
if len(rectcol) > 0:
|
|
patches = PatchCollection(rectcol, **kwargs)
|
|
patches.set_array(np.array(data))
|
|
patches.set_clim(vmin, vmax)
|
|
|
|
else:
|
|
patches = None
|
|
|
|
return patches
|
|
|
|
def get_grid_line_collection(self, **kwargs):
|
|
"""
|
|
Get a LineCollection of the grid
|
|
|
|
Parameters
|
|
----------
|
|
**kwargs : dictionary
|
|
keyword arguments passed to matplotlib.collections.LineCollection
|
|
|
|
Returns
|
|
-------
|
|
linecollection : matplotlib.collections.LineCollection
|
|
"""
|
|
if plt is None:
|
|
err_msg = (
|
|
"matplotlib must be installed to "
|
|
+ "use get_grid_line_collection()"
|
|
)
|
|
raise ImportError(err_msg)
|
|
else:
|
|
from matplotlib.patches import Polygon
|
|
from matplotlib.collections import PatchCollection
|
|
|
|
color = "grey"
|
|
if "ec" in kwargs:
|
|
color = kwargs.pop("ec")
|
|
if color in kwargs:
|
|
color = kwargs.pop("color")
|
|
|
|
rectcol = []
|
|
for _, verts in sorted(self.projpts.items()):
|
|
verts = plotutil.UnstructuredPlotUtilities.arctan2(np.array(verts))
|
|
|
|
rectcol.append(Polygon(verts, closed=True))
|
|
|
|
if len(rectcol) > 0:
|
|
patches = PatchCollection(
|
|
rectcol, edgecolor=color, facecolor="none", **kwargs
|
|
)
|
|
else:
|
|
patches = None
|
|
|
|
return patches
|
|
|
|
def set_zpts(self, vs):
|
|
"""
|
|
Get an array of projection vertices corrected for
|
|
elevations based on minimum of cell elevation
|
|
(self.elev) or passed vs numpy.ndarray
|
|
|
|
Parameters
|
|
----------
|
|
vs : numpy.ndarray
|
|
Two-dimensional array to plot.
|
|
|
|
Returns
|
|
-------
|
|
zpts : dict
|
|
|
|
"""
|
|
# make vertex array based on projection direction
|
|
if vs is not None:
|
|
if not isinstance(vs, np.ndarray):
|
|
vs = np.array(vs)
|
|
|
|
if self.direction == "x":
|
|
xyix = 0
|
|
else:
|
|
xyix = -1
|
|
|
|
projpts = {}
|
|
for k in range(1, self.mg.nlay + 1):
|
|
top = self.elev[k - 1, :]
|
|
botm = self.elev[k, :]
|
|
adjnn = (k - 1) * self.mg.ncpl
|
|
d0 = 0
|
|
for nn, verts in sorted(
|
|
self.xypts.items(), key=lambda q: q[-1][xyix][xyix]
|
|
):
|
|
if vs is None:
|
|
t = top[nn]
|
|
else:
|
|
t = vs[nn]
|
|
if top[nn] < vs[nn]:
|
|
t = top[nn]
|
|
b = botm[nn]
|
|
if self.geographic_coords:
|
|
if self.direction == "x":
|
|
projt = [(v[0], t) for v in verts]
|
|
projb = [(v[0], b) for v in verts]
|
|
else:
|
|
projt = [(v[1], t) for v in verts]
|
|
projb = [(v[1], b) for v in verts]
|
|
else:
|
|
verts = np.array(verts).T
|
|
a2 = (np.max(verts[0]) - np.min(verts[0])) ** 2
|
|
b2 = (np.max(verts[1]) - np.min(verts[1])) ** 2
|
|
c = np.sqrt(a2 + b2)
|
|
d1 = d0 + c
|
|
projt = [(d0, t), (d1, t)]
|
|
projb = [(d0, b), (d1, b)]
|
|
d0 += c
|
|
|
|
projpts[nn + adjnn] = projt + projb
|
|
|
|
return projpts
|
|
|
|
def set_zcentergrid(self, vs, kstep=1):
|
|
"""
|
|
Get an array of z elevations at the center of a cell that is based
|
|
on minimum of cell top elevation (self.elev) or passed vs numpy.ndarray
|
|
|
|
Parameters
|
|
----------
|
|
vs : numpy.ndarray
|
|
Three-dimensional array to plot.
|
|
|
|
Returns
|
|
-------
|
|
zcentergrid : numpy.ndarray
|
|
|
|
"""
|
|
verts = self.set_zpts(vs)
|
|
zcenters = [
|
|
np.mean(np.array(v).T[1])
|
|
for i, v in sorted(verts.items())
|
|
if (i // self.mg.ncpl) % kstep == 0
|
|
]
|
|
return zcenters
|
|
|
|
def get_extent(self):
|
|
"""
|
|
Get the extent of the rotated and offset grid
|
|
|
|
Returns
|
|
-------
|
|
tuple : (xmin, xmax, ymin, ymax)
|
|
"""
|
|
xpts = []
|
|
for _, verts in self.projpts.items():
|
|
for v in verts:
|
|
xpts.append(v[0])
|
|
|
|
xmin = np.min(xpts)
|
|
xmax = np.max(xpts)
|
|
|
|
ymin = np.min(self.elev)
|
|
ymax = np.max(self.elev)
|
|
|
|
return (xmin, xmax, ymin, ymax)
|