防止未分组的pcolor(网格)数据的虚假水平线

时间:2017-10-02 14:22:46

标签: python matplotlib cartopy

当我有一段穿过antimeridian的未经过拉的纬度/经度/数据对时,经度从-180转换到+180,我怎样才能防止pcolor(mesh)的图形绘制网格单元填满整个地球?我的问题与here相同,只是我使用的是cartopy而不是basemap。对链接问题(约为basemap)的近5年之久的评论声称有一个cartopy解决方案,但尚未公布。

示例代码:

#!/usr/bin/env python3.6

import numpy
import matplotlib.pyplot
import cartopy.crs

lons = numpy.array([[-174.719, -175.297, -175.883],
       [-175.164, -175.734, -176.312],
       [-175.594, -176.164, -176.734],
       [-176.016, -176.578, -177.148],
       [-176.43 , -176.984, -177.547],
       [-176.836, -177.383, -177.938],
       [-177.227, -177.773, -178.312],
       [-177.609, -178.148, -178.688],
       [-177.984, -178.516, -179.047],
       [-178.352, -178.875, -179.398],
       [-179.727,  179.766,  179.266],
       [ 179.945,  179.445,  178.945],
       [ 179.625,  179.133,  178.641],
       [ 179.312,  178.828,  178.336],
       [ 179.008,  178.523,  178.039],
       [ 178.711,  178.234,  177.75 ],
       [ 178.414,  177.945,  177.469],
       [ 178.133,  177.656,  177.188],
       [ 177.844,  177.383,  176.914],
       [ 177.57 ,  177.109,  176.648]])

lats = numpy.array([[ 67.391,  67.492,  67.586],
       [ 67.055,  67.148,  67.25 ],
       [ 66.711,  66.812,  66.906],
       [ 66.375,  66.469,  66.562],
       [ 66.031,  66.125,  66.219],
       [ 65.688,  65.781,  65.875],
       [ 65.344,  65.438,  65.523],
       [ 65.   ,  65.094,  65.18 ],
       [ 64.656,  64.742,  64.836],
       [ 64.312,  64.398,  64.484],
       [ 62.922,  63.   ,  63.086],
       [ 62.57 ,  62.648,  62.734],
       [ 62.219,  62.297,  62.383],
       [ 61.867,  61.945,  62.023],
       [ 61.516,  61.594,  61.672],
       [ 61.164,  61.242,  61.32 ],
       [ 60.812,  60.891,  60.961],
       [ 60.812,  60.891,  60.961],
       [ 60.461,  60.531,  60.609],
       [ 60.102,  60.18 ,  60.25 ]])

data = numpy.array([[ 231.73,  231.56,  231.22],
       [ 231.72,  231.72,  231.72],
       [ 232.24,  232.73,  233.37],
       [ 233.22,  233.69,  234.01],
       [ 234.33,  234.94,  235.39],
       [ 234.5 ,  235.11,  235.71],
       [ 235.41,  235.71,  236.  ],
       [ 235.27,  235.72,  236.31],
       [ 234.67,  235.43,  235.73],
       [ 235.43,  236.17,  235.88],
       [ 236.18,  236.18,  236.18],
       [ 236.07,  236.36,  236.79],
       [ 235.8 ,  236.1 ,  235.8 ],
       [ 236.84,  236.84,  236.55],
       [ 238.27,  238.27,  238.54],
       [ 237.72,  237.44,  237.72], 
       [ 238.42,  238.28,  238.28],
       [ 238.57,  238.57,  238.43],
       [ 240.17,  240.04,  239.65],
       [ 241.21,  241.21,  241.09]])

proj = cartopy.crs.Mollweide() 
ax = matplotlib.pyplot.axes(projection=proj)
trans = proj.transform_points(cartopy.crs.Geodetic(), lons, lats)
ax.coastlines()
ax.pcolormesh(trans[:, :, 0], trans[:, :, 1], data, transform=proj)

matplotlib.pyplot.savefig("/tmp/test.png")

预期输出将是一张地图,其中一些数据集中在北太平洋的某个地方。实际上,我得到的是一张横跨整个地球宽度的非常细长的地图:

Elongated map

