简体   繁体   中英

Split self-intersecting linestring into non-self-intersecting linestrings

I have a list of coordinates defining a line string that might intersect with itself:

coordinates = [
    [0, 3],
    [0, 5],
    [4, 5],
    [4, 0],
    [0, 0],
    [0, 5],
    [2, 5]
]

How can I split the linestring into smaller linestrings so none of the linestrings intersects with itself?

  • smallest number of linestrings
  • line strings should have equal number of coordinates as possible

the desired outcome in this case would be:

line0 = [
    [0, 3],
    [0, 5],
    [4, 5],
    [4, 0]
]
line1 = [
    [4, 0],
    [0, 0],
    [0, 5],
    [2, 5]
]

My attempt

In my attempt so far I construct an intersection matrix using Shapely Linestrings to find the intersections:

from shapely.geometry import LineString
from itertools import product, zip_longest
import numpy as np

def get_intersection_matrix(coordinates):
    linestrings = [
        (ix, LineString([c0, c1]))
        for ix, (c0, c1) in enumerate(zip(coordinates[:-1], coordinates[1:]))
    ]
    M = np.zeros((len(linestrings), len(linestrings)))
    for (ix0, ls0), (ix1, ls1) in combinations(linestrings, 2):
        if abs(ix0 - ix1) == 1: # ignore connecting segments
            continue
        if ls0.intersects(ls1):
            M[ix0, ix1], M[ix1, ix0] = 1, 1
    return M

which outputs what I call the "intersection matrix":

>> get_intersection_matrix(coordinates)
array([[0, 0, 0, 0, 1, 1],
       [0, 0, 0, 0, 1, 1],
       [0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0],
       [1, 1, 0, 0, 0, 0],
       [1, 1, 0, 0, 0, 0]])

That you can read as:

  • segment 1 intersects with segment 5 and 6
  • segment 2 intersects with segment 5 and 6
  • segment 5 intersects with segment 1 and 2
  • segment 6 intersects with segment 1 and 2

Also; I think that the number of "intersection clusters" indicate the number of linestrings: no_clusters + 1

How I solve it now... I changed my intersection matrix, so at no intersection the value is 1 and at any intersection the value is 0.

def get_intersection_matrix(coordinates):
    linestrings = [
        (ix, LineString([c0, c1]))
        for ix, (c0, c1) in enumerate(zip(coordinates[:-1], coordinates[1:]))
    ]
    M = np.ones((len(linestrings), len(linestrings)))
    for (ix0, ls0), (ix1, ls1) in combinations(linestrings, 2):
        if abs(ix0 - ix1) == 1:  # ignore connecting segments
            continue
        if ls0.intersects(ls1):
            M[ix0, ix1], M[ix1, ix0] = 0, 0
    return M
>> M = get_intersection_matrix(coordinates)
>> M
array([[1., 1., 1., 1., 0., 0.],
       [1., 1., 1., 1., 0., 0.],
       [1., 1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1., 1.],
       [0., 0., 1., 1., 1., 1.],
       [0., 0., 1., 1., 1., 1.]])
  • any combination of split indexes is given by: itertools.combinations(range(1, len(M)), nr_split_ixs) where also ix1 < ix2 <... < ixn

at one split index you get two squares that should not contain any 0's, and the squares can be optimized by a minimum sum!

This is a legal (but not the best) split with split_ix = 4 and the sum of the two boxes is 16+4 = 20. 在此处输入图像描述

This is a better legal (no zeros) split where the sum of the two boxes is 9+9=18

在此处输入图像描述

The method to calculate the scored split indexes:

def get_scored_split_ixs_combination(M, nr_split_ixs):
    ixs_scores = []
    for ixs in combinations(range(1, len(M)), nr_split_ixs):
        splitted_matrix = [
            M[i0:i1, i0:i1] for i0, i1 in zip((0, *ixs), (*ixs, len(M)))
        ]
        # check if no matrices have zeros
        if not all([(m > 0).all() for m in splitted_matrix]):
            # ilegal ixs combination
            continue
        ixs_scores.append((ixs, sum([m.sum() for m in splitted_matrix])))
    return ixs_scores

if the return is empty there are no legal options and you should increase the number of splits.

Now return the best split option by increment the number of splits:

def get_best_split_ixs_combination(M):
    nr_split_ixs = 0
    while True:
        ixs_scores = get_scored_split_ixs_combination(M, nr_split_ixs)
        if ixs_scores:
            return min(ixs_scores, key=lambda x: x[1])[0]
        nr_split_ixs +=1

>> get_best_split_ixs_combination(M)
(3,)

And finally wrap it all together:

def get_non_intersecting_linestrings(coordinates):
    M = get_intersection_matrix(coordinates)
    split_indexes = get_best_split_ixs_combination(M)
    return [
        coordinates[i1:i2]
        for i1, i2 in zip([0] + split_indexes, split_indexes + [len(coordinates)])
    ]
>> get_non_intersecting_linestrings(coordinates)
[[[0, 3], [0, 5], [4, 5]], [[4, 0], [0, 0], [0, 5], [2, 5]]]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM