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.6 | packaged by conda-forge | (main, Sep 30 2024, 18:08:52) [GCC 13.3.0]
numpy version: 2.1.1
matplotlib version: 3.9.2
flopy version: 3.8.2
[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/tmp58xa7uej/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 0x7f3709545af0>
[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/tmp58xa7uej/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 0x7f370951eba0>
[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 0x7f3709192090>,
 <flopy.utils.geometry.Polygon at 0x7f37091905f0>,
 <flopy.utils.geometry.Polygon at 0x7f3709191e20>,
 <flopy.utils.geometry.Polygon at 0x7f3709192120>,
 <flopy.utils.geometry.Polygon at 0x7f374c5a2480>,
 <flopy.utils.geometry.Polygon at 0x7f37092a6ae0>,
 <flopy.utils.geometry.Polygon at 0x7f370911c080>,
 <flopy.utils.geometry.Polygon at 0x7f370911c1a0>,
 <flopy.utils.geometry.Polygon at 0x7f370911c110>,
 <flopy.utils.geometry.Polygon at 0x7f370911c2f0>,
 <flopy.utils.geometry.Polygon at 0x7f370911c320>,
 <flopy.utils.geometry.Polygon at 0x7f370911c3b0>,
 <flopy.utils.geometry.Polygon at 0x7f370911c440>,
 <flopy.utils.geometry.Polygon at 0x7f370911c4d0>,
 <flopy.utils.geometry.Polygon at 0x7f370911c560>,
 <flopy.utils.geometry.Polygon at 0x7f370911c5f0>,
 <flopy.utils.geometry.Polygon at 0x7f370911c680>,
 <flopy.utils.geometry.Polygon at 0x7f370911c710>,
 <flopy.utils.geometry.Polygon at 0x7f370911c7a0>,
 <flopy.utils.geometry.Polygon at 0x7f370911c830>,
 <flopy.utils.geometry.Polygon at 0x7f370911c8c0>,
 <flopy.utils.geometry.Polygon at 0x7f370911c950>,
 <flopy.utils.geometry.Polygon at 0x7f370911c9e0>,
 <flopy.utils.geometry.Polygon at 0x7f370911ca70>,
 <flopy.utils.geometry.Polygon at 0x7f370911cb00>,
 <flopy.utils.geometry.Polygon at 0x7f370911cb90>,
 <flopy.utils.geometry.Polygon at 0x7f370911cc20>,
 <flopy.utils.geometry.Polygon at 0x7f370911ccb0>,
 <flopy.utils.geometry.Polygon at 0x7f370911cd40>,
 <flopy.utils.geometry.Polygon at 0x7f370911cdd0>,
 <flopy.utils.geometry.Polygon at 0x7f370911ce60>,
 <flopy.utils.geometry.Polygon at 0x7f370911cef0>,
 <flopy.utils.geometry.Polygon at 0x7f370911cf80>,
 <flopy.utils.geometry.Polygon at 0x7f370911d010>,
 <flopy.utils.geometry.Polygon at 0x7f370911d0a0>,
 <flopy.utils.geometry.Polygon at 0x7f370911d130>,
 <flopy.utils.geometry.Polygon at 0x7f370911d1c0>,
 <flopy.utils.geometry.Polygon at 0x7f370911d250>,
 <flopy.utils.geometry.Polygon at 0x7f370911d2e0>,
 <flopy.utils.geometry.Polygon at 0x7f370911d370>,
 <flopy.utils.geometry.Polygon at 0x7f370911d400>,
 <flopy.utils.geometry.Polygon at 0x7f370911d490>,
 <flopy.utils.geometry.Polygon at 0x7f370911d520>,
 <flopy.utils.geometry.Polygon at 0x7f370911d5b0>,
 <flopy.utils.geometry.Polygon at 0x7f370911d640>,
 <flopy.utils.geometry.Polygon at 0x7f370911d6d0>]
[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/tmp58xa7uej/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 -902.573245 273516.843790 5.089025e+06
1 1 -953.350001 273804.621374 5.096600e+06
2 2 -972.716468 276989.196527 5.096587e+06
3 3 -1129.635945 274979.409857 5.095367e+06
4 4 -1155.183342 278021.382541 5.089482e+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/tmp58xa7uej/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/tmp58xa7uej/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.19528723, -0.64711899,  3.18513861,  2.15505478,  2.26469503,
        2.33884757,  1.50166013, -0.6552975 ,  0.39781775,  2.80057885,
        0.33438872,  0.15945925,  2.64090936, -1.4213624 ,  0.49017941,
       -0.93890541, -0.24501289,  1.55328694,  0.62158299,  0.77784069,
        0.37890683,  0.25885062,  0.27568405,  1.16689214,  1.89901724,
        0.95102286,  0.51638401,  0.54204279,  2.27652859,  0.35239003,
        1.40439726,  1.15938013, -0.38577674,  0.38624539,  1.45570676,
        1.57423675,  0.36192747,  1.96014905,  0.57132614,  1.16771826])
[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/tmp58xa7uej/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/tmp58xa7uej/shapefile_export/hk.shp')
../_images/Notebooks_shapefile_export_example_52_1.png
[36]:
try:
    # ignore PermissionError on Windows
    temp_dir.cleanup()
except:
    pass