geospatial 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,65 @@
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 'curve'
22
+
23
+ require 'pry'
24
+
25
+ module Geospatial
26
+ module Hilbert
27
+ class Curve
28
+ def traverse(&block)
29
+ return to_enum(:traverse) unless block_given?
30
+
31
+ traverse_recurse(@order-1, 0, 0, self.origin, self.size, &block)
32
+ end
33
+
34
+ def bit_width
35
+ @dimensions.count
36
+ end
37
+
38
+ # Traversal enumerates all regions of a curve, top-down.
39
+ def traverse_recurse(order, mask, value, origin, size, &block)
40
+ half_size = size.collect{|value| value * 0.5}.freeze
41
+ prefix_mask = (1 << order) | mask
42
+
43
+ (2**bit_width).times do |prefix|
44
+ # These both do the same thing, not sure which one is faster:
45
+ child_value = (value << @dimensions.count) | prefix
46
+ prefix = child_value << (order*bit_width)
47
+
48
+ index = HilbertIndex.from_integral(prefix, bit_width, @order).to_ordinal
49
+
50
+ index = index & prefix_mask
51
+
52
+ child_origin = @dimensions.unmap(index.axes).freeze
53
+
54
+ # puts "yield(#{child_origin}, #{half_size}, #{prefix}, #{order})"
55
+ # We avoid calling traverse_recurse simply to hit the callback on the leaf nodes:
56
+ result = yield child_origin, half_size, prefix, order
57
+
58
+ if order > 0 and result != :skip
59
+ self.traverse_recurse(order - 1, prefix_mask, child_value, child_origin, half_size, &block)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,106 +1,127 @@
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.
1
20
 
2
21
  module Geospatial
3
- class Hilbert
4
- # Quadrants are numbered 0 to 3, and are in the following order:
5
- # y
6
- # 1 | 3 | 2 |
7
- # 0 | 0 | 1 |
8
- # 0 1 x
9
- # The origin is in the lower left, and the most rapidly changing value is along the x axis for the initial rotation.
22
+ module Hilbert
23
+ # Convert between Hilbert index and N-dimensional points.
24
+ #
25
+ # The Hilbert index is expressed as an array of transposed bits.
26
+ #
27
+ # Example: 5 bits for each of n=3 coordinates.
28
+ # 15-bit Hilbert integer = A B C D E F G H I J K L M N O is stored
29
+ # as its Transpose ^
30
+ # X[0] = A D G J M X[2]| 7
31
+ # X[1] = B E H K N <-------> | /X[1]
32
+ # X[2] = C F I L O axes |/
33
+ # high low 0------> X[0]
34
+ #
35
+ # This algorithm is derived from work done by John Skilling and published in "Programming the Hilbert curve".
10
36
 
