weather-forecasts 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|