05: Unstructured grid and general mesh generation with FloPy

In this notebook we’ll cover how to build Structured and Unstructured Meshes (Grids) with FloPy for use in MODFLOW models.

This notebook will start with generating a simple Structured Grid and we will use this grid object to begin developing Unstructured Meshes. The grids generated in theis notebook include the following:

  • Regular structured grid (review)

  • Irregular structured grid with variable row and column spacings

  • Local Grid Refinement (LGR): A locally refined mesh that can be subsetted into a second model within a MODFLOW simulation

  • Quadtree Mesh: A mesh with a four fold (quad) refinement for each grid cell in a given refinement area

  • Triangular Mesh: A triangular mesh generated using Delaunay triangulation

  • Voronoi Mesh: A mesh generated from the connected centers of the Delaunay triangulation circumcircles

[1]:
import warnings
from pathlib import Path

import flopy
import geopandas as gpd
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import contextily as ctx
from dataretrieval import nldi, nwis
from flopy.discretization import StructuredGrid, VertexGrid
from flopy.plot import PlotMapView
from flopy.utils.gridgen import Gridgen
from flopy.utils.gridintersect import GridIntersect
from flopy.utils.lgrutil import Lgr
from flopy.utils.rasters import Raster
from flopy.utils.triangle import Triangle
from flopy.utils.voronoi import VoronoiGrid
from shapely.errors import ShapelyDeprecationWarning

warnings.simplefilter("ignore", DeprecationWarning)
warnings.simplefilter("ignore", ShapelyDeprecationWarning)
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[1], line 9
      7 import numpy as np
      8 import pandas as pd
----> 9 import contextily as ctx
     10 from dataretrieval import nldi, nwis
     11 from flopy.discretization import StructuredGrid, VertexGrid

ModuleNotFoundError: No module named 'contextily'

Creating a regular structured (rectilinear) grid

This activity will start by creating a simple sturctured (rectilinear) grid from a 1/3 arc second Digital Elevation Model of the Sagehen Creek watershed as an illustration of how we can create an initial mesh that can be used to develop a model from.

This is a quick review for more information, see the previous activity 04_Modelgrid_and_intersection.ipynb

Load the Digital Elevation Model and visualize it

[2]:
data_ws = Path("./data")
geospatial_ws = data_ws / "unstructured_grid_data"
dem = geospatial_ws / "dem.img"
rstr = Raster.load(dem)
rstr.plot();
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[2], line 4
      2 geospatial_ws = data_ws / "unstructured_grid_data"
      3 dem = geospatial_ws / "dem.img"
----> 4 rstr = Raster.load(dem)
      5 rstr.plot();

NameError: name 'Raster' is not defined

Get the DEM boundaries

[3]:
xmin, xmax, ymin, ymax = rstr.bounds
epsg = rstr.crs.to_epsg()
print(f"{xmin=}, {xmax=}")
print(f"{epsg=}")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[3], line 1
----> 1 xmin, xmax, ymin, ymax = rstr.bounds
      2 epsg = rstr.crs.to_epsg()
      3 print(f"{xmin=}, {xmax=}")

NameError: name 'rstr' is not defined

And begin developing an initial mesh for the model

[4]:
# live code modelgrid creation
cellsize = 200

Finally create a fake top, bottom, and idomain for the model for now. These will be refined in a subsequent step.

[5]:
# live code modelgrid creation

And now we can generate a StructuredGrid object (called sgrid) that can be used for developing model inputs

[6]:
# live code modelgrid creation

We can plot our SturcturedGrid object over the existing raster data to check that it is correctly orientied in space

[7]:
fig, ax = plt.subplots(figsize=(6, 6))
rstr.plot(ax=ax)
pmv = PlotMapView(modelgrid=sgrid, ax=ax)
pmv.plot_grid(lw=0.5);
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[7], line 2
      1 fig, ax = plt.subplots(figsize=(6, 6))
----> 2 rstr.plot(ax=ax)
      3 pmv = PlotMapView(modelgrid=sgrid, ax=ax)
      4 pmv.plot_grid(lw=0.5);

NameError: name 'rstr' is not defined
../../_images/notebooks_part1_flopy_05_Unstructured_Grid_generation_14_1.png

Resampling data to finish creating our initial grid.

Once we have an initial grid, raster and vector data can be resampled or intersected with the grid to begin building a model domain. Although the focus of this notebook is to generate different types of meshes for modeling, the resampling and intersection process in this notebook can be applied many different data types and be used to generate boundary conditions within a MODFLOW model.

Raster resampling

We’ll use FloPy’s built in raster resampling method to generate values for land surface elevation (top) using the existing StructuredGrid object. For more information on raster resampling, please see this example

[8]:
top = rstr.resample_to_grid(
    sgrid, rstr.bands[0], method="min", extrapolate_edges=True
)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[8], line 1
----> 1 top = rstr.resample_to_grid(
      2     sgrid, rstr.bands[0], method="min", extrapolate_edges=True
      3 )

NameError: name 'rstr' is not defined
[9]:
fig, ax = plt.subplots(figsize=(7, 7))
pmv = PlotMapView(modelgrid=sgrid)
pc = pmv.plot_array(top)
pmv.plot_grid(lw=0.5)
plt.colorbar(pc, shrink=0.7);
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[9], line 2
      1 fig, ax = plt.subplots(figsize=(7, 7))
----> 2 pmv = PlotMapView(modelgrid=sgrid)
      3 pc = pmv.plot_array(top)
      4 pmv.plot_grid(lw=0.5)

NameError: name 'PlotMapView' is not defined
../../_images/notebooks_part1_flopy_05_Unstructured_Grid_generation_18_1.png

Vector (shapefile) intersection review

We can also intersect lines, points, and polygons with an existing modelgrid object. In this example, we’ll load up the watershed boundary and get the active and inactive areas of the grid. For more information on intersecting shapefiles with a modelgrid/model see this example

Our first step in this process is to get the watershed boundary. We can use the dataretrieval package to begin downloading information about the watershed from NWIS.

Note: this information has been stored in the repository as shapefiles if internet access is not available.

[10]:
# the get_info() call accepts a decimal lat/lon bounding box
griddf = sgrid.geo_dataframe.set_crs(epsg=26911)
grid_wgs = griddf.to_crs(epsg=4326)

wxmin, wxmax, wymin, wymax = grid_wgs.total_bounds
wgs_bounds = [wxmin, wymin, wxmax, wymax]

try:
    info, metadata = nwis.get_info(bBox=[f"{i :.2f}" for i in wgs_bounds])
except (ValueError, ConnectionError, NameError):
    info = gpd.read_file(geospatial_ws / "gage_info.shp")

info.head()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[10], line 2
      1 # the get_info() call accepts a decimal lat/lon bounding box
----> 2 griddf = sgrid.geo_dataframe.set_crs(epsg=26911)
      3 grid_wgs = griddf.to_crs(epsg=4326)
      5 wxmin, wxmax, wymin, wymax = grid_wgs.total_bounds

NameError: name 'sgrid' is not defined

Reproject the gage data into UTM Zone 11 N

[11]:
info = info.to_crs(epsg=epsg)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[11], line 1
----> 1 info = info.to_crs(epsg=epsg)

NameError: name 'info' is not defined

And plot the gage data on the raster

[12]:
fig, ax = plt.subplots()

rstr.plot(ax=ax)
info.plot(
    column="site_no",
    ax=ax,
    cmap="tab20",
    legend=True,
    legend_kwds={"fontsize": 6},
);
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[12], line 3
      1 fig, ax = plt.subplots()
----> 3 rstr.plot(ax=ax)
      4 info.plot(
      5     column="site_no",
      6     ax=ax,
   (...)      9     legend_kwds={"fontsize": 6},
     10 );

NameError: name 'rstr' is not defined
../../_images/notebooks_part1_flopy_05_Unstructured_Grid_generation_25_1.png

Grab the site record for gage number 10343500

[13]:
sitedf = info.loc[info.site_no == "10343500"]
sitedf
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[13], line 1
----> 1 sitedf = info.loc[info.site_no == "10343500"]
      2 sitedf

NameError: name 'info' is not defined

Get the upstream basin from nldi using the NWIS stream gage station code

[14]:
try:
    basindf = nldi.get_basin(
        feature_source="nwissite",
        feature_id=f"USGS-{sitedf.site_no.values[0]}",
    )
    basindf = basindf.to_crs(epsg=epsg)
except (ValueError, ConnectionError, NameError):
    basindf = gpd.read_file(geospatial_ws / "basin_boundary.shp")
/home/runner/micromamba/envs/pyclass-docs/lib/python3.11/site-packages/pyogrio/__init__.py:7: DeprecationWarning: The 'shapely.geos' module is deprecated, and will be removed in a future version. All attributes of 'shapely.geos' are available directly from the top-level 'shapely' namespace (since shapely 2.0.0).
  import shapely.geos  # noqa: F401
---------------------------------------------------------------------------
DataSourceError                           Traceback (most recent call last)
Cell In[14], line 8
      6     basindf = basindf.to_crs(epsg=epsg)
      7 except (ValueError, ConnectionError, NameError):
----> 8     basindf = gpd.read_file(geospatial_ws / "basin_boundary.shp")

File ~/micromamba/envs/pyclass-docs/lib/python3.11/site-packages/geopandas/io/file.py:316, in _read_file(filename, bbox, mask, columns, rows, engine, **kwargs)
    313             filename = response.read()
    315 if engine == "pyogrio":
--> 316     return _read_file_pyogrio(
    317         filename, bbox=bbox, mask=mask, columns=columns, rows=rows, **kwargs
    318     )
    320 elif engine == "fiona":
    321     if pd.api.types.is_file_like(filename):

File ~/micromamba/envs/pyclass-docs/lib/python3.11/site-packages/geopandas/io/file.py:576, in _read_file_pyogrio(path_or_bytes, bbox, mask, rows, **kwargs)
    567     warnings.warn(
    568         "The 'include_fields' and 'ignore_fields' keywords are deprecated, and "
    569         "will be removed in a future release. You can use the 'columns' keyword "
   (...)    572         stacklevel=3,
    573     )
    574     kwargs["columns"] = kwargs.pop("include_fields")
--> 576 return pyogrio.read_dataframe(path_or_bytes, bbox=bbox, **kwargs)

File ~/micromamba/envs/pyclass-docs/lib/python3.11/site-packages/pyogrio/geopandas.py:275, in read_dataframe(path_or_buffer, layer, encoding, columns, read_geometry, force_2d, skip_features, max_features, where, bbox, mask, fids, sql, sql_dialect, fid_as_index, use_arrow, on_invalid, arrow_to_pandas_kwargs, **kwargs)
    270 if not use_arrow:
    271     # For arrow, datetimes are read as is.
    272     # For numpy IO, datetimes are read as string values to preserve timezone info
    273     # as numpy does not directly support timezones.
    274     kwargs["datetime_as_string"] = True
--> 275 result = read_func(
    276     path_or_buffer,
    277     layer=layer,
    278     encoding=encoding,
    279     columns=columns,
    280     read_geometry=read_geometry,
    281     force_2d=gdal_force_2d,
    282     skip_features=skip_features,
    283     max_features=max_features,
    284     where=where,
    285     bbox=bbox,
    286     mask=mask,
    287     fids=fids,
    288     sql=sql,
    289     sql_dialect=sql_dialect,
    290     return_fids=fid_as_index,
    291     **kwargs,
    292 )
    294 if use_arrow:
    295     import pyarrow as pa

File ~/micromamba/envs/pyclass-docs/lib/python3.11/site-packages/pyogrio/raw.py:198, in read(path_or_buffer, layer, encoding, columns, read_geometry, force_2d, skip_features, max_features, where, bbox, mask, fids, sql, sql_dialect, return_fids, datetime_as_string, **kwargs)
     59 """Read OGR data source into numpy arrays.
     60
     61 IMPORTANT: non-linear geometry types (e.g., MultiSurface) are converted
   (...)    194
    195 """
    196 dataset_kwargs = _preprocess_options_key_value(kwargs) if kwargs else {}
--> 198 return ogr_read(
    199     get_vsi_path_or_buffer(path_or_buffer),
    200     layer=layer,
    201     encoding=encoding,
    202     columns=columns,
    203     read_geometry=read_geometry,
    204     force_2d=force_2d,
    205     skip_features=skip_features,
    206     max_features=max_features or 0,
    207     where=where,
    208     bbox=bbox,
    209     mask=_mask_to_wkb(mask),
    210     fids=fids,
    211     sql=sql,
    212     sql_dialect=sql_dialect,
    213     return_fids=return_fids,
    214     dataset_kwargs=dataset_kwargs,
    215     datetime_as_string=datetime_as_string,
    216 )

File pyogrio/_io.pyx:1293, in pyogrio._io.ogr_read()

File pyogrio/_io.pyx:232, in pyogrio._io.ogr_open()

DataSourceError: data/unstructured_grid_data/basin_boundary.shp: No such file or directory

Plot up the basin boundary with the existing raster

