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