当我有一段穿过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")
预期输出将是一张地图,其中一些数据集中在北太平洋的某个地方。实际上,我得到的是一张横跨整个地球宽度的非常细长的地图:
我将数据限制在少数几点,以便我可以更轻松地将其纳入问题,但实际上我有一个完整的极地卫星数据轨道,总是穿过两极,因此总是穿过子午线。真实轨道的结果可能如下所示:
更改中央经度可重新定位问题。我可以通过选择远离地图边缘的中心经度来降低严重程度。在此示例中,绘制了与上一个地图中相同的数据,但中心经度为90°E:
2012年的This pull request似乎是相关的,所以显然应该有一个相关的功能,但我不知道如何使用它。任何全局地图投影都会出现此问题。我正在使用cartopy 0.15.1。
如何正确绘制?
答案 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()
唉,这不是我们得到的。如果我们记得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)
回到你的例子 - 我们有一堆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()
如果有兴趣,我建议您打开一个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)
完整代码:
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()