terraformer 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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