attack-barometer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +266 -0
  3. data/VERSION.yml +4 -0
  4. data/bin/barometer +63 -0
  5. data/lib/barometer/base.rb +52 -0
  6. data/lib/barometer/data/current.rb +93 -0
  7. data/lib/barometer/data/distance.rb +131 -0
  8. data/lib/barometer/data/forecast.rb +66 -0
  9. data/lib/barometer/data/geo.rb +98 -0
  10. data/lib/barometer/data/location.rb +20 -0
  11. data/lib/barometer/data/measurement.rb +161 -0
  12. data/lib/barometer/data/pressure.rb +133 -0
  13. data/lib/barometer/data/speed.rb +147 -0
  14. data/lib/barometer/data/sun.rb +35 -0
  15. data/lib/barometer/data/temperature.rb +164 -0
  16. data/lib/barometer/data/units.rb +55 -0
  17. data/lib/barometer/data/zone.rb +124 -0
  18. data/lib/barometer/data.rb +15 -0
  19. data/lib/barometer/extensions/graticule.rb +50 -0
  20. data/lib/barometer/extensions/httparty.rb +21 -0
  21. data/lib/barometer/query.rb +228 -0
  22. data/lib/barometer/services/google.rb +146 -0
  23. data/lib/barometer/services/noaa.rb +6 -0
  24. data/lib/barometer/services/service.rb +324 -0
  25. data/lib/barometer/services/weather_bug.rb +6 -0
  26. data/lib/barometer/services/weather_dot_com.rb +6 -0
  27. data/lib/barometer/services/wunderground.rb +285 -0
  28. data/lib/barometer/services/yahoo.rb +274 -0
  29. data/lib/barometer/services.rb +6 -0
  30. data/lib/barometer/weather.rb +187 -0
  31. data/lib/barometer.rb +52 -0
  32. data/spec/barometer_spec.rb +162 -0
  33. data/spec/data_current_spec.rb +225 -0
  34. data/spec/data_distance_spec.rb +336 -0
  35. data/spec/data_forecast_spec.rb +150 -0
  36. data/spec/data_geo_spec.rb +90 -0
  37. data/spec/data_location_spec.rb +59 -0
  38. data/spec/data_measurement_spec.rb +411 -0
  39. data/spec/data_pressure_spec.rb +336 -0
  40. data/spec/data_speed_spec.rb +374 -0
  41. data/spec/data_sun_spec.rb +76 -0
  42. data/spec/data_temperature_spec.rb +396 -0
  43. data/spec/data_zone_spec.rb +133 -0
  44. data/spec/fixtures/current_calgary_ab.xml +1 -0
  45. data/spec/fixtures/forecast_calgary_ab.xml +1 -0
  46. data/spec/fixtures/geocode_40_73.xml +1 -0
  47. data/spec/fixtures/geocode_90210.xml +1 -0
  48. data/spec/fixtures/geocode_T5B4M9.xml +1 -0
  49. data/spec/fixtures/geocode_calgary_ab.xml +1 -0
  50. data/spec/fixtures/geocode_newyork_ny.xml +1 -0
  51. data/spec/fixtures/google_calgary_ab.xml +1 -0
  52. data/spec/fixtures/yahoo_90210.xml +1 -0
  53. data/spec/query_spec.rb +469 -0
  54. data/spec/service_google_spec.rb +144 -0
  55. data/spec/service_wunderground_spec.rb +330 -0
  56. data/spec/service_yahoo_spec.rb +299 -0
  57. data/spec/services_spec.rb +1106 -0
  58. data/spec/spec_helper.rb +14 -0
  59. data/spec/units_spec.rb +101 -0
  60. data/spec/weather_spec.rb +265 -0
  61. metadata +119 -0