[15]:
fig, ax = plt.subplots()
rstr.plot(ax=ax)
basindf.plot(ax=ax, facecolor="None", edgecolor="k", lw=1.5)
info.plot(
    column="site_no",
    ax=ax,
    cmap="tab20",
    legend=True,
    legend_kwds={"fontsize": 6},
);
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[15], line 2
      1 fig, ax = plt.subplots()
----> 2 rstr.plot(ax=ax)
      3 basindf.plot(ax=ax, facecolor="None", edgecolor="k", lw=1.5)
      4 info.plot(
      5     column="site_no",
      6     ax=ax,
   (...)      9     legend_kwds={"fontsize": 6},
     10 );

NameError: name 'rstr' is not defined
../../_images/notebooks_part1_flopy_05_Unstructured_Grid_generation_31_1.png

Doing the intersection

[16]:
ix = GridIntersect(sgrid, method="vertex")
result = ix.intersect(basindf.geometry.values[0], contains_centroid=True)
rowcol = result["cellids"]
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[16], line 1
----> 1 ix = GridIntersect(sgrid, method="vertex")
      2 result = ix.intersect(basindf.geometry.values[0], contains_centroid=True)
      3 rowcol = result["cellids"]

NameError: name 'GridIntersect' is not defined
[17]:
row, col = list(zip(*rowcol))
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[17], line 1
----> 1 row, col = list(zip(*rowcol))

NameError: name 'rowcol' is not defined

Building idomain from the intersection

[18]:
idomain = np.zeros((nlay, nrow, ncol), dtype=int)
idomain[:, row, col] = 1
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[18], line 1
----> 1 idomain = np.zeros((nlay, nrow, ncol), dtype=int)
      2 idomain[:, row, col] = 1

NameError: name 'nlay' is not defined

Putting it all together into a completed StructuredGrid

[19]:
sgrid = StructuredGrid(
    delc=sgrid.delc,
    delr=sgrid.delr,
    top=top,
    botm=sgrid.botm,
    idomain=idomain,
    nlay=sgrid.nlay,
    nrow=sgrid.nrow,
    ncol=sgrid.ncol,
    xoff=sgrid.xoffset,
    yoff=sgrid.yoffset,
    crs=sgrid.crs,
)
sgrid
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[19], line 1
----> 1 sgrid = StructuredGrid(
      2     delc=sgrid.delc,
      3     delr=sgrid.delr,
      4     top=top,
      5     botm=sgrid.botm,
      6     idomain=idomain,
      7     nlay=sgrid.nlay,
      8     nrow=sgrid.nrow,
      9     ncol=sgrid.ncol,
     10     xoff=sgrid.xoffset,
     11     yoff=sgrid.yoffset,
     12     crs=sgrid.crs,
     13 )
     14 sgrid

NameError: name 'StructuredGrid' is not defined

Plotting the completed grid with gage locations and the watershed boundary

[20]:
fig, ax = plt.subplots(figsize=(5, 5))
pmv = PlotMapView(modelgrid=sgrid)
pc = pmv.plot_array(sgrid.top)
ib = pmv.plot_inactive()
pmv.plot_grid(lw=0.5)
plt.colorbar(pc, shrink=0.7)

basindf.plot(ax=ax, facecolor="None", edgecolor="r", lw=1.5)
sitedf.plot(
    column="site_no",
    ax=ax,
    cmap="tab20",
    legend=True,
    legend_kwds={"fontsize": 6},
    zorder=6
);
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[20], line 2
      1 fig, ax = plt.subplots(figsize=(5, 5))
----> 2 pmv = PlotMapView(modelgrid=sgrid)
      3 pc = pmv.plot_array(sgrid.top)
      4 ib = pmv.plot_inactive()

NameError: name 'PlotMapView' is not defined
../../_images/notebooks_part1_flopy_05_Unstructured_Grid_generation_40_1.png

Because this is a watershed, we can also overlay a shapefile of the streams and get the stream cells using GridIntersect

NHD-Plus stream lines can be downloaded using streamgage information with the dataretrieval package

[21]:
try:
    nhddf = nldi.get_flowlines(
        navigation_mode="UT",
        distance=999,
        feature_source="nwissite",
        feature_id=f"USGS-{sitedf.site_no.values[0]}",
    )
    nhddf = nhddf.to_crs(epsg=epsg)
except (ValueError, ConnectionError, NameError):
    nhddf = gpd.read_file(geospatial_ws / "nhd_streams.shp")

