barometer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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