weather-forecasts 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +10 -0
  6. data/LICENSE +20 -0
  7. data/README.md +135 -0
  8. data/bin/wf-console +21 -0
  9. data/lib/weather-forecasts.rb +1 -0
  10. data/lib/weather_forecasts.rb +23 -0
  11. data/lib/weather_forecasts/client.rb +65 -0
  12. data/lib/weather_forecasts/client/error.rb +18 -0
  13. data/lib/weather_forecasts/client/help_console.rb +9 -0
  14. data/lib/weather_forecasts/client/query.rb +155 -0
  15. data/lib/weather_forecasts/client/query/query_property.rb +68 -0
  16. data/lib/weather_forecasts/client/query/query_utilities.rb +16 -0
  17. data/lib/weather_forecasts/client/query/select_by_days_query.rb +35 -0
  18. data/lib/weather_forecasts/client/query/select_coordinates_by_cities_query.rb +47 -0
  19. data/lib/weather_forecasts/client/query/select_coordinates_by_zip_query.rb +34 -0
  20. data/lib/weather_forecasts/client/query/select_corner_coordinates_query.rb +38 -0
  21. data/lib/weather_forecasts/client/query/select_gridpoint_coordinates_query.rb +35 -0
  22. data/lib/weather_forecasts/client/query/select_linepoint_coordinates_query.rb +33 -0
  23. data/lib/weather_forecasts/client/query/select_query.rb +48 -0
  24. data/lib/weather_forecasts/client/query/select_square_coordinates_query.rb +35 -0
  25. data/lib/weather_forecasts/client/query/selection_attributes.rb +19 -0
  26. data/lib/weather_forecasts/client/version.rb +5 -0
  27. data/lib/weather_forecasts/dwml.rb +38 -0
  28. data/lib/weather_forecasts/dwml/data_extractor.rb +55 -0
  29. data/lib/weather_forecasts/dwml/head_extractor.rb +49 -0
  30. data/lib/weather_forecasts/dwml/location.rb +33 -0
  31. data/lib/weather_forecasts/dwml/parameter_extractor.rb +281 -0
  32. data/lib/weather_forecasts/dwml/time_layout.rb +55 -0
  33. data/spec/fixtures/vcr_cassettes/select_by_days_query.yml +557 -0
  34. data/spec/fixtures/vcr_cassettes/select_coordinates_by_cities_query.yml +437 -0
  35. data/spec/fixtures/vcr_cassettes/select_coordinates_by_zip_query.yml +437 -0
  36. data/spec/fixtures/vcr_cassettes/select_corner_coordinates_query.yml +438 -0
  37. data/spec/fixtures/vcr_cassettes/select_gridpoint_coordinates_query.yml +467 -0
  38. data/spec/fixtures/vcr_cassettes/select_linepoint_coordinates_query.yml +440 -0
  39. data/spec/fixtures/vcr_cassettes/select_query.yml +1647 -0
  40. data/spec/fixtures/vcr_cassettes/select_square_coordinates_query.yml +443 -0
  41. data/spec/lib/ndfd/client/query/query_property_spec.rb +41 -0
  42. data/spec/lib/ndfd/client/query/select_by_days_query_spec.rb +35 -0
  43. data/spec/lib/ndfd/client/query/select_coordinates_by_cities_query_spec.rb +33 -0
  44. data/spec/lib/ndfd/client/query/select_coordinates_by_zip_query_spec.rb +34 -0
  45. data/spec/lib/ndfd/client/query/select_corner_coordinates_query_spec.rb +35 -0
  46. data/spec/lib/ndfd/client/query/select_gridpoint_coordinates_query_spec.rb +37 -0
  47. data/spec/lib/ndfd/client/query/select_linepoint_coordinates_query_spec.rb +36 -0
  48. data/spec/lib/ndfd/client/query/select_query_spec.rb +46 -0
  49. data/spec/lib/ndfd/client/query/select_square_coordinates_query_spec.rb +37 -0
  50. data/spec/lib/ndfd/client/query/selection_attributes_spec.rb +13 -0
  51. data/spec/lib/ndfd/client/query_spec.rb +91 -0
  52. data/spec/lib/ndfd/client_spec.rb +61 -0
  53. data/spec/spec_helper.rb +33 -0
  54. data/spec/support/resources/sample_document.xml +61 -0
  55. data/weather-forecasts.gemspec +39 -0
  56. metadata +224 -0
