attack-barometer 0.5.0 → 0.6.0

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.
Files changed (64) hide show
  1. data/README.rdoc +51 -9
  2. data/VERSION.yml +1 -1
  3. data/bin/barometer +57 -7
  4. data/lib/barometer/base.rb +3 -0
  5. data/lib/barometer/data/sun.rb +10 -0
  6. data/lib/barometer/data/zone.rb +79 -188
  7. data/lib/barometer/data.rb +11 -6
  8. data/lib/barometer/formats/coordinates.rb +4 -1
  9. data/lib/barometer/formats/geocode.rb +9 -7
  10. data/lib/barometer/formats/icao.rb +2 -2
  11. data/lib/barometer/formats/weather_id.rb +2 -2
  12. data/lib/barometer/measurements/common.rb +113 -0
  13. data/lib/barometer/{data → measurements}/current.rb +17 -42
  14. data/lib/barometer/measurements/forecast.rb +62 -0
  15. data/lib/barometer/measurements/forecast_array.rb +72 -0
  16. data/lib/barometer/{data → measurements}/measurement.rb +57 -45
  17. data/lib/barometer/measurements/night.rb +27 -0
  18. data/lib/barometer/query.rb +55 -5
  19. data/lib/barometer/services.rb +3 -1
  20. data/lib/barometer/translations/zone_codes.yml +360 -0
  21. data/lib/barometer/weather.rb +5 -4
  22. data/lib/barometer/weather_services/google.rb +19 -35
  23. data/lib/barometer/weather_services/service.rb +113 -255
  24. data/lib/barometer/weather_services/weather_bug.rb +291 -2
  25. data/lib/barometer/weather_services/weather_dot_com.rb +45 -54
  26. data/lib/barometer/weather_services/wunderground.rb +83 -89
  27. data/lib/barometer/weather_services/yahoo.rb +44 -91
  28. data/lib/barometer/web_services/geocode.rb +1 -0
  29. data/lib/barometer/web_services/timezone.rb +40 -0
  30. data/lib/barometer/web_services/weather_id.rb +17 -2
  31. data/lib/barometer.rb +11 -0
  32. data/lib/demometer/demometer.rb +28 -0
  33. data/lib/demometer/public/css/master.css +259 -1
  34. data/lib/demometer/views/index.erb +2 -0
  35. data/lib/demometer/views/layout.erb +3 -2
  36. data/lib/demometer/views/measurement.erb +4 -1
  37. data/lib/demometer/views/readme.erb +116 -88
  38. data/spec/data/sun_spec.rb +53 -0
  39. data/spec/data/zone_spec.rb +330 -100
  40. data/spec/fixtures/formats/weather_id/ksfo.xml +1 -0
  41. data/spec/fixtures/services/weather_bug/90210_current.xml +1 -0
  42. data/spec/fixtures/services/weather_bug/90210_forecast.xml +1 -0
  43. data/spec/formats/weather_id_spec.rb +10 -5
  44. data/spec/measurements/common_spec.rb +352 -0
  45. data/spec/{data → measurements}/current_spec.rb +40 -103
  46. data/spec/measurements/forecast_array_spec.rb +165 -0
  47. data/spec/measurements/forecast_spec.rb +135 -0
  48. data/spec/{data → measurements}/measurement_spec.rb +86 -107
  49. data/spec/measurements/night_measurement_spec.rb +49 -0
  50. data/spec/query_spec.rb +12 -2
  51. data/spec/spec_helper.rb +28 -1
  52. data/spec/weather_services/google_spec.rb +27 -117
  53. data/spec/weather_services/services_spec.rb +49 -1024
  54. data/spec/weather_services/weather_bug_spec.rb +274 -0
  55. data/spec/weather_services/weather_dot_com_spec.rb +45 -125
  56. data/spec/weather_services/wunderground_spec.rb +42 -136
  57. data/spec/weather_services/yahoo_spec.rb +26 -116
  58. data/spec/weather_spec.rb +45 -45
  59. metadata +23 -11
  60. data/lib/barometer/data/forecast.rb +0 -84
  61. data/lib/barometer/data/night.rb +0 -69
  62. data/lib/barometer/extensions/graticule.rb +0 -51
  63. data/spec/data/forecast_spec.rb +0 -192
  64. data/spec/data/night_measurement_spec.rb +0 -136