@@ -0,0 +1,285 @@
1
+ module Barometer
2
+ #
3
+ # = Weather Underground
4
+ # www.wunderground.com
5
+ #
6
+ # - key required: NO
7
+ # - registration required: NO
8
+ # - supported countries: ALL
9
+ #
10
+ # === performs geo coding
11
+ # - city: YES
12
+ # - coordinates: YES
13
+ # NOTE: provides geo data for location and weather station
14
+ #
15
+ # === time info
16
+ # - sun rise/set: YES (today only)
17
+ # - provides timezone: YES
18
+ # - requires TZInfo: YES
19
+ #
20
+ # == resources
21
+ # - API: http://wiki.wunderground.com/index.php/API_-_XML
22
+ #
23
+ # === Possible queries:
24
+ # - http://api.wunderground.com/auto/wui/geo/GeoLookupXML/index.xml?query=94107
25
+ # - http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=KSFO
26
+ # - http://api.wunderground.com/weatherstation/WXCurrentObXML.asp?ID=KCASANFR70
27
+ # - http://api.wunderground.com/auto/wui/geo/AlertsXML/index.xml?query=86445
28
+ # - http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml?query=Chicago,IL
29
+ #
30
+ # where query can be:
31
+ # - zipcode (US or Canadian)
32
+ # - city state; city, state
33
+ # - city
34
+ # - state
35
+ # - country
36
+ # - airport code (3-letter or 4-letter)
37
+ # - lat,lon
38
+ #
39
+ class Wunderground < Service
40
+
41
+ def self.accepted_formats
42
+ [:zipcode, :postalcode, :coordinates, :geocode]
43
+ end
44
+
45
+ def self.source_name
46
+ :wunderground
47
+ end
48
+
49
+ # these are the icon codes that indicate "wet", used by wet? function
50
+ def self.wet_icon_codes
51
+ %w(flurries rain sleet snow tstorms nt_flurries nt_rain nt_sleet nt_snow nt_tstorms)
52
+ end
53
+ def self.sunny_icon_codes
54
+ %w(clear mostlysunny partlysunny sunny partlycloudy)
55
+ end
56
+
57
+ def self._measure(measurement, query, metric=true)
58
+ raise ArgumentError unless measurement.is_a?(Barometer::Measurement)
59
+ raise ArgumentError unless query.is_a?(Barometer::Query)
60
+ measurement.source = self.source_name
61
+
62
+ # get current measurement
63
+ begin
64
+ current_result = self.get_current(query.preferred)
65
+ measurement.current = self.build_current(current_result, metric)
66
+ rescue Timeout::Error => e
67
+ return measurement
68
+ end
69
+
70
+ # get forecast measurement
71
+ begin
72
+ forecast_result = self.get_forecast(query.preferred)
73
+ measurement.forecast = self.build_forecast(forecast_result, metric)
74
+ rescue Timeout::Error => e
75
+ return measurement
76
+ end
77
+
78
+ measurement.location = self.build_location(current_result)
79
+ measurement.station = self.build_station(current_result)
80
+ measurement.timezone = self.build_timezone(forecast_result)
81
+
82
+ # add sun data to current
83
+ sun = nil
84
+ if measurement.current
85
+ sun = self.build_sun(forecast_result, measurement.timezone)
86
+ measurement.current.sun = sun
87
+ end
88
+ # use todays sun data for all future days
89
+ if measurement.forecast && sun
90
+ start_date = Date.parse(measurement.current.time)
91
+ measurement.forecast.each do |forecast|
92
+ days_in_future = forecast.date - start_date
93
+ forecast.sun = Barometer::Sun.add_days!(sun,days_in_future.to_i)
94
+ end
95
+ end
96
+
97
+ measurement
98
+ end
99
+
100
+ def self.build_current(data, metric=true)
101
+ raise ArgumentError unless data.is_a?(Hash)
102
+
103
+ current = CurrentMeasurement.new
104
+ #current.time = Time.parse(current_result['observation_time_rfc822']) unless
105
+ # current_result['observation_time_rfc822'].blank?
106
+ current.time = data['observation_time_rfc822']
107
+ current.local_time = data['observation_time']
108
+ current.humidity = data['relative_humidity'].to_i
109
+ current.icon = data['icon'] if data['icon']
110
+
111
+ current.temperature = Temperature.new(metric)
112
+ current.temperature << [data['temp_c'], data['temp_f']]
113
+
114
+ current.wind = Speed.new(metric)
115
+ current.wind.mph = data['wind_mph'].to_f
116
+ current.wind.degrees = data['wind_degrees'].to_i
117
+ current.wind.direction = data['wind_dir']
118
+
119
+ current.pressure = Pressure.new(metric)
120
+ current.pressure << [data['pressure_mb'], data['pressure_in']]
121
+
122
+ current.dew_point = Temperature.new(metric)
123
+ current.dew_point << [data['dewpoint_c'], data['dewpoint_f']]
124
+
125
+ current.heat_index = Temperature.new(metric)
126
+ current.heat_index << [data['heat_index_c'], data['heat_index_f']]
127
+
128
+ current.wind_chill = Temperature.new(metric)
129
+ current.wind_chill << [data['windchill_c'], data['windchill_f']]
130
+
131
+ current.visibility = Distance.new(metric)
132
+ current.visibility << [data['visibility_km'], data['visibility_mi']]
133
+
134
+ current
135
+ end
136
+
137
+ def self.build_forecast(data, metric=true)
138
+ raise ArgumentError unless data.is_a?(Hash)
139
+ forecasts = []
140
+ # go through each forecast and create an instance
141
+ if data && data['simpleforecast'] &&
142
+ data['simpleforecast']['forecastday']
143
+
144
+ data['simpleforecast']['forecastday'].each do |forecast|
145
+ forecast_measurement = ForecastMeasurement.new
146
+ forecast_measurement.icon = forecast['icon']
147
+ forecast_measurement.date = Date.parse(forecast['date']['pretty'])
148
+ forecast_measurement.pop = forecast['pop'].to_i
149
+
150
+ forecast_measurement.high = Temperature.new(metric)
151
+ forecast_measurement.high << [forecast['high']['celsius'],forecast['high']['fahrenheit']]
152
+
153
+ forecast_measurement.low = Temperature.new(metric)
154
+ forecast_measurement.low << [forecast['low']['celsius'],forecast['low']['fahrenheit']]
155
+
156
+ forecasts << forecast_measurement
157
+ end
158
+ end
159
+ forecasts
160
+ end
161
+
162
+ def self.build_location(data)
163
+ raise ArgumentError unless data.is_a?(Hash)
164
+ location = Location.new
165
+ if data['display_location']
166
+ location.name = data['display_location']['full']
167
+ location.city = data['display_location']['city']
168
+ location.state_name = data['display_location']['state_name']
169
+ location.state_code = data['display_location']['state']
170
+ location.country_code = data['display_location']['country']
171
+ location.zip_code = data['display_location']['zip']
172
+ location.latitude = data['display_location']['latitude']
173
+ location.longitude = data['display_location']['longitude']
174
+ end
175
+ location
176
+ end
177
+
178
+ def self.build_station(data)
179
+ raise ArgumentError unless data.is_a?(Hash)
180
+ station = Location.new
181
+ station.id = data['station_id']
182
+ if data['observation_location']
183
+ station.name = data['observation_location']['full']
184
+ station.city = data['observation_location']['city']
185
+ station.state_name = data['observation_location']['state_name']
186
+ station.state_code = data['observation_location']['state']
187
+ station.country_code = data['observation_location']['country']
188
+ station.zip_code = data['observation_location']['zip']
189
+ station.latitude = data['observation_location']['latitude']
190
+ station.longitude = data['observation_location']['longitude']
191
+ end
192
+ station
193
+ end
194
+
195
+ # <forecastday>
196
+ # <date>
197
+ # <pretty_short>9:00 PM CST</pretty_short>
198
+ # <pretty>9:00 PM CST on January 15, 2008</pretty>
199
+ # <isdst>0</isdst>
200
+ # <tz_short>CST</tz_short>
201
+ # <tz_long>America/Chicago</tz_long>
202
+ # </date>
203
+ # </forecastday>
204
+ def self.build_timezone(data)
205
+ raise ArgumentError unless data.is_a?(Hash)
206
+ timezone = nil
207
+ if data && data['simpleforecast'] &&
208
+ data['simpleforecast']['forecastday'] &&
209
+ data['simpleforecast']['forecastday'].first &&
210
+ data['simpleforecast']['forecastday'].first['date']
211
+ timezone = Barometer::Zone.new(
212
+ data['simpleforecast']['forecastday'].first['date']['tz_long']
213
+ )
214
+ end
215
+ timezone
216
+ end
217
+
218
+ def self.build_sun(data, timezone)
219
+ raise ArgumentError unless data.is_a?(Hash)
220
+ raise ArgumentError unless timezone.is_a?(Barometer::Zone)
221
+
222
+ sun = nil
223
+ if data
224
+ time = nil
225
+ if data['simpleforecast'] &&
226
+ data['simpleforecast']['forecastday'] &&
227
+ data['simpleforecast']['forecastday'].first &&
228
+ data['simpleforecast']['forecastday'].first['date']
229
+
230
+ # construct current date
231
+ date_data = data['simpleforecast']['forecastday'].first['date']
232
+ time = Time.local(
233
+ date_data['year'], date_data['month'], date_data['day'],
234
+ date_data['hour'], date_data['min'], date_data['sec']
235
+ )
236
+ end
237
+ if time && data['moon_phase']
238
+ # get the sun rise and set times (ie "6:32 am")
239
+ if data['moon_phase']['sunrise']
240
+ rise = Time.local(
241
+ time.year, time.month, time.day,
242
+ data['moon_phase']['sunrise']['hour'],
243
+ data['moon_phase']['sunrise']['minute']
244
+ )
245
+ end
246
+ if data['moon_phase']['sunset']
247
+ set = Time.local(
248
+ time.year, time.month, time.day,
249
+ data['moon_phase']['sunset']['hour'],
250
+ data['moon_phase']['sunset']['minute']
251
+ )
252
+ end
253
+
254
+ sun = Sun.new(
255
+ timezone.tz.local_to_utc(rise),
256
+ timezone.tz.local_to_utc(set)
257
+ )
258
+ end
259
+ end
260
+
261
+ sun || Sun.new
262
+ end
263
+
264
+ # use HTTParty to get the current weather
265
+ def self.get_current(query)
266
+ Barometer::Wunderground.get(
267
+ "http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml",
268
+ :query => {:query => query},
269
+ :format => :xml,
270
+ :timeout => Barometer.timeout
271
+ )['current_observation']
272
+ end
273
+
274
+ # use HTTParty to get the forecasted weather
275
+ def self.get_forecast(query)
276
+ Barometer::Wunderground.get(
277
+ "http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml",
278
+ :query => {:query => query},
279
+ :format => :xml,
280
+ :timeout => Barometer.timeout
281
+ )['forecast']
282
+ end
283
+
284
+ end
285
+ end
@@ -0,0 +1,274 @@
1
+ module Barometer
2
+ #
3
+ # = Yahoo Weather
4
+ # www.yahoo.com
5
+ #
6
+ # - key required: NO
7
+ # - registration required: NO
8
+ # - supported countries: US (by zipcode), International (by Yahoo Location ID)
9
+ #
10
+ # === performs geo coding
11
+ # - city: YES
12
+ # - coordinates: YES
13
+ #
14
+ # === time info
15
+ # - sun rise/set: YES (today only)
16
+ # - provides timezone: PARTIAL (just short code)
17
+ # - requires TZInfo: NO
18
+ # NOTE: since this only supports US, the short code can be used
19
+ # to convert times (until yahoo location id support is added)
20
+ #
21
+ # == resources
22
+ # - API: http://developer.yahoo.com/weather/
23
+ #
24
+ # === Possible queries:
25
+ # - http://weather.yahooapis.com/forecastrss?p=94089
26
+ # - http://weather.yahooapis.com/forecastrss?p=USCA1116
27
+ # - http://weather.yahooapis.com/forecastrss?p=FRXX0076&u=c
28
+ #
29
+ # where query can be:
30
+ # - zipcode (US)
31
+ # - Yahoo Location ID (International) - not currently supported
32
+ #
33
+ # == notes
34
+ # - the Yahoo Location ID is a propreitary number (possibly shared with weather.com)
35
+ # so this driver currently does not provide a way to get/use this number,
36
+ # therefore International support is currently missing
37
+ #
38
+ class Yahoo < Service
39
+
40
+ def self.accepted_formats
41
+ [:zipcode]
42
+ end
43
+
44
+ def self.source_name
45
+ :yahoo
46
+ end
47
+
48
+ # these are the icon codes that indicate "wet", used by wet? function
49
+ def self.wet_icon_codes
50
+ codes = [1] + (3..18).to_a + [35] + (37..43).to_a + (45..47).to_a
51
+ codes.collect {|c| c.to_s}
52
+ end
53
+ def self.sunny_icon_codes
54
+ codes = (29..34).to_a + [36]
55
+ codes.collect {|c| c.to_s}
56
+ end
57
+
58
+ # override, only currently supports US
59
+ def self.supports_country?(query=nil)
60
+ query && query.country_code && query.country_code.downcase == "us"
61
+ end
62
+
63
+ def self._measure(measurement, query, metric=true)
64
+ raise ArgumentError unless measurement.is_a?(Barometer::Measurement)
65
+ raise ArgumentError unless query.is_a?(Barometer::Query)
66
+ measurement.source = self.source_name
67
+
68
+ begin
69
+ result = self.get_all(query.preferred, metric)
70
+ rescue Timeout::Error => e
71
+ return measurement
72
+ end
73
+
74
+ measurement.current = self.build_current(result, metric)
75
+ measurement.forecast = self.build_forecast(result, metric)
76
+ measurement.location = self.build_location(result, query.geo)
77
+
78
+ # add to current
79
+ sun = nil
80
+ if measurement.current
81
+ sun = self.build_sun(result)
82
+ measurement.current.sun = sun
83
+ end
84
+ # use todays sun data for all future days
85
+ if measurement.forecast && sun
86
+ start_date = Date.parse(measurement.current.local_time)
87
+ measurement.forecast.each do |forecast|
88
+ days_in_future = forecast.date - start_date
89
+ forecast.sun = Barometer::Sun.add_days!(sun,days_in_future.to_i)
90
+ end
91
+ end
92
+
93
+ measurement
94
+ end
95
+
96
+ def self.build_current(data, metric=true)
97
+ raise ArgumentError unless data.is_a?(Hash)
98
+ current = CurrentMeasurement.new
99
+ if data
100
+ if data['item'] && data['item']['yweather:condition']
101
+ condition_result = data['item']['yweather:condition']
102
+ current.local_time = condition_result['date']
103
+ current.icon = condition_result['code']
104
+ current.condition = condition_result['text']
105
+ current.temperature = Temperature.new(metric)
106
+ current.temperature << condition_result['temp']
107
+ end
108
+ if data['yweather:atmosphere']
109
+ atmosphere_result = data['yweather:atmosphere']
110
+ current.humidity = atmosphere_result['humidity'].to_i
111
+ current.pressure = Pressure.new(metric)
112
+ current.pressure << atmosphere_result['pressure']
113
+ current.visibility = Distance.new(metric)
114
+ current.visibility << atmosphere_result['visibility']
115
+ end
116
+ if data['yweather:wind']
117
+ wind_result = data['yweather:wind']
118
+ current.wind = Speed.new(metric)
119
+ current.wind << wind_result['speed']
120
+ current.wind.degrees = wind_result['degrees'].to_f
121
+ current.wind_chill = Temperature.new(metric)
122
+ current.wind_chill << wind_result['chill']
123
+ end
124
+ end
125
+ current
126
+ end
127
+
128
+ def self.build_forecast(data, metric=true)
129
+ raise ArgumentError unless data.is_a?(Hash)
130
+ forecasts = []
131
+
132
+ if data && data['item'] && data['item']['yweather:forecast']
133
+ forecast_result = data['item']['yweather:forecast']
134
+
135
+ forecast_result.each do |forecast|
136
+ forecast_measurement = ForecastMeasurement.new
137
+ forecast_measurement.icon = forecast['code']
138
+ forecast_measurement.date = Date.parse(forecast['date'])
139
+ forecast_measurement.condition = forecast['text']
140
+ forecast_measurement.high = Temperature.new(metric)
141
+ forecast_measurement.high << forecast['high'].to_f
142
+ forecast_measurement.low = Temperature.new(metric)
143
+ forecast_measurement.low << forecast['low'].to_f
144
+ forecasts << forecast_measurement
145
+ end
146
+ end
147
+ forecasts
148
+ end
149
+
150
+ def self.build_location(data, geo=nil)
151
+ raise ArgumentError unless data.is_a?(Hash)
152
+ raise ArgumentError unless (geo.nil? || geo.is_a?(Barometer::Geo))
153
+ location = Location.new
154
+ # use the geocoded data if available, otherwise get data from result
155
+ if geo
156
+ location.city = geo.locality
157
+ location.state_code = geo.region
158
+ location.country = geo.country
159
+ location.country_code = geo.country_code
160
+ location.latitude = geo.latitude
161
+ location.longitude = geo.longitude
162
+ else
163
+ if data && data['yweather:location']
164
+ location.city = data['yweather:location']['city']
165
+ location.state_code = data['yweather:location']['region']
166
+ location.country_code = data['yweather:location']['country']
167
+ if data['item']
168
+ location.latitude = data['item']['geo:lat']
169
+ location.longitude = data['item']['geo:long']
170
+ end
171
+ end
172
+ end
173
+ location
174
+ end
175
+
176
+ def self.build_sun(data)
177
+ raise ArgumentError unless data.is_a?(Hash)
178
+ sun = nil
179
+ if data && data['yweather:astronomy'] && data['item']
180
+ # get the TIME ZONE CODE
181
+ zone_match = data['item']['pubDate'].match(/ ([A-Z]*)$/)
182
+ zone = zone_match[1] if zone_match
183
+ # get the sun rise and set
184
+ rise = Barometer::Zone.merge(
185
+ data['yweather:astronomy']['sunrise'],
186
+ data['item']['pubDate'],
187
+ zone
188
+ )
189
+ set = Barometer::Zone.merge(
190
+ data['yweather:astronomy']['sunset'],
191
+ data['item']['pubDate'],
192
+ zone
193
+ )
194
+ sun = Sun.new(rise, set)
195
+ end
196
+ sun || Sun.new
197
+ end
198
+
199
+ # def self.build_timezone(data)
200
+ # raise ArgumentError unless data.is_a?(Hash)
201
+ #
202
+ # timezone = nil
203
+ # if data && data['simpleforecast'] &&
204
+ # data['simpleforecast']['forecastday'] &&
205
+ # data['simpleforecast']['forecastday'].first &&
206
+ # data['simpleforecast']['forecastday'].first['date']
207
+ # timezone = Barometer::Zone.new(Time.now.utc,data['simpleforecast']['forecastday'].first['date']['tz_long'])
208
+ # end
209
+ # timezone
210
+ # end
211
+
212
+ # use HTTParty to get the current weather
213
+ def self.get_all(query, metric=true)
214
+ Barometer::Yahoo.get(
215
+ "http://weather.yahooapis.com/forecastrss",
216
+ :query => {:p => query, :u => (metric ? 'c' : 'f')},
217
+ :format => :xml,
218
+ :timeout => Barometer.timeout
219
+ )['rss']['channel']
220
+ end
221
+
222
+ end
223
+ end
224
+
225
+ # Condition Codes
226
+ # 0 tornado
227
+ # 1 tropical storm
228
+ # 2 hurricane
229
+ # 3 severe thunderstorms
230
+ # 4 thunderstorms
231
+ # 5 mixed rain and snow
232
+ # 6 mixed rain and sleet
233
+ # 7 mixed snow and sleet
234
+ # 8 freezing drizzle
235
+ # 9 drizzle
236
+ # 10 freezing rain
237
+ # 11 showers
238
+ # 12 showers
239
+ # 13 snow flurries
240
+ # 14 light snow showers
241
+ # 15 blowing snow
242
+ # 16 snow
243
+ # 17 hail
244
+ # 18 sleet
245
+ # 19 dust
246
+ # 20 foggy
247
+ # 21 haze
248
+ # 22 smoky
249
+ # 23 blustery
250
+ # 24 windy
251
+ # 25 cold
252
+ # 26 cloudy
253
+ # 27 mostly cloudy (night)
254
+ # 28 mostly cloudy (day)
255
+ # 29 partly cloudy (night)
256
+ # 30 partly cloudy (day)
257
+ # 31 clear (night)
258
+ # 32 sunny
259
+ # 33 fair (night)
260
+ # 34 fair (day)
261
+ # 35 mixed rain and hail
262
+ # 36 hot
263
+ # 37 isolated thunderstorms
264
+ # 38 scattered thunderstorms
265
+ # 39 scattered thunderstorms
266
+ # 40 scattered showers
267
+ # 41 heavy snow
268
+ # 42 scattered snow showers
269
+ # 43 heavy snow
270
+ # 44 partly cloudy
271
+ # 45 thundershowers
272
+ # 46 snow showers
273
+ # 47 isolated thundershowers
274
+ # 3200 not available
@@ -0,0 +1,6 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+
3
+ require 'services/service'
4
+ require 'services/wunderground'
5
+ require 'services/google'
6
+ require 'services/yahoo'