barometer 0.7.3 → 0.8.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 (138) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +7 -0
  3. data/LICENSE +1 -1
  4. data/{README.rdoc → README.md} +124 -110
  5. data/Rakefile +1 -21
  6. data/TODO +8 -9
  7. data/barometer.gemspec +20 -19
  8. data/bin/barometer +36 -83
  9. data/lib/barometer.rb +13 -11
  10. data/lib/barometer/base.rb +10 -10
  11. data/lib/barometer/data.rb +1 -1
  12. data/lib/barometer/data/distance.rb +25 -25
  13. data/lib/barometer/data/geo.rb +9 -9
  14. data/lib/barometer/data/local_datetime.rb +24 -20
  15. data/lib/barometer/data/local_time.rb +13 -13
  16. data/lib/barometer/data/location.rb +6 -6
  17. data/lib/barometer/data/pressure.rb +24 -24
  18. data/lib/barometer/data/speed.rb +28 -28
  19. data/lib/barometer/data/sun.rb +7 -7
  20. data/lib/barometer/data/temperature.rb +29 -29
  21. data/lib/barometer/data/units.rb +9 -9
  22. data/lib/barometer/data/zone.rb +19 -19
  23. data/lib/barometer/formats.rb +1 -1
  24. data/lib/barometer/formats/coordinates.rb +7 -7
  25. data/lib/barometer/formats/format.rb +6 -6
  26. data/lib/barometer/formats/geocode.rb +5 -5
  27. data/lib/barometer/formats/icao.rb +6 -6
  28. data/lib/barometer/formats/postalcode.rb +3 -3
  29. data/lib/barometer/formats/short_zipcode.rb +2 -2
  30. data/lib/barometer/formats/weather_id.rb +10 -10
  31. data/lib/barometer/formats/woe_id.rb +20 -20
  32. data/lib/barometer/formats/zipcode.rb +3 -3
  33. data/lib/barometer/key_file_parser.rb +20 -0
  34. data/lib/barometer/measurements/measurement.rb +32 -32
  35. data/lib/barometer/measurements/result.rb +39 -39
  36. data/lib/barometer/measurements/result_array.rb +12 -12
  37. data/lib/barometer/query.rb +15 -15
  38. data/lib/barometer/services.rb +3 -3
  39. data/lib/barometer/translations/icao_country_codes.yml +20 -20
  40. data/lib/barometer/translations/weather_country_codes.yml +1 -1
  41. data/lib/barometer/translations/zone_codes.yml +2 -2
  42. data/lib/barometer/version.rb +3 -0
  43. data/lib/barometer/weather.rb +27 -27
  44. data/lib/barometer/weather_services/noaa.rb +314 -3
  45. data/lib/barometer/weather_services/service.rb +32 -30
  46. data/lib/barometer/weather_services/weather_bug.rb +35 -33
  47. data/lib/barometer/weather_services/wunderground.rb +31 -29
  48. data/lib/barometer/weather_services/yahoo.rb +36 -35
  49. data/lib/barometer/web_services/geocode.rb +5 -7
  50. data/lib/barometer/web_services/noaa_station_id.rb +53 -0
  51. data/lib/barometer/web_services/placemaker.rb +11 -13
  52. data/lib/barometer/web_services/timezone.rb +5 -7
  53. data/lib/barometer/web_services/weather_id.rb +4 -6
  54. data/lib/barometer/web_services/web_service.rb +4 -4
  55. data/spec/barometer_spec.rb +25 -27
  56. data/spec/cassettes/Barometer.json +1 -0
  57. data/spec/cassettes/Query.json +1 -0
  58. data/spec/cassettes/Query_Format_Coordinates.json +1 -0
  59. data/spec/cassettes/Query_Format_Geocode.json +1 -0
  60. data/spec/cassettes/Query_Format_WeatherID.json +1 -0
  61. data/spec/cassettes/Query_Format_WoeID.json +1 -0
  62. data/spec/cassettes/WeatherService.json +1 -0
  63. data/spec/cassettes/WeatherService_Noaa.json +1 -0
  64. data/spec/cassettes/WeatherService_WeatherBug.json +1 -0
  65. data/spec/cassettes/WeatherService_Wunderground.json +1 -0
  66. data/spec/cassettes/WeatherService_Yahoo.json +1 -0
  67. data/spec/cassettes/WebService_Geocode.json +1 -0
  68. data/spec/cassettes/WebService_NoaaStation.json +1 -0
  69. data/spec/data/distance_spec.rb +60 -60
  70. data/spec/data/geo_spec.rb +23 -23
  71. data/spec/data/local_datetime_spec.rb +44 -44
  72. data/spec/data/local_time_spec.rb +47 -47
  73. data/spec/data/location_spec.rb +16 -16
  74. data/spec/data/pressure_spec.rb +61 -61
  75. data/spec/data/speed_spec.rb +69 -69
  76. data/spec/data/sun_spec.rb +25 -25
  77. data/spec/data/temperature_spec.rb +68 -68
  78. data/spec/data/units_spec.rb +21 -21
  79. data/spec/data/zone_spec.rb +35 -35
  80. data/spec/formats/coordinates_spec.rb +27 -27
  81. data/spec/formats/format_spec.rb +17 -25
  82. data/spec/formats/geocode_spec.rb +23 -31
  83. data/spec/formats/icao_spec.rb +26 -32
  84. data/spec/formats/postalcode_spec.rb +22 -28
  85. data/spec/formats/short_zipcode_spec.rb +20 -26
  86. data/spec/formats/weather_id_spec.rb +57 -67
  87. data/spec/formats/woe_id_spec.rb +59 -59
  88. data/spec/formats/zipcode_spec.rb +39 -47
  89. data/spec/key_file_parser_spec.rb +28 -0
  90. data/spec/measurements/measurement_spec.rb +79 -133
  91. data/spec/measurements/result_array_spec.rb +23 -38
  92. data/spec/measurements/result_spec.rb +100 -128
  93. data/spec/query_spec.rb +83 -100
  94. data/spec/spec_helper.rb +24 -6
  95. data/spec/weather_services/noaa_spec.rb +179 -0
  96. data/spec/weather_services/services_spec.rb +28 -36
  97. data/spec/weather_services/weather_bug_spec.rb +57 -77
  98. data/spec/weather_services/wunderground_spec.rb +36 -65
  99. data/spec/weather_services/yahoo_spec.rb +38 -60
  100. data/spec/weather_spec.rb +79 -79
  101. data/spec/web_services/geocode_spec.rb +7 -11
  102. data/spec/web_services/noaa_station_id_spec.rb +33 -0
  103. data/spec/web_services/placemaker_spec.rb +7 -12
  104. data/spec/web_services/web_services_spec.rb +3 -9
  105. metadata +214 -163
  106. data/VERSION.yml +0 -5
  107. data/lib/barometer/weather_services/google.rb +0 -142
  108. data/lib/barometer/weather_services/weather_dot_com.rb +0 -279
  109. data/spec/fakeweb_helper.rb +0 -179
  110. data/spec/fixtures/formats/weather_id/90210.xml +0 -7
  111. data/spec/fixtures/formats/weather_id/from_USGA0028.xml +0 -3
  112. data/spec/fixtures/formats/weather_id/ksfo.xml +0 -1
  113. data/spec/fixtures/formats/weather_id/manhattan.xml +0 -7
  114. data/spec/fixtures/formats/weather_id/new_york.xml +0 -1
  115. data/spec/fixtures/formats/weather_id/the_hills.xml +0 -1
  116. data/spec/fixtures/geocode/40_73_v3.json +0 -497
  117. data/spec/fixtures/geocode/90210_v3.json +0 -63
  118. data/spec/fixtures/geocode/T5B4M9_v3.json +0 -68
  119. data/spec/fixtures/geocode/atlanta_v3.json +0 -58
  120. data/spec/fixtures/geocode/calgary_ab_v3.json +0 -58
  121. data/spec/fixtures/geocode/ksfo_v3.json +0 -73
  122. data/spec/fixtures/geocode/newyork_ny_v3.json +0 -58
  123. data/spec/fixtures/services/google/calgary_ab.xml +0 -1
  124. data/spec/fixtures/services/placemaker/T5B4M9.xml +0 -65
  125. data/spec/fixtures/services/placemaker/atlanta.xml +0 -65
  126. data/spec/fixtures/services/placemaker/coords.xml +0 -65
  127. data/spec/fixtures/services/placemaker/ksfo.xml +0 -65
  128. data/spec/fixtures/services/placemaker/new_york.xml +0 -65
  129. data/spec/fixtures/services/placemaker/the_hills.xml +0 -65
  130. data/spec/fixtures/services/placemaker/w615702.xml +0 -47
  131. data/spec/fixtures/services/weather_bug/90210_current.xml +0 -93
  132. data/spec/fixtures/services/weather_bug/90210_forecast.xml +0 -76
  133. data/spec/fixtures/services/weather_dot_com/90210.xml +0 -1
  134. data/spec/fixtures/services/wunderground/current_calgary_ab.xml +0 -9
  135. data/spec/fixtures/services/wunderground/forecast_calgary_ab.xml +0 -13
  136. data/spec/fixtures/services/yahoo/90210.xml +0 -3
  137. data/spec/weather_services/google_spec.rb +0 -181
  138. data/spec/weather_services/weather_dot_com_spec.rb +0 -224
