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