Plot Polar Gridded Sea Ice Concentrations using Cartopy - python

I am trying to make some plots of Polar Gridded Sea Ice Concentrations from NSIDC. The data is delivered in the Polar Stereographic Projection and Grid, an example file (binary,Arctic,25 km resolution) can be downloaded at:
http://nsidc.org/data/NSIDC-0081
When I read the data using numpy, and then plot it just using matplotlib's imshow function, it works.
import numpy as np
import matplotlib.pyplot as plt
infile='c:\\nt_20150326_f17_nrt_n.bin'
fr=open(infile,'rb')
hdr=fr.read(300)
ice=np.fromfile(fr,dtype=np.uint8)
ice=ice.reshape(448,304)
#Convert to the fractional parameter range of 0.0 to 1.0
ice = ice/250.
#mask all land and missing values
ice=np.ma.masked_greater(ice,1.0)
fr.close()
#Show ice concentration
plt.imshow(ice)
When I try to plot it using Cartopy, it runs without any errors but only returns an empty coastline.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig=plt.figure(figsize=(3, 3))
ax = plt.axes(projection=ccrs.NorthPolarStereo())
ax.coastlines(resolution='110m',linewidth=0.5)
ax.set_extent([-180,180,50,90],crs=ccrs.PlateCarree())
ax.gridlines()
#set ice extent from Polar Stereographic Projection and Grid document
extent=[-9.97,168.35,30.98,34.35]
ax.imshow(ice,cmap=plt.cm.Blues, vmin=1,vmax=100,
extent=extent,transform=ccrs.PlateCarree())
Anything wrong? How do I show my ice concentration data?
My Cartopy's version is 0.12.0rc1.
Below is the Arctic Polar Stereographic Grid from document:
Northern Hemisphere Grid Coordinates
X (km) Y (km) Latitude (deg) Longitude (deg)
-3850 5850 30.98 168.35 corner
3750 5850 31.37 102.34 corner
3750 -5350 34.35 350.03 corner
-3850 -5350 33.92 279.26 corner
Here is the IPython Notebook:
http://nbviewer.ipython.org/github/xue1527/MyWork/blob/master/Plot%20Arctic%20Sea%20Ice%20Concentration.ipynb

When I downloaded the data I found the grid specifications:
Upper Left Corner X Coordinate: -3850000.0
Upper Left Corner Y Coordinate: 5850000.0
Lower Right Corner X Coordinate: 3750000.0
Lower Right Corner Y Coordinate: -5350000.0
With that you can create a grid and use pcolormesh instead of imshow.
import numpy as np
dx = dy = 25000
x = np.arange(-3850000, +3750000, +dx)
y = np.arange(+5850000, -5350000, -dy)
Here is the full notebook:
http://nbviewer.ipython.org/gist/ocefpaf/47ef0c38a5a429704170

I know that this question is 7 years old at this point, but I recently ran into the exact same issue and couldn't find an answer. So hopefully this helps some other poor, frustrated grad student.
I ended up having to transform each point individually and then it successfully plotted with contourf, like this:
import numpy as np
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
# lats and lons should be 2d grids
s = np.size(lats)
lats.flatten(); lons.flatten()
X = []; Y = []
for itr in range(len(lats)):
x,y = ccrs.NorthPolarStereo().transform_point(lons[itr], lats[itr], ccrs.PlateCarree())
X.append(x); Y.append(y)
X = np.reshape(X, (s[0], s[1])); Y = np.reshape(Y, (s[0], s[1]))
fig = plt.figure()
ax1 = plt.subplot(1,1,1, projection=ccrs.NorthPolarStereo())
ax1.set_extent([-180, 180, 45, 90], ccrs.PlateCarree())
ax1.contourf(X,Y,vals)

Related

How to plot an ortographic projection of the celestial sphere with equatorial coordinates in python, for a given latitude?

