noaa_weather_client 0.0.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 +17 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/.yardopts +2 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +107 -0
- data/Rakefile +8 -0
- data/bin/noaa_weather_client +43 -0
- data/data/xml/current_observation.xsd +79 -0
- data/data/xml/dwml.xsd +97 -0
- data/data/xml/location.xsd +142 -0
- data/data/xml/meta_data.xsd +100 -0
- data/data/xml/moreWeatherInformation.xsd +23 -0
- data/data/xml/ndfd_data.xsd +43 -0
- data/data/xml/parameters.xsd +1173 -0
- data/data/xml/summarizationType.xsd +29 -0
- data/data/xml/time_layout.xsd +51 -0
- data/lib/noaa_weather_client.rb +9 -0
- data/lib/noaa_weather_client/cli.rb +53 -0
- data/lib/noaa_weather_client/cli/templates.rb +53 -0
- data/lib/noaa_weather_client/client.rb +61 -0
- data/lib/noaa_weather_client/errors.rb +7 -0
- data/lib/noaa_weather_client/responses/current_observation.rb +93 -0
- data/lib/noaa_weather_client/responses/forecast.rb +84 -0
- data/lib/noaa_weather_client/responses/generic_response.rb +9 -0
- data/lib/noaa_weather_client/responses/lat_lon_list.rb +25 -0
- data/lib/noaa_weather_client/responses/reactive_xml_response.rb +29 -0
- data/lib/noaa_weather_client/responses/station.rb +28 -0
- data/lib/noaa_weather_client/responses/stations.rb +41 -0
- data/lib/noaa_weather_client/responses/validatable_xml_response.rb +22 -0
- data/lib/noaa_weather_client/rest_client_factory.rb +12 -0
- data/lib/noaa_weather_client/services/calculate_distance_between_lat_lon.rb +20 -0
- data/lib/noaa_weather_client/services/current_observations.rb +32 -0
- data/lib/noaa_weather_client/services/find_nearest_station.rb +16 -0
- data/lib/noaa_weather_client/services/forecast_by_day.rb +52 -0
- data/lib/noaa_weather_client/services/postal_code_to_coordinate.rb +36 -0
- data/lib/noaa_weather_client/services/rest_service.rb +28 -0
- data/lib/noaa_weather_client/services/soap_service.rb +16 -0
- data/lib/noaa_weather_client/services/weather_stations.rb +32 -0
- data/lib/noaa_weather_client/soap_client_factory.rb +17 -0
- data/lib/noaa_weather_client/station_filters.rb +8 -0
- data/lib/noaa_weather_client/version.rb +3 -0
- data/lib/noaa_weather_client/xml_parser_factory.rb +9 -0
- data/noaa_weather_client.gemspec +27 -0
- data/spec/fixtures/vcr_cassettes/current_observations.yml +25890 -0
- data/spec/fixtures/vcr_cassettes/forecast_by_day_3.yml +772 -0
- data/spec/fixtures/vcr_cassettes/forecast_by_day_7.yml +829 -0
- data/spec/fixtures/vcr_cassettes/nearest_weather_station.yml +25842 -0
- data/spec/fixtures/vcr_cassettes/postal_code_to_coordinate.yml +75 -0
- data/spec/fixtures/vcr_cassettes/weather_stations.yml +25842 -0
- data/spec/fixtures/xml/forecast.xml +144 -0
- data/spec/lib/noaa_client/client_spec.rb +93 -0
- data/spec/lib/noaa_client/responses/current_observation_spec.rb +122 -0
- data/spec/lib/noaa_client/responses/forecast_spec.rb +66 -0
- data/spec/lib/noaa_client/responses/lat_lon_list_spec.rb +30 -0
- data/spec/lib/noaa_client/responses/station_spec.rb +53 -0
- data/spec/lib/noaa_client/responses/stations_spec.rb +86 -0
- data/spec/lib/noaa_client/rest_client_factory_spec.rb +15 -0
- data/spec/lib/noaa_client/services/calculate_distance_between_lat_lon_spec.rb +16 -0
- data/spec/lib/noaa_client/services/current_observations_spec.rb +47 -0
- data/spec/lib/noaa_client/services/find_nearest_station_spec.rb +36 -0
- data/spec/lib/noaa_client/services/forecast_by_day_spec.rb +62 -0
- data/spec/lib/noaa_client/services/postal_code_to_coordinate_spec.rb +41 -0
- data/spec/lib/noaa_client/services/rest_service_spec.rb +45 -0
- data/spec/lib/noaa_client/services/soap_service_spec.rb +56 -0
- data/spec/lib/noaa_client/services/weather_stations_spec.rb +40 -0
- data/spec/lib/noaa_client/soap_client_factory_spec.rb +13 -0
- data/spec/lib/noaa_client/xml_parser_factory_spec.rb +14 -0
- data/spec/spec_helper.rb +31 -0
- metadata +228 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative '../xml_parser_factory'
|
2
|
+
require_relative 'reactive_xml_response'
|
3
|
+
require_relative 'validatable_xml_response'
|
4
|
+
|
5
|
+
module NoaaWeatherClient
|
6
|
+
module Responses
|
7
|
+
class LatLonList
|
8
|
+
include ReactiveXmlResponse
|
9
|
+
include ValidatableXmlResponse
|
10
|
+
|
11
|
+
def initialize(response)
|
12
|
+
@source = XmlParserFactory.build_parser.parse(response)
|
13
|
+
validate! @source, :dwml
|
14
|
+
end
|
15
|
+
|
16
|
+
def latitude
|
17
|
+
@latitude ||= latLonList.split(",").first.to_f
|
18
|
+
end
|
19
|
+
|
20
|
+
def longitude
|
21
|
+
@longitude ||= latLonList.split(",").last.to_f
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module NoaaWeatherClient
|
2
|
+
module Responses
|
3
|
+
module ReactiveXmlResponse
|
4
|
+
def method_missing(method_name, *arguments, &block)
|
5
|
+
if tag = source.css(method_name.to_s)
|
6
|
+
if block
|
7
|
+
block.call tag.text
|
8
|
+
else
|
9
|
+
tag.text
|
10
|
+
end
|
11
|
+
else
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def respond_to_missing?(method_name, include_private = false)
|
17
|
+
source.css(method_name.to_s) || super
|
18
|
+
end
|
19
|
+
|
20
|
+
def source
|
21
|
+
@source || NullResponse.new
|
22
|
+
end
|
23
|
+
|
24
|
+
class NullResponse
|
25
|
+
def css(*args); end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 'reactive_xml_response'
|
2
|
+
|
3
|
+
module NoaaWeatherClient
|
4
|
+
module Responses
|
5
|
+
class Station
|
6
|
+
include ReactiveXmlResponse
|
7
|
+
|
8
|
+
def initialize(station)
|
9
|
+
@source = station
|
10
|
+
end
|
11
|
+
|
12
|
+
def latitude
|
13
|
+
source.css('latitude').text.to_f
|
14
|
+
end
|
15
|
+
|
16
|
+
def longitude
|
17
|
+
source.css('longitude').text.to_f
|
18
|
+
end
|
19
|
+
|
20
|
+
def xml_url
|
21
|
+
@xml_url ||= begin
|
22
|
+
path = URI(source.css('xml_url').text).path
|
23
|
+
"http://w1.weather.gov#{path}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative '../xml_parser_factory'
|
2
|
+
require_relative 'station'
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module NoaaWeatherClient
|
6
|
+
module Responses
|
7
|
+
class Stations
|
8
|
+
include Enumerable
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
def_delegators :stations, :'[]', :fetch, :sort_by!, :take, :size
|
12
|
+
|
13
|
+
def initialize(response, options = {})
|
14
|
+
@body = XmlParserFactory.build_parser.parse response
|
15
|
+
@options = options
|
16
|
+
end
|
17
|
+
|
18
|
+
def each
|
19
|
+
stations.each { |s| yield s }
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_xml
|
23
|
+
body.to_xml
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :body, :options
|
29
|
+
|
30
|
+
def stations
|
31
|
+
@stations ||= body.css('station').map do |station|
|
32
|
+
station_class.new station
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def station_class
|
37
|
+
options.fetch(:station_class, Station)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative '../errors'
|
2
|
+
|
3
|
+
module NoaaWeatherClient
|
4
|
+
module Responses
|
5
|
+
module ValidatableXmlResponse
|
6
|
+
SCHEMA_PATH = File.expand_path("../../../../data/xml", __FILE__)
|
7
|
+
|
8
|
+
def validate!(doc, schema_name)
|
9
|
+
# chdir to help Nokogiri load included schemas
|
10
|
+
Dir.chdir(SCHEMA_PATH) do
|
11
|
+
schema_file = File.join(SCHEMA_PATH, "#{schema_name}.xsd")
|
12
|
+
schema = Nokogiri::XML::Schema(File.read(schema_file))
|
13
|
+
errors = schema.validate(doc)
|
14
|
+
unless errors.empty?
|
15
|
+
raise Errors::InvalidXmlError, "Invalid xml: #{errors.map(&:message).join("\n")}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module NoaaWeatherClient
|
5
|
+
module RestClientFactory
|
6
|
+
def self.build_client(options = {})
|
7
|
+
provider = options.fetch(:provider, Net::HTTP)
|
8
|
+
uri = URI(options.fetch(:url))
|
9
|
+
provider.new uri.host, uri.port
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module NoaaWeatherClient
|
2
|
+
module Services
|
3
|
+
module CalculateDistanceBetweenLatLon
|
4
|
+
extend Math
|
5
|
+
|
6
|
+
def self.get_distance(lat1, lon1, lat2, lon2)
|
7
|
+
lat1, lon1, lat2, lon2 = convert_to_radian(lat1, lon1, lat2, lon2)
|
8
|
+
|
9
|
+
radius = 6371 #km
|
10
|
+
s = sin(lat1) * sin(lat2)
|
11
|
+
c = cos(lat1) * cos(lat2) * cos(lon2-lon1)
|
12
|
+
acos(s + c) * radius;
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.convert_to_radian(*args)
|
16
|
+
args.map { |arg| arg / 180 * Math::PI }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative 'rest_service'
|
2
|
+
require_relative '../responses/current_observation'
|
3
|
+
|
4
|
+
module NoaaWeatherClient
|
5
|
+
module Services
|
6
|
+
class CurrentObservations
|
7
|
+
include RestService
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def fetch(station, options = {})
|
14
|
+
rest_service.object_from_response(:get,
|
15
|
+
station.xml_url,
|
16
|
+
response_class: response_class)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :options
|
22
|
+
|
23
|
+
def rest_service
|
24
|
+
options.fetch(:rest_service, self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def response_class
|
28
|
+
options.fetch(:response_class, Responses::CurrentObservation)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative 'calculate_distance_between_lat_lon'
|
2
|
+
|
3
|
+
module NoaaWeatherClient
|
4
|
+
module Services
|
5
|
+
module FindNearestStation
|
6
|
+
def self.find(lat, lon, stations, options = {})
|
7
|
+
calc = options.fetch(:calculator, CalculateDistanceBetweenLatLon)
|
8
|
+
filter = options.fetch(:filter, nil)
|
9
|
+
count = options.fetch(:count, 1)
|
10
|
+
stations.select!(&filter) if filter
|
11
|
+
stations.sort_by! { |s| calc.get_distance(lat, lon, s.latitude, s.longitude) }
|
12
|
+
count == 1 ? stations.first : stations.take(count)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative 'soap_service'
|
2
|
+
require_relative '../responses/forecast'
|
3
|
+
|
4
|
+
module NoaaWeatherClient
|
5
|
+
module Services
|
6
|
+
class ForecastByDay
|
7
|
+
include SoapService
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def fetch(lat, lon, options = {})
|
14
|
+
fopts = build_formated_options options.merge({ latitude: lat.to_s, longitude: lon.to_s })
|
15
|
+
soap_service.object_from_response(:ndf_dgen_by_day, fopts, response_class: response_class)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :options
|
21
|
+
|
22
|
+
def soap_service
|
23
|
+
options.fetch(:soap_service, self)
|
24
|
+
end
|
25
|
+
|
26
|
+
def response_class
|
27
|
+
options.fetch(:response_class, Responses::Forecast)
|
28
|
+
end
|
29
|
+
|
30
|
+
def build_formated_options(options)
|
31
|
+
opts = options.dup
|
32
|
+
{
|
33
|
+
latitude: opts.delete(:latitude),
|
34
|
+
longitude: opts.delete(:longitude),
|
35
|
+
startDate: opts.delete(:start_date) { Date.today.to_s },
|
36
|
+
numDays: opts.delete(:days) { 7 },
|
37
|
+
unit: unit!(opts),
|
38
|
+
format: opts.delete(:format) { '24 hourly' }
|
39
|
+
}.merge!(opts)
|
40
|
+
end
|
41
|
+
|
42
|
+
def unit!(options)
|
43
|
+
u = options.delete(:unit) { :standard }
|
44
|
+
if u == :standard
|
45
|
+
'e'
|
46
|
+
elsif u == :metric
|
47
|
+
'm'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative 'rest_service'
|
2
|
+
require_relative '../responses/lat_lon_list'
|
3
|
+
|
4
|
+
module NoaaWeatherClient
|
5
|
+
module Services
|
6
|
+
class PostalCodeToCoordinate
|
7
|
+
include RestService
|
8
|
+
|
9
|
+
URL = "http://graphical.weather.gov/xml/sample_products/browser_interface/ndfdXMLclient.php?listZipCodeList="
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
def resolve(zip, options = {})
|
16
|
+
rest_service.object_from_response(:get, build_url(zip), response_class: response_class)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :options
|
22
|
+
|
23
|
+
def rest_service
|
24
|
+
options.fetch(:rest_service, self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def response_class
|
28
|
+
options.fetch(:response_class, Responses::LatLonList)
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_url(zip)
|
32
|
+
"#{URL}#{zip}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require_relative '../rest_client_factory'
|
3
|
+
require_relative '../responses/generic_response'
|
4
|
+
require_relative '../errors'
|
5
|
+
|
6
|
+
module NoaaWeatherClient
|
7
|
+
module RestService
|
8
|
+
def object_from_response(action, url, options = {})
|
9
|
+
response_class = options.fetch(:response_class, Responses::GenericResponse)
|
10
|
+
client = options.fetch(:client) { RestClientFactory.build_client(url: url) }
|
11
|
+
request = build_request_for_action action, url, options
|
12
|
+
response_class.new client.request(request).body
|
13
|
+
rescue Net::HTTPError,
|
14
|
+
Net::HTTPRetriableError,
|
15
|
+
Net::HTTPServerException,
|
16
|
+
Net::HTTPFatalError => e
|
17
|
+
raise Errors::CommunicationError, e.message, e.backtrace
|
18
|
+
end
|
19
|
+
|
20
|
+
# @note Much more to be done here when needed.
|
21
|
+
def build_request_for_action(action, url, options = {})
|
22
|
+
r_class = case action
|
23
|
+
when :get then Net::HTTP::Get
|
24
|
+
end
|
25
|
+
r_class.new url
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'savon'
|
2
|
+
require_relative '../soap_client_factory'
|
3
|
+
require_relative '../responses/generic_response'
|
4
|
+
require_relative '../errors'
|
5
|
+
|
6
|
+
module NoaaWeatherClient
|
7
|
+
module SoapService
|
8
|
+
def object_from_response(soap_action, message, options = {})
|
9
|
+
client = options.fetch(:client, SoapClientFactory.build_client)
|
10
|
+
response_class = options.fetch(:response_class, Responses::GenericResponse)
|
11
|
+
response_class.new client.call(soap_action, message: message).body
|
12
|
+
rescue Savon::Error => e
|
13
|
+
raise Errors::CommunicationError, e.message, e.backtrace
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative 'rest_service'
|
2
|
+
require_relative '../responses/stations'
|
3
|
+
|
4
|
+
module NoaaWeatherClient
|
5
|
+
module Services
|
6
|
+
class WeatherStations
|
7
|
+
include RestService
|
8
|
+
|
9
|
+
URL = 'http://w1.weather.gov/xml/current_obs/index.xml'
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch(options = {})
|
16
|
+
rest_service.object_from_response(:get, URL, response_class: response_class)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :options
|
22
|
+
|
23
|
+
def rest_service
|
24
|
+
options.fetch(:rest_service, self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def response_class
|
28
|
+
options.fetch(:response_class, Responses::Stations)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'savon'
|
2
|
+
|
3
|
+
module NoaaWeatherClient
|
4
|
+
module SoapClientFactory
|
5
|
+
WSDL = 'http://graphical.weather.gov/xml/DWMLgen/wsdl/ndfdXML.wsdl'
|
6
|
+
|
7
|
+
def self.build_client(options = {})
|
8
|
+
provider = options.fetch(:provider, Savon)
|
9
|
+
wsdl = options.fetch(:wsdl, WSDL)
|
10
|
+
provider.client(wsdl: wsdl,
|
11
|
+
log: false,
|
12
|
+
open_timeout: 10,
|
13
|
+
read_timeout: 30,
|
14
|
+
ssl_verify_mode: :none)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|