问题
I'm trying to plot global Aerosol Optical Depths (AOD), and the values are typically around 0.2, but in some regions can reach 1.2 or more. Ideally I want to plot these high values, without losing the detail of the smaller values. A log scale color bar isn't really appropriate either, so I've tried to use two linear ranges as described in the docs:
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import numpy as np
import cartopy.crs as ccrs
class MidpointNormalize(colors.Normalize):
def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
self.midpoint = midpoint
colors.Normalize.__init__(self, vmin, vmax, clip)
def __call__(self, value, clip=None):
# I'm ignoring masked values and all kinds of edge cases to make a
# simple example...
x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]
res = np.ma.masked_array(np.interp(value, x, y))
return res
This breaks when I try to do a pcolormesh plot with Cartopy though. Creating dummy data as per one of the gallery examples:
def sample_data(shape=(73, 145)):
"""Returns ``lons``, ``lats`` and ``data`` of some fake data."""
nlats, nlons = shape
lats = np.linspace(-np.pi / 2, np.pi / 2, nlats)
lons = np.linspace(0, 2 * np.pi, nlons)
lons, lats = np.meshgrid(lons, lats)
wave = 0.75 * (np.sin(2 * lats) ** 8) * np.cos(4 * lons)
mean = 0.5 * np.cos(2 * lats) * ((np.sin(2 * lats)) ** 2 + 2)
lats = np.rad2deg(lats)
lons = np.rad2deg(lons)
data = wave + mean
return lons, lats, data
ax = plt.axes(projection=ccrs.Mollweide())
lons, lats, data = sample_data()
ax.contourf(lons, lats, data,
transform=ccrs.PlateCarree(),
cmap='spectral', norm=MidpointNormalize(midpoint=0.8))
ax.coastlines()
ax.set_global()
plt.show()
Gives me this, which looks OK:
However, when using the pcolormesh equivalent does not seem to work, it has a smeared set of values between 0 and 180 degrees longitude (the right half of the plot) instead of the wavy pattern seen in the contour plot:
ax.pcolormesh(lons, lats, data,
transform=ccrs.PlateCarree(),
cmap='spectral', norm=MidpointNormalize(midpoint=0.8))
How can I make this work for pcolormesh? I typically see this when I've done something wrong with Cartopy projection/transformation so presumably this is something to do with the way Cartopy does wrapping around the dateline or one of the edge cases the simple matplotlib example ignores, but I can't figure it out.
Note that this only occurs when using the custom Normalization instance; without it, also pcolormesh works as expected.
回答1:
It seems to have something to do with the masking inside the normalization class. So here is a version that is working:
class MidpointNormalize(colors.Normalize):
def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
self.midpoint = midpoint
colors.Normalize.__init__(self, vmin, vmax, clip)
def __call__(self, value, clip=None):
result, is_scalar = self.process_value(value)
(vmin,), _ = self.process_value(self.vmin)
(vmax,), _ = self.process_value(self.vmax)
resdat = np.asarray(result.data)
result = np.ma.array(resdat, mask=result.mask, copy=False)
x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]
res = np.interp(result, x, y)
result = np.ma.array(res, mask=result.mask, copy=False)
if is_scalar:
result = result[0]
return result
The complete code:
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import numpy as np
import cartopy.crs as ccrs
class MidpointNormalize(colors.Normalize):
def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
self.midpoint = midpoint
colors.Normalize.__init__(self, vmin, vmax, clip)
def __call__(self, value, clip=None):
result, is_scalar = self.process_value(value)
(vmin,), _ = self.process_value(self.vmin)
(vmax,), _ = self.process_value(self.vmax)
resdat = np.asarray(result.data)
result = np.ma.array(resdat, mask=result.mask, copy=False)
x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]
res = np.interp(result, x, y)
result = np.ma.array(res, mask=result.mask, copy=False)
if is_scalar:
result = result[0]
return result
def sample_data(shape=(73, 145)):
"""Returns ``lons``, ``lats`` and ``data`` of some fake data."""
nlats, nlons = shape
lats = np.linspace(-np.pi / 2, np.pi / 2, nlats)
lons = np.linspace(0, 2 * np.pi, nlons)
lons, lats = np.meshgrid(lons, lats)
wave = 0.75 * (np.sin(2 * lats) ** 8) * np.cos(4 * lons)
mean = 0.5 * np.cos(2 * lats) * ((np.sin(2 * lats)) ** 2 + 2)
lats = np.rad2deg(lats)
lons = np.rad2deg(lons)
data = wave + mean
return lons, lats, data
ax = plt.axes(projection=ccrs.Mollweide())
lons, lats, data = sample_data()
norm = norm=MidpointNormalize(midpoint=0.8)
cm = ax.pcolormesh(lons, lats, data,
transform=ccrs.PlateCarree(),
cmap='spectral', norm=norm )
ax.coastlines()
plt.colorbar(cm, orientation="horizontal")
ax.set_global()
plt.show()
produces
来源:https://stackoverflow.com/questions/43984077/cartopy-pcolormesh-with-re-normalized-colorbar