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 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