metar-parser 1.4.2 → 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 +7 -49
  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 -13
  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 +106 -87
  25. data/lib/metar/data.rb +25 -23
  26. data/lib/metar/i18n.rb +5 -2
  27. data/lib/metar/parser.rb +47 -22
  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 +4 -2
  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 +224 -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 -43
@@ -1,86 +1,104 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "metar/i18n"
2
4
 
3
- class Metar::Data::WeatherPhenomenon < Metar::Data::Base
4
- Modifiers = {
5
- '+' => 'heavy',
6
- '-' => 'light',
7
- 'VC' => 'nearby',
8
- '-VC' => 'nearby light',
9
- '+VC' => 'nearby heavy',
10
- }
5
+ module Metar
6
+ module Data
7
+ class WeatherPhenomenon < Metar::Data::Base
8
+ MODIFIERS = {
9
+ '+' => 'heavy',
10
+ '-' => 'light',
11
+ 'VC' => 'nearby',
12
+ '-VC' => 'nearby light',
13
+ '+VC' => 'nearby heavy'
14
+ }.freeze
11
15
 
12
- Descriptors = {
13
- 'BC' => 'patches of',
14
- 'BL' => 'blowing',
15
- 'DR' => 'low drifting',
16
- 'FZ' => 'freezing',
17
- 'MI' => 'shallow',
18
- 'PR' => 'partial',
19
- 'SH' => 'shower of',
20
- 'TS' => 'thunderstorm and',
21
- }
16
+ DESCRIPTORS = {
17
+ 'BC' => 'patches of',
18
+ 'BL' => 'blowing',
19
+ 'DR' => 'low drifting',
20
+ 'FZ' => 'freezing',
21
+ 'MI' => 'shallow',
22
+ 'PR' => 'partial',
23
+ 'SH' => 'shower of',
24
+ 'TS' => 'thunderstorm and'
25
+ }.freeze
22
26
 
23
- Phenomena = {
24
- 'BR' => 'mist',
25
- 'DU' => 'dust',
26
- 'DZ' => 'drizzle',
27
- 'FG' => 'fog',
28
- 'FU' => 'smoke',
29
- 'GR' => 'hail',
30
- 'GS' => 'small hail',
31
- 'HZ' => 'haze',
32
- 'IC' => 'ice crystals',
33
- 'PL' => 'ice pellets',
34
- 'PO' => 'dust whirls',
35
- 'PY' => 'spray', # US only
36
- 'RA' => 'rain',
37
- 'SA' => 'sand',
38
- 'SH' => 'shower',
39
- 'SN' => 'snow',
40
- 'SG' => 'snow grains',
41
- 'SQ' => 'squall',
42
- 'UP' => 'unknown phenomenon', # => AUTO
43
- 'VA' => 'volcanic ash',
44
- 'FC' => 'funnel cloud',
45
- 'SS' => 'sand storm',
46
- 'DS' => 'dust storm',
47
- 'TS' => 'thunderstorm',
48
- }
27
+ PHENOMENA = {
28
+ 'BR' => 'mist',
29
+ 'DU' => 'dust',
30
+ 'DZ' => 'drizzle',
31
+ 'FG' => 'fog',
32
+ 'FU' => 'smoke',
33
+ 'GR' => 'hail',
34
+ 'GS' => 'small hail',
35
+ 'HZ' => 'haze',
36
+ 'IC' => 'ice crystals',
37
+ 'PL' => 'ice pellets',
38
+ 'PO' => 'dust whirls',
39
+ 'PY' => 'spray', # US only
40
+ 'RA' => 'rain',
41
+ 'SA' => 'sand',
42
+ 'SH' => 'shower',
43
+ 'SN' => 'snow',
44
+ 'SG' => 'snow grains',
45
+ 'SQ' => 'squall',
46
+ 'UP' => 'unknown phenomenon', # => AUTO
47
+ 'VA' => 'volcanic ash',
48
+ 'FC' => 'funnel cloud',
49
+ 'SS' => 'sand storm',
50
+ 'DS' => 'dust storm',
51
+ 'TS' => 'thunderstorm'
52
+ }.freeze
49
53
 
50
- # Accepts all standard (and some non-standard) present weather codes
51
- def self.parse(raw)
52
- phenomena = Phenomena.keys.join('|')
53
- descriptors = Descriptors.keys.join('|')
54
- modifiers = Modifiers.keys.join('|')
55
- modifiers.gsub!(/([\+\-])/) { |m| "\\#{m}" }
56
- rxp = Regexp.new("^(RE)?(#{modifiers})?(#{descriptors})?((?:#{phenomena}){1,2})$")
57
- m = rxp.match(raw)
58
- return nil if m.nil?
54
+ # Accepts all standard (and some non-standard) present weather codes
55
+ def self.parse(raw)
56
+ modifiers = MODIFIERS.keys.join('|')
57
+ modifiers.gsub!(/([\+\-])/) { |m| "\\#{m}" }
59
58
 
