Linee di ritaglio “avide” con poligono


9

Desidero agganciare una serie di polilinee (linee nere nell'immagine sotto) al confine esterno di un poligono. Eventuali vuoti all'interno del poligono devono essere ignorati. La mia uscita ideale sono le linee gialle tratteggiate. Le linee iniziali possono essere o non essere diritte. L'immagine è un esempio semplificato, in realtà il poligono è molto più complesso e ci sono centinaia di linee. Non penso che uno scafo convesso funzionerebbe (ma potrei sbagliarmi). Sono aperto a soluzioni in arcgis, qgis, arcpy, shapely, ecc. La codifica sarebbe preferibilmente in pitone se sono aperta ad altre opzioni se necessario. Arcgis sarebbe anche preferibile per rendere più facile per i miei colleghi condividere lo strumento, ma non è un requisito.

Il meglio che mi viene in mente in questo momento è quello di intersecare una singola linea con il poligono creando un insieme di punti in tutte le intersezioni di confine. Ordina i punti in base alla distanza all'inizio della linea. I punti più lontani e più vicini (FAC) saranno il confine esterno del poligono. Quindi utilizzare i punti FAC per selezionare i vertici corretti dalla linea originale e creare la linea tratteggiata gialla dai punti appropriati. Dovrebbe funzionare ma sembra più complicato del necessario.

Alcuni pensieri aggiuntivi:

  • Le linee sono "abbastanza" lineari che dovrebbe funzionare un semplice calcolo della distanza tra punti, non dovrebbe essere necessario un riferimento lineare.
  • Sarebbe facile in arcpy se ci fosse uno strumento per dividere una linea in un punto ma non riesco a trovarne una.

Qualcuno pensa?

Esempio


+1, problema interessante! Sono curioso di vedere quali soluzioni sono disponibili =)
Joseph

Solo la tua linea di mezzo è difficile da raggiungere: la parte superiore e inferiore provengono da una clip dopo aver riempito tutti i vuoti. Di conseguenza, penso che dovresti focalizzare la tua domanda su questo e restringere il suo ambito a solo ArcPy se questo è il tuo strumento preferito. Puoi sempre chiedere un altro strumento, se questo non fornisce una soluzione.
PolyGeo

le linee attraversano più poligoni?
Emil Brundage,

Emil, supponiamo che le linee possano attraversare più poligoni. Tuttavia, oltre alla geometria, non vi è alcuna differenza tra i poligoni in modo che possano essere dissolti, uniti in una funzione multipart, ecc. Se ciò rende l'algoritmo più semplice. Una linea che attraversa più poligoni sarebbe probabilmente rara e che può essere un caso segnalato da trattare a mano, se necessario.
Mike Bannister,

Qual è il tuo livello di licenza?
Emil Brundage,

Risposte:


4

Voglio aggiungere la mia soluzione pyQGIS, nient'altro.

from PyQt4.QtCore import QVariant
from qgis.analysis import QgsGeometryAnalyzer

# get layers
lines = QgsMapLayerRegistry.instance().mapLayersByName('lines')[0]
clipper = QgsMapLayerRegistry.instance().mapLayersByName('clipper')[0]

# prepare result layer
clipped = QgsVectorLayer('LineString?crs=epsg:4326', 'clipped', 'memory')
clipped.startEditing()
clipped.addAttribute(QgsField('fid', QVariant.Int))
fni = clipped.fieldNameIndex('fid')
clipped.commitChanges()

prov = clipped.dataProvider()
fields = prov.fields()

for line in lines.getFeatures():
    # to increase performance filter possible clippers 
    clippers = clipper.getFeatures(QgsFeatureRequest().setFilterRect(line.geometry().boundingBox()))
    for clip in clippers:
            # split the line
            line1 = line.geometry().splitGeometry(clip.geometry().asPolygon()[0], True)
            feats = []
            # get the split points
            vertices = [QgsPoint(vert[0], vert[1]) for vert in line1[2]]
            for part in line1[1]:
                # for each split part check, if first AND last vertex equal to split points
                if part.vertexAt(0) in vertices and part.vertexAt(len(part.asPolyline())-1) in vertices:
                    # if so create feature and set fid to original line's id
                    feat = QgsFeature(fields)
                    feat.setAttributes([line.id()])
                    feat.setGeometry(part)
                    feats.append(feat)

            prov.addFeatures(feats)

# expose layer
clipped.updateExtents()
QgsMapLayerRegistry.instance().addMapLayers([clipped])

# now dissolve lines having the same value in field fni: here original line's id
diss = QgsGeometryAnalyzer()
diss.dissolve(clipped, 'E:\\clipped.shp', uniqueIdField=fni)

