Shapely: ops.split comportement incohérent avec LineStrings et MultiLineStrings

Créé le 11 avr. 2019  ·  5Commentaires  ·  Source: Toblerity/Shapely

Comportement attendu et comportement réel et étapes pour reproduire le problème

Le fractionnement d'une chaîne de lignes A avec une chaîne de lignes B en intersection ne produit pas la sortie attendue (renvoie A). La division de A avec une chaîne multiligne contenant B et C (C recoupant également A) produit la sortie souhaitée (3 segments). Cela semble incohérent.

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.

Système opérateur

Ubuntu 18.04.2 LTS

Version galbée et provenance

Python 3.6.7 - Shapely 1.6.4.post2 (installé à partir de PyPI à l'aide de pip)

Commentaire le plus utile

Je pense que cela fonctionnerait bien, oui. Et le comportement a du sens pour moi.

Pendant un moment, j'ai pensé que nous pourrions filtrer les goems dans une MultiLineString splitter pour ceux qui croisent line et prendre la différence entre ceux-ci uniquement. Mais cela ne résoudrait pas le cas où une seule chaîne splitter ligne line qu'elle croise à un autre point. Je pense que la seule façon d'obtenir un comportement cohérent ici tout en utilisant difference pour le levage de charges lourdes est de diviser les géométries linéaires qui se touchent.

Je soupçonne que crosses a peut-être été utilisé, donc la fonction est revenue tôt lorsque seule la limite de line touche le splitter . En fait, ce cas est testé pour . Mais notre cas actuel, où la frontière de splitter touche line , n'est pas dans les tests, et peut avoir été un angle mort. Cependant, je suis un natif de Shapely sans grande expérience avec d'autres bibliothèques de fonctionnalités simples. Et je ne sais pas si seul le fractionnement lors du croisement est un comportement standard pour les géométries linéaires.

C'était il y a 5 ans, mais si @georgeouzou se souvient peut-être qu'il pourrait confirmer ou expliquer pourquoi crosses été utilisé ?

Nous pouvons avoir notre gâteau et le manger aussi si nous voulons revenir tôt dans le cas testé, mais continuer à diviser dans le cas de ce numéro. Cela nécessiterait d'utiliser directement relate . Mais je doute qu'il y ait une grande différence de performance de toute façon.

Tous les 5 commentaires

D'accord. Je vois le problème ici.

Visuellement, ces lignes de ligne ressemblent à ceci :

image

Lorsque vous divisez l'horizontale ( A ) par la grande verticale du haut ( B ) qui ne touche que A , rien ne se passe. Mais diviser A par C fonctionne car C traverse A .

Maintenant, lorsque vous divisez A par MultiLineString([B, C]) , il se divise en trois chaînes de ligne, et non en deux comme vous vous en doutez. Je suis d'accord que cela semble incohérent.

Le code à l'origine de cela est en fait assez accessible ( permalien ).

    <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]

Vous pouvez voir que le splitter doit traverser line pour que tout fractionnement commence. Mais une fois ce point de contrôle passé, un simple appel à difference est effectué pour générer les géométries fractionnées. Mais difference ne fait pas de distinction entre le croisement et le simple toucher ! Donc, si à un moment quelconque splitter croise line , alors tous les points qui se touchent simplement seront divisés.

@Jeremiah-England merci d'avoir creusé cela ! Il semble que, pour les lignes au moins, nous pourrions changer le test des croix en un test des touches et obtenir des résultats moins surprenants ? Qu'est ce que tu penses de ça?

Je pense que cela fonctionnerait bien, oui. Et le comportement a du sens pour moi.

Pendant un moment, j'ai pensé que nous pourrions filtrer les goems dans une MultiLineString splitter pour ceux qui croisent line et prendre la différence entre ceux-ci uniquement. Mais cela ne résoudrait pas le cas où une seule chaîne splitter ligne line qu'elle croise à un autre point. Je pense que la seule façon d'obtenir un comportement cohérent ici tout en utilisant difference pour le levage de charges lourdes est de diviser les géométries linéaires qui se touchent.

Je soupçonne que crosses a peut-être été utilisé, donc la fonction est revenue tôt lorsque seule la limite de line touche le splitter . En fait, ce cas est testé pour . Mais notre cas actuel, où la frontière de splitter touche line , n'est pas dans les tests, et peut avoir été un angle mort. Cependant, je suis un natif de Shapely sans grande expérience avec d'autres bibliothèques de fonctionnalités simples. Et je ne sais pas si seul le fractionnement lors du croisement est un comportement standard pour les géométries linéaires.

C'était il y a 5 ans, mais si @georgeouzou se souvient peut-être qu'il pourrait confirmer ou expliquer pourquoi crosses été utilisé ?

Nous pouvons avoir notre gâteau et le manger aussi si nous voulons revenir tôt dans le cas testé, mais continuer à diviser dans le cas de ce numéro. Cela nécessiterait d'utiliser directement relate . Mais je doute qu'il y ait une grande différence de performance de toute façon.

@Jeremiah-England ma mémoire est un peu trouble sur celui-ci, mais en voyant à nouveau le code, je peux confirmer qu'il s'agissait d'un angle mort. Le code doit également vérifier que le cas actuel est complet. Pourriez-vous continuer avec les changements ?

Salut @georgeouzou , merci d'avoir pris le temps de regarder ça !

Et, oui, si tout le monde est d'accord, j'aimerais résoudre ce cas et soumettre un PR à un moment donné.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

akadouri picture akadouri  ·  4Commentaires

MarkWieczorek picture MarkWieczorek  ·  4Commentaires

sgillies picture sgillies  ·  6Commentaires

LostFan123 picture LostFan123  ·  5Commentaires

jrobichaud picture jrobichaud  ·  3Commentaires