quadtree 1.0.4 → 1.0.6
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.
- checksums.yaml +5 -5
- data/.editorconfig +12 -12
- data/.gitignore +208 -208
- data/.rspec +3 -3
- data/CODE_OF_CONDUCT.md +74 -74
- data/Gemfile +6 -6
- data/LICENSE.txt +21 -21
- data/README.md +65 -64
- data/Rakefile +55 -13
- data/VERSION +1 -0
- data/lib/quadtree.rb +14 -12
- data/lib/quadtree/axis_aligned_bounding_box.rb +108 -108
- data/lib/quadtree/point.rb +107 -71
- data/lib/quadtree/quadtree.rb +127 -127
- data/lib/quadtree/unknown_type_error.rb +7 -0
- data/lib/quadtree/version.rb +5 -4
- data/quadtree.gemspec +47 -28
- metadata +21 -9
- data/.travis.yml +0 -5
- data/bin/console +0 -7
- data/bin/setup +0 -8
- data/bitbucket-pipelines.yml +0 -17
data/lib/quadtree/point.rb
CHANGED
@@ -1,71 +1,107 @@
|
|
1
|
-
module Quadtree
|
2
|
-
# Simple coordinate object to represent points in some space.
|
3
|
-
class Point
|
4
|
-
|
5
|
-
# The X coordinate of this instance.
|
6
|
-
# @return [Float] X coordinate.
|
7
|
-
attr_accessor :x
|
8
|
-
|
9
|
-
# The Y coordinate of this instance.
|
10
|
-
# @return [Float] Y coordinate.
|
11
|
-
attr_accessor :y
|
12
|
-
|
13
|
-
# Optional payload attached to this instance.
|
14
|
-
# @return [Object] payload attached to this instance.
|
15
|
-
attr_accessor :data
|
16
|
-
|
17
|
-
# @param x [Float,
|
18
|
-
# @param y [Float,
|
19
|
-
# @param data [Object] payload payload attached to this instance
|
20
|
-
# (optional).
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
#
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
1
|
+
module Quadtree
|
2
|
+
# Simple coordinate object to represent points in some space.
|
3
|
+
class Point
|
4
|
+
|
5
|
+
# The X coordinate of this instance.
|
6
|
+
# @return [Float, Integer] X coordinate.
|
7
|
+
attr_accessor :x
|
8
|
+
|
9
|
+
# The Y coordinate of this instance.
|
10
|
+
# @return [Float, Integer] Y coordinate.
|
11
|
+
attr_accessor :y
|
12
|
+
|
13
|
+
# Optional payload attached to this instance.
|
14
|
+
# @return [Object] payload attached to this instance.
|
15
|
+
attr_accessor :data
|
16
|
+
|
17
|
+
# @param x [Float, Integer] X coordinate.
|
18
|
+
# @param y [Float, Integer] Y coordinate.
|
19
|
+
# @param data [Object] payload payload attached to this instance
|
20
|
+
# (optional).
|
21
|
+
# @raise [UnknownTypeError] if one or more input parameters (+x+ and +y+)
|
22
|
+
# has the wrong type.
|
23
|
+
def initialize(x, y, data=nil)
|
24
|
+
|
25
|
+
self.x = get_typed_numeric(x)
|
26
|
+
self.y = get_typed_numeric(y)
|
27
|
+
|
28
|
+
self.data = data unless data.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
# This will calculate distance to another {Point}, given that they are
|
32
|
+
# both in the same 2D space.
|
33
|
+
#
|
34
|
+
# @param other [Point] the other {Point}.
|
35
|
+
# @return [Float] the distance to the other {Point}.
|
36
|
+
def distance_to(other)
|
37
|
+
Math.sqrt((other.x - self.x) ** 2 + (other.y - self.y) ** 2)
|
38
|
+
end
|
39
|
+
|
40
|
+
# This will calculate distance to another {Point} using the Haversine
|
41
|
+
# formula. This means that it will treat {#x} as longitude and {#y} as
|
42
|
+
# latitude!
|
43
|
+
#
|
44
|
+
# a = sin²(Δφ/2) + cos φ_1 ⋅ cos φ_2 ⋅ sin²(Δλ/2)
|
45
|
+
#
|
46
|
+
# c = 2 ⋅ atan2( √a, √(1−a) )
|
47
|
+
#
|
48
|
+
# d = R ⋅ c
|
49
|
+
#
|
50
|
+
# where φ is latitude, λ is longitude, R is earth’s radius (mean
|
51
|
+
# radius = 6 371 km);
|
52
|
+
# note that angles need to be in radians to pass to trig functions!
|
53
|
+
#
|
54
|
+
# @param other [Point] the other {Point}.
|
55
|
+
# @return [Float] the distance, in meters, to the other {Point}.
|
56
|
+
def haversine_distance_to(other)
|
57
|
+
# earth's radius
|
58
|
+
r = 6371 * 1000.0
|
59
|
+
# coverting degrees to radians
|
60
|
+
lat1 = self.y * (Math::PI / 180.0)
|
61
|
+
lat2 = other.y * (Math::PI / 180.0)
|
62
|
+
dlat = (other.y - self.y) * (Math::PI / 180.0)
|
63
|
+
dlon = (other.x - self.x) * (Math::PI / 180.0)
|
64
|
+
|
65
|
+
# a = sin²(Δφ/2) + cos φ_1 ⋅ cos φ_2 ⋅ sin²(Δλ/2)
|
66
|
+
a = Math.sin(dlat / 2.0) * Math.sin(dlat / 2.0) +
|
67
|
+
Math.cos(lat1) * Math.cos(lat2) *
|
68
|
+
Math.sin(dlon / 2.0) * Math.sin(dlon / 2.0)
|
69
|
+
# c = 2 ⋅ atan2( √a, √(1−a) )
|
70
|
+
c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
|
71
|
+
# d = R ⋅ c
|
72
|
+
return r * c
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def get_typed_numeric(any_input)
|
78
|
+
typed_output = nil
|
79
|
+
# Try integer first since float will parse integers too
|
80
|
+
return get_integer(any_input) unless get_integer(any_input).nil?
|
81
|
+
# Try Float next
|
82
|
+
return get_float(any_input) unless get_float(any_input).nil?
|
83
|
+
|
84
|
+
raise UnknownTypeError.new "Unknown type for parameter: #{any_input.class}"
|
85
|
+
|
86
|
+
#typed_output
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_integer(any_input)
|
90
|
+
return Integer(any_input) if any_input.is_a? String
|
91
|
+
return any_input if any_input.is_a? Integer
|
92
|
+
|
93
|
+
nil
|
94
|
+
rescue
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def get_float(any_input)
|
99
|
+
return Float(any_input) if any_input.is_a? String
|
100
|
+
return any_input if any_input.is_a? Float
|
101
|
+
|
102
|
+
nil
|
103
|
+
rescue
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/quadtree/quadtree.rb
CHANGED
@@ -1,127 +1,127 @@
|
|
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] number of {Point}s this {Quadtree} can hold.
|
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
|
-
# North west corner of this quad.
|
22
|
-
# @return [Quadtree]
|
23
|
-
attr_accessor :north_west
|
24
|
-
|
25
|
-
# North east corner of this quad.
|
26
|
-
# @return [Quadtree]
|
27
|
-
attr_accessor :north_east
|
28
|
-
|
29
|
-
# South west corner of this quad.
|
30
|
-
# @return [Quadtree]
|
31
|
-
attr_accessor :south_west
|
32
|
-
|
33
|
-
# South east corner of this quad.
|
34
|
-
# @return [Quadtree]
|
35
|
-
attr_accessor :south_east
|
36
|
-
|
37
|
-
# @param boundary [AxisAlignedBoundingBox] the boundary for this {Quadtree}
|
38
|
-
def initialize(boundary)
|
39
|
-
self.boundary = boundary
|
40
|
-
self.points = []
|
41
|
-
self.north_west = nil
|
42
|
-
self.north_east = nil
|
43
|
-
self.south_west = nil
|
44
|
-
self.south_east = nil
|
45
|
-
end
|
46
|
-
|
47
|
-
# Insert a {Point} in this {Quadtree}.
|
48
|
-
#
|
49
|
-
# @param point [Point] the point to insert.
|
50
|
-
# @return [Boolean] +true+ on success, +false+ otherwise.
|
51
|
-
def insert!(point)
|
52
|
-
return false unless self.boundary.contains_point?(point)
|
53
|
-
|
54
|
-
if self.points.size < NODE_CAPACITY
|
55
|
-
self.points << point
|
56
|
-
return true
|
57
|
-
end
|
58
|
-
|
59
|
-
subdivide! if self.north_west.nil?
|
60
|
-
return true if self.north_west.insert!(point)
|
61
|
-
return true if self.north_east.insert!(point)
|
62
|
-
return true if self.south_west.insert!(point)
|
63
|
-
return true if self.south_east.insert!(point)
|
64
|
-
|
65
|
-
false
|
66
|
-
end
|
67
|
-
|
68
|
-
# Finds all points contained within a range.
|
69
|
-
#
|
70
|
-
# @param range [AxisAlignedBoundingBox] the range to search within.
|
71
|
-
# @return [Array<Point>]
|
72
|
-
def query_range(range)
|
73
|
-
# Prepare an array of results
|
74
|
-
points_in_range = []
|
75
|
-
|
76
|
-
# Automatically abort if the range does not intersect this quad
|
77
|
-
return points_in_range unless self.boundary.intersects?(range)
|
78
|
-
|
79
|
-
# Check objects at this quad level
|
80
|
-
self.points.each do |point|
|
81
|
-
points_in_range << point if range.contains_point?(point)
|
82
|
-
end
|
83
|
-
|
84
|
-
# Terminate here, if there are no children
|
85
|
-
return points_in_range if self.north_west.nil?
|
86
|
-
|
87
|
-
# Otherwise, add the points from the children
|
88
|
-
points_in_range += self.north_west.query_range(range)
|
89
|
-
points_in_range += self.north_east.query_range(range)
|
90
|
-
points_in_range += self.south_west.query_range(range)
|
91
|
-
points_in_range += self.south_east.query_range(range)
|
92
|
-
|
93
|
-
points_in_range
|
94
|
-
end
|
95
|
-
|
96
|
-
private
|
97
|
-
|
98
|
-
# @return [Boolean]
|
99
|
-
def subdivide!
|
100
|
-
left_edge = self.boundary.left
|
101
|
-
right_edge = self.boundary.right
|
102
|
-
top_edge = self.boundary.top
|
103
|
-
bottom_edge = self.boundary.bottom
|
104
|
-
quad_half_dimension = self.boundary.half_dimension / 2
|
105
|
-
|
106
|
-
north_west_center = Point.new left_edge + quad_half_dimension, top_edge - quad_half_dimension
|
107
|
-
north_east_center = Point.new right_edge - quad_half_dimension, top_edge - quad_half_dimension
|
108
|
-
south_east_center = Point.new left_edge + quad_half_dimension, bottom_edge + quad_half_dimension
|
109
|
-
south_west_center = Point.new right_edge - quad_half_dimension, bottom_edge + quad_half_dimension
|
110
|
-
|
111
|
-
north_west_boundary = AxisAlignedBoundingBox.new north_west_center, quad_half_dimension
|
112
|
-
north_east_boundary = AxisAlignedBoundingBox.new north_east_center, quad_half_dimension
|
113
|
-
south_west_boundary = AxisAlignedBoundingBox.new south_west_center, quad_half_dimension
|
114
|
-
south_east_boundary = AxisAlignedBoundingBox.new south_east_center, quad_half_dimension
|
115
|
-
|
116
|
-
self.north_west = Quadtree.new north_west_boundary
|
117
|
-
self.north_east = Quadtree.new north_east_boundary
|
118
|
-
self.south_west = Quadtree.new south_west_boundary
|
119
|
-
self.south_east = Quadtree.new south_east_boundary
|
120
|
-
|
121
|
-
true
|
122
|
-
rescue => error
|
123
|
-
puts "Something went wrong: #{error}"
|
124
|
-
false
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
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] number of {Point}s this {Quadtree} can hold.
|
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
|
+
# North west corner of this quad.
|
22
|
+
# @return [Quadtree]
|
23
|
+
attr_accessor :north_west
|
24
|
+
|
25
|
+
# North east corner of this quad.
|
26
|
+
# @return [Quadtree]
|
27
|
+
attr_accessor :north_east
|
28
|
+
|
29
|
+
# South west corner of this quad.
|
30
|
+
# @return [Quadtree]
|
31
|
+
attr_accessor :south_west
|
32
|
+
|
33
|
+
# South east corner of this quad.
|
34
|
+
# @return [Quadtree]
|
35
|
+
attr_accessor :south_east
|
36
|
+
|
37
|
+
# @param boundary [AxisAlignedBoundingBox] the boundary for this {Quadtree}
|
38
|
+
def initialize(boundary)
|
39
|
+
self.boundary = boundary
|
40
|
+
self.points = []
|
41
|
+
self.north_west = nil
|
42
|
+
self.north_east = nil
|
43
|
+
self.south_west = nil
|
44
|
+
self.south_east = nil
|
45
|
+
end
|
46
|
+
|
47
|
+
# Insert a {Point} in this {Quadtree}.
|
48
|
+
#
|
49
|
+
# @param point [Point] the point to insert.
|
50
|
+
# @return [Boolean] +true+ on success, +false+ otherwise.
|
51
|
+
def insert!(point)
|
52
|
+
return false unless self.boundary.contains_point?(point)
|
53
|
+
|
54
|
+
if self.points.size < NODE_CAPACITY
|
55
|
+
self.points << point
|
56
|
+
return true
|
57
|
+
end
|
58
|
+
|
59
|
+
subdivide! if self.north_west.nil?
|
60
|
+
return true if self.north_west.insert!(point)
|
61
|
+
return true if self.north_east.insert!(point)
|
62
|
+
return true if self.south_west.insert!(point)
|
63
|
+
return true if self.south_east.insert!(point)
|
64
|
+
|
65
|
+
false
|
66
|
+
end
|
67
|
+
|
68
|
+
# Finds all points contained within a range.
|
69
|
+
#
|
70
|
+
# @param range [AxisAlignedBoundingBox] the range to search within.
|
71
|
+
# @return [Array<Point>]
|
72
|
+
def query_range(range)
|
73
|
+
# Prepare an array of results
|
74
|
+
points_in_range = []
|
75
|
+
|
76
|
+
# Automatically abort if the range does not intersect this quad
|
77
|
+
return points_in_range unless self.boundary.intersects?(range)
|
78
|
+
|
79
|
+
# Check objects at this quad level
|
80
|
+
self.points.each do |point|
|
81
|
+
points_in_range << point if range.contains_point?(point)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Terminate here, if there are no children
|
85
|
+
return points_in_range if self.north_west.nil?
|
86
|
+
|
87
|
+
# Otherwise, add the points from the children
|
88
|
+
points_in_range += self.north_west.query_range(range)
|
89
|
+
points_in_range += self.north_east.query_range(range)
|
90
|
+
points_in_range += self.south_west.query_range(range)
|
91
|
+
points_in_range += self.south_east.query_range(range)
|
92
|
+
|
93
|
+
points_in_range
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
# @return [Boolean]
|
99
|
+
def subdivide!
|
100
|
+
left_edge = self.boundary.left
|
101
|
+
right_edge = self.boundary.right
|
102
|
+
top_edge = self.boundary.top
|
103
|
+
bottom_edge = self.boundary.bottom
|
104
|
+
quad_half_dimension = self.boundary.half_dimension / 2
|
105
|
+
|
106
|
+
north_west_center = Point.new left_edge + quad_half_dimension, top_edge - quad_half_dimension
|
107
|
+
north_east_center = Point.new right_edge - quad_half_dimension, top_edge - quad_half_dimension
|
108
|
+
south_east_center = Point.new left_edge + quad_half_dimension, bottom_edge + quad_half_dimension
|
109
|
+
south_west_center = Point.new right_edge - quad_half_dimension, bottom_edge + quad_half_dimension
|
110
|
+
|
111
|
+
north_west_boundary = AxisAlignedBoundingBox.new north_west_center, quad_half_dimension
|
112
|
+
north_east_boundary = AxisAlignedBoundingBox.new north_east_center, quad_half_dimension
|
113
|
+
south_west_boundary = AxisAlignedBoundingBox.new south_west_center, quad_half_dimension
|
114
|
+
south_east_boundary = AxisAlignedBoundingBox.new south_east_center, quad_half_dimension
|
115
|
+
|
116
|
+
self.north_west = Quadtree.new north_west_boundary
|
117
|
+
self.north_east = Quadtree.new north_east_boundary
|
118
|
+
self.south_west = Quadtree.new south_west_boundary
|
119
|
+
self.south_east = Quadtree.new south_east_boundary
|
120
|
+
|
121
|
+
true
|
122
|
+
rescue => error
|
123
|
+
puts "Something went wrong: #{error}"
|
124
|
+
false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/quadtree/version.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
require "version"
|
2
|
+
|
3
|
+
module Quadtree
|
4
|
+
is_versioned
|
5
|
+
end
|