11
- # Four quadrants/rotations, the direction indicates the axis of the final two coordinates (e.g. 2 -> 3) and is for informal use only.
12
- A = 0 # LEFT
13
- # | 3 | 2 |
14
- # | 0 | 1 |
15
-
16
- B = 1 # DOWN
17
- # | 1 | 2 |
18
- # | 0 | 3 |
19
-
20
- C = 2 # RIGHT
21
- # | 1 | 0 |
22
- # | 2 | 3 |
23
-
24
- D = 3 # UP
25
- # | 3 | 0 |
26
- # | 2 | 1 |
27
-
28
- # This maps the identity rotation/quadrants into their prefix quadrant. The prefix quadrant is the 2 bit number (0 to 3) which identifies along the curve which quadrant the value falls into. This can be computed by looking at how the curve for a given rotation and looking at the correspondence between the identity quadrants and the curve's traversal.
29
- ROTATE = [
30
- [A, B, C, D], # A is the identity
31
- [A, D, C, B], # Map A onto B.
32
- [C, D, A, B], # Map A onto C.
33
- [C, B, A, D], # Map A onto D.
34
- ].freeze
35
-
36
- # Rotate quadrant by rotation. The provided quadrant is with respect to the Up rotation.
37
- # Note that this function is self-inverting in the sense that rotate(r, rotate(r, x)) == x.
38
- def self.rotate(rotation, quadrant)
39
- ROTATE[rotation][quadrant]
40
- end
41
-
42
- # These prefixes are generated by the following graph:
43
- # Rotation | 0 1 2 3 (Prefix)
44
- # A | B A A D
45
- # B | A B B C
46
- # C | D C C B
47
- # D | C D D A
48
- # We can compute this matrix by looking how the given prefix quadrant maps onto a curve one level down the tree, given the current rotation. We identify that colums 1 and 2 are the same as the input so we take advantage of this by mapping only the columns which are different, i.e. for prefix 0 and 3.
49
-
50
- PREFIX0 = [B, A, D, C].freeze
51
- PREFIX3 = [D, C, B, A].freeze
52
-
53
- # Given the current rotation and the prefix quadrant, compute the next rotation one level down the tree.
54
- def self.next_rotation(rotation, prefix)
55
- if prefix == 0
56
- PREFIX0[rotation]
57
- elsif prefix == 3
58
- PREFIX3[rotation]
59
- else
60
- rotation
61
- end
62
- end
63
-
64
- # Compute which quadrant this bit is in.
65
- def self.normalized_quadrant(x, y, bit_offset)
66
- mask = 1 << bit_offset
37
+ # Convert the Hilbert index into an N-dimensional point expressed as a vector of uints.
38
+ # @param transposed_index The Hilbert index stored in transposed form.
39
+ # @param bits Number of bits per coordinate.
40
+ # @return Coordinate vector.
41
+ def self.unmap(transposed_index, bits)
42
+ x = transposed_index.dup #var X = (uint[])transposedIndex.Clone();
43
+ n = x.length # n: Number of dimensions
44
+ m = 1 << bits
67
45
 
68
- if (y & mask) == 0
69
- if (x & mask) == 0
70
- return 0
71
- else
72
- return 1
73
- end
74
- else
75
- if (x & mask) == 0
76
- return 3
77
- else
78
- return 2
46
+ # Gray decode by H ^ (H/2)
47
+ t = x[n-1] >> 1 # t = X[n - 1] >> 1;
48
+
49
+ (n-1).downto(1) {|i| x[i] ^= x[i-1]}
50
+ x[0] ^= t
51
+
52
+ # Undo excess work
53
+ q = 2
54
+ while q != m
55
+ p = q - 1
56
+
57
+ i = n - 1
58
+ while i >= 0
59
+ if x[i] & q != 0
60
+ x[0] ^= p # invert
61
+ else
62
+ t = (x[0] ^ x[i]) & p;
63
+ x[0] ^= t;
64
+ x[i] ^= t;
65
+ end
66
+
67
+ i -= 1
79
68
  end
69
+
70
+ q <<= 1
80
71
  end
72
+
73
+ return x
81
74
  end
82
75
 
83
- def self.hash(x, y, order)
84
- result = 0
85
- # The initial rotation depends on the order:
86
- rotation = order.even? ? A : B
76
+ # Given the coordinates of a point in N-Dimensional space, find the distance to that point along the Hilbert curve.
77
+ # That distance will be transposed; broken into pieces and distributed into an array.
78
+ #
79
+ # The number of dimensions is the length of the hilbert_index array.
80
+ # @param hilbert_index Point in N-space.
81
+ # @param bits Depth of the Hilbert curve. If bits is one, this is the top-level Hilbert curve.
82
+ # @return The Hilbert distance (or index) as a transposed Hilbert index.
83
+ def self.map(hilbert_axes, bits)
84
+ x = hilbert_axes.dup
85
+ n = x.length # n: Number of dimensions
86
+ m = 1 << (bits - 1)
87
87
 
88
- order.downto(0) do |i|
89
- # This computes the normalized quadrant for the ith bit of x, y:
90
- quadrant = self.normalized_quadrant(x, y, i)
88
+ # Inverse undo
89
+ q = m
90
+ while q > 1
91
+ p = q - 1
92
+ i = 0
91
93
 