我将数据限制在少数几点,以便我可以更轻松地将其纳入问题,但实际上我有一个完整的极地卫星数据轨道,总是穿过两极,因此总是穿过子午线。真实轨道的结果可能如下所示:

Map full of nonsense

更改中央经度可重新定位问题。我可以通过选择远离地图边缘的中心经度来降低严重程度。在此示例中,绘制了与上一个地图中相同的数据,但中心经度为90°E:

Still bad but different

2012年的

This pull request似乎是相关的,所以显然应该有一个相关的功能,但我不知道如何使用它。任何全局地图投影都会出现此问题。我正在使用cartopy 0.15.1。

如何正确绘制?

2 个答案:

答案 0 :(得分:1)

首先,感谢提供一些数据和一段代码来重现 - 这意味着我可以快速关注问题本身,而不是重现问题。

cartopy和basemap之间的主要区别在于cartopy可以为您处理矢量/栅格转换。完全有可能让地图以底图的方式运行,在这种情况下,用户可以自己转换数据。您提供的示例正是通过手动将lats / lons转换为目标投影来实现此目的。如果没有太多关心,您将很快找到反问题,例如您遇到过的问题。值得庆幸的是,cartopy 在数据转换方面非常谨慎,我鼓励您使用它。

在伪代码中,您的代码执行:

create a mollweide map
convert your lats/lons to mollweide coordinate system
plot newly converted mollweide data on mollweide map

在实践中,我们希望用cartopy改变范例并执行:

create a mollweide map
plot lat/lon data on mollweide map

通过这样做,我们正在为纸板提供正确转换数据的必要背景。

您的代码的主要更改是绘制原始数据(以lats / lons为单位),而不是您手工转换的坐标:

ax.pcolormesh(lons, lats, data, transform=ccrs.PlateCarree())

在这种情况下,我使用了PlateCarree投影而不是大地坐标系,因为我们目前没有实现大地测量的彩色框(即大圈),并且基本上是生成恒定纬度/经度的盒子。 / p>

使用此功能,我们最终会生成一个与您问题中的第一张图像非常相似的图表,这不完全符合您的要求。这样做的原因是你定义的一些盒子在PlateCarree投影空间中的宽度为~360度(这是一张平坦的纸片,并不知道环绕/子午线)。

让我们来看看一个人为的例子。如果您考虑测地线,可能希望以下代码在地图的任一侧生成两个小方框:

import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import shapely.geometry as sgeom

box = sgeom.box(minx=170, maxx=-170, miny=40, maxy=60)

proj = ccrs.Mollweide()

ax = plt.axes(projection=proj)
ax.coastlines()
ax.add_geometries([box], ccrs.PlateCarree(), facecolor='coral', 
                  edgecolor='black', alpha=0.5)

plt.show()

Big box, when we wanted two little ones...

唉,这不是我们得到的。如果我们记得Plate Carree投影是一个二维笛卡尔投影,其中两点之间唯一有效的直线是一条直线,这是有道理的 - 它对包围反映号的人一无所知。

(值得注意的是:如果我们要将几何投影更改为大地测量,那么我们会在给定点之间绘制大圆圈并获得所需的方框)

因此,为了生成所需的盒子,我们需要盒子的坐标有一个小的x范围,而不是一个接近360度的范围。值得庆幸的是,cartopy允许我们定义超过180度的PlateCarree坐标值 - 这是能够定义具有小x范围的PlateCarree框的关键。

import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import shapely.geometry as sgeom

box = sgeom.box(minx=170, maxx=190, miny=40, maxy=60)

proj = ccrs.Mollweide()

ax = plt.axes(projection=proj)
ax.coastlines()
ax.add_geometries([box], ccrs.PlateCarree(), facecolor='coral', 
                  edgecolor='black', alpha=0.5)

two boxes - hurrah!

回到你的例子 - 我们有一堆lat / lons,它们真正定义了大地测量补丁。 Cartopy还不能确定大地坐标 - 解决方法是pcolormesh PlateCarree坐标。尽管大地坐标和PlateCarree坐标可以互换,但它们具有根本不同的拓扑结构。

