metar-parser 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/lib/metar/data/base.rb +15 -0
  3. data/lib/metar/data/density_altitude.rb +15 -0
  4. data/lib/metar/data/direction.rb +9 -0
  5. data/lib/metar/data/distance.rb +27 -0
  6. data/lib/metar/data/lightning.rb +61 -0
  7. data/lib/metar/data/observer.rb +24 -0
  8. data/lib/metar/data/pressure.rb +23 -0
  9. data/lib/metar/data/remark.rb +98 -0
  10. data/lib/metar/data/runway_visible_range.rb +85 -0
  11. data/lib/metar/data/sky_condition.rb +61 -0
  12. data/lib/metar/data/speed.rb +22 -0
  13. data/lib/metar/data/station_code.rb +7 -0
  14. data/lib/metar/data/temperature.rb +21 -0
  15. data/lib/metar/data/temperature_and_dew_point.rb +18 -0
  16. data/lib/metar/data/time.rb +54 -0
  17. data/lib/metar/data/variable_wind.rb +25 -0
  18. data/lib/metar/data/vertical_visibility.rb +26 -0
  19. data/lib/metar/data/visibility.rb +71 -0
  20. data/lib/metar/data/visibility_remark.rb +8 -0
  21. data/lib/metar/data/weather_phenomenon.rb +86 -0
  22. data/lib/metar/data/wind.rb +82 -0
  23. data/lib/metar/data.rb +22 -636
  24. data/lib/metar/i18n.rb +6 -0
  25. data/lib/metar/parser.rb +165 -120
  26. data/lib/metar/report.rb +1 -1
  27. data/lib/metar/version.rb +2 -2
  28. data/lib/metar.rb +7 -6
  29. data/locales/de.yml +1 -0
  30. data/locales/en.yml +1 -0
  31. data/locales/it.yml +2 -1
  32. data/locales/pt-BR.yml +1 -0
  33. data/spec/data/density_altitude_spec.rb +12 -0
  34. data/spec/{distance_spec.rb → data/distance_spec.rb} +1 -1
  35. data/spec/data/lightning_spec.rb +49 -0
  36. data/spec/data/pressure_spec.rb +22 -0
  37. data/spec/data/remark_spec.rb +99 -0
  38. data/spec/data/runway_visible_range_spec.rb +92 -0
  39. data/spec/{sky_condition_spec.rb → data/sky_condition_spec.rb} +10 -6
  40. data/spec/data/speed_spec.rb +45 -0
  41. data/spec/data/temperature_spec.rb +36 -0
  42. data/spec/{variable_wind_spec.rb → data/variable_wind_spec.rb} +6 -6
  43. data/spec/{vertical_visibility_spec.rb → data/vertical_visibility_spec.rb} +2 -2
  44. data/spec/{data_spec.rb → data/visibility_remark_spec.rb} +1 -11
  45. data/spec/{visibility_spec.rb → data/visibility_spec.rb} +9 -7
  46. data/spec/{weather_phenomenon_spec.rb → data/weather_phenomenon_spec.rb} +7 -3
  47. data/spec/{wind_spec.rb → data/wind_spec.rb} +10 -7
  48. data/spec/parser_spec.rb +107 -13
  49. data/spec/report_spec.rb +12 -1
  50. data/spec/spec_helper.rb +1 -1
  51. data/spec/station_spec.rb +2 -1
  52. metadata +56 -31
  53. data/spec/pressure_spec.rb +0 -22
  54. data/spec/remark_spec.rb +0 -147
  55. data/spec/runway_visible_range_spec.rb +0 -81
  56. data/spec/speed_spec.rb +0 -45
  57. data/spec/temperature_spec.rb +0 -36
data/lib/metar/data.rb CHANGED
@@ -1,639 +1,25 @@
1
1
  # encoding: utf-8
2
- require 'i18n'
3
- require 'm9t'
4
2
 
