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.
@@ -0,0 +1,116 @@
1
+ module Terraformer
2
+
3
+ class Geodesic
4
+
5
+ MAX_ITERS = 20
6
+ attr_accessor :precision
7
+
8
+ def self.test
9
+ a = Coordinate.new -122.6764, 45.5165
10
+ b = a + [0.02, 0.02]
11
+ r = a.distance_and_bearing_to b
12
+ puts "distance between #{a} and #{b} : #{r[:distance]}"
13
+ puts "initial bearing: #{r[:bearing][:initial]}"
14
+ puts "final bearing: #{r[:bearing][:final]}"
15
+ end
16
+
17
+ class << self
18
+ [:sin, :cos, :tan, :sqrt, :atan, :atan2].each do |m|
19
+ define_method m do |*a|
20
+ BigMath.__send__ m, *a.push(PRECISION)
21
+ end
22
+ end
23
+ end
24
+
25
+ # http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf
26
+ #
27
+ # ported from https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/location/java/android/location/Location.java
28
+ #
29
+ def self.compute_distance_and_bearing lat_1, lon_1, lat_2, lon_2
30
+ lat_1 = lat_1.to_rad
31
+ lat_2 = lat_2.to_rad
32
+ lon_1 = lon_1.to_rad
33
+ lon_2 = lon_2.to_rad
34
+
35
+ a = 6378137.to_d
36
+ b = 6356752.3142.to_d
37
+ f = (a - b) / a
38
+ a_sq_minus_b_sq_over_b_sq = (a * a - b * b) / (b * b)
39
+
40
+ _l = lon_2 - lon_1
41
+ _a = BigMath::ZERO
42
+ _u1 = atan((BigMath::ONE - f) * tan(lat_1))
43
+ _u2 = atan((BigMath::ONE - f) * tan(lat_2))
44
+
45
+ cos_u1 = cos(_u1)
46
+ cos_u2 = cos(_u2)
47
+ sin_u1 = sin(_u1)
48
+ sin_u2 = sin(_u2)
49
+ cos_u1_cos_u2 = cos_u1 * cos_u2
50
+ sin_u1_sin_u2 = sin_u1 * sin_u2
51
+
52
+ sigma = BigMath::ZERO
53
+ delta_sigma = BigMath::ZERO
54
+ cos_sq_alpha = BigMath::ZERO
55
+ cos2_s_m = BigMath::ZERO
56
+ cos_sigma = BigMath::ZERO
57
+ sin_sigma = BigMath::ZERO
58
+ cos_lambda = BigMath::ZERO
59
+ sin_lambda = BigMath::ZERO
60
+
61
+ _lambda = _l
62
+ MAX_ITERS.times do |n|
63
+ _lambda_orig = _lambda
64
+ cos_lambda = cos(_lambda)
65
+ sin_lambda = sin(_lambda)
66
+ t1 = cos_u2 * sin_lambda
67
+ t2 = cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos_lambda
68
+ sin_sq_sigma = t1 * t1 + t2 * t2
69
+ sin_sigma = sqrt(sin_sq_sigma)
70
+ cos_sigma = sin_u1_sin_u2 + cos_u1_cos_u2 * cos_lambda
71
+ sigma = atan2(sin_sigma, cos_sigma)
72
+ sin_alpha = (sin_sigma == BigMath::ZERO) ? BigMath::ZERO : (cos_u1_cos_u2 * sin_lambda / sin_sigma)
73
+ cos_sq_alpha = BigMath::ONE - sin_alpha * sin_alpha
74
+ cos_2_s_m = (cos_sq_alpha == BigMath::ZERO) ? BigMath::ZERO : (cos_sigma - BigMath::TWO * sin_u1_sin_u2 / cos_sq_alpha)
75
+
76
+ u_squared = cos_sq_alpha * a_sq_minus_b_sq_over_b_sq
77
+ _a = BigMath::ONE + (u_squared / 16384.0) *
78
+ (4096.0 + u_squared *
79
+ (-768.0 + u_squared * (320.0 - 175.0 * u_squared)))
80
+ _b = (u_squared / 1024.0) *
81
+ (256.0 + u_squared *
82
+ (-128.0 + u_squared * (74.0 - 47.0 * u_squared)))
83
+ _c = (f / 16.0) *
84
+ cos_sq_alpha *
85
+ (4.0 + f * (4.0 - 3.0 * cos_sq_alpha))
86
+ cos2_s_m_sq = cos2_s_m * cos2_s_m
87
+ delta_sigma = _b * sin_sigma *
88
+ (cos2_s_m + (_b / 4.0) *
89
+ (cos_sigma * (BigMath::N_ONE + BigMath::TWO * cos2_s_m_sq) -
90
+ (_b / 6.0) * cos2_s_m *
91
+ (-3.0 + 4.0 * sin_sigma * sin_sigma) *
92
+ (-3.0 + 4.0 * cos2_s_m_sq)))
93
+
94
+ _lambda = _l +
95
+ (BigMath::ONE - _c) * f * sin_alpha *
96
+ (sigma + _c * sin_sigma *
97
+ (cos2_s_m + _c * cos_sigma *
98
+ (BigMath::N_ONE + BigMath::TWO * cos2_s_m * cos2_s_m)))
99
+
100
+ delta = (_lambda - _lambda_orig) / _lambda
101
+ if delta.abs < 1.0e-12
102
+ break
103
+ end
104
+ end
105
+
106
+ { distance: (b * _a * (sigma - delta_sigma)),
107
+ bearing: {
108
+ initial: atan2(cos_u2 * sin_lambda, cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos_lambda).to_deg,
109
+ final: atan2(cos_u1 * sin_lambda, -(sin_u1) * cos_u2 + cos_u1 * sin_u2 * cos_lambda).to_deg
110
+ }
111
+ }
112
+ end
113
+
114
+ end
115
+
116
+ end
@@ -0,0 +1,92 @@
1
+ module Terraformer
2
+ class Geometry < Primitive
3
+
4
+ # geometric "helpers"
5
+ #
6
+ module ClassMethods
7
+
8
+ def coordinates_contain_point? coordinates, point
9
+ contains = false
10
+ i = -1
11
+ l = coordinates.length
12
+ j = l - 1
13
+ loop do
14
+ break unless (i += 1) < l
15
+
16
+ if ((coordinates[i][1] <= point[1] && point[1] < coordinates[j][1]) ||
17
+ (coordinates[j][1] <= point[1] && point[1] < coordinates[i][1])) &&
18
+ (point[0] < (coordinates[j][0] - coordinates[i][0]) *
19
+ (point[1] - coordinates[i][1]) / (coordinates[j][1] - coordinates[i][1]) + coordinates[i][0])
20
+
21
+ contains = !contains
22
+ end
23
+ j = i
24
+ end
25
+ contains
26
+ end
27
+
28
+ def edge_intersects_edge? a1, a2, b1, b2
29
+ ua_t = (b2[0] - b1[0]) * (a1[1] - b1[1]) - (b2[1] - b1[1]) * (a1[0] - b1[0])
30
+ ub_t = (a2[0] - a1[0]) * (a1[1] - b1[1]) - (a2[1] - a1[1]) * (a1[0] - b1[0])
31
+ u_b = (b2[1] - b1[1]) * (a2[0] - a1[0]) - (b2[0] - b1[0]) * (a2[1] - a1[1])
32
+ if u_b != 0
33
+ ua = ua_t / u_b
34
+ ub = ub_t / u_b
35
+ return true if 0 <= ua && ua <= 1 && 0 <= ub && ub <= 1
36
+ end
37
+ false
38
+ end
39
+
40
+ def arrays_intersect_arrays? a, b
41
+ case
42
+ when a[0].class == Coordinate
43
+ case
44
+ when b[0].class == Coordinate
45
+ a.each_cons(2) do |a1, a2|
46
+ b.each_cons(2) do |b1, b2|
47
+ return true if edge_intersects_edge?(a1, a2, b1, b2)
48
+ end
49
+ end
50
+ when b[0].class == Array
51
+ b.each {|e| return true if intersects = arrays_intersect_arrays?(a, e)}
52
+ end
53
+ when a[0].class == Array
54
+ a.each {|e| return true if intersects = arrays_intersect_arrays?(e, b)}
55
+ end
56
+ false
57
+ end
58
+
59
+ def line_contains_point? line, point
60
+ raise ArgumentError unless Array === line and
61
+ line.length == 2 and
62
+ Coordinate === line[0] and
63
+ Coordinate === line[1]
64
+ point = point.coordinates if Point === point
65
+ raise ArgumentError unless Coordinate === point
66
+
67
+ return true if line[0] == point or line[1] == point
68
+
69
+ dxp = point.x - line[0].x
70
+ dyp = point.y - line[0].y
71
+
72
+ dxl = line[1].x - line[0].x
73
+ dyl = line[1].y - line[0].y
74
+
75
+ cross = dxp * dyl - dyp * dxl
76
+ return false unless cross == BigMath::ZERO
77
+
78
+ if dxl.abs >= dyl.abs
79
+ return dxl > BigMath::ZERO ?
80
+ line[0].x <= point.x && point.x <= line[1].x :
81
+ line[1].x <= point.x && point.x <= line[0].x
82
+ else
83
+ return dyl > BigMath::ZERO ?
84
+ line[0].y <= point.y && point.y <= line[1].y :
85
+ line[1].y <= point.y && point.y <= line[0].y
86
+ end
87
+ end
88
+
89
+ end
90
+
91
+ end
92
+ end
@@ -1,14 +1,20 @@
1
+ require 'terraformer/geometry/class_methods'
2
+
1
3
  module Terraformer