在您给出的示例中,可以将数据转换为有效的PlateCarree拓扑,方法是将360添加到0以下的值。不幸的是,这对于穿过中央子午线的几何图形无效 - 这将是一点点更多参与,并将成为IMO的有用扩展。

现在最终的代码如下:

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

lons = np.array([[-174.719, -175.297, -175.883],
       [-175.164, -175.734, -176.312],
       [-175.594, -176.164, -176.734],
       [-176.016, -176.578, -177.148],
       [-176.43 , -176.984, -177.547],
       [-176.836, -177.383, -177.938],
       [-177.227, -177.773, -178.312],
       [-177.609, -178.148, -178.688],
       [-177.984, -178.516, -179.047],
       [-178.352, -178.875, -179.398],
       [-179.727,  179.766,  179.266],
       [ 179.945,  179.445,  178.945],
       [ 179.625,  179.133,  178.641],
       [ 179.312,  178.828,  178.336],
       [ 179.008,  178.523,  178.039],
       [ 178.711,  178.234,  177.75 ],
       [ 178.414,  177.945,  177.469],
       [ 178.133,  177.656,  177.188],
       [ 177.844,  177.383,  176.914],
       [ 177.57 ,  177.109,  176.648]])

lats = np.array([[ 67.391,  67.492,  67.586],
       [ 67.055,  67.148,  67.25 ],
       [ 66.711,  66.812,  66.906],
       [ 66.375,  66.469,  66.562],
       [ 66.031,  66.125,  66.219],
       [ 65.688,  65.781,  65.875],
       [ 65.344,  65.438,  65.523],
       [ 65.   ,  65.094,  65.18 ],
       [ 64.656,  64.742,  64.836],
       [ 64.312,  64.398,  64.484],
       [ 62.922,  63.   ,  63.086],
       [ 62.57 ,  62.648,  62.734],
       [ 62.219,  62.297,  62.383],
       [ 61.867,  61.945,  62.023],
       [ 61.516,  61.594,  61.672],
       [ 61.164,  61.242,  61.32 ],
       [ 60.812,  60.891,  60.961],
       [ 60.812,  60.891,  60.961],
       [ 60.461,  60.531,  60.609],
       [ 60.102,  60.18 ,  60.25 ]])

data = np.array([[ 231.73,  231.56,  231.22],
       [ 231.72,  231.72,  231.72],
       [ 232.24,  232.73,  233.37],
       [ 233.22,  233.69,  234.01],
       [ 234.33,  234.94,  235.39],
       [ 234.5 ,  235.11,  235.71],
       [ 235.41,  235.71,  236.  ],
       [ 235.27,  235.72,  236.31],
       [ 234.67,  235.43,  235.73],
       [ 235.43,  236.17,  235.88],
       [ 236.18,  236.18,  236.18],
       [ 236.07,  236.36,  236.79],
       [ 235.8 ,  236.1 ,  235.8 ],
       [ 236.84,  236.84,  236.55],
       [ 238.27,  238.27,  238.54],
       [ 237.72,  237.44,  237.72], 
       [ 238.42,  238.28,  238.28],
       [ 238.57,  238.57,  238.43],
       [ 240.17,  240.04,  239.65],
       [ 241.21,  241.21,  241.09]])

proj = ccrs.PlateCarree(central_longitude=180) 
ax = plt.axes(projection=proj)
ax.coastlines('50m')
ax.margins(0.3)

lons[lons < 0] += 360
ax.pcolormesh(lons, lats, data, transform=ccrs.PlateCarree())

plt.show()

the resulting geometry

如果有兴趣,我建议您打开一个cartopy功能请求,添加一个通常将大地测量pcolormesh边界转换为板块的功能的功能。可以在https://github.com/SciTools/cartopy/issues/new找到折扣跟踪器。

答案 1 :(得分:0)

编辑:请注意,有两个跟进问题:

这将完全显示给定问题的解决方法**

<小时/> 原始答案:

我不确定以下内容是否有很大帮助,但让我们试一试。如果您能够在-180和180之间发生偏移的位置分割数据,则可以绘制两个不同的pcolor图,这样可以防止pcolorshapes围绕地球旋转一次。

