rubyvor 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +60 -0
- data/Manifest.txt +30 -0
- data/README.rdoc +76 -0
- data/Rakefile +41 -0
- data/ext/Doc +30 -0
- data/ext/edgelist.c +204 -0
- data/ext/extconf.rb +3 -0
- data/ext/geometry.c +219 -0
- data/ext/heap.c +118 -0
- data/ext/memory.c +118 -0
- data/ext/output.c +251 -0
- data/ext/rb_cComputation.c +369 -0
- data/ext/rb_cPoint.c +35 -0
- data/ext/rb_cPriorityQueue.c +120 -0
- data/ext/ruby_vor_c.c +115 -0
- data/ext/ruby_vor_c.h +40 -0
- data/ext/vdefs.h +150 -0
- data/ext/voronoi.c +271 -0
- data/lib/ruby_vor.rb +16 -0
- data/lib/ruby_vor/computation.rb +136 -0
- data/lib/ruby_vor/geo_ruby_extensions.rb +15 -0
- data/lib/ruby_vor/point.rb +32 -0
- data/lib/ruby_vor/priority_queue.rb +87 -0
- data/lib/ruby_vor/version.rb +3 -0
- data/lib/ruby_vor/visualizer.rb +218 -0
- data/rubyvor.gemspec +35 -0
- data/test/test_computation.rb +354 -0
- data/test/test_point.rb +100 -0
- data/test/test_priority_queue.rb +129 -0
- data/test/test_voronoi_interface.rb +161 -0
- metadata +99 -0
data/ext/voronoi.c
ADDED
@@ -0,0 +1,271 @@
|
|
1
|
+
|
2
|
+
/*** VORONOI.C ***/
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
#include <vdefs.h>
|
6
|
+
#include <stdio.h>
|
7
|
+
|
8
|
+
VoronoiState rubyvorState;
|
9
|
+
|
10
|
+
/* Static method definitions: C -> Ruby storage methods. */
|
11
|
+
static void storeTriangulationTriplet(const int, const int, const int);
|
12
|
+
static void storeLine(const float, const float, const float);
|
13
|
+
static void storeEndpoint(const int, const int, const int);
|
14
|
+
static void storeVertex(const float, const float);
|
15
|
+
static void storeSite(const float, const float);
|
16
|
+
|
17
|
+
/*** implicit parameters: nsites, sqrt_nsites, xmin, xmax, ymin, ymax,
|
18
|
+
: deltax, deltay (can all be estimates).
|
19
|
+
: Performance suffers if they are wrong; better to make nsites,
|
20
|
+
: deltax, and deltay too big than too small. (?)
|
21
|
+
***/
|
22
|
+
|
23
|
+
void initialize_state(int debug)
|
24
|
+
{
|
25
|
+
/* Set up our initial state */
|
26
|
+
rubyvorState.debug = debug;
|
27
|
+
rubyvorState.plot = 0;
|
28
|
+
rubyvorState.nsites = 0;
|
29
|
+
rubyvorState.siteidx = 0;
|
30
|
+
|
31
|
+
rubyvorState.storeT = storeTriangulationTriplet;
|
32
|
+
rubyvorState.storeL = storeLine;
|
33
|
+
rubyvorState.storeE = storeEndpoint;
|
34
|
+
rubyvorState.storeV = storeVertex;
|
35
|
+
rubyvorState.storeS = storeSite;
|
36
|
+
|
37
|
+
/* Initialize the Site Freelist */
|
38
|
+
freeinit(&(rubyvorState.sfl), sizeof(Site)) ;
|
39
|
+
|
40
|
+
/* Initialize the geometry module */
|
41
|
+
geominit() ;
|
42
|
+
|
43
|
+
/* TODO: remove C plot references */
|
44
|
+
if (rubyvorState.plot)
|
45
|
+
plotinit();
|
46
|
+
}
|
47
|
+
|
48
|
+
void
|
49
|
+
voronoi(Site *(*nextsite)(void))
|
50
|
+
{
|
51
|
+
Site * newsite, * bot, * top, * temp, * p, * v ;
|
52
|
+
Point newintstar ;
|
53
|
+
int pm , c;
|
54
|
+
Halfedge * lbnd, * rbnd, * llbnd, * rrbnd, * bisector ;
|
55
|
+
Edge * e ;
|
56
|
+
|
57
|
+
newintstar.x = newintstar.y = c = 0;
|
58
|
+
|
59
|
+
PQinitialize() ;
|
60
|
+
rubyvorState.bottomsite = (*nextsite)() ;
|
61
|
+
out_site(rubyvorState.bottomsite) ;
|
62
|
+
ELinitialize() ;
|
63
|
+
newsite = (*nextsite)() ;
|
64
|
+
|
65
|
+
while (1)
|
66
|
+
{
|
67
|
+
if(!PQempty())
|
68
|
+
newintstar = PQ_min() ;
|
69
|
+
|
70
|
+
if (newsite != (Site *)NULL && (PQempty()
|
71
|
+
|| newsite -> coord.y < newintstar.y
|
72
|
+
|| (newsite->coord.y == newintstar.y
|
73
|
+
&& newsite->coord.x < newintstar.x)))
|
74
|
+
{
|
75
|
+
/* new site is smallest */
|
76
|
+
{
|
77
|
+
out_site(newsite) ;
|
78
|
+
}
|
79
|
+
lbnd = ELleftbnd(&(newsite->coord)) ;
|
80
|
+
rbnd = ELright(lbnd) ;
|
81
|
+
bot = rightreg(lbnd) ;
|
82
|
+
e = bisect(bot, newsite) ;
|
83
|
+
bisector = HEcreate(e, le) ;
|
84
|
+
ELinsert(lbnd, bisector) ;
|
85
|
+
p = intersect(lbnd, bisector) ;
|
86
|
+
if (p != (Site *)NULL)
|
87
|
+
{
|
88
|
+
PQdelete(lbnd) ;
|
89
|
+
PQinsert(lbnd, p, dist(p,newsite)) ;
|
90
|
+
}
|
91
|
+
lbnd = bisector ;
|
92
|
+
bisector = HEcreate(e, re) ;
|
93
|
+
ELinsert(lbnd, bisector) ;
|
94
|
+
p = intersect(bisector, rbnd) ;
|
95
|
+
if (p != (Site *)NULL)
|
96
|
+
{
|
97
|
+
PQinsert(bisector, p, dist(p,newsite)) ;
|
98
|
+
}
|
99
|
+
newsite = (*nextsite)() ;
|
100
|
+
}
|
101
|
+
else if (!PQempty()) /* intersection is smallest */
|
102
|
+
{
|
103
|
+
lbnd = PQextractmin() ;
|
104
|
+
llbnd = ELleft(lbnd) ;
|
105
|
+
rbnd = ELright(lbnd) ;
|
106
|
+
rrbnd = ELright(rbnd) ;
|
107
|
+
bot = leftreg(lbnd) ;
|
108
|
+
top = rightreg(rbnd) ;
|
109
|
+
out_triple(bot, top, rightreg(lbnd)) ;
|
110
|
+
v = lbnd->vertex ;
|
111
|
+
makevertex(v) ;
|
112
|
+
endpoint(lbnd->ELedge, lbnd->ELpm, v);
|
113
|
+
endpoint(rbnd->ELedge, rbnd->ELpm, v) ;
|
114
|
+
ELdelete(lbnd) ;
|
115
|
+
PQdelete(rbnd) ;
|
116
|
+
ELdelete(rbnd) ;
|
117
|
+
pm = le ;
|
118
|
+
if (bot->coord.y > top->coord.y)
|
119
|
+
{
|
120
|
+
temp = bot ;
|
121
|
+
bot = top ;
|
122
|
+
top = temp ;
|
123
|
+
pm = re ;
|
124
|
+
}
|
125
|
+
e = bisect(bot, top) ;
|
126
|
+
bisector = HEcreate(e, pm) ;
|
127
|
+
ELinsert(llbnd, bisector) ;
|
128
|
+
endpoint(e, re-pm, v) ;
|
129
|
+
deref(v) ;
|
130
|
+
p = intersect(llbnd, bisector) ;
|
131
|
+
if (p != (Site *) NULL)
|
132
|
+
{
|
133
|
+
PQdelete(llbnd) ;
|
134
|
+
PQinsert(llbnd, p, dist(p,bot)) ;
|
135
|
+
}
|
136
|
+
p = intersect(bisector, rrbnd) ;
|
137
|
+
if (p != (Site *) NULL)
|
138
|
+
{
|
139
|
+
PQinsert(bisector, p, dist(p,bot)) ;
|
140
|
+
}
|
141
|
+
}
|
142
|
+
else
|
143
|
+
{
|
144
|
+
break ;
|
145
|
+
}
|
146
|
+
}
|
147
|
+
|
148
|
+
for( lbnd = ELright(getELleftend()) ;
|
149
|
+
lbnd != getELrightend() ;
|
150
|
+
lbnd = ELright(lbnd))
|
151
|
+
{
|
152
|
+
e = lbnd->ELedge ;
|
153
|
+
out_ep(e) ;
|
154
|
+
}
|
155
|
+
}
|
156
|
+
|
157
|
+
|
158
|
+
|
159
|
+
|
160
|
+
/*
|
161
|
+
* Static storage methods
|
162
|
+
*/
|
163
|
+
|
164
|
+
/*** stores a triplet of point indices that comprise a Delaunay triangle ***/
|
165
|
+
static void
|
166
|
+
storeTriangulationTriplet(const int a, const int b, const int c)
|
167
|
+
{
|
168
|
+
VALUE trArray, triplet;
|
169
|
+
|
170
|
+
/* Create a new triplet from the three incoming points */
|
171
|
+
triplet = rb_ary_new2(3);
|
172
|
+
|
173
|
+
rb_ary_push(triplet, INT2FIX(a));
|
174
|
+
rb_ary_push(triplet, INT2FIX(b));
|
175
|
+
rb_ary_push(triplet, INT2FIX(c));
|
176
|
+
|
177
|
+
/* Get the existing raw triangulation */
|
178
|
+
trArray = rb_funcall(*(VALUE *)rubyvorState.comp, rb_intern("delaunay_triangulation_raw"), 0);
|
179
|
+
|
180
|
+
/* Add the new triplet to it */
|
181
|
+
rb_ary_push(trArray, triplet);
|
182
|
+
}
|
183
|
+
|
184
|
+
|
185
|
+
/*** stores a line defined by ax + by = c ***/
|
186
|
+
static void
|
187
|
+
storeLine(const float a, const float b, const float c)
|
188
|
+
{
|
189
|
+
VALUE lArray, line;
|
190
|
+
|
191
|
+
/* Create a new line from the three values */
|
192
|
+
line = rb_ary_new2(4);
|
193
|
+
rb_ary_push(line, ID2SYM(rb_intern("l")));
|
194
|
+
rb_ary_push(line, rb_float_new(a));
|
195
|
+
rb_ary_push(line, rb_float_new(b));
|
196
|
+
rb_ary_push(line, rb_float_new(c));
|
197
|
+
|
198
|
+
/* Get the existing raw voronoi diagram */
|
199
|
+
lArray = rb_funcall(*(VALUE *)rubyvorState.comp, rb_intern("voronoi_diagram_raw"), 0);
|
200
|
+
|
201
|
+
/* Add the new line to it */
|
202
|
+
rb_ary_push(lArray, line);
|
203
|
+
}
|
204
|
+
|
205
|
+
|
206
|
+
/***
|
207
|
+
* Stores a Voronoi segment which is a subsegment of line number l;
|
208
|
+
* with endpoints numbered v1 and v2. If v1 or v2 is -1, the line
|
209
|
+
* extends to infinity.
|
210
|
+
***/
|
211
|
+
static void
|
212
|
+
storeEndpoint(const int l, const int v1, const int v2)
|
213
|
+
{
|
214
|
+
VALUE eArray, endpoint;
|
215
|
+
|
216
|
+
/* Create a new endpoint from the three values */
|
217
|
+
endpoint = rb_ary_new2(4);
|
218
|
+
rb_ary_push(endpoint, ID2SYM(rb_intern("e")));
|
219
|
+
rb_ary_push(endpoint, INT2FIX(l));
|
220
|
+
rb_ary_push(endpoint, INT2FIX(v1));
|
221
|
+
rb_ary_push(endpoint, INT2FIX(v2));
|
222
|
+
|
223
|
+
/* Get the existing raw voronoi diagram */
|
224
|
+
eArray = rb_funcall(*(VALUE *)rubyvorState.comp, rb_intern("voronoi_diagram_raw"), 0);
|
225
|
+
|
226
|
+
/* Add the new endpoint to it */
|
227
|
+
rb_ary_push(eArray, endpoint);
|
228
|
+
}
|
229
|
+
|
230
|
+
|
231
|
+
/*** stores a vertex at (a,b) ***/
|
232
|
+
static void
|
233
|
+
storeVertex(const float a, const float b)
|
234
|
+
{
|
235
|
+
VALUE vArray, vertex;
|
236
|
+
|
237
|
+
/* Create a new vertex from the coordinates */
|
238
|
+
vertex = rb_ary_new2(3);
|
239
|
+
rb_ary_push(vertex, ID2SYM(rb_intern("v")));
|
240
|
+
rb_ary_push(vertex, rb_float_new(a));
|
241
|
+
rb_ary_push(vertex, rb_float_new(b));
|
242
|
+
|
243
|
+
/* Get the existing raw voronoi diagram */
|
244
|
+
vArray = rb_funcall(*(VALUE *)rubyvorState.comp, rb_intern("voronoi_diagram_raw"), 0);
|
245
|
+
|
246
|
+
/* Add the new vertex to it */
|
247
|
+
rb_ary_push(vArray, vertex);
|
248
|
+
}
|
249
|
+
|
250
|
+
|
251
|
+
/***
|
252
|
+
* stores an input site at (x,y)
|
253
|
+
* TODO: redundant?
|
254
|
+
***/
|
255
|
+
static void
|
256
|
+
storeSite(const float x, const float y)
|
257
|
+
{
|
258
|
+
VALUE sArray, site;
|
259
|
+
|
260
|
+
/* Create a new site from the coordinates */
|
261
|
+
site = rb_ary_new2(3);
|
262
|
+
rb_ary_push(site, ID2SYM(rb_intern("s")));
|
263
|
+
rb_ary_push(site, rb_float_new(x));
|
264
|
+
rb_ary_push(site, rb_float_new(y));
|
265
|
+
|
266
|
+
/* Get the existing raw voronoi diagram */
|
267
|
+
sArray = rb_funcall(*(VALUE *)rubyvorState.comp, rb_intern("voronoi_diagram_raw"), 0);
|
268
|
+
|
269
|
+
/* Add the new site to it */
|
270
|
+
rb_ary_push(sArray, site);
|
271
|
+
}
|
data/lib/ruby_vor.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', 'ext')
|
3
|
+
|
4
|
+
require 'ruby_vor/version'
|
5
|
+
require 'ruby_vor/point'
|
6
|
+
require 'ruby_vor/priority_queue'
|
7
|
+
require 'ruby_vor/computation'
|
8
|
+
require 'ruby_vor/geo_ruby_extensions'
|
9
|
+
require 'ruby_vor/visualizer'
|
10
|
+
|
11
|
+
# Require ruby_vor.so last to clobber old from_points
|
12
|
+
require 'ruby_vor_c.so'
|
13
|
+
|
14
|
+
# DOC HERE
|
15
|
+
module RubyVor
|
16
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module RubyVor
|
2
|
+
module VDDT
|
3
|
+
class Computation
|
4
|
+
attr_reader :points, :voronoi_diagram_raw, :delaunay_triangulation_raw, :no_neighbor_response
|
5
|
+
|
6
|
+
DIST_PROC = lambda{|a,b| a.distance_from(b)}
|
7
|
+
NO_NEIGHBOR_RESPONSES = [:raise, :use_all, :ignore]
|
8
|
+
|
9
|
+
# Create a computation from an existing set of raw data.
|
10
|
+
def initialize
|
11
|
+
@points = []
|
12
|
+
|
13
|
+
@voronoi_diagram_raw = []
|
14
|
+
@delaunay_triangulation_raw = []
|
15
|
+
|
16
|
+
@nn_graph = nil
|
17
|
+
@mst = nil
|
18
|
+
|
19
|
+
@no_neighbor_response = :use_all
|
20
|
+
end
|
21
|
+
|
22
|
+
# Decided what action to take if we find a point with no neighbors
|
23
|
+
# while computing the nn_graph.
|
24
|
+
#
|
25
|
+
# Choices are:
|
26
|
+
# * :use_all - include all other nodes as potential neighbors. This choice is the default, and can lead to higher big-O lower bounds when clustering.
|
27
|
+
# * :raise - raise an error
|
28
|
+
# * :ignore - leave this node disconnected from the rest of the graph.
|
29
|
+
def no_neighbor_response=(v)
|
30
|
+
@no_neighbor_response = v if NO_NEIGHBOR_RESPONSES.include?(v)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Uses the nearest-neighbors information encapsulated by the Delaunay triangulation as a seed for clustering:
|
34
|
+
# We take the edges (there are O(n) of them, because defined by the triangulation and delete any edge above a certain distance.
|
35
|
+
#
|
36
|
+
# This method allows the caller to pass in a lambda for customizing distance calculations. For instance, to use a GeoRuby::SimpleFeatures::Point, one would:
|
37
|
+
# > cluster_by_distance(50 lambda{|a,b| a.spherical_distance(b, 3958.754)}) # this rejects edges greater than 50 miles, using spherical distance as a measure
|
38
|
+
def cluster_by_distance(max_distance, dist_proc=DIST_PROC)
|
39
|
+
clusters = []
|
40
|
+
nodes = (0..points.length-1).to_a
|
41
|
+
visited = [false] * points.length
|
42
|
+
graph = []
|
43
|
+
v = 0
|
44
|
+
|
45
|
+
nn_graph.each_with_index do |neighbors,nv|
|
46
|
+
graph[nv] = neighbors.select do |neighbor|
|
47
|
+
dist_proc[points[nv], points[neighbor]] < max_distance
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
until nodes.empty?
|
52
|
+
v = nodes.pop
|
53
|
+
|
54
|
+
next if visited[v]
|
55
|
+
|
56
|
+
cluster = []
|
57
|
+
visited[v] = true
|
58
|
+
cluster.push(v)
|
59
|
+
|
60
|
+
children = graph[v]
|
61
|
+
until children.nil? || children.empty?
|
62
|
+
cnode = children.pop
|
63
|
+
next if cnode.nil? || visited[cnode]
|
64
|
+
|
65
|
+
visited[cnode] = true
|
66
|
+
cluster.push(cnode)
|
67
|
+
children.concat(graph[cnode])
|
68
|
+
end
|
69
|
+
|
70
|
+
clusters.push(cluster)
|
71
|
+
end
|
72
|
+
|
73
|
+
clusters
|
74
|
+
end
|
75
|
+
|
76
|
+
def cluster_by_size(sizes=[], dist_proc=DIST_PROC)
|
77
|
+
# * Take MST, and
|
78
|
+
# 1. For n in sizes (taken in descending order), delete the n most expensive edges from MST
|
79
|
+
# * TODO: use a MaxHeap?
|
80
|
+
# 2. Determine remaining connectivity using BFS as above?
|
81
|
+
# 3. Some other more efficient connectivity test?
|
82
|
+
# 4. Return {n1 => cluster, n2 => cluster} for all n.
|
83
|
+
|
84
|
+
sized_clusters = sizes.inject({}) {|h,s| h[s] = []; h}
|
85
|
+
|
86
|
+
|
87
|
+
mst = minimum_spanning_tree(dist_proc).to_a
|
88
|
+
mst.sort!{|a,b|a.last <=> b.last}
|
89
|
+
|
90
|
+
sizes = sizes.sort
|
91
|
+
last_size = 0
|
92
|
+
|
93
|
+
while current_size = sizes.shift
|
94
|
+
current_size -= 1
|
95
|
+
|
96
|
+
# Remove edge count delta
|
97
|
+
delta = current_size - last_size
|
98
|
+
mst.slice!(-delta,delta)
|
99
|
+
|
100
|
+
graph = (1..points.length).to_a.map{|v| []}
|
101
|
+
visited = [nil] * points.length
|
102
|
+
clusters = []
|
103
|
+
|
104
|
+
mst.each do |edge,weight|
|
105
|
+
graph[edge[1]].push(edge[0])
|
106
|
+
graph[edge[0]].push(edge[1])
|
107
|
+
end
|
108
|
+
|
109
|
+
for node in 0..points.length-1
|
110
|
+
next if visited[node]
|
111
|
+
|
112
|
+
cluster = [node]
|
113
|
+
visited[node] = true
|
114
|
+
|
115
|
+
neighbors = graph[node]
|
116
|
+
while v = neighbors.pop
|
117
|
+
next if visited[v]
|
118
|
+
|
119
|
+
cluster.push(v)
|
120
|
+
visited[v] = true
|
121
|
+
neighbors.concat(graph[v])
|
122
|
+
end
|
123
|
+
|
124
|
+
clusters.push(cluster)
|
125
|
+
end
|
126
|
+
|
127
|
+
sized_clusters[current_size + 1] = clusters
|
128
|
+
last_size = current_size
|
129
|
+
end
|
130
|
+
|
131
|
+
sized_clusters
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
if require 'geo_ruby'
|
2
|
+
# Let us call uniq on a set of Points or use one as a Hash key.
|
3
|
+
module GeoRuby
|
4
|
+
module SimpleFeatures
|
5
|
+
class Point < Geometry
|
6
|
+
def eql?(other)
|
7
|
+
self == other
|
8
|
+
end
|
9
|
+
def hash
|
10
|
+
[x,y,z,m].hash
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module RubyVor
|
2
|
+
class Point
|
3
|
+
attr_reader :x, :y
|
4
|
+
def initialize(x=0.0,y=0.0)
|
5
|
+
raise TypeError, 'Must be able to convert point values into floats' unless x.respond_to?(:to_f) && y.respond_to?(:to_f)
|
6
|
+
@x = x.to_f
|
7
|
+
@y = y.to_f
|
8
|
+
end
|
9
|
+
|
10
|
+
def <=>(p)
|
11
|
+
(@x != p.x) ? @x <=> p.x : @y <=> p.y
|
12
|
+
end
|
13
|
+
|
14
|
+
def <(p)
|
15
|
+
(self <=> p) == -1
|
16
|
+
end
|
17
|
+
|
18
|
+
def >(p)
|
19
|
+
(self <=> p) == 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def ==(p)
|
23
|
+
@x == p.x && @y == p.y
|
24
|
+
end
|
25
|
+
alias :eql? :==
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
"(#{@x},#{@y})"
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|