forked from jansonh/Voronoi
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Voronoi.py
248 lines (201 loc) · 7.79 KB
/
Voronoi.py
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
import random
import math
from DataType import Point, Event, Arc, Segment, PriorityQueue
# Source: (C++) http://www.cs.hmc.edu/~mbrubeck/voronoi.html
class Voronoi:
def __init__(self, points):
self.output = [] # list of line segment
self.arc = None # binary tree for parabola arcs
self.points = PriorityQueue() # site events
self.event = PriorityQueue() # circle events
# bounding box
self.x0 = -50.0
self.x1 = -50.0
self.y0 = 550.0
self.y1 = 550.0
# insert points to site event
for pts in points:
point = Point(pts[0], pts[1])
self.points.push(point)
# keep track of bounding box size
if point.x < self.x0: self.x0 = point.x
if point.y < self.y0: self.y0 = point.y
if point.x > self.x1: self.x1 = point.x
if point.y > self.y1: self.y1 = point.y
# add margins to the bounding box
dx = (self.x1 - self.x0 + 1) / 5.0
dy = (self.y1 - self.y0 + 1) / 5.0
self.x0 = self.x0 - dx
self.x1 = self.x1 + dx
self.y0 = self.y0 - dy
self.y1 = self.y1 + dy
def process(self):
while not self.points.empty():
if not self.event.empty() and (self.event.top().x <= self.points.top().x):
self.process_event() # handle circle event
else:
self.process_point() # handle site event
# after all points, process remaining circle events
while not self.event.empty():
self.process_event()
self.finish_edges()
def process_point(self):
# get next event from site pq
p = self.points.pop()
# add new arc (parabola)
self.arc_insert(p)
def process_event(self):
# get next event from circle pq
e = self.event.pop()
if e.valid:
# start new edge
s = Segment(e.p)
self.output.append(s)
# remove associated arc (parabola)
a = e.a
if a.pprev is not None:
a.pprev.pnext = a.pnext
a.pprev.s1 = s
if a.pnext is not None:
a.pnext.pprev = a.pprev
a.pnext.s0 = s
# finish the edges before and after a
if a.s0 is not None: a.s0.finish(e.p)
if a.s1 is not None: a.s1.finish(e.p)
# recheck circle events on either side of p
if a.pprev is not None: self.check_circle_event(a.pprev, e.x)
if a.pnext is not None: self.check_circle_event(a.pnext, e.x)
def arc_insert(self, p):
if self.arc is None:
self.arc = Arc(p)
else:
# find the current arcs at p.y
i = self.arc
while i is not None:
flag, z = self.intersect(p, i)
if flag:
# new parabola intersects arc i
flag, zz = self.intersect(p, i.pnext)
if (i.pnext is not None) and (not flag):
i.pnext.pprev = Arc(i.p, i, i.pnext)
i.pnext = i.pnext.pprev
else:
i.pnext = Arc(i.p, i)
i.pnext.s1 = i.s1
# add p between i and i.pnext
i.pnext.pprev = Arc(p, i, i.pnext)
i.pnext = i.pnext.pprev
i = i.pnext # now i points to the new arc
# add new half-edges connected to i's endpoints
seg = Segment(z)
self.output.append(seg)
i.pprev.s1 = i.s0 = seg
seg = Segment(z)
self.output.append(seg)
i.pnext.s0 = i.s1 = seg
# check for new circle events around the new arc
self.check_circle_event(i, p.x)
self.check_circle_event(i.pprev, p.x)
self.check_circle_event(i.pnext, p.x)
return
i = i.pnext
# if p never intersects an arc, append it to the list
i = self.arc
while i.pnext is not None:
i = i.pnext
i.pnext = Arc(p, i)
# insert new segment between p and i
x = self.x0
y = (i.pnext.p.y + i.p.y) / 2.0;
start = Point(x, y)
seg = Segment(start)
i.s1 = i.pnext.s0 = seg
self.output.append(seg)
def check_circle_event(self, i, x0):
# look for a new circle event for arc i
if (i.e is not None) and (i.e.x <> self.x0):
i.e.valid = False
i.e = None
if (i.pprev is None) or (i.pnext is None): return
flag, x, o = self.circle(i.pprev.p, i.p, i.pnext.p)
if flag and (x > self.x0):
i.e = Event(x, o, i)
self.event.push(i.e)
def circle(self, a, b, c):
# check if bc is a "right turn" from ab
if ((b.x - a.x)*(c.y - a.y) - (c.x - a.x)*(b.y - a.y)) > 0: return False, None, None
# Joseph O'Rourke, Computational Geometry in C (2nd ed.) p.189
A = b.x - a.x
B = b.y - a.y
C = c.x - a.x
D = c.y - a.y
E = A*(a.x + b.x) + B*(a.y + b.y)
F = C*(a.x + c.x) + D*(a.y + c.y)
G = 2*(A*(c.y - b.y) - B*(c.x - b.x))
if (G == 0): return False, None, None # Points are co-linear
# point o is the center of the circle
ox = 1.0 * (D*E - B*F) / G
oy = 1.0 * (A*F - C*E) / G
# o.x plus radius equals max x coord
x = ox + math.sqrt((a.x-ox)**2 + (a.y-oy)**2)
o = Point(ox, oy)
return True, x, o
def intersect(self, p, i):
# check whether a new parabola at point p intersect with arc i
if (i is None): return False, None
if (i.p.x == p.x): return False, None
a = 0.0
b = 0.0
if i.pprev is not None:
a = (self.intersection(i.pprev.p, i.p, 1.0*p.x)).y
if i.pnext is not None:
b = (self.intersection(i.p, i.pnext.p, 1.0*p.x)).y
if (((i.pprev is None) or (a <= p.y)) and ((i.pnext is None) or (p.y <= b))):
py = p.y
px = 1.0 * ((i.p.x)**2 + (i.p.y-py)**2 - p.x**2) / (2*i.p.x - 2*p.x)
res = Point(px, py)
return True, res
return False, None
def intersection(self, p0, p1, l):
# get the intersection of two parabolas
p = p0
if (p0.x == p1.x):
py = (p0.y + p1.y) / 2.0
elif (p1.x == l):
py = p1.y
elif (p0.x == l):
py = p0.y
p = p1
else:
# use quadratic formula
z0 = 2.0 * (p0.x - l)
z1 = 2.0 * (p1.x - l)
a = 1.0/z0 - 1.0/z1;
b = -2.0 * (p0.y/z0 - p1.y/z1)
c = 1.0 * (p0.y**2 + p0.x**2 - l**2) / z0 - 1.0 * (p1.y**2 + p1.x**2 - l**2) / z1
py = 1.0 * (-b-math.sqrt(b*b - 4*a*c)) / (2*a)
px = 1.0 * (p.x**2 + (p.y-py)**2 - l**2) / (2*p.x-2*l)
res = Point(px, py)
return res
def finish_edges(self):
l = self.x1 + (self.x1 - self.x0) + (self.y1 - self.y0)
i = self.arc
while i.pnext is not None:
if i.s1 is not None:
p = self.intersection(i.p, i.pnext.p, l*2.0)
i.s1.finish(p)
i = i.pnext
def print_output(self):
it = 0
for o in self.output:
it = it + 1
p0 = o.start
p1 = o.end
print (p0.x, p0.y, p1.x, p1.y)
def get_output(self):
res = []
for o in self.output:
p0 = o.start
p1 = o.end
res.append((p0.x, p0.y, p1.x, p1.y))
return res