使用带有 cartopy 投影的箭袋时箭头长度错误

Wrong arrow length using quiver with cartopy projections

我想用 cartopy.

绘制一个矢量场,矢量表示地图上一个点到另一个点之间的位移

我的代码在使用 PlateCarree() 转换时按预期工作,但箭头长度与我测试的所有其他投影相差几个数量级。

这是一个 MWE,应该可以很清楚地说明问题:

import matplotlib.pyplot as plt
import cartopy.crs as ccrs

import numpy

# Want to test for several different projections
projections = [
    ccrs.PlateCarree(),
    ccrs.EqualEarth(),
    ccrs.Mollweide(),
    ccrs.AzimuthalEquidistant(),
]
# ALl the coordinates will be given in the PlateCarree coordinate system.
coordinate_ccrs = ccrs.PlateCarree()

# We want N**2 points over the latitude/longitude values.
N = 5
lat, lon = numpy.meshgrid(numpy.linspace(-80, 80, N), numpy.linspace(-170, 170, N))
lat, lon = lat.flatten(), lon.flatten()

# We want arrows to appear, let make a small perturbation and try
# to do an arrow from (lon, lat) to (lon + perturbation, lat + perturbation).
rng = numpy.random.default_rng()
perturbation_amplitude = 10
lat_perturbation = perturbation_amplitude * rng.random(N * N)
lon_perturbation = perturbation_amplitude * rng.random(N * N)

# Create the matplotlib figure and axes, no projection for the moment as this
# will be changed later.
fig, axes = plt.subplots(2, 2)
axes = axes.flatten()

for i, projection in enumerate(projections):
    # Replace the existing ax with an ax with the desired projection.
    ax = axes[i]
    fig.delaxes(ax)
    ax = axes[i] = fig.add_subplot(2, 2, i + 1, projection=projection)
    # Make the plot readable.
    ax.set_global()
    ax.gridlines(draw_labels="x")

    # Non pertubed points are plotted in black.
    ax.plot(lon, lat, "k.", ms=5, transform=coordinate_ccrs)
    # Perturbed points are plotted in red.
    ax.plot(
        lon + lon_perturbation,
        lat + lat_perturbation,
        "r.",
        ms=5,
        transform=coordinate_ccrs,
    )
    # We try to draw arrows from a given black dot to its corresponding
    # red dot.
    ax.quiver(
        lon,
        lat,
        lon_perturbation,
        lat_perturbation,
        transform=coordinate_ccrs,
        # From https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.quiver.html?highlight=quiver#matplotlib.axes.Axes.quiver
        # look at the documentation of the "scale_unit" parameter.
        # The next 3 parameters are what matplotlib tell us to do. From
        # matplotlib documentation:
        #   To plot vectors in the x-y plane, with u and v having the same units
        #   as x and y, use angles='xy', scale_units='xy', scale=1.
        angles="xy",
        scale_units="xy",
        scale=1,
        # Simply make the arrows nicer, removing these last 3 parameters do not
        # solve the issue.
        minshaft=2,
        minlength=0.5,
        width=0.002,
    )

# Show everything
plt.show()

在屏幕上显示如下图像:

PlateCarree 转换是唯一显示箭头的转换。事实上,其他 3 个投影中有箭头,但我为了看到它们,我需要在调用 quiver 时使用 scale=0.00001 将它们缩放 10000,这给出:

我在使用 cartopy 时是否犯了错误 API,这是预期的行为,我在文档中遗漏了什么,还是这是一个错误?

虽然关于 github 关于 cartopy 实现 quiver-plot 转换 GitHub-issues 有相当多的争论 GitHub-issues 实际上有一种方法可以让你的情节看起来像你想要的那样...

但是,在考虑这个问题时...我注意到在使用投影 quiver-plots 时您可能需要考虑一件事...

在我看来,re-projected 箭头很可能需要弯曲才能真正可视化原始数据中提供的相同方向!

(在 input-crs 中,箭头指向 A 点到 B 点的直线,但是如果 re-project 这些点,则连接 A 和 B 的“直线”现在位于一般是曲线,所以如果原来的方向是正确的,我认为新的方向应该用弯曲的箭头表示...)

也就是说,您可以通过手动转换点而不是让 cartopy 完成工作来实现您想要的效果:

import matplotlib.pyplot as plt
import cartopy.crs as ccrs

import numpy

# Want to test for several different projections
projections = [
    ccrs.PlateCarree(),
    ccrs.EqualEarth(),
    ccrs.Mollweide(),
    ccrs.AzimuthalEquidistant(),
]
# ALl the coordinates will be given in the PlateCarree coordinate system.
coordinate_ccrs = ccrs.PlateCarree()

# We want N**2 points over the latitude/longitude values.
N = 5
lat, lon = numpy.meshgrid(numpy.linspace(-80, 80, N), numpy.linspace(-170, 170, N))
lat, lon = lat.flatten(), lon.flatten()

# We want arrows to appear, let make a small perturbation and try
# to do an arrow from (lon, lat) to (lon + perturbation, lat + perturbation).
rng = numpy.random.default_rng()
perturbation_amplitude = 10
lat_perturbation = perturbation_amplitude * rng.random(N * N)
lon_perturbation = perturbation_amplitude * rng.random(N * N)

# Create the matplotlib figure and axes, no projection for the moment as this
# will be changed later.
fig, axes = plt.subplots(2, 2)
axes = axes.flatten()

for i, projection in enumerate(projections):
    # Replace the existing ax with an ax with the desired projection.
    ax = axes[i]
    fig.delaxes(ax)
    ax = axes[i] = fig.add_subplot(2, 2, i + 1, projection=projection)
    # Make the plot readable.
    ax.set_global()
    ax.gridlines(draw_labels="x")

    # Non pertubed points are plotted in black.
    ax.plot(lon, lat, "k.", ms=5, transform=coordinate_ccrs)
    # Perturbed points are plotted in red.
    ax.plot(
        lon + lon_perturbation,
        lat + lat_perturbation,
        "r.",
        ms=5,
        transform=coordinate_ccrs,
    )
    
    
    xy_start = projection.transform_points(coordinate_ccrs, lon, lat)[:,:-1].T
    xy_end = projection.transform_points(coordinate_ccrs, lon + lon_perturbation, 
                                         lat + lat_perturbation)[:,:-1].T
    
    # We try to draw arrows from a given black dot to its corresponding
    # red dot.
    ax.quiver(
        *xy_start, 
        *(xy_end - xy_start),
        # From https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.quiver.html?highlight=quiver#matplotlib.axes.Axes.quiver
        # look at the documentation of the "scale_unit" parameter.
        # The next 3 parameters are what matplotlib tell us to do. From
        # matplotlib documentation:
        #   To plot vectors in the x-y plane, with u and v having the same units
        #   as x and y, use angles='xy', scale_units='xy', scale=1.
        angles="xy",
        scale_units="xy",
        scale=1,
        # Simply make the arrows nicer, removing these last 3 parameters do not
        # solve the issue.
        minshaft=2,
        minlength=0.5,
        width=0.002,
    )

# Show everything
plt.show()