Il mio caso di test - prima del ritaglio: prima della clip

Dopo il ritaglio:

dopo

Per ottenere il set completo di attributi delle linee originali penso che sarebbe meglio unirli con il risultato. Altrimenti devono essere creati nella sezione preparazione e impostati nel ciclo più interno. Ma non ho testato se passano il processo di dissoluzione o se si perdono, perché in linea di principio potrebbero avere valori diversi.


Risposta molto concisa. In che modo le schermate di QGIS assomigliano sempre a QGIS?
Mike Bannister,

3

Sarebbe facile in arcpy se ci fosse uno strumento per dividere una linea in un punto ma non riesco a trovarne una.

Se esegui Integrate con i poligoni e le linee come input, aggiungerà un vertice a ciascuno di essi in cui si intersecano. (Attenzione, poiché Integrate modifica gli input invece di produrre nuovi output.)

Una volta che sei sicuro che ci sono vertici coincidenti, puoi iterare sui vertici della linea e testare per vedere se ognuno tocca l'altra funzione. Dall'elenco ordinato di vertici che toccano, prendi il minimo e il massimo dal set. Quindi, crea due righe per ogni funzione, A: (inizio, ..., min) e B: (max, ..., fine).

Un'altra opzione, anche se non sono sicuro che ArcPy preservi l'ordinamento delle parti di feature in base all'ordinamento dei vertici nell'oggetto di input, sarebbe eseguire la clip così com'è. Per la linea di mezzo nell'esempio, dovrebbe risultare in una funzione multipart con tre parti. A seconda dell'ordine, è possibile scorrere su ogni riga multipart prodotta da Clip e rimuovere tutto tranne la prima e l'ultima parte della funzione multipart out.


3

In questo caso sono tre i problemi da affrontare:

  • fori
  • Linee tra poligoni
  • Linee di fondo

inserisci qui la descrizione dell'immagine

fori

Poiché qualsiasi linea all'interno di un foro verrà mantenuta, rimuovere i fori dai poligoni. Nella sceneggiatura sotto lo faccio usando cursori e geometrie.

Linee tra poligoni

Le linee che toccano due poligoni devono essere rimosse. Nello script seguente lo faccio eseguendo un join spaziale di one to many, con le mie linee come la mia classe di funzionalità di input e i miei poligoni come la mia classe di funzionalità di join. Ogni linea che viene generata due volte tocca due poligoni e viene rimossa.

Linee di fondo

Per rimuovere le linee che toccano solo un poligono su un'estremità, converto le linee in punti finali. Quindi uso i feature layer e le selezioni per determinare quali punti finali sono mobili. Seleziono i punti finali che intersecano i poligoni. Quindi cambio la mia selezione. Seleziona i punti finali che non intersecano i poligoni. Seleziono qualsiasi linea che interseca questi punti selezionati e li elimino.

Risultato

inserisci qui la descrizione dell'immagine

ipotesi

  • Gli input sono classi di caratteristiche di geodatabase di file
  • La licenza avanzata ArcGIS è disponibile (a causa di un erasee un feature vertices to points)
  • Le linee continue e connesse sono un'unica funzione
  • I poligoni non si sovrappongono
  • Non ci sono poligoni multipart

copione

Lo script seguente mostra una classe caratteristica con il nome della classe caratteristica linea più _GreedyClip, nello stesso geodatabase della classe caratteristica linea. È inoltre necessaria una posizione dell'area di lavoro.

#input polygon feature class
polyFc = r"C:\Users\e1b8\Desktop\E1B8\Workspace\Workspace.gdb\testPolygon2"
#input line feature class
lineFc = r"C:\Users\e1b8\Desktop\E1B8\Workspace\Workspace.gdb\testLine"
#workspace
workspace = r"in_memory"

print "importing"
import arcpy
import os

#generate a unique ArcGIS file name
def UniqueFileName(location = "in_memory", name = "file", extension = ""):
    if extension:
        outName = os.path.join (location, name + "." + extension)
    else:
        outName = os.path.join (location, name)
    i = 0
    while arcpy.Exists (outName):
        i += 1
        if extension:
            outName = os.path.join (location, "{0}_{1}.{2}".format (name, i, extension))
        else:
            outName = os.path.join (location, "{0}_{1}".format (name, i))
    return outName

