【问题标题】:3D CartoPy similar to Matplotlib-Basemap类似于 Matplotlib-Basemap 的 3D CartoPy
【发布时间】:2014-07-10 05:26:29
【问题描述】:

我是 Python 新手,有一个关于 Cartopy 能否用于 3D 绘图的问题。下面是使用matplotlibBasemap 的示例。

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.basemap import Basemap

m = Basemap(projection='merc',
            llcrnrlat=52.0,urcrnrlat=58.0,
            llcrnrlon=19.0,urcrnrlon=40.0,
            rsphere=6371200.,resolution='h',area_thresh=10)

fig = plt.figure()
ax = Axes3D(fig)
ax.add_collection3d(m.drawcoastlines(linewidth=0.25))
ax.add_collection3d(m.drawcountries(linewidth=0.35))
ax.add_collection3d(m.drawrivers(color='blue'))

ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Height')

fig.show()

这将在 3D 轴内创建地图,以便您可以在曲面上绘制对象。但是使用 Cartopy 会返回 matplotlib.axes.GeoAxesSubplot。不清楚如何使用matplotlib-basemap 将其添加到上面的 3D 图形/轴。

那么,有人可以指点一下如何使用 Cartopy 绘制类似的 3D 绘图吗?

【问题讨论】:

    标签: python-2.7 matplotlib 3d matplotlib-basemap cartopy


    【解决方案1】:

    底图 mpl3d 是一个非常简洁的 hack,但它并没有被设计为以所描述的方式运行。因此,除了简单的海岸线之外,您目前无法使用相同的技术。例如,填充的大洲就不适用于 AFAICT。

    也就是说,使用 cartopy 时可以使用类似的 hack。由于我们可以通用地访问 shapefile 信息,因此该解决方案应该适用于任何折线 shapefile,例如海岸线。

    第一步是获取 shapefile 和相应的几何图形:

    feature = cartopy.feature.NaturalEarthFeature('physical', 'coastline', '110m')
    geoms = feature.geometries()
    

    接下来,我们可以将这些转换为所需的投影:

    target_projection = ccrs.PlateCarree()
    geoms = [target_projection.project_geometry(geom, feature.crs)
             for geom in geoms]
    

    由于这些是匀称的几何图形,因此我们希望将它们转换为 matplotlib 路径:

    from cartopy.mpl.patch import geos_to_path
    import itertools
    
    paths = list(itertools.chain.from_iterable(geos_to_path(geom)
                                                 for geom in geoms))
    

    使用路径,我们应该能够在 matplotlib 中创建一个 PathCollection,并将其添加到轴上,但遗憾的是,Axes3D 似乎无法处理 PathCollection 实例,因此我们需要通过构造一个 LineCollection (就像底图一样)。遗憾的是 LineCollections 不采用路径,而是采用段,我们可以用它来计算:

    segments = []
    for path in paths:
        vertices = [vertex for vertex, _ in path.iter_segments()]
        vertices = np.asarray(vertices)
        segments.append(vertices)
    

    综合起来,我们最终会得到与您的代码生成的底图相似的结果:

    import itertools
    
    from mpl_toolkits.mplot3d import Axes3D
    import matplotlib.pyplot as plt
    from matplotlib.collections import LineCollection
    import numpy as np
    
    import cartopy.feature
    from cartopy.mpl.patch import geos_to_path
    import cartopy.crs as ccrs
    
    
    fig = plt.figure()
    ax = Axes3D(fig, xlim=[-180, 180], ylim=[-90, 90])
    ax.set_zlim(bottom=0)
    
    
    target_projection = ccrs.PlateCarree()
    
    feature = cartopy.feature.NaturalEarthFeature('physical', 'coastline', '110m')
    geoms = feature.geometries()
    
    geoms = [target_projection.project_geometry(geom, feature.crs)
             for geom in geoms]
    
    paths = list(itertools.chain.from_iterable(geos_to_path(geom) for geom in geoms))
    
    # At this point, we start working around mpl3d's slightly broken interfaces.
    # So we produce a LineCollection rather than a PathCollection.
    segments = []
    for path in paths:
        vertices = [vertex for vertex, _ in path.iter_segments()]
        vertices = np.asarray(vertices)
        segments.append(vertices)
    
    lc = LineCollection(segments, color='black')
    
    ax.add_collection3d(lc)
    
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Height')
    
    plt.show()
    

    最重要的是,mpl3d 似乎可以很好地处理 PolyCollection,这将是我要研究的填充几何图形的路线,例如陆地轮廓(与海岸线相反,它是严格的轮廓)。

    重要的一步是将路径转换为多边形,并在 PolyCollection 对象中使用它们:

    concat = lambda iterable: list(itertools.chain.from_iterable(iterable))
    
    polys = concat(path.to_polygons() for path in paths)
    lc = PolyCollection(polys, edgecolor='black',
                        facecolor='green', closed=False)
    

    此案例的完整代码如下所示:

    import itertools
    
    from mpl_toolkits.mplot3d import Axes3D
    import matplotlib.pyplot as plt
    from matplotlib.collections import LineCollection, PolyCollection
    import numpy as np
    
    import cartopy.feature
    from cartopy.mpl.patch import geos_to_path
    import cartopy.crs as ccrs
    
    
    fig = plt.figure()
    ax = Axes3D(fig, xlim=[-180, 180], ylim=[-90, 90])
    ax.set_zlim(bottom=0)
    
    
    concat = lambda iterable: list(itertools.chain.from_iterable(iterable))
    
    target_projection = ccrs.PlateCarree()
    
    feature = cartopy.feature.NaturalEarthFeature('physical', 'land', '110m')
    geoms = feature.geometries()
    
    geoms = [target_projection.project_geometry(geom, feature.crs)
             for geom in geoms]
    
    paths = concat(geos_to_path(geom) for geom in geoms)
    
    polys = concat(path.to_polygons() for path in paths)
    
    lc = PolyCollection(polys, edgecolor='black',
                        facecolor='green', closed=False)
    
    ax.add_collection3d(lc)
    
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Height')
    
    plt.show()
    

    屈服:

    【讨论】:

    • 佩尔森,感谢您的出色回答。是否可以仅绘制地球的一部分而不是整个轮廓,并使用不同的 cartopy 投影来绘制?
    • 是否可以在此之上绘制热图或散点图?
    • 忽略:回答here
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-24
    • 2012-11-29
    • 1970-01-01
    • 2018-08-05
    • 1970-01-01
    • 2012-11-16
    • 1970-01-01
    相关资源
    最近更新 更多