92
- # Given the normalised quadrant, compute the prefix bits for the given quadrant for the given hilbert curve rotation:
93
- prefix = rotate(rotation, quadrant)
94
+ while i < n
95
+ if (x[i] & q) != 0
96
+ x[0] ^= p # invert
97
+ else
98
+ t = (x[0] ^ x[i]) & p;
99
+ x[0] ^= t;
100
+ x[i] ^= t;
101
+ end
102
+
103
+ i += 1
104
+ end
94
105
 
95
- # These both do the same thing, not sure which one is faster:
96
- result = (result << 2) | prefix
97
- #result |= (rotated << (i * 2))
106
+ q >>= 1
107
+ end # exchange
108
+
109
+ # Gray encode
110
+ 1.upto(n-1) {|i| x[i] ^= x[i-1]}
111
+
112
+ t = 0
113
+ q = m
114
+ while q > 1
115
+ if x[n-1] & q != 0
116
+ t ^= q - 1
117
+ end
98
118
 
99
- # Given the current rotation and the prefix for the hilbert curve, compute the next rotation one level in:
100
- rotation = next_rotation(rotation, prefix)
119
+ q >>= 1
101
120
  end
102
121
 
103
- return result
122
+ 0.upto(n-1) {|i| x[i] ^= t}
123
+
124
+ return x
104
125
  end
105
126
  end
106
- end
127
+ end
@@ -0,0 +1,80 @@
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 'interleave'
22
+ require_relative 'hilbert'
23
+
24
+ module Geospatial
25
+ # This class represents a n-dimentional index.
26
+ # @param axes Point in n-space, e.g. [3, 7].
27
+ # @param bits Number of bits to use for each axis, e.g. 8.
28
+ class Index
29
+ def initialize(axes, bits)
30
+ @axes = axes
31
+ @bits = bits
32
+ end
33
+
34
+ def self.from_integral(integral, width, bits)
35
+ self.new(Interleave.unmap(integral, width), bits)
36
+ end
37
+
38
+ attr :axes
39
+ attr :bits
40
+
41
+ def & mask
42
+ self.class.new(axes.collect{|axis| axis & mask}, @bits)
43
+ end
44
+
45
+ def hash
46
+ @axes.hash
47
+ end
48
+
49
+ def eql?(other)
50
+ self.class.eql?(other.class) and @axes.eql?(other.axes) and @bits.eql?(other.bits)
51
+ end
52
+
53
+ def to_i
54
+ Interleave.map(@axes, @bits)
55
+ end
56
+
57
+ def bit_length
58
+ @axes.size * @bits
59
+ end
60
+
61
+ def inspect
62
+ i = self.to_i
63
+ "\#<#{self.class}[#{@bits}] 0b#{i.to_s(2).rjust(bit_length, '0')} (#{i}) #{@axes.inspect}>"
64
+ end
65
+ end
66
+
67
+ # Represents an index on the hilbert curve.
68
+ class HilbertIndex < Index
69
+ def to_ordinal
70
+ OrdinalIndex.new(Hilbert.unmap(@axes, @bits), @bits)
71
+ end
72
+ end
73
+
74
+ # Represents an index in ordinal space.
75
+ class OrdinalIndex < Index
76
+ def to_hilbert
77
+ HilbertIndex.new(Hilbert.map(@axes, @bits), @bits)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,69 @@
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
+ module Geospatial
22
+ module Interleave
23
+ # Convert a Tranpose Hilbert index into a Hilbert integer
24
+ # 15-bit Hilbert integer = A B C D E F G H I J K L M N O is stored
25
+ # as its Transpose:
26
+ # x[0] = A D G J M
27
+ # x[1] = B E H K N
28
+ # x[2] = C F I L O
29
+ # |--bits-|
30
+ def self.map(index, bits)
31
+ result = 0
32
+
33
+ index.each_with_index do |x, i|
34
+ offset = index.size - (i+1)
35
+
36
+ bits.times do |j|
37
+ result |= (x & 1) << (j*index.size+offset)
38
+
39
+ x >>= 1
40
+
41
+ break if x == 0
42
+ end
43
+ end
44
+
45
+ return result
46
+ end
47
+
48
+ def self.unmap(integral, width)
49
+ result = [0] * width
50
+ mask = (1 << width) - 1
51
+ offset = 0
52
+
53
+ while integral != 0
54
+ # N times, look at each bit and append
55
+ width.times do |i|
56
+ bit = (integral >> i) & 1
57
+ result[-1-i] |= bit << offset
58
+ end
59
+
60
+ # Pop first n bits
61
+ integral >>= width
62
+
63
+ offset += 1
64
+ end
65
+
66
+ return result
67
+ end
68
+ end
69
+ end
@@ -1,3 +1,22 @@
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.
1
20
 
