barometer 0.3.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +78 -70
- data/VERSION.yml +2 -2
- data/bin/barometer +100 -37
- data/lib/barometer.rb +12 -8
- data/lib/barometer/base.rb +48 -20
- data/lib/barometer/data.rb +5 -1
- data/lib/barometer/data/current.rb +23 -15
- data/lib/barometer/data/distance.rb +15 -5
- data/lib/barometer/data/forecast.rb +23 -5
- data/lib/barometer/data/geo.rb +16 -54
- data/lib/barometer/data/local_datetime.rb +137 -0
- data/lib/barometer/data/local_time.rb +134 -0
- data/lib/barometer/data/location.rb +6 -1
- data/lib/barometer/data/measurement.rb +71 -42
- data/lib/barometer/data/night.rb +69 -0
- data/lib/barometer/data/pressure.rb +15 -5
- data/lib/barometer/data/speed.rb +16 -5
- data/lib/barometer/data/sun.rb +8 -20
- data/lib/barometer/data/temperature.rb +22 -9
- data/lib/barometer/data/units.rb +10 -19
- data/lib/barometer/data/zone.rb +135 -9
- data/lib/barometer/formats.rb +12 -0
- data/lib/barometer/formats/coordinates.rb +42 -0
- data/lib/barometer/formats/format.rb +46 -0
- data/lib/barometer/formats/geocode.rb +51 -0
- data/lib/barometer/formats/icao.rb +37 -0
- data/lib/barometer/formats/postalcode.rb +22 -0
- data/lib/barometer/formats/short_zipcode.rb +17 -0
- data/lib/barometer/formats/weather_id.rb +107 -0
- data/lib/barometer/formats/zipcode.rb +31 -0
- data/lib/barometer/query.rb +61 -232
- data/lib/barometer/services.rb +14 -4
- data/lib/barometer/translations/icao_country_codes.yml +9 -0
- data/lib/barometer/translations/weather_country_codes.yml +17 -0
- data/lib/barometer/weather.rb +51 -30
- data/lib/barometer/{services → weather_services}/google.rb +23 -26
- data/lib/barometer/weather_services/noaa.rb +6 -0
- data/lib/barometer/{services → weather_services}/service.rb +101 -92
- data/lib/barometer/weather_services/weather_bug.rb +6 -0
- data/lib/barometer/weather_services/weather_dot_com.rb +261 -0
- data/lib/barometer/{services → weather_services}/wunderground.rb +58 -76
- data/lib/barometer/{services → weather_services}/yahoo.rb +91 -121
- data/lib/barometer/web_services/geocode.rb +33 -0
- data/lib/barometer/web_services/weather_id.rb +37 -0
- data/lib/barometer/web_services/web_service.rb +32 -0
- data/lib/demometer/demometer.rb +31 -4
- data/lib/demometer/views/forecast.erb +20 -0
- data/lib/demometer/views/index.erb +10 -3
- data/lib/demometer/views/measurement.erb +8 -3
- data/lib/demometer/views/readme.erb +63 -24
- data/spec/barometer_spec.rb +18 -36
- data/spec/{data_current_spec.rb → data/current_spec.rb} +73 -49
- data/spec/{data_distance_spec.rb → data/distance_spec.rb} +30 -30
- data/spec/{data_forecast_spec.rb → data/forecast_spec.rb} +57 -15
- data/spec/data/geo_spec.rb +91 -0
- data/spec/data/local_datetime_spec.rb +269 -0
- data/spec/data/local_time_spec.rb +239 -0
- data/spec/{data_location_spec.rb → data/location_spec.rb} +12 -1
- data/spec/{data_measurement_spec.rb → data/measurement_spec.rb} +135 -66
- data/spec/data/night_measurement_spec.rb +136 -0
- data/spec/{data_pressure_spec.rb → data/pressure_spec.rb} +29 -29
- data/spec/{data_speed_spec.rb → data/speed_spec.rb} +30 -30
- data/spec/data/sun_spec.rb +49 -0
- data/spec/{data_temperature_spec.rb → data/temperature_spec.rb} +44 -44
- data/spec/{units_spec.rb → data/units_spec.rb} +6 -6
- data/spec/{data_zone_spec.rb → data/zone_spec.rb} +15 -15
- data/spec/fixtures/formats/weather_id/90210.xml +1 -0
- data/spec/fixtures/formats/weather_id/atlanta.xml +1 -0
- data/spec/fixtures/formats/weather_id/from_USGA0028.xml +1 -0
- data/spec/fixtures/formats/weather_id/new_york.xml +1 -0
- data/spec/fixtures/{geocode_40_73.xml → geocode/40_73.xml} +0 -0
- data/spec/fixtures/{geocode_90210.xml → geocode/90210.xml} +0 -0
- data/spec/fixtures/{geocode_T5B4M9.xml → geocode/T5B4M9.xml} +0 -0
- data/spec/fixtures/geocode/atlanta.xml +1 -0
- data/spec/fixtures/{geocode_calgary_ab.xml → geocode/calgary_ab.xml} +0 -0
- data/spec/fixtures/{geocode_ksfo.xml → geocode/ksfo.xml} +0 -0
- data/spec/fixtures/{geocode_newyork_ny.xml → geocode/newyork_ny.xml} +0 -0
- data/spec/fixtures/{google_calgary_ab.xml → services/google/calgary_ab.xml} +0 -0
- data/spec/fixtures/services/weather_dot_com/90210.xml +1 -0
- data/spec/fixtures/{current_calgary_ab.xml → services/wunderground/current_calgary_ab.xml} +0 -0
- data/spec/fixtures/{forecast_calgary_ab.xml → services/wunderground/forecast_calgary_ab.xml} +0 -0
- data/spec/fixtures/{yahoo_90210.xml → services/yahoo/90210.xml} +0 -0
- data/spec/formats/coordinates_spec.rb +158 -0
- data/spec/formats/format_spec.rb +73 -0
- data/spec/formats/geocode_spec.rb +179 -0
- data/spec/formats/icao_spec.rb +61 -0
- data/spec/formats/postalcode_spec.rb +59 -0
- data/spec/formats/short_zipcode_spec.rb +53 -0
- data/spec/formats/weather_id_spec.rb +191 -0
- data/spec/formats/zipcode_spec.rb +111 -0
- data/spec/query_spec.rb +261 -288
- data/spec/spec_helper.rb +128 -4
- data/spec/{service_google_spec.rb → weather_services/google_spec.rb} +46 -46
- data/spec/weather_services/services_spec.rb +1118 -0
- data/spec/weather_services/weather_dot_com_spec.rb +327 -0
- data/spec/weather_services/wunderground_spec.rb +332 -0
- data/spec/{service_yahoo_spec.rb → weather_services/yahoo_spec.rb} +65 -81
- data/spec/weather_spec.rb +73 -61
- data/spec/web_services/geocode_spec.rb +45 -0
- data/spec/web_services/web_services_spec.rb +26 -0
- metadata +88 -36
- data/lib/barometer/services/noaa.rb +0 -6
- data/lib/barometer/services/weather_bug.rb +0 -6
- data/lib/barometer/services/weather_dot_com.rb +0 -6
- data/spec/data_geo_spec.rb +0 -94
- data/spec/data_sun_spec.rb +0 -76
- data/spec/service_wunderground_spec.rb +0 -330
- data/spec/services_spec.rb +0 -1106
@@ -0,0 +1,31 @@
|
|
1
|
+
module Barometer
|
2
|
+
#
|
3
|
+
# Format: Zip Code
|
4
|
+
#
|
5
|
+
# eg. 90210 or 90210-5555
|
6
|
+
#
|
7
|
+
# This class is used to determine if a query is a
|
8
|
+
# :zip_code, how to convert to a :zip_code
|
9
|
+
# and what the country_code is.
|
10
|
+
#
|
11
|
+
class Query::Format::Zipcode < Query::Format
|
12
|
+
|
13
|
+
def self.format; :zipcode; end
|
14
|
+
def self.country_code(query=nil); "US"; end
|
15
|
+
def self.regex; /(^[0-9]{5}$)|(^[0-9]{5}-[0-9]{4}$)/; end
|
16
|
+
def self.convertable_formats; [:short_zipcode]; end
|
17
|
+
|
18
|
+
# convert to this format, X -> :zipcode
|
19
|
+
#
|
20
|
+
def self.to(original_query)
|
21
|
+
raise ArgumentError unless is_a_query?(original_query)
|
22
|
+
return nil unless converts?(original_query)
|
23
|
+
converted_query = Barometer::Query.new
|
24
|
+
converted_query.q = original_query.q
|
25
|
+
converted_query.format = format
|
26
|
+
converted_query.country_code = country_code(converted_query.q)
|
27
|
+
converted_query
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/lib/barometer/query.rb
CHANGED
@@ -12,266 +12,95 @@ module Barometer
|
|
12
12
|
# certain formats, and only permit certain formats. The Query class
|
13
13
|
# will attempt to either return the query string as-is if acceptable,
|
14
14
|
# or it will attempt to convert it to a format that is acceptable
|
15
|
-
# (most likely this conversion will
|
15
|
+
# (most likely this conversion will use Googles geocoding service using
|
16
16
|
# the Graticule gem). Worst case scenario is that the Weather API will
|
17
17
|
# not accept the query string.
|
18
18
|
#
|
19
19
|
class Query
|
20
20
|
|
21
|
-
#
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
21
|
+
# This array defines the order to check a query for the format
|
22
|
+
#
|
23
|
+
FORMATS = %w(
|
24
|
+
ShortZipcode Zipcode Postalcode WeatherID Coordinates Icao Geocode
|
25
|
+
)
|
26
|
+
FORMAT_MAP = {
|
27
|
+
:short_zipcode => "ShortZipcode", :zipcode => "Zipcode",
|
28
|
+
:postalcode => "Postalcode", :weather_id => "WeatherID",
|
29
|
+
:coordinates => "Coordinates", :icao => "Icao",
|
30
|
+
:geocode => "Geocode"
|
31
|
+
}
|
32
|
+
|
33
|
+
attr_accessor :format, :q, :country_code, :geo
|
28
34
|
|
29
35
|
def initialize(query=nil)
|
36
|
+
return unless query
|
30
37
|
@q = query
|
31
38
|
self.analyze!
|
32
39
|
end
|
33
|
-
|
40
|
+
|
34
41
|
# analyze the saved query to determine the format.
|
42
|
+
# this delegates the detection to each formats class
|
43
|
+
# until th right one is found
|
44
|
+
#
|
35
45
|
def analyze!
|
36
46
|
return unless @q
|
37
|
-
|
38
|
-
@
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
elsif Barometer::Query.is_coordinates?(@q)
|
44
|
-
@format = :coordinates
|
45
|
-
elsif Barometer::Query.is_icao?(@q)
|
46
|
-
@format = :icao
|
47
|
-
# @country_code = Barometer::Query.icao_to_country_code(@q)
|
48
|
-
else
|
49
|
-
@format = :geocode
|
47
|
+
FORMATS.each do |format|
|
48
|
+
if Query::Format.const_get(format.to_s).is?(@q)
|
49
|
+
@format = Query::Format.const_get(format.to_s).format
|
50
|
+
@country_code = Query::Format.const_get(format.to_s).country_code(@q)
|
51
|
+
break
|
52
|
+
end
|
50
53
|
end
|
51
54
|
end
|
52
55
|
|
53
56
|
# take a list of acceptable (and ordered by preference) formats and convert
|
54
|
-
# the current query (q) into the most preferred and acceptable format.
|
55
|
-
# side effect of
|
57
|
+
# the current query (q) into the most preferred and acceptable format. a
|
58
|
+
# side effect of the conversions may reveal the country_code, if so save it
|
59
|
+
#
|
56
60
|
def convert!(preferred_formats=nil)
|
57
61
|
raise ArgumentError unless (preferred_formats && preferred_formats.size > 0)
|
58
|
-
# reset preferred
|
59
|
-
@preferred = nil
|
60
62
|
|
61
|
-
#
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
if preferred_format == @format
|
67
|
-
converted = true
|
68
|
-
@preferred ||= @q
|
69
|
-
end
|
70
|
-
|
71
|
-
unless converted
|
72
|
-
case preferred_format
|
73
|
-
when :coordinates
|
74
|
-
geocoded = true
|
75
|
-
@preferred, @country_code, @geo = Barometer::Query.to_coordinates(@q, @format)
|
76
|
-
when :geocode
|
77
|
-
geocoded = true
|
78
|
-
@preferred, @country_code, @geo = Barometer::Query.to_geocode(@q, @format)
|
79
|
-
end
|
80
|
-
end
|
63
|
+
# why convert if we are already there?
|
64
|
+
skip_conversion = false
|
65
|
+
if preferred_formats.include?(@format.to_sym)
|
66
|
+
skip_conversion = true
|
67
|
+
converted_query = self.dup
|
81
68
|
end
|
82
69
|
|
83
|
-
|
84
|
-
|
85
|
-
|
70
|
+
unless skip_conversion
|
71
|
+
# go through each acceptable format and try to convert to that
|
72
|
+
converted = false
|
73
|
+
converted_query = Barometer::Query.new
|
74
|
+
preferred_formats.each do |preferred_format|
|
75
|
+
klass = FORMAT_MAP[preferred_format.to_sym]
|
76
|
+
if preferred_format == @format
|
77
|
+
converted = true
|
78
|
+
converted_query = Barometer::Query.new(@q)
|
79
|
+
end
|
80
|
+
unless converted
|
81
|
+
converted_query = Query::Format.const_get(klass.to_s).to(self)
|
82
|
+
converted = true if converted_query
|
83
|
+
end
|
84
|
+
if converted
|
85
|
+
converted_query.country_code ||= Query::Format.const_get(klass.to_s).country_code(converted_query.q)
|
86
|
+
break
|
87
|
+
end
|
88
|
+
end
|
86
89
|
end
|
87
90
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
#
|
94
|
-
|
95
|
-
def zipcode?; @format == :zipcode; end
|
96
|
-
def postalcode?; @format == :postalcode; end
|
97
|
-
def coordinates?; @format == :coordinates; end
|
98
|
-
def geocode?; @format == :geocode; end
|
99
|
-
def icao?; @format == :icao; end
|
100
|
-
|
101
|
-
def self.is_us_zipcode?(query)
|
102
|
-
us_zipcode_regex = /(^[0-9]{5}$)|(^[0-9]{5}-[0-9]{4}$)/
|
103
|
-
return !(query =~ us_zipcode_regex).nil?
|
104
|
-
end
|
105
|
-
|
106
|
-
def self.is_canadian_postcode?(query)
|
107
|
-
# Rules: no D, F, I, O, Q, or U anywhere
|
108
|
-
# Basic validation: ^[ABCEGHJ-NPRSTVXY]{1}[0-9]{1}[ABCEGHJ-NPRSTV-Z]{1}[ ]?[0-9]{1}[ABCEGHJ-NPRSTV-Z]{1}[0-9]{1}$
|
109
|
-
# Extended validation: ^(A(0[ABCEGHJ-NPR]|1[ABCEGHK-NSV-Y]|2[ABHNV]|5[A]|8[A])|B(0[CEHJ-NPRSTVW]|1[ABCEGHJ-NPRSTV-Y]|2[ABCEGHJNRSTV-Z]|3[ABEGHJ-NPRSTVZ]|4[ABCEGHNPRV]|5[A]|6[L]|9[A])|C(0[AB]|1[ABCEN])|E(1[ABCEGHJNVWX]|2[AEGHJ-NPRSV]|3[ABCELNVYZ]|4[ABCEGHJ-NPRSTV-Z]|5[ABCEGHJ-NPRSTV]|6[ABCEGHJKL]|7[ABCEGHJ-NP]|8[ABCEGJ-NPRST]|9[ABCEGH])|G(0[ACEGHJ-NPRSTV-Z]|1[ABCEGHJ-NPRSTV-Y]|2[ABCEGJ-N]|3[ABCEGHJ-NZ]|4[ARSTVWXZ]|5[ABCHJLMNRTVXYZ]|6[ABCEGHJKLPRSTVWXZ]|7[ABGHJKNPSTXYZ]|8[ABCEGHJ-NPTVWYZ]|9[ABCHNPRTX])|H(0[HM]|1[ABCEGHJ-NPRSTV-Z]|2[ABCEGHJ-NPRSTV-Z]|3[ABCEGHJ-NPRSTV-Z]|4[ABCEGHJ-NPRSTV-Z]|5[AB]|7[ABCEGHJ-NPRSTV-Y]|8[NPRSTYZ]|9[ABCEGHJKPRSWX])|J(0[ABCEGHJ-NPRSTV-Z]|1[ACEGHJ-NRSTXZ]|2[ABCEGHJ-NRSTWXY]|3[ABEGHLMNPRTVXYZ]|4[BGHJ-NPRSTV-Z]|5[ABCJ-MRTV-Z]|6[AEJKNRSTVWYXZ]|7[ABCEGHJ-NPRTV-Z]|8[ABCEGHLMNPRTVXYZ]|9[ABEHJLNTVXYZ])|K(0[ABCEGHJ-M]|1[ABCEGHJ-NPRSTV-Z]|2[ABCEGHJ-MPRSTVW]|4[ABCKMPR]|6[AHJKTV]|7[ACGHK-NPRSV]|8[ABHNPRV]|9[AHJKLV])|L(0[[ABCEGHJ-NPRS]]|1[ABCEGHJ-NPRSTV-Z]|2[AEGHJMNPRSTVW]|3[BCKMPRSTVXYZ]|4[ABCEGHJ-NPRSTV-Z]|5[ABCEGHJ-NPRSTVW]|6[ABCEGHJ-MPRSTV-Z]|7[ABCEGJ-NPRST]|8[EGHJ-NPRSTVW]|9[ABCGHK-NPRSTVWYZ])|M(1[BCEGHJ-NPRSTVWX]|2[HJ-NPR]|3[ABCHJ-N]|4[ABCEGHJ-NPRSTV-Y]|5[ABCEGHJ-NPRSTVWX]|6[ABCEGHJ-NPRS]|7[AY]|8[V-Z]|9[ABCLMNPRVW])|N(0[ABCEGHJ-NPR]|1[ACEGHKLMPRST]|2[ABCEGHJ-NPRTVZ]|3[ABCEHLPRSTVWY]|4[BGKLNSTVWXZ]|5[ACHLPRV-Z]|6[ABCEGHJ-NP]|7[AGLMSTVWX]|8[AHMNPRSTV-Y]|9[ABCEGHJKVY])|P(0[ABCEGHJ-NPRSTV-Y]|1[ABCHLP]|2[ABN]|3[ABCEGLNPY]|4[NPR]|5[AEN]|6[ABC]|7[ABCEGJKL]|8[NT]|9[AN])|R(0[ABCEGHJ-M]|1[ABN]|2[CEGHJ-NPRV-Y]|3[ABCEGHJ-NPRSTV-Y]|4[AHJKL]|5[AGH]|6[MW]|7[ABCN]|8[AN]|9[A])|S(0[ACEGHJ-NP]|2[V]|3[N]|4[AHLNPRSTV-Z]|6[HJKVWX]|7[HJ-NPRSTVW]|9[AHVX])|T(0[ABCEGHJ-MPV]|1[ABCGHJ-MPRSV-Y]|2[ABCEGHJ-NPRSTV-Z]|3[ABCEGHJ-NPRZ]|4[ABCEGHJLNPRSTVX]|5[ABCEGHJ-NPRSTV-Z]|6[ABCEGHJ-NPRSTVWX]|7[AENPSVXYZ]|8[ABCEGHLNRSVWX]|9[ACEGHJKMNSVWX])|V(0[ABCEGHJ-NPRSTVWX]|1[ABCEGHJ-NPRSTV-Z]|2[ABCEGHJ-NPRSTV-Z]|3[ABCEGHJ-NRSTV-Y]|4[ABCEGK-NPRSTVWXZ]|5[ABCEGHJ-NPRSTV-Z]|6[ABCEGHJ-NPRSTV-Z]|7[ABCEGHJ-NPRSTV-Y]|8[ABCGJ-NPRSTV-Z]|9[ABCEGHJ-NPRSTV-Z])|X(0[ABCGX]|1[A])|Y(0[AB]|1[A]))[ ]?[0-9]{1}[ABCEGHJ-NPRSTV-Z]{1}[0-9]{1}$
|
110
|
-
ca_postcode_regex = /^[A-Z]{1}[\d]{1}[A-Z]{1}[ ]?[\d]{1}[A-Z]{1}[\d]{1}$/
|
111
|
-
return !(query =~ ca_postcode_regex).nil?
|
112
|
-
end
|
113
|
-
|
114
|
-
def self.is_coordinates?(query)
|
115
|
-
coordinates_regex = /^[-]?[0-9\.]+[,]{1}[-]?[0-9\.]+$/
|
116
|
-
return !(query =~ coordinates_regex).nil?
|
117
|
-
end
|
118
|
-
|
119
|
-
def self.is_icao?(query)
|
120
|
-
# allow any 3 or 4 letter word ... unfortunately this means some locations
|
121
|
-
# (ie Utah, Goa, Kiev, etc) will be detected as ICAO. This won't matter for
|
122
|
-
# returning weather results ... it will just effect what happens to the query.
|
123
|
-
# For example, wunderground will accept :icao above :coordinates and :geocode,
|
124
|
-
# which means that a city like Kiev would normally get converted to :coordinates
|
125
|
-
# but in this case it will be detected as :icao so it will be passed as is.
|
126
|
-
# Currently, only wunderground accepts ICAO, and they process ICAO the same as a
|
127
|
-
# city name, so it doesn't matter.
|
128
|
-
icao_regex = /^[A-Za-z]{3,4}$/
|
129
|
-
return !(query =~ icao_regex).nil?
|
130
|
-
end
|
131
|
-
|
132
|
-
#
|
133
|
-
# CONVERTERS
|
134
|
-
#
|
135
|
-
|
136
|
-
# this will take all query formats and convert them to coordinates
|
137
|
-
# accepts- :zipcode, :postalcode, :geocode, :icao
|
138
|
-
# returns- :coordinates
|
139
|
-
# if the conversion fails, return nil
|
140
|
-
def self.to_coordinates(query, format)
|
141
|
-
country_code = self.format_to_country_code(format)
|
142
|
-
geo = self.geocode(query, country_code)
|
143
|
-
country_code ||= geo.country_code if geo
|
144
|
-
return nil unless geo && geo.longitude && geo.latitude
|
145
|
-
["#{geo.latitude},#{geo.longitude}", country_code, geo]
|
146
|
-
end
|
147
|
-
|
148
|
-
# this will take all query formats and convert them to coorinates
|
149
|
-
# accepts- :zipcode, :postalcode, :coordinates, :icao
|
150
|
-
# returns- :geocode
|
151
|
-
def self.to_geocode(query, format)
|
152
|
-
perform_geocode = false
|
153
|
-
perform_geocode = true if self.has_geocode_key?
|
154
|
-
|
155
|
-
# some formats can't convert, no need to geocode then
|
156
|
-
skip_formats = [:postalcode]
|
157
|
-
perform_geocode = false if skip_formats.include?(format)
|
158
|
-
|
159
|
-
country_code = self.format_to_country_code(format)
|
160
|
-
if perform_geocode
|
161
|
-
geo = self.geocode(query, country_code)
|
162
|
-
country_code ||= geo.country_code if geo
|
163
|
-
# different formats have different acceptance criteria
|
164
|
-
q = nil
|
165
|
-
case format
|
166
|
-
when :icao
|
167
|
-
return nil unless geo && geo.address && geo.country
|
168
|
-
q = "#{geo.address}, #{geo.country}"
|
91
|
+
# force geocode?, unless we already did
|
92
|
+
#
|
93
|
+
if Barometer.force_geocode && !@geo
|
94
|
+
if converted_query && converted_query.geo
|
95
|
+
@geo = converted_query.geo
|
169
96
|
else
|
170
|
-
|
171
|
-
|
97
|
+
geo_query = Query::Format::Coordinates.to(converted_query)
|
98
|
+
@geo = geo_query.geo if (geo_query && geo_query.geo)
|
172
99
|
end
|
173
|
-
return [q, country_code, geo]
|
174
|
-
else
|
175
|
-
# without geocoding, the best we can do is just make use the given query as
|
176
|
-
# the query for the "geocode" format
|
177
|
-
return [query, country_code, nil]
|
178
100
|
end
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
#
|
183
|
-
# --- TODO ---
|
184
|
-
# The following methods need more coverage tests
|
185
|
-
#
|
186
|
-
|
187
|
-
def self.has_geocode_key?
|
188
|
-
# quick check to see that the Google API key exists for geocoding
|
189
|
-
self.google_geocode_key && !self.google_geocode_key.nil?
|
190
|
-
end
|
191
|
-
|
192
|
-
# if Graticule exists, use it, otherwise use HTTParty
|
193
|
-
def self.geocode(query, country_code=nil)
|
194
|
-
use_graticule = false
|
195
|
-
unless Barometer::skip_graticule
|
196
|
-
begin
|
197
|
-
require 'rubygems'
|
198
|
-
require 'graticule'
|
199
|
-
$:.unshift(File.dirname(__FILE__))
|
200
|
-
# load some changes to Graticule
|
201
|
-
# TODO: attempt to get changes into Graticule gem
|
202
|
-
require 'extensions/graticule'
|
203
|
-
use_graticule = true
|
204
|
-
rescue LoadError
|
205
|
-
# do nothing, we will use HTTParty
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
if use_graticule
|
210
|
-
geo = self.geocode_graticule(query, country_code)
|
211
|
-
else
|
212
|
-
geo = self.geocode_httparty(query, country_code)
|
213
|
-
end
|
214
|
-
geo
|
215
|
-
end
|
216
|
-
|
217
|
-
def self.geocode_graticule(query, country_code=nil)
|
218
|
-
return nil unless self.has_geocode_key?
|
219
|
-
geocoder = Graticule.service(:google).new(self.google_geocode_key)
|
220
|
-
location = geocoder.locate(query, country_code)
|
221
|
-
geo = Barometer::Geo.new(location)
|
222
|
-
end
|
223
|
-
|
224
|
-
def self.geocode_httparty(query, country_code=nil)
|
225
|
-
return nil unless self.has_geocode_key?
|
226
|
-
location = Barometer::Service.get(
|
227
|
-
"http://maps.google.com/maps/geo",
|
228
|
-
:query => {
|
229
|
-
:gl => country_code,
|
230
|
-
:key => self.google_geocode_key,
|
231
|
-
:output => "xml",
|
232
|
-
:q => query
|
233
|
-
},
|
234
|
-
:format => :xml
|
235
|
-
)['kml']['Response']
|
236
|
-
geo = Barometer::Geo.new(location)
|
237
|
-
end
|
238
|
-
|
239
|
-
def self.format_to_country_code(format)
|
240
|
-
return nil unless format
|
241
|
-
case format
|
242
|
-
when :zipcode
|
243
|
-
country_code = "US"
|
244
|
-
when :postalcode
|
245
|
-
country_code = "CA"
|
246
|
-
else
|
247
|
-
country_code = nil
|
248
|
-
end
|
249
|
-
country_code
|
101
|
+
|
102
|
+
converted_query
|
250
103
|
end
|
251
104
|
|
252
|
-
# todo, the fist letter in a 4-letter icao can designate country:
|
253
|
-
# c=canada
|
254
|
-
# k=usa
|
255
|
-
# etc...
|
256
|
-
# def self.icao_to_country_code(icao_code)
|
257
|
-
# return unless icao_code.is_a?(String)
|
258
|
-
# country_code = nil
|
259
|
-
# if icao_code.size == 4
|
260
|
-
# case icao_code.first_letter
|
261
|
-
# when "C"
|
262
|
-
# country_code = "CA"
|
263
|
-
# when "K"
|
264
|
-
# country_code = "US"
|
265
|
-
# end
|
266
|
-
# if coutry_code.nil?
|
267
|
-
# case icao_code.first_two_letters
|
268
|
-
# when "ET"
|
269
|
-
# country_code = "GERMANY"
|
270
|
-
# end
|
271
|
-
# end
|
272
|
-
# end
|
273
|
-
# country_code
|
274
|
-
# end
|
275
|
-
|
276
105
|
end
|
277
106
|
end
|
data/lib/barometer/services.rb
CHANGED
@@ -1,6 +1,16 @@
|
|
1
1
|
$:.unshift(File.dirname(__FILE__))
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
require '
|
6
|
-
require '
|
3
|
+
# weather services
|
4
|
+
#
|
5
|
+
require 'weather_services/service'
|
6
|
+
require 'weather_services/wunderground'
|
7
|
+
require 'weather_services/google'
|
8
|
+
require 'weather_services/yahoo'
|
9
|
+
require 'weather_services/weather_dot_com'
|
10
|
+
|
11
|
+
#
|
12
|
+
# web services (non weather)
|
13
|
+
#
|
14
|
+
require 'web_services/web_service'
|
15
|
+
require 'web_services/geocode'
|
16
|
+
require 'web_services/weather_id'
|
data/lib/barometer/weather.rb
CHANGED
@@ -1,22 +1,25 @@
|
|
1
1
|
module Barometer
|
2
|
-
|
2
|
+
#
|
3
|
+
# Weather
|
4
|
+
#
|
5
|
+
# holds all the measurements taken and provdes
|
6
|
+
# methods to interact with the data
|
7
|
+
#
|
3
8
|
class Weather
|
4
9
|
|
5
|
-
# hash of measurements indexed by :source
|
6
10
|
attr_accessor :measurements
|
7
11
|
|
8
|
-
def initialize
|
9
|
-
@measurements = []
|
10
|
-
end
|
12
|
+
def initialize; @measurements = []; end
|
11
13
|
|
12
|
-
# the default
|
13
|
-
#
|
14
|
+
# the default measurement is the first successful measurement
|
15
|
+
#
|
14
16
|
def default
|
15
17
|
return nil unless self.sources
|
16
18
|
self.source(self.sources.first)
|
17
19
|
end
|
18
20
|
|
19
21
|
# find the measurement for the given source, if it exists
|
22
|
+
#
|
20
23
|
def source(source)
|
21
24
|
raise ArgumentError unless (source.is_a?(String) || source.is_a?(Symbol))
|
22
25
|
@measurements.each do |measurement|
|
@@ -26,6 +29,7 @@ module Barometer
|
|
26
29
|
end
|
27
30
|
|
28
31
|
# list successful sources
|
32
|
+
#
|
29
33
|
def sources
|
30
34
|
@measurements.collect {|m| m.source.to_sym if m.success?}.compact
|
31
35
|
end
|
@@ -34,6 +38,7 @@ module Barometer
|
|
34
38
|
# Quick access methods
|
35
39
|
#
|
36
40
|
|
41
|
+
def metric?; self.default ? self.default.metric? : true; end
|
37
42
|
def current; (default = self.default) ? default.current : nil; end
|
38
43
|
def forecast; (default = self.default) ? default.forecast : nil; end
|
39
44
|
def now; self.current; end
|
@@ -48,11 +53,16 @@ module Barometer
|
|
48
53
|
default && default.forecast ? default.forecast[1] : nil
|
49
54
|
end
|
50
55
|
|
56
|
+
# measurement search
|
57
|
+
# this will search the default measurements forecasts looking for
|
58
|
+
# the matching date
|
59
|
+
#
|
51
60
|
def for(query)
|
52
61
|
default = self.default
|
53
62
|
default && default.forecast ? default.for(query) : nil
|
54
63
|
end
|
55
64
|
|
65
|
+
|
56
66
|
#
|
57
67
|
# helper methods
|
58
68
|
#
|
@@ -64,18 +74,23 @@ module Barometer
|
|
64
74
|
# averages
|
65
75
|
#
|
66
76
|
|
67
|
-
def metric?
|
68
|
-
self.default ? self.default.metric? : true
|
69
|
-
end
|
70
|
-
|
71
77
|
# this assumes calculating for current, and that "to_f" for a value
|
72
78
|
# will return the value needed
|
73
79
|
# value_name = the name of the value we are averaging
|
80
|
+
# if a measurement has weighting, it will respect that
|
81
|
+
#
|
74
82
|
def current_average(value_name)
|
75
83
|
values = []
|
76
84
|
@measurements.each do |measurement|
|
77
|
-
|
78
|
-
measurement.
|
85
|
+
if measurement.weight && measurement.weight > 1
|
86
|
+
measurement.weight.times do
|
87
|
+
values << measurement.current.send(value_name).to_f if measurement.success? &&
|
88
|
+
measurement.current.send(value_name)
|
89
|
+
end
|
90
|
+
else
|
91
|
+
values << measurement.current.send(value_name).to_f if measurement.success? &&
|
92
|
+
measurement.current.send(value_name)
|
93
|
+
end
|
79
94
|
end
|
80
95
|
values.compact!
|
81
96
|
return nil unless values && values.size > 0
|
@@ -85,7 +100,7 @@ module Barometer
|
|
85
100
|
def average(value_name, do_average=true, class_name=nil)
|
86
101
|
if class_name
|
87
102
|
if do_average
|
88
|
-
avg =
|
103
|
+
avg = Data.const_get(class_name).new(self.metric?)
|
89
104
|
avg << self.current_average(value_name)
|
90
105
|
else
|
91
106
|
avg = self.now.send(value_name)
|
@@ -97,6 +112,7 @@ module Barometer
|
|
97
112
|
end
|
98
113
|
|
99
114
|
# average of all values
|
115
|
+
#
|
100
116
|
def humidity(do_average=true); average("humidity",do_average); end
|
101
117
|
def temperature(do_average=true); average("temperature",do_average,"Temperature"); end
|
102
118
|
def wind(do_average=true); average("wind",do_average,"Speed"); end
|
@@ -123,61 +139,66 @@ module Barometer
|
|
123
139
|
# pass the question on to each successful measurement until we get an answer
|
124
140
|
#
|
125
141
|
|
126
|
-
def windy?(threshold=10,
|
142
|
+
def windy?(threshold=10, time_string=nil)
|
143
|
+
local_datetime = Data::LocalDateTime.parse(time_string)
|
127
144
|
raise ArgumentError unless (threshold.is_a?(Fixnum) || threshold.is_a?(Float))
|
128
|
-
raise ArgumentError unless
|
145
|
+
raise ArgumentError unless (local_datetime.nil? || local_datetime.is_a?(Data::LocalDateTime))
|
129
146
|
|
130
147
|
is_windy = nil
|
131
148
|
@measurements.each do |measurement|
|
132
149
|
if measurement.success?
|
133
|
-
is_windy = measurement.windy?(threshold,
|
150
|
+
is_windy = measurement.windy?(threshold, local_datetime)
|
134
151
|
return is_windy if !is_windy.nil?
|
135
152
|
end
|
136
153
|
end
|
137
154
|
is_windy
|
138
155
|
end
|
139
156
|
|
140
|
-
def wet?(threshold=50,
|
157
|
+
def wet?(threshold=50, time_string=nil)
|
158
|
+
local_datetime = Data::LocalDateTime.parse(time_string)
|
141
159
|
raise ArgumentError unless (threshold.is_a?(Fixnum) || threshold.is_a?(Float))
|
142
|
-
raise ArgumentError unless
|
160
|
+
raise ArgumentError unless (local_datetime.nil? || local_datetime.is_a?(Data::LocalDateTime))
|
143
161
|
|
144
162
|
is_wet = nil
|
145
163
|
@measurements.each do |measurement|
|
146
164
|
if measurement.success?
|
147
|
-
is_wet = measurement.wet?(threshold,
|
165
|
+
is_wet = measurement.wet?(threshold, local_datetime)
|
148
166
|
return is_wet if !is_wet.nil?
|
149
167
|
end
|
150
168
|
end
|
151
169
|
is_wet
|
152
170
|
end
|
153
171
|
|
154
|
-
def day?(
|
155
|
-
|
172
|
+
def day?(time_string=nil)
|
173
|
+
local_datetime = Data::LocalDateTime.parse(time_string)
|
174
|
+
raise ArgumentError unless (local_datetime.nil? || local_datetime.is_a?(Data::LocalDateTime))
|
156
175
|
|
157
176
|
is_day = nil
|
158
177
|
@measurements.each do |measurement|
|
159
178
|
if measurement.success?
|
160
|
-
is_day = measurement.day?(
|
179
|
+
is_day = measurement.day?(local_datetime)
|
161
180
|
return is_day if !is_day.nil?
|
162
181
|
end
|
163
182
|
end
|
164
183
|
is_day
|
165
184
|
end
|
166
185
|
|
167
|
-
def night?(
|
168
|
-
|
169
|
-
|
186
|
+
def night?(time_string=nil)
|
187
|
+
local_datetime = Data::LocalDateTime.parse(time_string)
|
188
|
+
raise ArgumentError unless (local_datetime.nil? || local_datetime.is_a?(Data::LocalDateTime))
|
189
|
+
is_day = self.day?(local_datetime)
|
170
190
|
is_day.nil? ? nil : !is_day
|
171
191
|
end
|
172
192
|
|
173
|
-
def sunny?(
|
174
|
-
|
193
|
+
def sunny?(time_string=nil)
|
194
|
+
local_datetime = Data::LocalDateTime.parse(time_string)
|
195
|
+
raise ArgumentError unless (local_datetime.nil? || local_datetime.is_a?(Data::LocalDateTime))
|
175
196
|
|
176
197
|
is_sunny = nil
|
177
198
|
@measurements.each do |measurement|
|
178
199
|
if measurement.success?
|
179
|
-
return false if self.day?(
|
180
|
-
is_sunny = measurement.sunny?(
|
200
|
+
return false if self.day?(local_datetime) == false
|
201
|
+
is_sunny = measurement.sunny?(local_datetime)
|
181
202
|
return is_sunny if !is_sunny.nil?
|
182
203
|
end
|
183
204
|
end
|