metar-parser 1.5.0 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -48
- data/Rakefile +2 -1
- data/lib/metar/data/base.rb +16 -10
- data/lib/metar/data/density_altitude.rb +16 -10
- data/lib/metar/data/direction.rb +10 -4
- data/lib/metar/data/distance.rb +27 -20
- data/lib/metar/data/lightning.rb +69 -60
- data/lib/metar/data/observer.rb +26 -20
- data/lib/metar/data/pressure.rb +28 -22
- data/lib/metar/data/remark.rb +146 -130
- data/lib/metar/data/runway_visible_range.rb +98 -78
- data/lib/metar/data/sky_condition.rb +68 -57
- data/lib/metar/data/speed.rb +21 -14
- data/lib/metar/data/station_code.rb +8 -4
- data/lib/metar/data/temperature.rb +21 -14
- data/lib/metar/data/temperature_and_dew_point.rb +22 -16
- data/lib/metar/data/time.rb +57 -47
- data/lib/metar/data/variable_wind.rb +30 -19
- data/lib/metar/data/vertical_visibility.rb +27 -21
- data/lib/metar/data/visibility.rb +91 -79
- data/lib/metar/data/visibility_remark.rb +16 -5
- data/lib/metar/data/weather_phenomenon.rb +92 -74
- data/lib/metar/data/wind.rb +105 -93
- data/lib/metar/data.rb +25 -23
- data/lib/metar/i18n.rb +5 -2
- data/lib/metar/parser.rb +46 -21
- data/lib/metar/raw.rb +32 -44
- data/lib/metar/report.rb +31 -20
- data/lib/metar/station.rb +29 -20
- data/lib/metar/version.rb +3 -1
- data/lib/metar.rb +2 -1
- data/locales/de.yml +1 -0
- data/locales/en.yml +1 -0
- data/locales/it.yml +1 -0
- data/locales/pt-BR.yml +1 -0
- data/spec/data/density_altitude_spec.rb +2 -1
- data/spec/data/distance_spec.rb +2 -1
- data/spec/data/lightning_spec.rb +26 -9
- data/spec/data/pressure_spec.rb +2 -0
- data/spec/data/remark_spec.rb +26 -9
- data/spec/data/runway_visible_range_spec.rb +71 -35
- data/spec/data/sky_condition_spec.rb +63 -19
- data/spec/data/speed_spec.rb +2 -0
- data/spec/data/temperature_spec.rb +2 -1
- data/spec/data/variable_wind_spec.rb +2 -0
- data/spec/data/vertical_visibility_spec.rb +4 -4
- data/spec/data/visibility_remark_spec.rb +2 -1
- data/spec/data/visibility_spec.rb +46 -25
- data/spec/data/weather_phenomenon_spec.rb +79 -24
- data/spec/data/wind_spec.rb +156 -38
- data/spec/i18n_spec.rb +2 -0
- data/spec/parser_spec.rb +192 -64
- data/spec/raw_spec.rb +40 -68
- data/spec/report_spec.rb +27 -25
- data/spec/spec_helper.rb +5 -6
- data/spec/station_spec.rb +92 -52
- metadata +53 -39
data/lib/metar/data/remark.rb
CHANGED
@@ -1,136 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "i18n"
|
2
4
|
require "m9t"
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
:
|
7
|
-
:
|
8
|
-
:
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
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
|
-
|
7
|
-
|
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
|
-
|
10
|
-
|
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
|
-
|
27
|
-
|
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
|
-
|
52
|
-
|
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
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
106
|
+
s
|
107
|
+
end
|
108
|
+
end
|
89
109
|
end
|
90
110
|
end
|
@@ -1,70 +1,81 @@
|
|
1
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
45
|
-
|
51
|
+
nil
|
52
|
+
end
|
46
53
|
|
47
|
-
|
54
|
+
attr_reader :quantity, :height, :type
|
48
55
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
data/lib/metar/data/speed.rb
CHANGED
@@ -1,20 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "i18n"
|
2
4
|
require "m9t"
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
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
|