2
21
  module Geospatial
3
22
  # This location is specifically relating to a WGS84 coordinate on Earth.
@@ -16,59 +35,61 @@ module Geospatial
16
35
  R2D = (180.0 / Math::PI)
17
36
  D2R = (Math::PI / 180.0)
18
37
 
19
- MIN_LATITUDE = -90.0 * D2R
20
- MAX_LATITUDE = 90 * D2R
21
- VALID_LATITUDE = MIN_LATITUDE...MAX_LATITUDE
22
-
23
38
  MIN_LONGITUDE = -180 * D2R
24
39
  MAX_LONGITUDE = 180 * D2R
25
40
  VALID_LONGITUDE = MIN_LONGITUDE...MAX_LONGITUDE
26
-
27
- def initialize(latitude, longitude, altitude = 0)
41
+
42
+ MIN_LATITUDE = -90.0 * D2R
43
+ MAX_LATITUDE = 90 * D2R
44
+ VALID_LATITUDE = MIN_LATITUDE...MAX_LATITUDE
45
+
46
+ def initialize(longitude, latitude)
28
47
  @latitude = latitude
29
48
  @longitude = longitude
30
- @altitude = altitude
31
49
  end
32
50
 
33
51
  def valid?
34
- VALID_LATITUDE.include? latitude and VALID_LONGITUDE.include? longitude
52
+ VALID_LONGITUDE.include?(longitude) and VALID_LATITUDE.include?(latitude)
53
+ end
54
+
55
+ def to_a
56
+ [@longitude, @latitude]
35
57
  end
36
58
 
37
59
  def to_s
38
- "#<Location latitude=#{@latitude} longitude=#{@longitude.to_f} altitude=#{@altitude.to_f}>"
60
+ "#<Location longitude=#{@longitude.to_f} latitude=#{@latitude}>"
39
61
  end
40
62
 
41
63
  alias inspect to_s
42
64
 
43
- attr :latitude
44
- attr :longitude
45
- attr :altitude
65
+ attr :longitude # -180 -> 180 (equivalent to x)
66
+ attr :latitude # -90 -> 90 (equivalent to y)
46
67
 
47
68
  # http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates
48
69
  def bounding_box(distance, radius = EARTH_RADIUS)
49
70
  raise ArgumentError.new("Invalid distance or radius") if distance < 0 or radius < 0
50
71
 
51
72
  # angular distance in radians on a great circle
52
- angular_distance = distance / (radius + self.altitude)
73
+ angular_distance = distance / radius
53
74
 
54
75
  min_latitude = (self.latitude * D2R) - angular_distance
55
76
  max_latitude = (self.latitude * D2R) + angular_distance
56
77
 
57
- if min_latitude > MIN_LAT and max_latitude < MAX_LAT
78
+ if min_latitude > MIN_LATITUDE and max_latitude < MAX_LATITUDE
58
79
  longitude_delta = Math::asin(Math::sin(angular_distance) / Math::cos(self.latitude * D2R))
59
80
 
60
81
  min_longitude = (self.longitude * D2R) - longitude_delta
61
- min_longitude += 2.0 * Math::PI if (min_longitude < MIN_LON)
82
+ min_longitude += 2.0 * Math::PI if (min_longitude < MIN_LONGITUDE)
62
83
 
63
84
  max_longitude = (self.longitude * D2R) + longitude_delta;
