geospatial 0.0.1 → 1.0.0

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.
@@ -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