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.
@@ -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
+ }
@@ -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