libgeo 0.1.0

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.
@@ -0,0 +1,80 @@
1
+ # encoding: utf-8
2
+
3
+ module Libgeo
4
+ class Coordinate
5
+ module HemiHelpers
6
+
7
+ ##
8
+ # Make coordinate northen
9
+ #
10
+ def north!
11
+ return unless valid_hemisphere?(:N)
12
+ @hemisphere = :N
13
+ end
14
+
15
+ ##
16
+ # Make coordinate southern
17
+ #
18
+ def south!
19
+ return unless valid_hemisphere?(:S)
20
+ @hemisphere = :S
21
+ end
22
+
23
+ ##
24
+ # Make coordinate western
25
+ #
26
+ def west!
27
+ return unless valid_hemisphere?(:W)
28
+ @hemisphere = :W
29
+ end
30
+
31
+ ##
32
+ # Make coordinate eastern
33
+ #
34
+ def east!
35
+ return unless valid_hemisphere?(:E)
36
+ @hemisphere = :E
37
+ end
38
+
39
+ ##
40
+ # Check if coordinate in northen hemisphere
41
+ #
42
+ # Returns: {Boolean}
43
+ #
44
+ def north?
45
+ normalize_hemi
46
+ hemisphere == :N
47
+ end
48
+
49
+ ##
50
+ # Check if coordinate in southern hemisphere
51
+ #
52
+ # Returns: {Boolean}
53
+ #
54
+ def south?
55
+ normalize_hemi
56
+ hemisphere == :S
57
+ end
58
+
59
+ ##
60
+ # Check if coordinate in western hemisphere
61
+ #
62
+ # Returns: {Boolean}
63
+ #
64
+ def west?
65
+ normalize_hemi
66
+ hemisphere == :W
67
+ end
68
+
69
+ ##
70
+ # Check if coordinate in eastern hemisphere
71
+ #
72
+ # Returns: {Boolean}
73
+ #
74
+ def east?
75
+ normalize_hemi
76
+ hemisphere == :E
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+
3
+ module Libgeo
4
+ class Coordinate
5
+ module Presenters
6
+
7
+ ##
8
+ # Represent coordinate as float
9
+ #
10
+ # Returns: {Float} decimal coordinate
11
+ #
12
+ def to_f
13
+ (negative_hemisphere? ? -1 : 1) * (degrees + minutes_only.to_d / 60).to_f
14
+ end
15
+
16
+ ##
17
+ # Represent coordinate in NMEA format
18
+ #
19
+ # Params:
20
+ # - formatter {Formatter} custom formatter (optional, default: '%2d%2M,%H')
21
+ #
22
+ # Returns: {String} formatted NMEA coordinate
23
+ #
24
+ def to_nmea(formatter=Format::NMEA_LAT)
25
+ to_s(formatter)
26
+ end
27
+
28
+ ##
29
+ # Format coordinate as string
30
+ #
31
+ # By default it uses standart DMS format
32
+ #
33
+ # Params:
34
+ # - formatter {Formatter} custom formatter (optional, default: '%2d°%2m′%S″%H')
35
+ #
36
+ # Returns: {String} formatted string
37
+ #
38
+ def to_s(formatter=Format::DMS)
39
+ formatter.format(self)
40
+ end
41
+ alias :to_dms :to_s
42
+
43
+ ##
44
+ # Represent coordinate as hash
45
+ #
46
+ # Returns: {Hash} with type, degrees, minutes, seconds and hemisphere
47
+ #
48
+ def to_hash
49
+ {
50
+ type: type,
51
+ degrees: degrees,
52
+ minutes: minutes,
53
+ seconds: seconds,
54
+ hemisphere: hemisphere
55
+ }
56
+ end
57
+
58
+ ##
59
+ # Inspect string
60
+ #
61
+ def inspect
62
+ "#<#{self.class} hemisphere=#{hemisphere} degrees=#{degrees} minutes=#{minutes} seconds=#{seconds}>"
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+
3
+ require 'libgeo/formatter/compiler'
4
+ require 'libgeo/formatter/evaluator'
5
+
6
+ module Libgeo
7
+
8
+ ##
9
+ # Class: coordinate formatter
10
+ #
11
+ # Compiles sprintf-like patterns and present
12
+ # given Coordinate as a DMS string
13
+ #
14
+ # Example:
15
+ #
16
+ # coord = Latitude.new(:N, 48, 4, 15.7)
17
+ # my_format = Formatter.new('%H%d°%2M′')
18
+ # my_format.format(coord) # => 'N48°04.26306′'
19
+ #
20
+ #
21
+ class Formatter
22
+
23
+ ##
24
+ # Source pattern
25
+ #
26
+ attr_reader :pattern
27
+
28
+ ##
29
+ # Create new string representing coordinate
30
+ #
31
+ # Params:
32
+ # - coordinate {Latitude|Longitude} source coordinate
33
+ # - evaluator {Class} private
34
+ #
35
+ # Returns: {String} formatted string
36
+ #
37
+ def format(coordinate, evaluator=Evaluator)
38
+ evaluator.new(coordinate).instance_eval(expression)
39
+ end
40
+
41
+ ##
42
+ # Represent instance as string
43
+ #
44
+ def inspect
45
+ "#<Libgeo::Formatter pattern=(#{pattern})>"
46
+ end
47
+ alias :to_s :inspect
48
+ alias :pretty_inspect :inspect
49
+
50
+ private
51
+
52
+ attr_reader :expression
53
+
54
+ def initialize(pattern)
55
+ @pattern = pattern.freeze
56
+ @expression = Compiler.new(pattern).compile
57
+ freeze
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,128 @@
1
+ # encoding: utf-8
2
+
3
+ require 'strscan'
4
+
5
+ module Libgeo
6
+ class Formatter
7
+
8
+ ##
9
+ # Class: template compiler
10
+ #
11
+ # Tooks the sprintf-like pattern string
12
+ # and compiles it to the ruby code
13
+ #
14
+ # Example:
15
+ #
16
+ # Compiler.new('%d:%2m').compile
17
+ #
18
+ class Compiler
19
+
20
+ ##
21
+ # Constant: set of keywords
22
+ #
23
+ # Defines keywords and corresponding ruby methods
24
+ # where `c` is a given coordinate object
25
+ #
26
+ KEYWORDS = {
27
+ 'd' => 'c.deg',
28
+ 'm' => 'c.mins',
29
+ 'M' => 'c.minutes_only',
30
+ 's' => 'c.secs.to_i',
31
+ 'S' => 'c.secs',
32
+ 'h' => 'c.hemi.to_s.downcase',
33
+ 'H' => 'c.hemi'
34
+ }.freeze
35
+
36
+ TOKEN_REG = /%\d?\w/.freeze
37
+ STRING_REG = /./.freeze
38
+
39
+ ##
40
+ # Resulted ruby expression
41
+ #
42
+ attr_reader :result
43
+
44
+ ##
45
+ # Compile the template
46
+ #
47
+ # Returns: {String} ruby expression
48
+ #
49
+ def compile
50
+ return result if result
51
+
52
+ scan until scanner.eos?
53
+
54
+ finalize_string
55
+
56
+ @result = parts.join(' << ').freeze
57
+ end
58
+
59
+ private
60
+
61
+ attr_reader :scanner, :parts
62
+
63
+ ##
64
+ # Constructor:
65
+ #
66
+ # Params:
67
+ # - pattern {String} pattern string
68
+ #
69
+ def initialize(pattern)
70
+ @scanner = StringScanner.new(pattern)
71
+ @parts = []
72
+ @curr_string = ''
73
+ end
74
+
75
+ ##
76
+ # Private: scan for subsrings and process them
77
+ #
78
+ def scan
79
+ if key = scanner.scan(TOKEN_REG)
80
+ finalize_string
81
+ process_token(key)
82
+ else
83
+ @curr_string << scanner.scan(STRING_REG)
84
+ end
85
+ end
86
+
87
+ ##
88
+ # Private: process % tokens
89
+ #
90
+ # When % token given - we split it to the parts: key and pad.
91
+ # For example `%2m` will have key `m` and pad `2`
92
+ # Then we add corresponding ruby code to buffer
93
+ # and wrap it with `pad()` function if pad given.
94
+ # For `%2m` we will receive `pad(c.mins.to_s, 2)` instruction
95
+ #
96
+ # Params:
97
+ # - key {String} matched % substring
98
+ #
99
+ def process_token(token)
100
+ key = token[-1]
101
+ pad = token[1..-2].to_i
102
+
103
+ instr = KEYWORDS[key] + '.to_s'
104
+ instr = "pad(#{instr}, #{pad})" if pad > 0
105
+
106
+ parts << instr
107
+ end
108
+
109
+ ##
110
+ # Private: finalize plain string
111
+ #
112
+ # To avoid redundant string operations in the final
113
+ # expression, we store each matched substring in the temporary
114
+ # instance variable. When the next % token given - we
115
+ # concatenate the content of this variable and clean it.
116
+ # As a result, we have string 'foo' instead of 'f' << 'o' << 'o'
117
+ #
118
+ def finalize_string
119
+ unless @curr_string.empty?
120
+ parts << %('#{@curr_string}')
121
+ @curr_string = ''
122
+ end
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+ end
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ module Libgeo
4
+ class Formatter
5
+
6
+ ##
7
+ # Class: sandbox for evaluating compiled expressions
8
+ #
9
+ class Evaluator < BasicObject
10
+
11
+ ##
12
+ # Coordinate
13
+ #
14
+ attr_reader :c
15
+
16
+ ##
17
+ # Add leading zeroes if needed
18
+ #
19
+ # Adds leading zeroes in case if int part of
20
+ # number doesn't have needed length
21
+ #
22
+ # Example:
23
+ #
24
+ # pad('3.14', 2) # => '03.14'
25
+ # pad('13.14', 2) # => '13.14'
26
+ # pad(42, 3) # => '042'
27
+ #
28
+ # Params:
29
+ # - val {String|Numeric} number to process
30
+ # - num {Fixnum} length of int part
31
+ #
32
+ # Returns: {String} number with or without zeroes added
33
+ #
34
+ def pad(val, i)
35
+ val = val.to_s.split('.')
36
+ val[0] = add_zeroes(val[0], i)
37
+ val.join('.')
38
+ end
39
+
40
+ private
41
+
42
+ def initialize(coordinate)
43
+ @c = coordinate
44
+ end
45
+
46
+ def add_zeroes(val, i)
47
+ num = i - val.length
48
+ num = num > 0 ? num : 0
49
+ ('0' * num) + val
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ module Libgeo
4
+ class Latitude < Coordinate
5
+
6
+ ##
7
+ # Constant: Valid hemisphere values
8
+ #
9
+ HEMISPHERES = Set.new([:N, :S]).freeze
10
+
11
+ POSITIVE_HEMISPHERE = :N
12
+ NEGATIVE_HEMISPHERE = :S
13
+
14
+ MAX_DEGREES = 90
15
+
16
+ ##
17
+ # Coordinate type
18
+ #
19
+ def type
20
+ :latitude
21
+ end
22
+
23
+ private
24
+
25
+ CORRECTIONS = {
26
+ N: Set.new(['N', 'north', :north]).freeze,
27
+ S: Set.new(['S', 'south', :south]).freeze
28
+ }.freeze
29
+
30
+ DIRECTIONS = {
31
+ :> => :N,
32
+ :< => :S
33
+ }.freeze
34
+
35
+ end
36
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+
3
+ module Libgeo
4
+ class Longitude < Coordinate
5
+
6
+ ##
7
+ # Constant: Valid hemisphere values
8
+ #
9
+ HEMISPHERES = Set.new([:W, :E]).freeze
10
+
11
+ POSITIVE_HEMISPHERE = :E
12
+ NEGATIVE_HEMISPHERE = :W
13
+
14
+ MAX_DEGREES = 180
15
+
16
+ ##
17
+ # Coordinate type
18
+ #
19
+ def type
20
+ :longitude
21
+ end
22
+
23
+ ##
24
+ # Represent coordinate in NMEA format
25
+ #
26
+ # Params:
27
+ # - formatter {Formatter} custom formatter (optional, default: '%2d%3M,%H')
28
+ #
29
+ # Returns: {String} formatted NMEA coordinate
30
+ #
31
+ def to_nmea(formatter=Format::NMEA_LON)
32
+ to_s(formatter)
33
+ end
34
+
35
+ private
36
+
37
+ CORRECTIONS = {
38
+ W: Set.new(['W', 'west', :west]).freeze,
39
+ E: Set.new(['E', 'east', :east]).freeze
40
+ }.freeze
41
+
42
+ DIRECTIONS = {
43
+ :> => :E,
44
+ :< => :W
45
+ }.freeze
46
+
47
+ end
48
+ end