Skip to content

Commit c31409c

Browse files
district10TANG ZHIXIONG
andauthored
Tzx/0122 (#5)
* lint code * update * there is a bug * fix, sync code to headers * update docs, ready to release Co-authored-by: TANG ZHIXIONG <dvora4tzx@gmail.com>
1 parent d131605 commit c31409c

File tree

8 files changed

+267
-22
lines changed

8 files changed

+267
-22
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
2020
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
2121
endif()
2222

23-
include_directories(BEFORE ${PROJECT_SOURCE_DIR}/headers/include
23+
include_directories(${PROJECT_SOURCE_DIR}/headers/include
2424
${PROJECT_SOURCE_DIR}/headers/include/cubao)
2525

2626
set(CMAKE_CXX_STANDARD 17)

Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ tar.gz:
7777
tar -cvz --exclude .git -f ../fast_crossing.tar.gz .
7878
ls -alh ../fast_crossing.tar.gz
7979

80+
81+
SYNC_OUTPUT_DIR := headers/include/cubao
82+
sync_headers:
83+
cp src/fast_crossing.hpp $(SYNC_OUTPUT_DIR)
84+
cp src/pybind11_fast_crossing.hpp $(SYNC_OUTPUT_DIR)
85+
cp src/pybind11_flatbush.hpp $(SYNC_OUTPUT_DIR)
86+
8087
# https://stackoverflow.com/a/25817631
8188
echo-% : ; @echo -n $($*)
8289
Echo-% : ; @echo $($*)

README.md

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,209 @@
11
# 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+
```

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def build_extension(self, ext):
124124
# logic and declaration, and simpler if you include description/version in a file.
125125
setup(
126126
name="fast_crossing",
127-
version="0.0.1",
127+
version="0.0.2",
128128
author="tzx",
129129
author_email="dvorak4tzx@gmail.com",
130130
url="https://github.com/cubao/fast-crossing",

src/fast_crossing.hpp

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,13 @@ struct FastCrossing
141141
label1, label2);
142142
}
143143
std::sort(ret.begin(), ret.end(), [](const auto &t1, const auto &t2) {
144-
return std::get<1>(t1)[0] < std::get<1>(t2)[0];
144+
double tt1 = std::get<1>(t1)[0];
145+
double tt2 = std::get<1>(t2)[0];
146+
if (tt1 != tt2) {
147+
return tt1 < tt2;
148+
}
149+
return std::make_tuple(std::get<3>(t1)[0], std::get<3>(t1)[1]) <
150+
std::make_tuple(std::get<3>(t2)[0], std::get<3>(t2)[1]);
145151
});
146152
if (dedup) {
147153
auto last = std::unique(
@@ -150,7 +156,8 @@ struct FastCrossing
150156
if (std::get<3>(t1)[0] != std::get<3>(t2)[0]) {
151157
return false;
152158
}
153-
return std::get<3>(t1) == std::get<3>(t2) ||
159+
return (std::get<3>(t1)[1] == std::get<3>(t2)[1] &&
160+
std::get<1>(t1)[1] == std::get<1>(t2)[1]) ||
154161
((std::get<3>(t1)[1] + 1) == std::get<3>(t2)[1] &&
155162
std::get<1>(t1)[1] == 1.0 &&
156163
std::get<1>(t2)[1] == 0.0) ||
@@ -187,13 +194,16 @@ struct FastCrossing
187194
auto &curr = hit.front();
188195
auto &prev_label = std::get<3>(prev);
189196
auto &curr_label = std::get<3>(curr);
190-
if (prev_label[0] == curr_label[0] &&
191-
(((prev_label[1] + 1) == curr_label[1] &&
192-
std::get<2>(prev)[1] == 1.0 &&
193-
std::get<2>(curr)[1] == 0.0) ||
194-
((prev_label[1] - 1) == curr_label[1] &&
195-
std::get<2>(prev)[1] == 0.0 &&
196-
std::get<2>(curr)[1] == 1.0))) {
197+
if ((prev_label == curr_label &&
198+
std::get<1>(prev)[1] == std::get<1>(curr)[1]) ||
199+
(prev_label[0] == curr_label[0] &&
200+
(prev_label[1] + 1) == curr_label[1] &&
201+
std::get<1>(prev)[1] == 1.0 &&
202+
std::get<1>(curr)[1] == 0.0) ||
203+
(prev_label[0] == curr_label[0] &&
204+
(prev_label[1] - 1) == curr_label[1] &&
205+
std::get<1>(prev)[1] == 0.0 &&
206+
std::get<1>(curr)[1] == 1.0)) {
197207
has_dup = true;
198208
}
199209
}
@@ -202,13 +212,14 @@ struct FastCrossing
202212
return ret;
203213
}
204214

205-
std::vector<IntersectionType> intersections(
206-
const Eigen::Ref<const FlatBush::PolylineType> &polyline) const
215+
std::vector<IntersectionType>
216+
intersections(const Eigen::Ref<const FlatBush::PolylineType> &polyline,
217+
bool dedup = true) const
207218
{
208219
PolylineType Nx3(polyline.rows(), 3);
209220
Nx3.leftCols<2>() = polyline;
210221
Nx3.col(2).setZero();
211-
return intersections(Nx3, true);
222+
return intersections(Nx3, dedup);
212223
}
213224

214225
static Eigen::Vector3d coordinates(const RowVectors &xyzs, int seg_index,

src/pybind11_fast_crossing.hpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,12 @@ CUBAO_INLINE void bind_fast_crossing(py::module &m)
6363
&FastCrossing::intersections, py::const_),
6464
"polyline"_a, py::kw_only(), "dedup"_a = true,
6565
"crossing intersections with polyline (sorted by t ratio)")
66-
.def(
67-
"intersections",
68-
py::overload_cast<
69-
const Eigen::Ref<const FastCrossing::FlatBush::PolylineType> &>(
70-
&FastCrossing::intersections, py::const_),
71-
"polyline"_a,
72-
"crossing intersections with polyline (sorted by t ratio)")
66+
.def("intersections",
67+
py::overload_cast<
68+
const Eigen::Ref<const FastCrossing::FlatBush::PolylineType> &,
69+
bool>(&FastCrossing::intersections, py::const_),
70+
"polyline"_a, py::kw_only(), "dedup"_a = true,
71+
"crossing intersections with polyline (sorted by t ratio)")
7372
.def("intersections",
7473
py::overload_cast<const FastCrossing::PolylineType &, double,
7574
double, bool>(&FastCrossing::intersections,

tests/test_basic.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,23 @@ def test_fast_crossing_filter_by_z():
155155
assert len(ret) == 2
156156
assert fc.coordinates(ret[0])[2] == 10
157157
assert fc.coordinates(ret[1])[2] == 20
158+
159+
160+
def test_fast_crossing_dedup():
161+
# should be stable
162+
for _ in range(100):
163+
fc = FastCrossing()
164+
fc.add_polyline([[0, 0, 0], [1, 0, 0], [2, 0, 0]])
165+
fc.add_polyline([[0, 1, 0], [1, 1, 0], [2, 1, 0]])
166+
167+
ret = fc.intersections([[1, -1], [1, 1]])
168+
assert len(ret) == 2
169+
assert np.all(ret[0][-1] == [0, 0]), ret
170+
assert np.all(ret[1][-1] == [1, 0]), ret
171+
assert ret[0][1][1] == 1.0, ret
172+
assert ret[1][1][1] == 1.0, ret
173+
174+
ret = fc.intersections([[1, -1], [1, 1]], dedup=False)
175+
# for idx, row in enumerate(ret):
176+
# print(idx, row)
177+
assert len(ret) == 4

0 commit comments

Comments
 (0)