背景
python作为胶水语言,近年来在气象数据处理与可视化中有着极为广泛地应用,诸多第三方库极为便利地满足了气象研究者处理数据与绘图的要求,处理数据的包:xarray、pandas,读取不同气象格式的包:netcdf4、h5py,用于模式后处理的wrf-python、python-CDO等等。 同样,python在气象可视化方面也有着许多包,一般而言,python的气象绘图是用matplotlib包绘图,用Basemao或cartopy包绘制地图底图,结合绘制。 Basemao包在2020年已经停止维护,目前主要使用cartopy库绘制底图。 然而cartopy图存在一些奇怪的bug,尤其在使用极地投影时,会出现:等值线重叠扭曲、风矢量无法对应,坐标标签无法添加等诸多问题。 其中,极地投影坐标的添加在cartioy20.0+中已经获得解决,具体参见我之前的博文:cartopy20.0+解决python极地投影问题,,然而,新版的cartopy仍然未解决在绘制极地区域时等值线扭曲重叠的问题,经过我的找寻,终于在Github上找到了解决方法。 具体的解决方法可以见:Plotting curvilinear with wrapping issue: suggestion on how to do it,下面我将以自己的数据为例,给大家查看效果。
实例
解决该问题主要使用了函数,z_masked_overlap,该函数解决了极地投影绘制contour、contourf绘制重叠的问题。 在源代码下,使用cartopy绘制等值线与等值线填充会出现:
proj =ccrs.NorthPolarStereo(central_longitude=0)
leftlon, rightlon, lowerlat, upperlat = (-180,180,60,90)
img_extent = [leftlon, rightlon, lowerlat, upperlat]
fig1 = plt.figure(figsize=(12,10))
f1_ax1 = fig1.add_axes([0.2, 0.3, 0.5, 0.5],projection = ccrs.NorthPolarStereo(central_longitude=0))
f1_ax1.set_extent(img_extent, ccrs.PlateCarree())
f1_ax1.add_feature(cfeature.COASTLINE.with_scale('110m'))
g1=f1_ax1.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=1, color='gray',linestyle='--')
```g1.xlocator = mticker.FixedLocator(np.linspace(-180,180,13))
g1.ylocator = mticker.FixedLocator(np.linspace(60, 90,4))
theta = np.linspace(0, 2*np.pi, 100)
center, radius = [0.5, 0.5], 0.44
verts = np.vstack([np.sin(theta), np.cos(theta)]).T
circle = mpath.Path(verts * radius + center)
levels = np.arange(5000,5600,50)
f1_ax1.set_boundary(circle, transform=f1_ax1.transAxes)
c7=f1_ax1.contourf(lon2D,lat2D,mhtsp,transform=ccrs.PlateCarree(),cmap=cmaps.matlab_jet,levels=20,vmin=5000,vmax=5600)
quiver = f1_ax1.quiver(lon2D, lat2D, musp, mvsp, pivot='tail',width=0.002, scale=200, color='black', headwidth=4,
regrid_shape=25,alpha=1,transform=ccrs.PlateCarree())
f1_ax1.quiverkey(quiver, 0.91, 1.03, 5, "5m/s",labelpos='E', coordinates='axes', fontproperties={'size': 10,'family':'Times New Roman'})
plt.show()
绘图如下:
可以看到,中央部分出现奇怪的多边形,等值线填充不对,风场也分布不对,这正是由于cartopy本身对于极地投影坐标的一些重叠部分处理不对的问题。
然而,这个问题再引入Plotting curvilinear with wrapping issue: suggestion on how to do it中编写的z_masked_overlap函数便可以轻松解决:
import os
import matplotlib.ticker as mticker
import netCDF4 as nc
import matplotlib.path as mpath
import cmaps
import matplotlib.pyplot as plt
import numpy as np
import numpy.ma as ma
import matplotlib as mpl
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from netCDF4 import Dataset
try:
import pykdtree.kdtree
_IS_PYKDTREE = True
except ImportError:
import scipy.spatial
_IS_PYKDTREE = False
from wrf import getvar, interplevel, vertcross,vinterp, ALL_TIMES, CoordPair, xy_to_ll, ll_to_xy, to_np, get_cartopy, latlon_coords, cartopy_xlim, cartopy_ylim
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
filepath='F:/ERA5/met_em/'
```def z_masked_overlap(axe, X, Y, Z, source_projection=None):
"""
for data in projection axe.projection
find and mask the overlaps (more 1/2 the axe.projection range)
X, Y either the coordinates in axe.projection or longitudes latitudes
Z the data
operation one of 'pcorlor', 'pcolormesh', 'countour', 'countourf'
if source_projection is a geodetic CRS data is in geodetic coordinates
and should first be projected in axe.projection
X, Y are 2D same dimension as Z for contour and contourf
same dimension as Z or with an extra row and column for pcolor
and pcolormesh
return ptx, pty, Z
"""
if not hasattr(axe, 'projection'):
return Z
if not isinstance(axe.projection, ccrs.Projection):
return Z
if len(X.shape) != 2 or len(Y.shape) != 2:
return Z
if (source_projection is not None and
isinstance(source_projection, ccrs.Geodetic)):
transformed_pts = axe.projection.transform_points(
source_projection, X, Y)
ptx, pty = transformed_pts[..., 0], transformed_pts[..., 1]
else:
ptx, pty = X, Y
with np.errstate(invalid='ignore'):
diagonal0_lengths = np.hypot(
ptx[1:, 1:] - ptx[:-1, :-1],
pty[1:, 1:] - pty[:-1, :-1]
)
diagonal1_lengths = np.hypot(
ptx[1:, :-1] - ptx[:-1, 1:],
pty[1:, :-1] - pty[:-1, 1:]
)
to_mask = (
(diagonal0_lengths > (
abs(axe.projection.x_limits[1]
- axe.projection.x_limits[0])) / 2) |
np.isnan(diagonal0_lengths) |
(diagonal1_lengths > (
abs(axe.projection.x_limits[1]
- axe.projection.x_limits[0])) / 2) |
np.isnan(diagonal1_lengths)
)
if (to_mask.shape[0] == Z.shape[0] - 1 and
to_mask.shape[1] == Z.shape[1] - 1):
to_mask_extended = np.zeros(Z.shape, dtype=bool)
to_mask_extended[:-1, :-1] = to_mask
to_mask_extended[-1, :] = to_mask_extended[-2, :]
to_mask_extended[:, -1] = to_mask_extended[:, -2]
to_mask = to_mask_extended
if np.any(to_mask):
Z_mask = getattr(Z, 'mask', None)
to_mask = to_mask if Z_mask is None else to_mask | Z_mask
Z = ma.masked_where(to_mask, Z)
return ptx, pty, Z
X, Y, masked_MDT = z_masked_overlap(
f1_ax1, lon2D, lat2D, mhtsp,
source_projection=ccrs.Geodetic())
f1_ax1.contour(X, Y, masked_MDT,
colors="black",levels=20,linestyle='--')
c7=f1_ax1.contourf(X, Y, masked_MDT,
levels=levels, cmap=cmaps.matlab_jet)
quiver = f1_ax1.quiver(X, Y, musp, mvsp, pivot='tail',width=0.002, scale=200, color='black', headwidth=4,
regrid_shape=30,alpha=1)
f1_ax1.quiverkey(quiver, 0.91, 1.03, 5, "5m/s",labelpos='E', coordinates='axes', fontproperties={'size': 10,'family':'Times New Roman'})
position=fig1.add_axes([0.2, 0.25, 0.5, 0.025])
font = {'family' : 'serif',
'color' : 'darkred',
'weight' : 'normal',
'size' : 16,
}
cb=fig1.colorbar(c7,cax=position,orientation='horizontal',format='%.1f',extend='both')
plt.show()
绘图: 可以看到,问题得到了较好地解决。
|