metar-parser 1.5.0 → 1.7.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -48
  3. data/Rakefile +2 -1
  4. data/lib/metar/data/base.rb +16 -10
  5. data/lib/metar/data/density_altitude.rb +16 -10
  6. data/lib/metar/data/direction.rb +10 -4
  7. data/lib/metar/data/distance.rb +27 -20
  8. data/lib/metar/data/lightning.rb +69 -60
  9. data/lib/metar/data/observer.rb +26 -20
  10. data/lib/metar/data/pressure.rb +28 -22
  11. data/lib/metar/data/remark.rb +146 -130
  12. data/lib/metar/data/runway_visible_range.rb +98 -78
  13. data/lib/metar/data/sky_condition.rb +68 -57
  14. data/lib/metar/data/speed.rb +21 -14
  15. data/lib/metar/data/station_code.rb +8 -4
  16. data/lib/metar/data/temperature.rb +21 -14
  17. data/lib/metar/data/temperature_and_dew_point.rb +22 -16
  18. data/lib/metar/data/time.rb +57 -47
  19. data/lib/metar/data/variable_wind.rb +30 -19
  20. data/lib/metar/data/vertical_visibility.rb +27 -21
  21. data/lib/metar/data/visibility.rb +91 -79
  22. data/lib/metar/data/visibility_remark.rb +16 -5
  23. data/lib/metar/data/weather_phenomenon.rb +92 -74
  24. data/lib/metar/data/wind.rb +105 -93
  25. data/lib/metar/data.rb +25 -23
  26. data/lib/metar/i18n.rb +5 -2
  27. data/lib/metar/parser.rb +46 -21
  28. data/lib/metar/raw.rb +32 -44
  29. data/lib/metar/report.rb +31 -20
  30. data/lib/metar/station.rb +29 -20
  31. data/lib/metar/version.rb +3 -1
  32. data/lib/metar.rb +2 -1
  33. data/locales/de.yml +1 -0
  34. data/locales/en.yml +1 -0
  35. data/locales/it.yml +1 -0
  36. data/locales/pt-BR.yml +1 -0
  37. data/spec/data/density_altitude_spec.rb +2 -1
  38. data/spec/data/distance_spec.rb +2 -1
  39. data/spec/data/lightning_spec.rb +26 -9
  40. data/spec/data/pressure_spec.rb +2 -0
  41. data/spec/data/remark_spec.rb +26 -9
  42. data/spec/data/runway_visible_range_spec.rb +71 -35
  43. data/spec/data/sky_condition_spec.rb +63 -19
  44. data/spec/data/speed_spec.rb +2 -0
  45. data/spec/data/temperature_spec.rb +2 -1
  46. data/spec/data/variable_wind_spec.rb +2 -0
  47. data/spec/data/vertical_visibility_spec.rb +4 -4
  48. data/spec/data/visibility_remark_spec.rb +2 -1
  49. data/spec/data/visibility_spec.rb +46 -25
  50. data/spec/data/weather_phenomenon_spec.rb +79 -24
  51. data/spec/data/wind_spec.rb +156 -38
  52. data/spec/i18n_spec.rb +2 -0
  53. data/spec/parser_spec.rb +192 -64
  54. data/spec/raw_spec.rb +40 -68
  55. data/spec/report_spec.rb +27 -25
  56. data/spec/spec_helper.rb +5 -6
  57. data/spec/station_spec.rb +92 -52
  58. metadata +53 -39
@@ -1,136 +1,152 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "i18n"
2
4
  require "m9t"
3
5
 
4
- class Metar::Data::Remark
5
- PRESSURE_CHANGE_CHARACTER = [
6
- :increasing_then_decreasing, # 0
7
- :increasing_then_steady, # 1
8
- :increasing, # 2
9
- :decreasing_or_steady_then_increasing, # 3
10
- :steady, # 4
11
- :decreasing_then_increasing, # 5
12
- :decreasing_then_steady, # 6
13
- :decreasing, # 7
14
- :steady_then_decreasing, # 8
15
- ]
16
-
17
- INDICATOR_TYPE = {
18
- 'TS' => :thunderstorm_information,
19
- 'PWI' => :precipitation_identifier,
20
- 'P' => :precipitation_amount,
21
- }
22
-
23
- COLOR_CODE = ['RED', 'AMB', 'YLO', 'GRN', 'WHT', 'BLU']
24
-
25
- def self.parse(raw)
26
- if !raw
27
- return nil
28
- end
29
-
30
- m1 = raw.match(/^([12])([01])(\d{3})$/)
31
- if m1
32
- extreme = {'1' => :maximum, '2' => :minimum}[m1[1]]
33
- value = sign(m1[2]) * tenths(m1[3])
34
- return Metar::Data::TemperatureExtreme.new(raw, extreme, value)
35
- end
36
-
37
- m2 = raw.match(/^4([01])(\d{3})([01])(\d{3})$/)
38
- if m2
39
- return [
40
- Metar::Data::TemperatureExtreme.new(raw, :maximum, sign(m2[1]) * tenths(m2[2])),
41
- Metar::Data::TemperatureExtreme.new(raw, :minimum, sign(m2[3]) * tenths(m2[4])),
42
- ]
43
- end
44
-
45
- m3 = raw.match(/^5([0-8])(\d{3})$/)
46
- if m3
47
- character = PRESSURE_CHANGE_CHARACTER[m3[1].to_i]
48
- return Metar::Data::PressureTendency.new(raw, character, tenths(m3[2]))
49
- end
50
-
51
- m4 = raw.match(/^6(\d{4})$/)
52
- if m4
53
- return Metar::Data::Precipitation.new(raw, 3, Metar::Data::Distance.new(inches_to_meters(m4[1]))) # actually 3 or 6 depending on reporting time
54
- end
55
-
56
- m5 = raw.match(/^7(\d{4})$/)
57
- if m5
58
- return Metar::Data::Precipitation.new(raw, 24, Metar::Data::Distance.new(inches_to_meters(m5[1])))
59
- end
60
-
61
- m6 = raw.match(/^A[0O]([12])$/)
62
- if m6
63
- type = [:with_precipitation_discriminator, :without_precipitation_discriminator][m6[1].to_i - 1]
64
- return Metar::Data::AutomatedStationType.new(raw, type)
65
- end
66
-
67
- m7 = raw.match(/^P(\d{4})$/)
68
- if m7
69
- return Metar::Data::Precipitation.new(raw, 1, Metar::Data::Distance.new(inches_to_meters(m7[1])))
70
- end
71
-
72
- m8 = raw.match(/^T([01])(\d{3})([01])(\d{3})$/)
73
- if m8
74
- temperature = Metar::Data::Temperature.new(sign(m8[1]) * tenths(m8[2]))
75
- dew_point = Metar::Data::Temperature.new(sign(m8[3]) * tenths(m8[4]))
76
- return Metar::Data::HourlyTemperatureAndDewPoint.new(raw, temperature, dew_point)
77
- end
78
-
79
- m9 = raw.match(/^SLP(\d{3})$/)
80
- if m9
81
- return Metar::Data::SeaLevelPressure.new(raw, M9t::Pressure.hectopascals(tenths(m9[1])))
6
+ module Metar
7
+ module Data
8
+ TemperatureExtreme = Struct.new(:raw, :extreme, :value)
9
+ PressureTendency = Struct.new(:raw, :character, :value)
10
+ Precipitation = Struct.new(:raw, :period, :amount)
11
+ AutomatedStationType = Struct.new(:raw, :type)
12
+ HourlyTemperatureAndDewPoint = Struct.new(:raw, :temperature, :dew_point)
13
+ SeaLevelPressure = Struct.new(:raw, :pressure)
14
+ SensorStatusIndicator = Struct.new(:raw, :type, :state)
15
+ ColorCode = Struct.new(:raw, :code)
16
+ MaintenanceNeeded = Struct.new(:raw)
17
+
18
+ class Remark
19
+ PRESSURE_CHANGE_CHARACTER = [
20
+ :increasing_then_decreasing, # 0
21
+ :increasing_then_steady, # 1
22
+ :increasing, # 2
23
+ :decreasing_or_steady_then_increasing, # 3
24
+ :steady, # 4
25
+ :decreasing_then_increasing, # 5
26
+ :decreasing_then_steady, # 6
27
+ :decreasing, # 7
28
+ :steady_then_decreasing # 8
29
+ ].freeze
30
+
31
+ INDICATOR_TYPE = {
32
+ 'TS' => :thunderstorm_information,
33
+ 'PWI' => :precipitation_identifier,
34
+ 'P' => :precipitation_amount
35
+ }.freeze
36
+
37
+ COLOR_CODE = %w(RED AMB YLO GRN WHT BLU).freeze
38
+
39
+ def self.parse(raw)
40
+ return nil if !raw
41
+
42
+ m1 = raw.match(/^([12])([01])(\d{3})$/)
43
+ if m1
44
+ extreme = {'1' => :maximum, '2' => :minimum}[m1[1]]
45
+ value = sign(m1[2]) * tenths(m1[3])
46
+ return Metar::Data::TemperatureExtreme.new(raw, extreme, value)
47
+ end
48
+
49
+ m2 = raw.match(/^4([01])(\d{3})([01])(\d{3})$/)
50
+ if m2
51
+ v1 = sign(m2[1]) * tenths(m2[2])
52
+ v2 = sign(m2[3]) * tenths(m2[4])
53
+ return [
54
+ Metar::Data::TemperatureExtreme.new(raw, :maximum, v1),
55
+ Metar::Data::TemperatureExtreme.new(raw, :minimum, v2)
56
+ ]
57
+ end
58
+
59
+ m3 = raw.match(/^5([0-8])(\d{3})$/)
60
+ if m3
61
+ character = PRESSURE_CHANGE_CHARACTER[m3[1].to_i]
62
+ return Metar::Data::PressureTendency.new(
63
+ raw, character, tenths(m3[2])
64
+ )
65
+ end
66
+
67
+ m4 = raw.match(/^6(\d{4})$/)
68
+ if m4
69
+ d = Metar::Data::Distance.new(inches_to_meters(m4[1]))
70
+ period = 3 # actually 3 or 6 depending on reporting time
71
+ return Metar::Data::Precipitation.new(raw, period, d)
72
+ end
73
+
74
+ m5 = raw.match(/^7(\d{4})$/)
75
+ if m5
76
+ d = Metar::Data::Distance.new(inches_to_meters(m5[1]))
77
+ return Metar::Data::Precipitation.new(raw, 24, d)
78
+ end
79
+
80
+ m6 = raw.match(/^A[0O]([12])$/)
81
+ if m6
82
+ index = m6[1].to_i - 1
83
+ type = %i(
84
+ with_precipitation_discriminator without_precipitation_discriminator
85
+ )[index]
86
+ return Metar::Data::AutomatedStationType.new(raw, type)
87
+ end
88
+
89
+ m7 = raw.match(/^P(\d{4})$/)
90
+ if m7
91
+ d = Metar::Data::Distance.new(inches_to_meters(m7[1]))
92
+ return Metar::Data::Precipitation.new(raw, 1, d)
93
+ end
94
+
95
+ m8 = raw.match(/^T([01])(\d{3})([01])(\d{3})$/)
96
+ if m8
97
+ temperature = Metar::Data::Temperature.new(
98
+ sign(m8[1]) * tenths(m8[2])
99
+ )
100
+ dew_point = Metar::Data::Temperature.new(
101
+ sign(m8[3]) * tenths(m8[4])
102
+ )
103
+ return Metar::Data::HourlyTemperatureAndDewPoint.new(
104
+ raw, temperature, dew_point
105
+ )
106
+ end
107
+
108
+ m9 = raw.match(/^SLP(\d{3})$/)
109
+ if m9
110
+ pressure = M9t::Pressure.hectopascals(tenths(m9[1]))
111
+ return Metar::Data::SeaLevelPressure.new(raw, pressure)
112
+ end
113
+
114
+ m10 = raw.match(/^(#{INDICATOR_TYPE.keys.join('|')})NO$/)
115
+ if m10
116
+ type = INDICATOR_TYPE[m10[1]]
117
+ return Metar::Data::SensorStatusIndicator.new(
118
+ raw, type, :not_available
119
+ )
120
+ end
121
+
122
+ m11 = raw.match(/^(#{COLOR_CODE.join('|')})$/)
123
+ return Metar::Data::ColorCode.new(raw, m11[1]) if m11
124
+
125
+ return Metar::Data::SkyCondition.new(raw) if raw == 'SKC'
126
+
127
+ return Metar::Data::MaintenanceNeeded.new(raw) if raw == '$'
128
+
129
+ nil
130
+ end
131
+
132
+ def self.sign(digit)
133
+ case digit
134
+ when '0'
135
+ 1.0
136
+ when '1'
137
+ -1.0
138
+ else
139
+ raise "Unexpected sign: #{digit}"
140
+ end
141
+ end
142
+
143
+ def self.tenths(digits)
144
+ digits.to_f / 10.0
145
+ end
146
+
147
+ def self.inches_to_meters(digits)
148
+ digits.to_f * 0.000254
149
+ end
82
150
  end
83
-
84
- m10 = raw.match(/^(#{INDICATOR_TYPE.keys.join('|')})NO$/)
85
- if m10
86
- type = INDICATOR_TYPE[m10[1]]
87
- return Metar::Data::SensorStatusIndicator.new(raw, type, :not_available)
88
- end
89
-
90
- m11 = raw.match(/^(#{COLOR_CODE.join('|')})$/)
91
- if m11
92
- return Metar::Data::ColorCode.new(raw, m11[1])
93
- end
94
-
95
- if raw == 'SKC'
96
- return Metar::Data::SkyCondition.new(raw)
97
- end
98
-
99
- if raw == '$'
100
- Metar::Data::MaintenanceNeeded.new(raw)
101
- end
102
-
103
- nil
104
151
  end
105
-
106
- def self.sign(digit)
107
- case digit
108
- when '0'
109
- 1.0
110
- when '1'
111
- -1.0
112
- else
113
- raise "Unexpected sign: #{digit}"
114
- end
115
- end
116
-
117
- def self.tenths(digits)
118
- digits.to_f / 10.0
119
- end
120
-
121
- def self.inches_to_meters(digits)
122
- digits.to_f * 0.000254
123
- end
124
- end
125
-
126
- module Metar::Data
127
- TemperatureExtreme = Struct.new(:raw, :extreme, :value)
128
- PressureTendency = Struct.new(:raw, :character, :value)
129
- Precipitation = Struct.new(:raw, :period, :amount)
130
- AutomatedStationType = Struct.new(:raw, :type)
131
- HourlyTemperatureAndDewPoint = Struct.new(:raw, :temperature, :dew_point)
132
- SeaLevelPressure = Struct.new(:raw, :pressure)
133
- SensorStatusIndicator = Struct.new(:raw, :type, :state)
134
- ColorCode = Struct.new(:raw, :code)
135
- MaintenanceNeeded = Struct.new(:raw)
136
152
  end
@@ -1,90 +1,110 @@
1
- class Metar::Data::RunwayVisibleRange < Metar::Data::Base
2
- TENDENCY = {'' => nil, 'N' => :no_change, 'U' => :improving, 'D' => :worsening}
3
- COMPARATOR = {'' => nil, 'P' => :more_than, 'M' => :less_than}
4
- UNITS = {'' => :meters, 'FT' => :feet}
1
+ # frozen_string_literal: true
5
2
 
6
- def self.parse(raw)
7
- return nil if raw.nil?
3
+ module Metar
4
+ module Data
5
+ class RunwayVisibleRange < Metar::Data::Base
6
+ TENDENCY = {
7
+ '' => nil,
8
+ 'N' => :no_change,
9
+ 'U' => :improving,
10
+ 'D' => :worsening
11
+ }.freeze
8
12
 
9
- m1 = raw.match(/^R(\d+[RLC]?)\/(P|M|)(\d{4})(FT|)\/?(N|U|D|)$/)
10
- if m1
11
- designator = m1[1]
12
- comparator = COMPARATOR[m1[2]]
13
- count = m1[3].to_f
14
- units = UNITS[m1[4]]
15
- tendency = TENDENCY[m1[5]]
16
- distance = Metar::Data::Distance.send(units, count)
17
- visibility = Metar::Data::Visibility.new(
18
- nil, distance: distance, comparator: comparator
19
- )
20
- return new(
21
- raw,
22
- designator: designator, visibility1: visibility, tendency: tendency
23
- )
24
- end
13
+ COMPARATOR = {'' => nil, 'P' => :more_than, 'M' => :less_than}.freeze
14
+ UNITS = {'' => :meters, 'FT' => :feet}.freeze
25
15
 
26
- m2 = raw.match(/^R(\d+[RLC]?)\/(P|M|)(\d{4})V(P|M|)(\d{4})(FT|)\/?(N|U|D)?$/)
27
- if m2
28
- designator = m2[1]
29
- comparator1 = COMPARATOR[m2[2]]
30
- count1 = m2[3].to_f
31
- comparator2 = COMPARATOR[m2[4]]
32
- count2 = m2[5].to_f
33
- units = UNITS[m2[6]]
34
- tendency = TENDENCY[m2[7]]
35
- distance1 = Metar::Data::Distance.send(units, count1)
36
- distance2 = Metar::Data::Distance.send(units, count2)
37
- visibility1 = Metar::Data::Visibility.new(
38
- nil, distance: distance1, comparator: comparator1
39
- )
40
- visibility2 = Metar::Data::Visibility.new(
41
- nil, distance: distance2, comparator: comparator2
42
- )
43
- return new(
44
- raw,
45
- designator: designator,
46
- visibility1: visibility1, visibility2: visibility2,
47
- tendency: tendency, units: units
48
- )
49
- end
16
+ def self.parse(raw)
17
+ return nil if raw.nil?
50
18
 
51
- nil
52
- end
19
+ m1 = raw.match(%r{^R(\d+[RLC]?)/(P|M|)(\d{4})(FT|)/?(N|U|D|)$})
20
+ if m1
21
+ designator = m1[1]
22
+ comparator = COMPARATOR[m1[2]]
23
+ count = m1[3].to_f
24
+ units = UNITS[m1[4]]
25
+ tendency = TENDENCY[m1[5]]
26
+ distance = Metar::Data::Distance.send(units, count)
27
+ visibility = Metar::Data::Visibility.new(
28
+ nil, distance: distance, comparator: comparator
29
+ )
30
+ return new(
31
+ raw,
32
+ designator: designator, visibility1: visibility, tendency: tendency
33
+ )
34
+ end
53
35
 
54
- attr_reader :designator, :visibility1, :visibility2, :tendency
36
+ m2 = raw.match(
37
+ %r{^R(\d+[RLC]?)/(P|M|)(\d{4})V(P|M|)(\d{4})(FT|)/?(N|U|D)?$}
38
+ )
39
+ if m2
40
+ designator = m2[1]
41
+ comparator1 = COMPARATOR[m2[2]]
42
+ count1 = m2[3].to_f
43
+ comparator2 = COMPARATOR[m2[4]]
44
+ count2 = m2[5].to_f
45
+ units = UNITS[m2[6]]
46
+ tendency = TENDENCY[m2[7]]
47
+ distance1 = Metar::Data::Distance.send(units, count1)
48
+ distance2 = Metar::Data::Distance.send(units, count2)
49
+ visibility1 = Metar::Data::Visibility.new(
50
+ nil, distance: distance1, comparator: comparator1
51
+ )
52
+ visibility2 = Metar::Data::Visibility.new(
53
+ nil, distance: distance2, comparator: comparator2
54
+ )
55
+ return new(
56
+ raw,
57
+ designator: designator,
58
+ visibility1: visibility1, visibility2: visibility2,
59
+ tendency: tendency, units: units
60
+ )
61
+ end
55
62
 
56
- def initialize(
57
- raw,
58
- designator:, visibility1:, visibility2: nil, tendency: nil, units: :meters
59
- )
60
- @raw = raw
61
- @designator, @visibility1, @visibility2, @tendency, @units = designator, visibility1, visibility2, tendency, units
62
- end
63
+ nil
64
+ end
65
+
66
+ attr_reader :designator, :visibility1, :visibility2, :tendency
63
67
 
64
- def to_s
65
- distance_options = {
66
- abbreviated: true,
67
- precision: 0,
68
- units: @units,
69
- }
70
- s =
71
- if @visibility2.nil?
72
- I18n.t('metar.runway_visible_range.runway') +
73
- ' ' + @designator +
74
- ': ' + @visibility1.to_s(distance_options)
75
- else
76
- I18n.t('metar.runway_visible_range.runway') +
77
- ' ' + @designator +
78
- ': ' + I18n.t('metar.runway_visible_range.from') +
79
- ' ' + @visibility1.to_s(distance_options) +
80
- ' ' + I18n.t('metar.runway_visible_range.to') +
81
- ' ' + @visibility2.to_s(distance_options)
68
+ def initialize(
69
+ raw,
70
+ designator:,
71
+ visibility1:,
72
+ visibility2: nil,
73
+ tendency: nil,
74
+ units: :meters
75
+ )
76
+ @raw = raw
77
+ @designator = designator
78
+ @visibility1 = visibility1
79
+ @visibility2 = visibility2
80
+ @tendency = tendency
81
+ @units = units
82
82
  end
83
83
 
84
- if ! tendency.nil?
85
- s += ' ' + I18n.t("tendency.#{tendency}")
86
- end
84
+ def to_s
85
+ distance_options = {
86
+ abbreviated: true,
87
+ precision: 0,
88
+ units: @units
89
+ }
90
+ s =
91
+ if @visibility2.nil?
92
+ I18n.t('metar.runway_visible_range.runway') +
93
+ ' ' + @designator +
94
+ ': ' + @visibility1.to_s(distance_options)
95
+ else
96
+ I18n.t('metar.runway_visible_range.runway') +
97
+ ' ' + @designator +
98
+ ': ' + I18n.t('metar.runway_visible_range.from') +
99
+ ' ' + @visibility1.to_s(distance_options) +
100
+ ' ' + I18n.t('metar.runway_visible_range.to') +
101
+ ' ' + @visibility2.to_s(distance_options)
102
+ end
103
+
104
+ s += ' ' + I18n.t("tendency.#{tendency}") if !tendency.nil?
87
105
 
88
- s
106
+ s
107
+ end
108
+ end
89
109
  end
90
110
  end
@@ -1,70 +1,81 @@
1
- class Metar::Data::SkyCondition < Metar::Data::Base
2
- QUANTITY = {'BKN' => 'broken', 'FEW' => 'few', 'OVC' => 'overcast', 'SCT' => 'scattered'}
3
- CONDITION = {
4
- 'CB' => 'cumulonimbus',
5
- 'TCU' => 'towering cumulus',
6
- '///' => nil, # cloud type unknown as observed by automatic system (15.9.1.7)
7
- '' => nil,
8
- }
9
- CLEAR_SKIES = [
10
- 'NSC', # WMO
11
- 'NCD', # WMO
12
- 'CLR',
13
- 'SKC',
14
- ]
1
+ # frozen_string_literal: true
15
2
 
16
- def self.parse(raw)
17
- if !raw
18
- return nil
19
- end
3
+ module Metar
4
+ module Data
5
+ class SkyCondition < Metar::Data::Base
6
+ QUANTITY = {
7
+ 'BKN' => 'broken',
8
+ 'FEW' => 'few',
9
+ 'OVC' => 'overcast',
10
+ 'SCT' => 'scattered'
11
+ }.freeze
20
12
 
21
- if CLEAR_SKIES.include?(raw)
22
- return new(raw)
23
- end
13
+ CONDITION = {
14
+ 'CB' => 'cumulonimbus',
15
+ 'TCU' => 'towering cumulus',
16
+ # /// - cloud type unknown as observed by automatic system (15.9.1.7)
17
+ '///' => nil,
18
+ '' => nil
19
+ }.freeze
20
+ CLEAR_SKIES = [
21
+ 'NSC', # WMO
22
+ 'NCD', # WMO
23
+ 'CLR',
24
+ 'SKC'
25
+ ].freeze
24
26
 
25
- m1 = raw.match(/^(BKN|FEW|OVC|SCT)(\d+|\/{3})(CB|TCU|\/{3}|)?$/)
26
- if m1
27
- quantity = QUANTITY[m1[1]]
28
- height =
29
- if m1[2] == '///'
30
- nil
31
- else
32
- Metar::Data::Distance.new(m1[2].to_i * 30.48)
27
+ def self.parse(raw)
28
+ return nil if !raw
29
+
30
+ return new(raw) if CLEAR_SKIES.include?(raw)
31
+
32
+ m1 = raw.match(%r{^(BKN|FEW|OVC|SCT)(\d+|/{3})(CB|TCU|/{3}|)?$})
33
+ if m1
34
+ quantity = QUANTITY[m1[1]]
35
+ height =
36
+ if m1[2] == '///'
37
+ nil
38
+ else
39
+ Metar::Data::Distance.new(m1[2].to_i * 30.48)
40
+ end
41
+ type = CONDITION[m1[3]]
42
+ return new(raw, quantity: quantity, height: height, type: type)
33
43
  end
34
- type = CONDITION[m1[3]]
35
- return new(raw, quantity: quantity, height: height, type: type)
36
- end
37
44
 
38
- m2 = raw.match(/^(CB|TCU)$/)
39
- if m2
40
- type = CONDITION[m2[1]]
41
- return new(raw, type: type)
42
- end
45
+ m2 = raw.match(/^(CB|TCU)$/)
46
+ if m2
47
+ type = CONDITION[m2[1]]
48
+ return new(raw, type: type)
49
+ end
43
50
 
44
- nil
45
- end
51
+ nil
52
+ end
46
53
 
47
- attr_reader :quantity, :height, :type
54
+ attr_reader :quantity, :height, :type
48
55
 
49
- def initialize(raw, quantity: nil, height: nil, type: nil)
50
- @raw = raw
51
- @quantity, @height, @type = quantity, height, type
52
- end
56
+ def initialize(raw, quantity: nil, height: nil, type: nil)
57
+ @raw = raw
58
+ @quantity = quantity
59
+ @height = height
60
+ @type = type
61
+ end
53
62
 
54
- def to_s
55
- if @height.nil?
56
- to_summary
57
- else
58
- to_summary + ' ' + I18n.t('metar.altitude.at') + ' ' + height.to_s
59
- end
60
- end
63
+ def to_s
64
+ if @height.nil?
65
+ to_summary
66
+ else
67
+ to_summary + ' ' + I18n.t('metar.altitude.at') + ' ' + height.to_s
68
+ end
69
+ end
61
70
 
62
- def to_summary
63
- if @quantity == nil and @height == nil and @type == nil
64
- I18n.t('metar.sky_conditions.clear skies')
65
- else
66
- type = @type ? ' ' + @type : ''
67
- I18n.t("metar.sky_conditions.#{@quantity}#{type}")
71
+ def to_summary
72
+ if @quantity.nil? && @height.nil? && @type.nil?
73
+ I18n.t('metar.sky_conditions.clear skies')
74
+ else
75
+ type = @type ? ' ' + @type : ''
76
+ I18n.t("metar.sky_conditions.#{@quantity}#{type}")
77
+ end
78
+ end
68
79
  end
69
80
  end
70
81
  end
@@ -1,20 +1,27 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "i18n"
2
4
  require "m9t"
3
5
 
4
- # Adds a parse method to the M9t base class
5
- class Metar::Data::Speed < M9t::Speed
6
- METAR_UNITS = {
7
- "" => :kilometers_per_hour,
8
- "KMH" => :kilometers_per_hour,
9
- "MPS" => :meters_per_second,
10
- "KT" => :knots,
11
- }
6
+ module Metar
7
+ module Data
8
+ class Speed < M9t::Speed
9
+ METAR_UNITS = {
10
+ "" => :kilometers_per_hour,
11
+ "KMH" => :kilometers_per_hour,
12
+ "MPS" => :meters_per_second,
13
+ "KT" => :knots
14
+ }.freeze
15
+
16
+ def self.parse(raw)
17
+ return nil if raw.nil?
18
+
19
+ m = raw.match(/^(\d+)(|KT|MPS|KMH)$/)
20
+ return nil if m.nil?
12
21
 
13
- def self.parse(raw)
14
- return nil if raw.nil?
15
- m = raw.match(/^(\d+)(|KT|MPS|KMH)$/)
16
- return nil if m.nil?
17
- # Call the appropriate factory method for the supplied units
18
- return send(METAR_UNITS[m[2]], m[1].to_i)
22
+ # Call the appropriate factory method for the supplied units
23
+ send(METAR_UNITS[m[2]], m[1].to_i)
24
+ end
25
+ end
19
26
  end
20
27
  end
@@ -1,7 +1,11 @@
1
- class Metar::Data::StationCode < Metar::Data::Base
2
- def self.parse(raw)
3
- if raw =~ /^[A-Z][A-Z0-9]{3}$/
4
- new(raw)
1
+ # frozen_string_literal: true
2
+
3
+ module Metar
4
+ module Data
5
+ class StationCode < Metar::Data::Base
6
+ def self.parse(raw)
7
+ new(raw) if raw =~ /^[A-Z][A-Z0-9]{3}$/
8
+ end
5
9
  end
6
10
  end
7
11
  end