Skip to content

Commit

Permalink
fixes #1094 is_inside_polygon() function was incorrect
Browse files Browse the repository at this point in the history
Replaced by the is_point_in_polygon_2d() function,
which even has a fast Cython implementation.
  • Loading branch information
mozman committed May 30, 2024
1 parent 0263a9b commit 09628f8
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 29 deletions.
2 changes: 2 additions & 0 deletions notes/pages/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
- {{issue 1082}}
- BUGFIX: add reactors to entities in `GROUP`
- {{issue 1085}}
- BUGFIX: `clipping.is_inside_polygon()` function was incorrect
- {{issue 1094}}
-
- ## Version 1.3.0 - 2024-05-01
id:: 65e30c28-021e-4c24-ab6e-a9e9fa7c6a51
Expand Down
26 changes: 2 additions & 24 deletions src/ezdxf/math/clipping.py
Original file line number Diff line number Diff line change
Expand Up @@ -683,30 +683,8 @@ def next_vertex_node(v: _Node) -> _Node:


def is_inside_polygon(vertex: Vec2, polygon: GHPolygon) -> bool:
"""Returns ``True`` if `vertex` is inside `polygon` (odd-even rule).
This function calculates the "winding" number for a point, which
represents the number of times a ray emitted from the point to
infinity intersects any edge of the polygon.
An even winding number means the point lies OUTSIDE the polygon;
an odd number means it lies INSIDE it.
"""
winding_number: int = 0
infinity = Vec2(polygon.max_x * 2, vertex.y)
for node in polygon:
if not node.intersect:
if (
line_intersection(
vertex,
infinity,
node.vtx,
next_vertex_node(node.next).vtx,
)[0]
is not None
):
winding_number += 1
return bool(winding_number % 2)
"""Returns ``True`` if `vertex` is inside `polygon`."""
return is_point_in_polygon_2d(vertex, polygon.points, abs_tol=TOLERANCE) >= 0


_ERROR = None, 0, 0
Expand Down
102 changes: 97 additions & 5 deletions tests/test_06_math/test_619_greiner_hormann.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# License: MIT License

