abscondment-rubyvor 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,137 @@
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,v|
46
+ graph[v] = neighbors.select do |neighbor|
47
+ dist_proc[points[v], points[neighbor]] < max_distance
48
+ end
49
+ end
50
+
51
+
52
+ until nodes.empty?
53
+ v = nodes.pop
54
+
55
+ next if visited[v]
56
+
57
+ cluster = []
58
+ visited[v] = true
59
+ cluster.push(v)
60
+
61
+ children = graph[v]
62
+ until children.nil? || children.empty?
63
+ cnode = children.pop
64
+ next if cnode.nil? || visited[cnode]
65
+
66
+ visited[cnode] = true
67
+ cluster.push(cnode)
68
+ children.concat(graph[cnode])
69
+ end
70
+
71
+ clusters.push(cluster)
72
+ end
73
+
74
+ clusters
75
+ end
76
+
77
+ def cluster_by_size(sizes=[], dist_proc=DIST_PROC)
78
+ # * Take MST, and
79
+ # 1. For n in sizes (taken in descending order), delete the n most expensive edges from MST
80
+ # * TODO: use a MaxHeap?
81
+ # 2. Determine remaining connectivity using BFS as above?
82
+ # 3. Some other more efficient connectivity test?
83
+ # 4. Return {n1 => cluster, n2 => cluster} for all n.
84
+
85
+ sized_clusters = sizes.inject({}) {|h,s| h[s] = []; h}
86
+
87
+
88
+ mst = minimum_spanning_tree(dist_proc).to_a
89
+ mst.sort!{|a,b|a.last <=> b.last}
90
+
91
+ sizes = sizes.sort
92
+ last_size = 0
93
+
94
+ while current_size = sizes.shift
95
+ current_size -= 1
96
+
97
+ # Remove edge count delta
98
+ delta = current_size - last_size
99
+ mst.slice!(-delta,delta)
100
+
101
+ graph = (1..points.length).to_a.map{|v| []}
102
+ visited = [nil] * points.length
103
+ clusters = []
104
+
105
+ mst.each do |edge,weight|
106
+ graph[edge[1]].push(edge[0])
107
+ graph[edge[0]].push(edge[1])
108
+ end
109
+
110
+ for node in 0..points.length-1
111
+ next if visited[node]
112
+
113
+ cluster = [node]
114
+ visited[node] = true
115
+
116
+ neighbors = graph[node]
117
+ while v = neighbors.pop
118
+ next if visited[v]
119
+
120
+ cluster.push(v)
121
+ visited[v] = true
122
+ neighbors.concat(graph[v])
123
+ end
124
+
125
+ clusters.push(cluster)
126
+ end
127
+
128
+ sized_clusters[current_size + 1] = clusters
129
+ last_size = current_size
130
+ end
131
+
132
+ sized_clusters
133
+ end
134
+
135
+ end
136
+ end
137
+ 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
@@ -0,0 +1,84 @@
1
+ module RubyVor
2
+ class PriorityQueue
3
+
4
+ attr_reader :data, :size
5
+
6
+ class << self
7
+ def build_queue(max_index=-1,&block)
8
+ data = []
9
+
10
+ index = 0
11
+ loop do
12
+ x = QueueItem.new(nil, nil, nil)
13
+
14
+ yield(x)
15
+ break if !(max_index < 0 || index < max_index) || x.priority.nil?
16
+
17
+ x.index = index
18
+ data.push(x)
19
+ index += 1
20
+ end
21
+
22
+ q = new
23
+ q.instance_variable_set(:@data, data)
24
+ q.instance_variable_set(:@size, data.length)
25
+ q.heapify()
26
+
27
+ return q
28
+ end
29
+ end
30
+
31
+ def initialize
32
+ @data = []
33
+ @size = 0
34
+ heapify()
35
+ end
36
+
37
+ def peek
38
+ @data[0]
39
+ end
40
+
41
+ def pop
42
+ return nil if @size < 1
43
+
44
+ r = @data[0]
45
+
46
+ @data[0] = @data[@size-1]
47
+ @data[0].index = 0
48
+ @data.delete_at(@size-1)
49
+
50
+ @size -= 1
51
+
52
+ percolate_down(0) if @size > 0
53
+
54
+ return r
55
+ end
56
+
57
+ def push(data, priority=data)
58
+ @size += 1
59
+ @data[@size - 1] = QueueItem.new(priority, @size - 1, data)
60
+ percolate_up(@size - 1)
61
+ end
62
+
63
+ # Implemented in C
64
+ def reorder_queue;end
65
+
66
+ protected
67
+
68
+ # Implemented in C
69
+ def percolate_up(i);end
70
+
71
+ # Implemented in C
72
+ def percolate_down(i);end
73
+
74
+ class QueueItem
75
+ attr_accessor :priority, :index, :data
76
+ def initialize(p, i, d)
77
+ @priority = p
78
+ @index = i
79
+ @data = d
80
+ end
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,3 @@
1
+ module RubyVor
2
+ VERSION = '0.1.2'
3
+ end
@@ -0,0 +1,218 @@
1
+ require 'xml/libxml' unless defined?(LibXML)
2
+ module RubyVor
3
+ class Visualizer
4
+
5
+ COLORS = %w{black red blue lime gray yellow purple orange pink}
6
+
7
+ # Support various versions of LibXML
8
+ include LibXML if defined?(LibXML)
9
+
10
+ def self.make_svg(computation, opts={})
11
+ @opts = opts = {
12
+ :name => 'vddt.svg',
13
+ :triangulation => true,
14
+ :voronoi_diagram => false,
15
+ :mst => false,
16
+ :cbd => false,
17
+ }.merge(opts)
18
+
19
+ line_colors = COLORS.clone
20
+
21
+ doc = XML::Document.new()
22
+
23
+ doc.root = XML::Node.new('svg')
24
+ doc.root['xmlns'] = 'http://www.w3.org/2000/svg'
25
+ doc.root['xml:space'] = 'preserve'
26
+
27
+ max_x = 0
28
+ min_x = Float::MAX
29
+ max_y = 0
30
+ min_y = Float::MAX
31
+ pmax_x = 0
32
+ pmin_x = Float::MAX
33
+ pmax_y = 0
34
+ pmin_y = Float::MAX
35
+
36
+ computation.points.each do |point|
37
+ max_x = point.x if point.x > max_x
38
+ min_x = point.x if point.x < min_x
39
+ max_y = point.y if point.y > max_y
40
+ min_y = point.y if point.y < min_y
41
+ pmax_x = point.x if point.x > pmax_x
42
+ pmin_x = point.x if point.x < pmin_x
43
+ pmax_y = point.y if point.y > pmax_y
44
+ pmin_y = point.y if point.y < pmin_y
45
+ end
46
+
47
+ if opts[:voronoi_diagram]
48
+ computation.voronoi_diagram_raw.each do |item|
49
+ if item.first == :v
50
+ max_x = item[1] if item[1] > max_x
51
+ min_x = item[1] if item[1] < min_x
52
+ max_y = item[2] if item[2] > max_y
53
+ min_y = item[2] if item[2] < min_y
54
+ end
55
+ end
56
+ end
57
+
58
+ opts[:offset_x] = -1.0 * min_x + 20
59
+ opts[:offset_y] = -1.0 * min_y + 20
60
+
61
+ if opts[:triangulation]
62
+ # Draw in the triangulation
63
+ color = line_colors.shift
64
+ computation.delaunay_triangulation_raw.each do |triplet|
65
+ for i in 0..2
66
+ line = line_from_points(computation.points[triplet[i % 2 + 1]], computation.points[triplet[i & 6]])
67
+ line['style'] = "stroke:#{color};stroke-width:1;"
68
+ doc.root << line
69
+ end
70
+ end
71
+ end
72
+
73
+ if opts[:mst]
74
+ color = line_colors.shift
75
+ computation.minimum_spanning_tree.each do |edge, weight|
76
+ line = line_from_points(computation.points[edge[0]], computation.points[edge[1]])
77
+ line['style'] = "stroke:#{color};stroke-width:1;"
78
+ doc.root << line
79
+ end
80
+ end
81
+
82
+ if opts[:cbd]
83
+ mst = computation.minimum_spanning_tree
84
+
85
+ computation.cluster_by_distance(opts[:cbd]).each do |cluster|
86
+
87
+ color = new_color
88
+ min_dist = Float::MAX
89
+ set_min = false
90
+ cluster.each do |a|
91
+ cluster.each do |b|
92
+ next if a == b
93
+ k = a < b ? [a,b] : [b,a]
94
+ next unless mst.has_key?(k)
95
+
96
+ if mst[k] < min_dist
97
+ min_dist = mst[k]
98
+ set_min = true
99
+ end
100
+ end
101
+ end
102
+
103
+ min_dist = 10 unless set_min
104
+
105
+
106
+ cluster.each do |v|
107
+ node = circle_from_point(computation.points[v])
108
+ node['r'] = (min_dist + 10).to_s
109
+ node['style'] = "fill:#{color};stroke:#{color};"
110
+ node['opacity'] = '0.4'
111
+
112
+ doc.root << node
113
+ end
114
+
115
+ end
116
+ end
117
+
118
+ if opts[:voronoi_diagram]
119
+ voronoi_vertices = []
120
+ draw_lines = []
121
+ draw_points = []
122
+ line_functions = []
123
+
124
+ xcut = (pmax_x + pmin_x) / 2.0
125
+ ycut = (pmax_y + pmin_y) / 2.0
126
+
127
+ color = line_colors.shift
128
+
129
+ computation.voronoi_diagram_raw.each do |item|
130
+ case item.first
131
+ when :v
132
+ # Draw a voronoi vertex
133
+ v = RubyVor::Point.new(item[1], item[2])
134
+ voronoi_vertices.push(v)
135
+ node = circle_from_point(v)
136
+ node['fill'] = 'red'
137
+ node['r'] = '2'
138
+ node['stroke'] = 'black'
139
+ node['stroke-width'] = '1'
140
+
141
+ draw_points << node
142
+ when :l
143
+ # :l a b c --> ax + by = c
144
+ a = item[1]
145
+ b = item[2]
146
+ c = item[3]
147
+ line_functions.push({ :y => lambda{|x| (c - a * x) / b},
148
+ :x => lambda{|y| (c - b * y) / a} })
149
+ when :e
150
+ if item[2] == -1 || item[3] == -1
151
+ from_vertex = voronoi_vertices[item[2] == -1 ? item[3] : item[2]]
152
+
153
+ next if from_vertex < RubyVor::Point.new(0,0)
154
+
155
+ if item[2] == -1
156
+ inf_vertex = RubyVor::Point.new(0, line_functions[item[1]][:y][0])
157
+ else
158
+ inf_vertex = RubyVor::Point.new(max_x, line_functions[item[1]][:y][max_x])
159
+ end
160
+
161
+ line = line_from_points(from_vertex, inf_vertex)
162
+ else
163
+ line = line_from_points(voronoi_vertices[item[2]], voronoi_vertices[item[3]])
164
+ end
165
+
166
+ line['style'] = "stroke:#{color};stroke-width:1;"
167
+ draw_lines << line
168
+
169
+ end
170
+ end
171
+
172
+ draw_lines.each {|l| doc.root << l}
173
+ draw_points.each {|p| doc.root << p}
174
+ end
175
+
176
+ # Now draw in nodes
177
+ computation.points.each do |point|
178
+ node = circle_from_point(point)
179
+ node['fill'] = 'lime'
180
+
181
+ doc.root << node
182
+ end
183
+
184
+ doc.root['width'] = (max_x + opts[:offset_x] + 50).to_s
185
+ doc.root['height'] = (max_y + opts[:offset_y] + 50).to_s
186
+
187
+ doc.save(opts[:name] || 'vddt.svg')
188
+ end
189
+
190
+ private
191
+ def self.line_from_points(a, b)
192
+ line = XML::Node.new('line')
193
+ line['x1'] = (a.x + @opts[:offset_x] + 10).to_s
194
+ line['y1'] = (a.y + @opts[:offset_y] + 10).to_s
195
+ line['x2'] = (b.x + @opts[:offset_x] + 10).to_s
196
+ line['y2'] = (b.y + @opts[:offset_y] + 10).to_s
197
+ line
198
+ end
199
+
200
+ def self.circle_from_point(point)
201
+ node = XML::Node.new('circle')
202
+ node['cx'] = (point.x + @opts[:offset_x] + 10).to_s
203
+ node['cy'] = (point.y + @opts[:offset_y] + 10).to_s
204
+ node['r'] = 5.to_s
205
+ node['stroke'] = 'black'
206
+ node['stroke-width'] = 2.to_s
207
+ node
208
+ end
209
+
210
+ def self.new_color
211
+ a = rand(256)
212
+ b = rand(256) | a
213
+ c = rand(256) ^ b
214
+
215
+ "rgb(#{[a,b,c].sort{|k,l| rand(3)-1}.join(',')})"
216
+ end
217
+ end
218
+ end