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.8 | packaged by conda-forge | (main, Dec 5 2024, 14:24:40) [GCC 13.3.0]
numpy version: 2.2.2
matplotlib version: 3.10.0
flopy version: 3.9.2
[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:306: 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:306: 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:306: 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:306: 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:306: 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:306: 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:306: 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:306: 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:306: 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:306: 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:306: 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:306: 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/tmpzrexau86/shapefile_export/model.shp')
[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 0x7efe2f456720>
[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/tmpzrexau86/shapefile_export/hk.shp')
[13]:
m.riv.stress_period_data
[13]:
<flopy.utils.util_list.MfList at 0x7efe5c6075f0>
[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,LineStringorPolygonclasses inflopy.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 0x7efe2ef1c4a0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1c380>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1c3b0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1c0b0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1c440>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1c470>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1c170>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1c200>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1c530>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1c620>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1c6b0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1c740>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1c770>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1c830>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1c8c0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1c920>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1c9b0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1ca40>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1caa0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1cb30>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1cbc0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1cc20>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1ccb0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1cd40>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1cda0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1ce30>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1cec0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1cf20>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1cfb0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d040>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d0a0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d130>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d1c0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d220>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d2b0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d340>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d3a0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d430>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d4c0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d520>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d5b0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d640>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d6a0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d730>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d7c0>,
<flopy.utils.geometry.Polygon at 0x7efe2ef1d820>]
[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/tmpzrexau86/shapefile_export/bcs.shp')
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 | -1033.838507 | 273421.372407 | 5.096773e+06 |
| 1 | 1 | -972.054171 | 273277.310423 | 5.094242e+06 |
| 2 | 2 | -1101.180781 | 276818.804032 | 5.092238e+06 |
| 3 | 3 | -1047.091716 | 275387.476105 | 5.095578e+06 |
| 4 | 4 | -908.052440 | 275518.856308 | 5.093442e+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/tmpzrexau86/shapefile_export/wel_data.shp')
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/tmpzrexau86/shapefile_export/riv_reaches.shp')
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([ 2.11010351, -1.17303947, 1.5189188 , 0.65323827, 0.67534293,
0.97046827, 0.3213523 , 1.10866227, 0.67432519, 0.09519737,
0.87216377, 0.27550969, 1.42316019, 0.07278213, 2.43017982,
0.23761213, 1.25616686, 2.54233497, 1.1894495 , 1.17881039,
0.33468471, 1.39127794, 1.01410957, 2.89868372, 1.10039871,
0.10223812, 1.25619949, 0.587142 , 0.5419148 , 0.92821039,
1.3060369 , 0.60202712, -1.06011751, 0.47308749, -0.15305491,
-0.41231277, 0.90989797, 1.71068004, 1.68535387, -0.2234895 ])
[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/tmpzrexau86/shapefile_export/riv_reaches.shp')
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:306: 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:306: 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:306: 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:306: 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:306: 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:306: 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:306: 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:306: 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:306: 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:306: 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:306: 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:306: 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/tmpzrexau86/shapefile_export/hk.shp')
[36]:
try:
# ignore PermissionError on Windows
temp_dir.cleanup()
except:
pass