import pytest
from ezdxf.math import Vec2
from ezdxf.math import Vec2, BoundingBox2d
from ezdxf.math.clipping import (
greiner_hormann_union,
greiner_hormann_difference,
Expand All @@ -21,6 +21,7 @@ class TestLineIntersection:
polygons.
"""

def test_start_point_is_not_an_intersection_point(self):
s1, s2 = Vec2(1, 1), Vec2(3, 1)
c1, c2 = Vec2(2, 1), Vec2(2, 2)
Expand Down Expand Up @@ -71,8 +72,8 @@ def test_parallel_horizontal_lines_do_not_intersect(self):
assert line_intersection(s1, s2, c1, c2)[0] is None

def test_collinear_lines_do_not_intersect(self):
s1, s2 = Vec2(0, 0), Vec2(4, 4)
c1, c2 = Vec2(2, 2), Vec2(4, 0)
s1, s2 = Vec2(0, 0), Vec2(2, 2)
c1, c2 = Vec2(3, 3), Vec2(4, 4)
assert line_intersection(s1, s2, c1, c2)[0] is None

@pytest.mark.parametrize(
Expand Down Expand Up @@ -162,8 +163,7 @@ def test_overlapping_polygons_are_united(self, rect, overlapping):
assert set(UNION_OVERLAPPING) == set(result)

def test_vertex_order_is_not_important(self, rect, overlapping):
"""The vertex order (clockwise or counter clockwise) is not important.
"""
"""The vertex order (clockwise or counter clockwise) is not important."""
rect.reverse()
result = greiner_hormann_union(rect, overlapping)[0]
assert set(UNION_OVERLAPPING) == set(result)
Expand Down Expand Up @@ -280,5 +280,97 @@ def test_polygon_outside_polygon(self, rect, outside):
assert len(greiner_hormann_intersection(rect, outside)) == 0


DATA_1094 = [
Vec2.list(
[
[-18.900001889999952, 1450.7000002308125],
[-18.900001889999952, 1470.7351470734998],
[1278.90012789, 1470.7351470734998],
[1278.90012789, 1450.70001562087],
[1503.6001841952093, 1450.7000156208703],
[1503.6001841952093, 1388.3850000081884],
[1503.6001841952093, 1388.3850000081884],
[1647.3246376764234, 1388.3850000081884],
[1647.3246376764234, 1548.1475422037274],
[1831.0471719722357, 1548.1475422037274],
[1831.0471719722357, 1388.3850000081884],
[1831.0471719722357, 1388.3850000081884],
[1980.6216566984945, 1388.3850000081884],
[1980.6216566984945, 1224.6149999918116],
[1831.0471719722357, 1224.6149999918116],
[1831.0471719722357, 1160.3525263520064],
[1647.3246376764234, 1160.3525263520064],
[1647.3246376764234, 1160.3525263520064],
[1647.3246376764234, 1224.6149999918116],
[1647.3246376764234, 1224.6149999918116],
[1503.6001841952093, 1224.6149999918116],
[1503.6001841952093, 1162.2999997691875],
[1278.90012789, 1162.2999997691877],
[1278.90012789, 1162.2999997691877],
[1278.90012789, 286.7000156208702],
[1503.6001841952093, 286.70001562087026],
[1503.6001841952093, 224.3850000081885],
[1503.6001841952093, 224.3850000081885],
[1647.3246376764234, 224.3850000081885],
[1647.3246376764234, 384.1475422037275],
[1831.0471719722357, 384.1475422037275],
[1831.0471719722357, 224.3850000081885],
[1831.0471719722357, 224.3850000081885],
[1980.6216566984945, 224.3850000081885],
[1980.6216566984945, 60.61499999181149],
[1831.0471719722357, 60.614999991811544],
[1831.0471719722357, -3.6474736479937064],
[1647.3246376764234, -3.6474736479937064],
[1647.3246376764234, -3.6474736479937064],
[1647.3246376764234, 60.61499999181149],
[1647.3246376764234, 60.61499999181149],
[1503.6001841952093, 60.61499999181149],
[1503.6001841952093, -1.7000002308124635],
[1278.90012789, -1.7000002308124635],
[1278.90012789, -1.7000002308124635],
[1278.90012789, -21.735002173499993],
[-18.900001890000045, -21.735002173499993],
[-18.900001890000045, -21.735002173499993],
[-18.900001890000045, -1.7000156208705448],
[-243.6001841952094, -1.7000156208702606],
[-243.6001841952094, -1.7000156208702606],
[-243.6001841952094, 60.61499999181149],
[-243.6001841952094, 60.61499999181149],
[-237.7502641782885, 60.61499999181149],
[-237.7502641782885, 224.3850000081885],
[-243.6001841952094, 224.3850000081885],
[-243.6001841952094, 286.70000023081246],
[-18.900001890000027, 286.7000002308125],
[-18.900001890000027, 286.7000002308125],
[-18.90000188999997, 1162.2999843791297],
[-243.6001841952094, 1162.2999843791297],
[-243.6001841952094, 1162.2999843791297],
[-243.6001841952094, 1450.7000002308125],
[-18.900001889999952, 1450.7000002308125],
]
),
Vec2.list(
[
[-392.2502641782885, 1224.6149999918116],
[-237.7502641782885, 1224.6149999918116],
[-237.7502641782885, 1388.3850000081884],
[-392.2502641782885, 1388.3850000081884],
[-392.2502641782885, 1224.6149999918116],
]
),
]


def test_issue_1094_is_inside_polygon_function_was_incorrect():
data = DATA_1094
box_expected = BoundingBox2d(data[0] + data[1])
result = greiner_hormann_union(data[0], data[1])

assert len(result) == 1
box_result = BoundingBox2d(result[0])
assert box_result.extmin.isclose(box_expected.extmin)
assert box_result.extmax.isclose(box_expected.extmax)


if __name__ == "__main__":
pytest.main([__file__])

0 comments on commit 09628f8

Please sign in to comment.