"""Module for map plots of surfaces, using matplotlib."""
import matplotlib.pyplot as plt
import matplotlib.patches as mplp
from matplotlib import ticker
import numpy as np
import numpy.ma as ma
from xtgeo.common import XTGeoDialog
from .baseplot import BasePlot
xtg = XTGeoDialog()
logger = xtg.functionlogger(__name__)
[docs]class Map(BasePlot):
"""Class for plotting a map, using matplotlib."""
[docs] def __init__(self):
"""The constructor method for a Map object."""
super().__init__()
clsname = f"{type(self).__module__}.{type(self).__name__}"
logger.info(clsname)
self._wells = None
self._surface = None
self._tight = False
self._wfence = None
self._showok = True # to indicate if plot is OK to show
self._legendtitle = "Map"
# =========================================================================
# Properties
# =========================================================================
@property
def pagesize(self):
"""Returns page size."""
return self._pagesize
# =========================================================================
# Functions methods (public)
# =========================================================================
[docs] def plot_surface(
self,
surf,
minvalue=None,
maxvalue=None,
contourlevels=None,
xlabelrotation=None,
colormap=None,
logarithmic=False,
): # pylint: disable=too-many-statements
"""Input a surface and plot it."""
# need a deep copy to avoid changes in the original surf
logger.info("The key contourlevels %s is not in use", contourlevels)
usesurf = surf.copy()
if usesurf.yflip < 0:
usesurf.swapaxes()
if abs(surf.rotation) > 0.001:
usesurf.unrotate()
xi, yi, zi = usesurf.get_xyz_values()
zimask = ma.getmaskarray(zi).copy() # yes need a copy!
legendticks = None
if minvalue is not None and maxvalue is not None:
minv = float(minvalue)
maxv = float(maxvalue)
step = (maxv - minv) / 10.0
legendticks = []
for i in range(10 + 1):
llabel = float(f"{minv + step * i:9.4f}")
legendticks.append(llabel)
zi.unshare_mask()
zi[zi < minv] = minv
zi[zi > maxv] = maxv
# need to restore the mask:
zi.mask = zimask
# note use surf.min, not usesurf.min here ...
notetxt = (
"Note: map values are truncated from ["
+ str(surf.values.min())
+ ", "
+ str(surf.values.max())
+ "] "
+ "to interval ["
+ str(minvalue)
+ ", "
+ str(maxvalue)
+ "]"
)
self._fig.text(0.99, 0.02, notetxt, ha="right", va="center", fontsize=8)
logger.info("Legendticks: %s", legendticks)
if minvalue is None:
minvalue = usesurf.values.min()
if maxvalue is None:
maxvalue = usesurf.values.max()
# this will override current instance colormap locally, and is
# therefore reset afterwards
keepcolor = self.colormap
if colormap is not None:
self.colormap = colormap
levels = np.linspace(minvalue, maxvalue, self.contourlevels)
logger.debug("Number of contour levels: %s", levels)
plt.setp(self._ax.xaxis.get_majorticklabels(), rotation=xlabelrotation)
# zi = ma.masked_where(zimask, zi)
# zi = ma.masked_greater(zi, xtgeo.UNDEF_LIMIT)
logger.info("Current colormap is %s, requested is %s", self.colormap, colormap)
logger.info("Current colormap name is %s", self.colormap.name)
if ma.std(zi) > 1e-07:
uselevels = levels
else:
uselevels = 1
try:
if logarithmic is False:
locator = None
ticks = legendticks
im = self._ax.contourf(
xi, yi, zi, uselevels, locator=locator, cmap=self.colormap
)
else:
logger.info("use LogLocator")
locator = ticker.LogLocator()
ticks = None
uselevels = None
im = self._ax.contourf(xi, yi, zi, locator=locator, cmap=self.colormap)
self._fig.colorbar(im, ticks=ticks)
except ValueError as err:
logger.warning("Could not make plot: %s", err)
plt.gca().set_aspect("equal", adjustable="box")
self.colormap = keepcolor
[docs] def plot_faults(
self,
fpoly,
idname="POLY_ID",
color="k",
edgecolor="k",
alpha=0.7,
linewidth=0.8,
):
"""Plot the faults.
Args:
fpoly (object): A XTGeo Polygons object
idname (str): Name of column which has the faults ID
color (c): Fill color model c according to Matplotlib_
edgecolor (c): Edge color according to Matplotlib_
alpha (float): Degree of opacity
linewidth (float): Line width
.. _Matplotlib: http://matplotlib.org/api/colors_api.html
"""
aff = fpoly.dataframe.groupby(idname)
for name, _group in aff:
# make a dataframe sorted on faults (groupname)
myfault = aff.get_group(name)
# make a list [(X,Y) ...];
af = list(zip(myfault["X_UTME"].values, myfault["Y_UTMN"].values))
px = mplp.Polygon(af, alpha=alpha, color=color, ec=edgecolor, lw=linewidth)
if px.get_closed():
self._ax.add_artist(px)
else:
IOError(f"A polygon is not closed: {px}")
[docs] def plot_polygons(self, fpoly, idname="POLY_ID", color="k", linewidth=0.8):
"""Plot a polygons instance.
Args:
fpoly (object): A XTGeo Polygons object
idname (str): Name of column which has the faults ID
color (c): Line color model c according to Matplotlib_
linewidth (float): Line width
.. _Matplotlib: http://matplotlib.org/api/colors_api.html
"""
aff = fpoly.dataframe.groupby(idname)
for _name, group in aff:
# make a dataframe sorted on groupname
pname = fpoly.name
xarr = group[fpoly.xname].values
yarr = group[fpoly.yname].values
self._ax.plot(xarr, yarr, label=pname, lw=linewidth, color=color)
self._ax.legend()
[docs] def plot_points(self, points):
"""Plot a points set on the map.
This can be be useful e.g. for plotting the underlying point set
that makes a gridded map.
Args:
points (Points): A XTGeo Points object X Y VALUE
"""
# This function is "in prep"
dataframe = points.dataframe
self._ax.scatter(
dataframe["X_UTME"].values, dataframe["Y_UTMN"].values, marker="x"
)
[docs] def plot_wells(self, wells):
"""Plot wells on the map.
Args:
wells (Wells): A XTGeo Wells object (contains a number of Well
instances).
"""
for well in wells.wells:
dataframe = well.dataframe
xval = dataframe["X_UTME"].values
yval = dataframe["Y_UTMN"].values
self._ax.plot(xval, yval)
self._ax.annotate(well.name, xy=(xval[-1], yval[-1]))