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