@@ -10,10 +10,11 @@ module Barometer
10
10
  # Service Class
11
11
  #
12
12
  # This is a base class for creating alternate weather api-consuming
13
- # drivers. Each driver inherits from this class.
13
+ # drivers. Each driver inherits from this class. This class creates
14
+ # some default behaviours, but they can easily be over-ridden.
14
15
  #
15
16
  # Basically, all a service is required to do is take a query
16
- # (ie "Paris") and return a complete Data::Measurement instance.
17
+ # (ie "Paris") and return a complete Barometer::Measurement instance.
17
18
  #
18
19
  class WeatherService
19
20
  # all service drivers will use the HTTParty gem
@@ -34,300 +35,157 @@ module Barometer
34
35
  def self.measure(query, metric=true)
35
36
  raise ArgumentError unless query.is_a?(Barometer::Query)
36
37
 
37
- measurement = Data::Measurement.new(self.source_name, metric)
38
- if self.meets_requirements?(query)
39
- converted_query = query.convert!(self.accepted_formats)
38
+ measurement = Barometer::Measurement.new(self._source_name, metric)
39
+ measurement.start_at = Time.now.utc
40
+ if self._meets_requirements?(query)
41
+ converted_query = query.convert!(self._accepted_formats)
40
42
  if converted_query
43
+ measurement.source = self._source_name
41
44
  measurement.query = converted_query.q
42
45
  measurement.format = converted_query.format
43
46
  measurement = self._measure(measurement, converted_query, metric)
44
47
  end
45
48
  end
49
+ measurement.end_at = Time.now.utc
46
50
  measurement
47
51
  end
48
52
 
49
- def self.meets_requirements?(query=nil)
50
- self.supports_country?(query) && (!self.requires_keys? || self.has_keys?)
51
- end
53
+ #########################################################################
54
+ # PRIVATE
55
+ # If class methods could be private, the remaining methods would be.
56
+ #
52
57
 
53
58
  #
54
- # NOTE: The following methods MUST be re-defined by each driver.
59
+ # REQUIRED
60
+ # re-defining these methods will be required
55
61
  #
56
62
 
57
- # STUB: define this method to actually retireve the source_name
58
- def self.source_name; raise NotImplementedError; end
59
-
60
- # STUB: define this method to indicate what query formats are accepted
61
- def self.accepted_formats; raise NotImplementedError; end
63
+ def self._source_name; raise NotImplementedError; end
64
+ def self._accepted_formats; raise NotImplementedError; end
65
+ def self._fetch(query=nil, metric=true); nil; end
66
+ def self._build_current(result=nil, metric=true); nil; end
67
+ def self._build_forecast(result=nil, metric=true); nil; end
62
68
 
63
- # STUB: define this method to measure the current & future weather
64
- def self._measure(measurement=nil, query=nil, metric=true)
65
- raise NotImplementedError
69
+ #
70
+ # PROBABLE
71
+ # re-defining these methods is probable though not a must
72
+ #
73
+
74
+ # data processing stubs
75
+ #
76
+ def self._build_location(result=nil, geo=nil); nil; end
77
+ def self._build_station(result=nil); Data::Location.new; end
78
+ def self._build_links(result=nil); {}; end
79
+ def self._build_sun(result=nil); Data::Sun.new; end
80
+ def self._build_timezone(result=nil); nil; end
81
+ def self._build_extra(measurement=nil, result=nil, metric=true); measurement; end
82
+ def self._build_local_time(measurement)
83
+ (measurement && measurement.timezone) ? Data::LocalTime.parse(measurement.timezone.now) : nil
66
84
  end
67
-
85
+
86
+ # given the result set, return the full_timezone or local time ...
87
+ # if not available return nil
88
+ def self._parse_full_timezone(result=nil); nil; end
89
+ def self._parse_local_time(result=nil); nil; end
90
+
91
+ # this returns an array of codes that indicate "wet"
92
+ def self._wet_icon_codes; nil; end
93
+ # this returns an array of codes that indicate "sunny"
94
+ def self._sunny_icon_codes; nil; end
95
+
68
96
  #