2
4
 
3
5
  class Geometry < Primitive
6
+ extend Terraformer::Geometry::ClassMethods
4
7
 
5
8
  MULTI_REGEX = /^Multi/
6
9
 
7
10
  attr_accessor :coordinates
8
11
 
9
12
  def initialize *args
10
- if args.length > 1 or Array === args[0]
13
+ case
14
+ when args.length > 1
11
15
  self.coordinates = Coordinate.from_array args
16
+ when Array === args[0]
17
+ self.coordinates = Coordinate.from_array args[0]
12
18
  else
13
19
  super *args do |arg|
14
20
  self.coordinates = Coordinate.from_array arg['coordinates']
@@ -31,6 +37,12 @@ module Terraformer
31
37
  self.class.new *coordinates.map_coordinate(&:to_geographic)
32
38
  end
33
39
 
40
+ def to_feature
41
+ f = Feature.new
42
+ f.geometry = self
43
+ f
44
+ end
45
+
34
46
  def first_coordinate
35
47
  raise NotImplementedError
36
48
  end
@@ -53,19 +65,38 @@ module Terraformer
53
65
  end
54
66
 
55
67
  def convex_hull
56
- raise NotImplementedError
68
+ ConvexHull.for coordinates
57
69
  end
58
70
 
59
- def contains other
71
+ def contains? other
60
72
  raise NotImplementedError
