metar-parser 1.1.4 → 1.1.5
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/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
|