69
- # NOTE: The following methods can be re-defined by each driver. [OPTIONAL]
97
+ # OPTIONAL
98
+ # re-defining these methods will be optional
70
99
  #
71
100
 
72
101
  # STUB: define this method to check for the existance of API keys,
73
102
  # this method is NOT needed if requires_keys? returns false
74
- def self.has_keys?; raise NotImplementedError; end
103
+ def self._has_keys?; raise NotImplementedError; end
75
104
 
76
105
  # STUB: define this method to check for the existance of API keys,
77
106
  # this method is NOT needed if requires_keys? returns false
78
- def self.keys=(keys=nil); nil; end
107
+ def self._keys=(keys=nil); nil; end
79
108
 
80
109
  # DEFAULT: override this if you need to determine if the country is specified
81
- def self.supports_country?(query=nil); true; end
110
+ def self._supports_country?(query=nil); true; end
82
111
 
83
112
  # DEFAULT: override this if you need to determine if API keys are required
84
- def self.requires_keys?; false; end
113
+ def self._requires_keys?; false; end
85
114
 
115
+ # data accessors
116
+ # (see the wunderground driver for an example of overriding these)
86
117
  #
87
- # answer simple questions
88
- #
89
-
90
- #
91
- # WINDY?
92
- #
93
- def self.windy?(measurement, threshold=10, time_string=nil)
94
- local_time = Data::LocalDateTime.parse(time_string) if time_string
95
- raise ArgumentError unless measurement.is_a?(Data::Measurement)
96
- raise ArgumentError unless (threshold.is_a?(Fixnum) || threshold.is_a?(Float))
97
- raise ArgumentError unless (local_time.is_a?(Data::LocalDateTime) || local_time.nil?)
98
-
99
- measurement.current?(local_time) ?
100
- self.currently_windy?(measurement, threshold) :
101
- self.forecasted_windy?(measurement, threshold, local_time)
102
- end
103
-
104
- # cookie cutter answer, a driver can override this if they answer it differently
105
- # if a service doesn't support obtaining the wind value, it will be ignored
106
- def self.currently_windy?(measurement, threshold=10)
107
- raise ArgumentError unless measurement.is_a?(Data::Measurement)
108
- raise ArgumentError unless (threshold.is_a?(Fixnum) || threshold.is_a?(Float))
109
- return nil if (!measurement.current || !measurement.current.wind?)
110
- measurement.metric? ?
111
- measurement.current.wind.kph.to_f >= threshold.to_f :
112
- measurement.current.wind.mph.to_f >= threshold.to_f
113
- end
118
+ def self._current_result(data=nil); data; end
119
+ def self._forecast_result(data=nil); data; end
120
+ def self._location_result(data=nil); data; end
121
+ def self._station_result(data=nil); data; end
122
+ def self._links_result(data=nil); data; end
123
+ def self._sun_result(data=nil); data; end
124
+ def self._timezone_result(data=nil); data; end
125
+ def self._time_result(data=nil); data; end
114
126
 
115
- # no driver can currently answer this question, so it doesn't have any code
116
- def self.forecasted_windy?(measurement, threshold, time_string); nil; end
117
-
118
127
  #
119
- # WET?
128
+ # COMPLETE
129
+ # re-defining these methods should not be needed, as the behavior
130
+ # can be adjusted using methods above
120
131
  #
