ArcGIS Masaüstünde Yalnızca Sınır Noktaları Kullanarak Nokta Katmanından Karmaşık Çokgen Oluşturma


11

Çokgen kenarlarını tanımlamak için karmaşık bir kılavuzdan sınır noktalarını kullanarak bir nokta katmanı bir çokgene dönüştürmek gerekiyor.

Bu ArcGIS Desktop 10.3 bir ModelBuilder çerçeveye dahil etmek gerekir. Çok sayıda gelen veri nedeniyle işlemin tekrarlanması (mümkünse) gerekecektir.

Nokta katmanı bir nehir segmenti üzerinde ızgaralıdır ve nehirlerin sınır noktalarını belirlemem ve bunları nehir segmentinin bir çokgen katmanını oluşturmak için birleştirmem gerekiyor.

Dışbükey gövde nehirlerin nasıl kıvrıldığı ile çalışmıyor gibi görünüyor, dışbükey bir gövde gibi temiz bir sınıra ihtiyacım var. Sadece sınır noktaları için katmanlarım var, ama çokgene ulaşmak için onları nasıl bağlayacağımı bilmiyorum.

Teorik süreç örneği


1
Ne istediğiniz bir olduğu içbükey gövde dışbükey zarf aksine, ArcGIS yerel olarak mevcut değildir. Nokta aralığınız oldukça küçükse, Öklid Mesafesi> Yeniden Sınıflandır> Genişlet> Rasterden Çokgene veya Toplama Noktalarını kullanabilirsiniz .
dmahr

2
Noktaları kullanarak TIN oluşturun. TIN'i (yalnızca sınır dışında) makul bir mesafe kullanarak ayırın. TIN'i üçgenlere dönüştürün ve doğru olmadığını düşündüğünüzü kaldırın. Üçgenleri birleştirin.
FelixIP

Teşekkür ederim, bunlar üzerinde çalışmaya ve onları test etmeye başladım.
A.Wittenberg

Bu web sitesi noktalardan şekiller çıkarmada yararlı olan Python kütüphanelerini tartışıyor gibi görünüyor. blog.thehumangeo.com/2014/05/12/drawing-boundaries-in-python Kodu denemedim, bu yüzden tüm kütüphanelerin Python kurulumu ile gelip gelmediğini bilmiyorum. Her neyse, umut verici görünüyor.
Richard Fairhurst

Felix'in yönteminde genişleme, bence: mappingcenter.esri.com/index.cfm?fa=ask.answers&q=1661 Ayrıca ET GeoWizards'ın bunun için bir aracı var. Ben COncave Hull Tahmincisi birkaç cevap bağlantılı olduğunu, ancak bağlantıların hepsi bozuk (Esri'nin en son web değişikliği sonra varsayalım) ve güncellenmiş bir bağlantı bulamıyorum.
Chris W

Yanıtlar:


22

Bu GeoNet iş parçacığı , dışbükey / içbükey gövdeler ve birçok resim, bağlantı ve ekler hakkında uzun bir tartışma yaptı. Ne yazık ki, Esri'nin eski forumu ve galerisi Geonet ile değiştirildiğinde veya kaldırıldığında tüm resimler, bağlantılar ve ekler kırıldı.

İşte Esri'den Bruce Harold'ın yarattığı İçbükey Hull Tahmincisi senaryomdaki varyasyonlarım. Sanırım versiyonumda bazı iyileştirmeler yapıldı.

Burada sıkıştırılmış araç dosyasını eklemek için bir yol görmüyorum, bu yüzden burada aracın sıkıştırılmış sürümü ile bir blog yazısı oluşturduk . İşte arayüzün bir resmi.

Konveks Gövde Kasa Tipi Arabirimi

İşte bazı çıktıların bir resmi (Bu resim için k faktörünü hatırlamıyorum). k, her tekne sınır noktası için aranan minimum komşu nokta sayısını belirtir. Daha yüksek k değerleri daha yumuşak sınırlara neden olur. Giriş verilerinin dengesiz dağıldığı durumlarda, hiçbir k değeri kapalı bir gövdeye neden olamaz.

misal

İşte kod:

# Author: ESRI
# Date:   August 2010
#
# Purpose: This script creates a concave hull polygon FC using a k-nearest neighbours approach
#          modified from that of A. Moreira and M. Y. Santos, University of Minho, Portugal.
#          It identifies a polygon which is the region occupied by an arbitrary set of points
#          by considering at least "k" nearest neighbouring points (30 >= k >= 3) amongst the set.
#          If input points have uneven spatial density then any value of k may not connect the
#          point "clusters" and outliers will be excluded from the polygon.  Pre-processing into
#          selection sets identifying clusters will allow finding hulls one at a time.  If the
#          found polygon does not enclose the input point features, higher values of k are tried
#          up to a maximum of 30.
#
# Author: Richard Fairhurst
# Date:   February 2012
#
# Update:  The script was enhanced by Richard Fairhurst to include an optional case field parameter.
#          The case field can be any numeric, string, or date field in the point input and is
#          used to sort the points and generate separate polygons for each case value in the output.
#          If the Case field is left blank the script will work on all input points as it did
#          in the original script.
#
#          A field named "POINT_CNT" is added to the output feature(s) to indicate the number of
#          unique point locations used to create the output polygon(s).
#
#          A field named "ENCLOSED" is added to the output feature(s) to indicates if all of the
#          input points were enclosed by the output polygon(s). An ENCLOSED value of 1 means all
#          points were enclosed. When the ENCLOSED value is 0 and Area and Perimeter are greater
#          than 0, either all points are touching the hull boundary or one or more outlier points
#          have been excluded from the output hull. Use selection sets or preprocess input data
#          to find enclosing hulls. When a feature with an ENCLOSED value of 0 and Empty or Null
#          geometry is created (Area and Perimeter are either 0 or Null) insufficient input points
#          were provided to create an actual polygon.
try:

    import arcpy
    import itertools
    import math
    import os
    import sys
    import traceback
    import string

    arcpy.overwriteOutput = True

    #Functions that consolidate reuable actions
    #

    #Function to return an OID list for k nearest eligible neighbours of a feature
    def kNeighbours(k,oid,pDict,excludeList=[]):
        hypotList = [math.hypot(pDict[oid][0]-pDict[id][0],pDict[oid][5]-pDict[id][6]) for id in pDict.keys() if id <> oid and id not in excludeList]
        hypotList.sort()
        hypotList = hypotList[0:k]
        oidList = [id for id in pDict.keys() if math.hypot(pDict[oid][0]-pDict[id][0],pDict[oid][7]-pDict[id][8]) in hypotList and id <> oid and id not in excludeList]
        return oidList

    #Function to rotate a point about another point, returning a list [X,Y]
    def RotateXY(x,y,xc=0,yc=0,angle=0):
        x = x - xc
        y = y - yc
        xr = (x * math.cos(angle)) - (y * math.sin(angle)) + xc
        yr = (x * math.sin(angle)) + (y * math.cos(angle)) + yc
        return [xr,yr]

    #Function finding the feature OID at the rightmost angle from an origin OID, with respect to an input angle
    def Rightmost(oid,angle,pDict,oidList):
        origxyList = [pDict[id] for id in pDict.keys() if id in oidList]
        rotxyList = []
        for p in range(len(origxyList)):
            rotxyList.append(RotateXY(origxyList[p][0],origxyList[p][9],pDict[oid][0],pDict[oid][10],angle))
        minATAN = min([math.atan2((xy[1]-pDict[oid][11]),(xy[0]-pDict[oid][0])) for xy in rotxyList])
        rightmostIndex = rotxyList.index([xy for xy in rotxyList if math.atan2((xy[1]-pDict[oid][1]),(xy[0]-pDict[oid][0])) == minATAN][0])
        return oidList[rightmostIndex]

    #Function to detect single-part polyline self-intersection    
    def selfIntersects(polyline):
        lList = []
        selfIntersects = False
        for n in range(0, len(line.getPart(0))-1):
            lList.append(arcpy.Polyline(arcpy.Array([line.getPart(0)[n],line.getPart(0)[n+1]])))
        for pair in itertools.product(lList, repeat=2): 
            if pair[0].crosses(pair[1]):
                selfIntersects = True
                break
        return selfIntersects

    #Function to construct the Hull
    def createHull(pDict, outCaseField, lastValue, kStart, dictCount, includeNull):
        #Value of k must result in enclosing all data points; create condition flag
        enclosesPoints = False
        notNullGeometry = False
        k = kStart

        if dictCount > 1:
            pList = [arcpy.Point(xy[0],xy[1]) for xy in pDict.values()]
            mPoint = arcpy.Multipoint(arcpy.Array(pList),sR)
            minY = min([xy[1] for xy in pDict.values()])


            while not enclosesPoints and k <= 30:
                arcpy.AddMessage("Finding hull for k = " + str(k))
                #Find start point (lowest Y value)
                startOID = [id for id in pDict.keys() if pDict[id][1] == minY][0]
                #Select the next point (rightmost turn from horizontal, from start point)
                kOIDList = kNeighbours(k,startOID,pDict,[])
                minATAN = min([math.atan2(pDict[id][14]-pDict[startOID][15],pDict[id][0]-pDict[startOID][0]) for id in kOIDList])
                nextOID = [id for id in kOIDList if math.atan2(pDict[id][1]-pDict[startOID][1],pDict[id][0]-pDict[startOID][0]) == minATAN][0]
                #Initialise the boundary array
                bArray = arcpy.Array(arcpy.Point(pDict[startOID][0],pDict[startOID][18]))
                bArray.add(arcpy.Point(pDict[nextOID][0],pDict[nextOID][19]))
                #Initialise current segment lists
                currentOID = nextOID
                prevOID = startOID
                #Initialise list to be excluded from candidate consideration (start point handled additionally later)
                excludeList = [startOID,nextOID]
                #Build the boundary array - taking the closest rightmost point that does not cause a self-intersection.
                steps = 2
                while currentOID <> startOID and len(pDict) <> len(excludeList):
                    try:
                        angle = math.atan2((pDict[currentOID][20]- pDict[prevOID][21]),(pDict[currentOID][0]- pDict[prevOID][0]))
                        oidList = kNeighbours(k,currentOID,pDict,excludeList)
                        nextOID = Rightmost(currentOID,0-angle,pDict,oidList)
                        pcArray = arcpy.Array([arcpy.Point(pDict[currentOID][0],pDict[currentOID][22]),\
                                            arcpy.Point(pDict[nextOID][0],pDict[nextOID][23])])
                        while arcpy.Polyline(bArray,sR).crosses(arcpy.Polyline(pcArray,sR)) and len(oidList) > 0:
                            #arcpy.AddMessage("Rightmost point from " + str(currentOID) + " : " + str(nextOID) + " causes self intersection - selecting again")
                            excludeList.append(nextOID)
                            oidList.remove(nextOID)
                            oidList = kNeighbours(k,currentOID,pDict,excludeList)
                            if len(oidList) > 0:
                                nextOID = Rightmost(currentOID,0-angle,pDict,oidList)
                                #arcpy.AddMessage("nextOID candidate: " + str(nextOID))
                                pcArray = arcpy.Array([arcpy.Point(pDict[currentOID][0],pDict[currentOID][24]),\
                                                    arcpy.Point(pDict[nextOID][0],pDict[nextOID][25])])
                        bArray.add(arcpy.Point(pDict[nextOID][0],pDict[nextOID][26]))
                        prevOID = currentOID
                        currentOID = nextOID
                        excludeList.append(currentOID)
                        #arcpy.AddMessage("CurrentOID = " + str(currentOID))
                        steps+=1
                        if steps == 4:
                            excludeList.remove(startOID)
                    except ValueError:
                        arcpy.AddMessage("Zero reachable nearest neighbours at " + str(pDict[currentOID]) + " , expanding search")
                        break
                #Close the boundary and test for enclosure
                bArray.add(arcpy.Point(pDict[startOID][0],pDict[startOID][27]))
                pPoly = arcpy.Polygon(bArray,sR)
                if pPoly.length == 0:
                    break
                else:
                    notNullGeometry = True
                if mPoint.within(arcpy.Polygon(bArray,sR)):
                    enclosesPoints = True
                else:
                    arcpy.AddMessage("Hull does not enclose data, incrementing k")
                    k+=1
            #
            if not mPoint.within(arcpy.Polygon(bArray,sR)):
                arcpy.AddWarning("Hull does not enclose data - probable cause is outlier points")

        #Insert the Polygons
        if (notNullGeometry and includeNull == False) or includeNull:
            rows = arcpy.InsertCursor(outFC)
            row = rows.newRow()
            if outCaseField > " " :
                row.setValue(outCaseField, lastValue)
            row.setValue("POINT_CNT", dictCount)
            if notNullGeometry:
                row.shape = arcpy.Polygon(bArray,sR)
                row.setValue("ENCLOSED", enclosesPoints)
            else:
                row.setValue("ENCLOSED", -1)
            rows.insertRow(row)
            del row
            del rows
        elif outCaseField > " ":
            arcpy.AddMessage("\nExcluded Null Geometry for case value " + str(lastValue) + "!")
        else:
            arcpy.AddMessage("\nExcluded Null Geometry!")

    # Main Body of the program.
    #
    #

    #Get the input feature class or layer
    inPoints = arcpy.GetParameterAsText(0)
    inDesc = arcpy.Describe(inPoints)
    inPath = os.path.dirname(inDesc.CatalogPath)
    sR = inDesc.spatialReference

    #Get k
    k = arcpy.GetParameter(1)
    kStart = k

    #Get output Feature Class
    outFC = arcpy.GetParameterAsText(2)
    outPath = os.path.dirname(outFC)
    outName = os.path.basename(outFC)

    #Get case field and ensure it is valid
    caseField = arcpy.GetParameterAsText(3)
    if caseField > " ":
        fields = inDesc.fields
        for field in fields:
            # Check the case field type
            if field.name == caseField:
                caseFieldType = field.type
                if caseFieldType not in ["SmallInteger", "Integer", "Single", "Double", "String", "Date"]:
                    arcpy.AddMessage("\nThe Case Field named " + caseField + " is not a valid case field type!  The Case Field will be ignored!\n")
                    caseField = " "
                else:
                    if caseFieldType in ["SmallInteger", "Integer", "Single", "Double"]:
                        caseFieldLength = 0
                        caseFieldScale = field.scale
                        caseFieldPrecision = field.precision
                    elif caseFieldType == "String":
                        caseFieldLength = field.length
                        caseFieldScale = 0
                        caseFieldPrecision = 0
                    else:
                        caseFieldLength = 0
                        caseFieldScale = 0
                        caseFieldPrecision = 0

    #Define an output case field name that is compliant with the output feature class
    outCaseField = str.upper(str(caseField))
    if outCaseField == "ENCLOSED":
        outCaseField = "ENCLOSED1"
    if outCaseField == "POINT_CNT":
        outCaseField = "POINT_CNT1"
    if outFC.split(".")[-1] in ("shp","dbf"):
        outCaseField = outCaseField[0,10] #field names in the output are limited to 10 charaters!

    #Get Include Null Geometry Feature flag
    if arcpy.GetParameterAsText(4) == "true":
        includeNull = True
    else:
        includeNull = False

    #Some housekeeping
    inDesc = arcpy.Describe(inPoints)
    sR = inDesc.spatialReference
    arcpy.env.OutputCoordinateSystem = sR
    oidName = str(inDesc.OIDFieldName)
    if inDesc.dataType == "FeatureClass":
        inPoints = arcpy.MakeFeatureLayer_management(inPoints)

    #Create the output
    arcpy.AddMessage("\nCreating Feature Class...")
    outFC = arcpy.CreateFeatureclass_management(outPath,outName,"POLYGON","#","#","#",sR).getOutput(0)
    if caseField > " ":
        if caseFieldType in ["SmallInteger", "Integer", "Single", "Double"]:
            arcpy.AddField_management(outFC, outCaseField, caseFieldType, str(caseFieldScale), str(caseFieldPrecision))
        elif caseFieldType == "String":
            arcpy.AddField_management(outFC, outCaseField, caseFieldType, "", "", str(caseFieldLength))
        else:
            arcpy.AddField_management(outFC, outCaseField, caseFieldType)
    arcpy.AddField_management(outFC, "POINT_CNT", "Long")
    arcpy.AddField_management(outFC, "ENCLOSED", "SmallInteger")

    #Build required data structures
    arcpy.AddMessage("\nCreating data structures...")
    rowCount = 0
    caseCount = 0
    dictCount = 0
    pDict = {} #dictionary keyed on oid with [X,Y] list values, no duplicate points
    if caseField > " ":
        for p in arcpy.SearchCursor(inPoints, "", "", "", caseField + " ASCENDING"):
            rowCount += 1
            if rowCount == 1:
                #Initialize lastValue variable when processing the first record.
                lastValue = p.getValue(caseField)
            if lastValue == p.getValue(caseField):
                #Continue processing the current point subset.
                if [p.shape.firstPoint.X,p.shape.firstPoint.Y] not in pDict.values():
                    pDict[p.getValue(inDesc.OIDFieldName)] = [p.shape.firstPoint.X,p.shape.firstPoint.Y]
                    dictCount += 1
            else:
                #Create a hull prior to processing the next case field subset.
                createHull(pDict, outCaseField, lastValue, kStart, dictCount, includeNull)
                if outCaseField > " ":
                    caseCount += 1
                #Reset variables for processing the next point subset.
                pDict = {}
                pDict[p.getValue(inDesc.OIDFieldName)] = [p.shape.firstPoint.X,p.shape.firstPoint.Y]
                lastValue = p.getValue(caseField)
                dictCount = 1
    else:
        for p in arcpy.SearchCursor(inPoints):
            rowCount += 1
            if [p.shape.firstPoint.X,p.shape.firstPoint.Y] not in pDict.values():
                pDict[p.getValue(inDesc.OIDFieldName)] = [p.shape.firstPoint.X,p.shape.firstPoint.Y]
                dictCount += 1
                lastValue = 0
    #Final create hull call and wrap up of the program's massaging
    createHull(pDict, outCaseField, lastValue, kStart, dictCount, includeNull)
    if outCaseField > " ":
        caseCount += 1
    arcpy.AddMessage("\n" + str(rowCount) + " points processed.  " + str(caseCount) + " case value(s) processed.")
    if caseField == " " and arcpy.GetParameterAsText(3) > " ":
        arcpy.AddMessage("\nThe Case Field named " + arcpy.GetParameterAsText(3) + " was not a valid field type and was ignored!")
    arcpy.AddMessage("\nFinished")


#Error handling    
except:
    tb = sys.exc_info()[2]
    tbinfo = traceback.format_tb(tb)[0]
    pymsg = "PYTHON ERRORS:\nTraceback Info:\n" + tbinfo + "\nError Info:\n    " + \
            str(sys.exc_type)+ ": " + str(sys.exc_value) + "\n"
    arcpy.AddError(pymsg)

    msgs = "GP ERRORS:\n" + arcpy.GetMessages(2) + "\n"
    arcpy.AddError(msgs)

Üç Altbölüm için bir dizi adres noktasında işlediğim resimler. Karşılaştırma için orijinal parseller gösterilir. Bu takım çalışması için başlangıç ​​k faktörü 3'e ayarlandı, ancak araç her bir çokgeni oluşturmadan önce her noktayı en az 6 ak faktöre ayarlandı (bunlardan biri için 9 ak faktörü kullanıldı). Araç, yeni gövde özelliği sınıfını ve 3 gövdenin tümünü 35 saniyenin altında yarattı. Gövdenin iç kısmını dolduran düzenli olarak dağılmış noktaların varlığı, sadece anahattı tanımlaması gereken nokta kümesini kullanmaktan daha doğru bir gövde anahattı oluşturmaya yardımcı olur.

Orijinal Parseller ve Adres Noktaları

Adres Noktalarından Oluşan Konkav Gövde

İçbükey Gövdelerin Orijinal Parsellere Yerleştirilmesi


Güncellenmiş / geliştirilmiş sürüm için teşekkürler! ArcGIS içbükey tekneler için en yüksek oyu alan soruyu burada arayabilir ve yanıtınızı orada da gönderebilirsiniz. Daha önceki bir yorumda bahsettiğim gibi, birkaç soru eski kopuk bağlantının ve bu cevabın yerine geçmenin faydalı olacağını belirtiyor. Alternatif olarak siz (veya birileri) bu sorular hakkında yorum yapabilir ve bunları bu soruya bağlayabilirsiniz.
Chris W

Bu mükemmel! Ama başka bir sorum daha var. Soruda belirtildiği gibi nehir sistemimi takip ederek, bu aracın nehrin ortasında atlamak istediğiniz bir adayı hesaba katmanın bir yolu var mı?
A.Wittenberg

Hayır, içinde bir delik bulunan bir gövde oluşturmanın bir yolu yok. Deliği ayrı ayrı çizmenin yanı sıra, bir delik olarak tutmak istediğiniz bölgeyi doldurmak için noktalar ekleyebilir ve bunlara bir "delik" niteliği atayabilirsiniz (diğer deliklerin diğer ilgisiz deliklerle birleşmemesi için benzersiz olması gerekir). Daha sonra deliği ayrı bir çokgen olarak tanımlamak için bir gövde oluşturulacaktır. Nehirleri ve delikleri aynı anda yaratabilirsiniz. Ardından katmanı kopyalayın ve kopyayı yalnızca delik çokgenlerini göstermek için bir tanım sorgusu ile atayın. Ardından bu delikleri tüm katmana karşı Sil özellikleri olarak kullanın.
Richard Fairhurst
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.