geo_position 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  A simple utility to allow you to convert DMS (Degrees, Minutes, Seconds)
4
4
  to a latitude and longitude. The initial driver for this utility was
5
5
  working on an application that took Geo Position information from EXIF
6
- data.
6
+ data and converted it to lat/lon for use in both the database storage
7
+ and querying Google's API.
7
8
 
8
9
  ## Installation
9
10
 
@@ -21,7 +22,11 @@ Or install it yourself as:
21
22
 
22
23
  ## Usage
23
24
 
24
- Usage is very simple right now:
25
+ I prefer smaller components that can be put together in different ways.
26
+
27
+ Usage is simple:
28
+
29
+ ### Convert from Degrees, Minutes, Seconds, and Direction to a float
25
30
 
26
31
  ```ruby
27
32
  # This can be any positive number between 0 and 360. It will be coerced
@@ -47,12 +52,52 @@ conversion.to_f
47
52
  => -12.061783333333333
48
53
  ```
49
54
 
55
+ ### Convert from Latitude to DMS
56
+ ```ruby
57
+ latitude = "70.4947"
58
+
59
+ conversion = GeoPosition::Conversion::Latitude.new(latitude)
60
+
61
+ conversion.to_s
62
+ => "70 deg 29' 40\" N"
63
+ ```
64
+
65
+ ### Convert from Longitude to DMS
66
+ ```ruby
67
+ longitude = "-157.441"
68
+
69
+ conversion = GeoPosition::Conversion::Longitude.new(longitude)
70
+
71
+ conversion.to_s
72
+ => "157 deg 26' 27\" W"
73
+ ```
74
+
75
+ ### Parsing from a DMS string
76
+ ```ruby
77
+ dms_string = "77 deg 8 42.00 W"
78
+
79
+ parser = GeoPosition::Parser::Dms.new(dms_string)
80
+
81
+ parser.degrees
82
+ => 77
83
+
84
+ parser.minutes
85
+ => 8
86
+
87
+ parser.seconds
88
+ => 42.0
89
+
90
+ parser.direction
91
+ => "W"
92
+
93
+ parser.to_hash
94
+ => {:degrees=>77, :minutes=>8, :seconds=>42.0, :direction=>"W"}
95
+ ```
96
+
97
+
98
+
50
99
  ## Todo
51
100
 
52
- * Add ability to parse from the EXIF string and extract into the
53
- conversion object
54
- * Add ability to convert from lat/lon to DMS
55
- * Add simple methods in the root object to handle conversions
56
101
  * Add a command line component
57
102
 
58
103
  ## Contributing
@@ -13,12 +13,12 @@ module GeoPosition
13
13
  # => -12.061783333333333
14
14
  #
15
15
  class Dms
16
- ALLOWED_DEGREES = (0.0..360.0)
16
+ ALLOWED_SECONDS = (0.0..60.0)
17
+ ALLOWED_DEGREES = (0.0..180.0)
17
18
  ALLOWED_DIRECTIONS = %w( N n E e S s W w )
18
19
  MINUTES_CONVERSION = 60
19
20
  SECONDS_CONVERSION = 3600
20
21
 
21
-
22
22
  # Creates a new instance of the DMS conversion object
23
23
  #
24
24
  # @param degrees [String,Integer]
@@ -31,8 +31,10 @@ module GeoPosition
31
31
  raise GeoPosition::Error::InvalidDirectionError.new("Please provided a direction of N, S, E, or W") unless valid_direction?(direction)
32
32
  raise GeoPosition::Error::InvalidFloatError.new("Arguments could not be coerced to a float") unless valid_floats?([degrees, minutes, seconds])
33
33
  raise GeoPosition::Error::InvalidDegreesError.new("Degrees must be between 0 and 360. %s was provided" % [degrees]) unless valid_degrees?(degrees)
