This page was generated from shapefile_export_example.py. It's also available as a notebook.

Shapefile export demo

The goal of this notebook is to demonstrate ways to export model information to shapefiles. This example will cover: * basic exporting of information for a model, individual package, or dataset * custom exporting of combined data from different packages * general exporting and importing of geographic data from other sources

[1]:
import os
[2]:
import sys
from tempfile import TemporaryDirectory

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

import flopy

print(sys.version)
print(f"numpy version: {np.__version__}")
print(f"matplotlib version: {mpl.__version__}")
print(f"flopy version: {flopy.__version__}")
3.12.5 | packaged by conda-forge | (main, Aug  8 2024, 18:36:51) [GCC 12.4.0]
numpy version: 2.1.0
matplotlib version: 3.9.2
flopy version: 3.8.1
[3]:
# temporary directory
temp_dir = TemporaryDirectory()
outdir = os.path.join(temp_dir.name, "shapefile_export")

# load an existing model
model_ws = "../../examples/data/freyberg"
m = flopy.modflow.Modflow.load(
    "freyberg.nam",
    model_ws=model_ws,
    verbose=False,
    check=False,
    exe_name="mfnwt",
)
[4]:
m.get_package_list()
[4]:
['DIS', 'BAS6', 'LPF', 'WEL', 'RIV', 'RCH', 'OC', 'PCG']

set the model coordinate information

the coordinate information where the grid is located in a projected coordinate system (e.g. UTM)

[5]:
grid = m.modelgrid
grid.set_coord_info(xoff=273170, yoff=5088657, crs=26916)
[6]:
grid.extent
[6]:
(np.float64(273170.0),
 np.float64(278170.0),
 np.float64(5088657.0),
 np.float64(5098657.0))

Declarative export using attached .export() methods

Export the whole model to a single shapefile

