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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 27549564ba7d079289298f08d49e921d0ed19441
4
- data.tar.gz: 5d621a88f01cd1f7eff842808776a536567ac28f
3
+ metadata.gz: 49cfd37dcf766f5758e4ed80757e6a966622888c
4
+ data.tar.gz: fca64a7df1f7dfb8ed2cf00f4ff6be1c8e0ddc5f
5
5
  SHA512:
6
- metadata.gz: 1629d4a4f49486fb44512a7e37505a7faea5f6ce1352696b49522fea11162ac5cffa15cf6094d1dec6310980caccbe8da8e1e4abf3c731d206f22a2ccee21273
7
- data.tar.gz: c697882b6880196f71120edd5dea0eac38a273e98da9989ee9fcf39dd9f32f41b78728213653aa432b16bdc18bd9d02c273ed1af38edbf96e1d839142383a767
6
+ metadata.gz: 3a5c67c0d1523e77ff65c743f3e148c79387a3f67daf4c60d2a26020c0ede6498a407dce9c5a1d6f0a143966e9dbf017ecc134412c0bbef0abc5ef277fbc5a2f
7
+ data.tar.gz: 7fcfee079394e072071d604c8f94f58b4eac5fef14e6590546880c66ea17324a32e113c4768e2b5be042db2c7705eed6eaeae2e520acebef138e64b13df5c11f
data/Gemfile CHANGED
@@ -3,6 +3,8 @@ source 'https://rubygems.org'
3
3
  gem 'rake'
4
4
 
5
5
  group :test do
6
+ gem 'http'
6
7
  gem 'pry'
7
8
  gem 'pry-nav'
9
+ gem 'ruby-prof'
8
10
  end
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
@@ -0,0 +1,12 @@
1
+ class BigDecimal
2
+
3
+ def to_deg
4
+ (self * Terraformer::DEGREES_PER_RADIAN).round Terraformer::PRECISION
5
+ end
6
+
7
+ def to_rad
8
+ (self * Terraformer::RADIANS_PER_DEGREE).round Terraformer::PRECISION
9
+ end
10
+
11
+ end
12
+
@@ -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
@@ -5,7 +5,7 @@ module Terraformer
5
5
 
6
6
  def bounds obj, format = :bbox
7
7
 
8
- obj = Terraformer.parse obj unless Primitive === obj
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.new n
24
+ BigDecimal(n)
21
25
  when BigDecimal
22
26
  n
23
27
  when Numeric
24
- n.to_d
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, _m = nil
31
- super 4
32
- case _x
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
- def m
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, m].map! {|e| e.nil? ? nil : e.to_f}.compact.to_json(*args)
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
@@ -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