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.
- 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
|