Issue w/ image crossing dateline in imshow & cartopy - python

I'm trying to plot a square grid of equally-spaced (in lat/lon) data using cartopy, matplotlib, and imshow. The data crosses the dateline, and I've had issues getting a map to work properly.
Here's an example of my issue:
import numpy as np
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
lat = np.arange(6000)*0.02 + (-59.99)
lon = np.arange(6000)*0.02 + (85.01)
dat = np.reshape(np.arange(6000*6000),[6000,6000])
tran = ccrs.PlateCarree()
proj = tran
plt.figure(figsize=(8,8))
ax = plt.axes(projection=proj)
print([lon[0],lon[-1],lat[0],lat[-1]])
ax.imshow(dat, extent=[lon[0],lon[-1],lat[0],lat[-1]],transform=tran,interpolation='nearest')
ax.coastlines(resolution='50m', color='black', linewidth=2)
ax.gridlines(crs=proj,draw_labels=True)
plt.show()
tran = ccrs.PlateCarree(central_longitude=180)
proj = tran
plt.figure(figsize=(8,8))
ax = plt.axes(projection=proj)
print([lon[0]-180,lon[-1]-180,lat[0],lat[-1]])
ax.imshow(dat, extent=[lon[0]-180,lon[-1]-180,lat[0],lat[-1]],transform=tran,interpolation='nearest')
ax.coastlines(resolution='50m', color='black', linewidth=2)
ax.gridlines(crs=tran,draw_labels=True)
plt.show()
The first plot yields this image, chopping off at 180E:
The second fixes the map issue, but the grid ticks are now wrong:
I've tried reprojecting, I think (where tran != proj), but it seemingly either hung or was taking too long.
I basically want the bottom image, but with the proper labels. I'm going to have more geolocated data to overplot, so I'd like to do it correctly instead of what seems like a hack right now.

With Cartopy, drawing a map crossing dateline is always challenging. Here is the code that plots the map you want.
import numpy as np
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
# demo raster data
n1 = 300
m1 = 0.4
lat = np.arange(n1)*m1 + (-59.99)
lon = np.arange(n1)*m1 + (85.01)
dat = np.reshape(np.arange(n1*n1), [n1,n1])
cm_lon=180 # for central meridian
tran = ccrs.PlateCarree(central_longitude = cm_lon)
proj = tran
plt.figure(figsize=(8,8))
ax = plt.axes(projection=proj)
ext = [lon[0]-cm_lon, lon[-1]-cm_lon, lat[0], lat[-1]]
#print(ext)
ax.imshow(dat, extent=ext, \
transform=tran, interpolation='nearest')
ax.coastlines(resolution='110m', color='black', linewidth=0.5, zorder=10)
# this draws grid lines only, must go beyond E/W extents
ax.gridlines(draw_labels=False, xlocs=[80,100,120,140,160,180,-180,-160,-140])
# this draw lables only, exclude those outside E/W extents
ax.gridlines(draw_labels=True, xlocs=[100,120,140,160,180,-160])
plt.show()
The resulting map:

Related

Rotate polar stereographic subplot

I am making a figure with subplots which are north polar stereographic projections. I also created a custom boundary shape to display subplot as a circle. However once reprojected, I want to be able to rotate the map, since my data is focusing on the US and thus I was hoping that each subplot would have the US facing "up," thus I would need to rotate it 270 degrees / -90 degrees.
Minimalistic code example pulled from cartopy example
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline # for notebook
map_projection = ccrs.NorthPolarStereo(central_longitude=0, )
data_projection = ccrs.PlateCarree()
theta = np.linspace(0, 2*np.pi, 100)
center, radius = [0.5, 0.5], 0.5 # by changing radius we can zoom in/out
verts = np.vstack([np.sin(theta), np.cos(theta)]).T
circle = mpl.path.Path(verts * radius + center)
plot_extent=[-179.9,180, 30, 90]
fig, ax1 = plt.subplots(1,1, figsize=(6,6), dpi=100, subplot_kw=dict(projection=map_projection))
ax1.set_boundary(circle, transform=ax1.transAxes)
ax1.coastlines(linewidths=1.0, color='grey')
ax1.add_feature(cfeature.BORDERS, linestyles='--', color='dimgrey', linewidths=0.8 )
ax1.set_extent(plot_extent, crs=ccrs.PlateCarree(),)
gl = ax1.gridlines(crs=ccrs.PlateCarree(), draw_labels=True,
linewidth=1, color='gray', alpha=0.5, linestyle='--', zorder=10)
I haven't yet found any good examples or documentation for what I am trying to do, however I am new to using matplotlib/cartopy.
You need to set central_longitude=-90.
So:
map_projection = ccrs.NorthPolarStereo(central_longitude=-90)

How to zoom into a specific latitude in cartopy.crs.Orthographic?

I'm unsure if this is possible, but I'm essentially trying to isolate the Arctic circle latitude (60N) in an orthographic map AND maintain the ellipsoid, not have the zoomed in image be a rectangle/square.
Here is what I have:
fig = plt.figure(figsize=[20, 10])
ax1 = plt.subplot(1, 1, 1, projection=ccrs.Orthographic(0, 90))
for ax in [ax1]:
ax.coastlines(zorder=2)
ax.stock_img()
ax.gridlines()
This gives the north polar view I want, but I would like for it to stop at 60N.
Current yield
To get a zoom-in and square extent of an orthographic map, You need to plot some control points (with .scatter, for example) or specify correct extent in projection coordinates (more difficult). Here is the code to try.
import cartopy
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
fig = plt.figure(figsize=[8, 8])
lonlatproj = ccrs.PlateCarree()
my_projn = ccrs.Orthographic(central_longitude=0,central_latitude=90)
ax1 = plt.subplot(1, 1, 1, projection=my_projn)
# set `lowlat` as lower limits of latitude to plot some points
# these points will determine the plot extents of the map
lowlat = 60 + 2.8 # and get 60
lons, lats = [-180,-90,0,90], [lowlat,lowlat,lowlat,lowlat]
# plot invisible points to get map extents
ax1.scatter(lons, lats, s=0, color='r', transform=lonlatproj)
#ax1.stock_img() #uncomment to get it plotted
ax1.coastlines(lw=0.5, zorder=2)
ax1.gridlines(lw=2, ec='black', draw_labels=True)
Method 2: By specifying correct extent in projection coordinates
import cartopy
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
fig = plt.figure(figsize=[8, 8])
lonlatproj = ccrs.PlateCarree()
my_projn = ccrs.Orthographic(central_longitude=0,central_latitude=90)
ax1 = plt.subplot(1, 1, 1, projection=my_projn)
# These 2 lines of code grab extents in projection coordinates
_, y_min = my_projn.transform_point(0, 60, lonlatproj) #(0.0, -3189068.5)
x_max, _ = my_projn.transform_point(90, 60, lonlatproj) #(3189068.5, 0)
# prep extents of the axis to plot map
pad = 25000
xmin,xmax,ymin,ymax = y_min-pad, x_max+pad, y_min-pad, x_max+pad
# set extents with prepped values
ax1.set_extent([xmin,xmax,ymin,ymax], crs=my_projn) # data/projection coordinates
ax1.stock_img()
ax1.coastlines(lw=0.5, zorder=2)
# plot other layers of data here using proper values of zorder
# finally, plot gridlines
ax1.gridlines(draw_labels=True, x_inline=False, y_inline=True,
color='k', linestyle='dashed', linewidth=0.5)
plt.show()
Method 3 Plot the map with circular boundary
The runnable code:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import matplotlib.path as mpath
import numpy as np
r_limit = 3214068.5 #from: ax.get_ylim() of above plot
# some settings
lonlatproj = ccrs.PlateCarree()
my_projn = ccrs.Orthographic(central_longitude=0, central_latitude=90)
fig = plt.figure(figsize=[8, 8])
ax = plt.subplot(1, 1, 1, projection=my_projn)
# add bluemarble image
ax.stock_img()
# add coastlines
ax.coastlines(lw=0.5, color="black", zorder=20)
# draw graticule (of meridian and parallel lines)
gls = ax.gridlines(draw_labels=True, crs=ccrs.PlateCarree(), lw=3, color="gold",
y_inline=True, xlocs=range(-180,180,30), ylocs=range(-80,91,10))
# add extra padding to the plot extents
r_extent = r_limit*1.0001
ax.set_xlim(-r_extent, r_extent)
ax.set_ylim(-r_extent, r_extent)
# Prep circular boundary
circle_path = mpath.Path.unit_circle()
circle_path = mpath.Path(circle_path.vertices.copy() * r_limit,
circle_path.codes.copy())
#set circle boundary
ax.set_boundary(circle_path)
#hide frame
ax.set_frame_on(False) #hide the rectangle frame
plt.show()

