metar-parser 1.5.0 → 1.7.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 +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)