barometer 0.3.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. data/README.rdoc +78 -70
  2. data/VERSION.yml +2 -2
  3. data/bin/barometer +100 -37
  4. data/lib/barometer.rb +12 -8
  5. data/lib/barometer/base.rb +48 -20
  6. data/lib/barometer/data.rb +5 -1
  7. data/lib/barometer/data/current.rb +23 -15
  8. data/lib/barometer/data/distance.rb +15 -5
  9. data/lib/barometer/data/forecast.rb +23 -5
  10. data/lib/barometer/data/geo.rb +16 -54
  11. data/lib/barometer/data/local_datetime.rb +137 -0
  12. data/lib/barometer/data/local_time.rb +134 -0
  13. data/lib/barometer/data/location.rb +6 -1
  14. data/lib/barometer/data/measurement.rb +71 -42
  15. data/lib/barometer/data/night.rb +69 -0
  16. data/lib/barometer/data/pressure.rb +15 -5
  17. data/lib/barometer/data/speed.rb +16 -5
  18. data/lib/barometer/data/sun.rb +8 -20
  19. data/lib/barometer/data/temperature.rb +22 -9
  20. data/lib/barometer/data/units.rb +10 -19
  21. data/lib/barometer/data/zone.rb +135 -9
  22. data/lib/barometer/formats.rb +12 -0
  23. data/lib/barometer/formats/coordinates.rb +42 -0
  24. data/lib/barometer/formats/format.rb +46 -0
  25. data/lib/barometer/formats/geocode.rb +51 -0
  26. data/lib/barometer/formats/icao.rb +37 -0
  27. data/lib/barometer/formats/postalcode.rb +22 -0
  28. data/lib/barometer/formats/short_zipcode.rb +17 -0
  29. data/lib/barometer/formats/weather_id.rb +107 -0
  30. data/lib/barometer/formats/zipcode.rb +31 -0
  31. data/lib/barometer/query.rb +61 -232
  32. data/lib/barometer/services.rb +14 -4
  33. data/lib/barometer/translations/icao_country_codes.yml +9 -0
  34. data/lib/barometer/translations/weather_country_codes.yml +17 -0
  35. data/lib/barometer/weather.rb +51 -30
  36. data/lib/barometer/{services → weather_services}/google.rb +23 -26
  37. data/lib/barometer/weather_services/noaa.rb +6 -0
  38. data/lib/barometer/{services → weather_services}/service.rb +101 -92
  39. data/lib/barometer/weather_services/weather_bug.rb +6 -0
  40. data/lib/barometer/weather_services/weather_dot_com.rb +261 -0
  41. data/lib/barometer/{services → weather_services}/wunderground.rb +58 -76
  42. data/lib/barometer/{services → weather_services}/yahoo.rb +91 -121
  43. data/lib/barometer/web_services/geocode.rb +33 -0
  44. data/lib/barometer/web_services/weather_id.rb +37 -0
  45. data/lib/barometer/web_services/web_service.rb +32 -0
  46. data/lib/demometer/demometer.rb +31 -4
  47. data/lib/demometer/views/forecast.erb +20 -0
  48. data/lib/demometer/views/index.erb +10 -3
  49. data/lib/demometer/views/measurement.erb +8 -3
  50. data/lib/demometer/views/readme.erb +63 -24
  51. data/spec/barometer_spec.rb +18 -36
  52. data/spec/{data_current_spec.rb → data/current_spec.rb} +73 -49
  53. data/spec/{data_distance_spec.rb → data/distance_spec.rb} +30 -30
  54. data/spec/{data_forecast_spec.rb → data/forecast_spec.rb} +57 -15
  55. data/spec/data/geo_spec.rb +91 -0
  56. data/spec/data/local_datetime_spec.rb +269 -0
  57. data/spec/data/local_time_spec.rb +239 -0
  58. data/spec/{data_location_spec.rb → data/location_spec.rb} +12 -1
  59. data/spec/{data_measurement_spec.rb → data/measurement_spec.rb} +135 -66
  60. data/spec/data/night_measurement_spec.rb +136 -0
  61. data/spec/{data_pressure_spec.rb → data/pressure_spec.rb} +29 -29
  62. data/spec/{data_speed_spec.rb → data/speed_spec.rb} +30 -30
  63. data/spec/data/sun_spec.rb +49 -0
  64. data/spec/{data_temperature_spec.rb → data/temperature_spec.rb} +44 -44
  65. data/spec/{units_spec.rb → data/units_spec.rb} +6 -6
  66. data/spec/{data_zone_spec.rb → data/zone_spec.rb} +15 -15
  67. data/spec/fixtures/formats/weather_id/90210.xml +1 -0
  68. data/spec/fixtures/formats/weather_id/atlanta.xml +1 -0
  69. data/spec/fixtures/formats/weather_id/from_USGA0028.xml +1 -0
  70. data/spec/fixtures/formats/weather_id/new_york.xml +1 -0
  71. data/spec/fixtures/{geocode_40_73.xml → geocode/40_73.xml} +0 -0
  72. data/spec/fixtures/{geocode_90210.xml → geocode/90210.xml} +0 -0
  73. data/spec/fixtures/{geocode_T5B4M9.xml → geocode/T5B4M9.xml} +0 -0
  74. data/spec/fixtures/geocode/atlanta.xml +1 -0
  75. data/spec/fixtures/{geocode_calgary_ab.xml → geocode/calgary_ab.xml} +0 -0
  76. data/spec/fixtures/{geocode_ksfo.xml → geocode/ksfo.xml} +0 -0
  77. data/spec/fixtures/{geocode_newyork_ny.xml → geocode/newyork_ny.xml} +0 -0
  78. data/spec/fixtures/{google_calgary_ab.xml → services/google/calgary_ab.xml} +0 -0
  79. data/spec/fixtures/services/weather_dot_com/90210.xml +1 -0
  80. data/spec/fixtures/{current_calgary_ab.xml → services/wunderground/current_calgary_ab.xml} +0 -0
  81. data/spec/fixtures/{forecast_calgary_ab.xml → services/wunderground/forecast_calgary_ab.xml} +0 -0
  82. data/spec/fixtures/{yahoo_90210.xml → services/yahoo/90210.xml} +0 -0
  83. data/spec/formats/coordinates_spec.rb +158 -0
  84. data/spec/formats/format_spec.rb +73 -0
  85. data/spec/formats/geocode_spec.rb +179 -0
  86. data/spec/formats/icao_spec.rb +61 -0
  87. data/spec/formats/postalcode_spec.rb +59 -0
  88. data/spec/formats/short_zipcode_spec.rb +53 -0
  89. data/spec/formats/weather_id_spec.rb +191 -0
  90. data/spec/formats/zipcode_spec.rb +111 -0
  91. data/spec/query_spec.rb +261 -288
  92. data/spec/spec_helper.rb +128 -4
  93. data/spec/{service_google_spec.rb → weather_services/google_spec.rb} +46 -46
  94. data/spec/weather_services/services_spec.rb +1118 -0
  95. data/spec/weather_services/weather_dot_com_spec.rb +327 -0
  96. data/spec/weather_services/wunderground_spec.rb +332 -0
  97. data/spec/{service_yahoo_spec.rb → weather_services/yahoo_spec.rb} +65 -81
  98. data/spec/weather_spec.rb +73 -61
  99. data/spec/web_services/geocode_spec.rb +45 -0
  100. data/spec/web_services/web_services_spec.rb +26 -0
  101. metadata +88 -36
  102. data/lib/barometer/services/noaa.rb +0 -6
  103. data/lib/barometer/services/weather_bug.rb +0 -6
  104. data/lib/barometer/services/weather_dot_com.rb +0 -6
  105. data/spec/data_geo_spec.rb +0 -94
  106. data/spec/data_sun_spec.rb +0 -76
  107. data/spec/service_wunderground_spec.rb +0 -330
  108. 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