61
73
  end
62
74
 
63
- def within other
75
+ def within? other
64
76
  raise NotImplementedError
65
77
  end
66
78
 
67
- def intersects other
68
- raise NotImplementedError
79
+ def intersects? other
80
+ [self, other].each do |e|
81
+ if [Point, MultiPoint].include? e.class
82
+ raise ArgumentError.new "unsupported type: #{e.type rescue e.class}"
83
+ end
84
+ end
85
+ return true if begin
86
+ within? other or other.within? self
87
+ rescue ArgumentError
88
+ false
89
+ end
90
+ Terraformer::Geometry.arrays_intersect_arrays? coordinates, other.coordinates
91
+ end
92
+
93
+ def == obj
94
+ return false unless obj.class == self.class
95
+ if block_given?
96
+ yield obj
97
+ else
98
+ self.coordinates == obj.coordinates
99
+ end
69
100
  end
70
101
 
71
102
  end
@@ -93,6 +124,10 @@ module Terraformer
93
124
  }
94
125
  end
95
126
 
127
+ def convex_hull
128
+ ConvexHull.for geometries.map &:coordinates
129
+ end
130
+
96
131
  end
97
132
 
98
133
  end
@@ -6,6 +6,81 @@ module Terraformer
6
6
  coordinates[0]
7
7
  end
8
8
 