@@ -1,6 +1,317 @@
1
1
  module Barometer
2
-
2
+ #
3
+ # = NOAA Weather
4
+ # http://www.weather.gov/
5
+ #
6
+ # - key required: NO
7
+ # - registration required: NO
8
+ # - supported countries: US only
9
+ #
10
+ # === performs geo coding
11
+ # - city: NO
12
+ # - coordinates: YES
13
+ #
14
+ # === time info
15
+ # - sun rise/set: NO
16
+ # - provides timezone: ?
17
+ # - requires TZInfo: ?
18
+ #
19
+ # == resources
20
+ # - API: http://www.weather.gov/forecasts/xml/rest.php
21
+ #
22
+ # === Possible queries:
23
+ # - http://www.weather.gov/forecasts/xml/sample_products/browser_interface/ndfdBrowserClientByDay.php? \
24
+ # format=24%20hourly&numDays=7&zipCodeList=90210
25
+ # - http://www.weather.gov/xml/current_obs/KSMO.xml
26
+ #
27
+ # what query can be:
28
+ # - zipcode
29
+ # - coordinates
30
+ #
31
+ # = NOAA terms of use
32
+ # see API url provided above
33
+ #
3
34
  class WeatherService::Noaa < WeatherService
35
+
36
+ #########################################################################
37
+ # PRIVATE
38
+ # If class methods could be private, the remaining methods would be.
39
+ #
40
+
41
+ def self._source_name; :noaa; end
42
+ def self._accepted_formats; [:zipcode, :coordinates]; end
43
+
44
+ # we can accept US, or we can try if the country is unknown
45
+ #
46
+ def self._supports_country?(query=nil)
47
+ ["US", nil, ""].include?(query.country_code)
48
+ end
49
+
50
+ def self._build_current(data, metric=true)
51
+ raise ArgumentError unless data.is_a?(Hash)
52
+
53
+ current = Measurement::Result.new
54
+ return current if data.empty?
55
+
56
+ if data && data['observation_time_rfc822'] && (time_match = data['observation_time_rfc822'].match(/(.* \d\d:\d\d:\d\d)/))
57
+ current.updated_at = Data::LocalDateTime.parse(time_match[1])
58
+ end
59
+
60
+ current.temperature = Data::Temperature.new(metric)
61
+ current.temperature << [data['temp_c'], data['temp_f']]
62
+
63
+ current.wind = Data::Speed.new(metric)
64
+ current.wind.mph = data['wind_mph'].to_f
65
+ current.wind.direction = data['wind_dir']
66
+ current.wind.degrees = data['wind_degrees'].to_i
67
+
68
+ current.humidity = data['relative_humidity'].to_i
69
+
70
+ current.pressure = Data::Pressure.new(metric)
71
+ current.pressure << [data['pressure_mb'], data['pressure_in']]
72
+
73
+ current.dew_point = Data::Temperature.new(metric)
74
+ current.dew_point << [data['dewpoint_c'], data['dewpoint_f']]
75
+
76
+ if data['windchill_c'] || data['windchill_f']
77
+ current.wind_chill = Data::Temperature.new(metric)
78
+ current.wind_chill << [data['windchill_c'], data['windchill_f']]
79
+ end
80
+
81
+ current.visibility = Data::Distance.new(metric)
82
+ current.visibility.m = data['visibility_mi'].to_f
83
+
84
+ current.condition = data['weather']
85
+ if data['icon_url_name']
86
+ icon_match = data['icon_url_name'].match(/(.*).(jpg|png)/)
87
+ current.icon = icon_match[1] if icon_match
88
+ end
89
+
90
+ current
91
+ end
92
+
93
+ def self._build_forecast(data, metric=true)
94
+ raise ArgumentError unless data.is_a?(Hash)
95
+
96
+ forecasts = Measurement::ResultArray.new
97
+ return forecasts unless data && data['time_layout']
98
+
99
+ twelve_hour_starts = []
100
+ twelve_hour_ends = []
101
+ data['time_layout'].each do |time_layout|
102
+ if time_layout["summarization"] == "24hourly"
103
+ twelve_hour_starts = time_layout["start_valid_time"]
104
+ twelve_hour_ends = time_layout["end_valid_time"]
105
+ break
106
+ end
107
+ end
108
+
109
+ daily_highs = []
110
+ daily_lows = []
111
+ data['parameters']['temperature'].each do |temps|
112
+ case temps["type"]
113
+ when "maximum"
114
+ daily_highs = temps['value']
115
+ when "minimum"
116
+ daily_lows = temps['value']
117
+ end
118
+ end
119
+
120
+ # NOAA returns 2 pop values for each day ... for each day, use the max pop value
121
+ #
122
+ daily_pops = []
123
+ if data['parameters']['probability_of_precipitation'] &&
124
+ data['parameters']['probability_of_precipitation']['value']
125
+ daily_pops = data['parameters']['probability_of_precipitation']['value'].collect{|i|i.respond_to?(:to_i) ? i.to_i : 0}.each_slice(2).to_a.collect{|x|x.max}
126
+ end
127
+
128
+ daily_conditions = []
129
+ if data['parameters']['weather'] &&
130
+ data['parameters']['weather']['weather_conditions']
131
+ daily_conditions = data['parameters']['weather']['weather_conditions'].collect{|c|c["weather_summary"]}
132
+ end
133
+
134
+ daily_icons = []
135
+ if data['parameters']['conditions_icon'] &&
136
+ data['parameters']['conditions_icon']['icon_link']
137
+ daily_icons = data['parameters']['conditions_icon']['icon_link'].collect{|c|c.match(/.*\/(.*)\.jpg/)[1]}
138
+ end
139
+
140
+ d = 0
141
+ # go through each forecast start date and create an instance
142
+ twelve_hour_starts.each do |start_date|
143
+ forecast_measurement = Measurement::Result.new(metric)
144
+
145
+ # day = 6am - 6am (next day)
146
+ date_s = Date.parse(start_date)
147
+ date_e = Date.parse(start_date) + 1
148
+ forecast_measurement.valid_start_date = Data::LocalDateTime.new(date_s.year,date_s.month,date_s.day,6,0,0)
149
+ forecast_measurement.valid_end_date = Data::LocalDateTime.new(date_e.year,date_e.month,date_e.day,5,59,59)
150
+
151
+ forecast_measurement.high = Data::Temperature.new(metric)
152
+ forecast_measurement.high.f = (daily_highs[d].respond_to?(:to_f) ? daily_highs[d].to_f : nil)
153
+ forecast_measurement.low = Data::Temperature.new(metric)
154
+ forecast_measurement.low.f = (daily_lows[d].respond_to?(:to_f) ? daily_lows[d].to_f : nil)
155
+
156
+ forecast_measurement.pop = daily_pops[d]
157
+ forecast_measurement.condition = daily_conditions[d]
158
+ forecast_measurement.icon = daily_icons[d]
159
+
160
+ forecasts << forecast_measurement
161
+ d += 1
162
+ end
163
+
164
+ forecasts
165
+ end
166
+
167
+ def self._build_location(data, geo=nil)
168
+ raise ArgumentError unless data.is_a?(Hash)
169
+ raise ArgumentError unless (geo.nil? || geo.is_a?(Data::Geo))
170
+ location = Data::Location.new
171
+ # use the geocoded data if available, otherwise get data from result
172
+ if geo
173
+ location.city = geo.locality
174
+ location.state_code = geo.region
175
+ location.country = geo.country
176
+ location.country_code = geo.country_code
177
+ location.latitude = geo.latitude
178
+ location.longitude = geo.longitude
179
+ else
180
+ if data && data['location']
181
+ location.city = data['location'].split(',')[0].strip
182
+ location.state_code = data['location'].split(',')[-1].strip
183
+ location.country_code = 'US'
184
+ end
185
+ end
186
+ location
187
+ end
188
+
189
+ def self._build_station(data)
190
+ raise ArgumentError unless data.is_a?(Hash)
191
+ station = Data::Location.new
192
+ station.id = data['station_id']
193
+ if data['location']
194
+ station.name = data['location']
195
+ station.city = data['location'].split(',')[0].strip
196
+ station.state_code = data['location'].split(',')[-1].strip
197
+ station.country_code = 'US'
198
+ station.latitude = data['latitude']
199
+ station.longitude = data['longitude']
200
+ end
201
+ station
202
+ end
203
+
204
+ def self._build_timezone(data)
205
+ if data && data['observation_time']
206
+ zone_match = data['observation_time'].match(/ ([A-Z]*)$/)
207
+ Data::Zone.new(zone_match[1]) if zone_match
208
+ end
209
+ end
210
+
211
+ # override default _fetch behavior
212
+ # this service requires TWO seperate http requests (one for current
213
+ # and one for forecasted weather) ... combine the results
214
+ #
215
+ def self._fetch(query, metric=true)
216
+ result = []
217
+ result << _fetch_forecast(query,metric)
218
+
219
+ # only proceed if we are getting results
220
+ #
221
+ # binding.pry
222
+ if result[0] && !result[0].empty?
223
+ # we need to use the lst/long from the forecast data (result[0])
224
+ # to get the closest "station_id", to get the current conditions
225
+ #
226
+ station_id = Barometer::WebService::NoaaStation.fetch(
227
+ result[0]["location"]["point"]["latitude"],
228
+ result[0]["location"]["point"]["longitude"]
229
+ )
230
+
231
+ result << _fetch_current(station_id,metric)
232
+ else
233
+ puts "NOAA cannot proceed to fetching current weather, lat/lon unknown" if Barometer::debug?
234
+ result << {}
235
+ end
236
+
237
+ result
238
+ end
239
+
240
+ # use HTTParty to get the current weather
241
+ #
242
+ def self._fetch_current(station_id, metric=true)
243
+ return {} unless station_id
244
+ puts "fetching NOAA current weather: #{station_id}" if Barometer::debug?
245
+
246
+ self.get(
247
+ "http://w1.weather.gov/xml/current_obs/#{station_id}.xml",
248
+ :query => {},
249
+ :format => :xml,
250
+ :timeout => Barometer.timeout
251
+ )["current_observation"]
252
+ end
253
+
254
+ # use HTTParty to get the forecasted weather
255
+ #
256
+ def self._fetch_forecast(query, metric=true)
257
+ puts "fetching NOAA forecast: #{query.q}" if Barometer::debug?
258
+
259
+ q = case query.format.to_sym
260
+ when :short_zipcode
261
+ { :zipCodeList => query.q }
262
+ when :zipcode
263
+ { :zipCodeList => query.q }
264
+ when :coordinates
265
+ { :lat => query.q.split(',')[0], :lon => query.q.split(',')[1] }
266
+ else
267
+ {}
268
+ end
269
+
270
+ result = self.get(
271
+ "http://graphical.weather.gov/xml/sample_products/browser_interface/ndfdBrowserClientByDay.php",
272
+ :query => {
273
+ :format => "24 hourly",
274
+ :numDays => "7"
275
+ }.merge(q),
276
+ :format => :xml,
277
+ :timeout => Barometer.timeout
278
+ )
279
+
280
+
281
+ # binding.pry
282
+
283
+ if result && result["dwml"] && result["dwml"]["data"]
284
+ result = result["dwml"]["data"]
285
+ else
286
+ return {}
287
+ end
288
+
289
+ # check that we have data ... we have to dig deep to find out since
290
+ # NOAA will return a good looking result, even when there isn't any data to return
291
+ #
292
+ if result && result['parameters'] &&
293
+ result['parameters']['temperature'] &&
294
+ result['parameters']['temperature'].first &&
295
+ result['parameters']['temperature'].first['value'] &&
296
+ !result['parameters']['temperature'].first['value'].collect{|t| t.respond_to?(:to_i) ? t.to_i : nil}.compact.empty?
297
+ else
298
+ return {}
299
+ end
300
+
301
+ result
302
+ end
303
+
304
+ # since we have two sets of data, override these calls to choose the
305
+ # right set of data
306
+ #
307
+ def self._current_result(data); data[1]; end
308
+ def self._forecast_result(data=nil); data[0]; end
309
+ def self._location_result(data=nil); data[1]; end
310
+ def self._station_result(data=nil); data[1]; end
311
+ def self._sun_result(data=nil); nil; end
312
+ def self._timezone_result(data=nil); data[1]; end
313
+ def self._time_result(data=nil); data[1]; end
314
+
4
315
  end
