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 +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
@@ -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
|
data/lib/terraformer/geometry.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/terraformer/point.rb
CHANGED
@@ -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
|