60
- recent = m[1] == "RE"
61
- modifier_code = m[2]
62
- descriptor_code = m[3]
63
- phenomena_codes = m[4].scan(/../)
64
- phenomena_phrase = phenomena_codes.map { |c| Phenomena[c] }.join(' and ')
59
+ descriptors = DESCRIPTORS.keys.join('|')
65
60
 
66
- new(
67
- raw,
68
- phenomenon: phenomena_phrase,
69
- modifier: Modifiers[modifier_code],
70
- descriptor: Descriptors[descriptor_code]
71
- )
72
- end
61
+ phenomena = PHENOMENA.keys.join('|')
73
62
 
74
- attr_reader :phenomenon, :modifier, :descriptor, :recent
63
+ rxp = Regexp.new(
64
+ "^(RE)?(#{modifiers})?(#{descriptors})?((?:#{phenomena}){1,2})$"
65
+ )
75
66
 
76
- def initialize(raw, phenomenon:, modifier: nil, descriptor: nil, recent: false)
77
- @raw = raw
78
- @phenomenon, @modifier, @descriptor = phenomenon, modifier, descriptor
79
- @recent = recent
80
- end
67
+ m = rxp.match(raw)
68
+ return nil if m.nil?
69
+
70
+ recent = m[1] == "RE"
71
+ modifier_code = m[2]
72
+ descriptor_code = m[3]
73
+ phenomena_codes = m[4].scan(/../)
74
+ phenomena = phenomena_codes.map { |c| PHENOMENA[c] }
75
+ phenomena_phrase = phenomena.join(' and ')
76
+
77
+ new(
78
+ raw,
79
+ phenomenon: phenomena_phrase,
80
+ modifier: MODIFIERS[modifier_code],
81
+ descriptor: DESCRIPTORS[descriptor_code],
82
+ recent: recent
83
+ )
84
+ end
85
+
86
+ attr_reader :phenomenon, :modifier, :descriptor, :recent
87
+
88
+ def initialize(
89
+ raw, phenomenon:, modifier: nil, descriptor: nil, recent: false
90
+ )
91
+ @raw = raw
92
+ @phenomenon = phenomenon
93
+ @modifier = modifier
94
+ @descriptor = descriptor
95
+ @recent = recent
96
+ end
81
97
 
82
- def to_s
83
- key = [modifier, descriptor, phenomenon].compact.join(' ')
84
- I18n.t("metar.present_weather.%s" % key)
98
+ def to_s
99
+ key = [modifier, descriptor, phenomenon].compact.join(' ')
100
+ I18n.t("metar.present_weather.#{key}")
101
+ end
102
+ end
85
103
  end
86
104
  end
@@ -1,101 +1,120 @@
1
- class Metar::Data::Wind < Metar::Data::Base
2
- def self.parse(raw)
3
- return nil if raw.nil?
4
-
5
- m1 = raw.match(/^(\d{3})(\d{2}(|MPS|KMH|KT))$/)
6
- if m1
7
- return nil if m1[1].to_i > 360
8
- return new(
9
- raw,
10
- direction: Metar::Data::Direction.new(m1[1]),
11
- speed: Metar::Data::Speed.parse(m1[2])
12
- )
13
- end
1
+ # frozen_string_literal: true
14
2
 
15
- m2 = raw.match(/^(\d{3})(\d{2})G(\d{2,3}(|MPS|KMH|KT))$/)
16
- if m2
17
- return nil if m2[1].to_i > 360
18
- return new(
19
- raw,
20
- direction: Metar::Data::Direction.new(m2[1]),
21
- speed: Metar::Data::Speed.parse(m2[2] + m2[4]),
22
- gusts: Metar::Data::Speed.parse(m2[3])
23
- )
24
- end
3
+ module Metar
4
+ module Data
5
+ class Wind < Metar::Data::Base
6
+ def self.parse(raw, strict: false)
7
+ return nil if raw.nil?
25
8
 
26
- m3 = raw.match(/^VRB(\d{2})G(\d{2,3})(|MPS|KMH|KT)$/)
27
- if m3
28
- speed = m3[1] + m3[3]
29
- gusts = m3[2] + m3[3]
30
- return new(
31
- raw,
32
- direction: :variable_direction,
33
- speed: Metar::Data::Speed.parse(speed),
34
- gusts: Metar::Data::Speed.parse(gusts)
35
- )
36
- end
9
+ plain_match =
10
+ if strict
11
+ /^(\d{3})(\d{2}(|MPS|KMH|KT))$/
12
+ else
13
+ /^(\d{3})(\d{2,3}(|MPS|KMH|KT))$/
14
+ end
37
15
 
