Shapely: ops.split comportamiento inconsistente con LineStrings y MultiLineStrings

Creado en 11 abr. 2019  ·  5Comentarios  ·  Fuente: Toblerity/Shapely

Comportamiento esperado y comportamiento real y pasos para reproducir el problema

Dividir una cadena lineal A con una cadena lineal B que se cruza no produce el resultado esperado (devuelve A). Dividir A con una multilínea que contiene B y C (C que interseca a A también) produce la salida deseada (3 segmentos). Esto parece inconsistente.

from shapely.ops import split
from shapely.geometry import LineString, MultiLineString

A = LineString([(0, 0), (10, 0)])
B = LineString([(5, 0), (5, 5)])
C = LineString([(1, -1), (1, 1)])
assert (split(A, B).wkt == "GEOMETRYCOLLECTION (LINESTRING (0 0, 10 0))")
# Does not split
# Expected: "GEOMETRYCOLLECTION (LINESTRING (0 0, 5 0), LINESTRING (5 0, 10 0))"

assert (A.intersection(B).wkt == "POINT (5 0)")
# Although A and B do intersect

assert (split(A, MultiLineString([B, C])).wkt ==
        "GEOMETRYCOLLECTION (LINESTRING (0 0, 1 0), LINESTRING (1 0, 5 0), LINESTRING (5 0, 10 0))")
# OK - A is split by both B and C - but A was not split by B in the previous example.

Sistema operativo

Ubuntu 18.04.2 LTS

Procedencia y versión bien formada

Python 3.6.7 - Shapely 1.6.4.post2 (instalado desde PyPI usando pip)

Comentario más útil

Creo que funcionaría bien, sí. Y el comportamiento tiene sentido para mí.

Por un momento, pensé que podríamos filtrar los goems en un MultiLineString splitter para aquellos que cruzan line y tomar la diferencia solo de esos. Pero eso no resolvería el caso en el que un solo LineString splitter engancha para tocar el line que cruza en otro punto. Creo que la única forma de obtener un comportamiento consistente aquí mientras se usa difference para el trabajo pesado es dividir las geometrías lineales que tocan.

Sospecho que tal vez se usó crosses por lo que la función regresó temprano cuando solo el límite de line toca el splitter . De hecho, este caso está probado . Pero nuestro caso actual, donde el límite de splitter toca line , no está en las pruebas y puede haber sido un punto ciego. Sin embargo, soy nativo de Shapely sin mucha experiencia con otras bibliotecas de funciones simples. Y no sé si solo dividir cuando se cruza es un comportamiento estándar para geometrías lineales.

Fue hace 5 años, pero si @georgeouzou recuerda, tal vez podría confirmar o explicar por qué se usó crosses .

Podemos tener nuestro pastel y comérnoslo también si queremos regresar temprano en el caso probado, pero continúe con la división en el caso de este número. Eso requeriría usar relate directamente. Pero dudo que haya una gran diferencia de rendimiento de cualquier manera.

Todos 5 comentarios

Está bien. Veo el problema aquí.

Visualmente, esas cadenas de líneas se ven así:

image

Cuando divide la horizontal ( A ) por la vertical grande en la parte superior ( B ) que solo toca A , no sucede nada. Pero dividir A por C funciona porque C cruza A.

Ahora, cuando divide A por MultiLineString([B, C]) , se divide en tres cadenas de líneas, no dos como cabría esperar. Estoy de acuerdo en que esto parece inconsistente.

El código que causa esto es bastante accesible ( enlace permanente ).

    <strong i="26">@staticmethod</strong>
    def _split_line_with_line(line, splitter):
        """Split a LineString with another (Multi)LineString or (Multi)Polygon"""

        # if splitter is a polygon, pick it's boundary
        if splitter.type in ('Polygon', 'MultiPolygon'):
            splitter = splitter.boundary

        assert(isinstance(line, LineString))
        assert(isinstance(splitter, LineString) or isinstance(splitter, MultiLineString))

        if splitter.crosses(line):
            # The lines cross --> return multilinestring from the split
            return line.difference(splitter)
        elif splitter.relate_pattern(line, '1********'):
            # The lines overlap at some segment (linear intersection of interiors)
            raise ValueError('Input geometry segment overlaps with the splitter.')
        else:
            # The lines do not cross --> return collection with identity line
            return [line]

Puede ver que se requiere splitter para cruzar line para que comience cualquier división. Pero una vez que se pasa ese punto de control, se realiza una simple llamada a difference para generar las geometrías divididas. ¡Pero difference no discrimina entre cruzar y simplemente tocar! Entonces, si en cualquier punto splitter cruza line , entonces todos los puntos que simplemente se toquen se dividirán.

@ Jeremiah-England, ¡gracias por investigar esto! Parece que, al menos para las líneas, podríamos cambiar la prueba de cruces a una prueba de toques y obtener resultados menos sorprendentes. ¿Qué piensas sobre eso?

Creo que funcionaría bien, sí. Y el comportamiento tiene sentido para mí.

Por un momento, pensé que podríamos filtrar los goems en un MultiLineString splitter para aquellos que cruzan line y tomar la diferencia solo de esos. Pero eso no resolvería el caso en el que un solo LineString splitter engancha para tocar el line que cruza en otro punto. Creo que la única forma de obtener un comportamiento consistente aquí mientras se usa difference para el trabajo pesado es dividir las geometrías lineales que tocan.

Sospecho que tal vez se usó crosses por lo que la función regresó temprano cuando solo el límite de line toca el splitter . De hecho, este caso está probado . Pero nuestro caso actual, donde el límite de splitter toca line , no está en las pruebas y puede haber sido un punto ciego. Sin embargo, soy nativo de Shapely sin mucha experiencia con otras bibliotecas de funciones simples. Y no sé si solo dividir cuando se cruza es un comportamiento estándar para geometrías lineales.

Fue hace 5 años, pero si @georgeouzou recuerda, tal vez podría confirmar o explicar por qué se usó crosses .

Podemos tener nuestro pastel y comérnoslo también si queremos regresar temprano en el caso probado, pero continúe con la división en el caso de este número. Eso requeriría usar relate directamente. Pero dudo que haya una gran diferencia de rendimiento de cualquier manera.

@ Jeremiah-England mi memoria está un poco nublada en este caso, pero al ver el código nuevamente, puedo confirmar que este fue un punto ciego. El código también debe probar para que el caso actual esté completo. ¿Podrías continuar con los cambios?

Hola @georgeouzou , ¡gracias por

Y, sí, si todo el mundo está de acuerdo, me gustaría elaborar este caso y enviar un PR en algún momento.

¿Fue útil esta página
0 / 5 - 0 calificaciones