metar-parser 1.5.0 → 1.6.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 +28 -19
  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 +43 -44
  58. metadata +56 -42
@@ -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::Temperature < M9t::Temperature
6
- def self.parse(raw)
7
- return nil if !raw
8
- m = raw.match(/^(M?)(\d+)$/)
9
- return nil if !m
10
- sign = m[1]
11
- value = m[2].to_i
12
- value *= -1 if sign == 'M'
13
- new(value)
14
- end
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
- def to_s(options = {})
17
- options = {abbreviated: true, precision: 0}.merge(options)
18
- super(options)
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
- class Metar::Data::TemperatureAndDewPoint < Metar::Data::Base
2
- def self.parse(raw)
3
- return nil if !raw
1
+ # frozen_string_literal: true
4
2
 
5
- m = raw.match(/^(M?\d+|XX|\/\/)\/(M?\d+|XX|\/\/)?$/)
6
- return nil if !m
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
- temperature = Metar::Data::Temperature.parse(m[1])
9
- dew_point = Metar::Data::Temperature.parse(m[2])
10
- new(raw, temperature: temperature, dew_point: dew_point)
11
- end
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
- attr_reader :temperature
14
- attr_reader :dew_point
15
-
16
- def initialize(raw, temperature:, dew_point:)
17
- @raw = raw
18
- @temperature = temperature
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
@@ -1,53 +1,63 @@
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
1
+ # frozen_string_literal: true
12
2
 
13
- m1 = raw.match(date_matcher)
14
- if m1
15
- day, hour, minute = m1[1].to_i, m1[2].to_i, m1[3].to_i
16
- else
17
- return nil if strict
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
- new(
27
- raw,
28
- strict: strict,
29
- year: year, month: month, day: day, hour: hour, minute: minute
30
- )
31
- end
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
- attr_reader :strict
34
- attr_reader :year
35
- attr_reader :month
36
- attr_reader :day
37
- attr_reader :hour
38
- attr_reader :minute
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
- def value
51
- Time.gm(year, month, day, hour, minute)
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
- class Metar::Data::VariableWind < Metar::Data::Base
2
- def self.parse(raw)
3
- return nil if raw.nil?
1
+ # frozen_string_literal: true
4
2
 
5
- m = raw.match(/^(\d+)V(\d+)$/)
6
- return nil if m.nil?
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
- return new(
9
- raw,
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
- attr_reader :direction1
16
- attr_reader :direction2
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
- def initialize(raw, direction1:, direction2:)
19
- @raw = raw
20
- @direction1, @direction2 = direction1, direction2
21
- end
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
- def to_s
24
- "#{direction1.to_s(units: :compass)} - #{direction2.to_s(units: :compass)}"
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
- class Metar::Data::VerticalVisibility < Metar::Data::Base
5
- def self.parse(raw)
6
- if !raw
7
- return nil
8
- end
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
- if raw == '///'
15
- return new(raw, distance: Metar::Data::Distance.new)
16
- end
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
- nil
19
- end
20
+ return new(raw, distance: Metar::Data::Distance.new) if raw == '///'
20
21
 
21
- attr_reader :distance
22
+ nil
23
+ end
22
24
 
23
- def initialize(raw, distance:)
24
- @raw = raw
25
- @distance = distance
26
- end
25
+ attr_reader :distance
27
26
 
28
- def value
29
- distance.value
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
- class Metar::Data::Visibility < Metar::Data::Base
2
- def self.parse(raw)
3
- if !raw
4
- return nil
5
- end
1
+ # frozen_string_literal: true
6
2
 
7
- if raw == '9999'
8
- return new(raw, distance: Metar::Data::Distance.new(10000), comparator: :more_than)
9
- end
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
- m1 = raw.match(/(\d{4})NDV/) # WMO
12
- if m1
13
- return new(raw, distance: Metar::Data::Distance.new(m1[1].to_f)) # Assuming meters
14
- end
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
- m2 = raw.match(/^((1|2)\s|)([1357])\/([248]|16)SM$/) # US
17
- if m2
18
- miles = m2[1].to_f + m2[3].to_f / m2[4].to_f
19
- distance = Metar::Data::Distance.miles(miles)
20
- distance.serialization_units = :miles
21
- return new(raw, distance: distance)
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
- m3 = raw.match(/^(\d+)SM$/) # US
25
- if m3
26
- distance = Metar::Data::Distance.miles(m3[1].to_f)
27
- distance.serialization_units = :miles
28
- return new(raw, distance: distance)
29
- end
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
- if raw == 'M1/4SM' # US
32
- distance = Metar::Data::Distance.miles(0.25)
33
- distance.serialization_units = :miles
34
- return new(raw, distance: distance, comparator: :less_than)
35
- end
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
- m4 = raw.match(/^(\d+)KM$/)
38
- if m4
39
- return new(raw, distance: Metar::Data::Distance.kilometers(m4[1]))
40
- end
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
- m5 = raw.match(/^(\d+)$/) # We assume meters
43
- if m5
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
- m6 = raw.match(/^(\d+)(N|NE|E|SE|S|SW|W|NW)$/)
48
- if m6
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
- nil
57
- end
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
- attr_reader :distance, :direction, :comparator
62
+ nil
63
+ end
60
64
 
61
- def initialize(raw, distance:, direction: nil, comparator: nil)
62
- @raw = raw
63
- @distance, @direction, @comparator = distance, direction, comparator
64
- end
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
- def to_s(options = {})
67
- distance_options = {
68
- abbreviated: true,
69
- precision: 0,
70
- units: :kilometers,
71
- }.merge(options)
72
- direction_options = {units: :compass}
73
- case
74
- when (@direction.nil? and @comparator.nil?)
75
- @distance.to_s(distance_options)
76
- when @comparator.nil?
77
- [
78
- @distance.to_s(distance_options),
79
- @direction.to_s(direction_options),
80
- ].join(' ')
81
- when @direction.nil?
82
- [
83
- I18n.t('comparison.' + @comparator.to_s),
84
- @distance.to_s(distance_options),
85
- ].join(' ')
86
- else
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
- class Metar::Data::VisibilityRemark < Metar::Data::Visibility
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
- new(raw, distance: distance, direction: direction, comparator: :more_than)
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