lib/barometer/weather_services/weather_bug.rb in barometer-0.8.0 vs lib/barometer/weather_services/weather_bug.rb in barometer-0.9.0

- old
+ new

@@ -1,297 +1,41 @@ -require 'crack' +require_relative 'weather_bug/current_api' +require_relative 'weather_bug/current_response' +require_relative 'weather_bug/forecast_api' +require_relative 'weather_bug/forecast_response' module Barometer - # - # = WeatherBug - # www.weatherbug.com - # - # - key required: YES (api_code) - # - registration required: YES - # - supported countries: US (by zipcode), International (by coordinates) - # - # === performs geo coding - # - city: YES - # - coordinates: PARTIAL (just for weather station) - # - # === time info - # - sun rise/set: YES - # - provides timezone: NO, but provides a timezone short code and utc offset - # - requires TZInfo: NO - # - # == resources - # - API: http://weather.weatherbug.com/corporate/products/API/help.aspx - # - # === Possible queries: - # - http://[API_Code].api.wxbug.net:80/getLiveWeatherRSS.aspx?ACode=[API_Code]&OutputType=1&UnitType=1&zipCode=90210 - # - # where query can be: - # - zipcode (US) [5 digits only] - # - coordinates (International) - # - # = WeatherBug.com terms of use - # ??? - # - # == notes - # - WeatherBug also supports queries using "citycode" and "stationID", but these - # are specific to WeatherBug and un-supported by Barometer - # - class WeatherService::WeatherBug < WeatherService - - @@api_code = nil - - def self.keys=(keys) - raise ArgumentError unless keys.is_a?(Hash) - keys.each do |key, value| - @@api_code = value.to_s if key.to_s.downcase == "code" + module WeatherService + class WeatherBug + def self.call(query, config={}) + WeatherBug.new(query, config).measure! end - end - ######################################################################### - # PRIVATE - # If class methods could be private, the remaining methods would be. - # - - def self._source_name; :weather_bug; end - def self._accepted_formats; [:short_zipcode, :coordinates]; end - - def self._has_keys?; !@@api_code.nil?; end - def self._requires_keys?; true; end - - def self._wet_icon_codes - codes = [5,6,8,9,11,12,14,15] + (18..22).to_a + [25] + (27..30).to_a + - [32,36] + (38..49).to_a + (52..63).to_a + (80..157).to_a + - (161..176).to_a - codes.collect {|c| c.to_s} - end - def self._sunny_icon_codes - codes = [0,2,3,4,7,26,31,64,65,75] - codes.collect {|c| c.to_s} - end - - def self._build_extra(measurement, result, metric=true) - #raise ArgumentError unless measurement.is_a?(Data::Measurement) - #raise ArgumentError unless query.is_a?(Barometer::Query) - - # use todays sun data for all future days - if measurement.forecast && measurement.current.sun - measurement.forecast.each do |forecast| - forecast.sun = measurement.current.sun - end + def initialize(query, config={}) + @query = query + @api_code = config[:keys][:code] if config[:keys] end - measurement - end + def measure! + validate_key! - def self._parse_local_time(data) - Data::LocalTime.new( - data["aws:ob_date"]["aws:hour"]["hour_24"].to_i, - data["aws:ob_date"]["aws:minute"]["number"].to_i, - data["aws:ob_date"]["aws:second"]["number"].to_i - ) if data && data["aws:ob_date"] - end + current_weather_api = CurrentApi.new(query, api_code) + response = CurrentResponse.new.parse(current_weather_api.get) - def self._build_timezone(data) - if data && data["aws:ob_date"] && data["aws:ob_date"]["aws:time_zone"] - Data::Zone.new(data["aws:ob_date"]["aws:time_zone"]["abbrv"]) + forecast_weather_api = ForecastApi.new(current_weather_api.query, api_code) + ForecastResponse.new(response).parse(forecast_weather_api.get) end - end - def self._build_current(data, metric=true) - raise ArgumentError unless data.is_a?(Hash) + private - current = Measurement::Result.new - # current.updated_at = Data::LocalDateTime.parse(data['observation_time']) if data['observation_time'] - current.humidity = data['aws:humidity'].to_i - current.condition = data['aws:current_condition'] if data['aws:current_condition'] - current.icon = data['aws:icon'].to_i.to_s if data['aws:icon'] + attr_reader :query, :api_code - current.temperature = Data::Temperature.new(metric) - current.temperature << data['aws:temp'] - - current.wind = Data::Speed.new(metric) - current.wind << data['aws:wind_speed'].to_f - current.wind.direction = data['aws:wind_direction'] - - current.pressure = Data::Pressure.new(metric) - current.pressure << data['aws:pressure'] - - current.dew_point = Data::Temperature.new(metric) - current.dew_point << data['aws:dew_point'] - - current.wind_chill = Data::Temperature.new(metric) - current.wind_chill << data['aws:feels_like'] - - current - end - - def self._build_forecast(data, metric=true) - raise ArgumentError unless data.is_a?(Hash) - forecasts = Measurement::ResultArray.new - # go through each forecast and create an instance - if data && data["forecast"] - start_date = Date.strptime(data['date'], "%m/%d/%Y %H:%M:%S %p") - i = 0 - data["forecast"].each do |forecast| - forecast_measurement = Measurement::Result.new - icon_match = forecast['image']['icon'].match(/cond(\d*)\.gif$/) - forecast_measurement.icon = icon_match[1].to_i.to_s if icon_match - forecast_measurement.date = start_date + i - forecast_measurement.condition = forecast['short_prediction'] - - forecast_measurement.high = Data::Temperature.new(metric) - forecast_measurement.high << forecast['high']['__content__'] - - forecast_measurement.low = Data::Temperature.new(metric) - forecast_measurement.low << forecast['low']['__content__'] - - forecasts << forecast_measurement - i += 1 + def validate_key! + unless api_code && !api_code.empty? + raise KeyRequired end end - forecasts end - - def self._build_location(data, geo=nil) - raise ArgumentError unless data.is_a?(Hash) - raise ArgumentError unless (geo.nil? || geo.is_a?(Data::Geo)) - location = Data::Location.new - # use the geocoded data if available, otherwise get data from result - if geo - location.city = geo.locality - location.state_code = geo.region - location.country = geo.country - location.country_code = geo.country_code - location.latitude = geo.latitude - location.longitude = geo.longitude - else - if data && data['location'] - location.city = data['location']['city'] - location.state_code = data['location']['state'] - location.zip_code = data['location']['zip'] - end - end - location - end - - def self._build_station(data) - raise ArgumentError unless data.is_a?(Hash) - station = Data::Location.new - station.id = data['aws:station_id'] - station.name = data['aws:station'] - station.city = data['aws:city_state'].split(',')[0].strip - station.state_code = data['aws:city_state'].split(',')[1].strip - station.country = data['aws:country'] - station.zip_code = data['aws:station_zipcode'] - station.latitude = data['aws:latitude'] - station.longitude = data['aws:longitude'] - station - end - - def self._build_sun(data) - raise ArgumentError unless data.is_a?(Hash) - sun = nil - if data - if data['aws:sunrise'] - rise = Data::LocalTime.new( - data['aws:sunrise']['aws:hour']['hour_24'].to_i, - data['aws:sunrise']['aws:minute']['number'].to_i, - data['aws:sunrise']['aws:second']['number'].to_i - ) - end - if data['aws:sunset'] - set = Data::LocalTime.new( - data['aws:sunset']['aws:hour']['hour_24'].to_i, - data['aws:sunset']['aws:minute']['number'].to_i, - data['aws:sunset']['aws:second']['number'].to_i - ) - sun = Data::Sun.new(rise,set) - end - end - sun || Data::Sun.new - end - - # override default _fetch behavior - # this service requires TWO seperate http requests (one for current - # and one for forecasted weather) ... combine the results - # - def self._fetch(query, metric=true) - result = [] - result << _fetch_current(query,metric) - result << _fetch_forecast(query,metric) - result - end - - # use HTTParty to get the current weather - # - def self._fetch_current(query, metric=true) - puts "fetch weatherbug current: #{query.q}" if Barometer::debug? - - q = ( query.format.to_sym == :short_zipcode ? - { :zipCode => query.q } : - { :lat => query.q.split(',')[0], :long => query.q.split(',')[1] }) - - # httparty and the xml builder it uses miss some information - # 1st - get the raw response - # 2nd - manually get the missing information - # 3rd - let httparty build xml as normal - # - response = self.get( - "http://#{@@api_code}.api.wxbug.net/getLiveWeatherRSS.aspx", - :query => { :ACode => @@api_code, - :OutputType => "1", :UnitType => (metric ? '1' : '0') - }.merge(q), - :format => :plain, - :timeout => Barometer.timeout - ) - - # get icon - icon_match = response.match(/cond(\d*)\.gif/) - icon = icon_match[1] if icon_match - - # get station zipcode - zip_match = response.match(/zipcode=\"(\d*)\"/) - zipcode = zip_match[1] if zip_match - - # build xml - output = ::Crack::XML.parse(response) - output = output["aws:weather"]["aws:ob"] - - # add missing data - output["aws:icon"] = icon - output["aws:station_zipcode"] = zipcode - - output - end - - # use HTTParty to get the current weather - # - def self._fetch_forecast(query, metric=true) - puts "fetch weatherbug forecast: #{query.q}" if Barometer::debug? - - q = ( query.format.to_sym == :short_zipcode ? - { :zipCode => query.q } : - { :lat => query.q.split(',')[0], :long => query.q.split(',')[1] }) - - self.get( - "http://#{@@api_code}.api.wxbug.net/getForecastRSS.aspx", - :query => { :ACode => @@api_code, - :OutputType => "1", :UnitType => (metric ? '1' : '0') - }.merge(q), - :format => :xml, - :timeout => Barometer.timeout - )["weather"]["forecasts"] - end - - # since we have two sets of data, override these calls to choose the - # right set of data - # - def self._current_result(data); data[0]; end - def self._forecast_result(data=nil); data[1]; end - def self._location_result(data=nil); data[1]; end - def self._station_result(data=nil); data[0]; end - def self._sun_result(data=nil); data[0]; end - def self._timezone_result(data=nil); data[0]; end - def self._time_result(data=nil); data[0]; end - end end + +Barometer::WeatherService.register(:weather_bug, Barometer::WeatherService::WeatherBug)