Tạo Đa giác phức tạp từ Lớp điểm chỉ sử dụng Điểm ranh giới trong Màn hình ArcGIS


11

Tôi cần chuyển đổi một lớp điểm thành đa giác, sử dụng các điểm biên từ một lưới phức tạp để xác định các cạnh của đa giác.

Tôi cần kết hợp điều này vào khung ModelBuilder trong ArcGIS Desktop 10.3. Lặp lại quá trình sẽ được yêu cầu (nếu có thể) vì có nhiều dữ liệu đến.

Lớp điểm được đặt trên một đoạn sông và tôi cần xác định các điểm ranh giới của sông và kết nối chúng để tạo ra một lớp đa giác của đoạn sông.

Vỏ lồi dường như không hoạt động với cách các con sông uốn khúc, tôi cần một ranh giới chặt chẽ sạch sẽ, không phải là một ngăn chặn như thân tàu lồi. Tôi có các lớp chỉ cho các điểm biên, nhưng tôi không biết cách kết nối chúng để đến đa giác.

Ví dụ về quá trình lý thuyết


1
Những gì bạn muốn là một thân tàu lõm , vốn không có sẵn trong ArcGIS, không giống như thân tàu lồi. Nếu khoảng cách điểm của bạn khá nhỏ, bạn có thể sử dụng Khoảng cách Euclide> Phân loại lại> Mở rộng> Raster thành Đa giác hoặc Điểm tổng hợp .
dmahr

2
Tạo TIN bằng các điểm. Phân định TIN (chỉ giới hạn ngoài) bằng khoảng cách hợp lý. Chuyển đổi TIN thành hình tam giác và loại bỏ những cái mà bạn cho là không chính xác. Hợp nhất các hình tam giác lại với nhau.
FelixIP

Cảm ơn bạn, tôi đã bắt đầu làm việc thông qua những điều này và thử nghiệm chúng.
A.Wittenberg

Trang web này dường như thảo luận về các thư viện Python hữu ích trong việc trích xuất các hình dạng từ các điểm. blog.thehumangeo.com/2014/05/12/drawing-boundaries-in-python Tôi chưa thử mã, vì vậy tôi không biết liệu tất cả các thư viện có cài đặt Python hay không. Dù sao, nó có vẻ đầy hứa hẹn.
Richard Fairhurst

Mở rộng theo phương pháp của Felix, tôi nghĩ: mapscenter.esri.com/index.cfm?fa=ask.answers&q=1661 Ngoài ra, ET GeoWizards có một công cụ cho việc này. Tôi lưu ý Công cụ ước tính thân máy được liên kết trong một số câu trả lời, nhưng tất cả các liên kết đều bị hỏng (tôi giả sử sau khi cải tổ web mới nhất của Esri) và tôi không thể tìm thấy liên kết được cập nhật.
Chris W

Câu trả lời:


21

Chủ đề GeoNet này đã có một cuộc thảo luận dài về chủ đề vỏ lồi / lõm và nhiều hình ảnh, liên kết và tệp đính kèm. Thật không may, tất cả các hình ảnh, liên kết và tệp đính kèm đã bị hỏng khi diễn đàn và thư viện cũ cho Esri được thay thế bởi Geonet hoặc gỡ xuống.

Dưới đây là các biến thể của tôi về kịch bản Công cụ Ước tính Lõm mà Bruce Harold của Esri đã tạo. Tôi nghĩ rằng phiên bản của tôi đã thực hiện một số cải tiến.

Tôi không thấy cách đính kèm tệp công cụ nén ở đây, vì vậy tôi đã tạo một bài đăng blog với phiên bản nén của công cụ ở đây . Dưới đây là hình ảnh của giao diện.

Convex Hull theo trường hợp giao diện

Đây là hình ảnh của một số kết quả đầu ra (Tôi không nhớ yếu tố k cho hình ảnh này). k chỉ ra số lượng điểm lân cận tối thiểu được tìm kiếm cho mỗi điểm ranh giới thân tàu. Giá trị cao hơn của k dẫn đến ranh giới mịn hơn. Trong trường hợp dữ liệu đầu vào phân tán không đều, giá trị của k có thể dẫn đến vỏ tàu kèm theo.

thí dụ

Đây là mã:

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

Dưới đây là những hình ảnh tôi vừa xử lý trên một tập hợp các điểm địa chỉ cho ba Phân mục. Để so sánh, các bưu kiện gốc được hiển thị. Hệ số k bắt đầu cho lần chạy công cụ này được đặt thành 3, nhưng công cụ đã lặp lại mỗi điểm được đặt thành ít nhất là hệ số 6 trước khi tạo mỗi đa giác (ak hệ số 9 được sử dụng cho một trong số chúng). Công cụ này đã tạo ra lớp tính năng thân tàu mới và cả 3 thân tàu trong vòng dưới 35 giây. Sự hiện diện của các điểm được phân phối thường xuyên lấp đầy phần bên trong thân tàu thực sự giúp tạo ra một phác thảo thân tàu chính xác hơn so với việc chỉ sử dụng tập hợp các điểm phải xác định phác thảo.

Bưu kiện gốc và Điểm địa chỉ

Thân lõm được tạo từ điểm địa chỉ

Lớp phủ của thân lõm trên bưu kiện gốc


Cảm ơn phiên bản cập nhật / cải tiến! Bạn có thể muốn tìm kiếm câu hỏi được bình chọn cao nhất cho thân tàu lõm ArcGIS ở đây và cũng gửi câu trả lời của bạn ở đó. Như tôi đã đề cập trong một bình luận trước đó, một số câu hỏi tham khảo rằng liên kết bị hỏng cũ và có câu trả lời này thay thế sẽ hữu ích. Ngoài ra, bạn (hoặc ai đó) có thể nhận xét về những câu hỏi đó và liên kết chúng với câu hỏi này.
Chris W

Thật tuyệt vời! Nhưng tôi có một câu hỏi khác. Theo hệ thống sông của tôi như được đặt ra trong câu hỏi, công cụ này có cách nào để giải thích cho một hòn đảo ở giữa một con sông mà bạn muốn bỏ qua không?
A.Wittenberg

Không, nó không có cách tạo thành một thân tàu có lỗ. Ngoài việc vẽ lỗ riêng biệt, bạn có thể thêm các điểm để lấp đầy khu vực bạn muốn giữ làm lỗ và gán chúng với thuộc tính "lỗ" (mỗi lỗ sẽ phải là duy nhất để tránh liên kết với các lỗ không liên quan khác). Một thân tàu sau đó sẽ được hình thành để xác định lỗ là một đa giác riêng biệt. Bạn có thể tạo ra các con sông và lỗ hổng cùng một lúc. Sau đó sao chép lớp và gán bản sao với truy vấn định nghĩa để chỉ hiển thị đa giác lỗ. Sau đó sử dụng các lỗ đó như các tính năng Xóa đối với toàn bộ lớp.
Richard Fairhurst
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.