rubyvor 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|