geo_calc 0.6.1 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/Gemfile +3 -0
  2. data/README.textile +12 -0
  3. data/VERSION +1 -1
  4. data/geo_calc.gemspec +29 -6
  5. data/lib/geo_calc/calc/destination.rb +1 -1
  6. data/lib/geo_calc/calc/distance.rb +1 -1
  7. data/lib/geo_calc/calc/rhumb.rb +2 -2
  8. data/lib/geo_calc/calc.rb +20 -21
  9. data/lib/geo_calc/dms/converter.rb +106 -0
  10. data/lib/geo_calc/dms.rb +5 -0
  11. data/lib/geo_calc/extensions/array.rb +26 -0
  12. data/lib/geo_calc/extensions/hash.rb +23 -0
  13. data/lib/geo_calc/extensions/math.rb +6 -0
  14. data/lib/geo_calc/extensions/numeric.rb +24 -0
  15. data/lib/geo_calc/extensions/string.rb +44 -0
  16. data/lib/geo_calc/extensions/symbol.rb +9 -0
  17. data/lib/geo_calc/extensions.rb +4 -0
  18. data/lib/geo_calc/geo_point/class_methods.rb +15 -0
  19. data/lib/geo_calc/geo_point/core_extension.rb +11 -0
  20. data/lib/geo_calc/geo_point/shared.rb +29 -0
  21. data/lib/geo_calc/geo_point.rb +42 -40
  22. data/lib/geo_calc/pretty_print.rb +2 -2
  23. data/lib/geo_calc.rb +5 -0
  24. data/lib/geo_units/converter.rb +123 -0
  25. data/lib/geo_units/numeric_ext.rb +117 -0
  26. data/lib/geo_units.rb +21 -0
  27. data/spec/geo_calc/core_ext/numeric_geo_ext_spec.rb +48 -50
  28. data/spec/geo_calc/core_ext_spec.rb +49 -51
  29. data/spec/geo_calc/dms/converter_spec.rb +60 -0
  30. data/spec/geo_calc/geo_point/class_methods_spec.rb +31 -0
  31. data/spec/geo_calc/geo_point/initializer_spec.rb +148 -0
  32. data/spec/geo_calc/geo_point/lat_lon.rb +115 -0
  33. data/spec/geo_calc/geo_point_spec.rb +4 -274
  34. data/spec/geo_units/converter_spec.rb +57 -0
  35. metadata +56 -17
  36. data/lib/geo_calc/core_ext.rb +0 -318
  37. data/lib/geo_calc/geo.rb +0 -171
  38. data/spec/geo_calc/geo_spec.rb +0 -99