Lack of Projection for Cartopy Contour

I'm trying to put some data onto a contourmap via cartopy. However, after plotting the data, the projection still seems to be off.
The surface_temp.X and surface_temp.Y are lat/lon, while masked_fill is the actual data values. This seems to have worked in basemap, but I'm not sure why it doesn't in cartopy.
Cartopy:
fig = plt.figure(figsize=(12,4.76), dpi=100)
fig.clf()
ax = plt.axes(projection=ccrs.Mercator())
ax.coastlines()
ax.contourf(surface_temp.X, surface_temp.Y, surface_temp.masked_fill[:], latlon = 'true', transform = ccrs.Mercator())
plt.show()
Basemap:
fig = plt.figure(figsize=(15,4.76), dpi=100)
fig.clf()
plt.axes([0,0,1,1], frameon=False)
plt.title(title)
m = Basemap(projection='merc',llcrnrlat=-80,urcrnrlat=80, llcrnrlon=0,urcrnrlon=360,lat_ts=20,resolution='c')
m.contourf(surface_temp.X, surface_temp.Y, surface_temp.masked_fill[:], latlon = 'true')
Basemap Result:
Cartopy Result (Contour commented out):
Cartopoy Result (Contour)
The paradigm of cartopy seems to be to always work on lat/lon coordinates. This means, you should not transform your data according to the projection, but stay in lat/lon.
Hence, instead of
ax.contourf(..., transform = ccrs.Mercator())
you would need
ax.contourf(..., transform = ccrs.PlateCarree())
A complete example:
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.examples.waves import sample_data
ax = plt.axes(projection=ccrs.Mercator())
lons, lats, data = sample_data(shape=(20, 40))
ax.contourf(lons, lats, data, transform=ccrs.PlateCarree())
ax.coastlines()
ax.gridlines()
plt.show()

Matplotlib: How to get a colour-gradient as an arrow next to a plot?

I am trying to create a plot with matplotlib that includes several different lines, offset artificially, that are coloured according to the RedBlue colourmap mpl.cm.RdBu. Now I want an arrow next to the plot that acts as an effective colourscale, meaning that it should have a colour gradient.
So far, I have managed to create the arrow itself using annotate with the help of this answer and drew a "Rainbow arrow" inside the plot using this brilliant answer (Note: You will need matplotlib 2.2.4 or older to run this part of the code, see comments.).
This is the MWE I can produce so far:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.transforms
import matplotlib.path
from matplotlib.collections import LineCollection
# from https://stackoverflow.com/questions/47163796/using-colormap-with-annotate-arrow-in-matplotlib
def rainbowarrow(ax, start, end, cmap="viridis", n=50,lw=3):
cmap = plt.get_cmap(cmap,n)
# Arrow shaft: LineCollection
x = np.linspace(start[0],end[0],n)
y = np.linspace(start[1],end[1],n)
points = np.array([x,y]).T.reshape(-1,1,2)
segments = np.concatenate([points[:-1],points[1:]], axis=1)
lc = LineCollection(segments, cmap=cmap, linewidth=lw)
lc.set_array(np.linspace(0,1,n))
ax.add_collection(lc)
# Arrow head: Triangle
tricoords = [(0,-0.4),(0.5,0),(0,0.4),(0,-0.4)]
angle = np.arctan2(end[1]-start[1],end[0]-start[0])
rot = matplotlib.transforms.Affine2D().rotate(angle)
tricoords2 = rot.transform(tricoords)
tri = matplotlib.path.Path(tricoords2, closed=True)
ax.scatter(end[0],end[1], c=1, s=(2*lw)**2, marker=tri, cmap=cmap,vmin=0)
ax.autoscale_view()
def plot_arrow(data,n):
fig, subfig = plt.subplots(1,1,figsize=(6.28,10)) # plotsize, etc
colorP=mpl.cm.RdBu(0.2)
i = 0
while i<=n-1:
subfig.plot(data[i,0], (data[i,1])+i, lw=2, color=mpl.cm.RdBu(1-i/20)) # plot of data
i=i+1
subfig.annotate('', xy=(1.1,0), xycoords='axes fraction', xytext=(1.1,1),
arrowprops=dict(arrowstyle="<-", lw = 3))
subfig.annotate('A', xy=(1.1,0), xycoords='axes fraction', xytext=(1.1,1))
subfig.annotate('B', xy=(1.1,0), xycoords='axes fraction', xytext=(1.1,0))
rainbowarrow(subfig, (1.1,3), (1.1,5), cmap='RdBu_r', n=100,lw=3)
plt.show(fig)
plt.close(fig)
# things to plot
np.random.seed(19680802)
n = 20
i = 0
data = np.empty([n,2,10])
while i<=n-1:
data[i]=np.sin(np.random.rand(10))
i = i+1
# actual plot
plot_arrow(data,n)
Here's the graph generated:
In a nutshell: I want the annotation arrow outside the plot to have the colour of the colourmap, as the small rainbow arrow inside the plot.

