การสร้างรูปหลายเหลี่ยมที่ซับซ้อนจากจุดของเลเยอร์โดยใช้เฉพาะจุดขอบเขตในเดสก์ท็อป ArcGIS


11

ฉันต้องแปลงเลเยอร์จุดเป็นรูปหลายเหลี่ยมโดยใช้จุดขอบเขตจากตารางที่ซับซ้อนเพื่อกำหนดขอบของรูปหลายเหลี่ยม

ฉันต้องรวมสิ่งนี้เข้ากับกรอบ ModelBuilder ใน ArcGIS Desktop 10.3 การวนซ้ำกระบวนการจะต้อง (ถ้าเป็นไปได้) เนื่องจากมีข้อมูลเข้ามามากมาย

เลเยอร์จุดถูก gridded ผ่านส่วนแม่น้ำและฉันจำเป็นต้องกำหนดจุดขอบเขตแม่น้ำและเชื่อมต่อพวกเขาเพื่อสร้างชั้นรูปหลายเหลี่ยมของส่วนแม่น้ำ

ตัวเรือนูนดูเหมือนจะไม่ทำงานกับวิธีที่แม่น้ำคดเคี้ยวฉันต้องการเขตแดนที่สะอาดและไม่ต้องมีการกักกันเหมือนตัวเรือนูน ฉันมีเลเยอร์เพียงแค่จุดขอบเขต แต่ฉันไม่รู้วิธีเชื่อมต่อพวกเขาเพื่อไปที่รูปหลายเหลี่ยม

ตัวอย่างของกระบวนการทางทฤษฎี


1
สิ่งที่คุณต้องการคือลำเรือเว้าซึ่งไม่สามารถใช้งานได้ใน ArcGIS ซึ่งแตกต่างจากลำตัวนูน หากระยะห่างระหว่างจุดของคุณมีขนาดเล็กค่อนข้างคุณสามารถใช้แบบยุคลิดระยะทาง> จัดกลุ่มใหม่> ขยาย> Raster ไปเป็นรูปหลายเหลี่ยมหรือคะแนนรวม
dmahr

2
สร้าง TIN โดยใช้คะแนน วาดเส้น TIN (นอกขอบเขตเท่านั้น) โดยใช้ระยะทางที่เหมาะสม แปลง TIN เป็นรูปสามเหลี่ยมแล้วนำออกที่คุณคิดว่าไม่ถูกต้อง รวมสามเหลี่ยมเข้าด้วยกัน
FelixIP

ขอบคุณฉันเริ่มทำงานผ่านสิ่งเหล่านี้และทดสอบพวกเขา
A.Wittenberg

เว็บไซต์นี้ดูเหมือนจะพูดคุยเกี่ยวกับห้องสมุด Python ที่มีประโยชน์ในการดึงรูปร่างออกมา blog.thehumangeo.com/2014/05/12/drawing-boundaries-in-python ฉันยังไม่ได้ลองใช้รหัสดังนั้นฉันไม่ทราบว่าไลบรารีทั้งหมดมาพร้อมกับการติดตั้ง Python หรือไม่ อย่างไรก็ตามมันดูมีแนวโน้ม
Richard Fairhurst

การขยายตัวของวิธีของเฟลิกซ์ฉันคิดว่า: mappingcenter.esri.com/index.cfm?fa=ask.answers&q=1661นอกจากนี้ET GeoWizardsยังมีเครื่องมือสำหรับสิ่งนี้ ฉันทราบว่า COncave Hull Estimator เชื่อมโยงอยู่หลายคำตอบ แต่ลิงก์ทั้งหมดใช้งานไม่ได้ (ฉันถือว่าหลังจากการสับเปลี่ยนเว็บล่าสุดของ Esri) และฉันไม่สามารถหาลิงค์ที่อัพเดตได้
Chris W

คำตอบ:


21

ด้าย GeoNet นี้มีการอภิปรายที่ยาวนานในเรื่องของเปลือกนูน / เว้าและรูปภาพลิงค์และสิ่งที่แนบมามากมาย น่าเสียดายที่รูปภาพลิงก์และไฟล์แนบทั้งหมดเสียหายเมื่อฟอรัมและแกลเลอรี่เก่าแก่ของ Esri ถูกแทนที่ด้วย Geonet หรือถูกถอดออก

นี่คือความแตกต่างของฉันในสคริปต์ตัวประมาณการเว้าที่บรูซฮาโรลด์แห่ง Esri สร้างขึ้น ฉันคิดว่ารุ่นของฉันทำการปรับปรุงหลายอย่าง

