【问题标题】:Create closed polygon from boundary points从边界点创建闭合多边形
【发布时间】:2013-04-25 22:02:35
【问题描述】:

我有一组定义区域边界的经纬度点。我想根据这些点创建一个多边形并在地图上绘制多边形并填充它。目前,我的多边形似乎由许多连接所有点的补丁组成,但是点的顺序不正确,当我尝试填充多边形时,我得到一个看起来很奇怪的区域(见附件)。

我根据多边形的中心对我的经纬度点(mypolyXY 数组)进行排序,但我的猜测是这不正确:

cent=(np.sum([p[0] for p in mypolyXY])/len(mypolyXY),np.sum([p[1] for p in mypolyXY])/len(mypolyXY))
# sort by polar angle
mypolyXY.sort(key=lambda p: math.atan2(p[1]-cent[1],p[0]-cent[0]))

我使用

绘制点位置(黑色圆圈)和我的多边形(彩色补丁)
scatter([p[0] for p in mypolyXY],[p[1] for p in mypolyXY],2)
p = Polygon(mypolyXY,facecolor=colors,edgecolor='none')
ax.add_artist(p)

我的问题是:如何根据我的经纬度点数组关闭多边形?

更新: 我对如何绘制多边形进行了更多测试。我删除了排序例程,只是按照它们在文件中出现的顺序使用数据。这似乎改善了结果,但正如@tcaswell 所提到的,多边形形状仍然会削弱自身(参见新图)。我希望可以有一个路径/多边形例程可以解决我的问题,并在某种程度上合并多边形边界内的所有形状或路径。非常欢迎提出建议。

更新 2:

我现在有了一个基于@Rutger Kassies 和 Roland Smith 建议的脚本的工作版本。我最终使用 org 阅读了 Shapefile,它的效果相对较好。它适用于标准 lmes_64.shp 文件,但是当我使用更详细的 LME 文件时,每个 LME 可能由多个多边形组成,这个脚本就崩溃了。我必须找到一种方法来合并具有相同 LME 名称的各种多边形才能使其工作。我附上了我最终得到的脚本,以防有人看它。我非常感谢 cmets 如何改进此脚本或使其更通用。该脚本在我从 netcdf 文件中读取的多边形区域内创建多边形并提取数据。输入文件的网格为-180到180和-90到90。

import numpy as np
import math
from pylab import *
import matplotlib.patches as patches
import string, os, sys
import datetime, types
from netCDF4 import Dataset
import matplotlib.nxutils as nx
from mpl_toolkits.basemap import Basemap
import ogr
import matplotlib.path as mpath
import matplotlib.patches as patches


def getLMEpolygon(coordinatefile,mymap,index,first):

    ds = ogr.Open(coordinatefile)
    lyr = ds.GetLayer(0)
    numberOfPolygons=lyr.GetFeatureCount()

    if first is False:
        ft = lyr.GetFeature(index)
        print "Found polygon:",  ft.items()['LME_NAME']
        geom = ft.GetGeometryRef()

        codes = []
        all_x = []
        all_y = []
        all_XY= []

        if (geom.GetGeometryType() == ogr.wkbPolygon):
          for i in range(geom.GetGeometryCount()):

            r = geom.GetGeometryRef(i)
            x = [r.GetX(j) for j in range(r.GetPointCount())]
            y = [r.GetY(j) for j in range(r.GetPointCount())]

            codes += [mpath.Path.MOVETO] + (len(x)-1)*[mpath.Path.LINETO]
            all_x += x
            all_y += y
            all_XY +=mymap(x,y)


        if len(all_XY)==0:
            all_XY=None
            mypoly=None
        else:
            mypoly=np.empty((len(all_XY[:][0]),2))
            mypoly[:,0]=all_XY[:][0]
            mypoly[:,1]=all_XY[:][3]
    else:
        print "Will extract data for %s polygons"%(numberOfPolygons)
        mypoly=None
    first=False
    return mypoly, first, numberOfPolygons


