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.
- data/.gitignore +1 -0
- data/.travis.yml +7 -0
- data/LICENSE +1 -1
- data/{README.rdoc → README.md} +124 -110
- data/Rakefile +1 -21
- data/TODO +8 -9
- data/barometer.gemspec +20 -19
- data/bin/barometer +36 -83
- data/lib/barometer.rb +13 -11
- data/lib/barometer/base.rb +10 -10
- data/lib/barometer/data.rb +1 -1
- data/lib/barometer/data/distance.rb +25 -25
- data/lib/barometer/data/geo.rb +9 -9
- data/lib/barometer/data/local_datetime.rb +24 -20
- data/lib/barometer/data/local_time.rb +13 -13
- data/lib/barometer/data/location.rb +6 -6
- data/lib/barometer/data/pressure.rb +24 -24
- data/lib/barometer/data/speed.rb +28 -28
- data/lib/barometer/data/sun.rb +7 -7
- data/lib/barometer/data/temperature.rb +29 -29
- data/lib/barometer/data/units.rb +9 -9
- data/lib/barometer/data/zone.rb +19 -19
- data/lib/barometer/formats.rb +1 -1
- data/lib/barometer/formats/coordinates.rb +7 -7
- data/lib/barometer/formats/format.rb +6 -6
- data/lib/barometer/formats/geocode.rb +5 -5
- data/lib/barometer/formats/icao.rb +6 -6
- data/lib/barometer/formats/postalcode.rb +3 -3
- data/lib/barometer/formats/short_zipcode.rb +2 -2
- data/lib/barometer/formats/weather_id.rb +10 -10
- data/lib/barometer/formats/woe_id.rb +20 -20
- data/lib/barometer/formats/zipcode.rb +3 -3
- data/lib/barometer/key_file_parser.rb +20 -0
- data/lib/barometer/measurements/measurement.rb +32 -32
- data/lib/barometer/measurements/result.rb +39 -39
- data/lib/barometer/measurements/result_array.rb +12 -12
- data/lib/barometer/query.rb +15 -15
- data/lib/barometer/services.rb +3 -3
- data/lib/barometer/translations/icao_country_codes.yml +20 -20
- data/lib/barometer/translations/weather_country_codes.yml +1 -1
- data/lib/barometer/translations/zone_codes.yml +2 -2
- data/lib/barometer/version.rb +3 -0
- data/lib/barometer/weather.rb +27 -27
- data/lib/barometer/weather_services/noaa.rb +314 -3
- data/lib/barometer/weather_services/service.rb +32 -30
- data/lib/barometer/weather_services/weather_bug.rb +35 -33
- data/lib/barometer/weather_services/wunderground.rb +31 -29
- data/lib/barometer/weather_services/yahoo.rb +36 -35
- data/lib/barometer/web_services/geocode.rb +5 -7
- data/lib/barometer/web_services/noaa_station_id.rb +53 -0
- data/lib/barometer/web_services/placemaker.rb +11 -13
- data/lib/barometer/web_services/timezone.rb +5 -7
- data/lib/barometer/web_services/weather_id.rb +4 -6
- data/lib/barometer/web_services/web_service.rb +4 -4
- data/spec/barometer_spec.rb +25 -27
- data/spec/cassettes/Barometer.json +1 -0
- data/spec/cassettes/Query.json +1 -0
- data/spec/cassettes/Query_Format_Coordinates.json +1 -0
- data/spec/cassettes/Query_Format_Geocode.json +1 -0
- data/spec/cassettes/Query_Format_WeatherID.json +1 -0
- data/spec/cassettes/Query_Format_WoeID.json +1 -0
- data/spec/cassettes/WeatherService.json +1 -0
- data/spec/cassettes/WeatherService_Noaa.json +1 -0
- data/spec/cassettes/WeatherService_WeatherBug.json +1 -0
- data/spec/cassettes/WeatherService_Wunderground.json +1 -0
- data/spec/cassettes/WeatherService_Yahoo.json +1 -0
- data/spec/cassettes/WebService_Geocode.json +1 -0
- data/spec/cassettes/WebService_NoaaStation.json +1 -0
- data/spec/data/distance_spec.rb +60 -60
- data/spec/data/geo_spec.rb +23 -23
- data/spec/data/local_datetime_spec.rb +44 -44
- data/spec/data/local_time_spec.rb +47 -47
- data/spec/data/location_spec.rb +16 -16
- data/spec/data/pressure_spec.rb +61 -61
- data/spec/data/speed_spec.rb +69 -69
- data/spec/data/sun_spec.rb +25 -25
- data/spec/data/temperature_spec.rb +68 -68
- data/spec/data/units_spec.rb +21 -21
- data/spec/data/zone_spec.rb +35 -35
- data/spec/formats/coordinates_spec.rb +27 -27
- data/spec/formats/format_spec.rb +17 -25
- data/spec/formats/geocode_spec.rb +23 -31
- data/spec/formats/icao_spec.rb +26 -32
- data/spec/formats/postalcode_spec.rb +22 -28
- data/spec/formats/short_zipcode_spec.rb +20 -26
- data/spec/formats/weather_id_spec.rb +57 -67
- data/spec/formats/woe_id_spec.rb +59 -59
- data/spec/formats/zipcode_spec.rb +39 -47
- data/spec/key_file_parser_spec.rb +28 -0
- data/spec/measurements/measurement_spec.rb +79 -133
- data/spec/measurements/result_array_spec.rb +23 -38
- data/spec/measurements/result_spec.rb +100 -128
- data/spec/query_spec.rb +83 -100
- data/spec/spec_helper.rb +24 -6
- data/spec/weather_services/noaa_spec.rb +179 -0
- data/spec/weather_services/services_spec.rb +28 -36
- data/spec/weather_services/weather_bug_spec.rb +57 -77
- data/spec/weather_services/wunderground_spec.rb +36 -65
- data/spec/weather_services/yahoo_spec.rb +38 -60
- data/spec/weather_spec.rb +79 -79
- data/spec/web_services/geocode_spec.rb +7 -11
- data/spec/web_services/noaa_station_id_spec.rb +33 -0
- data/spec/web_services/placemaker_spec.rb +7 -12
- data/spec/web_services/web_services_spec.rb +3 -9
- metadata +214 -163
- data/VERSION.yml +0 -5
- data/lib/barometer/weather_services/google.rb +0 -142
- data/lib/barometer/weather_services/weather_dot_com.rb +0 -279
- data/spec/fakeweb_helper.rb +0 -179
- data/spec/fixtures/formats/weather_id/90210.xml +0 -7
- data/spec/fixtures/formats/weather_id/from_USGA0028.xml +0 -3
- data/spec/fixtures/formats/weather_id/ksfo.xml +0 -1
- data/spec/fixtures/formats/weather_id/manhattan.xml +0 -7
- data/spec/fixtures/formats/weather_id/new_york.xml +0 -1
- data/spec/fixtures/formats/weather_id/the_hills.xml +0 -1
- data/spec/fixtures/geocode/40_73_v3.json +0 -497
- data/spec/fixtures/geocode/90210_v3.json +0 -63
- data/spec/fixtures/geocode/T5B4M9_v3.json +0 -68
- data/spec/fixtures/geocode/atlanta_v3.json +0 -58
- data/spec/fixtures/geocode/calgary_ab_v3.json +0 -58
- data/spec/fixtures/geocode/ksfo_v3.json +0 -73
- data/spec/fixtures/geocode/newyork_ny_v3.json +0 -58
- data/spec/fixtures/services/google/calgary_ab.xml +0 -1
- data/spec/fixtures/services/placemaker/T5B4M9.xml +0 -65
- data/spec/fixtures/services/placemaker/atlanta.xml +0 -65
- data/spec/fixtures/services/placemaker/coords.xml +0 -65
- data/spec/fixtures/services/placemaker/ksfo.xml +0 -65
- data/spec/fixtures/services/placemaker/new_york.xml +0 -65
- data/spec/fixtures/services/placemaker/the_hills.xml +0 -65
- data/spec/fixtures/services/placemaker/w615702.xml +0 -47
- data/spec/fixtures/services/weather_bug/90210_current.xml +0 -93
- data/spec/fixtures/services/weather_bug/90210_forecast.xml +0 -76
- data/spec/fixtures/services/weather_dot_com/90210.xml +0 -1
- data/spec/fixtures/services/wunderground/current_calgary_ab.xml +0 -9
- data/spec/fixtures/services/wunderground/forecast_calgary_ab.xml +0 -13
- data/spec/fixtures/services/yahoo/90210.xml +0 -3
- data/spec/weather_services/google_spec.rb +0 -181
- 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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|