metar-parser 1.1.4 → 1.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/metar/data.rb +132 -229
- data/lib/metar/parser.rb +5 -1
- data/lib/metar/report.rb +1 -1
- data/lib/metar/version.rb +1 -1
- data/spec/unit/parser_spec.rb +72 -129
- data/spec/unit/report_spec.rb +6 -0
- data/spec/unit/wind_spec.rb +50 -56
- metadata +4 -4
data/lib/metar/data.rb
CHANGED
@@ -4,14 +4,13 @@ require 'm9t'
|
|
4
4
|
|
5
5
|
module Metar
|
6
6
|
locales_path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'locales'))
|
7
|
-
I18n.load_path += Dir.glob("#{
|
7
|
+
I18n.load_path += Dir.glob("#{locales_path}/*.yml")
|
8
8
|
|
9
9
|
class Distance < M9t::Distance
|
10
|
-
|
11
10
|
attr_accessor :units
|
12
11
|
|
13
12
|
# nil is taken to mean 'data unavailable'
|
14
|
-
def initialize(
|
13
|
+
def initialize(meters = nil)
|
15
14
|
@units = :meters
|
16
15
|
if meters
|
17
16
|
super
|
@@ -21,19 +20,19 @@ module Metar
|
|
21
20
|
end
|
22
21
|
|
23
22
|
# Handles nil case differently to M9t::Distance
|
24
|
-
def to_s(
|
25
|
-
options = {
|
26
|
-
|
27
|
-
|
23
|
+
def to_s(options = {})
|
24
|
+
options = {
|
25
|
+
:units => @units,
|
26
|
+
:precision => 0,
|
27
|
+
:abbreviated => true
|
28
|
+
}.merge(options)
|
28
29
|
return I18n.t('metar.distance.unknown') if @value.nil?
|
29
|
-
super(
|
30
|
+
super(options)
|
30
31
|
end
|
31
|
-
|
32
32
|
end
|
33
33
|
|
34
34
|
# Adds a parse method to the M9t base class
|
35
35
|
class Speed < M9t::Speed
|
36
|
-
|
37
36
|
METAR_UNITS = {
|
38
37
|
'' => :kilometers_per_hour,
|
39
38
|
'KMH' => :kilometers_per_hour,
|
@@ -41,43 +40,39 @@ module Metar
|
|
41
40
|
'KT' => :knots,
|
42
41
|
}
|
43
42
|
|
44
|
-
def
|
43
|
+
def self.parse(s)
|
45
44
|
case
|
46
45
|
when s =~ /^(\d+)(|KT|MPS|KMH)$/
|
47
46
|
# Call the appropriate factory method for the supplied units
|
48
|
-
send(
|
47
|
+
send(METAR_UNITS[$2], $1.to_i)
|
49
48
|
else
|
50
49
|
nil
|
51
50
|
end
|
52
51
|
end
|
53
|
-
|
54
52
|
end
|
55
53
|
|
56
54
|
# Adds a parse method to the M9t base class
|
57
55
|
class Temperature < M9t::Temperature
|
58
|
-
|
59
|
-
def Temperature.parse(s)
|
56
|
+
def self.parse(s)
|
60
57
|
if s =~ /^(M?)(\d+)$/
|
61
58
|
sign = $1
|
62
59
|
value = $2.to_i
|
63
60
|
value *= -1 if sign == 'M'
|
64
|
-
new(
|
61
|
+
new(value)
|
65
62
|
else
|
66
63
|
nil
|
67
64
|
end
|
68
65
|
end
|
69
66
|
|
70
|
-
def to_s(
|
71
|
-
options = {
|
72
|
-
super(
|
67
|
+
def to_s(options = {})
|
68
|
+
options = {:abbreviated => true, :precision => 0}.merge(options)
|
69
|
+
super(options)
|
73
70
|
end
|
74
|
-
|
75
71
|
end
|
76
72
|
|
77
73
|
# Adds a parse method to the M9t base class
|
78
74
|
class Pressure < M9t::Pressure
|
79
|
-
|
80
|
-
def Pressure.parse(pressure)
|
75
|
+
def self.parse(pressure)
|
81
76
|
case
|
82
77
|
when pressure =~ /^Q(\d{4})$/
|
83
78
|
hectopascals($1.to_f)
|
@@ -87,44 +82,34 @@ module Metar
|
|
87
82
|
nil
|
88
83
|
end
|
89
84
|
end
|
90
|
-
|
91
85
|
end
|
92
86
|
|
93
87
|
class Direction < M9t::Direction
|
94
|
-
def initialize(
|
95
|
-
direction = M9t::Direction::normalize(
|
96
|
-
super(
|
88
|
+
def initialize(direction)
|
89
|
+
direction = M9t::Direction::normalize(direction.to_f)
|
90
|
+
super(direction)
|
97
91
|
end
|
98
92
|
end
|
99
93
|
|
100
94
|
class Wind
|
101
|
-
|
102
|
-
def Wind.parse(s)
|
95
|
+
def self.parse(s)
|
103
96
|
case
|
104
97
|
when s =~ /^(\d{3})(\d{2}(|MPS|KMH|KT))$/
|
105
98
|
return nil if $1.to_i > 360
|
106
|
-
new(
|
107
|
-
Speed.parse( $2 ) )
|
99
|
+
new(Direction.new($1), Speed.parse($2))
|
108
100
|
when s =~ /^(\d{3})(\d{2})G(\d{2,3}(|MPS|KMH|KT))$/
|
109
101
|
return nil if $1.to_i > 360
|
110
|
-
new(
|
111
|
-
Speed.parse( $2 + $4 ),
|
112
|
-
Speed.parse( $3 ) )
|
102
|
+
new(Direction.new($1), Speed.parse($2 + $4), Speed.parse($3))
|
113
103
|
when s =~ /^VRB(\d{2})G(\d{2,3})(|MPS|KMH|KT)$/
|
114
104
|
speed = $1 + $3
|
115
105
|
gusts = $2 + $3
|
116
|
-
new(
|
117
|
-
Speed.parse(speed),
|
118
|
-
Speed.parse(gusts))
|
106
|
+
new(:variable_direction, Speed.parse(speed), Speed.parse(gusts))
|
119
107
|
when s =~ /^VRB(\d{2}(|MPS|KMH|KT))$/
|
120
|
-
new(
|
121
|
-
Speed.parse($1))
|
108
|
+
new(:variable_direction, Speed.parse($1))
|
122
109
|
when s =~ /^\/{3}(\d{2}(|MPS|KMH|KT))$/
|
123
|
-
new(
|
124
|
-
Speed.parse($1))
|
110
|
+
new(:unknown_direction, Speed.parse($1))
|
125
111
|
when s =~ %r(^/////(|MPS|KMH|KT)$)
|
126
|
-
new(
|
127
|
-
:unknown_speed)
|
112
|
+
new(:unknown_direction, :unknown_speed)
|
128
113
|
else
|
129
114
|
nil
|
130
115
|
end
|
@@ -132,21 +117,25 @@ module Metar
|
|
132
117
|
|
133
118
|
attr_reader :direction, :speed, :gusts
|
134
119
|
|
135
|
-
def initialize(
|
120
|
+
def initialize(direction, speed, gusts = nil)
|
136
121
|
@direction, @speed, @gusts = direction, speed, gusts
|
137
122
|
end
|
138
123
|
|
139
|
-
def to_s(
|
140
|
-
options = {
|
141
|
-
|
124
|
+
def to_s(options = {})
|
125
|
+
options = {
|
126
|
+
:direction_units => :compass,
|
127
|
+
:speed_units => :kilometers_per_hour
|
128
|
+
}.merge(options)
|
142
129
|
speed =
|
143
130
|
case @speed
|
144
131
|
when :unknown_speed
|
145
132
|
I18n.t('metar.wind.unknown_speed')
|
146
133
|
else
|
147
|
-
@speed.to_s(
|
148
|
-
|
149
|
-
|
134
|
+
@speed.to_s(
|
135
|
+
:abbreviated => true,
|
136
|
+
:precision => 0,
|
137
|
+
:units => options[:speed_units]
|
138
|
+
)
|
150
139
|
end
|
151
140
|
direction =
|
152
141
|
case @direction
|
@@ -155,23 +144,23 @@ module Metar
|
|
155
144
|
when :unknown_direction
|
156
145
|
I18n.t('metar.wind.unknown_direction')
|
157
146
|
else
|
158
|
-
@direction.to_s(
|
147
|
+
@direction.to_s(:units => options[:direction_units])
|
159
148
|
end
|
160
|
-
s = "#{
|
161
|
-
if
|
162
|
-
g =
|
163
|
-
|
164
|
-
|
165
|
-
|
149
|
+
s = "#{speed} #{direction}"
|
150
|
+
if not @gusts.nil?
|
151
|
+
g = @gusts.to_s(
|
152
|
+
:abbreviated => true,
|
153
|
+
:precision => 0,
|
154
|
+
:units => options[:speed_units]
|
155
|
+
)
|
156
|
+
s += " #{I18n.t('metar.wind.gusts')} #{g}"
|
166
157
|
end
|
167
158
|
s
|
168
159
|
end
|
169
|
-
|
170
160
|
end
|
171
161
|
|
172
162
|
class VariableWind
|
173
|
-
|
174
|
-
def VariableWind.parse(variable_wind)
|
163
|
+
def self.parse(variable_wind)
|
175
164
|
if variable_wind =~ /^(\d+)V(\d+)$/
|
176
165
|
new(Direction.new($1), Direction.new($2))
|
177
166
|
else
|
@@ -186,38 +175,36 @@ module Metar
|
|
186
175
|
end
|
187
176
|
|
188
177
|
def to_s
|
189
|
-
"#{
|
178
|
+
"#{@direction1.to_s(:units => :compass)} - #{@direction2.to_s(:units => :compass)}"
|
190
179
|
end
|
191
|
-
|
192
180
|
end
|
193
181
|
|
194
182
|
class Visibility
|
195
|
-
|
196
|
-
def Visibility.parse(s)
|
183
|
+
def self.parse(s)
|
197
184
|
case
|
198
185
|
when s == '9999'
|
199
|
-
new(
|
186
|
+
new(Distance.new(10000), nil, :more_than)
|
200
187
|
when s =~ /(\d{4})NDV/ # WMO
|
201
|
-
new(
|
188
|
+
new(Distance.new($1.to_f)) # Assuming meters
|
202
189
|
when (s =~ /^((1|2)\s|)([1357])\/([248]|16)SM$/) # US
|
203
190
|
miles = $1.to_f + $3.to_f / $4.to_f
|
204
|
-
distance = Distance.miles(
|
191
|
+
distance = Distance.miles(miles)
|
205
192
|
distance.units = :miles
|
206
|
-
new(
|
193
|
+
new(distance)
|
207
194
|
when s =~ /^(\d+)SM$/ # US
|
208
|
-
distance = Distance.miles(
|
195
|
+
distance = Distance.miles($1.to_f)
|
209
196
|
distance.units = :miles
|
210
|
-
new(
|
197
|
+
new(distance)
|
211
198
|
when s == 'M1/4SM' # US
|
212
|
-
distance = Distance.miles(
|
199
|
+
distance = Distance.miles(0.25)
|
213
200
|
distance.units = :miles
|
214
|
-
new(
|
201
|
+
new(distance, nil, :less_than)
|
215
202
|
when s =~ /^(\d+)KM$/
|
216
|
-
new(
|
203
|
+
new(Distance.kilometers($1))
|
217
204
|
when s =~ /^(\d+)$/ # We assume meters
|
218
|
-
new(
|
205
|
+
new(Distance.new($1))
|
219
206
|
when s =~ /^(\d+)(N|NE|E|SE|S|SW|W|NW)$/
|
220
|
-
new(
|
207
|
+
new(Distance.meters($1), M9t::Direction.compass($2))
|
221
208
|
else
|
222
209
|
nil
|
223
210
|
end
|
@@ -229,36 +216,42 @@ module Metar
|
|
229
216
|
@distance, @direction, @comparator = distance, direction, comparator
|
230
217
|
end
|
231
218
|
|
232
|
-
def to_s(
|
233
|
-
distance_options = {
|
234
|
-
|
235
|
-
|
236
|
-
|
219
|
+
def to_s(options = {})
|
220
|
+
distance_options = {
|
221
|
+
:abbreviated => true,
|
222
|
+
:precision => 0,
|
223
|
+
:units => :kilometers
|
224
|
+
}.merge(options)
|
225
|
+
direction_options = {:units => :compass}
|
237
226
|
case
|
238
|
-
when (
|
239
|
-
@distance.to_s(
|
227
|
+
when (@direction.nil? and @comparator.nil?)
|
228
|
+
@distance.to_s(distance_options)
|
240
229
|
when @comparator.nil?
|
241
|
-
|
242
|
-
|
230
|
+
[
|
231
|
+
@distance.to_s(distance_options),
|
232
|
+
@direction.to_s(direction_options)
|
233
|
+
].join(' ')
|
243
234
|
when @direction.nil?
|
244
|
-
|
245
|
-
|
235
|
+
[
|
236
|
+
I18n.t('comparison.' + @comparator.to_s),
|
237
|
+
@distance.to_s(distance_options)
|
238
|
+
].join(' ')
|
246
239
|
else
|
247
|
-
|
248
|
-
|
249
|
-
|
240
|
+
[
|
241
|
+
I18n.t('comparison.' + @comparator.to_s),
|
242
|
+
@distance.to_s(distance_options),
|
243
|
+
@direction.to_s(direction_options)
|
244
|
+
].join(' ')
|
250
245
|
end
|
251
246
|
end
|
252
|
-
|
253
247
|
end
|
254
248
|
|
255
249
|
class RunwayVisibleRange
|
250
|
+
TENDENCY = {'' => nil, 'N' => :no_change, 'U' => :improving, 'D' => :worsening}
|
251
|
+
COMPARATOR = {'' => nil, 'P' => :more_than, 'M' => :less_than}
|
252
|
+
UNITS = {'' => :meters, 'FT' => :feet}
|
256
253
|
|
257
|
-
|
258
|
-
COMPARATOR = { '' => nil, 'P' => :more_than, 'M' => :less_than }
|
259
|
-
UNITS = { '' => :meters, 'FT' => :feet }
|
260
|
-
|
261
|
-
def RunwayVisibleRange.parse(runway_visible_range)
|
254
|
+
def self.parse(runway_visible_range)
|
262
255
|
case
|
263
256
|
when runway_visible_range =~ /^R(\d+[RLC]?)\/(P|M|)(\d{4})(N|U|D|)(FT|)$/
|
264
257
|
designator = $1
|
@@ -266,7 +259,7 @@ module Metar
|
|
266
259
|
count = $3.to_f
|
267
260
|
tendency = TENDENCY[$4]
|
268
261
|
units = UNITS[$5]
|
269
|
-
distance = Distance.send(
|
262
|
+
distance = Distance.send(units, count)
|
270
263
|
visibility = Visibility.new(distance, nil, comparator)
|
271
264
|
new(designator, visibility, nil, tendency)
|
272
265
|
when runway_visible_range =~ /^R(\d+[RLC]?)\/(P|M|)(\d{4})V(P|M|)(\d{4})(N|U|D)?(FT|)$/
|
@@ -277,50 +270,50 @@ module Metar
|
|
277
270
|
count2 = $5.to_f
|
278
271
|
tendency = TENDENCY[$6]
|
279
272
|
units = UNITS[$7]
|
280
|
-
distance1 = Distance.send(
|
281
|
-
distance2 = Distance.send(
|
282
|
-
visibility1 = Visibility.new(
|
283
|
-
visibility2 = Visibility.new(
|
284
|
-
new(
|
273
|
+
distance1 = Distance.send(units, count1)
|
274
|
+
distance2 = Distance.send(units, count2)
|
275
|
+
visibility1 = Visibility.new(distance1, nil, comparator1)
|
276
|
+
visibility2 = Visibility.new(distance2, nil, comparator2)
|
277
|
+
new(designator, visibility1, visibility2, tendency, units)
|
285
278
|
else
|
286
279
|
nil
|
287
280
|
end
|
288
281
|
end
|
289
282
|
|
290
283
|
attr_reader :designator, :visibility1, :visibility2, :tendency
|
291
|
-
def initialize(
|
284
|
+
def initialize(designator, visibility1, visibility2 = nil, tendency = nil, units = :meters)
|
292
285
|
@designator, @visibility1, @visibility2, @tendency, @units = designator, visibility1, visibility2, tendency, units
|
293
286
|
end
|
294
287
|
|
295
288
|
def to_s
|
296
|
-
distance_options = {
|
297
|
-
|
298
|
-
|
289
|
+
distance_options = {
|
290
|
+
:abbreviated => true,
|
291
|
+
:precision => 0,
|
292
|
+
:units => @units
|
293
|
+
}
|
299
294
|
s =
|
300
295
|
if @visibility2.nil?
|
301
|
-
I18n.t(
|
302
|
-
|
303
|
-
|
296
|
+
I18n.t('metar.runway_visible_range.runway') +
|
297
|
+
' ' + @designator +
|
298
|
+
': ' + @visibility1.to_s(distance_options)
|
304
299
|
else
|
305
|
-
I18n.t(
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
300
|
+
I18n.t('metar.runway_visible_range.runway') +
|
301
|
+
' ' + @designator +
|
302
|
+
': ' + I18n.t('metar.runway_visible_range.from') +
|
303
|
+
' ' + @visibility1.to_s(distance_options) +
|
304
|
+
' ' + I18n.t('metar.runway_visible_range.to') +
|
305
|
+
' ' + @visibility2.to_s(distance_options)
|
311
306
|
end
|
312
307
|
|
313
308
|
if ! tendency.nil?
|
314
|
-
s += ' ' + I18n.t(
|
309
|
+
s += ' ' + I18n.t("tendency.#{tendency}")
|
315
310
|
end
|
316
311
|
|
317
312
|
s
|
318
313
|
end
|
319
|
-
|
320
314
|
end
|
321
315
|
|
322
316
|
class WeatherPhenomenon
|
323
|
-
|
324
317
|
Modifiers = {
|
325
318
|
'+' => 'heavy',
|
326
319
|
'-' => 'light',
|
@@ -368,21 +361,21 @@ module Metar
|
|
368
361
|
}
|
369
362
|
|
370
363
|
# Accepts all standard (and some non-standard) present weather codes
|
371
|
-
def
|
364
|
+
def self.parse(s)
|
372
365
|
phenomena = Phenomena.keys.join('|')
|
373
366
|
descriptors = Descriptors.keys.join('|')
|
374
367
|
modifiers = Modifiers.keys.join('|')
|
375
368
|
modifiers.gsub!(/([\+\-])/) { "\\#$1" }
|
376
|
-
rxp = Regexp.new("^(#{
|
369
|
+
rxp = Regexp.new("^(#{modifiers})?(#{descriptors})?((?:#{phenomena}){1,2})$")
|
377
370
|
m = rxp.match(s)
|
378
371
|
return nil if m.nil?
|
379
372
|
|
380
373
|
modifier_code = m[1]
|
381
374
|
descriptor_code = m[2]
|
382
375
|
phenomena_codes = m[3].scan(/../)
|
383
|
-
phenomena_phrase = phenomena_codes.map{ |c| Phenomena[c] }.join(' and ')
|
376
|
+
phenomena_phrase = phenomena_codes.map { |c| Phenomena[c] }.join(' and ')
|
384
377
|
|
385
|
-
|
378
|
+
new(phenomena_phrase, Modifiers[modifier_code], Descriptors[descriptor_code])
|
386
379
|
end
|
387
380
|
|
388
381
|
attr_reader :phenomenon, :modifier, :descriptor
|
@@ -393,11 +386,9 @@ module Metar
|
|
393
386
|
def to_s
|
394
387
|
I18n.t("metar.present_weather.%s" % [@modifier, @descriptor, @phenomenon].compact.join(' '))
|
395
388
|
end
|
396
|
-
|
397
389
|
end
|
398
390
|
|
399
391
|
class SkyCondition
|
400
|
-
|
401
392
|
QUANTITY = {'BKN' => 'broken', 'FEW' => 'few', 'OVC' => 'overcast', 'SCT' => 'scattered'}
|
402
393
|
CONDITION = {
|
403
394
|
'CB' => 'cumulonimbus',
|
@@ -412,22 +403,22 @@ module Metar
|
|
412
403
|
'SKC',
|
413
404
|
]
|
414
405
|
|
415
|
-
def
|
406
|
+
def self.parse(sky_condition)
|
416
407
|
case
|
417
|
-
when CLEAR_SKIES.include?(
|
408
|
+
when CLEAR_SKIES.include?(sky_condition)
|
418
409
|
new
|
419
410
|
when sky_condition =~ /^(BKN|FEW|OVC|SCT)(\d+|\/{3})(CB|TCU|\/{3}|)?$/
|
420
|
-
quantity = QUANTITY[
|
411
|
+
quantity = QUANTITY[$1]
|
421
412
|
height =
|
422
413
|
if $2 == '///'
|
423
414
|
nil
|
424
415
|
else
|
425
|
-
Distance.new(
|
416
|
+
Distance.new($2.to_i * 30.48)
|
426
417
|
end
|
427
|
-
type = CONDITION[
|
418
|
+
type = CONDITION[$3]
|
428
419
|
new(quantity, height, type)
|
429
420
|
when sky_condition =~ /^(CB|TCU)$/
|
430
|
-
type = CONDITION[
|
421
|
+
type = CONDITION[$1]
|
431
422
|
new(nil, nil, type)
|
432
423
|
else
|
433
424
|
nil
|
@@ -452,29 +443,25 @@ module Metar
|
|
452
443
|
I18n.t('metar.sky_conditions.clear skies')
|
453
444
|
else
|
454
445
|
type = @type ? ' ' + @type : ''
|
455
|
-
I18n.t("metar.sky_conditions.#{
|
446
|
+
I18n.t("metar.sky_conditions.#{@quantity}#{type}")
|
456
447
|
end
|
457
448
|
end
|
458
|
-
|
459
449
|
end
|
460
450
|
|
461
451
|
class VerticalVisibility
|
462
|
-
|
463
|
-
def VerticalVisibility.parse( vertical_visibility )
|
452
|
+
def self.parse(vertical_visibility)
|
464
453
|
case
|
465
454
|
when vertical_visibility =~ /^VV(\d{3})$/
|
466
|
-
Distance.new(
|
455
|
+
Distance.new($1.to_f * 30.48)
|
467
456
|
when vertical_visibility == '///'
|
468
457
|
Distance.new
|
469
458
|
else
|
470
459
|
nil
|
471
460
|
end
|
472
461
|
end
|
473
|
-
|
474
462
|
end
|
475
463
|
|
476
464
|
class Remark
|
477
|
-
|
478
465
|
PRESSURE_CHANGE_CHARACTER = [
|
479
466
|
:increasing_then_decreasing, # 0
|
480
467
|
:increasing_then_steady, # 1
|
@@ -488,7 +475,7 @@ module Metar
|
|
488
475
|
]
|
489
476
|
|
490
477
|
INDICATOR_TYPE = {
|
491
|
-
'TS'
|
478
|
+
'TS' => :thunderstorm_information,
|
492
479
|
'PWI' => :precipitation_identifier,
|
493
480
|
'P' => :precipitation_amount,
|
494
481
|
}
|
@@ -556,89 +543,20 @@ module Metar
|
|
556
543
|
def self.inches_to_meters(digits)
|
557
544
|
digits.to_f * 0.000254
|
558
545
|
end
|
559
|
-
|
560
|
-
end
|
561
|
-
|
562
|
-
class TemperatureExtreme
|
563
|
-
|
564
|
-
attr_accessor :value
|
565
|
-
attr_accessor :extreme
|
566
|
-
|
567
|
-
def initialize(extreme, value)
|
568
|
-
@extreme, @value = extreme, value
|
569
|
-
end
|
570
|
-
|
571
546
|
end
|
572
547
|
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
548
|
+
TemperatureExtreme = Struct.new(:extreme, :value)
|
549
|
+
PressureTendency = Struct.new(:character, :value)
|
550
|
+
Precipitation = Struct.new(:period, :amount)
|
551
|
+
AutomatedStationType = Struct.new(:type)
|
552
|
+
HourlyTemperaturAndDewPoint = Struct.new(:temperature, :dew_point)
|
553
|
+
SeaLevelPressure = Struct.new(:pressure)
|
554
|
+
SensorStatusIndicator = Struct.new(:type, :state)
|
555
|
+
ColorCode = Struct.new(:code)
|
577
556
|
|
578
|
-
|
579
|
-
@character, @value = character, value
|
580
|
-
end
|
581
|
-
|
582
|
-
end
|
583
|
-
|
584
|
-
class Precipitation
|
585
|
-
|
586
|
-
attr_accessor :period
|
587
|
-
attr_accessor :amount
|
588
|
-
|
589
|
-
def initialize(period, amount)
|
590
|
-
@period, @amount = period, amount
|
591
|
-
end
|
592
|
-
|
593
|
-
end
|
594
|
-
|
595
|
-
class AutomatedStationType
|
596
|
-
|
597
|
-
attr_accessor :type
|
598
|
-
|
599
|
-
def initialize(type)
|
600
|
-
@type = type
|
601
|
-
end
|
602
|
-
|
603
|
-
end
|
604
|
-
|
605
|
-
class HourlyTemperaturAndDewPoint
|
606
|
-
|
607
|
-
attr_accessor :temperature
|
608
|
-
attr_accessor :dew_point
|
609
|
-
|
610
|
-
def initialize(temperature, dew_point)
|
611
|
-
@temperature, @dew_point = temperature, dew_point
|
612
|
-
end
|
613
|
-
|
614
|
-
end
|
615
|
-
|
616
|
-
class SeaLevelPressure
|
617
|
-
|
618
|
-
attr_accessor :pressure
|
619
|
-
|
620
|
-
def initialize(pressure)
|
621
|
-
@pressure = pressure
|
622
|
-
end
|
623
|
-
|
624
|
-
end
|
625
|
-
|
626
|
-
class SensorStatusIndicator
|
627
|
-
|
628
|
-
attr_accessor :type
|
629
|
-
attr_accessor :state
|
630
|
-
|
631
|
-
def initialize(type, state)
|
632
|
-
@type, @state = type, state
|
633
|
-
end
|
634
|
-
|
635
|
-
end
|
636
|
-
|
637
|
-
class MaintenanceNeeded
|
638
|
-
end
|
557
|
+
class MaintenanceNeeded; end
|
639
558
|
|
640
559
|
class Lightning
|
641
|
-
|
642
560
|
TYPE = {'' => :default}
|
643
561
|
|
644
562
|
def self.parse_chunks(chunks)
|
@@ -695,18 +613,15 @@ module Metar
|
|
695
613
|
end
|
696
614
|
|
697
615
|
class VisibilityRemark < Visibility
|
698
|
-
|
699
616
|
def self.parse(chunk)
|
700
617
|
chunk =~ /^(\d{4})([NESW]?)$/
|
701
618
|
distance = Distance.new($1)
|
702
619
|
|
703
620
|
new(distance, $2, :more_than)
|
704
621
|
end
|
705
|
-
|
706
622
|
end
|
707
623
|
|
708
624
|
class DensityAltitude
|
709
|
-
|
710
625
|
def self.parse(chunk)
|
711
626
|
chunk =~ /^(\d+)(FT)$/
|
712
627
|
height = Distance.feet($1)
|
@@ -719,18 +634,6 @@ module Metar
|
|
719
634
|
def initialize(height)
|
720
635
|
@height = height
|
721
636
|
end
|
722
|
-
|
723
|
-
end
|
724
|
-
|
725
|
-
class ColorCode
|
726
|
-
|
727
|
-
attr_accessor :code
|
728
|
-
|
729
|
-
def initialize(code)
|
730
|
-
@code = code
|
731
|
-
end
|
732
|
-
|
733
637
|
end
|
734
|
-
|
735
638
|
end
|
736
639
|
|
data/lib/metar/parser.rb
CHANGED
data/lib/metar/report.rb
CHANGED
data/lib/metar/version.rb
CHANGED
data/spec/unit/parser_spec.rb
CHANGED
@@ -1,32 +1,28 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
2
|
+
require 'spec_helper'
|
3
3
|
|
4
4
|
describe Metar::Parser do
|
5
|
-
|
6
|
-
after :each do
|
5
|
+
after do
|
7
6
|
Metar::Parser.compliance = :loose
|
8
7
|
end
|
9
8
|
|
10
9
|
context '.for_cccc' do
|
11
|
-
|
12
10
|
it 'returns a loaded parser' do
|
13
|
-
station = stub(
|
14
|
-
raw = stub(
|
15
|
-
:time => '2010/02/06 16:10'
|
16
|
-
Metar::Station.stub!(
|
17
|
-
Metar::Raw::Noaa.stub!(
|
11
|
+
station = stub('station')
|
12
|
+
raw = stub('raw', :metar => "XXXX 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000",
|
13
|
+
:time => '2010/02/06 16:10')
|
14
|
+
Metar::Station.stub!(:new => station)
|
15
|
+
Metar::Raw::Noaa.stub!(:new => raw)
|
18
16
|
|
19
|
-
parser = Metar::Parser.for_cccc(
|
17
|
+
parser = Metar::Parser.for_cccc('XXXX')
|
20
18
|
|
21
|
-
parser.
|
22
|
-
parser.station_code.
|
19
|
+
expect(parser).to be_a(Metar::Parser)
|
20
|
+
expect(parser.station_code).to eq('XXXX')
|
23
21
|
end
|
24
|
-
|
25
22
|
end
|
26
23
|
|
27
24
|
context 'attributes' do
|
28
|
-
|
29
|
-
before :each do
|
25
|
+
before do
|
30
26
|
@call_time = Time.parse('2011-05-06 16:35')
|
31
27
|
Time.stub!(:now).and_return(@call_time)
|
32
28
|
end
|
@@ -34,315 +30,262 @@ describe Metar::Parser do
|
|
34
30
|
it '.location missing' do
|
35
31
|
expect do
|
36
32
|
setup_parser("FUBAR 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
37
|
-
end.
|
33
|
+
end.to raise_error(Metar::ParseError, /Expecting location/)
|
38
34
|
end
|
39
35
|
|
40
36
|
context 'datetime' do
|
41
|
-
|
42
37
|
it 'is parsed' do
|
43
38
|
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
44
39
|
|
45
|
-
parser.time.
|
40
|
+
expect(parser.time).to eq(Time.gm(2011, 05, 06, 16, 10))
|
46
41
|
end
|
47
42
|
|
48
43
|
it 'throws an error is missing' do
|
49
44
|
expect do
|
50
45
|
setup_parser("PAIL 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
51
|
-
end.
|
46
|
+
end.to raise_error(Metar::ParseError, /Expecting datetime/)
|
52
47
|
end
|
53
48
|
|
54
49
|
context 'in strict mode' do
|
55
|
-
|
56
|
-
before :each do
|
50
|
+
before do
|
57
51
|
Metar::Parser.compliance = :strict
|
58
52
|
end
|
59
53
|
|
60
54
|
it 'less than 6 numerals fails' do
|
61
55
|
expect do
|
62
56
|
parser = setup_parser('MMCE 21645Z 12010KT 8SM SKC 29/26 A2992 RMK')
|
63
|
-
end.
|
57
|
+
end.to raise_error(Metar::ParseError, /Expecting datetime/)
|
64
58
|
end
|
65
|
-
|
66
59
|
end
|
67
60
|
|
68
61
|
context 'in loose mode' do
|
69
|
-
|
70
62
|
it '5 numerals parses' do
|
71
63
|
parser = setup_parser('MMCE 21645Z 12010KT 8SM SKC 29/26 A2992 RMK')
|
72
64
|
|
73
|
-
parser.time.
|
65
|
+
expect(parser.time).to eq(Time.gm(2011, 05, 02, 16, 45))
|
74
66
|
end
|
75
67
|
|
76
68
|
it "with 4 numerals parses, takes today's day" do
|
77
69
|
parser = setup_parser('HKML 1600Z 19010KT 9999 FEW022 25/22 Q1015')
|
78
70
|
|
79
|
-
parser.time.
|
71
|
+
expect(parser.time).to eq(Time.gm(2011, 05, 06, 16, 00))
|
80
72
|
end
|
81
|
-
|
82
73
|
end
|
83
|
-
|
84
74
|
end
|
85
75
|
|
86
76
|
context '.observer' do
|
87
|
-
|
88
77
|
it 'real' do
|
89
78
|
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
90
79
|
|
91
|
-
parser.observer.
|
80
|
+
expect(parser.observer).to eq(:real)
|
92
81
|
end
|
93
82
|
|
94
83
|
it 'auto' do
|
95
84
|
parser = setup_parser("CYXS 151034Z AUTO 09003KT 1/8SM FZFG VV001 M03/M03 A3019 RMK SLP263 ICG")
|
96
85
|
|
97
|
-
parser.observer.
|
86
|
+
expect(parser.observer).to eq(:auto)
|
98
87
|
end
|
99
88
|
|
100
89
|
it 'corrected' do
|
101
90
|
parser = setup_parser("PAIL 061610Z COR 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
102
91
|
|
103
|
-
parser.observer.
|
92
|
+
expect(parser.observer).to eq(:corrected)
|
104
93
|
end
|
105
94
|
|
106
95
|
it 'corrected (Canadian)' do
|
107
96
|
parser = setup_parser('CYZU 310100Z CCA 26004KT 15SM FEW009 BKN040TCU BKN100 OVC210 15/12 A2996 RETS RMK SF1TCU4AC2CI1 SLP149')
|
108
97
|
|
109
|
-
parser.observer.
|
98
|
+
expect(parser.observer).to eq(:corrected)
|
110
99
|
end
|
111
|
-
|
112
100
|
end
|
113
101
|
|
114
102
|
it 'wind' do
|
115
103
|
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
116
104
|
|
117
|
-
parser.wind.direction.value.
|
118
|
-
parser.wind.speed.to_knots.
|
105
|
+
expect(parser.wind.direction.value).to be_within(0.0001).of(240)
|
106
|
+
expect(parser.wind.speed.to_knots).to be_within(0.0001).of(6)
|
119
107
|
end
|
120
108
|
|
121
109
|
it 'variable_wind' do
|
122
110
|
parser = setup_parser("LIRQ 061520Z 01007KT 350V050 9999 SCT035 BKN080 08/02 Q1005")
|
123
111
|
|
124
|
-
parser.variable_wind.direction1.value.
|
125
|
-
|
126
|
-
parser.variable_wind.direction2.value.
|
127
|
-
should be_within( 0.0001 ).of( 50 )
|
112
|
+
expect(parser.variable_wind.direction1.value).to be_within(0.0001).of(350)
|
113
|
+
expect(parser.variable_wind.direction2.value).to be_within(0.0001).of(50)
|
128
114
|
end
|
129
115
|
|
130
116
|
context '.visibility' do
|
131
117
|
it 'CAVOK' do
|
132
118
|
parser = setup_parser("PAIL 061610Z 24006KT CAVOK M17/M20 A2910 RMK AO2 P0000")
|
133
119
|
|
134
|
-
parser.visibility.distance.value.
|
135
|
-
|
136
|
-
parser.
|
137
|
-
|
138
|
-
parser.
|
139
|
-
|
140
|
-
parser.present_weather[ 0 ].phenomenon.
|
141
|
-
should == 'No significant weather'
|
142
|
-
parser.sky_conditions.size.
|
143
|
-
should == 1
|
144
|
-
parser.sky_conditions[ 0 ].type.
|
145
|
-
should == nil
|
120
|
+
expect(parser.visibility.distance.value).to be_within(0.01).of(10000.00)
|
121
|
+
expect(parser.visibility.comparator).to eq(:more_than)
|
122
|
+
expect(parser.present_weather.size).to eq(1)
|
123
|
+
expect(parser.present_weather[0].phenomenon).to eq('No significant weather')
|
124
|
+
expect(parser.sky_conditions.size).to eq(1)
|
125
|
+
expect(parser.sky_conditions[0].type).to eq(nil)
|
146
126
|
end
|
147
127
|
|
148
128
|
it 'visibility_miles_and_fractions' do
|
149
129
|
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
150
130
|
|
151
|
-
parser.visibility.distance.to_miles.
|
152
|
-
should be_within( 0.01 ).of( 1.75 )
|
131
|
+
expect(parser.visibility.distance.to_miles).to be_within(0.01).of(1.75)
|
153
132
|
end
|
154
133
|
|
155
134
|
it 'in meters' do
|
156
135
|
parser = setup_parser('VABB 282210Z 22005KT 4000 HZ SCT018 FEW025TCU BKN100 28/25 Q1003 NOSIG')
|
157
136
|
|
158
|
-
parser.visibility.distance.value.
|
159
|
-
should be_within(0.01).of(4000)
|
137
|
+
expect(parser.visibility.distance.value).to be_within(0.01).of(4000)
|
160
138
|
end
|
161
139
|
|
162
140
|
it '//// with automatic observer' do
|
163
141
|
parser = setup_parser("CYXS 151034Z AUTO 09003KT //// FZFG VV001 M03/M03 A3019 RMK SLP263 ICG")
|
164
142
|
|
165
|
-
parser.visibility.
|
143
|
+
expect(parser.visibility).to be_nil
|
166
144
|
end
|
167
145
|
end
|
168
146
|
|
169
147
|
it 'runway_visible_range' do
|
170
148
|
parser = setup_parser("ESSB 151020Z 26003KT 2000 R12/1000N R30/1500N VV002 M07/M07 Q1013 1271//55")
|
171
|
-
parser.runway_visible_range.
|
172
|
-
|
173
|
-
parser.runway_visible_range[0].
|
174
|
-
|
175
|
-
parser.runway_visible_range[0].visibility1.distance.value.
|
176
|
-
should == 1000
|
177
|
-
parser.runway_visible_range[0].tendency.
|
178
|
-
should == :no_change
|
149
|
+
expect(parser.runway_visible_range.size).to eq(2)
|
150
|
+
expect(parser.runway_visible_range[0].designator).to eq('12')
|
151
|
+
expect(parser.runway_visible_range[0].visibility1.distance.value).to eq(1000)
|
152
|
+
expect(parser.runway_visible_range[0].tendency).to eq(:no_change)
|
179
153
|
end
|
180
154
|
|
181
155
|
it 'runway_visible_range_defaults_to_empty_array' do
|
182
156
|
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
183
157
|
|
184
|
-
parser.runway_visible_range.
|
185
|
-
should == 0
|
158
|
+
expect(parser.runway_visible_range.size).to eq(0)
|
186
159
|
end
|
187
160
|
|
188
161
|
it 'runway_visible_range_variable' do
|
189
162
|
parser = setup_parser("KPDX 151108Z 11006KT 1/4SM R10R/1600VP6000FT FG OVC002 05/05 A3022 RMK AO2")
|
190
163
|
|
191
|
-
parser.runway_visible_range[0].visibility1.distance.to_feet.
|
192
|
-
|
193
|
-
parser.runway_visible_range[0].visibility2.distance.to_feet.
|
194
|
-
should == 6000.0
|
164
|
+
expect(parser.runway_visible_range[0].visibility1.distance.to_feet).to eq(1600.0)
|
165
|
+
expect(parser.runway_visible_range[0].visibility2.distance.to_feet).to eq(6000.0)
|
195
166
|
end
|
196
167
|
|
197
168
|
context '.present_weather' do
|
198
|
-
|
199
169
|
it 'normal' do
|
200
170
|
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
201
171
|
|
202
|
-
parser.present_weather.size.
|
203
|
-
|
204
|
-
parser.present_weather[0].
|
205
|
-
should == 'light'
|
206
|
-
parser.present_weather[0].phenomenon.
|
207
|
-
should == 'snow'
|
172
|
+
expect(parser.present_weather.size).to eq(1)
|
173
|
+
expect(parser.present_weather[0].modifier).to eq('light')
|
174
|
+
expect(parser.present_weather[0].phenomenon).to eq('snow')
|
208
175
|
end
|
209
176
|
|
210
177
|
it 'auto + //' do
|
211
178
|
parser = setup_parser("PAIL 061610Z AUTO 24006KT 1 3/4SM // BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
212
179
|
|
213
|
-
parser.present_weather.size.
|
214
|
-
|
215
|
-
parser.present_weather[0].phenomenon.
|
216
|
-
should == 'not observed'
|
180
|
+
expect(parser.present_weather.size).to eq(1)
|
181
|
+
expect(parser.present_weather[0].phenomenon).to eq('not observed')
|
217
182
|
end
|
218
|
-
|
219
183
|
end
|
220
184
|
|
221
185
|
it 'present_weather_defaults_to_empty_array' do
|
222
186
|
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
223
|
-
parser.present_weather.
|
224
|
-
should == 0
|
187
|
+
expect(parser.present_weather.size).to eq(0)
|
225
188
|
end
|
226
189
|
|
227
190
|
context '.sky_conditions' do
|
228
|
-
|
229
191
|
it 'normal' do
|
230
192
|
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
231
193
|
|
232
|
-
parser.sky_conditions.size.
|
233
|
-
|
234
|
-
parser.sky_conditions[0].
|
235
|
-
|
236
|
-
parser.sky_conditions[
|
237
|
-
should == 487.68
|
238
|
-
parser.sky_conditions[1].quantity.
|
239
|
-
should == 'overcast'
|
240
|
-
parser.sky_conditions[1].height.value.
|
241
|
-
should == 914.40
|
194
|
+
expect(parser.sky_conditions.size).to eq(2)
|
195
|
+
expect(parser.sky_conditions[0].quantity).to eq('broken')
|
196
|
+
expect(parser.sky_conditions[0].height.value).to eq(487.68)
|
197
|
+
expect(parser.sky_conditions[1].quantity).to eq('overcast')
|
198
|
+
expect(parser.sky_conditions[1].height.value).to eq(914.40)
|
242
199
|
end
|
243
200
|
|
244
201
|
it 'auto + ///' do
|
245
202
|
parser = setup_parser("PAIL 061610Z AUTO 24006KT 1 3/4SM /// M17/M20 A2910 RMK AO2 P0000")
|
246
203
|
|
247
|
-
parser.sky_conditions.size.
|
248
|
-
should == 0
|
204
|
+
expect(parser.sky_conditions.size).to eq(0)
|
249
205
|
end
|
250
|
-
|
251
206
|
end
|
252
207
|
|
253
208
|
it 'sky_conditions_defaults_to_empty_array' do
|
254
209
|
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN M17/M20 A2910 RMK AO2 P0000")
|
255
|
-
parser.sky_conditions.
|
256
|
-
should == 0
|
210
|
+
expect(parser.sky_conditions.size).to eq(0)
|
257
211
|
end
|
258
212
|
|
259
213
|
it 'vertical_visibility' do
|
260
214
|
parser = setup_parser("CYXS 151034Z AUTO 09003KT 1/8SM FZFG VV001 M03/M03 A3019 RMK SLP263 ICG")
|
261
|
-
parser.vertical_visibility.value.
|
262
|
-
should == 30.48
|
215
|
+
expect(parser.vertical_visibility.value).to eq(30.48)
|
263
216
|
end
|
264
217
|
|
265
218
|
it 'temperature' do
|
266
219
|
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
267
|
-
parser.temperature.value.
|
220
|
+
expect(parser.temperature.value).to eq(-17)
|
268
221
|
end
|
269
222
|
|
270
223
|
it 'dew_point' do
|
271
224
|
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
272
|
-
parser.dew_point.value.
|
225
|
+
expect(parser.dew_point.value).to eq(-20)
|
273
226
|
end
|
274
227
|
|
275
228
|
it 'sea_level_pressure' do
|
276
229
|
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
277
|
-
parser.sea_level_pressure.to_inches_of_mercury.
|
278
|
-
should == 29.10
|
230
|
+
expect(parser.sea_level_pressure.to_inches_of_mercury).to eq(29.10)
|
279
231
|
end
|
280
232
|
|
281
233
|
it 'recent weather' do
|
282
234
|
parser = setup_parser("CYQH 310110Z 00000KT 20SM SCT035CB BKN050 RETS RMK CB4SC1")
|
283
235
|
|
284
|
-
parser.recent_weather.
|
285
|
-
parser.recent_weather.size.
|
286
|
-
parser.recent_weather[0].phenomenon.
|
287
|
-
should == 'thunderstorm'
|
236
|
+
expect(parser.recent_weather).to be_a Array
|
237
|
+
expect(parser.recent_weather.size).to eq(1)
|
238
|
+
expect(parser.recent_weather[0].phenomenon).to eq('thunderstorm')
|
288
239
|
end
|
289
240
|
|
290
241
|
context 'remarks' do
|
291
|
-
|
292
242
|
it 'are collected' do
|
293
243
|
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 RMK AO2 P0000")
|
294
244
|
|
295
|
-
parser.remarks.
|
296
|
-
parser.remarks.size.
|
245
|
+
expect(parser.remarks).to be_a Array
|
246
|
+
expect(parser.remarks.size).to eq(2)
|
297
247
|
end
|
298
248
|
|
299
249
|
it 'remarks defaults to empty array' do
|
300
250
|
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910")
|
301
251
|
|
302
|
-
parser.remarks.
|
303
|
-
parser.remarks.
|
252
|
+
expect(parser.remarks).to be_a Array
|
253
|
+
expect(parser.remarks.size).to eq(0)
|
304
254
|
end
|
305
255
|
|
306
256
|
it 'parses known remarks' do
|
307
257
|
parser = setup_parser('CYZT 052200Z 31010KT 20SM SKC 17/12 A3005 RMK SLP174 20046')
|
308
258
|
|
309
|
-
parser.remarks[0].
|
310
|
-
parser.remarks[1].
|
259
|
+
expect(parser.remarks[0]).to be_a(Metar::SeaLevelPressure)
|
260
|
+
expect(parser.remarks[1]).to be_temperature_extreme(:minimum, 4.6)
|
311
261
|
end
|
312
262
|
|
313
263
|
context 'in strict mode' do
|
314
|
-
|
315
|
-
before :each do
|
264
|
+
before do
|
316
265
|
Metar::Parser.compliance = :strict
|
317
266
|
end
|
318
267
|
|
319
268
|
it 'unparsed data causes an error' do
|
320
269
|
expect do
|
321
270
|
setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 FOO RMK AO2 P0000")
|
322
|
-
end.
|
271
|
+
end.to raise_error(Metar::ParseError, /Unparsable text found/)
|
323
272
|
end
|
324
|
-
|
325
273
|
end
|
326
274
|
|
327
275
|
context 'in loose mode' do
|
328
|
-
|
329
276
|
it 'unparsed data is collected' do
|
330
277
|
parser = setup_parser("PAIL 061610Z 24006KT 1 3/4SM -SN BKN016 OVC030 M17/M20 A2910 FOO RMK AO2 P0000")
|
331
278
|
|
332
|
-
parser.unparsed.
|
333
|
-
parser.remarks.size.
|
279
|
+
expect(parser.unparsed).to eq(['FOO'])
|
280
|
+
expect(parser.remarks.size).to eq(2)
|
334
281
|
end
|
335
|
-
|
336
282
|
end
|
337
|
-
|
338
283
|
end
|
339
284
|
|
340
285
|
def setup_parser(metar)
|
341
286
|
raw = Metar::Raw::Data.new(metar)
|
342
287
|
Metar::Parser.new(raw)
|
343
288
|
end
|
344
|
-
|
345
289
|
end
|
346
|
-
|
347
290
|
end
|
348
291
|
|
data/spec/unit/report_spec.rb
CHANGED
@@ -49,6 +49,12 @@ describe Metar::Report do
|
|
49
49
|
|
50
50
|
context '#time' do
|
51
51
|
specify { subject.time. should == @metar_time }
|
52
|
+
|
53
|
+
it 'zero-pads single figure minutes' do
|
54
|
+
@parser.stub(:time => Time.parse('10:02'))
|
55
|
+
|
56
|
+
expect(subject.time).to eq('10:02')
|
57
|
+
end
|
52
58
|
end
|
53
59
|
|
54
60
|
context '#observer' do
|
data/spec/unit/wind_spec.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
load File.expand_path(
|
2
|
+
load File.expand_path('../spec_helper.rb', File.dirname(__FILE__))
|
3
3
|
|
4
|
-
RSpec::Matchers.define :be_wind do |
|
5
|
-
match do |
|
6
|
-
if wind.nil? && [
|
4
|
+
RSpec::Matchers.define :be_wind do |direction, speed, gusts|
|
5
|
+
match do |wind|
|
6
|
+
if wind.nil? && [direction, speed, gusts].all?(&:nil?)
|
7
7
|
true
|
8
8
|
elsif wind.nil?
|
9
9
|
false
|
@@ -13,15 +13,15 @@ RSpec::Matchers.define :be_wind do | direction, speed, gusts |
|
|
13
13
|
false
|
14
14
|
elsif wind.gusts.nil? != gusts.nil?
|
15
15
|
false
|
16
|
-
elsif direction.is_a?(
|
16
|
+
elsif direction.is_a?(Symbol) && wind.direction != direction
|
17
17
|
false
|
18
|
-
elsif direction.is_a?(
|
18
|
+
elsif direction.is_a?(M9t::Direction) && (wind.direction.value - direction).abs > 0.01
|
19
19
|
false
|
20
|
-
elsif speed.is_a?(
|
20
|
+
elsif speed.is_a?(Symbol) && wind.speed != speed
|
21
21
|
false
|
22
|
-
elsif speed.is_a?(
|
22
|
+
elsif speed.is_a?(Metar::Speed) && (wind.speed.value - speed).abs > 0.01
|
23
23
|
false
|
24
|
-
elsif ! wind.gusts.nil?
|
24
|
+
elsif ! wind.gusts.nil? && (wind.gusts.value - gusts).abs > 0.01
|
25
25
|
false
|
26
26
|
else
|
27
27
|
true
|
@@ -30,52 +30,48 @@ RSpec::Matchers.define :be_wind do | direction, speed, gusts |
|
|
30
30
|
end
|
31
31
|
|
32
32
|
describe Metar::Wind do
|
33
|
-
|
34
33
|
context '.parse' do
|
35
|
-
|
36
34
|
[
|
37
35
|
# Direction and speed
|
38
|
-
[
|
39
|
-
[
|
40
|
-
[
|
41
|
-
[
|
42
|
-
[
|
43
|
-
[
|
36
|
+
['treats 5 digits as degrees and kilometers per hour', '12345', [123.0, 12.50, nil]],
|
37
|
+
['understands 5 digits + KMH', '12345KMH', [123.0, 12.50, nil]],
|
38
|
+
['understands 5 digits + MPS', '12345MPS', [123.0, 45.00, nil]],
|
39
|
+
['understands 5 digits + KT', '12345KT', [123.0, 23.15, nil]],
|
40
|
+
['rounds 360 down to 0', '36045KT', [ 0.0, 23.15, nil]],
|
41
|
+
['returns nil for directions outside 0 to 360', '88845KT', [nil, nil, nil]],
|
44
42
|
# +gusts
|
45
|
-
[
|
46
|
-
[
|
47
|
-
[
|
48
|
-
[
|
43
|
+
['understands 5 digits + G + 2 digits', '12345G67', [123.0, 12.50, 18.61]],
|
44
|
+
['understands 5 digits + G + 2 digits + KMH', '12345G67KMH', [123.0, 12.50, 18.61]],
|
45
|
+
['understands 5 digits + G + 2 digits + MPS', '12345G67MPS', [123.0, 45.00, 67.00]],
|
46
|
+
['understands 5 digits + G + 2 digits + KT', '12345G67KT', [123.0, 23.15, 34.47]],
|
49
47
|
# Variable direction
|
50
|
-
[
|
51
|
-
[
|
52
|
-
[
|
53
|
-
[
|
48
|
+
['understands VRB + 2 digits', 'VRB12', [:variable_direction, 3.33, nil]],
|
49
|
+
['understands VRB + 2 digits + KMH', 'VRB12KMH', [:variable_direction, 3.33, nil]],
|
50
|
+
['understands VRB + 2 digits + MPS', 'VRB12MPS', [:variable_direction, 12.00, nil]],
|
51
|
+
['understands VRB + 2 digits + KT', 'VRB12KT', [:variable_direction, 6.17, nil]],
|
54
52
|
# + gusts
|
55
|
-
[
|
56
|
-
[
|
57
|
-
[
|
58
|
-
[
|
53
|
+
['understands VRB + 2 digits + G + 2 digits', 'VRB45G67', [:variable_direction, 12.50, 18.61]],
|
54
|
+
['understands VRB + 2 digits + G + 2 digits + KMH', 'VRB45G67KMH', [:variable_direction, 12.50, 18.61]],
|
55
|
+
['understands VRB + 2 digits + G + 2 digits + MPS', 'VRB45G67MPS', [:variable_direction, 45.00, 67.00]],
|
56
|
+
['understands VRB + 2 digits + G + 2 digits + KT', 'VRB45G67KT', [:variable_direction, 23.15, 34.47]],
|
59
57
|
# Unknown direction
|
60
|
-
[
|
61
|
-
[
|
62
|
-
[
|
63
|
-
[
|
58
|
+
['understands /// + 2 digits', '///12', [:unknown_direction, 3.33, nil]],
|
59
|
+
['understands /// + 2 digits + KMH', '///12KMH', [:unknown_direction, 3.33, nil]],
|
60
|
+
['understands /// + 2 digits + MPS', '///12MPS', [:unknown_direction, 12.00, nil]],
|
61
|
+
['understands /// + 2 digits + KT', '///12KT', [:unknown_direction, 6.17, nil]],
|
64
62
|
# Unknown direction and speed
|
65
|
-
[
|
63
|
+
['understands /////', '/////', [:unknown_direction, :unknown_speed, nil]],
|
66
64
|
# Bad data
|
67
|
-
[
|
68
|
-
[
|
69
|
-
].each do |
|
65
|
+
['returns nil for badly formatted values', 'XYZ12KT', [nil, nil, nil]],
|
66
|
+
['returns nil for nil', nil, [nil, nil, nil]],
|
67
|
+
].each do |docstring, raw, expected|
|
70
68
|
example docstring do
|
71
|
-
Metar::Wind.parse(
|
69
|
+
Metar::Wind.parse(raw).should be_wind(*expected)
|
72
70
|
end
|
73
71
|
end
|
74
|
-
|
75
72
|
end
|
76
73
|
|
77
74
|
context '#to_s' do
|
78
|
-
|
79
75
|
before :each do
|
80
76
|
@locale = I18n.locale
|
81
77
|
I18n.locale = :it
|
@@ -86,28 +82,26 @@ describe Metar::Wind do
|
|
86
82
|
end
|
87
83
|
|
88
84
|
[
|
89
|
-
[
|
90
|
-
[
|
91
|
-
[
|
92
|
-
[
|
93
|
-
[
|
94
|
-
[
|
95
|
-
[
|
96
|
-
[
|
97
|
-
[
|
98
|
-
[
|
99
|
-
].each do |
|
100
|
-
direction ||= M9t::Direction.new(
|
101
|
-
speed ||= Metar::Speed.new(
|
85
|
+
['should format speed and direction', :en, [nil, nil, nil ], '443km/h ESE' ],
|
86
|
+
['should handle variable_direction', :en, [:variable_direction, nil, nil ], '443km/h variable direction' ],
|
87
|
+
['should handle unknown_direction', :en, [:unknown_direction, nil, nil ], '443km/h unknown direction' ],
|
88
|
+
['should handle unknown_speed', :en, [nil, :unknown_speed, nil ], 'unknown speed ESE' ],
|
89
|
+
['should include gusts', :en, [nil, nil, Metar::Speed.new(123)], '443km/h ESE gusts 443km/h' ],
|
90
|
+
['should format speed and direction', :it, [nil, nil, nil ], '443km/h ESE' ],
|
91
|
+
['should handle variable_direction', :it, [:variable_direction, nil, nil ], '443km/h direzione variabile' ],
|
92
|
+
['should handle unknown_direction', :it, [:unknown_direction, nil, nil ], '443km/h direzione sconosciuta'],
|
93
|
+
['should handle unknown_speed', :it, [nil, :unknown_speed, nil ], 'velocità sconosciuta ESE' ],
|
94
|
+
['should include gusts', :it, [nil, nil, Metar::Speed.new(123)], '443km/h ESE folate di 443km/h'],
|
95
|
+
].each do |docstring, locale, (direction, speed, gusts), expected|
|
96
|
+
direction ||= M9t::Direction.new(123)
|
97
|
+
speed ||= Metar::Speed.new(123)
|
102
98
|
|
103
99
|
example docstring + " (#{locale})" do
|
104
100
|
I18n.locale = locale
|
105
|
-
Metar::Wind.new(
|
101
|
+
Metar::Wind.new(direction, speed, gusts).to_s.
|
106
102
|
should == expected
|
107
103
|
end
|
108
104
|
end
|
109
|
-
|
110
105
|
end
|
111
|
-
|
112
106
|
end
|
113
107
|
|
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.5
|
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: 2013-
|
12
|
+
date: 2013-05-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -194,7 +194,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
194
194
|
version: '0'
|
195
195
|
segments:
|
196
196
|
- 0
|
197
|
-
hash:
|
197
|
+
hash: 2864761200794302878
|
198
198
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
199
199
|
none: false
|
200
200
|
requirements:
|
@@ -203,7 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
203
203
|
version: '0'
|
204
204
|
segments:
|
205
205
|
- 0
|
206
|
-
hash:
|
206
|
+
hash: 2864761200794302878
|
207
207
|
requirements: []
|
208
208
|
rubyforge_project: nowarning
|
209
209
|
rubygems_version: 1.8.23
|