bribera-rubyvor 0.0.2 → 0.1.1

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.
@@ -1,13 +1,16 @@
1
1
  $:.unshift File.dirname(__FILE__)
2
2
  $:.unshift File.join(File.dirname(__FILE__), '..', 'ext')
3
- require 'voronoi_interface.so'
3
+
4
4
  require 'ruby_vor/version'
5
5
  require 'ruby_vor/point'
6
- require 'ruby_vor/decomposition'
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'
7
13
 
14
+ # DOC HERE
8
15
  module RubyVor
9
- def self.test_l
10
- VDDT::Decomposition.from_points(Point.test_large)
11
- end
12
-
13
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
@@ -1,19 +1,32 @@
1
1
  module RubyVor
2
2
  class Point
3
- attr_accessor :x, :y
3
+ attr_reader :x, :y
4
4
  def initialize(x=0.0,y=0.0)
5
- @x = x
6
- @y = y
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
7
8
  end
8
9
 
9
- class << self
10
- def test_large
11
- a = []
12
- 1000000.times {
13
- a << new(100000.0 * rand, 100000.0 * rand)
14
- }
15
- a
16
- end
10
+ def <=>(p)
11
+ (@x != p.x) ? @x <=> p.x : @y <=> p.y
17
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
+
18
31
  end
19
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
@@ -1,3 +1,3 @@
1
1
  module RubyVor
2
- VERSION = '0.0.2'
2
+ VERSION = '0.1.1'
3
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