simple_metar_parser 0.0.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.
- data/Gemfile +13 -0
- data/Gemfile.lock +28 -0
- data/LICENSE.txt +165 -0
- data/README.rdoc +55 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/simple_metar_parser.rb +7 -0
- data/lib/simple_metar_parser/metar.rb +120 -0
- data/lib/simple_metar_parser/metar/base.rb +24 -0
- data/lib/simple_metar_parser/metar/clouds.rb +81 -0
- data/lib/simple_metar_parser/metar/metar_city.rb +36 -0
- data/lib/simple_metar_parser/metar/metar_other.rb +27 -0
- data/lib/simple_metar_parser/metar/metar_specials.rb +210 -0
- data/lib/simple_metar_parser/metar/metar_time.rb +40 -0
- data/lib/simple_metar_parser/metar/pressure.rb +52 -0
- data/lib/simple_metar_parser/metar/temperature.rb +101 -0
- data/lib/simple_metar_parser/metar/visibility.rb +47 -0
- data/lib/simple_metar_parser/metar/wind.rb +144 -0
- data/lib/simple_metar_parser/parser.rb +7 -0
- data/lib/simple_metar_parser/tmp/tmp.rb +34 -0
- metadata +114 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
module SimpleMetarParser
|
2
|
+
class Base
|
3
|
+
|
4
|
+
def initialize(_parent)
|
5
|
+
@parent = _parent
|
6
|
+
reset
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :parent
|
10
|
+
|
11
|
+
def reset
|
12
|
+
# implement
|
13
|
+
end
|
14
|
+
|
15
|
+
def decode_split(s)
|
16
|
+
# implement
|
17
|
+
end
|
18
|
+
|
19
|
+
def post_process
|
20
|
+
# implement
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module SimpleMetarParser
|
4
|
+
class Clouds < Base
|
5
|
+
|
6
|
+
# Cloud level - clear sky
|
7
|
+
CLOUD_CLEAR = (0 * 100.0 / 8.0).round
|
8
|
+
# Cloud level - few clouds
|
9
|
+
CLOUD_FEW = (1.5 * 100.0 / 8.0).round
|
10
|
+
#Cloud level - scattered
|
11
|
+
CLOUD_SCATTERED = (3.5 * 100.0 / 8.0).round
|
12
|
+
#Cloud level - broken
|
13
|
+
CLOUD_BROKEN = (6 * 100.0 / 8.0).round
|
14
|
+
#Cloud level - overcast
|
15
|
+
CLOUD_OVERCAST = (8 * 100.0 / 8.0).round
|
16
|
+
#Cloud level - not significant
|
17
|
+
CLOUD_NOT_SIGN = (0.5 * 100.0 / 8.0).round
|
18
|
+
|
19
|
+
def reset
|
20
|
+
@clouds = Array.new
|
21
|
+
@clouds_max = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :clouds, :clouds_max
|
25
|
+
|
26
|
+
def decode_split(s)
|
27
|
+
if s =~ /^(SKC|FEW|SCT|BKN|OVC|NSC)(\d{3}?)$/
|
28
|
+
cl = case $1
|
29
|
+
when "SKC" then
|
30
|
+
CLOUD_CLEAR
|
31
|
+
when "FEW" then
|
32
|
+
CLOUD_FEW
|
33
|
+
when "SCT" then
|
34
|
+
CLOUD_SCATTERED
|
35
|
+
when "BKN" then
|
36
|
+
CLOUD_BROKEN
|
37
|
+
when "OVC" then
|
38
|
+
CLOUD_OVERCAST
|
39
|
+
when "NSC" then
|
40
|
+
CLOUD_NOT_SIGN
|
41
|
+
else
|
42
|
+
CLOUD_CLEAR
|
43
|
+
end
|
44
|
+
|
45
|
+
cloud = {
|
46
|
+
:coverage => cl
|
47
|
+
}
|
48
|
+
# optionally cloud bottom
|
49
|
+
unless '' == $2.to_s
|
50
|
+
cloud[:bottom] = $2.to_i * 30
|
51
|
+
end
|
52
|
+
|
53
|
+
@clouds << cloud
|
54
|
+
@clouds.uniq!
|
55
|
+
end
|
56
|
+
|
57
|
+
# obscured by clouds, vertical visibility
|
58
|
+
if s =~ /^(VV)(\d{3}?)$/
|
59
|
+
@clouds << {
|
60
|
+
:coverage => CLOUD_OVERCAST,
|
61
|
+
:vertical_visibility => $2.to_i * 30
|
62
|
+
}
|
63
|
+
|
64
|
+
@clouds.uniq!
|
65
|
+
end
|
66
|
+
|
67
|
+
if s =~ /^(CAVOK)$/
|
68
|
+
# everything is awesome :)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
# Calculate numeric description of clouds
|
74
|
+
def post_process
|
75
|
+
@clouds_max = 0
|
76
|
+
@clouds.each do |c|
|
77
|
+
@clouds_max = c[:coverage] if @clouds_max < c[:coverage]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module SimpleMetarParser
|
4
|
+
class MetarCity < Base
|
5
|
+
|
6
|
+
# You can set AR model for fetching additional information about city
|
7
|
+
def self.rails_model=(klass)
|
8
|
+
@@rails_model_class = klass
|
9
|
+
end
|
10
|
+
|
11
|
+
# Get city information using AR
|
12
|
+
def self.find_by_metar(metar)
|
13
|
+
return nil if not defined? @@rails_model_class or @@rails_model_class.nil?
|
14
|
+
@@rails_model_class.find_by_metar(metar)
|
15
|
+
end
|
16
|
+
|
17
|
+
def reset
|
18
|
+
@code = nil
|
19
|
+
@model = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def decode_split(s)
|
23
|
+
if s =~ /^([A-Z]{4})$/ and not s == 'AUTO' and not s == 'GRID' and not s == 'WNDS'
|
24
|
+
@code = $1
|
25
|
+
@model = self.class.find_by_metar(@code)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Addition city information fetched using AR
|
30
|
+
attr_reader :model
|
31
|
+
|
32
|
+
# Metar code of city
|
33
|
+
attr_reader :code
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module SimpleMetarParser
|
4
|
+
class MetarOther < Base
|
5
|
+
|
6
|
+
def reset
|
7
|
+
@station = nil
|
8
|
+
@station_auto = false
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :station, :station_auto
|
12
|
+
|
13
|
+
def decode_split(s)
|
14
|
+
if s.strip == 'AO1'
|
15
|
+
@station = :auto_without_precipitation
|
16
|
+
elsif s.strip == 'A02'
|
17
|
+
@station = :auto_with_precipitation
|
18
|
+
end
|
19
|
+
|
20
|
+
# fully automated station
|
21
|
+
if s.strip == 'AUTO'
|
22
|
+
@station_auto = true
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module SimpleMetarParser
|
4
|
+
class MetarSpecials < Base
|
5
|
+
|
6
|
+
# description http://www.ofcm.gov/fmh-1/pdf/H-CH8.pdf
|
7
|
+
|
8
|
+
def reset
|
9
|
+
@specials = Array.new
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :specials
|
13
|
+
|
14
|
+
attr_reader :snow_metar
|
15
|
+
|
16
|
+
attr_reader :rain_metar
|
17
|
+
|
18
|
+
def decode_split(s)
|
19
|
+
decode_specials(s)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Calculate numeric description of clouds
|
23
|
+
def post_process
|
24
|
+
calculate_rain_and_snow
|
25
|
+
end
|
26
|
+
|
27
|
+
def decode_specials(s)
|
28
|
+
if s =~ /^(VC|\-|\+|\b)(MI|PR|BC|DR|BL|SH|TS|FZ|)(DZ|RA|SN|SG|IC|PE|GR|GS|UP|)(BR|FG|FU|VA|DU|SA|HZ|PY|)(PO|SQ|FC|SS|)$/
|
29
|
+
intensity = case $1
|
30
|
+
when "VC" then
|
31
|
+
"in the vicinity"
|
32
|
+
when "+" then
|
33
|
+
"heavy"
|
34
|
+
when "-" then
|
35
|
+
"light"
|
36
|
+
else
|
37
|
+
"moderate"
|
38
|
+
end
|
39
|
+
|
40
|
+
descriptor = case $2
|
41
|
+
when "MI" then
|
42
|
+
"shallow"
|
43
|
+
when "PR" then
|
44
|
+
"partial"
|
45
|
+
when "BC" then
|
46
|
+
"patches"
|
47
|
+
when "DR" then
|
48
|
+
"low drifting"
|
49
|
+
when "BL" then
|
50
|
+
"blowing"
|
51
|
+
when "SH" then
|
52
|
+
"shower"
|
53
|
+
when "TS" then
|
54
|
+
"thunderstorm"
|
55
|
+
when "FZ" then
|
56
|
+
"freezing"
|
57
|
+
else
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
precipitation = case $3
|
62
|
+
when "DZ" then
|
63
|
+
"drizzle"
|
64
|
+
when "RA" then
|
65
|
+
"rain"
|
66
|
+
when "SN" then
|
67
|
+
"snow"
|
68
|
+
when "SG" then
|
69
|
+
"snow grains"
|
70
|
+
when "IC" then
|
71
|
+
"ice crystals"
|
72
|
+
when "PE" then
|
73
|
+
"ice pellets"
|
74
|
+
when "GR" then
|
75
|
+
"hail"
|
76
|
+
when "GS" then
|
77
|
+
"small hail/snow pellets"
|
78
|
+
when "UP" then
|
79
|
+
"unknown"
|
80
|
+
else
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
|
84
|
+
obscuration = case $4
|
85
|
+
when "BR" then
|
86
|
+
"mist"
|
87
|
+
when "FG" then
|
88
|
+
"fog"
|
89
|
+
when "FU" then
|
90
|
+
"smoke"
|
91
|
+
when "VA" then
|
92
|
+
"volcanic ash"
|
93
|
+
when "DU" then
|
94
|
+
"dust"
|
95
|
+
when "SA" then
|
96
|
+
"sand"
|
97
|
+
when "HZ" then
|
98
|
+
"haze"
|
99
|
+
when "PY" then
|
100
|
+
"spray"
|
101
|
+
else
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
misc = case $5
|
106
|
+
when "PO" then
|
107
|
+
"dust whirls"
|
108
|
+
when "SQ" then
|
109
|
+
"squalls"
|
110
|
+
#when "FC " then "funnel cloud/tornado/waterspout"
|
111
|
+
when "FC" then
|
112
|
+
"funnel cloud/tornado/waterspout"
|
113
|
+
when "SS" then
|
114
|
+
"duststorm"
|
115
|
+
else
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
|
119
|
+
# when no sensible data do nothing
|
120
|
+
return if descriptor.nil? and precipitation.nil? and obscuration.nil? and misc.nil?
|
121
|
+
|
122
|
+
@specials << {
|
123
|
+
:intensity => intensity,
|
124
|
+
:intensity_raw => $1,
|
125
|
+
:descriptor => descriptor,
|
126
|
+
:descriptor_raw => $2,
|
127
|
+
:precipitation => precipitation,
|
128
|
+
:precipitation_raw => $3,
|
129
|
+
:obscuration => obscuration,
|
130
|
+
:obscuration_raw => $4,
|
131
|
+
:misc => misc,
|
132
|
+
:misc_raw => $5
|
133
|
+
}
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Calculate precipitation in self defined units and aproximated real world units
|
139
|
+
def calculate_rain_and_snow
|
140
|
+
@snow_metar = 0
|
141
|
+
@rain_metar = 0
|
142
|
+
|
143
|
+
self.specials.each do |s|
|
144
|
+
new_rain = 0
|
145
|
+
new_snow = 0
|
146
|
+
coefficient = 1
|
147
|
+
case s[:precipitation]
|
148
|
+
when 'drizzle' then
|
149
|
+
new_rain = 5
|
150
|
+
|
151
|
+
when 'rain' then
|
152
|
+
new_rain = 10
|
153
|
+
|
154
|
+
when 'snow' then
|
155
|
+
new_snow = 10
|
156
|
+
|
157
|
+
when 'snow grains' then
|
158
|
+
new_snow = 5
|
159
|
+
|
160
|
+
when 'ice crystals' then
|
161
|
+
new_snow = 1
|
162
|
+
new_rain = 1
|
163
|
+
|
164
|
+
when 'ice pellets' then
|
165
|
+
new_snow = 2
|
166
|
+
new_rain = 2
|
167
|
+
|
168
|
+
when 'hail' then
|
169
|
+
new_snow = 3
|
170
|
+
new_rain = 3
|
171
|
+
|
172
|
+
when 'small hail/snow pellets' then
|
173
|
+
new_snow = 1
|
174
|
+
new_rain = 1
|
175
|
+
end
|
176
|
+
|
177
|
+
case s[:intensity]
|
178
|
+
when 'in the vicinity' then
|
179
|
+
coefficient = 1.5
|
180
|
+
when 'heavy' then
|
181
|
+
coefficient = 3
|
182
|
+
when 'light' then
|
183
|
+
coefficient = 0.5
|
184
|
+
when 'moderate' then
|
185
|
+
coefficient = 1
|
186
|
+
end
|
187
|
+
|
188
|
+
snow = new_snow * coefficient
|
189
|
+
rain = new_rain * coefficient
|
190
|
+
|
191
|
+
if @snow_metar < snow
|
192
|
+
@snow_metar = snow
|
193
|
+
end
|
194
|
+
if @rain_metar < rain
|
195
|
+
@rain_metar = rain
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
# http://www.ofcm.gov/fmh-1/pdf/H-CH8.pdf page 3
|
201
|
+
# 10 units means more than 0.3 (I assume 0.5) inch per hour, so:
|
202
|
+
# 10 units => 0.5 * 25.4mm
|
203
|
+
real_world_coefficient = 0.5 * 25.4 / 10.0
|
204
|
+
|
205
|
+
@snow_metar *= real_world_coefficient
|
206
|
+
@rain_metar *= real_world_coefficient
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module SimpleMetarParser
|
4
|
+
class MetarTime < Base
|
5
|
+
|
6
|
+
def year
|
7
|
+
self.parent.year
|
8
|
+
end
|
9
|
+
|
10
|
+
def month
|
11
|
+
self.parent.month
|
12
|
+
end
|
13
|
+
|
14
|
+
def reset
|
15
|
+
@time = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def decode_split(s)
|
19
|
+
if s =~ /(\d{2})(\d{2})(\d{2})Z/
|
20
|
+
@time = Time.utc(self.year, self.month, $1.to_i, $2.to_i, $3.to_i, 0, 0)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Time "from"
|
25
|
+
attr_reader :time
|
26
|
+
alias :time_from :time
|
27
|
+
|
28
|
+
# Interval of one metar
|
29
|
+
def time_interval
|
30
|
+
self.parent.options[:time_interval]
|
31
|
+
end
|
32
|
+
|
33
|
+
# End of time period
|
34
|
+
def time_to
|
35
|
+
self.time_from + self.time_interval
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module SimpleMetarParser
|
4
|
+
class Pressure < Base
|
5
|
+
|
6
|
+
HG_INCH_TO_HPA = 1013.0/2992.1
|
7
|
+
HG_MM_TO_HPA = HG_INCH_TO_HPA * 25.4
|
8
|
+
|
9
|
+
def reset
|
10
|
+
@pressure = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :pressure
|
14
|
+
|
15
|
+
def decode_split(s)
|
16
|
+
# Europe
|
17
|
+
if s =~ /Q(\d{4})/
|
18
|
+
@pressure = $1.to_i
|
19
|
+
end
|
20
|
+
# US
|
21
|
+
if s =~ /A(\d{4})/
|
22
|
+
#1013 hPa = 29.921 inNg
|
23
|
+
@pressure=(($1.to_f) * HG_INCH_TO_HPA).round
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def pressure_hg_mm
|
28
|
+
return nil if self.pressure.nil?
|
29
|
+
(@pressure / HG_MM_TO_HPA).round
|
30
|
+
end
|
31
|
+
|
32
|
+
def pressure_hg_inch
|
33
|
+
(@pressure / HG_INCH_TO_HPA).round
|
34
|
+
end
|
35
|
+
|
36
|
+
# Pressure in hPa
|
37
|
+
def hpa
|
38
|
+
self.pressure
|
39
|
+
end
|
40
|
+
|
41
|
+
# mm of Hg
|
42
|
+
def hg_mm
|
43
|
+
self.pressure_hg_mm
|
44
|
+
end
|
45
|
+
|
46
|
+
# inches of Hg
|
47
|
+
def hg_inch
|
48
|
+
self.pressure_hg_inch
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|