@@ -0,0 +1,123 @@
1
+ require 'geo_calc/calc'
2
+ require 'geo_calc/extensions'
3
+
4
+ module GeoUnits
5
+ module Converter
6
+ # Convert numeric degrees to deg/min/sec latitude (suffixed with N/S)
7
+ #
8
+ # @param {Number} deg: Degrees
9
+ # @param {String} [format=dms]: Return value as 'd', 'dm', 'dms'
10
+ # @param {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
11
+ # @returns {String} Deg/min/seconds
12
+
13
+ def to_lat deg, format = :dms, dp = 0
14
+ deg = deg.normalize_lat
15
+ _lat = GeoCalc::Dms::Converter.to_dms deg, format, dp
16
+ _lat == '' ? '' : _lat[1..-1] + (deg<0 ? 'S' : 'N') # knock off initial '0' for lat!
17
+ end
18
+
19
+ # Convert numeric degrees to deg/min/sec longitude (suffixed with E/W)
20
+ #
21
+ # @param {Number} deg: Degrees
22
+ # @param {String} [format=dms]: Return value as 'd', 'dm', 'dms'
23
+ # @param {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
24
+ # @returns {String} Deg/min/seconds
25
+
26
+ def to_lon deg, format = :dms, dp = 0
27
+ deg = deg.normalize_lng
28
+ lon = GeoCalc::Dms::Converter.to_dms deg, format, dp
29
+ lon == '' ? '' : lon + (deg<0 ? 'W' : 'E')
30
+ end
31
+
32
+
33
+ # Convert numeric degrees to deg/min/sec as a bearing (0º..360º)
34
+ #
35
+ # @param {Number} deg: Degrees
36
+ # @param {String} [format=dms]: Return value as 'd', 'dm', 'dms'
37
+ # @param {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
38
+ # @returns {String} Deg/min/seconds
39
+
40
+ def to_brng deg, format = :dms, dp = 0
41
+ deg = (deg.to_f + 360) % 360 # normalise -ve values to 180º..360º
42
+ brng = GeoCalc::Dms::Converter.to_dms deg, format, dp
43
+ brng.gsub /360/, '0' # just in case rounding took us up to 360º!
44
+ end
45
+
46
+ protected
47
+
48
+ include ::GeoCalc::NumericCheckExt
49
+
50
+ # Converts numeric degrees to radians
51
+ def to_rad degrees
52
+ degrees * Math::PI / 180
53
+ end
54
+ alias_method :to_radians, :to_rad
55
+ alias_method :as_rad, :to_rad
56
+ alias_method :as_radians, :to_rad
57
+ alias_method :in_rad, :to_rad
58
+ alias_method :in_radians, :to_rad
59
+
60
+
61
+ # Converts radians to numeric (signed) degrees
62
+ # latitude (north to south) from equator +90 up then -90 down (equator again) = 180 then 180 for south = 360 total
63
+ # longitude (west to east) east +180, west -180 = 360 total
64
+ def to_deg radians
65
+ radians * 180 / Math::PI
66
+ end
67
+
68
+ alias_method :to_degrees, :to_deg
69
+ alias_method :as_deg, :to_deg
70
+ alias_method :as_degrees, :to_deg
71
+ alias_method :in_deg, :to_deg
72
+ alias_method :in_degrees, :to_deg
73
+
74
+ extend self
75
+ end
76
+
77
+ # all degrees between -180 and 180
78
+ def normalize_lng deg
79
+ case deg
80
+ when -360..-180
81
+ deg % 180
82
+ when -180..0
83
+ -180 + (deg % 180)
84
+ when 0..180
85
+ deg
86
+ when 180..360
87
+ deg % 180
88
+ else
89
+ raise ArgumentError, "Degrees #{deg} out of range, must be between -360 to 360"
90
+ end
91
+ end
92
+
93
+ # all degrees between -90 and 90
94
+ def normalize_lat deg
95
+ case deg
96
+ when -360..-270
97
+ deg % 90
98
+ when -270..-180
99
+ 90 - (deg % 90)
100
+ when -180..-90
101
+ - (deg % 90)
102
+ when -90..0
103
+ -90 + (deg % 90)
104
+ when 0..90
105
+ deg
106
+ when 90..180
107
+ deg % 90
108
+ when 180..270
109
+ - (deg % 90)
110
+ when 270..360
111
+ - 90 + (deg % 90)
112
+ else
113
+ raise ArgumentError, "Degrees #{deg} out of range, must be between -360 to 360"
114
+ end
115
+ end
116
+
117
+ def normalize_deg degrees, shift = 0
118
+ (degrees + shift) % 360
119
+ end
120
+ alias_method :normalize_degrees, :normalize_deg
121
+
122
+ extend self
123
+ end
@@ -0,0 +1,117 @@
1
+ module GeoUnits
2
+ module NumericExt
3
+ def to_lat
4
+ normalize_lat
5
+ end
6
+
7
+ def to_lng
8
+ normalize_lng
9
+ end
10
+
11
+ def is_between? lower, upper
12
+ (lower..upper).cover? self
13
+ end
14
+
15
+
16
+ def to_dms format = :dms, dp = nil
17
+ GeoCalc::Dms::Converter.to_dms self, format, dp
18
+ end
19
+
20
+ def to_lat_dms format = :dms, dp = nil
21
+ GeoUnits::Converter.to_lat self, format, dp
22
+ end
23
+
24
+ def to_lon_dms format = :dms, dp = nil
25
+ GeoUnits::Converter.to_lon self, format, dp
26
+ end
27
+
28
+ # Converts numeric degrees to radians
29
+ def to_rad
30
+ self * Math::PI / 180
31
+ end
32
+ alias_method :to_radians, :to_rad
33
+ alias_method :as_rad, :to_rad
34
+ alias_method :as_radians, :to_rad
35
+ alias_method :in_rad, :to_rad
36
+ alias_method :in_radians, :to_rad
37
+
38
+
39
+ # Converts radians to numeric (signed) degrees
40
+ # latitude (north to south) from equator +90 up then -90 down (equator again) = 180 then 180 for south = 360 total
41
+ # longitude (west to east) east +180, west -180 = 360 total
42
+ def to_deg
43
+ self * 180 / Math::PI
44
+ end
45
+
46
+ alias_method :to_degrees, :to_deg
47
+ alias_method :as_deg, :to_deg
48
+ alias_method :as_degrees, :to_deg
49
+ alias_method :in_deg, :to_deg
50
+ alias_method :in_degrees, :to_deg
51
+
52
+
53
+ # Formats the significant digits of a number, using only fixed-point notation (no exponential)
54
+ #
55
+ # @param {Number} precision: Number of significant digits to appear in the returned string
56
+ # @returns {String} A string representation of number which contains precision significant digits
57
+ def to_precision precision
58
+ self.round(precision).to_s
59
+ end
60
+ alias_method :to_fixed, :to_precision
61
+
62
+ # all degrees between -180 and 180
63
+ def normalize_lng
64
+ case self
65
+ when -360, 0, 360
66
+ 0
67
+ when -360..-180
68
+ self % 180
69
+ when -180..0
70
+ -180 + (self % 180)
71
+ when 0..180
72
+ self
73
+ when 180..360
74
+ self % 180
75
+ else
76
+ return (self % 360).normalize_lng if self > 360
77
+ return (360 - (self % 360)).normalize_lng if self < -360
78
+ raise ArgumentError, "Degrees #{self} out of range"
79
+ end
80
+ end
81
+
82
+ # all degrees between -90 and 90
83
+ def normalize_lat
84
+ case self
85
+ when -360, 0, 360
86
+ 0
87
+ when -180, 180
88
+ 0
89
+ when -360..-270
90
+ self % 90
91
+ when -270..-180
92
+ 90 - (self % 90)
93
+ when -180..-90
94
+ - (self % 90)
95
+ when -90..0
96
+ -90 + (self % 90)
97
+ when 0..90
98
+ self
99
+ when 90..180
100
+ self % 90
101
+ when 180..270
102
+ - (self % 90)
103
+ when 270..360
104
+ - 90 + (self % 90)
105
+ else
106
+ return (self % 360).normalize_lat if self > 360
107
+ return (360 - (self % 360)).normalize_lat if self < -360
108
+ raise ArgumentError, "Degrees #{self} out of range"
109
+ end
110
+ end
111
+
112
+ def normalize_deg shift = 0
113
+ (self + shift) % 360
114
+ end
115
+ alias_method :normalize_degrees, :normalize_deg
116
+ end
117
+ end
data/lib/geo_units.rb ADDED
@@ -0,0 +1,21 @@
1
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2
+ # Geodesy representation conversion functions (c) Chris Veness 2002-2010
3
+ # - www.movable-type.co.uk/scripts/latlong.html
4
+ #
5
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6
+
7
+ # Parses string representing degrees/minutes/seconds into numeric degrees
8
+ #
9
+ # This is very flexible on formats, allowing signed decimal degrees, or deg-min-sec optionally
10
+ # suffixed by compass direction (NSEW). A variety of separators are accepted (eg 3º 37' 09"W)
11
+ # or fixed-width format without separators (eg 0033709W). Seconds and minutes may be omitted.
12
+ # (Note minimal validation is done).
13
+ #
14
+ # @param {String|Number} dmsStr: Degrees or deg/min/sec in variety of formats
15
+ # @returns {Number} Degrees as decimal number
16
+ # @throws ArgumentError
17
+
18
+ module GeoUnits
19
+ autoload :Converter, 'geo_units/converter'
20
+ autoload :NumericExt, 'geo_units/numeric_ext'
21
+ end
@@ -3,71 +3,69 @@ require 'spec_helper'
3
3
  # - www.movable-type.co.uk/scripts/latlong.html