I am trying to obtain an ortographic projection of the celestial sphere, with equatorial coordinates, as seen from a certain latitude, as in the following picture:
(Grid obtained from Skychart/Cartes du ciel)
This image is a print of Skychart/Cartes du ciel, showing the equatorial grid for an observer at 23°S latitude. I want to be able to reproduce the exact same image in Python (apart from the dark blue background). My first attempt was to use CartoPy, setting the central latitude as -23, as follows:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
ax = plt.axes(projection=ccrs.Orthographic(central_latitude=-23))
ax.gridlines()
plt.show()
but the resulting picture looks like this:
From the position of the south pole, I believe setting the central latitude to the observer's latitude in CartoPy does not solve my problem. Is there another way, either with CartoPy or another package (maybe AstroPy? - I have never used it) to obtain the same plot as Skychart (Image 1) in python?
First of all, your first image is Azimuthal Equidistant Projection. So that, it is quite different from your second plot (Orthographic projection). To get the plot (first image) like that using Cartopy requires some steps that are interesting to follow. Here is the code with comments that produces the output plot that I consider a good result.
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.path as mpath
import numpy as np
r_limit = 20037508 #from: ax.get_ylim() of full extent
# this makes circle for clipping the plot
pts = [] #unit circle vertices
cds = [] #path codes
numps = 32
for ix,ea in enumerate(np.linspace(0, 2*np.pi, numps)):
#print(ea)
xi = np.cos(ea)
yi = np.sin(ea)
pts.append([xi,yi])
if (ix==0):
# start
cds.append(1)
elif (ix==numps-1):
# close
cds.append(79)
else:
cds.append(4)
# make them np.array for easy uses
vertices = np.array(pts)
codes = np.array(cds)
# manipulate them to create a required clip_path
scale = r_limit*0.5
big_ccl = mpath.Path(vertices*scale, codes)
clippat = plt.Polygon(big_ccl.vertices[:, :], visible=True, fill=False, ec='red')
# create axis to plot `AzimuthalEquidistant` projection
# this uses specific `central_latitude`
ax = plt.axes(projection=ccrs.AzimuthalEquidistant(central_latitude=-23))
# add the clip_path
ax.add_patch(clippat)
# draw graticule (of meridian and parallel lines)
# applying clip_path to get only required extents plotted
ax.gridlines(draw_labels=False, crs=ccrs.PlateCarree(),
xlocs=range(-180,180,30), ylocs=range(-80,90,20), clip_path=clippat)
# specify radial extents, use them to set limits of plot
r_extent = r_limit/(2-0.05) # special to the question
ax.set_xlim(-r_extent, r_extent)
ax.set_ylim(-r_extent, r_extent)
ax.set_frame_on(False) #hide the rectangle frame
plt.show()

How to plot contours from a polar stereographic grib2 file in Python

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.

Matplotlib: Focus on specific lon/lat using spstere projection

