weird contour plot with polar projections using matplotlib and basemap - python

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.

Related

Plotting over a background python basemap

How do you go about plotting data over a background image in Python?
For example if I had some gridded pressure data of shape [180,360] (lat,lon)
I could easily plot data by;
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
m = Basemap(projection='ortho',resolution='c',lat_0=45,lon_0=0)
lat = np.linspace(-90,90,180)
lon = np.linspace(-180,180,360)
lon,lat = np.meshgrid(lon,lat)
X, Y = m(lon, lat)
m.contourf(X,Y,Pressure)
plt.show()
etc etc. But if I add a background , e.g.
m.bluemarble()
I cant plot on top of this layer. I've heard of imshow, but how does that take into account gridded data? Not sure how to plot pressure on top of this. Or possibly the alpha attribute in plotting. Thanks!
For example setting alpha to 0.5 in the plt function, I get some horrible mix of colours (and white lines randomly appear);

python: scatter does not display

I have some trouble using scatter in python. I have 2 vectors of size 100 of latitudes and longitudes, and a vector 100 of corresponding data of values between 1 and 2.
I am trying to display these data on a north polar stereographic projected map, but nothing appears on the figure. Here is my code:
import numpy as np
import matplotlib as mpl
from matplotlib import pyplot as plt
from matplotlib import colors as mpl_colors
from mpl_toolkits.basemap import Basemap`
lon=np.linspace(-180,180,num=100)
lat=np.linspace(75,75,num=100)
data=1+np.random.rand(100)
cmap = plt.cm.jet
norm_color = mpl_colors.Normalize(vmin=None, vmax=None, clip=False)
fig=plt.figure()
m = Basemap(projection='npstere',boundinglat=65,lon_0=310,resolution='l')
x,y = m(lon,lat)
m.scatter(x, y,marker='+',c=data,cmap=cmap, norm=norm_color,edgecolor='none')
plt.show()
When I just scatter the coordinates: m.scatter(x, y,marker='+') it works fine (I have "+" at the corresponding coordinates, i.e on the 75° latitude projected circle). But when I want to add the data, nothing is displayed.
Where am I not using scatter right ?
I found what was the problem.
It is the simultaneous use of the options marker='+' and edgecolor='None'.
The latter allows to change the color of the edge of the marker. With the marker '+', the edge is the marker itself: that is why it was not displayed.
In replacement of the line m.scatter(x, y,marker='+',c=data,cmap=cmap, norm=norm_color,edgecolor='none') the following work:
m.scatter(x, y,marker='+',c=data,cmap=cmap, norm=norm_color)
or
m.scatter(x, y,marker='o',c=data,cmap=cmap, norm=norm_color,edgecolor='none')

Mask Ocean or Land from data using Cartopy

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

Half of the world masked when using maskoceans in Basemap

I would like to mask oceans when plotting the data from a netCDF dataset. I followed the great instructions given in the answer to this question. It works great for half of the world, but somehow, everything west of Greenwich is masked as well, both ocean and land.
Here is my code:
import netCDF4
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import mpl_toolkits
from mpl_toolkits import basemap
from mpl_toolkits.basemap import Basemap, maskoceans
filename = 'myfile.nc'
vmin = 0.
vmax = 1
nc = netCDF4.Dataset(filename, 'r')
data = nc.variables['sum'][:]
lats_1d = nc.variables['lat'][:]
lons_1d = nc.variables['lon'][:]
lons, lats = np.meshgrid(lons_1d, lats_1d)
labels = ['DJF', 'MAM', 'JJA', 'SON']
cmap = cm.RdYlBu
cmap.set_over('#00FF00')
my_dpi = 96
fig = plt.figure(figsize=(1200/my_dpi, 800./my_dpi))
for season in range(4):
ax = fig.add_subplot(2, 2, season+1)
map1 = basemap.Basemap(resolution='c', projection='kav7', lon_0=0)
map1.drawcoastlines()
map1.drawcountries()
nc_new = maskoceans(lons,lats,data[season,:,:],resolution='c', grid = 1.25)
datapc = map1.pcolormesh(lons, lats, nc_new, vmin=vmin, vmax=vmax, cmap=cmap, latlon=True)
plt.title(labels[season])
fig.tight_layout(pad=1, w_pad=1, h_pad=4)
ax = fig.add_axes([0.05, 0.52, 0.9, 0.025])
cb = plt.colorbar(cax=ax, orientation='horizontal', cmap=cmap,
extend='max', format="%.2f",
ticks=[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1])
plt.show()
I know that a somewhat similar issue was raised here but never got answered, and it appears that in the end, the problem was mixing up lat-long coordinates with x-y ones. I tried switching to x-y coordinates but got the same half-map. Any idea of what can be happening here?
N.B. when plotting the unmasked data using datapc = map1.pcolormesh(lons, lats, data[season,:,:], vmin=vmin, vmax=vmax, cmap=cmap, latlon=True) the whole world is plotted (land + oceans).
As you've identified, the points with longitudes -180 to 0 are not being plotted. Assuming they're in your data, they must be being masked or discarded for some reason.
My intuition was that the dataset longitudes ran 0-360 instead of -180 to 180, which was confirmed in the comments.
The quick fix for this is to add
lons_1d[lons_1d>180]-=360
just after you pull out lons_1d from nc. This works because lons_1d is a numpy array and it uses numpy boolean array indexing (often called "fancy" indexing) to conditionally select the longitude values greater than 180 and subtract 360 from them.
As you note that the pcolormesh plot works if you omit the mask, this looks like a bug with wrapping in the maskoceans function, or at least unexpected behaviour.
For reference - I do not think you are the first to experience similar "wrapping" type issues with masks, I think this issue on the matplotlib github looks rather similar.

How to change the location the 'r' axis for matplotlib polar plot?

How to change the location the 'r' axis for matplotlib polar plot?
I am trying to change the location of the r axis in a polar plot.
At the moment it is being covered up by the data, but there is a gap in the data at theta = 340-360 degrees (in my real data example this is actually approx. 45 degrees) so it would be good if i could put axis labels there, in the data gap.
import random
import numpy as np
import matplotlib.pyplot as plt
sampleSize=1000
az=[]
inc=[]
for i in range(sampleSize):
az.append(random.randint(0,340)) #not to the full 360 to represent my natural gap in the data
inc.append(random.randint(0,90))
plt.figure()
plt.polar(np.radians(az),inc,'o')
plt.show()
One way that I can think of is to use .set_rgrids method:
f=plt.figure()
ax = f.add_axes([0.1, 0.1, 0.8, 0.8], projection='polar')
ax.plot(np.radians(az), inc, 'o')
ax.set_rgrids([10,20,30,40,50,60,70,80,90], angle=345.)

Categories