Líneas de recorte "codiciosas" con polígono

9

Deseo recortar un conjunto de polilíneas (líneas negras en la imagen a continuación) en el límite exterior de un polígono. Cualquier vacío dentro del polígono debe ser ignorado. Mi salida ideal son las líneas amarillas discontinuas. Las líneas iniciales pueden o no ser rectas. La imagen es un ejemplo simplificado, en realidad el polígono es mucho más complejo y hay cientos de líneas. No creo que un casco convexo funcione (pero podría estar equivocado). Estoy abierto a soluciones en arcgis, qgis, arcpy, shapely, etc. La codificación preferiblemente estaría en python porque estoy abierto a otras opciones si es necesario. Arcgis también sería preferible para facilitar a mis compañeros de trabajo compartir la herramienta, pero no es un requisito.

Lo mejor que puedo pensar en este momento es intersectar una línea individual con el polígono creando un conjunto de puntos en todas las intersecciones de límites. Ordenar los puntos por distancia al inicio de la línea. Los puntos más lejanos y más cercanos (FAC) serán el límite exterior del polígono. Luego use los puntos FAC para seleccionar los vértices apropiados de la línea original y cree la línea punteada amarilla a partir de los puntos apropiados. Debería funcionar, pero parece más complicado de lo necesario.

Algunas reflexiones adicionales:

  • Las líneas son lineales "suficientes" para que un simple cálculo de distancia entre puntos funcione, no debería ser necesaria una referencia lineal.
  • Esto sería fácil en arcpy si hubiera una herramienta para dividir una línea en un punto, pero no puedo encontrar una.

¿Alguien piensa?

Ejemplo

Mike Bannister
fuente
+1, problema interesante! Estoy ansioso por ver qué soluciones están disponibles =)
Joseph
Solo su línea media es difícil de lograr: la parte superior e inferior solo provienen de un clip después de llenar cualquier vacío. En consecuencia, creo que debería centrar su pregunta en eso y limitar su alcance solo a ArcPy si esa es su herramienta preferida. Siempre puede preguntar acerca de otra herramienta, si eso no produce una solución.
PolyGeo
Por qué las líneas cruzan múltiples polígonos?
Emil Brundage
Emil, supongamos que las líneas pueden cruzarse sobre múltiples polígonos. Sin embargo, aparte de la geometría, no hay diferencia entre los polígonos, por lo que se pueden disolver, fusionar en una función multiparte, etc., si eso facilita el algoritmo. Un cruce de línea sobre múltiples polígonos probablemente sea raro y ese puede ser un caso marcado para ser tratado a mano si es necesario.
Mike Bannister el
¿Cuál es su nivel de licencia?
Emil Brundage

Respuestas:

4

Quiero lanzar mi solución pyQGIS, nada más.

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)

Mi caso de prueba - antes del recorte: antes del clip

Después del recorte:

después

Para obtener el conjunto completo de atributos de las líneas originales, creo que sería mejor unirlos con el resultado. De lo contrario, debe crearse en la sección de preparación y establecerse en el bucle más interno. Pero no he probado si pasan el proceso de disolución o si se pierden, porque en principio podrían tener valores diferentes.

Detlev
fuente
Respuesta muy concisa. ¿Cómo se ven siempre las capturas de pantalla de QGIS como QGIS?
Mike Bannister
3

Esto sería fácil en arcpy si hubiera una herramienta para dividir una línea en un punto, pero no puedo encontrar una.

Si ejecuta Integrar con los polígonos y líneas como entradas, agregará un vértice a cada uno donde se cruzan. (Cuidado, ya que Integrate modifica las entradas en lugar de producir nuevas salidas).

Una vez que esté seguro de que hay vértices coincidentes, puede iterar sobre los vértices de la línea y probar para ver si cada uno toca la otra entidad. De la lista ordenada de vértices que se tocan, tome el mínimo y el máximo del conjunto. Luego, haga dos líneas de cada característica, A: (inicio, ..., mínimo) y B: (máximo, ..., final).

Otra opción, aunque no estoy seguro de si ArcPy conserva el orden de las partes de la entidad en función del orden de los vértices en el objeto de entrada, sería ejecutar el clip tal como está. Para la línea media en su ejemplo, debería resultar en una característica de varias partes con tres partes. Dependiendo del orden, puede iterar sobre cada línea multiparte producida por Clip y eliminar todas las partes excepto la primera y la última parte de la función multiparte.

John Reiser
fuente
3

Hay tres problemas con los que lidiar en este caso:

  • Agujeros
  • Líneas entre polígonos
  • Lineas finales

ingrese la descripción de la imagen aquí

Agujeros

Como se mantendrá cualquier línea dentro de un agujero, elimine los agujeros de los polígonos. En la secuencia de comandos a continuación lo hago mediante el uso de cursores y geometrías.

Líneas entre polígonos

Las líneas que tocan dos polígonos deben eliminarse. En el siguiente script, lo hago realizando una unión espacial de one to many, con mis líneas como mi clase de entidad de entrada y mis polígonos como mi clase de entidad de unión. Cualquier línea que se genera dos veces toca dos polígonos y se elimina.

Lineas finales

Para eliminar líneas que solo tocan un polígono en un extremo, convierto líneas en puntos finales. Luego hago uso de capas y selecciones de entidades para determinar qué puntos finales son flotantes. Selecciono los puntos finales que se cruzan con los polígonos. Luego cambio mi selección. Esto selecciona puntos finales que no se cruzan con polígonos. Selecciono cualquier línea que intersecte estos puntos seleccionados y los elimino.

Resultado

ingrese la descripción de la imagen aquí

Supuestos

  • Las entradas son clases de entidad de geodatabase de archivos
  • La licencia avanzada de ArcGIS está disponible (debido a una erasey a feature vertices to points)
  • Las líneas continuas conectadas son una característica única
  • Los polígonos no se superponen
  • No hay polígonos multiparte

Guión

El siguiente script genera una clase de entidad con el nombre de su clase de entidad de línea plus _GreedyClip, en la misma geodatabase que su clase de entidad de línea. También se necesita una ubicación del espacio de trabajo.

#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)  
Emil Brundage
fuente
Buena solución Emil. Eso es menos código del que terminé.
Mike Bannister el