How to draw contourf plot for a particular shape in Python-Matplotlib-Basemap

For example, I have a 3-D array data represent the chemical concentration among the area shown like:
(source: tietuku.com)
And I want to plot it just in some administrative division(not a square) that belong to this domain.
Now I can read and plot shapefile in Basemap, but I can't find a way to draw some elements beyond it?
If it's possible, How to make the figure size smaller? Because when I add shapefile in basemap, the output file is way larger.
Wish for your reply! Thanks!
I have done something similar for plotting trace-gas concentrations within counties within the US, which I think is very similar to what you are trying to do.
import pandas as pd, numpy as np, datetime as dt, pytz
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
from mpl_toolkits.basemap import Basemap
from os import getcwd, chdir
from pylab import *
# create figure
fig = figure( figsize=(6.25,4.5) )
ax = fig.add_subplot(111)
m = Basemap(projection='cyl', llcrnrlat=minlat, urcrnrlat=maxlat, llcrnrlon=minlon, urcrnrlon=maxlon, resolution='i')
lw = 0.1
m.drawcoastlines(linewidth=lw)
m.drawparallels(np.arange(30, 50, 2), labels=[1,0,0,1], fontsize=6, labelstyle='', rotation=0, linewidth=0.25)
m.drawmeridians(np.arange(-86,-70,2), labels=[1,0,0,1], fontsize=6, labelstyle='', rotation=45, linewidth=0.25)
m.drawstates(linewidth=lw)
m.drawcountries(linewidth=lw)
m.drawcounties(linewidth=0.01)
ax2 = gca()
a = np.ones( (10,10) )
b = a*10
cb = ax.scatter(x=a, y=a, s=1, c=b, vmin=0, vmax=10) # create dummy plot to allow colorbar insertion
# insert colorbar
axins = inset_axes(ax,
width="5%", # width = 10% of parent_bbox width
height="100%", # height : 50%
loc=6,
bbox_to_anchor=(1.05, 0., 1, 1),
bbox_transform=ax.transAxes,
borderpad=0,
)
cbar = colorbar(cb, cax=axins)
cbar.set_label('NO\$\mathrm{_2}$ (ppb)', fontsize=8)
cbar.ax.tick_params(labelsize=8)
# fill in counties
# I think this is the part you are most interested in
county_mean = county_data.mean()
# since I want to color within the map to match a specific color
# within the cbar, I define it here based on the cbar's color coding
if pd.isnull(county_mean.no2)[0]:
color = (1,1,1)
else:
color = cbar.to_rgba(county_mean.no2[0])
# get the polygon values that define the county boundaries
# here, "r" represents the county PID (identifier)
poly = Polygon(m.counties[r], facecolor=color, edgecolor='k')
ax2.add_patch(poly)
This is a general outline for how you can do this. With some modifications I think you will be on your way.

Categories

Resources