4
4
  describe GeoPoint do
5
5
  describe 'ruby core Class extensions' do
6
- describe NumericGeoExt do
7
- describe '#to_rad' do
8
- it 'should convert 0 degrees to 0 radians' do
9
- 0.to_rad.should == 0
10
- end
6
+ describe '#to_rad' do
7
+ it 'should convert 0 degrees to 0 radians' do
8
+ 0.to_rad.should == 0
9
+ end
11
10
 
12
- it 'should convert 180 degrees to PI radians' do
13
- 180.to_rad.should == Math::PI
14
- end
11
+ it 'should convert 180 degrees to PI radians' do
12
+ 180.to_rad.should == Math::PI
13
+ end
15
14
 
16
- it 'should convert 360 degrees to 6.28 radians' do
17
- 360.to_rad.should == Math::PI*2
18
- end
15
+ it 'should convert 360 degrees to 6.28 radians' do
16
+ 360.to_rad.should == Math::PI*2
19
17
  end
18
+ end
20
19
 
21
- describe '#to_radians' do
22
- it 'should alias to_rad' do
23
- 360.to_rad.should == 360.to_radians
24
- end
20
+ describe '#to_radians' do
21
+ it 'should alias to_rad' do
22
+ 360.to_rad.should == 360.to_radians
25
23
  end