@@ -0,0 +1,55 @@
1
+ require 'weather_forecasts/dwml/location'
2
+ require 'weather_forecasts/dwml/time_layout'
3
+ require 'weather_forecasts/dwml/parameter_extractor'
4
+
5
+ module WeatherForecasts
6
+ class DWML
7
+ class DataExtractor
8
+ attr_reader :output, :element
9
+
10
+ def initialize(element)
11
+ @element = element
12
+ @locations = []
13
+ @time_layouts = []
14
+ @output = {}
15
+ end
16
+
17
+ def process
18
+ extract_locations
19
+ extract_time_layouts
20
+ extract_parameters
21
+
22
+ output
23
+ end
24
+
25
+ protected
26
+
27
+ def extract_locations
28
+ @locations = Location.extract(element.xpath("location"))
29
+ end
30
+
31
+ def extract_time_layouts
32
+ @time_layouts = TimeLayout.extract(element.xpath("time-layout"))
33
+ end
34
+
35
+ def extract_parameters
36
+ parameters = element.xpath("parameters")
37
+
38
+ @output.merge!(
39
+ :parameters => parameters.inject({}) do |memo, parameter|
40
+ location = location_for_parameter(parameter)
41
+ extractor = ParameterExtractor.new(parameter, location, @time_layouts)
42
+ memo.merge!(location.location_key => extractor.process)
43
+ memo
44
+ end
45
+ )
46
+ end
47
+
48
+ def location_for_parameter(parameter)
49
+ @locations.detect do |location|
50
+ parameter.attributes["applicable-location"].text == location.location_key
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,49 @@
1
+ module WeatherForecasts
2
+ class DWML
3
+ class HeadExtractor
4
+ attr_reader :output, :element
5
+
6
+ def initialize(element)
7
+ @element = element
8
+ @output = {}
9
+ end
10
+
11
+ def process
12
+ build_product
13
+ build_source
14
+ output
15
+ end
16
+
17
+ protected
18
+
19
+ def build_product
20
+ creation_date = Time.zone.parse(element.xpath('product/creation-date').text)
21
+
22
+ @output.merge!(
23
+ :product => {
24
+ :title => element.xpath('product/title').text,
25
+ :field => element.xpath('product/field').text,
26
+ :category => element.xpath('product/category').text,
27
+ :creation_date => creation_date
28
+ }
29
+ )
30
+ end
31
+
32
+ def build_source
33
+ sub_center = element.xpath('source/production-center/sub-center').text
34
+ production_center = element.xpath('source/production-center').text
35
+
36
+ @output.merge!(
37
+ :source => {
38
+ :more_information => element.xpath('source/more-information').text,
39
+ :product_center => production_center.gsub(sub_center, " - #{sub_center}"),
40
+ :disclaimer => element.xpath('source/disclaimer').text,
41
+ :credit => element.xpath('source/credit').text,
42
+ :credit_logo => element.xpath('source/credit-logo').text,
43
+ :feedback => element.xpath('source/feedback').text
44
+ }
45
+ )
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,33 @@
1
+ module WeatherForecasts
2
+ class DWML
3
+ class Location
4
+ class << self
5
+ def extract(elements)
6
+ elements.map { |element| new(element) }
7
+ end
8
+ end
9
+
10
+ attr_reader :element, :location_key, :latitude, :longitude
11
+
12
+ def initialize(element)
13
+ @element = element
14
+
15
+ extract_key
16
+ extract_coords
17
+ end
18
+
19
+ protected
20
+
21
+ def extract_key
22
+ @location_key = element.xpath("location-key").first.text
23
+ end
24
+
25
+ def extract_coords
26
+ point = element.xpath('point').first
27
+
28
+ @latitude = point.attributes["latitude"].text.to_f
29
+ @longitude = point.attributes["longitude"].text.to_f
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,281 @@
1
+ module WeatherForecasts
2
+ class DWML
3
+ class ParameterExtractor
4
+ attr_reader :output, :element, :location, :time_layouts
5
+
6
+ def initialize(element, location, time_layouts)
7
+ @element = element
8
+ @location = location
9
+ @time_layouts = time_layouts
10
+
11
+ @output = {
12
+ :latitude => location.latitude,
13
+ :longitude => location.longitude
14
+ }
15
+ end
16
+
17
+ def process
18
+ extract_temperatures
19
+ extract_precipitation
20
+ extract_wind_speed
21
+ extract_wind_direction
22
+ extract_cloud_cover
23
+ extract_probability_of_precipitation
24
+ extract_fire_weather
25
+ extract_convective_hazard
26
+ extract_climate_anomaly
27
+ extract_humidity
28
+ extract_weather
29
+ extract_conditions_icons
30
+ extract_hazards
31
+ extract_water_state
32
+
33
+ output
34
+ end
35
+
36
+ protected
37
+
38
+ def extract_temperatures
39
+ extract_basic_time_series(:temperature)
40
+ end
41
+
42
+ def extract_precipitation
43
+ extract_basic_time_series(:precipitation)
44
+ end
45
+
46
+ def extract_wind_speed
47
+ extract_basic_time_series(:"wind-speed")
48
+ end
49
+
50
+ def extract_wind_direction
51
+ extract_basic_time_series(:direction)
52
+ end
53
+
54
+ def extract_cloud_cover
55
+ extract_basic_time_series(:"cloud-amount")
56
+ end
57
+
58
+ def extract_probability_of_precipitation
59
+ extract_basic_time_series(:"probability-of-precipitation")
60
+ end
61
+
62
+ def extract_humidity
63
+ extract_basic_time_series(:humidity)
64
+ end
65
+
66
+ def extract_fire_weather
67
+ extract_basic_time_series(:"fire-weather")
68
+ end
69
+
70
+ def extract_water_state
71
+ node = element.xpath("water-state").first
72
+ return if node.blank?
73
+
74
+ @output[:"water-state"] = {}
75
+ layout = lookup_time_layout(node)
76
+
77
+ waves_node = node.xpath("waves").first
78
+ @output[:"water-state"][:type] = waves_node.attributes["type"].text
79
+ @output[:"water-state"][:unit] = waves_node.attributes["units"].text
80
+ @output[:"water-state"][:values] = []
81
+
82
+ waves_node.xpath("value").each_with_index do |value, index|
83
+ @output[:"water-state"][:values] << {
84
+ :value => value.text,
85
+ :start_time => layout.valid_times[index].start
86
+ }
87
+ end
88
+ end
89
+
90
+ def extract_hazards
91
+ node = element.xpath("hazards").first
92
+ return if node.blank?
93
+
94
+ layout = lookup_time_layout(node)
95
+
96
+ @output[:hazards] = {
97
+ :name => node.xpath("name").first.text,
98
+ :conditions => node.xpath("hazard-conditions").each_with_index.map do |condition_node, index|
99
+ hazard_node = condition_node.xpath("hazard").first
100
+ next if hazard_node.blank?
101
+
102
+ {
103
+ :code => hazard_node.attributes["hazardCode"].text,
104
+ :phenomena => hazard_node.attributes["phenomena"].text,
105
+ :significance => hazard_node.attributes["significance"].text,
106
+ :type => hazard_node.attributes["hazardType"].text,
107
+ :url => hazard_node.xpath("hazardTextURL").first.text,
108
+ :start_time => layout.valid_times[index].start
109
+ }
110
+ end.compact
111
+ }
112
+ end
113
+
114
+ def extract_conditions_icons
115
+ node = element.xpath("conditions-icon").first
116
+ return if node.blank?
117
+
118
+ layout = lookup_time_layout(node)
119
+
120
+ @output[:"conditions-icon"] = {
121
+ :name => node.xpath("name").text,
122
+ :type => node.attributes["type"].text,
123
+ :links => node.xpath("icon-link").each_with_index.map do |icon_node, index|
124
+ {
125
+ :link => icon_node.text,
126
+ :start_time => layout.valid_times[index].start
127
+ }
128
+ end
129
+ }
130
+ end
131
+
132
+ def extract_convective_hazard
133
+ return if element.xpath("convective-hazard").blank?
134
+
135
+ @output[:"convective-hazard"] ||= {
136
+ :outlook => { :name => nil, :values => []},
137
+ :"severe-component" => []
138
+ }
139
+
140
+ extract_convective_hazard_outlook
141
+ extract_convective_hazard_severity
142
+ end
143
+
144
+ def extract_convective_hazard_outlook
145
+ outlook_node = element.xpath("convective-hazard/outlook").first
146
+ layout = lookup_time_layout(outlook_node)
147
+
148
+ @output[:"convective-hazard"][:outlook][:name] = outlook_node.xpath("node").text
149
+
150
+ outlook_node.xpath("value").each_with_index do |value, index|
151
+ @output[:"convective-hazard"][:outlook][:values] << {
152
+ :start_time => layout.valid_times[index].start,
153
+ :end_time => layout.valid_times[index].stop,
154
+ :value => value.text
155
+ }
156
+ end
157
+ end
158
+
159
+ def extract_convective_hazard_severity
160
+ element.xpath("convective-hazard/severe-component").each do |node|
161
+ layout = lookup_time_layout(node)
162
+
163
+ hsh = {
164
+ :name => node.xpath("name").first.text,
165
+ :type => node.attributes["type"].text,
166
+ :unit => node.attributes["units"].text,
167
+ :values => node.xpath("value").each_with_index.map do |value, index|
168
+ {
169
+ :value => value.text,
170
+ :start_time => layout.valid_times[index].start,
171
+ :end_time => layout.valid_times[index].stop
172
+ }
173
+ end
174
+ }
175
+
176
+ @output[:"convective-hazard"][:"severe-component"] << hsh
177
+ end
178
+ end
179
+
180
+ def extract_climate_anomaly
181
+ return if element.xpath("climate-anomaly").blank?
182
+
183
+ [:weekly, :monthly, :seasonal].each do |period|
184
+ element.xpath("climate-anomaly/#{period.to_s}").each_with_index do |node|
185
+ layout = lookup_time_layout(node)
186
+ valid_time = layout.valid_times.first
187
+
188
+ @output[:"climate-anomaly"] ||= {}
189
+ @output[:"climate-anomaly"][period] ||= []
190
+ @output[:"climate-anomaly"][period] << {
191
+ :name => node.xpath("name").first.text,
192
+ :value => node.xpath("value").first.text,
193
+ :type => node.attributes["type"].text,
194
+ :unit => node.attributes["units"].text,
195
+ :start_time => valid_time.start,
196
+ :end_time => valid_time.stop
197
+ }
198
+ end
199
+ end
200
+ end
201
+
202
+ def extract_weather
203
+ node = element.xpath("weather")
204
+ return if node.blank?
205
+
206
+ node.map do |weather_node|
207
+ @output[:weather] ||= {}
208
+ @output[:weather][:name] = weather_node.xpath("name").text
209
+ @output[:weather][:conditions] ||= []
210
+
211
+ layout = lookup_time_layout(weather_node)
212
+
213
+ weather_node.xpath("weather-conditions").each_with_index do |condition, index|
214
+ value = condition.xpath("value").first
215
+ next if value.blank?
216
+
217
+ visibility_node = value.xpath("visibility").first
218
+ visibility = if visibility_node.present? && visibility_node.text.present?
219
+ {
220
+ :unit => visibility_node.attributes["units"].text,
221
+ :value => visibility_node.text.to_f
222
+ }
223
+ else
224
+ ""
225
+ end
226
+
227
+ hsh = {
228
+ :start_time => layout.valid_times[index].start,
229
+ :coverage => value.attributes["coverage"].text,
230
+ :intensity => value.attributes["intensity"].text,
231
+ :"weather-type" => value.attributes["weather-type"].text,
232
+ :qualifier => value.attributes["qualifier"].text,
233
+ :visibility => visibility
234
+ }
235
+
236
+ additive = value.attributes["additive"]
237
+ hsh.merge!(:additive => additive) if additive.present?
238
+
239
+ @output[:weather][:conditions] << hsh
240
+ end
241
+ end
242
+ end
243
+
244
+ def extract_basic_time_series(metric)
245
+ metric_node = element.xpath(metric.to_s)
246
+ return if metric_node.blank?
247
+
248
+ metric_node.map do |node|
249
+ layout = lookup_time_layout(node)
250
+ type = node.attributes["type"].text.to_sym
251
+ unit = node.attributes["units"].try(:text)
252
+
253
+ @output[metric] ||= {}
254
+ @output[metric][type] ||= {}
255
+ @output[metric][type][:name] = node.xpath("name").text
256
+ @output[metric][type][:values] = []
257
+
258
+ node.xpath("value").each_with_index do |value, index|
259
+ hsh = {
260
+ :value => value.text.to_f,
261
+ :start_time => layout.valid_times[index].start
262
+ }
263
+
264
+ end_time = layout.valid_times[index].stop
265
+
266
+ hsh.merge!(:unit => unit) if unit.present?
267
+ hsh.merge!(:end_time => end_time) if end_time.present?
268
+
269
+ @output[metric][type][:values] << hsh
270
+ end
271
+ end
272
+ end
273
+
274
+ def lookup_time_layout(node)
275
+ @time_layouts.detect do |layout|
276
+ node.attributes["time-layout"].text == layout.layout_key
277
+ end
278
+ end
279
+ end
280
+ end
281
+ end
@@ -0,0 +1,55 @@
1
+ module WeatherForecasts
2
+ class DWML
3
+ class TimeLayout
4
+ class << self
5
+ def extract(elements)
6
+ elements.map { |element| new(element) }
7
+ end
8
+ end
9
+
10
+ attr_reader :element, :time_coordinate, :summarization, :layout_key, :valid_times
11
+
12
+ def initialize(element)
13
+ @element = element
14
+ @valid_times = []
15
+
16
+ extract_time_coordinate
17
+ extract_summarization
18
+ extract_layout_key
19
+ extract_valid_times
20
+ end
21
+
22
+ protected
23
+
24
+ def extract_time_coordinate
25
+ @time_coordinate = element.attributes["time-coordinate"].text
26
+ end
27
+
28
+ def extract_summarization
29
+ @summarization = element.attributes["summarization"].text
30
+ end
31
+
32
+ def extract_layout_key
33
+ @layout_key = element.xpath("layout-key").first.text
34
+ end
35
+
36
+ def extract_valid_times
37
+ start_times = element.xpath("start-valid-time")
38
+ stop_times = element.xpath("end-valid-time")
39
+
40
+ start_times.each_with_index do |start_time, index|
41
+ @valid_times << ValidTime.new(start_time.text, stop_times[index].try(:text))
42
+ end
43
+ end
44
+
45
+ class ValidTime
46
+ attr_reader :start, :stop
47
+
48
+ def initialize(start, stop)
49
+ @start = Time.zone.parse(start.to_s)
50
+ @stop = Time.zone.parse(stop.to_s)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end