121
- def self.wet?(measurement, threshold=50, time_string=nil)
122
- local_time = Data::LocalDateTime.parse(time_string) if time_string
123
- raise ArgumentError unless measurement.is_a?(Data::Measurement)
124
- raise ArgumentError unless (threshold.is_a?(Fixnum) || threshold.is_a?(Float))
125
- raise ArgumentError unless (local_time.is_a?(Data::LocalDateTime) || local_time.nil?)
126
- measurement.current?(local_time) ?
127
- self.currently_wet?(measurement, threshold) :
128
- self.forecasted_wet?(measurement, threshold, local_time)
129
- end
130
-
131
- # cookie cutter answer
132
- def self.currently_wet?(measurement, threshold=50)
133
- raise ArgumentError unless measurement.is_a?(Data::Measurement)
134
- raise ArgumentError unless (threshold.is_a?(Fixnum) || threshold.is_a?(Float))
135
- return nil unless measurement.current
136
- self.currently_wet_by_icon?(measurement.current) ||
137
- self.currently_wet_by_dewpoint?(measurement) ||
138
- self.currently_wet_by_humidity?(measurement.current) ||
139
- self.currently_wet_by_pop?(measurement, threshold)
140
- end
141
-
142
- # cookie cutter answer
143
- def self.currently_wet_by_dewpoint?(measurement)
144
- raise ArgumentError unless measurement.is_a?(Data::Measurement)
145
- return nil if (!measurement.current || !measurement.current.temperature? ||
146
- !measurement.current.dew_point?)
147
- measurement.metric? ?
148
- measurement.current.temperature.c.to_f <= measurement.current.dew_point.c.to_f :
149
- measurement.current.temperature.f.to_f <= measurement.current.dew_point.f.to_f
150
- end
151
-
152
- # cookie cutter answer
153
- def self.currently_wet_by_humidity?(current_measurement)
154
- raise ArgumentError unless current_measurement.is_a?(Data::CurrentMeasurement)
155
- return nil unless current_measurement.humidity?
156
- current_measurement.humidity.to_i >= 99
157
- end
158
-
159
- # cookie cutter answer
160
- def self.currently_wet_by_pop?(measurement, threshold=50)
161
- raise ArgumentError unless measurement.is_a?(Data::Measurement)
162
- raise ArgumentError unless (threshold.is_a?(Fixnum) || threshold.is_a?(Float))
163
- return nil unless measurement.forecast
164
- # get todays forecast
165
- forecast_measurement = measurement.for
166
- return nil unless forecast_measurement
167
- forecast_measurement.pop.to_f >= threshold.to_f
168
- end
169
-
170
- # cookie cutter answer
171
- def self.forecasted_wet?(measurement, threshold=50, time_string=nil)
172
- local_time = Data::LocalDateTime.parse(time_string) if time_string
173
- raise ArgumentError unless measurement.is_a?(Data::Measurement)
174
- raise ArgumentError unless (threshold.is_a?(Fixnum) || threshold.is_a?(Float))
175
- raise ArgumentError unless (local_time.is_a?(Data::LocalDateTime) || local_time.nil?)
176
- return nil unless measurement.forecast
177
- forecast_measurement = measurement.for(local_time)
178
- return nil unless forecast_measurement
179
- self.forecasted_wet_by_icon?(forecast_measurement) ||
180
- self.forecasted_wet_by_pop?(forecast_measurement, threshold)
181
- end
182
-
183
- # cookie cutter answer
184
- def self.forecasted_wet_by_pop?(forecast_measurement, threshold=50)
185
- raise ArgumentError unless forecast_measurement.is_a?(Data::ForecastMeasurement)
186
- raise ArgumentError unless (threshold.is_a?(Fixnum) || threshold.is_a?(Float))
187
- return nil unless forecast_measurement.pop?
188
- forecast_measurement.pop.to_f >= threshold.to_f
189
- end
190
-
191
- def self.currently_wet_by_icon?(current_measurement)
192
- raise ArgumentError unless current_measurement.is_a?(Data::CurrentMeasurement)
193
- return nil unless self.wet_icon_codes
194
- return nil unless current_measurement.icon?
195
- current_measurement.icon.is_a?(String) ?
196
- self.wet_icon_codes.include?(current_measurement.icon.to_s.downcase) :
197
- self.wet_icon_codes.include?(current_measurement.icon)
198
- end
199
-
200
- def self.forecasted_wet_by_icon?(forecast_measurement)
201
- raise ArgumentError unless forecast_measurement.is_a?(Data::ForecastMeasurement)
202
- return nil unless self.wet_icon_codes
203
- return nil unless forecast_measurement.icon?
204
- forecast_measurement.icon.is_a?(String) ?
205
- self.wet_icon_codes.include?(forecast_measurement.icon.to_s.downcase) :
206
- self.wet_icon_codes.include?(forecast_measurement.icon)
207
- end
208
-
209
- # this returns an array of codes that indicate "wet"
210
- def self.wet_icon_codes; nil; end
211
132
 
133
+ # this is the generic measuring and data processing for each weather service
134
+ # driver. this method should be re-defined if the driver in question
135
+ # doesn't fit into "generic" (ie wunderground)
212
136
  #
