带有 cartopy 的灵活地图

Flexible map ticklables with cartopy

我经常发现自己需要绘制(很多)不同区域和区域大小的地图。我希望这些地图具有指示经度和纬度的刻度标签(类似于此示例:https://scitools.org.uk/cartopy/docs/v0.15/examples/tick_labels.html)。

但是,那里建议的解决方案对我不起作用,因为它需要有关区域扩展的先验知识。这些年来,我编写了几种过于复杂的函数,以便尝试以一种灵活的方式使它工作。 所以我现在想知道的是:是否有一个简单的解决方案可以将纬度和经度标记标签放入变量扩展的地图?

这里有点接近,但仍然很不可靠:

import numpy as np
import cartopy.crs as ccrs 
from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
import matplotlib.pyplot as plt


def auto_labeling(lon=np.linspace(-10, 40, 10), lat=np.linspace(30, 70, 10), filename='test1.png'):
    proj = ccrs.PlateCarree(central_longitude=0)
    data = np.ones((len(lon), len(lat)))
    ax = plt.subplot(projection=proj)
    ax.pcolormesh(lon, lat, data, transform=ccrs.PlateCarree(), alpha=.5)
    ax.coastlines()
    ax.set_xticks(ax.get_xticks(), crs=ccrs.PlateCarree())
    ax.set_yticks(ax.get_yticks(), crs=ccrs.PlateCarree())
    lon_formatter = LongitudeFormatter()
    lat_formatter = LatitudeFormatter()
    ax.xaxis.set_major_formatter(lon_formatter)
    ax.yaxis.set_major_formatter(lat_formatter)
    plt.savefig(filename, dpi=300)
    plt.close()


if __name__ == '__main__':
    auto_labeling(filename='test3.png')  # nice
    auto_labeling(np.linspace(-120, 120, 10), filename='test4.png')  # not nice but somewhat okay
    auto_labeling(np.linspace(-120, 120, 10), np.linspace(-70, 70, 10), filename='test5.png')  # nice
    # auto_labeling(np.linspace(-180, 180, 10), np.linspace(-90, 90, 10), filename='test6.png')  # fails

PlateCaree 扩展了 ax.set_extent([min(lon), max(lon), min(lat), max(lat)]) 的区域。 ax.pcolormesh() 被注释掉,因为它会导致错误。我对这一点了解不够。

import numpy as np
import cartopy.crs as ccrs 
from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
import matplotlib.pyplot as plt

def auto_labeling(lon=np.arange(-10, 40, 10), lat=np.arange(30, 70, 10), filename='test1.png'):
    proj = ccrs.PlateCarree(central_longitude=0)
    data = np.ones((len(lon), len(lat)))
    plt.figure(figsize=(16,9))
    ax = plt.subplot(projection=proj)
    ax.set_extent([min(lon), max(lon), min(lat), max(lat)])
    # ax.pcolormesh(lon, lat, data, transform=ccrs.PlateCarree(), alpha=.5)
    ax.coastlines()
    ax.set_xticks(lon, crs=ccrs.PlateCarree())
    ax.set_yticks(lat, crs=ccrs.PlateCarree())
    lon_formatter = LongitudeFormatter()
    lat_formatter = LatitudeFormatter()
    ax.xaxis.set_major_formatter(lon_formatter)
    ax.yaxis.set_major_formatter(lat_formatter)
    # plt.savefig(filename, dpi=300)
    # plt.close()
    plt.show()


if __name__ == '__main__':
    auto_labeling(filename='test3.png')  # nice
    auto_labeling(np.arange(-120, 120, 60), filename='test4.png')  # not nice but somewhat okay
    auto_labeling(np.arange(-120, 120, 60), np.arange(-70, 70, 30), filename='test5.png')  # nice

好的,@r-beginners 的 set_extend 让我走上了正确的轨道。我仍然不明白正在发生的一切,但有两件事似乎很重要:

  • 自动创建的刻度需要限制在 [-180, 180] & [-90, 90] 否则我也会报错
  • 设置扩展使情节在我尝试过的所有情况下看起来都不错

我还添加了一个偏移参数,用于处理 @swatchai 默认情况下在每个方向上将范围扩大半个网格单元格的问题。也可以更多,因为我觉得这有时看起来不错。

import numpy as np
import cartopy.crs as ccrs
from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
import matplotlib.pyplot as plt


def auto_labeling(lon, lat, filename, offset_dx=.5, offset_dy=.5):
    assert len(np.unique(lon[1:] - lon[:-1])) == 1
    assert len(np.unique(lat[1:] - lat[:-1])) == 1
    yy, xx = np.meshgrid(lat, lon)
    data = np.ones((len(lon), len(lat)))

    proj = ccrs.PlateCarree(central_longitude=0)
    ax = plt.subplot(projection=proj)
    ax.pcolormesh(xx, yy, data, transform=ccrs.PlateCarree(), alpha=.5)
    ax.coastlines()

    xticks = ax.get_xticks()
    yticks = ax.get_yticks()
    xticks = xticks[(xticks>=-180) & (xticks<=180)]
    yticks = yticks[(yticks>=-90) & (yticks<=90)]
    ax.set_xticks(xticks, crs=ccrs.PlateCarree())
    ax.set_yticks(yticks, crs=ccrs.PlateCarree())

    lon_formatter = LongitudeFormatter()
    lat_formatter = LatitudeFormatter()
    ax.xaxis.set_major_formatter(lon_formatter)
    ax.yaxis.set_major_formatter(lat_formatter)

    # set the plot extend
    dx = (lon[1] - lon[0])*offset_dx
    dy = (lat[1] - lat[0])*offset_dy
    lon_min = max([-180, min(lon) - dx])
    lon_max = min([180, max(lon) + dx])
    lat_min = max([-90, min(lat) - dy])
    lat_max = min([90, max(lat) + dy])
    ax.set_xlim(lon_min, lon_max)
    ax.set_ylim(lat_min, lat_max)

    plt.savefig(filename, dpi=300)
    plt.close()

if __name__ == '__main__':
    auto_labeling(np.arange(-10, 40+2.5, 2.5), np.arange(30, 70+2.5, 2.5), 'test1.png', 1, 1)
    auto_labeling(np.arange(-120, 120+2.5, 2.5), np.arange(30, 70+2.5, 2.5), 'test2.png')
    auto_labeling(np.arange(-120, 120+2.5, 2.5), np.arange(-70, 70+2.5, 2.5), 'test3.png')
    auto_labeling(np.arange(-180+1.25, 180, 2.5), np.arange(-90+1.25, 90, 2.5), 'test4.png', 2, 3)  # offset is ignored for this case