9
+ def linear_ring?
10
+ coordinates.length > 3 and coordinates.first == coordinates.last
11
+ end
12
+
13
+ def lines
14
+ ls = []
15
+ coordinates.each_cons(2) {|l| ls << l}
16
+ ls
17
+ end
18
+
19
+ def contains? obj
20
+ case obj
21
+ when Point
22
+ lines.any? {|l| Geometry.line_contains_point? l, obj.coordinates}
23
+ when LineString
24
+ self == obj or coordinates.slice_exists? obj.coordinates
25
+ # todo this does not case for a line string of different coordinates
26
+ # that is actually contained yet
27
+ when MultiLineString
28
+ obj.line_strings.all? {|ls| ls.within? self}
29
+ else
30
+ raise ArgumentError.new "unsupported type: #{obj.type rescue obj.class}"
31
+ end
32
+ end
33
+
34
+ def within? obj
35
+ case obj
36
+ when LineString
37
+ self == obj or obj.coordinates.slice_exists? coordinates
38
+ # todo this does not case for a line string of different coordinates
39
+ # that is actually contained yet
40
+ when MultiLineString
41
+ obj.line_strings.any? {|ls| ls.contains? self}
42
+ when Polygon
43
+ obj.contains? self
44
+ when MultiPolygon
45
+ obj.polygons.any? {|p| p.contains? self}
46
+ else
47
+ raise ArgumentError.new "unsupported type: #{obj.type rescue obj.class}"
48
+ end
49
+ end
50
+
51
+ def points
52
+ coordinates.map &:to_point
53
+ end
54
+ alias_method :vertices, :points
55
+
56
+ def point_at idx
57
+ coordinates[idx].to_point
58
+ end
59
+ alias_method :vertex_at, :point_at
60
+
61
+ def add_vertex p
62
+ p = p.coordinates if Point === p
63
+ raise ArgumentError unless Coordinate === p
64
+ coordinates << p
65
+ end
66
+ alias_method :<<, :add_vertex
67
+
68
+ def insert_vertex idx, p
69
+ p = p.coordinates if Point === p
70
+ raise ArgumentError unless Coordinate === p
71
+ coordinates.insert idx, p
72
+ end
73
+
74
+ def remove_vertex p
75
+ p = p.coordinates if Point === p
76
+ raise ArgumentError unless Coordinate === p
77
+ coordinates.delete p
78
+ end
79
+
80
+ def remove_vertex_at idx
81
+ coordinates.delete_at idx
82
+ end
83
+
9
84
  end
10
85
 
11
86
  end
@@ -2,10 +2,72 @@ module Terraformer
2
2
 
3
3
  class MultiLineString < Geometry
4
4
 
5
+ def initialize *args
6
+ case
7
+ when Coordinate === args[0] # only one
8
+ self.coordinates = [Coordinate.from_array(args)]
9
+ when Array === args[0] # multiple?
10
+ self.coordinates = Coordinate.from args
11
+ when LineString === args[0]
12
+ self.coordinates = args.map &:coordinates
13
+ else
14
+ super *args
15
+ end
16
+ end
17
+
5
18
  def first_coordinate
6
19
  coordinates[0][0]
7
20
  end
8
21
 
22
+ def line_strings
23
+ coordinates.map {|ls| LineString.new ls}
24
+ end
25
+
26
+ def == obj
27
+ super obj do |o|
28
+ equal = true
29
+ lses = line_strings.sort
30
+ olses = o.line_strings.sort
31
+ lses.each_with_index do |ls, i|
32
+ equal = ls == olses[i]
33
+ break unless equal
34
+ end
35
+ equal
36
+ end
37
+ end
38
+
39
+ def contains? obj
40
+ case obj
41
+ when Point
42
+ line_strings.any? {|ls| ls.contains? obj}
43
+ when MultiPoint
44
+ obj.points.all? {|p| line_strings.any? {|ls| ls.contains? p}}
45
+ when LineString
46
+ line_strings.any? {|ls| ls == obj or ls.coordinates.slice_exists? obj.coordinates}
47
+ when MultiLineString
48
+ obj.line_strings.all? do |ols|
49
+ line_strings.any? do |ls|
50
+ ls == ols or ls.coordinates.slice_exists? ols.coordinates
51
+ end
52
+ end
53
+ else
54
+ raise ArgumentError.new "unsupported type: #{obj.type rescue obj.class}"
55
+ end
56
+ end
57
+
58
+ def within? obj
59
+ case obj
60
+ when MultiLineString
61
+ obj.contains? self
62
+ when Polygon
63
+ obj.contains? self
64
+ when MultiPolygon
65
+ obj.contains? self
66
+ else
67
+ raise ArgumentError.new "unsupported type: #{obj.type rescue obj.class}"
68
+ end
69
+ end
70
+
9
71
  end
10
72
 
11
73
  end
@@ -2,10 +2,73 @@ module Terraformer
2
2
 
3
3
  class MultiPoint < Geometry
4
4
 
5
+ def initialize *args
6
+ case
7
+ when Point === args[0]
8
+ self.coordinates = args.map &:coordinates
9
+ else
10
+ super *args
11
+ end
12
+ end
13
+
5
14
  def first_coordinate
6
15
  coordinates[0]
7
16
  end
8
17
 
18
+ def points
19
+ coordinates.map {|p| Point.new p}
20
+ end
21
+
22
+ def == obj
23
+ super obj do |o|
24
+ self.coordinates.sort == obj.coordinates.sort
25
+ end
26
+ end
27
+
28
+ def contains? obj
29
+ points.any? {|p| p.contains? obj}
30
+ end
31
+
32
+ def within? obj
33
+ case obj
34
+ when MultiPoint
35
+ points.all? {|p| obj.contains? p}
36
+ when LineString
37
+ points.all? {|p| obj.contains? p}
38
+ when MultiLineString
39
+ points.all? {|p| obj.contains? p}
40
+ when Polygon
41
+ obj.contains? self
42
+ when MultiPolygon
43
+ points.all? {|p| obj.polygons.any? {|polygon| polygon.contains? p}}
44
+ else
45
+ raise ArgumentError.new "unsupported type: #{obj.type rescue obj.class}"
46
+ end
47
+ end
48
+
49
+ def add_point p
50
+ p = p.coordinates if Point === p
51
+ raise ArugmentError unless Coordinate === p
52
+ coordinates << p
53
+ end
54
+ alias_method :<<, :add_point
55
+
56
+ def insert_point idx, p
57
+ p = p.coordinates if Point === p
58
+ raise ArugmentError unless Coordinate === p
59
+ coordinates.insert idx, p
60
+ end
61
+
62
+ def remove_point p
63
+ p = p.coordinates if Point === p
64
+ raise ArugmentError unless Coordinate === p
65
+ coordinates.delete p
66
+ end
67
+
68
+ def remove_point_at idx
69
+ coordinates.delete_at idx
70
+ end
71
+
9
72
  end
10
73
 
11
74
  end
@@ -2,10 +2,55 @@ module Terraformer
2
2
 
3
3
  class MultiPolygon < Geometry
4
4
 
5
+ def initialize *args
6
+ case
7
+ when Coordinate === args[0] # only one
8
+ self.coordinates = [[Coordinate.from_array(args)]]
9
+ when Array === args[0] # multiple?
10
+ self.coordinates = [Coordinate.from(args)]
11
+ when Polygon === args[0]
12
+ self.coordinates = args.map &:coordinates
13
+ else
14
+ super *args
15
+ end
16
+ end
17
+
5
18
  def first_coordinate
6
19
  coordinates[0][0][0]
7
20
  end
8
21
 
22
+ def polygons
23
+ coordinates.map {|p| Polygon.new *p}
24
+ end
25
+
26
+ def == obj
27
+ super obj do |o|
28
+ equal = true
29
+ ps = polygons.sort
30
+ ops = o.polygons.sort
31
+ ps.each_with_index do |p, i|
32
+ equal = p == ops[i]
33
+ break unless equal
34
+ end
35
+ equal
36
+ end
37
+ end
38
+
39
+ def contains? obj
40
+ polygons.any? {|p| p.contains? obj}
41
+ end
42
+
43
+ def within? obj
44
+ case obj
45
+ when Polygon
46
+ polygons.all? {|p| p.within? obj}
47
+ when MultiPolygon
48
+ polygons.all? {|p| obj.polygons.any? {|op| op.contains? p}}
49
+ else
50
+ raise ArgumentError.new "unsupported type: #{obj.type rescue obj.class}"
51
+ end
52
+ end
53
+
9
54
  end
10
55
 
11
56
  end
@@ -6,6 +6,55 @@ module Terraformer
6
6
  coordinates
7
7
  end
8
8
 
9
+ def distance_and_bearing_to obj
10
+ case obj
11
+ when Point
12
+ first_coordinate.distance_and_bearing_to obj.first_coordinate
13
+
14
+ # todo other cases
15
+ end
16
+ end
17
+
18
+ def distance_to obj
19
+ distance_and_bearing_to(obj)[:distance]
20
+ end
21
+
22
+ def initial_bearing_to obj
23
+ distance_and_bearing_to(obj)[:bearing][:initial]
24
+ end
25
+
26
+ def final_bearing_to obj
27
+ distance_and_bearing_to(obj)[:bearing][:final]
28
+ end
29
+
30
+ def contains? obj
31
+ case obj
32
+ when Point
33
+ self == obj
34
+ else
35
+ raise ArgumentError.new "unsupported type: #{obj.type rescue obj.class}"
36
+ end
37
+ end
38
+
39
+ def within? obj
40
+ case obj
41
+ when Point
42
+ self == obj
43
+ when MultiPoint
44
+ obj.coordinates.any? {|c| self.coordinates == c}
45
+ when LineString
46
+ obj.coordinates.any? {|c| self.coordinates == c}
47
+ when MultiLineString
48
+ obj.line_strings.any? {|ls| within? ls}
49
+ when Polygon
50
+ obj.contains? self
51
+ when MultiPolygon
52
+ obj.polygons.any? {|p| p.contains? self}
53
+ else
54
+ raise ArgumentError unless Geometry === obj
55
+ end
56
+ end
57
+
9
58
  end
10
59
 
11
60
  end