rquad 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/CHANGES +2 -0
- data/LICENSE +21 -0
- data/README +7 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/lib/rquad.rb +2 -0
- data/lib/rquad/quadtree.rb +353 -0
- data/lib/rquad/vector.rb +162 -0
- data/rquad.gemspec +55 -0
- data/test/helper.rb +9 -0
- data/test/rquad/test_quadtree.rb +247 -0
- data/test/rquad/test_vector.rb +95 -0
- metadata +82 -0
data/.gitignore
ADDED
data/CHANGES
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
LICENSE
|
2
|
+
|
3
|
+
Copyright (c) 2008, Andrew Cantino, Iteration Labs, LLC, http://iterationlabs.com
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Welcome to the rQuad Ruby Quadtree library.
|
2
|
+
|
3
|
+
This package is distributed subject to the 'MIT License' found in the LICENSE file in this directory.
|
4
|
+
|
5
|
+
The source is freely available online: http://github.com/iterationlabs/rquad/tree/master
|
6
|
+
|
7
|
+
If you make modifications to this software and would be willing to contribute them back to the community, please send them in for possible inclusion in future releases!
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "rquad"
|
8
|
+
gem.summary = %Q{Ruby Quadtree Library}
|
9
|
+
gem.description = %Q{Ruby Quadtree Library}
|
10
|
+
gem.email = "roman.scherer@nugg.ad"
|
11
|
+
gem.homepage = "http://github.com/r0man/rquad"
|
12
|
+
gem.authors = ["Roman Scherer"]
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
end
|
15
|
+
Jeweler::GemcutterTasks.new
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rake/testtask'
|
21
|
+
Rake::TestTask.new(:test) do |test|
|
22
|
+
test.libs << 'lib' << 'test'
|
23
|
+
test.pattern = 'test/**/test_*.rb'
|
24
|
+
test.verbose = true
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
require 'rcov/rcovtask'
|
29
|
+
Rcov::RcovTask.new do |test|
|
30
|
+
test.libs << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
rescue LoadError
|
35
|
+
task :rcov do
|
36
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
task :test => :check_dependencies
|
41
|
+
|
42
|
+
task :default => :test
|
43
|
+
|
44
|
+
require 'rake/rdoctask'
|
45
|
+
Rake::RDocTask.new do |rdoc|
|
46
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
47
|
+
|
48
|
+
rdoc.rdoc_dir = 'rdoc'
|
49
|
+
rdoc.title = "rquad #{version}"
|
50
|
+
rdoc.rdoc_files.include('README*')
|
51
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
52
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
data/lib/rquad.rb
ADDED
@@ -0,0 +1,353 @@
|
|
1
|
+
# SOFTWARE INFO
|
2
|
+
#
|
3
|
+
# This file is part of the quadtree.rb Ruby quadtree library, distributed
|
4
|
+
# subject to the 'MIT License' below. This software is available for
|
5
|
+
# download at http://iterationlabs.com/free_software/quadtree.
|
6
|
+
#
|
7
|
+
# If you make modifications to this software and would be willing to
|
8
|
+
# contribute them back to the community, please send them to us for
|
9
|
+
# possible inclusion in future releases!
|
10
|
+
#
|
11
|
+
# LICENSE
|
12
|
+
#
|
13
|
+
# Copyright (c) 2008, Iteration Labs, LLC, http://iterationlabs.com
|
14
|
+
#
|
15
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
16
|
+
# of this software and associated documentation files (the "Software"), to deal
|
17
|
+
# in the Software without restriction, including without limitation the rights
|
18
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
19
|
+
# copies of the Software, and to permit persons to whom the Software is
|
20
|
+
# furnished to do so, subject to the following conditions:
|
21
|
+
#
|
22
|
+
# The above copyright notice and this permission notice shall be included in
|
23
|
+
# all copies or substantial portions of the Software.
|
24
|
+
#
|
25
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
26
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
27
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
28
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
29
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
30
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
31
|
+
# THE SOFTWARE.
|
32
|
+
#
|
33
|
+
require "rquad/vector"
|
34
|
+
|
35
|
+
module RQuad
|
36
|
+
|
37
|
+
# A payload for a QuadTree. Hs accessors for vector, data, and node.
|
38
|
+
class QuadTreePayload
|
39
|
+
attr_accessor :vector, :data, :node
|
40
|
+
|
41
|
+
# Initialize a QuadTreePayload with a Vector, some data (any class).
|
42
|
+
def initialize(v, d, n = nil)
|
43
|
+
self.node = n
|
44
|
+
self.vector = v
|
45
|
+
self.data = d
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# A quadtree node that can contain QuadTreePayloads and other QuadTree nodes. A quadtree is a tree that subdivides space into recursively defined quadrents that (in this implementation) can contain no more than one spacially-unique payload. Quadtrees are good for answering questions about neighborhoods of points in a space.
|
50
|
+
#
|
51
|
+
# Making a quadtree is simple, just initialize a new QuadTree with two vectors, its top left and bottom right points, then add QuadTreePayloads to it and it will store them in an efficiently constructed quadtree structure. You may then ask for all payloads in a region, all payloads near a point, etc.
|
52
|
+
#
|
53
|
+
# Example usage with longitudes and latitudes:
|
54
|
+
#
|
55
|
+
# qt = QuadTree.new(Vector.new(-180, 90), Vector.new(180, -90))
|
56
|
+
# qt.add(QuadTreePayload.new(Vector.new(lng1, lat1), entity1))
|
57
|
+
# qt.add(QuadTreePayload.new(Vector.new(lng2, lat2), entity2))
|
58
|
+
# qt.add(QuadTreePayload.new(Vector.new(lng3, lat3), entity3))
|
59
|
+
# qt.add(QuadTreePayload.new(Vector.new(lng4, lat4), entity4))
|
60
|
+
#
|
61
|
+
class QuadTree
|
62
|
+
# You may ask a QuadTree for its `payload`, `tl` point, `br` point, and `depth`.
|
63
|
+
attr_accessor :payload, :tl, :br, :depth
|
64
|
+
|
65
|
+
# Initialize a new QuadTree with two vectors: its top-left corner and its bottom-right corner. Optionally, you can also provide a reference to this node's parent node.
|
66
|
+
def initialize(tl, br, parent_node = nil)
|
67
|
+
@parent = parent_node
|
68
|
+
@tl = tl
|
69
|
+
@br = br
|
70
|
+
@size = 0
|
71
|
+
@summed_contained_vectors = Vector.new(0,0)
|
72
|
+
@depth = 0
|
73
|
+
end
|
74
|
+
|
75
|
+
# Add a QuadTreePayload to this QuadTree. If this node is empty, it will be stored in this node. If not, both the new payload and the old one will be recursively added to the appropriate one of the four children of this node. There is a special case: if this node already has a payload and the new payload has an identical position to the existing payload, then the new payload will be stored here in ddition to the existing payload.
|
76
|
+
# jitter_proc - a Proc object that, if provided, is used to jitter payloads with identical vectors (accepts and returns a QuadTreePayload).
|
77
|
+
def add(geo_data, depth = 1, jitter_proc = nil)
|
78
|
+
geo_data.node = self
|
79
|
+
if size > 0
|
80
|
+
if @payload && (@payload.first.vector == geo_data.vector)
|
81
|
+
# The vectors match.
|
82
|
+
if jitter_proc
|
83
|
+
@payload << jitter_proc.call(geo_data)
|
84
|
+
else
|
85
|
+
@payload << geo_data
|
86
|
+
end
|
87
|
+
else
|
88
|
+
# The vectors don't match.
|
89
|
+
if payload
|
90
|
+
@payload.each { |p| add_into_subtree(p, depth + 1, jitter_proc) }
|
91
|
+
@payload = nil
|
92
|
+
end
|
93
|
+
add_into_subtree(geo_data, depth + 1, jitter_proc)
|
94
|
+
end
|
95
|
+
else
|
96
|
+
@payload = [geo_data]
|
97
|
+
@depth = depth
|
98
|
+
end
|
99
|
+
@summed_contained_vectors += geo_data.vector
|
100
|
+
@size += 1
|
101
|
+
end
|
102
|
+
|
103
|
+
# This method returns the payloads contained under this node in the quadtree. It takes an options hash with the following optional keys:
|
104
|
+
# :max_count - the maximum number of payloads to return, provided via a breadth-first search.
|
105
|
+
# :details_proc - a Proc object to which every internal node at the maximum deoth achieved by the search is passed -- this is useful for providing summary statistics about subtrees that were not explored by this traversal due to a :max_count limitation.
|
106
|
+
# :requirement_proc - a Proc object that, if provided, must return true when evaluating a payload in order for that payload to be returned.
|
107
|
+
# Returns a Hash with keys :payloads, an array of all of the payloads below this node, and :details, the mapped result of applying :details_proc (if provided) to every internal node at the mximum depth achieved by the search.
|
108
|
+
def get_contained(options = {})
|
109
|
+
payloads = []
|
110
|
+
internal_nodes = []
|
111
|
+
last_depth = -1
|
112
|
+
breadth_first_each do |node, depth|
|
113
|
+
break if options[:max_count] && payloads.length >= options[:max_count] && (!options[:details_proc] || depth != last_depth)
|
114
|
+
|
115
|
+
if node.payload
|
116
|
+
internal_nodes.delete_if {|i| i.parent_of?(node)} if options[:details_proc]
|
117
|
+
node.payload.each do |entry|
|
118
|
+
if !options[:requirement_proc] || options[:requirement_proc].call(entry)
|
119
|
+
payloads << entry
|
120
|
+
end
|
121
|
+
end
|
122
|
+
elsif options[:details_proc] && (node.tlq? || node.trq? || node.blq? || node.brq?)
|
123
|
+
internal_nodes.delete_if {|i| i.parent_of?(node)}
|
124
|
+
internal_nodes << node
|
125
|
+
end
|
126
|
+
last_depth = depth
|
127
|
+
end
|
128
|
+
{ :payloads => payloads, :details => (options[:details_proc] ? internal_nodes.map {|i| options[:details_proc].call(i)} : nil) }
|
129
|
+
end
|
130
|
+
|
131
|
+
# Calls get_contained and only returns the :payloads key. Accepts the same options as get_contained except for the :details_proc option.
|
132
|
+
def get_contained_payloads(options = {})
|
133
|
+
get_contained(options)[:payloads]
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns the centroid of the payloads contained in this quadtree.
|
137
|
+
def center_of_mass
|
138
|
+
@summed_contained_vectors / @size
|
139
|
+
end
|
140
|
+
|
141
|
+
# Performs a breath-first traversal of this quadtree, yielding [node, depth] for each node.
|
142
|
+
def breadth_first_each
|
143
|
+
queue = [self]
|
144
|
+
while queue.length > 0
|
145
|
+
node = queue.shift
|
146
|
+
queue << node.tlq if node.tlq?
|
147
|
+
queue << node.trq if node.trq?
|
148
|
+
queue << node.blq if node.blq?
|
149
|
+
queue << node.brq if node.brq?
|
150
|
+
yield node, node.depth
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Yields each payload in this quadtree via a breadth-first traversal.
|
155
|
+
def each_payload
|
156
|
+
breadth_first_each do |node, depth|
|
157
|
+
next unless node.payload
|
158
|
+
node.payload.each do |payload|
|
159
|
+
yield payload
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# True if this node is a direct parent of `node`.
|
165
|
+
def parent_of?(node)
|
166
|
+
node && node == tlq(false) || node == trq(false) || node == blq(false) || node == brq(false)
|
167
|
+
end
|
168
|
+
|
169
|
+
# True if this node is a direct child of `node`.
|
170
|
+
def child_of?(node)
|
171
|
+
node.parent_of?(self)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Yields all pseudo-leaves formed when the graph is cut off at a certain depth, plus any leaves encountered before that depth.
|
175
|
+
def leaves_each(leaf_depth)
|
176
|
+
stack = [self]
|
177
|
+
while stack.length > 0
|
178
|
+
node = stack.pop
|
179
|
+
start_size = stack.length
|
180
|
+
stack << node.tlq if node.tlq? && node.tlq.depth < leaf_depth + depth
|
181
|
+
stack << node.trq if node.trq? && node.trq.depth < leaf_depth + depth
|
182
|
+
stack << node.blq if node.blq? && node.blq.depth < leaf_depth + depth
|
183
|
+
stack << node.brq if node.brq? && node.brq.depth < leaf_depth + depth
|
184
|
+
if node.depth == leaf_depth + depth - 1 || (!node.tlq? && !node.trq? && !node.blq? && !node.brq?)
|
185
|
+
yield node
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Returns the size of this node: the number of contained payloads.
|
191
|
+
def size
|
192
|
+
@size
|
193
|
+
end
|
194
|
+
|
195
|
+
# Returns this node's parent node or nil if this node is a root node.
|
196
|
+
def parent
|
197
|
+
@parent
|
198
|
+
end
|
199
|
+
|
200
|
+
# Returns approxametly `approx_number` payloads near `location`.
|
201
|
+
def approx_near(location, approx_number)
|
202
|
+
if approx_number >= size
|
203
|
+
return get_contained_payloads
|
204
|
+
else
|
205
|
+
get_containing_quad(location).approx_near(location, approx_number)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Returns up to `max_number` payloads inside of the region specified by `region_tl` and `region_br`.
|
210
|
+
def payloads_in_region(region_tl, region_br, max_number = nil)
|
211
|
+
quad1 = get_containing_quad(region_tl)
|
212
|
+
quad2 = get_containing_quad(region_br)
|
213
|
+
if quad1 == quad2 && payload.nil?
|
214
|
+
quad1.payloads_in_region(region_tl, region_br, max_number)
|
215
|
+
else
|
216
|
+
region_containment_proc = lambda do |i|
|
217
|
+
region_tl.x <= i.vector.x && region_br.x >= i.vector.x && region_tl.y >= i.vector.y && region_br.y <= i.vector.y
|
218
|
+
end
|
219
|
+
get_contained_payloads(:max_count => max_number, :requirement_proc => region_containment_proc)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Returns an array of [centroid (Vector), count] pairs summarizing the set of centroids at a certain tree depth. That is, it provides centroids and counts of all of the subtrees still available at depth `depth`, plus any that terminated above that depth.
|
224
|
+
def center_of_masses_in_region(region_tl, region_br, depth)
|
225
|
+
quad1 = get_containing_quad(region_tl)
|
226
|
+
quad2 = get_containing_quad(region_br)
|
227
|
+
if quad1 == quad2 && payload.nil?
|
228
|
+
quad1.center_of_masses_in_region(region_tl, region_br, depth)
|
229
|
+
else
|
230
|
+
centers_of_mass = []
|
231
|
+
leaves_each(depth) do |node|
|
232
|
+
centers_of_mass << [node.center_of_mass, node.size]
|
233
|
+
end
|
234
|
+
centers_of_mass
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Returns a hash with keys :payloads and :details. The :payloads are payloads found, while details are for nodes that didn't get to be explored because the requisite number of payloads were already found.
|
239
|
+
def payloads_and_centers_in_region(region_tl, region_br, approx_num_to_return)
|
240
|
+
quad1 = get_containing_quad(region_tl)
|
241
|
+
quad2 = get_containing_quad(region_br)
|
242
|
+
if quad1 == quad2 && payload.nil?
|
243
|
+
quad1.payloads_and_centers_in_region(region_tl, region_br, approx_num_to_return)
|
244
|
+
else
|
245
|
+
region_containment_proc = lambda do |i|
|
246
|
+
region_tl.x <= i.vector.x && region_br.x >= i.vector.x && region_tl.y >= i.vector.y && region_br.y <= i.vector.y
|
247
|
+
end
|
248
|
+
details_proc = lambda do |i|
|
249
|
+
[i.center_of_mass, i.size]
|
250
|
+
end
|
251
|
+
get_contained(:max_count => approx_num_to_return, :requirement_proc => region_containment_proc, :details_proc => details_proc)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# The top-left quadrent of this quadtree. If `build` is true, this will make the quadrent quadtree if it doesn't alredy exist.
|
256
|
+
def tlq(build = true)
|
257
|
+
@tlq ||= QuadTree.new(Vector.new(tl), Vector.new(tl.x + (br.x - tl.x) / 2.0, br.y + (tl.y - br.y) / 2.0), self) if build
|
258
|
+
@tlq
|
259
|
+
end
|
260
|
+
|
261
|
+
# The top-right quadrent of this quadtree. If `build` is true, this will make the quadrent quadtree if it doesn't alredy exist.
|
262
|
+
def trq(build = true)
|
263
|
+
@trq ||= QuadTree.new(Vector.new(tl.x + (br.x - tl.x) / 2.0, tl.y), Vector.new(br.x, br.y + (tl.y - br.y) / 2.0), self) if build
|
264
|
+
@trq
|
265
|
+
end
|
266
|
+
|
267
|
+
# The bottom-left quadrent of this quadtree. If `build` is true, this will make the quadrent quadtree if it doesn't alredy exist.
|
268
|
+
def blq(build = true)
|
269
|
+
@blq ||= QuadTree.new(Vector.new(tl.x, br.y + (tl.y - br.y) / 2.0), Vector.new(tl.x + (br.x - tl.x) / 2.0, br.y), self) if build
|
270
|
+
@blq
|
271
|
+
end
|
272
|
+
|
273
|
+
# The bottom-right quadrent of this quadtree. If `build` is true, this will make the quadrent quadtree if it doesn't alredy exist.
|
274
|
+
def brq(build = true)
|
275
|
+
@brq ||= QuadTree.new(Vector.new(tl.x + (br.x - tl.x) / 2.0, br.y + (tl.y - br.y) / 2.0), Vector.new(br), self) if build
|
276
|
+
@brq
|
277
|
+
end
|
278
|
+
|
279
|
+
# Returns true if this quadtree has a top-left quadrent already defined.
|
280
|
+
def tlq?
|
281
|
+
@tlq && (@tlq.payload || (@tlq.tlq? || @tlq.trq? || @tlq.blq? || @tlq.brq?))
|
282
|
+
end
|
283
|
+
|
284
|
+
# Returns true if this quadtree has a top-right quadrent already defined.
|
285
|
+
def trq?
|
286
|
+
@trq && (@trq.payload || (@trq.tlq? || @trq.trq? || @trq.blq? || @trq.brq?))
|
287
|
+
end
|
288
|
+
|
289
|
+
# Returns true if this quadtree has a bottom-left quadrent already defined.
|
290
|
+
def blq?
|
291
|
+
@blq && (@blq.payload || (@blq.tlq? || @blq.trq? || @blq.blq? || @blq.brq?))
|
292
|
+
end
|
293
|
+
|
294
|
+
# Returns true if this quadtree has a bottom-right quadrent already defined.
|
295
|
+
def brq?
|
296
|
+
@brq && (@brq.payload || (@brq.tlq? || @brq.trq? || @brq.blq? || @brq.brq?))
|
297
|
+
end
|
298
|
+
|
299
|
+
# Returns true if Vector `v` falls inside of this quadtree.
|
300
|
+
def inside?(v)
|
301
|
+
# Greedy, so the order of comparison of quads will matter.
|
302
|
+
tl.x <= v.x && br.x >= v.x && tl.y >= v.y && br.y <= v.y
|
303
|
+
end
|
304
|
+
|
305
|
+
# Clips Vector `v` to the bounds of this quadtree.
|
306
|
+
def clip_vector(v)
|
307
|
+
v = v.dup
|
308
|
+
v.x = tl.x if v.x < tl.x
|
309
|
+
v.y = tl.y if v.y > tl.y
|
310
|
+
v.x = br.x if v.x > br.x
|
311
|
+
v.y = br.y if v.y < br.y
|
312
|
+
v
|
313
|
+
end
|
314
|
+
|
315
|
+
# Scans back up a quadtree from this node until a node is found that contains the region defined by `in_tl` and `in_br`, at which point the subtree size is returned from that point.
|
316
|
+
# (Only scans back up the tree, won't scan down.)
|
317
|
+
def family_size_at_width(in_tl, in_br)
|
318
|
+
if (inside?(in_tl) && inside?(in_br)) || parent.nil?
|
319
|
+
size
|
320
|
+
else
|
321
|
+
parent.family_size_at_width(in_tl, in_br)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def to_s
|
326
|
+
"[Quadtree #{object_id}, size: #{size}, depth: #{depth}]"
|
327
|
+
end
|
328
|
+
|
329
|
+
def inspect
|
330
|
+
to_s
|
331
|
+
end
|
332
|
+
|
333
|
+
private
|
334
|
+
|
335
|
+
def add_into_subtree(geo_data, depth = 1, jitter_proc = nil)
|
336
|
+
get_containing_quad(geo_data.vector).add(geo_data, depth, jitter_proc)
|
337
|
+
end
|
338
|
+
|
339
|
+
def get_containing_quad(vector)
|
340
|
+
if tlq.inside?(vector)
|
341
|
+
tlq
|
342
|
+
elsif trq.inside?(vector)
|
343
|
+
trq
|
344
|
+
elsif blq.inside?(vector)
|
345
|
+
blq
|
346
|
+
elsif brq.inside?(vector)
|
347
|
+
brq
|
348
|
+
else
|
349
|
+
raise "This shouldn't happen! #{vector} isn't in any of my quads! (#{self.to_s})"
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
data/lib/rquad/vector.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
# SOFTWARE INFO
|
2
|
+
#
|
3
|
+
# This file is part of the quadtree.rb Ruby quadtree library, distributed
|
4
|
+
# subject to the 'MIT License' below. This software is available for
|
5
|
+
# download at http://iterationlabs.com/free_software/quadtree.
|
6
|
+
#
|
7
|
+
# If you make modifications to this software and would be willing to
|
8
|
+
# contribute them back to the community, please send them to us for
|
9
|
+
# possible inclusion in future releases!
|
10
|
+
#
|
11
|
+
# LICENSE
|
12
|
+
#
|
13
|
+
# Copyright (c) 2008, Iteration Labs, LLC, http://iterationlabs.com
|
14
|
+
#
|
15
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
16
|
+
# of this software and associated documentation files (the "Software"), to deal
|
17
|
+
# in the Software without restriction, including without limitation the rights
|
18
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
19
|
+
# copies of the Software, and to permit persons to whom the Software is
|
20
|
+
# furnished to do so, subject to the following conditions:
|
21
|
+
#
|
22
|
+
# The above copyright notice and this permission notice shall be included in
|
23
|
+
# all copies or substantial portions of the Software.
|
24
|
+
#
|
25
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
26
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
27
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
28
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
29
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
30
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
31
|
+
# THE SOFTWARE.
|
32
|
+
#
|
33
|
+
|
34
|
+
# A class for representing a simple 2D or 3D vector. Primarily for use with the QudTree class. Very simple and minimilistic.
|
35
|
+
# irb(main):002:0> v = Vector.new(1, 2)
|
36
|
+
# => #<Vector:0xb7c8dacc @x=1.0, @y=2.0>
|
37
|
+
# irb(main):003:0> v + Vector.new(10, 11)
|
38
|
+
# => #<Vector:0xb7c86d30 @x=11.0, @y=13.0>
|
39
|
+
# irb(main):004:0> (v + Vector.new(10, 11)).length
|
40
|
+
# => 17.0293863659264
|
41
|
+
# irb(main):005:0> v * -2
|
42
|
+
# => #<Vector:0xb7c73834 @x=-2.0, @y=-4.0>
|
43
|
+
module RQuad
|
44
|
+
class Vector
|
45
|
+
# Initialize a Vector with either another Vector, an Array, or 2-3 numbers.
|
46
|
+
def initialize(x = nil, y = nil, z = nil)
|
47
|
+
if x && x.kind_of?(Vector)
|
48
|
+
@x = x.x
|
49
|
+
@y = x.y
|
50
|
+
@z = x.z
|
51
|
+
elsif x && x.kind_of?(Array)
|
52
|
+
@x = x[0]
|
53
|
+
@y = x[1]
|
54
|
+
@z = x[2]
|
55
|
+
else
|
56
|
+
@x = x.to_f if x
|
57
|
+
@y = y.to_f if y
|
58
|
+
@z = z.to_f if z
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# The X component of this vector.
|
63
|
+
def x
|
64
|
+
@x
|
65
|
+
end
|
66
|
+
|
67
|
+
# The Y component of this vector.
|
68
|
+
def y
|
69
|
+
@y
|
70
|
+
end
|
71
|
+
|
72
|
+
# The Z component of this vector.
|
73
|
+
def z
|
74
|
+
@z
|
75
|
+
end
|
76
|
+
|
77
|
+
# Set the X component of this vector.
|
78
|
+
def x=(new_x)
|
79
|
+
@x = new_x.to_f if new_x
|
80
|
+
end
|
81
|
+
|
82
|
+
# Set the Y component of this vector.
|
83
|
+
def y=(new_y)
|
84
|
+
@y = new_y.to_f if new_y
|
85
|
+
end
|
86
|
+
|
87
|
+
# Set the Z component of this vector.
|
88
|
+
def z=(new_z)
|
89
|
+
@z = new_z.to_f if new_z
|
90
|
+
end
|
91
|
+
|
92
|
+
# The length of this vector in 2D or 3D space.
|
93
|
+
def length
|
94
|
+
if z
|
95
|
+
Math.sqrt(x * x + y * y + z * z)
|
96
|
+
elsif x && y
|
97
|
+
Math.sqrt(x * x + y * y)
|
98
|
+
else
|
99
|
+
nil
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# The Euclidean distnce between this and another Vector `other`.
|
104
|
+
def dist_to(other)
|
105
|
+
(other - self).length
|
106
|
+
end
|
107
|
+
|
108
|
+
# This vector minus another Vector `other`.
|
109
|
+
def -(other)
|
110
|
+
self + other * -1
|
111
|
+
end
|
112
|
+
|
113
|
+
# Divide this vector by a `scalar`.
|
114
|
+
def /(scalar)
|
115
|
+
if z
|
116
|
+
Vector.new(x / scalar, y / scalar, z / scalar)
|
117
|
+
elsif x && y
|
118
|
+
Vector.new(x / scalar, y / scalar)
|
119
|
+
else
|
120
|
+
Vector.new
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Multiply this vector by a `scalar`.
|
125
|
+
def *(scalar)
|
126
|
+
if z
|
127
|
+
Vector.new(x * scalar, y * scalar, z * scalar)
|
128
|
+
elsif x && y
|
129
|
+
Vector.new(x * scalar, y * scalar)
|
130
|
+
else
|
131
|
+
Vector.new
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Add another Vector `other` to this vector.
|
136
|
+
def +(other)
|
137
|
+
if z && other.z
|
138
|
+
Vector.new(x + other.x, y + other.y, z + other.z)
|
139
|
+
elsif x && y && other.x && other.y
|
140
|
+
Vector.new(x + other.x, y + other.y)
|
141
|
+
else
|
142
|
+
Vector.new
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Test if this vector is equal to another Vector `other`.
|
147
|
+
def ==(other)
|
148
|
+
result = (other.x == x && other.y == y && other.z == z)
|
149
|
+
# puts "(#{other.x} == #{x} && #{other.y} == #{y} && #{other.z} == #{z}) = #{result.inspect}"
|
150
|
+
result
|
151
|
+
end
|
152
|
+
|
153
|
+
# Display this vector as a String, either in <x, y> or <x, y, z> notation.
|
154
|
+
def to_s
|
155
|
+
if z
|
156
|
+
"<#{x ? x : 'nil'}, #{y ? y : 'nil'}, #{z}>"
|
157
|
+
else
|
158
|
+
"<#{x ? x : 'nil'}, #{y ? y : 'nil'}>"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
data/rquad.gemspec
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{rquad}
|
8
|
+
s.version = "0.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Roman Scherer"]
|
12
|
+
s.date = %q{2010-11-02}
|
13
|
+
s.description = %q{Ruby Quadtree Library}
|
14
|
+
s.email = %q{roman.scherer@nugg.ad}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"CHANGES",
|
22
|
+
"LICENSE",
|
23
|
+
"README",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/rquad.rb",
|
27
|
+
"lib/rquad/quadtree.rb",
|
28
|
+
"lib/rquad/vector.rb",
|
29
|
+
"rquad.gemspec",
|
30
|
+
"test/helper.rb",
|
31
|
+
"test/rquad/test_quadtree.rb",
|
32
|
+
"test/rquad/test_vector.rb"
|
33
|
+
]
|
34
|
+
s.homepage = %q{http://github.com/r0man/rquad}
|
35
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
s.rubygems_version = %q{1.3.7}
|
38
|
+
s.summary = %q{Ruby Quadtree Library}
|
39
|
+
s.test_files = [
|
40
|
+
"test/rquad/test_quadtree.rb",
|
41
|
+
"test/rquad/test_vector.rb",
|
42
|
+
"test/helper.rb"
|
43
|
+
]
|
44
|
+
|
45
|
+
if s.respond_to? :specification_version then
|
46
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
47
|
+
s.specification_version = 3
|
48
|
+
|
49
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
50
|
+
else
|
51
|
+
end
|
52
|
+
else
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
data/test/helper.rb
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# SOFTWARE INFO
|
3
|
+
#
|
4
|
+
# This file is part of the quadtree.rb Ruby quadtree library, distributed
|
5
|
+
# subject to the 'MIT License' below. This software is available for
|
6
|
+
# download at http://iterationlabs.com/free_software/quadtree.
|
7
|
+
#
|
8
|
+
# If you make modifications to this software and would be willing to
|
9
|
+
# contribute them back to the community, please send them to us for
|
10
|
+
# possible inclusion in future releases!
|
11
|
+
#
|
12
|
+
# LICENSE
|
13
|
+
#
|
14
|
+
# Copyright (c) 2008, Iteration Labs, LLC, http://iterationlabs.com
|
15
|
+
#
|
16
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
17
|
+
# of this software and associated documentation files (the "Software"), to deal
|
18
|
+
# in the Software without restriction, including without limitation the rights
|
19
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
20
|
+
# copies of the Software, and to permit persons to whom the Software is
|
21
|
+
# furnished to do so, subject to the following conditions:
|
22
|
+
#
|
23
|
+
# The above copyright notice and this permission notice shall be included in
|
24
|
+
# all copies or substantial portions of the Software.
|
25
|
+
#
|
26
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
27
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
28
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
29
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
30
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
31
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
32
|
+
# THE SOFTWARE.
|
33
|
+
#
|
34
|
+
|
35
|
+
require 'test/unit'
|
36
|
+
require 'fileutils'
|
37
|
+
require "rquad/quadtree"
|
38
|
+
require "rquad/vector"
|
39
|
+
|
40
|
+
module RQuad
|
41
|
+
|
42
|
+
class TestQuadTree < Test::Unit::TestCase
|
43
|
+
def standard_quad
|
44
|
+
q = QuadTree.new(Vector.new(0,100), Vector.new(100,0))
|
45
|
+
q.add(QuadTreePayload.new(Vector.new(10, 10), :a))
|
46
|
+
q.add(QuadTreePayload.new(Vector.new(75, 75), :b))
|
47
|
+
q.add(QuadTreePayload.new(Vector.new(5, 99), :c))
|
48
|
+
q.add(QuadTreePayload.new(Vector.new(99, 5), :d))
|
49
|
+
q.add(QuadTreePayload.new(Vector.new(99, 4), :e))
|
50
|
+
q
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_new_quadtree
|
54
|
+
q = QuadTree.new(Vector.new(0,100), Vector.new(100,0))
|
55
|
+
assert_equal 0, q.size
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_adding_element
|
59
|
+
q = QuadTree.new(Vector.new(0,100), Vector.new(100,0))
|
60
|
+
q.add(QuadTreePayload.new(Vector.new(10, 10), 'hello'))
|
61
|
+
assert_equal 1, q.size
|
62
|
+
assert_equal 'hello', q.payload.first.data
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_adding_a_few_elements
|
66
|
+
q = QuadTree.new(Vector.new(0,100), Vector.new(100,0))
|
67
|
+
q.add(QuadTreePayload.new(Vector.new(10, 10), :a))
|
68
|
+
q.add(QuadTreePayload.new(Vector.new(75, 75), :b))
|
69
|
+
q.add(QuadTreePayload.new(Vector.new(5, 99), :c))
|
70
|
+
q.add(QuadTreePayload.new(Vector.new(99, 5), :d))
|
71
|
+
assert_equal 4, q.size
|
72
|
+
assert_equal nil, q.payload
|
73
|
+
assert_equal 1, q.tlq.size
|
74
|
+
assert_equal 1, q.trq.size
|
75
|
+
assert_equal 1, q.blq.size
|
76
|
+
assert_equal 1, q.brq.size
|
77
|
+
assert_equal :c, q.tlq.payload.first.data
|
78
|
+
assert_equal :b, q.trq.payload.first.data
|
79
|
+
assert_equal :a, q.blq.payload.first.data
|
80
|
+
assert_equal :d, q.brq.payload.first.data
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_greedy_adding_of_elements_on_boundaries
|
84
|
+
q = QuadTree.new(Vector.new(0,100), Vector.new(100,0))
|
85
|
+
q.add(QuadTreePayload.new(Vector.new(50, 50), :a))
|
86
|
+
assert_equal 1, q.size
|
87
|
+
assert_equal :a, q.payload.first.data
|
88
|
+
q.add(QuadTreePayload.new(Vector.new(0, 50), :b))
|
89
|
+
assert_equal :a, q.tlq.brq.payload.first.data
|
90
|
+
assert_equal :b, q.tlq.blq.payload.first.data
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_adding_two_identical_elements
|
94
|
+
q = QuadTree.new(Vector.new(0,100), Vector.new(100,0))
|
95
|
+
q.add(QuadTreePayload.new(Vector.new(10, 10), 'hello'))
|
96
|
+
assert_equal 1, q.size
|
97
|
+
assert_equal 'hello', q.payload.first.data
|
98
|
+
q.add(QuadTreePayload.new(Vector.new(10, 10), 'hi!'))
|
99
|
+
assert_equal 2, q.size
|
100
|
+
assert_equal 2, q.payload.length
|
101
|
+
assert_equal 'hello', q.payload.first.data
|
102
|
+
assert_equal 'hi!', q.payload.last.data
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_approx_near
|
106
|
+
q = standard_quad
|
107
|
+
assert_equal 5, q.approx_near(Vector.new(50, 50), 5).length
|
108
|
+
assert_equal 5, q.approx_near(Vector.new(50, 50), 10).length
|
109
|
+
assert_equal true, q.approx_near(Vector.new(99, 1), 3).any? {|i| i.vector == Vector.new(99, 4)}
|
110
|
+
assert_equal true, q.approx_near(Vector.new(99, 1), 3).any? {|i| i.vector == Vector.new(99, 5)}
|
111
|
+
assert_equal false, q.approx_near(Vector.new(99, 1), 3).any? {|i| i.vector == Vector.new(10, 10)}
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_payloads_in_region_in_small_region
|
115
|
+
q = standard_quad
|
116
|
+
assert_equal 2, q.payloads_in_region(Vector.new(98, 10), Vector.new(100, 0)).length
|
117
|
+
assert_equal 1, q.payloads_in_region(Vector.new(98, 10), Vector.new(100, 0), 1).length
|
118
|
+
assert_equal true, q.payloads_in_region(Vector.new(98, 10), Vector.new(100, 0)).any? {|i| i.vector == Vector.new(99, 4)}
|
119
|
+
assert_equal true, q.payloads_in_region(Vector.new(98, 10), Vector.new(100, 0)).any? {|i| i.vector == Vector.new(99, 5)}
|
120
|
+
assert_equal false, q.payloads_in_region(Vector.new(98, 10), Vector.new(100, 0)).any? {|i| i.vector == Vector.new(10, 10)}
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_payloads_in_region_in_large_region
|
124
|
+
q = standard_quad
|
125
|
+
assert_equal 5, q.payloads_in_region(Vector.new(0, 100), Vector.new(100, 0)).length
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_adding_a_third_element_to_two_identical_elements
|
129
|
+
q = QuadTree.new(Vector.new(0,100), Vector.new(100,0))
|
130
|
+
q.add(QuadTreePayload.new(Vector.new(10, 10), 'hello'))
|
131
|
+
assert_equal 1, q.size
|
132
|
+
assert_equal 'hello', q.payload.first.data
|
133
|
+
q.add(QuadTreePayload.new(Vector.new(10, 10), 'hi!'))
|
134
|
+
assert_equal 2, q.size
|
135
|
+
assert_equal 2, q.payload.length
|
136
|
+
assert_equal 'hello', q.payload.first.data
|
137
|
+
assert_equal 'hi!', q.payload.last.data
|
138
|
+
|
139
|
+
q.add(QuadTreePayload.new(Vector.new(20,20), 'third wheel'))
|
140
|
+
assert_equal 3, q.size
|
141
|
+
assert_equal nil, q.payload
|
142
|
+
assert_equal 2, q.blq.blq.blq.payload.length
|
143
|
+
assert_equal 1, q.blq.blq.trq.payload.length
|
144
|
+
assert_equal 'third wheel', q.blq.blq.trq.payload.first.data
|
145
|
+
|
146
|
+
# And test get_contained_payloads while we're at it.
|
147
|
+
assert_equal 3, q.get_contained_payloads.length
|
148
|
+
assert_equal 3, q.blq.blq.get_contained_payloads.length
|
149
|
+
assert_equal 1, q.blq.blq.trq.get_contained_payloads.length
|
150
|
+
assert_equal 2, q.blq.blq.blq.get_contained_payloads.length
|
151
|
+
assert_equal 0, q.blq.blq.brq.get_contained_payloads.length
|
152
|
+
end
|
153
|
+
|
154
|
+
def test_get_contained_payloads_with_number_limit
|
155
|
+
q = standard_quad
|
156
|
+
assert_equal 1, q.get_contained_payloads(:max_count => 1).length
|
157
|
+
assert_equal 4, q.get_contained_payloads(:max_count => 4).length
|
158
|
+
assert_equal 5, q.get_contained_payloads(:max_count => 20).length
|
159
|
+
assert_equal 5, q.get_contained_payloads.length
|
160
|
+
|
161
|
+
assert_equal true, q.get_contained_payloads(:max_count => 4).any? {|i| i.vector == Vector.new(10, 10)}
|
162
|
+
assert_equal true, q.get_contained_payloads(:max_count => 4).any? {|i| i.vector == Vector.new(75, 75)}
|
163
|
+
assert_equal true, q.get_contained_payloads(:max_count => 4).any? {|i| i.vector == Vector.new(5, 99)}
|
164
|
+
assert_equal true, q.get_contained_payloads(:max_count => 4).any? {|i| i.vector == Vector.new(99, 5)}
|
165
|
+
assert_equal false, q.get_contained_payloads(:max_count => 4).any? {|i| i.vector == Vector.new(11, 10)} # because it's lower and quads go clockwise
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_center_of_mass
|
169
|
+
q = QuadTree.new(Vector.new(0,100), Vector.new(100,0))
|
170
|
+
q.add(QuadTreePayload.new(Vector.new(30, 55), :a))
|
171
|
+
q.add(QuadTreePayload.new(Vector.new(70, 55), :b))
|
172
|
+
assert_equal Vector.new(50,55), q.center_of_mass
|
173
|
+
assert_equal Vector.new(30,55), q.tlq.center_of_mass
|
174
|
+
assert_equal Vector.new(70,55), q.trq.center_of_mass
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_depth
|
178
|
+
q = standard_quad
|
179
|
+
assert_equal 1, q.depth
|
180
|
+
assert_equal 2, q.tlq.depth
|
181
|
+
assert_equal 2, q.trq.depth
|
182
|
+
assert_equal 2, q.blq.depth
|
183
|
+
assert_equal 2, q.brq.depth
|
184
|
+
assert_equal 3, q.brq.brq.depth
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_leaves_each
|
188
|
+
q = standard_quad
|
189
|
+
leaves = []
|
190
|
+
q.leaves_each(100) do |leaf|
|
191
|
+
leaves << leaf
|
192
|
+
end
|
193
|
+
assert_equal 5, leaves.length
|
194
|
+
|
195
|
+
leaves = []
|
196
|
+
q.leaves_each(3) do |leaf|
|
197
|
+
leaves << leaf
|
198
|
+
end
|
199
|
+
assert_equal 4, leaves.length
|
200
|
+
end
|
201
|
+
|
202
|
+
def test_clip_vector
|
203
|
+
q = QuadTree.new(Vector.new(0,100), Vector.new(100,0))
|
204
|
+
assert_equal Vector.new(0, 100), q.clip_vector(Vector.new(-1, 100))
|
205
|
+
assert_equal Vector.new(100, 100), q.clip_vector(Vector.new(200, 200))
|
206
|
+
assert_equal Vector.new(0, 0), q.clip_vector(Vector.new(-200, -200))
|
207
|
+
assert_equal Vector.new(100, 0), q.clip_vector(Vector.new(200, -200))
|
208
|
+
end
|
209
|
+
|
210
|
+
def test_child_of_and_parent_of
|
211
|
+
q = standard_quad
|
212
|
+
assert_equal true, q.blq.child_of?(q)
|
213
|
+
assert_equal true, q.trq.child_of?(q)
|
214
|
+
assert_equal true, q.brq.tlq.child_of?(q.brq)
|
215
|
+
assert_equal true, q.brq.parent_of?(q.brq.brq)
|
216
|
+
end
|
217
|
+
|
218
|
+
def test_payloads_and_centers_in_region
|
219
|
+
q = standard_quad
|
220
|
+
assert_equal 1, q.payloads_and_centers_in_region(Vector.new(0,100), Vector.new(100, 0), 1)[:details].length
|
221
|
+
assert_equal 2, q.payloads_and_centers_in_region(Vector.new(0,100), Vector.new(100, 0), 1)[:details].first.last
|
222
|
+
assert_equal 3, q.payloads_and_centers_in_region(Vector.new(0,100), Vector.new(100, 0), 1)[:payloads].length
|
223
|
+
end
|
224
|
+
|
225
|
+
def test_parent
|
226
|
+
q = standard_quad
|
227
|
+
assert_equal q.brq, q.brq.brq.parent
|
228
|
+
assert_equal q, q.brq.parent
|
229
|
+
assert_equal nil, q.parent
|
230
|
+
end
|
231
|
+
|
232
|
+
def test_quadtreepayload_node
|
233
|
+
q = standard_quad
|
234
|
+
assert_equal q.blq, q.get_contained_payloads(:max_count => 5).find {|i| i.vector == Vector.new(10, 10)}.node
|
235
|
+
end
|
236
|
+
|
237
|
+
def test_family_size_at_width
|
238
|
+
q = standard_quad
|
239
|
+
tl = Vector.new(0, 49)
|
240
|
+
br = Vector.new(49, 0)
|
241
|
+
assert_equal 5, q.family_size_at_width(tl, br)
|
242
|
+
assert_equal 5, q.brq.brq.family_size_at_width(tl, br)
|
243
|
+
assert_equal 1, q.blq.family_size_at_width(tl, br)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# SOFTWARE INFO
|
3
|
+
#
|
4
|
+
# This file is part of the quadtree.rb Ruby quadtree library, distributed
|
5
|
+
# subject to the 'MIT License' below. This software is available for
|
6
|
+
# download at http://iterationlabs.com/free_software/quadtree.
|
7
|
+
#
|
8
|
+
# If you make modifications to this software and would be willing to
|
9
|
+
# contribute them back to the community, please send them to us for
|
10
|
+
# possible inclusion in future releases!
|
11
|
+
#
|
12
|
+
# LICENSE
|
13
|
+
#
|
14
|
+
# Copyright (c) 2008, Iteration Labs, LLC, http://iterationlabs.com
|
15
|
+
#
|
16
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
17
|
+
# of this software and associated documentation files (the "Software"), to deal
|
18
|
+
# in the Software without restriction, including without limitation the rights
|
19
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
20
|
+
# copies of the Software, and to permit persons to whom the Software is
|
21
|
+
# furnished to do so, subject to the following conditions:
|
22
|
+
#
|
23
|
+
# The above copyright notice and this permission notice shall be included in
|
24
|
+
# all copies or substantial portions of the Software.
|
25
|
+
#
|
26
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
27
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
28
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
29
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
30
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
31
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
32
|
+
# THE SOFTWARE.
|
33
|
+
#
|
34
|
+
|
35
|
+
require 'test/unit'
|
36
|
+
require 'fileutils'
|
37
|
+
require "rquad/vector"
|
38
|
+
|
39
|
+
module RQuad
|
40
|
+
|
41
|
+
class TestVector < Test::Unit::TestCase
|
42
|
+
def test_new_vector
|
43
|
+
assert_equal nil, Vector.new.x
|
44
|
+
assert_equal nil, Vector.new.y
|
45
|
+
assert_equal nil, Vector.new.z
|
46
|
+
|
47
|
+
v = Vector.new(1, 2, 3)
|
48
|
+
v2 = Vector.new(v)
|
49
|
+
assert_equal 1, v2.x
|
50
|
+
assert_equal 2, v2.y
|
51
|
+
assert_equal 3, v2.z
|
52
|
+
assert_equal v, v2
|
53
|
+
|
54
|
+
v3 = Vector.new([1, 2, 3])
|
55
|
+
assert_equal v, v3
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_multiply
|
59
|
+
assert_equal Vector.new(4, 6), Vector.new(2, 3) * 2
|
60
|
+
assert_equal Vector.new(0, -10), Vector.new(0, 1) * -10
|
61
|
+
assert_equal Vector.new(2, -10, 100), Vector.new(-0.2, 1, -10) * -10
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_add
|
65
|
+
assert_equal Vector.new(2, 3), Vector.new(1, 1) + Vector.new(1, 2)
|
66
|
+
assert_equal Vector.new(2, 3), Vector.new(-1, 10) + Vector.new(3, -7.0)
|
67
|
+
assert_equal Vector.new(2, 3, -1),
|
68
|
+
Vector.new(-1, 10, 2) + Vector.new(3, -7.0, -3)
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_subtract
|
72
|
+
assert_equal Vector.new(2, 3), Vector.new(4, 4) - Vector.new(2, 1)
|
73
|
+
assert_equal Vector.new(5, -1), Vector.new(-1, 10) - Vector.new(-6, 11)
|
74
|
+
assert_equal Vector.new(5, -1, 10),
|
75
|
+
Vector.new(-1, 10, 12) - Vector.new(-6, 11, 2)
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_length
|
79
|
+
assert_equal 5, Vector.new(5, 0).length
|
80
|
+
assert_equal 5.0, Vector.new(0, 5, 0).length
|
81
|
+
assert_equal 5, Vector.new(3, 4, 0).length
|
82
|
+
assert_equal 5, Vector.new(0, 3, 4).length
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_equality
|
86
|
+
assert_equal Vector.new(5, 0), Vector.new(5, 0)
|
87
|
+
assert_equal Vector.new(-1, -2), Vector.new(-1, -2)
|
88
|
+
assert_equal Vector.new(-1, -2, -4), Vector.new(-1, -2, -4)
|
89
|
+
assert_not_equal Vector.new(-1, -2, 5), Vector.new(-1, -2, 4)
|
90
|
+
assert_not_equal Vector.new(-1, -2), Vector.new(-1, -2, 4)
|
91
|
+
assert_not_equal Vector.new(-2, -2), Vector.new(-1, -2)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rquad
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Roman Scherer
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-11-02 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Ruby Quadtree Library
|
23
|
+
email: roman.scherer@nugg.ad
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- LICENSE
|
30
|
+
- README
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- CHANGES
|
34
|
+
- LICENSE
|
35
|
+
- README
|
36
|
+
- Rakefile
|
37
|
+
- VERSION
|
38
|
+
- lib/rquad.rb
|
39
|
+
- lib/rquad/quadtree.rb
|
40
|
+
- lib/rquad/vector.rb
|
41
|
+
- rquad.gemspec
|
42
|
+
- test/helper.rb
|
43
|
+
- test/rquad/test_quadtree.rb
|
44
|
+
- test/rquad/test_vector.rb
|
45
|
+
has_rdoc: true
|
46
|
+
homepage: http://github.com/r0man/rquad
|
47
|
+
licenses: []
|
48
|
+
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options:
|
51
|
+
- --charset=UTF-8
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
hash: 3
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.3.7
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: Ruby Quadtree Library
|
79
|
+
test_files:
|
80
|
+
- test/rquad/test_quadtree.rb
|
81
|
+
- test/rquad/test_vector.rb
|
82
|
+
- test/helper.rb
|