nhddf.head()
[21]:
nhdplus_co geometry
0 8933522 LINESTRING (221038.59 4369598.73, 221076.943 4...
1 8933608 LINESTRING (220451.353 4367186.202, 220542.627...
2 8933524 LINESTRING (220541.209 4369697.393, 220691.816...
3 8934342 LINESTRING (219823.023 4371753.116, 219905.68 ...
4 8933520 LINESTRING (220240.836 4369758.678, 220245.466...

Intersect stream lines with the existing modelgrid

[22]:
rowcols = []
ix = GridIntersect(sgrid, method="vertex")
for geom in nhddf.geometry.values:
    rcs = ix.intersects(geom)["cellids"]
    rowcols.extend(list(rcs))
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[22], line 2
      1 rowcols = []
----> 2 ix = GridIntersect(sgrid, method="vertex")
      3 for geom in nhddf.geometry.values:
      4     rcs = ix.intersects(geom)["cellids"]

NameError: name 'GridIntersect' is not defined
[23]:
row, col = list(zip(*rowcols))
strms = np.zeros((nrow, ncol))
strms[row, col] = 1
strms
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[23], line 1
----> 1 row, col = list(zip(*rowcols))
      2 strms = np.zeros((nrow, ncol))
      3 strms[row, col] = 1

ValueError: not enough values to unpack (expected 2, got 0)

Plot the intersection results

[24]:
fig, ax = plt.subplots(figsize=(5, 5))
pmv = PlotMapView(modelgrid=sgrid, ax=ax)
pc = pmv.plot_array(sgrid.top, cmap="viridis")
pmv.plot_array(strms, masked_values=[0,], alpha=0.3, cmap="Reds_r")
pmv.plot_inactive()
pmv.plot_grid(lw=0.5)
plt.colorbar(pc, shrink=0.7)
nhddf.plot(ax=ax);
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[24], line 2
      1 fig, ax = plt.subplots(figsize=(5, 5))
----> 2 pmv = PlotMapView(modelgrid=sgrid, ax=ax)
      3 pc = pmv.plot_array(sgrid.top, cmap="viridis")
      4 pmv.plot_array(strms, masked_values=[0,], alpha=0.3, cmap="Reds_r")

NameError: name 'PlotMapView' is not defined
../../_images/notebooks_part1_flopy_05_Unstructured_Grid_generation_48_1.png

Information from the grid we’ve created here can now be used for generating more complex meshes.

Irregular structured Grid (DIS)

In this example, an irregular structured grid is created to demonstrate how to add local refinement to an area of the model using a structured (rectilinear) grid.

[25]:
lx = xmax - xmin
ly = ymax - ymin
cellsize = 200
rcellsize = 100

smooth = np.linspace(100, 200, 10).tolist()
delr = 9 * [cellsize] + smooth[::-1] + 8 * [rcellsize] + smooth + 9 * [cellsize]
delc = 8 * [cellsize] + smooth[::-1] + 7 * [rcellsize] + smooth + 8 * [cellsize]
nlay = 1
nrow = len(delc)
ncol = len(delr)

# create some fake data for the moment
top = np.ones((nrow, ncol))
botm = np.zeros((nlay, nrow, ncol))
idomain = np.ones(botm.shape, dtype=int)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[25], line 1
----> 1 lx = xmax - xmin
      2 ly = ymax - ymin
      3 cellsize = 200

NameError: name 'xmax' is not defined
[26]:
rfgrid = StructuredGrid(
    delc=np.array(delc),
    delr=np.array(delr),
    nlay=nlay,
    top=top,
    botm=botm,
    idomain=idomain,
    xoff=xmin,
    yoff=ymin
)
print(nlay, nrow, ncol)
print(rfgrid.nrow, rfgrid.ncol)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[26], line 1
----> 1 rfgrid = StructuredGrid(
      2     delc=np.array(delc),
      3     delr=np.array(delr),
      4     nlay=nlay,
      5     top=top,
      6     botm=botm,
      7     idomain=idomain,
      8     xoff=xmin,
      9     yoff=ymin
     10 )
     11 print(nlay, nrow, ncol)
     12 print(rfgrid.nrow, rfgrid.ncol)

NameError: name 'StructuredGrid' is not defined

Intersect the basin boundary with the grid to set active and inactive cells (idomain)

[27]:
ix = GridIntersect(rfgrid, method="vertex")
result = ix.intersect(basindf.geometry.values[0], contains_centroid=True)
rowcol = result["cellids"]

row, col = list(zip(*rowcol))
idomain = np.zeros((nlay, nrow, ncol), dtype=int)
idomain[:, row, col] = 1

# tricky way so we don't have to recreate the grid
rfgrid._idomain = idomain
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[27], line 1
----> 1 ix = GridIntersect(rfgrid, method="vertex")
      2 result = ix.intersect(basindf.geometry.values[0], contains_centroid=True)
      3 rowcol = result["cellids"]

NameError: name 'GridIntersect' is not defined

Resample the top elevation

[28]:
top = rstr.resample_to_grid(
    rfgrid, rstr.bands[0], method="min", extrapolate_edges=True
)

# tricky way so we don't have to recreate the grid
rfgrid._top = top
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[28], line 1
----> 1 top = rstr.resample_to_grid(
      2     rfgrid, rstr.bands[0], method="min", extrapolate_edges=True
      3 )
      5 # tricky way so we don't have to recreate the grid
      6 rfgrid._top = top

NameError: name 'rstr' is not defined

Now we can plot the grid

[29]:
fig, ax = plt.subplots(figsize=(5, 5))
pmv = PlotMapView(modelgrid=rfgrid)
pc = pmv.plot_array(rfgrid.top, cmap="viridis")
ib = pmv.plot_inactive()
pmv.plot_grid(lw=0.5)
plt.colorbar(pc, shrink=0.7)

basindf.plot(ax=ax, facecolor="None", edgecolor="r", lw=1.5)
sitedf.plot(
    column="site_no",
    ax=ax,
    cmap="tab20",
    legend=True,
    legend_kwds={"fontsize": 6},
    zorder=6
);
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[29], line 2
      1 fig, ax = plt.subplots(figsize=(5, 5))
----> 2 pmv = PlotMapView(modelgrid=rfgrid)
      3 pc = pmv.plot_array(rfgrid.top, cmap="viridis")
      4 ib = pmv.plot_inactive()

NameError: name 'PlotMapView' is not defined
../../_images/notebooks_part1_flopy_05_Unstructured_Grid_generation_58_1.png

Local Grid Refinement (LGR) mesh

A Local Grid Refinement (LGR) mesh can be created in an area of interest to get finer discretization than the existing/surrounding mesh. FloPy’s Lgr utility, creates the refined mesh as a seperate modelgrid, and calculates the exchanges between the multi-model simulation mesh that results from refinement.

The Lgr utility has a number of input parameters:

  • nlayp: the number of parent model layers

  • nrowp: the number of parent model rows

  • ncolp: the number of parent model columns

  • delrp: the parent model delr array

  • delcp: the parent model delc array

  • topp: the parent model top array

  • botmp: the parent model botm array

  • idomainp: an idomain array that is used to create the child grid. Values of 1 indicate parent model cells, values of 0 indicate child model cells. The child model must be a rectangular region that is continuous.

  • ncpp: number of child cells along the face of a parent cell

  • ncppl: number of child cells per parent layer

  • xllp: (optional) x location of parent grid lower left corner

  • yllp: (optional) y-location of parent grid lower left corner

Load up the refinement area polygon

[30]:
lgr_gdf = gpd.read_file(geospatial_ws / "lgr_bound.shp")

Plot it to see if it lines up properly with our model

[31]:
fig, ax = plt.subplots(figsize=(5, 5))
pmv = PlotMapView(modelgrid=sgrid, ax=ax)
pc = pmv.plot_array(sgrid.top, cmap="viridis")
pmv.plot_array(strms, masked_values=[0,], alpha=0.3, cmap="Reds_r")
pmv.plot_inactive()
pmv.plot_grid(lw=0.5)
nhddf.plot(ax=ax)
lgr_gdf.plot(ax=ax, alpha=0.5)
plt.colorbar(pc, shrink=0.7);
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[31], line 2
      1 fig, ax = plt.subplots(figsize=(5, 5))
----> 2 pmv = PlotMapView(modelgrid=sgrid, ax=ax)
      3 pc = pmv.plot_array(sgrid.top, cmap="viridis")
      4 pmv.plot_array(strms, masked_values=[0,], alpha=0.3, cmap="Reds_r")

NameError: name 'PlotMapView' is not defined
../../_images/notebooks_part1_flopy_05_Unstructured_Grid_generation_63_1.png

Creating the parent/child idomainp array for Lgr using GridIntersect

[32]:
lgr_gdf
[32]:
Id geometry
0 0 POLYGON ((221180.716 4369310.065, 219973.134 4...

Activity: Create the idomainp (parent, child) array from the polygon provided by the lgr geodataframe

idomainp should be the same shape as the existing modelgrid’s idomain array. The child model extent should be represented with the value 0 and the parent 1.

Hint: GridIntersect() can be used to find the cellids where the lgr polygon intersects with the existing modelgrid

[33]:
# live code this/give it as an activity
idomainp = np.ones(sgrid.idomain.shape, dtype=int)

gix = GridIntersect(sgrid, method="vertex")
result = gix.intersect(lgr_gdf.geometry.values[0])
cid = result["cellids"]

crow, ccol = list(zip(*cid))
idomainp[:, crow, ccol] = 0
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[33], line 2
      1 # live code this/give it as an activity
----> 2 idomainp = np.ones(sgrid.idomain.shape, dtype=int)
      4 gix = GridIntersect(sgrid, method="vertex")
      5 result = gix.intersect(lgr_gdf.geometry.values[0])

NameError: name 'sgrid' is not defined

Creating the Lgr object/child grid

[34]:
lgr = Lgr(
    nlayp=sgrid.nlay,
    nrowp=sgrid.nrow,
    ncolp=sgrid.ncol,
    delrp=sgrid.delr,
    delcp=sgrid.delc,
    topp=sgrid.top,
    botmp=sgrid.botm,
    idomainp=idomainp,
    ncpp=4,
    xllp=sgrid.xoffset,
    yllp=sgrid.yoffset,
)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[34], line 1
----> 1 lgr = Lgr(
      2     nlayp=sgrid.nlay,
      3     nrowp=sgrid.nrow,
      4     ncolp=sgrid.ncol,
      5     delrp=sgrid.delr,
      6     delcp=sgrid.delc,
      7     topp=sgrid.top,
      8     botmp=sgrid.botm,
      9     idomainp=idomainp,
     10     ncpp=4,
     11     xllp=sgrid.xoffset,
     12     yllp=sgrid.yoffset,
     13 )

NameError: name 'Lgr' is not defined

Get a StructuredGrid of the child model

[35]:
childgrid = lgr.child.modelgrid
childgrid
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[35], line 1
----> 1 childgrid = lgr.child.modelgrid
      2 childgrid

NameError: name 'lgr' is not defined

Resample top elevations to the child grid

[36]:
childtop = rstr.resample_to_grid(childgrid, band=rstr.bands[0], method="min")
print(childtop.shape)
print(childgrid.shape)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[36], line 1
----> 1 childtop = rstr.resample_to_grid(childgrid, band=rstr.bands[0], method="min")
      2 print(childtop.shape)
      3 print(childgrid.shape)

NameError: name 'rstr' is not defined

Reset the top elevations in the childgrid object

[37]:
childgrid._top = childtop
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[37], line 1
----> 1 childgrid._top = childtop

NameError: name 'childtop' is not defined

Intersect streams with the Child mesh to define stream cells

[38]:
ix = GridIntersect(childgrid, method="vertex")
cstrmcells = []
for geom in nhddf.geometry.values:
    rcs = ix.intersects(geom)["cellids"]
    if len(rowcol) > 0:
        cstrmcells.extend(list(rcs))

cstrms = np.zeros((childgrid.nrow, childgrid.ncol), dtype=int)
row, col = list(zip(*cstrmcells))
cstrms[row, col] = 1
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[38], line 1
----> 1 ix = GridIntersect(childgrid, method="vertex")
      2 cstrmcells = []
      3 for geom in nhddf.geometry.values:

NameError: name 'GridIntersect' is not defined

And now we can visualize the new LGR mesh

[39]:
fig, ax = plt.subplots(figsize=(10, 10))
vmin, vmax = np.min(sgrid.top), np.max(sgrid.top)

# plot the parent
pmvp = PlotMapView(modelgrid=sgrid, ax=ax)
ptop = sgrid.top.copy()
ptop[crow, ccol] = np.nan
strms[crow, ccol] = np.nan
pc = pmvp.plot_array(ptop, cmap="viridis", vmin=vmin, vmax=vmax)
pmvp.plot_array(strms, masked_values=[0,], alpha=0.3, cmap="Reds_r")
pmvp.plot_inactive()
pmvp.plot_grid(lw=0.3)
nhddf.plot(ax=ax)

# plot the child
pmvc = PlotMapView(modelgrid=childgrid, ax=ax, extent=sgrid.extent)
pmvc.plot_array(childtop, cmap="viridis", vmin=vmin, vmax=vmax)
pmvc.plot_array(cstrms, masked_values=[0,], alpha=0.3, cmap="Reds_r")
pmvc.plot_grid(lw=0.3)
plt.colorbar(pc, shrink=0.7);
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[39], line 2
      1 fig, ax = plt.subplots(figsize=(10, 10))
----> 2 vmin, vmax = np.min(sgrid.top), np.max(sgrid.top)
      4 # plot the parent
      5 pmvp = PlotMapView(modelgrid=sgrid, ax=ax)

NameError: name 'sgrid' is not defined
../../_images/notebooks_part1_flopy_05_Unstructured_Grid_generation_79_1.png

Quadtree Mesh

A quadtree mesh is an unstructured refinement to a rectilinear modelgrid. In a quadtree mesh each node is split into 4 child nodes per level of refinement. The figure below is from Lien and others, 2019 and shows multiple levels of recursive refinement.

Quadmesh.png

For this example, we will be using GRIDGEN and the FloPy utility Gridgen to create quadtree refinement around the streams in our example.

[40]:
gridgen_dir = data_ws / "sagehen_gridgen"
gridgen_dir.mkdir(exist_ok=True)

The Gridgen class a number of input parameters and further documentation/examples of the class can be found here. For our example, we will be refining the area around the stream channels.

[41]:
g = Gridgen(sgrid, model_ws=gridgen_dir)
for geom in nhddf.geometry.values:
    xy = list(zip(*geom.coords.xy))
    g.add_refinement_features([xy,], "line", 2, [0,])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[41], line 1
----> 1 g = Gridgen(sgrid, model_ws=gridgen_dir)
      2 for geom in nhddf.geometry.values:
      3     xy = list(zip(*geom.coords.xy))

NameError: name 'Gridgen' is not defined
[42]:
g.build()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[42], line 1
----> 1 g.build()

NameError: name 'g' is not defined

After gridgen builds the mesh, it can be used to create a VertexGrid (or DISV) that represents the basin

[43]:
gridprops = g.get_gridprops_disv()
gridprops.pop("nvert")

quadgrid = VertexGrid(**gridprops)
quadgrid.extent
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[43], line 1
----> 1 gridprops = g.get_gridprops_disv()
      2 gridprops.pop("nvert")
      4 quadgrid = VertexGrid(**gridprops)

NameError: name 'g' is not defined

Resample top elevations to the quadtree mesh and intersect the basin boundary and stream vectors

[44]:
# create the top array
top = rstr.resample_to_grid(quadgrid, band=rstr.bands[0], method="min")
quadgrid._top = top
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[44], line 2
      1 # create the top array
----> 2 top = rstr.resample_to_grid(quadgrid, band=rstr.bands[0], method="min")
      3 quadgrid._top = top

NameError: name 'rstr' is not defined
[45]:
# create the idomain array
idomain = np.zeros(quadgrid.shape, dtype=int)
ix = GridIntersect(quadgrid, method="vertex")
nodes = ix.intersect(basindf.geometry.values[0], contains_centroid=True)["cellids"]

idomain[:, list(nodes)] = 1
quadgrid._idomain = idomain
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[45], line 2
      1 # create the idomain array
----> 2 idomain = np.zeros(quadgrid.shape, dtype=int)
      3 ix = GridIntersect(quadgrid, method="vertex")
      4 nodes = ix.intersect(basindf.geometry.values[0], contains_centroid=True)["cellids"]

NameError: name 'quadgrid' is not defined
[46]:
# create an array of stream cells
qstr = []
ix = GridIntersect(quadgrid, method="vertex")
for geom in nhddf.geometry.values:
    nodes = ix.intersects(geom)["cellids"]
    qstr.extend(list(nodes))
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[46], line 3
      1 # create an array of stream cells
      2 qstr = []
----> 3 ix = GridIntersect(quadgrid, method="vertex")
      4 for geom in nhddf.geometry.values:
      5     nodes = ix.intersects(geom)["cellids"]

NameError: name 'GridIntersect' is not defined
[47]:
qstrms = np.zeros((quadgrid.ncpl,), dtype=int)
qstrms[qstr] = 1
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[47], line 1
----> 1 qstrms = np.zeros((quadgrid.ncpl,), dtype=int)
      2 qstrms[qstr] = 1

NameError: name 'quadgrid' is not defined

Plot the quadtree mesh with top elevations and streams

[48]:
fig, ax = plt.subplots(figsize=(5, 5))

pmv = PlotMapView(modelgrid=quadgrid)
pc = pmv.plot_array(quadgrid.top)
pmv.plot_array(qstrms, masked_values=[0,], alpha=0.3, cmap="Reds_r")
pmv.plot_inactive()
pmv.plot_grid(lw=0.3)
nhddf.plot(ax=ax)
plt.colorbar(pc, shrink=0.7);
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[48], line 3
      1 fig, ax = plt.subplots(figsize=(5, 5))
----> 3 pmv = PlotMapView(modelgrid=quadgrid)
      4 pc = pmv.plot_array(quadgrid.top)
      5 pmv.plot_array(qstrms, masked_values=[0,], alpha=0.3, cmap="Reds_r")

NameError: name 'PlotMapView' is not defined
../../_images/notebooks_part1_flopy_05_Unstructured_Grid_generation_93_1.png

Triangular Mesh

An unstructured/vertex based triangular mesh can be generated using the code “triangle”(Shewchuk, 2002). And the FloPy utility, Triangle that creates inputs for and translates the outputs from “triangle”.

For this example, we will be using triangle to generate a mesh and add refinement around the streams.

[49]:
tri_dir = data_ws / "sagehen_tri"
tri_dir.mkdir(exist_ok=True)

For this example, we will use the watershed boundary we downloaded from NLDI and create a dissovled polygon of stream segments to generate our triangulation regions.

[50]:
basindf.head()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[50], line 1
----> 1 basindf.head()

NameError: name 'basindf' is not defined

We will also dissolve and buffer the stream network into a single polygon that defines our area of refinement

[51]:
bufgdf = nhddf.dissolve()
bufgdf["geometry"] = bufgdf.geometry.buffer(100, cap_style=2, join_style=3)
bufgdf.plot();
../../_images/notebooks_part1_flopy_05_Unstructured_Grid_generation_99_0.png

These two polygons can now be used to generate a triangular mesh with the Triangle utility. The Triangle class has a number of input parameters. For more detail on the inputs and additional examples please see this notebook

[52]:
# define point locations within the watershed and the stream for adding regions
wsloc = (220000, 4368000)
stloc = (219250, 4370000)
[53]:
tri = Triangle(angle=30, model_ws=tri_dir)

# define the model/mesh boundary
tri.add_polygon(basindf.geometry.values[0])
tri.add_region(wsloc, 0, maximum_area=100 * 300)

# define the stream refinement area
tri.add_polygon(bufgdf.geometry.values[0])
tri.add_region(stloc, 1, maximum_area=40 * 40)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[53], line 1
----> 1 tri = Triangle(angle=30, model_ws=tri_dir)
      3 # define the model/mesh boundary
      4 tri.add_polygon(basindf.geometry.values[0])

NameError: name 'Triangle' is not defined
[54]:
tri.build()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[54], line 1
----> 1 tri.build()

NameError: name 'tri' is not defined

Visualize the triangular mesh

[55]:
fig, ax = plt.subplots(figsize=(6, 6))
tri.plot(ax=ax)
nhddf.plot(ax=ax);
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[55], line 2
      1 fig, ax = plt.subplots(figsize=(6, 6))
----> 2 tri.plot(ax=ax)
      3 nhddf.plot(ax=ax);

NameError: name 'tri' is not defined
../../_images/notebooks_part1_flopy_05_Unstructured_Grid_generation_105_1.png

Create a VertexGrid from the triangular mesh and resample the land surface elevations

[56]:
cell2d = tri.get_cell2d()
vertices = tri.get_vertices()
ncpl = len(cell2d)
nlay = 1
idomain = np.ones((nlay, ncpl), dtype=int)

# set fake values for top and botm for now)
top = np.ones((ncpl,))
botm = np.zeros((nlay, ncpl))
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[56], line 1
----> 1 cell2d = tri.get_cell2d()
      2 vertices = tri.get_vertices()
      3 ncpl = len(cell2d)

NameError: name 'tri' is not defined
[57]:
trigrid = VertexGrid(
    vertices=vertices,
    cell2d=cell2d,
    ncpl=ncpl,
    nlay=nlay,
    idomain=idomain,
    top=top,
    botm=botm,
    crs="EPSG:26911",
)
trigrid.extent
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[57], line 1
----> 1 trigrid = VertexGrid(
      2     vertices=vertices,
      3     cell2d=cell2d,
      4     ncpl=ncpl,
      5     nlay=nlay,
      6     idomain=idomain,
      7     top=top,
      8     botm=botm,
      9     crs="EPSG:26911",
     10 )
     11 trigrid.extent

NameError: name 'VertexGrid' is not defined
[58]:
top = rstr.resample_to_grid(
    trigrid, band=rstr.bands[0], method="min", extrapolate_edges=True
)
trigrid._top = top
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[58], line 1
----> 1 top = rstr.resample_to_grid(
      2     trigrid, band=rstr.bands[0], method="min", extrapolate_edges=True
      3 )
      4 trigrid._top = top

NameError: name 'rstr' is not defined

And perform intersection to identify stream cells

[59]:
tristr = []
ix = GridIntersect(trigrid, method="vertex")
for geom in nhddf.geometry.values:
    nodes = ix.intersects(geom)["cellids"]
    tristr.extend(list(nodes))

tristrms = np.zeros((trigrid.ncpl,), dtype=int)
tristrms[tristr] = 1
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[59], line 2
      1 tristr = []
----> 2 ix = GridIntersect(trigrid, method="vertex")
      3 for geom in nhddf.geometry.values:
      4     nodes = ix.intersects(geom)["cellids"]

NameError: name 'GridIntersect' is not defined

Now to plot up the triangular grid

[60]:
fig, ax = plt.subplots(figsize=(7, 7))

pmv = PlotMapView(modelgrid=trigrid)
pc = pmv.plot_array(trigrid.top)
pmv.plot_array(tristrms, masked_values=[0,], alpha=0.3, cmap="Reds_r")
pmv.plot_inactive()
pmv.plot_grid(lw=0.3)
nhddf.plot(ax=ax)
plt.colorbar(pc, shrink=0.7);
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[60], line 3
      1 fig, ax = plt.subplots(figsize=(7, 7))
----> 3 pmv = PlotMapView(modelgrid=trigrid)
      4 pc = pmv.plot_array(trigrid.top)
      5 pmv.plot_array(tristrms, masked_values=[0,], alpha=0.3, cmap="Reds_r")

NameError: name 'PlotMapView' is not defined
../../_images/notebooks_part1_flopy_05_Unstructured_Grid_generation_113_1.png

Voronoi Mesh

A voronoi mesh can be generated from the circumcirle centroids of each triangular node in a Delaunay triangulation. The centroid of each circumcirle produces a vertex for the voronoi mesh and these vertices are connected via adjacency relationships of the triangulation.

FloPy has a built in utility VoronoiGrid that produces voronoi meshes from Delaunay triangulations. For more information and additional examples on VoronoiGrid’s usage, see this notebook

To generate a voronoi mesh, we just need to pass our triangluation object (tri) to the VoronoiGrid utility

[61]:
vor = VoronoiGrid(tri)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[61], line 1
----> 1 vor = VoronoiGrid(tri)

NameError: name 'VoronoiGrid' is not defined

Build a VertexGrid

[62]:
gridprops = vor.get_gridprops_vertexgrid()
nlay = 1
idomain = np.ones(gridprops["ncpl"], dtype=int)
top = idomain.copy().astype(float)
botm = np.zeros((nlay, gridprops["ncpl"]))
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[62], line 1
----> 1 gridprops = vor.get_gridprops_vertexgrid()
      2 nlay = 1
      3 idomain = np.ones(gridprops["ncpl"], dtype=int)

NameError: name 'vor' is not defined
[63]:
vorgrid = VertexGrid(
    nlay=nlay, idomain=idomain, top=top, botm=botm, **gridprops
)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[63], line 1
----> 1 vorgrid = VertexGrid(
      2     nlay=nlay, idomain=idomain, top=top, botm=botm, **gridprops
      3 )

NameError: name 'VertexGrid' is not defined

Resample the land surface elevation from the DEM to the voronoi grid

[64]:
top = rstr.resample_to_grid(
    vorgrid, band=rstr.bands[0], method="min", extrapolate_edges=True
)
vorgrid._top = top
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[64], line 1
----> 1 top = rstr.resample_to_grid(
      2     vorgrid, band=rstr.bands[0], method="min", extrapolate_edges=True
      3 )
      4 vorgrid._top = top

NameError: name 'rstr' is not defined

Identify stream cells with GridIntersect

[65]:
vorst = []
ix = GridIntersect(vorgrid, method="vertex")
for geom in nhddf.geometry.values:
    nodes = ix.intersects(geom)["cellids"]
    vorst.extend(list(nodes))

vorstrms = np.zeros((vorgrid.ncpl,), dtype=int)
vorstrms[vorst] = 1
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[65], line 2
      1 vorst = []
----> 2 ix = GridIntersect(vorgrid, method="vertex")
      3 for geom in nhddf.geometry.values:
      4     nodes = ix.intersects(geom)["cellids"]

NameError: name 'GridIntersect' is not defined

Finally plot the voronoi mesh with the DEM data and stream locations

[66]:
fig, ax = plt.subplots(figsize=(7, 7))

pmv = PlotMapView(modelgrid=vorgrid)
pc = pmv.plot_array(vorgrid.top)
pmv.plot_array(vorstrms, masked_values=[0], alpha=0.3, cmap="Reds_r")
pmv.plot_inactive()
pmv.plot_grid(lw=0.3)
nhddf.plot(ax=ax)
plt.colorbar(pc, shrink=0.7);
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[66], line 3
      1 fig, ax = plt.subplots(figsize=(7, 7))
----> 3 pmv = PlotMapView(modelgrid=vorgrid)
      4 pc = pmv.plot_array(vorgrid.top)
      5 pmv.plot_array(vorstrms, masked_values=[0], alpha=0.3, cmap="Reds_r")

NameError: name 'PlotMapView' is not defined
../../_images/notebooks_part1_flopy_05_Unstructured_Grid_generation_125_1.png

plot the meshes we produced side by side for comparison!

[67]:
from flopy.plot import styles
from matplotlib.gridspec import GridSpec

with styles.USGSMap():
    fig, axm = plt.subplots(nrows=3, ncols=2, figsize=(10,12))
    axs = axm.ravel()

    # structured grid
    pmv = PlotMapView(modelgrid=sgrid, ax=axs[0])
    pmv.plot_array(sgrid.top, cmap="viridis")
    pmv.plot_array(strms, masked_values=[0,], alpha=0.3, cmap="Reds_r")
    pmv.plot_inactive()
    pmv.plot_grid(lw=0.3)
    nhddf.plot(ax=axs[0])
    styles.heading(axs[0], "A. ", heading="Structured Grid", x=0.08)

    # non-uniform DIS
    pmv = PlotMapView(modelgrid=rfgrid, ax=axs[1])
    pmv.plot_array(rfgrid.top, cmap="viridis")
    pmv.plot_inactive()
    pmv.plot_grid(lw=0.3)
    nhddf.plot(ax=axs[1])
    styles.heading(axs[1], "B.", heading="Structured Grid: non-uniform", x=0.08)

    # LGR grid
    pmvp = PlotMapView(modelgrid=sgrid, ax=axs[2])
    ptop = sgrid.top.copy()
    ptop[crow, ccol] = np.nan
    strms[crow, ccol] = np.nan
    pc = pmvp.plot_array(ptop, cmap="viridis", vmin=vmin, vmax=vmax)
    pmvp.plot_array(strms, masked_values=[0], alpha=0.3, cmap="Reds_r")
    pmvp.plot_inactive()
    pmvp.plot_grid(lw=0.3)
    nhddf.plot(ax=axs[2])

    pmvc = PlotMapView(modelgrid=childgrid, ax=axs[2])
    pmvc.plot_array(childtop, cmap="viridis", vmin=vmin, vmax=vmax)
    pmvc.plot_array(cstrms, masked_values=[0], alpha=0.3, cmap="Reds_r")
    pmvc.plot_grid(lw=0.3)
    styles.heading(axs[2], "C. ", heading="LGR Grid", x=0.08)

    # Quadtree grid
    pmv = PlotMapView(modelgrid=quadgrid, ax=axs[3])
    pc = pmv.plot_array(quadgrid.top)
    pmv.plot_array(qstrms, masked_values=[0,], alpha=0.3, cmap="Reds_r")
    pmv.plot_inactive()
    pmv.plot_grid(lw=0.2)
    nhddf.plot(ax=axs[3])
    styles.heading(axs[3], "D. ", heading="Quadtree Grid", x=0.08)

    # Triangular grid
    pmv = PlotMapView(modelgrid=trigrid, ax=axs[4])
    pc = pmv.plot_array(trigrid.top)
    pmv.plot_array(tristrms, masked_values=[0,], alpha=0.3, cmap="Reds_r")
    pmv.plot_inactive()
    pmv.plot_grid(lw=0.3)
    nhddf.plot(ax=axs[4])
    styles.heading(axs[4], "E. ", heading="Triangular Grid", x=0.08)

    # Voronoi grid
    pmv = PlotMapView(modelgrid=vorgrid, ax=axs[5])
    pc = pmv.plot_array(vorgrid.top)
    pmv.plot_array(vorstrms, masked_values=[0,], alpha=0.3, cmap="Reds_r")
    pmv.plot_inactive()
    pmv.plot_grid(lw=0.3)
    nhddf.plot(ax=axs[5])
    styles.heading(axs[5], "F. ", heading="Voronoi Grid", x=0.08)

    # colorbar


    # ax5.spines[["left", "right", "top", "bottom"]].set_visible(False)
    fig.colorbar(
        pc,
        ax=axm,
        location="top",
        label="Land surface elevation, in meters",
        shrink=0.5,
        fraction=.05
    )

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[67], line 9
      6 axs = axm.ravel()
      8 # structured grid
----> 9 pmv = PlotMapView(modelgrid=sgrid, ax=axs[0])
     10 pmv.plot_array(sgrid.top, cmap="viridis")
     11 pmv.plot_array(strms, masked_values=[0,], alpha=0.3, cmap="Reds_r")

NameError: name 'PlotMapView' is not defined
../../_images/notebooks_part1_flopy_05_Unstructured_Grid_generation_127_1.png

More information

For more inforation and and examples on how to use the utilities presented in this notebook please see:

Examples

API reference

Activity (If time permits); Build a Voronoi mesh/Vertex Grid:

Define a voronoi grid for the basin upstream of the Black Earth Creek streamgauge near Cross Plains, WI. The upstream basin information is supplied by NLDI and has been saved to the class repo in case of internet connectivity or NLDI service issues.

Some of the previous code can be re-used to create a voronoi mesh and subsequent VertexGrid representation of the basin. See the Triangular and Voronoi grid sections of the notebook.

Optional, use the Upstream Main segment of Black Earth Creek to refine the voronoi mesh around the stream location. Refinement control points have been pre-defined below.

[68]:
epsg = 5070  # NAD83 CONUS Albers
# try:
    # todo: update this for black earth creek near cross plains WI site-no 05406479
be_cr_nr_cp = "05406479"
basindf = nldi.get_basin(
    feature_source="nwissite", feature_id=f"USGS-{be_cr_nr_cp}"
)
basindf = basindf.to_crs(epsg=epsg)
basindf.to_file(geospatial_ws / "black_earth_basin.shp")
# except (ValueError, ConnectionError, NameError):
#     basindf = gpd.read_file(geospatial_ws / "black_earth_basin.shp")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[68], line 5
      2 # try:
      3     # todo: update this for black earth creek near cross plains WI site-no 05406479
      4 be_cr_nr_cp = "05406479"
----> 5 basindf = nldi.get_basin(
      6     feature_source="nwissite", feature_id=f"USGS-{be_cr_nr_cp}"
      7 )
      8 basindf = basindf.to_crs(epsg=epsg)
      9 basindf.to_file(geospatial_ws / "black_earth_basin.shp")

NameError: name 'nldi' is not defined
[69]:
try:
    nhddf = nldi.get_flowlines(
        navigation_mode="UM",
        distance=9999,
        feature_source="nwissite",
        feature_id=f"USGS-{be_cr_nr_cp}",
    )
    nhddf = nhddf.to_crs(epsg=epsg)
    nhddf.to_file(geospatial_ws / "black_earth_creek_main.shp")
except (ValueError, ConnectionError, NameError):
    nhddf = gpd.read_file(geospatial_ws / "black_earth_creek_main.shp")

Visualize the example “study area”

[70]:
fig, ax = plt.subplots()

basindf.plot(ax=ax, facecolor="None", edgecolor="k")
nhddf.plot(ax=ax)
ctx.add_basemap(
    ax=ax,
    crs=basindf.crs,
    source=ctx.providers.USGS.USTopo
)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[70], line 3
      1 fig, ax = plt.subplots()
----> 3 basindf.plot(ax=ax, facecolor="None", edgecolor="k")
      4 nhddf.plot(ax=ax)
      5 ctx.add_basemap(
      6     ax=ax,
      7     crs=basindf.crs,
      8     source=ctx.providers.USGS.USTopo
      9 )

NameError: name 'basindf' is not defined
../../_images/notebooks_part1_flopy_05_Unstructured_Grid_generation_133_1.png

Grid refinement control points are defined here for the optional part of this exercise

[71]:
wsloc = basindf.centroid.values[0]
stloc = list(zip(*nhddf.geometry.values[10].xy))[0]
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[71], line 1
----> 1 wsloc = basindf.centroid.values[0]
      2 stloc = list(zip(*nhddf.geometry.values[10].xy))[0]

NameError: name 'basindf' is not defined
[ ]:

[ ]:

[ ]: