weatherbug 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.
data/lib/weatherbug.rb ADDED
@@ -0,0 +1,117 @@
1
+
2
+ require 'rubygems'
3
+ require 'nokogiri'
4
+ require 'open-uri'
5
+
6
+ module WeatherBug
7
+
8
+ require 'weatherbug/hash_methods'
9
+ require 'weatherbug/transformable_data'
10
+ autoload :Station, 'weatherbug/station'
11
+ autoload :StationHint, 'weatherbug/station_hint'
12
+ autoload :LiveObservation, 'weatherbug/live_observation'
13
+ autoload :Forecast, 'weatherbug/forecast'
14
+
15
+ API_URL = 'datafeed.weatherbug.com'
16
+
17
+
18
+ # Set your application's partner ID
19
+ def self.partner_id=(partner_id)
20
+ @partner_id = partner_id
21
+ end
22
+
23
+ # Get the 25 closest stations, or less in less dense areas
24
+ def self.nearby_stations(lookup_options)
25
+ HashMethods.valid_keys(lookup_options, [:zip_code, :city_code, :postal_code, :include_pws])
26
+ HashMethods.valid_one_only(lookup_options, [:zip_code, :city_code])
27
+ HashMethods.valid_one_only(lookup_options, [:postal_code, :city_code])
28
+ HashMethods.valid_needs(lookup_options, :zip_code, :postal_code) if lookup_options.has_key?(:postal_code)
29
+ response = make_request('StationList', HashMethods.convert_symbols(lookup_options))
30
+
31
+ response.xpath('/aws:weather/aws:station').map { |station| WeatherBug::Station.from_document(station) }
32
+ end
33
+
34
+ def self.closest_station(lookup_options)
35
+ HashMethods.valid_keys(lookup_options, [:zip_code, :city_code, :postal_code, :include_pws])
36
+ HashMethods.valid_one_only(lookup_options, [:zip_code, :city_code])
37
+ HashMethods.valid_one_only(lookup_options, [:postal_code, :city_code])
38
+ HashMethods.valid_needs(lookup_options, :zip_code, :postal_code) if lookup_options.has_key?(:postal_code)
39
+ params = HashMethods.convert_symbols(lookup_options)
40
+ params['ListType'] = '1'
41
+ response = make_request('StationList', params)
42
+
43
+ WeatherBug::Station.from_document response.xpath('/aws:weather/aws:station').first
44
+ end
45
+
46
+ def self.get_station(station_id)
47
+ params = {'StationId' => station_id}
48
+ response = make_request('StationInfo', params)
49
+
50
+ WeatherBug::Station.from_document response.xpath('/aws:weather/aws:station').first
51
+ end
52
+
53
+ def self.live_observation(station, unit_type = :f)
54
+ station_id = station.is_a?(WeatherBug::Station) ? station.station_id : station
55
+ params = {'StationId' => station_id}
56
+ params['UnitType'] = '1' if unit_type == :c
57
+ response = make_request('LiveObservations', params)
58
+
59
+ live_observation = WeatherBug::LiveObservation.from_document response.xpath('/aws:weather/aws:ob').first
60
+ live_observation.send(:station_reference=, station) if station.is_a?(WeatherBug::Station)
61
+ live_observation
62
+ end
63
+
64
+ # Get the list of stations in a bounding box
65
+ # Does not return full stations
66
+ def self.stations_in_box(top_right_lat, top_right_lng, bot_left_lat, bot_left_lng)
67
+ params = {
68
+ 'LatitudeTopRight' => top_right_lat,
69
+ 'LongitudeTopRight' => top_right_lng,
70
+ 'LatitudeBottomLeft' => bot_left_lat,
71
+ 'LongitudeBottomLeft' => bot_left_lng
72
+ }
73
+ response = make_request('StationListByLatLng', params)
74
+
75
+ station_data = response.xpath('/aws:weather/aws:stations/aws:station')
76
+ return nil unless station_data
77
+
78
+ station_data.map do |sdata|
79
+ WeatherBug::StationHint.from_document(sdata)
80
+ end
81
+ end
82
+
83
+ def self.forecast(options)
84
+ self.retrieve_forecast(1, options)
85
+ end
86
+
87
+ private
88
+
89
+ # TODO add validation for fields
90
+ def self.retrieve_forecast(forecast_type, options)
91
+ HashMethods.valid_keys(options, [:latitude, :longitude])
92
+ params = HashMethods.convert_symbols(options)
93
+ params['ForecastType'] = forecast_type
94
+ response = make_request(options.has_key?(:longitude) ? 'ClosestForecastByLatLng' : 'Forecast', params)
95
+
96
+ forecast_data = response.xpath('/aws:weather/aws:forecasts/aws:forecast')
97
+ return nil unless forecast_data
98
+
99
+ forecast_data.map do |fdata|
100
+ WeatherBug::Forecast.from_document(fdata)
101
+ end
102
+ end
103
+
104
+ # Make the actual request, unwrap it and send it back
105
+ # Handles errors that may come up
106
+ def self.make_request(method, params)
107
+ params['RequestType'] = method
108
+ params['PartnerId'] = @partner_id
109
+ data = open("http://#{API_URL}/GetXml.aspx?#{params.map { |k, v| "#{k}=#{v}" }.join('&')}")
110
+ raise Exception.new("Unexpected exception - empty response") unless data
111
+ Nokogiri::XML(data)
112
+ end
113
+
114
+ end
115
+
116
+ WeatherBug.partner_id = 'f0c313fe-fffe-4f5d-96dc-5a5d6ec9eb30'
117
+ stations = WeatherBug::nearby_stations(:zip_code => '08005')
@@ -0,0 +1,18 @@
1
+ module WeatherBug
2
+
3
+ class Forecast < TransformableData
4
+
5
+ register_transformer 'aws:title', :required => true
6
+ register_transformer 'aws:city'
7
+ register_transformer 'aws:short-title'
8
+ register_transformer 'aws:icon'
9
+ register_transformer 'aws:description'
10
+ register_transformer 'aws:prediction'
11
+ register_transformer 'aws:lo', :name => :temp_low, :transform => :to_i
12
+ register_transformer 'aws:hi', :name => :temp_high, :transform => :to_i
13
+ register_transformer 'aws:hi/@units', :name => :temp_units
14
+ register_transformer 'aws:prob-of-precip', :name => :probability_of_precipitation
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,40 @@
1
+ module WeatherBug
2
+
3
+ class HashMethods
4
+
5
+ WB_MAPPING = {
6
+ :zip_code => 'ZipCode',
7
+ :city_code => 'CityCode',
8
+ :postal_code => 'PostalCode',
9
+ :include_pws => 'IncludePWS',
10
+ :latitude => 'Latitude',
11
+ :longitude => 'Longitude'
12
+ }
13
+
14
+ # Raise an argument error if we get invalid keys at all
15
+ def self.valid_keys(hash, allowed)
16
+ invalids = nil
17
+ hash.keys.each { |key| (invalids ||= []; invalids << key) unless allowed.include?(key) }
18
+ raise ArgumentError.new("Invalid options: #{invalids.join(', ')}") unless invalids.nil?
19
+ end
20
+
21
+ def self.valid_one_only(hash, conflictions)
22
+ conflicts = []
23
+ conflictions.each { |c| conflicts << c if hash.keys.include?(c) }
24
+ raise ArgumentError.new("Cannot combine options: #{conflicts.join(', ')}") if conflicts.size > 1
25
+ end
26
+
27
+ def self.valid_needs(hash, key, key_for)
28
+ raise ArgumentError.new("You must supply option #{key} to use #{key_for}") unless hash.has_key?(key)
29
+ end
30
+
31
+ def self.convert_symbols(hash)
32
+ hash.inject({}) do |rhash, (original_key, value)|
33
+ rhash[WB_MAPPING.fetch(original_key, original_key)] = value
34
+ rhash
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,34 @@
1
+ module WeatherBug
2
+
3
+ class LiveObservation < TransformableData
4
+
5
+ register_transformer 'aws:station-id'
6
+ register_transformer 'aws:station', :name => :station_name
7
+ register_transformer 'aws:temp-high/@units', :name => :temp_units
8
+ register_transformer 'aws:temp-high', :name => :temp_high, :transform => :to_i
9
+ register_transformer 'aws:temp-low', :name => :temp_low, :transform => :to_i
10
+ register_transformer 'aws:dew-point', :transform => :to_i
11
+ register_transformer 'aws:elevation', :transform => :to_i
12
+ register_transformer 'aws:feels-like', :transform => :to_i
13
+ register_transformer 'aws:humidity', :transform => :to_i
14
+ register_transformer 'aws:wind-direction'
15
+ register_transformer 'aws:wind-speed', :transform => :to_i
16
+ register_transformer 'aws:pressure', :transform => :to_f
17
+
18
+ def self.from_document(document)
19
+ lops = super
20
+ raise ArgumentError.new('No such station') if lops.station_id.empty?
21
+ lops
22
+ end
23
+
24
+ def station
25
+ @station_reference ||= WeatherBug.get_station(self.station_id)
26
+ end
27
+
28
+ private
29
+
30
+ attr_writer :station_reference
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,33 @@
1
+ module WeatherBug
2
+
3
+ class Station < TransformableData
4
+
5
+ register_transformer '@id', :name => :station_id
6
+ register_transformer '@name'
7
+ register_transformer '@distance', :transform => :to_f
8
+ register_transformer '@zipcode', :name => :zip_code
9
+ register_transformer '@city-code', :transform => :to_i
10
+ register_transformer '@station-type'
11
+ register_transformer '@city'
12
+ register_transformer '@state'
13
+ register_transformer '@country'
14
+ register_transformer '@latitude', :transform => :to_f
15
+ register_transformer '@longitude', :transform => :to_f
16
+
17
+ def self.from_document(document)
18
+ station = super
19
+ raise ArgumentError.new('No such station') if station.station_id.empty?
20
+ station
21
+ end
22
+
23
+ def live_observation(unit = :f)
24
+ WeatherBug.live_observation(self, unit)
25
+ end
26
+
27
+ def forecast
28
+ WeatherBug.forecast(:latitude => latitude, :longitude => longitude)
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,14 @@
1
+ module WeatherBug
2
+
3
+ class StationHint < TransformableData
4
+
5
+ register_transformer '@id', :name => :station_id
6
+ register_transformer '@name'
7
+
8
+ def station
9
+ @station ||= WeatherBug.get_station(station_id)
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,31 @@
1
+ module WeatherBug
2
+
3
+ # This is a super-convenient class for transorming data and requiring things in the
4
+ # different associated WeatherBug data classes
5
+ class TransformableData
6
+
7
+ def self.register_transformer(name, options = {})
8
+ @transformers ||= {}
9
+ options[:name] = name.split(/[:@]/).last.gsub('-', '_').to_sym unless options.has_key?(:name)
10
+ private; attr_writer options[:name]
11
+ public; attr_reader options[:name]
12
+ @transformers[name] = options
13
+ end
14
+
15
+ def self.from_document(document)
16
+ object = self.new
17
+ @transformers.each do |key, options|
18
+ value = document.xpath(key).text
19
+ # If this thing is empty, skip it
20
+ next if value.nil?
21
+ # If this thing needs to be transformed, transform it (from a string)
22
+ value = value.send(options[:transform]) if options.has_key?(:transform)
23
+ # Assign it on the class
24
+ object.send("#{options[:name]}=", value)
25
+ end
26
+ object
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,9 @@
1
+ module WeatherBug
2
+
3
+ VERSION = [0, 0, 1]
4
+
5
+ def self.version
6
+ VERSION.join('.')
7
+ end
8
+
9
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: weatherbug
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - John Crepezzi
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-19 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ description: Light Wrapper for the WeatherBug partner API
36
+ email: john.crepezzi@patch.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - lib/weatherbug/forecast.rb
45
+ - lib/weatherbug/hash_methods.rb
46
+ - lib/weatherbug/live_observation.rb
47
+ - lib/weatherbug/station.rb
48
+ - lib/weatherbug/station_hint.rb
49
+ - lib/weatherbug/transformable_data.rb
50
+ - lib/weatherbug/version.rb
51
+ - lib/weatherbug.rb
52
+ has_rdoc: true
53
+ homepage: http://github.com/seejohnrun/weatherbug
54
+ licenses: []
55
+
56
+ post_install_message:
57
+ rdoc_options: []
58
+
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ hash: 3
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ requirements: []
80
+
81
+ rubyforge_project: weatherbug
82
+ rubygems_version: 1.3.7
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: Ruby WeatherBug partner API
86
+ test_files: []
87
+