• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

morganjwilliams / pyrolite / 17569160869

09 Sep 2025 01:41AM UTC coverage: 91.465% (-0.1%) from 91.614%
17569160869

push

github

morganjwilliams
Add uncertainties, add optional deps for pyproject.toml; WIP demo NB

6226 of 6807 relevant lines covered (91.46%)

10.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.68
/pyrolite/util/plot/center.py
1
"""
2
Functions for calculating the visual center of a polygon.
3
Taken from https://github.com/Twista/python-polylabel,
4
Originally released under an MIT licence.
5
"""
6

7
from math import sqrt, inf
12✔
8
import time
12✔
9
from queue import PriorityQueue
12✔
10

11

12
def _point_to_polygon_distance(x, y, polygon):
12✔
13
    inside = False
12✔
14
    min_dist_sq = inf
12✔
15

16
    for ring in polygon:
12✔
17
        b = ring[-1]
12✔
18
        for a in ring:
12✔
19
            if (a[1] > y) != (b[1] > y) and (
12✔
20
                x < (b[0] - a[0]) * (y - a[1]) / (b[1] - a[1]) + a[0]
21
            ):
22
                inside = not inside
12✔
23

24
            min_dist_sq = min(min_dist_sq, _get_seg_dist_sq(x, y, a, b))
12✔
25
            b = a
12✔
26

27
    result = sqrt(min_dist_sq)
12✔
28
    if not inside:
12✔
29
        return -result
12✔
30
    return result
12✔
31

32

33
def _get_seg_dist_sq(px, py, a, b):
12✔
34
    x = a[0]
12✔
35
    y = a[1]
12✔
36
    dx = b[0] - x
12✔
37
    dy = b[1] - y
12✔
38

39
    if dx != 0 or dy != 0:
12✔
40
        t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy)
12✔
41

42
        if t > 1:
12✔
43
            x = b[0]
12✔
44
            y = b[1]
12✔
45

46
        elif t > 0:
12✔
47
            x += dx * t
12✔
48
            y += dy * t
12✔
49

50
    dx = px - x
12✔
51
    dy = py - y
12✔
52

53
    return dx * dx + dy * dy
12✔
54

55

56
class Cell(object):
12✔
57
    def __init__(self, x, y, h, polygon):
12✔
58
        self.h = h
12✔
59
        self.y = y
12✔
60
        self.x = x
12✔
61
        self.d = _point_to_polygon_distance(x, y, polygon)
12✔
62
        self.max = self.d + self.h * sqrt(2)
12✔
63

64
    def __lt__(self, other):
12✔
65
        return self.max < other.max
×
66

67
    def __lte__(self, other):
12✔
68
        return self.max <= other.max
×
69

70
    def __gt__(self, other):
12✔
71
        return self.max > other.max
×
72

73
    def __gte__(self, other):
12✔
74
        return self.max >= other.max
×
75

76
    def __eq__(self, other):
12✔
77
        return self.max == other.max
4✔
78

79

80
def _get_centroid_cell(polygon):
12✔
81
    area = 0
12✔
82
    x = 0
12✔
83
    y = 0
12✔
84
    points = polygon[0]
12✔
85
    b = points[-1]  # prev
12✔
86
    for a in points:
12✔
87
        f = a[0] * b[1] - b[0] * a[1]
12✔
88
        x += (a[0] + b[0]) * f
12✔
89
        y += (a[1] + b[1]) * f
12✔
90
        area += f * 3
12✔
91
        b = a
12✔
92
    if area == 0:
12✔
93
        return Cell(points[0][0], points[0][1], 0, polygon)
×
94
    return Cell(x / area, y / area, 0, polygon)
12✔
95

96
    pass
97

98

99
def visual_center(polygon, precision=1.0, debug=False, with_distance=False):
12✔
100
    # find bounding box
101
    first_item = polygon[0][0]
12✔
102
    min_x = first_item[0]
12✔
103
    min_y = first_item[1]
12✔
104
    max_x = first_item[0]
12✔
105
    max_y = first_item[1]
12✔
106
    for p in polygon[0]:
12✔
107
        if p[0] < min_x:
12✔
108
            min_x = p[0]
×
109
        if p[1] < min_y:
12✔
110
            min_y = p[1]
12✔
111
        if p[0] > max_x:
12✔
112
            max_x = p[0]
12✔
113
        if p[1] > max_y:
12✔
114
            max_y = p[1]
12✔
115

116
    width = max_x - min_x
12✔
117
    height = max_y - min_y
12✔
118
    cell_size = min(width, height)
12✔
119
    h = cell_size / 2.0
12✔
120

121
    cell_queue = PriorityQueue()
12✔
122

123
    if cell_size == 0:
12✔
124
        if with_distance:
×
125
            return [min_x, min_y], None
×
126
        else:
127
            return [min_x, min_y]
×
128

129
    # cover polygon with initial cells
130
    x = min_x
12✔
131
    while x < max_x:
12✔
132
        y = min_y
12✔
133
        while y < max_y:
12✔
134
            c = Cell(x + h, y + h, h, polygon)
12✔
135
            y += cell_size
12✔
136
            cell_queue.put((-c.max, time.time(), c))
12✔
137
        x += cell_size
12✔
138

139
    best_cell = _get_centroid_cell(polygon)
12✔
140

141
    bbox_cell = Cell(min_x + width / 2, min_y + height / 2, 0, polygon)
12✔
142
    if bbox_cell.d > best_cell.d:
12✔
143
        best_cell = bbox_cell
12✔
144

145
    num_of_probes = cell_queue.qsize()
12✔
146
    while not cell_queue.empty():
12✔
147
        _, __, cell = cell_queue.get()
12✔
148

149
        if cell.d > best_cell.d:
12✔
150
            best_cell = cell
12✔
151

152
            if debug:
12✔
153
                print(
×
154
                    "found best {} after {} probes".format(
155
                        round(1e4 * cell.d) / 1e4, num_of_probes
156
                    )
157
                )
158

159
        if cell.max - best_cell.d <= precision:
12✔
160
            continue
12✔
161

162
        h = cell.h / 2
12✔
163
        c = Cell(cell.x - h, cell.y - h, h, polygon)
12✔
164
        cell_queue.put((-c.max, time.time(), c))
12✔
165
        c = Cell(cell.x + h, cell.y - h, h, polygon)
12✔
166
        cell_queue.put((-c.max, time.time(), c))
12✔
167
        c = Cell(cell.x - h, cell.y + h, h, polygon)
12✔
168
        cell_queue.put((-c.max, time.time(), c))
12✔
169
        c = Cell(cell.x + h, cell.y + h, h, polygon)
12✔
170
        cell_queue.put((-c.max, time.time(), c))
12✔
171
        num_of_probes += 4
12✔
172

173
    if debug:
12✔
174
        print("num probes: {}".format(num_of_probes))
×
175
        print("best distance: {}".format(best_cell.d))
×
176
    if with_distance:
12✔
177
        return [best_cell.x, best_cell.y], best_cell.d
×
178
    else:
179
        return [best_cell.x, best_cell.y]
12✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc