Crossing Dateline with cartopy.io.img_tiles - python

I'm trying to figure out how to generate a map that crosses the dateline with Cartopy and a terrain from img_tiles. Here is what I have so far:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import cartopy.feature as cfeature
import cartopy.io.img_tiles as cimgt
import shapely.geometry as sgeom
my_dpi = 96
plt.figure(figsize=(1530/my_dpi, 900/my_dpi), dpi=my_dpi, frameon=False)
plt.subplots_adjust(left=0.0, right=1.0, top=1.0, bottom=0)
ax = plt.axes(projection=ccrs.Mercator(central_longitude=180))
terrain = cimgt.Stamen('terrain-background')
ax.add_image(terrain, 4)
states = cfeature.NaturalEarthFeature('cultural', 'admin_1_states_provinces', '10m', edgecolor='darkblue',facecolor='none')
ax.add_feature(states, linewidth = 0.1, linestyle='-')
# draw box
box = sgeom.box(minx=69, maxx=210, miny=-57, maxy=13.5)
ax.add_geometries([box], ccrs.PlateCarree(), facecolor='coral',
edgecolor='black', alpha=0.5)
# Set extent
ax.set_extent(oceania_coords, crs=ccrs.PlateCarree())
plt.show()
When I draw a box around the region I want to zoom in on, it looks correct.
When I try to ax.set_extent on this range, it seems to set all of the cfeatures correctly but screws up with the img_tiles features.
Is there any way to work around this? Thanks for the help!

I have a solution that is good enough for me, by abutting two subplots with the appropriate ratios and borders turned off. There is a tiny artifact on the seam, but I'm mostly slicing ocean in this frame so I'm ok with it. When I have Russia in the frame, it's more obvious.
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import cartopy.feature as cfeature
import cartopy.io.img_tiles as cimgt
import shapely.geometry as sgeom
import matplotlib.gridspec as gridspec
my_dpi=96
f = plt.figure(figsize=(1530/my_dpi, 900/my_dpi), dpi=my_dpi, frameon=False)
spec = gridspec.GridSpec(ncols=2, nrows=1,width_ratios=[111,30])
plt.subplots_adjust(left=0.0, right=1.0, top=1.0, bottom=0)
ax1 = f.add_subplot(spec[0],projection=ccrs.Mercator(central_longitude=180))
terrain = cimgt.Stamen('terrain-background')
ax1.add_image(terrain, 3)
states = cfeature.NaturalEarthFeature('cultural', 'admin_1_states_provinces', '10m', edgecolor='darkblue',facecolor='none')
ax1.add_feature(states, linewidth = 0.1, linestyle='-')
ax1.set_extent([69, 180, -57, 13.5], crs=ccrs.PlateCarree())
plt.gca().outline_patch.set_visible(False)
ax2 = f.add_subplot(spec[1],projection=ccrs.Mercator(central_longitude=180))
ax2.add_image(terrain, 3)
ax2.add_feature(states, linewidth = 0.1, linestyle='-')
ax2.set_extent([-180,-150, -57, 13.5], crs=ccrs.PlateCarree())
plt.gca().outline_patch.set_visible(False)
plt.subplots_adjust(wspace=0)

Related

Cartopy: draw gridline labels, but not the gridlines themselves

I can use ax.gridlines() to draw gridlines in Cartopy, and I can control whether the labels are drawn with the draw_labels= kwarg.
However, I want to draw only the labels, and not draw the gridlines. Is there a straightforward way to do that? Or do I need to drop down to "lower-level" Matplotlib functionality?
Yes, You can set the color of the grid lines to none.
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
#ref: https://scitools.org.uk/cartopy/docs/latest/gallery/gridlines_and_labels/gridliner.html#sphx-glr-gallery-gridlines-and-labels-gridliner-py
rotated_crs = ccrs.RotatedPole(pole_longitude=120.0, pole_latitude=70.0)
fig = plt.figure(figsize=(12,6))
ax0 = fig.add_subplot(1,2,1, projection=rotated_crs)
ax0.set_extent([-6, 1, 47.5, 51.5], crs=ccrs.PlateCarree())
ax0.add_feature(cfeature.LAND.with_scale('110m'))
ax0.gridlines(draw_labels=True, dms=True, x_inline=False, y_inline=False)
ax0.set_title('Original',fontsize = 24)
# non-gridlines
ax1 = fig.add_subplot(1,2,2, projection=rotated_crs)
ax1.set_extent([-6, 1, 47.5, 51.5], crs=ccrs.PlateCarree())
ax1.add_feature(cfeature.LAND.with_scale('110m'))
ax1.gridlines(draw_labels=True, dms=True, x_inline=False, y_inline=False,color = "None")
ax1.set_title('Non-gridlines',fontsize = 24)
plt.savefig("grid.png")

