libgeo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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