5
- module Metar
6
- locales_path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'locales'))
7
- I18n.load_path += Dir.glob("#{locales_path}/*.yml")
8
- I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
9
-
10
- class Distance < M9t::Distance
11
- attr_accessor :units
12
-
13
- # nil is taken to mean 'data unavailable'
14
- def initialize(meters = nil)
15
- @units = :meters
16
- if meters
17
- super
18
- else
19
- @value = nil
20
- end
21
- end
22
-
23
- # Handles nil case differently to M9t::Distance
24
- def to_s(options = {})
25
- options = {
26
- units: @units,
27
- precision: 0,
28
- abbreviated: true,
29
- }.merge(options)
30
- return I18n.t('metar.distance.unknown') if @value.nil?
31
- super(options)
32
- end
33
- end
34
-
35
- # Adds a parse method to the M9t base class
36
- class Speed < M9t::Speed
37
- METAR_UNITS = {
38
- '' => :kilometers_per_hour,
39
- 'KMH' => :kilometers_per_hour,
40
- 'MPS' => :meters_per_second,
41
- 'KT' => :knots,
42
- }
43
-
44
- def self.parse(s)
45
- case
46
- when s =~ /^(\d+)(|KT|MPS|KMH)$/
47
- # Call the appropriate factory method for the supplied units
48
- send(METAR_UNITS[$2], $1.to_i)
49
- else
50
- nil
51
- end
52
- end
53
- end
54
-
55
- # Adds a parse method to the M9t base class
56
- class Temperature < M9t::Temperature
57
- def self.parse(s)
58
- if s =~ /^(M?)(\d+)$/
59
- sign = $1
60
- value = $2.to_i
61
- value *= -1 if sign == 'M'
62
- new(value)
63
- else
64
- nil
65
- end
66
- end
67
-
68
- def to_s(options = {})
69
- options = {abbreviated: true, precision: 0}.merge(options)
70
- super(options)
71
- end
72
- end
73
-
74
- # Adds a parse method to the M9t base class
75
- class Pressure < M9t::Pressure
76
- def self.parse(pressure)
77
- case
78
- when pressure =~ /^Q(\d{4})$/
79
- hectopascals($1.to_f)
80
- when pressure =~ /^A(\d{4})$/
81
- inches_of_mercury($1.to_f / 100.0)
82
- else
83
- nil
84
- end
85
- end
86
- end
87
-
88
- class Direction < M9t::Direction
89
- def initialize(direction)
90
- direction = M9t::Direction::normalize(direction.to_f)
91
- super(direction)
92
- end
93
- end
94
-
95
- class Wind
96
- def self.parse(s)
97
- case
98
- when s =~ /^(\d{3})(\d{2}(|MPS|KMH|KT))$/
99
- return nil if $1.to_i > 360
100
- new(Direction.new($1), Speed.parse($2))
101
- when s =~ /^(\d{3})(\d{2})G(\d{2,3}(|MPS|KMH|KT))$/
102
- return nil if $1.to_i > 360
103
- new(Direction.new($1), Speed.parse($2 + $4), Speed.parse($3))
104
- when s =~ /^VRB(\d{2})G(\d{2,3})(|MPS|KMH|KT)$/
105
- speed = $1 + $3
106
- gusts = $2 + $3
107
- new(:variable_direction, Speed.parse(speed), Speed.parse(gusts))
108
- when s =~ /^VRB(\d{2}(|MPS|KMH|KT))$/
109
- new(:variable_direction, Speed.parse($1))
110
- when s =~ /^\/{3}(\d{2}(|MPS|KMH|KT))$/
111
- new(:unknown_direction, Speed.parse($1))
112
- when s =~ %r(^/////(|MPS|KMH|KT)$)
113
- new(:unknown_direction, :unknown_speed)
114
- else
115
- nil
116
- end
117
- end
118
-
119
- attr_reader :direction, :speed, :gusts
120
-
121
- def initialize(direction, speed, gusts = nil)
122
- @direction, @speed, @gusts = direction, speed, gusts
123
- end
124
-
125
- def to_s(options = {})
126
- options = {
127
- direction_units: :compass,
128
- speed_units: :kilometers_per_hour,
129
- }.merge(options)
130
- speed =
131
- case @speed
132
- when :unknown_speed
133
- I18n.t('metar.wind.unknown_speed')
134
- else
135
- @speed.to_s(
136
- abbreviated: true,
137
- precision: 0,
138
- units: options[:speed_units]
139
- )
140
- end
141
- direction =
142
- case @direction
143
- when :variable_direction
144
- I18n.t('metar.wind.variable_direction')
145
- when :unknown_direction
146
- I18n.t('metar.wind.unknown_direction')
147
- else
148
- @direction.to_s(units: options[:direction_units])
149
- end
150
- s = "#{speed} #{direction}"
151
- if not @gusts.nil?
152
- g = @gusts.to_s(
153
- abbreviated: true,
154
- precision: 0,
155
- units: options[:speed_units]
156
- )
157
- s += " #{I18n.t('metar.wind.gusts')} #{g}"
158
- end
159
- s
160
- end
161
- end
162
-
163
- class VariableWind
164
- def self.parse(variable_wind)
165
- if variable_wind =~ /^(\d+)V(\d+)$/
166
- new(Direction.new($1), Direction.new($2))
167
- else
168
- nil
169
- end
170
- end
171
-
172
- attr_reader :direction1, :direction2
173
-
174
- def initialize(direction1, direction2)
175
- @direction1, @direction2 = direction1, direction2
176
- end
177
-
178
- def to_s
179
- "#{@direction1.to_s(units: :compass)} - #{@direction2.to_s(units: :compass)}"
180
- end
181
- end
182
-
183
- class Visibility
184
- def self.parse(s)
185
- case
186
- when s == '9999'
187
- new(Distance.new(10000), nil, :more_than)
188
- when s =~ /(\d{4})NDV/ # WMO
189
- new(Distance.new($1.to_f)) # Assuming meters
190
- when (s =~ /^((1|2)\s|)([1357])\/([248]|16)SM$/) # US
191
- miles = $1.to_f + $3.to_f / $4.to_f
192
- distance = Distance.miles(miles)
193
- distance.units = :miles
194
- new(distance)
195
- when s =~ /^(\d+)SM$/ # US
196
- distance = Distance.miles($1.to_f)
197
- distance.units = :miles
198
- new(distance)
199
- when s == 'M1/4SM' # US
200
- distance = Distance.miles(0.25)
201
- distance.units = :miles
202
- new(distance, nil, :less_than)
203
- when s =~ /^(\d+)KM$/
204
- new(Distance.kilometers($1))
205
- when s =~ /^(\d+)$/ # We assume meters
206
- new(Distance.new($1))
207
- when s =~ /^(\d+)(N|NE|E|SE|S|SW|W|NW)$/
208
- new(Distance.meters($1), M9t::Direction.compass($2))
209
- else
210
- nil
211
- end
212
- end
213
-
214
- attr_reader :distance, :direction, :comparator
215
-
216
- def initialize(distance, direction = nil, comparator = nil)
217
- @distance, @direction, @comparator = distance, direction, comparator
218
- end
219
-
220
- def to_s(options = {})
221
- distance_options = {
222
- abbreviated: true,
223
- precision: 0,
224
- units: :kilometers,
225
- }.merge(options)
226
- direction_options = {units: :compass}
227
- case
228
- when (@direction.nil? and @comparator.nil?)
229
- @distance.to_s(distance_options)
230
- when @comparator.nil?
231
- [
232
- @distance.to_s(distance_options),
233
- @direction.to_s(direction_options),
234
- ].join(' ')
235
- when @direction.nil?
236
- [
237
- I18n.t('comparison.' + @comparator.to_s),
238
- @distance.to_s(distance_options),
239
- ].join(' ')
240
- else
241
- [
242
- I18n.t('comparison.' + @comparator.to_s),
243
- @distance.to_s(distance_options),
244
- @direction.to_s(direction_options),
245
- ].join(' ')
246
- end
247
- end
248
- end
249
-
250
- class RunwayVisibleRange
251
- TENDENCY = {'' => nil, 'N' => :no_change, 'U' => :improving, 'D' => :worsening}
252
- COMPARATOR = {'' => nil, 'P' => :more_than, 'M' => :less_than}
253
- UNITS = {'' => :meters, 'FT' => :feet}
254
-
255
- def self.parse(runway_visible_range)
256
- case
257
- when runway_visible_range =~ /^R(\d+[RLC]?)\/(P|M|)(\d{4})(N|U|D|)(FT|)$/
258
- designator = $1
259
- comparator = COMPARATOR[$2]
260
- count = $3.to_f
261
- tendency = TENDENCY[$4]
262
- units = UNITS[$5]
263
- distance = Distance.send(units, count)
264
- visibility = Visibility.new(distance, nil, comparator)
265
- new(designator, visibility, nil, tendency)
266
- when runway_visible_range =~ /^R(\d+[RLC]?)\/(P|M|)(\d{4})V(P|M|)(\d{4})(N|U|D)?(FT|)$/
267
- designator = $1
268
- comparator1 = COMPARATOR[$2]
269
- count1 = $3.to_f
270
- comparator2 = COMPARATOR[$4]
271
- count2 = $5.to_f
272
- tendency = TENDENCY[$6]
273
- units = UNITS[$7]
274
- distance1 = Distance.send(units, count1)
275
- distance2 = Distance.send(units, count2)
276
- visibility1 = Visibility.new(distance1, nil, comparator1)
277
- visibility2 = Visibility.new(distance2, nil, comparator2)
278
- new(designator, visibility1, visibility2, tendency, units)
279
- else
280
- nil
281
- end
282
- end
283
-
284
- attr_reader :designator, :visibility1, :visibility2, :tendency
285
- def initialize(designator, visibility1, visibility2 = nil, tendency = nil, units = :meters)
286
- @designator, @visibility1, @visibility2, @tendency, @units = designator, visibility1, visibility2, tendency, units
287
- end
288
-
289
- def to_s
290
- distance_options = {
291
- abbreviated: true,
292
- precision: 0,
293
- units: @units,
294
- }
295
- s =
296
- if @visibility2.nil?
297
- I18n.t('metar.runway_visible_range.runway') +
298
- ' ' + @designator +
299
- ': ' + @visibility1.to_s(distance_options)
300
- else
301
- I18n.t('metar.runway_visible_range.runway') +
302
- ' ' + @designator +
303
- ': ' + I18n.t('metar.runway_visible_range.from') +
304
- ' ' + @visibility1.to_s(distance_options) +
305
- ' ' + I18n.t('metar.runway_visible_range.to') +
306
- ' ' + @visibility2.to_s(distance_options)
307
- end
308
-
309
- if ! tendency.nil?
310
- s += ' ' + I18n.t("tendency.#{tendency}")
311
- end
312
-
313
- s
314
- end
315
- end
316
-
317
- class WeatherPhenomenon
318
- Modifiers = {
319
- '+' => 'heavy',
320
- '-' => 'light',
321
- 'VC' => 'nearby',
322
- '-VC' => 'nearby light',
323
- '+VC' => 'nearby heavy',
324
- }
325
-
326
- Descriptors = {
327
- 'BC' => 'patches of',
328
- 'BL' => 'blowing',
329
- 'DR' => 'low drifting',
330
- 'FZ' => 'freezing',
331
- 'MI' => 'shallow',
332
- 'PR' => 'partial',
333
- 'SH' => 'shower of',
334
- 'TS' => 'thunderstorm and',
335
- }
336
-
337
- Phenomena = {
338
- 'BR' => 'mist',
339
- 'DU' => 'dust',
340
- 'DZ' => 'drizzle',
341
- 'FG' => 'fog',
342
- 'FU' => 'smoke',
343
- 'GR' => 'hail',
344
- 'GS' => 'small hail',
345
- 'HZ' => 'haze',
346
- 'IC' => 'ice crystals',
347
- 'PL' => 'ice pellets',
348
- 'PO' => 'dust whirls',
349
- 'PY' => 'spray', # US only
350
- 'RA' => 'rain',
351
- 'SA' => 'sand',
352
- 'SH' => 'shower',
353
- 'SN' => 'snow',
354
- 'SG' => 'snow grains',
355
- 'SQ' => 'squall',
356
- 'UP' => 'unknown phenomenon', # => AUTO
357
- 'VA' => 'volcanic ash',
358
- 'FC' => 'funnel cloud',
359
- 'SS' => 'sand storm',
360
- 'DS' => 'dust storm',
361
- 'TS' => 'thunderstorm',
362
- }
363
-
364
- # Accepts all standard (and some non-standard) present weather codes
365
- def self.parse(s)
366
- phenomena = Phenomena.keys.join('|')
367
- descriptors = Descriptors.keys.join('|')
368
- modifiers = Modifiers.keys.join('|')
369
- modifiers.gsub!(/([\+\-])/) { "\\#$1" }
370
- rxp = Regexp.new("^(#{modifiers})?(#{descriptors})?((?:#{phenomena}){1,2})$")
371
- m = rxp.match(s)
372
- return nil if m.nil?
373
-
374
- modifier_code = m[1]
375
- descriptor_code = m[2]
376
- phenomena_codes = m[3].scan(/../)
377
- phenomena_phrase = phenomena_codes.map { |c| Phenomena[c] }.join(' and ')
378
-
379
- new(phenomena_phrase, Modifiers[modifier_code], Descriptors[descriptor_code])
380
- end
381
-
382
- attr_reader :phenomenon, :modifier, :descriptor
383
- def initialize(phenomenon, modifier = nil, descriptor = nil)
384
- @phenomenon, @modifier, @descriptor = phenomenon, modifier, descriptor
385
- end
386
-
387
- def to_s
388
- I18n.t("metar.present_weather.%s" % [@modifier, @descriptor, @phenomenon].compact.join(' '))
389
- end
390
- end
391
-
392
- class SkyCondition
393
- QUANTITY = {'BKN' => 'broken', 'FEW' => 'few', 'OVC' => 'overcast', 'SCT' => 'scattered'}
394
- CONDITION = {
395
- 'CB' => 'cumulonimbus',
396
- 'TCU' => 'towering cumulus',
397
- '///' => nil, # cloud type unknown as observed by automatic system (15.9.1.7)
398
- '' => nil,
399
- }
400
- CLEAR_SKIES = [
401
- 'NSC', # WMO
402
- 'NCD', # WMO
403
- 'CLR',
404
- 'SKC',
405
- ]
406
-
407
- def self.parse(sky_condition)
408
- case
409
- when CLEAR_SKIES.include?(sky_condition)
410
- new
411
- when sky_condition =~ /^(BKN|FEW|OVC|SCT)(\d+|\/{3})(CB|TCU|\/{3}|)?$/
412
- quantity = QUANTITY[$1]
413
- height =
414
- if $2 == '///'
415
- nil
416
- else
417
- Distance.new($2.to_i * 30.48)
418
- end
419
- type = CONDITION[$3]
420
- new(quantity, height, type)
421
- when sky_condition =~ /^(CB|TCU)$/
422
- type = CONDITION[$1]
423
- new(nil, nil, type)
424
- else
425
- nil
426
- end
427
- end
428
-
429
- attr_reader :quantity, :height, :type
430
- def initialize(quantity = nil, height = nil, type = nil)
431
- @quantity, @height, @type = quantity, height, type
432
- end
433
-
434
- def to_s
435
- if @height.nil?
436
- to_summary
437
- else
438
- to_summary + ' ' + I18n.t('metar.altitude.at') + ' ' + height.to_s
439
- end
440
- end
441
-
442
- def to_summary
443
- if @quantity == nil and @height == nil and @type == nil
444
- I18n.t('metar.sky_conditions.clear skies')
445
- else
446
- type = @type ? ' ' + @type : ''
447
- I18n.t("metar.sky_conditions.#{@quantity}#{type}")
448
- end
449
- end
450
- end
451
-
452
- class VerticalVisibility
453
- def self.parse(vertical_visibility)
454
- case
455
- when vertical_visibility =~ /^VV(\d{3})$/
456
- Distance.new($1.to_f * 30.48)
457
- when vertical_visibility == '///'
458
- Distance.new
459
- else
460
- nil
461
- end
462
- end
463
- end
464
-
465
- class Remark
466
- PRESSURE_CHANGE_CHARACTER = [
467
- :increasing_then_decreasing, # 0
468
- :increasing_then_steady, # 1
469
- :increasing, # 2
470
- :decreasing_or_steady_then_increasing, # 3
471
- :steady, # 4
472
- :decreasing_then_increasing, # 5
473
- :decreasing_then_steady, # 6
474
- :decreasing, # 7
475
- :steady_then_decreasing, # 8
476
- ]
477
-
478
- INDICATOR_TYPE = {
479
- 'TS' => :thunderstorm_information,
480
- 'PWI' => :precipitation_identifier,
481
- 'P' => :precipitation_amount,
482
- }
483
-
484
- COLOR_CODE = ['RED', 'AMB', 'YLO', 'GRN', 'WHT', 'BLU']
485
-
486
- def self.parse(s)
487
- case s
488
- when /^([12])([01])(\d{3})$/
489
- extreme = {'1' => :maximum, '2' => :minimum}[$1]
490
- value = sign($2) * tenths($3)
491
- TemperatureExtreme.new(extreme, value)
492
- when /^4([01])(\d{3})([01])(\d{3})$/
493
- [
494
- TemperatureExtreme.new(:maximum, sign($1) * tenths($2)),
495
- TemperatureExtreme.new(:minimum, sign($3) * tenths($4)),
496
- ]
497
- when /^5([0-8])(\d{3})$/
498
- character = PRESSURE_CHANGE_CHARACTER[$1.to_i]
499
- PressureTendency.new(character, tenths($2))
500
- when /^6(\d{4})$/
501
- Precipitation.new(3, Distance.new(inches_to_meters($1))) # actually 3 or 6 depending on reporting time
502
- when /^7(\d{4})$/
503
- Precipitation.new(24, Distance.new(inches_to_meters($1)))
504
- when /^A[0O]([12])$/
505
- type = [:with_precipitation_discriminator, :without_precipitation_discriminator][$1.to_i - 1]
506
- AutomatedStationType.new(type)
507
- when /^P(\d{4})$/
508
- Precipitation.new(1, Distance.new(inches_to_meters($1)))
509
- when /^T([01])(\d{3})([01])(\d{3})$/
510
- temperature = Temperature.new(sign($1) * tenths($2))
511
- dew_point = Temperature.new(sign($3) * tenths($4))
512
- HourlyTemperaturAndDewPoint.new(temperature, dew_point)
513
- when /^SLP(\d{3})$/
514
- SeaLevelPressure.new(Pressure.hectopascals(tenths($1)))
515
- when /^(#{INDICATOR_TYPE.keys.join('|')})NO$/
516
- type = INDICATOR_TYPE[$1]
517
- SensorStatusIndicator.new(:type, :not_available)
518
- when /^(#{COLOR_CODE.join('|')})$/
519
- ColorCode.new($1)
520
- when 'SKC'
521
- SkyCondition.new
522
- when '$'
523
- MaintenanceNeeded.new
524
- else
525
- nil
526
- end
527
- end
528
-
529
- def self.sign(digit)
530
- case digit
531
- when '0'
532
- 1.0
533
- when '1'
534
- -1.0
535
- else
536
- raise "Unexpected sign: #{digit}"
537
- end
538
- end
539
-
540
- def self.tenths(digits)
541
- digits.to_f / 10.0
542
- end
543
-
544
- def self.inches_to_meters(digits)
545
- digits.to_f * 0.000254
546
- end
547
- end
548
-
549
- TemperatureExtreme = Struct.new(:extreme, :value)
550
- PressureTendency = Struct.new(:character, :value)
551
- Precipitation = Struct.new(:period, :amount)
552
- AutomatedStationType = Struct.new(:type)
553
- HourlyTemperaturAndDewPoint = Struct.new(:temperature, :dew_point)
554
- SeaLevelPressure = Struct.new(:pressure)
555
- SensorStatusIndicator = Struct.new(:type, :state)
556
- ColorCode = Struct.new(:code)
557
-
558
- class MaintenanceNeeded; end
559
-
560
- class Lightning
561
- TYPE = {'' => :default}
562
-
563
- def self.parse_chunks(chunks)
564
- ltg = chunks.shift
565
- m = ltg.match(/^LTG(|CG|IC|CC|CA)$/)
566
- raise 'first chunk is not lightning' if m.nil?
567
- type = TYPE[m[1]]
568
-
569
- frequency = nil
570
- distance = nil
571
- directions = []
572
-
573
- if chunks[0] == 'DSNT'
574
- distance = Distance.miles(10) # Should be >10SM, not 10SM
575
- chunks.shift
576
- end
577
-
578
- loop do
579
- if is_compass?(chunks[0])
580
- directions << chunks.shift
581
- elsif chunks[0] == 'ALQDS'
582
- directions += ['N', 'E', 'S', 'W']
583
- chunks.shift
584
- elsif chunks[0] =~ /^([NESW]{1,2})-([NESW]{1,2})$/
585
- if is_compass?($1) and is_compass?($2)
586
- directions += [$1, $2]
587
- chunks.shift
588
- else
589
- break
590
- end
591
- elsif chunks[0] == 'AND'
592
- chunks.shift
593
- else
594
- break
595
- end
596
- end
597
-
598
- new(frequency, type, distance, directions)
599
- end
600
-
601
- def self.is_compass?(s)
602
- s =~ /^([NESW]|NE|SE|SW|NW)$/
603
- end
604
-
605
- attr_accessor :frequency
606
- attr_accessor :type
607
- attr_accessor :distance
608
- attr_accessor :directions
609
-
610
- def initialize(frequency, type, distance, directions)
611
- @frequency, @type, @distance, @directions = frequency, type, distance, directions
612
- end
613
-
614
- end
615
-
616
- class VisibilityRemark < Visibility
617
- def self.parse(chunk)
618
- metres, direction = chunk.scan(/^(\d{4})([NESW]?)$/)[0]
619
- distance = Distance.new(metres)
620
-
621
- new(distance, direction, :more_than)
622
- end
623
- end
624
-
625
- class DensityAltitude
626
- def self.parse(chunk)
627
- feet = chunk[/^(\d+)(FT)/, 1]
628
- height = Distance.feet(feet)
629
-
630
- new(height)
631
- end
632
-
633
- attr_accessor :height
634
-
635
- def initialize(height)
636
- @height = height
637
- end
638
- end
3
+ module Metar::Data
4
+ autoload :Base, "metar/data/base"
5
+ autoload :DensityAltitude, "metar/data/density_altitude"
6
+ autoload :Direction, "metar/data/direction"
7
+ autoload :Distance, "metar/data/distance"
8
+ autoload :Lightning, "metar/data/lightning"
9
+ autoload :Observer, "metar/data/observer"
10
+ autoload :Pressure, "metar/data/pressure"
11
+ autoload :Remark, "metar/data/remark"
12
+ autoload :RunwayVisibleRange, "metar/data/runway_visible_range"
13
+ autoload :SkyCondition, "metar/data/sky_condition"
14
+ autoload :Speed, "metar/data/speed"
15
+ autoload :StationCode, "metar/data/station_code"
16
+ autoload :Temperature, "metar/data/temperature"
17
+ autoload :TemperatureAndDewPoint, "metar/data/temperature_and_dew_point"
18
+ autoload :Time, "metar/data/time"
19
+ autoload :VariableWind, "metar/data/variable_wind"
20
+ autoload :VerticalVisibility, "metar/data/vertical_visibility"
21
+ autoload :Visibility, "metar/data/visibility"
22
+ autoload :VisibilityRemark, "metar/data/visibility_remark"
23
+ autoload :WeatherPhenomenon, "metar/data/weather_phenomenon"
24
+ autoload :Wind, "metar/data/wind"
639
25
  end
data/lib/metar/i18n.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "i18n"
2
+
3
+ locales_path = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "locales"))
4
+ I18n.load_path += Dir.glob("#{locales_path}/*.yml")
5
+ I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
6
+