libgeo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 795971339fd7c0de9304b188ec33f51057cd4adb
4
+ data.tar.gz: 27003885f254776998b25dd67b6aa41d84c24270
5
+ SHA512:
6
+ metadata.gz: ab071931a81588e30804c7f008d77eb8bb49c9abeaae7b2e155a99a3e76aa6e12a97eedfa10bfc35a83582f45e8426ff7041267594bd400442c276b96aea0027
7
+ data.tar.gz: 9367c5f271715e96b56de6033d41b18ed918e0f874baeaa23cce61cbca2fb113d803e675d61c4135b385bf2b73f8299450fc8a72a76371632b6781501eb91126
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
@@ -0,0 +1,13 @@
1
+ bundler_args: --without dev
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
6
+ - rbx-2.2
7
+ - jruby-19mode
8
+
9
+ branches:
10
+ only:
11
+ - master
12
+
13
+ script: bundle exec rspec
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in libgeo.gemspec
4
+ gemspec
5
+
6
+ gem 'rspec', '~> 3.0.0.beta'
7
+
8
+ group :dev do
9
+ gem 'simplecov'
10
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Andrey Savchenko
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,60 @@
1
+ # Libgeo
2
+
3
+ Collection of geographical primitives
4
+
5
+ - [![Build Status](https://travis-ci.org/Ptico/libgeo.png?branch=master)](https://travis-ci.org/Ptico/libgeo)
6
+ - [![Code Climate](https://codeclimate.com/github/Ptico/libgeo.png)](https://codeclimate.com/github/Ptico/libgeo)
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'libgeo'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install libgeo
21
+
22
+ ## Usage
23
+
24
+ ### Latitude/Longitude
25
+
26
+ ```ruby
27
+ lng = Longitude.decimal(39.342679)
28
+
29
+ lng.hemisphere # => :E
30
+ lng.degrees # => 39
31
+ lng.minutes # => 20
32
+ lng.seconds # => 33.6444
33
+
34
+ lng.western? # => false
35
+ lng.eastern? # => true
36
+
37
+ lng.to_s # => '39°20′33.6444″E'
38
+ lng.to_nmea # => '03920.56074,E'
39
+
40
+ lng.western!
41
+ lng.hemisphere # => :W
42
+
43
+ ltt = Latitude.nmea('03920.56074,N')
44
+
45
+ ltt.to_s # => '39°20′33.6444″N'
46
+
47
+ ltt_dms = Latitude.dms('39°20′33.6444″N')
48
+
49
+ ltt_dms.to_s # => '39°20′33.6444″N'
50
+ ltt_dms.to_nmea # => '3920.56074,N'
51
+
52
+ ```
53
+
54
+ ## Contributing
55
+
56
+ 1. Fork it ( http://github.com/Ptico/libgeo/fork )
57
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
58
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
59
+ 4. Push to the branch (`git push origin my-new-feature`)
60
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ require 'set'
4
+
5
+ require 'libgeo/version'
6
+ require 'libgeo/formatter'
7
+
8
+ module Libgeo
9
+
10
+ NORTH = :N
11
+ SOUTH = :S
12
+ WEST = :W
13
+ EAST = :E
14
+
15
+ module Format
16
+ DMS = Formatter.new('%2d°%2m′%S″%H')
17
+ NMEA_LAT = Formatter.new('%2d%2M,%H')
18
+ NMEA_LON = Formatter.new('%3d%2M,%H')
19
+ end
20
+ end
21
+
22
+ require 'libgeo/coordinate'
23
+ require 'libgeo/latitude'
24
+ require 'libgeo/longitude'
@@ -0,0 +1,187 @@
1
+ # encoding: utf-8
2
+
3
+ require 'bigdecimal'
4
+ require 'bigdecimal/util'
5
+ require 'set'
6
+
7
+ require 'libgeo/coordinate/class_methods'
8
+ require 'libgeo/coordinate/presenters'
9
+ require 'libgeo/coordinate/hemi_helpers'
10
+
11
+ module Libgeo
12
+ ##
13
+ # Class: basic coordinate
14
+ #
15
+ # Provides basic functionality for Latitude and Longitude.
16
+ # In some cases can be used as standalone class.
17
+ #
18
+ class Coordinate
19
+ extend ClassMethods
20
+ include Presenters
21
+ include HemiHelpers
22
+
23
+ HEMISPHERES = Set.new([:N, :E, :S, :W]).freeze
24
+ NEGATIVE_HEMISPHERES = Set.new([:S, :W]).freeze
25
+
26
+ attr_accessor :degrees, :minutes, :seconds, :hemisphere
27
+
28
+ ##
29
+ # Shortcuts
30
+ alias :hemi :hemisphere
31
+ alias :deg :degrees
32
+ alias :mins :minutes
33
+ alias :secs :seconds
34
+
35
+ ##
36
+ # Coordinate type
37
+ #
38
+ def type
39
+ nil
40
+ end
41
+
42
+ ##
43
+ # Check coordinates equality
44
+ #
45
+ # Returns: {Boolean} true if coordinates are same
46
+ #
47
+ def eql?(other)
48
+ degrees.eql?(other.degrees) &&
49
+ minutes.eql?(other.minutes) &&
50
+ seconds.eql?(other.seconds) &&
51
+ hemisphere.eql?(other.hemisphere)
52
+ end
53
+ alias_method :==, :eql?
54
+
55
+ ##
56
+ # Decimal minutes with seconds included
57
+ #
58
+ # Returns: {Float} minutes
59
+ #
60
+ def minutes_only
61
+ (minutes + seconds.to_d / 60).to_f
62
+ end
63
+
64
+ ##
65
+ # Hemisphere validator
66
+ #
67
+ # Validates if given value can be assigned to Coordinate
68
+ # or if current hemi is valid
69
+ #
70
+ # Params:
71
+ # - value hemisphere value to check (optional)
72
+ #
73
+ # Returns: {Boolean}
74
+ #
75
+ def valid_hemisphere?(value=nil)
76
+ self.class::HEMISPHERES.include?(value || hemisphere)
77
+ end
78
+
79
+ ##
80
+ # Check if current hemisphere negative
81
+ #
82
+ # Returns: {Boolean}
83
+ #
84
+ def negative_hemisphere?
85
+ if valid_hemisphere?
86
+ NEGATIVE_HEMISPHERES.include?(hemi)
87
+ elsif direction
88
+ :< == direction
89
+ else
90
+ false
91
+ end
92
+ end
93
+
94
+ ##
95
+ # Freeze all the things!
96
+ #
97
+ def freeze
98
+ normalize_data
99
+
100
+ [@degrees, @minutes, @seconds, @hemisphere, @direction].each { |ivar| ivar.freeze }
101
+
102
+ super
103
+ end
104
+
105
+ private
106
+
107
+ attr_reader :direction
108
+
109
+ ##
110
+ # Constructor:
111
+ #
112
+ # Params:
113
+ # - hemi_or_dir {Symbol|String} hemisphere value or direction
114
+ # - degrees {Fixnum} degrees part
115
+ # - minutes {Fixnum} minutes part
116
+ # - seconds {Float} seconds part
117
+ #
118
+ def initialize(hemi_or_dir, degrees, minutes, seconds)
119
+ @degrees = degrees
120
+ @minutes = minutes
121
+ @seconds = seconds
122
+
123
+ if [:<, :>].include?(hemi_or_dir)
124
+ @direction = hemi_or_dir
125
+ else
126
+ @hemisphere = hemi_or_dir
127
+ end
128
+
129
+ normalize_data
130
+ end
131
+
132
+ ##
133
+ # Private: make sure that all attrs has right types
134
+ #
135
+ def normalize_data
136
+ normalize_hemi
137
+ normalize_values
138
+ end
139
+
140
+ ##
141
+ # Private: detect and assign hemisphere value
142
+ #
143
+ # Check if hemisphere can be properly detected
144
+ # and assign it
145
+ #
146
+ def normalize_hemi
147
+ if hemisphere
148
+ normalize_hemi_wording
149
+ elsif direction
150
+ get_hemi_from_direction
151
+ end
152
+ end
153
+
154
+ ##
155
+ # Private: normalize hemisphere value
156
+ #
157
+ # If hemisphere have incorrect value - lookup for
158
+ # available correction from dictionary
159
+ #
160
+ def normalize_hemi_wording
161
+ unless valid_hemisphere?
162
+ self.class::CORRECTIONS.each_pair do |v, c|
163
+ @hemisphere = v if c.include?(hemisphere)
164
+ end
165
+ end
166
+ end
167
+
168
+ ##
169
+ # Private: get hemisphere value from direction
170
+ #
171
+ # Mostly for plain Coordinate instance (not Latitude or Longitude)
172
+ #
173
+ def get_hemi_from_direction
174
+ @hemisphere = self.class::DIRECTIONS[direction]
175
+ end
176
+
177
+ ##
178
+ # Private: normalize attribute types
179
+ #
180
+ def normalize_values
181
+ @degrees = degrees.to_i
182
+ @minutes = minutes.to_i
183
+ @seconds = seconds.to_f
184
+ end
185
+
186
+ end
187
+ end
@@ -0,0 +1,243 @@
1
+ # encoding: utf-8
2
+
3
+ module Libgeo
4
+ class Coordinate
5
+ module ClassMethods
6
+
7
+ DMS_SEPARATORS = /['°′"`\ \,]+/.freeze
8
+ NMEA_SEPARATORS = /(,\ |,|\ )/.freeze
9
+
10
+ ##
11
+ # Factory: make a coordinate from decimal value
12
+ #
13
+ # Examples:
14
+ #
15
+ # Longitude.decimal(39.342679) # => #<Longitude hemisphere=E degrees=39 minutes=20 ...
16
+ #
17
+ # Params:
18
+ # - value {Float} decimal coordinate
19
+ #
20
+ # Returns: {Latitude|Longitude|Coordinate} instance
21
+ #
22
+ def decimal(value)
23
+ minutes = value.abs.to_d.modulo(1) * 60 # extract minutes from decimal degrees
24
+ create(dir_from(value), value.abs.to_i, *min_with_sec(minutes))
25
+ end
26
+
27
+ ##
28
+ # Factory: make a coordinate from nmea input
29
+ #
30
+ # Examples:
31
+ #
32
+ # Longitude.nmea('03920.56074,E') # => #<Longitude hemisphere=E degrees=39 minutes=20 ...
33
+ #
34
+ # Params:
35
+ # - input {String} nmea coordinate
36
+ #
37
+ # Returns: {Latitude|Longitude|Coordinate} instance
38
+ #
39
+ def nmea(input)
40
+ value, hemi = prepare_nmea(input)
41
+
42
+ value = value.to_f
43
+
44
+ direction = dir_from_values(value, hemi)
45
+
46
+ degrees = value.to_i.abs / 100
47
+
48
+ create(direction, degrees, *min_with_sec_nmea(value.abs), hemi)
49
+ end
50
+
51
+ ##
52
+ # Factory: make a coordinate from dms value
53
+ #
54
+ # Examples:
55
+ #
56
+ # Longitude.dms("58°39′13.5 S") # => #<Longitude hemisphere=S degrees=58 minutes=39 ...
57
+ #
58
+ # Params:
59
+ # - inputs {String} dms coordinate
60
+ #
61
+ # Returns: {Latitude|Longitude|Coordinate} instance
62
+ #
63
+ def dms(input)
64
+ string_values = input.split(DMS_SEPARATORS)
65
+
66
+ degrees = string_values[0].to_i # get degrees and minutes
67
+ minutes = (string_values[1] || 0).to_i
68
+ seconds = (string_values[2] || 0).to_f
69
+ hemi = string_values[3]
70
+
71
+ direction = dir_from_values(degrees, hemi)
72
+
73
+ create(direction, degrees.abs, minutes, seconds, hemi)
74
+ end
75
+
76
+ ##
77
+ # Factory: make a coordinate from degrees and full minutes
78
+ #
79
+ # Params:
80
+ # - degrees {Fixnum} degrees part
81
+ # - minutes {Float} full minutes, with seconds
82
+ #
83
+ # Examples:
84
+ #
85
+ # Latitude.degrees_minutes(-39, 20.56074) # => #<Latitude hemisphere=S degrees=39 minutes=20 ...
86
+ #
87
+ # Returns: {Latitude|Longitude|Coordinate} instance
88
+ #
89
+ def degrees_minutes(degrees, minutes)
90
+ create(dir_from(degrees), degrees.abs.to_i, *min_with_sec(minutes))
91
+ end
92
+
93
+ private
94
+
95
+ ##
96
+ # Private: extract splited values from nmea notation
97
+ #
98
+ # Params:
99
+ # - input {String} input string in nmea notation
100
+ #
101
+ # Examples:
102
+ #
103
+ # input # => "03922.54, E"
104
+ # prepare_nmea(input) # => ["03922.54", "E"]
105
+ #
106
+ # input2 # => "+03922.54"
107
+ # prepare_nmea(input) # => ["+03922.54"]
108
+ #
109
+ # Returns: [{String}, {String}] numbers and hemisphere
110
+ #
111
+ def prepare_nmea(input)
112
+ splited_values = input.split(NMEA_SEPARATORS)
113
+
114
+ splited_values.delete_if { |str| str =~ NMEA_SEPARATORS }
115
+ end
116
+
117
+ ##
118
+ # Private: extract integer minutes and float seconds from float minutes
119
+ #
120
+ # Params:
121
+ # - minutes {Float|Decimal} minutes
122
+ #
123
+ # Returns: [{Ineteger}, {Float}] rounded minutes and exracted seconds
124
+ #
125
+ def min_with_sec(minutes_f)
126
+ seconds = minutes_f.to_d.modulo(1) * 60
127
+
128
+ [minutes_f.to_i, seconds.to_f]
129
+ end
130
+
131
+ ##
132
+ # Private: extract minutes and seconds from nmea coord
133
+ #
134
+ # Params:
135
+ # - value {Float} nmea coordinate
136
+ #
137
+ # Returns: [{Ineteger}, {Float}] extracted minutes and seconds
138
+ #
139
+ def min_with_sec_nmea(value)
140
+ minutes = value.to_i % 100
141
+ seconds = (value.modulo(1) * 60).round(4)
142
+
143
+ [minutes, seconds]
144
+ end
145
+
146
+ ##
147
+ # Private: detect direction based on neg/pos
148
+ #
149
+ # Params:
150
+ # - degrees {Integer} degrees
151
+ #
152
+ # Examples:
153
+ #
154
+ # dir_from(-5) # => :<
155
+ # dir_from(10) # => :>
156
+ #
157
+ # Returns: {Symbol} symbol of direction
158
+ #
159
+ def dir_from(degrees)
160
+ degrees < 0 ? :< : :>
161
+ end
162
+
163
+ ##
164
+ # Private: detect direction from degrees of hemisphere
165
+ #
166
+ # Params:
167
+ # - numbers {Ineteger|Float|Decimal} numerical value of coordinate (degrees)
168
+ # - hemi {String|Symbol|nil} hemisphere if available
169
+ #
170
+ # Examples:
171
+ #
172
+ # dir_from_values(43, 'S') # => :<
173
+ # dir_from_values(43, nil) # => :>
174
+ #
175
+ # Returns: {Symbol} symbol of direction
176
+ #
177
+ def dir_from_values(numbers, hemi)
178
+ if hemi
179
+ (NEGATIVE_HEMISPHERES.include? hemi.to_sym) ? :< : :>
180
+ else
181
+ dir_from(numbers.to_i)
182
+ end
183
+ end
184
+
185
+ ##
186
+ # Private: fabric. Make a coordinate from given values
187
+ #
188
+ # Params:
189
+ # - direction {Symbol} symbol of hemisphere direction
190
+ # - degrees {Integer}
191
+ # - minutes {Integer}
192
+ # - second {Integer|Float}
193
+ # - hemi {Symbol|String} (optional) hemisphere
194
+ #
195
+ # Raises:
196
+ # - {ArgumentError} on wrong params values
197
+ #
198
+ # Returns: {Longitude|Latotude} created coordinate
199
+
200
+ def create(direction, degrees, minutes, seconds, hemi=nil)
201
+ validate_values(degrees, minutes, seconds, hemi)
202
+
203
+ self.new(direction, degrees, minutes, seconds)
204
+ end
205
+
206
+ ##
207
+ # Private: validates given values
208
+ #
209
+ # Params:
210
+ # - degrees {Ineteger}
211
+ # - minutes {Integer}
212
+ # - seconds {Ineteger|Float}
213
+ # - hemi {Symbol|String|nil} hemisphere
214
+ #
215
+ # Raises:
216
+ # - {ArgumentError} on wrong values
217
+ #
218
+ def validate_values(degrees, minutes, seconds, hemi)
219
+ if degrees > self::MAX_DEGREES || minutes > 60 || seconds > 60
220
+ raise ArgumentError.new('values out of range')
221
+ end
222
+
223
+ validate_hemisphere(hemi.to_sym) if hemi
224
+ end
225
+
226
+
227
+ ##
228
+ # Hemisphere validator
229
+ #
230
+ # Validates given hemisphere
231
+ #
232
+ # Params:
233
+ # - value {Symbol} - hemisphere
234
+ #
235
+ # Raises:
236
+ # - {ArgumentError} on wrong hemisphere
237
+ #
238
+ def validate_hemisphere(value)
239
+ raise ArgumentError.new('wrong hemisphere') unless HEMISPHERES.include?(value)
240
+ end
241
+ end
242
+ end
243
+ end