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

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()

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)

Get X,Y Position in Figure (not in Plot)

I have a question about the X and Y Position in figures. How you can see I am working with gridspec for a better layout and adding Text to a figure. The problem is that I am trying to get the exact Position manually. Which means I am changing the X and Y in fig.text(0.2, 0.5, 'matplotlib') until I get the final figure.
import matplotlib.pylab as plt
import numpy as np
vector = np.arange(0,100)
time = np.arange(0,vector.shape[0])
fig = plt.figure(figsize=(10,10))
plt.rcParams['axes.grid'] = True
gs = fig.add_gridspec(2, 2)
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(time,vector)
fig.text(0.2, 0.5, 'matplotlib')
At Link I already found an interactive solution but its only working for the Plot.
Does someone have an idea how to manage this?
You can create a blended transform, where the y-coordinates have a figure transform. And the x-coordinates have a axes transform. The figure transform is measured 0 at the left/bottom and 1 at the right/top of the figure. The axes transform is similar, but regarding the axes. The parameter clip_on=False allows to draw outside the axes region (text allows this by default).
import matplotlib.transforms as mtransforms
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
fig, ax = plt.subplots(gridspec_kw={})
# the x coords of this transformation are axes, and the y coord are fig
trans = mtransforms.blended_transform_factory(ax.transAxes, fig.transFigure)
x, w = 1, -0.3 # axes coordinates
y, h = 0.04, 0.06 # figure coordinates
ax.text(x + w / 2, y + h / 2, 'hello', transform=trans, ha='center', va='center')
rect = mpatches.Rectangle((x, y), w, h, transform=trans, edgecolor='crimson', facecolor='yellow', clip_on=False)
ax.add_patch(rect)
fig.tight_layout(pad=2)
plt.show()
PS: You can set the vertical alignment va='right' to have the right margin of the text align with the right axis. You can also use transform=ax.transAxes with negative y-coordinates to plot everything relative to the axes.

Exclude Cartopy elements from Matplotlib legend

I'm plotting scatter points onto a map and seeing unwanted rectangles in my legend, despite the insertion of label='_nolegend_':
# import functions
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.io.img_tiles as cimgt
# Create a Stamen terrain background instance
stamen_terrain = cimgt.Stamen('terrain-background')
fig = plt.figure(figsize = (10,10))
ax = fig.add_subplot(1, 1, 1, projection=stamen_terrain.crs, label='_nolegend_')
# Set range of map, stipulate zoom level
ax.set_extent([-122.7, -121.5, 37.15, 38.15], crs=ccrs.Geodetic())
ax.add_image(stamen_terrain, 12, label='_nolegend_')
# Add scatter point
ax.scatter(-122.4194, 37.7749, s=55, c='k', transform=ccrs.PlateCarree())
ax.legend(('','','San Francisco'), loc = 3)
plt.show()
How to remove the rectangles, and just show the scatter point in the legend?
The problem is that you set labels for each of the elements in the axes via ('','','San Francisco'). Instead just set the label to the scatter itself
ax.scatter(..., label="Some City")
ax.legend(loc=3)
Alternatively, if you don't want to give the scatter a label, you can pass the handle and label to the legend:
sc = ax.scatter(...)
ax.legend(handles=[sc], labels=['Some City'], loc = 3)

matplotlib set_aspect(num) on an axis doesn't resize display box in a gridspec

I'm plotting an image with their two projections (x and y) in a GridSpec. When I use the set_aspect on the central image, the image size box isn't resized for its minimal size (without blank) as you can see below. Does somebody have a solution to resolve this case?
Matplotlib 3.0.2, Python 3.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
plt.rcParams['toolbar'] = 'toolmanager'
matplotlib.use('Qt5Agg')
ldata = np.random.random((256, 256))
xhisto = np.sum(ldata, axis=0)
yhisto = np.sum(ldata, axis=1)
fig = plt.figure()
gs = plt.GridSpec(2, 2, height_ratios=[10,1], width_ratios=[3,30], wspace=0.1, hspace=0.1)
ax_image = plt.subplot(gs[1])
ax_histoy = plt.subplot(gs[0], sharey=ax_image)
ax_histox = plt.subplot(gs[3], sharex=ax_image)
plt.subplots_adjust(right=0.8)
colorAx = plt.axes([0.85, 0.4, 0.02, 0.45])
im = ax_image.imshow(ldata, cmap='jet', interpolation='none', aspect='auto')
ax_histox.plot(xhisto)
ax_histoy.plot(yhisto, range(256))
ax_image.invert_yaxis()
ax_image.tick_params(labelbottom=False, labelleft=False)
ax_histoy.spines['right'].set_visible(False)
ax_histoy.spines['bottom'].set_visible(False)
ax_histox.spines['right'].set_visible(False)
ax_histox.spines['top'].set_visible(False)
ax_histoy.set_ylim(1,256)
ax_histox.set_xlim(1,256)
ax_histox.set_xlabel('X')
ax_histoy.set_ylabel('Y')
ax_image.set_title('Matplotlib - Plot 2D')
ax_histoy.tick_params(axis='x',labelsize=8,labelrotation=90)
ax_histox.tick_params(axis='y',labelsize=8)
ax_histoy.xaxis.tick_top()
ax_histox.yaxis.tick_left()
plt.colorbar(im, cax = colorAx)
ax_image.set_aspect(0.5)
plt.show()
I try to find a solution for resizing the height of the projection on the left
Using the example as you explain give the save result with a ratio different as 1:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
# Fixing random state for reproducibility
np.random.seed(19680801)
# the random data
x = np.random.randn(1000)
y = np.random.randn(1000)
fig, axScatter = plt.subplots(figsize=(5.5, 5.5))
# the scatter plot:
axScatter.scatter(x, y)
axScatter.set_aspect(0.3)
# create new axes on the right and on the top of the current axes
# The first argument of the new_vertical(new_horizontal) method is
# the height (width) of the axes to be created in inches.
divider = make_axes_locatable(axScatter)
axHistx = divider.append_axes("top", 1.2, pad=0.1, sharex=axScatter)
axHisty = divider.append_axes("right", 1.2, pad=0.1, sharey=axScatter)
# make some labels invisible
axHistx.xaxis.set_tick_params(labelbottom=False)
axHisty.yaxis.set_tick_params(labelleft=False)
# now determine nice limits by hand:
binwidth = 0.25
xymax = max(np.max(np.abs(x)), np.max(np.abs(y)))
lim = (int(xymax/binwidth) + 1)*binwidth
bins = np.arange(-lim, lim + binwidth, binwidth)
axHistx.hist(x, bins=bins)
axHisty.hist(y, bins=bins, orientation='horizontal')
# the xaxis of axHistx and yaxis of axHisty are shared with axScatter,
# thus there is no need to manually adjust the xlim and ylim of these
# axis.
axHistx.set_yticks([0, 50, 100])
axHisty.set_xticks([0, 50, 100])
plt.show()
Result with axes_grid
Could it solved with axes_grid ???

Issue w/ image crossing dateline in imshow & cartopy

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:

Categories

Resources