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