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 +15 -9
- data/Rakefile +23 -14
- data/bin/download_raw.rb +0 -2
- data/lib/metar/data.rb +84 -73
- data/lib/metar/parser.rb +2 -8
- data/lib/metar/raw.rb +1 -5
- data/lib/metar/report.rb +22 -48
- data/lib/metar/station.rb +16 -63
- data/lib/metar/version.rb +1 -1
- data/locales/en.yml +8 -2
- data/locales/it.yml +6 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/unit/distance_spec.rb +85 -0
- data/spec/unit/parser_spec.rb +234 -0
- data/spec/unit/pressure_spec.rb +32 -0
- data/spec/unit/raw_spec.rb +196 -0
- data/spec/unit/report_spec.rb +187 -0
- data/spec/unit/runway_visible_range_spec.rb +88 -0
- data/spec/unit/sky_condition_spec.rb +73 -0
- data/spec/unit/speed_spec.rb +50 -0
- data/spec/unit/station_spec.rb +254 -0
- data/spec/unit/temperature_spec.rb +47 -0
- data/spec/unit/variable_wind_spec.rb +34 -0
- data/spec/unit/vertical_visibility_spec.rb +37 -0
- data/spec/unit/visibility_spec.rb +84 -0
- data/spec/unit/weather_phenomenon_spec.rb +68 -0
- data/spec/unit/wind_spec.rb +99 -0
- metadata +85 -18
- data/test/all_tests.rb +0 -6
- data/test/metar_test_helper.rb +0 -33
- data/test/unit/data_test.rb +0 -376
- data/test/unit/parser_test.rb +0 -144
- data/test/unit/raw_test.rb +0 -37
- data/test/unit/report_test.rb +0 -105
- data/test/unit/station_test.rb +0 -88
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
|
-
|
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 '
|
3
|
-
require '
|
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 => :
|
14
|
+
task :default => :spec
|
14
15
|
|
15
|
-
|
16
|
-
t.
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
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 =
|
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
|
29
|
-
:precision
|
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
|
-
|
104
|
+
Speed.parse( $2 + $4 ),
|
105
|
+
Speed.parse( $3 ) )
|
109
106
|
when s =~ /^VRB(\d{2}(|MPS|KMH|KT))$/
|
110
|
-
new(:variable_direction,
|
107
|
+
new( :variable_direction,
|
108
|
+
Speed.parse($1))
|
111
109
|
when s =~ /^\/{3}(\d{2}(|MPS|KMH|KT))$/
|
112
|
-
new(:unknown_direction,
|
113
|
-
|
114
|
-
|
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, :
|
120
|
+
attr_reader :direction, :speed, :gusts
|
121
121
|
|
122
|
-
def initialize( direction, speed,
|
123
|
-
@
|
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 =>
|
145
|
+
@direction.to_s( :units => options[ :direction_units ] )
|
137
146
|
end
|
138
|
-
speed
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
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
|
-
|
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 (
|
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
|
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
|
-
|
418
|
-
|
419
|
-
|
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
|
86
|
+
def self.for_cccc(cccc)
|
88
87
|
station = Metar::Station.new(cccc)
|
89
88
|
raw = Metar::Raw.new(station)
|
90
|
-
|
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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|