Class: Geospatial::Polygon
- Inherits:
-
Object
- Object
- Geospatial::Polygon
- Defined in:
- lib/geospatial/polygon.rb
Instance Attribute Summary collapse
-
#points ⇒ Object
readonly
Returns the value of attribute points.
Class Method Summary collapse
Instance Method Summary collapse
- #[](index) ⇒ Object
- #area(radius = 1.0) ⇒ Object
- #bounding_box ⇒ Object
- #edge_intersection(a, b) ⇒ Object
- #edges ⇒ Object
- #freeze ⇒ Object
- #include?(other) ⇒ Boolean
- #include_point?(point) ⇒ Boolean
-
#initialize(points, bounding_box = nil) ⇒ Polygon
constructor
A new instance of Polygon.
- #intersect?(other) ⇒ Boolean
- #intersect_with_box?(other) ⇒ Boolean
- #simplify ⇒ Object
-
#subdivide ⇒ Object
polygon.subdivide do |a, b| a = Geospatial::Location.new(*a) b = Geospatial::Location.new(*b) if a.distance_from(b) > maximum_distance a.midpoints(b, 2) end end.
- #to_s ⇒ Object
-
#winding_number(p) ⇒ Number
Test a 2D point for inclusion in the polygon.
Constructor Details
#initialize(points, bounding_box = nil) ⇒ Polygon
Returns a new instance of Polygon.
41 42 43 44 |
# File 'lib/geospatial/polygon.rb', line 41 def initialize(points, bounding_box = nil) @points = points @bounding_box = bounding_box end |
Instance Attribute Details
#points ⇒ Object (readonly)
Returns the value of attribute points.
46 47 48 |
# File 'lib/geospatial/polygon.rb', line 46 def points @points end |
Class Method Details
.[](*points) ⇒ Object
25 26 27 |
# File 'lib/geospatial/polygon.rb', line 25 def self.[] *points self.new(points) end |
.dump(polygon) ⇒ Object
35 36 37 38 39 |
# File 'lib/geospatial/polygon.rb', line 35 def self.dump(polygon) if polygon JSON.dump(polygon.points.map(&:to_a)) end end |
.is_left(p0, p1, p2) ⇒ Object
142 143 144 145 146 147 |
# File 'lib/geospatial/polygon.rb', line 142 def self.is_left(p0, p1, p2) a = p1 - p0 b = p2 - p0 return (a[0] * b[1]) - (b[0] * a[1]) end |
.load(data) ⇒ Object
29 30 31 32 33 |
# File 'lib/geospatial/polygon.rb', line 29 def self.load(data) if data self.new(JSON.parse(data).map{|point| Vector.elements(point)}) end end |
Instance Method Details
#[](index) ⇒ Object
48 49 50 51 52 53 |
# File 'lib/geospatial/polygon.rb', line 48 def [] index a = @points[index.floor] b = @points[index.ceil % @points.size] return a + (b - a) * (index % 1.0) end |
#area(radius = 1.0) ⇒ Object
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/geospatial/polygon.rb', line 81 def area(radius = 1.0) if @points.size > 2 area = 0.0 self.edges.each do |p1, p2| r1 = (p2[0] - p1[0]) * D2R r2 = 2 + Math::sin(p1[1] * D2R) + Math::sin(p2[1] * D2R) area += r1 * r2 end return (area * radius * radius / 2.0).abs else return 0.0 end end |
#bounding_box ⇒ Object
59 60 61 |
# File 'lib/geospatial/polygon.rb', line 59 def bounding_box @bounding_box ||= Box.enclosing_points(@points).freeze end |
#edge_intersection(a, b) ⇒ Object
185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/geospatial/polygon.rb', line 185 def edge_intersection(a, b) line = Line.new(a, b) edges.each_with_index do |(pa, pb), i| edge = Line.new(pa, pb) if line.intersect?(edge) return i end end return nil end |
#edges ⇒ Object
70 71 72 73 74 75 76 77 78 |
# File 'lib/geospatial/polygon.rb', line 70 def edges return to_enum(:edges) unless block_given? size = @points.size @points.each_with_index do |point, index| yield point, @points[(index+1)%size] end end |
#freeze ⇒ Object
63 64 65 66 67 68 |
# File 'lib/geospatial/polygon.rb', line 63 def freeze @points.freeze bounding_box.freeze super end |
#include?(other) ⇒ Boolean
208 209 210 |
# File 'lib/geospatial/polygon.rb', line 208 def include?(other) other.corners.all?{|corner| self.include_point?(corner)} end |
#include_point?(point) ⇒ Boolean
171 172 173 174 175 |
# File 'lib/geospatial/polygon.rb', line 171 def include_point?(point) return false unless bounding_box.include_point?(point) self.winding_number(point).odd? end |
#intersect?(other) ⇒ Boolean
199 200 201 202 203 204 205 206 |
# File 'lib/geospatial/polygon.rb', line 199 def intersect?(other) case other when Box intersect_with_box?(other) when Circle intersect_with_circle?(other) end end |
#intersect_with_box?(other) ⇒ Boolean
177 178 179 180 181 182 183 |
# File 'lib/geospatial/polygon.rb', line 177 def intersect_with_box?(other) return true if @points.any?{|point| other.include_point?(point)} return true if other.corners.any?{|corner| self.include_point?(corner)} return false end |
#simplify ⇒ Object
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/geospatial/polygon.rb', line 98 def simplify simplified_points = @points.first(1) (1...@points.size).each do |index| point = @points[index] next_point = @points[(index+1) % @points.size] if yield(simplified_points.last, point, next_point) simplified_points << point end end self.class.new(simplified_points, bounding_box) end |
#subdivide ⇒ Object
polygon.subdivide do |a, b| a = Geospatial::Location.new(*a) b = Geospatial::Location.new(*b) if a.distance_from(b) > maximum_distance a.midpoints(b, 2) end end
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/geospatial/polygon.rb', line 122 def subdivide simplified_points = @points.first(1) (1..@points.size).each do |index| point = @points[index % @points.size] next_point = @points[(index+1) % @points.size] if points = yield(simplified_points.last, point, next_point) simplified_points.concat(points) end # Polygons are represented by a closed sequence of points, but we need to subdivide by the last point at the first point too. However, we don't add the first point a 2nd time. if index < @points.size simplified_points << point end end self.class.new(simplified_points, bounding_box) end |
#to_s ⇒ Object
55 56 57 |
# File 'lib/geospatial/polygon.rb', line 55 def to_s "#{self.class}#{@points.inspect}" end |
#winding_number(p) ⇒ Number
Test a 2D point for inclusion in the polygon.
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/geospatial/polygon.rb', line 152 def winding_number(p) count = 0 edges.each do |pa, pb| if pa[1] <= p[1] if pb[1] >= p[1] and Polygon.is_left(pa, pb, p) > 0 count += 1 end else if pb[1] <= p[1] and Polygon.is_left(pa, pb, p) < 0 count -= 1 end end end return count end |