Test Setup Failed
Push — master ( e5a2bf...9cd780 )
by Ken M.
53s
created

Line.intersect_with()   F

Complexity

Conditions 11

Size

Total Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
c 1
b 0
f 0
dl 0
loc 50
rs 3.375

How to fix   Complexity   

Complexity

Complex classes like Line.intersect_with() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
class Line(object):
2
    # Line class. mainly used for intersection check.
3
4
    def __init__(self, point1, point2):
5
        self.point1 = point1
6
        self.point2 = point2
7
8
    def intersect_with(self, another_line):
9
        """Check another_line intersect with itself or not.
10
11
        Args:
12
            another_line (Line): another line
13
14
        Returns:
15
            bool: return true when intersects
16
17
        """
18
19
        # Find the four orientations needed for general and special cases
20
        o1 = self.orientation(self.point1, self.point2, another_line.point1)
21
        o2 = self.orientation(self.point1, self.point2, another_line.point2)
22
        o3 = self.orientation(another_line.point1, another_line.point2,
23
                              self.point1)
24
        o4 = self.orientation(another_line.point1, another_line.point2,
25
                              self.point2)
26
27
        # General case
28
        if o1 != o2 and o3 != o4:
29
            return True
30
31
        # Special Cases
32
        # p1, q1 and p2 are colinear and p2 lies on segment p1q1
33
        if o1 == 0 and self.on_segment(self.point1,
34
                                       another_line.point1,
35
                                       self.point2):
36
            return True
37
38
        # p1, q1 and p2 are colinear and q2 lies on segment p1q1
39
        if o2 == 0 and self.on_segment(self.point1,
40
                                       another_line.point2,
41
                                       self.point2):
42
            return True
43
44
        # p2, q2 and p1 are colinear and p1 lies on segment p2q2
45
        if o3 == 0 and self.on_segment(another_line.point1,
46
                                       self.point1,
47
                                       another_line.point2):
48
            return True
49
50
        # p2, q2 and q1 are colinear and q1 lies on segment p2q2
51
        if o4 == 0 and self.on_segment(another_line.point1,
52
                                       self.point2,
53
                                       another_line.point2):
54
            return True
55
56
        # Doesn't fall in any of the above cases
57
        return False
58
59
    def on_segment(self, point1, point2, point3):
60
        """Check point q lies on line pr or not.
61
62
        Given three colinear points p, q, r
63
        the function checks if point q lies on line segment 'pr'.
64
65
        Args:
66
            point1 (Point): p
67
            point2 (Point): q
68
            point3 (Point): r
69
70
        Returns:
71
            bool: true when they are on segment
72
73
        """
74
75
        if (point2.x <= max(point1.x, point3.x)
76
                and point2.x >= min(point1.x, point3.x)
77
                and point2.y <= max(point1.y, point3.y)
78
                and point2.y >= min(point1.y, point3.y)):
79
            return True
80
        else:
81
            return False
82
83
    def orientation(self, point1, point2, point3):
84
        """To find orientation of ordered triplet (p, q, r).
85
86
        Args:
87
            point1 (Point): p
88
            point2 (Point): q
89
            point3 (Point): r
90
91
        Returns:
92
            int: 0 --> p, q and r are colinear,
93
                 1 --> Clockwise
94
                 2 --> Counterclockwise
95
96
        """
97
        val = (point2.y - point1.y) * (point3.x - point2.x) - \
98
            (point2.x - point1.x) * (point3.y - point2.y)
99
100
        if val == 0:
101
            return 0
102
        elif val > 0:
103
            return 1
104
        else:
105
            return 2
106
107
    def double_length(self):
108
        """Double the length of current line from point1 to point2.
109
110
        Returns:
111
            tuple: new end point coordinations
112
113
        """
114
        return (self.point1.x + 2 * (self.point2.x - self.point1.x),
115
                self.point1.y + 2 * (self.point2.y - self.point1.y))
116
117
118
class Point(object):
119
    # class for point, mainly used in Line class
120
121
    def __init__(self, x, y):
122
        self.x = x
123
        self.y = y
124
125
126
def is_inside(polygon, point):
127
    line2 = Line(Point(point[0], point[1]), Point(100, point[1]))
128
    intersections = 0
129
    point_pairs = [
130
        i for i in zip(polygon, polygon[1:])] + [(polygon[-1], polygon[0])]
131
    while point_pairs:
132
        # we draw polygon reversely
133
        i = point_pairs.pop()
134
        q1 = Point(i[0][0], i[0][1])
135
        p1 = Point(i[1][0], i[1][1])
136
        line1 = Line(p1, q1)
137
        if line1.intersect_with(line2):
138
            # double the length when find a intersection
139
            if point_pairs:
140
                point_pairs[-1] = (point_pairs[-1][0],
141
                                   line1.double_length())
142
            if line1.orientation(p1, Point(*point), q1) == 0:
143
                return line1.on_segment(p1, Point(*point), q1)
144
            intersections += 1
145
    if intersections % 2:
146
        return True
147
    else:
148
        return False
149
150
151
if __name__ == '__main__':  # pragma: no cover
152
    assert is_inside(((1, 1), (1, 3), (3, 3), (3, 1)),
153
                     (2, 2)) is True, "First"
154
    assert is_inside(((1, 1), (1, 3), (3, 3), (3, 1)),
155
                     (4, 2)) is False, "Second"
156
    assert is_inside(((1, 1), (4, 1), (2, 3)),
157
                     (3, 2)) is True, "Third"
158
    assert is_inside(((1, 1), (4, 1), (1, 3)),
159
                     (3, 3)) is False, "Fourth"
160
    assert is_inside(((2, 1), (4, 1), (5, 3), (3, 4), (1, 3)),
161
                     (4, 3)) is True, "Fifth"
162
    assert is_inside(((2, 1), (4, 1), (3, 2), (3, 4), (1, 3)),
163
                     (4, 3)) is False, "Sixth"
164
    assert is_inside(((1, 1), (3, 2), (5, 1), (4, 3), (5, 5),
165
                      (3, 4), (1, 5), (2, 3)),
166
                     (3, 3)) is True, "Seventh"
167
    assert is_inside(((1, 1), (1, 5), (5, 5), (5, 4), (2, 4),
168
                      (2, 2), (5, 2), (5, 1)),
169
                     (4, 3)) is False, "Eighth"
170