连接墙端点

Connecting wall endpoints

我有以下代表墙的点列表:

walls = [[  76.7403, 1283.2495,  894.5198, 1310.0122],
        [ 864.3867,  415.2406,  891.3730, 1287.3810],
        [  80.0975,  402.9981,  899.7578,  428.0596],
        [  75.9861,  404.3390,  101.6148, 1292.0922],
        [ 469.9275, 1118.6304,  481.1644, 1300.6523],
        [ 500.6000,  897.3496,  876.6120,  908.2079],
        [ 102.0548,  753.6523,  402.9403,  764.0107],
        [ 247.2823,  968.0134,  320.4769,  977.5980],
        [ 503.2201,  898.0987,  515.2742, 1136.6272],
        [ 400.9270,  689.1513,  411.8462,  907.2622],
        [ 353.0995, 1123.1868,  362.8233, 1293.9554],
        [ 248.6094,  967.5870,  261.0458, 1124.8018],
        [ 246.0828,  896.9332,  411.2629,  906.4570],
        [ 880.6799,   60.5249,  892.5886,  411.2219],
        [ 249.5214, 1116.5377,  259.7009, 1290.2698],
        [ 309.1409,  899.7199,  320.3546,  978.5977],
        [  85.6537, 1118.2689,  259.5562, 1128.9491],
        [ 249.0276,  763.1385,  261.5212,  905.9616],
        [  86.2850, 1117.8192,  501.5602, 1128.5548],
        [ 240.1799, 1117.4913,  385.9280, 1128.2761],
        [ 288.5830, 1117.4141,  515.1036, 1128.3738],
        [ 512.2842, 1015.8013,  592.7465, 1022.5025],
        [ 249.2041,  748.7802,  260.6894, 1140.3009],
        [ 401.1811,  687.2712,  411.9943,  779.8344],
        [ 380.8932, 1040.3300,  386.9687, 1126.2324]]  

我正在从这些墙中形成一组多边形:

polys = []
for w in walls:
  x0, y0, x1, y1 = w
  poly = [
            (x0, y0), (x1, y0),
            (x1, y1), (x0, y1)
        ]
  poly = list(itertools.chain.from_iterable(poly))
  polys.append(poly)

多边形最终如下所示:

我想做的是连接墙壁端点,使它们不会如图所示重叠。执行此操作的最佳算法是什么?

这是生成图像的代码:

<svg width="1200" height="1300">
<polygon points='76.7403, 1283.2495, 894.5198, 1283.2495, 894.5198, 1310.0122, 76.7403, 1310.0122'/>
<polygon points='864.3867, 415.2406, 891.373, 415.2406, 891.373, 1287.381, 864.3867, 1287.381'/>
<polygon points='80.0975, 402.9981, 899.7578, 402.9981, 899.7578, 428.0596, 80.0975, 428.0596'/>
<polygon points='75.9861, 404.339, 101.6148, 404.339, 101.6148, 1292.0922, 75.9861, 1292.0922'/>
<polygon points='469.9275, 1118.6304, 481.1644, 1118.6304, 481.1644, 1300.6523, 469.9275, 1300.6523'/>
<polygon points='500.6, 897.3496, 876.612, 897.3496, 876.612, 908.2079, 500.6, 908.2079'/>
<polygon points='102.0548, 753.6523, 402.9403, 753.6523, 402.9403, 764.0107, 102.0548, 764.0107'/>
<polygon points='247.2823, 968.0134, 320.4769, 968.0134, 320.4769, 977.598, 247.2823, 977.598'/>
<polygon points='503.2201, 898.0987, 515.2742, 898.0987, 515.2742, 1136.6272, 503.2201, 1136.6272'/>
<polygon points='400.927, 689.1513, 411.8462, 689.1513, 411.8462, 907.2622, 400.927, 907.2622'/>
<polygon points='353.0995, 1123.1868, 362.8233, 1123.1868, 362.8233, 1293.9554, 353.0995, 1293.9554'/>
<polygon points='248.6094, 967.587, 261.0458, 967.587, 261.0458, 1124.8018, 248.6094, 1124.8018'/>
<polygon points='246.0828, 896.9332, 411.2629, 896.9332, 411.2629, 906.457, 246.0828, 906.457'/>
<polygon points='880.6799, 60.5249, 892.5886, 60.5249, 892.5886, 411.2219, 880.6799, 411.2219'/>
<polygon points='249.5214, 1116.5377, 259.7009, 1116.5377, 259.7009, 1290.2698, 249.5214, 1290.2698'/>
<polygon points='309.1409, 899.7199, 320.3546, 899.7199, 320.3546, 978.5977, 309.1409, 978.5977'/>
<polygon points='85.6537, 1118.2689, 259.5562, 1118.2689, 259.5562, 1128.9491, 85.6537, 1128.9491'/>
<polygon points='249.0276, 763.1385, 261.5212, 763.1385, 261.5212, 905.9616, 249.0276, 905.9616'/>
<polygon points='86.285, 1117.8192, 501.5602, 1117.8192, 501.5602, 1128.5548, 86.285, 1128.5548'/>
<polygon points='240.1799, 1117.4913, 385.928, 1117.4913, 385.928, 1128.2761, 240.1799, 1128.2761'/>
<polygon points='288.583, 1117.4141, 515.1036, 1117.4141, 515.1036, 1128.3738, 288.583, 1128.3738'/>
<polygon points='512.2842, 1015.8013, 592.7465, 1015.8013, 592.7465, 1022.5025, 512.2842, 1022.5025'/>
<polygon points='249.2041, 748.7802, 260.6894, 748.7802, 260.6894, 1140.3009, 249.2041, 1140.3009'/>
<polygon points='401.1811, 687.2712, 411.9943, 687.2712, 411.9943, 779.8344, 401.1811, 779.8344'/>
<polygon points='380.8932, 1040.33, 386.9687, 1040.33, 386.9687, 1126.2324, 380.8932, 1126.2324'/>
</svg>