ผมไม่ได้เห็นวิธีการแนบไฟล์เครื่องมือซิปที่นี่เพื่อให้ฉันได้สร้างบล็อกโพสต์กับรุ่นซิปของเครื่องมือที่นี่ นี่คือรูปภาพของอินเทอร์เฟซ

Hull นูนโดย Case Interface

นี่คือรูปภาพของเอาท์พุทบางอย่าง (ฉันจำไม่ได้ว่าปัจจัย k สำหรับภาพนี้) k บ่งชี้จำนวนจุดเพื่อนบ้านขั้นต่ำที่ค้นหาจุดขอบเขตของฮัลล์แต่ละจุด ค่าที่สูงขึ้นของ k ส่งผลให้ขอบเขตราบรื่นขึ้น ในกรณีที่ข้อมูลอินพุตถูกกระจายอย่างไม่สม่ำเสมอค่าของ k อาจส่งผลให้ฮัลล์ล้อมรอบ

ตัวอย่าง

นี่คือรหัส:

# 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)

นี่คือรูปภาพที่ฉันเพิ่งประมวลผลบนชุดของจุดที่อยู่สำหรับสามเขตการปกครอง สำหรับการเปรียบเทียบพัสดุต้นฉบับจะปรากฏขึ้น ปัจจัยเริ่มต้น k สำหรับการเรียกใช้เครื่องมือนี้ถูกตั้งค่าเป็น 3 แต่เครื่องมือทำซ้ำแต่ละจุดที่ตั้งค่าไว้ที่อย่างน้อยปัจจัย ak เป็น 6 ก่อนที่จะสร้างรูปหลายเหลี่ยมแต่ละรูป เครื่องมือสร้างคลาสคุณลักษณะฮัลล์ใหม่และทั้ง 3 ฮัลล์ภายในไม่ถึง 35 วินาที การปรากฏตัวของจุดกระจายที่สม่ำเสมออยู่ภายในตัวถังนั้นช่วยในการสร้างโครงร่างลำเรือที่แม่นยำกว่าการใช้ชุดจุดที่ควรกำหนดโครงร่าง

หีบห่อและที่อยู่เดิม

ลำเรือเว้าสร้างขึ้นจากจุดที่อยู่

การซ้อนทับของเว้าฮัลล์บนหีบห่อดั้งเดิม


ขอบคุณสำหรับรุ่นที่ปรับปรุง / ปรับปรุง! คุณอาจต้องการค้นหาคำถามที่โหวตสูงสุดสำหรับเรือเว้า ArcGIS ที่นี่และโพสต์คำตอบของคุณที่นั่นเช่นกัน ดังที่ฉันได้กล่าวถึงในความคิดเห็นก่อนหน้านี้คำถามหลายข้ออ้างอิงว่าลิงก์ที่ใช้งานไม่ได้เก่าและมีคำตอบนี้เพื่อทดแทนจะมีประโยชน์ อีกทางหนึ่งคุณ (หรือบางคน) สามารถแสดงความคิดเห็นในคำถามเหล่านั้นและเชื่อมโยงพวกเขาเข้ากับคำถามนี้
Chris W

มันยอดเยี่ยมมาก! แต่ฉันมีคำถามอื่น การติดตามระบบแม่น้ำของฉันตามที่คำถามถูกตั้งไว้เครื่องมือนี้มีวิธีที่จะอธิบายถึงเกาะที่อยู่กลางแม่น้ำที่คุณไม่ต้องการหรือไม่
A.Wittenberg

ไม่มันไม่มีวิธีในการสร้างฮัลล์ที่มีรูอยู่ภายใน นอกเหนือจากการวาดหลุมแยกคุณสามารถเพิ่มคะแนนเพื่อเติมพื้นที่ที่คุณต้องการเก็บไว้เป็นหลุมและกำหนดให้พวกเขาด้วยคุณลักษณะ "หลุม" (แต่ละหลุมจะต้องไม่ซ้ำกันเพื่อหลีกเลี่ยงการเข้าร่วมกับหลุมที่ไม่เกี่ยวข้องอื่น ๆ ) จากนั้นฮัลล์จะถูกสร้างขึ้นเพื่อกำหนดหลุมเป็นรูปหลายเหลี่ยมที่แยกต่างหาก คุณสามารถสร้างแม่น้ำและหลุมในเวลาเดียวกัน จากนั้นคัดลอกเลเยอร์และกำหนดสำเนาด้วยคิวรีคำจำกัดความเพื่อแสดงรูปหลายเหลี่ยมรูเท่านั้น จากนั้นใช้รูเหล่านั้นเป็นคุณสมบัติลบกับเลเยอร์ทั้งหมด
Richard Fairhurst
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.