I have a code that creates a 3d topographic surface from lat, lon and elev data.
I'm using ax.plot_surface, which creates a topographic surface that looks like this:
I would like to smooth the data to create a picture that looks more like this:
Is there a better way to smooth out the interpolation done by mesh grid?
my_data is sorted by [lat,lon,elev] size(912,3)
Code below
import os
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from scipy.interpolate import griddata
fig = plt.figure()
ax = Axes3D(fig)
my_data = np.genfromtxt('2014_0.01_v3_HDF5.txt', delimiter = ',', skip_header = 1)
my_data[my_data==0] = np.nan
my_data = my_data[~np.isnan(my_data).any(axis=1)]
X = my_data[:,0]
Y = my_data[:,1]
Z = my_data[:,2]
xi = np.linspace(X.min(),X.max(),(len(Z)/3))
yi = np.linspace(Y.min(),Y.max(),(len(Z)/3))
zi = griddata((X, Y), Z, (xi[None,:], yi[:,None]), method='nearest')
xig, yig = np.meshgrid(xi, yi)
surf = ax.plot_surface(xig, yig, zi, cmap='gist_earth')
fig.colorbar(surf, shrink=0.5, aspect=5)
ax.set_title('2014 ATM Data 0.01 Degree Spacing')
ax.set_xlabel('Latitude')
ax.set_ylabel('Longitude')
ax.set_zlabel('Elevation (m)')
ax.set_zlim3d(0,8000)
You can replace the method of interpolation from nearest to cubic. It gives you a far better surface.
zi = griddata((X, Y), Z, (xi[None,:], yi[:,None]), method='cubic')
Related
I am trying to follow a MATLAB example of meshgrid + interpolation. The example code is found HERE. On that site, I am going through the following example: Example – Displaying Nonuniform Data on a Surface.
Now, I would like to produce a similar plot in Python (Numpy + Matplotlib) to what is shown there in MATLAB. This is the plot that MATLAB produces:
I am having trouble with doing this in Python. Here is my code and my output in Python 2.7:
from matplotlib.mlab import griddata
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
x = np.random.rand(200)*16 - 8
y = np.random.rand(200)*16 - 8
r = np.sqrt(x**2 + y**2)
z = np.sin(r)/r
xi = np.linspace(min(x),max(x), 100)
yi = np.linspace(min(y),max(y), 200)
X,Y = np.meshgrid(xi,yi)
Z = griddata(x, y, z, X, Y, interp='linear')
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1,cmap=cm.jet)
Here is the result of my attempt at doing this with matplotlib and NumPy..
Could someone please help me recreate the MATLAB plot in matplotlib, as either a mesh or a surface plot?
So it seems that the major differences in the look have to do with the default number of lines plotted by matlab, which can be adjusted by increasing rstride and cstride. In terms of color, in order for the colormap to be scaled properly it is probably best in this case to set your limits, vmin and vmax because when automatically set, it will use the min and max of Z, but in this case, they are both nan, so you could use np.nanmin and np.nanmax.
from matplotlib.mlab import griddata
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
x = np.random.rand(200)*16 - 8
y = np.random.rand(200)*16 - 8
r = np.sqrt(x**2 + y**2)
z = np.sin(r)/r
xi = np.linspace(min(x),max(x), 100)
yi = np.linspace(min(y),max(y), 200)
X,Y = np.meshgrid(xi,yi)
Z = griddata(x, y, z, X, Y, interp='linear')
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X, Y, Z, rstride=5, cstride=5, cmap=cm.jet, vmin=np.nanmin(Z), vmax=np.nanmax(Z), shade=False)
scat = ax.scatter(x, y, z)
In matplotlib unfortunately I get some annoying overlapping/'clipping' problems, where Axes3d doesn't always properly determine the order in which object should be displayed.
I have a polygon shapefile (the state of Illinois) and a CSV file with (lat, lon, zvalue). I want to plot a smooth contour plot representing those zvalues. Following is my code:
import glob
import fiona
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from mpl_toolkits.basemap import Basemap
from matplotlib.mlab import griddata
# Read in the tabulated data
tabfname = glob.glob("Outputs\\*.csv")[0]
df = pd.read_table(tabfname, sep=",")
print(df.head())
lat, lon, z = list(df.y), list(df.x), list(df["Theil Sen Slope"])
z0, z1, z2 = np.min(z)+0.03, np.mean(z), np.max(z)-0.01
# Read some metadata of the shapefile
shp = glob.glob("GIS\\*.shp")[0]
with fiona.drivers():
with fiona.open(shp) as src:
bnds = src.bounds
extent = [values for values in bnds]
lono = np.mean([extent[0], extent[2]])
lato = np.mean([extent[1], extent[3]])
llcrnrlon = extent[0]-0.5
llcrnrlat = extent[1]-0.5
urcrnrlon = extent[2]+0.5
urcrnrlat = extent[3]+0.5
# Create a Basemap
fig = plt.figure()
ax = fig.add_subplot(111)
m = Basemap(llcrnrlon=llcrnrlon, llcrnrlat=llcrnrlat,
urcrnrlon=urcrnrlon, urcrnrlat=urcrnrlat,
resolution='i', projection='tmerc' , lat_0 = lato, lon_0 = lono)
# Read in and display the shapefile
m.readshapefile(shp.split(".")[0], 'shf', zorder=2, drawbounds=True)
# Compute the number of bins to aggregate data
nx = 100
ny = 100
# Create a mesh and interpolate data
xi = np.linspace(llcrnrlon, urcrnrlon, nx)
yi = np.linspace(llcrnrlat, urcrnrlat, ny)
xgrid, ygrid = np.meshgrid(xi, yi)
xs, ys = m(xgrid, ygrid)
zs = griddata(lon, lat, z, xgrid, ygrid, interp='nn')
# Plot the contour map
conf = m.contourf(xs, ys, zs, 30, zorder=1, cmap='jet')
cbar = m.colorbar(conf, location='bottom', pad="5%", ticks=(z0, z1, z2))
# Scatter plot of the points that make up the contour
for x, y in zip(lon, lat):
X, Y = m(x,y)
m.scatter(X, Y, zorder=4, color='black', s=1)
plt.show()
fig.savefig("Myplot.png", format="png")
And this is the output I got(The scattered black dots are there to show the spatial distribution of the points from which the interpolation was generated. I used Nearest Neighbor interpolation method here.):
I basically referred to the examples given in the following two links to plot this:
https://gist.github.com/urschrei/29cd446ae8a8ec60ddbc
https://matplotlib.org/basemap/users/examples.html
Now this image has 3 problems:
The interpolated contour does not expand within the whole of the shapefile
The part of the contour plot protruding out of the shapefile boundary is not masked off
The contour is not smooth.
What I want is to overcome these three deficiencies of my plot and generate a smooth and nice looking plot similar to the ones shown below (Source: https://doi.org/10.1175/JCLI3557.1 ):
How do I achieve that?
How can I read in four columns of data to create a surface plot which is colored by the fourth variable? In my case, the data was generated using four nested for loops, so the rightmost columns change most frequently while the leftmost columns change least frequently.
Here is what I've tried so far. It is creating a solid colored graph but the coloring is wrong.
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
import pylab
from scipy.interpolate import griddata
dat = open('ex.csv', 'w')
dat.write('x,y,z,c\n')
for x in range(20):
for y in range(20):
dat.write(','.join([str(s) for s in [x,y,x+y,x+y,'\n']]))
dat.close()
fig = matplotlib.pyplot.gcf()
subdat = np.genfromtxt('ex.csv', delimiter=',',skiprows=1)
X = subdat[:,0]
Y = subdat[:,1]
Z = subdat[:,2]
C = subdat[:,3]
xi = np.linspace(X.min(),X.max(),100)
yi = np.linspace(Y.min(),Y.max(),100)
zi = griddata((X, Y), Z, (xi[None,:], yi[:,None]), method='cubic')
ci = griddata((X, Y), C, (xi[None,:], yi[:,None]), method='cubic')
ax1 = fig.add_subplot(111, projection='3d')
xig, yig = np.meshgrid(xi, yi)
surf = ax1.plot_surface(xig, yig, zi,facecolors=cm.rainbow(ci))
m = cm.ScalarMappable(cmap=cm.rainbow)
m.set_array(ci)
col = plt.colorbar(m)
plt.show()
(coloring is wrong, should be the same as elevation value with continuous gradient)
The problem here is that the facecolors aren't normalizing as might be expected. Try this, which does the normalizing explicitely:
norm = matplotlib.colors.Normalize()
surf = ax1.plot_surface(xig, yig, zi, facecolors=cm.rainbow(norm(ci)))
Is it possible to plot multiple surfaces in one pyplot figure? Here is my attempt. The ax.plot_surface command seems to reset the figure, as I only get a single plane in the resulting plot. I am hoping to produce "stacked" planes, each with distinctive colors, and a color bar showing the numeric value of each color. Currently my colors show up wrong.
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
import pylab
from scipy.interpolate import griddata
dat = open('ex.csv', 'w')
dat.write('x,y,z,c\n')
for x in range(20):
for y in range(20):
for c in range(0,7):
dat.write(','.join([str(s) for s in [x,y,x+y+c,c/10.0,'\n']]))
dat.close()
fig = matplotlib.pyplot.gcf()
dat = np.genfromtxt('ex.csv', delimiter=',',skip_header=1)
X_dat = dat[:,0]
Y_dat = dat[:,1]
Z_dat = dat[:,2]
C_dat = dat[:,3]
ax1 = fig.add_subplot(111, projection='3d')
for color in np.unique(C_dat):
X, Y, Z, C = np.array([]), np.array([]), np.array([]), np.array([])
for i in range(len(X_dat)):
if C_dat[i]==color:
X = np.append(X,X_dat[i])
Y = np.append(Y,Y_dat[i])
Z = np.append(Z,Z_dat[i])
C = np.append(C,C_dat[i])
xi = np.linspace(X.min(),X.max(),100)
yi = np.linspace(Y.min(),Y.max(),100)
zi = griddata((X, Y), Z, (xi[None,:], yi[:,None]), method='cubic')
ci = griddata((X, Y), C, (xi[None,:], yi[:,None]), method='cubic')
xig, yig = np.meshgrid(xi, yi)
surf = ax1.plot_surface(xig, yig, zi,facecolors=cm.rainbow(ci), alpha = 0.7)
xi = np.linspace(X_dat.min(),X_dat.max(),100)
yi = np.linspace(Y_dat.min(),Y_dat.max(),100)
ci = griddata((X_dat, Y_dat), C_dat, (xi[None,:], yi[:,None]), method='cubic')
m = cm.ScalarMappable(cmap=cm.rainbow)
m.set_array(ci)
col = plt.colorbar(m)
plt.show()
(there should be a red plane)
Move the line
ax1 = fig.add_subplot(111, projection='3d')
outside of the for color in... loop. By recreating the axes each iteration, you hide the previously created surfaces
EDIT (to answer second question about colormaps)
You need to normalise your data. Currently, you have facecolors in the range 0 to 0.6, so when you feed the maximum (0.6) to cm.rainbow, you get green, not red (since it expects a range of 0 to 1).
Here's a modified script, which I think works as it should. We use Normalise from matplotlib.colors with a vmin and vmax determined from your C_dat data. Then, use facecolors=cm.rainbow(norm(ci)) to set the colors of your surfaces.
You also then want to set the array of your ScalarMappable using the values in C_dat, so we don't need to use griddata again here.
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib.colors as colors
from mpl_toolkits.mplot3d import Axes3D
import pylab
from scipy.interpolate import griddata
dat = open('ex.csv', 'w')
dat.write('x,y,z,c\n')
for x in range(20):
for y in range(20):
for c in range(0,7):
dat.write(','.join([str(s) for s in [x,y,x+y+c,c/10.0,'\n']]))
dat.close()
fig = matplotlib.pyplot.gcf()
dat = np.genfromtxt('ex.csv', delimiter=',',skip_header=1)
X_dat = dat[:,0]
Y_dat = dat[:,1]
Z_dat = dat[:,2]
C_dat = dat[:,3]
# Create a Normalize instance.
norm = colors.Normalize(vmin=C_dat.min(),vmax=C_dat.max())
ax1 = fig.add_subplot(111, projection='3d')
for color in np.unique(C_dat):
X, Y, Z, C = np.array([]), np.array([]), np.array([]), np.array([])
for i in range(len(X_dat)):
if C_dat[i]==color:
X = np.append(X,X_dat[i])
Y = np.append(Y,Y_dat[i])
Z = np.append(Z,Z_dat[i])
C = np.append(C,C_dat[i])
xi = np.linspace(X.min(),X.max(),100)
yi = np.linspace(Y.min(),Y.max(),100)
zi = griddata((X, Y), Z, (xi[None,:], yi[:,None]), method='cubic')
ci = griddata((X, Y), C, (xi[None,:], yi[:,None]), method='cubic')
xig, yig = np.meshgrid(xi, yi)
# Note the use of norm in the facecolors option
surf = ax1.plot_surface(xig, yig, zi,facecolors=cm.rainbow(norm(ci)), alpha = 0.7)
m = cm.ScalarMappable(cmap=cm.rainbow)
m.set_array(np.unique(C_dat))
col = plt.colorbar(m)
plt.show()
So I have some 3D data that I am able to plot just fine except the edges look jagged.
The relevant code:
import numpy as np
from matplotlib import cm
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
x = np.arange(-1, 1, 0.01)
y = np.arange(-1, 1, 0.01)
x, y = np.meshgrid(x, y)
rho = np.sqrt(x**2 + y**2)
# Attempts at masking shown here
# My Mask
row=0
while row<np.shape(x)[0]:
col=0
while col<np.shape(x)[1]:
if rho[row][col] > 1:
rho[row][col] = None
col=col+1
row=row+1
# Calculate & Plot
z = rho**2
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(x, y, z, rstride=8, cstride=8, cmap=cm.bone, alpha=0.15, linewidth=0.25)
plt.show()
Produces:
This is so close to what I want except the edges are jagged.
If I disable my mask in the code above & replace it with rho = np.ma.masked_where(rho > 1, rho) it gives:
It isn't jagged but not want I want in the corners.
Any suggestions on different masking or plotting methods to get rid of this jaggedness?
Did you consider using polar coordinates (like in this example) ?
Something like:
import numpy as np
from matplotlib import cm
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# create supporting points in polar coordinates
r = np.linspace(0,1.25,50)
p = np.linspace(0,2*np.pi,50)
R,P = np.meshgrid(r,p)
# transform them to cartesian system
x, y = R * np.cos(P), R * np.sin(P)
rho = np.sqrt(x**2 + y**2)
# Calculate & Plot
z = rho**2
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(x, y, z, rstride=1, cstride=1, cmap=cm.bone, alpha=0.15, linewidth=0.25)
plt.show()