metar-parser 1.5.0 → 1.7.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 +29 -20
  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 +92 -52
  58. metadata +53 -39
data/lib/metar/raw.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'date'
2
4
  require 'net/ftp'
3
5
  require 'time'
@@ -7,21 +9,23 @@ module Metar
7
9
  class Base
8
10
  attr_reader :metar
9
11
  attr_reader :time
10
- alias :to_s :metar
12
+ alias to_s metar
11
13
  end
12
14
 
13
15
  ##
14
16
  # Use this class when you have a METAR string and the date of reading
15
17
  class Data < Base
16
18
  def initialize(metar, time = nil)
17
- if time == nil
18
- warn <<-EOT
19
+ if time.nil?
20
+ warn <<-WARNING
19
21
  Using Metar::Raw::Data without a time parameter is deprecated.
20
22
  Please supply the reading time as the second parameter.
21
- EOT
23
+ WARNING
22
24
  time = Time.now
23
25
  end
24
- @metar, @time = metar, time
26
+
27
+ @metar = metar
28
+ @time = time
25
29
  end
26
30
  end
27
31
 
@@ -39,6 +43,7 @@ module Metar
39
43
 
40
44
  def time
41
45
  return @time if @time
46
+
42
47
  dom = day_of_month
43
48
  date = Date.today
44
49
  loop do
@@ -57,60 +62,42 @@ module Metar
57
62
  def datetime
58
63
  datetime = metar[/^\w{4} (\d{6})Z/, 1]
59
64
  raise "The METAR string must have a 6 digit datetime" if datetime.nil?
65
+
60
66
  datetime
61
67
  end
62
68
 
63
69
  def day_of_month
64
70
  dom = datetime[0..1].to_i
65
71
  raise "Day of month must be at most 31" if dom > 31
66
- raise "Day of month must be greater than 0" if dom == 0
72
+ raise "Day of month must be greater than 0" if dom.zero?
73
+
67
74
  dom
68
75
  end
69
76
  end
70
77
 
71
78
  # Collects METAR data from the NOAA site via FTP
72
79
  class Noaa < Base
73
- @@connection = nil
74
-
75
- class << self
76
-
77
- def connection
78
- return @@connection if @@connection
79
- connect
80
- @@connection
81
- end
82
-
83
- def connect
84
- @@connection = Net::FTP.new('tgftp.nws.noaa.gov')
85
- @@connection.login
86
- @@connection.chdir('data/observations/metar/stations')
87
- @@connection.passive = true
88
- end
89
-
90
- def disconnect
91
- return if @@connection.nil?
92
- @@connection.close
93
- @@connection = nil
94
- end
95
-
96
- def fetch(cccc)
97
- attempts = 0
98
- while attempts < 2
99
- begin
100
- s = ''
101
- connection.retrbinary("RETR #{ cccc }.TXT", 1024) do |chunk|
102
- s << chunk
103
- end
104
- disconnect
105
- return s
106
- rescue Net::FTPPermError, Net::FTPTempError, EOFError => e
107
- connect
108
- attempts += 1
80
+ def self.fetch(cccc)
81
+ connection = Net::FTP.new('tgftp.nws.noaa.gov')
82
+ connection.login
83
+ connection.chdir('data/observations/metar/stations')
84
+ connection.passive = true
85
+
86
+ attempts = 0
87
+ while attempts < 2
88
+ begin
89
+ s = ''
90
+ connection.retrbinary("RETR #{cccc}.TXT", 1024) do |chunk|
91
+ s += chunk
109
92
  end
93
+ connection.close
94
+ return s
95
+ rescue Net::FTPPermError, Net::FTPTempError, EOFError
96
+ attempts += 1
110
97
  end
111
- raise "Net::FTP.retrbinary failed #{attempts} times"
112
98
  end
113
99
 
100
+ raise "Net::FTP.retrbinary failed #{attempts} times"
114
101
  end
115
102
 
116
103
  # Station is a string containing the CCCC code, or
@@ -124,7 +111,7 @@ module Metar
124
111
  @data
125
112
  end
126
113
  # #raw is deprecated, use #data
127
- alias :raw :data
114
+ alias raw data
128
115
 
129
116
  def time
130
117
  fetch
@@ -140,6 +127,7 @@ module Metar
140
127
 
141
128
  def fetch
142
129
  return if @data
130
+
143
131
  @data = Noaa.fetch(@cccc)
144
132
  parse
145
133
  end
data/lib/metar/report.rb CHANGED
@@ -1,24 +1,27 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ require "metar/data/remark"
2
4
 
3
5
  module Metar
4
6
  class Report
5
- ATTRIBUTES = [
6
- :station_name,
7
- :station_country,
8
- :time,
9
- :wind,
10
- :visibility,
11
- :minimum_visibility,
12
- :present_weather,
13
- :sky_summary,
14
- :temperature,
15
- ]
7
+ ATTRIBUTES = %i(
8
+ station_name
9
+ station_country
10
+ time
11
+ wind
12
+ visibility
13
+ minimum_visibility
14
+ present_weather
15
+ sky_summary
16
+ temperature
17
+ ).freeze
16
18
 
17
19
  attr_reader :parser, :station
18
20
 
19
21
  def initialize(parser)
20
22
  @parser = parser
21
- @station = Station.find_by_cccc(@parser.station_code) # TODO: parser should return the station
23
+ # TODO: parser should return the station
24
+ @station = Station.find_by_cccc(@parser.station_code)
22
25
  end
23
26
 
24
27
  def station_name
@@ -38,7 +41,10 @@ module Metar
38
41
  end
39
42
 
40
43
  def time
41
- "%u:%02u" % [@parser.time.hour, @parser.time.min]
44
+ format(
45
+ "%<hour>u:%02<min>u",
46
+ hour: @parser.time.hour, min: @parser.time.min
47
+ )
42
48
  end
43
49
 
44
50
  def observer
@@ -62,7 +68,7 @@ module Metar
62
68
  end
63
69
 
64
70
  def runway_visible_range
65
- @parser.runway_visible_range.collect { |rvr| rvr.to_s }.join(', ')
71
+ @parser.runway_visible_range.map(&:to_s).join(', ')
66
72
  end
67
73
 
68
74
  def present_weather
@@ -70,12 +76,15 @@ module Metar
70
76
  end
71
77
 
72
78
  def sky_summary
73
- return I18n.t('metar.sky_conditions.clear skies') if @parser.sky_conditions.length == 0
79
+ if @parser.sky_conditions.empty?
80
+ return I18n.t('metar.sky_conditions.clear skies')
81
+ end
82
+
74
83
  @parser.sky_conditions[-1].to_summary
75
84
  end
76
85
 
77
86
  def sky_conditions
78
- @parser.sky_conditions.collect { |sky| sky.to_s }.join(', ')
87
+ @parser.sky_conditions.map(&:to_s).join(', ')
79
88
  end
80
89
 
81
90
  def vertical_visibility
@@ -100,7 +109,9 @@ module Metar
100
109
 
101
110
  def to_s
102
111
  attributes.collect do |attribute|
103
- I18n.t('metar.' + attribute[:attribute].to_s + '.title') + ': ' + attribute[:value]
112
+ I18n.t('metar.' + attribute[:attribute].to_s + '.title') +
113
+ ': ' +
114
+ attribute[:value]
104
115
  end.join("\n") + "\n"
105
116
  end
106
117
 
@@ -108,8 +119,8 @@ module Metar
108
119
 
109
120
  def attributes
110
121
  a = Metar::Report::ATTRIBUTES.map do |key|
111
- value = self.send(key).to_s
112
- {:attribute => key, :value => value} if not value.empty?
122
+ value = send(key).to_s
123
+ {attribute: key, value: value} if !value.empty?
113
124
  end
114
125
  a.compact
115
126
  end
data/lib/metar/station.rb CHANGED
@@ -1,19 +1,24 @@
1
- require 'open-uri'
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'uri'
2
5
  require 'set'
3
6
 
4
7
  # A Station can be created without downloading data from the Internet.
5
- # The class downloads and caches the NOAA station list when it is first requested.
6
- # As soon of any of the attributes are read, the data is downloaded (if necessary), and attributes are set.
8
+ # The class downloads and caches the NOAA station list
9
+ # when it is first requested.
10
+ # As soon of any of the attributes are read, the data is downloaded
11
+ # (if necessary), and attributes are set.
7
12
 
8
13
  module Metar
9
14
  class Station
10
- NOAA_STATION_LIST_URL = 'http://tgftp.nws.noaa.gov/data/nsd_cccc.txt'
15
+ NOAA_STATION_LIST_URL = 'https://tgftp.nws.noaa.gov/data/nsd_cccc.txt'
11
16
 
12
17
  class << self
13
18
  @nsd_cccc = nil # Contains the text of the station list
14
19
 
15
20
  def countries
16
- all_structures.reduce(Set.new) { |a, s| a.add(s[ :country ]) }.to_a.sort
21
+ all_structures.reduce(Set.new) { |a, s| a.add(s[:country]) }.to_a.sort
17
22
  end
18
23
 
19
24
  def all
@@ -30,33 +35,36 @@ module Metar
30
35
 
31
36
  # Does the given CCCC code exist?
32
37
  def exist?(cccc)
33
- not find_data_by_cccc(cccc).nil?
38
+ !find_data_by_cccc(cccc).nil?
34
39
  end
35
40
 
36
41
  def find_all_by_country(country)
37
42
  all.select { |s| s.country == country }
38
43
  end
39
44
 
40
- def to_longitude(s)
41
- m = s.match(/^(\d+)-(\d+)([EW])/)
45
+ def to_longitude(longitude)
46
+ m = longitude.match(/^(\d+)-(\d+)([EW])/)
42
47
  return nil if !m
48
+
43
49
  (m[3] == 'E' ? 1.0 : -1.0) * (m[1].to_f + m[2].to_f / 60.0)
44
50
  end
45
51
 
46
- def to_latitude(s)
47
- m = s.match(/^(\d+)-(\d+)([SN])/)
52
+ def to_latitude(latitude)
53
+ m = latitude.match(/^(\d+)-(\d+)([SN])/)
48
54
  return nil if !m
49
- (m[3] == 'E' ? 1.0 : -1.0) * (m[1].to_f + m[2].to_f / 60.0)
55
+
56
+ (m[3] == 'N' ? 1.0 : -1.0) * (m[1].to_f + m[2].to_f / 60.0)
50
57
  end
51
58
  end
52
59
 
53
60
  attr_reader :cccc, :name, :state, :country, :longitude, :latitude, :raw
54
- alias :code :cccc
61
+ alias code cccc
55
62
 
56
63
  # No check is made on the existence of the station
57
64
  def initialize(cccc, noaa_data)
58
65
  raise "Station identifier must not be nil" if cccc.nil?
59
66
  raise "Station identifier must not be empty" if cccc.to_s == ''
67
+
60
68
  @cccc = cccc
61
69
  load! noaa_data
62
70
  end
@@ -76,7 +84,9 @@ module Metar
76
84
  @structures = nil
77
85
 
78
86
  def download_stations
79
- open(NOAA_STATION_LIST_URL) { |fil| fil.read }
87
+ uri = URI.parse(NOAA_STATION_LIST_URL)
88
+ response = Net::HTTP.get_response(uri)
89
+ response.body
80
90
  end
81
91
 
82
92
  def all_structures
@@ -88,13 +98,13 @@ module Metar
88
98
  @nsd_cccc.each_line do |station|
89
99
  fields = station.split(';')
90
100
  @structures << {
91
- cccc: fields[0],
92
- name: fields[3],
93
- state: fields[4],
94
- country: fields[5],
95
- latitude: fields[7],
101
+ cccc: fields[0],
102
+ name: fields[3],
103
+ state: fields[4],
104
+ country: fields[5],
105
+ latitude: fields[7],
96
106
  longitude: fields[8],
97
- raw: station.clone,
107
+ raw: station.clone
98
108
  }
99
109
  end
100
110
 
@@ -104,7 +114,6 @@ module Metar
104
114
  def find_data_by_cccc(cccc)
105
115
  all_structures.find { |station| station[:cccc] == cccc }
106
116
  end
107
-
108
117
  end
109
118
 
110
119
  def load!(noaa_data)
data/lib/metar/version.rb CHANGED
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Metar
2
4
  module VERSION #:nodoc:
3
5
  MAJOR = 1
4
- MINOR = 5
6
+ MINOR = 7
5
7
  TINY = 0
6
8
 
7
9
  STRING = [MAJOR, MINOR, TINY].join('.')
data/lib/metar.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "metar/i18n"
2
4
  require "metar/raw"
3
5
  require "metar/station"
@@ -16,4 +18,3 @@ module Metar
16
18
 
17
19
  autoload :Data, "metar/data"
18
20
  end
19
-
data/locales/de.yml CHANGED
@@ -69,6 +69,7 @@ de:
69
69
  present_weather:
70
70
  title: Wetter
71
71
  not observed: nicht beobachtet
72
+ no significant weather: kein signifikantes Wetter
72
73
  mist: Nebel
73
74
  dust: Staub
74
75
  blowing dust: aufgewirbelter Staub
data/locales/en.yml CHANGED
@@ -71,6 +71,7 @@ en:
71
71
  present_weather:
72
72
  title: weather
73
73
  not observed: not observed
74
+ no significant weather: no significant weather
74
75
  mist: mist
75
76
  dust: dust
76
77
  blowing dust: blowing dust
data/locales/it.yml CHANGED
@@ -69,6 +69,7 @@ it:
69
69
  present_weather:
70
70
  title: tempo
71
71
  not observed: non osservato
72
+ no significant weather: nessun tempo significativo
72
73
  mist: foschia
73
74
  dust: polvere
74
75
  blowing dust: polviscolo
data/locales/pt-BR.yml CHANGED
@@ -90,6 +90,7 @@ pt-BR:
90
90
  present_weather:
91
91
  title: clima
92
92
  not observed: não observado
93
+ no significant weather: sem clima significativo
93
94
  mist: misto
94
95
  dust: poeira
95
96
  blowing dust: sopro com poeira
@@ -1,4 +1,5 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  require "spec_helper"
3
4
 
4
5
  RSpec.describe Metar::Data::DensityAltitude do
@@ -1,4 +1,5 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  require "spec_helper"
3
4
 
4
5
  describe Metar::Data::Distance do
@@ -1,13 +1,30 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "spec_helper"
2
4
 
3
5
  describe Metar::Data::Lightning do
4
6
  context '.parse_chunks' do
5
7
  [
6
- ['direction', 'LTG SE', [:default, nil, ['SE']]],
7
- ['distance direction', 'LTG DSNT SE', [:default, 16093.44, ['SE']]],
8
- ['distance direction and direction', 'LTG DSNT NE AND W', [:default, 16093.44, ['NE', 'W']]],
9
- ['distance direction-direction', 'LTG DSNT SE-SW', [:default, 16093.44, ['SE', 'SW']]],
10
- ['distance all quandrants', 'LTG DSNT ALQDS', [:default, 16093.44, ['N', 'E', 'S', 'W']]],
8
+ [
9
+ 'direction', 'LTG SE',
10
+ [:default, nil, ['SE']]
11
+ ],
12
+ [
13
+ 'distance direction', 'LTG DSNT SE',
14
+ [:default, 16_093.44, ['SE']]
15
+ ],
16
+ [
17
+ 'distance direction and direction', 'LTG DSNT NE AND W',
18
+ [:default, 16_093.44, %w(NE W)]
19
+ ],
20
+ [
21
+ 'distance direction-direction', 'LTG DSNT SE-SW',
22
+ [:default, 16_093.44, %w(SE SW)]
23
+ ],
24
+ [
25
+ 'distance all quandrants', 'LTG DSNT ALQDS',
26
+ [:default, 16_093.44, %w(N E S W)]
27
+ ]
11
28
  ].each do |docstring, section, expected|
12
29
  example docstring do
13
30
  chunks = section.split(' ')
@@ -25,9 +42,9 @@ describe Metar::Data::Lightning do
25
42
  end
26
43
 
27
44
  it 'removes parsed chunks' do
28
- chunks = ['LTG', 'DSNT', 'SE', 'FOO']
45
+ chunks = %w(LTG DSNT SE FOO)
29
46
 
30
- r = described_class.parse_chunks(chunks)
47
+ described_class.parse_chunks(chunks)
31
48
 
32
49
  expect(chunks).to eq(['FOO'])
33
50
  end
@@ -39,9 +56,9 @@ describe Metar::Data::Lightning do
39
56
  end
40
57
 
41
58
  it "doesn't not fail if all chunks are parsed" do
42
- chunks = ['LTG', 'DSNT', 'SE']
59
+ chunks = %w(LTG DSNT SE)
43
60
 
44
- r = described_class.parse_chunks(chunks)
61
+ described_class.parse_chunks(chunks)
45
62
 
46
63
  expect(chunks).to eq([])
47
64
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "spec_helper"
2
4
 
3
5
  describe Metar::Data::Pressure do
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "spec_helper"
2
4
 
3
5
  describe Metar::Data::Remark do
4
6
  context '.parse' do
5
7
  it 'delegate to subclasses' do
6
- expect(described_class.parse('21012')).to be_a(Metar::Data::TemperatureExtreme)
8
+ expect(described_class.parse('21012')).
9
+ to be_a(Metar::Data::TemperatureExtreme)
7
10
  end
8
11
 
9
12
  it 'returns nil for unrecognised' do
@@ -12,13 +15,14 @@ describe Metar::Data::Remark do
12
15
 
13
16
  context '6-hour maximum or minimum' do
14
17
  [
15
- ['positive maximum', '10046', [:maximum, 4.6]],
18
+ ['positive maximum', '10046', [:maximum, 4.6]],
16
19
  ['negative maximum', '11012', [:maximum, -1.2]],
17
- ['positive minimum', '20046', [:minimum, 4.6]],
18
- ['negative minimum', '21012', [:minimum, -1.2]],
20
+ ['positive minimum', '20046', [:minimum, 4.6]],
21
+ ['negative minimum', '21012', [:minimum, -1.2]]
19
22
  ].each do |docstring, raw, expected|
20
23
  example docstring do
21
- expect(described_class.parse(raw)).to be_temperature_extreme(*expected)
24
+ expect(described_class.parse(raw)).
25
+ to be_temperature_extreme(*expected)
22
26
  end
23
27
  end
24
28
  end
@@ -27,7 +31,7 @@ describe Metar::Data::Remark do
27
31
  it 'returns minimum and maximum' do
28
32
  max, min = described_class.parse('400461006')
29
33
 
30
- expect(max).to be_temperature_extreme(:maximum, 4.6)
34
+ expect(max).to be_temperature_extreme(:maximum, 4.6)
31
35
  expect(min).to be_temperature_extreme(:minimum, -0.6)
32
36
  end
33
37
  end
@@ -63,10 +67,23 @@ describe Metar::Data::Remark do
63
67
  end
64
68
 
65
69
  context 'automated station' do
66
-
67
70
  [
68
- ['with precipitation dicriminator', 'AO1', [Metar::Data::AutomatedStationType, :with_precipitation_discriminator]],
69
- ['without precipitation dicriminator', 'AO2', [Metar::Data::AutomatedStationType, :without_precipitation_discriminator]],
71
+ [
72
+ 'with precipitation dicriminator',
73
+ 'AO1',
74
+ [
75
+ Metar::Data::AutomatedStationType,
76
+ :with_precipitation_discriminator
77
+ ]
78
+ ],
79
+ [
80
+ 'without precipitation dicriminator',
81
+ 'AO2',
82
+ [
83
+ Metar::Data::AutomatedStationType,
84
+ :without_precipitation_discriminator
85
+ ]
86
+ ]
70
87
  ].each do |docstring, raw, expected|
71
88
  example docstring do
72
89
  aut = described_class.parse(raw)