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
import sys
[2]:
from pathlib import Path
from tempfile import TemporaryDirectory

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

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.11 | packaged by conda-forge | (main, Jun  4 2025, 14:45:31) [GCC 13.3.0]
numpy version: 2.3.3
matplotlib version: 3.10.6
flopy version: 3.9.5
[3]:
# temporary directory
temp_dir = TemporaryDirectory()
outdir = os.path.join(temp_dir.name, "shapefile_export")

# Check if we are in the repository and define the data path.

try:
    root = Path(git.Repo(".", search_parent_directories=True).working_dir)
except:
    root = None

data_path = root / "examples" / "data" if root else Path.cwd()

sim_name = "freyberg"

file_names = {
    "freyberg.bas": "63266024019fef07306b8b639c6c67d5e4b22f73e42dcaa9db18b5e0f692c097",
    "freyberg.dis": "62d0163bf36c7ee9f7ee3683263e08a0abcdedf267beedce6dd181600380b0a2",
    "freyberg.githds": "abe92497b55e6f6c73306e81399209e1cada34cf794a7867d776cfd18303673b",
    "freyberg.gitlist": "aef02c664344a288264d5f21e08a748150e43bb721a16b0e3f423e6e3e293056",
    "freyberg.lpf": "06500bff979424f58e5e4fbd07a7bdeb0c78f31bd08640196044b6ccefa7a1fe",
    "freyberg.nam": "e66321007bb603ef55ed2ba41f4035ba6891da704a4cbd3967f0c66ef1532c8f",
    "freyberg.oc": "532905839ccbfce01184980c230b6305812610b537520bf5a4abbcd3bd703ef4",
    "freyberg.pcg": "0d1686fac4680219fffdb56909296c5031029974171e25d4304e70fa96ebfc38",
    "freyberg.rch": "37a1e113a7ec16b61417d1fa9710dd111a595de738a367bd34fd4a359c480906",
    "freyberg.riv": "7492a1d5eb23d6812ec7c8227d0ad4d1e1b35631a765c71182b71e3bd6a6d31d",
    "freyberg.wel": "00aa55f59797c02f0be5318a523b36b168fc6651f238f34e8b0938c04292d3e7",
}
for fname, fhash in file_names.items():
    pooch.retrieve(
        url=f"https://github.com/modflowpy/flopy/raw/develop/examples/data/{sim_name}/{fname}",
        fname=fname,
        path=data_path / sim_name,
        known_hash=fhash,
    )

# load an existing model
model_ws = data_path / sim_name
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:307: 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:307: 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:307: 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:307: 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:307: 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:307: 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:307: 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:307: 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:307: 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:307: 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:307: 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:307: 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/tmpfb56b_w9/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 0x7f569e18e810>
[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/tmpfb56b_w9/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 0x7f569e408200>
[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 0x7f569db2c800>,
 <flopy.utils.geometry.Polygon at 0x7f569db2cf80>,
 <flopy.utils.geometry.Polygon at 0x7f569db2c7a0>,
 <flopy.utils.geometry.Polygon at 0x7f569db2cf50>,
 <flopy.utils.geometry.Polygon at 0x7f569db2cfe0>,
 <flopy.utils.geometry.Polygon at 0x7f569db2c140>,
 <flopy.utils.geometry.Polygon at 0x7f569db2c590>,
 <flopy.utils.geometry.Polygon at 0x7f569db2c4a0>,
 <flopy.utils.geometry.Polygon at 0x7f569db2ce00>,
 <flopy.utils.geometry.Polygon at 0x7f569db2c320>,
 <flopy.utils.geometry.Polygon at 0x7f569db2cb60>,
 <flopy.utils.geometry.Polygon at 0x7f569db2c560>,
 <flopy.utils.geometry.Polygon at 0x7f569db2cbc0>,
 <flopy.utils.geometry.Polygon at 0x7f569db2cec0>,
 <flopy.utils.geometry.Polygon at 0x7f569db2cc20>,
 <flopy.utils.geometry.Polygon at 0x7f569db2cce0>,
 <flopy.utils.geometry.Polygon at 0x7f569db2cd40>,
 <flopy.utils.geometry.Polygon at 0x7f569db2cdd0>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d0a0>,
 <flopy.utils.geometry.Polygon at 0x7f569db2c260>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d100>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d190>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d220>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d280>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d310>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d3a0>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d400>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d490>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d520>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d580>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d610>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d6a0>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d700>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d790>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d820>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d880>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d910>,
 <flopy.utils.geometry.Polygon at 0x7f569db2d9a0>,
 <flopy.utils.geometry.Polygon at 0x7f569db2da00>,
 <flopy.utils.geometry.Polygon at 0x7f569db2da90>,
 <flopy.utils.geometry.Polygon at 0x7f569db2db20>,
 <flopy.utils.geometry.Polygon at 0x7f569db2db80>,
 <flopy.utils.geometry.Polygon at 0x7f569db2dc10>,
 <flopy.utils.geometry.Polygon at 0x7f569db2dca0>,
 <flopy.utils.geometry.Polygon at 0x7f569db2dd00>,
 <flopy.utils.geometry.Polygon at 0x7f569db2dd90>]
[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/tmpfb56b_w9/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 -769.964868 277704.701908 5.092909e+06
1 1 -1101.306421 273222.139068 5.090142e+06
2 2 -808.748711 274869.018570 5.093207e+06
3 3 -832.513583 276094.523627 5.089216e+06
4 4 -1003.731919 274646.296976 5.089692e+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/tmpfb56b_w9/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/tmpfb56b_w9/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([ 1.82681392, -0.45301175,  1.61066961,  1.16781698,  1.85756662,
        1.4885357 ,  1.9013058 ,  0.02000442,  1.71525566,  2.72582008,
        1.73890451,  0.49587514,  1.38062726,  2.48088537,  0.14147153,
        2.04048054,  1.87847273,  0.988992  ,  1.61041313,  0.43117195,
       -1.95134159, -0.18799461,  1.6657746 ,  1.08467637,  0.84061121,
        0.54642547,  1.30309341,  0.66247942,  0.86352634, -0.63867731,
        2.13761642,  0.99376175,  1.81231796, -0.57210619,  2.01100063,
        0.82270027,  0.2497115 ,  1.69620545,  1.21715958,  1.71973553])
[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/tmpfb56b_w9/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:307: 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:307: 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:307: 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:307: 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:307: 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:307: 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:307: 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:307: 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:307: 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:307: 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:307: 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:307: 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/tmpfb56b_w9/shapefile_export/hk.shp')
../_images/Notebooks_shapefile_export_example_52_1.png
[36]:
try:
    # ignore PermissionError on Windows
    temp_dir.cleanup()
except:
    pass