I am generating a groundwater elevation contour and a streamplot in matplotlib
The contour indicates that the elevation is decreasing in many areas but the groundwater flow (streamplot) is pointed uphill. I have circled the arrows that seem to be pointed the wrong direction.
The arrows toward the bottom of the map appear to be pointed the correct direction. Does anyone know why this might be?
And here is most of the code which generates this plot:
#create empty arrays to fill up!
x_values = []
y_values = []
z_values = []
#iterate over wells and fill the arrays with well data
for well in well_arr:
x_values.append(well['xpos'])
y_values.append(well['ypos'])
z_values.append(well['value'])
#initialize numpy array as required for interpolation functions
x = np.array(x_values, dtype=np.float)
y = np.array(y_values, dtype=np.float)
z = np.array(z_values, dtype=np.float)
#create a list of x, y coordinate tuples
points = zip(x, y)
#create a grid on which to interpolate data
xi, yi = np.linspace(0, image['width'], image['width']),
np.linspace(0, image['height'], image['height'])
xi, yi = np.meshgrid(xi, yi)
#interpolate the data with the matlab griddata function
zi = griddata(x, y, z, xi, yi, interp='nn')
#create a matplotlib figure and adjust the width and heights
fig = plt.figure(figsize=(image['width']/72, image['height']/72))
#create a single subplot, just takes over the whole figure if only one is specified
ax = fig.add_subplot(111, frameon=False, xticks=[], yticks=[])
#create the contours
kwargs = {}
if groundwater_contours:
kwargs['colors'] = 'b'
CS = plt.contour(xi, yi, zi, linewidths=linewidth, **kwargs)
#add a streamplot
dx, dy = np.gradient(zi)
plt.streamplot(xi, yi, dx, dy, color='c', density=1, arrowsize=3)
I'm guessing, but your problem is probably because you have an inherent transpose going on. 2D numpy arrays are indexed as row, column. "x, y" indexing is column, row. In this context, numpy.gradient
is basically going to return dy, dx and not dx, dy.
Try changing the line:
dx, dy = np.gradient(zi)
to:
dy, dx = np.gradient(zi)
Also, if your depths are defined as positive-up, it should be:
dy, dx = np.gradient(-zi)
However, I'm assuming you have positive-down depth conventions, so I'll leave that part of of the examples below. (So higher values are assumed to be deeper/lower in the example data below, and water will flow towards the high values.)
For example, if we modify the code you gave to use random data and fill in a few variables that are coming from outside the scope of your code sample (so that it's a stand-alone example):
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.mlab import griddata
# Generate some reproducible but random data
np.random.seed(1981)
width, height = 200, 300
x, y, z = np.random.random((3,10))
x *= width
y *= height
#create a list of x, y coordinate tuples
points = zip(x, y)
#create a grid on which to interpolate data
xi, yi = np.linspace(0, width, width), np.linspace(0, height, height)
xi, yi = np.meshgrid(xi, yi)
#interpolate the data with the matlab griddata function
zi = griddata(x, y, z, xi, yi, interp='nn')
#create a matplotlib figure and adjust the width and heights
fig = plt.figure()
#create a single subplot, just takes over the whole figure if only one is specified
ax = fig.add_subplot(111, frameon=False, xticks=[], yticks=[])
#create the contours
CS = plt.contour(xi, yi, zi, linewidths=1, colors='b')
#add a streamplot
dx, dy = np.gradient(zi)
plt.streamplot(xi, yi, dx, dy, color='c', density=1, arrowsize=3)
plt.show()
The result will look like this:
Notice that there are lots of places where the flow lines are not perpendicular to the contours. That's an even easier indicator than the incorrect direction of the arrows that something is going wrong. (Though "perpendicular" assumes an aspect ratio of 1 for the plot, which isn't quite true for these plots unless you set it.)
If we just change the line
dx, dy = np.gradient(zi)
to:
dy, dx = np.gradient(zi)
We'll get the correct result:
On a side note, griddata
is a poor choice in this case.
First, it's not a "smooth" interpolation method. It uses delaunay triangulation, which makes "sharp" ridges at triangle boundaries. This leads to anomalous gradients in those locations.
Second, it limits interpolation to the convex hull of your data points, which may or may not be a good choice.
A radial basis function (or any other smooth interpolant) is a much better choice for interpolation.
As an example, if we modify your code snippet to use an RBF:
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import Rbf
# Generate data
np.random.seed(1981)
width, height = 200, 300
x, y, z = np.random.random((3,10))
x *= width
y *= height
#create a grid on which to interpolate data
xi, yi = np.mgrid[0:width:1j*width, 0:height:1j*height]
#interpolate the data with the matlab griddata function
interp = Rbf(x, y, z, function='linear')
zi = interp(xi, yi)
#create a matplotlib figure and adjust the width and heights
fig, ax = plt.subplots(subplot_kw=dict(frameon=False, xticks=[], yticks=[]))
#create the contours and streamplot
CS = plt.contour(xi, yi, zi, linewidths=1, colors='b')
dy, dx = np.gradient(zi.T)
plt.streamplot(xi[:,0], yi[0,:], dx, dy, color='c', density=1, arrowsize=3)
plt.show()
(You'll notice the intersections are not quite perpendicular due to the non-equal aspect ratio of the plot. They're all 90 degrees if we set the aspect ratio of the plot to 1, however.)
As a side-by-side comparison of the two methods:
You can specify the arrow style with arrowstyle='->'
. Try both of these and see if this works for you:
plt.streamplot(xi, yi, dx, dy, color='c', density=1, arrowsize=3,
arrowstyle='<-')
plt.streamplot(xi, yi, dx, dy, color='c', density=1, arrowsize=3,
arrowstyle='->')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With