attack-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/base.rb +52 -0
  6. data/lib/barometer/data/current.rb +93 -0
  7. data/lib/barometer/data/distance.rb +131 -0
  8. data/lib/barometer/data/forecast.rb +66 -0
  9. data/lib/barometer/data/geo.rb +98 -0
  10. data/lib/barometer/data/location.rb +20 -0
  11. data/lib/barometer/data/measurement.rb +161 -0
  12. data/lib/barometer/data/pressure.rb +133 -0
  13. data/lib/barometer/data/speed.rb +147 -0
  14. data/lib/barometer/data/sun.rb +35 -0
  15. data/lib/barometer/data/temperature.rb +164 -0
  16. data/lib/barometer/data/units.rb +55 -0
  17. data/lib/barometer/data/zone.rb +124 -0
  18. data/lib/barometer/data.rb +15 -0
  19. data/lib/barometer/extensions/graticule.rb +50 -0
  20. data/lib/barometer/extensions/httparty.rb +21 -0
  21. data/lib/barometer/query.rb +228 -0
  22. data/lib/barometer/services/google.rb +146 -0
  23. data/lib/barometer/services/noaa.rb +6 -0
  24. data/lib/barometer/services/service.rb +324 -0
  25. data/lib/barometer/services/weather_bug.rb +6 -0
  26. data/lib/barometer/services/weather_dot_com.rb +6 -0
  27. data/lib/barometer/services/wunderground.rb +285 -0
  28. data/lib/barometer/services/yahoo.rb +274 -0
  29. data/lib/barometer/services.rb +6 -0
  30. data/lib/barometer/weather.rb +187 -0
  31. data/lib/barometer.rb +52 -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
