geospatial 1.10.0 → 1.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +14 -7
- data/README.md +9 -0
- data/geospatial.gemspec +3 -3
- data/lib/geospatial/histogram.rb +25 -5
- data/lib/geospatial/line.rb +64 -0
- data/lib/geospatial/location.rb +40 -22
- data/lib/geospatial/polygon.rb +104 -9
- data/lib/geospatial/version.rb +1 -1
- data/spec/geospatial/line_spec.rb +32 -0
- data/spec/geospatial/location_spec.rb +28 -0
- data/spec/geospatial/polygon_spec.rb +32 -4
- data/spec/spec_helper.rb +2 -19
- metadata +24 -9
- data/.simplecov +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ef4b5ff93a87b5fb2733e6bbbe587be842c11ba2365131be98760bb66ee7143
|
4
|
+
data.tar.gz: 8468357c04f732b2a843ef154b7e93b8732dc334b9e76da989f22428b3eb18bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c97d956c5ef2b8b959d85f4ce07877020000b0d24ba5496edf82ef824b3d23c7c7f9471e5e48ae53f9524aab2703dd11449dc476d7288a7d91a0c7fb58e8c13
|
7
|
+
data.tar.gz: 368fc193f939b360aa1945198c054aa7bf0f05620d1307b95a0ed89f1a09a374b605c2baecc3244e2ba81bc0fe70546ff357e705d3e423d3543163cf64394a89
|
data/.travis.yml
CHANGED
@@ -1,13 +1,20 @@
|
|
1
1
|
language: ruby
|
2
|
+
dist: xenial
|
2
3
|
cache: bundler
|
3
|
-
|
4
|
-
- 2.3
|
5
|
-
- 2.4
|
6
|
-
- 2.5
|
7
|
-
- 2.6
|
8
|
-
- jruby-head
|
9
|
-
- ruby-head
|
4
|
+
|
10
5
|
matrix:
|
6
|
+
include:
|
7
|
+
- rvm: 2.3
|
8
|
+
- rvm: 2.4
|
9
|
+
- rvm: 2.5
|
10
|
+
- rvm: 2.6
|
11
|
+
- rvm: 2.6
|
12
|
+
env: COVERAGE=PartialSummary,Coveralls
|
13
|
+
- rvm: truffleruby
|
14
|
+
- rvm: jruby-head
|
15
|
+
env: JRUBY_OPTS="--debug -X+O"
|
16
|
+
- rvm: ruby-head
|
11
17
|
allow_failures:
|
18
|
+
- rvm: truffleruby
|
12
19
|
- rvm: ruby-head
|
13
20
|
- rvm: jruby-head
|
data/README.md
CHANGED
@@ -73,6 +73,15 @@ The Hilbert curve is multi-dimensional and therefore can represent multi-dimensi
|
|
73
73
|
|
74
74
|
Mathematically speaking, it's possible to [compose curves together](https://github.com/cne1x/sfseize) to form curves of different precision/properties. However, how these fit together generally is a bit more complex, especially in terms of exploring the curve via traversal.
|
75
75
|
|
76
|
+
### Finding the distance between two locations
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
lake_tekapo = Geospatial::Location.new(170.53, -43.89) # Lake Tekapo, New Zealand.
|
80
|
+
lake_alex = Geospatial::Location.new(170.45, -43.94) # Lake Alex, New Zealand.
|
81
|
+
distance = lake_alex.distance_from(lake_tekapo)
|
82
|
+
puts distance # 8479.056408162045 meters
|
83
|
+
```
|
84
|
+
|
76
85
|
## Contributing
|
77
86
|
|
78
87
|
1. Fork it
|
data/geospatial.gemspec
CHANGED
@@ -16,9 +16,9 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
18
|
spec.require_paths = ["lib"]
|
19
|
-
|
19
|
+
|
20
|
+
spec.add_development_dependency "covered"
|
20
21
|
spec.add_development_dependency "rspec", "~> 3.6"
|
21
|
-
|
22
|
-
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "bundler"
|
23
23
|
spec.add_development_dependency "rake"
|
24
24
|
end
|
data/lib/geospatial/histogram.rb
CHANGED
@@ -7,26 +7,37 @@
|
|
7
7
|
module Geospatial
|
8
8
|
# This location is specifically relating to a WGS84 coordinate on Earth.
|
9
9
|
class Histogram
|
10
|
-
def initialize(min: 0, max: 1, scale: 0.1)
|
10
|
+
def initialize(min: 0, max: 1, scale: 0.1, items: true)
|
11
11
|
@min = min
|
12
12
|
@max = max
|
13
13
|
@scale = scale
|
14
14
|
|
15
15
|
@count = 0
|
16
16
|
|
17
|
+
if items
|
18
|
+
@items = Hash.new{|h,k| h[k] = Array.new}
|
19
|
+
end
|
20
|
+
|
17
21
|
@size = ((@max - @min) / @scale).ceil
|
18
22
|
@bins = [0] * @size
|
19
23
|
@offset = 0
|
20
24
|
@scale = scale
|
21
25
|
end
|
22
26
|
|
23
|
-
|
27
|
+
attr :bins
|
28
|
+
attr :items
|
24
29
|
|
25
30
|
attr :count
|
26
31
|
|
27
32
|
attr :offset
|
28
33
|
attr :scale
|
29
34
|
|
35
|
+
def bins= bins
|
36
|
+
raise ArgumentError, "Incorrect length" unless bins.size == @size
|
37
|
+
|
38
|
+
@bins = bins
|
39
|
+
end
|
40
|
+
|
30
41
|
def [] index
|
31
42
|
@bins[index]
|
32
43
|
end
|
@@ -43,12 +54,16 @@ module Geospatial
|
|
43
54
|
@min + (index * @scale)
|
44
55
|
end
|
45
56
|
|
46
|
-
def add(value, amount = 1)
|
57
|
+
def add(value, amount = 1, item: nil)
|
47
58
|
index = map(value).floor % @size
|
48
59
|
|
49
60
|
if !block_given? or yield(index, value)
|
50
61
|
@count += 1
|
51
62
|
@bins[index] += amount
|
63
|
+
|
64
|
+
if @items and item
|
65
|
+
@items[index] << item
|
66
|
+
end
|
52
67
|
end
|
53
68
|
|
54
69
|
return index
|
@@ -124,16 +139,21 @@ module Geospatial
|
|
124
139
|
end
|
125
140
|
end
|
126
141
|
|
142
|
+
def peaks
|
143
|
+
self.class.new(@derivative)
|
144
|
+
end
|
145
|
+
|
127
146
|
def segments
|
128
147
|
return to_enum(:segments) unless block_given?
|
129
148
|
|
130
|
-
|
149
|
+
peaks = self.peaks
|
150
|
+
gradients = peaks.to_a
|
131
151
|
|
132
152
|
return if gradients.empty?
|
133
153
|
|
134
154
|
index, gradient = gradients.first
|
135
155
|
|
136
|
-
if gradient
|
156
|
+
if gradient > 0
|
137
157
|
gradients.push gradients.shift
|
138
158
|
end
|
139
159
|
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Copyright, 2015, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANT1 OF AN1 KIND, E0PRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILIT1,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COP1RIGHT HOLDERS BE LIABLE FOR AN1 CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILIT1, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'matrix'
|
22
|
+
|
23
|
+
module Geospatial
|
24
|
+
class Line
|
25
|
+
def initialize(a, b)
|
26
|
+
@a = a
|
27
|
+
@b = b
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :a
|
31
|
+
attr_reader :b
|
32
|
+
|
33
|
+
def offset
|
34
|
+
@b - @a
|
35
|
+
end
|
36
|
+
|
37
|
+
def intersect?(other)
|
38
|
+
t = self.offset
|
39
|
+
o = other.offset
|
40
|
+
|
41
|
+
d = (o[1] * t[0]) - (o[0] * t[1])
|
42
|
+
|
43
|
+
return false if d.zero?
|
44
|
+
|
45
|
+
na = o[0] * (self.a[1] - other.a[1]) - o[1] * (self.a[0] - other.a[0])
|
46
|
+
|
47
|
+
left_time = na.fdiv(d);
|
48
|
+
|
49
|
+
if left_time < 0.0 or left_time > 1.0
|
50
|
+
return false
|
51
|
+
end
|
52
|
+
|
53
|
+
nb = t[0] * (self.a[1] - other.a[1]) - t[1] * (self.a[0] - other.a[0])
|
54
|
+
|
55
|
+
right_time = nb.fdiv(d)
|
56
|
+
|
57
|
+
if right_time < 0.0 or right_time > 1.0
|
58
|
+
return false
|
59
|
+
end
|
60
|
+
|
61
|
+
return left_time
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/geospatial/location.rb
CHANGED
@@ -21,31 +21,31 @@
|
|
21
21
|
require_relative 'distance'
|
22
22
|
|
23
23
|
module Geospatial
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
# WGS 84 eccentricity
|
35
|
-
WGS84_E = 8.1819190842622e-2
|
24
|
+
# WGS 84 semi-major axis constant in meters
|
25
|
+
WGS84_A = 6378137.0
|
26
|
+
# WGS 84 semi-minor axis constant in meters
|
27
|
+
WGS84_B = 6356752.3
|
28
|
+
|
29
|
+
# Earth Radius
|
30
|
+
R = (WGS84_A + WGS84_B) / 2.0
|
31
|
+
|
32
|
+
# WGS 84 eccentricity
|
33
|
+
WGS84_E = 8.1819190842622e-2
|
36
34
|
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
# Radians to degrees multiplier
|
36
|
+
R2D = (180.0 / Math::PI)
|
37
|
+
D2R = (Math::PI / 180.0)
|
40
38
|
|
41
|
-
|
42
|
-
|
43
|
-
|
39
|
+
MIN_LONGITUDE = -180.0 * D2R
|
40
|
+
MAX_LONGITUDE = 180.0 * D2R
|
41
|
+
VALID_LONGITUDE = -180.0...180.0
|
44
42
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
MIN_LATITUDE = -90.0 * D2R
|
44
|
+
MAX_LATITUDE = 90.0 * D2R
|
45
|
+
VALID_LATITUDE = -90.0...90.0
|
46
|
+
|
47
|
+
# This location is specifically relating to a WGS84 coordinate on Earth.
|
48
|
+
class Location
|
49
49
|
class << self
|
50
50
|
def from_ecef(x, y, z)
|
51
51
|
# Constants (WGS ellipsoid)
|
@@ -173,6 +173,7 @@ module Geospatial
|
|
173
173
|
return d
|
174
174
|
end
|
175
175
|
|
176
|
+
# @return [Numeric] bearing in degrees.
|
176
177
|
def bearing_from(other)
|
177
178
|
lon1 = other.longitude * D2R
|
178
179
|
lat1 = other.latitude * D2R
|
@@ -185,6 +186,8 @@ module Geospatial
|
|
185
186
|
) * R2D
|
186
187
|
end
|
187
188
|
|
189
|
+
# @param distance [Numeric] distance in meters.
|
190
|
+
# @param bearing [Numeric] bearing in degrees.
|
188
191
|
def location_by(bearing, distance)
|
189
192
|
lon1 = self.longitude * D2R
|
190
193
|
lat1 = self.latitude * D2R
|
@@ -199,5 +202,20 @@ module Geospatial
|
|
199
202
|
def - other
|
200
203
|
Distance.new(self.distance_from(other))
|
201
204
|
end
|
205
|
+
|
206
|
+
# Compute count midpoints between self and other.
|
207
|
+
# @param count [Integer] the number of segments to generate.
|
208
|
+
def midpoints_to(other, count)
|
209
|
+
return to_enum(:midpoints_to, other, count) unless block_given?
|
210
|
+
|
211
|
+
distance = other.distance_from(self)
|
212
|
+
bearing = other.bearing_from(self)
|
213
|
+
|
214
|
+
step = distance / count
|
215
|
+
|
216
|
+
(1...count).each do |i|
|
217
|
+
yield self.location_by(bearing, step * i)
|
218
|
+
end
|
219
|
+
end
|
202
220
|
end
|
203
221
|
end
|
data/lib/geospatial/polygon.rb
CHANGED
@@ -26,19 +26,38 @@ module Geospatial
|
|
26
26
|
self.new(points)
|
27
27
|
end
|
28
28
|
|
29
|
-
def
|
29
|
+
def self.load(data)
|
30
|
+
if data
|
31
|
+
self.new(JSON.parse(data).map{|point| Vector.elements(point)})
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.dump(polygon)
|
36
|
+
if polygon
|
37
|
+
JSON.dump(polygon.points.map(&:to_a))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(points, bounding_box = nil)
|
30
42
|
@points = points
|
31
|
-
@bounding_box =
|
43
|
+
@bounding_box = bounding_box
|
32
44
|
end
|
33
45
|
|
34
46
|
attr :points
|
35
47
|
|
48
|
+
def [] index
|
49
|
+
a = @points[index.floor]
|
50
|
+
b = @points[index.ceil % @points.size]
|
51
|
+
|
52
|
+
return a + (b - a) * (index % 1.0)
|
53
|
+
end
|
54
|
+
|
36
55
|
def to_s
|
37
56
|
"#{self.class}#{@points.inspect}"
|
38
57
|
end
|
39
58
|
|
40
59
|
def bounding_box
|
41
|
-
@bounding_box ||= Box.enclosing_points(@points)
|
60
|
+
@bounding_box ||= Box.enclosing_points(@points).freeze
|
42
61
|
end
|
43
62
|
|
44
63
|
def freeze
|
@@ -51,11 +70,73 @@ module Geospatial
|
|
51
70
|
def edges
|
52
71
|
return to_enum(:edges) unless block_given?
|
53
72
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
73
|
+
size = @points.size
|
74
|
+
|
75
|
+
@points.each_with_index do |point, index|
|
76
|
+
yield point, @points[(index+1)%size]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# @param [Float] radius The radius of the sphere on which to compute the area.
|
81
|
+
def area(radius = 1.0)
|
82
|
+
if @points.size > 2
|
83
|
+
area = 0.0
|
84
|
+
|
85
|
+
self.edges.each do |p1, p2|
|
86
|
+
r1 = (p2[0] - p1[0]) * D2R
|
87
|
+
r2 = 2 + Math::sin(p1[1] * D2R) + Math::sin(p2[1] * D2R)
|
88
|
+
|
89
|
+
area += r1 * r2
|
90
|
+
end
|
91
|
+
|
92
|
+
return (area * radius * radius / 2.0).abs
|
93
|
+
else
|
94
|
+
return 0.0
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def simplify
|
99
|
+
simplified_points = @points.first(1)
|
100
|
+
|
101
|
+
(1...@points.size).each do |index|
|
102
|
+
point = @points[index]
|
103
|
+
|
104
|
+
next_point = @points[(index+1) % @points.size]
|
105
|
+
|
106
|
+
if yield(simplified_points.last, point, next_point)
|
107
|
+
simplified_points << point
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
self.class.new(simplified_points, bounding_box)
|
112
|
+
end
|
113
|
+
|
114
|
+
# @example
|
115
|
+
# polygon.subdivide do |a, b|
|
116
|
+
# a = Geospatial::Location.new(*a)
|
117
|
+
# b = Geospatial::Location.new(*b)
|
118
|
+
# if a.distance_from(b) > maximum_distance
|
119
|
+
# a.midpoints(b, 2)
|
120
|
+
# end
|
121
|
+
# end
|
122
|
+
def subdivide
|
123
|
+
simplified_points = @points.first(1)
|
124
|
+
|
125
|
+
(1..@points.size).each do |index|
|
126
|
+
point = @points[index % @points.size]
|
127
|
+
next_point = @points[(index+1) % @points.size]
|
128
|
+
|
129
|
+
if points = yield(simplified_points.last, point, next_point)
|
130
|
+
simplified_points.concat(points)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Polygons are represented by a closed sequence of points, but we need to subdivide by the last point at the first point too. However, we don't add the first point a 2nd time.
|
134
|
+
if index < @points.size
|
135
|
+
simplified_points << point
|
136
|
+
end
|
58
137
|
end
|
138
|
+
|
139
|
+
self.class.new(simplified_points, bounding_box)
|
59
140
|
end
|
60
141
|
|
61
142
|
def self.is_left(p0, p1, p2)
|
@@ -72,7 +153,7 @@ module Geospatial
|
|
72
153
|
count = 0
|
73
154
|
|
74
155
|
edges.each do |pa, pb|
|
75
|
-
if pa[1] <= p[1]
|
156
|
+
if pa[1] <= p[1]
|
76
157
|
if pb[1] >= p[1] and Polygon.is_left(pa, pb, p) > 0
|
77
158
|
count += 1
|
78
159
|
end
|
@@ -90,7 +171,7 @@ module Geospatial
|
|
90
171
|
def include_point?(point)
|
91
172
|
return false unless bounding_box.include_point?(point)
|
92
173
|
|
93
|
-
self.winding_number(point)
|
174
|
+
self.winding_number(point).odd?
|
94
175
|
end
|
95
176
|
|
96
177
|
def intersect_with_box?(other)
|
@@ -101,6 +182,20 @@ module Geospatial
|
|
101
182
|
return false
|
102
183
|
end
|
103
184
|
|
185
|
+
def edge_intersection(a, b)
|
186
|
+
line = Line.new(a, b)
|
187
|
+
|
188
|
+
edges.each_with_index do |(pa, pb), i|
|
189
|
+
edge = Line.new(pa, pb)
|
190
|
+
|
191
|
+
if line.intersect?(edge)
|
192
|
+
return i
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
return nil
|
197
|
+
end
|
198
|
+
|
104
199
|
def intersect?(other)
|
105
200
|
case other
|
106
201
|
when Box
|
data/lib/geospatial/version.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Copyright, 2015, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'geospatial/line'
|
22
|
+
|
23
|
+
RSpec.describe Geospatial::Line do
|
24
|
+
it "should have intersection" do
|
25
|
+
a = Geospatial::Line.new(Vector[0, 0], Vector[10, 10])
|
26
|
+
b = Geospatial::Line.new(Vector[10, 0], Vector[0, 10])
|
27
|
+
|
28
|
+
t = a.intersect?(b)
|
29
|
+
|
30
|
+
expect(t).to be == 0.5
|
31
|
+
end
|
32
|
+
end
|
@@ -25,6 +25,11 @@ RSpec.describe Geospatial::Location do
|
|
25
25
|
let(:lake_tekapo) {Geospatial::Location.new(170.53, -43.89)}
|
26
26
|
let(:lake_alex) {Geospatial::Location.new(170.45, -43.94)}
|
27
27
|
|
28
|
+
it "should be valid" do
|
29
|
+
expect(lake_tekapo).to be_valid
|
30
|
+
expect(lake_alex).to be_valid
|
31
|
+
end
|
32
|
+
|
28
33
|
it "should compute the correct distance between two points" do
|
29
34
|
expect(lake_alex.distance_from(lake_tekapo)).to be_within(100).of(8_500)
|
30
35
|
end
|
@@ -64,6 +69,29 @@ RSpec.describe Geospatial::Location do
|
|
64
69
|
expect(south.bearing_from(north)).to be_within(0.1).of(180)
|
65
70
|
end
|
66
71
|
end
|
72
|
+
|
73
|
+
describe '#midpoints_to' do
|
74
|
+
let(:north) {Geospatial::Location.new(10, 10)}
|
75
|
+
let(:south) {Geospatial::Location.new(-10, -10)}
|
76
|
+
|
77
|
+
it "can generate one midpoint" do
|
78
|
+
midpoints = north.midpoints_to(south, 4).to_a
|
79
|
+
|
80
|
+
expect(midpoints.size).to be == 3
|
81
|
+
|
82
|
+
expect(midpoints[0].distance_from(
|
83
|
+
Geospatial::Location.new(4.9616, 5.0190)
|
84
|
+
)).to be < 10
|
85
|
+
|
86
|
+
expect(midpoints[1].distance_from(
|
87
|
+
Geospatial::Location.new(0, 0)
|
88
|
+
)).to be < 10
|
89
|
+
|
90
|
+
expect(midpoints[2].distance_from(
|
91
|
+
Geospatial::Location.new(-4.9616, -5.0190)
|
92
|
+
)).to be < 10
|
93
|
+
end
|
94
|
+
end
|
67
95
|
end
|
68
96
|
|
69
97
|
require 'bigdecimal'
|
@@ -105,28 +105,49 @@ RSpec.describe Geospatial::Polygon do
|
|
105
105
|
end
|
106
106
|
end
|
107
107
|
|
108
|
-
RSpec.shared_context "visualize polygon" do
|
108
|
+
RSpec.shared_context "visualize polygon" do |depth = 12|
|
109
109
|
it "can generate visualisation" do
|
110
110
|
map = Geospatial::Map.for_earth(30)
|
111
111
|
|
112
112
|
coordinates = region_string.split(/\s+/).collect{|coordinate| Vector.elements(coordinate.split(',').collect(&:to_f).first(2))}
|
113
113
|
region = Geospatial::Polygon.new(coordinates)
|
114
114
|
|
115
|
+
maximum_distance = 100_000
|
116
|
+
|
117
|
+
region = region.subdivide do |a, b|
|
118
|
+
a = Geospatial::Location.new(*a)
|
119
|
+
b = Geospatial::Location.new(*b)
|
120
|
+
|
121
|
+
distance = a.distance_from(b)
|
122
|
+
|
123
|
+
if distance > maximum_distance
|
124
|
+
a.midpoints_to(b, (distance / maximum_distance).ceil).map{|p| Vector[*p]}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
115
128
|
Geospatial::Visualization.for_map(map) do |pdf, origin|
|
116
129
|
region.edges do |pa, pb|
|
117
130
|
pdf.line (origin + pa).to_a, (origin + pb).to_a
|
118
131
|
end
|
119
132
|
|
120
133
|
count = 0
|
121
|
-
map.traverse(region, depth:
|
134
|
+
map.traverse(region, depth: depth) do |child, prefix, order|
|
122
135
|
count += 1
|
123
136
|
size = child.size
|
124
137
|
top_left = (origin + child.min) + Vector[0, size[1]]
|
125
138
|
pdf.rectangle(top_left.to_a, *size.to_a)
|
126
|
-
# puts "#{top_left.to_a} #{size.to_a}"
|
139
|
+
# $stderr.puts "#{top_left.to_a} #{size.to_a}"
|
127
140
|
end
|
128
141
|
|
129
|
-
# puts "count=#{count}"
|
142
|
+
# $stderr.puts "count=#{count}"
|
143
|
+
|
144
|
+
pdf.fill_and_stroke
|
145
|
+
|
146
|
+
pdf.fill_color 'aaaaff'
|
147
|
+
|
148
|
+
region.points.each do |p|
|
149
|
+
pdf.fill_circle((origin + p).to_a, 0.01)
|
150
|
+
end
|
130
151
|
|
131
152
|
pdf.fill_and_stroke
|
132
153
|
end.render_file "#{self.class.description}.pdf"
|
@@ -144,3 +165,10 @@ RSpec.describe "Whanganui Polygon" do
|
|
144
165
|
|
145
166
|
include_context "visualize polygon"
|
146
167
|
end
|
168
|
+
|
169
|
+
|
170
|
+
RSpec.describe "New Zealand Bounding Box" do
|
171
|
+
let(:region_string) {"165.7930426042,-47.8671059558 179.1461410947,-47.8671059558 179.1461410947,-33.4566032676 165.7930426042,-33.4566032676"}
|
172
|
+
|
173
|
+
include_context "visualize polygon", 20
|
174
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,23 +1,6 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
4
|
-
require 'simplecov'
|
5
|
-
|
6
|
-
SimpleCov.start do
|
7
|
-
add_filter "/spec/"
|
8
|
-
end
|
9
|
-
|
10
|
-
if ENV['TRAVIS']
|
11
|
-
require 'coveralls'
|
12
|
-
Coveralls.wear!
|
13
|
-
end
|
14
|
-
rescue LoadError
|
15
|
-
warn "Could not load simplecov: #{$!}"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
require "bundler/setup"
|
20
|
-
require "geospatial"
|
2
|
+
require 'covered/rspec'
|
3
|
+
require 'geospatial'
|
21
4
|
|
22
5
|
RSpec.configure do |config|
|
23
6
|
# Enable flags like --only-failures and --next-failure
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: geospatial
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-09-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: covered
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: rspec
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -28,16 +42,16 @@ dependencies:
|
|
28
42
|
name: bundler
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
|
-
- - "
|
45
|
+
- - ">="
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
47
|
+
version: '0'
|
34
48
|
type: :development
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
|
-
- - "
|
52
|
+
- - ">="
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rake
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -61,7 +75,6 @@ extra_rdoc_files: []
|
|
61
75
|
files:
|
62
76
|
- ".gitignore"
|
63
77
|
- ".rspec"
|
64
|
-
- ".simplecov"
|
65
78
|
- ".travis.yml"
|
66
79
|
- Gemfile
|
67
80
|
- README.md
|
@@ -79,6 +92,7 @@ files:
|
|
79
92
|
- lib/geospatial/histogram.rb
|
80
93
|
- lib/geospatial/index.rb
|
81
94
|
- lib/geospatial/interleave.rb
|
95
|
+
- lib/geospatial/line.rb
|
82
96
|
- lib/geospatial/location.rb
|
83
97
|
- lib/geospatial/map.rb
|
84
98
|
- lib/geospatial/map/index.rb
|
@@ -94,6 +108,7 @@ files:
|
|
94
108
|
- spec/geospatial/hilbert_traverse_spec.rb
|
95
109
|
- spec/geospatial/histogram_spec.rb
|
96
110
|
- spec/geospatial/index_spec.rb
|
111
|
+
- spec/geospatial/line_spec.rb
|
97
112
|
- spec/geospatial/location_spec.rb
|
98
113
|
- spec/geospatial/map_index_spec.rb
|
99
114
|
- spec/geospatial/map_spec.rb
|
@@ -122,8 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
137
|
- !ruby/object:Gem::Version
|
123
138
|
version: '0'
|
124
139
|
requirements: []
|
125
|
-
|
126
|
-
rubygems_version: 2.7.7
|
140
|
+
rubygems_version: 3.0.4
|
127
141
|
signing_key:
|
128
142
|
specification_version: 4
|
129
143
|
summary: Provides abstractions for dealing with geographical locations efficiently
|
@@ -137,6 +151,7 @@ test_files:
|
|
137
151
|
- spec/geospatial/hilbert_traverse_spec.rb
|
138
152
|
- spec/geospatial/histogram_spec.rb
|
139
153
|
- spec/geospatial/index_spec.rb
|
154
|
+
- spec/geospatial/line_spec.rb
|
140
155
|
- spec/geospatial/location_spec.rb
|
141
156
|
- spec/geospatial/map_index_spec.rb
|
142
157
|
- spec/geospatial/map_spec.rb
|