|
1 | 1 | # fast crossing
|
| 2 | + |
| 3 | +## Installation |
| 4 | + |
| 5 | +### via pip |
| 6 | + |
| 7 | +``` |
| 8 | +pip install fast-crossing |
| 9 | +``` |
| 10 | + |
| 11 | +### from source |
| 12 | + |
| 13 | +```bash |
| 14 | +git clone --recursive https://github.com/cubao/fast-crossing |
| 15 | +pip install ./fast-crossing |
| 16 | +``` |
| 17 | + |
| 18 | +Or |
| 19 | + |
| 20 | +``` |
| 21 | +pip install git+https://github.com/cubao/fast-crossing.git |
| 22 | +``` |
| 23 | + |
| 24 | +(you can build wheels for later reuse by ` pip wheel git+https://github.com/cubao/fast-crossing.git`) |
| 25 | + |
| 26 | +## Usage & Tests |
| 27 | + |
| 28 | +See [`tests/test_basic.py`](tests/test_basic.py): |
| 29 | + |
| 30 | +```python |
| 31 | +import numpy as np |
| 32 | +import pytest |
| 33 | +from fast_crossing import FastCrossing |
| 34 | + |
| 35 | + |
| 36 | +def test_fast_crossing(): |
| 37 | + fc = FastCrossing() |
| 38 | + |
| 39 | + # add your polylines |
| 40 | + """ |
| 41 | + 2 C |
| 42 | + | |
| 43 | + 1 D |
| 44 | + 0 | 5 |
| 45 | + A---------------o------------------B |
| 46 | + | |
| 47 | + | |
| 48 | + -2 E |
| 49 | + """ |
| 50 | + fc.add_polyline(np.array([[0.0, 0.0], [5.0, 0.0]])) # AB |
| 51 | + fc.add_polyline(np.array([[2.5, 2.0], [2.5, 1.0], [2.5, -2.0]])) # CDE |
| 52 | + |
| 53 | + # build index |
| 54 | + fc.finish() |
| 55 | + |
| 56 | + # num_poylines |
| 57 | + assert 2 == fc.num_poylines() |
| 58 | + rulers = fc.polyline_rulers() |
| 59 | + assert len(rulers) == 2 |
| 60 | + ruler0 = fc.polyline_ruler(0) |
| 61 | + ruler1 = fc.polyline_ruler(1) |
| 62 | + assert not ruler0.is_wgs84() |
| 63 | + assert not ruler1.is_wgs84() |
| 64 | + assert ruler0.length() == 5 |
| 65 | + assert ruler1.length() == 4 |
| 66 | + assert fc.polyline_ruler(10) is None |
| 67 | + |
| 68 | + # intersections |
| 69 | + ret = fc.intersections([1.5, 0], [3.5, 2]) |
| 70 | + assert len(ret) == 2 |
| 71 | + assert np.linalg.norm(fc.coordinates(ret[0]) - [1.5, 0, 0]) < 1e-15 |
| 72 | + assert np.linalg.norm(fc.coordinates(ret[1]) - [2.5, 1, 0]) < 1e-15 |
| 73 | + xyz = fc.coordinates(0, 0, 0.2) |
| 74 | + assert np.linalg.norm(xyz - [1.0, 0, 0]) < 1e-15 |
| 75 | + with pytest.raises(IndexError) as excinfo: |
| 76 | + xyz = fc.coordinates(2, 0, 0.5) |
| 77 | + assert "map::at" in str(excinfo) |
| 78 | + |
| 79 | + # query all line segment intersections |
| 80 | + # [ |
| 81 | + # (array([2.5, 0. ]), |
| 82 | + # array([0.5 , 0.33333333]), |
| 83 | + # array([0, 0], dtype=int32), |
| 84 | + # array([1, 1], dtype=int32)) |
| 85 | + # ] |
| 86 | + ret = fc.intersections() |
| 87 | + # print(ret) |
| 88 | + assert len(ret) == 1 |
| 89 | + for xy, ts, label1, label2 in ret: |
| 90 | + # xy 是交点,即图中的 o 点坐标 |
| 91 | + # t,s 是分位点,(0.5, 0.33) |
| 92 | + # 0.5 -> o 在 AB 1/2 处 |
| 93 | + # 0.33 -> o 在 DE 1/3 处 |
| 94 | + # label1 是 line segment 索引,(polyline_index, point_index) |
| 95 | + # e.g. (0, 0),polyline AB 的第一段 |
| 96 | + # label2 是另一个条 line seg 的索引 |
| 97 | + # e.g. (1, 1),polyline CDE 的第二段(DE 段) |
| 98 | + # print(xy) |
| 99 | + # print(ts) |
| 100 | + # print(label1) |
| 101 | + # print(label2) |
| 102 | + assert np.all(xy == [2.5, 0]) |
| 103 | + assert np.all(ts == [0.5, 1 / 3.0]) |
| 104 | + assert np.all(label1 == [0, 0]) |
| 105 | + assert np.all(label2 == [1, 1]) |
| 106 | + |
| 107 | + # query intersections against provided polyline |
| 108 | + polyline = np.array([[-6.0, -1.0], [-5.0, 1.0], [5.0, -1.0]]) |
| 109 | + ret = fc.intersections(polyline) |
| 110 | + ret = np.array(ret) # 还是转化成 numpy 比较好用 |
| 111 | + xy = ret[:, 0] # 直接取出所有交点 |
| 112 | + ts = ret[:, 1] # 所有分位点 |
| 113 | + label1 = ret[:, 2] # 所有 label1(当前 polyline 的 label) |
| 114 | + label2 = ret[:, 3] # tree 中 line segs 的 label |
| 115 | + # print(ret, xy, ts, label1, label2) |
| 116 | + assert np.all(xy[0] == [0, 0]) |
| 117 | + assert np.all(xy[1] == [2.5, -0.5]) |
| 118 | + assert np.all(ts[0] == [0.5, 0]) |
| 119 | + assert np.all(ts[1] == [0.75, 0.5]) |
| 120 | + assert np.all(label1 == [[0, 1], [0, 1]]) |
| 121 | + assert np.all(label2 == [[0, 0], [1, 1]]) |
| 122 | + |
| 123 | + polyline2 = np.column_stack((polyline, np.zeros(len(polyline)))) |
| 124 | + ret2 = np.array(fc.intersections(polyline2[:, :2])) |
| 125 | + assert str(ret) == str(ret2) |
| 126 | + |
| 127 | + |
| 128 | +def test_fast_crossing_intersection3d(): |
| 129 | + fc = FastCrossing() |
| 130 | + """ |
| 131 | + 2 C |
| 132 | + | |
| 133 | + 1 D |
| 134 | + 0 | 5 |
| 135 | + A---------------o------------------B |
| 136 | + | |
| 137 | + | |
| 138 | + -2 E |
| 139 | + """ |
| 140 | + fc.add_polyline(np.array([[0.0, 0.0, 0.0], [5.0, 0.0, 100]])) # AB |
| 141 | + fc.add_polyline(np.array([[2.5, 2.0, 0.0], [2.5, 1.0, 100], [2.5, -2.0, 0]])) # CDE |
| 142 | + fc.finish() |
| 143 | + ret = fc.intersections() |
| 144 | + assert len(ret) == 1 |
| 145 | + ret = ret[0] |
| 146 | + xyz1 = fc.coordinates(ret, second=False) |
| 147 | + xyz2 = fc.coordinates(ret) |
| 148 | + assert np.linalg.norm(xyz1 - [2.5, 0, 50]) < 1e-10 |
| 149 | + assert np.linalg.norm(xyz2 - [2.5, 0, 2 / 3 * 100.0]) < 1e-10 |
| 150 | + |
| 151 | + |
| 152 | +def test_fast_crossing_auto_rebuild_flatbush(): |
| 153 | + fc = FastCrossing() |
| 154 | + fc.add_polyline(np.array([[0.0, 0.0, 0.0], [5.0, 0.0, 100]])) # AB |
| 155 | + fc.add_polyline(np.array([[2.5, 2.0, 0.0], [2.5, 1.0, 100], [2.5, -2.0, 0]])) # CDE |
| 156 | + ret = fc.intersections() |
| 157 | + assert len(ret) == 1 |
| 158 | + |
| 159 | + fc.add_polyline([[1.5, 0], [3.5, 2]]) |
| 160 | + ret = fc.intersections() |
| 161 | + assert len(ret) == 4 # should dedup to 3? |
| 162 | + |
| 163 | + |
| 164 | +def test_fast_crossing_filter_by_z(): |
| 165 | + fc = FastCrossing() |
| 166 | + fc.add_polyline([[0, 0, 0], [1, 0, 0]]) |
| 167 | + fc.add_polyline([[0, 0, 10], [1, 0, 10]]) |
| 168 | + fc.add_polyline([[0, 0, 20], [1, 0, 20]]) |
| 169 | + ret = fc.intersections([[0.5, -1], [0.5, 1]]) |
| 170 | + assert len(ret) == 3 |
| 171 | + |
| 172 | + ret = fc.intersections([[0.5, -1], [0.5, 1]], z_min=-1, z_max=1) |
| 173 | + assert len(ret) == 1 |
| 174 | + assert fc.coordinates(ret[0])[2] == 0 |
| 175 | + |
| 176 | + ret = fc.intersections([[0.5, -1, 10], [0.5, 1, 10]], z_min=-1, z_max=1) |
| 177 | + assert len(ret) == 1 |
| 178 | + assert fc.coordinates(ret[0])[2] == 10 |
| 179 | + |
| 180 | + ret = fc.intersections([[0.5, -1, 20], [0.5, 1, 20]], z_min=-1, z_max=1) |
| 181 | + assert len(ret) == 1 |
| 182 | + assert fc.coordinates(ret[0])[2] == 20 |
| 183 | + |
| 184 | + ret = fc.intersections([[0.5, -1, 15], [0.5, 1, 15]], z_min=-6, z_max=6) |
| 185 | + assert len(ret) == 2 |
| 186 | + assert fc.coordinates(ret[0])[2] == 10 |
| 187 | + assert fc.coordinates(ret[1])[2] == 20 |
| 188 | + |
| 189 | + |
| 190 | +def test_fast_crossing_dedup(): |
| 191 | + # should be stable |
| 192 | + for _ in range(100): |
| 193 | + fc = FastCrossing() |
| 194 | + fc.add_polyline([[0, 0, 0], [1, 0, 0], [2, 0, 0]]) |
| 195 | + fc.add_polyline([[0, 1, 0], [1, 1, 0], [2, 1, 0]]) |
| 196 | + |
| 197 | + ret = fc.intersections([[1, -1], [1, 1]]) |
| 198 | + assert len(ret) == 2 |
| 199 | + assert np.all(ret[0][-1] == [0, 0]), ret |
| 200 | + assert np.all(ret[1][-1] == [1, 0]), ret |
| 201 | + assert ret[0][1][1] == 1.0, ret |
| 202 | + assert ret[1][1][1] == 1.0, ret |
| 203 | + |
| 204 | + ret = fc.intersections([[1, -1], [1, 1]], dedup=False) |
| 205 | + # for idx, row in enumerate(ret): |
| 206 | + # print(idx, row) |
| 207 | + assert len(ret) == 4 |
| 208 | + |
| 209 | +``` |
0 commit comments