24
+ end
26
25
 
27
- describe '#to_deg' do
28
- it 'should convert 0 radians to 0 degrees' do
29
- 0.to_deg.should == 0
30
- end
26
+ describe '#to_deg' do
27
+ it 'should convert 0 radians to 0 degrees' do
28
+ 0.to_deg.should == 0
29
+ end
31
30
 
32
- it 'should convert PI radians to 180 degrees' do
33
- 180.to_rad.to_deg.should be_within(0.01).of(180)
34
- end
31
+ it 'should convert PI radians to 180 degrees' do
32
+ 180.to_rad.to_deg.should be_within(0.01).of(180)
33
+ end
35
34
 
36
- it 'should convert 6.28 radians to 360 degrees' do
37
- 360.to_rad.to_deg.should be_within(0.01).of(360)
38
- end
35
+ it 'should convert 6.28 radians to 360 degrees' do
36
+ 360.to_rad.to_deg.should be_within(0.01).of(360)
39
37
  end
40
-
41
- describe '#to_degrees' do
42
- it 'should alias to_deg' do
43
- 360.to_deg.should == 360.to_degrees
44
- end
38
+ end
39
+
40
+ describe '#to_degrees' do
41
+ it 'should alias to_deg' do
42
+ 360.to_deg.should == 360.to_degrees
45
43
  end
46
-
47
- describe '#to_fixed' do
48
- it 'should make precision 4' do
49
- 1.123456.to_fixed(2).should == '1.12'
50
- end
44
+ end
45
+
46
+ describe '#to_fixed' do
47
+ it 'should make precision 4' do
48
+ 1.123456.to_fixed(2).should == '1.12'
51
49
  end
50
+ end
52
51
 
53
- describe '#to_precision' do
54
- it 'should alis to_fixed' do
55
- 1.123456.to_precision(4).should == '1.1235'
56
- end
52
+ describe '#to_precision' do
53
+ it 'should alis to_fixed' do
54
+ 1.123456.to_precision(4).should == '1.1235'
57
55
  end
