From 5da4da5e1effc4afb8d0db4f1c9e1b191b694219 Mon Sep 17 00:00:00 2001 From: Joshua Larsen Date: Fri, 21 May 2021 17:10:31 -0700 Subject: [PATCH 1/2] fix(map.py): fix slow plotting due to PatchCollections * changed plot_array() and plot_grid() to use pathcollection, quadmesh and linecollections * fixed get_coords issue with integer arrays --- autotest/t007_test.py | 16 +++---- flopy/discretization/grid.py | 29 +------------ flopy/discretization/structuredgrid.py | 26 ++++++++++++ flopy/discretization/unstructuredgrid.py | 12 +++--- flopy/discretization/vertexgrid.py | 28 ++++++++++++ flopy/plot/map.py | 54 +++++++++++------------- 6 files changed, 95 insertions(+), 70 deletions(-) diff --git a/autotest/t007_test.py b/autotest/t007_test.py index 2012c61e26..aeb95bc10a 100644 --- a/autotest/t007_test.py +++ b/autotest/t007_test.py @@ -1191,8 +1191,8 @@ def test_sr_with_Map(): plt.close() def check_vertices(): - xllp, yllp = pc._paths[780].vertices[3] - xulp, yulp = pc._paths[0].vertices[0] + xllp, yllp = pc._paths[0].vertices[0] + xulp, yulp = pc._paths[0].vertices[1] assert np.abs(xllp - xll) < 1e-6 assert np.abs(yllp - yll) < 1e-6 assert np.abs(xulp - xul) < 1e-6 @@ -1304,7 +1304,7 @@ def test_modelgrid_with_PlotMapView(): xll, yll, rotation = 500000.0, 2934000.0, 45.0 def check_vertices(): - xllp, yllp = pc._paths[780].vertices[3] + xllp, yllp = pc._paths[0].vertices[0] assert np.abs(xllp - xll) < 1e-6 assert np.abs(yllp - yll) < 1e-6 @@ -1334,7 +1334,7 @@ def check_vertices(): def test_mapview_plot_bc(): - from matplotlib.collections import QuadMesh, PatchCollection + from matplotlib.collections import QuadMesh, PathCollection import matplotlib.pyplot as plt sim_name = "mfsim.nam" @@ -1352,7 +1352,7 @@ def test_mapview_plot_bc(): raise AssertionError("Boundary condition was not drawn") for col in ax.collections: - if not isinstance(col, PatchCollection): + if not isinstance(col, (QuadMesh, PathCollection)): raise AssertionError("Unexpected collection type") plt.close() @@ -1370,7 +1370,7 @@ def test_mapview_plot_bc(): raise AssertionError("Boundary condition was not drawn") for col in ax.collections: - if not isinstance(col, PatchCollection): + if not isinstance(col, (QuadMesh, PathCollection)): raise AssertionError("Unexpected collection type") plt.close() @@ -1395,7 +1395,7 @@ def test_mapview_plot_bc(): raise AssertionError("Boundary condition was not drawn") for col in ax.collections: - if not isinstance(col, PatchCollection): + if not isinstance(col, (QuadMesh, PathCollection)): raise AssertionError("Unexpected collection type") plt.close() @@ -1413,7 +1413,7 @@ def test_mapview_plot_bc(): raise AssertionError("Boundary condition was not drawn") for col in ax.collections: - if not isinstance(col, PatchCollection): + if not isinstance(col, (QuadMesh, PathCollection)): raise AssertionError("Unexpected collection type") plt.close() diff --git a/flopy/discretization/grid.py b/flopy/discretization/grid.py index 2974ca2398..14ac7ca545 100644 --- a/flopy/discretization/grid.py +++ b/flopy/discretization/grid.py @@ -493,32 +493,7 @@ def cross_section_set_contour_arrays( @property def map_polygons(self): - """ - Get a list of matplotlib Polygon patches for plotting - - Returns - ------- - list of Polygon objects - """ - try: - from matplotlib.patches import Polygon - except ImportError: - raise ImportError("matplotlib required to use this method") - cache_index = "xyzgrid" - if ( - cache_index not in self._cache_dict - or self._cache_dict[cache_index].out_of_date - ): - self.xyzvertices - self._polygons = None - - if self._polygons is None: - self._polygons = [ - Polygon(self.get_cell_vertices(nn), closed=True) - for nn in range(self.ncpl) - ] - - return copy.copy(self._polygons) + raise NotImplementedError("must define map_polygons in child class") def get_plottable_layer_array(self, plotarray, layer): raise NotImplementedError( @@ -558,7 +533,7 @@ def get_coords(self, x, y): x = np.array(x) y = np.array(y) if not np.isscalar(x): - x, y = x.copy(), y.copy() + x, y = x.astype(float, copy=True), y.astype(float, copy=True) x += self._xoff y += self._yoff diff --git a/flopy/discretization/structuredgrid.py b/flopy/discretization/structuredgrid.py index 3d4c7b74a1..0035c7f05f 100644 --- a/flopy/discretization/structuredgrid.py +++ b/flopy/discretization/structuredgrid.py @@ -726,6 +726,32 @@ def is_rectilinear(self): else: return self._cache_dict[cache_index].data_nocopy + @property + def map_polygons(self): + """ + Get a list of matplotlib Polygon patches for plotting + + Returns + ------- + list of Polygon objects + """ + try: + import matplotlib.path as mpath + except ImportError: + raise ImportError("matplotlib required to use this method") + cache_index = "xyzgrid" + if ( + cache_index not in self._cache_dict + or self._cache_dict[cache_index].out_of_date + ): + self.xyzvertices + self._polygons = None + + if self._polygons is None: + self._polygons = (self.xvertices, self.yvertices) + + return self._polygons + ############### ### Methods ### ############### diff --git a/flopy/discretization/unstructuredgrid.py b/flopy/discretization/unstructuredgrid.py index 5a2a483ee4..5a535a76be 100644 --- a/flopy/discretization/unstructuredgrid.py +++ b/flopy/discretization/unstructuredgrid.py @@ -405,14 +405,14 @@ def map_polygons(self): list or dict of matplotlib.collections.Polygon """ try: - from matplotlib.patches import Polygon + from matplotlib.path import Path except ImportError: raise ImportError("matplotlib required to use this method") cache_index = "xyzgrid" if ( - cache_index not in self._cache_dict - or self._cache_dict[cache_index].out_of_date + cache_index not in self._cache_dict + or self._cache_dict[cache_index].out_of_date ): self.xyzvertices self._polygons = None @@ -429,12 +429,12 @@ def map_polygons(self): if ilay not in self._polygons: self._polygons[ilay] = [] - p = Polygon(self.get_cell_vertices(nn), closed=True) + p = Path(self.get_cell_vertices(nn)) self._polygons[ilay].append(p) else: self._polygons = [ - Polygon(self.get_cell_vertices(nn), closed=True) - for nn in range(self.ncpl[0]) + Path(self.get_cell_vertices(nn)) for + nn in range(self.ncpl[0]) ] return copy.copy(self._polygons) diff --git a/flopy/discretization/vertexgrid.py b/flopy/discretization/vertexgrid.py index 887739f28a..c67d0767a5 100644 --- a/flopy/discretization/vertexgrid.py +++ b/flopy/discretization/vertexgrid.py @@ -198,6 +198,34 @@ def xyzvertices(self): else: return self._cache_dict[cache_index].data_nocopy + @property + def map_polygons(self): + """ + Get a list of matplotlib Polygon patches for plotting + + Returns + ------- + list of Polygon objects + """ + try: + import matplotlib.path as mpath + except ImportError: + raise ImportError("matplotlib required to use this method") + cache_index = "xyzgrid" + if ( + cache_index not in self._cache_dict + or self._cache_dict[cache_index].out_of_date + ): + self.xyzvertices + self._polygons = None + if self._polygons is None: + self._polygons = [ + mpath.Path(self.get_cell_vertices(nn)) + for nn in range(self.ncpl) + ] + + return copy.copy(self._polygons) + def intersect(self, x, y, local=False, forgive=False): """ Get the CELL2D number of a point with coordinates x and y diff --git a/flopy/plot/map.py b/flopy/plot/map.py index 93f667755f..6f7dbf2653 100644 --- a/flopy/plot/map.py +++ b/flopy/plot/map.py @@ -5,8 +5,8 @@ try: import matplotlib.pyplot as plt import matplotlib.colors - from matplotlib.collections import PatchCollection - from matplotlib.patches import Polygon + from matplotlib.collections import PathCollection, LineCollection + from matplotlib.path import Path except (ImportError, ModuleNotFoundError): plt = None @@ -112,7 +112,6 @@ def plot_array(self, a, masked_values=None, **kwargs): # Use the model grid to pass back an array of the correct shape plotarray = self.mg.get_plottable_layer_array(a, self.layer) - plotarray = plotarray.ravel() # if masked_values are provided mask the plotting array if masked_values is not None: @@ -129,20 +128,26 @@ def plot_array(self, a, masked_values=None, **kwargs): if isinstance(polygons, dict): polygons = polygons[self.layer] - collection = PatchCollection(polygons) - collection.set_array(plotarray) + if len(polygons) == 0: + return + + if not isinstance(polygons[0], Path): + collection = ax.pcolormesh( + self.mg.xvertices, self.mg.yvertices, plotarray + ) + + else: + plotarray = plotarray.ravel() + collection = PathCollection(polygons) + collection.set_array(plotarray) # set max and min vmin = kwargs.pop("vmin", None) vmax = kwargs.pop("vmax", None) - # limit the color range + # set matplotlib kwargs collection.set_clim(vmin=vmin, vmax=vmax) - - # send rest of kwargs to quadmesh collection.set(**kwargs) - - # add collection to axis ax.add_collection(collection) # set limits @@ -362,30 +367,21 @@ def plot_grid(self, **kwargs): from matplotlib.collections import PatchCollection ax = kwargs.pop("ax", self.ax) - edgecolor = kwargs.pop("colors", "grey") - edgecolor = kwargs.pop("color", edgecolor) - edgecolor = kwargs.pop("ec", edgecolor) - edgecolor = kwargs.pop("edgecolor", edgecolor) - facecolor = kwargs.pop("facecolor", "none") - facecolor = kwargs.pop("fc", facecolor) + colors = kwargs.pop("colors", "grey") + colors = kwargs.pop("color", colors) + colors = kwargs.pop("ec", colors) + colors = kwargs.pop("edgecolor", colors) - # use cached patch collection for plotting - polygons = self.mg.map_polygons - if isinstance(polygons, dict): - polygons = polygons[self.layer] + grid_lines = self.mg.grid_lines + if isinstance(grid_lines, dict): + grid_lines = grid_lines[self.layer] - if len(polygons) > 0: - patches = PatchCollection( - polygons, edgecolor=edgecolor, facecolor=facecolor, **kwargs - ) - else: - patches = None + collection = LineCollection(grid_lines, colors=colors, **kwargs) - ax.add_collection(patches) + ax.add_collection(collection) ax.set_xlim(self.extent[0], self.extent[1]) ax.set_ylim(self.extent[2], self.extent[3]) - - return patches + return collection def plot_bc( self, From c94a4b050daff493b0f1ebb0052b7bc736f0b40e Mon Sep 17 00:00:00 2001 From: Joshua Larsen Date: Fri, 21 May 2021 18:33:45 -0700 Subject: [PATCH 2/2] linting --- flopy/discretization/structuredgrid.py | 4 ++-- flopy/discretization/unstructuredgrid.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/flopy/discretization/structuredgrid.py b/flopy/discretization/structuredgrid.py index 0035c7f05f..9191de8a30 100644 --- a/flopy/discretization/structuredgrid.py +++ b/flopy/discretization/structuredgrid.py @@ -741,8 +741,8 @@ def map_polygons(self): raise ImportError("matplotlib required to use this method") cache_index = "xyzgrid" if ( - cache_index not in self._cache_dict - or self._cache_dict[cache_index].out_of_date + cache_index not in self._cache_dict + or self._cache_dict[cache_index].out_of_date ): self.xyzvertices self._polygons = None diff --git a/flopy/discretization/unstructuredgrid.py b/flopy/discretization/unstructuredgrid.py index 5a535a76be..d936a75d76 100644 --- a/flopy/discretization/unstructuredgrid.py +++ b/flopy/discretization/unstructuredgrid.py @@ -411,8 +411,8 @@ def map_polygons(self): cache_index = "xyzgrid" if ( - cache_index not in self._cache_dict - or self._cache_dict[cache_index].out_of_date + cache_index not in self._cache_dict + or self._cache_dict[cache_index].out_of_date ): self.xyzvertices self._polygons = None @@ -433,8 +433,8 @@ def map_polygons(self): self._polygons[ilay].append(p) else: self._polygons = [ - Path(self.get_cell_vertices(nn)) for - nn in range(self.ncpl[0]) + Path(self.get_cell_vertices(nn)) + for nn in range(self.ncpl[0]) ] return copy.copy(self._polygons)