geo_vectors 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|