Skip to content

Commit 2cea09e

Browse files
test(LAB-2654): allow intersected geojson polygons if with order (#1638)
1 parent 32130d9 commit 2cea09e

5 files changed

Lines changed: 149 additions & 185 deletions

File tree

src/kili/utils/labels/geojson/polygon.py

Lines changed: 11 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -7,72 +7,9 @@
77
)
88

99

10-
def has_intersection(vertices):
11-
"""Returns a boolean to indicate if the vertices intersect each other."""
12-
n = len(vertices)
13-
14-
for i in range(n):
15-
x1, y1 = vertices[i]["x"], vertices[i]["y"]
16-
x2, y2 = vertices[(i + 1) % n]["x"], vertices[(i + 1) % n]["y"]
17-
18-
for j in range(i + 2, n):
19-
x3, y3 = vertices[j]["x"], vertices[j]["y"]
20-
x4, y4 = vertices[(j + 1) % n]["x"], vertices[(j + 1) % n]["y"]
21-
22-
if x1 == x4 and y1 == y4:
23-
continue
24-
25-
if do_edges_intersect(x1, y1, x2, y2, x3, y3, x4, y4):
26-
return True
27-
28-
return False
29-
30-
31-
def orientation(px, py, qx, qy, rx, ry):
32-
"""Returns a number to identify the orientation of the pair of edges.
33-
34-
0 is colinear, 1 is clockwise, -1 is counterclock.
35-
"""
36-
val = (qy - py) * (rx - qx) - (qx - px) * (ry - qy)
37-
if val == 0:
38-
return 0
39-
return 1 if val > 0 else -1
40-
41-
42-
def do_edges_intersect(x1, y1, x2, y2, x3, y3, x4, y4):
43-
"""Returns a boolean to indicate if the vertices identified 1 and 2 intersect with 3 and 4."""
44-
o1 = orientation(x1, y1, x2, y2, x3, y3)
45-
o2 = orientation(x1, y1, x2, y2, x4, y4)
46-
o3 = orientation(x3, y3, x4, y4, x1, y1)
47-
o4 = orientation(x3, y3, x4, y4, x2, y2)
48-
49-
# General case
50-
if o1 != o2 and o3 != o4:
51-
return True
52-
53-
# Special cases
54-
if o1 == 0 and on_segment(x1, y1, x3, y3, x2, y2):
55-
return True
56-
if o2 == 0 and on_segment(x1, y1, x4, y4, x2, y2):
57-
return True
58-
if o3 == 0 and on_segment(x3, y3, x1, y1, x4, y4):
59-
return True
60-
if o4 == 0 and on_segment(x3, y3, x2, y2, x4, y4):
61-
return True
62-
63-
return False
64-
65-
66-
def on_segment(px, py, qx, qy, rx, ry):
67-
"""Returns a boolean to indicate if the first vertex (q) is on the segment (pr)."""
68-
qy_not_on_segment = min(py, ry) <= qy <= max(py, ry)
69-
qx_not_on_segment = min(px, rx) <= qx <= max(px, rx)
70-
71-
return qy_not_on_segment and qx_not_on_segment
72-
73-
74-
def is_clockwise(vertices):
75-
"""Returns a boolean to indicate if the vertices are stored clockwise in the array.
10+
def get_oriented_area(vertices):
11+
"""Returns the area value which gives an indication on the vertices order.
12+
Positive if counter-clockwise, negative if clockwise.
7613
7714
This function uses the Shoelace formula :
7815
see also : https://en.wikipedia.org/wiki/Shoelace_formula
@@ -83,9 +20,9 @@ def is_clockwise(vertices):
8320
for i in range(n):
8421
x1, y1 = vertices[i]["x"], vertices[i]["y"]
8522
x2, y2 = vertices[(i + 1) % n]["x"], vertices[(i + 1) % n]["y"]
86-
sum_product += (x2 - x1) * (y2 + y1)
23+
sum_product += (x1 - x2) * (y2 + y1)
8724

88-
return sum_product > 0
25+
return sum_product
8926

9027