When using Cartopy (Python) to make Orthographic plot, how to crop the map / set the latitude/longitude boundaries of image?

I'm plotting an orthographic projection of the north pole next to one of the south pole. However, I want to crop the images so that I only see latitudes within maybe 10 or degrees of the poles, rather than all the way down to the equator. Here is my code so far:
# %% Import packages
import os
import matplotlib.pyplot as plt
import sys
import pandas as pd
import numpy as np
from geopack import geopack
import cartopy.crs as ccrs
import cartopy.feature as cfeature
fig = plt.figure(figsize=(20,10))
# North
ax = fig.add_subplot(1,2,1, projection=ccrs.Orthographic(central_latitude=90))
ax.stock_img()
ax.coastlines()
ax.add_feature(cfeature.BORDERS, linestyle="--")
# South
ax = fig.add_subplot(1,2,2, projection=ccrs.Orthographic(central_latitude=-90))
ax.stock_img()
ax.coastlines()
ax.add_feature(cfeature.BORDERS, linestyle="--")
plt.show()
The plot that my code currently generates
Any help would be appreciated. Thanks!
Map zooming may not be supported by the Orthographic projection in Cartopy.
I try the South[North]PolarStereo() projection, using set_extent to limit the map region, and clipping the plot by a circular path. The output figrure may meet your requirements.
Here is the code.
# %% Import packages
#import os
import matplotlib.pyplot as plt
#import sys
#import pandas as pd
import numpy as np
#from geopack import geopack
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.path as mpath
# ref: https://scitools.org.uk/cartopy/docs/latest/gallery/lines_and_polygons/always_circular_stereo.html#sphx-glr-gallery-lines-and-polygons-always-circular-stereo-py
def add_circle_boundary(ax):
# Compute a circle in axes coordinates, which we can use as a boundary
# for the map. We can pan/zoom as much as we like - the boundary will be
# permanently circular.
theta = np.linspace(0, 2*np.pi, 100)
center, radius = [0.5, 0.5], 0.5
verts = np.vstack([np.sin(theta), np.cos(theta)]).T
circle = mpath.Path(verts * radius + center)
ax.set_boundary(circle, transform=ax.transAxes)
fig = plt.figure(figsize=(20,10))
# South
#ax = fig.add_subplot(1,2,1, projection=ccrs.Orthographic(central_latitude=90))
#ax = fig.add_subplot(1,2,1, projection=ccrs.NorthPolarStereo())
ax = fig.add_subplot(1,2,1, projection=ccrs.SouthPolarStereo())
ax.stock_img()
ax.coastlines()
ax.gridlines()
ax.set_title('Original',fontsize = 24)
#ax.add_feature(cfeature.BORDERS, linestyle="--")
ax.set_extent([-180, 180, -90, 0], crs=ccrs.PlateCarree())
add_circle_boundary(ax)
# South zoomed
ax1 = fig.add_subplot(1,2,2, projection=ccrs.SouthPolarStereo())
ax1.stock_img()
ax1.coastlines()
ax1.gridlines()
#ax1.add_feature(cfeature.BORDERS, linestyle="--")
ax1.set_extent([-180, 180, -90, -60], crs=ccrs.PlateCarree())
ax1.set_title('Zoomed',fontsize = 24)
add_circle_boundary(ax1)
#plt.show()
plt.savefig("out.png")

How to remove the frame and axes around a Cartopy/Matplotlib plot?

