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.
@@ -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, Numeric] X coordinate.
18
- # @param y [Float, Numeric] Y coordinate.
19
- # @param data [Object] payload payload attached to this instance
20
- # (optional).
21
- def initialize(x, y, data=nil)
22
- @x = x.to_f
23
- @y = y.to_f
24
- @data = data unless data.nil?
25
- end
26
-
27
- # This will calculate distance to another {Point}, given that they are
28
- # both in the same 2D space.
29
- #
30
- # @param other [Point] the other {Point}.
31
- # @return [Float] the distance to the other {Point}.
32
- def distance_to(other)
33
- Math.sqrt((other.x - self.x) ** 2 + (other.y - self.y) ** 2)
34
- end
35
-
36
- # This will calculate distance to another {Point} using the Haversine
37
- # formula. This means that it will treat {#x} as longitude and {#y} as
38
- # latitude!
39
- #
40
- # a = sin²(Δφ/2) + cos φ_1 cos φ_2 ⋅ sin²(Δλ/2)
41
- #
42
- # c = 2 ⋅ atan2( √a, √(1−a) )
43
- #
44
- # d = Rc
45
- #
46
- # where φ is latitude, λ is longitude, R is earth’s radius (mean
47
- # radius = 6 371 km);
48
- # note that angles need to be in radians to pass to trig functions!
49
- #
50
- # @param other [Point] the other {Point}.
51
- # @return [Float] the distance, in meters, to the other {Point}.
52
- def haversine_distance_to(other)
53
- # earth's radius
54
- r = 6371 * 1000.0
55
- # coverting degrees to radians
56
- lat1 = self.y * (Math::PI / 180.0)
57
- lat2 = other.y * (Math::PI / 180.0)
58
- dlat = (other.y - self.y) * (Math::PI / 180.0)
59
- dlon = (other.x - self.x) * (Math::PI / 180.0)
60
-
61
- # a = sin²(Δφ/2) + cos φ_1 ⋅ cos φ_2 ⋅ sin²(Δλ/2)
62
- a = Math.sin(dlat / 2.0) * Math.sin(dlat / 2.0) +
63
- Math.cos(lat1) * Math.cos(lat2) *
64
- Math.sin(dlon / 2.0) * Math.sin(dlon / 2.0)
65
- # c = 2 ⋅ atan2( √a, (1−a) )
66
- c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
67
- # d = R ⋅ c
68
- return r * c
69
- end
70
- end
71
- end
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
@@ -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
@@ -0,0 +1,7 @@
1
+ module Quadtree
2
+ # An error for unknown types.
3
+ # @since 1.0.5
4
+ class UnknownTypeError < StandardError
5
+
6
+ end
7
+ end
@@ -1,4 +1,5 @@
1
- module Quadtree
2
- # Current version
3
- VERSION = "1.0.4"
4
- end
1
+ require "version"
2
+
3
+ module Quadtree
4
+ is_versioned
5
+ end