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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/Gemfile +10 -0
- data/LICENSE +20 -0
- data/README.md +135 -0
- data/bin/wf-console +21 -0
- data/lib/weather-forecasts.rb +1 -0
- data/lib/weather_forecasts.rb +23 -0
- data/lib/weather_forecasts/client.rb +65 -0
- data/lib/weather_forecasts/client/error.rb +18 -0
- data/lib/weather_forecasts/client/help_console.rb +9 -0
- data/lib/weather_forecasts/client/query.rb +155 -0
- data/lib/weather_forecasts/client/query/query_property.rb +68 -0
- data/lib/weather_forecasts/client/query/query_utilities.rb +16 -0
- data/lib/weather_forecasts/client/query/select_by_days_query.rb +35 -0
- data/lib/weather_forecasts/client/query/select_coordinates_by_cities_query.rb +47 -0
- data/lib/weather_forecasts/client/query/select_coordinates_by_zip_query.rb +34 -0
- data/lib/weather_forecasts/client/query/select_corner_coordinates_query.rb +38 -0
- data/lib/weather_forecasts/client/query/select_gridpoint_coordinates_query.rb +35 -0
- data/lib/weather_forecasts/client/query/select_linepoint_coordinates_query.rb +33 -0
- data/lib/weather_forecasts/client/query/select_query.rb +48 -0
- data/lib/weather_forecasts/client/query/select_square_coordinates_query.rb +35 -0
- data/lib/weather_forecasts/client/query/selection_attributes.rb +19 -0
- data/lib/weather_forecasts/client/version.rb +5 -0
- data/lib/weather_forecasts/dwml.rb +38 -0
- data/lib/weather_forecasts/dwml/data_extractor.rb +55 -0
- data/lib/weather_forecasts/dwml/head_extractor.rb +49 -0
- data/lib/weather_forecasts/dwml/location.rb +33 -0
- data/lib/weather_forecasts/dwml/parameter_extractor.rb +281 -0
- data/lib/weather_forecasts/dwml/time_layout.rb +55 -0
- data/spec/fixtures/vcr_cassettes/select_by_days_query.yml +557 -0
- data/spec/fixtures/vcr_cassettes/select_coordinates_by_cities_query.yml +437 -0
- data/spec/fixtures/vcr_cassettes/select_coordinates_by_zip_query.yml +437 -0
- data/spec/fixtures/vcr_cassettes/select_corner_coordinates_query.yml +438 -0
- data/spec/fixtures/vcr_cassettes/select_gridpoint_coordinates_query.yml +467 -0
- data/spec/fixtures/vcr_cassettes/select_linepoint_coordinates_query.yml +440 -0
- data/spec/fixtures/vcr_cassettes/select_query.yml +1647 -0
- data/spec/fixtures/vcr_cassettes/select_square_coordinates_query.yml +443 -0
- data/spec/lib/ndfd/client/query/query_property_spec.rb +41 -0
- data/spec/lib/ndfd/client/query/select_by_days_query_spec.rb +35 -0
- data/spec/lib/ndfd/client/query/select_coordinates_by_cities_query_spec.rb +33 -0
- data/spec/lib/ndfd/client/query/select_coordinates_by_zip_query_spec.rb +34 -0
- data/spec/lib/ndfd/client/query/select_corner_coordinates_query_spec.rb +35 -0
- data/spec/lib/ndfd/client/query/select_gridpoint_coordinates_query_spec.rb +37 -0
- data/spec/lib/ndfd/client/query/select_linepoint_coordinates_query_spec.rb +36 -0
- data/spec/lib/ndfd/client/query/select_query_spec.rb +46 -0
- data/spec/lib/ndfd/client/query/select_square_coordinates_query_spec.rb +37 -0
- data/spec/lib/ndfd/client/query/selection_attributes_spec.rb +13 -0
- data/spec/lib/ndfd/client/query_spec.rb +91 -0
- data/spec/lib/ndfd/client_spec.rb +61 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/support/resources/sample_document.xml +61 -0
- data/weather-forecasts.gemspec +39 -0
- 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,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
|