geo_vectors 0.5.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.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Distance calc notes.txt +64 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +20 -0
- data/README.textile +178 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/geo_vectors.gemspec +115 -0
- data/lib/geo_vectors.rb +7 -0
- data/lib/geo_vectors/bearing_vector.rb +87 -0
- data/lib/geo_vectors/core_ext.rb +54 -0
- data/lib/geo_vectors/direction_vector.rb +59 -0
- data/lib/geo_vectors/geo_point.rb +33 -0
- data/lib/geo_vectors/geo_vector.rb +56 -0
- data/lib/geo_vectors/geo_vectors.rb +92 -0
- data/lib/geo_vectors/point_vector.rb +85 -0
- data/lib/geo_vectors/point_vector/point_ops.rb +23 -0
- data/lib/geo_vectors/point_vector/vector_ops.rb +38 -0
- data/lib/geo_vectors/util.rb +3 -0
- data/lib/geo_vectors/util/calc.rb +89 -0
- data/lib/geo_vectors/util/geo_direction.rb +101 -0
- data/lib/geo_vectors/util/geo_distance.rb +135 -0
- data/lib/geo_vectors/util/geo_units.rb +53 -0
- data/lib/geo_vectors/vector_parser.rb +54 -0
- data/spec/geo_vectors/API proposal guide.txt +142 -0
- data/spec/geo_vectors/bearing_vector/add_vector_spec.rb +30 -0
- data/spec/geo_vectors/bearing_vector/random_spec.rb +18 -0
- data/spec/geo_vectors/bearing_vector_spec.rb +34 -0
- data/spec/geo_vectors/direction_vector/add_vector_spec.rb +30 -0
- data/spec/geo_vectors/direction_vector/point_add_spec.rb +0 -0
- data/spec/geo_vectors/direction_vector/random_spec.rb +16 -0
- data/spec/geo_vectors/direction_vector/subtract_vector_spec.rb +0 -0
- data/spec/geo_vectors/direction_vector_spec.rb +27 -0
- data/spec/geo_vectors/geo_vectors_spec.rb +108 -0
- data/spec/geo_vectors/point_vector/add_vector_spec.rb +135 -0
- data/spec/geo_vectors/point_vector/initializer_spec.rb +82 -0
- data/spec/geo_vectors/point_vector/point_add_spec.rb +45 -0
- data/spec/geo_vectors/point_vector/random_spec.rb +17 -0
- data/spec/geo_vectors/point_vector/scale_vector_spec.rb +52 -0
- data/spec/geo_vectors/point_vector/subtract_vector_spec.rb +80 -0
- data/spec/geo_vectors/point_vector_spec.rb +111 -0
- data/spec/geo_vectors/util/geo_direction_spec.rb +74 -0
- data/spec/geo_vectors/util/geo_distance_spec.rb +70 -0
- data/spec/geo_vectors/util/geo_units_spec.rb +23 -0
- data/spec/spec_helper.rb +7 -0
- metadata +218 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
class PointVector < GeoVector
|
2
|
+
module PointOps
|
3
|
+
# return new point from adding vector to point
|
4
|
+
def add_to_point point
|
5
|
+
add_to_point! point.dup
|
6
|
+
end
|
7
|
+
|
8
|
+
# add vector directly to point (destructive update)
|
9
|
+
def add_to_point! point
|
10
|
+
point.lat = lat + point.lat
|
11
|
+
point.lng = lng + point.lng
|
12
|
+
point
|
13
|
+
end
|
14
|
+
|
15
|
+
def sub_from_point point
|
16
|
+
reverse.add_to_point point
|
17
|
+
end
|
18
|
+
|
19
|
+
def sub_from_point! point
|
20
|
+
reverse.add_to_point! point
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class PointVector < GeoVector
|
2
|
+
module VectorOps
|
3
|
+
def random_vector
|
4
|
+
lat_max = point.lat.abs
|
5
|
+
lng_max = point.lng.abs
|
6
|
+
rand_lat = rand(lat_max * 2) - lat_max
|
7
|
+
rand_lng = rand(lng_max * 2) - lng_max
|
8
|
+
PointVector.new [rand_lat, rand_lng]
|
9
|
+
end
|
10
|
+
|
11
|
+
def add *args
|
12
|
+
self.dup.add! *args
|
13
|
+
end
|
14
|
+
alias_method :<<, :add
|
15
|
+
alias_method :+, :add
|
16
|
+
|
17
|
+
def add! *args
|
18
|
+
vector = GeoVector::Parser.create_vector *args
|
19
|
+
case vector
|
20
|
+
when PointVector
|
21
|
+
v2 = add_to_point vector
|
22
|
+
self.point = v2.to_arr
|
23
|
+
self
|
24
|
+
else
|
25
|
+
GeoVectors.new self, vector
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def sub *args
|
30
|
+
self.dup.sub! *args
|
31
|
+
end
|
32
|
+
|
33
|
+
def sub! *args
|
34
|
+
vector = GeoVector::Parser.create_vector *args
|
35
|
+
add! vector.reverse
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Calc
|
2
|
+
protected
|
3
|
+
|
4
|
+
def earth_radius_radians
|
5
|
+
2 * Math::PI
|
6
|
+
end
|
7
|
+
|
8
|
+
def bounding_box(point, radius, options = {})
|
9
|
+
lat,lon = extract_coordinates(point)
|
10
|
+
radius = radius.to_f
|
11
|
+
units = options[:units] || :mi
|
12
|
+
[
|
13
|
+
lat - (radius / latitude_degree_distance(units)),
|
14
|
+
lon - (radius / longitude_degree_distance(lat, units)),
|
15
|
+
lat + (radius / latitude_degree_distance(units)),
|
16
|
+
lon + (radius / longitude_degree_distance(lat, units))
|
17
|
+
]
|
18
|
+
end
|
19
|
+
|
20
|
+
def calc_point vector, mode
|
21
|
+
vector = relative_vector(vector) if mode == :relative
|
22
|
+
GeoPoint.new lat + vector.lat, lng + vector.lng
|
23
|
+
end
|
24
|
+
|
25
|
+
def relative_vector vector
|
26
|
+
delta_lng = vector.lng / longitude_radian_distance(lat)
|
27
|
+
delta_lat = vector.lat / latitude_radian_distance
|
28
|
+
Vector.new delta_lat, delta_lng
|
29
|
+
end
|
30
|
+
|
31
|
+
def degrees_to_radians degrees
|
32
|
+
degrees / 57.29578
|
33
|
+
end
|
34
|
+
|
35
|
+
def delta_lat degrees, distance
|
36
|
+
rad = degrees_to_radians(degrees)
|
37
|
+
Math.sin(rad) * distance
|
38
|
+
end
|
39
|
+
|
40
|
+
def delta_lng degrees, distance
|
41
|
+
rad = degrees_to_radians(degrees)
|
42
|
+
Math.cos(rad) * distance
|
43
|
+
end
|
44
|
+
|
45
|
+
def latitude_radian_distance
|
46
|
+
0.017453292519943295
|
47
|
+
end
|
48
|
+
|
49
|
+
def longitude_radian_distance(latitude)
|
50
|
+
latitude_radian_distance * Math.cos(latitude)
|
51
|
+
end
|
52
|
+
|
53
|
+
def distance_to_radians(distance, units = :mi)
|
54
|
+
distance.to_f / earth_radius(units)
|
55
|
+
end
|
56
|
+
|
57
|
+
def radians_to_distance(radians, units = :mi)
|
58
|
+
radians * earth_radius(units)
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Distance spanned by one degree of latitude in the given units.
|
63
|
+
#
|
64
|
+
def latitude_degree_distance(units = :mi)
|
65
|
+
2 * Math::PI * earth_radius(units) / 360
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Convert kilometers to miles.
|
70
|
+
#
|
71
|
+
def to_miles(km)
|
72
|
+
km * km_in_mi
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Radius of the Earth in the given units (:mi or :km). Default is :mi.
|
77
|
+
#
|
78
|
+
def earth_radius(units = :mi)
|
79
|
+
units == :km ? EARTH_RADIUS : to_miles(EARTH_RADIUS)
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Distance spanned by one degree of longitude at the given latitude.
|
84
|
+
# This ranges from around 69 miles at the equator to zero at the poles.
|
85
|
+
#
|
86
|
+
def longitude_degree_distance(latitude, units = :mi)
|
87
|
+
latitude_degree_distance(units) * Math.cos(to_radians(latitude))
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module GeoDirection
|
2
|
+
def valid_directions
|
3
|
+
[:north, :south, :east, :west, :N, :S, :E, :W, :NW, :NE, :SW, :SE]
|
4
|
+
end
|
5
|
+
|
6
|
+
def directions
|
7
|
+
[:N, :S, :E, :W, :NW, :NE, :SW, :SE]
|
8
|
+
end
|
9
|
+
|
10
|
+
def random_direction
|
11
|
+
directions[rand(directions.size)]
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid_direction? dir
|
15
|
+
valid_directions.include? dir
|
16
|
+
end
|
17
|
+
|
18
|
+
def dir_to_bearing dir
|
19
|
+
dir = dir_to_bearing_map[dir]
|
20
|
+
raise "No bearing for direction #{dir} could be found" if !dir
|
21
|
+
dir
|
22
|
+
end
|
23
|
+
|
24
|
+
def bearing_to_dir brng
|
25
|
+
dir = bearing_to_dir_map[:"_#{brng}"]
|
26
|
+
raise "No direction could be for the bearing #{brng}" if !dir
|
27
|
+
dir
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_valid_direction dir
|
31
|
+
return dir_map[dir] if dir_map[dir]
|
32
|
+
raise "Not a valid direction: #{dir}" if !directions.include? dir
|
33
|
+
dir
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_bearing_vector direction, distance
|
37
|
+
distance, direction = [direction, distance] if direction.kind_of?(GeoDistance) || distance.kind_of?(Symbol)
|
38
|
+
BearingVector.new distance, dir_to_bearing(direction)
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_point_vector direction, distance
|
42
|
+
# dist degrees
|
43
|
+
dd = distance.to_deg
|
44
|
+
|
45
|
+
lng, lat = case direction
|
46
|
+
when :N
|
47
|
+
[0, -dd]
|
48
|
+
when :S
|
49
|
+
[0, dd]
|
50
|
+
when :W
|
51
|
+
[-dd, 0]
|
52
|
+
when :E
|
53
|
+
[dd, 0]
|
54
|
+
when :NW
|
55
|
+
[dd, b]
|
56
|
+
when :SW
|
57
|
+
[dd, -dd]
|
58
|
+
when :NE
|
59
|
+
[-dd, dd]
|
60
|
+
when :SE
|
61
|
+
[-dd, -dd]
|
62
|
+
end
|
63
|
+
[lat, lng].vector
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def check_direction! dir
|
69
|
+
raise ArgumentError, "Not a valid direction: #{dir}" if !valid_direction?
|
70
|
+
end
|
71
|
+
|
72
|
+
def dir_map
|
73
|
+
{:east => :E, :north => :N, :west => :W, :south => :S}
|
74
|
+
end
|
75
|
+
|
76
|
+
def bearing_to_dir_map
|
77
|
+
{
|
78
|
+
:_0 => :N, # East
|
79
|
+
:_45 => :NE, # North East
|
80
|
+
:_90 => :N, # North
|
81
|
+
:_135 => :NW, # North West
|
82
|
+
:_180 => :W, # West (2*90)
|
83
|
+
:_225 => :SW, # South West
|
84
|
+
:_270 => :S, # South (3*90)
|
85
|
+
:_315 => :SE # South East
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def dir_to_bearing_map
|
90
|
+
{
|
91
|
+
:E => 0, # East
|
92
|
+
:NE => 45, # North East
|
93
|
+
:N => 90, # North
|
94
|
+
:NW => 135, # North West
|
95
|
+
:W => 180, # West (2*90)
|
96
|
+
:SW => 225, # South West
|
97
|
+
:S => 270, # South (3*90)
|
98
|
+
:SE => 315 # South East
|
99
|
+
}
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'geo_vectors/util/geo_units'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
|
4
|
+
class GeoDistance
|
5
|
+
include NumericCheckExt
|
6
|
+
include GeoUnits
|
7
|
+
include Comparable
|
8
|
+
|
9
|
+
attr_accessor :unit, :number
|
10
|
+
|
11
|
+
def initialize number, unit = :kms
|
12
|
+
check_unit! unit
|
13
|
+
check_numeric! number
|
14
|
+
|
15
|
+
@unit = unit
|
16
|
+
@number = number
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
"distance: #{number} #{unit}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def * factor
|
24
|
+
dist = self.dup
|
25
|
+
dist.number *= factor
|
26
|
+
dist
|
27
|
+
end
|
28
|
+
|
29
|
+
def / factor
|
30
|
+
dist = self.dup
|
31
|
+
dist.number /= factor
|
32
|
+
dist
|
33
|
+
end
|
34
|
+
|
35
|
+
# compare 2 distances
|
36
|
+
def <=> dist
|
37
|
+
dist = extract_distance(dist).as(unit)
|
38
|
+
if number < dist.number
|
39
|
+
-1
|
40
|
+
elsif number > dist.number
|
41
|
+
1
|
42
|
+
else
|
43
|
+
0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def convert_to_meters
|
48
|
+
(unit == :radians) ? radians_to(:meters) : number / meters_map[unit]
|
49
|
+
end
|
50
|
+
|
51
|
+
# convert to unit (see GeoMagic)
|
52
|
+
def as unit
|
53
|
+
check_unit! unit
|
54
|
+
dist = self.dup
|
55
|
+
dist.number = convert_to_meters * meters_map[unit]
|
56
|
+
dist.unit = unit
|
57
|
+
dist
|
58
|
+
end
|
59
|
+
|
60
|
+
# from GeoUnits
|
61
|
+
GeoUnits.valid_units.map(&:to_s).each do |unit|
|
62
|
+
one_unit = unit.singularize
|
63
|
+
class_eval %{
|
64
|
+
def #{one_unit}
|
65
|
+
as(:#{unit}).number
|
66
|
+
end
|
67
|
+
|
68
|
+
def as_#{unit}
|
69
|
+
as(:#{unit})
|
70
|
+
end
|
71
|
+
|
72
|
+
alias_method :to_#{unit}, :#{one_unit}
|
73
|
+
alias_method :in_#{unit}, :#{one_unit}
|
74
|
+
alias_method :#{unit.pluralize}, :#{one_unit}
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def random
|
79
|
+
rand(number.to_f * 100) / 100.0
|
80
|
+
end
|
81
|
+
|
82
|
+
def radians_to unit
|
83
|
+
check_unit! unit
|
84
|
+
earth_radius[unit] * number
|
85
|
+
end
|
86
|
+
|
87
|
+
protected
|
88
|
+
|
89
|
+
include GeoUnits::UnitMaps
|
90
|
+
|
91
|
+
# converts a number into a distance of same unit
|
92
|
+
def extract_distance dist
|
93
|
+
is_numeric?(dist) ? dist.send(unit) : dist
|
94
|
+
end
|
95
|
+
|
96
|
+
# used to extend Fixnum and Float
|
97
|
+
module Unit
|
98
|
+
extend GeoUnits
|
99
|
+
extend GeoUnits::UnitMaps
|
100
|
+
|
101
|
+
# from GeoUnits
|
102
|
+
valid_units.map(&:to_s).each do |unit|
|
103
|
+
one_unit = unit.singularize
|
104
|
+
class_eval %{
|
105
|
+
def #{one_unit}
|
106
|
+
GeoDistance.new self, :#{unit}
|
107
|
+
end
|
108
|
+
alias_method :to_#{unit}, :#{one_unit}
|
109
|
+
alias_method :in_#{unit}, :#{one_unit}
|
110
|
+
alias_method :as_#{unit}, :#{one_unit}
|
111
|
+
alias_method :#{unit.pluralize}, :#{one_unit}
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def [] key
|
116
|
+
raise ArgumentError, "Invalid unit key #{key}" if !respond_to? key
|
117
|
+
earth_radius[key] * self
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module Extract
|
122
|
+
def extract_distance dist
|
123
|
+
case dist
|
124
|
+
when Fixnum, Float
|
125
|
+
dist.km
|
126
|
+
when GeoDistance
|
127
|
+
dist
|
128
|
+
else
|
129
|
+
raise ArgumentError, "Could not convert #{dist} to a GeoDistance"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
include Extract
|
135
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module GeoUnits
|
2
|
+
module Methods
|
3
|
+
def valid_units
|
4
|
+
[:feet, :meters, :kms, :miles, :radians]
|
5
|
+
end
|
6
|
+
|
7
|
+
def valid_unit? unit
|
8
|
+
valid_units.include? unit
|
9
|
+
end
|
10
|
+
|
11
|
+
# The default unit is assumed to be kms
|
12
|
+
# This can be changed
|
13
|
+
# Example:
|
14
|
+
# GeoVector.default_unit = :km
|
15
|
+
|
16
|
+
def default_unit
|
17
|
+
@default_unit || :kms
|
18
|
+
end
|
19
|
+
|
20
|
+
def default_unit= unit
|
21
|
+
@default_unit || :kms
|
22
|
+
end
|
23
|
+
|
24
|
+
def check_unit! unit
|
25
|
+
raise ArgumentError, "Not a valid unit" if !valid_unit? unit
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
include Methods
|
30
|
+
extend Methods
|
31
|
+
|
32
|
+
module UnitMaps
|
33
|
+
|
34
|
+
def earth_radius
|
35
|
+
{
|
36
|
+
:feet => 20895592,
|
37
|
+
:meters => 6371000,
|
38
|
+
:kms => 6371,
|
39
|
+
:miles => 3956
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def meters_map
|
44
|
+
{
|
45
|
+
:feet => 3.2808,
|
46
|
+
:meters => 1,
|
47
|
+
:kms => 0.001,
|
48
|
+
:miles => 0.00062137,
|
49
|
+
:radians => 111170
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|