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