不太清楚你的最终目标是什么。 你想剪掉哪条线?所有松散的末端还是只是有点太长的末端?那么线条粗细不同呢?

我想出的最简单的方法是将 walls 转换为具有特定宽度的线,而不是离散化这些线的端点,这样生成的图像看起来更清晰:

import matplotlib.pyplot as plt
import numpy as np


def walls2lines(walls):
    lines = []
    widths = []
    for w in walls:
        x0, y0, x1, y1 = w
        if (x1-x0) > (y1 - y0):
            widths.append(y1-y0)
            lines.append(((x0, y0 + widths[-1] / 2), (x1, y0 + widths[-1] / 2)))
        else:
            widths.append(x1-x0)
            lines.append(((x0 + widths[-1] / 2, y0), (x0 + widths[-1] / 2, y1)))

    return np.array(lines), np.array(widths)


def discretize_np(x, step):
    difference = x % step  # distance to the next discrete value
    difference[difference > step / 2] -= step  # round correctly
    return x - difference


lines, widths = walls2lines(walls=walls)
lines = discretize_np(lines, step=50)
widths *= 10 / widths.max()

fig = plt.figure()
ax = fig.subplots()

for l, w in zip(lines, widths):
    ax.plot(*l.T, lw=w, alpha=0.5)

另一种可能性是真正查看不同矩形的重叠并尝试移动相关边界。但是你有这么多不同的案例,很难找到一个好的通用解决方案,这真的取决于你在寻找什么。

我在 walls2overlap 中添加了 eps 以包含不完全相交的矩形。我评论了一行以不同方式处理自由端。 大量的微调和反复试验...

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection


def walls2overlap(walls, eps=0):

    idx_overlap = np.ones((len(walls), len(walls)), dtype=bool)
    for i, w_i in enumerate(walls):
        for j, w_j in enumerate(walls):
            (x0_i, x1_i), (y0_i, y1_i) = w_i
            (x0_j, x1_j), (y0_j, y1_j) = w_j
            if (x0_i > x1_j+eps or x1_i < x0_j-eps or
                y0_i > y1_j+eps or y1_i < y0_j-eps):
                idx_overlap[i, j] = False

    return idx_overlap


def get_orientations(walls):
    """
    0: horizontal, 1: vertical
    """
    (x0, x1), (y0, y1) = walls.transpose(1, 2, 0)
    return ((x1 -x0) < (y1 - y0)).astype(int)


def cut_walls(walls, idx_overlap):
    """Move the walls to the most extreme boundary
     of all intersecting walls of different orientation."""
    for i, w_i in enumerate(walls):
        o_i = orientations[i]
        idx_temp = np.logical_and(idx_overlap[i], orientations != o_i)
        if idx_temp.sum() <= 0:  # Set to 1, to keep free ended walls
            continue
        walls[i, o_i, 0] = np.min(walls[idx_temp, o_i, 0])
        walls[i, o_i, 1] = np.max(walls[idx_temp, o_i, 1])

    return walls


walls = np.reshape(walls, (-1, 2, 2), order='F')
idx_overlap = walls2overlap(walls=walls, eps=10)
orientations = get_orientations(walls)
walls = cut_walls(walls=walls, idx_overlap=idx_overlap)

fig, ax = new_fig(aspect=1)
rects = [Rectangle(xy=w[:, 0], width=w[0, 1] - w[0, 0], height=w[1, 1] - w[1, 0])
         for w in walls]
ax.add_collection(PatchCollection(rects, color='k', alpha=0.5))