weather-forecasts 1.1.1

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 (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