All of a sudden, probably after some module update, I get an extra box/frame with x (0,1) and y (0,1) axes around my Cartopy map. How do I remove this?
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
#Set the projection information
proj = ccrs.NorthPolarStereo(true_scale_latitude = 75)
#Create a figure with an axes object on which we will plot. Pass the projection to that axes.
fig, ax = plt.subplots(figsize=(8,6))
ax = plt.axes(projection=proj)
ax.coastlines('10m')
ax.set_extent([-180, 180, 65, 90], crs=ccrs.PlateCarree())
Weird extra frame around the Cartopy plot
I have tried:
ax.axis('off')
right_side = ax.spines["right"]
right_side.set_visible(False)
plt.box(False)
plt.xticks([])
plt.yticks([])
plt.box(on=None)
Any other ideas would be highly appreciated.
This is a similar issue to: How to remove the frame around my Cartopy/Matplotlib plot
My first ever answer here. This clears projection border and won't spill over if you have multiple Axes(ax1, ax2)
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
#Set the projection information
proj = ccrs.NorthPolarStereo(true_scale_latitude=75)
#create fig, add 1 or more subplots and declare projection frameon status
fig = plt.figure(figsize=(8, 6), )
ax = fig.add_subplot(projection=proj, frameon=False)
ax.coastlines('10m')
ax.set_extent([-180, 180, 65, 90], crs=ccrs.PlateCarree())
This solves the issue:
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig,ax = plt.subplots(figsize=(8,6), subplot_kw={"projection": ccrs.NorthPolarStereo(true_scale_latitude = 75)})
ax.coastlines('10m')
ax.set_extent([-180, 180, 65, 90], crs=ccrs.PlateCarree())

how to make map lines visible over data in cartopy?

