metar-parser 1.2.1 → 1.3.0

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.
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
+