56
+ end
58
57
 
59
- describe '#normalize' do
60
- it 'should turn 180 deg and normalize' do
61
- 361.normalize_deg(180).should == 181
62
- end
63
- it 'should normalize deg' do
64
- 361.normalize_deg.should == 1
65
- end
58
+ describe '#normalize' do
59
+ it 'should turn 180 deg and normalize' do
60
+ 361.normalize_deg(180).should == 181
61
+ end
62
+ it 'should normalize deg' do
63
+ 361.normalize_deg.should == 1
64
+ end
66
65
 
67
- it 'should alias with #normalize_degrees' do
68
- 362.normalize_degrees.should == 2
69
- end
66
+ it 'should alias with #normalize_degrees' do
67
+ 362.normalize_degrees.should == 2
70
68
  end
71
- end # NumericGeoExt
69
+ end
72
70
  end
73
71
  end
@@ -3,56 +3,54 @@ require 'spec_helper'
3
3
  # - www.movable-type.co.uk/scripts/latlong.html
4
4
  describe GeoPoint do
5
5
  describe 'ruby core Class extensions' do
6
- describe NumericLatLngExt do
7
- describe 'Fixnum extension' do
8
- describe '#to_lat' do
9
- it 'should set origin at 0,0' do
10
- origin = [0, 0].geo_point
11
- origin.lat.should == 0
12
- origin.lng.should == 0
13
- end
14
-
15
- it 'should return latitude degree value for 360' do
16
- 360.to_lat.should == 0
17
- end
18
-
19
- it 'should normalize degrees before converting to latitude, so 361 becomes 1' do
20
- 361.to_lat.should == 1
21
- end
22
- end
23
-
24
- describe '#to_lng' do
25
- it 'should return latitude degree value for 360' do
26
- 90.to_lng.should == 90
27
- end
28
-
29
- it 'should normalize degrees before converting to latitude, so 361 becomes 1' do
30
- 91.to_lng.should == 91
31
- end
32
- end
33
- end
34
-
35
- describe 'Float extension' do
36
- describe '#to_lat' do
37
- it 'should return latitude degree value for 360' do
38
- (360.0).to_lat.should == 0
39
- end
40
-
41
- it 'should normalize degrees before converting to latitude, so 361 becomes 1' do
42
- (361.1).to_lat.should be_within(0.01).of(1.1)
43
- end
44
- end
45
-
46
- describe '#to_lng' do
47
- it 'should return latitude degree value for 360' do
48
- (360.0).to_lng.should == 0
49
- end
50
-
51
- it 'should normalize degrees before converting to latitude, so 361 becomes 1' do
52
- (361.1).to_lng.should be_within(0.01).of(1.1)
53
- end
54
- end
55
- end
6
+ describe 'Fixnum extension' do
7
+ describe '#to_lat' do
8
+ it 'should set origin at 0,0' do
9
+ origin = [0, 0].geo_point
10
+ origin.lat.should == 0
11
+ origin.lng.should == 0
12
+ end
13
+
14
+ it 'should return latitude degree value for 360' do
15
+ 360.to_lat.should == 0
16
+ end
17
+
18
+ it 'should normalize degrees before converting to latitude, so 361 becomes 1' do
19
+ 361.to_lat.should == 1
20
+ end
21
+ end
22
+
23
+ describe '#to_lng' do
24
+ it 'should return latitude degree value for 360' do
25
+ 90.to_lng.should == 90
26
+ end
27
+
28
+ it 'should normalize degrees before converting to latitude, so 361 becomes 1' do
29
+ 91.to_lng.should == 91
30
+ end
31
+ end
56
32
  end
