terraformer 0.0.1 → 0.0.2
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 +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
|