64
- max_longitude -= 2.0 * Math::PI if (max_longitude > MAX_LON)
85
+ max_longitude -= 2.0 * Math::PI if (max_longitude > MAX_LONGITUDE)
65
86
  else
66
87
  # a pole is within the distance
67
- min_latitude = [min_latitude, MIN_LAT].max
68
- max_latitude = [max_latitude, MAX_LAT].min
88
+ min_latitude = [min_latitude, MIN_LATITUDE].max
89
+ max_latitude = [max_latitude, MAX_LATITUDE].min
69
90
 
70
- min_longitude = MIN_LON
71
- max_longitude = MAX_LON
91
+ min_longitude = MIN_LONGITUDE
92
+ max_longitude = MAX_LONGITUDE
72
93
  end
73
94
 
74
95
  return {
@@ -78,17 +99,17 @@ module Geospatial
78
99
  end
79
100
 
80
101
  # Converts latitude, longitude to ECEF coordinate system
81
- def to_ecef(alt)
82
- clat = Math::cos(lat * D2R)
83
- slat = Math::sin(lat * D2R)
102
+ def to_ecef
84
103
  clon = Math::cos(lon * D2R)
85
104
  slon = Math::sin(lon * D2R)
86
-
105
+ clat = Math::cos(lat * D2R)
106
+ slat = Math::sin(lat * D2R)
107
+
87
108
  n = WGS84_A / Math::sqrt(1.0 - WGS84_E * WGS84_E * slat * slat)
88
109
 
89
- x = (n + alt) * clat * clon
90
- y = (n + alt) * clat * slon
91
- z = (n * (1.0 - WGS84_E * WGS84_E) + alt) * slat
110
+ x = n * clat * clon
111
+ y = n * clat * slon
112
+ z = n * (1.0 - WGS84_E * WGS84_E) * slat
92
113
 
93
114
  return x, y, z
94
115
  end
@@ -108,18 +129,18 @@ module Geospatial
108
129
  lat = Math::atan2((z+ep*ep*b*(Math::sin(th) ** 3)), (p-e*e*a*(Math::cos(th)**3)))
109
130
 
110
131
  n = a / Math::sqrt(1.0-e*e*(Math::sin(lat) ** 2))
111
- alt = p / Math::cos(lat)-n
112
-
113
- return self.new(lat*R2D, lon*R2D, alt)
132
+ # alt = p / Math::cos(lat)-n
133
+
134
+ return self.new(lat*R2D, lon*R2D)
114
135
  end
115
136
 
116
137
  # calculate distance in metres between us and something else
117
138
  # ref: http://codingandweb.blogspot.co.nz/2012/04/calculating-distance-between-two-points.html
118
139
  def distance_from(other_position)
119
- rlat1 = self.latitude * D2R
120
140
  rlong1 = self.longitude * D2R
121
- rlat2 = other_position.latitude * D2R
141
+ rlat1 = self.latitude * D2R
122
142
  rlong2 = other_position.longitude * D2R
143
+ rlat2 = other_position.latitude * D2R
123
144
 
124
145
  dlon = rlong1 - rlong2
125
146
  dlat = rlat1 - rlat2
@@ -0,0 +1,57 @@
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 '../map'
22
+
23
+ module Geospatial
24
+ class Map
25
+ # Uses dependency injection to generate a class to `load` and `dump` a serialized column.
26
+ class Index
27
+ class << self
28
+ attr_accessor :map
29
+
30
+ def load(hash)
31
+ if hash
32
+ map.point_for_hash(hash)
33
+ end
34
+ end
35
+
36
+ def dump(point)
37
+ if point.is_a?(Point)
38
+ point.hash
39
+ elsif point.respond_to?(:to_a)
40
+ map.hash_for_coordinates(point.to_a)
41
+ elsif !point.nil?
42
+ raise ArgumentError.new("Could not convert #{point} on #{map}!")
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ # serialize :point, Map.for_earth.index
49
+ def index
50
+ klass = Class.new(Index)
51
+
52
+ klass.map = self
53
+
54
+ return klass
55
+ end
56
+ end
57
+ end