metar-parser 1.1.0 → 1.1.1
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 +12 -0
- data/lib/metar/data.rb +259 -0
- data/lib/metar/parser.rb +88 -12
- data/lib/metar/station.rb +0 -21
- data/lib/metar/version.rb +1 -1
- data/locales/en.yml +1 -0
- data/locales/it.yml +1 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/unit/parser_spec.rb +91 -19
- data/spec/unit/remark_spec.rb +173 -0
- data/spec/unit/station_spec.rb +1 -71
- data/spec/unit/weather_phenomenon_spec.rb +6 -10
- metadata +34 -33
- data/bin/download_raw.rb +0 -55
data/README.md
CHANGED
@@ -67,6 +67,18 @@ raw = Metar::Raw::Data.new( metar_string )
|
|
67
67
|
parser = Metar::Parser.new( raw )
|
68
68
|
```
|
69
69
|
|
70
|
+
Compliance
|
71
|
+
==========
|
72
|
+
|
73
|
+
By default, the parser runs in 'loose' compliance mode. That means that it tries to
|
74
|
+
accept non-standard input.
|
75
|
+
|
76
|
+
If you only want to accept standards-compliant METAR strings, do this:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
Metar::Parse.compliance = :strict
|
80
|
+
```
|
81
|
+
|
70
82
|
Changelog
|
71
83
|
=========
|
72
84
|
|
data/lib/metar/data.rb
CHANGED
@@ -467,5 +467,264 @@ module Metar
|
|
467
467
|
|
468
468
|
end
|
469
469
|
|
470
|
+
class Remark
|
471
|
+
|
472
|
+
PRESSURE_CHANGE_CHARACTER = [
|
473
|
+
:increasing_then_decreasing, # 0
|
474
|
+
:increasing_then_steady, # 1
|
475
|
+
:increasing, # 2
|
476
|
+
:decreasing_or_steady_then_increasing, # 3
|
477
|
+
:steady, # 4
|
478
|
+
:decreasing_then_increasing, # 5
|
479
|
+
:decreasing_then_steady, # 6
|
480
|
+
:decreasing, # 7
|
481
|
+
:steady_then_decreasing, # 8
|
482
|
+
]
|
483
|
+
|
484
|
+
INDICATOR_TYPE = {
|
485
|
+
'TS' => :thunderstorm_information,
|
486
|
+
'PWI' => :precipitation_identifier,
|
487
|
+
'P' => :precipitation_amount,
|
488
|
+
}
|
489
|
+
|
490
|
+
COLOR_CODE = ['RED', 'AMB', 'YLO', 'GRN', 'WHT', 'BLU']
|
491
|
+
|
492
|
+
def self.parse(s)
|
493
|
+
case s
|
494
|
+
when /^([12])([01])(\d{3})$/
|
495
|
+
extreme = {'1' => :maximum, '2' => :minimum}[$1]
|
496
|
+
value = sign($2) * tenths($3)
|
497
|
+
TemperatureExtreme.new(extreme, value)
|
498
|
+
when /^4([01])(\d{3})([01])(\d{3})$/
|
499
|
+
[
|
500
|
+
TemperatureExtreme.new(:maximum, sign($1) * tenths($2)),
|
501
|
+
TemperatureExtreme.new(:minimum, sign($3) * tenths($4))
|
502
|
+
]
|
503
|
+
when /^5([0-8])(\d{3})$/
|
504
|
+
character = PRESSURE_CHANGE_CHARACTER[$1.to_i]
|
505
|
+
PressureTendency.new(character, tenths($2))
|
506
|
+
when /^6(\d{4})$/
|
507
|
+
Precipitation.new(3, Distance.new(inches_to_meters($1))) # actually 3 or 6 depending on reporting time
|
508
|
+
when /^7(\d{4})$/
|
509
|
+
Precipitation.new(24, Distance.new(inches_to_meters($1)))
|
510
|
+
when /^A[0O]([12])$/
|
511
|
+
type = [:with_precipitation_discriminator, :without_precipitation_discriminator][$1.to_i - 1]
|
512
|
+
AutomatedStationType.new(type)
|
513
|
+
when /^P(\d{4})$/
|
514
|
+
Precipitation.new(1, Distance.new(inches_to_meters($1)))
|
515
|
+
when /^T([01])(\d{3})([01])(\d{3})$/
|
516
|
+
temperature = Temperature.new(sign($1) * tenths($2))
|
517
|
+
dew_point = Temperature.new(sign($3) * tenths($4))
|
518
|
+
HourlyTemperaturAndDewPoint.new(temperature, dew_point)
|
519
|
+
when /^SLP(\d{3})$/
|
520
|
+
SeaLevelPressure.new(Pressure.hectopascals(tenths($1)))
|
521
|
+
when /^(#{INDICATOR_TYPE.keys.join('|')})NO$/
|
522
|
+
type = INDICATOR_TYPE[$1]
|
523
|
+
SensorStatusIndicator.new(:type, :not_available)
|
524
|
+
when /^(#{COLOR_CODE.join('|')})$/
|
525
|
+
ColorCode.new($1)
|
526
|
+
when 'SKC'
|
527
|
+
SkyCondition.new
|
528
|
+
when '$'
|
529
|
+
MaintenanceNeeded.new
|
530
|
+
else
|
531
|
+
nil
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
def self.sign(digit)
|
536
|
+
case digit
|
537
|
+
when '0'
|
538
|
+
1.0
|
539
|
+
when '1'
|
540
|
+
-1.0
|
541
|
+
else
|
542
|
+
raise "Unexpected sign: #{digit}"
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
def self.tenths(digits)
|
547
|
+
digits.to_f / 10.0
|
548
|
+
end
|
549
|
+
|
550
|
+
def self.inches_to_meters(digits)
|
551
|
+
digits.to_f * 0.000254
|
552
|
+
end
|
553
|
+
|
554
|
+
end
|
555
|
+
|
556
|
+
class TemperatureExtreme
|
557
|
+
|
558
|
+
attr_accessor :value
|
559
|
+
attr_accessor :extreme
|
560
|
+
|
561
|
+
def initialize(extreme, value)
|
562
|
+
@extreme, @value = extreme, value
|
563
|
+
end
|
564
|
+
|
565
|
+
end
|
566
|
+
|
567
|
+
class PressureTendency
|
568
|
+
|
569
|
+
attr_accessor :value
|
570
|
+
attr_accessor :character
|
571
|
+
|
572
|
+
def initialize(character, value)
|
573
|
+
@character, @value = character, value
|
574
|
+
end
|
575
|
+
|
576
|
+
end
|
577
|
+
|
578
|
+
class Precipitation
|
579
|
+
|
580
|
+
attr_accessor :period
|
581
|
+
attr_accessor :amount
|
582
|
+
|
583
|
+
def initialize(period, amount)
|
584
|
+
@period, @amount = period, amount
|
585
|
+
end
|
586
|
+
|
587
|
+
end
|
588
|
+
|
589
|
+
class AutomatedStationType
|
590
|
+
|
591
|
+
attr_accessor :type
|
592
|
+
|
593
|
+
def initialize(type)
|
594
|
+
@type = type
|
595
|
+
end
|
596
|
+
|
597
|
+
end
|
598
|
+
|
599
|
+
class HourlyTemperaturAndDewPoint
|
600
|
+
|
601
|
+
attr_accessor :temperature
|
602
|
+
attr_accessor :dew_point
|
603
|
+
|
604
|
+
def initialize(temperature, dew_point)
|
605
|
+
@temperature, @dew_point = temperature, dew_point
|
606
|
+
end
|
607
|
+
|
608
|
+
end
|
609
|
+
|
610
|
+
class SeaLevelPressure
|
611
|
+
|
612
|
+
attr_accessor :pressure
|
613
|
+
|
614
|
+
def initialize(pressure)
|
615
|
+
@pressure = pressure
|
616
|
+
end
|
617
|
+
|
618
|
+
end
|
619
|
+
|
620
|
+
class SensorStatusIndicator
|
621
|
+
|
622
|
+
attr_accessor :type
|
623
|
+
attr_accessor :state
|
624
|
+
|
625
|
+
def initialize(type, state)
|
626
|
+
@type, @state = type, state
|
627
|
+
end
|
628
|
+
|
629
|
+
end
|
630
|
+
|
631
|
+
class MaintenanceNeeded
|
632
|
+
end
|
633
|
+
|
634
|
+
class Lightning
|
635
|
+
|
636
|
+
TYPE = {'' => :default}
|
637
|
+
|
638
|
+
def self.parse_chunks(chunks)
|
639
|
+
ltg = chunks.shift
|
640
|
+
m = ltg.match(/^LTG(|CG|IC|CC|CA)$/)
|
641
|
+
raise 'first chunk is not lightning' if m.nil?
|
642
|
+
type = TYPE[m[1]]
|
643
|
+
|
644
|
+
frequency = nil
|
645
|
+
distance = nil
|
646
|
+
directions = []
|
647
|
+
|
648
|
+
if chunks[0] == 'DSNT'
|
649
|
+
distance = Distance.miles(10) # Should be >10SM, not 10SM
|
650
|
+
chunks.shift
|
651
|
+
end
|
652
|
+
|
653
|
+
loop do
|
654
|
+
if is_compass?(chunks[0])
|
655
|
+
directions << chunks.shift
|
656
|
+
elsif chunks[0] == 'ALQDS'
|
657
|
+
directions += ['N', 'E', 'S', 'W']
|
658
|
+
chunks.shift
|
659
|
+
elsif chunks[0] =~ /^([NESW]{1,2})-([NESW]{1,2})$/
|
660
|
+
if is_compass?($1) and is_compass?($2)
|
661
|
+
directions += [$1, $2]
|
662
|
+
chunks.shift
|
663
|
+
else
|
664
|
+
break
|
665
|
+
end
|
666
|
+
elsif chunks[0] == 'AND'
|
667
|
+
chunks.shift
|
668
|
+
else
|
669
|
+
break
|
670
|
+
end
|
671
|
+
end
|
672
|
+
|
673
|
+
new(frequency, type, distance, directions)
|
674
|
+
end
|
675
|
+
|
676
|
+
def self.is_compass?(s)
|
677
|
+
s =~ /^([NESW]|NE|SE|SW|NW)$/
|
678
|
+
end
|
679
|
+
|
680
|
+
attr_accessor :frequency
|
681
|
+
attr_accessor :type
|
682
|
+
attr_accessor :distance
|
683
|
+
attr_accessor :directions
|
684
|
+
|
685
|
+
def initialize(frequency, type, distance, directions)
|
686
|
+
@frequency, @type, @distance, @directions = frequency, type, distance, directions
|
687
|
+
end
|
688
|
+
|
689
|
+
end
|
690
|
+
|
691
|
+
class VisibilityRemark < Visibility
|
692
|
+
|
693
|
+
def self.parse(chunk)
|
694
|
+
chunk =~ /^(\d{4})([NESW]?)$/
|
695
|
+
distance = Distance.new($1)
|
696
|
+
|
697
|
+
new(distance, $2, :more_than)
|
698
|
+
end
|
699
|
+
|
700
|
+
end
|
701
|
+
|
702
|
+
class DensityAltitude
|
703
|
+
|
704
|
+
def self.parse(chunk)
|
705
|
+
chunk =~ /^(\d+)(FT)$/
|
706
|
+
height = Distance.feet($1)
|
707
|
+
|
708
|
+
new(height)
|
709
|
+
end
|
710
|
+
|
711
|
+
attr_accessor :height
|
712
|
+
|
713
|
+
def initialize(height)
|
714
|
+
@height = height
|
715
|
+
end
|
716
|
+
|
717
|
+
end
|
718
|
+
|
719
|
+
class ColorCode
|
720
|
+
|
721
|
+
attr_accessor :code
|
722
|
+
|
723
|
+
def initialize(code)
|
724
|
+
@code = code
|
725
|
+
end
|
726
|
+
|
727
|
+
end
|
728
|
+
|
470
729
|
end
|
471
730
|
|
data/lib/metar/parser.rb
CHANGED
@@ -9,11 +9,26 @@ module Metar
|
|
9
9
|
new(raw)
|
10
10
|
end
|
11
11
|
|
12
|
+
COMPLIANCE = [:strict, :loose]
|
13
|
+
|
14
|
+
def self.thread_attributes
|
15
|
+
Thread.current[:metar_parser] ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.compliance
|
19
|
+
thread_attributes[:compliance] ||= :loose
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.compliance=(compliance)
|
23
|
+
raise 'Unknown compliance' unless COMPLIANCE.find(compliance)
|
24
|
+
thread_attributes[:compliance] = compliance
|
25
|
+
end
|
26
|
+
|
12
27
|
attr_reader :raw, :metar
|
13
28
|
attr_reader :station_code, :observer, :wind, :variable_wind, :visibility,
|
14
29
|
:minimum_visibility, :runway_visible_range, :present_weather, :sky_conditions,
|
15
30
|
:vertical_visibility, :temperature, :dew_point, :sea_level_pressure,
|
16
|
-
:recent_weather, :remarks
|
31
|
+
:recent_weather, :unparsed, :remarks
|
17
32
|
|
18
33
|
def initialize(raw)
|
19
34
|
@raw = raw
|
@@ -44,6 +59,7 @@ module Metar
|
|
44
59
|
@dew_point = nil
|
45
60
|
@sea_level_pressure = nil
|
46
61
|
@recent_weather = []
|
62
|
+
@unparsed = []
|
47
63
|
@remarks = []
|
48
64
|
|
49
65
|
seek_location
|
@@ -63,6 +79,7 @@ module Metar
|
|
63
79
|
seek_temperature_dew_point
|
64
80
|
seek_sea_level_pressure
|
65
81
|
seek_recent_weather
|
82
|
+
seek_to_remarks
|
66
83
|
seek_remarks
|
67
84
|
end
|
68
85
|
|
@@ -75,12 +92,30 @@ module Metar
|
|
75
92
|
end
|
76
93
|
|
77
94
|
def seek_datetime
|
78
|
-
|
79
|
-
|
80
|
-
|
95
|
+
found = false
|
96
|
+
date_matcher =
|
97
|
+
if strict?
|
98
|
+
/^(\d{2})(\d{2})(\d{2})Z$/
|
99
|
+
else
|
100
|
+
/^(\d{1,2})(\d{2})(\d{2})Z$/
|
101
|
+
end
|
102
|
+
if @chunks[0] =~ date_matcher
|
81
103
|
@day, @hour, @minute = $1.to_i, $2.to_i, $3.to_i
|
104
|
+
found = true
|
105
|
+
else
|
106
|
+
if not strict?
|
107
|
+
if @chunks[0] =~ /^(\d{1,2})(\d{2})Z$/
|
108
|
+
# The day is missing, use today's date
|
109
|
+
@day = Time.now.day
|
110
|
+
@hour, @minute = $1.to_i, $2.to_i, $3.to_i
|
111
|
+
found = true
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
if found
|
116
|
+
@chunks.shift
|
82
117
|
else
|
83
|
-
raise ParseError.new("Expecting datetime, found '#{
|
118
|
+
raise ParseError.new("Expecting datetime, found '#{@chunks[0]}' in #{@metar}")
|
84
119
|
end
|
85
120
|
end
|
86
121
|
|
@@ -238,19 +273,60 @@ module Metar
|
|
238
273
|
m = /^RE/.match(@chunks[0])
|
239
274
|
break if m.nil?
|
240
275
|
recent_weather = Metar::WeatherPhenomenon.parse(m.post_match)
|
241
|
-
if recent_weather
|
242
|
-
|
243
|
-
|
276
|
+
break if recent_weather.nil?
|
277
|
+
@chunks.shift
|
278
|
+
@recent_weather << recent_weather
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def seek_to_remarks
|
283
|
+
if strict?
|
284
|
+
if @chunks.size > 0 and @chunks[0] != 'RMK'
|
285
|
+
raise ParseError.new("Unparsable text found: '#{@chunks.join(' ')}'")
|
286
|
+
end
|
287
|
+
else
|
288
|
+
while @chunks.size > 0 and @chunks[0] != 'RMK' do
|
289
|
+
@unparsed << @chunks.shift
|
244
290
|
end
|
245
291
|
end
|
246
292
|
end
|
247
293
|
|
248
294
|
def seek_remarks
|
249
|
-
if @chunks
|
250
|
-
|
295
|
+
return if @chunks.size == 0
|
296
|
+
raise 'seek_remarks calls without remark' if @chunks[0] != 'RMK'
|
297
|
+
|
298
|
+
@chunks.shift # Drop 'RMK'
|
299
|
+
@remarks = []
|
300
|
+
loop do
|
301
|
+
break if @chunks.size == 0
|
302
|
+
r = Metar::Remark.parse(@chunks[0])
|
303
|
+
if r
|
304
|
+
@remarks += [*r]
|
305
|
+
@chunks.shift
|
306
|
+
next
|
307
|
+
end
|
308
|
+
if @chunks[0] == 'VIS' and @chunks.size >= 3 and @chunks[1] == 'MIN'
|
309
|
+
@chunks.shift(3)
|
310
|
+
r = Metar::VisibilityRemark.parse(@chunks[2])
|
311
|
+
@remarks << r
|
312
|
+
end
|
313
|
+
if @chunks[0] == 'DENSITY' and @chunks.size >= 3 and @chunks[1] == 'ALT'
|
314
|
+
@chunks.shift(3)
|
315
|
+
r = Metar::DensityAltitude.parse(@chunks[2])
|
316
|
+
@remarks << r
|
317
|
+
end
|
318
|
+
case
|
319
|
+
when @chunks[0] =~ /^LTG(|CG|IC|CC|CA)$/
|
320
|
+
r = Metar::Lightning.parse_chunks(@chunks)
|
321
|
+
@remarks << r
|
322
|
+
else
|
323
|
+
@remarks << @chunks.shift
|
324
|
+
end
|
251
325
|
end
|
252
|
-
|
253
|
-
|
326
|
+
end
|
327
|
+
|
328
|
+
def strict?
|
329
|
+
self.class.compliance == :strict
|
254
330
|
end
|
255
331
|
|
256
332
|
end
|
data/lib/metar/station.rb
CHANGED
@@ -14,22 +14,6 @@ module Metar
|
|
14
14
|
|
15
15
|
@nsd_cccc = nil # Contains the text of the station list
|
16
16
|
|
17
|
-
def download_local
|
18
|
-
nsd_cccc = Metar::Station.download_stations
|
19
|
-
File.open(Metar::Station.local_nsd_path, 'w') do |fil|
|
20
|
-
fil.write nsd_cccc
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
# Load local copy of the station list
|
25
|
-
# and download it first if missing
|
26
|
-
def load_local
|
27
|
-
download_local if not File.exist?(Metar::Station.local_nsd_path)
|
28
|
-
@nsd_cccc = File.open(Metar::Station.local_nsd_path) do |fil|
|
29
|
-
fil.read
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
17
|
def countries
|
34
18
|
all_structures.reduce( Set.new ) { |a, s| a.add( s[ :country ] ) }.to_a.sort
|
35
19
|
end
|
@@ -96,11 +80,6 @@ module Metar
|
|
96
80
|
open(NOAA_STATION_LIST_URL) { |fil| fil.read }
|
97
81
|
end
|
98
82
|
|
99
|
-
# Path for saving a local copy of the nsc_cccc station list
|
100
|
-
def local_nsd_path
|
101
|
-
File.join(File.expand_path(File.dirname(__FILE__) + '/../../'), 'data', 'nsd_cccc.txt')
|
102
|
-
end
|
103
|
-
|
104
83
|
def all_structures
|
105
84
|
return @structures if @structures
|
106
85
|
|
data/lib/metar/version.rb
CHANGED
data/locales/en.yml
CHANGED
@@ -138,6 +138,7 @@ en:
|
|
138
138
|
light dust storm: light dust storm
|
139
139
|
nearby dust storm: nearby dust storm
|
140
140
|
thunderstorm: thunderstorm
|
141
|
+
thunderstorm and rain: thunderstorm and rain
|
141
142
|
nearby thunderstorm: nearby thunderstorm
|
142
143
|
heavy thunderstorm and hail: heavy thunderstorm and hail
|
143
144
|
light thunderstorm and hail: light thunderstorm and hail
|
data/locales/it.yml
CHANGED
@@ -138,6 +138,7 @@ it:
|
|
138
138
|
light dust storm: tempesta di polvere leggera
|
139
139
|
nearby dust storm: tempesta di polvere vicina
|
140
140
|
thunderstorm: temporale
|
141
|
+
thunderstorm and rain: temporale con pioggia
|
141
142
|
nearby thunderstorm: temporale vicino
|
142
143
|
heavy thunderstorm and hail: temporale con grandine intenso
|
143
144
|
light thunderstorm and hail: temporale con grandine leggero
|
data/spec/spec_helper.rb
CHANGED
@@ -12,3 +12,17 @@ end
|
|
12
12
|
|
13
13
|
require File.expand_path( File.dirname(__FILE__) + '/../lib/metar' )
|
14
14
|
|
15
|
+
RSpec::Matchers.define :be_temperature_extreme do |extreme, value|
|
16
|
+
match do |remark|
|
17
|
+
if not remark.is_a?(Metar::TemperatureExtreme)
|
18
|
+
false
|
19
|
+
elsif remark.extreme != extreme
|
20
|
+
false
|
21
|
+
elsif remark.value != value
|
22
|
+
false
|
23
|
+
else
|
24
|
+
true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
data/spec/unit/parser_spec.rb
CHANGED
@@ -3,6 +3,10 @@ load File.expand_path( '../spec_helper.rb', File.dirname(__FILE__) )
|
|
3
3
|
|
4
4
|
describe Metar::Parser do
|
5
5
|
|
6
|
+
after :each do
|
7
|
+
Metar::Parser.compliance = :loose
|
8
|
+
end
|
9
|
+
|
6
10
|
context '.for_cccc' do
|
7
11
|
|
8
12
|
it 'returns a loaded parser' do
|
@@ -33,16 +37,50 @@ describe Metar::Parser do
|
|
33
37
|
end. to raise_error( Metar::ParseError, /Expecting location/ )
|
34
38
|
end
|
35
39
|
|
36
|
-
|
37
|
-
expect do
|
38
|
-
setup_parser("PAIL 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
39
|
-
end. to raise_error( Metar::ParseError, /Expecting datetime/ )
|
40
|
-
end
|
40
|
+
context 'datetime' do
|
41
41
|
|
42
|
-
|
43
|
-
|
42
|
+
it 'is parsed' do
|
43
|
+
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
44
|
+
|
45
|
+
parser.time. should == Time.gm(2011, 05, 06, 16, 10)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'throws an error is missing' do
|
49
|
+
expect do
|
50
|
+
setup_parser("PAIL 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
51
|
+
end. to raise_error( Metar::ParseError, /Expecting datetime/ )
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'in strict mode' do
|
55
|
+
|
56
|
+
before :each do
|
57
|
+
Metar::Parser.compliance = :strict
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'less than 6 numerals fails' do
|
61
|
+
expect do
|
62
|
+
parser = setup_parser('MMCE 21645Z 12010KT 8SM SKC 29/26 A2992 RMK')
|
63
|
+
end. to raise_error(Metar::ParseError, /Expecting datetime/)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'in loose mode' do
|
69
|
+
|
70
|
+
it '5 numerals parses' do
|
71
|
+
parser = setup_parser('MMCE 21645Z 12010KT 8SM SKC 29/26 A2992 RMK')
|
72
|
+
|
73
|
+
parser.time. should == Time.gm(2011, 05, 02, 16, 45)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "with 4 numerals parses, takes today's day" do
|
77
|
+
parser = setup_parser('HKML 1600Z 19010KT 9999 FEW022 25/22 Q1015')
|
78
|
+
|
79
|
+
parser.time. should == Time.gm(2011, 05, 06, 16, 00)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
44
83
|
|
45
|
-
parser.time. should == Time.gm(2011, 05, 06, 16, 10)
|
46
84
|
end
|
47
85
|
|
48
86
|
context '.observer' do
|
@@ -249,20 +287,54 @@ describe Metar::Parser do
|
|
249
287
|
should == 'thunderstorm'
|
250
288
|
end
|
251
289
|
|
252
|
-
|
253
|
-
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
290
|
+
context 'remarks' do
|
254
291
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
292
|
+
it 'are collected' do
|
293
|
+
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
294
|
+
|
295
|
+
parser.remarks. should be_a Array
|
296
|
+
parser.remarks.size. should == 2
|
297
|
+
end
|
260
298
|
|
261
|
-
|
262
|
-
|
299
|
+
it 'remarks defaults to empty array' do
|
300
|
+
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910")
|
301
|
+
|
302
|
+
parser.remarks. should be_a Array
|
303
|
+
parser.remarks.length. should == 0
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'parses known remarks' do
|
307
|
+
parser = setup_parser('CYZT 052200Z 31010KT 20SM SKC 17/12 A3005 RMK SLP174 20046')
|
308
|
+
|
309
|
+
parser.remarks[0]. should be_a(Metar::SeaLevelPressure)
|
310
|
+
parser.remarks[1]. should be_temperature_extreme(:minimum, 4.6)
|
311
|
+
end
|
312
|
+
|
313
|
+
context 'in strict mode' do
|
314
|
+
|
315
|
+
before :each do
|
316
|
+
Metar::Parser.compliance = :strict
|
317
|
+
end
|
318
|
+
|
319
|
+
it 'unparsed data causes an error' do
|
320
|
+
expect do
|
321
|
+
setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 FOO RMK AO2 P0000")
|
322
|
+
end. to raise_error(Metar::ParseError, /Unparsable text found/)
|
323
|
+
end
|
324
|
+
|
325
|
+
end
|
326
|
+
|
327
|
+
context 'in loose mode' do
|
328
|
+
|
329
|
+
it 'unparsed data is collected' do
|
330
|
+
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 FOO RMK AO2 P0000")
|
331
|
+
|
332
|
+
parser.unparsed. should == ['FOO']
|
333
|
+
parser.remarks.size. should == 2
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|
263
337
|
|
264
|
-
parser.remarks. should be_a Array
|
265
|
-
parser.remarks.length. should == 0
|
266
338
|
end
|
267
339
|
|
268
340
|
def setup_parser(metar)
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
load File.expand_path( '../spec_helper.rb', File.dirname(__FILE__) )
|
3
|
+
|
4
|
+
describe Metar::Remark do
|
5
|
+
|
6
|
+
context '.parse' do
|
7
|
+
|
8
|
+
it 'should delegate to subclasses' do
|
9
|
+
Metar::Remark.parse('21012'). should be_a(Metar::TemperatureExtreme)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should return nil for unrecognised' do
|
13
|
+
Metar::Remark.parse('FOO'). should be_nil
|
14
|
+
end
|
15
|
+
|
16
|
+
context '6-hour maximum or minimum' do
|
17
|
+
|
18
|
+
[
|
19
|
+
['positive maximum', '10046', [:maximum, 4.6]],
|
20
|
+
['negative maximum', '11012', [:maximum, -1.2]],
|
21
|
+
['positive minimum', '20046', [:minimum, 4.6]],
|
22
|
+
['negative minimum', '21012', [:minimum, -1.2]],
|
23
|
+
].each do |docstring, raw, expected|
|
24
|
+
example docstring do
|
25
|
+
Metar::Remark.parse(raw).
|
26
|
+
should be_temperature_extreme(*expected)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
context '24-hour maximum and minimum' do
|
33
|
+
|
34
|
+
it 'returns minimum and maximum' do
|
35
|
+
max, min = Metar::Remark.parse('400461006')
|
36
|
+
|
37
|
+
max. should be_temperature_extreme(:maximum, 4.6)
|
38
|
+
min. should be_temperature_extreme(:minimum, -0.6)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'pressure tendency' do
|
44
|
+
|
45
|
+
it 'steady_then_decreasing' do
|
46
|
+
pt = Metar::Remark.parse('58033')
|
47
|
+
|
48
|
+
pt. should be_a(Metar::PressureTendency)
|
49
|
+
pt.character. should == :steady_then_decreasing
|
50
|
+
pt.value. should == 3.3
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
context '3-hour and 6-hour precipitation' do
|
56
|
+
|
57
|
+
it '60009' do
|
58
|
+
pr = Metar::Remark.parse('60009')
|
59
|
+
|
60
|
+
pr. should be_a(Metar::Precipitation)
|
61
|
+
pr.period. should == 3
|
62
|
+
pr.amount.value. should == 0.002286
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
context '24-hour precipitation' do
|
68
|
+
|
69
|
+
it '70015' do
|
70
|
+
pr = Metar::Remark.parse('70015')
|
71
|
+
|
72
|
+
pr. should be_a(Metar::Precipitation)
|
73
|
+
pr.period. should == 24
|
74
|
+
pr.amount.value. should == 0.003810
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'automated station' do
|
80
|
+
|
81
|
+
[
|
82
|
+
['with precipitation dicriminator', 'AO1', [Metar::AutomatedStationType, :with_precipitation_discriminator]],
|
83
|
+
['without precipitation dicriminator', 'AO2', [Metar::AutomatedStationType, :without_precipitation_discriminator]],
|
84
|
+
].each do |docstring, raw, expected|
|
85
|
+
example docstring do
|
86
|
+
aut = Metar::Remark.parse(raw)
|
87
|
+
|
88
|
+
aut. should be_a(expected[0])
|
89
|
+
aut.type. should == expected[1]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'sea-level pressure' do
|
96
|
+
|
97
|
+
it 'SLP125' do
|
98
|
+
slp = Metar::Remark.parse('SLP125')
|
99
|
+
|
100
|
+
slp. should be_a(Metar::SeaLevelPressure)
|
101
|
+
slp.pressure.value. should == 0.0125
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'hourly temperature and dew point' do
|
107
|
+
|
108
|
+
it 'T00640036' do
|
109
|
+
htm = Metar::Remark.parse('T00641036')
|
110
|
+
|
111
|
+
htm. should be_a(Metar::HourlyTemperaturAndDewPoint)
|
112
|
+
htm.temperature.value. should == 6.4
|
113
|
+
htm.dew_point.value. should == -3.6
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
describe Metar::Lightning do
|
123
|
+
|
124
|
+
context '.parse_chunks' do
|
125
|
+
|
126
|
+
[
|
127
|
+
['direction', 'LTG SE', [:default, nil, ['SE']]],
|
128
|
+
['distance direction', 'LTG DSNT SE', [:default, 16093.44, ['SE']]],
|
129
|
+
['distance direction and direction', 'LTG DSNT NE AND W', [:default, 16093.44, ['NE', 'W']]],
|
130
|
+
['distance direction-direction', 'LTG DSNT SE-SW', [:default, 16093.44, ['SE', 'SW']]],
|
131
|
+
['distance all quandrants', 'LTG DSNT ALQDS', [:default, 16093.44, ['N', 'E', 'S', 'W']]],
|
132
|
+
].each do |docstring, section, expected|
|
133
|
+
example docstring do
|
134
|
+
chunks = section.split(' ')
|
135
|
+
r = Metar::Lightning.parse_chunks(chunks)
|
136
|
+
|
137
|
+
r. should be_a(Metar::Lightning)
|
138
|
+
r.type. should == expected[0]
|
139
|
+
if expected[1]
|
140
|
+
r.distance.value. should == expected[1]
|
141
|
+
else
|
142
|
+
r.distance. should be_nil
|
143
|
+
end
|
144
|
+
r.directions. should == expected[2]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should remove parsed chunks' do
|
149
|
+
chunks = ['LTG', 'DSNT', 'SE', 'FOO']
|
150
|
+
|
151
|
+
r = Metar::Lightning.parse_chunks(chunks)
|
152
|
+
|
153
|
+
chunks. should == ['FOO']
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'should fail if the first chunk is not LTGnnn' do
|
157
|
+
expect do
|
158
|
+
Metar::Lightning.parse_chunks(['FOO'])
|
159
|
+
end. to raise_error(RuntimeError, /not lightning/)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'should not fail if all chunks are parsed' do
|
163
|
+
chunks = ['LTG', 'DSNT', 'SE']
|
164
|
+
|
165
|
+
r = Metar::Lightning.parse_chunks(chunks)
|
166
|
+
|
167
|
+
chunks. should == []
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
|
data/spec/unit/station_spec.rb
CHANGED
@@ -21,74 +21,10 @@ describe Metar::Station do
|
|
21
21
|
@file = stub( 'file' )
|
22
22
|
end
|
23
23
|
|
24
|
-
context '.download_local' do
|
25
|
-
|
26
|
-
it 'downloads the station list' do
|
27
|
-
File.stub!( :open => @file )
|
28
|
-
|
29
|
-
Metar::Station. should_receive( :open ).
|
30
|
-
with( /^http/ )
|
31
|
-
|
32
|
-
Metar::Station.download_local
|
33
|
-
end
|
34
|
-
|
35
|
-
it 'saves the list to disk' do
|
36
|
-
Metar::Station.stub!( :open ).and_return( 'aaa' )
|
37
|
-
|
38
|
-
File. should_receive( :open ) do | path, mode, &block |
|
39
|
-
path. should =~ %r(data/nsd_cccc.txt$)
|
40
|
-
mode. should == 'w'
|
41
|
-
block.call @file
|
42
|
-
end
|
43
|
-
@file. should_receive( :write ).
|
44
|
-
with( 'aaa' )
|
45
|
-
|
46
|
-
Metar::Station.download_local
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
context '.load_local' do
|
52
|
-
|
53
|
-
it 'downloads the station list, if missing, then loads it' do
|
54
|
-
File. should_receive( :exist? ).
|
55
|
-
and_return( false )
|
56
|
-
# open-uri call
|
57
|
-
Metar::Station. should_receive( :open ).
|
58
|
-
and_return( 'aaa' )
|
59
|
-
|
60
|
-
outfile = stub( 'outfile' )
|
61
|
-
File. should_receive( :open ).once.with( %r(nsd_cccc.txt), 'w' ) do | *args, &block |
|
62
|
-
block.call outfile
|
63
|
-
end
|
64
|
-
outfile. should_receive( :write ).
|
65
|
-
with( 'aaa' )
|
66
|
-
|
67
|
-
infile = stub( 'infile' )
|
68
|
-
File. should_receive( :open ).once.with( %r(nsd_cccc.txt) ) do | *args, &block |
|
69
|
-
block.call infile
|
70
|
-
end
|
71
|
-
infile. should_receive( :read ).
|
72
|
-
and_return( 'bbb' )
|
73
|
-
|
74
|
-
Metar::Station.load_local. should == 'bbb'
|
75
|
-
end
|
76
|
-
|
77
|
-
it 'loads the file, if already present' do
|
78
|
-
File. should_receive( :exist? ).
|
79
|
-
and_return( true )
|
80
|
-
|
81
|
-
File. should_receive( :open ).once.with( %r(nsd_cccc.txt) )
|
82
|
-
|
83
|
-
Metar::Station.load_local
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
|
88
24
|
context 'using structures' do
|
89
25
|
|
90
26
|
before :each do
|
91
|
-
|
27
|
+
Metar::Station.stub!(:open).with(Metar::Station::NOAA_STATION_LIST_URL).and_return(nsd_file)
|
92
28
|
end
|
93
29
|
|
94
30
|
context '.countries' do
|
@@ -143,12 +79,6 @@ EOT
|
|
143
79
|
StringIO.new( nsd_text )
|
144
80
|
end
|
145
81
|
|
146
|
-
def preload_data
|
147
|
-
File.stub!( :exist? ).and_return( true )
|
148
|
-
File.stub!( :open ).with( %r(nsd_cccc.txt) ).and_return( nsd_file )
|
149
|
-
Metar::Station.load_local
|
150
|
-
end
|
151
|
-
|
152
82
|
end
|
153
83
|
|
154
84
|
context '.to_longitude' do
|
@@ -20,12 +20,11 @@ RSpec::Matchers.define :be_weather_phenomenon do | modifier, descriptor, phenome
|
|
20
20
|
end
|
21
21
|
|
22
22
|
describe Metar::WeatherPhenomenon do
|
23
|
-
|
24
23
|
context '.parse' do
|
25
|
-
|
26
24
|
[
|
27
25
|
[ 'simple phenomenon', 'BR', [ nil, nil, 'mist' ] ],
|
28
26
|
[ 'descriptor + phenomenon', 'BCFG', [ nil, 'patches of', 'fog' ] ],
|
27
|
+
[ 'thunderstorm and rain', 'TSRA', [ nil, 'thunderstorm and', 'rain' ] ],
|
29
28
|
[ 'intensity + phenomenon', '+RA', [ 'heavy', nil, 'rain' ] ],
|
30
29
|
[ 'intensity + proximity + phenomenon', '-VCTSRA', [ 'nearby light', 'thunderstorm and', 'rain' ] ],
|
31
30
|
[ '2 phenomena: SN RA', 'SNRA', [ nil, nil, 'snow and rain' ] ],
|
@@ -37,11 +36,9 @@ describe Metar::WeatherPhenomenon do
|
|
37
36
|
Metar::WeatherPhenomenon.parse( raw ).should be_weather_phenomenon( *expected )
|
38
37
|
end
|
39
38
|
end
|
40
|
-
|
41
39
|
end
|
42
40
|
|
43
41
|
context '#to_s' do
|
44
|
-
|
45
42
|
before :all do
|
46
43
|
@locale = I18n.locale
|
47
44
|
I18n.locale = :it
|
@@ -52,10 +49,11 @@ describe Metar::WeatherPhenomenon do
|
|
52
49
|
end
|
53
50
|
|
54
51
|
[
|
55
|
-
[ 'simple phenomenon',
|
56
|
-
[ 'simple phenomenon',
|
57
|
-
[ 'descriptor + phenomenon', :en, [ nil,
|
58
|
-
[ '
|
52
|
+
[ 'simple phenomenon', :en, [ nil, nil, 'mist' ], 'mist' ],
|
53
|
+
[ 'simple phenomenon', :it, [ nil, nil, 'mist' ], 'foschia' ],
|
54
|
+
[ 'descriptor + phenomenon', :en, [ nil, 'patches of', 'fog' ], 'patches of fog' ],
|
55
|
+
[ 'thunderstorm and rain', :en, [ nil, 'thunderstorm and', 'rain' ], 'thunderstorm and rain' ],
|
56
|
+
[ 'modifier + phenomenon', :en, ['heavy', nil, 'drizzle' ], 'heavy drizzle' ],
|
59
57
|
[ 'modifier + descriptor + phenomenon', :en, ['heavy', 'freezing', 'drizzle' ], 'heavy freezing drizzle' ],
|
60
58
|
].each do | docstring, locale, ( modifier, descriptor, phenomenon ), expected |
|
61
59
|
example docstring + " (#{locale})" do
|
@@ -64,8 +62,6 @@ describe Metar::WeatherPhenomenon do
|
|
64
62
|
should == expected
|
65
63
|
end
|
66
64
|
end
|
67
|
-
|
68
65
|
end
|
69
|
-
|
70
66
|
end
|
71
67
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: metar-parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-10-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -82,7 +82,7 @@ dependencies:
|
|
82
82
|
requirements:
|
83
83
|
- - ! '>='
|
84
84
|
- !ruby/object:Gem::Version
|
85
|
-
version: 2.
|
85
|
+
version: 2.11.0
|
86
86
|
type: :development
|
87
87
|
prerelease: false
|
88
88
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -90,7 +90,7 @@ dependencies:
|
|
90
90
|
requirements:
|
91
91
|
- - ! '>='
|
92
92
|
- !ruby/object:Gem::Version
|
93
|
-
version: 2.
|
93
|
+
version: 2.11.0
|
94
94
|
- !ruby/object:Gem::Dependency
|
95
95
|
name: simplecov
|
96
96
|
requirement: !ruby/object:Gem::Requirement
|
@@ -151,33 +151,33 @@ files:
|
|
151
151
|
- README.md
|
152
152
|
- COPYING
|
153
153
|
- Rakefile
|
154
|
-
- bin/download_raw.rb
|
155
154
|
- bin/parse_raw.rb
|
156
|
-
- lib/metar.rb
|
157
|
-
- lib/metar/data.rb
|
158
|
-
- lib/metar/station.rb
|
159
155
|
- lib/metar/parser.rb
|
160
156
|
- lib/metar/report.rb
|
161
|
-
- lib/metar/version.rb
|
162
157
|
- lib/metar/raw.rb
|
163
|
-
-
|
164
|
-
-
|
158
|
+
- lib/metar/station.rb
|
159
|
+
- lib/metar/data.rb
|
160
|
+
- lib/metar/version.rb
|
161
|
+
- lib/metar.rb
|
162
|
+
- spec/spec_helper.rb
|
165
163
|
- spec/unit/report_spec.rb
|
166
|
-
- spec/unit/
|
167
|
-
- spec/unit/
|
164
|
+
- spec/unit/pressure_spec.rb
|
165
|
+
- spec/unit/station_spec.rb
|
168
166
|
- spec/unit/visibility_spec.rb
|
167
|
+
- spec/unit/sky_condition_spec.rb
|
168
|
+
- spec/unit/speed_spec.rb
|
169
|
+
- spec/unit/wind_spec.rb
|
170
|
+
- spec/unit/distance_spec.rb
|
171
|
+
- spec/unit/temperature_spec.rb
|
172
|
+
- spec/unit/vertical_visibility_spec.rb
|
173
|
+
- spec/unit/remark_spec.rb
|
174
|
+
- spec/unit/runway_visible_range_spec.rb
|
169
175
|
- spec/unit/weather_phenomenon_spec.rb
|
170
|
-
- spec/unit/
|
176
|
+
- spec/unit/raw_spec.rb
|
171
177
|
- spec/unit/parser_spec.rb
|
172
|
-
- spec/unit/pressure_spec.rb
|
173
178
|
- spec/unit/variable_wind_spec.rb
|
174
|
-
- spec/unit/wind_spec.rb
|
175
|
-
- spec/unit/raw_spec.rb
|
176
|
-
- spec/unit/speed_spec.rb
|
177
|
-
- spec/unit/distance_spec.rb
|
178
|
-
- spec/spec_helper.rb
|
179
|
-
- locales/it.yml
|
180
179
|
- locales/en.yml
|
180
|
+
- locales/it.yml
|
181
181
|
homepage: http://github.com/joeyates/metar-parser
|
182
182
|
licenses: []
|
183
183
|
post_install_message:
|
@@ -192,7 +192,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
192
192
|
version: '0'
|
193
193
|
segments:
|
194
194
|
- 0
|
195
|
-
hash: -
|
195
|
+
hash: -171469053592145990
|
196
196
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
197
197
|
none: false
|
198
198
|
requirements:
|
@@ -201,7 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
201
201
|
version: '0'
|
202
202
|
segments:
|
203
203
|
- 0
|
204
|
-
hash: -
|
204
|
+
hash: -171469053592145990
|
205
205
|
requirements: []
|
206
206
|
rubyforge_project: nowarning
|
207
207
|
rubygems_version: 1.8.23
|
@@ -209,19 +209,20 @@ signing_key:
|
|
209
209
|
specification_version: 3
|
210
210
|
summary: A Ruby gem for worldwide weather reports
|
211
211
|
test_files:
|
212
|
-
- spec/unit/runway_visible_range_spec.rb
|
213
|
-
- spec/unit/vertical_visibility_spec.rb
|
214
212
|
- spec/unit/report_spec.rb
|
215
|
-
- spec/unit/
|
216
|
-
- spec/unit/
|
213
|
+
- spec/unit/pressure_spec.rb
|
214
|
+
- spec/unit/station_spec.rb
|
217
215
|
- spec/unit/visibility_spec.rb
|
216
|
+
- spec/unit/sky_condition_spec.rb
|
217
|
+
- spec/unit/speed_spec.rb
|
218
|
+
- spec/unit/wind_spec.rb
|
219
|
+
- spec/unit/distance_spec.rb
|
220
|
+
- spec/unit/temperature_spec.rb
|
221
|
+
- spec/unit/vertical_visibility_spec.rb
|
222
|
+
- spec/unit/remark_spec.rb
|
223
|
+
- spec/unit/runway_visible_range_spec.rb
|
218
224
|
- spec/unit/weather_phenomenon_spec.rb
|
219
|
-
- spec/unit/
|
225
|
+
- spec/unit/raw_spec.rb
|
220
226
|
- spec/unit/parser_spec.rb
|
221
|
-
- spec/unit/pressure_spec.rb
|
222
227
|
- spec/unit/variable_wind_spec.rb
|
223
|
-
- spec/unit/wind_spec.rb
|
224
|
-
- spec/unit/raw_spec.rb
|
225
|
-
- spec/unit/speed_spec.rb
|
226
|
-
- spec/unit/distance_spec.rb
|
227
228
|
has_rdoc:
|
data/bin/download_raw.rb
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
=begin
|
4
|
-
|
5
|
-
This script downloads the current weather report for each station.
|
6
|
-
|
7
|
-
=end
|
8
|
-
|
9
|
-
require 'rubygems' if RUBY_VERSION < '1.9'
|
10
|
-
require 'yaml'
|
11
|
-
require File.join(File.expand_path(File.dirname(__FILE__) + '/../lib'), 'metar')
|
12
|
-
|
13
|
-
Metar::Station.load_local
|
14
|
-
|
15
|
-
('A'..'Z').each do |initial|
|
16
|
-
|
17
|
-
stations = {}
|
18
|
-
|
19
|
-
Metar::Station.all.each do |station|
|
20
|
-
|
21
|
-
next if station.cccc[0, 1] < initial
|
22
|
-
break if station.cccc[0, 1] > initial
|
23
|
-
|
24
|
-
print station.cccc
|
25
|
-
raw = nil
|
26
|
-
begin
|
27
|
-
raw = Metar::Raw.new(station.cccc)
|
28
|
-
rescue Net::FTPPermError => e
|
29
|
-
puts ": Not available - #{ e }"
|
30
|
-
next
|
31
|
-
rescue
|
32
|
-
puts ": Other error - #{ e }"
|
33
|
-
next
|
34
|
-
end
|
35
|
-
|
36
|
-
stations[station.cccc] = raw.raw.clone
|
37
|
-
puts ': OK'
|
38
|
-
end
|
39
|
-
|
40
|
-
filename = File.join(File.expand_path(File.dirname(__FILE__) + '/../data'), "stations.#{ initial }.yml")
|
41
|
-
File.open(filename, 'w') { |fil| fil.write stations.to_yaml }
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
# Merge into one file
|
46
|
-
stations = {}
|
47
|
-
('A'..'Z').each do |initial|
|
48
|
-
filename = File.join(File.expand_path(File.dirname(__FILE__) + '/../data'), "stations.#{ initial }.yml")
|
49
|
-
next if not File.exist?(filename)
|
50
|
-
h = YAML.load_file(filename)
|
51
|
-
stations.merge!(h)
|
52
|
-
end
|
53
|
-
|
54
|
-
filename = File.join(File.expand_path(File.dirname(__FILE__) + '/../data'), "stations.yml")
|
55
|
-
File.open(filename, 'w') { |fil| fil.write stations.to_yaml }
|