5
-
6
- end
316
+
317
+ end
@@ -8,14 +8,14 @@ module Barometer
8
8
  # This is a base class for creating alternate weather api-consuming
9
9
  # drivers. Each driver inherits from this class. This class creates
10
10
  # some default behaviours, but they can easily be over-ridden.
11
- #
11
+ #
12
12
  # Basically, all a service is required to do is take a query
13
13
  # (ie "Paris") and return a complete Barometer::Measurement instance.
14
14
  #
15
15
  class WeatherService
16
16
  # all service drivers will use the HTTParty gem
17
17
  include HTTParty
18
-
18
+
19
19
  # retrieves the weather source Service object
20
20
  def self.source(source_name)
21
21
  raise ArgumentError unless (source_name.is_a?(String) || source_name.is_a?(Symbol))
@@ -30,43 +30,43 @@ module Barometer
30
30
  #
31
31
  def self.measure(query, metric=true)
32
32
  raise ArgumentError unless query.is_a?(Barometer::Query)
33
-
33
+
34
34
  measurement = Barometer::Measurement.new(self._source_name, metric)
35
35
  measurement.start_at = Time.now.utc
36
- if self._meets_requirements?(query)
37
- converted_query = query.convert!(self._accepted_formats)
38
- if converted_query
39
- measurement.source = self._source_name
40
- measurement.query = converted_query.q
41
- measurement.format = converted_query.format
42
- measurement = self._measure(measurement, converted_query, metric)
43
- end
36
+
37
+ converted_query = query.convert!(self._accepted_formats)
38
+ if converted_query
39
+ measurement.source = self._source_name
40
+ measurement.query = converted_query.q
41
+ measurement.format = converted_query.format
42
+ measurement = self._measure(measurement, converted_query, metric)
44
43
  end