57
- end
33
+
34
+ describe 'Float extension' do
35
+ describe '#to_lat' do
36
+ it 'should return latitude degree value for 360' do
37
+ (360.0).to_lat.should == 0
38
+ end
39
+
40
+ it 'should normalize degrees before converting to latitude, so 361 becomes 1' do
41
+ (361.1).to_lat.should be_within(0.01).of(1.1)
42
+ end
43
+ end
44
+
45
+ describe '#to_lng' do
46
+ it 'should return latitude degree value for 360' do
47
+ (360.0).to_lng.should == 0
48
+ end
49
+
50
+ it 'should normalize degrees before converting to latitude, so 361 becomes 1' do
51
+ (361.1).to_lng.should be_within(0.01).of(1.1)
52
+ end
53
+ end
54
+ end
55
+ end
58
56
  end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ class Parser
4
+ include GeoCalc::Dms::Converter
5
+ end
6
+
7
+ def parser
8
+ Parser.new
9
+ end
10
+
11
+ # - www.movable-type.co.uk/scripts/latlong.html
12
+ describe GeoCalc::Dms::Converter do
13
+ # # @param {String|Number} dmsStr: Degrees or deg/min/sec in variety of formats
14
+ # @returns {Number} Degrees as decimal number
15
+ describe '#parse_dms' do
16
+ it 'should convert "58 38 38N" to a Float of degrees (58..59)' do
17
+ deg = parser.parse_dms("58 38 38N")
18
+ deg.should be_a(Float)
19
+ deg.should be_between(58, 59)
20
+ end
21
+
22
+ it 'should convert "01 38 38W" to a Float of degrees (1..2)' do
23
+ deg = parser.parse_dms("01 38 38W")
24
+ deg.should be_a(Float)
25
+ deg.should < 0
26
+ deg.should > -2
27
+ end
28
+
29
+ it 'should convert "005 38 E" to a Float of degrees (5..6)' do
30
+ deg = parser.parse_dms("005 38 E")
31
+ deg.should be_a(Float)
32
+ deg.should be_between(5, 6)
33
+ end
34
+ end
35
+
36
+ # deg, format = :dms, dp = 0
37
+ describe '#to_dms' do
38
+ it 'should convert 58.3 to a String in DMS format' do
39
+ dms = parser.to_dms(58.3)
40
+ dms.should be_a(String)
41
+ expr = Regexp.escape "058".concat("\u00B0", "18", "\u2032", "00", "\u2033")
42
+ dms.should match expr
43
+ end
44
+
45
+ it 'should convert 58.3 to a String in DM format' do
46
+ dm = parser.to_dms(58.3, :dm, 2)
47
+ dm.should be_a(String)
48
+ expr = Regexp.escape "058".concat("\u00B0", "18", "\u2032")
49
+ dm.should match expr
50
+ end
51
+
52
+ it 'should convert 58.3 to a String in D format' do
53
+ d = parser.to_dms(58.3, :d, 2)
54
+ d.should be_a(String)
55
+ m = Regexp.escape "058".concat("\u00B0")
56
+ d.should match m
57
+ end
58
+ end
59
+ end
60
+
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ # - www.movable-type.co.uk/scripts/latlong.html
4
+ describe GeoPoint do
5
+ describe 'Class methods' do
6
+ describe '#coord_mode' do
7
+ it 'should change global coordinates mode' do
8
+ GeoPoint.coord_mode = :lng_lat
9
+ GeoPoint.coord_mode.should == :lng_lat
10
+
11
+ GeoPoint.coord_mode = :lat_lng
12
+ GeoPoint.coord_mode.should == :lat_lng
13
+ end
14
+
15
+ it 'shoould not allow setting invalid coord mode' do
16
+ lambda { GeoPoint.coord_mode = :blip }.should raise_error
17
+ end
18
+ end
19
+
20
+ describe '#earth_radius_km' do
21
+ it 'should change global earth_radius_km' do
22
+ GeoPoint.earth_radius_km = 6360
23
+ GeoPoint.earth_radius_km.should == 6360
24
+ end
25
+
26
+ it 'shoould not allow setting invalid earth radius' do
27
+ lambda { GeoPoint.earth_radius_km = 6100 }.should raise_error
28
+ end
29
+ end
30
+ end
31
+ end