import numpy as np
from functools import partial
from shapely.geometry import Point, LineString
from shapely.ops import substring, linemerge
from shapely.ops import unary_union
from itertools import combinations
class removeRingOnLineSting():
# 去除自相交線上的小環
# 基本思路 1 定位自相交點 2 自相交點分割線 3 分割線合并 根據長度篩選最長的兩個
# todo1 環上環
def __init__(self, ls, torelance=30):
self.ls = ls
self.torelance = torelance
self.cp = self.crossPoint()
def crossPoint(self):
# 定位自相交點
N = int(self.ls.length / self.torelance)
sp = np.linspace(0, 1, N)[1:-1]
pfun = partial(substring, geom=self.ls, normalized=1)
cp = set()
for p in sp:
t1 = pfun(start_dist=0, end_dist=p)
tp = pfun(start_dist=p, end_dist=p)
t2 = pfun(start_dist=p, end_dist=1)
if not t1.touches(t2):
mp = [_ for _ in t1.intersection(t2).geoms if _ != tp]
cp.update(mp)
tx = lambda x: round(x, 3)
cp = [(tx(p.x), tx(p.y)) for p in cp]
cp = set([Point(_) for _ in cp])
return cp
def removeRing(self):
spliter = [p.buffer(self.torelance) for p in self.cp]
spliter = unary_union(spliter)
mls = self.ls.symmetric_difference(spliter) # 自相交點緩沖裁剪線
mls = [_ for _ in mls.geoms if _.geom_type == 'LineString'] # 過濾
mls = self.spliterFilter(mls) # 過濾環 及 自相交點附近的線
rls = self.mergeLinesWithGap(mls) # 合并剩余的線
return rls
def spliterFilter(self, geoms):
# 把起點和終點都在spliter附近的部分過濾掉
# 思想是計算起點和終點之間的距離
pfun = lambda x: substring(x, start_dist=0, end_dist=0, normalized=1)
ptun = lambda x: substring(x, start_dist=1, end_dist=1, normalized=1)
flag = [pfun(_).distance(ptun(_)) >= self.torelance * 2 for _ in geoms]
geoms = [g for g, _ in zip(geoms, flag) if _]
return geoms
def mergeLinesWithGap(self, lines):
pfun = lambda x: substring(x, start_dist=0, end_dist=0, normalized=1)
ptun = lambda x: substring(x, start_dist=1, end_dist=1, normalized=1)
fps = ((i, pfun(_)) for i, _ in enumerate(lines))
tps = ((j, ptun(_)) for j, _ in enumerate(lines))
ftp = list(fps) + list(tps)
tt = combinations(ftp, 2)
para = ((f, t) for (i, f), (j, t) in tt if i != j)
para = ((f, t) for f, t in para if f.distance(t) <= self.torelance * 2)
gap_line = [LineString(_) for _ in para]
gap_line.extend(lines)
return linemerge(gap_line)
if __name__ == '__main__':
pnts = [(-7,0), (1,0), (1,1), (0,1), (0,-2), (-1,-2),(-1,-1),(7,-1)]
ls = LineString(pnts)
algo = removeRingOnLineSting(ls, torelance=0.2)
rls = algo.removeRing() # LINESTRING (-7 0, -0.2 0, 0 -0.2, 0 -0.8, 0.2 -1, 7 -1)
print(rls.wkt)