@@ -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
@@ -0,0 +1,93 @@
1
+ module Barometer
2
+ #
3
+ # Current Measurement
4
+ # a data class for current weather conditions
5
+ #
6
+ # This is basically a data holding class for the current weather
7
+ # conditions.
8
+ #
9
+ class CurrentMeasurement
10
+
11
+ attr_accessor :time, :local_time
12
+ attr_reader :humidity, :icon, :condition
13
+ attr_reader :temperature, :dew_point, :heat_index, :wind_chill
14
+ attr_reader :wind, :pressure, :visibility, :sun
15
+
16
+ def time=(time)
17
+ #raise ArgumentError unless time.is_a?(Time)
18
+ @time = time
19
+ end
20
+
21
+ def humidity=(humidity)
22
+ raise ArgumentError unless
23
+ (humidity.is_a?(Fixnum) || humidity.is_a?(Float))
24
+ @humidity = humidity
25
+ end
26
+
27
+ def icon=(icon)
28
+ raise ArgumentError unless icon.is_a?(String)
29
+ @icon = icon
30
+ end
31
+
32
+ def condition=(condition)
33
+ raise ArgumentError unless condition.is_a?(String)
34
+ @condition = condition
35
+ end
36
+
37
+ def temperature=(temperature)
38
+ raise ArgumentError unless temperature.is_a?(Barometer::Temperature)
39
+ @temperature = temperature
40
+ end
41
+
42
+ def dew_point=(dew_point)
43
+ raise ArgumentError unless dew_point.is_a?(Barometer::Temperature)
44
+ @dew_point = dew_point
45
+ end
46
+
47
+ def heat_index=(heat_index)
48
+ raise ArgumentError unless heat_index.is_a?(Barometer::Temperature)
49
+ @heat_index = heat_index
50
+ end
51
+
52
+ def wind_chill=(wind_chill)
53
+ raise ArgumentError unless wind_chill.is_a?(Barometer::Temperature)
54
+ @wind_chill = wind_chill
55
+ end
56
+
57
+ def wind=(wind)
58
+ raise ArgumentError unless wind.is_a?(Barometer::Speed)
59
+ @wind = wind
60
+ end
61
+
62
+ def pressure=(pressure)
63
+ raise ArgumentError unless pressure.is_a?(Barometer::Pressure)
64
+ @pressure = pressure
65
+ end
66
+
67
+ def visibility=(visibility)
68
+ raise ArgumentError unless visibility.is_a?(Barometer::Distance)
69
+ @visibility = visibility
70
+ end
71
+
72
+ def sun=(sun)
73
+ raise ArgumentError unless sun.is_a?(Barometer::Sun)
74
+ @sun = sun
75
+ end
76
+
77
+ #
78
+ # helpers
79
+ #
80
+
81
+ # creates "?" helpers for all attributes (which maps to nil?)
82
+ def method_missing(method,*args)
83
+ # if the method ends in ?, then strip it off and see if we
84
+ # respond to the method without the ?
85
+ if (call_method = method.to_s.chomp!("?")) && respond_to?(call_method)
86
+ return send(call_method).nil? ? false : true
87
+ else
88
+ super(method,*args)
89
+ end
90
+ end
91
+
92
+ end
93
+ end
@@ -0,0 +1,131 @@
1
+ module Barometer
2
+ #
3
+ # A simple Distance class
4
+ #
5
+ # Think of this like the Integer class. Enhancement
6
+ # is that you can create a number (in a certain unit), then
7
+ # get that number back already converted to another unit.
8
+ #
9
+ # All comparison operations will be done in metric
10
+ #
11
+ # NOTE: this currently only supports the scale of
12
+ # kilometers (km) and miles (m). There is currently
13
+ # no way to scale to smaller units (eg km -> m -> mm)
14
+ class Distance < Barometer::Units
15
+
16
+ METRIC_UNITS = "km"
17
+ IMPERIAL_UNITS = "m"
18
+
19
+ attr_accessor :kilometers, :miles
20
+
21
+ def initialize(metric=true)
22
+ @kilometers = nil
23
+ @miles = nil
24
+ super(metric)
25
+ end
26
+
27
+ def metric_default=(value); self.km = value; end
28
+ def imperial_default=(value); self.m = value; end
29
+
30
+ #
31
+ # CONVERTERS
32
+ #
33
+
34
+ def self.km_to_m(km)
35
+ return nil unless km && (km.is_a?(Integer) || km.is_a?(Float))
36
+ km.to_f * 0.622
37
+ end
38
+
39
+ def self.m_to_km(m)
40
+ return nil unless m && (m.is_a?(Integer) || m.is_a?(Float))
41
+ m.to_f * 1.609
42
+ end
43
+
44
+ #
45
+ # ACCESSORS
46
+ #
47
+
48
+ # store kilometers
49
+ def km=(km)
50
+ return if !km || !(km.is_a?(Integer) || km.is_a?(Float))
51
+ @kilometers = km.to_f
52
+ self.update_miles(km.to_f)
53
+ end
54
+
55
+ # store miles
56
+ def m=(m)
57
+ return if !m || !(m.is_a?(Integer) || m.is_a?(Float))
58
+ @miles = m.to_f
59
+ self.update_kilometers(m.to_f)
60
+ end
61
+
62
+ # return the stored kilometers or convert from miles
63
+ def km(as_integer=true)
64
+ km = (@kilometers || Distance.m_to_km(@miles))
65
+ km ? (as_integer ? km.to_i : (100*km).round/100.0) : nil
66
+ end
67
+
68
+ # return the stored miles or convert from kilometers
69
+ def m(as_integer=true)
70
+ m = (@miles || Distance.km_to_m(@kilometers))
71
+ m ? (as_integer ? m.to_i : (100*m).round/100.0) : nil
72
+ end
73
+
74
+ #
75
+ # OPERATORS
76
+ #
77
+
78
+ def <=>(other)
79
+ self.km <=> other.km
80
+ end
81
+
82
+ #
83
+ # HELPERS
84
+ #
85
+
86
+ # will just return the value (no units)
87
+ def to_i(metric=nil)
88
+ (metric || (metric.nil? && self.metric?)) ? self.km : self.m
89
+ end
90
+
91
+ # will just return the value (no units) with more precision
92
+ def to_f(metric=nil)
93
+ (metric || (metric.nil? && self.metric?)) ? self.km(false) : self.m(false)
94
+ end
95
+
96
+ # will return the value with units
97
+ def to_s(metric=nil)
98
+ (metric || (metric.nil? && self.metric?)) ? "#{self.km} #{METRIC_UNITS}" : "#{self.m} #{IMPERIAL_UNITS}"
99
+ end
100
+
101
+ # will just return the units (no value)
102
+ def units(metric=nil)
103
+ (metric || (metric.nil? && self.metric?)) ? METRIC_UNITS : IMPERIAL_UNITS
104
+ end
105
+
106
+ # when we set miles, it is possible the a non-equivalent value of
107
+ # kilometers remains. if so, clear it.
108
+ def update_kilometers(m)
109
+ return unless @kilometers
110
+ difference = Distance.m_to_km(m.to_f) - @kilometers
111
+ # only clear kilometers if the stored kilometers is off be more then 1 unit
112
+ # then the conversion of miles
113
+ @kilometers = nil unless difference.abs <= 1.0
114
+ end
115
+
116
+ # when we set kilometers, it is possible the a non-equivalent value of
117
+ # miles remains. if so, clear it.
118
+ def update_miles(km)
119
+ return unless @miles
120
+ difference = Distance.km_to_m(km.to_f) - @miles
121
+ # only clear miles if the stored miles is off be more then 1 unit
122
+ # then the conversion of kilometers
123
+ @miles = nil unless difference.abs <= 1.0
124
+ end
125
+
126
+ def nil?
127
+ (@kilometers || @miles) ? false : true
128
+ end
129
+
130
+ end
131
+ end
@@ -0,0 +1,66 @@
1
+ require 'date'
2
+ module Barometer
3
+ #
4
+ # Forecast Measurement
5
+ # a data class for forecasted weather conditions
6
+ #
7
+ # This is basically a data holding class for the forecasted weather
8
+ # conditions.
9
+ #
10
+ class ForecastMeasurement
11
+
12
+ attr_reader :date, :icon, :condition
13
+ attr_reader :low, :high, :pop, :sun
14
+
15
+ def date=(date)
16
+ raise ArgumentError unless date.is_a?(Date)
17
+ @date = date
18
+ end
19
+
20
+ def icon=(icon)
21
+ raise ArgumentError unless icon.is_a?(String)
22
+ @icon = icon
23
+ end
24
+
25
+ def condition=(condition)
26
+ raise ArgumentError unless condition.is_a?(String)
27
+ @condition = condition
28
+ end
29
+
30
+ def high=(high)
31
+ raise ArgumentError unless high.is_a?(Barometer::Temperature)
32
+ @high = high
33
+ end
34
+
35
+ def low=(low)
36
+ raise ArgumentError unless low.is_a?(Barometer::Temperature)
37
+ @low = low
38
+ end
39
+
40
+ def pop=(pop)
41
+ raise ArgumentError unless pop.is_a?(Fixnum)
42
+ @pop = pop
43
+ end
44
+
45
+ def sun=(sun)
46
+ raise ArgumentError unless sun.is_a?(Barometer::Sun)
47
+ @sun = sun
48
+ end
49
+
50
+ #
51
+ # helpers
52
+ #
53
+
54
+ # creates "?" helpers for all attributes (which maps to nil?)
55
+ def method_missing(method,*args)
56
+ # if the method ends in ?, then strip it off and see if we
57
+ # respond to the method without the ?
58
+ if (call_method = method.to_s.chomp!("?")) && respond_to?(call_method)
59
+ return send(call_method).nil? ? false : true
60
+ else
61
+ super(method,*args)
62
+ end
63
+ end
64
+
65
+ end
66
+ end