@@ -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 in Googles geocoding service using
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
- # OPTIONAL: key required by Google for geocoding
22
- @@google_geocode_key = nil
23
- def self.google_geocode_key; @@google_geocode_key || Barometer.google_geocode_key; end;
24
- def self.google_geocode_key=(key); @@google_geocode_key = key; end;
25
-
26
- attr_reader :format
27
- attr_accessor :q, :preferred, :country_code, :geo
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
- if Barometer::Query.is_us_zipcode?(@q)
38
- @format = :zipcode
39
- @country_code = Barometer::Query.format_to_country_code(@format)
40
- elsif Barometer::Query.is_canadian_postcode?(@q)
41
- @format = :postalcode
42
- @country_code = Barometer::Query.format_to_country_code(@format)
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. as a
55
- # side effect of some conversions, the country_code might be known, then save it
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
- # go through each acceptable format and try to convert to that
62
- converted = false
63
- geocoded = false
64
- preferred_formats.each do |preferred_format|
65
- # we are already in this format, return this
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
- # if we haven't already geocoded and we are forcing it, do it now
84
- if !geocoded && Barometer.force_geocode
85
- not_used_coords, not_used_code, @geo = Barometer::Query.to_coordinates(@q, @format)
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
- @preferred
89
- end
90
-
91
- #
92
- # HELPERS
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
- return nil unless geo && geo.locality && geo.region && geo.country
171
- q = "#{geo.locality}, #{geo.region}, #{geo.country}"
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
- return nil
180
- end
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
@@ -1,6 +1,16 @@
1
1
  $:.unshift(File.dirname(__FILE__))
2
2
 
3
- require 'services/service'
4
- require 'services/wunderground'
5
- require 'services/google'
6
- require 'services/yahoo'
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'
@@ -0,0 +1,9 @@
1
+ # first term - icao code
2
+ # second term - standard country_code
3
+ #
4
+ one_letter:
5
+ C : CA
6
+ K : US
7
+
8
+ two_letter:
9
+ ET : DE
@@ -0,0 +1,17 @@
1
+ # first term - weather.com non-standard country_code
2
+ # second term - standard country_code
3
+ #
4
+ AS : AU
5
+ BF : BS
6
+ CS : CR
7
+ CH : CN
8
+ CI : CL
9
+ EI : IE
10
+ GM : DE
11
+ HO : HN
12
+ IS : IL
13
+ JA : JP
14
+ PO : PT
15
+ SP : ES
16
+ SN : SG
17
+
@@ -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 source is the first source/measurement that we
13
- # have successful results for
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
- values << measurement.current.send(value_name).to_f if measurement.success? &&
78
- measurement.current.send(value_name)
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 = Barometer.const_get(class_name).new(self.metric?)
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, utc_time=Time.now.utc)
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 utc_time.is_a?(Time)
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, utc_time)
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, utc_time=Time.now.utc)
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 utc_time.is_a?(Time)
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, utc_time)
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?(utc_time=Time.now.utc)
155
- raise ArgumentError unless utc_time.is_a?(Time)
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?(utc_time)
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?(utc_time=Time.now.utc)
168
- raise ArgumentError unless utc_time.is_a?(Time)
169
- is_day = self.day?(utc_time)
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?(utc_time=Time.now.utc)
174
- raise ArgumentError unless utc_time.is_a?(Time)
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?(utc_time) == false
180
- is_sunny = measurement.sunny?(utc_time)
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