metar-parser 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/metar/data/base.rb +15 -0
- data/lib/metar/data/density_altitude.rb +15 -0
- data/lib/metar/data/direction.rb +9 -0
- data/lib/metar/data/distance.rb +27 -0
- data/lib/metar/data/lightning.rb +61 -0
- data/lib/metar/data/observer.rb +24 -0
- data/lib/metar/data/pressure.rb +23 -0
- data/lib/metar/data/remark.rb +98 -0
- data/lib/metar/data/runway_visible_range.rb +85 -0
- data/lib/metar/data/sky_condition.rb +61 -0
- data/lib/metar/data/speed.rb +22 -0
- data/lib/metar/data/station_code.rb +7 -0
- data/lib/metar/data/temperature.rb +21 -0
- data/lib/metar/data/temperature_and_dew_point.rb +18 -0
- data/lib/metar/data/time.rb +54 -0
- data/lib/metar/data/variable_wind.rb +25 -0
- data/lib/metar/data/vertical_visibility.rb +26 -0
- data/lib/metar/data/visibility.rb +71 -0
- data/lib/metar/data/visibility_remark.rb +8 -0
- data/lib/metar/data/weather_phenomenon.rb +86 -0
- data/lib/metar/data/wind.rb +82 -0
- data/lib/metar/data.rb +22 -636
- data/lib/metar/i18n.rb +6 -0
- data/lib/metar/parser.rb +165 -120
- data/lib/metar/report.rb +1 -1
- data/lib/metar/version.rb +2 -2
- data/lib/metar.rb +7 -6
- data/locales/de.yml +1 -0
- data/locales/en.yml +1 -0
- data/locales/it.yml +2 -1
- data/locales/pt-BR.yml +1 -0
- data/spec/data/density_altitude_spec.rb +12 -0
- data/spec/{distance_spec.rb → data/distance_spec.rb} +1 -1
- data/spec/data/lightning_spec.rb +49 -0
- data/spec/data/pressure_spec.rb +22 -0
- data/spec/data/remark_spec.rb +99 -0
- data/spec/data/runway_visible_range_spec.rb +92 -0
- data/spec/{sky_condition_spec.rb → data/sky_condition_spec.rb} +10 -6
- data/spec/data/speed_spec.rb +45 -0
- data/spec/data/temperature_spec.rb +36 -0
- data/spec/{variable_wind_spec.rb → data/variable_wind_spec.rb} +6 -6
- data/spec/{vertical_visibility_spec.rb → data/vertical_visibility_spec.rb} +2 -2
- data/spec/{data_spec.rb → data/visibility_remark_spec.rb} +1 -11
- data/spec/{visibility_spec.rb → data/visibility_spec.rb} +9 -7
- data/spec/{weather_phenomenon_spec.rb → data/weather_phenomenon_spec.rb} +7 -3
- data/spec/{wind_spec.rb → data/wind_spec.rb} +10 -7
- data/spec/parser_spec.rb +107 -13
- data/spec/report_spec.rb +12 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/station_spec.rb +2 -1
- metadata +56 -31
- data/spec/pressure_spec.rb +0 -22
- data/spec/remark_spec.rb +0 -147
- data/spec/runway_visible_range_spec.rb +0 -81
- data/spec/speed_spec.rb +0 -45
- data/spec/temperature_spec.rb +0 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae0329de719ceb6a7302bf28a0b2eb60224b6220
|
4
|
+
data.tar.gz: 2f480c17d9cd2f3df30ee882ebbf45605bdae90d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1c5d02054171c2b5d1a70d01caf6be3a2af8fa574e7a1b93595e0e5ce9e2b079bcab873bf2b2a82b9331c2dea2b7df01d493fe40f234d198c0f1c6b71712c32
|
7
|
+
data.tar.gz: ae061b3580ad88b8463af928ae8537bc58a99ae95c3553a9eb68a89320f4fa5712de1e6a84bfefe5cc0bdb7e877fbf7e619c59844e83a72a8e0f7d618ae244ee
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Metar::Data::DensityAltitude < Metar::Data::Base
|
2
|
+
def self.parse(raw)
|
3
|
+
feet = raw[/^(\d+)(FT)/, 1]
|
4
|
+
height = Metar::Data::Distance.feet(feet)
|
5
|
+
|
6
|
+
new(raw, height: height)
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :height
|
10
|
+
|
11
|
+
def initialize(raw, height:)
|
12
|
+
@raw = raw
|
13
|
+
@height = height
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "i18n"
|
2
|
+
require "m9t"
|
3
|
+
|
4
|
+
class Metar::Data::Distance < M9t::Distance
|
5
|
+
attr_accessor :units
|
6
|
+
|
7
|
+
# nil is taken to mean 'data unavailable'
|
8
|
+
def initialize(meters = nil)
|
9
|
+
@units = :meters
|
10
|
+
if meters
|
11
|
+
super
|
12
|
+
else
|
13
|
+
@value = nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Handles nil case differently to M9t::Distance
|
18
|
+
def to_s(options = {})
|
19
|
+
options = {
|
20
|
+
units: units,
|
21
|
+
precision: 0,
|
22
|
+
abbreviated: true,
|
23
|
+
}.merge(options)
|
24
|
+
return I18n.t("metar.distance.unknown") if @value.nil?
|
25
|
+
super(options)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class Metar::Data::Lightning < Metar::Data::Base
|
2
|
+
TYPE = {'' => :default}
|
3
|
+
|
4
|
+
def self.parse_chunks(chunks)
|
5
|
+
raw = chunks.shift
|
6
|
+
m = raw.match(/^LTG(|CG|IC|CC|CA)$/)
|
7
|
+
raise 'first chunk is not lightning' if m.nil?
|
8
|
+
type = TYPE[m[1]]
|
9
|
+
|
10
|
+
frequency = nil
|
11
|
+
distance = nil
|
12
|
+
directions = []
|
13
|
+
|
14
|
+
if chunks[0] == 'DSNT'
|
15
|
+
distance = Metar::Data::Distance.miles(10) # Should be >10SM, not 10SM
|
16
|
+
raw += " " + chunks.shift
|
17
|
+
end
|
18
|
+
|
19
|
+
loop do
|
20
|
+
if is_compass?(chunks[0])
|
21
|
+
direction = chunks.shift
|
22
|
+
raw += " " + direction
|
23
|
+
directions << direction
|
24
|
+
elsif chunks[0] == 'ALQDS'
|
25
|
+
directions += ['N', 'E', 'S', 'W']
|
26
|
+
raw += " " + chunks.shift
|
27
|
+
elsif chunks[0] =~ /^([NESW]{1,2})-([NESW]{1,2})$/
|
28
|
+
if is_compass?($1) and is_compass?($2)
|
29
|
+
directions += [$1, $2]
|
30
|
+
raw += " " + chunks.shift
|
31
|
+
else
|
32
|
+
break
|
33
|
+
end
|
34
|
+
elsif chunks[0] == 'AND'
|
35
|
+
raw += " " + chunks.shift
|
36
|
+
else
|
37
|
+
break
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
new(
|
42
|
+
raw,
|
43
|
+
frequency: frequency, type: type,
|
44
|
+
distance: distance, directions: directions
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.is_compass?(s)
|
49
|
+
s =~ /^([NESW]|NE|SE|SW|NW)$/
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_accessor :frequency
|
53
|
+
attr_accessor :type
|
54
|
+
attr_accessor :distance
|
55
|
+
attr_accessor :directions
|
56
|
+
|
57
|
+
def initialize(raw, frequency:, type:, distance:, directions:)
|
58
|
+
@raw = raw
|
59
|
+
@frequency, @type, @distance, @directions = frequency, type, distance, directions
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Metar::Data::Observer < Metar::Data::Base
|
2
|
+
def self.parse(raw)
|
3
|
+
case
|
4
|
+
when raw == 'AUTO' # WMO 15.4
|
5
|
+
new(raw, value: :auto)
|
6
|
+
when raw == 'COR' # WMO specified code word for correction
|
7
|
+
new(raw, value: :corrected)
|
8
|
+
when raw =~ /CC[A-Z]/ # Canadian correction
|
9
|
+
# Canada uses CCA for first correction, CCB for second, etc...
|
10
|
+
new(raw, value: :corrected)
|
11
|
+
when raw == 'RTD' # Delayed observation, no comments on observer
|
12
|
+
new(raw, value: :rtd)
|
13
|
+
else
|
14
|
+
new(nil, value: :real)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :value
|
19
|
+
|
20
|
+
def initialize(raw, value:)
|
21
|
+
@raw = raw
|
22
|
+
@value = value
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Metar::Data::Pressure < Metar::Data::Base
|
2
|
+
def self.parse(raw)
|
3
|
+
case
|
4
|
+
when raw =~ /^Q(\d{4})$/
|
5
|
+
new(raw, pressure: M9t::Pressure.hectopascals($1.to_f))
|
6
|
+
when raw =~ /^A(\d{4})$/
|
7
|
+
new(raw, pressure: M9t::Pressure.inches_of_mercury($1.to_f / 100.0))
|
8
|
+
else
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :pressure
|
14
|
+
|
15
|
+
def initialize(raw, pressure:)
|
16
|
+
@raw = raw
|
17
|
+
@pressure = pressure
|
18
|
+
end
|
19
|
+
|
20
|
+
def value
|
21
|
+
pressure.value
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "i18n"
|
2
|
+
require "m9t"
|
3
|
+
|
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
|
+
case raw
|
27
|
+
when /^([12])([01])(\d{3})$/
|
28
|
+
extreme = {'1' => :maximum, '2' => :minimum}[$1]
|
29
|
+
value = sign($2) * tenths($3)
|
30
|
+
Metar::Data::TemperatureExtreme.new(raw, extreme, value)
|
31
|
+
when /^4([01])(\d{3})([01])(\d{3})$/
|
32
|
+
[
|
33
|
+
Metar::Data::TemperatureExtreme.new(raw, :maximum, sign($1) * tenths($2)),
|
34
|
+
Metar::Data::TemperatureExtreme.new(raw, :minimum, sign($3) * tenths($4)),
|
35
|
+
]
|
36
|
+
when /^5([0-8])(\d{3})$/
|
37
|
+
character = PRESSURE_CHANGE_CHARACTER[$1.to_i]
|
38
|
+
Metar::Data::PressureTendency.new(raw, character, tenths($2))
|
39
|
+
when /^6(\d{4})$/
|
40
|
+
Metar::Data::Precipitation.new(raw, 3, Metar::Data::Distance.new(inches_to_meters($1))) # actually 3 or 6 depending on reporting time
|
41
|
+
when /^7(\d{4})$/
|
42
|
+
Metar::Data::Precipitation.new(raw, 24, Metar::Data::Distance.new(inches_to_meters($1)))
|
43
|
+
when /^A[0O]([12])$/
|
44
|
+
type = [:with_precipitation_discriminator, :without_precipitation_discriminator][$1.to_i - 1]
|
45
|
+
Metar::Data::AutomatedStationType.new(raw, type)
|
46
|
+
when /^P(\d{4})$/
|
47
|
+
Metar::Data::Precipitation.new(raw, 1, Metar::Data::Distance.new(inches_to_meters($1)))
|
48
|
+
when /^T([01])(\d{3})([01])(\d{3})$/
|
49
|
+
temperature = Metar::Data::Temperature.new(sign($1) * tenths($2))
|
50
|
+
dew_point = Metar::Data::Temperature.new(sign($3) * tenths($4))
|
51
|
+
Metar::Data::HourlyTemperatureAndDewPoint.new(raw, temperature, dew_point)
|
52
|
+
when /^SLP(\d{3})$/
|
53
|
+
Metar::Data::SeaLevelPressure.new(raw, M9t::Pressure.hectopascals(tenths($1)))
|
54
|
+
when /^(#{INDICATOR_TYPE.keys.join('|')})NO$/
|
55
|
+
type = INDICATOR_TYPE[$1]
|
56
|
+
Metar::Data::SensorStatusIndicator.new(raw, :type, :not_available)
|
57
|
+
when /^(#{COLOR_CODE.join('|')})$/
|
58
|
+
Metar::Data::ColorCode.new(raw, $1)
|
59
|
+
when 'SKC'
|
60
|
+
Metar::Data::SkyCondition.new(raw)
|
61
|
+
when '$'
|
62
|
+
Metar::Data::MaintenanceNeeded.new(raw)
|
63
|
+
else
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.sign(digit)
|
69
|
+
case digit
|
70
|
+
when '0'
|
71
|
+
1.0
|
72
|
+
when '1'
|
73
|
+
-1.0
|
74
|
+
else
|
75
|
+
raise "Unexpected sign: #{digit}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.tenths(digits)
|
80
|
+
digits.to_f / 10.0
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.inches_to_meters(digits)
|
84
|
+
digits.to_f * 0.000254
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
module Metar::Data
|
89
|
+
TemperatureExtreme = Struct.new(:raw, :extreme, :value)
|
90
|
+
PressureTendency = Struct.new(:raw, :character, :value)
|
91
|
+
Precipitation = Struct.new(:raw, :period, :amount)
|
92
|
+
AutomatedStationType = Struct.new(:raw, :type)
|
93
|
+
HourlyTemperatureAndDewPoint = Struct.new(:raw, :temperature, :dew_point)
|
94
|
+
SeaLevelPressure = Struct.new(:raw, :pressure)
|
95
|
+
SensorStatusIndicator = Struct.new(:raw, :type, :state)
|
96
|
+
ColorCode = Struct.new(:raw, :code)
|
97
|
+
MaintenanceNeeded = Struct.new(:raw)
|
98
|
+
end
|
@@ -0,0 +1,85 @@
|
|
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}
|
5
|
+
|
6
|
+
def self.parse(raw)
|
7
|
+
case
|
8
|
+
when raw =~ /^R(\d+[RLC]?)\/(P|M|)(\d{4})(FT|)\/?(N|U|D|)$/
|
9
|
+
designator = $1
|
10
|
+
comparator = COMPARATOR[$2]
|
11
|
+
count = $3.to_f
|
12
|
+
units = UNITS[$4]
|
13
|
+
tendency = TENDENCY[$5]
|
14
|
+
distance = Metar::Data::Distance.send(units, count)
|
15
|
+
visibility = Metar::Data::Visibility.new(
|
16
|
+
nil, distance: distance, comparator: comparator
|
17
|
+
)
|
18
|
+
new(
|
19
|
+
raw,
|
20
|
+
designator: designator, visibility1: visibility, tendency: tendency
|
21
|
+
)
|
22
|
+
when raw =~ /^R(\d+[RLC]?)\/(P|M|)(\d{4})V(P|M|)(\d{4})(FT|)\/?(N|U|D)?$/
|
23
|
+
designator = $1
|
24
|
+
comparator1 = COMPARATOR[$2]
|
25
|
+
count1 = $3.to_f
|
26
|
+
comparator2 = COMPARATOR[$4]
|
27
|
+
count2 = $5.to_f
|
28
|
+
units = UNITS[$6]
|
29
|
+
tendency = TENDENCY[$7]
|
30
|
+
distance1 = Metar::Data::Distance.send(units, count1)
|
31
|
+
distance2 = Metar::Data::Distance.send(units, count2)
|
32
|
+
visibility1 = Metar::Data::Visibility.new(
|
33
|
+
nil, distance: distance1, comparator: comparator1
|
34
|
+
)
|
35
|
+
visibility2 = Metar::Data::Visibility.new(
|
36
|
+
nil, distance: distance2, comparator: comparator2
|
37
|
+
)
|
38
|
+
new(
|
39
|
+
raw,
|
40
|
+
designator: designator,
|
41
|
+
visibility1: visibility1, visibility2: visibility2,
|
42
|
+
tendency: tendency, units: units
|
43
|
+
)
|
44
|
+
else
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_reader :designator, :visibility1, :visibility2, :tendency
|
50
|
+
|
51
|
+
def initialize(
|
52
|
+
raw,
|
53
|
+
designator:, visibility1:, visibility2: nil, tendency: nil, units: :meters
|
54
|
+
)
|
55
|
+
@raw = raw
|
56
|
+
@designator, @visibility1, @visibility2, @tendency, @units = designator, visibility1, visibility2, tendency, units
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_s
|
60
|
+
distance_options = {
|
61
|
+
abbreviated: true,
|
62
|
+
precision: 0,
|
63
|
+
units: @units,
|
64
|
+
}
|
65
|
+
s =
|
66
|
+
if @visibility2.nil?
|
67
|
+
I18n.t('metar.runway_visible_range.runway') +
|
68
|
+
' ' + @designator +
|
69
|
+
': ' + @visibility1.to_s(distance_options)
|
70
|
+
else
|
71
|
+
I18n.t('metar.runway_visible_range.runway') +
|
72
|
+
' ' + @designator +
|
73
|
+
': ' + I18n.t('metar.runway_visible_range.from') +
|
74
|
+
' ' + @visibility1.to_s(distance_options) +
|
75
|
+
' ' + I18n.t('metar.runway_visible_range.to') +
|
76
|
+
' ' + @visibility2.to_s(distance_options)
|
77
|
+
end
|
78
|
+
|
79
|
+
if ! tendency.nil?
|
80
|
+
s += ' ' + I18n.t("tendency.#{tendency}")
|
81
|
+
end
|
82
|
+
|
83
|
+
s
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,61 @@
|
|
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
|
+
]
|
15
|
+
|
16
|
+
def self.parse(raw)
|
17
|
+
case
|
18
|
+
when CLEAR_SKIES.include?(raw)
|
19
|
+
new(raw)
|
20
|
+
when raw =~ /^(BKN|FEW|OVC|SCT)(\d+|\/{3})(CB|TCU|\/{3}|)?$/
|
21
|
+
quantity = QUANTITY[$1]
|
22
|
+
height =
|
23
|
+
if $2 == '///'
|
24
|
+
nil
|
25
|
+
else
|
26
|
+
Metar::Data::Distance.new($2.to_i * 30.48)
|
27
|
+
end
|
28
|
+
type = CONDITION[$3]
|
29
|
+
new(raw, quantity: quantity, height: height, type: type)
|
30
|
+
when raw =~ /^(CB|TCU)$/
|
31
|
+
type = CONDITION[$1]
|
32
|
+
new(raw, type: type)
|
33
|
+
else
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :quantity, :height, :type
|
39
|
+
|
40
|
+
def initialize(raw, quantity: nil, height: nil, type: nil)
|
41
|
+
@raw = raw
|
42
|
+
@quantity, @height, @type = quantity, height, type
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
if @height.nil?
|
47
|
+
to_summary
|
48
|
+
else
|
49
|
+
to_summary + ' ' + I18n.t('metar.altitude.at') + ' ' + height.to_s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_summary
|
54
|
+
if @quantity == nil and @height == nil and @type == nil
|
55
|
+
I18n.t('metar.sky_conditions.clear skies')
|
56
|
+
else
|
57
|
+
type = @type ? ' ' + @type : ''
|
58
|
+
I18n.t("metar.sky_conditions.#{@quantity}#{type}")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "i18n"
|
2
|
+
require "m9t"
|
3
|
+
|
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
|
+
}
|
12
|
+
|
13
|
+
def self.parse(raw)
|
14
|
+
case
|
15
|
+
when raw =~ /^(\d+)(|KT|MPS|KMH)$/
|
16
|
+
# Call the appropriate factory method for the supplied units
|
17
|
+
send(METAR_UNITS[$2], $1.to_i)
|
18
|
+
else
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "i18n"
|
2
|
+
require "m9t"
|
3
|
+
|
4
|
+
# Adds a parse method to the M9t base class
|
5
|
+
class Metar::Data::Temperature < M9t::Temperature
|
6
|
+
def self.parse(raw)
|
7
|
+
if raw =~ /^(M?)(\d+)$/
|
8
|
+
sign = $1
|
9
|
+
value = $2.to_i
|
10
|
+
value *= -1 if sign == 'M'
|
11
|
+
new(value)
|
12
|
+
else
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s(options = {})
|
18
|
+
options = {abbreviated: true, precision: 0}.merge(options)
|
19
|
+
super(options)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Metar::Data::TemperatureAndDewPoint < Metar::Data::Base
|
2
|
+
def self.parse(raw)
|
3
|
+
if raw =~ /^(M?\d+|XX|\/\/)\/(M?\d+|XX|\/\/)?$/
|
4
|
+
temperature = Metar::Data::Temperature.parse($1)
|
5
|
+
dew_point = Metar::Data::Temperature.parse($2)
|
6
|
+
new(raw, temperature: temperature, dew_point: dew_point)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :temperature
|
11
|
+
attr_reader :dew_point
|
12
|
+
|
13
|
+
def initialize(raw, temperature:, dew_point:)
|
14
|
+
@raw = raw
|
15
|
+
@temperature = temperature
|
16
|
+
@dew_point = dew_point
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class Metar::Data::Time < Metar::Data::Base
|
2
|
+
def self.parse(raw, year: nil, month: nil, strict: true)
|
3
|
+
year ||= DateTime.now.year
|
4
|
+
month ||= DateTime.now.month
|
5
|
+
|
6
|
+
date_matcher =
|
7
|
+
if strict
|
8
|
+
/^(\d{2})(\d{2})(\d{2})Z$/
|
9
|
+
else
|
10
|
+
/^(\d{1,2})(\d{2})(\d{2})Z$/
|
11
|
+
end
|
12
|
+
|
13
|
+
if raw =~ date_matcher
|
14
|
+
day, hour, minute = $1.to_i, $2.to_i, $3.to_i
|
15
|
+
else
|
16
|
+
return nil if strict
|
17
|
+
|
18
|
+
if raw =~ /^(\d{1,2})(\d{2})Z$/
|
19
|
+
# The day is missing, use today's date
|
20
|
+
day = Time.now.day
|
21
|
+
hour, minute = $1.to_i, $2.to_i
|
22
|
+
else
|
23
|
+
return nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
new(
|
28
|
+
raw,
|
29
|
+
strict: strict,
|
30
|
+
year: year, month: month, day: day, hour: hour, minute: minute
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :strict
|
35
|
+
attr_reader :year
|
36
|
+
attr_reader :month
|
37
|
+
attr_reader :day
|
38
|
+
attr_reader :hour
|
39
|
+
attr_reader :minute
|
40
|
+
|
41
|
+
def initialize(raw, strict:, year:, month:, day:, hour:, minute:)
|
42
|
+
@raw = raw
|
43
|
+
@strict = strict
|
44
|
+
@year = year
|
45
|
+
@month = month
|
46
|
+
@day = day
|
47
|
+
@hour = hour
|
48
|
+
@minute = minute
|
49
|
+
end
|
50
|
+
|
51
|
+
def value
|
52
|
+
Time.gm(year, month, day, hour, minute)
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Metar::Data::VariableWind < Metar::Data::Base
|
2
|
+
def self.parse(raw)
|
3
|
+
if raw =~ /^(\d+)V(\d+)$/
|
4
|
+
new(
|
5
|
+
raw,
|
6
|
+
direction1: Metar::Data::Direction.new($1),
|
7
|
+
direction2: Metar::Data::Direction.new($2)
|
8
|
+
)
|
9
|
+
else
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :direction1
|
15
|
+
attr_reader :direction2
|
16
|
+
|
17
|
+
def initialize(raw, direction1:, direction2:)
|
18
|
+
@raw = raw
|
19
|
+
@direction1, @direction2 = direction1, direction2
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"#{direction1.to_s(units: :compass)} - #{direction2.to_s(units: :compass)}"
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "i18n"
|
2
|
+
require "m9t"
|
3
|
+
|
4
|
+
class Metar::Data::VerticalVisibility < Metar::Data::Base
|
5
|
+
def self.parse(raw)
|
6
|
+
case
|
7
|
+
when raw =~ /^VV(\d{3})$/
|
8
|
+
new(raw, distance: Metar::Data::Distance.new($1.to_f * 30.48))
|
9
|
+
when raw == '///'
|
10
|
+
new(raw, distance: Metar::Data::Distance.new)
|
11
|
+
else
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :distance
|
17
|
+
|
18
|
+
def initialize(raw, distance:)
|
19
|
+
@raw = raw
|
20
|
+
@distance = distance
|
21
|
+
end
|
22
|
+
|
23
|
+
def value
|
24
|
+
distance.value
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Metar::Data::Visibility < Metar::Data::Base
|
2
|
+
def self.parse(raw)
|
3
|
+
case
|
4
|
+
when raw == '9999'
|
5
|
+
new(raw, distance: Metar::Data::Distance.new(10000), comparator: :more_than)
|
6
|
+
when raw =~ /(\d{4})NDV/ # WMO
|
7
|
+
new(raw, distance: Metar::Data::Distance.new($1.to_f)) # Assuming meters
|
8
|
+
when (raw =~ /^((1|2)\s|)([1357])\/([248]|16)SM$/) # US
|
9
|
+
miles = $1.to_f + $3.to_f / $4.to_f
|
10
|
+
distance = Metar::Data::Distance.miles(miles)
|
11
|
+
distance.units = :miles
|
12
|
+
new(raw, distance: distance)
|
13
|
+
when raw =~ /^(\d+)SM$/ # US
|
14
|
+
distance = Metar::Data::Distance.miles($1.to_f)
|
15
|
+
distance.units = :miles
|
16
|
+
new(raw, distance: distance)
|
17
|
+
when raw == 'M1/4SM' # US
|
18
|
+
distance = Metar::Data::Distance.miles(0.25)
|
19
|
+
distance.units = :miles
|
20
|
+
new(raw, distance: distance, comparator: :less_than)
|
21
|
+
when raw =~ /^(\d+)KM$/
|
22
|
+
new(raw, distance: Metar::Data::Distance.kilometers($1))
|
23
|
+
when raw =~ /^(\d+)$/ # We assume meters
|
24
|
+
new(raw, distance: Metar::Data::Distance.new($1))
|
25
|
+
when raw =~ /^(\d+)(N|NE|E|SE|S|SW|W|NW)$/
|
26
|
+
new(
|
27
|
+
raw,
|
28
|
+
distance: Metar::Data::Distance.meters($1),
|
29
|
+
direction: M9t::Direction.compass($2)
|
30
|
+
)
|
31
|
+
else
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :distance, :direction, :comparator
|
37
|
+
|
38
|
+
def initialize(raw, distance:, direction: nil, comparator: nil)
|
39
|
+
@raw = raw
|
40
|
+
@distance, @direction, @comparator = distance, direction, comparator
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s(options = {})
|
44
|
+
distance_options = {
|
45
|
+
abbreviated: true,
|
46
|
+
precision: 0,
|
47
|
+
units: :kilometers,
|
48
|
+
}.merge(options)
|
49
|
+
direction_options = {units: :compass}
|
50
|
+
case
|
51
|
+
when (@direction.nil? and @comparator.nil?)
|
52
|
+
@distance.to_s(distance_options)
|
53
|
+
when @comparator.nil?
|
54
|
+
[
|
55
|
+
@distance.to_s(distance_options),
|
56
|
+
@direction.to_s(direction_options),
|
57
|
+
].join(' ')
|
58
|
+
when @direction.nil?
|
59
|
+
[
|
60
|
+
I18n.t('comparison.' + @comparator.to_s),
|
61
|
+
@distance.to_s(distance_options),
|
62
|
+
].join(' ')
|
63
|
+
else
|
64
|
+
[
|
65
|
+
I18n.t('comparison.' + @comparator.to_s),
|
66
|
+
@distance.to_s(distance_options),
|
67
|
+
@direction.to_s(direction_options),
|
68
|
+
].join(' ')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|