metar-parser 1.5.0 → 1.6.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 +28 -19
- 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 +43 -44
- metadata +56 -42
@@ -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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
module Metar
|
7
|
+
module Data
|
8
|
+
class Temperature < M9t::Temperature
|
9
|
+
def self.parse(raw)
|
10
|
+
return nil if !raw
|
11
|
+
|
12
|
+
m = raw.match(/^(M?)(\d+)$/)
|
13
|
+
return nil if !m
|
14
|
+
|
15
|
+
sign = m[1]
|
16
|
+
value = m[2].to_i
|
17
|
+
value *= -1 if sign == 'M'
|
18
|
+
new(value)
|
19
|
+
end
|
15
20
|
|
16
|
-
|
17
|
-
|
18
|
-
|
21
|
+
def to_s(options = {})
|
22
|
+
options = {abbreviated: true, precision: 0}.merge(options)
|
23
|
+
super(options)
|
24
|
+
end
|
25
|
+
end
|
19
26
|
end
|
20
27
|
end
|
@@ -1,21 +1,27 @@
|
|
1
|
-
|
2
|
-
def self.parse(raw)
|
3
|
-
return nil if !raw
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
|
3
|
+
module Metar
|
4
|
+
module Data
|
5
|
+
class TemperatureAndDewPoint < Metar::Data::Base
|
6
|
+
def self.parse(raw)
|
7
|
+
return nil if !raw
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
m = raw.match(%r{^(M?\d+|XX|//)\/(M?\d+|XX|//)?$})
|
10
|
+
return nil if !m
|
11
|
+
|
12
|
+
temperature = Metar::Data::Temperature.parse(m[1])
|
13
|
+
dew_point = Metar::Data::Temperature.parse(m[2])
|
14
|
+
new(raw, temperature: temperature, dew_point: dew_point)
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :temperature
|
18
|
+
attr_reader :dew_point
|
12
19
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@dew_point = dew_point
|
20
|
+
def initialize(raw, temperature:, dew_point:)
|
21
|
+
@raw = raw
|
22
|
+
@temperature = temperature
|
23
|
+
@dew_point = dew_point
|
24
|
+
end
|
25
|
+
end
|
20
26
|
end
|
21
27
|
end
|
data/lib/metar/data/time.rb
CHANGED
@@ -1,53 +1,63 @@
|
|
1
|
-
|
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
|
1
|
+
# frozen_string_literal: true
|
12
2
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
m2 = raw.match(/^(\d{1,2})(\d{2})Z$/)
|
20
|
-
return nil if !m2
|
21
|
-
# The day is missing, use today's date
|
22
|
-
day = Time.now.day
|
23
|
-
hour, minute = m2[1].to_i, m2[2].to_i
|
24
|
-
end
|
3
|
+
module Metar
|
4
|
+
module Data
|
5
|
+
class Time < Metar::Data::Base
|
6
|
+
def self.parse(raw, year: nil, month: nil, strict: true)
|
7
|
+
year ||= DateTime.now.year
|
8
|
+
month ||= DateTime.now.month
|
25
9
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
10
|
+
date_matcher =
|
11
|
+
if strict
|
12
|
+
/^(\d{2})(\d{2})(\d{2})Z$/
|
13
|
+
else
|
14
|
+
/^(\d{1,2})(\d{2})(\d{2})Z$/
|
15
|
+
end
|
32
16
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
def initialize(raw, strict:, year:, month:, day:, hour:, minute:)
|
41
|
-
@raw = raw
|
42
|
-
@strict = strict
|
43
|
-
@year = year
|
44
|
-
@month = month
|
45
|
-
@day = day
|
46
|
-
@hour = hour
|
47
|
-
@minute = minute
|
48
|
-
end
|
17
|
+
m1 = raw.match(date_matcher)
|
18
|
+
if m1
|
19
|
+
day = m1[1].to_i
|
20
|
+
hour = m1[2].to_i
|
21
|
+
minute = m1[3].to_i
|
22
|
+
else
|
23
|
+
return nil if strict
|
49
24
|
|
50
|
-
|
51
|
-
|
25
|
+
m2 = raw.match(/^(\d{1,2})(\d{2})Z$/)
|
26
|
+
return nil if !m2
|
27
|
+
|
28
|
+
# The day is missing, use today's date
|
29
|
+
day = ::Time.now.day
|
30
|
+
hour = m2[1].to_i
|
31
|
+
minute = m2[2].to_i
|
32
|
+
end
|
33
|
+
|
34
|
+
new(
|
35
|
+
raw,
|
36
|
+
strict: strict,
|
37
|
+
year: year, month: month, day: day, hour: hour, minute: minute
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :strict
|
42
|
+
attr_reader :year
|
43
|
+
attr_reader :month
|
44
|
+
attr_reader :day
|
45
|
+
attr_reader :hour
|
46
|
+
attr_reader :minute
|
47
|
+
|
48
|
+
def initialize(raw, strict:, year:, month:, day:, hour:, minute:)
|
49
|
+
@raw = raw
|
50
|
+
@strict = strict
|
51
|
+
@year = year
|
52
|
+
@month = month
|
53
|
+
@day = day
|
54
|
+
@hour = hour
|
55
|
+
@minute = minute
|
56
|
+
end
|
57
|
+
|
58
|
+
def value
|
59
|
+
::Time.gm(year, month, day, hour, minute)
|
60
|
+
end
|
61
|
+
end
|
52
62
|
end
|
53
63
|
end
|
@@ -1,26 +1,37 @@
|
|
1
|
-
|
2
|
-
def self.parse(raw)
|
3
|
-
return nil if raw.nil?
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
|
3
|
+
module Metar
|
4
|
+
module Data
|
5
|
+
class VariableWind < Metar::Data::Base
|
6
|
+
def self.parse(raw)
|
7
|
+
return nil if raw.nil?
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
direction1: Metar::Data::Direction.new(m[1]),
|
11
|
-
direction2: Metar::Data::Direction.new(m[2])
|
12
|
-
)
|
13
|
-
end
|
9
|
+
m = raw.match(/^(\d+)V(\d+)$/)
|
10
|
+
return nil if m.nil?
|
14
11
|
|
15
|
-
|
16
|
-
|
12
|
+
new(
|
13
|
+
raw,
|
14
|
+
direction1: Metar::Data::Direction.new(m[1]),
|
15
|
+
direction2: Metar::Data::Direction.new(m[2])
|
16
|
+
)
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
attr_reader :direction1
|
20
|
+
attr_reader :direction2
|
21
|
+
|
22
|
+
def initialize(raw, direction1:, direction2:)
|
23
|
+
@raw = raw
|
24
|
+
@direction1 = direction1
|
25
|
+
@direction2 = direction2
|
26
|
+
end
|
22
27
|
|
23
|
-
|
24
|
-
|
28
|
+
def to_s
|
29
|
+
format(
|
30
|
+
"%<direction1>s - %<direction2>s",
|
31
|
+
direction1: direction1.to_s(units: :compass),
|
32
|
+
direction2: direction2.to_s(units: :compass)
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
25
36
|
end
|
26
37
|
end
|
@@ -1,31 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "i18n"
|
2
4
|
require "m9t"
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
m1 = raw.match(/^VV(\d{3})$/)
|
10
|
-
if m1
|
11
|
-
return new(raw, distance: Metar::Data::Distance.new(m1[1].to_f * 30.48))
|
12
|
-
end
|
6
|
+
module Metar
|
7
|
+
module Data
|
8
|
+
class VerticalVisibility < Metar::Data::Base
|
9
|
+
def self.parse(raw)
|
10
|
+
return nil if !raw
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
m1 = raw.match(/^VV(\d{3})$/)
|
13
|
+
if m1
|
14
|
+
return new(
|
15
|
+
raw,
|
16
|
+
distance: Metar::Data::Distance.new(m1[1].to_f * 30.48)
|
17
|
+
)
|
18
|
+
end
|
17
19
|
|
18
|
-
|
19
|
-
end
|
20
|
+
return new(raw, distance: Metar::Data::Distance.new) if raw == '///'
|
20
21
|
|
21
|
-
|
22
|
+
nil
|
23
|
+
end
|
22
24
|
|
23
|
-
|
24
|
-
@raw = raw
|
25
|
-
@distance = distance
|
26
|
-
end
|
25
|
+
attr_reader :distance
|
27
26
|
|
28
|
-
|
29
|
-
|
27
|
+
def initialize(raw, distance:)
|
28
|
+
@raw = raw
|
29
|
+
@distance = distance
|
30
|
+
end
|
31
|
+
|
32
|
+
def value
|
33
|
+
distance.value
|
34
|
+
end
|
35
|
+
end
|
30
36
|
end
|
31
37
|
end
|
@@ -1,94 +1,106 @@
|
|
1
|
-
|
2
|
-
def self.parse(raw)
|
3
|
-
if !raw
|
4
|
-
return nil
|
5
|
-
end
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
module Metar
|
4
|
+
module Data
|
5
|
+
class Visibility < Metar::Data::Base
|
6
|
+
def self.parse(raw)
|
7
|
+
return nil if !raw
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
if raw == '9999'
|
10
|
+
return new(
|
11
|
+
raw,
|
12
|
+
distance: Metar::Data::Distance.new(10_000),
|
13
|
+
comparator: :more_than
|
14
|
+
)
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
17
|
+
m1 = raw.match(/(\d{4})NDV/) # WMO
|
18
|
+
if m1
|
19
|
+
return new(
|
20
|
+
raw, distance: Metar::Data::Distance.new(m1[1].to_f)
|
21
|
+
) # Assuming meters
|
22
|
+
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
m2 = raw.match(%r{^((1|2)\s|)([1357])/([248]|16)SM$}) # US
|
25
|
+
if m2
|
26
|
+
numerator = m2[3].to_f
|
27
|
+
denominator = m2[4].to_f
|
28
|
+
miles = m2[1].to_f + numerator / denominator
|
29
|
+
distance = Metar::Data::Distance.miles(miles)
|
30
|
+
distance.serialization_units = :miles
|
31
|
+
return new(raw, distance: distance)
|
32
|
+
end
|
30
33
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
34
|
+
m3 = raw.match(/^(\d+)SM$/) # US
|
35
|
+
if m3
|
36
|
+
distance = Metar::Data::Distance.miles(m3[1].to_f)
|
37
|
+
distance.serialization_units = :miles
|
38
|
+
return new(raw, distance: distance)
|
39
|
+
end
|
36
40
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
+
if raw == 'M1/4SM' # US
|
42
|
+
distance = Metar::Data::Distance.miles(0.25)
|
43
|
+
distance.serialization_units = :miles
|
44
|
+
return new(raw, distance: distance, comparator: :less_than)
|
45
|
+
end
|
41
46
|
|
42
|
-
|
43
|
-
|
44
|
-
return new(raw, distance: Metar::Data::Distance.new(m5[1]))
|
45
|
-
end
|
47
|
+
m4 = raw.match(/^(\d+)KM$/)
|
48
|
+
return new(raw, distance: Metar::Data::Distance.kilometers(m4[1])) if m4
|
46
49
|
|
47
|
-
|
48
|
-
|
49
|
-
return new(
|
50
|
-
raw,
|
51
|
-
distance: Metar::Data::Distance.meters(m6[1]),
|
52
|
-
direction: M9t::Direction.compass(m6[2])
|
53
|
-
)
|
54
|
-
end
|
50
|
+
m5 = raw.match(/^(\d+)$/) # We assume meters
|
51
|
+
return new(raw, distance: Metar::Data::Distance.new(m5[1])) if m5
|
55
52
|
|
56
|
-
|
57
|
-
|
53
|
+
m6 = raw.match(/^(\d+)(N|NE|E|SE|S|SW|W|NW)$/)
|
54
|
+
if m6
|
55
|
+
return new(
|
56
|
+
raw,
|
57
|
+
distance: Metar::Data::Distance.meters(m6[1]),
|
58
|
+
direction: M9t::Direction.compass(m6[2])
|
59
|
+
)
|
60
|
+
end
|
58
61
|
|
59
|
-
|
62
|
+
nil
|
63
|
+
end
|
60
64
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
+
attr_reader :distance, :direction, :comparator
|
66
|
+
|
67
|
+
def initialize(raw, distance:, direction: nil, comparator: nil)
|
68
|
+
@raw = raw
|
69
|
+
@distance = distance
|
70
|
+
@direction = direction
|
71
|
+
@comparator = comparator
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_s(options = {})
|
75
|
+
distance_options = {
|
76
|
+
abbreviated: true,
|
77
|
+
precision: 0,
|
78
|
+
units: :kilometers
|
79
|
+
}.merge(options)
|
80
|
+
|
81
|
+
direction_options = {units: :compass}
|
65
82
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
[
|
88
|
-
I18n.t('comparison.' + @comparator.to_s),
|
89
|
-
@distance.to_s(distance_options),
|
90
|
-
@direction.to_s(direction_options),
|
91
|
-
].join(' ')
|
83
|
+
case
|
84
|
+
when @direction.nil? && @comparator.nil?
|
85
|
+
@distance.to_s(distance_options)
|
86
|
+
when @comparator.nil?
|
87
|
+
[
|
88
|
+
@distance.to_s(distance_options),
|
89
|
+
@direction.to_s(direction_options)
|
90
|
+
].join(' ')
|
91
|
+
when @direction.nil?
|
92
|
+
[
|
93
|
+
I18n.t('comparison.' + @comparator.to_s),
|
94
|
+
@distance.to_s(distance_options)
|
95
|
+
].join(' ')
|
96
|
+
else
|
97
|
+
[
|
98
|
+
I18n.t('comparison.' + @comparator.to_s),
|
99
|
+
@distance.to_s(distance_options),
|
100
|
+
@direction.to_s(direction_options)
|
101
|
+
].join(' ')
|
102
|
+
end
|
103
|
+
end
|
92
104
|
end
|
93
105
|
end
|
94
106
|
end
|
@@ -1,8 +1,19 @@
|
|
1
|
-
|
2
|
-
def self.parse(raw)
|
3
|
-
metres, direction = raw.scan(/^(\d{4})([NESW]?)$/)[0]
|
4
|
-
distance = Metar::Data::Distance.new(metres)
|
1
|
+
# frozen_string_literal: true
|
5
2
|
|
6
|
-
|
3
|
+
module Metar
|
4
|
+
module Data
|
5
|
+
class VisibilityRemark < Metar::Data::Visibility
|
6
|
+
def self.parse(raw)
|
7
|
+
metres, direction = raw.scan(/^(\d{4})([NESW]?)$/)[0]
|
8
|
+
distance = Metar::Data::Distance.new(metres)
|
9
|
+
|
10
|
+
new(
|
11
|
+
raw,
|
12
|
+
distance: distance,
|
13
|
+
direction: direction,
|
14
|
+
comparator: :more_than
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
7
18
|
end
|
8
19
|
end
|