metar-parser 0.9.11 → 0.9.12
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.
- 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
|