#remove holes from polygons
def RemoveHoles (inFc, workspace):
    outFc = UniqueFileName (workspace)
    array = arcpy.Array ()
    sr = arcpy.Describe (inFc).spatialReference
    outPath, outName = os.path.split (outFc)
    arcpy.CreateFeatureclass_management (outPath, outName, "POLYGON", spatial_reference = sr)
    with arcpy.da.InsertCursor (outFc, "SHAPE@") as iCurs:
        with arcpy.da.SearchCursor (inFc, "SHAPE@") as sCurs:
            for geom, in sCurs:
                try:
                    part = geom.getPart (0)
                except:
                    continue
                for pnt in part:
                    if not pnt:
                        break
                    array.add (pnt)
                polygon = arcpy.Polygon (array)
                array.removeAll ()
                row = (polygon,)
                iCurs.insertRow (row)
    del iCurs
    del sCurs
    return outFc

#split line fc by polygon fc
def SplitLinesByPolygon (lineFc, polygonFc, workspace):
    #clip
    clipFc = UniqueFileName(workspace)
    arcpy.Clip_analysis (lineFc, polygonFc, clipFc)
    #erase
    eraseFc = UniqueFileName(workspace)
    arcpy.Erase_analysis (lineFc, polygonFc, eraseFc)
    #merge
    mergeFc = UniqueFileName(workspace)
    arcpy.Merge_management ([clipFc, eraseFc], mergeFc)
    #multipart to singlepart
    outFc = UniqueFileName(workspace)
    arcpy.MultipartToSinglepart_management (mergeFc, outFc)
    #delete intermediate data
    for trash in [clipFc, eraseFc, mergeFc]:
        arcpy.Delete_management (trash)
    return outFc

#remove lines between two polygons and end lines
def RemoveLines (inFc, polygonFc, workspace):
    #check if "TARGET_FID" is in fields
    flds = [f.name for f in arcpy.ListFields (inFc)]
    if "TARGET_FID" in flds:
        #delete "TARGET_FID" field
        arcpy.DeleteField_management (inFc, "TARGET_FID")
    #spatial join
    sjFc = UniqueFileName(workspace)
    arcpy.SpatialJoin_analysis (inFc, polygonFc, sjFc, "JOIN_ONE_TO_MANY")
    #list of TARGET_FIDs
    targetFids = [fid for fid, in arcpy.da.SearchCursor (sjFc, "TARGET_FID")]
    #target FIDs with multiple occurances
    deleteFids = [dFid for dFid in targetFids if targetFids.count (dFid) > 1]
    if deleteFids:
        #delete rows with update cursor
        with arcpy.da.UpdateCursor (inFc, "OID@") as cursor:
            for oid, in cursor:
                if oid in deleteFids:
                    cursor.deleteRow ()
        del cursor
    #feature vertices to points
    vertFc = UniqueFileName(workspace)
    arcpy.FeatureVerticesToPoints_management (inFc, vertFc, "BOTH_ENDS")
    #select points intersecting polygons
    arcpy.MakeFeatureLayer_management (vertFc, "vertLyr")
    arcpy.SelectLayerByLocation_management ("vertLyr", "", polygonFc, "1 FEET")
    #switch selection
    arcpy.SelectLayerByAttribute_management ("vertLyr", "SWITCH_SELECTION")
    arcpy.MakeFeatureLayer_management (inFc, "lineLyr")
    #check for selection
    if arcpy.Describe ("vertLyr").FIDSet:
        #select lines by selected points
        arcpy.SelectLayerByLocation_management ("lineLyr", "", "vertLyr", "1 FEET")
        #double check selection (should always have selection)
        if arcpy.Describe ("lineLyr").FIDSet:
            #delete selected rows
            arcpy.DeleteFeatures_management ("lineLyr")

    #delete intermediate data
    for trash in [sjFc, "vertLyr", "lineLyr"]:
        arcpy.Delete_management (trash)

#main script
def main (polyFc, lineFc, workspace):

    #remove holes
    print "removing holes"
    holelessPolyFc = RemoveHoles (polyFc, workspace)

    #split line at polygons
    print "splitting lines at polygons"
    splitFc = SplitLinesByPolygon (lineFc, holelessPolyFc, workspace)

    #delete unwanted lines
    print "removing unwanted lines"
    RemoveLines (splitFc, polyFc, workspace)

    #create output feature class
    outFc = lineFc + "_GreedyClip"
    outFcPath, outFcName = os.path.split (outFc)
    outFc = UniqueFileName (outFcPath, outFcName)
    arcpy.CopyFeatures_management (splitFc, outFc)
    print "created:"
    print outFc
    print
    print "cleaning up"
    #delete intermediate data
    for trash in [holelessPolyFc, splitFc]:
        arcpy.Delete_management (trash)

    print "done"                    

if __name__ == "__main__":
    main (polyFc, lineFc, workspace)  

Bella soluzione Emil. Questo è meno codice di quello che ho finito.
Mike Bannister,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.