barometer 0.7.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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