barometer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +266 -0
  3. data/VERSION.yml +4 -0
  4. data/bin/barometer +63 -0
  5. data/lib/barometer.rb +52 -0
  6. data/lib/barometer/base.rb +52 -0
  7. data/lib/barometer/data.rb +15 -0
  8. data/lib/barometer/data/current.rb +93 -0
  9. data/lib/barometer/data/distance.rb +131 -0
  10. data/lib/barometer/data/forecast.rb +66 -0
  11. data/lib/barometer/data/geo.rb +98 -0
  12. data/lib/barometer/data/location.rb +20 -0
  13. data/lib/barometer/data/measurement.rb +161 -0
  14. data/lib/barometer/data/pressure.rb +133 -0
  15. data/lib/barometer/data/speed.rb +147 -0
  16. data/lib/barometer/data/sun.rb +35 -0
  17. data/lib/barometer/data/temperature.rb +164 -0
  18. data/lib/barometer/data/units.rb +55 -0
  19. data/lib/barometer/data/zone.rb +124 -0
  20. data/lib/barometer/extensions/graticule.rb +50 -0
  21. data/lib/barometer/extensions/httparty.rb +21 -0
  22. data/lib/barometer/query.rb +228 -0
  23. data/lib/barometer/services.rb +6 -0
  24. data/lib/barometer/services/google.rb +146 -0
  25. data/lib/barometer/services/noaa.rb +6 -0
  26. data/lib/barometer/services/service.rb +324 -0
  27. data/lib/barometer/services/weather_bug.rb +6 -0
  28. data/lib/barometer/services/weather_dot_com.rb +6 -0
  29. data/lib/barometer/services/wunderground.rb +285 -0
  30. data/lib/barometer/services/yahoo.rb +274 -0
  31. data/lib/barometer/weather.rb +187 -0
  32. data/spec/barometer_spec.rb +162 -0
  33. data/spec/data_current_spec.rb +225 -0
  34. data/spec/data_distance_spec.rb +336 -0
  35. data/spec/data_forecast_spec.rb +150 -0
  36. data/spec/data_geo_spec.rb +90 -0
  37. data/spec/data_location_spec.rb +59 -0
  38. data/spec/data_measurement_spec.rb +411 -0
  39. data/spec/data_pressure_spec.rb +336 -0
  40. data/spec/data_speed_spec.rb +374 -0
  41. data/spec/data_sun_spec.rb +76 -0
  42. data/spec/data_temperature_spec.rb +396 -0
  43. data/spec/data_zone_spec.rb +133 -0
  44. data/spec/fixtures/current_calgary_ab.xml +1 -0
  45. data/spec/fixtures/forecast_calgary_ab.xml +1 -0
  46. data/spec/fixtures/geocode_40_73.xml +1 -0
  47. data/spec/fixtures/geocode_90210.xml +1 -0
  48. data/spec/fixtures/geocode_T5B4M9.xml +1 -0
  49. data/spec/fixtures/geocode_calgary_ab.xml +1 -0
  50. data/spec/fixtures/geocode_newyork_ny.xml +1 -0
  51. data/spec/fixtures/google_calgary_ab.xml +1 -0
  52. data/spec/fixtures/yahoo_90210.xml +1 -0
  53. data/spec/query_spec.rb +469 -0
  54. data/spec/service_google_spec.rb +144 -0
  55. data/spec/service_wunderground_spec.rb +330 -0
  56. data/spec/service_yahoo_spec.rb +299 -0
  57. data/spec/services_spec.rb +1106 -0
  58. data/spec/spec_helper.rb +14 -0
  59. data/spec/units_spec.rb +101 -0
  60. data/spec/weather_spec.rb +265 -0
  61. metadata +119 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Mark
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,266 @@
1
+ = barometer
2
+
3
+ A multi API consuming weather forecasting superstar.
4
+
5
+ Barometer provides a common public API to one or more weather services (APIs)
6
+ of your choice. Weather services can co-exist to retrieve extensive
7
+ information, or they can be used in a hierarchical configuration where lower
8
+ preferred weather services are only used if previous services are
9
+ unavailable.
10
+
11
+ == status
12
+
13
+ Currently this project is in development and will only work for a few weather
14
+ services (wunderground, google, yahoo).
15
+
16
+ Features to be added before first release:
17
+ - gem setup/config, apply to rubyforge
18
+
19
+ Features to be added in future releases:
20
+ - even more weather service drivers (noaa, weather.com, weatherbug)
21
+ - ability to query multiple services and combine/average the results
22
+ - support iaco as a query format
23
+
24
+ = dependencies
25
+
26
+ === HTTParty
27
+
28
+ Why? HTTParty was created and designed specifically for consuming web services.
29
+ I choose to use this over using the Net::HTTP library directly to allow for
30
+ faster development of this project.
31
+
32
+ HTTParty is also extended to include configurable Timoout support.
33
+
34
+ === tzinfo
35
+
36
+ Why? Barometer deals with time information for locations all over the world.
37
+ This information doesn't mean that much if it can't be converted to times
38
+ that don't correspond to the applicable timezone.
39
+ Tzinfo handles this time zone manipulation.
40
+
41
+ === graticule (very soft dependency)
42
+
43
+ Why? Barometer returns the weather for a given location. Most weather service
44
+ APIs are somewhat restricted on the query format they receive. To bridge
45
+ this gap and allow for maximum flexibility on the 'barometer' query format,
46
+ the query will be geo-coded using the Google geocoding service, if required.
47
+ Graticule can provide this geocoding interface.
48
+
49
+ Using Graticule requires a free Google API key for geocoding. It is possible
50
+ to use barometer without geocoding, though your query format will be
51
+ limited to that of the weather service API.
52
+
53
+ ALTERNATE: If you supply a Google API key but don't install the Graticule gem,
54
+ HTTParty will be used instead to provide the same geocoding. Basically
55
+ Graticule is only used if it exists.
56
+
57
+ NOTE: you can force Barometer not to use Graticule, even if you have it installed
58
+ using the following:
59
+
60
+ Barometer.skip_graticule = true
61
+
62
+ = usage
63
+
64
+ You can use barometer right out of the box, as it is configured to use one
65
+ register-less (no API key required) international weather service
66
+ (wunderground.com).
67
+
68
+ For better results, signup for a google-map key and enhance your barometer
69
+ with geo-coding.
70
+
71
+ require 'barometer'
72
+
73
+ Barometer.google_geocode_key = "THE_GOOGLE_API_KEY"
74
+ barometer = Barometer.new("Paris")
75
+ weather = barometer.measure
76
+
77
+ puts weather.current.temperture
78
+
79
+ == multiple weather API, with hierarchy
80
+
81
+ require 'barometer'
82
+
83
+ Barometer.google_geocode_key = "THE_GOOGLE_API_KEY"
84
+ # use yahoo and google, if they both fail, use wunderground
85
+ Barometer.selection = { 1 => [:yahoo, :google], 2 => :wunderground }
86
+
87
+ barometer = Barometer.new("Paris")
88
+ weather = barometer.measure
89
+
90
+ puts weather.current.temperture
91
+
92
+ == command line
93
+
94
+ You can use barometer from the command line.
95
+
96
+ # barometer berlin
97
+
98
+ This will output the weather information for the given query.
99
+
100
+ === fail
101
+
102
+ What would cause a weather service to fail? The most obvious is that the
103
+ particular weather service in currently unavailable or not reachable.
104
+ Other possible reasons would include not having the API (or a valid API
105
+ key for the particular weather service, if required), not providing a
106
+ valid query, or providing a query for a location not supported by the
107
+ weather service.
108
+
109
+ For example, if you look at the example above, the query of "Paris" refers
110
+ to a city in France. Yahoo weather services only supports
111
+ weather results for USA (at least at the time of writing). Therefore,
112
+ Barometer would not use Yahoo, just Google and failover to use Wunderground
113
+ (if needed).
114
+
115
+ === bootstrapping
116
+
117
+ You can use weather service drivers directly. Below is an example to use
118
+ Wunderground, but since the driver interface is abstracted it will be the
119
+ same for all supported services.
120
+
121
+ require 'barometer'
122
+ Barometer.google_geocode_key = "THE_GOOGLE_API_KEY"
123
+
124
+ query = Barometer::Query.new("Paris")
125
+ weather = Barometer::Service.source(:wunderground).measure(query)
126
+
127
+ puts weather.current.temperture
128
+
129
+ # OR, even more raw
130
+
131
+ measurement = Barometer::Measurement.new
132
+ weather = Barometer::Wunderground.measure_all(measurement, "Paris")
133
+
134
+ puts weather.current.temperture
135
+
136
+
137
+ NOTE: The disadvantage to using the drivers directly is that you lose the
138
+ advantage of redundancy/failover added by the Module as a whole.
139
+
140
+ NOTE: You still must create the Barometer::Query object with your query
141
+ string instead of directly feeding the query string to the service (as in
142
+ bootstrap example #1). The Barometer::Query object has behavior required
143
+ by the service that a regular String doesn't have. Using a driver directly
144
+ WILL accept a String (as in bootstrap example #2).
145
+
146
+ == searching
147
+
148
+ After you have measured the data, Barometer provides several methods to help
149
+ you get the data you are after. All examples assume you already have measured
150
+ the data as shown in the above examples.
151
+
152
+ === by preference (default service)
153
+
154
+ weather.default # returns measurement for default source
155
+ weather.current # returns current_measurement for default
156
+ weather.now # returns current_measurement for default
157
+ weather.forecast # returns all forecast_measurements for default
158
+ weather.today # returns forecast_measurement for default today
159
+ weather.tomorrow # returns forecast_measurement for default tomorrow
160
+
161
+ puts weather.now.temperature.c
162
+ puts weather.tomorrow.high.c
163
+
164
+ === by source
165
+
166
+ weather.source(:wunderground) # returns measurement for specified source
167
+ weather.sources # lists all successful sources
168
+
169
+ puts weather.source(:wunderground).current.temperature.c
170
+
171
+ === by date
172
+
173
+ # note, the date is the date of the locations weather, not the date of the
174
+ # user measuring the weather
175
+ date = Date.parse("01-01-2009")
176
+ weather.for(date) # returns forecast_measurement for default on date
177
+ weather.source(:wunderground).for(date) # same as above but specific source
178
+
179
+ puts weather.source(:wunderground).for(date).high.c
180
+
181
+ === by time
182
+
183
+ # note, the time is the time of the locations weather, not the time of the
184
+ # user measuring the weather
185
+ time = Time.parse("13:00 01-01-2009")
186
+ weather.for(time) # returns forecast_measurement for default at time
187
+ weather.source(:wunderground).for(time) # same as above but specific source
188
+
189
+ puts weather.source(:wunderground).for(time).low.f
190
+
191
+ == simple answers
192
+
193
+ After you have measured the data, Barometer provides several "simple answer"
194
+ methods to help you get answers to some basic questions. All examples assume
195
+ you already have measured the data as shown in the above examples.
196
+
197
+ All of these questions are ultimately specific to the weather source(s) you
198
+ are configured to use. All sources that have successfully measured data
199
+ will be asked, but if there is no data that can answer the question then
200
+ there will be no answer.
201
+
202
+ === is it windy?
203
+
204
+ # 1st parameter is the threshold wind speed for being windy
205
+ # 2nd parameter is the utc_time for which you want to know the answer,
206
+ # this defaults to the current time
207
+ # NOTE: in my example the values are metric, so the threshold is 10 kph
208
+
209
+ weather.windy?(10)
210
+
211
+ === is it wet?
212
+
213
+ # 1st parameter is the threshold pop (%) for being wet
214
+ # 2nd parameter is the utc_time for which you want to know the answer,
215
+ # this defaults to the current time
216
+ # NOTE: in my example the threshold is 50 %
217
+
218
+ weather.wet?(50)
219
+
220
+ === is it sunny?
221
+
222
+ # 1st parameter is the utc_time for which you want to know the answer,
223
+ # this defaults to the current time
224
+
225
+ weather.sunny?
226
+
227
+ === is it day?
228
+
229
+ # 1st parameter is the utc_time for which you want to know the answer,
230
+ # this defaults to the current time
231
+
232
+ weather.day?
233
+
234
+ === is it night?
235
+
236
+ # 1st parameter is the utc_time for which you want to know the answer,
237
+ # this defaults to the current time
238
+
239
+ weather.night?
240
+
241
+ = design
242
+
243
+ - create a Barometer instance
244
+ - supply a query, there are very little restrictions on the format:
245
+ - city, country, specific address (basically anything Google will geocode)
246
+ - US zip code (skips geocoding if weather service accepts this directly)
247
+ - postal code (skips geocoding if weather service accepts this directly)
248
+ - latitude and longitude (skips geocoding if weather service accepts this
249
+ directly)
250
+ - TODO: international airport code (skips geocoding if weather service
251
+ accepts this directly)
252
+ - if geocoding required, geocode the query
253
+ - determine which weather services will be queried (one or multiple)
254
+ - query the weather services
255
+ - save the data
256
+ - repeat weather service queries as needed
257
+
258
+ = extending
259
+
260
+ Barometer attempts to be a common API to any weather service API. I have included
261
+ several weather service 'drivers', but I know there are many more available.
262
+ Please use the provided ones as examples to create more.
263
+
264
+ == copyright
265
+
266
+ Copyright (c) 2009 Mark G. See LICENSE for details.
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 0
data/bin/barometer ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/barometer'
4
+ #require 'rubygems'
5
+ #require 'attack-barometer'
6
+
7
+ # TODO
8
+
9
+ # set default Google Key ... maybe need a config file?
10
+ Barometer.google_geocode_key = "ABQIAAAAq8TH4offRcGrok8JVY_MyxRi_j0U6kJrkFvY4-OX2XYmEAa76BSFwMlSow1YgX8BOPUeve_shMG7xw"
11
+
12
+ # take command line paramters
13
+ # service: --yahoo, --wunderground, --google
14
+ # units: -m --metric, -i --imperial
15
+ # geocode: -g --geocode (force geocode)
16
+ # timeout: -t 15 --timeout 15
17
+ # skip: --skip (skip graticule)
18
+ # help: -h --help
19
+ # wet threshold: -w --wet
20
+ # windy threshold: -v --wind
21
+ # pop threshold: -p --pop
22
+ # time: -a --at
23
+
24
+ # prettier output
25
+ # show simple answers
26
+ # more help
27
+ # error display (out of sources, etc.)
28
+
29
+ if ARGV.size == 0
30
+ puts 'Barometer [Powered by wunderground]'
31
+ puts 'USAGE: barometer [query]'
32
+ puts 'EXAMPLES:'
33
+ puts ' barometer paris'
34
+ exit
35
+ end
36
+
37
+ barometer = Barometer.new(ARGV[0])
38
+ weather = barometer.measure(true)
39
+
40
+ def y(value)
41
+ value ? "yes" : "no"
42
+ end
43
+
44
+ if weather
45
+ puts "###################################################"
46
+ puts "# #{weather.default.location.name}"
47
+ puts "#"
48
+ puts "# (lat: #{weather.default.location.latitude}, long: #{weather.default.location.longitude})"
49
+ puts "###################################################"
50
+ puts " -- CURRENT --"
51
+ puts " temperature: #{weather.now.temperature}"
52
+ puts
53
+ puts " -- QUESTIONS --"
54
+ puts " day? : #{y(weather.day?)}"
55
+ puts " sunny?: #{y(weather.sunny?)}"
56
+ puts " windy?: #{y(weather.windy?)}"
57
+ puts " wet? : #{y(weather.wet?)}"
58
+ puts
59
+ puts
60
+ puts " -- INFO --"
61
+ puts " http://github.com/attack/barometer"
62
+ puts "---------------------------------------------------"
63
+ end
data/lib/barometer.rb ADDED
@@ -0,0 +1,52 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+
3
+ require 'barometer/base'
4
+ require 'barometer/query'
5
+ require 'barometer/weather'
6
+ require 'barometer/services'
7
+ require 'barometer/data'
8
+
9
+ module Barometer
10
+
11
+ @@google_geocode_key = nil
12
+ def self.google_geocode_key; @@google_geocode_key; end;
13
+ def self.google_geocode_key=(key); @@google_geocode_key = key; end;
14
+
15
+ @@skip_graticule = false
16
+ def self.skip_graticule; @@skip_graticule; end;
17
+ def self.skip_graticule=(value); @@skip_graticule = value; end;
18
+
19
+ # sometimes a query is used as is and never gets geocoded (ie zipcode)
20
+ # often, it is useful have queries geocoded to know where in the
21
+ # world that query points to. you can force the geocoding of
22
+ # queries (even when not required) so that you have the geocoded
23
+ # data. the reason this isn't the default is that it will use an
24
+ # extra web service query when not normally required
25
+ @@force_geocode = false
26
+ def self.force_geocode; @@force_geocode; end;
27
+ def self.force_geocode=(value); @@force_geocode = value; end;
28
+ def self.force_geocode!; @@force_geocode = true; end;
29
+
30
+ @@timeout = 15
31
+ def self.timeout; @@timeout; end;
32
+ def self.timeout=(value); @@timeout = value; end;
33
+
34
+ def self.new(query=nil)
35
+ Barometer::Base.new(query)
36
+ end
37
+
38
+ def self.selection=(selection=nil)
39
+ Barometer::Base.selection = selection
40
+ end
41
+
42
+ # shortcut to Barometer::Service.source method
43
+ # allows Barometer.source(:wunderground)
44
+ def self.source(source)
45
+ Barometer::Service.source(source)
46
+ end
47
+
48
+ # custom errors
49
+ class OutOfSources < StandardError; end
50
+
51
+ end
52
+
@@ -0,0 +1,52 @@
1
+ module Barometer
2
+
3
+ class Base
4
+
5
+ # allow the configuration of specific weather APIs to be used,
6
+ # and the order in which they would be used
7
+ @@selection = { 1 => [:wunderground] }
8
+ def self.selection; @@selection; end;
9
+ def self.selection=(hash); @@selection = hash; end;
10
+
11
+ attr_reader :query
12
+ attr_accessor :weather, :success
13
+
14
+ def initialize(query=nil)
15
+ @query = Barometer::Query.new(query)
16
+ @weather = Barometer::Weather.new
17
+ @success = false
18
+ end
19
+
20
+ def measure(metric=nil)
21
+ return nil unless @query
22
+
23
+ level = 1
24
+ until self.success?
25
+ if sources = @@selection[level]
26
+ if sources.is_a?(Array)
27
+ sources.each do |source|
28
+ measurement = Barometer.source(source.to_sym).measure(@query, metric)
29
+ @success = true if measurement.success?
30
+ @weather.measurements << measurement
31
+ end
32
+ else
33
+ measurement = Barometer.source(sources.to_sym).measure(@query, metric)
34
+ @success = true if measurement.success?
35
+ @weather.measurements << measurement
36
+ end
37
+ else
38
+ raise OutOfSources
39
+ end
40
+ level += 1
41
+ end
42
+
43
+ @weather
44
+ end
45
+
46
+ def success?
47
+ @success
48
+ end
49
+
50
+ end
51
+
52
+ end