metar-parser 1.4.2 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
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
-