213
- # DAY?
214
- #
215
- def self.day?(measurement, time_string=nil)
216
- local_datetime = Data::LocalDateTime.parse(time_string) if time_string
217
- raise ArgumentError unless measurement.is_a?(Data::Measurement)
218
- raise ArgumentError unless (local_datetime.is_a?(Data::LocalDateTime) || local_datetime.nil?)
219
-
220
- measurement.current?(local_datetime) ?
221
- self.currently_day?(measurement) :
222
- self.forecasted_day?(measurement, local_datetime)
223
- end
224
-
225
- def self.currently_day?(measurement)
226
- raise ArgumentError unless measurement.is_a?(Data::Measurement)
227
- return nil unless measurement.current && measurement.current.sun
228
- self.currently_after_sunrise?(measurement.current) &&
229
- self.currently_before_sunset?(measurement.current)
230
- end
231
-
232
- def self.currently_after_sunrise?(current_measurement)
233
- raise ArgumentError unless current_measurement.is_a?(Data::CurrentMeasurement)
234
- return nil unless current_measurement.current_at &&
235
- current_measurement.sun && current_measurement.sun.rise
236
- #Time.now.utc >= current_measurement.sun.rise
237
- current_measurement.current_at >= current_measurement.sun.rise
238
- end
239
-
240
- def self.currently_before_sunset?(current_measurement)
241
- raise ArgumentError unless current_measurement.is_a?(Data::CurrentMeasurement)
242
- return nil unless current_measurement.current_at &&
243
- current_measurement.sun && current_measurement.sun.set
244
- #Time.now.utc <= current_measurement.sun.set
245
- current_measurement.current_at <= current_measurement.sun.set
246
- end
247
-
248
- def self.forecasted_day?(measurement, time_string=nil)
249
- local_datetime = Data::LocalDateTime.parse(time_string) if time_string
250
- raise ArgumentError unless measurement.is_a?(Data::Measurement)
251
- raise ArgumentError unless (local_datetime.is_a?(Data::LocalDateTime) || local_datetime.nil?)
252
- return nil unless measurement.forecast
253
- forecast_measurement = measurement.for(local_datetime)
254
- return nil unless forecast_measurement
255
- self.forecasted_after_sunrise?(forecast_measurement, local_datetime) &&
256
- self.forecasted_before_sunset?(forecast_measurement, local_datetime)
257
- end
258
-
259
- def self.forecasted_after_sunrise?(forecast_measurement, time_string)
260
- local_datetime = Data::LocalDateTime.parse(time_string) if time_string
261
- raise ArgumentError unless forecast_measurement.is_a?(Data::ForecastMeasurement)
262
- raise ArgumentError unless (local_datetime.is_a?(Data::LocalDateTime) || local_datetime.nil?)
263
- return nil unless forecast_measurement.sun && forecast_measurement.sun.rise
264
- local_datetime >= forecast_measurement.sun.rise
265
- end
266
-
267
- def self.forecasted_before_sunset?(forecast_measurement, time_string)
268
- local_datetime = Data::LocalDateTime.parse(time_string) if time_string
269
- raise ArgumentError unless forecast_measurement.is_a?(Data::ForecastMeasurement)
270
- raise ArgumentError unless (local_datetime.is_a?(Data::LocalDateTime) || local_datetime.nil?)
271
- return nil unless forecast_measurement.sun && forecast_measurement.sun.set
272
- local_datetime <= forecast_measurement.sun.set
137
+ def self._measure(measurement, query, metric=true)
138
+ raise ArgumentError unless measurement.is_a?(Barometer::Measurement)
139
+ raise ArgumentError unless query.is_a?(Barometer::Query)
140
+
141
+ begin
142
+ result = _fetch(query, metric)
143
+ rescue Timeout::Error => e
144
+ return measurement
145
+ end
146
+
147
+ if result
148
+ measurement.current = _build_current(_current_result(result), metric)
149
+ measurement.forecast = _build_forecast(_forecast_result(result), metric)
150
+ measurement.location = _build_location(_location_result(result), query.geo)
151
+ measurement.station = _build_station(_station_result(result))
152
+ measurement.links = _build_links(_links_result(result))
153
+ measurement.current.sun = _build_sun(_sun_result(result)) if measurement.current
154
+ measurement.timezone = _timezone(_timezone_result(result), query, measurement.location)
155
+ if local_time = _local_time(_time_result(result), measurement)
156
+ measurement.measured_at = local_time
157
+ measurement.current.current_at = local_time
158
+ end
159
+ measurement = _build_extra(measurement, result, metric)
160
+ end
161
+
162
+ measurement
273
163
  end
