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
@@ -0,0 +1,98 @@
|
|
1
|
+
module Barometer
|
2
|
+
#
|
3
|
+
# A simple Geo class
|
4
|
+
#
|
5
|
+
# Used to store location data from Graticule or HTTParty and convert
|
6
|
+
# into just the data needed for geocoding
|
7
|
+
#
|
8
|
+
class Geo
|
9
|
+
|
10
|
+
attr_accessor :latitude, :longitude
|
11
|
+
attr_accessor :locality, :region, :country, :country_code
|
12
|
+
|
13
|
+
#
|
14
|
+
# this will take a Location object (either generated by Graticule
|
15
|
+
# or HTTParty), and fill in the applicable data
|
16
|
+
#
|
17
|
+
def initialize(location=nil)
|
18
|
+
return unless location
|
19
|
+
|
20
|
+
has_graticule = false
|
21
|
+
begin
|
22
|
+
Graticule
|
23
|
+
has_graticule = true
|
24
|
+
rescue
|
25
|
+
# do nothing, Graticule not available
|
26
|
+
end
|
27
|
+
|
28
|
+
if has_graticule
|
29
|
+
raise ArgumentError unless
|
30
|
+
(location.is_a?(Graticule::Location) || location.is_a?(Hash))
|
31
|
+
else
|
32
|
+
raise ArgumentError unless location.is_a?(Hash)
|
33
|
+
end
|
34
|
+
|
35
|
+
if has_graticule && location.class == Graticule::Location
|
36
|
+
self.build_from_graticule(location)
|
37
|
+
elsif location.class == Hash
|
38
|
+
self.build_from_httparty(location)
|
39
|
+
end
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_from_graticule(location=nil)
|
44
|
+
return nil unless location
|
45
|
+
|
46
|
+
begin
|
47
|
+
require 'rubygems'
|
48
|
+
require 'graticule'
|
49
|
+
$:.unshift(File.dirname(__FILE__))
|
50
|
+
# load some changes to Graticule
|
51
|
+
# TODO: attempt to get changes into Graticule gem
|
52
|
+
require 'extensions/graticule'
|
53
|
+
end
|
54
|
+
|
55
|
+
raise ArgumentError unless location.is_a?(Graticule::Location)
|
56
|
+
|
57
|
+
@latitude = location.latitude
|
58
|
+
@longitude = location.longitude
|
59
|
+
@locality = location.locality
|
60
|
+
@region = location.region
|
61
|
+
@country = location.country
|
62
|
+
@country_code = location.country_code
|
63
|
+
end
|
64
|
+
|
65
|
+
def build_from_httparty(location=nil)
|
66
|
+
return nil unless location
|
67
|
+
raise ArgumentError unless location.is_a?(Hash)
|
68
|
+
|
69
|
+
placemark = location["Placemark"]
|
70
|
+
placemark = placemark.first if placemark.is_a?(Array)
|
71
|
+
|
72
|
+
if placemark && placemark["Point"] && placemark["Point"]["coordinates"]
|
73
|
+
@latitude = placemark["Point"]["coordinates"].split(',')[1].to_f
|
74
|
+
@longitude = placemark["Point"]["coordinates"].split(',')[0].to_f
|
75
|
+
end
|
76
|
+
if placemark && placemark["AddressDetails"] && placemark["AddressDetails"]["Country"]
|
77
|
+
if placemark["AddressDetails"]["Country"]["AdministrativeArea"]
|
78
|
+
if placemark["AddressDetails"]["Country"]["AdministrativeArea"]["SubAdministrativeArea"]
|
79
|
+
locality = placemark["AddressDetails"]["Country"]["AdministrativeArea"]["SubAdministrativeArea"]["Locality"]
|
80
|
+
else
|
81
|
+
locality = placemark["AddressDetails"]["Country"]["AdministrativeArea"]["Locality"]
|
82
|
+
end
|
83
|
+
if locality
|
84
|
+
@locality = locality["LocalityName"]
|
85
|
+
end
|
86
|
+
@region = placemark["AddressDetails"]["Country"]["AdministrativeArea"]["AdministrativeAreaName"]
|
87
|
+
end
|
88
|
+
@country = placemark["AddressDetails"]["Country"]["CountryName"]
|
89
|
+
@country_code = placemark["AddressDetails"]["Country"]["CountryNameCode"]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def coordinates
|
94
|
+
[@latitude, @longitude].join(',')
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Barometer
|
2
|
+
#
|
3
|
+
# A simple Location class
|
4
|
+
#
|
5
|
+
# Used to store location information about the station that
|
6
|
+
# gave the measurement data for a weather query, or the location
|
7
|
+
# that was queried
|
8
|
+
#
|
9
|
+
class Location
|
10
|
+
|
11
|
+
attr_accessor :id, :name, :city
|
12
|
+
attr_accessor :state_name, :state_code, :country, :country_code, :zip_code
|
13
|
+
attr_accessor :latitude, :longitude
|
14
|
+
|
15
|
+
def coordinates
|
16
|
+
[@latitude, @longitude].join(',')
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
module Barometer
|
2
|
+
#
|
3
|
+
# Measurement
|
4
|
+
# a class that holds the response from a weather service
|
5
|
+
#
|
6
|
+
# its main purpose is to hold all the data collected from a weather service
|
7
|
+
# as it is passed to the weather object
|
8
|
+
#
|
9
|
+
# this response includes
|
10
|
+
# - current weather data (using the CurrentMeasurement class)
|
11
|
+
# - forecasted weather data (an Array of instances of the ForecastMeasurement class)
|
12
|
+
# - time_zone information (for the location in question)
|
13
|
+
# - weather station information (for the station that gave collected the data)
|
14
|
+
#
|
15
|
+
class Measurement
|
16
|
+
|
17
|
+
# the weather service source
|
18
|
+
attr_reader :source
|
19
|
+
# current and forecasted data
|
20
|
+
attr_reader :current, :forecast
|
21
|
+
attr_reader :timezone, :station, :location
|
22
|
+
attr_reader :success, :time
|
23
|
+
attr_accessor :metric
|
24
|
+
|
25
|
+
def initialize(source=nil, metric=true)
|
26
|
+
@source = source
|
27
|
+
@metric = metric
|
28
|
+
@success = false
|
29
|
+
end
|
30
|
+
|
31
|
+
def success!
|
32
|
+
if current && current.temperature && !current.temperature.c.nil?
|
33
|
+
@success = true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def stamp!; @time = Time.now.utc; end
|
38
|
+
def success?; @success; end
|
39
|
+
def metric?; @metric; end
|
40
|
+
def metric!; @metric=true; end
|
41
|
+
def imperial!; @metric=false; end
|
42
|
+
|
43
|
+
# this will tell us if the measurement is still current ... if it is still
|
44
|
+
# current this means that the CurrentMeasurement can still used as now
|
45
|
+
#
|
46
|
+
# what it also means is that if you took a measurement right now (time = now)
|
47
|
+
# and then asked if current?(time_in_future) that current? would be true for
|
48
|
+
# any time_in_future within 4 hours of now
|
49
|
+
#
|
50
|
+
# where is this useful? lets say you take the measurement now (time = now),
|
51
|
+
# and then you want to know if self.windy?(5_hours_in_future) ... we could
|
52
|
+
# not use the current data for this answser as the time 5_hours_in_future
|
53
|
+
# is not current
|
54
|
+
def current?(utc_time=Time.now.utc)
|
55
|
+
return false unless @time
|
56
|
+
raise ArgumentError unless utc_time.is_a?(Time)
|
57
|
+
hours_still_current = 4
|
58
|
+
difference = (@time - utc_time).to_i
|
59
|
+
difference = (difference*(-1)).to_i if difference < 0
|
60
|
+
difference <= (60*60*hours_still_current).to_i
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Returns a forecast for a day given by a Date, DateTime,
|
65
|
+
# Time, or a string that can be parsed to a date
|
66
|
+
#
|
67
|
+
# credit: http://github.com/jdpace/weatherman/
|
68
|
+
#
|
69
|
+
def for(date=nil)
|
70
|
+
date = @timezone.today unless date || !@timezone
|
71
|
+
date ||= Date.today
|
72
|
+
return nil unless (@forecast && @forecast.size > 0)
|
73
|
+
|
74
|
+
# Format date into a Date class
|
75
|
+
date = case date.class.name
|
76
|
+
when 'String'
|
77
|
+
Date.parse(date)
|
78
|
+
when 'Date'
|
79
|
+
date
|
80
|
+
when 'DateTime'
|
81
|
+
Date.new(date.year, date.month, date.day)
|
82
|
+
when 'Time'
|
83
|
+
Date.new(date.year, date.month, date.day)
|
84
|
+
end
|
85
|
+
|
86
|
+
day = nil
|
87
|
+
@forecast.each do |f|
|
88
|
+
day = f if date == f.date
|
89
|
+
end
|
90
|
+
return day
|
91
|
+
end
|
92
|
+
|
93
|
+
def source=(source)
|
94
|
+
raise ArgumentError unless source.is_a?(Symbol)
|
95
|
+
@source = source
|
96
|
+
end
|
97
|
+
|
98
|
+
def time=(time=Time.now.utc)
|
99
|
+
raise ArgumentError unless time.is_a?(Time)
|
100
|
+
@time = time
|
101
|
+
end
|
102
|
+
|
103
|
+
def current=(current)
|
104
|
+
raise ArgumentError unless current.is_a?(Barometer::CurrentMeasurement)
|
105
|
+
@current = current
|
106
|
+
self.stamp!
|
107
|
+
# self-determine success
|
108
|
+
self.success!
|
109
|
+
end
|
110
|
+
|
111
|
+
def forecast=(forecast)
|
112
|
+
raise ArgumentError unless forecast.is_a?(Array)
|
113
|
+
@forecast = forecast
|
114
|
+
end
|
115
|
+
|
116
|
+
def timezone=(timezone)
|
117
|
+
#raise ArgumentError unless timezone.is_a?(String)
|
118
|
+
raise ArgumentError unless timezone.is_a?(Barometer::Zone)
|
119
|
+
@timezone = timezone
|
120
|
+
end
|
121
|
+
|
122
|
+
def station=(station)
|
123
|
+
raise ArgumentError unless station.is_a?(Barometer::Location)
|
124
|
+
@station = station
|
125
|
+
end
|
126
|
+
|
127
|
+
def location=(location)
|
128
|
+
raise ArgumentError unless location.is_a?(Barometer::Location)
|
129
|
+
@location = location
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# simple questions
|
134
|
+
# pass questions to the source
|
135
|
+
#
|
136
|
+
|
137
|
+
def windy?(threshold=10, utc_time=Time.now.utc)
|
138
|
+
raise ArgumentError unless (threshold.is_a?(Fixnum) || threshold.is_a?(Float))
|
139
|
+
raise ArgumentError unless utc_time.is_a?(Time)
|
140
|
+
Barometer::Service.source(@source).windy?(self, threshold, utc_time)
|
141
|
+
end
|
142
|
+
|
143
|
+
def wet?(threshold=50, utc_time=Time.now.utc)
|
144
|
+
raise ArgumentError unless (threshold.is_a?(Fixnum) || threshold.is_a?(Float))
|
145
|
+
raise ArgumentError unless utc_time.is_a?(Time)
|
146
|
+
Barometer::Service.source(@source).wet?(self, threshold, utc_time)
|
147
|
+
end
|
148
|
+
|
149
|
+
def day?(utc_time=Time.now.utc)
|
150
|
+
raise ArgumentError unless utc_time.is_a?(Time)
|
151
|
+
Barometer::Service.source(@source).day?(self, utc_time)
|
152
|
+
end
|
153
|
+
|
154
|
+
def sunny?(utc_time=Time.now.utc)
|
155
|
+
raise ArgumentError unless utc_time.is_a?(Time)
|
156
|
+
return false if self.day?(utc_time) == false
|
157
|
+
Barometer::Service.source(@source).sunny?(self, utc_time)
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Barometer
|
2
|
+
#
|
3
|
+
# A simple Pressure 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: the metric units for pressure aren't commonly used, except
|
12
|
+
# that this class was designed for storing weather data,
|
13
|
+
# and it seems that it is more common in this case
|
14
|
+
#
|
15
|
+
class Pressure < Barometer::Units
|
16
|
+
|
17
|
+
METRIC_UNITS = "mb"
|
18
|
+
IMPERIAL_UNITS = "in"
|
19
|
+
|
20
|
+
attr_accessor :millibars, :inches
|
21
|
+
|
22
|
+
def initialize(metric=true)
|
23
|
+
@millibars = nil
|
24
|
+
@inches = nil
|
25
|
+
super(metric)
|
26
|
+
end
|
27
|
+
|
28
|
+
def metric_default=(value); self.mb = value; end
|
29
|
+
def imperial_default=(value); self.in = value; end
|
30
|
+
|
31
|
+
#
|
32
|
+
# CONVERTERS
|
33
|
+
#
|
34
|
+
|
35
|
+
def self.mb_to_in(mb)
|
36
|
+
return nil unless mb && (mb.is_a?(Integer) || mb.is_a?(Float))
|
37
|
+
mb.to_f * 0.02953
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.in_to_mb(inches)
|
41
|
+
return nil unless inches &&
|
42
|
+
(inches.is_a?(Integer) || inches.is_a?(Float))
|
43
|
+
inches.to_f * 33.8639
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# ACCESSORS
|
48
|
+
#
|
49
|
+
|
50
|
+
# store millibars
|
51
|
+
def mb=(mb)
|
52
|
+
return if !mb || !(mb.is_a?(Integer) || mb.is_a?(Float))
|
53
|
+
@millibars = mb.to_f
|
54
|
+
self.update_inches(mb.to_f)
|
55
|
+
end
|
56
|
+
|
57
|
+
# store inches
|
58
|
+
def in=(inches)
|
59
|
+
return if !inches || !(inches.is_a?(Integer) || inches.is_a?(Float))
|
60
|
+
@inches = inches.to_f
|
61
|
+
self.update_millibars(inches.to_f)
|
62
|
+
end
|
63
|
+
|
64
|
+
# return the stored millibars or convert from inches
|
65
|
+
def mb(as_integer=true)
|
66
|
+
mb = (@millibars || Pressure.in_to_mb(@inches))
|
67
|
+
mb ? (as_integer ? mb.to_i : (100*mb).round/100.0) : nil
|
68
|
+
end
|
69
|
+
|
70
|
+
# return the stored inches or convert from millibars
|
71
|
+
def in(as_integer=true)
|
72
|
+
inches = (@inches || Pressure.mb_to_in(@millibars))
|
73
|
+
inches ? (as_integer ? inches.to_i : (100*inches).round/100.0) : nil
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# OPERATORS
|
78
|
+
#
|
79
|
+
|
80
|
+
def <=>(other)
|
81
|
+
self.mb <=> other.mb
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# HELPERS
|
86
|
+
#
|
87
|
+
|
88
|
+
# will just return the value (no units)
|
89
|
+
def to_i(metric=nil)
|
90
|
+
(metric || (metric.nil? && self.metric?)) ? self.mb : self.in
|
91
|
+
end
|
92
|
+
|
93
|
+
# will just return the value (no units) with more precision
|
94
|
+
def to_f(metric=nil)
|
95
|
+
(metric || (metric.nil? && self.metric?)) ? self.mb(false) : self.in(false)
|
96
|
+
end
|
97
|
+
|
98
|
+
# will return the value with units
|
99
|
+
def to_s(metric=nil)
|
100
|
+
(metric || (metric.nil? && self.metric?)) ? "#{self.mb} #{METRIC_UNITS}" : "#{self.in} #{IMPERIAL_UNITS}"
|
101
|
+
end
|
102
|
+
|
103
|
+
# will just return the units (no value)
|
104
|
+
def units(metric=nil)
|
105
|
+
(metric || (metric.nil? && self.metric?)) ? METRIC_UNITS : IMPERIAL_UNITS
|
106
|
+
end
|
107
|
+
|
108
|
+
# when we set inches, it is possible the a non-equivalent value of
|
109
|
+
# millibars remains. if so, clear it.
|
110
|
+
def update_millibars(inches)
|
111
|
+
return unless @millibars
|
112
|
+
difference = Pressure.in_to_mb(inches.to_f) - @millibars
|
113
|
+
# only clear millibars if the stored millibars is off be more then 1 unit
|
114
|
+
# then the conversion of inches
|
115
|
+
@millibars = nil unless difference.abs <= 1.0
|
116
|
+
end
|
117
|
+
|
118
|
+
# when we set millibars, it is possible the a non-equivalent value of
|
119
|
+
# inches remains. if so, clear it.
|
120
|
+
def update_inches(mb)
|
121
|
+
return unless @inches
|
122
|
+
difference = Pressure.mb_to_in(mb.to_f) - @inches
|
123
|
+
# only clear inches if the stored inches is off be more then 1 unit
|
124
|
+
# then the conversion of millibars
|
125
|
+
@inches = nil unless difference.abs <= 1.0
|
126
|
+
end
|
127
|
+
|
128
|
+
def nil?
|
129
|
+
(@millibars || @inches) ? false : true
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Barometer
|
2
|
+
#
|
3
|
+
# A simple Speed 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
|
+
# Speed is a vector, thus it has a perticular direction, although
|
10
|
+
# the direction is independent of the units
|
11
|
+
#
|
12
|
+
# All comparison operations will be done in metric
|
13
|
+
#
|
14
|
+
# NOTE: this currently only supports the scale of
|
15
|
+
# kilometers (km) and miles (m) per hour. There is currently
|
16
|
+
# no way to scale to smaller units (eg km -> m -> mm)
|
17
|
+
class Speed < Barometer::Units
|
18
|
+
|
19
|
+
METRIC_UNITS = "kph"
|
20
|
+
IMPERIAL_UNITS = "mph"
|
21
|
+
|
22
|
+
attr_accessor :kilometers, :miles
|
23
|
+
attr_accessor :degrees, :direction
|
24
|
+
|
25
|
+
def initialize(metric=true)
|
26
|
+
@kilometers = nil
|
27
|
+
@miles = nil
|
28
|
+
@degrees = nil
|
29
|
+
@direction = nil
|
30
|
+
super(metric)
|
31
|
+
end
|
32
|
+
|
33
|
+
def metric_default=(value); self.kph = value; end
|
34
|
+
def imperial_default=(value); self.mph = value; end
|
35
|
+
|
36
|
+
#
|
37
|
+
# CONVERTERS
|
38
|
+
#
|
39
|
+
|
40
|
+
def self.km_to_m(km)
|
41
|
+
return nil unless km && (km.is_a?(Integer) || km.is_a?(Float))
|
42
|
+
km.to_f * 0.622
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.m_to_km(m)
|
46
|
+
return nil unless m && (m.is_a?(Integer) || m.is_a?(Float))
|
47
|
+
m.to_f * 1.609
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# ACCESSORS
|
52
|
+
#
|
53
|
+
|
54
|
+
# store kilometers per hour
|
55
|
+
def kph=(kph)
|
56
|
+
return if !kph || !(kph.is_a?(Integer) || kph.is_a?(Float))
|
57
|
+
@kilometers = kph.to_f
|
58
|
+
self.update_miles(kph.to_f)
|
59
|
+
end
|
60
|
+
|
61
|
+
# store miles per hour
|
62
|
+
def mph=(mph)
|
63
|
+
return if !mph || !(mph.is_a?(Integer) || mph.is_a?(Float))
|
64
|
+
@miles = mph.to_f
|
65
|
+
self.update_kilometers(mph.to_f)
|
66
|
+
end
|
67
|
+
|
68
|
+
def direction=(direction)
|
69
|
+
return if !direction || !direction.is_a?(String)
|
70
|
+
@direction = direction
|
71
|
+
end
|
72
|
+
|
73
|
+
def degrees=(degrees)
|
74
|
+
return if !degrees || !(degrees.is_a?(Integer) || degrees.is_a?(Float))
|
75
|
+
@degrees = degrees
|
76
|
+
end
|
77
|
+
|
78
|
+
# return the stored kilometers or convert from miles
|
79
|
+
def kph(as_integer=true)
|
80
|
+
km = (@kilometers || Speed.m_to_km(@miles))
|
81
|
+
km ? (as_integer ? km.to_i : (100*km).round/100.0) : nil
|
82
|
+
end
|
83
|
+
|
84
|
+
# return the stored miles or convert from kilometers
|
85
|
+
def mph(as_integer=true)
|
86
|
+
m = (@miles || Speed.km_to_m(@kilometers))
|
87
|
+
m ? (as_integer ? m.to_i : (100*m).round/100.0) : nil
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# OPERATORS
|
92
|
+
#
|
93
|
+
|
94
|
+
def <=>(other)
|
95
|
+
self.kph <=> other.kph
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# HELPERS
|
100
|
+
#
|
101
|
+
|
102
|
+
# will just return the value (no units)
|
103
|
+
def to_i(metric=nil)
|
104
|
+
(metric || (metric.nil? && self.metric?)) ? self.kph : self.mph
|
105
|
+
end
|
106
|
+
|
107
|
+
# will just return the value (no units) with more precision
|
108
|
+
def to_f(metric=nil)
|
109
|
+
(metric || (metric.nil? && self.metric?)) ? self.kph(false) : self.mph(false)
|
110
|
+
end
|
111
|
+
|
112
|
+
# will return the value with units
|
113
|
+
def to_s(metric=nil)
|
114
|
+
(metric || (metric.nil? && self.metric?)) ? "#{self.kph} #{METRIC_UNITS}" : "#{self.mph} #{IMPERIAL_UNITS}"
|
115
|
+
end
|
116
|
+
|
117
|
+
# will just return the units (no value)
|
118
|
+
def units(metric=nil)
|
119
|
+
(metric || (metric.nil? && self.metric?)) ? METRIC_UNITS : IMPERIAL_UNITS
|
120
|
+
end
|
121
|
+
|
122
|
+
# when we set miles, it is possible the a non-equivalent value of
|
123
|
+
# kilometers remains. if so, clear it.
|
124
|
+
def update_kilometers(m)
|
125
|
+
return unless @kilometers
|
126
|
+
difference = Speed.m_to_km(m.to_f) - @kilometers
|
127
|
+
# only clear kilometers if the stored kilometers is off be more then 1 unit
|
128
|
+
# then the conversion of miles
|
129
|
+
@kilometers = nil unless difference.abs <= 1.0
|
130
|
+
end
|
131
|
+
|
132
|
+
# when we set kilometers, it is possible the a non-equivalent value of
|
133
|
+
# miles remains. if so, clear it.
|
134
|
+
def update_miles(km)
|
135
|
+
return unless @miles
|
136
|
+
difference = Speed.km_to_m(km.to_f) - @miles
|
137
|
+
# only clear miles if the stored miles is off be more then 1 unit
|
138
|
+
# then the conversion of kilometers
|
139
|
+
@miles = nil unless difference.abs <= 1.0
|
140
|
+
end
|
141
|
+
|
142
|
+
def nil?
|
143
|
+
(@kilometers || @miles) ? false : true
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Barometer
|
2
|
+
#
|
3
|
+
# A simple Sun class
|
4
|
+
#
|
5
|
+
# Used to store sunrise and sunset information
|
6
|
+
#
|
7
|
+
class Sun
|
8
|
+
|
9
|
+
def initialize(rise=nil, set=nil)
|
10
|
+
raise ArgumentError unless (rise.is_a?(Time) || rise.nil?)
|
11
|
+
raise ArgumentError unless (set.is_a?(Time) || set.nil?)
|
12
|
+
@rise_utc = rise
|
13
|
+
@set_utc = set
|
14
|
+
end
|
15
|
+
|
16
|
+
def rise; @rise_utc; end
|
17
|
+
def set; @set_utc; end
|
18
|
+
|
19
|
+
# useful for incrementing the sunrise and sunset times by exactly
|
20
|
+
# N days ... used when using the same sun data for other days
|
21
|
+
def self.add_days!(sun, n=1)
|
22
|
+
raise ArgumentError unless sun.is_a?(Barometer::Sun)
|
23
|
+
raise ArgumentError unless n.is_a?(Fixnum)
|
24
|
+
seconds_to_add = 60*60*24*n
|
25
|
+
rise_utc = sun.rise + seconds_to_add
|
26
|
+
set_utc = sun.set + seconds_to_add
|
27
|
+
self.new(rise_utc, set_utc)
|
28
|
+
end
|
29
|
+
|
30
|
+
def nil?
|
31
|
+
(@rise_utc || @set_utc) ? false : true
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|