def openCMIP5file(CMIP5name,myvar,mymap):
    if os.path.exists(CMIP5name):
        myfile=Dataset(CMIP5name)
        print "Opened CMIP5 file: %s"%(CMIP5name)
    else:
        print "Could not find CMIP5 input file %s : abort"%(CMIP5name)
        sys.exit()
    mydata=np.squeeze(myfile.variables[myvar][-1,:,:]) - 273.15
    lonCMIP5=np.squeeze(myfile.variables["lon"][:])
    latCMIP5=np.squeeze(myfile.variables["lat"][:])

    lons,lats=np.meshgrid(lonCMIP5,latCMIP5)

    lons=lons.flatten()
    lats=lats.flatten()
    mygrid=np.empty((len(lats),2))
    mymapgrid=np.empty((len(lats),2))

    for i in xrange(len(lats)):
        mygrid[i,0]=lons[i]
        mygrid[i,1]=lats[i]
        X,Y=mymap(lons[i],lats[i])
        mymapgrid[i,0]=X
        mymapgrid[i,1]=Y

    return mydata, mygrid, mymapgrid

def drawMap(NUM_COLORS):

    ax = plt.subplot(111)
    cm = plt.get_cmap('RdBu')
    ax.set_color_cycle([cm(1.*j/NUM_COLORS) for j in range(NUM_COLORS)])
    mymap = Basemap(resolution='l',projection='robin',lon_0=0)

    mymap.drawcountries()

    mymap.drawcoastlines()
    mymap.fillcontinents(color='grey',lake_color='white')
    mymap.drawparallels(np.arange(-90.,120.,30.))
    mymap.drawmeridians(np.arange(0.,360.,60.))
    mymap.drawmapboundary(fill_color='white')

    return ax, mymap, cm

"""Edit the correct names below:"""

LMEcoordinatefile='ShapefileBoundaries/lmes_64.shp'
CMIP5file='tos_Omon_CCSM4_rcp85_r1i1p1_200601-210012_regrid.nc'

mydebug=False
doPoints=False
first=True


"""initialize the map:"""
mymap=None
mypolyXY, first, numberOfPolygons = getLMEpolygon(LMEcoordinatefile, mymap, 0,first)
NUM_COLORS=numberOfPolygons
ax, mymap, cm = drawMap(NUM_COLORS)


"""Get the CMIP5 data together with the grid"""
SST,mygrid, mymapgrid = openCMIP5file(CMIP5file,"tos",mymap)

"""For each LME of interest create a polygon of coordinates defining the boundaries"""
for counter in xrange(numberOfPolygons-1):

    mypolyXY,first,numberOfPolygons = getLMEpolygon(LMEcoordinatefile, mymap,counter,first)

    if mypolyXY != None:
        """Find the indices inside the grid that are within the polygon"""
        insideBoolean = plt.mlab.inside_poly(np.c_[mymapgrid[:,0],mymapgrid[:,1]],np.c_[mypolyXY[:,0],mypolyXY[:,1]])
        SST=SST.flatten()
        SST=np.ma.masked_where(SST>50,SST)

        mymapgrid=np.c_[mymapgrid[:,0],mymapgrid[:,1]]
        myaverageSST=np.mean(SST[insideBoolean])

        mycolor=cm(myaverageSST/SST.max())
        scaled_z = (myaverageSST - SST.min()) / SST.ptp()
        colors = plt.cm.coolwarm(scaled_z)

        scatter([p[0] for p in mypolyXY],[p[1] for p in mypolyXY],2)

        p = Polygon(mypolyXY,facecolor=colors,edgecolor='none')
        ax.add_artist(p)

        if doPoints is True:

            for point in xrange(len(insideBoolean)):
                pointX=mymapgrid[insideBoolean[point],0]
                pointY=mymapgrid[insideBoolean[point],1]
                ax.scatter(pointX,pointY,8,color=colors)
                ax.hold(True)