34
+ raise GeoPosition::Error::InvalidMinutesError.new("Minutes must be between 0 and 60. %s was provided" % [minutes]) unless valid_minutes?(minutes)
35
+ raise GeoPosition::Error::InvalidSecondsError.new("Seonds must be between 0 and 60. %s was provided" % [seconds]) unless valid_seconds?(seconds)
34
36
 
35
- @degrees = degrees # Can only be between 0 and 360
37
+ @degrees = degrees
36
38
  @minutes = minutes
37
39
  @seconds = seconds
38
40
 
@@ -94,9 +96,17 @@ module GeoPosition
94
96
  ALLOWED_DEGREES.include?(deg.to_f.abs)
95
97
  end
96
98
 
99
+ def valid_minutes?(min)
100
+ ALLOWED_SECONDS.include?(min.to_f.abs)
101
+ end
102
+
103
+ def valid_seconds?(sec)
104
+ ALLOWED_SECONDS.include?(sec.to_f.abs)
105
+ end
106
+
97
107
  def convert!
98
108
  result = (self.degrees + ((self.minutes/MINUTES_CONVERSION) + (self.seconds/SECONDS_CONVERSION)))
99
- if negative? then -(result) else result end
109
+ if negative? then (result * -1) else result end
100
110
  end
101
111
 
102
112
  def negative?
@@ -0,0 +1,62 @@
1
+ module GeoPosition
2
+ module Conversion
3
+ class Latitude
4
+ BOUNDS = (-90.0..90.0)
5
+
6
+ def initialize(latitude)
7
+ raise GeoPosition::Error::InvalidFloatError.new("Arguments could not be coerced to a float") unless valid_float?(latitude)
8
+ raise GeoPosition::Error::InvalidLatitudeError.new("Latitde must be between -90 and 90 degrees: %s provided" % [latitude]) unless within_bounds?(latitude)
9
+
10
+ @latitude = latitude.to_f
11
+
12
+ @degrees = 0
13
+ @minutes = 0
14
+ @seconds = 0
15
+
16
+ convert!
17
+ end
18
+
19
+ def degrees
20
+ @degrees
21
+ end
22
+
23
+ def minutes
24
+ @minutes
25
+ end
26
+
27
+ def seconds
28
+ @seconds
29
+ end
30
+
31
+ def direction
32
+ determine_direction
33
+ end
34
+
35
+ def to_s
36
+ "%s deg %s' %s\" %s" % [self.degrees, self.minutes, self.seconds, self.direction]
37
+ end
38
+
39
+ private
40
+ def within_bounds?(lat)
41
+ BOUNDS.include?(lat.to_f)
42
+ end
43
+
44
+ def valid_float?(lat)
45
+ lat.respond_to?(:to_f)
46
+ end
47
+
48
+ def convert!
49
+ decimal = @latitude.abs
50
+ @degrees = decimal.floor
51
+
52
+ initial_seconds = (decimal - @degrees) * 3600
53
+ @minutes = initial_seconds.floor / 60
54
+ @seconds = (initial_seconds - (@minutes * 60)).floor
55
+ end
56
+
57
+ def determine_direction
58
+ if @latitude < 0 then 'S' else 'N' end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,62 @@
1
+ module GeoPosition
2
+ module Conversion
3
+ class Longitude
4
+ BOUNDS = (-180.0..180.0)
5
+
6
+ def initialize(longitude)
7
+ raise GeoPosition::Error::InvalidFloatError.new("Arguments could not be coerced to a float") unless valid_float?(longitude)
8
+ raise GeoPosition::Error::InvalidLongitudeError.new("Latitde must be between -180 and 180 degrees: %s provided" % [longitude]) unless within_bounds?(longitude)
9
+
10
+ @longitude = longitude.to_f
11
+
12
+ @degrees = 0
13
+ @minutes = 0
14
+ @seconds = 0
15
+
16
+ convert!
17
+ end
18
+
19
+ def degrees
20
+ @degrees
21
+ end
22
+
23
+ def minutes
24
+ @minutes
25
+ end
26
+
27
+ def seconds
28
+ @seconds
29
+ end
30
+
31
+ def direction
32
+ determine_direction
33
+ end
34
+
35
+ def to_s
36
+ "%s deg %s' %s\" %s" % [self.degrees, self.minutes, self.seconds, self.direction]
37
+ end
38
+
39
+ private
40
+ def within_bounds?(lat)
41
+ BOUNDS.include?(lat.to_f)
42
+ end
43
+
44
+ def valid_float?(lat)
45
+ lat.respond_to?(:to_f)
46
+ end
47
+
48
+ def convert!
49
+ decimal = @longitude.abs
50
+ @degrees = decimal.floor
51
+
52
+ initial_seconds = (decimal - @degrees) * 3600
53
+ @minutes = initial_seconds.floor / 60
54
+ @seconds = (initial_seconds - (@minutes * 60)).floor
55
+ end
56
+
57
+ def determine_direction
58
+ if @longitude < 0 then 'W' else 'E' end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,6 @@
1
+ module GeoPosition
2
+ module Error
3
+ class InvalidDmsStringError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module GeoPosition
2
+ module Error
3
+ class InvalidLatitudeError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module GeoPosition
2
+ module Error
3
+ class InvalidLongitudeError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module GeoPosition
2
+ module Error
3
+ class InvalidMinutesError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module GeoPosition
2
+ module Error
3
+ class InvalidSecondsError < StandardError
4
+ end
5
+ end
6
+ end
@@ -3,5 +3,11 @@ module GeoPosition
3
3
  autoload :InvalidDirectionError, File.join(File.dirname(__FILE__), 'error/invalid_direction_error')