I am trying to make map lines always on top of data, but cannot find the right command/options to do so in cartopy. In the plot below, I want the thick blue line "under" the black state lines but on top of the beige states.
Code:
import matplotlib.pyplot as plt
import numpy as np
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import os,sys
conus_proj = ccrs.LambertConformal(central_longitude=-96,central_latitude=39.0)
fig = plt.figure(figsize=(10,8))
ax = fig.add_subplot(1,1,1,projection=conus_proj)
ax.set_extent([-120,-70,22,50])
#ax.add_feature(cfeature.BORDERS)
ax.add_feature(cfeature.COASTLINE)
ax.add_feature(cfeature.OCEAN, facecolor='#CCFEFF')
ax.add_feature(cfeature.LAKES, facecolor='#CCFEFF')
ax.add_feature(cfeature.RIVERS, facecolor='#CCFEFF')
ax.add_feature(cfeature.LAND, facecolor='#FFE9B5')
state_borders = cfeature.NaturalEarthFeature(category='cultural', name='admin_1_states_provinces_lakes', scale='50m', facecolor='#FFE9B5')
ax.add_feature(state_borders, edgecolor='black')
plt.plot([-120,-70],[35,45],linewidth=8, transform=ccrs.PlateCarree())
plt.show()
I have tried changing the zorder of ax.add_feature(state_borders...) and plt.plot(...) but have received weird results. The state borders are on top by default with pcolormesh, but don't appear to be so with plt.plot
Here is the output of the above code:
The reason things turn out differently for pcolormesh vs. plot is that those have different default zorders. If I set the zorder for the state borders (note below that I use Cartopy's built-in support for states) to 10, I get them to appear on top of the plot:
import matplotlib.pyplot as plt
import numpy as np
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import os,sys
conus_proj = ccrs.LambertConformal(central_longitude=-96,central_latitude=39.0)
fig = plt.figure(figsize=(10,8))
ax = fig.add_subplot(1,1,1,projection=conus_proj)
ax.set_extent([-120,-70,22,50])
ax.add_feature(cfeature.COASTLINE)
ax.add_feature(cfeature.OCEAN, facecolor='#CCFEFF')
ax.add_feature(cfeature.LAKES, facecolor='#CCFEFF')
ax.add_feature(cfeature.RIVERS, edgecolor='#CCFEFF')
ax.add_feature(cfeature.LAND, facecolor='#FFE9B5')
ax.add_feature(cfeature.STATES, edgecolor='black', zorder=10)
plt.plot([-120,-70],[35,45],linewidth=8, transform=ccrs.PlateCarree())
plt.show()
I also had to remove the face colors on some of the features.

How to easily add a sub_axes with proper position and size in matplotlib and cartopy?

I want to add a 2nd axes at the top right corner of a 1st axes. After googling, I found two ways to do things like this: fig.add_axes(), and mpl_toolkits.axes_grid.inset_locator.inset_axes. But the fig.add_axes() doesn't accept transform arg. So the following code throws an error. So the position can't be under the parent axes coordinates but the figure coordinates.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig, ax = plt.subplots(1, 1, subplot_kw={'projection': ccrs.PlateCarree()})
ax2 = fig.add_axes([0.8, 0, 0.2, 0.2], transform=ax.transAxes, projection=ccrs.PlateCarree())
And inset_axes() doesn't accept the projection arg, so I can't add ax2 as a cartopy geo-axes.
from mpl_toolkits.axes_grid.inset_locator import inset_axes
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig, ax = plt.subplots(1, 1, subplot_kw={'projection': ccrs.PlateCarree()})
# The following line doesn't work
ax2 = inset_axes(ax, width='20%', height='20%', axes_kwargs={'projection': ccrs.PlateCarree()})
# Doesn't work neither:
ax2 = inset_axes(ax, width='20%', height='20%', projection=ccrs.PlateCarree())
I've asked the question at matplotlib issue. It seems the following code works well as long as it's not a cartopy axes.
import matplotlib as mpl
fig, ax = plt.subplots(1, 1)
box = mpl.transforms.Bbox.from_bounds(0.8, 0.8, 0.2, 0.2)
ax2 = fig.add_axes(fig.transFigure.inverted().transform_bbox(ax.transAxes.transform_bbox(box)))
Question:
How to easily add a sub_axes with proper position and size in matplotlib and cartopy?
As I understand, after ax.set_extend(), the size of axes will change. So maybe is there a way that some point of sub_axes (eg: top right corner of ax2) can be anchored at one fixed position of the parent_axes (eg: top right corner of ax1)?
As inset_axes() doesn't accept projection arg, the roundabout way is to use InsetPosition(). This way you can create an axes in the usual way (using projection), and then "link" both axes using InsetPosition(). The main advantage over using subplots or similar is that the inset position is fixed, you can resize the figure or change the main plot area and the inset will always be in the same place relative to the main axes. This was based on this answer: specific location for inset axes, just adding the cartopy way of doing things.
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from mpl_toolkits.axes_grid1.inset_locator import InsetPosition
from shapely.geometry.polygon import LinearRing
extent = [-60, -30, -40, -10]
lonmin, lonmax, latmin, latmax = extent
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
ax.set_extent(extent, crs=ccrs.PlateCarree())
ax.add_feature(cfeature.LAND)
ax.add_feature(cfeature.OCEAN)
ax.add_feature(cfeature.COASTLINE)
# inset location relative to main plot (ax) in normalized units
inset_x = 1
inset_y = 1
inset_size = 0.2
ax2 = plt.axes([0, 0, 1, 1], projection=ccrs.Orthographic(
central_latitude=(latmin + latmax) / 2,
central_longitude=(lonmin + lonmax) / 2))
ax2.set_global()
ax2.add_feature(cfeature.LAND)
ax2.add_feature(cfeature.OCEAN)
ax2.add_feature(cfeature.COASTLINE)
ip = InsetPosition(ax, [inset_x - inset_size / 2,
inset_y - inset_size / 2,
inset_size,
inset_size])
ax2.set_axes_locator(ip)
nvert = 100
lons = np.r_[np.linspace(lonmin, lonmin, nvert),
np.linspace(lonmin, lonmax, nvert),
np.linspace(lonmax, lonmax, nvert)].tolist()
lats = np.r_[np.linspace(latmin, latmax, nvert),
np.linspace(latmax, latmax, nvert),
np.linspace(latmax, latmin, nvert)].tolist()
ring = LinearRing(list(zip(lons, lats)))
ax2.add_geometries([ring], ccrs.PlateCarree(),
facecolor='none', edgecolor='red', linewidth=0.75)
I may have figured something out.
According to the answer this question. I can get the position of both axes, then reposition the 2nd axes. The code was like:
import matplotlib.pyplot as plt
from cartopy import crs as ccrs
fig, ax = plt.subplots(1, 1, subplot_kw={'projection': ccrs.PlateCarree()})
ax2 = fig.add_axes([0.8, 0.8, 0.2, 0.2], projection=ccrs.PlateCarree())
ax.set_extent([100, 120, 20, 40])
ax.coastlines()
ax2.set_global()
ax2.coastlines()
ax2.stock_img()
def reposition():
plt.draw()
p1 = ax.get_position()
p2 = ax2.get_position()
ax2.set_position([p1.x1-p2.width, p1.y1-p2.height, p2.width, p2.height])
reposition()
plt.show()
The result is just what I want.

Categories