44
+
45
45
  measurement.end_at = Time.now.utc
46
46
  measurement
47
47
  end
48
-
48
+
49
49
  #########################################################################
50
50
  # PRIVATE
51
51
  # If class methods could be private, the remaining methods would be.
52
52
  #
53
-
53
+
54
54
  #
55
55
  # REQUIRED
56
56
  # re-defining these methods will be required
57
57
  #
58
-
58
+
59
59
  def self._source_name; raise NotImplementedError; end
60
60
  def self._accepted_formats; raise NotImplementedError; end
61
61
  def self._fetch(query=nil, metric=true); nil; end
62
62
  def self._build_current(result=nil, metric=true); nil; end
63
63
  def self._build_forecast(result=nil, metric=true); nil; end
64
-
64
+
65
65
  #
66
66
  # PROBABLE
67
67
  # re-defining these methods is probable though not a must
68
68
  #
69
-
69
+
70
70
  # data processing stubs
71
71
  #
72
72
  def self._build_location(result=nil, geo=nil); nil; end
@@ -78,17 +78,17 @@ module Barometer
78
78
  def self._build_local_time(measurement)
79
79
  (measurement && measurement.timezone) ? Data::LocalTime.parse(measurement.timezone.now) : nil
80
80
  end
81
-
81
+
82
82
  # given the result set, return the full_timezone or local time ...
