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.
Related
I am trying to define upper and lower limits for both color-bar and the contour map using Pythons matplotlib and cartopy.
Context: I am plotting monthly average temperature data across an X (lat) and Y (lon) grid, via a 'FOR' loop for a total of 228 months. For each month the maximum and minimum temperature slightly varies. ie. some months may have a spatial temp range from 25 degcel to 28 degcel and in other months the spatial temp range may be from 26 to 32 degcel. Currently my code results in maps with color-bars and color contours that are independent of each other and hence not directly comparable.
What I want to achieve is that all cmaps produced from the loop follow the same colorbar and scale, i.e. from 25 degcel to 32 degcel.
Previously i would use 'clim' to define limits but that does not seem to work for this. Now what I am using is vmaxandvmin which partially works,i.e. it plots the map using the same scale but the colorbar is still independent. Any hints in the right direction are appreciated.
here is a sample of my code.
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from netCDF4 import Dataset
import matplotlib.pyplot as plt
import numpy as np
file = ('monthlyaverages.nc')
ds = Dataset(file, 'r')
ds.variables.keys()
lons = ds.variables['lon'][:]
lats=ds.variables['lat'][:]
time = ds.variables['time'][:]
sst = ds.variables['analysed_sst'][:]
for t in range(1,228):
plt.figure(figsize=(6,5))
ax1 = plt.subplot2grid((1,1), (0,0), colspan=1, projection=ccrs.PlateCarree(central_longitude=180))
ax1.coastlines(resolution='50m')
fill=ax1.contourf(lons, lats, sst[t,:,:].squeeze(), levels=10,
cmap=plt.cm.RdBu_r, transform=ccrs.PlateCarree(), vmax = 32, vmin = 25)
cb = plt.colorbar(fill, orientation='vertical', fraction=0.04, pad=0.04, location ='left')#
plt.title('Average SST for the month # '+ str(t))
plt.savefig('sst_'+ str(t)+'.tiff')```
thanks in advance.
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()
I have some meteorological data about some stations in Tenerife Island (this Dataframe has lat,lon and temperature in certain points of the island) (The source of the data is AEMET opendata, so they are supposed to be trustworthy)
I have plotted the Basemap successfully, but when I wanted to plot the points in my Dataframe over the Basemap, the come really weird.
Here the data I have:
I changed the order so, when I use imshow the order is x,y,temperature, where x are latitudes and y are longitudes.
To plot the map I have written:
m = Basemap(llcrnrlon=-17,llcrnrlat=27.8,urcrnrlon=-16,urcrnrlat=28.7,resolution='i',projection='merc')
im = m.imshow(temp, cmap='BuPu')
cbi=plt.colorbar(im,shrink=0.7,format='%.1f')
plt.show()
The result is giving me:
But, I wanna plot only points over their location, e.g., if the point is in the coordinates x,y it should only appeared a little point over this position in the map.
Any helping hand?
Thanks!
Check this code:
import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
df = pd.read_csv('data.csv')
m = Basemap(llcrnrlon = -17, llcrnrlat = 27.8, urcrnrlon = -16, urcrnrlat = 28.7, resolution = 'i', projection = 'merc')
m.drawcoastlines(color = 'black')
x, y = m(list(df['x']), list(df['y']))
m.scatter(x, y,
c = df['temperatura'],
s = 100,
cmap = 'RdBu_r')
plt.colorbar()
plt.show()
which gives this map:
You used imshow(), but this funciton is useful for plotting image from a NxM matrix, so you will always get a colored rectangle, as in you purple image.
You have data in a x-y (lon-lat) coordinate format, so you could plot a scatterplot, showing the temperature in those point on the map, like my code does, where those temperature where measured.
If you want a distributed colored map (in techincal terms a temperature field), like this:
you need a distributed data like a meshgrid.
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()
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)