I would like to mask the Land area from Sea Surface Temperature Data over the globe. I am using Cartopy to plot the data.
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from netCDF4 import Dataset
f = Dataset('sst.mnmean.nc')
sst = f.variables['sst'][0,:,:]
lats = f.variables['lat'][:]
lons = f.variables['lon'][:]
ax = plt.axes(projection=ccrs.PlateCarree())
ax.coastlines()
plot = ax.contourf(lons, lats, sst, 60, transform=ccrs.PlateCarree())
cb = plt.colorbar(plot)
plt.show()
The above code plots data like this:
I would like to mask out the Land from this.
I went through the cartopy documentation and came across the method called add_feature. The code is as follows:
import numpy as np
import matplotlib.pyplot as plt
import cartopy as cart
from mpl_toolkits.basemap import Basemap
from netCDF4 import Dataset
f = Dataset('sst.mnmean.nc')
sst = f.variables['sst'][0,:,:]
lats = f.variables['lat'][:]
lons = f.variables['lon'][:]
ax = plt.axes(projection=cart.crs.PlateCarree())
ax.coastlines()
ax.add_feature(cart.feature.LAND, zorder=100, edgecolor='k')
ax.set_global()
plot = ax.contourf(lons, lats, sst, 60, transform=cart.crs.PlateCarree())
cb = plt.colorbar(plot)
plt.show()
The plot now looks like this.
To mask the oceans, change cart.feature.LAND to cart.feature.OCEAN
The accepted solution does not really mask the data, the plot is simply covered in parts by overlying a map. While this nicely works for the given problem, sometimes an actual mask is required to remove unwanted parts of the data. Such a mask can easily be created based on a rasterized map for land or ocean.
With the code below, a temporary figure is created, whose resolution corresponds to the given data. After plotting the land map, a rasterized image of the map is obtained with tostring_rgb(). This image, similar to a binary image, can then be directly used to create the mask for the data.
The advantage of this solution is that it can be applied to more general problems, such as plotting two different data sets over land and ocean respectively. The benefit improves when plotting image-like data since transparency can be used to achieve smooth edges by considering the color gradient of the rasterized mask. This can easily be done with PIL.Image.fromarray(mask) followed by convert('L') and finally applying putalpha(mask) on the given image.
import matplotlib.pyplot as plt
import numpy as np
import cartopy
import netCDF4
# load data
data = netCDF4.Dataset('sst.mnmean.nc')
sst = data.variables['sst'][0,:,:]
lats = data.variables['lat'][:]
lons = data.variables['lon'][:]
# prepare temporary plot and create mask from rasterized map
proj = {'projection': cartopy.crs.PlateCarree()}
fig, ax = plt.subplots(figsize=(len(lons)/100, len(lats)/100), dpi=100, subplot_kw=proj)
fig.subplots_adjust(left=0.0, bottom=0.0, right=1.0, top=1.0)
ax.set_frame_on(False)
ax.add_feature(cartopy.feature.LAND, facecolor='black')
fig.canvas.draw()
mask = fig.canvas.tostring_rgb()
ncols, nrows = fig.canvas.get_width_height()
plt.close(fig)
mask = np.frombuffer(mask, dtype=np.uint8).reshape(nrows, ncols, 3)
mask = mask.mean(axis=2)
sst = np.where(mask>0, sst, np.nan)
# create actual plot
fig, ax = plt.subplots(subplot_kw=proj)
ax.contourf(lons, lats, sst, 60, transform=cartopy.crs.PlateCarree(central_longitude=180))
ax.coastlines()
plt.show()
For masking land area, it would be easier to use basemap.
from mpl_toolkits.basemap import Basemap
map = Basemap(projection='mill',lon_0=180) # create projection
.... # whatever processing needed
map.fillcontinents(color='coral') # mask land mass
See basemap example here
Related
I am processing x, y, and z data to have a floor map with high and lows. Z being a displacement sensor. I need to plot a topographical map with gradients. I currently have a 3D scatter plot and a contour plot using matplotlib widgets. Those work great, but a wireframe map or topgraphical map would work best. Either 2D or 3D work as well. Thank you in advance!
Current outputs:
3D Scatter
3D Contour
Example of what I am trying to achieve:
Bokeh surface 3D plot
2D plot
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import holoviews as hv
from bokeh.models import ColumnDataSource
from mpl_toolkits.mplot3d import Axes3D
from holoviews import opts
hv.extension('bokeh', 'matplotlib')
%matplotlib widget
%matplotlib inline
%matplotlib nbagg
%matplotlib ipympl
plt.style.use('seaborn-white')
#Extend width of Jupyter Notebook
from IPython.core.display import display, HTML
display(HTML("<style>.container { width:100% !important; }</style>"))
#Read CSV
df = pd.read_csv('Floor Scan.csv')
clean_df = df.dropna(axis = 0, how ='any')
print(clean_df)
print('')
z_offset = (clean_df['Displacement (in)'].min())
z_offset_abs = abs(z_offset)
print("Minimum Z:" + str(z_offset))
#3D SCATTER
fig = plt.figure(figsize=(20,10))
ax = fig.add_subplot(111, projection='3d')
x = clean_df['fActualPosition_X (-)']
y = clean_df['fActualPosition_Y (-)']
z = clean_df['Displacement (in)']
ax.scatter(x, y, (z + z_offset_abs), c='b', marker='^')
plt.xlabel("fActualPosition_X (-)")
plt.ylabel("fActualPosition_Y (-)")
plt.show()
plt.savefig('Floor_Map_Scatter_3D.svg')
#3D CONTOUR
fig = plt.figure(figsize=(20,10))
ax = fig.add_subplot(111, projection='3d')
X = clean_df['fActualPosition_X (-)'].astype(np.uint8)
Y = clean_df['fActualPosition_Y (-)'].astype(np.uint8)
Z = clean_df['Displacement (in)'].astype(np.uint8)
flatX = np.asarray(clean_df['fActualPosition_X (-)'])
flatY = np.asarray(clean_df['fActualPosition_Y (-)'])
flatZ = np.asarray(clean_df['Displacement (in)'])
# flatX, flatY = np.meshgrid(X, Y)
# flatZ = function(flatX, flatY, Z)
# print(flatX)
# print('')
# print(flatY)
# print('')
# print(flatZ)
# print('')
plt.tricontourf(flatX, flatY, (flatZ+z_offset_abs),20)
plt.show();
plt.savefig('Floor_Map_Contour_3D.svg')
It sounds like your original data is in the form of isolated points (from a range-measuring device like LIDAR?), and what you want is not simply to plot those points, but first to infer or interpolate a surface from those points and then plot that surface. The two desired examples both take an already calculated grid of values and plot them either as a surface or as an image, so first you need to make such a grid, which is not strictly a plotting problem but one of data processing.
One typical way of creating the grid is to aggregate the values into Cartesian coordinates, basically just counting the average value of the scatter points per grid cell. Another is to connect up all the points into a triangular mesh, which may or may not actually form a surface (a function mapping from x,y -> z).
You can use our library Datashader to aggregate just about any set of data into a regular grid, and can then display it as images or contours using hvPlot (https://hvplot.holoviz.org/user_guide/Gridded_Data.html) or as a surface or wireframe using HoloViews (http://holoviews.org/reference/elements/plotly/Surface.html#elements-plotly-gallery-surface).
If you want an unstructured grid, you can use scipy.spatial to compute a triangulation, then HoloViews to visualize it (http://holoviews.org/reference/elements/bokeh/TriMesh.html#elements-bokeh-gallery-trimesh).
I'm stuck here.
My code:
import os
import matplotlib.pyplot as plt
from netCDF4 import Dataset as netcdf_dataset
import numpy as np
from cartopy import config
import cartopy.crs as ccrs
fname = os.path.join("path", "file")
dataset = netcdf_dataset(fname)
lats = dataset.variables['lat'][:]
lons = dataset.variables['lon'][:]
IVT = dataset.variables['IVT'][0,:,:]
IVTm = dataset.variables['IVTm'][0,:,:]
ax = plt.axes(projection=ccrs.PlateCarree())
map_ivt=ax.contourf(lons, lats, IVT, 60,
transform=ccrs.PlateCarree())
map_ivt=ax.contourf(lons, lats, IVTm, 60,
transform=ccrs.PlateCarree())
plt.colorbar(map_ivt, orientation='horizontal')
ax.coastlines()
ax.gridlines()
plt.show()
This is my result (I think that my palette is not showing the right value, both the variables have values over 800):
Can't find the proper way to plot 'IVT' and 'IVTm' on the same map as in the example under here (IVT in red, IVTm in blue):
I would need to plot the two quantities with a palette that goes in both way as in the example.
Thank you.
In order to have this work with two separate calls to contourf, you'd have to make a custom colormap that has alpha of 0 for certain values, which seems like a lot of work. The easy way here, if the values really don't overlap, is to combine them together into a single array and plot that with contourf:
IVT_combined = np.where(IVT > 0, IVT, IVTm)
I am trying to plot a CMC grib2 pressure forecast file using matplotlib to plot the pressure contours. The description of the grib2 grid can be found here: https://weather.gc.ca/grib/grib2_reg_10km_e.html. The grib2 file is found in this directory: http://dd.weather.gc.ca/model_gem_regional/10km/grib2/00/000/ and starts with CMC_reg_PRMSL_MSL_0_ps10km followed by the date. It is a grib file containing pressure at mean sea level.
My problem is that I end up having some straight line contours that follow the lines of latitude on top of the actual pressure contours. I thought it might be because I am plotting in PlateCarree as opposed to Geodetic but the contour plot will not allow using Geodetic. The result of my plot is:
Code is as follows:
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import datetime as dt
import cartopy
import cartopy.crs as ccrs
import Nio
gr = Nio.open_file('./data/CMC_reg_PRMSL_MSL_0_ps10km_2018111800_P000.grib2', 'r')
print(gr)
names = gr.variables.keys()
print("Variable Names:", names)
dims = gr.dimensions
print("Dimensions: ", dims)
attr = gr.attributes.keys()
print("Attributes: ", attr)
obs = gr.variables['PRMSL_P0_L101_GST0'][:]
lats = gr.variables["gridlat_0"][:]
lons = gr.variables["gridlon_0"][:]
fig = plt.figure(figsize=(15, 2))
intervals = range(95000, 105000, 400)
ax=plt.axes([0.,0.,1.,1.],projection=ccrs.PlateCarree())
obsobj = plt.contour(lons, lats, obs, intervals, cmap='jet',transform=ccrs.PlateCarree())
states_provinces = cartopy.feature.NaturalEarthFeature(
category='cultural',
name='admin_1_states_provinces_lines',
scale='50m',
facecolor='none')
ax.add_feature(cartopy.feature.BORDERS)
ax.coastlines(resolution='10m')
ax.add_feature(states_provinces,edgecolor='gray')
obsobj.clabel()
colbar =plt.colorbar(obsobj)
Any suggestions would be appreciated.
UPDATE
For anyone without PyNIO the following can be used to reproduce using the dump files in the comments section.
Just remove all the references to NIO and replace the lats, lons, obs assignment with the following.
lats = np.load('lats.dump')
lons = np.load('lons.dump')
obs = np.load('obs.dump')
The problem
The problem is that the grid winds around the earth. Hence there will be points on the grid at -180° whose nearst neighbor sits at +180°, i.e. the grid wraps around the antimeridian. The following plots the grid index along both directions. One can see that the first grid row (black) appears on both sides of the plot.
Hence a contour line following the pacific westwards needs to then cross straight through the plot to continue towards japan on the other side of the plot. This will lead to the undesired lines
A solution
A solution is to mask the outer points of the PlateCarree out. Those occur in the middle of the grid. Cutting the grid at coordinates of longitude larger than 179° or smaller than -179°, as well as leaving the north pole out would look like
where the blue denotes the cut out points.
Applying this to the contour plot gives:
import matplotlib.pyplot as plt
import numpy as np
import cartopy
import cartopy.crs as ccrs
lats = np.load('data/lats.dump')
lons = np.load('data/lons.dump')
obs = np.load('data/obs.dump')
intervals = range(95000, 105000, 400)
fig, ax = plt.subplots(figsize=(15,4), subplot_kw=dict(projection=ccrs.PlateCarree()))
fig.subplots_adjust(left=0.03, right=0.97, top=0.8, bottom=0.2)
mask = (lons > 179) | (lons < -179) | (lats > 89)
maskedobs = np.ma.array(obs, mask=mask)
pc = ax.contour(lons, lats, maskedobs, intervals, cmap='jet', transform=ccrs.PlateCarree())
ax.add_feature(cartopy.feature.BORDERS)
ax.coastlines(resolution='10m')
colbar =plt.colorbar(pc)
plt.show()
If you are sum up your longitude by +180 to avoid negative coordinates, your code should be running. A coordinate transformation should be legit from my point of view.
I'm trying to recreate this projection using matplotlib Fundamental Lune Plot. The reference material associated with this specific projection is here, Carl Tape Moment Tensors
The geophysics behind the plot isn't crucial, but essentially its a projection between longitudes of -30 and 30 degrees and latitudes -90 to 90. I've thought that Basemap might be a good way of creating the projection, but I cannot seem to figure out how to only show this fundamental lune section. Here is what I've been playing around with,but it still shows the entire globe:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
m = Basemap(
resolution='l', # coastline resolution, can be 'l' (low), 'h'
projection='hammer', # Hammer projection
lat_ts=0, # latitude of true scale
lon_0=0, # longitude of the plotting domain center
lat_0=0) # latitude of the plotting domain center
# draw parallels and meridians.
m.drawparallels(np.arange(-90.,90.,10.))
m.drawmeridians(np.arange(-30.,31.,10.))
ax = plt.gca()
plt.show()
Can anybody offer some guidance or suggestions?
In Basemap, I believe the Hammer projection is "global", meaning that it doesn't take extent inputs so it makes sense the entire globe would always show. As of 2022 Basemap is heavily deprecated / not supported.
I was able to make the plot you want using Cartopy instead. The following code produces image below and on the left, with some demo data:
import matplotlib.pyplot as plt
import numpy as np
import cartopy.crs as ccrs
import matplotlib.path as mpath
# The Sinusoidal projection was added to Cartopy in version 0.14
fig = plt.figure(figsize=(3, 5))
ax = fig.add_subplot(111, projection=ccrs.Sinusoidal())
# Here I define a matplotlib Path object to use as the boundary
outlinex = np.concatenate([[-30],np.tile(-30,180), np.tile(30,180),[-30]])
outliney = np.concatenate([[-90],np.arange(-90,90),np.arange(89,-91,-1),[-90]])
outlinecodes = np.array([mpath.Path.MOVETO]+[mpath.Path.LINETO]*360+[mpath.Path.MOVETO])
outlinepath = mpath.Path(np.column_stack([outlinex[::-1], outliney[::-1]]), outlinecodes[::-1])
# For good measure, plot some data
ax.plot(np.arange(-10,25), np.linspace(80,45,35), transform=ccrs.Geodetic())
ax.plot(np.tile(25,91),np.arange(45,-46,-1), transform=ccrs.Geodetic())
# Plot gridlines and set the boundary
ax.gridlines(xlocs=np.arange(-30,31,10), ylocs=np.arange(-90,91,45))
ax.set_boundary(outlinepath, transform=ccrs.Geodetic())
# The plotting will have automatically set the extents, so set them to what we want
ax.set_extent((-30,30,-90,90))
plt.show()
Note, that if you omit the set_boundary elements and just use the set_extent, you'll get the image on the right, rather than the image on the left.
I am making Polar Stereographic Projection maps of some climate model outputs. For some of these data, the plot looks weird. For example, in this figure:
only two color contours showed up while the actual data should span much wider range. Furthermore, a large portion of the region should be blank since the data are masked out by netcdf module already (they are undefined).
from netCDF4 import Dataset
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.basemap import Basemap
from pylab import *
fig_index=1
fig = plt.figure(num=fig_index, figsize=(12,7), facecolor='w')
fbot_levels = arange(0.05,1.0,0.05)
fname='alb.nc4'
ncfile = Dataset(fname, 'r', format='NETCDF4')
TS2=ncfile.variables['SIALB'][0]
LON=ncfile.variables['lon'][:]
LAT=ncfile.variables['lat'][:]
ncfile.close()
lon,lat=np.meshgrid(LON,LAT)
ax2 = plt.axes([0.2, 0.225, 0.6, 0.6])
meridians=[0,1,1,1]
m = Basemap(projection='spstere',lon_0=0,boundinglat=-45)
m.drawcoastlines()
x, y =m(lon,lat)
plt.contourf(x,y,TS2, fbot_levels, origin='lower')
m.drawparallels(np.arange(-90.,120.,15.),labels=[1,0,0,0]) # draw parallels
m.drawmeridians(np.arange(0.,420.,30.),labels=meridians) # draw meridians
coloraxis = [0.1, 0.1, 0.8, 0.035]
cx = fig.add_axes(coloraxis, label='m', title='K')
cbar=plt.colorbar(cax=cx,orientation='horizontal',ticks=list(fbot_levels))
plt.show()
You can find the dataset in netcdf format which is used to generate the figure here
https://dl.dropboxusercontent.com/u/45427012/alb.nc4
I am using basemap-1.0.6 with matplotlib-1.2.1 on py2.7.
Your Basemap object (m) also serves as the mpl axes. When plotting, you should use that instead of using plt.. So:
m.contourf(x,y,TS2, fbot_levels, origin='lower')
Stretching the levels between 0.5 and 0.9 highlights the different contours further.