83
83
  # if not available return nil
84
84
  def self._parse_full_timezone(result=nil); nil; end
85
85
  def self._parse_local_time(result=nil); nil; end
86
-
86
+
87
87
  # this returns an array of codes that indicate "wet"
88
88
  def self._wet_icon_codes; nil; end
89
89
  # this returns an array of codes that indicate "sunny"
90
90
  def self._sunny_icon_codes; nil; end
91
-
91
+
92
92
  #
93
93
  # OPTIONAL
94
94
  # re-defining these methods will be optional
@@ -104,10 +104,10 @@ module Barometer
104
104
 
105
105
  # DEFAULT: override this if you need to determine if the country is specified
106
106
  def self._supports_country?(query=nil); true; end
107
-
107
+
108
108
  # DEFAULT: override this if you need to determine if API keys are required
109
109
  def self._requires_keys?; false; end
110
-
110
+
111
111
  # data accessors
112
112
  # (see the wunderground driver for an example of overriding these)
113
113
  #
@@ -125,7 +125,7 @@ module Barometer
125
125
  # re-defining these methods should not be needed, as the behavior
126
126
  # can be adjusted using methods above
127
127
  #
128
-
128
+
129
129
  # this is the generic measuring and data processing for each weather service
130
130
  # driver. this method should be re-defined if the driver in question