4
4
  autoload :InvalidFloatError, File.join(File.dirname(__FILE__), 'error/invalid_float_error')
5
5
  autoload :InvalidDegreesError, File.join(File.dirname(__FILE__), 'error/invalid_degrees_error')
6
+ autoload :InvalidMinutesError, File.join(File.dirname(__FILE__), 'error/invalid_minutes_error')
7
+ autoload :InvalidSecondsError, File.join(File.dirname(__FILE__), 'error/invalid_seconds_error')
8
+
9
+ autoload :InvalidDmsStringError, File.join(File.dirname(__FILE__), 'error/invalid_dms_string_error')
10
+ autoload :InvalidLatitudeError, File.join(File.dirname(__FILE__), 'error/invalid_latitude_error')
11
+ autoload :InvalidLongitudeError, File.join(File.dirname(__FILE__), 'error/invalid_longitude_error')
6
12
  end
7
13
  end
@@ -0,0 +1,49 @@
1
+ module GeoPosition
2
+ module Parser
3
+ class Dms
4
+ FORMAT_REGEX = Regexp.new(/^(?<degrees>[\d]+)\s{1}[^\d]*(?<minutes>[\d]+)\s{1}(?<seconds>[\d]+(\.?[\d]+))\s{1}(?<direction>[nNsSeEwW]{1})/)
5
+
6
+ def initialize(dms_string)
7
+ sanitize_string!(dms_string)
8
+
9
+ raise GeoPosition::Error::InvalidDmsStringError.new('String could not be parsed') unless valid_string?(dms_string)
10
+
11
+ @dms_string = dms_string.to_s
12
+ end
13
+
14
+ def degrees
15
+ parsed[:degrees].to_i
16
+ end
17
+
18
+ def minutes
19
+ parsed[:minutes].to_i
20
+ end
21
+
22
+ def seconds
23
+ parsed[:seconds].to_f
24
+ end
25
+
26
+ def direction
27
+ parsed[:direction].upcase
28
+ end
29
+
30
+ def to_hash
31
+ keys = [:degrees, :minutes, :seconds, :direction]
32
+ keys.inject({}) { |hsh, key| hsh[key] = self.send(key); hsh }
33
+ end
34
+
35
+ private
36
+ def valid_string?(str)
37
+ FORMAT_REGEX.match(str.to_s)
38
+ end
39
+
40
+ def sanitize_string!(str)
41
+ str.gsub!(/['"]/, '')
42
+ end
43
+
44
+ def parsed
45
+ @dms_string.match(FORMAT_REGEX)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,3 +1,3 @@
1
1
  module GeoPosition
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.5"
3
3
  end
data/lib/geo_position.rb CHANGED
@@ -9,9 +9,26 @@ require File.join(root, 'geo_position', 'error')
9
9
  # Conversions
10
10
  require File.join(root, 'geo_position', 'conversion')
11
11
  require File.join(root, 'geo_position', 'conversion', 'dms')
12
+ require File.join(root, 'geo_position', 'conversion', 'latitude')
13
+ require File.join(root, 'geo_position', 'conversion', 'longitude')
12
14
 
13
15
  # Parsers
14
16
  require File.join(root, 'geo_position', 'parser')
17
+ require File.join(root, 'geo_position', 'parser', 'dms')
15
18
 
16
19
  module GeoPosition
20
+ def self.from_dms(degrees, minutes, seconds, direction)
21
+ conversion = GeoPosition::Conversion::Dms.new(degrees, minutes, seconds, direction)
22
+ conversion.to_f
23
+ end
24
+
25
+ def self.from_latitude(latitude)
26
+ conversion = GeoPosition::Conversion::Latitude.new(latitude)
27
+ conversion.to_s
28
+ end
29
+
30
+ def self.from_longitude(longitude)
31
+ conversion = GeoPosition::Conversion::Longitude.new(longitude)
32
+ conversion.to_s
33
+ end
17
34
  end
@@ -20,8 +20,16 @@ describe GeoPosition::Conversion::Dms do
20
20
  lambda { described_class.new([12], [3], [42.42], 'w') }.should raise_error(GeoPosition::Error::InvalidFloatError)
21
21
  end
22
22
 
23
- it "raises an exception if degrees are greater than 360" do
24
- lambda{ described_class.new(361, 12, 123, 'n') }.should raise_error(GeoPosition::Error::InvalidDegreesError)
23
+ it "raises an exception if degrees are greater than 180" do
24
+ lambda{ described_class.new(181, 12, 123, 'n') }.should raise_error(GeoPosition::Error::InvalidDegreesError)
25
+ end
26
+
27
+ it "raises an exception if minutes are greater than 60" do
28
+ lambda { described_class.new(12, 61, 12, 'n') }.should raise_error(GeoPosition::Error::InvalidMinutesError)
29
+ end
30
+
31
+ it "raises an exception if seconds are greater than 60" do
32
+ lambda { described_class.new(12, 30, 61, 'n') }.should raise_error(GeoPosition::Error::InvalidSecondsError)
25
33
  end
26
34
  end
27
35
 
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe GeoPosition::Conversion::Latitude do
4
+ let(:latitude) { '70.4947' }
5
+ subject { described_class.new(latitude) }
6
+
7
+ context('Error Handling') do
8
+ it "raises an exception if latitude is greater than 90" do
9
+ lambda { described_class.new(91) }.should raise_error(GeoPosition::Error::InvalidLatitudeError)
10
+ end
11
+
12
+ it "raises an exception if latitude is greater than -90" do
13
+ lambda { described_class.new(-91) }.should raise_error(GeoPosition::Error::InvalidLatitudeError)
14
+ end
15
+
16
+ it "raises an exception if latitude cannot be coerced to a float" do
17
+ lambda { described_class.new([12]) }.should raise_error(GeoPosition::Error::InvalidFloatError)
18
+ end
19
+ end
20
+
21
+ it "responds to #to_s" do
22
+ subject.should respond_to(:to_s)
23
+ end
24
+
25
+ it "returns 'N' for the direction" do
26
+ subject.direction.should == 'N'
27
+ end
28
+
29
+ it "returns 'S' for the direction" do
30
+ south = latitude.to_f * -1
31
+ lc = described_class.new(south)
32
+ lc.direction.should == 'S'
33
+ end
34
+
35
+ it "returns 70 degrees" do
36
+ subject.degrees.should eql(70)
37
+ end
38
+
39
+ it "returns 29 minutes" do
40
+ subject.minutes.should eql(29)
41
+ end
42
+
43
+ it "returns 40 seconds" do
44
+ subject.seconds.should eql(40)
45
+ end
46
+
47
+ it "returns the DMS string" do
48
+ subject.to_s.should == "70 deg 29' 40\" N"
49
+ end
50
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe GeoPosition::Conversion::Longitude do
4
+ let(:longitude) { '-157.441' }
5
+ subject { described_class.new(longitude) }
6
+
7
+ context('Error Handling') do
8
+ it "raises an exception if longitude is greater than 180" do
9
+ lambda { described_class.new(181) }.should raise_error(GeoPosition::Error::InvalidLongitudeError)
10
+ end
11
+
12
+ it "raises an exception if longitude is greater than -180" do
13
+ lambda { described_class.new(-181) }.should raise_error(GeoPosition::Error::InvalidLongitudeError)
14
+ end
15
+
16
+ it "raises an exception if longitude cannot be coerced to a float" do
17
+ lambda { described_class.new([12]) }.should raise_error(GeoPosition::Error::InvalidFloatError)
18
+ end
19
+ end
20
+
21
+ it "responds to #to_s" do
22
+ subject.should respond_to(:to_s)
23
+ end
24
+
25
+ it "returns 'W' for the direction" do
26
+ subject.direction.should == 'W'
27
+ end
28
+
29
+ it "returns 'E' for the direction" do
30
+ east = longitude.to_f * -1
31
+ lc = described_class.new(east)
32
+ lc.direction.should == 'E'
33
+ end
34
+
35
+ it "returns 157 degrees" do
36
+ subject.degrees.should eql(157)
37
+ end
38
+
39
+ it "returns 26 minutes" do
40
+ subject.minutes.should eql(26)
41
+ end
42
+
43
+ it "returns 27 seconds" do
44
+ subject.seconds.should eql(27)
45
+ end
46
+
47
+ it "returns the DMS string" do
48
+ subject.to_s.should == "157 deg 26' 27\" W"
49
+ end
50
+ end
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ describe GeoPosition::Parser::Dms do
4
+ let(:dms_string) { "40 deg 20' 13.20\" N" }
5
+ subject { described_class.new(dms_string) }
6
+
7
+ context('Error Handling') do
8
+ it "raises an exception if the string is not in an accepted format" do
9
+ lambda { described_class.new('invalid format') }.should raise_error(GeoPosition::Error::InvalidDmsStringError)
10
+ end
11
+ end
12
+
13
+ context("Simple format") do
14
+ let(:simple_string) { "12 12 42.42 n" }
15
+ subject { described_class.new(simple_string) }
16
+
17
+ it "returns 12 degrees" do
18
+ subject.degrees.should eql(12)
19
+ end
20
+
21
+ it "returns 12 minutes" do
22
+ subject.minutes.should eql(12)
23
+ end
24
+
25
+ it "returns 42.42 seconds" do
26
+ subject.seconds.should eql(42.42)
27
+ end
28
+
29
+ it "returns 'W' for the direction" do
30
+ subject.direction.should == "N"
31
+ end
32
+
33
+ it "serializes to a hash" do
34
+ expected = {
35
+ :degrees => 12,
36
+ :minutes => 12,
37
+ :seconds => 42.42,
38
+ :direction => 'N'
39
+ }
40
+ subject.to_hash.should == expected
41
+ end
42
+ end
43
+
44
+ context("With 'deg' in the name") do
45
+ it "returns 40 degrees" do
46
+ subject.degrees.should eql(40)
47
+ end
48
+
49
+ it "returns 20 minutes" do
50
+ subject.minutes.should eql(20)
51
+ end
52
+
53
+ it "returns 13.20 seconds" do
54
+ subject.seconds.should eql(13.20)
55
+ end
56
+
57
+ it "returns 'N' for the direction" do
58
+ subject.direction.should == "N"
59
+ end
60
+
61
+ it "serializes to a hash" do
62
+ expected = {
63
+ :degrees => 40,
64
+ :minutes => 20,
65
+ :seconds => 13.20,
66
+ :direction => 'N'
67
+ }
68
+ subject.to_hash.should == expected
69
+ end
70
+ end
71
+
72
+ it "responds to #degrees" do
73
+ subject.should respond_to(:degrees)
74
+ end
75
+
76
+ it "responds to #minutes" do
77
+ subject.should respond_to(:minutes)
78
+ end
79
+
80
+ it "responds to #seconds" do
81
+ subject.should respond_to(:seconds)
82
+ end
83
+
84
+ it "responds to #direction" do
85
+ subject.should respond_to(:direction)
86
+ end
87
+
88
+ it "responds to #to_hash" do
89
+ subject.should respond_to(:to_hash)
90
+ end
91
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: geo_position
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-03 00:00:00.000000000 Z
12
+ date: 2012-11-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -93,14 +93,25 @@ files:
93
93
  - lib/geo_position.rb
94
94
  - lib/geo_position/conversion.rb
95
95
  - lib/geo_position/conversion/dms.rb
96
+ - lib/geo_position/conversion/latitude.rb
97
+ - lib/geo_position/conversion/longitude.rb
96
98
  - lib/geo_position/error.rb
97
99
  - lib/geo_position/error/invalid_degrees_error.rb
98
100
  - lib/geo_position/error/invalid_direction_error.rb
101
+ - lib/geo_position/error/invalid_dms_string_error.rb
99
102
  - lib/geo_position/error/invalid_float_error.rb
103
+ - lib/geo_position/error/invalid_latitude_error.rb
104
+ - lib/geo_position/error/invalid_longitude_error.rb
105
+ - lib/geo_position/error/invalid_minutes_error.rb
106
+ - lib/geo_position/error/invalid_seconds_error.rb
100
107
  - lib/geo_position/parser.rb
108
+ - lib/geo_position/parser/dms.rb
101
109
  - lib/geo_position/version.rb
102
110
  - reload_yard
103
111
  - spec/conversion/dms_spec.rb
112
+ - spec/conversion/latitude_spec.rb
113
+ - spec/conversion/longitude_spec.rb
114
+ - spec/parser/dms_spec.rb
104
115
  - spec/spec_helper.rb
105
116
  homepage: https://github.com/archivability/geo_position
106
117
  licenses: []
@@ -116,7 +127,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
116
127
  version: '0'
117
128
  segments:
118
129
  - 0
119
- hash: -3480515325090187080
130
+ hash: 1503743246014824343
120
131
  required_rubygems_version: !ruby/object:Gem::Requirement
121
132
  none: false
122
133
  requirements:
@@ -125,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
136
  version: '0'
126
137
  segments:
127
138
  - 0
128
- hash: -3480515325090187080
139
+ hash: 1503743246014824343
129
140
  requirements: []
130
141
  rubyforge_project:
131
142
  rubygems_version: 1.8.24
@@ -134,5 +145,8 @@ specification_version: 3
134
145
  summary: Converting between Deciman/Hours/Seconds and Latitude/Longitude
135
146
  test_files:
136
147
  - spec/conversion/dms_spec.rb
148
+ - spec/conversion/latitude_spec.rb
149
+ - spec/conversion/longitude_spec.rb
150
+ - spec/parser/dms_spec.rb
137
151
  - spec/spec_helper.rb
138
152
  has_rdoc: