attack-barometer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +266 -0
- data/VERSION.yml +4 -0
- data/bin/barometer +63 -0
- data/lib/barometer/base.rb +52 -0
- data/lib/barometer/data/current.rb +93 -0
- data/lib/barometer/data/distance.rb +131 -0
- data/lib/barometer/data/forecast.rb +66 -0
- data/lib/barometer/data/geo.rb +98 -0
- data/lib/barometer/data/location.rb +20 -0
- data/lib/barometer/data/measurement.rb +161 -0
- data/lib/barometer/data/pressure.rb +133 -0
- data/lib/barometer/data/speed.rb +147 -0
- data/lib/barometer/data/sun.rb +35 -0
- data/lib/barometer/data/temperature.rb +164 -0
- data/lib/barometer/data/units.rb +55 -0
- data/lib/barometer/data/zone.rb +124 -0
- data/lib/barometer/data.rb +15 -0
- data/lib/barometer/extensions/graticule.rb +50 -0
- data/lib/barometer/extensions/httparty.rb +21 -0
- data/lib/barometer/query.rb +228 -0
- data/lib/barometer/services/google.rb +146 -0
- data/lib/barometer/services/noaa.rb +6 -0
- data/lib/barometer/services/service.rb +324 -0
- data/lib/barometer/services/weather_bug.rb +6 -0
- data/lib/barometer/services/weather_dot_com.rb +6 -0
- data/lib/barometer/services/wunderground.rb +285 -0
- data/lib/barometer/services/yahoo.rb +274 -0
- data/lib/barometer/services.rb +6 -0
- data/lib/barometer/weather.rb +187 -0
- data/lib/barometer.rb +52 -0
- data/spec/barometer_spec.rb +162 -0
- data/spec/data_current_spec.rb +225 -0
- data/spec/data_distance_spec.rb +336 -0
- data/spec/data_forecast_spec.rb +150 -0
- data/spec/data_geo_spec.rb +90 -0
- data/spec/data_location_spec.rb +59 -0
- data/spec/data_measurement_spec.rb +411 -0
- data/spec/data_pressure_spec.rb +336 -0
- data/spec/data_speed_spec.rb +374 -0
- data/spec/data_sun_spec.rb +76 -0
- data/spec/data_temperature_spec.rb +396 -0
- data/spec/data_zone_spec.rb +133 -0
- data/spec/fixtures/current_calgary_ab.xml +1 -0
- data/spec/fixtures/forecast_calgary_ab.xml +1 -0
- data/spec/fixtures/geocode_40_73.xml +1 -0
- data/spec/fixtures/geocode_90210.xml +1 -0
- data/spec/fixtures/geocode_T5B4M9.xml +1 -0
- data/spec/fixtures/geocode_calgary_ab.xml +1 -0
- data/spec/fixtures/geocode_newyork_ny.xml +1 -0
- data/spec/fixtures/google_calgary_ab.xml +1 -0
- data/spec/fixtures/yahoo_90210.xml +1 -0
- data/spec/query_spec.rb +469 -0
- data/spec/service_google_spec.rb +144 -0
- data/spec/service_wunderground_spec.rb +330 -0
- data/spec/service_yahoo_spec.rb +299 -0
- data/spec/services_spec.rb +1106 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/units_spec.rb +101 -0
- data/spec/weather_spec.rb +265 -0
- 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
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
|