I am trying to focus my map on a specific area of Antarctica using 'spstere' projection from the matplotlib package in Python. I am able to plot the whole of Antarctica but this time I want to 'zoom' in and have a closer look at a specific area of the continent.
Similar examples using other projections (Pyplot contour plot - clabel spacing; http://matplotlib.org/basemap/api/basemap_api.html; https://matplotlib.org/basemap/users/examples.html) are available online but I have not been able to apply those to the 'spstere' projection over Antarctica.
I basically want to focus my map on the region of the Antarctic Peninsula, which spans roughly from
llcrnrlon=-100,urcrnrlon=-30,llcrnrlat=-90,urcrnrlat=-55.0
I have tried to use this code with the 'spstere' proj but python only takes into account boundinglat and lon_0. I've tried to change the values for boundinglat and lon_0 but it does not work either.
Any idea how I could go about? I have also tried using other projections such as 'cyl' but instead of getting a nice square like the 'spstere' proj, I get a horizontal rectangle.
m = Basemap(projection='cyl',lon_0=0,lat_0=0,\
llcrnrlon=-180,urcrnrlon=180,llcrnrlat=-90,urcrnrlat=-55.0,resolution='c')
Any help would be highly appreciated!
Using the Polar Stereographic Projection 'spstere', you can get the antarctic region by using e.g. boundinglat=-60:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
m = Basemap(projection='spstere',boundinglat=-60,lon_0=180,resolution='c')
m.drawcoastlines()
plt.show()
Note that 'spstere' is always centered at the south pole.
In order to have a map, which is not centered at the south pole, you need to use the "stere" projection. Setting the corners for the "stere" projection is not straigt forward.
One may therefore use a plot in 'spstere' projection and find some points which would enclose the region of interest. In this case e.g.
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
m = Basemap(projection='spstere',boundinglat=-50,
lon_0=180+(-100+-30)/2.,resolution='c')
m.drawmeridians(np.arange(0,360,30),labels=[1,1,1,0])
m.drawparallels(np.arange(-90,90,5))
m.drawcoastlines()
xll, yll = m(-150,-70) # <-- find those points by looking at meridians and parallels
xur, yur = m(-30,-55)
m.scatter([xll,xur], [yll, yur], c="crimson")
plt.show()
Using those points, (-150,-70, -30,-55), as the corners of the map, you can then plot a map using 'stere' projection.
m = Basemap(projection='stere',resolution='c',
lat_0=-90, lon_0=(-100+-30)/2., lat_ts=(-90.+-55.)/2.,
llcrnrlon=-150,urcrnrlon=-30,llcrnrlat=-70,urcrnrlat=-55)
If this heuristic method is not wanted, you may automate this procedure by creating a dummy map in 'spstere' projection, calculate the coordinates from the rectangle in question (llcrnrlon=-100,urcrnrlon=-30,llcrnrlat=-90,urcrnrlat=-55.0) and create a new basemap in stere projection with them. The function below is taken from the ActiveState site (author PG).
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
def polar_stere(lon_w, lon_e, lat_s, lat_n, **kwargs):
'''Returns a Basemap object (NPS/SPS) focused in a region.
lon_w, lon_e, lat_s, lat_n -- Graphic limits in geographical coordinates.
W and S directions are negative.
**kwargs -- Aditional arguments for Basemap object.
'''
lon_0 = lon_w + (lon_e - lon_w) / 2.
ref = lat_s if abs(lat_s) > abs(lat_n) else lat_n
lat_0 = np.copysign(90., ref)
proj = 'npstere' if lat_0 > 0 else 'spstere'
prj = Basemap(projection=proj, lon_0=lon_0, lat_0=lat_0,
boundinglat=0, resolution='c')
lons = [lon_w, lon_e, lon_w, lon_e, lon_0, lon_0]
lats = [lat_s, lat_s, lat_n, lat_n, lat_s, lat_n]
x, y = prj(lons, lats)
ll_lon, ll_lat = prj(min(x), min(y), inverse=True)
ur_lon, ur_lat = prj(max(x), max(y), inverse=True)
return Basemap(projection='stere', lat_0=lat_0, lon_0=lon_0,
llcrnrlon=ll_lon, llcrnrlat=ll_lat,
urcrnrlon=ur_lon, urcrnrlat=ur_lat, **kwargs)
llcrnrlon=-100
urcrnrlon=-30
llcrnrlat=-90
urcrnrlat=-55.0
m = polar_stere(llcrnrlon, urcrnrlon, llcrnrlat, urcrnrlat)
m.drawmeridians(np.arange(0,360,30),labels=[1,1,1,0])
m.drawparallels(np.arange(-90,90,30),labels=[1,1,1,1])
m.drawcoastlines()
plt.show()

matplotlib Basemap Fundamental Lune

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.

defining a partial geostationary projection with matplotlib Basemap

i'm having trouble creating a custom geostationary projection with Basemap. what i'd need is not a full globe projection, but the upper third of a full disc (full width, one third from the top down ). i found an example here:
http://matplotlib.org/basemap/users/geos.html
explaining how to make a projection for the upper right quadrant of the disc using the llcrnrx, llcrnry, urcrnrx, and urcrnry keywords. following the example, i thought i would get my result like this:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
lon_0 = 9.5
fig = plt.figure()
m1 = Basemap(projection='geos',lon_0=lon_0,resolution=None)
m = Basemap(projection='geos',lon_0=lon_0,resolution='l',
# lower left
llcrnrx=0., llcrnry=0.,
# calculate the upper right coordinates, full width
# two 3rds of the height
urcrnrx=m1.urcrnrx, urcrnry=m1.urcrnry-m1.urcrnry/3.)
m.drawcoastlines()
plt.show()
which gives me a weird plot. from the example i though that for the new projection the lower left corner is always (0,0), then you just calculate the width and height coordinates (upper right). this obviously is not the case. with just bare experimentation i have this now:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
lon_0 = 9.5
fig = plt.figure()
m1 = Basemap(projection='geos',lon_0=lon_0,resolution=None)
m = Basemap(projection='geos',lon_0=lon_0,resolution='l',
llcrnrx=-m1.urcrnrx/2., llcrnry=m1.urcrnry/4.,
urcrnrx=m1.urcrnrx/2., urcrnry=m1.urcrnry/2.)
m.drawcoastlines()
plt.show()
which gives me a rough estimate on what i'm after, but i have no idea why this works and what the corner points really represent.
i hope you guys get the drift...

Categories