if doPoints is True:
    colorbar()
print "Extracted average values for %s LMEs"%(numberOfPolygons)
plt.savefig('LMEs.png',dpi=300)
plt.show()

附上最终图片。感谢大家的帮助。

干杯,特隆德

【问题讨论】:

  • 你是对的,但这不是我的问题,因为只有在我将代码写入 Stackoverflow 时才会出现名称混淆。在我的程序中,变量一直被称为 mypolyXY。对于那个很抱歉。我认为问题在于经纬度点的顺序不是 Roland Smith 建议的顺序。只是不知道如何解决这个问题。不过感谢您的帮助。 T
  • 看了一会儿,我明白了;你的形状削弱了它的自我,这就是为什么按角度排序不起作用的原因。这些数据的来源是什么?
  • 该 csv 中的数据不是有效的多边形,我怀疑它的来源。所有点都等于这个 shapefile 中的点:lme.noaa.gov/…
  • 是的。 CSV 文件是从包含 LME 多边形的 Shapefile 创建的。定义 Shapefile 多边形的经纬度点按顺序保存到 CSV 文件中。我认为这将使我能够使用 Matplotlib 和 Python 重新创建多边形?
  • @TrondKristiansen:我认为您需要解析 .shp 文件本身。请参阅我的更新答案。

标签: python numpy matplotlib


【解决方案1】:

拥有一个点数组是不够的。您需要知道点的顺序。通常,多边形的点按顺序给出。所以你从第一个点到第二个点画一条线,然后从第二个点到第三个点等等。

如果您的列表不是按顺序排列的,您需要额外的信息才能制作一个按顺序排列的列表。

shapefile(参见documentation)包含一个形状列表,如 Null 形状、Point、PolyLine、Polygon,其变体还包含 Z 和 M(测量)坐标。因此,仅倾倒积分是行不通的。您必须将它们分成不同的形状并渲染您感兴趣的形状。在这种情况下,可能是折线或多边形。有关这些形状的数据格式,请参见上面的链接。请记住,文件的某些部分是 big-endian,而其他部分是 little-endian。真是一团糟。

我建议使用struct 模块来解析二进制.shp 文件,因为再次根据文档,单个多边形的点的,它们形成一个封闭的链(最后一点与第一点相同)。

您可以尝试使用当前坐标列表的另一件事是从一个点开始,然后在列表中进一步查找相同的点。这些相同点之间的一切都应该是一个多边形。这可能不是万无一失的,但看看它能让你走多远。

【讨论】:

    【解决方案2】:

    我建议使用原始的 Shapefile,它的格式适合存储多边形。作为 OGR 的替代方案,您可以使用 Shapely,或将多边形导出到 Wkt 等。

    import ogr
    import matplotlib.path as mpath
    import matplotlib.patches as patches
    import matplotlib.pyplot as plt
    
    ds = ogr.Open('lmes_64.shp')
    lyr = ds.GetLayer(0)
    ft = lyr.GetFeature(38)
    geom = ft.GetGeometryRef()
    ds = None
    
    codes = []
    all_x = []
    all_y = []
    
    if (geom.GetGeometryType() == ogr.wkbPolygon):
      for i in range(geom.GetGeometryCount()):
    
        r = geom.GetGeometryRef(i)
        x = [r.GetX(j) for j in range(r.GetPointCount())]
        y = [r.GetY(j) for j in range(r.GetPointCount())]
    
        codes += [mpath.Path.MOVETO] + (len(x)-1)*[mpath.Path.LINETO]
        all_x += x
        all_y += y
    
    if (geom.GetGeometryType() == ogr.wkbMultiPolygon):
      codes = []
      for i in range(geom.GetGeometryCount()):
        # Read ring geometry and create path
        r = geom.GetGeometryRef(i)
        for part in r:
          x = [part.GetX(j) for j in range(part.GetPointCount())]
          y = [part.GetY(j) for j in range(part.GetPointCount())]
          # skip boundary between individual rings
          codes += [mpath.Path.MOVETO] + (len(x)-1)*[mpath.Path.LINETO]
          all_x += x
          all_y += y
    
    carib_path = mpath.Path(np.column_stack((all_x,all_y)), codes)    
    carib_patch = patches.PathPatch(carib_path, facecolor='orange', lw=2)
    
    poly1 = patches.Polygon([[-80,20],[-75,20],[-75,15],[-80,15],[-80,20]], zorder=5, fc='none', lw=3)
    poly2 = patches.Polygon([[-65,25],[-60,25],[-60,20],[-65,20],[-65,25]], zorder=5, fc='none', lw=3)
    
    
    fig, ax = plt.subplots(1,1)
    
    for poly in [poly1, poly2]:
        if carib_path.intersects_path(poly.get_path()):
            poly.set_edgecolor('g')
        else:
            poly.set_edgecolor('r')
    
        ax.add_patch(poly)
    
    ax.add_patch(carib_patch)
    ax.autoscale_view()
    

    如果您想要真正简单的 Shapefile 处理,还可以查看 Fiona(OGR 的包装器)。

    【讨论】:

    • 这是一个很好的建议。我测试了它,它似乎工作。但是,我仍然需要访问 LME 的多边形,因为我需要使用 plt.mlab.inside_poly 例程在 LME 中提取数据。我可以在你的代码中定义每个多边形吗: mpoly=np.empty((len(all_x),2)) mypoly=np.array((all_x,all_y), dtype=float)?
    • 路径有一个可以使用的.intersects_path() 方法。如果您想测试与多边形的交叉点,请执行以下操作:mypath.intersects_path(mypoly.get_path(),值 1 表示交叉点。
    • 我已经编辑了我的示例以包含一些交叉点处理。
    • 我的 shapefile 又遇到了一个问题:它包含多面体。您对如何从 Shapefiles 中的多面体创建多边形有任何建议吗?谢谢你的帮助!特隆德
    • 其实很简单。但这就是我检查ogr.wkbPolygon 的原因,因为对于OGR 界面,它们的处理方式略有不同。多面体的GeometryRef() 包含更多部分。我会更新我的答案以包含它。
    【解决方案3】:

    这里有一篇关于 shapefile 和底图的博文:http://www.geophysique.be/2013/02/12/matplotlib-basemap-tutorial-10-shapefiles-unleached-continued/

    如果您想尝试一下,cartopy 也可能是一个选择。从 shapefile 绘制数据被设计得相当容易:

    import matplotlib.pyplot as plt
    
    import cartopy.crs as ccrs
    import cartopy.io.shapereader as shpreader
    
    # pick a shapefile - Cartopy makes it easy to access Natural Earth
    # shapefiles, but it could be anything
    shapename = 'admin_1_states_provinces_lakes_shp'
    states_shp = shpreader.natural_earth(resolution='110m',
                                          category='cultural', name=shapename)
    

    .

    # states_shp is just a filename to a shapefile
    >>> print states_shp
    /Users/pelson/.local/share/cartopy/shapefiles/natural_earth/cultural/110m_admin_1_states_provinces_lakes_shp.shp
    

    .

    # Create the mpl axes of a PlateCarree map
    ax = plt.axes(projection=ccrs.PlateCarree())
    
    # Read the shapes from the shapefile into a list of shapely geometries.
    geoms = shpreader.Reader(states_shp).geometries()
    
    # Add the shapes in the shapefile to the axes
    ax.add_geometries(geoms, ccrs.PlateCarree(),
                      facecolor='coral', edgecolor='black')
    
    plt.show()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-04
      • 2017-08-27
      • 2013-01-23
      • 1970-01-01
      • 2019-08-10
      相关资源
      最近更新 更多