geo_position 0.0.3 → 0.0.5

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/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: