metar-parser 0.9.11 → 0.9.12

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -32,14 +32,27 @@ puts station.report.to_s
32
32
  Countries
33
33
  ---------
34
34
 
35
+ List countries:
36
+
35
37
  ```ruby
36
- # List countries:
37
38
  puts Metar::Station.countries
39
+ ```
40
+
41
+ Find a country's weather stations:
38
42
 
39
- # Find a country's weather stations:
43
+ ```ruby
40
44
  spanish = Metar::Station.find_all_by_country( 'Spain' )
41
45
  ```
42
46
 
47
+ Get The Data
48
+ ------------
49
+ ```ruby
50
+ station = Metar::Station.find_by_cccc( 'KPDX' )
51
+ parser = station.parser
52
+ puts parser.temperature.value
53
+ ```
54
+
55
+
43
56
  The METAR Data Format
44
57
  =====================
45
58
 
@@ -72,10 +85,3 @@ There are two gems which read the National Oceanic and Atmospheric Association's
72
85
  Interactive map:
73
86
  * http://www.spatiality.at/metarr/frontend/
74
87
 
75
- Testing
76
- =======
77
-
78
- The tests use a local copy of the weather stations list: data/nsd_cccc.txt
79
-
80
- If missing, the file gets downloaded before running tests.
81
-
data/Rakefile CHANGED
@@ -1,8 +1,9 @@
1
- require 'rubygems'
2
- require 'rake/gempackagetask'
3
- require 'rake/rdoctask'
4
- require 'rake/testtask'
1
+ require 'rubygems' if RUBY_VERSION < '1.9'
2
+ require 'rubygems/package_task'
3
+ require 'rdoc/task'
5
4
  require 'rake/clean'
5
+ require 'rcov/rcovtask' if RUBY_VERSION < '1.9'
6
+ require 'rspec/core/rake_task'
6
7
 
7
8
  $:.unshift(File.dirname(__FILE__) + '/lib')
8
9
  require 'metar'
@@ -10,19 +11,26 @@ require 'metar'
10
11
  RDOC_OPTS = ['--quiet', '--title', 'METAR Weather Report Parser', '--main', 'README.rdoc', '--inline-source']
11
12
  RDOC_PATH = 'doc/rdoc'
12
13
 
13
- task :default => :test
14
+ task :default => :spec
14
15
 
15
- Rake::TestTask.new do |t|
16
- t.libs << 'test'
17
- t.test_files = FileList['test/**/*.rb']
18
- t.verbose = true
16
+ RSpec::Core::RakeTask.new do | t |
17
+ t.pattern = 'spec/**/*_spec.rb'
19
18
  end
20
19
 
21
- Rake::RDocTask.new do |rdoc|
22
- rdoc.rdoc_dir = RDOC_PATH
23
- rdoc.options += RDOC_OPTS
24
- rdoc.main = 'README.rdoc'
25
- rdoc.rdoc_files.add ['README.rdoc', 'COPYING', 'lib/**/*.rb']
20
+ if RUBY_VERSION < '1.9'
21
+ Rake::RDocTask.new do |rdoc|
22
+ rdoc.rdoc_dir = RDOC_PATH
23
+ rdoc.options += RDOC_OPTS
24
+ rdoc.main = 'README.rdoc'
25
+ rdoc.rdoc_files.add ['README.rdoc', 'COPYING', 'lib/**/*.rb']
26
+ end
27
+
28
+ RSpec::Core::RakeTask.new( 'spec:rcov' ) do |t|
29
+ t.pattern = 'spec/**/*_spec.rb'
30
+ t.rcov = true
31
+ t.rcov_opts = [ '--exclude', 'spec/,/gems/' ]
32
+ end
33
+
26
34
  end
27
35
 
28
36
  desc "Build the gem"
@@ -34,3 +42,4 @@ desc "Publish a new version of the gem"
34
42
  task :release => :build do
35
43
  `gem push metar-parser-#{Metar::VERSION::STRING}.gem`
36
44
  end
45
+
data/bin/download_raw.rb CHANGED
@@ -16,8 +16,6 @@ Metar::Station.load_local
16
16
 
17
17
  stations = {}
18
18
 
19
- Metar::Raw.cache_connection
20
-
21
19
  Metar::Station.all.each do |station|
22
20
 
23
21
  next if station.cccc[0, 1] < initial
data/lib/metar/data.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  # encoding: utf-8
2
- require 'rubygems' if RUBY_VERSION < '1.9'
3
2
  require 'i18n'
4
3
  require 'm9t'
5
4
 
@@ -7,15 +6,13 @@ module Metar
7
6
  locales_path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'locales'))
8
7
  I18n.load_path += Dir.glob("#{ locales_path }/*.yml")
9
8
 
10
- # Subclasses M9t::Distance
11
- # Uses kilometers as desired default output unit
12
9
  class Distance < M9t::Distance
13
10
 
14
11
  attr_accessor :units
15
12
 
16
13
  # nil is taken to mean 'data unavailable'
17
14
  def initialize( meters = nil )
18
- @units = units || :meters
15
+ @units = :meters
19
16
  if meters
20
17
  super
21
18
  else
@@ -25,8 +22,8 @@ module Metar
25
22
 
26
23
  # Handles nil case differently to M9t::Distance
27
24
  def to_s( options = {} )
28
- options = { :units => @units,
29
- :precision => 0,
25
+ options = { :units => @units,
26
+ :precision => 0,
30
27
  :abbreviated => true }.merge( options )
31
28
  return I18n.t('metar.distance.unknown') if @value.nil?
32
29
  super( options )
@@ -38,6 +35,7 @@ module Metar
38
35
  class Speed < M9t::Speed
39
36
 
40
37
  METAR_UNITS = {
38
+ '' => :kilometers_per_hour,
41
39
  'KMH' => :kilometers_per_hour,
42
40
  'MPS' => :meters_per_second,
43
41
  'KT' => :knots,
@@ -45,11 +43,9 @@ module Metar
45
43
 
46
44
  def Speed.parse(s)
47
45
  case
48
- when s =~ /^(\d+)(KT|MPS|KMH)$/
46
+ when s =~ /^(\d+)(|KT|MPS|KMH)$/
49
47
  # Call the appropriate factory method for the supplied units
50
48
  send( METAR_UNITS[$2], $1.to_i )
51
- when s =~ /^(\d+)$/
52
- kilometers_per_hour( $1.to_i )
53
49
  else
54
50
  nil
55
51
  end
@@ -99,33 +95,46 @@ module Metar
99
95
  def Wind.parse(s)
100
96
  case
101
97
  when s =~ /^(\d{3})(\d{2}(|MPS|KMH|KT))$/
98
+ return nil if $1.to_i > 359
102
99
  new( M9t::Direction.new( $1 ),
103
- Speed.parse( $2 ),
104
- :direction_units => :compass )
100
+ Speed.parse( $2 ) )
105
101
  when s =~ /^(\d{3})(\d{2})G(\d{2,3}(|MPS|KMH|KT))$/
102
+ return nil if $1.to_i > 359
106
103
  new( M9t::Direction.new( $1 ),
107
- Speed.parse( $2 ),
108
- :direction_units => :compass )
104
+ Speed.parse( $2 + $4 ),
105
+ Speed.parse( $3 ) )
109
106
  when s =~ /^VRB(\d{2}(|MPS|KMH|KT))$/
110
- new(:variable_direction, Speed.parse($1))
107
+ new( :variable_direction,
108
+ Speed.parse($1))
111
109
  when s =~ /^\/{3}(\d{2}(|MPS|KMH|KT))$/
112
- new(:unknown_direction, Speed.parse($1))
113
- when s =~ /^\/{3}(\/{2}(|MPS|KMH|KT))$/
114
- new(:unknown_direction, :unknown)
110
+ new( :unknown_direction,
111
+ Speed.parse($1))
112
+ when s =~ %r(^/////(|MPS|KMH|KT)$)
113
+ new( :unknown_direction,
114
+ :unknown_speed)
115
115
  else
116
116
  nil
117
117
  end
118
118
  end
119
119
 
120
- attr_reader :direction, :speed, :options
120
+ attr_reader :direction, :speed, :gusts
121
121
 
122
- def initialize( direction, speed, options = {} )
123
- @options = { :direction_units => :compass,
124
- :speed_units => :kilometers_per_hour }.merge( options )
125
- @direction, @speed = direction, speed
122
+ def initialize( direction, speed, gusts = nil )
123
+ @direction, @speed, @gusts = direction, speed, gusts
126
124
  end
127
125
 
128
- def to_s
126
+ def to_s( options = {} )
127
+ options = { :direction_units => :compass,
128
+ :speed_units => :kilometers_per_hour }.merge( options )
129
+ speed =
130
+ case @speed
131
+ when :unknown_speed
132
+ I18n.t('metar.wind.unknown_speed')
133
+ else
134
+ @speed.to_s( :abbreviated => true,
135
+ :precision => 0,
136
+ :units => options[ :speed_units ] )
137
+ end
129
138
  direction =
130
139
  case @direction
131
140
  when :variable_direction
@@ -133,18 +142,16 @@ module Metar
133
142
  when :unknown_direction
134
143
  I18n.t('metar.wind.unknown_direction')
135
144
  else
136
- @direction.to_s( :units => @options[ :direction_units ] )
145
+ @direction.to_s( :units => options[ :direction_units ] )
137
146
  end
138
- speed =
139
- case @speed
140
- when :unknown
141
- I18n.t('metar.wind.unknown_speed')
142
- else
143
- @speed.to_s( :abbreviated => true,
144
- :precision => 0,
145
- :units => @options[ :speed_units ] )
146
- end
147
- "#{ speed } #{ direction }"
147
+ s = "#{ speed } #{ direction }"
148
+ if ! @gusts.nil?
149
+ g = @gusts.to_s( :abbreviated => true,
150
+ :precision => 0,
151
+ :units => options[ :speed_units ] )
152
+ s += " #{ I18n.t('metar.wind.gusts') } #{ g }"
153
+ end
154
+ s
148
155
  end
149
156
 
150
157
  end
@@ -166,7 +173,7 @@ module Metar
166
173
  end
167
174
 
168
175
  def to_s
169
- "#{ @direction1 } - #{ @direction2 }"
176
+ "#{ @direction1.to_s( :units => :compass ) } - #{ @direction2.to_s( :units => :compass ) }"
170
177
  end
171
178
 
172
179
  end
@@ -276,18 +283,25 @@ module Metar
276
283
  distance_options = { :abbreviated => true,
277
284
  :precision => 0,
278
285
  :units => @units }
279
- if @visibility2.nil?
280
- I18n.t('metar.runway_visible_range.runway') +
281
- ' ' + @designator +
282
- ': ' + @visibility1.to_s( distance_options )
283
- else
284
- I18n.t('metar.runway_visible_range.runway') +
285
- ' ' + @designator +
286
- ': ' + I18n.t('metar.runway_visible_range.from') +
287
- ' ' + @visibility1.to_s( distance_options ) +
288
- ' ' + I18n.t('metar.runway_visible_range.to') +
289
- ' ' + @visibility2.to_s( distance_options )
286
+ s =
287
+ if @visibility2.nil?
288
+ I18n.t( 'metar.runway_visible_range.runway') +
289
+ ' ' + @designator +
290
+ ': ' + @visibility1.to_s( distance_options )
291
+ else
292
+ I18n.t( 'metar.runway_visible_range.runway') +
293
+ ' ' + @designator +
294
+ ': ' + I18n.t('metar.runway_visible_range.from') +
295
+ ' ' + @visibility1.to_s( distance_options ) +
296
+ ' ' + I18n.t('metar.runway_visible_range.to') +
297
+ ' ' + @visibility2.to_s( distance_options )
298
+ end
299
+
300
+ if ! tendency.nil?
301
+ s += ' ' + I18n.t( "tendency.#{ tendency }" )
290
302
  end
303
+
304
+ s
291
305
  end
292
306
 
293
307
  end
@@ -367,9 +381,7 @@ module Metar
367
381
  end
368
382
 
369
383
  def to_s
370
- modifier = @modifier ? @modifier + ' ' : ''
371
- descriptor = @descriptor ? @descriptor + ' ' : ''
372
- I18n.t("metar.present_weather.%s%s%s" % [modifier, descriptor, @phenomenon])
384
+ I18n.t("metar.present_weather.%s" % [@modifier, @descriptor, @phenomenon].compact.join(' '))
373
385
  end
374
386
 
375
387
  end
@@ -377,31 +389,27 @@ module Metar
377
389
  class SkyCondition
378
390
 
379
391
  QUANTITY = {'BKN' => 'broken', 'FEW' => 'few', 'OVC' => 'overcast', 'SCT' => 'scattered'}
392
+ CONDITION = {
393
+ 'CB' => 'cumulonimbus',
394
+ 'TCU' => 'towering cumulus',
395
+ '///' => nil,
396
+ '' => nil
397
+ }
398
+ CLEAR_SKIES = [
399
+ 'NSC', # WMO
400
+ 'NCD', # WMO
401
+ 'CLR',
402
+ 'SKC',
403
+ ]
380
404
 
381
405
  def SkyCondition.parse(sky_condition)
382
406
  case
383
- when (sky_condition == 'NSC' or sky_condition == 'NCD') # WMO
384
- new
385
- when sky_condition == 'CLR'
386
- new
387
- when sky_condition == 'SKC'
407
+ when CLEAR_SKIES.include?( sky_condition )
388
408
  new
389
- when sky_condition =~ /^(BKN|FEW|OVC|SCT)(\d+)(CB|TCU|\/{3})?$/
390
- quantity = QUANTITY[$1]
391
- height = Distance.new( $2.to_i * 30.0 )
392
- type =
393
- case $3
394
- when 'CB'
395
- 'cumulonimbus'
396
- when 'TCU'
397
- 'towering cumulus'
398
- when nil
399
- nil
400
- when '///'
401
- nil
402
- else
403
- raise ParseError.new("Unexpected sky condition type: #$3")
404
- end
409
+ when sky_condition =~ /^(BKN|FEW|OVC|SCT)(\d+)(CB|TCU|\/{3}|)?$/
410
+ quantity = QUANTITY[ $1 ]
411
+ height = Distance.new( $2.to_i * 30.0 )
412
+ type = CONDITION[ $3 ]
405
413
  new(quantity, height, type)
406
414
  else
407
415
  nil
@@ -414,9 +422,11 @@ module Metar
414
422
  end
415
423
 
416
424
  def to_s
417
- conditions = to_summary
418
- conditions += ' ' + I18n.t('metar.altitude.at') + ' ' + height.to_s if not @height.nil?
419
- conditions
425
+ if @height.nil?
426
+ to_summary
427
+ else
428
+ to_summary + ' ' + I18n.t('metar.altitude.at') + ' ' + height.to_s
429
+ end
420
430
  end
421
431
 
422
432
  def to_summary
@@ -446,3 +456,4 @@ module Metar
446
456
  end
447
457
 
448
458
  end
459
+
data/lib/metar/parser.rb CHANGED
@@ -1,4 +1,3 @@
1
- require 'rubygems' if RUBY_VERSION < '1.9'
2
1
  require 'aasm'
3
2
  require File.join(File.dirname(__FILE__), 'data')
4
3
 
@@ -84,12 +83,10 @@ module Metar
84
83
  transitions :from => [:remarks], :to => :end
85
84
  end
86
85
 
87
- def Parser.for_cccc(cccc)
86
+ def self.for_cccc(cccc)
88
87
  station = Metar::Station.new(cccc)
89
88
  raw = Metar::Raw.new(station)
90
- parser = new(raw)
91
- parser.analyze
92
- parser
89
+ new(raw)
93
90
  end
94
91
 
95
92
  attr_reader :raw, :metar, :time
@@ -313,9 +310,6 @@ module Metar
313
310
  end
314
311
 
315
312
  def seek_end
316
- if @chunks.length > 0
317
- raise ParseError.new("Unexpected tokens found at end of string: found '#{ @chunks.join(' ') }'")
318
- end
319
313
  done!
320
314
  end
321
315
 
data/lib/metar/raw.rb CHANGED
@@ -1,4 +1,3 @@
1
- require 'rubygems' if RUBY_VERSION < '1.9'
2
1
  require 'net/ftp'
3
2
 
4
3
  module Metar
@@ -9,10 +8,6 @@ module Metar
9
8
 
10
9
  class << self
11
10
 
12
- def cache_connection
13
- @@connection = connection
14
- end
15
-
16
11
  def connection
17
12
  return @@connection if @@connection
18
13
  connect
@@ -93,3 +88,4 @@ module Metar
93
88
  end
94
89
 
95
90
  end
91
+
data/lib/metar/report.rb CHANGED
@@ -4,50 +4,22 @@ module Metar
4
4
 
5
5
  class Report
6
6
 
7
- KNOWN_ATTRIBUTES =
8
- [
9
- :station_code, :station_name, :station_country,
10
- :date, :time, :observer,
11
- :wind, :variable_wind,
12
- :visibility, :runway_visible_range,
13
- :present_weather,
14
- :sky_summary, :sky_conditions,
15
- :temperature, :dew_point, :remarks
16
- ]
17
-
18
- DEFAULT_ATTRIBUTES =
19
- [
20
- :station_name, :station_country,
21
- :time,
22
- :wind,
23
- :visibility,
24
- :present_weather,
25
- :sky_summary,
26
- :temperature
27
- ]
28
-
29
- instance_eval do
30
-
31
- def reset_options!
32
- @attributes = DEFAULT_ATTRIBUTES.clone
33
- end
34
-
35
- def attributes
36
- @attributes
37
- end
38
-
39
- def attributes=(attributes)
40
- @attributes = attributes.clone
41
- end
42
-
43
- reset_options!
44
- end
7
+ ATTRIBUTES = [
8
+ :station_name,
9
+ :station_country,
10
+ :time,
11
+ :wind,
12
+ :visibility,
13
+ :present_weather,
14
+ :sky_summary,
15
+ :temperature
16
+ ]
45
17
 
46
18
  attr_reader :parser, :station
47
19
 
48
20
  def initialize(parser)
49
21
  @parser = parser
50
- @station = Station.find_by_cccc(@parser.station_code)
22
+ @station = Station.find_by_cccc(@parser.station_code) # TODO: parser should return the station
51
23
  end
52
24
 
53
25
  def station_name
@@ -122,19 +94,21 @@ module Metar
122
94
  def remarks
123
95
  @parser.remarks.join(', ')
124
96
  end
125
-
126
- def attributes
127
- Metar::Report.attributes.reduce([]) do |memo, key|
128
- value = self.send(key).to_s
129
- memo << {:attribute => key, :value => value} if not value.empty?
130
- memo
131
- end
132
- end
133
97
 
134
98
  def to_s
135
99
  attributes.collect do |attribute|
136
100
  I18n.t('metar.' + attribute[:attribute].to_s + '.title') + ': ' + attribute[:value]
137
- end.join("\n")
101
+ end.join("\n") + "\n"
102
+ end
103
+
104
+ private
105
+
106
+ def attributes
107
+ a = Metar::Report::ATTRIBUTES.map do | key |
108
+ value = self.send(key).to_s
109
+ {:attribute => key, :value => value} if not value.empty?
110
+ end
111
+ a.compact
138
112
  end
139
113
 
140
114
  end