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,68 @@
1
+ module WeatherForecasts
2
+ class Client
3
+ class QueryProperty
4
+ attr_accessor :name, :type, :options, :required_keys, :required, :default
5
+
6
+ def initialize(attr, parameters = {})
7
+ @name = attr
8
+ @default = parameters[:default]
9
+ @options = parameters[:options]
10
+ @type = parameters[:type] || String
11
+ @required = parameters[:required] || false
12
+ @required_keys = parameters[:required_keys] || []
13
+ end
14
+
15
+ def valid?(conditions)
16
+ @conditions = conditions
17
+
18
+ validate_required
19
+ validate_type
20
+ validate_required_keys
21
+ validate_options
22
+ end
23
+
24
+ protected
25
+
26
+ def validate_required
27
+ if required && @conditions[name.to_sym].blank?
28
+ raise RequiredPropertyError, "The property, #{name.to_s}, is required."
29
+ end
30
+ end
31
+
32
+ def validate_type
33
+ unless @conditions[name.to_sym].is_a?(type)
34
+ raise InvalidPropertyTypeError, "The type for #{name} should be an instance of #{type.to_s}."
35
+ end
36
+ end
37
+
38
+ def validate_required_keys
39
+ return if required_keys.blank?
40
+ params_sets = @conditions[name.to_sym]
41
+
42
+ if params_sets.present? && params_sets.is_a?(Array)
43
+ params_sets.each { |hsh| check_required_keys_missing(hsh.keys) }
44
+ elsif params_sets.present? && params_sets.is_a?(Hash)
45
+ check_required_keys_missing(params_sets.keys)
46
+ else
47
+ raise_required_keys_missing_error(required_keys)
48
+ end
49
+ end
50
+
51
+ def validate_options
52
+ value = @conditions[name.to_sym]
53
+ if options.present? && !options.include?(value)
54
+ raise InvalidOptionSpecifiedError, "The option, #{value}, is not valid. Please use one of the following: #{options.join(', ')}"
55
+ end
56
+ end
57
+
58
+ def check_required_keys_missing(keys)
59
+ missing_keys = required_keys - keys
60
+ raise_required_keys_missing_error(missing_keys) if missing_keys.present?
61
+ end
62
+
63
+ def raise_required_keys_missing_error(missing_keys)
64
+ raise RequiredKeysMissingError, "Required keys (#{missing_keys.join(', ')}) are missing for one of the data points (also, they must be in an Array)."
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,16 @@
1
+ module WeatherForecasts
2
+ class Client
3
+ module QueryUtilities
4
+ def build_coordinates(coordinates)
5
+ coordinates.map { |hsh| "#{hsh[:latitude]},#{hsh[:longitude]}" }.join(" ")
6
+ end
7
+
8
+ def transform_coordinate_list(document)
9
+ document.xpath("//latLonList").text.split.map do |coord|
10
+ latitude, longitude = coord.split(",").map(&:to_f)
11
+ { :latitude => latitude, :longitude => longitude }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,35 @@
1
+ module WeatherForecasts
2
+ class Client
3
+ class SelectByDaysQuery < Query
4
+ include QueryUtilities
5
+
6
+ property :coordinates, :type => Array, :required => true, :required_keys => [:latitude, :longitude]
7
+ property :days, :type => Fixnum, :required => true
8
+
9
+ property :unit, :type => String, :options => ["e", "m"], :default => "e"
10
+ property :format, :type => String, :options => ["24 hourly", "12 hourly"], :default => "24 hourly"
11
+
12
+ property :start_date, :type => ActiveSupport::TimeWithZone
13
+
14
+ def execute
15
+ validate
16
+
17
+ response = soap_client.call(:ndf_dgen_by_day_lat_lon_list, :message => build_message)
18
+ document = Nokogiri::XML(response.body[:ndf_dgen_by_day_lat_lon_list_response][:dwml_by_day_out])
19
+ DWML.new(document).process
20
+ end
21
+
22
+ protected
23
+
24
+ def build_message
25
+ {
26
+ :listLatLong => build_coordinates(conditions[:coordinates]),
27
+ :startDate => conditions[:start_date].iso8601,
28
+ :numDay => conditions[:days],
29
+ :Unit => conditions[:unit],
30
+ :format => conditions[:format]
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,47 @@
1
+ module WeatherForecasts
2
+ class Client
3
+ class SelectCoordinatesByCitiesQuery < Query
4
+ CITY_OPTIONS = {
5
+ :all => 1234,
6
+ :primary => 1,
7
+ :secondary => 2,
8
+ :tertiary => 3,
9
+ :quaternary => 4,
10
+ :primary_and_secondary => 12,
11
+ :tertiary_and_quaternar => 34
12
+ }
13
+
14
+ property :display, :type => Symbol, :options => CITY_OPTIONS.keys, :required => true
15
+
16
+ def execute
17
+ validate
18
+
19
+ response = soap_client.call(:lat_lon_list_city_names, :message => build_message)
20
+ document = parse_xml(response)
21
+ transform_document_to_mapped_list(document)
22
+ end
23
+
24
+ protected
25
+
26
+ def build_message
27
+ {
28
+ :displayLevel => CITY_OPTIONS[conditions[:display]]
29
+ }
30
+ end
31
+
32
+ def parse_xml(response)
33
+ Nokogiri::XML(response.body[:lat_lon_list_city_names_response][:list_lat_lon_out])
34
+ end
35
+
36
+ def transform_document_to_mapped_list(document)
37
+ cities = document.xpath("//cityNameList").text.split('|')
38
+ coords = document.xpath("//latLonList").text.split
39
+
40
+ results = [cities, coords].transpose.inject({}) do |memo, (city, coord)|
41
+ latitude, longitude = coord.split(",").map(&:to_f)
42
+ memo.merge(city => { :latitude => latitude, :longitude => longitude } )
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,34 @@
1
+ module WeatherForecasts
2
+ class Client
3
+ class SelectCoordinatesByZipQuery < Query
4
+ property :zip, :type => Array, :required => true
5
+
6
+ def execute
7
+ validate
8
+
9
+ response = soap_client.call(:lat_lon_list_zip_code, :message => build_message)
10
+ document = parse_xml(response)
11
+ transform_document_to_mapped_list(document)
12
+ end
13
+
14
+ protected
15
+
16
+ def build_message
17
+ {
18
+ :zipCodeList => conditions[:zip].join(" ")
19
+ }
20
+ end
21
+
22
+ def parse_xml(response)
23
+ Nokogiri::XML(response.body[:lat_lon_list_zip_code_response][:list_lat_lon_out])
24
+ end
25
+
26
+ def transform_document_to_mapped_list(document)
27
+ document.xpath("//latLonList").text.split.each_with_index.inject({}) do |memo, (coord, i)|
28
+ latitude, longitude = coord.split(",").map(&:to_f)
29
+ memo.merge(conditions[:zip][i] => { :latitude => latitude, :longitude => longitude })
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,38 @@
1
+ module WeatherForecasts
2
+ class Client
3
+ class SelectCornerCoordinatesQuery < Query
4
+ include QueryUtilities
5
+
6
+ SECTOR_GRID_OPTIONS = [
7
+ :conus,
8
+ :alaska,
9
+ :nhemi,
10
+ :guam,
11
+ :hawaii,
12
+ :puertori
13
+ ]
14
+
15
+ property :sector, :type => Symbol, :options => SECTOR_GRID_OPTIONS, :required => true
16
+
17
+ def execute
18
+ validate
19
+
20
+ response = soap_client.call(:corner_points, :message => build_message)
21
+ document = parse_xml(response)
22
+ transform_coordinate_list(document)
23
+ end
24
+
25
+ protected
26
+
27
+ def build_message
28
+ {
29
+ :sector => conditions[:sector].to_s,
30
+ }
31
+ end
32
+
33
+ def parse_xml(response)
34
+ Nokogiri::XML(response.body[:corner_points_response][:list_lat_lon_out])
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,35 @@
1
+ module WeatherForecasts
2
+ class Client
3
+ class SelectGridpointCoordinatesQuery < Query
4
+ include QueryUtilities
5
+
6
+ property :lower_left_coordinate, :type => Hash, :required => true, :required_keys => [:latitude, :longitude]
7
+ property :upper_right_coordinate, :type => Hash, :required => true, :required_keys => [:latitude, :longitude]
8
+ property :resolution, :type => Numeric, :default => 5
9
+
10
+ def execute
11
+ validate
12
+
13
+ response = soap_client.call(:lat_lon_list_subgrid, :message => build_message)
14
+ document = parse_xml(response)
15
+ transform_coordinate_list(document)
16
+ end
17
+
18
+ protected
19
+
20
+ def build_message
21
+ {
22
+ :lowerLeftLatitude => conditions[:lower_left_coordinate][:latitude],
23
+ :lowerLeftLongitude => conditions[:lower_left_coordinate][:longitude],
24
+ :upperRightLatitude => conditions[:upper_right_coordinate][:latitude],
25
+ :upperRightLongitude => conditions[:upper_right_coordinate][:longitude],
26
+ :resolution => conditions[:resolution]
27
+ }
28
+ end
29
+
30
+ def parse_xml(response)
31
+ Nokogiri::XML(response.body[:lat_lon_list_subgrid_response][:list_lat_lon_out])
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,33 @@
1
+ module WeatherForecasts
2
+ class Client
3
+ class SelectLinepointCoordinatesQuery < Query
4
+ include QueryUtilities
5
+
6
+ property :start_coordinate, :type => Hash, :required => true, :required_keys => [:latitude, :longitude]
7
+ property :stop_coordinate, :type => Hash, :required => true, :required_keys => [:latitude, :longitude]
8
+
9
+ def execute
10
+ validate
11
+
12
+ response = soap_client.call(:lat_lon_list_line, :message => build_message)
13
+ document = parse_xml(response)
14
+ transform_coordinate_list(document)
15
+ end
16
+
17
+ protected
18
+
19
+ def build_message
20
+ {
21
+ :endPoint1Lat => conditions[:start_coordinate][:latitude],
22
+ :endPoint1Lon => conditions[:start_coordinate][:longitude],
23
+ :endPoint2Lat => conditions[:stop_coordinate][:latitude],
24
+ :endPoint2Lon => conditions[:stop_coordinate][:longitude]
25
+ }
26
+ end
27
+
28
+ def parse_xml(response)
29
+ Nokogiri::XML(response.body[:lat_lon_list_line_response][:list_lat_lon_out])
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,48 @@
1
+ module WeatherForecasts
2
+ class Client
3
+ class SelectQuery < Query
4
+ include QueryUtilities
5
+
6
+ set_selection_attributes *FORECAST_ELEMENTS.keys
7
+
8
+ property :coordinates, :type => Array, :required => true, :required_keys => [:latitude, :longitude]
9
+ property :product, :type => String, :options => ["time-series", "glance"], :required => true, :default => "time-series"
10
+ property :unit, :type => String, :options => ["e", "m"], :default => "e"
11
+
12
+ property :start_time, :type => ActiveSupport::TimeWithZone
13
+ property :end_time, :type => ActiveSupport::TimeWithZone
14
+
15
+ def execute
16
+ validate
17
+
18
+ response = soap_client.call(:ndf_dgen_lat_lon_list, :message => build_message)
19
+ document = Nokogiri::XML(response.body[:ndf_dgen_lat_lon_list_response][:dwml_out])
20
+ transform_to_hash(document)
21
+ end
22
+
23
+ protected
24
+
25
+ def transform_to_hash(doc)
26
+ WeatherForecasts::DWML.new(doc).process
27
+ end
28
+
29
+ def build_message
30
+ {
31
+ :listLatLon => build_coordinates(conditions[:coordinates]),
32
+ :product => conditions[:product],
33
+ :startTime => conditions[:start_time].iso8601,
34
+ :endTime => conditions[:end_time].iso8601,
35
+ :Unit => conditions[:unit],
36
+ :weatherParameters => build_weather_parameters
37
+ }
38
+ end
39
+
40
+ def build_weather_parameters
41
+ available_selections.attributes.inject({}) do |memo, attr|
42
+ is_requested = select_attributes.include?(attr) ? 1 : 0
43
+ memo.merge!(attr => is_requested)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,35 @@
1
+ module WeatherForecasts
2
+ class Client
3
+ class SelectSquareCoordinatesQuery < Query
4
+ include QueryUtilities
5
+
6
+ property :center_point, :type => Hash, :required => true, :required_keys => [:latitude, :longitude]
7
+ property :distance, :type => Hash, :required => true, :required_keys => [:latitude, :longitude]
8
+ property :resolution, :type => Numeric, :default => 5
9
+
10
+ def execute
11
+ validate
12
+
13
+ response = soap_client.call(:lat_lon_list_square, :message => build_message)
14
+ document = parse_xml(response)
15
+ transform_coordinate_list(document)
16
+ end
17
+
18
+ protected
19
+
20
+ def build_message
21
+ {
22
+ :centerPointLat => conditions[:center_point][:latitude],
23
+ :centerPointLon => conditions[:center_point][:longitude],
24
+ :distanceLat => conditions[:distance][:latitude],
25
+ :distanceLon => conditions[:distance][:longitude],
26
+ :resolution => conditions[:resolution]
27
+ }
28
+ end
29
+
30
+ def parse_xml(response)
31
+ Nokogiri::XML(response.body[:lat_lon_list_square_response][:list_lat_lon_out])
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,19 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+
3
+ module WeatherForecasts
4
+ class Client
5
+ class SelectionAttributes
6
+ attr_reader :attributes
7
+
8
+ def initialize(attributes)
9
+ @attributes = Array.wrap(attributes)
10
+ end
11
+
12
+ def valid?(attr)
13
+ unless attributes.include?(attr)
14
+ raise InvalidSelectionAttributeError, "The selection attribute, `#{attr}`, is not a valid attribute on this query."
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ module WeatherForecasts
2
+ class Client
3
+ VERSION = "1.1.1"
4
+ end
5
+ end
@@ -0,0 +1,38 @@
1
+ require 'multi_json'
2
+
3
+ require 'weather_forecasts/dwml/head_extractor'
4
+ require 'weather_forecasts/dwml/data_extractor'
5
+
6
+ #
7
+ # Note: See http://graphical.weather.gov/xml/mdl/XML/Design/MDL_XML_Design.pdf
8
+ # for authoritative type definitions
9
+ ################################################################################
10
+
11
+ module WeatherForecasts
12
+ class DWML
13
+ attr_reader :output, :xmldoc
14
+
15
+ def initialize(xmldoc)
16
+ @xmldoc = xmldoc
17
+ @output = {}
18
+ end
19
+
20
+ def process
21
+ build_head
22
+ build_data
23
+ output
24
+ end
25
+
26
+ protected
27
+
28
+ def build_head
29
+ extractor = HeadExtractor.new(xmldoc.xpath("//dwml/head").first)
30
+ @output.merge!(extractor.process)
31
+ end
32
+
33
+ def build_data
34
+ extractor = DataExtractor.new(xmldoc.xpath("//dwml/data").first)
35
+ @output.merge!(extractor.process)
36
+ end
37
+ end
38
+ end