I'm very new to Python but have been learning lots over the last few months. I'm trying to plot NOAA swell height data from a grib2 file located here: ftp://ftpprd.ncep.noaa.gov/pub/data/nccf/com/wave/prod/wave.20140122/nww3.t06z.grib.grib2
I use Basemap and a tutorial that I found on a Basemap forum.
A minimum working example is below, but I'm getting some strange white boxes around the coastline.
import Nio
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
f = Nio.open_file('nww3.t12z.grib(2).grib2')
lons = f.variables['lon_0'][:]
lats = f.variables['lat_0'][::-1] # flip latitudes so data goes S-->N
times = f.variables['forecast_time0'][:]
ntime = 5
data = f.variables['HTSGW_P0_L1_GLL0'][ntime,::-1]
fig = plt.figure(figsize=(16,16))
m = Basemap(llcrnrlon=-35.,llcrnrlat=42.,urcrnrlon=5.,urcrnrlat=65.,
projection='lcc',lat_1=10.,lat_2=15.,lon_0=10.,
resolution ='h',area_thresh=1000.)
x, y = m(*np.meshgrid(lons, lats))
m.fillcontinents(color='#477519')
m.drawcoastlines(linewidth=0.5, color='k', antialiased=1, ax=None, zorder=None )
m.contourf(x, y, data, np.arange(0,9.9,0.1))
plt.show()
This is the result (the top panel; I would like it to look like the bottom panel): http://oi43.tinypic.com/s2s3m0.jpg
Sorry I don't have enough points to post images.
Thanks in advance,
Al
Related
So I have written a script for making figures that worked alright (see mock-up example below). But when I add a legend to the figure, the execution time increases a lot. I don't really understand what is happening here, I would naively expect that simply adding a legend is not a complex thing.
I suspect this has something to do with the cartopy projection, since it works alright if I don't use this.
What is the problem here, and how can I avoid this?
Problematic code:
import numpy as np
import xarray as xr
import matplotlib
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
# Mockup dataset
num = 300
lat = np.linspace(-54,-59,num=num)
lon = np.linspace(-5,5, num=num)
data = np.outer(lat,lon)
ds = xr.DataArray(data=data,
dims=["lat", "lon"],
coords=dict(lon=lon, lat=lat))
# Map projection
map_proj = ccrs.SouthPolarStereo()
ax = plt.axes(projection=map_proj)
ax.gridlines(draw_labels=True)
ax.set_extent([-3,4,-58,-54])
# Plot image
ds.plot(cmap="gray",
add_colorbar=False,
transform=ccrs.PlateCarree(), # data projection
subplot_kws={'projection': map_proj}) # map projection
# Plot contours
cs = ds.plot.contour(transform=ccrs.PlateCarree())
# Make legend
proxy = [matplotlib.lines.Line2D([],[], c=pc.get_color()[0]) for pc in cs.collections]
labels = ["foo"] * len(cs.collections)
plt.legend(proxy, labels)
Code without cartopy projection:
import numpy as np
import xarray as xr
import matplotlib
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
# Mockup dataset
num = 300
lat = np.linspace(-54,-59,num=num)
lon = np.linspace(-5,5, num=num)
data = np.outer(lat,lon)
ds = xr.DataArray(data=data,
dims=["lat", "lon"],
coords=dict(lon=lon, lat=lat))
# Plot image
ds.plot(cmap="gray",
add_colorbar=False) # map projection
# Plot contours
cs = ds.plot.contour()
# Make legend
proxy = [matplotlib.lines.Line2D([],[], c=pc.get_color()[0]) for pc in cs.collections]
plt.legend(proxy, labels)
plt.legend(proxy, labels) defaults to loc='best' which uses an algorithm that can be slow if you have lots of data in your axes, and particularly slow if that data also has complicated transforms. Instead do ax.legend(proxy, labels, loc='upper right') manually. See https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.legend.html
I'm trying to plot data around the Antarctica while masking the continent. While I'm using basemap and it has an option to easily mask continents using map.fillcontinents(), the continent considered by basemap includes the ice shelves, which I do not want to mask.
I tried using geopandas from a code I found on the Internet. This works, except the coastline produces an undesired line in what I assume is the beginning/end of the polygon for the Antarctica:
import numpy as np
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
import geopandas as gpd
import shapely
from descartes import PolygonPatch
lats = np.arange(-90,-59,1)
lons = np.arange(0,361,1)
X, Y = np.meshgrid(lons, lats)
data = np.random.rand(len(lats),len(lons))
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
fig=plt.figure(dpi=150)
ax = fig.add_subplot(111)
m = Basemap(projection='spstere',boundinglat=-60,lon_0=180,resolution='i',round=True)
xi, yi = m(X,Y)
cf = m.contourf(xi,yi,data)
patches = []
selection = world[world.name == 'Antarctica']
for poly in selection.geometry:
if poly.geom_type == 'Polygon':
mpoly = shapely.ops.transform(m, poly)
patches.append(PolygonPatch(mpoly))
elif poly.geom_type == 'MultiPolygon':
for subpoly in poly:
mpoly = shapely.ops.transform(m, poly)
patches.append(PolygonPatch(mpoly))
else:
print(poly, 'blah')
ax.add_collection(PatchCollection(patches, match_original=True,color='w',edgecolor='k'))
The same line appears when I try to use other shapefiles, such as the land one that is available to download for free from Natural Earth Data. So I edited this shapefile in QGIS to remove the borders of the Antarctica. The problem now is that I don't know how to mask everything that's inside the shapefile (and couldn't find how to do it either). I also tried combining the previous code with geopandas by setting the linewidth=0, and adding on top the shapefile I created. The problem is that they are not exactly the same:
Any suggestion on how to mask using a shapefile, or with geopandas but without the line?
Edit: Using Thomas Khün's previous answer with my edited shapefile produces a well masked Antarctica/continents, but the coastline goes outside the round edges of the map:
I uploaded here the edited shapefile I used, but it's the Natural Earth Data 50m land shapefile without the line.
Here an example of how to achieve what you want. I basically followed the Basemap example how to deal with shapefiles and added a bit of shapely magic to restrict the outlines to the map boundaries. Note that I first tried to extract the map outline from ax.patches, but that somehow didn't work, so I defined a circle which has a radius of boundinglat and transformed it using the Basemap coordinate transformation functionality.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
from matplotlib.collections import PatchCollection
from matplotlib.patches import Polygon
import shapely
from shapely.geometry import Polygon as sPolygon
boundinglat = -40
lats = np.arange(-90,boundinglat+1,1)
lons = np.arange(0,361,1)
X, Y = np.meshgrid(lons, lats)
data = np.random.rand(len(lats),len(lons))
fig, ax = plt.subplots(nrows=1, ncols=1, dpi=150)
m = Basemap(
ax = ax,
projection='spstere',boundinglat=boundinglat,lon_0=180,
resolution='i',round=True
)
xi, yi = m(X,Y)
cf = m.contourf(xi,yi,data)
#adjust the path to the shapefile here:
result = m.readshapefile(
'shapefiles/AntarcticaWGS84_contorno', 'antarctica',
zorder = 10, color = 'k', drawbounds = False)
#defining the outline of the map as shapely Polygon:
rim = [np.linspace(0,360,100),np.ones(100)*boundinglat,]
outline = sPolygon(np.asarray(m(rim[0],rim[1])).T)
#following Basemap tutorial for shapefiles
patches = []
for info, shape in zip(m.antarctica_info, m.antarctica):
#instead of a matplotlib Polygon, create first a shapely Polygon
poly = sPolygon(shape)
#check if the Polygon, or parts of it are inside the map:
if poly.intersects(outline):
#if yes, cut and insert
intersect = poly.intersection(outline)
verts = np.array(intersect.exterior.coords.xy)
patches.append(Polygon(verts.T, True))
ax.add_collection(PatchCollection(
patches, facecolor= 'w', edgecolor='k', linewidths=1., zorder=2
))
plt.show()
The result looks like this:
Hope this helps.
For anyone still trying to figure out a simple way to mask a grid from a shapefile, here is a gallery example from the python package Antarctic-Plots which makes this simple.
from antarctic_plots import maps, fetch, utils
import pyogrio
# fetch a grid and shapefile
grid = fetch.bedmachine(layer='surface')
shape = fetch.groundingline()
# subset the grounding line from the coastline
gdf = pyogrio.read_dataframe(shape)
groundingline = gdf[gdf.Id_text == "Grounded ice or land"]
# plot the grid
fig = maps.plot_grd(grid)
# plot the shapefile
fig.plot(groundingline, pen='1p,red')
fig.show()
# mask the inside region
masked_inside = utils.mask_from_shp(
shapefile=groundingline, xr_grid=grid, masked=True)
masked_inside.plot()
# mask the outside region
masked_outside = utils.mask_from_shp(
shapefile=groundingline, xr_grid=grid, masked=True, invert=False)
masked_outside.plot()
I am using python 3.6 to plot precipitation data from CMIP5, the file I have downloaded is a netCDF4 file. I have used this code on another similar file and it worked out fine so I am not sure what the problem is. I am not receiving any error message with this code, it just displays a world map that is all one color when it should be a variety of colors. The variables found in this file are time, time_bnds, lat, lat_bnds, lon, lon_bnds, and prc. prc is the precipitation variable and the one I an interested in plotting. Any ideas would be helpful, Thank you!
Here is my code
from mpl_toolkits.basemap import Basemap, cm
from netCDF4 import Dataset as NetCDFFile
import matplotlib.pyplot as plt
nc = NetCDFFile('filename.nc','r')
p = nc.variables['prc']
data = p[:,:,0]
fig = plt.figure(figsize=(8,8))
ax = fig.add_axes([0.1,0.1,0.8,0.8])
m = Basemap(projection='cyl',lon_0=180,lat_0=0,resolution='l')
m.drawcoastlines()
m.drawstates()
m.drawcountries()
ny = data.shape[0]; nx = data.shape[1]
lons, lats = m.makegrid(nx,ny)
x,y = m(lons, lats) # compute map proj coordinates.
cs=plt.contourf(x,-y,data,range(0,1000,10),cmap=cm.s3pcpn,latlon=True)
cbar = m.colorbar(cs,location='bottom',pad="5%")
cbar.set_label('mm')
plt.show()
I am trying to identify the indices of the masked pixels when using
maskoceans
so I can then call only the land pixels in a code I have that is currently going through the whole globe, even though I do not care about the ocean pixels. I was trying different methods to do so, and noticed that my plots were looking really weird. Eventually, I realized that something was getting mixed up in my lat/lon indices, even though I am not actually touching them! Here is the code:
import numpy as np
import netCDF4
from datetime import datetime, timedelta
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
import matplotlib.dates as mpldates
import heat_transfer_coeffs
from dew_interface import get_dew
from matplotlib.dates import date2num, num2date
import numpy as np
import netCDF4
import heat_transfer_coeffs as htc
from jug.task import TaskGenerator
import matplotlib.cm as cm
import mpl_toolkits
from mpl_toolkits import basemap
from mpl_toolkits.basemap import Basemap, maskoceans
np.seterr(all='raise')
# set global vars
ifile = netCDF4.Dataset('/Users/myfile.nc', 'r')
times = ifile.variables['time'][:].astype(np.float64) # hours since beginning of dataset
lats_1d = ifile.variables['latitude'][:] # 90..-90
lons_1d = ifile.variables['longitude'][:] # 0..360
lons_1d[lons_1d>180]-=360 #putting longitude into -180..180
lons, lats = np.meshgrid(lons_1d, lats_1d)
ntimes, nlats, nlons = ifile.variables['tm'].shape
ifile.close()
map1 = basemap.Basemap(resolution='c', projection='mill',llcrnrlat=-36 , urcrnrlat=10, llcrnrlon=5 , urcrnrlon=52)
#Mask the oceans
new_lon = maskoceans(lons,lats,lons,resolution='c', grid = 10)
new_lat = maskoceans(lons,lats,lats,resolution='c', grid = 10)
fig = plt.figure
pc = map1.pcolormesh(lons, lats, new_lat, vmin=0, vmax=34, cmap=cm.RdYlBu, latlon=True)
plt.show()
for iii in range(new_lon.shape[1]):
index = np.where(new_lon.mask[:,iii] == False)
index2 = np.where(new_lon.mask[:,iii] == True)
new_lon[index[0],iii] = 34
new_lon[index2[0],iii] = 0
fig = plt.figure
pc = map1.pcolormesh(lons, lats, new_lat, vmin=0, vmax=34, cmap=cm.RdYlBu, latlon=True)
plt.show()
The first figure I get shows the expected map of Africa with oceans masked and the land values corresponding to the latitude (until saturation of the colorbar at 34, but that value was just taken as an example)
However, the second figure, which should plot the exact same thing as the first one, comes out all messed up, even though the loop in between the first and second figure doesn't touch any of the parameters involved in plotting it:
If I comment out the loop in between figure 1 and 2, figure 2 looks just like figure 1. Any idea about what is going on here?
Short answer, your loop is modifying the variables lons and lats indirectly.
Explanation: the function maskoceans creates a masked array from input array. The masked array and the input array share the same data, so that lons and new_lon share the same data, same thing for lats and new_lat. This means that when you modify new_lon in your loop, you are also modifying lons. That is the source of your problem. The only difference is that new_lon and new_lat are associated with a mask that is used to choose valid data points.
Solution: Make a copy of the initial array before you call maskoceans. You can do that with:
import copy
lons1 = copy.copy(lons)
lats1 = copy.copy(lats)
Then you use lons1 and lats1 to call maskoceans.
I have trouble with matplotlib / pyplot / basemap. I plot contour lines (air pressure) on a map. I use clabel to show the value of the contour lines.
But the problem is: the padding between the value and the contour line is too much. I have found the parameter "inline_spacing", which i have set to zero. But there is still to much free space. Any ideas?
Python Code:
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import pygrib
filename = "file.grib2"
grbs = pygrib.open('/data/' + filename)
grb = grbs[2]
data = grb.values
datac = data*0.01
lats, lons = grb.latlons()
fig = plt.figure()
m = Basemap(projection='stere',lon_0=5,lat_0=90.0,\
llcrnrlon=-25.0,urcrnrlon=60.0,llcrnrlat=30.0,urcrnrlat=60.0,resolution='l')
x, y = m(lons, lats)
levs = range(940,1065,5)
S1=plt.contour(x,y,datac,levs,linewidths=0.5,colors='b')
plt.clabel(S1,inline=1,inline_spacing=0,fontsize=8,fmt='%1.0f',colors='b')
m.drawmapboundary(fill_color='w')
m.drawcoastlines(linewidth=0.2)
plt.savefig('test.png', bbox_inches='tight',pad_inches=0.05, dpi=100)
Thanks.
The "inline_spacing" parameter can be set to negative values. It gave me a warning, but trying -2 or -3 should probably fix your problem.
cb = plt.clabel(S1,inline=1,inline_spacing=0,fontsize=8,fmt='%1.0f',colors='b')
[txt.set_bbox(dict(boxstyle='square,pad=0',fc='red')) for txt in cb]
Matplotlib Text class create a bbox. You need to set the pad = 0.Then inline_spacing works.
Refer to the Question!