Skip to content

Commit 5d80e2d

Browse files
committed
Merge branch 'release/v1.0.0'
2 parents 1b4b480 + 4c7d626 commit 5d80e2d

15 files changed

+484
-33
lines changed

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 2
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true

README.md

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# Quadtree
22

3-
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/quadtree`. To experiment with that code, run `bin/console` for an interactive prompt.
4-
5-
TODO: Delete this and the text above, and describe your gem
3+
Quadtrees in Ruby. For searching spatially related nodes in some space, you know.
64

75
## Installation
86

@@ -22,7 +20,28 @@ Or install it yourself as:
2220

2321
## Usage
2422

25-
TODO: Write usage instructions here
23+
Load it in your code to start building quadtrees:
24+
25+
```ruby
26+
require 'quadtree'
27+
28+
boundary = Quadtree::AxisAlignedBoundingBox.new(Quadtree::Point.new(19.8470050, 60.3747940), 8944.0)
29+
qt = Quadtree::Quadtree.new(boundary)
30+
```
31+
32+
Then you can do lookups and such:
33+
34+
```ruby
35+
getaboden = Quadtree::Point.new(19.8470050, 60.3747940, "Getaboden")
36+
knutnas = Quadtree::Point.new(19.8271170, 60.3505570, "Knutnäs")
37+
boundary = Quadtree::AxisAlignedBoundingBox.new(Quadtree::Point.new(19.8470050, 60.3747940), 8944.0)
38+
boundary2 = Quadtree::AxisAlignedBoundingBox.new(Quadtree::Point.new(19.8470050, 60.3747940), 4472.0)
39+
qt.insert! getaboden
40+
qt.insert! knutnas
41+
qt.query_range(boundary2)
42+
# [#<Quadtree::Point:0x00007fdcb19e0698 @data="Getaboden", @x=19.847005, @y=60.374794>,
43+
#<Quadtree::Point:0x00007fdcb19ec7b8 @data="Knutnäs", @x=19.827117, @y=60.350557>]
44+
```
2645

2746
## Development
2847

@@ -32,7 +51,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
3251

3352
## Contributing
3453

35-
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/quadtree. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
54+
Bug reports and pull requests are welcome on Bitbucket at https://bitbucket.org/janlindblom/ruby-quadtree. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
3655

3756
## License
3857

Rakefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
require "bundler/gem_tasks"
22
require "rspec/core/rake_task"
3+
require "yard"
4+
require "yard/rake/yardoc_task"
35

46
RSpec::Core::RakeTask.new(:spec)
57

8+
YARD::Rake::YardocTask.new do |t|
9+
t.files = ['lib/**/*.rb']
10+
t.stats_options = ['--list-undoc']
11+
end
12+
613
task :default => :spec

bin/console

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,5 @@
33
require "bundler/setup"
44
require "quadtree"
55

6-
# You can add fixtures and/or initialization code here to make experimenting
7-
# with your gem easier. You can also use a different console, if you like.
8-
9-
# (If you use this, don't forget to add pry to your Gemfile!)
10-
# require "pry"
11-
# Pry.start
12-
13-
require "irb"
14-
IRB.start(__FILE__)
6+
require "pry"
7+
Pry.start

lib/quadtree.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
require "quadtree/version"
22

3+
require "quadtree/point"
4+
require "quadtree/axis_aligned_bounding_box"
5+
require "quadtree/quadtree"
6+
7+
# Quadtrees.
8+
#
9+
# @since 1.0.0
10+
# @author Jan Lindblom <janlindblom@fastmail.fm>
311
module Quadtree
4-
# Your code goes here...
12+
513
end
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
module Quadtree
2+
# Axis-aligned bounding box with half dimension and center.
3+
class AxisAlignedBoundingBox
4+
5+
# @return [Point]
6+
attr_accessor :center
7+
# @return [Float]
8+
attr_accessor :half_dimension
9+
10+
# @param [Point] center
11+
# @param [Float] half_dimension
12+
def initialize(center, half_dimension)
13+
@center = center
14+
@half_dimension = half_dimension.to_f
15+
end
16+
17+
# @param [Point] point
18+
# @return [Boolean]
19+
def contains_point?(point)
20+
if point.x >= self.center.x - self.half_dimension and point.x <= self.center.x + self.half_dimension
21+
if point.y >= self.center.y - self.half_dimension and point.y <= self.center.y + self.half_dimension
22+
return true
23+
end
24+
end
25+
false
26+
end
27+
28+
# @param [AxisAlignedBoundingBox] other
29+
# @return [Boolean]
30+
def intersects?(other)
31+
other_lt_corner = Point.new(other.left, other.top)
32+
other_rt_corner = Point.new(other.right, other.top)
33+
other_lb_corner = Point.new(other.left, other.bottom)
34+
other_rb_corner = Point.new(other.right, other.bottom)
35+
36+
[other_lt_corner, other_rt_corner, other_lb_corner, other_rb_corner].each do |corner|
37+
return true if self.contains_point?(corner)
38+
end
39+
false
40+
end
41+
42+
# @return [Float]
43+
def left
44+
@center.x - @half_dimension
45+
end
46+
47+
# @return [Float]
48+
def right
49+
@center.x + @half_dimension
50+
end
51+
52+
# Get the Y coordinate of the top edge of this AABB.
53+
#
54+
# @return [Float] the Y coordinate of the top edge of this AABB.
55+
def top
56+
@center.y + @half_dimension
57+
end
58+
59+
# Get the Y coordinate of the bottom edge of this AABB.
60+
#
61+
# @return [Float] the Y coordinate of the bottom edge of this AABB.
62+
def bottom
63+
@center.y - @half_dimension
64+
end
65+
66+
# @return [Float]
67+
def width
68+
span
69+
end
70+
71+
# @return [Float]
72+
def height
73+
span
74+
end
75+
76+
private
77+
78+
def span
79+
@half_dimension * 2
80+
end
81+
end
82+
end

lib/quadtree/point.rb

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
module Quadtree
2+
# Simple coordinate object to represent points in some space.
3+
class Point
4+
5+
# @return [Float] X coordinate
6+
attr_accessor :x
7+
8+
# @return [Float] Y coordinate
9+
attr_accessor :y
10+
11+
# Payload attached to this {Point}.
12+
attr_accessor :data
13+
14+
# Create a new {Point}.
15+
#
16+
# @param [Float] x X coordinate
17+
# @param [Float] y Y coordinate
18+
def initialize(x, y, data=nil)
19+
@x = x
20+
@y = y
21+
@data = data unless data.nil?
22+
end
23+
24+
# This will calculate distance to another, given that they are both
25+
# on the same flat two dimensional plane.
26+
#
27+
# @param [Point] other the other {Point}.
28+
# @return [Float] the distance to the other {Point}.
29+
def distance_to(other)
30+
Math.sqrt((other.x - self.x) ** 2 + (other.y - self.y) ** 2)
31+
end
32+
33+
# This will calculate distance to another point using the Haversine
34+
# formula. This means that it will treat #x as longitude and #y as
35+
# latitude!
36+
#
37+
# a = sin²(Δφ/2) + cos φ_1 ⋅ cos φ_2 ⋅ sin²(Δλ/2)
38+
# c = 2 ⋅ atan2( √a, √(1−a) )
39+
# d = R ⋅ c
40+
#
41+
# where φ is latitude, λ is longitude, R is earth’s radius (mean
42+
# radius = 6 371 km);
43+
# note that angles need to be in radians to pass to trig functions!
44+
#
45+
# @param [Point] other the other {Point}.
46+
# @return [Float] the distance, in meters, to the other {Point}.
47+
def haversine_distance_to(other)
48+
# earth's radius
49+
r = 6371 * 1000.0
50+
# coverting degrees to radians
51+
lat1 = self.y * (Math::PI / 180)
52+
lat2 = other.y * (Math::PI / 180)
53+
dlat = (other.y - self.y) * (Math::PI / 180)
54+
dlon = (other.x - self.x) * (Math::PI / 180)
55+
56+
# a = sin²(Δφ/2) + cos φ_1 ⋅ cos φ_2 ⋅ sin²(Δλ/2)
57+
a = Math.sin(dlat / 2) * Math.sin(dlat / 2) +
58+
Math.cos(lat1) * Math.cos(lat2) *
59+
Math.sin(dlon / 2) * Math.sin(dlon / 2)
60+
# c = 2 ⋅ atan2( √a, √(1−a) )
61+
c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
62+
# d = R ⋅ c
63+
return r * c
64+
end
65+
end
66+
end

lib/quadtree/quadtree.rb

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
module Quadtree
2+
# A Quadtree
3+
class Quadtree
4+
5+
# Arbitrary constant to indicate how many elements can be stored in this
6+
# quad tree node.
7+
# @return [Integer]
8+
NODE_CAPACITY = 4
9+
10+
# Axis-aligned bounding box stored as a center with half-dimensions to
11+
# represent the boundaries of this quad tree.
12+
# @return [AxisAlignedBoundingBox]
13+
attr_accessor :boundary
14+
15+
# Points in this quad tree node.
16+
# @return [Array<Point>]
17+
attr_accessor :points
18+
19+
# Children
20+
21+
# @return [Quadtree]
22+
attr_accessor :north_west
23+
# @return [Quadtree]
24+
attr_accessor :north_east
25+
# @return [Quadtree]
26+
attr_accessor :south_west
27+
# @return [Quadtree]
28+
attr_accessor :south_east
29+
30+
def initialize(boundary)
31+
@boundary = boundary
32+
@points = []
33+
@north_west = nil
34+
@north_east = nil
35+
@south_west = nil
36+
@south_east = nil
37+
end
38+
39+
# @param [Point] point
40+
# @return [Boolean]
41+
def insert!(point)
42+
return false unless @boundary.contains_point?(point)
43+
44+
if points.size < NODE_CAPACITY
45+
@points << point
46+
return true
47+
end
48+
49+
subdivide! if @north_west.nil?
50+
return true if @north_west.insert(point)
51+
return true if @north_east.insert(point)
52+
return true if @south_west.insert(point)
53+
return true if @south_east.insert(point)
54+
55+
false
56+
end
57+
58+
# Finds all points contained within a range.
59+
#
60+
# @param [AxisAlignedBoundingBox] range the range to search within.
61+
# @return [Array<Point>]
62+
def query_range(range)
63+
# Prepare an array of results
64+
points_in_range = []
65+
66+
# Automatically abort if the range does not intersect this quad
67+
return points_in_range unless @boundary.intersects?(range)
68+
69+
# Check objects at this quad level
70+
@points.each do |point|
71+
points_in_range << point if range.contains_point?(point)
72+
end
73+
74+
# Terminate here, if there are no children
75+
return points_in_range if @north_west.nil?
76+
77+
# Otherwise, add the points from the children
78+
points_in_range += @north_west.query_range(range)
79+
points_in_range += @north_east.query_range(range)
80+
points_in_range += @south_west.query_range(range)
81+
points_in_range += @south_east.query_range(range)
82+
83+
points_in_range
84+
end
85+
86+
private
87+
88+
# @return [Boolean]
89+
def subdivide!
90+
left_edge = @boundary.left
91+
right_edge = @boundary.right
92+
top_edge = @boundary.top
93+
bottom_edge = @boundary.bottom
94+
quad_half_dimension = @boundary.half_dimension / 2
95+
96+
north_west_center = Quadtree::Point.new left_edge + quad_half_dimension, top_edge - quad_half_dimension
97+
north_east_center = Quadtree::Point.new right_edge - quad_half_dimension, top_edge - quad_half_dimension
98+
south_east_center = Quadtree::Point.new left_edge + quad_half_dimension, bottom_edge + quad_half_dimension
99+
south_west_center = Quadtree::Point.new right_edge - quad_half_dimension, bottom_edge + quad_half_dimension
100+
101+
north_west_boundary = Quadtree::AxisAlignedBoundingBox.new north_west_center, quad_half_dimension
102+
north_east_boundary = Quadtree::AxisAlignedBoundingBox.new north_east_center, quad_half_dimension
103+
south_west_boundary = Quadtree::AxisAlignedBoundingBox.new south_west_center, quad_half_dimension
104+
south_east_boundary = Quadtree::AxisAlignedBoundingBox.new south_east_center, quad_half_dimension
105+
106+
@north_west = Quadtree::Quadtree.new north_west_boundary
107+
@north_east = Quadtree::Quadtree.new north_east_boundary
108+
@south_west = Quadtree::Quadtree.new south_west_boundary
109+
@south_east = Quadtree::Quadtree.new south_east_boundary
110+
111+
true
112+
rescue
113+
false
114+
end
115+
end
116+
end

lib/quadtree/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module Quadtree
2-
VERSION = "0.1.0"
2+
VERSION = "1.0.0"
33
end

0 commit comments

Comments
 (0)