274
164
 
165
+ # either get the timezone based on coords, or build it from the data
275
166
  #
276
- # SUNNY?
277
- #
278
- def self.sunny?(measurement, time_string=nil)
279
- local_time = Data::LocalDateTime.parse(time_string) if time_string
280
- raise ArgumentError unless measurement.is_a?(Data::Measurement)
281
- raise ArgumentError unless (local_time.is_a?(Data::LocalDateTime) || local_time.nil?)
282
- measurement.current?(local_time) ?
283
- self.currently_sunny?(measurement) :
284
- self.forecasted_sunny?(measurement, local_time)
285
- end
286
-
287
- # cookie cutter answer
288
- def self.currently_sunny?(measurement)
289
- raise ArgumentError unless measurement.is_a?(Data::Measurement)
290
- return nil unless measurement.current
291
- return false if self.currently_day?(measurement) == false
292
- self.currently_sunny_by_icon?(measurement.current)
167
+ def self._timezone(result=nil, query=nil, location=nil)
168
+ if full_timezone = _parse_full_timezone(result)
169
+ full_timezone
170
+ elsif query && query.timezone
171
+ query.timezone
172
+ elsif Barometer.enhance_timezone && location &&
173
+ location.latitude && location.longitude
174
+ WebService::Timezone.fetch(location.latitude, location.longitude)
175
+ else
176
+ _build_timezone(result)
177
+ end
293
178
  end
294
179
 
295
- # cookie cutter answer
296
- def self.forecasted_sunny?(measurement, time_string=nil)
297
- local_time = Data::LocalDateTime.parse(time_string) if time_string
298
- raise ArgumentError unless measurement.is_a?(Data::Measurement)
299
- raise ArgumentError unless (local_time.is_a?(Data::LocalDateTime) || local_time.nil?)
300
- return nil unless measurement.forecast
301
- return false if self.forecasted_day?(measurement, local_time) == false
302
- forecast_measurement = measurement.for(local_time)
303
- return nil unless forecast_measurement
304
- self.forecasted_sunny_by_icon?(forecast_measurement)
305
- end
306
-
307
- def self.currently_sunny_by_icon?(current_measurement)
308
- raise ArgumentError unless current_measurement.is_a?(Data::CurrentMeasurement)
309
- return nil unless self.sunny_icon_codes
310
- return nil unless current_measurement.icon?
311
- current_measurement.icon.is_a?(String) ?
312
- self.sunny_icon_codes.include?(current_measurement.icon.to_s.downcase) :
313
- self.sunny_icon_codes.include?(current_measurement.icon)
180
+ # return the current local time (as Data::LocalTime)
181
+ #
182
+ def self._local_time(result, measurement=nil)
183
+ _parse_local_time(result) || _build_local_time(measurement)
314
184
  end
315
185
 
316
- def self.forecasted_sunny_by_icon?(forecast_measurement)
317
- raise ArgumentError unless forecast_measurement.is_a?(Data::ForecastMeasurement)
318
- return nil unless self.sunny_icon_codes
319
- return nil unless forecast_measurement.icon?
320
- forecast_measurement.icon.is_a?(String) ?
321
- self.sunny_icon_codes.include?(forecast_measurement.icon.to_s.downcase) :
322
- self.sunny_icon_codes.include?(forecast_measurement.icon)
186
+ def self._meets_requirements?(query=nil)
187
+ self._supports_country?(query) && (!self._requires_keys? || self._has_keys?)
323
188
  end
324
-
325
- # this returns an array of codes that indicate "sunny"
326
- def self.sunny_icon_codes; nil; end
327
189
 
328
190
  end
329
- end
330
-
331
- # def key_name
332
- # # what variables holds the api key?
333
- # end
191
+ end