barometer 0.3.2 → 0.5.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/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
|