Skip to main content

fast crossing

Project description

fast crossing

(See jupyter notebook here)

Fast polyline (line segments) intersection (fast version of bentley-ottmann).

Installation

via pip

pip install -U fast-crossing

from source

git clone --recursive https://github.com/cubao/fast-crossing
pip install ./fast-crossing

Or

pip install git+https://github.com/cubao/fast-crossing.git

(you can build wheels for later reuse by pip wheel git+https://github.com/cubao/fast-crossing.git)

Related

Inspired by anvaka/isect: Segments intersection detection library.

Usage & Tests

See tests/test_basic.py:

import numpy as np
import pytest
from fast_crossing import FastCrossing


def test_fast_crossing():
    fc = FastCrossing()

    # add your polylines
    """
                    2 C
                    |
                    1 D
    0               |                  5
    A---------------o------------------B
                    |
                    |
                    -2 E
    """
    fc.add_polyline(np.array([[0.0, 0.0], [5.0, 0.0]]))  # AB
    fc.add_polyline(np.array([[2.5, 2.0], [2.5, 1.0], [2.5, -2.0]]))  # CDE

    # build index
    fc.finish()

    # num_poylines
    assert 2 == fc.num_poylines()
    rulers = fc.polyline_rulers()
    assert len(rulers) == 2
    ruler0 = fc.polyline_ruler(0)
    ruler1 = fc.polyline_ruler(1)
    assert not ruler0.is_wgs84()
    assert not ruler1.is_wgs84()
    assert ruler0.length() == 5
    assert ruler1.length() == 4
    assert fc.polyline_ruler(10) is None

    # intersections
    ret = fc.intersections([1.5, 0], [3.5, 2])
    assert len(ret) == 2
    assert np.linalg.norm(fc.coordinates(ret[0]) - [1.5, 0, 0]) < 1e-15
    assert np.linalg.norm(fc.coordinates(ret[1]) - [2.5, 1, 0]) < 1e-15
    xyz = fc.coordinates(0, 0, 0.2)
    assert np.linalg.norm(xyz - [1.0, 0, 0]) < 1e-15
    with pytest.raises(IndexError) as excinfo:
        xyz = fc.coordinates(2, 0, 0.5)
    assert "map::at" in str(excinfo)

    # query all line segment intersections
    # [
    #    (array([2.5, 0. ]),
    #     array([0.5       , 0.33333333]),
    #     array([0, 0], dtype=int32),
    #     array([1, 1], dtype=int32))
    # ]
    ret = fc.intersections()
    # print(ret)
    assert len(ret) == 1
    for xy, ts, label1, label2 in ret:
        # xy 是交点,即图中的 o 点坐标
        # t,s 是分位点,(0.5, 0.33)
        #        0.5  ->   o 在 AB 1/2 处
        #        0.33 ->   o 在 DE 1/3 处
        # label1 是 line segment 索引,(polyline_index, point_index)
        #        e.g. (0, 0),polyline AB 的第一段
        # label2 是另一个条 line seg 的索引
        #        e.g. (1, 1),polyline CDE 的第二段(DE 段)
        # print(xy)
        # print(ts)
        # print(label1)
        # print(label2)
        assert np.all(xy == [2.5, 0])
        assert np.all(ts == [0.5, 1 / 3.0])
        assert np.all(label1 == [0, 0])
        assert np.all(label2 == [1, 1])

    # query intersections against provided polyline
    polyline = np.array([[-6.0, -1.0], [-5.0, 1.0], [5.0, -1.0]])
    ret = fc.intersections(polyline)
    ret = np.array(ret)  # 还是转化成 numpy 比较好用
    xy = ret[:, 0]  # 直接取出所有交点
    ts = ret[:, 1]  # 所有分位点
    label1 = ret[:, 2]  # 所有 label1(当前 polyline 的 label)
    label2 = ret[:, 3]  # tree 中 line segs 的 label
    # print(ret, xy, ts, label1, label2)
    assert np.all(xy[0] == [0, 0])
    assert np.all(xy[1] == [2.5, -0.5])
    assert np.all(ts[0] == [0.5, 0])
    assert np.all(ts[1] == [0.75, 0.5])
    assert np.all(label1 == [[0, 1], [0, 1]])
    assert np.all(label2 == [[0, 0], [1, 1]])

    polyline2 = np.column_stack((polyline, np.zeros(len(polyline))))
    ret2 = np.array(fc.intersections(polyline2[:, :2]))
    assert str(ret) == str(ret2)


def test_fast_crossing_intersection3d():
    fc = FastCrossing()
    """
                    2 C
                    |
                    1 D
    0               |                  5
    A---------------o------------------B
                    |
                    |
                    -2 E
    """
    fc.add_polyline(np.array([[0.0, 0.0, 0.0], [5.0, 0.0, 100]]))  # AB
    fc.add_polyline(np.array([[2.5, 2.0, 0.0], [2.5, 1.0, 100], [2.5, -2.0, 0]]))  # CDE
    fc.finish()
    ret = fc.intersections()
    assert len(ret) == 1
    ret = ret[0]
    xyz1 = fc.coordinates(ret, second=False)
    xyz2 = fc.coordinates(ret)
    assert np.linalg.norm(xyz1 - [2.5, 0, 50]) < 1e-10
    assert np.linalg.norm(xyz2 - [2.5, 0, 2 / 3 * 100.0]) < 1e-10


def test_fast_crossing_auto_rebuild_flatbush():
    fc = FastCrossing()
    fc.add_polyline(np.array([[0.0, 0.0, 0.0], [5.0, 0.0, 100]]))  # AB
    fc.add_polyline(np.array([[2.5, 2.0, 0.0], [2.5, 1.0, 100], [2.5, -2.0, 0]]))  # CDE
    ret = fc.intersections()
    assert len(ret) == 1

    fc.add_polyline([[1.5, 0], [3.5, 2]])
    ret = fc.intersections()
    assert len(ret) == 4  # should dedup to 3?


def test_fast_crossing_filter_by_z():
    fc = FastCrossing()
    fc.add_polyline([[0, 0, 0], [1, 0, 0]])
    fc.add_polyline([[0, 0, 10], [1, 0, 10]])
    fc.add_polyline([[0, 0, 20], [1, 0, 20]])
    ret = fc.intersections([[0.5, -1], [0.5, 1]])
    assert len(ret) == 3

    ret = fc.intersections([[0.5, -1], [0.5, 1]], z_min=-1, z_max=1)
    assert len(ret) == 1
    assert fc.coordinates(ret[0])[2] == 0

    ret = fc.intersections([[0.5, -1, 10], [0.5, 1, 10]], z_min=-1, z_max=1)
    assert len(ret) == 1
    assert fc.coordinates(ret[0])[2] == 10

    ret = fc.intersections([[0.5, -1, 20], [0.5, 1, 20]], z_min=-1, z_max=1)
    assert len(ret) == 1
    assert fc.coordinates(ret[0])[2] == 20

    ret = fc.intersections([[0.5, -1, 15], [0.5, 1, 15]], z_min=-6, z_max=6)
    assert len(ret) == 2
    assert fc.coordinates(ret[0])[2] == 10
    assert fc.coordinates(ret[1])[2] == 20


def test_fast_crossing_dedup():
    # should be stable
    for _ in range(100):
        fc = FastCrossing()
        fc.add_polyline([[0, 0, 0], [1, 0, 0], [2, 0, 0]])
        fc.add_polyline([[0, 1, 0], [1, 1, 0], [2, 1, 0]])

        ret = fc.intersections([[1, -1], [1, 1]])
        assert len(ret) == 2
        assert np.all(ret[0][-1] == [0, 0]), ret
        assert np.all(ret[1][-1] == [1, 0]), ret
        assert ret[0][1][1] == 1.0, ret
        assert ret[1][1][1] == 1.0, ret

        ret = fc.intersections([[1, -1], [1, 1]], dedup=False)
        # for idx, row in enumerate(ret):
        #     print(idx, row)
        assert len(ret) == 4

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fast_crossing-0.0.8.tar.gz (3.3 MB view hashes)

Uploaded Source

Built Distributions

fast_crossing-0.0.8-cp311-cp311-win_amd64.whl (267.6 kB view hashes)

Uploaded CPython 3.11 Windows x86-64

fast_crossing-0.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (347.3 kB view hashes)

Uploaded CPython 3.11 manylinux: glibc 2.17+ x86-64

fast_crossing-0.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (321.5 kB view hashes)

Uploaded CPython 3.11 manylinux: glibc 2.17+ ARM64

fast_crossing-0.0.8-cp311-cp311-macosx_11_0_arm64.whl (277.6 kB view hashes)

Uploaded CPython 3.11 macOS 11.0+ ARM64

fast_crossing-0.0.8-cp311-cp311-macosx_10_9_x86_64.whl (312.8 kB view hashes)

Uploaded CPython 3.11 macOS 10.9+ x86-64

fast_crossing-0.0.8-cp310-cp310-win_amd64.whl (267.7 kB view hashes)

Uploaded CPython 3.10 Windows x86-64

fast_crossing-0.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (347.2 kB view hashes)

Uploaded CPython 3.10 manylinux: glibc 2.17+ x86-64

fast_crossing-0.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (321.6 kB view hashes)

Uploaded CPython 3.10 manylinux: glibc 2.17+ ARM64

fast_crossing-0.0.8-cp310-cp310-macosx_11_0_arm64.whl (277.6 kB view hashes)

Uploaded CPython 3.10 macOS 11.0+ ARM64

fast_crossing-0.0.8-cp310-cp310-macosx_10_9_x86_64.whl (312.8 kB view hashes)

Uploaded CPython 3.10 macOS 10.9+ x86-64

fast_crossing-0.0.8-cp39-cp39-win_amd64.whl (267.9 kB view hashes)

Uploaded CPython 3.9 Windows x86-64

fast_crossing-0.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (347.6 kB view hashes)

Uploaded CPython 3.9 manylinux: glibc 2.17+ x86-64

fast_crossing-0.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (321.8 kB view hashes)

Uploaded CPython 3.9 manylinux: glibc 2.17+ ARM64

fast_crossing-0.0.8-cp39-cp39-macosx_11_0_arm64.whl (277.6 kB view hashes)

Uploaded CPython 3.9 macOS 11.0+ ARM64

fast_crossing-0.0.8-cp39-cp39-macosx_10_9_x86_64.whl (312.9 kB view hashes)

Uploaded CPython 3.9 macOS 10.9+ x86-64

fast_crossing-0.0.8-cp38-cp38-win_amd64.whl (267.7 kB view hashes)

Uploaded CPython 3.8 Windows x86-64

fast_crossing-0.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (347.2 kB view hashes)

Uploaded CPython 3.8 manylinux: glibc 2.17+ x86-64

fast_crossing-0.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (321.4 kB view hashes)

Uploaded CPython 3.8 manylinux: glibc 2.17+ ARM64

fast_crossing-0.0.8-cp38-cp38-macosx_11_0_arm64.whl (277.5 kB view hashes)

Uploaded CPython 3.8 macOS 11.0+ ARM64

fast_crossing-0.0.8-cp38-cp38-macosx_10_9_x86_64.whl (312.8 kB view hashes)

Uploaded CPython 3.8 macOS 10.9+ x86-64

fast_crossing-0.0.8-cp37-cp37m-win_amd64.whl (265.8 kB view hashes)

Uploaded CPython 3.7m Windows x86-64

fast_crossing-0.0.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (356.9 kB view hashes)

Uploaded CPython 3.7m manylinux: glibc 2.17+ x86-64

fast_crossing-0.0.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (333.3 kB view hashes)

Uploaded CPython 3.7m manylinux: glibc 2.17+ ARM64

fast_crossing-0.0.8-cp37-cp37m-macosx_10_9_x86_64.whl (306.7 kB view hashes)

Uploaded CPython 3.7m macOS 10.9+ x86-64

fast_crossing-0.0.8-cp36-cp36m-win_amd64.whl (265.8 kB view hashes)

Uploaded CPython 3.6m Windows x86-64

fast_crossing-0.0.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (356.7 kB view hashes)

Uploaded CPython 3.6m manylinux: glibc 2.17+ x86-64

fast_crossing-0.0.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (334.4 kB view hashes)

Uploaded CPython 3.6m manylinux: glibc 2.17+ ARM64

fast_crossing-0.0.8-cp36-cp36m-macosx_10_9_x86_64.whl (306.8 kB view hashes)

Uploaded CPython 3.6m macOS 10.9+ x86-64

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page