38
- m4 = raw.match(/^VRB(\d{2}(|MPS|KMH|KT))$/)
39
- if m4
40
- speed = Metar::Data::Speed.parse(m4[1])
41
- return new(raw, direction: :variable_direction, speed: speed)
42
- end
16
+ m1 = raw.match(plain_match)
17
+ if m1
18
+ return nil if m1[1].to_i > 360
43
19
 
44
- m5 = raw.match(/^\/{3}(\d{2}(|MPS|KMH|KT))$/)
45
- if m5
46
- speed = Metar::Data::Speed.parse(m5[1])
47
- return new(raw, direction: :unknown_direction, speed: speed)
48
- end
20
+ return new(
21
+ raw,
22
+ direction: Metar::Data::Direction.new(m1[1]),
23
+ speed: Metar::Data::Speed.parse(m1[2])
24
+ )
25
+ end
49
26
 
50
- m6 = raw.match(%r(^/////(|MPS|KMH|KT)$))
51
- if m6
52
- return new(raw, direction: :unknown_direction, speed: :unknown_speed)
53
- end
27
+ m2 = raw.match(/^(\d{3})(\d{2})G(\d{2,3}(|MPS|KMH|KT))$/)
28
+ if m2
29
+ return nil if m2[1].to_i > 360
54
30
 
55
- nil
56
- end
31
+ return new(
32
+ raw,
33
+ direction: Metar::Data::Direction.new(m2[1]),
34
+ speed: Metar::Data::Speed.parse(m2[2] + m2[4]),
35
+ gusts: Metar::Data::Speed.parse(m2[3])
36
+ )
37
+ end
57
38
 
58
- attr_reader :direction, :speed, :gusts
39
+ m3 = raw.match(/^VRB(\d{2})G(\d{2,3})(|MPS|KMH|KT)$/)
40
+ if m3
41
+ speed = m3[1] + m3[3]
42
+ gusts = m3[2] + m3[3]
43
+ return new(
44
+ raw,
45
+ direction: :variable_direction,
46
+ speed: Metar::Data::Speed.parse(speed),
47
+ gusts: Metar::Data::Speed.parse(gusts)
48
+ )
49
+ end
59
50
 
60
- def initialize(raw, direction:, speed:, gusts: nil)
61
- @raw = raw
62
- @direction, @speed, @gusts = direction, speed, gusts
63
- end
51
+ m4 = raw.match(/^VRB(\d{2}(|MPS|KMH|KT))$/)
52
+ if m4
53
+ speed = Metar::Data::Speed.parse(m4[1])
54
+ return new(raw, direction: :variable_direction, speed: speed)
55
+ end
64
56
 
65
- def to_s(options = {})
66
- options = {
67
- direction_units: :compass,
68
- speed_units: :kilometers_per_hour,
69
- }.merge(options)
70
- speed =
71
- case @speed
72
- when :unknown_speed
73
- I18n.t('metar.wind.unknown_speed')
74
- else
75
- @speed.to_s(
76
- abbreviated: true,
77
- precision: 0,
78
- units: options[:speed_units]
79
- )
57
+ m5 = raw.match(%r{^/{3}(\d{2}(|MPS|KMH|KT))$})
58
+ if m5
59
+ speed = Metar::Data::Speed.parse(m5[1])
60
+ return new(raw, direction: :unknown_direction, speed: speed)
61
+ end
62
+
63
+ m6 = raw.match(%r{^/////(|MPS|KMH|KT)$})
64
+ if m6
65
+ return new(raw, direction: :unknown_direction, speed: :unknown_speed)
66
+ end
67
+
68
+ nil
69
+ end
70
+
71
+ attr_reader :direction, :speed, :gusts
72
+
73
+ def initialize(raw, direction:, speed:, gusts: nil)
74
+ @raw = raw
75
+ @direction = direction
76
+ @speed = speed
77
+ @gusts = gusts
80
78
  end
81
- direction =
82
- case @direction
83
- when :variable_direction
84
- I18n.t('metar.wind.variable_direction')
85
- when :unknown_direction
86
- I18n.t('metar.wind.unknown_direction')
87
- else
88
- @direction.to_s(units: options[:direction_units])
79
+
80
+ def to_s(options = {})
81
+ options = {
82
+ direction_units: :compass,
83
+ speed_units: :kilometers_per_hour
84
+ }.merge(options)
85
+ speed =
86
+ case @speed
87
+ when :unknown_speed
88
+ I18n.t('metar.wind.unknown_speed')
89
+ else
90
+ @speed.to_s(
91
+ abbreviated: true,
92
+ precision: 0,
93
+ units: options[:speed_units]
94
+ )
95
+ end
96
+ direction =
97
+ case @direction
98
+ when :variable_direction
99
+ I18n.t('metar.wind.variable_direction')
100
+ when :unknown_direction
101
+ I18n.t('metar.wind.unknown_direction')
102
+ else
103
+ @direction.to_s(units: options[:direction_units])
104
+ end
105
+ s = "#{speed} #{direction}"
106
+
107
+ if !@gusts.nil?
108
+ g = @gusts.to_s(
109
+ abbreviated: true,
110
+ precision: 0,
111
+ units: options[:speed_units]
112
+ )
113
+ s += " #{I18n.t('metar.wind.gusts')} #{g}"
114
+ end
115
+
116
+ s
89
117
  end
90
- s = "#{speed} #{direction}"
91
- if not @gusts.nil?
92
- g = @gusts.to_s(
93
- abbreviated: true,
94
- precision: 0,
95
- units: options[:speed_units]
96
- )
97
- s += " #{I18n.t('metar.wind.gusts')} #{g}"
98
118
  end
99
- s
100
119
  end
101
120
  end
data/lib/metar/data.rb CHANGED
@@ -1,25 +1,27 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- module Metar::Data
4
- autoload :Base, "metar/data/base"
5
- autoload :DensityAltitude, "metar/data/density_altitude"
6
- autoload :Direction, "metar/data/direction"
7
- autoload :Distance, "metar/data/distance"
8
- autoload :Lightning, "metar/data/lightning"
9
- autoload :Observer, "metar/data/observer"
10
- autoload :Pressure, "metar/data/pressure"
11
- autoload :Remark, "metar/data/remark"
12
- autoload :RunwayVisibleRange, "metar/data/runway_visible_range"
13
- autoload :SkyCondition, "metar/data/sky_condition"
14
- autoload :Speed, "metar/data/speed"
15
- autoload :StationCode, "metar/data/station_code"
16
- autoload :Temperature, "metar/data/temperature"
17
- autoload :TemperatureAndDewPoint, "metar/data/temperature_and_dew_point"
18
- autoload :Time, "metar/data/time"
19
- autoload :VariableWind, "metar/data/variable_wind"
20
- autoload :VerticalVisibility, "metar/data/vertical_visibility"
21
- autoload :Visibility, "metar/data/visibility"
22
- autoload :VisibilityRemark, "metar/data/visibility_remark"
23
- autoload :WeatherPhenomenon, "metar/data/weather_phenomenon"
24
- autoload :Wind, "metar/data/wind"
3
+ module Metar
4
+ module Data
5
+ autoload :Base, "metar/data/base"
6
+ autoload :DensityAltitude, "metar/data/density_altitude"
7
+ autoload :Direction, "metar/data/direction"
8
+ autoload :Distance, "metar/data/distance"
9
+ autoload :Lightning, "metar/data/lightning"
10
+ autoload :Observer, "metar/data/observer"
11
+ autoload :Pressure, "metar/data/pressure"
12
+ autoload :Remark, "metar/data/remark"
13
+ autoload :RunwayVisibleRange, "metar/data/runway_visible_range"
14
+ autoload :SkyCondition, "metar/data/sky_condition"
15
+ autoload :Speed, "metar/data/speed"
16
+ autoload :StationCode, "metar/data/station_code"
17
+ autoload :Temperature, "metar/data/temperature"
18
+ autoload :TemperatureAndDewPoint, "metar/data/temperature_and_dew_point"
19
+ autoload :Time, "metar/data/time"
20
+ autoload :VariableWind, "metar/data/variable_wind"
21
+ autoload :VerticalVisibility, "metar/data/vertical_visibility"
22
+ autoload :Visibility, "metar/data/visibility"
23
+ autoload :VisibilityRemark, "metar/data/visibility_remark"
24
+ autoload :WeatherPhenomenon, "metar/data/weather_phenomenon"
25
+ autoload :Wind, "metar/data/wind"
26
+ end
25
27
  end
data/lib/metar/i18n.rb CHANGED
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "i18n"
2
4
 
3
- locales_path = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "locales"))
5
+ locales_path = File.expand_path(
6
+ File.join(File.dirname(__FILE__), "..", "..", "locales")
7
+ )
4
8
  I18n.load_path += Dir.glob("#{locales_path}/*.yml")
5
9
  I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
6
-