File size: 3,969 Bytes
9f084b2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"""Tests for geometry/polygon.py — polygon operations."""

from __future__ import annotations

import pytest
from src.app.geometry import polygon


class TestPolygonToBbox:
    def test_rectangle(self) -> None:
        poly = [(100, 200), (400, 200), (400, 250), (100, 250)]
        result = polygon.polygon_to_bbox(poly)
        assert result == (100, 200, 300, 50)

    def test_triangle(self) -> None:
        poly = [(0, 0), (100, 0), (50, 80)]
        result = polygon.polygon_to_bbox(poly)
        assert result == (0, 0, 100, 80)

    def test_too_few_points(self) -> None:
        with pytest.raises(ValueError, match="at least 3"):
            polygon.polygon_to_bbox([(0, 0), (1, 1)])


class TestBboxToPolygon:
    def test_rectangle(self) -> None:
        result = polygon.bbox_to_polygon((10, 20, 100, 50))
        assert result == [(10, 20), (110, 20), (110, 70), (10, 70)]

    def test_roundtrip(self) -> None:
        original = (10, 20, 100, 50)
        poly = polygon.bbox_to_polygon(original)
        back = polygon.polygon_to_bbox(poly)
        assert back == original


class TestPolygonArea:
    def test_unit_square(self) -> None:
        poly = [(0, 0), (1, 0), (1, 1), (0, 1)]
        assert polygon.polygon_area(poly) == pytest.approx(1.0)

    def test_rectangle(self) -> None:
        poly = [(0, 0), (10, 0), (10, 5), (0, 5)]
        assert polygon.polygon_area(poly) == pytest.approx(50.0)

    def test_triangle(self) -> None:
        poly = [(0, 0), (10, 0), (5, 8)]
        assert polygon.polygon_area(poly) == pytest.approx(40.0)

    def test_too_few_points(self) -> None:
        assert polygon.polygon_area([(0, 0)]) == 0.0


class TestPolygonCentroid:
    def test_square(self) -> None:
        poly = [(0, 0), (10, 0), (10, 10), (0, 10)]
        cx, cy = polygon.polygon_centroid(poly)
        assert cx == pytest.approx(5.0)
        assert cy == pytest.approx(5.0)

    def test_too_few(self) -> None:
        with pytest.raises(ValueError, match="at least 3"):
            polygon.polygon_centroid([(0, 0)])


class TestValidatePolygon:
    def test_valid_polygon(self) -> None:
        poly = [(0, 0), (10, 0), (10, 10), (0, 10)]
        assert polygon.validate_polygon(poly) == []

    def test_too_few_points(self) -> None:
        warnings = polygon.validate_polygon([(0, 0)])
        assert any("fewer than 3" in w for w in warnings)

    def test_duplicate_consecutive(self) -> None:
        poly = [(0, 0), (10, 0), (10, 0), (0, 10)]
        warnings = polygon.validate_polygon(poly)
        assert any("Duplicate" in w for w in warnings)

    def test_negative_coords(self) -> None:
        poly = [(-5, 0), (10, 0), (10, 10)]
        warnings = polygon.validate_polygon(poly)
        assert any("Negative" in w for w in warnings)

    def test_degenerate_line(self) -> None:
        poly = [(0, 0), (10, 0), (20, 0)]
        warnings = polygon.validate_polygon(poly)
        assert any("zero area" in w for w in warnings)


class TestClockwise:
    """In screen coordinates (y-axis down), the signed-area formula gives
    positive for CW when vertices go right-then-down (like (0,0)→(0,10)→(10,10)→(10,0)).
    """

    def test_clockwise(self) -> None:
        # In screen coords: left-down-right-up = clockwise
        cw = [(0, 0), (0, 10), (10, 10), (10, 0)]
        assert polygon.is_clockwise(cw) is True

    def test_counter_clockwise(self) -> None:
        # In screen coords: right-down-left-up = counter-clockwise
        ccw = [(0, 0), (10, 0), (10, 10), (0, 10)]
        assert polygon.is_clockwise(ccw) is False

    def test_ensure_clockwise_already(self) -> None:
        cw = [(0, 0), (0, 10), (10, 10), (10, 0)]
        result = polygon.ensure_clockwise(cw)
        assert result == cw

    def test_ensure_clockwise_reverses(self) -> None:
        ccw = [(0, 0), (10, 0), (10, 10), (0, 10)]
        result = polygon.ensure_clockwise(ccw)
        assert polygon.is_clockwise(result) is True