[7]:
fname = f"{outdir}/model.shp"
m.export(fname)
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for delc array, DIS package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for delr array, DIS package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for laycbd array, DIS package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for nstp array, DIS package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for perlen array, DIS package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for steady array, DIS package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for tsmult array, DIS package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for chani array, LPF package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for layavg array, LPF package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for laytyp array, LPF package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for layvka array, LPF package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for laywet array, LPF package
  warn(
[8]:
ax = plt.subplot(1, 1, 1, aspect="equal")
extents = grid.extent
pc = flopy.plot.plot_shapefile(fname, ax=ax, edgecolor="k", facecolor="none")
ax.set_xlim(extents[0], extents[1])
ax.set_ylim(extents[2], extents[3])
ax.set_title(fname)
[8]:
Text(0.5, 1.0, '/tmp/tmphkdgh9y6/shapefile_export/model.shp')
../_images/Notebooks_shapefile_export_example_10_1.png
[9]:
fname = f"{outdir}/wel.shp"
m.wel.export(fname)

Export a package to a shapefile

Export a FloPy list or array object

[10]:
m.lpf.hk
[10]:
<flopy.utils.util_array.Util3d at 0x7f4a90c407d0>
[11]:
fname = f"{outdir}/hk.shp"
m.lpf.hk.export(f"{outdir}/hk.shp")
[12]:
ax = plt.subplot(1, 1, 1, aspect="equal")
extents = grid.extent
a = m.lpf.hk.array.ravel()
pc = flopy.plot.plot_shapefile(fname, ax=ax, a=a)
ax.set_xlim(extents[0], extents[1])
ax.set_ylim(extents[2], extents[3])
ax.set_title(fname)
[12]:
Text(0.5, 1.0, '/tmp/tmphkdgh9y6/shapefile_export/hk.shp')
../_images/Notebooks_shapefile_export_example_16_1.png
[13]:
m.riv.stress_period_data
[13]:
<flopy.utils.util_list.MfList at 0x7f4a90c40d10>
[14]:
m.riv.stress_period_data.export(f"{outdir}/riv_spd.shp")

MfList.export() exports the whole grid by default, regardless of the locations of the boundary cells

sparse=True only exports the boundary cells in the MfList

[15]:
m.riv.stress_period_data.export(f"{outdir}/riv_spd.shp", sparse=True)
[16]:
m.wel.stress_period_data.export(f"{outdir}/wel_spd.shp", sparse=True)

Ad-hoc exporting using recarray2shp

  • The main idea is to create a recarray with all of the attribute information, and a list of geometry features (one feature per row in the recarray)

  • each geometry feature is an instance of the Point, LineString or Polygon classes in flopy.utils.geometry. The shapefile format requires all the features to be of the same type.

  • We will use pandas dataframes for these examples because they are easy to work with, and then convert them to recarrays prior to exporting.

[17]:
from flopy.export.shapefile_utils import recarray2shp

combining data from different packages

write a shapefile of RIV and WEL package cells

[18]:
wellspd = pd.DataFrame(m.wel.stress_period_data[0])
rivspd = pd.DataFrame(m.riv.stress_period_data[0])
spd = pd.concat([wellspd, rivspd])
spd.head()
[18]:
k i j flux iface stage cond rbot
0 0 8 15 -0.00820 0.0 NaN NaN NaN
1 0 10 12 -0.00410 0.0 NaN NaN NaN
2 0 19 13 -0.00390 0.0 NaN NaN NaN
3 0 25 9 -0.00083 0.0 NaN NaN NaN
4 0 28 5 -0.00072 0.0 NaN NaN NaN
[19]:
from flopy.utils.geometry import Polygon

vertices = []
for row, col in zip(spd.i, spd.j):
    vertices.append(grid.get_cell_vertices(row, col))
polygons = [Polygon(vrt) for vrt in vertices]
polygons
[19]:
[<flopy.utils.geometry.Polygon at 0x7f4a907ecb00>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ec350>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ec230>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ec560>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ecb30>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ec590>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ec3e0>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ecc50>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ec2c0>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ecb90>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ecdd0>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ece60>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ecf20>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ecf80>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ed1f0>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ed280>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ed310>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ed3a0>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ed430>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ed4c0>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ed550>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ed5e0>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ed670>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ed700>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ed790>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ed820>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ed8b0>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ed940>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ed9d0>,
 <flopy.utils.geometry.Polygon at 0x7f4a907eda60>,
 <flopy.utils.geometry.Polygon at 0x7f4a907edaf0>,
 <flopy.utils.geometry.Polygon at 0x7f4a907edb80>,
 <flopy.utils.geometry.Polygon at 0x7f4a907edc10>,
 <flopy.utils.geometry.Polygon at 0x7f4a907edca0>,
 <flopy.utils.geometry.Polygon at 0x7f4a907edd30>,
 <flopy.utils.geometry.Polygon at 0x7f4a907eddc0>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ede50>,
 <flopy.utils.geometry.Polygon at 0x7f4a907edee0>,
 <flopy.utils.geometry.Polygon at 0x7f4a907edf70>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ee000>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ee090>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ee120>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ee1b0>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ee240>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ee2d0>,
 <flopy.utils.geometry.Polygon at 0x7f4a907ee360>]
[20]:
fname = f"{outdir}/bcs.shp"
recarray2shp(spd.to_records(), geoms=polygons, shpname=fname, crs=grid.epsg)
[21]:
ax = plt.subplot(1, 1, 1, aspect="equal")
extents = grid.extent
pc = flopy.plot.plot_shapefile(fname, ax=ax)
ax.set_xlim(extents[0], extents[1])
ax.set_ylim(extents[2], extents[3])
ax.set_title(fname)
[21]:
Text(0.5, 1.0, '/tmp/tmphkdgh9y6/shapefile_export/bcs.shp')
../_images/Notebooks_shapefile_export_example_30_1.png

exporting other data

Suppose we have some well data with actual locations that we want to export to a shapefile

[22]:
welldata = pd.DataFrame(
    {
        "wellID": np.arange(0, 10),
        "q": np.random.randn(10) * 100 - 1000,
        "x_utm": np.random.rand(10) * 5000 + grid.xoffset,
        "y_utm": grid.yoffset + np.random.rand(10) * 10000,
    }
)
welldata.head()
[22]:
wellID q x_utm y_utm
0 0 -1043.960901 274148.312327 5.097112e+06
1 1 -1023.794620 274589.655406 5.093667e+06
2 2 -983.827382 275378.803076 5.098484e+06
3 3 -1148.416268 273379.672683 5.093523e+06
4 4 -1190.611024 276525.706170 5.096648e+06
[23]:
from flopy.utils.geometry import Point

geoms = [Point(x, y) for x, y in zip(welldata.x_utm, welldata.y_utm)]

fname = f"{outdir}/wel_data.shp"
recarray2shp(welldata.to_records(), geoms=geoms, shpname=fname, crs=grid.epsg)
[24]:
ax = plt.subplot(1, 1, 1, aspect="equal")
extents = grid.extent
pc = flopy.plot.plot_shapefile(fname, ax=ax, radius=100)
ax.set_xlim(extents[0], extents[1])
ax.set_ylim(extents[2], extents[3])
ax.set_title(fname)
[24]:
Text(0.5, 1.0, '/tmp/tmphkdgh9y6/shapefile_export/wel_data.shp')
../_images/Notebooks_shapefile_export_example_35_1.png

Adding attribute data to an existing shapefile

Suppose we have a GIS coverage representing the river in the riv package

[25]:
from flopy.utils.geometry import LineString

### make up a linestring shapefile of the river reaches
i, j = m.riv.stress_period_data[0].i, m.riv.stress_period_data[0].j
x0 = grid.xyzcellcenters[0][i[0], j[0]]
x1 = grid.xyzcellcenters[0][i[-1], j[-1]]
y0 = grid.xyzcellcenters[1][i[0], j[0]]
y1 = grid.xyzcellcenters[1][i[-1], j[-1]]
x = np.linspace(x0, x1, m.nrow + 1)
y = np.linspace(y0, y1, m.nrow + 1)
l0 = zip(list(zip(x[:-1], y[:-1])), list(zip(x[1:], y[1:])))
lines = [LineString(l) for l in l0]

rivdata = pd.DataFrame(m.riv.stress_period_data[0])
rivdata["reach"] = np.arange(len(lines))
lines_shapefile = f"{outdir}/riv_reaches.shp"
recarray2shp(
    rivdata.to_records(index=False),
    geoms=lines,
    shpname=lines_shapefile,
    crs=grid.epsg,
)
[26]:
ax = plt.subplot(1, 1, 1, aspect="equal")
extents = grid.extent
pc = flopy.plot.plot_shapefile(lines_shapefile, ax=ax, radius=25)
ax.set_xlim(extents[0], extents[1])
ax.set_ylim(extents[2], extents[3])
ax.set_title(lines_shapefile)
[26]:
Text(0.5, 1.0, '/tmp/tmphkdgh9y6/shapefile_export/riv_reaches.shp')
../_images/Notebooks_shapefile_export_example_38_1.png

shp2recarray reads a shapefile into a numpy record array, which can easily be converted to a DataFrame

[27]:
from flopy.export.shapefile_utils import shp2recarray
[28]:
linesdata = shp2recarray(lines_shapefile)
linesdata = pd.DataFrame(linesdata)
linesdata.head()
[28]:
k i j stage cond rbot iface reach geometry
0 0 0 14 20.100000 0.05 20.00 0.0 0 <flopy.utils.geometry.LineString object at 0x7...
1 0 1 14 19.870001 0.05 19.75 0.0 1 <flopy.utils.geometry.LineString object at 0x7...
2 0 2 14 19.650000 0.05 19.50 0.0 2 <flopy.utils.geometry.LineString object at 0x7...
3 0 3 14 19.420000 0.05 19.25 0.0 3 <flopy.utils.geometry.LineString object at 0x7...
4 0 4 14 19.190001 0.05 19.00 0.0 4 <flopy.utils.geometry.LineString object at 0x7...
[29]:
# make up some fluxes between the river and aquifer at each reach
q = np.random.randn(len(linesdata)) + 1
q
[29]:
array([ 0.3733417 ,  1.55200474,  0.69728781,  3.30299472,  1.41106304,
        0.24762923,  2.33724735,  0.9306728 ,  1.83987057,  0.73033869,
        0.9004142 ,  0.46086947,  0.8576054 ,  0.17617068, -0.32274156,
        2.62648181,  2.24234359, -0.01057331,  0.49170438, -0.34871364,
        0.52854513,  1.25951771,  0.57614206,  1.33933567,  0.75299772,
        1.27387896,  1.49188811,  2.21163679,  0.11383537,  2.39430562,
        0.62890895,  1.78682744,  0.78855643,  0.79052408,  0.92761477,
        1.40261382, -0.10652299,  0.75498096,  0.95103184,  0.1681    ])
[30]:
linesdata["qreach"] = q
linesdata["qstream"] = np.cumsum(q)
[31]:
recarray2shp(
    linesdata.drop("geometry", axis=1).to_records(),
    geoms=linesdata.geometry.values,
    shpname=lines_shapefile,
    crs=grid.epsg,
)
[32]:
ax = plt.subplot(1, 1, 1, aspect="equal")
extents = grid.extent
pc = flopy.plot.plot_shapefile(lines_shapefile, ax=ax, radius=25)
ax.set_xlim(extents[0], extents[1])
ax.set_ylim(extents[2], extents[3])
ax.set_title(lines_shapefile)
[32]:
Text(0.5, 1.0, '/tmp/tmphkdgh9y6/shapefile_export/riv_reaches.shp')
../_images/Notebooks_shapefile_export_example_47_1.png

Overriding the model’s modelgrid with a user supplied modelgrid

In some cases it may be necessary to override the model’s modelgrid instance with a seperate modelgrid. An example of this is if the model discretization is in feet and the user would like it projected in meters. Exporting can be accomplished by supplying a modelgrid as a kwarg in any of the export() methods within flopy. Below is an example:

[33]:
mg0 = m.modelgrid

# build a new modelgrid instance with discretization in meters
modelgrid = flopy.discretization.StructuredGrid(
    delc=mg0.delc * 0.3048,
    delr=mg0.delr * 0.3048,
    top=mg0.top,
    botm=mg0.botm,
    idomain=mg0.idomain,
    xoff=mg0.xoffset * 0.3048,
    yoff=mg0.yoffset * 0.3048,
)

# exporting an entire model
m.export(f"{outdir}/freyberg.shp", modelgrid=modelgrid)
No CRS information for writing a .prj file.
Supply an valid coordinate system reference to the attached modelgrid object or .export() method.
No CRS information for writing a .prj file.
Supply an valid coordinate system reference to the attached modelgrid object or .export() method.
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for delc array, DIS package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for delr array, DIS package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for laycbd array, DIS package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for nstp array, DIS package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for perlen array, DIS package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for steady array, DIS package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for tsmult array, DIS package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for chani array, LPF package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for layavg array, LPF package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for laytyp array, LPF package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for layvka array, LPF package
  warn(
/home/runner/micromamba/envs/flopy/lib/python3.12/site-packages/flopy/export/shapefile_utils.py:319: UserWarning: Failed to get data for laywet array, LPF package
  warn(

And for a specific parameter the method is the same

[34]:
fname = f"{outdir}/hk.shp"
m.lpf.hk.export(fname, modelgrid=modelgrid)
No CRS information for writing a .prj file.
Supply an valid coordinate system reference to the attached modelgrid object or .export() method.
[35]:
ax = plt.subplot(1, 1, 1, aspect="equal")
extents = modelgrid.extent
a = m.lpf.hk.array.ravel()
pc = flopy.plot.plot_shapefile(fname, ax=ax, a=a)
ax.set_xlim(extents[0], extents[1])
ax.set_ylim(extents[2], extents[3])
ax.set_title(fname)
[35]:
Text(0.5, 1.0, '/tmp/tmphkdgh9y6/shapefile_export/hk.shp')
../_images/Notebooks_shapefile_export_example_52_1.png
[36]:
try:
    # ignore PermissionError on Windows
    temp_dir.cleanup()
except:
    pass