geospatial 0.0.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +11 -2
- data/Gemfile +12 -1
- data/README.md +37 -8
- data/Rakefile +11 -0
- data/australia.png +0 -0
- data/geospatial.gemspec +1 -1
- data/lib/geospatial/box.rb +126 -0
- data/lib/geospatial/circle.rb +109 -0
- data/lib/geospatial/dimensions.rb +130 -0
- data/lib/geospatial/filter.rb +82 -0
- data/lib/geospatial/hilbert/curve.rb +79 -0
- data/lib/geospatial/hilbert/traverse.rb +65 -0
- data/lib/geospatial/hilbert.rb +111 -90
- data/lib/geospatial/index.rb +80 -0
- data/lib/geospatial/interleave.rb +69 -0
- data/lib/geospatial/location.rb +53 -32
- data/lib/geospatial/map/index.rb +57 -0
- data/lib/geospatial/map.rb +153 -0
- data/lib/geospatial/polygon.rb +115 -0
- data/lib/geospatial/version.rb +21 -1
- data/lib/geospatial.rb +22 -3
- data/spec/geospatial/box_spec.rb +42 -0
- data/spec/geospatial/circle_spec.rb +76 -0
- data/spec/geospatial/dimensions_spec.rb +35 -0
- data/spec/geospatial/hilbert_curve_spec.rb +59 -0
- data/spec/geospatial/hilbert_traverse_spec.rb +63 -0
- data/spec/geospatial/index_spec.rb +113 -0
- data/spec/{geoquery → geospatial}/location_spec.rb +4 -4
- data/spec/geospatial/map_index_spec.rb +41 -0
- data/spec/geospatial/map_spec.rb +71 -0
- data/spec/geospatial/polygon_spec.rb +96 -0
- data/spec/geospatial/sorted.rb +6 -0
- data/spec/geospatial/visualization.rb +46 -0
- data/spec/geospatial/world.png +0 -0
- metadata +43 -9
- data/spec/geoquery/hilbert_spec.rb +0 -72
@@ -0,0 +1,153 @@
|
|
1
|
+
# Copyright, 2016, 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_relative 'location'
|
22
|
+
require_relative 'box'
|
23
|
+
require_relative 'filter'
|
24
|
+
require_relative 'hilbert/traverse'
|
25
|
+
|
26
|
+
module Geospatial
|
27
|
+
# A point is a location on a map with a specific hash representation based on the map. A point might store multi-dimentional data (e.g. longitude, latitude, time) which is hashed to a single column.
|
28
|
+
class Point
|
29
|
+
def initialize(map, coordinates, object = nil)
|
30
|
+
@map = map
|
31
|
+
@coordinates = coordinates
|
32
|
+
@object = object
|
33
|
+
end
|
34
|
+
|
35
|
+
attr :object
|
36
|
+
|
37
|
+
def [] index
|
38
|
+
@coordinates[index]
|
39
|
+
end
|
40
|
+
|
41
|
+
def []= index, value
|
42
|
+
@coordinates[index] = value
|
43
|
+
end
|
44
|
+
|
45
|
+
attr :coordinates
|
46
|
+
|
47
|
+
def eql?(other)
|
48
|
+
self.class.eql?(other.class) and @coordinates.eql?(other.coordinates)
|
49
|
+
end
|
50
|
+
|
51
|
+
def hash
|
52
|
+
@hash ||= @map.hash_for_coordinates(@coordinates).freeze
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Map
|
57
|
+
def self.for_earth(order = 20)
|
58
|
+
self.new(Hilbert::Curve.new(Dimensions.for_earth, order))
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize(curve)
|
62
|
+
@curve = curve
|
63
|
+
@points = []
|
64
|
+
@bounds = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
def order
|
68
|
+
@curve.order
|
69
|
+
end
|
70
|
+
|
71
|
+
def bounds
|
72
|
+
unless @bounds
|
73
|
+
origin = @curve.origin
|
74
|
+
size = @curve.size
|
75
|
+
|
76
|
+
@bounds = Box.new(origin, size)
|
77
|
+
end
|
78
|
+
|
79
|
+
return @bounds
|
80
|
+
end
|
81
|
+
|
82
|
+
attr :points
|
83
|
+
|
84
|
+
def hash_for_coordinates(coordinates)
|
85
|
+
@curve.map(coordinates)
|
86
|
+
end
|
87
|
+
|
88
|
+
def point_for_hash(hash)
|
89
|
+
Point.new(self, @curve.unmap(hash))
|
90
|
+
end
|
91
|
+
|
92
|
+
def point_for_coordinates(coordinates, object = nil)
|
93
|
+
Point.new(self, coordinates, object)
|
94
|
+
end
|
95
|
+
|
96
|
+
def point_for_object(object)
|
97
|
+
Point.new(self, object.to_a, object)
|
98
|
+
end
|
99
|
+
|
100
|
+
def << object
|
101
|
+
@points << point_for_coordinates(object.to_a, object)
|
102
|
+
|
103
|
+
return self
|
104
|
+
end
|
105
|
+
|
106
|
+
def count
|
107
|
+
@points.count
|
108
|
+
end
|
109
|
+
|
110
|
+
def sort!
|
111
|
+
@points.sort_by!(&:hash)
|
112
|
+
end
|
113
|
+
|
114
|
+
def query(region, **options)
|
115
|
+
filter = filter_for(region, **options)
|
116
|
+
|
117
|
+
return filter.apply(@points).map(&:object)
|
118
|
+
end
|
119
|
+
|
120
|
+
def traverse(region, depth: 0)
|
121
|
+
@curve.traverse do |child_origin, child_size, prefix, order|
|
122
|
+
child = Box.new(Vector.elements(child_origin), Vector.elements(child_size))
|
123
|
+
|
124
|
+
# puts "Considering (order=#{order}) #{child.inspect}..."
|
125
|
+
|
126
|
+
if region.intersect?(child)
|
127
|
+
if order == depth # at bottom
|
128
|
+
# puts "at bottom -> found prefix #{prefix.to_s(2)} (#{child.inspect})"
|
129
|
+
yield(child, prefix, order); :skip
|
130
|
+
elsif region.include?(child)
|
131
|
+
# puts "include child -> found prefix #{prefix.to_s(2)} (#{child.inspect})"
|
132
|
+
yield(child, prefix, order); :skip
|
133
|
+
else
|
134
|
+
# puts "going deeper..."
|
135
|
+
end
|
136
|
+
else
|
137
|
+
# puts "out of bounds."
|
138
|
+
:skip
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def filter_for(region, **options)
|
144
|
+
filter = Filter.new
|
145
|
+
|
146
|
+
traverse(region, **options) do |child, prefix, order|
|
147
|
+
filter.add(prefix, order)
|
148
|
+
end
|
149
|
+
|
150
|
+
return filter
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# Copyright, 2016, 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_relative 'box'
|
22
|
+
|
23
|
+
module Geospatial
|
24
|
+
class Polygon
|
25
|
+
def self.[] *points
|
26
|
+
self.new(points)
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(points)
|
30
|
+
@points = points
|
31
|
+
@box = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
"#{self.class}#{@points.inspect}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def bounding_box
|
39
|
+
@bounding_box ||= Box.enclosing_points(@points)
|
40
|
+
end
|
41
|
+
|
42
|
+
def freeze
|
43
|
+
@points.freeze
|
44
|
+
bounding_box.freeze
|
45
|
+
|
46
|
+
super
|
47
|
+
end
|
48
|
+
|
49
|
+
def edges
|
50
|
+
return to_enum(:edges) unless block_given?
|
51
|
+
|
52
|
+
previous = @points.last
|
53
|
+
@points.each do |point|
|
54
|
+
yield previous, point
|
55
|
+
previous = point
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.is_left(p0, p1, p2)
|
60
|
+
a = p1 - p0
|
61
|
+
b = p2 - p0
|
62
|
+
|
63
|
+
return (a[0] * b[1]) - (b[0] * a[1])
|
64
|
+
end
|
65
|
+
|
66
|
+
# Test a 2D point for inclusion in the polygon.
|
67
|
+
# @param [Vector] p The point to test.
|
68
|
+
# @return [Number] The number of times the polygon winds around the point (0 if outside).
|
69
|
+
def winding_number(p)
|
70
|
+
count = 0
|
71
|
+
|
72
|
+
edges.each do |pa, pb|
|
73
|
+
if pa[1] <= p[1]
|
74
|
+
if pb[1] >= p[1] and Polygon.is_left(pa, pb, p) > 0
|
75
|
+
count += 1
|
76
|
+
end
|
77
|
+
else
|
78
|
+
if pb[1] <= p[1] and Polygon.is_left(pa, pb, p) < 0
|
79
|
+
count -= 1
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
return count
|
86
|
+
end
|
87
|
+
|
88
|
+
def include_point?(point)
|
89
|
+
return false unless bounding_box.include_point?(point)
|
90
|
+
|
91
|
+
self.winding_number(point) == 1
|
92
|
+
end
|
93
|
+
|
94
|
+
def intersect_with_box?(other)
|
95
|
+
return true if @points.any?{|point| other.include_point?(point)}
|
96
|
+
|
97
|
+
return true if other.corners.any?{|corner| self.include_point?(corner)}
|
98
|
+
|
99
|
+
return false
|
100
|
+
end
|
101
|
+
|
102
|
+
def intersect?(other)
|
103
|
+
case other
|
104
|
+
when Box
|
105
|
+
intersect_with_box?(other)
|
106
|
+
when Circle
|
107
|
+
intersect_with_circle?(other)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def include?(other)
|
112
|
+
other.corners.all?{|corner| self.include_point?(corner)}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/geospatial/version.rb
CHANGED
@@ -1,3 +1,23 @@
|
|
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
|
+
|
1
21
|
module Geospatial
|
2
|
-
VERSION = "0.0
|
22
|
+
VERSION = "1.0.0"
|
3
23
|
end
|
data/lib/geospatial.rb
CHANGED
@@ -1,4 +1,23 @@
|
|
1
|
-
|
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.
|
2
20
|
|
3
|
-
|
4
|
-
|
21
|
+
require_relative "geospatial/version"
|
22
|
+
|
23
|
+
require_relative "geospatial/location"
|
@@ -0,0 +1,42 @@
|
|
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/map'
|
22
|
+
|
23
|
+
module Geospatial::BoxSpec
|
24
|
+
describe Geospatial::Box do
|
25
|
+
let(:square) {Geospatial::Box.new(Vector[-1, -1], Vector[2, 2])}
|
26
|
+
|
27
|
+
# it "convert to integral" do
|
28
|
+
# expect(square.to_integral([-1, -1], 4)).to be == [0, 0]
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# it "convert from integral" do
|
32
|
+
# expect(square.from_integral([0, 0], 4)).to be == [-1, -1]
|
33
|
+
# end
|
34
|
+
|
35
|
+
let(:new_zealand) {Geospatial::Box.from_bounds(Vector[166.0, -48.0], Vector[180.0, -34.0])}
|
36
|
+
let(:child) {Geospatial::Box.new(Vector[135.0, -67.5], Vector[45.0, 22.5])}
|
37
|
+
|
38
|
+
it "should intersect" do
|
39
|
+
expect(new_zealand).to be_intersect(child)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,76 @@
|
|
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/circle'
|
22
|
+
require_relative 'visualization'
|
23
|
+
|
24
|
+
RSpec.describe Geospatial::Circle do
|
25
|
+
let(:lake_tekapo) {Geospatial::Location.new(170.53, -43.89)}
|
26
|
+
let(:lake_alex) {Geospatial::Location.new(170.45, -43.94)}
|
27
|
+
let(:sydney) {Geospatial::Location.new(151.21, -33.85)}
|
28
|
+
|
29
|
+
let(:new_zealand) {Geospatial::Box.from_bounds(Vector[166.0, -48.0], Vector[180.0, -34.0])}
|
30
|
+
let(:australia) {Geospatial::Box.from_bounds(Vector[112.0, -45.0], Vector[155.0, -10.0])}
|
31
|
+
|
32
|
+
let(:circle_lake_tekapo) {Geospatial::Circle.new(lake_tekapo, 10_000)}
|
33
|
+
let(:circle_lake_alex) {Geospatial::Circle.new(lake_tekapo, 10_000)}
|
34
|
+
let(:circle_sydney) {Geospatial::Circle.new(sydney, 10_000)}
|
35
|
+
let(:circle_new_zealand) {Geospatial::Circle.new(lake_tekapo, 1_000_000)}
|
36
|
+
|
37
|
+
it "should intersect circles" do
|
38
|
+
expect(circle_lake_tekapo).to be_intersect(circle_lake_alex)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should not intersect distant circles" do
|
42
|
+
expect(circle_lake_tekapo).to_not be_intersect(circle_sydney)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should include circles" do
|
46
|
+
expect(circle_new_zealand).to be_include(circle_lake_tekapo)
|
47
|
+
expect(circle_new_zealand).to be_include(circle_lake_alex)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should not include distant circles" do
|
51
|
+
expect(circle_new_zealand).to_not be_include(circle_sydney)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "can generate visualisation" do
|
55
|
+
map = Geospatial::Map.for_earth
|
56
|
+
|
57
|
+
map << lake_tekapo
|
58
|
+
|
59
|
+
circle = Geospatial::Circle.new(lake_tekapo, 100_000)
|
60
|
+
|
61
|
+
Geospatial::Visualization.for_map(map) do |pdf, origin|
|
62
|
+
#count = 0
|
63
|
+
map.traverse(circle, depth: map.order - 10) do |child, prefix, order|
|
64
|
+
#count += 1
|
65
|
+
size = child.size
|
66
|
+
top_left = (origin + child.min) + Vector[0, size[1]]
|
67
|
+
pdf.rectangle(top_left.to_a, *size.to_a)
|
68
|
+
# puts "#{top_left.to_a} #{size.to_a}"
|
69
|
+
end
|
70
|
+
|
71
|
+
#puts "count=#{count}"
|
72
|
+
|
73
|
+
pdf.fill_and_stroke
|
74
|
+
end.render_file "circle.pdf"
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,35 @@
|
|
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/dimensions'
|
22
|
+
|
23
|
+
RSpec.describe Geospatial::Dimension.new(-180.0, 360.0) do
|
24
|
+
it "should map values" do
|
25
|
+
expect(subject.map(-180)).to be == 0.0
|
26
|
+
expect(subject.map(0)).to be == 0.5
|
27
|
+
expect(subject.map(180)).to be == 1.0
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should unmap values" do
|
31
|
+
expect(subject.unmap(0.0)).to be == -180
|
32
|
+
expect(subject.unmap(0.5)).to be == 0
|
33
|
+
expect(subject.unmap(1.0)).to be == 180
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Copyright, 2016, 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/location'
|
22
|
+
require 'geospatial/hilbert/curve'
|
23
|
+
|
24
|
+
RSpec.shared_examples_for "invertible function" do |input|
|
25
|
+
it "should round-trip coordinates" do
|
26
|
+
mapped = subject.map(input)
|
27
|
+
output = subject.unmap(mapped)
|
28
|
+
|
29
|
+
input.each_with_index do |value, index|
|
30
|
+
expect(output[index]).to be_within(tolerance).of(value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
RSpec.shared_context "earth invertible curve" do
|
36
|
+
it_behaves_like "invertible function", [-180, -90]
|
37
|
+
it_behaves_like "invertible function", [0, 0]
|
38
|
+
it_behaves_like "invertible function", [179, 89]
|
39
|
+
|
40
|
+
it_behaves_like "invertible function", [170.53, -43.89]
|
41
|
+
end
|
42
|
+
|
43
|
+
RSpec.describe Geospatial::Hilbert::Curve.new(Geospatial::Dimensions.for_earth, 6) do
|
44
|
+
let(:tolerance) {10.0}
|
45
|
+
|
46
|
+
include_context "earth invertible curve"
|
47
|
+
end
|
48
|
+
|
49
|
+
RSpec.describe Geospatial::Hilbert::Curve.new(Geospatial::Dimensions.for_earth, 8) do
|
50
|
+
let(:tolerance) {1.0}
|
51
|
+
|
52
|
+
include_context "earth invertible curve"
|
53
|
+
end
|
54
|
+
|
55
|
+
RSpec.describe Geospatial::Hilbert::Curve.new(Geospatial::Dimensions.for_earth, 32) do
|
56
|
+
let(:tolerance) {0.000001}
|
57
|
+
|
58
|
+
include_context "earth invertible curve"
|
59
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
|
2
|
+
require 'geospatial/hilbert/traverse'
|
3
|
+
|
4
|
+
require_relative 'sorted'
|
5
|
+
|
6
|
+
RSpec.describe Geospatial::Hilbert::Curve do
|
7
|
+
let(:dimensions) {Geospatial::Dimensions.from_ranges(0..4, 0..4)}
|
8
|
+
subject {Geospatial::Hilbert::Curve.new(dimensions, 2)}
|
9
|
+
|
10
|
+
it "traverses and generates all prefixes" do
|
11
|
+
items = subject.traverse.to_a
|
12
|
+
|
13
|
+
# 4 items of order 1, 16 items of order 0.
|
14
|
+
expect(items.size).to be == (4 + 16)
|
15
|
+
|
16
|
+
expect(items[0]).to be == [[0, 0], [2, 2], 0b0000, 1]
|
17
|
+
expect(items[1]).to be == [[0, 0], [1, 1], 0b0000, 0]
|
18
|
+
expect(items[2]).to be == [[1, 0], [1, 1], 0b0001, 0]
|
19
|
+
expect(items[3]).to be == [[1, 1], [1, 1], 0b0010, 0]
|
20
|
+
expect(items[4]).to be == [[0, 1], [1, 1], 0b0011, 0]
|
21
|
+
|
22
|
+
expect(items[5]).to be == [[0, 2], [2, 2], 0b0100, 1]
|
23
|
+
expect(items[6]).to be == [[0, 2], [1, 1], 0b0100, 0]
|
24
|
+
expect(items[7]).to be == [[0, 3], [1, 1], 0b0101, 0]
|
25
|
+
expect(items[8]).to be == [[1, 3], [1, 1], 0b0110, 0]
|
26
|
+
expect(items[9]).to be == [[1, 2], [1, 1], 0b0111, 0]
|
27
|
+
|
28
|
+
expect(items[10]).to be == [[2, 2], [2, 2], 0b1000, 1]
|
29
|
+
expect(items[11]).to be == [[2, 2], [1, 1], 0b1000, 0]
|
30
|
+
expect(items[12]).to be == [[2, 3], [1, 1], 0b1001, 0]
|
31
|
+
expect(items[13]).to be == [[3, 3], [1, 1], 0b1010, 0]
|
32
|
+
expect(items[14]).to be == [[3, 2], [1, 1], 0b1011, 0]
|
33
|
+
|
34
|
+
# This result here might seem a bit unexpected, but it's correct. For a 2-dimentional curve, the origin of the quadrant isn't always the same as the origin of the curve in that region. If you draw a 2D curve of order 2, you will see that the origin of the first 3 sub-curves is in the bottom left, but the last sub-curve has it's origin in the top right.
|
35
|
+
expect(items[15]).to be == [[2, 0], [2, 2], 0b1100, 1]
|
36
|
+
expect(items[16]).to be == [[3, 1], [1, 1], 0b1100, 0]
|
37
|
+
expect(items[17]).to be == [[2, 1], [1, 1], 0b1101, 0]
|
38
|
+
expect(items[18]).to be == [[2, 0], [1, 1], 0b1110, 0]
|
39
|
+
expect(items[19]).to be == [[3, 0], [1, 1], 0b1111, 0]
|
40
|
+
|
41
|
+
prefixes = items.collect{|item| item[2]}
|
42
|
+
expect(prefixes).to be_sorted
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
RSpec.describe Geospatial::Hilbert::Curve do
|
47
|
+
let(:order) {4}
|
48
|
+
let(:divisions) {2**order}
|
49
|
+
let(:dimensions) {Geospatial::Dimensions.from_ranges(0..divisions, 0..divisions)}
|
50
|
+
subject {Geospatial::Hilbert::Curve.new(dimensions, order)}
|
51
|
+
|
52
|
+
it "traverses and generates valid matching hashes" do
|
53
|
+
subject.traverse do |origin, size, prefix, depth|
|
54
|
+
if depth == 0
|
55
|
+
index = Geospatial::OrdinalIndex.new(origin.collect(&:to_i), order).to_hilbert
|
56
|
+
|
57
|
+
# puts "Child origin=#{origin.inspect} prefix=#{prefix.to_s(2)} order=#{order}"
|
58
|
+
|
59
|
+
expect(prefix).to be == index.to_i
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|