使用测试数据,这很容易;我只是更改了其中一个数据点的符号。但是也可以将一行数据排除在外。然后单独绘制数据,得到所需的结果。请注意,需要进行标准化以确保两个图的色彩映射都是同步的。

norm = plt.Normalize(data.min(), data.max())
    ax.pcolormesh(trans[:10, :, 0], trans[:10, :, 1], data[:10,:], transform=proj, norm=norm)
    ax.pcolormesh(trans[10:, :, 0], trans[10:, :, 1], data[10:,:], transform=proj, norm=norm)

enter image description here ..... enter image description here

完整代码:

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

lons = numpy.array([[-174.719, -175.297, -175.883],
       [-175.164, -175.734, -176.312],
       [-175.594, -176.164, -176.734],
       [-176.016, -176.578, -177.148],
       [-176.43 , -176.984, -177.547],
       [-176.836, -177.383, -177.938],
       [-177.227, -177.773, -178.312],
       [-177.609, -178.148, -178.688],
       [-177.984, -178.516, -179.047],
       [-178.352, -178.875, -179.398],
       [ 179.999,  179.766,  179.266], #<- changed sign here
       [ 179.945,  179.445,  178.945],
       [ 179.625,  179.133,  178.641],
       [ 179.312,  178.828,  178.336],
       [ 179.008,  178.523,  178.039],
       [ 178.711,  178.234,  177.75 ],
       [ 178.414,  177.945,  177.469],
       [ 178.133,  177.656,  177.188],
       [ 177.844,  177.383,  176.914],
       [ 177.57 ,  177.109,  176.648]])

lats = numpy.array([[ 67.391,  67.492,  67.586],
       [ 67.055,  67.148,  67.25 ],
       [ 66.711,  66.812,  66.906],
       [ 66.375,  66.469,  66.562],
       [ 66.031,  66.125,  66.219],
       [ 65.688,  65.781,  65.875],
       [ 65.344,  65.438,  65.523],
       [ 65.   ,  65.094,  65.18 ],
       [ 64.656,  64.742,  64.836],
       [ 64.312,  64.398,  64.484],
       [ 62.922,  63.   ,  63.086],
       [ 62.57 ,  62.648,  62.734],
       [ 62.219,  62.297,  62.383],
       [ 61.867,  61.945,  62.023],
       [ 61.516,  61.594,  61.672],
       [ 61.164,  61.242,  61.32 ],
       [ 60.812,  60.891,  60.961],
       [ 60.812,  60.891,  60.961],
       [ 60.461,  60.531,  60.609],
       [ 60.102,  60.18 ,  60.25 ]])

data = numpy.array([[ 231.73,  231.56,  231.22],
       [ 231.72,  231.72,  231.72],
       [ 232.24,  232.73,  233.37],
       [ 233.22,  233.69,  234.01],
       [ 234.33,  234.94,  235.39],
       [ 234.5 ,  235.11,  235.71],
       [ 235.41,  235.71,  236.  ],
       [ 235.27,  235.72,  236.31],
       [ 234.67,  235.43,  235.73],
       [ 235.43,  236.17,  235.88],
       [ 236.18,  236.18,  236.18],
       [ 236.07,  236.36,  236.79],
       [ 235.8 ,  236.1 ,  235.8 ],
       [ 236.84,  236.84,  236.55],
       [ 238.27,  238.27,  238.54],
       [ 237.72,  237.44,  237.72], 
       [ 238.42,  238.28,  238.28],
       [ 238.57,  238.57,  238.43],
       [ 240.17,  240.04,  239.65],
       [ 241.21,  241.21,  241.09]])
print lons.shape, lats.shape, data.shape
proj = cartopy.crs.Mollweide() 
ax = plt.axes(projection=proj)
trans = proj.transform_points(cartopy.crs.Geodetic(), lons, lats)
ax.coastlines()
norm = plt.Normalize(data.min(), data.max())
ax.pcolormesh(trans[:10, :, 0], trans[:10, :, 1], data[:10,:], transform=proj, norm=norm)
ax.pcolormesh(trans[10:, :, 0], trans[10:, :, 1], data[10:,:], transform=proj, norm=norm)

plt.show()