131
131
  # doesn't fit into "generic" (ie wunderground)
@@ -133,13 +133,15 @@ module Barometer
133
133
  def self._measure(measurement, query, metric=true)
134
134
  raise ArgumentError unless measurement.is_a?(Barometer::Measurement)
135
135
  raise ArgumentError unless query.is_a?(Barometer::Query)
136
-
136
+
137
+ return measurement unless self._meets_requirements?(query)
138
+
137
139
  begin
138
140
  result = _fetch(query, metric)
139
141
  rescue Timeout::Error => e
140
142
  return measurement
141
143
  end
142
-
144
+
143
145
  if result
144
146
  measurement.current = _build_current(_current_result(result), metric)
145
147
  measurement.forecast = _build_forecast(_forecast_result(result), metric)
@@ -154,10 +156,10 @@ module Barometer
154
156
  end
155
157
  measurement = _build_extra(measurement, result, metric)
156
158
  end
157
-
159
+
158
160
  measurement
159
161
  end
160
-
162
+
161
163
  # either get the timezone based on coords, or build it from the data
162
164
  #
163
165
  def self._timezone(result=nil, query=nil, location=nil)
@@ -172,16 +174,16 @@ module Barometer
172
174
  _build_timezone(result)
173
175
  end
174
176
  end
175
-
177
+
176
178
  # return the current local time (as Data::LocalTime)
177
179
  #
178
180
  def self._local_time(result, measurement=nil)
179
181
  _parse_local_time(result) || _build_local_time(measurement)
180
182
  end
181
-
183
+
182
184
  def self._meets_requirements?(query=nil)
183
185
  self._supports_country?(query) && (!self._requires_keys? || self._has_keys?)
184
186
  end
185
-
187
+
186
188
  end
187
- end
189
+ end