geo_units 0.2.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/Gemfile +13 -0
- data/LICENSE.txt +20 -0
- data/README.textile +22 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/geo_units.gemspec +67 -0
- data/lib/geo_units/converter.rb +120 -0
- data/lib/geo_units/core_ext.rb +13 -0
- data/lib/geo_units/dms_converter.rb +107 -0
- data/lib/geo_units/numeric_ext.rb +117 -0
- data/lib/geo_units.rb +101 -0
- data/spec/geo_units/converter_spec.rb +57 -0
- data/spec/geo_units/dms_converter_spec.rb +60 -0
- data/spec/spec_helper.rb +6 -0
- metadata +121 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
|
4
|
+
gem "sugar-high", '~> 0.4.6.2'
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "rspec", ">= 2.5.0"
|
10
|
+
gem "bundler", "~> 1.0.6"
|
11
|
+
gem "jeweler", "~> 1.6.2"
|
12
|
+
gem "rcov", ">= 0"
|
13
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Kristian Mandrup
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
h1. Geo Units
|
2
|
+
|
3
|
+
Distance unit functionality (conversions etc.) for Geo libraries. See specs for details.
|
4
|
+
|
5
|
+
This gem is used by the _geo_point_ and _geo_calc_ gems and perhaps by others. The aim is that it will contain most of the basic
|
6
|
+
distance unit functionality required by typical Geo projects.
|
7
|
+
|
8
|
+
h2. Contributing to geo_units
|
9
|
+
|
10
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
11
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
12
|
+
* Fork the project
|
13
|
+
* Start a feature/bugfix branch
|
14
|
+
* Commit and push until you are happy with your contribution
|
15
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
16
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
17
|
+
|
18
|
+
h2. Copyright
|
19
|
+
|
20
|
+
Copyright (c) 2011 Kristian Mandrup. See LICENSE.txt for
|
21
|
+
further details.
|
22
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "geo_units"
|
18
|
+
gem.homepage = "http://github.com/kristianmandrup/geo_units"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Distance unit modules and functionality for use in geo libraries}
|
21
|
+
gem.description = %Q{Easily convert between different distance units such as kms, miles etc.}
|
22
|
+
gem.email = "kmandrup@gmail.com"
|
23
|
+
gem.authors = ["Kristian Mandrup"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core'
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
spec.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :spec
|
40
|
+
|
41
|
+
require 'rake/rdoctask'
|
42
|
+
Rake::RDocTask.new do |rdoc|
|
43
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
44
|
+
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = "geo_units #{version}"
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
data/geo_units.gemspec
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{geo_units}
|
8
|
+
s.version = "0.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = [%q{Kristian Mandrup}]
|
12
|
+
s.date = %q{2011-06-13}
|
13
|
+
s.description = %q{Easily convert between different distance units such as kms, miles etc.}
|
14
|
+
s.email = %q{kmandrup@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.textile"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rspec",
|
22
|
+
"Gemfile",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.textile",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"geo_units.gemspec",
|
28
|
+
"lib/geo_units.rb",
|
29
|
+
"lib/geo_units/converter.rb",
|
30
|
+
"lib/geo_units/core_ext.rb",
|
31
|
+
"lib/geo_units/dms_converter.rb",
|
32
|
+
"lib/geo_units/numeric_ext.rb",
|
33
|
+
"spec/geo_units/converter_spec.rb",
|
34
|
+
"spec/geo_units/dms_converter_spec.rb",
|
35
|
+
"spec/spec_helper.rb"
|
36
|
+
]
|
37
|
+
s.homepage = %q{http://github.com/kristianmandrup/geo_units}
|
38
|
+
s.licenses = [%q{MIT}]
|
39
|
+
s.require_paths = [%q{lib}]
|
40
|
+
s.rubygems_version = %q{1.8.5}
|
41
|
+
s.summary = %q{Distance unit modules and functionality for use in geo libraries}
|
42
|
+
|
43
|
+
if s.respond_to? :specification_version then
|
44
|
+
s.specification_version = 3
|
45
|
+
|
46
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
47
|
+
s.add_runtime_dependency(%q<sugar-high>, ["~> 0.4.6.2"])
|
48
|
+
s.add_development_dependency(%q<rspec>, [">= 2.5.0"])
|
49
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.6"])
|
50
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.6.2"])
|
51
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
52
|
+
else
|
53
|
+
s.add_dependency(%q<sugar-high>, ["~> 0.4.6.2"])
|
54
|
+
s.add_dependency(%q<rspec>, [">= 2.5.0"])
|
55
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.6"])
|
56
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
|
57
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
58
|
+
end
|
59
|
+
else
|
60
|
+
s.add_dependency(%q<sugar-high>, ["~> 0.4.6.2"])
|
61
|
+
s.add_dependency(%q<rspec>, [">= 2.5.0"])
|
62
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.6"])
|
63
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
|
64
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module GeoUnits
|
2
|
+
module Converter
|
3
|
+
# Convert numeric degrees to deg/min/sec latitude (suffixed with N/S)
|
4
|
+
#
|
5
|
+
# @param {Number} deg: Degrees
|
6
|
+
# @param {String} [format=dms]: Return value as 'd', 'dm', 'dms'
|
7
|
+
# @param {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
|
8
|
+
# @returns {String} Deg/min/seconds
|
9
|
+
|
10
|
+
def to_lat deg, format = :dms, dp = 0
|
11
|
+
deg = deg.normalize_lat
|
12
|
+
_lat = DmsConverter.to_dms deg, format, dp
|
13
|
+
_lat == '' ? '' : _lat[1..-1] + (deg<0 ? 'S' : 'N') # knock off initial '0' for lat!
|
14
|
+
end
|
15
|
+
|
16
|
+
# Convert numeric degrees to deg/min/sec longitude (suffixed with E/W)
|
17
|
+
#
|
18
|
+
# @param {Number} deg: Degrees
|
19
|
+
# @param {String} [format=dms]: Return value as 'd', 'dm', 'dms'
|
20
|
+
# @param {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
|
21
|
+
# @returns {String} Deg/min/seconds
|
22
|
+
|
23
|
+
def to_lon deg, format = :dms, dp = 0
|
24
|
+
deg = deg.normalize_lng
|
25
|
+
lon = DmsConverter.to_dms deg, format, dp
|
26
|
+
lon == '' ? '' : lon + (deg<0 ? 'W' : 'E')
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
# Convert numeric degrees to deg/min/sec as a bearing (0º..360º)
|
31
|
+
#
|
32
|
+
# @param {Number} deg: Degrees
|
33
|
+
# @param {String} [format=dms]: Return value as 'd', 'dm', 'dms'
|
34
|
+
# @param {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
|
35
|
+
# @returns {String} Deg/min/seconds
|
36
|
+
|
37
|
+
def to_brng deg, format = :dms, dp = 0
|
38
|
+
deg = (deg.to_f + 360) % 360 # normalise -ve values to 180º..360º
|
39
|
+
brng = DmsConverter.to_dms deg, format, dp
|
40
|
+
brng.gsub /360/, '0' # just in case rounding took us up to 360º!
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
include NumericCheckExt # from sugar-high/numeric
|
46
|
+
|
47
|
+
# Converts numeric degrees to radians
|
48
|
+
def to_rad degrees
|
49
|
+
degrees * Math::PI / 180
|
50
|
+
end
|
51
|
+
alias_method :to_radians, :to_rad
|
52
|
+
alias_method :as_rad, :to_rad
|
53
|
+
alias_method :as_radians, :to_rad
|
54
|
+
alias_method :in_rad, :to_rad
|
55
|
+
alias_method :in_radians, :to_rad
|
56
|
+
|
57
|
+
|
58
|
+
# Converts radians to numeric (signed) degrees
|
59
|
+
# latitude (north to south) from equator +90 up then -90 down (equator again) = 180 then 180 for south = 360 total
|
60
|
+
# longitude (west to east) east +180, west -180 = 360 total
|
61
|
+
def to_deg radians
|
62
|
+
radians * 180 / Math::PI
|
63
|
+
end
|
64
|
+
|
65
|
+
alias_method :to_degrees, :to_deg
|
66
|
+
alias_method :as_deg, :to_deg
|
67
|
+
alias_method :as_degrees, :to_deg
|
68
|
+
alias_method :in_deg, :to_deg
|
69
|
+
alias_method :in_degrees, :to_deg
|
70
|
+
|
71
|
+
extend self
|
72
|
+
end
|
73
|
+
|
74
|
+
# all degrees between -180 and 180
|
75
|
+
def normalize_lng deg
|
76
|
+
case deg
|
77
|
+
when -360..-180
|
78
|
+
deg % 180
|
79
|
+
when -180..0
|
80
|
+
-180 + (deg % 180)
|
81
|
+
when 0..180
|
82
|
+
deg
|
83
|
+
when 180..360
|
84
|
+
deg % 180
|
85
|
+
else
|
86
|
+
raise ArgumentError, "Degrees #{deg} out of range, must be between -360 to 360"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# all degrees between -90 and 90
|
91
|
+
def normalize_lat deg
|
92
|
+
case deg
|
93
|
+
when -360..-270
|
94
|
+
deg % 90
|
95
|
+
when -270..-180
|
96
|
+
90 - (deg % 90)
|
97
|
+
when -180..-90
|
98
|
+
- (deg % 90)
|
99
|
+
when -90..0
|
100
|
+
-90 + (deg % 90)
|
101
|
+
when 0..90
|
102
|
+
deg
|
103
|
+
when 90..180
|
104
|
+
deg % 90
|
105
|
+
when 180..270
|
106
|
+
- (deg % 90)
|
107
|
+
when 270..360
|
108
|
+
- 90 + (deg % 90)
|
109
|
+
else
|
110
|
+
raise ArgumentError, "Degrees #{deg} out of range, must be between -360 to 360"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def normalize_deg degrees, shift = 0
|
115
|
+
(degrees + shift) % 360
|
116
|
+
end
|
117
|
+
alias_method :normalize_degrees, :normalize_deg
|
118
|
+
|
119
|
+
extend self
|
120
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'sugar-high/numeric'
|
2
|
+
require 'sugar-high/string'
|
3
|
+
|
4
|
+
module GeoUnits
|
5
|
+
module DmsConverter
|
6
|
+
include NumericCheckExt
|
7
|
+
|
8
|
+
def parse_dms dms_str
|
9
|
+
# check for signed decimal degrees without NSEW, if so return it directly
|
10
|
+
return dms_str if is_numeric?(dms_str)
|
11
|
+
|
12
|
+
# strip off any sign or compass dir'n & split out separate d/m/s
|
13
|
+
dms = dms_str.strip.gsub(/^-/,'').gsub(/[NSEW]$/i,'').split(/[^0-9.,]+/).map(&:strip).map(&:to_f)
|
14
|
+
return nil if dms.empty?
|
15
|
+
|
16
|
+
# and convert to decimal degrees...
|
17
|
+
deg = case dms.length
|
18
|
+
when 3 # interpret 3-part result as d/m/s
|
19
|
+
dms[0]/1 + dms[1]/60 + dms[2]/3600
|
20
|
+
when 2 # interpret 2-part result as d/m
|
21
|
+
dms[0]/1 + dms[1]/60
|
22
|
+
when 1 # just d (possibly decimal) or non-separated dddmmss
|
23
|
+
d = dms[0];
|
24
|
+
# check for fixed-width unseparated format eg 0033709W
|
25
|
+
d = "0#{d}" if (/[NS]/i.match(dms_str)) # - normalise N/S to 3-digit degrees
|
26
|
+
d = "#{d.slice(0,3)/1}#{deg.slice(3,5)/60}#{deg.slice(5)/3600}" if (/[0-9]{7}/.match(deg))
|
27
|
+
d
|
28
|
+
else
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
return nil if !deg
|
32
|
+
deg = (deg * -1) if (/^-|[WS]$/i.match(dms_str.strip)) # take '-', west and south as -ve
|
33
|
+
deg.to_f
|
34
|
+
end
|
35
|
+
|
36
|
+
# Convert decimal degrees to deg/min/sec format
|
37
|
+
# - degree, prime, double-prime symbols are added, but sign is discarded, though no compass
|
38
|
+
# direction is added
|
39
|
+
#
|
40
|
+
#
|
41
|
+
# @param {Number} deg: Degrees
|
42
|
+
# @param {String} [format=dms]: Return value as 'd', 'dm', 'dms'
|
43
|
+
# @param {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
|
44
|
+
# @returns {String} deg formatted as deg/min/secs according to specified format
|
45
|
+
# @throws {TypeError} deg is an object, perhaps DOM object without .value?
|
46
|
+
|
47
|
+
def to_dms deg, format = :dms, dp = nil
|
48
|
+
deg = begin
|
49
|
+
deg.to_f
|
50
|
+
rescue
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
return nil if !deg # give up here if we can't make a number from deg
|
54
|
+
|
55
|
+
# default values
|
56
|
+
format ||= :dms
|
57
|
+
dp = if dp.nil?
|
58
|
+
case format.to_sym
|
59
|
+
when :d
|
60
|
+
4
|
61
|
+
when :dm
|
62
|
+
2
|
63
|
+
else
|
64
|
+
0 # default
|
65
|
+
end
|
66
|
+
end
|
67
|
+
dp ||= 0
|
68
|
+
|
69
|
+
deg = deg.abs # (unsigned result ready for appending compass dir'n)
|
70
|
+
|
71
|
+
case format
|
72
|
+
when :d
|
73
|
+
d = deg.round(dp) # round degrees
|
74
|
+
ds = "0#{d}" if (d <100) # pad with leading zeros
|
75
|
+
ds = "0#{ds}" if (d <10)
|
76
|
+
dms = ds.to_s.concat("\u00B0") # add º symbol
|
77
|
+
when :dm
|
78
|
+
min = (deg*60).round(dp) # convert degrees to minutes & round
|
79
|
+
d = d.to_i
|
80
|
+
d = (min / 60).floor # get component deg/min
|
81
|
+
m = (min % 60).round(dp) # pad with trailing zeros
|
82
|
+
ds = d
|
83
|
+
ms = m
|
84
|
+
ds = "0#{d}" if (d<100) # pad with leading zeros
|
85
|
+
ds = "0#{d}" if (d<10)
|
86
|
+
ms = "0#{m}" if (m<10)
|
87
|
+
dms = ds.to_s.concat("\u00B0", ms, "\u2032") # add º, ' symbols
|
88
|
+
when :dms
|
89
|
+
sec = (deg * 3600).round # convert degrees to seconds & round
|
90
|
+
d = (sec / 3600).floor # get component deg/min/sec
|
91
|
+
m = ((sec / 60) % 60).floor
|
92
|
+
s = (sec % 60).round(dp) # pad with trailing zeros
|
93
|
+
ds = d
|
94
|
+
ms = m
|
95
|
+
ss = s
|
96
|
+
ds = "0#{d}" if (d < 100) # pad with leading zeros
|
97
|
+
ds = "0#{ds}" if (d < 10)
|
98
|
+
ms = "0#{m}" if (m < 10)
|
99
|
+
ss = "0#{s}" if (s < 10)
|
100
|
+
dms = ds.to_s.concat("\u00B0", ms, "\u2032", ss, "\u2033") # add º, ', " symbols
|
101
|
+
end
|
102
|
+
return dms
|
103
|
+
end
|
104
|
+
|
105
|
+
extend self
|
106
|
+
end
|
107
|
+
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::DmsConverter.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,101 @@
|
|
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
|
+
require 'sugar-high/numeric'
|
19
|
+
|
20
|
+
module GeoUnits
|
21
|
+
autoload :Converter, 'geo_units/converter'
|
22
|
+
autoload :DmsConverter, 'geo_units/dms_converter'
|
23
|
+
autoload :NumericExt, 'geo_units/numeric_ext'
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
def key unit = :km
|
27
|
+
unit = unit.to_sym
|
28
|
+
methods.grep(/_unit/).each do |meth|
|
29
|
+
return meth.to_s.chomp('_unit').to_sym if send(meth).include? unit
|
30
|
+
end
|
31
|
+
raise ArgumentError, "Unknown unit key: #{unit}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def precision
|
35
|
+
{
|
36
|
+
:feet => 0,
|
37
|
+
:meters => 2,
|
38
|
+
:kms => 4,
|
39
|
+
:miles => 4,
|
40
|
+
:radians => 4
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
# from mongoid-geo, as suggested by niedhui :)
|
45
|
+
def radian_multiplier
|
46
|
+
{
|
47
|
+
:feet => 364491.8,
|
48
|
+
:meters => 111170,
|
49
|
+
:kms => 111.17,
|
50
|
+
:miles => 69.407,
|
51
|
+
:radians => 1
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def meters_multiplier
|
56
|
+
{
|
57
|
+
:feet => 0.305,
|
58
|
+
:meters => 1,
|
59
|
+
:kms => 6371,
|
60
|
+
:miles => 3959
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def meters_map
|
65
|
+
{
|
66
|
+
:feet => 3.2808,
|
67
|
+
:meters => 1,
|
68
|
+
:kms => 0.001,
|
69
|
+
:miles => 0.00062137,
|
70
|
+
:radians => 111170
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def feet_unit
|
77
|
+
[:ft, :feet, :foot]
|
78
|
+
end
|
79
|
+
|
80
|
+
def meters_unit
|
81
|
+
[:m, :meter, :meters]
|
82
|
+
end
|
83
|
+
|
84
|
+
def kms_unit
|
85
|
+
[:km, :kms, :kilometer, :kilometers]
|
86
|
+
end
|
87
|
+
|
88
|
+
def miles_unit
|
89
|
+
[:mil, :mile, :miles]
|
90
|
+
end
|
91
|
+
|
92
|
+
def radians_unit
|
93
|
+
[:rad, :radians]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
extend ClassMethods
|
98
|
+
end
|
99
|
+
|
100
|
+
require 'geo_units/core_ext'
|
101
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Converter
|
4
|
+
include GeoUnits::Converter
|
5
|
+
end
|
6
|
+
|
7
|
+
def converter
|
8
|
+
Converter.new
|
9
|
+
end
|
10
|
+
|
11
|
+
# - www.movable-type.co.uk/scripts/latlong.html
|
12
|
+
describe GeoUnits::Converter do
|
13
|
+
# deg, format, dp
|
14
|
+
describe '#to_lat' do
|
15
|
+
it 'should convert 58.3 to a latitude String in North direction' do
|
16
|
+
str_lat = converter.to_lat(58.3)
|
17
|
+
str_lat.should be_a(String)
|
18
|
+
expr = Regexp.escape "58".concat("\u00B0", "18", "\u2032", "00", "\u2033", "N")
|
19
|
+
str_lat.should match expr
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should convert -58.3 to a latitude String in South direction' do
|
23
|
+
str_lat = converter.to_lat(-58.3)
|
24
|
+
str_lat.should be_a(String)
|
25
|
+
expr = Regexp.escape "58".concat("\u00B0", "18", "\u2032", "00", "\u2033", "S")
|
26
|
+
str_lat.should match expr
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# deg, format, dp
|
31
|
+
describe '#to_lon' do
|
32
|
+
it 'should convert 58.3 to a longitude String' do
|
33
|
+
str_lat = converter.to_lon(58.3)
|
34
|
+
str_lat.should be_a(String)
|
35
|
+
expr = Regexp.escape "58".concat("\u00B0", "18", "\u2032", "00", "\u2033", "E")
|
36
|
+
str_lat.should match expr
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should convert 58.3 to a longitude String' do
|
40
|
+
str_lat = converter.to_lon(-58.3)
|
41
|
+
str_lat.should be_a(String)
|
42
|
+
expr = Regexp.escape "58".concat("\u00B0", "18", "\u2032", "00", "\u2033", "W")
|
43
|
+
str_lat.should match expr
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Convert numeric degrees to deg/min/sec as a bearing (0º..360º)
|
48
|
+
# deg, format, dp
|
49
|
+
describe '#to_brng' do
|
50
|
+
it 'should convert 58.3 to a longitude String' do
|
51
|
+
brng = converter.to_brng(-58.3)
|
52
|
+
brng.to_f.should be_between(0, 360)
|
53
|
+
expr = Regexp.escape "301".concat("\u00B0", "42", "\u2032", "00")
|
54
|
+
brng.should match expr
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Parser
|
4
|
+
include GeoUnits::DmsConverter
|
5
|
+
end
|
6
|
+
|
7
|
+
def parser
|
8
|
+
Parser.new
|
9
|
+
end
|
10
|
+
|
11
|
+
# - www.movable-type.co.uk/scripts/latlong.html
|
12
|
+
describe GeoUnits::DmsConverter 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
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: geo_units
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kristian Mandrup
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-06-13 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: sugar-high
|
16
|
+
requirement: &2154936400 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.4.6.2
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2154936400
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &2154935800 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.5.0
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2154935800
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: bundler
|
38
|
+
requirement: &2154935220 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.0.6
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2154935220
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: jeweler
|
49
|
+
requirement: &2154934640 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.6.2
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *2154934640
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rcov
|
60
|
+
requirement: &2154934040 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *2154934040
|
69
|
+
description: Easily convert between different distance units such as kms, miles etc.
|
70
|
+
email: kmandrup@gmail.com
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files:
|
74
|
+
- LICENSE.txt
|
75
|
+
- README.textile
|
76
|
+
files:
|
77
|
+
- .document
|
78
|
+
- .rspec
|
79
|
+
- Gemfile
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.textile
|
82
|
+
- Rakefile
|
83
|
+
- VERSION
|
84
|
+
- geo_units.gemspec
|
85
|
+
- lib/geo_units.rb
|
86
|
+
- lib/geo_units/converter.rb
|
87
|
+
- lib/geo_units/core_ext.rb
|
88
|
+
- lib/geo_units/dms_converter.rb
|
89
|
+
- lib/geo_units/numeric_ext.rb
|
90
|
+
- spec/geo_units/converter_spec.rb
|
91
|
+
- spec/geo_units/dms_converter_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
homepage: http://github.com/kristianmandrup/geo_units
|
94
|
+
licenses:
|
95
|
+
- MIT
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
hash: 2458043336653105499
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ! '>='
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
requirements: []
|
116
|
+
rubyforge_project:
|
117
|
+
rubygems_version: 1.8.5
|
118
|
+
signing_key:
|
119
|
+
specification_version: 3
|
120
|
+
summary: Distance unit modules and functionality for use in geo libraries
|
121
|
+
test_files: []
|