9128
def order_counter_clockwise(vertices):
@@ -95,15 +32,15 @@ def order_counter_clockwise(vertices):
9532
For more information on the order expected for GeoJson :
9633
https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6
9734
"""
98-
if has_intersection(vertices):
35+
order = get_oriented_area(vertices)
36+
if order < 0:
37+
vertices.reverse()
38+
elif order == 0:
9939
raise ConversionError(
100-
f"Polygon has edges intersection when looking at {vertices} and cannot be exported to \
101-
GeoJson format."
40+
f"Polygon order could not be identified as clockwise nor counter-clockwise because of \
41+
edges intersection in {vertices} and thus cannot be exported to GeoJson format."
10242
)
10343

104-
if is_clockwise(vertices):
105-
vertices.reverse()
106-
10744
return vertices
10845

10946

tests/unit/utils/labels/test_data/has_intersection_test_cases.py

Lines changed: 0 additions & 86 deletions
This file was deleted.

tests/unit/utils/labels/test_data/kili_polygon_to_geojson_polygon_test_cases.py

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,129 @@
11
test_cases = [
22
(
33
"test_case_1",
4+
[
5+
{"x": 0, "y": 0},
6+
{"x": 1, "y": 0},
7+
{"x": 1, "y": 1},
8+
{"x": 0, "y": 1},
9+
],
10+
{
11+
"coordinates": [[[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]],
12+
"type": "Polygon",
13+
},
14+
),
15+
(
16+
"test_case_2",
17+
[
18+
{"x": 0, "y": 0},
19+
{"x": 0, "y": 1},
20+
{"x": 1, "y": 1},
21+
{"x": 1, "y": 0},
22+
],
23+
{
24+
"coordinates": [[[1, 0], [1, 1], [0, 1], [0, 0], [1, 0]]],
25+
"type": "Polygon",
26+
},
27+
),
28+
(
29+
"test_case_3",
30+
[
31+
{"x": 0, "y": 0},
32+
{"x": 0, "y": 2},
33+
{"x": 1, "y": 1},
34+
{"x": 2, "y": 2},
35+
{"x": 2, "y": 0},
36+
],
37+
{
38+
"coordinates": [[[2, 0], [2, 2], [1, 1], [0, 2], [0, 0], [2, 0]]],
39+
"type": "Polygon",
40+
},
41+
),
42+
(
43+
"test_case_4",
44+
[
45+
{"x": 0, "y": 0},
46+
{"x": 0, "y": 2},
47+
{"x": 2, "y": 2},
48+
{"x": 1, "y": 0},
49+
{"x": 0, "y": 1},
50+
],
51+
{
52+
"coordinates": [[[0, 1], [1, 0], [2, 2], [0, 2], [0, 0], [0, 1]]],
53+
"type": "Polygon",
54+
},
55+
),
56+
(
57+
"test_case_5",
58+
[
59+
{"x": 0, "y": 1},
60+
{"x": 1, "y": 0},
61+
{"x": 2, "y": 2},
62+
{"x": 0, "y": 2},
63+
{"x": 0, "y": 0},
64+
],
65+
{
66+
"coordinates": [[[0, 1], [1, 0], [2, 2], [0, 2], [0, 0], [0, 1]]],
67+
"type": "Polygon",
68+
},
69+
),
70+
(
71+
"test_case_6",
72+
[
73+
{"x": 0, "y": 0},
74+
{"x": 2, "y": 2},
75+
{"x": 2, "y": 0},
76+
{"x": 1, "y": 0},
77+
{"x": 0, "y": 2},
78+
],
79+
{
80+
"coordinates": [[[0, 2], [1, 0], [2, 0], [2, 2], [0, 0], [0, 2]]],
81+
"type": "Polygon",
82+
},
83+
),
84+
(
85+
"test_case_7",
86+
[
87+
{"x": 0, "y": 0},
88+
{"x": 2, "y": 0},
89+
{"x": 2, "y": 2},
90+
{"x": 0, "y": 1},
91+
{"x": 0, "y": 2},
92+
],
93+
{
94+
"coordinates": [[[0, 0], [2, 0], [2, 2], [0, 1], [0, 2], [0, 0]]],
95+
"type": "Polygon",
96+
},
97+
),
98+
(
99+
"test_case_8",
100+
[
101+
{"x": -68.40459303313284, "y": -23.5274667659679},
102+
{"x": -68.40651350514116, "y": -23.52735316457091},
103+
{"x": -68.40645155443121, "y": -23.52536512424942},
104+
{"x": -68.40527449094223, "y": -23.52536512424942},
105+
{"x": -68.40477888526267, "y": -23.52536512424942},
106+
{"x": -68.40446913171293, "y": -23.52536512424942},
107+
{"x": -68.40459303313284, "y": -23.525308322656073},
108+
],
109+
{
110+
"coordinates": [
111+
[
112+
[-68.40459303313284, -23.525308322656073],
113+
[-68.40446913171293, -23.52536512424942],
114+
[-68.40477888526267, -23.52536512424942],
115+
[-68.40527449094223, -23.52536512424942],
116+
[-68.40645155443121, -23.52536512424942],
117+
[-68.40651350514116, -23.52735316457091],
118+
[-68.40459303313284, -23.5274667659679],
119+
[-68.40459303313284, -23.525308322656073],
120+
]
121+
],
122+
"type": "Polygon",
123+
},
124+
),
125+
(
126+
"test_case_9",
4127
[
5128
{"x": 4.4310542897769665, "y": 52.19847958268726},
6129
{"x": 4.424421731366076, "y": 52.202545578240006},
@@ -37,5 +160,5 @@
37160
],
38161
"type": "Polygon",
39162
},
40-
)
163+
),
41164
]

tests/unit/utils/labels/test_data/kili_polygon_to_geojson_polygon_test_error_cases.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,18 @@
2727
"Cross segments #3",
2828
[
2929
{"x": 0, "y": 0},
30-
{"x": 2, "y": 2},
31-
{"x": 2, "y": 0},
30+
{"x": 0, "y": 1},
3231
{"x": 1, "y": 0},
33-
{"x": 0, "y": 2},
32+
{"x": 1, "y": 1},
3433
],
3534
ConversionError,
3635
),
3736
(
3837
"Cross segments #4",
3938
[
4039
{"x": 0, "y": 0},
41-
{"x": 2, "y": 0},
4240
{"x": 2, "y": 2},
43-
{"x": 0, "y": 1},
41+
{"x": 2, "y": 0},
4442
{"x": 0, "y": 2},
4543
],
4644
ConversionError,
@@ -49,10 +47,19 @@
4947
"Cross segments #5",
5048
[
5149
{"x": 0, "y": 0},
50+
{"x": 2, "y": 0},
5251
{"x": 0, "y": 2},
5352
{"x": 2, "y": 2},
54-
{"x": 1, "y": 0},
55-
{"x": 0, "y": 1},
53+
],
54+
ConversionError,
55+
),
56+
(
57+
"Cross segments #6",
58+
[
59+
{"x": 0, "y": 0},
60+
{"x": 0, "y": 2},
61+
{"x": 2, "y": 0},
62+
{"x": 2, "y": 2},
5663
],
5764
ConversionError,
5865
),

0 commit comments

Comments
 (0)