terraformer 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/lib/ext/array.rb +16 -0
- data/lib/ext/big_decimal.rb +12 -0
- data/lib/ext/big_math.rb +36 -0
- data/lib/ext/enumerable.rb +79 -0
- data/lib/ext/hash.rb +21 -0
- data/lib/terraformer/bounds.rb +7 -2
- data/lib/terraformer/circle.rb +50 -0
- data/lib/terraformer/convex_hull.rb +92 -0
- data/lib/terraformer/coordinate.rb +104 -15
- data/lib/terraformer/feature.rb +24 -0
- data/lib/terraformer/geodesic.rb +116 -0
- data/lib/terraformer/geometry/class_methods.rb +92 -0
- data/lib/terraformer/geometry.rb +41 -6
- data/lib/terraformer/line_string.rb +75 -0
- data/lib/terraformer/multi_line_string.rb +62 -0
- data/lib/terraformer/multi_point.rb +63 -0
- data/lib/terraformer/multi_polygon.rb +45 -0
- data/lib/terraformer/point.rb +49 -0
- data/lib/terraformer/polygon.rb +106 -0
- data/lib/terraformer/version.rb +1 -1
- data/lib/terraformer.rb +22 -53
- data/test/convex_hull_spec.rb +26 -0
- data/test/examples/circle.geojson +130 -130
- data/test/examples/line_string.geojson +1 -1
- data/test/examples/multi_point.geojson +1 -1
- data/test/examples/sf_county.geojson +1 -0
- data/test/examples/waldocanyon.geojson +1 -0
- data/test/geometry_spec.rb +1082 -0
- data/test/helper.rb +12 -0
- data/test/primitive_spec.rb +64 -0
- data/test/terraformer_spec.rb +42 -2
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49cfd37dcf766f5758e4ed80757e6a966622888c
|
4
|
+
data.tar.gz: fca64a7df1f7dfb8ed2cf00f4ff6be1c8e0ddc5f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a5c67c0d1523e77ff65c743f3e148c79387a3f67daf4c60d2a26020c0ede6498a407dce9c5a1d6f0a143966e9dbf017ecc134412c0bbef0abc5ef277fbc5a2f
|
7
|
+
data.tar.gz: 7fcfee079394e072071d604c8f94f58b4eac5fef14e6590546880c66ea17324a32e113c4768e2b5be042db2c7705eed6eaeae2e520acebef138e64b13df5c11f
|
data/Gemfile
CHANGED
data/lib/ext/array.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
class Array
|
2
|
+
|
3
|
+
def slice_exists? slice
|
4
|
+
start = slice.first
|
5
|
+
len = slice.size
|
6
|
+
each_with_index do |e, i|
|
7
|
+
return true if e == start && self[i,len] == slice
|
8
|
+
end
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_c
|
13
|
+
Terraformer::Coordinate.from_array self
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/lib/ext/big_math.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module BigMath
|
2
|
+
|
3
|
+
N_ONE = -1.to_d
|
4
|
+
ZERO = 0.to_d
|
5
|
+
ONE = 1.to_d
|
6
|
+
TWO = 2.to_d
|
7
|
+
|
8
|
+
# http://en.wikipedia.org/wiki/Atan2#Definition_and_computation
|
9
|
+
#
|
10
|
+
def self.atan2 y, x, precision
|
11
|
+
case
|
12
|
+
when x > ZERO
|
13
|
+
BigMath.atan((y / x), precision)
|
14
|
+
when y >= ZERO && x < ZERO
|
15
|
+
BigMath.atan((y / x), precision) + Terraformer::PI
|
16
|
+
when y < ZERO && x < ZERO
|
17
|
+
BigMath.atan((y / x), precision) - Terraformer::PI
|
18
|
+
when x == ZERO
|
19
|
+
case
|
20
|
+
when y > ZERO
|
21
|
+
Terraformer::PI / TWO
|
22
|
+
when y < ZERO
|
23
|
+
-(Terraformer::PI / TWO)
|
24
|
+
when y == ZERO
|
25
|
+
BigDecimal::NAN
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# http://en.wikipedia.org/wiki/Trigonometric_functions#Right-angled_triangle_definitions
|
31
|
+
#
|
32
|
+
def self.tan theta, precision
|
33
|
+
BigMath.sin(theta, precision) / BigMath.cos(theta, precision)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Enumerable
|
2
|
+
|
3
|
+
def each_coordinate opts = {}, &block
|
4
|
+
iter_coordinate :each, opts, &block
|
5
|
+
end
|
6
|
+
|
7
|
+
def map_coordinate opts = {}, &block
|
8
|
+
iter_coordinate :map, opts, &block
|
9
|
+
end
|
10
|
+
alias_method :collect_coordinate, :map_coordinate
|
11
|
+
|
12
|
+
def map_coordinate! opts = {}, &block
|
13
|
+
iter_coordinate :map!, opts, &block
|
14
|
+
end
|
15
|
+
alias_method :collect_coordinate!, :map_coordinate!
|
16
|
+
|
17
|
+
def iter_coordinate meth, opts = {}, &block
|
18
|
+
opts[:recurse] = true if opts[:recurse].nil?
|
19
|
+
|
20
|
+
if Array === self and Numeric === self[0]
|
21
|
+
yield self
|
22
|
+
else
|
23
|
+
|
24
|
+
self.__send__ meth do |pair|
|
25
|
+
raise IndexError unless Array === pair
|
26
|
+
case pair[0]
|
27
|
+
when Numeric
|
28
|
+
yield pair
|
29
|
+
when Array
|
30
|
+
pair.iter_coordinate meth, opts, &block if opts[:recurse]
|
31
|
+
else
|
32
|
+
raise IndexError.new "#{pair[0]} is not a Numeric or Array type"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def rotate_until &block
|
39
|
+
return if block[]
|
40
|
+
found = false
|
41
|
+
length.times do
|
42
|
+
push shift
|
43
|
+
if block[]
|
44
|
+
found = true
|
45
|
+
break
|
46
|
+
end
|
47
|
+
end
|
48
|
+
raise IndexError unless found
|
49
|
+
end
|
50
|
+
|
51
|
+
def rotate_until_first_equals obj
|
52
|
+
rotate_until { at(0) == obj }
|
53
|
+
end
|
54
|
+
|
55
|
+
def polygonally_equal_to? obj
|
56
|
+
raise ArgumentError unless Enumerable === obj
|
57
|
+
return false if self.length != obj.length
|
58
|
+
|
59
|
+
equal = true
|
60
|
+
|
61
|
+
# clone so can pop/rotate
|
62
|
+
me = self.clone
|
63
|
+
obj = obj.clone
|
64
|
+
|
65
|
+
# pop to drop the duplicate, polygon-closing, coordinate
|
66
|
+
me.pop
|
67
|
+
obj.pop
|
68
|
+
|
69
|
+
begin
|
70
|
+
obj.rotate_until_first_equals me[0]
|
71
|
+
equal = me == obj
|
72
|
+
rescue IndexError
|
73
|
+
equal = false
|
74
|
+
end
|
75
|
+
|
76
|
+
equal
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
data/lib/ext/hash.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
unless instance_methods.include? :only
|
4
|
+
# same as select_with_keys without forcing `Symbol` usage
|
5
|
+
#
|
6
|
+
def only *ks
|
7
|
+
ks = ks.compact.uniq
|
8
|
+
select {|k,v| ks.include? k}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
unless instance_methods.include? :not
|
13
|
+
# same as select_without_keys without forcing `Symbol` usage
|
14
|
+
#
|
15
|
+
def not *ks
|
16
|
+
ks = ks.compact.uniq
|
17
|
+
select {|k,v| !ks.include? k}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/lib/terraformer/bounds.rb
CHANGED
@@ -5,7 +5,7 @@ module Terraformer
|
|
5
5
|
|
6
6
|
def bounds obj, format = :bbox
|
7
7
|
|
8
|
-
obj = Terraformer.parse obj unless
|
8
|
+
obj = Terraformer.parse obj unless Geometry === obj
|
9
9
|
|
10
10
|
bbox = case obj.type
|
11
11
|
when 'Point'
|
@@ -33,6 +33,8 @@ module Terraformer
|
|
33
33
|
|
34
34
|
case format
|
35
35
|
when :bbox
|
36
|
+
# yikes!
|
37
|
+
bbox.extend BBox
|
36
38
|
bbox
|
37
39
|
when :polygon
|
38
40
|
Polygon.new [[bbox[0], bbox[1]],
|
@@ -51,7 +53,7 @@ module Terraformer
|
|
51
53
|
bounds_for_array a, (nesting - 1), b
|
52
54
|
end
|
53
55
|
else
|
54
|
-
array.reduce box do |b, lonlat|
|
56
|
+
bbox = array.reduce box do |b, lonlat|
|
55
57
|
lon, lat = *lonlat
|
56
58
|
set = ->(d, i, t){ b[i] = d if b[i].nil? or d.send(t, b[i]) }
|
57
59
|
set[lon, X1, :<]
|
@@ -60,6 +62,9 @@ module Terraformer
|
|
60
62
|
set[lat, Y2, :>]
|
61
63
|
b
|
62
64
|
end
|
65
|
+
# yikes!
|
66
|
+
bbox.extend BBox
|
67
|
+
bbox
|
63
68
|
end
|
64
69
|
end
|
65
70
|
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Terraformer
|
2
|
+
class Circle
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
attr_accessor :polygon
|
6
|
+
attr_reader :center, :radius, :resolution
|
7
|
+
def_delegators :@polygon, :contains?, :within?, :intersects?, :to_feature, :to_json
|
8
|
+
|
9
|
+
def initialize c, r, res = DEFAULT_BUFFER_RESOLUTION
|
10
|
+
self.center = c
|
11
|
+
self.radius = r
|
12
|
+
self.resolution = res
|
13
|
+
recalculate
|
14
|
+
end
|
15
|
+
|
16
|
+
def recalculate
|
17
|
+
@polygon = @center.buffer @radius, @resolution
|
18
|
+
@dirty = false
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def center= c
|
23
|
+
c = Coordinate.from_array c if Array === c and Numeric === c[0]
|
24
|
+
c = c.coordinates if Point === c
|
25
|
+
raise ArgumentError unless Coordinate === c
|
26
|
+
@center = c
|
27
|
+
@dirty = true
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def radius= r
|
32
|
+
raise ArgumentError unless Numeric === r
|
33
|
+
@radius = r
|
34
|
+
@dirty = true
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def resolution= res
|
39
|
+
raise ArgumentError unless Fixnum === res
|
40
|
+
@resolution = res
|
41
|
+
@dirty = true
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def dirty?
|
46
|
+
@dirty
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Terraformer
|
2
|
+
module ConvexHull
|
3
|
+
|
4
|
+
DEFAULT_IMPL = :monotone
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
attr_accessor :impl
|
9
|
+
|
10
|
+
def test
|
11
|
+
require 'ruby-prof'
|
12
|
+
wc = Terraformer.parse 'test/examples/waldocanyon.geojson'
|
13
|
+
RubyProf.start
|
14
|
+
wc.convex_hull
|
15
|
+
result = RubyProf.stop
|
16
|
+
printer = RubyProf::FlatPrinter.new(result)
|
17
|
+
printer.print(STDOUT)
|
18
|
+
# printer = RubyProf::GraphPrinter.new(result)
|
19
|
+
# printer.print(STDOUT, {})
|
20
|
+
end
|
21
|
+
|
22
|
+
def for points
|
23
|
+
hull = __send__ @impl || DEFAULT_IMPL, flatten_coordinates_from(points)
|
24
|
+
if hull.length == 1
|
25
|
+
Point.new hull[0]
|
26
|
+
else
|
27
|
+
Polygon.new hull
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def flatten_coordinates_from points
|
34
|
+
cs = []
|
35
|
+
points.each_coordinate {|c| cs << c}
|
36
|
+
cs.sort!.uniq!
|
37
|
+
cs
|
38
|
+
end
|
39
|
+
|
40
|
+
# http://tixxit.wordpress.com/2009/12/09/jarvis-march/
|
41
|
+
#
|
42
|
+
def turn p, q, r
|
43
|
+
((q[0] - p[0]) * (r[1] - p[1]) - (r[0] - p[0]) * (q[1] - p[1])) <=> BigMath::ZERO
|
44
|
+
end
|
45
|
+
|
46
|
+
def next_point points, p
|
47
|
+
q = p
|
48
|
+
points.each do |r|
|
49
|
+
t = turn p, q, r
|
50
|
+
q = r if t == BigMath::N_ONE or t == BigMath::ZERO &&
|
51
|
+
p.squared_euclidean_distance_to(r) > p.squared_euclidean_distance_to(q)
|
52
|
+
end
|
53
|
+
q
|
54
|
+
end
|
55
|
+
|
56
|
+
def jarvis_march points
|
57
|
+
return points if points.length < 3
|
58
|
+
hull = [points.sort[0]]
|
59
|
+
hull.each do |p|
|
60
|
+
q = next_point points, p
|
61
|
+
hull << q if q != hull[0]
|
62
|
+
end
|
63
|
+
hull << hull[0]
|
64
|
+
hull
|
65
|
+
end
|
66
|
+
|
67
|
+
# http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain#Ruby
|
68
|
+
#
|
69
|
+
def cross o, a, b
|
70
|
+
(a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])
|
71
|
+
end
|
72
|
+
|
73
|
+
def monotone points
|
74
|
+
return points if points.length < 3
|
75
|
+
lower = Array.new
|
76
|
+
points.each{|p|
|
77
|
+
while lower.length > 1 and cross(lower[-2], lower[-1], p) <= 0 do lower.pop end
|
78
|
+
lower.push(p)
|
79
|
+
}
|
80
|
+
upper = Array.new
|
81
|
+
points.reverse_each{|p|
|
82
|
+
while upper.length > 1 and cross(upper[-2], upper[-1], p) <= 0 do upper.pop end
|
83
|
+
upper.push(p)
|
84
|
+
}
|
85
|
+
hull = lower[0...-1] + upper[0...-1]
|
86
|
+
hull << hull[0]
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
@@ -2,6 +2,10 @@ module Terraformer
|
|
2
2
|
|
3
3
|
class Coordinate < ::Array
|
4
4
|
|
5
|
+
# http://en.wikipedia.org/wiki/Earth_radius#Mean_radius
|
6
|
+
#
|
7
|
+
EARTH_MEAN_RADIUS = 6371009.to_d
|
8
|
+
|
5
9
|
attr_accessor :crs
|
6
10
|
|
7
11
|
class << self
|
@@ -17,24 +21,26 @@ module Terraformer
|
|
17
21
|
def big_decimal n
|
18
22
|
case n
|
19
23
|
when String
|
20
|
-
BigDecimal
|
24
|
+
BigDecimal(n)
|
21
25
|
when BigDecimal
|
22
26
|
n
|
23
27
|
when Numeric
|
24
|
-
n.
|
28
|
+
BigDecimal(n.to_s)
|
29
|
+
else
|
30
|
+
raise ArgumentError
|
25
31
|
end
|
26
32
|
end
|
27
33
|
|
28
34
|
end
|
29
35
|
|
30
|
-
def initialize _x, _y = nil, _z = nil
|
31
|
-
super
|
32
|
-
case
|
33
|
-
when Array
|
36
|
+
def initialize _x, _y = nil, _z = nil
|
37
|
+
super 3
|
38
|
+
case
|
39
|
+
when Array === _x
|
34
40
|
raise ArgumentError if _y
|
35
41
|
self.x = _x[0]
|
36
42
|
self.y = _x[1]
|
37
|
-
when Numeric
|
43
|
+
when Numeric === _x || String === _x
|
38
44
|
raise ArgumentError unless _y
|
39
45
|
self.x = _x
|
40
46
|
self.y = _y
|
@@ -63,11 +69,7 @@ module Terraformer
|
|
63
69
|
self[2]
|
64
70
|
end
|
65
71
|
|
66
|
-
|
67
|
-
self[3]
|
68
|
-
end
|
69
|
-
|
70
|
-
[:z=, :m=, :<<, :+ , :-, :*, :&, :|].each do |sym|
|
72
|
+
[:z=, :<<, :*, :&, :|].each do |sym|
|
71
73
|
define_method(sym){|*a| raise NotImplementedError }
|
72
74
|
end
|
73
75
|
|
@@ -78,7 +80,7 @@ module Terraformer
|
|
78
80
|
(Math::PI / 2).to_d -
|
79
81
|
(2 * BigMath.atan(BigMath.exp(-1.0 * y / EARTH_RADIUS, PRECISION), PRECISION))
|
80
82
|
).to_deg
|
81
|
-
geog = self.class.new _x, _y
|
83
|
+
geog = self.class.new _x.round(PRECISION), _y.round(PRECISION)
|
82
84
|
geog.crs = GEOGRAPHIC_CRS
|
83
85
|
geog
|
84
86
|
end
|
@@ -87,13 +89,21 @@ module Terraformer
|
|
87
89
|
_x = x.to_rad * EARTH_RADIUS
|
88
90
|
syr = BigMath.sin y.to_rad, PRECISION
|
89
91
|
_y = (EARTH_RADIUS / 2.0) * BigMath.log((1.0 + syr) / (1.0 - syr), PRECISION)
|
90
|
-
merc = self.class.new _x, _y
|
92
|
+
merc = self.class.new _x.round(PRECISION), _y.round(PRECISION)
|
91
93
|
merc.crs = MERCATOR_CRS
|
92
94
|
merc
|
93
95
|
end
|
94
96
|
|
97
|
+
def to_s
|
98
|
+
[x,y,z].compact.join ','
|
99
|
+
end
|
100
|
+
|
95
101
|
def to_json *args
|
96
|
-
[x, y, z
|
102
|
+
[x, y, z].map! {|e| e.to_f if e}.compact.to_json(*args)
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_point
|
106
|
+
Point.new self
|
97
107
|
end
|
98
108
|
|
99
109
|
def geographic?
|
@@ -115,6 +125,85 @@ module Terraformer
|
|
115
125
|
Polygon.new(coordinates).to_geographic
|
116
126
|
end
|
117
127
|
|
128
|
+
def <=> other
|
129
|
+
raise ArgumentError unless Coordinate === other
|
130
|
+
dx = x - other.x
|
131
|
+
dy = y - other.y
|
132
|
+
case
|
133
|
+
when dx > dy; 1
|
134
|
+
when dx < dy; -1
|
135
|
+
else; 0
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def arithmetic operator, obj
|
140
|
+
case obj
|
141
|
+
when Array
|
142
|
+
_x = self.x.__send__ operator, obj[0] if obj[0]
|
143
|
+
_y = self.y.__send__ operator, obj[1] if obj[1]
|
144
|
+
Coordinate.new((_x || x), (_y || y))
|
145
|
+
else
|
146
|
+
raise NotImplementedError
|
147
|
+
end
|
148
|
+
end
|
149
|
+
private :arithmetic
|
150
|
+
|
151
|
+
def + obj
|
152
|
+
arithmetic :+, obj
|
153
|
+
end
|
154
|
+
|
155
|
+
def - obj
|
156
|
+
arithmetic :-, obj
|
157
|
+
end
|
158
|
+
|
159
|
+
def squared_euclidean_distance_to obj
|
160
|
+
raise ArgumentError unless Coordinate === obj
|
161
|
+
dx = obj.x - x
|
162
|
+
dy = obj.y - y
|
163
|
+
dx * dx + dy * dy
|
164
|
+
end
|
165
|
+
|
166
|
+
def euclidean_distance_to obj
|
167
|
+
BigMath.sqrt squared_euclidean_distance_to(obj), PRECISION
|
168
|
+
end
|
169
|
+
|
170
|
+
def haversine_distance_to other
|
171
|
+
raise ArgumentError unless Coordinate === other
|
172
|
+
|
173
|
+
d_lat = (self.y - other.y).to_rad
|
174
|
+
d_lon = (self.x - other.x).to_rad
|
175
|
+
|
176
|
+
lat_r = self.y.to_rad
|
177
|
+
other_lat_r = other.y.to_rad
|
178
|
+
|
179
|
+
a = BigMath.sin(d_lat / 2, PRECISION)**2 +
|
180
|
+
BigMath.sin(d_lon / 2, PRECISION)**2 *
|
181
|
+
BigMath.cos(lat_r, PRECISION) * BigMath.cos(other_lat_r, PRECISION)
|
182
|
+
|
183
|
+
y = BigMath.sqrt a, PRECISION
|
184
|
+
x = BigMath.sqrt (1 - a), PRECISION
|
185
|
+
c = 2 * BigMath.atan2(y, x, PRECISION)
|
186
|
+
|
187
|
+
c * EARTH_MEAN_RADIUS
|
188
|
+
end
|
189
|
+
|
190
|
+
def distance_and_bearing_to obj
|
191
|
+
raise ArgumentError unless Coordinate === obj
|
192
|
+
Geodesic.compute_distance_and_bearing y, x, obj.y, obj.x
|
193
|
+
end
|
194
|
+
|
195
|
+
def distance_to obj
|
196
|
+
distance_and_bearing_to(obj)[:distance]
|
197
|
+
end
|
198
|
+
|
199
|
+
def initial_bearing_to obj
|
200
|
+
distance_and_bearing_to(obj)[:bearing][:initial]
|
201
|
+
end
|
202
|
+
|
203
|
+
def final_bearing_to obj
|
204
|
+
distance_and_bearing_to(obj)[:bearing][:final]
|
205
|
+
end
|
206
|
+
|
118
207
|
end
|
119
208
|
|
120
209
|
end
|
data/lib/terraformer/feature.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
module Terraformer
|
2
2
|
|
3
3
|
class Feature < Primitive
|
4
|
+
extend Forwardable
|
4
5
|
|
5
6
|
attr_accessor :id, :geometry
|
6
7
|
attr_writer :properties
|
7
8
|
|
9
|
+
def_delegator :@geometry, :convex_hull
|
10
|
+
|
8
11
|
def initialize *args
|
9
12
|
unless args.empty?
|
10
13
|
super *args do |arg|
|
@@ -29,12 +32,24 @@ module Terraformer
|
|
29
32
|
h
|
30
33
|
end
|
31
34
|
|
35
|
+
def great_circle_distance other
|
36
|
+
other = other.geometry if Feature === other
|
37
|
+
self.geometry.great_circle_distance other
|
38
|
+
end
|
39
|
+
|
32
40
|
end
|
33
41
|
|
34
42
|
class FeatureCollection < Primitive
|
35
43
|
|
36
44
|
attr_writer :features
|
37
45
|
|
46
|
+
def self.with_features *f
|
47
|
+
raise ArgumentError unless f.all? {|e| Feature === e}
|
48
|
+
fc = FeatureCollection.new
|
49
|
+
fc.features = f
|
50
|
+
fc
|
51
|
+
end
|
52
|
+
|
38
53
|
def initialize *args
|
39
54
|
unless args.empty?
|
40
55
|
super *args do |arg|
|
@@ -47,6 +62,11 @@ module Terraformer
|
|
47
62
|
@features ||= []
|
48
63
|
end
|
49
64
|
|
65
|
+
def << feature
|
66
|
+
raise ArgumentError unless Feature === feature
|
67
|
+
features << feature
|
68
|
+
end
|
69
|
+
|
50
70
|
def to_hash
|
51
71
|
{
|
52
72
|
type: type,
|
@@ -54,6 +74,10 @@ module Terraformer
|
|
54
74
|
}
|
55
75
|
end
|
56
76
|
|
77
|
+
def convex_hull
|
78
|
+
ConvexHull.for features.map(&:geometry).map(&:coordinates)
|
79
|
+
end
|
80
|
+
|
57
81
|
end
|
58
82
|
|
59
83
|
end
|