Cartopy 背景色(数据域外)

Cartopy background color (outside of data domain)

在 Cartopy 地图中,我希望未被任何数据(在我的域之外)覆盖的区域着色为例如浅灰色。 玩过 background_patch 并查看了这个示例 但仍然无法弄清楚如何做我想做的事。

这是一个人为的例子,我用红线使域边界可见。相反,我希望将红线以外的区域涂成浅灰色。

非常感谢!

编辑: 将投影更改为 LambertConformal 以证明下面提出的解决方案 () 仅适用于矩形网格。请参阅下面的其他数字。

import matplotlib.pyplot as plt
import matplotlib as mpl
import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import numpy as np

#create some lons/lats
lats = np.linspace(20,40,50)
lons = np.linspace(110,130,50)
lons,lats = np.meshgrid(lons,lats)

#some data
thedata = np.zeros_like(lats)
#some 'cloud' in the data
thedata[5:8,7:13] = 1

#theproj = ccrs.Mercator()
theproj = ccrs.LambertConformal() #choose another projection to obtain non-rectangular grid
ef, axar = plt.subplots(1,1, subplot_kw={'projection': theproj})#, 'axisbg': 'w'
ef.subplots_adjust(hspace=0.,wspace=0.,bottom=0.05,top=0.95,left=0.03,right=0.98)    
axar.coastlines()

mycmap = mpl.colors.ListedColormap(['white', 'black'])
bounds=[0,0.5,1]
norm = mpl.colors.BoundaryNorm(bounds, mycmap.N)

im = axar.pcolormesh(lons,lats,thedata,cmap=mycmap, transform=ccrs.PlateCarree())
im.set_norm(norm)

#make the extent larger to see a margin outside of the domain
axar.set_extent([lons[0,0]-1,lons[-1,-1]+1,lats[0,0]-1,lats[-1,-1]+1])


#for illustration: make the domain bounds visible
#but instead of the red domain bounds I would like to have the background (outside of the domain) in some color (lightgrey)
axar.plot(lons[:,0],lats[:,0],'r', transform=ccrs.PlateCarree())
axar.plot(lons[:,-1],lats[:,0],'r', transform=ccrs.PlateCarree())
axar.plot(lons[0,:],lats[0,:],'r', transform=ccrs.PlateCarree())
axar.plot(lons[0,:],lats[-1,:],'r', transform=ccrs.PlateCarree())

#some decoration to see where we are
gl = axar.gridlines(crs=ccrs.PlateCarree(), draw_labels=True,
                  linewidth=2, color='gray', alpha=0.5, linestyle='--')
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER    

plt.show()

当然,如果你不使用 cartopy,这可以单独使用 matplotlib 来实现:

import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import matplotlib.patches as mpatches
from matplotlib.path import Path
import numpy as np


# Create some lons/lats
lats = np.linspace(20,40,50)
lons = np.linspace(110,130,50)
lons,lats = np.meshgrid(lons,lats)

# Some data with 'cloud'.
thedata = np.zeros_like(lats)
thedata[5:8, 7:13] = 1

ax = plt.axes()

mycmap = mcolors.ListedColormap(['white', 'black'])
bounds=[0, 0.5, 1]
norm = mcolors.BoundaryNorm(bounds, mycmap.N)

im = ax.pcolormesh(lons, lats, thedata, cmap=mycmap,
                   norm=norm)

data_extent = np.array((lons[0,0], lons[-1,-1], lats[0,0], lats[-1,-1]))

# Make the extent larger to see a margin outside of the domain
ax.set_xlim(data_extent[:2] + [-1, 1])
ax.set_ylim(data_extent[2:] + [-1, 1])

# Create a path which has the exterior of the map, with an interior of the data we care about.
path_with_hole = Path([[-180, 90],
                       [180, 90],
                       [180, -90],
                       [-180, -90],
                       [-180, 90],
                       [data_extent[0], data_extent[2]],
                       [data_extent[1], data_extent[2]],
                       [data_extent[1], data_extent[3]],
                       [data_extent[0], data_extent[3]],
                       [data_extent[0], data_extent[2]]],
                      codes=[Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO,
                             Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO])

geom = mpatches.PathPatch(path_with_hole, facecolor='lightgrey',
                          edgecolor='white',
                          hatch='xxxx', alpha=0.6)
ax.add_patch(geom, )

plt.show()

关键是我们生成了一个Path,它的外部是地图,内部是我们感兴趣的领域。我们可以通过将路径变成一个补丁(你在 matplotlib 图上实际看到的东西)来将该路径添加到轴上。

我们可以以显而易见的方式在 cartopy 图形中使用此技术:

import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import matplotlib.patches as mpatches
from matplotlib.path import Path
import numpy as np


# Create some lons/lats
lats = np.linspace(20,40,50)
lons = np.linspace(110,130,50)
lons,lats = np.meshgrid(lons,lats)

# Some data with 'cloud'.
thedata = np.zeros_like(lats)
thedata[5:8, 7:13] = 1

pc = ccrs.PlateCarree()
ax = plt.axes(projection=ccrs.Mercator())
ax.coastlines()

# Some decoration to see where we are
gl = ax.gridlines(crs=pc,
                  draw_labels=True,
                  linewidth=2, color='gray', alpha=0.5,
                  linestyle='--')
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER

mycmap = mcolors.ListedColormap(['white', 'black'])
bounds=[0, 0.5, 1]
norm = mcolors.BoundaryNorm(bounds, mycmap.N)

im = ax.pcolormesh(lons, lats, thedata, cmap=mycmap,
                   norm=norm, transform=pc)

proj_extent = np.array(list(pc.x_limits) + list(pc.y_limits))
data_extent = np.array((lons[0,0], lons[-1,-1], lats[0,0], lats[-1,-1]))

# Make the extent larger to see a margin outside of the domain
ax.set_extent(data_extent + [-2, 2, -2, 2])

# Create a path which has the exterior of the map, with an interior of the data we care about.
path_with_hole = Path([[proj_extent[0], proj_extent[3]],
                       [proj_extent[1], proj_extent[3]],
                       [proj_extent[1], proj_extent[2]],
                       [proj_extent[0], proj_extent[2]],
                       [proj_extent[0], proj_extent[3]],
                       [data_extent[0], data_extent[2]],
                       [data_extent[1], data_extent[2]],
                       [data_extent[1], data_extent[3]],
                       [data_extent[0], data_extent[3]],
                       [data_extent[0], data_extent[2]]],
                      codes=[Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO,
                             Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO])

geom = mpatches.PathPatch(path_with_hole, facecolor='lightgrey',
                          edgecolor='white',
                          hatch='xxxx', alpha=0.6, transform=pc)
ax.add_patch(geom)

plt.show()

HTH

编辑: 你的问题特别提到了 LambertConformal,而且这个解决方案似乎不起作用。事实证明,问题不在于解决方案,而在于 Cartopy 的 LambertConformal 定义本身的分辨率太低。

遗憾的是,解决方法非常严格:有必要覆盖 LambertConformal 投影并修改幻数阈值。这在未来应该会容易得多。

class BetterLambertConformal(ccrs.LambertConformal):
    def __init__(self, *args, **kwargs):
        ccrs.LambertConformal.__init__(self, *args, **kwargs)
        self._threshold = 1e4

    @property
    def threshold(self):
        return self._threshold

ax = plt.axes(projection=BetterLambertConformal())