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
@@ -6,9 +6,10 @@ module Barometer
6
6
  # A simple Zone class
7
7
  #
8
8
  # Used for building and converting timezone aware date and times
9
- # Really, these are just wrappers for TZInfo conversions.
9
+ # Really, these are just wrappers for TZInfo conversions plus
10
+ # some extras.
10
11
  #
11
- class Zone
12
+ class Data::Zone
12
13
 
13
14
  attr_accessor :timezone, :tz
14
15
 
@@ -31,12 +32,12 @@ module Barometer
31
32
 
32
33
  # return Time.now.utc for the set timezone
33
34
  def now
34
- Barometer::Zone.now(@timezone)
35
+ Data::Zone.now(@timezone)
35
36
  end
36
37
 
37
38
  # return Date.today for the set timezone
38
39
  def today
39
- Barometer::Zone.today(@timezone)
40
+ Data::Zone.today(@timezone)
40
41
  end
41
42
 
42
43
  def local_to_utc(local_time)
@@ -78,12 +79,14 @@ module Barometer
78
79
  end
79
80
  end
80
81
 
81
- # takes a time (any timezone), and a TimeZone Short Code (ie PST) and
82
- # converts the time to UTC accorsing to that time_zone
82
+ # takes a time (any timezone), and a TimeZone Short Code (ie PST) or
83
+ # the number of hours offset from UTC time and
84
+ # converts the time to UTC according to that time_zone
83
85
  # NOTE: No Tests
84
- def self.code_to_utc(time, timezone_code)
86
+ def self.code_to_utc(time, timezone)
85
87
  raise ArgumentError unless time.is_a?(Time)
86
- offset = Time.zone_offset(timezone_code) || 0
88
+
89
+ offset = self.zone_to_offset(timezone)
87
90
 
88
91
  Time.utc(
89
92
  time.year, time.month, time.day,
@@ -117,7 +120,130 @@ module Barometer
117
120
  reference_date.year, reference_date.month, reference_date.day,
118
121
  reference_time.hour, reference_time.min, reference_time.sec
119
122
  )
120
- timezone_code ? Barometer::Zone.code_to_utc(new_time,timezone_code) : new_time
123
+ timezone_code ? Data::Zone.code_to_utc(new_time,timezone_code) : new_time
124
+ end
125
+
126
+ #
127
+ # Known conflicts
128
+ # IRT (ireland and india)
129
+ # CST (central standard time, china standard time)
130
+ #
131
+ def self.zone_to_offset(timezone)
132
+ offset = 0
133
+ seconds_in_hour = 60*60
134
+ # do we have a short_timezone code, or an offset?
135
+ if timezone.is_a?(Fixnum) ||
136
+ (timezone.is_a?(String) && timezone.to_i.to_s == timezone)
137
+ # we have an offset, convert to second
138
+ offset = timezone.to_i * seconds_in_hour
139
+ else
140
+ # try to use Time
141
+ unless offset = Time.zone_offset(timezone)
142
+ # that would have been too easy, do it manually
143
+ # http://www.timeanddate.com/library/abbreviations/timezones/
144
+ # http://www.worldtimezone.com/wtz-names/timezonenames.html
145
+ zone_offsets = {
146
+ :A => 1, :ACDT => 10.5, :ACST => 9.5, :ADT => -3, :AEDT => 11,
147
+ :AEST => 10, :AFT => 4.5, :AHDT => -9, :AHST => -10, :AKDT => -8,
148
+ :AKST => -9, :AMST => 5, :AMT => 4, :ANAST => 13, :ANAT => 12,
149
+ :ART => -3, :AST => -4, :AT => -1, :AWDT => 9, :AWST => 8,
150
+ :AZOST => 0, :AZOT => -1, :AZST => 5, :AZT => 4,
151
+
152
+ :B => 2, :BADT => 4, :BAT => 6, :BDST => 2, :BDT => 6, :BET => -11,
153
+ :BNT => 8, :BORT => 8, :BOT => -4, :BRA => -3, :BST => 1, :BT => 6,
154
+ :BTT => 6,
155
+
156
+ :C => 3, :CAT => 2, :CCT => 8, :CEST => 2, :CET => 1, :CHADT => 13.75,
157
+ :CHAST => 12.75, :CHST => 10, :CKT => -10, :CLST => -3, :CLT => -4,
158
+ :COT => -5, :CUT => 0, :CVT => -1, :CWT => 8.75, :CXT => 7, :CEDT => 2,
159
+
160
+ :D => 4, :DAVT => 7, :DDUT => 10, :DNT => 1, :DST => 2,
161
+
162
+ :E => 5, :EASST => -5, :EAST => -6, :EAT => 3, :ECT => -5, :EEST => 3,
163
+ :EET => 2, :EGST => 0, :EGT => -1, :EMT => 1, :EEDT => 3,
164
+
165
+ :F => 6, :FDT => -1, :FJST => 13, :FJT => 12, :FKST => -3, :FKT => -4,
166
+ :FST => 2, :FWT => 1,
167
+
168
+ :G => 7, :GALT => -6, :GAMT => -9, :GEST => 5, :GET => 4, :GFT => -3,
169
+ :GILT => 12, :GST => 4, :GT => 0, :GYT => -4, :GZ => 0,
170
+
171
+ :H => 8, :HAA => -3, :HAC => -5, :HADT => -9, :HAE => -4, :HAP => -7,
172
+ :HAR => -6, :HAST => -10, :HAT => -2.5, :HAY => -8, :HDT => -9.5,
173
+ :HFE => 2, :HFH => 1, :HG => 0, :HKT => 8, :HNA => -4, :HNC => -6,
174
+ :HNE => -5, :HNP => -8, :HNR => -7, :HNT => -3.5, :HNY => -9,
175
+ :HOE => 1, :HST => -10,
176
+
177
+ :I => 9, :ICT => 4, :IDLE => 12, :IDLW => -12, :IDT => 1, :IOT => 5,
178
+ :IRDT => 4.5, :IRKST => 9, :IRKT => 8, :IRST => 3.5, :IRT => 3.5,
179
+ :IST => 1, :IT => 3.5, :ITA => 1,
180
+
181
+ :JAVT => 7, :JAYT => 9, :JFDT => -3, :JFST => -4, :JST => 9, :JT => 7,
182
+
183
+ :K => 10, :KDT => 10, :KGST => 6, :KGT => 5, :KOST => 12, :KOVT => 7,
184
+ :KOVST => 8, :KRAST => 8, :KRAT => 7, :KST => 9,
185
+
186
+ :L => 11, :LHDT => 11, :LHST => 10.5, :LIGT => 10, :LINT => 14, :LKT => 6,
187
+ :LST => 1,
188
+
189
+ :M => 12, :MAGST => 12, :MAGT => 11, :MAL => 8, :MART => -9.5, :MAT => 3,
190
+ :MAWT => 6, :MBT => 8, :MED => 2, :MEDST => 2, :MEST => 2, :MESZ => 2,
191
+ :MET => 1, :MEWT => 1, :MEX => -6, :MEZ => 1, :MHT => 12, :MIT => 9.5,
192
+ :MMT => 6.5, :MNT => 8, :MNST => 9, :MPT => 10, :MSD => 4, :MSK => 3,
193
+ :MSKS => 4, :MT => 8.5, :MUT => 4, :MUST => 5, :MVT => 5, :MYT => 8,
194
+ :MFPT => -10,
195
+
196
+ :N => -1, :NCT => 11, :NDT => -2.5, :NFT => 11.5, :NOR => 1, :NOVST => 7,
197
+ :NOVT => 6, :NPT => 5.75, :NRT => 12, :NST => -3.5, :NSUT => 6.5,
198
+ :NT => -11, :NUT => -11, :NZDT => 13, :NZST => 12, :NZT => 12,
199
+
200
+ :O => -2, :OESZ => 3, :OEZ => 2, :OMSK => 7, :OMSST => 7, :OMST => 6,
201
+
202
+ :P => -3, :PET => -5, :PETST => 13, :PETT => 12, :PGT => 10, :PHOT => 13,
203
+ :PHT => 8, :PIT => 8, :PKT => 5, :PKST => 6, :PMDT => -2, :PMST => -3,
204
+ :PMT => -3, :PNT => -8.5, :PONT => 11, :PYST => -3, :PYT => -4, :PWT => 9,
205
+
206
+ :Q => -4,
207
+
208
+ :R => -5, :R1T => 2, :R2T => 3, :RET => 4, :ROK => 9, :ROTT => -3,
209
+
210
+ :S => -6, :SADT => 10.5, :SAMST => 5, :SAMT => 4, :SAST => 2, :SBT => 11,
211
+ :SCT => 4, :SCDT => 13, :SCST => 12, :SET => 1, :SGT => 8, :SIT => 8,
212
+ :SLT => -4, :SLST => -3, :SRT => -3, :SST => -11, :SYST => 3, :SWT => 1,
213
+ :SYT => 2,
214
+
215
+ :T => -7, :TAHT => -10, :TFT => 5, :THA => 7, :THAT => -10, :TJT => 5,
216
+ :TKT => -10, :TMT => 5, :TOT => 13, :TRUK => 10, :TPT => 9, :TRUT => 10,
217
+ :TST => 3, :TUC => 0, :TVT => 12, :TWT => 8,
218
+
219
+ :U => -8, :ULAST => 9, :ULAT => 8, :USZ1 => 2, :USZ1S => 3, :USZ3 => 4,
220
+ :USZ3S => 5, :USZ4 => 5, :USZ4S => 6, :USZ5 => 6, :USZ5S => 7, :USZ6 => 7,
221
+ :USZ6S => 8, :USZ7 => 8, :USZ7S => 9, :USZ8 => 9, :USZ8S => 10, :USZ9 => 10,
222
+ :USZ9S => 11, :UTZ => -3, :UYT => -3, :UYST => -2, :UZ10 => 11, :UZ10S => 12,
223
+ :UZ11 => 12, :UZ11S => 13, :UZ12 => 12, :UZ12S => 13, :UZT => 5,
224
+
225
+ :V => -9, :VET => -4.5, :VLAST => 11, :VLAT => 10, :VOST => 6, :VST => -4.5,
226
+ :VTZ => -2, :VUT => 11,
227
+
228
+ :W => -10, :WAKT => 12, :WAST => 2, :WAT => 1, :WCT => 8.75, :WEST => 1,
229
+ :WESZ => 1, :WET => 0, :WEZ => 0, :WFT => 12, :WGST => -2, :WGT => -3,
230
+ :WIB => 7, :WITA => 8, :WIT => 9, :WST => 8, :WKST => 5, :WTZ => -1,
231
+ :WUT => 1, :WEDT => 1, :WDT => 9,
232
+
233
+ :X => -11,
234
+
235
+ :Y => -12, :YAKST => 10, :YAKT => 9, :YAPT => 10, :YDT => -8, :YEKST => 6,
236
+ :YEKT => 5, :YST => -9,
237
+
238
+ :Z => 0
239
+ }
240
+ # unknown
241
+ # :HL => X, :LST => X, :LT => X, :OZ => X :SZ => X :TAI => X :UT => X :WZ => X
242
+
243
+ offset = (zone_offsets[timezone.to_s.upcase.to_sym] || 0) * seconds_in_hour
244
+ end
245
+ end
246
+ return offset
121
247
  end
122
248
 
123
249
  end
@@ -0,0 +1,12 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+
3
+ # query formats
4
+ #
5
+ require 'formats/format'
6
+ require 'formats/short_zipcode'
7
+ require 'formats/zipcode'
8
+ require 'formats/postalcode'
9
+ require 'formats/weather_id'
10
+ require 'formats/coordinates'
11
+ require 'formats/icao'
12
+ require 'formats/geocode'
@@ -0,0 +1,42 @@
1
+ module Barometer
2
+ #
3
+ # Format: Coordinates
4
+ #
5
+ # eg. 123.1234,-123.123
6
+ #
7
+ # This class is used to determine if a query is a
8
+ # :coordinates and how to convert to :coordinates.
9
+ #
10
+ class Query::Format::Coordinates < Query::Format
11
+
12
+ def self.format; :coordinates; end
13
+ def self.regex; /^[-]?[0-9\.]+[,]{1}[-]?[0-9\.]+$/; end
14
+ def self.convertable_formats
15
+ [:short_zipcode, :zipcode, :postalcode, :weather_id, :coordinates, :icao, :geocode]
16
+ end
17
+
18
+ # convert to this format, X -> :coordinates
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
+
25
+ # pre-convert
26
+ #
27
+ pre_query = nil
28
+ if original_query.format == :weather_id
29
+ pre_query = Query::Format::WeatherID.reverse(original_query)
30
+ end
31
+
32
+ # convert & adjust
33
+ #
34
+ converted_query = Query::Format::Geocode.geocode(pre_query || original_query)
35
+ converted_query.q = converted_query.geo.coordinates if converted_query.geo
36
+ converted_query.format = format
37
+
38
+ converted_query
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,46 @@
1
+ module Barometer
2
+ #
3
+ # Base Format Class
4
+ #
5
+ # Fromats are used to determine if a query is of a certain
6
+ # format, how to convert to and from that format
7
+ # and what the country_code is for that format (if possible).
8
+ # Some formats require external Web Services to help
9
+ # in the converision. (ie :weather_id -> :geocode)
10
+ #
11
+ class Query::Format
12
+
13
+ # stubs
14
+ #
15
+ def self.regex; raise NotImplementedError; end
16
+ def self.format; raise NotImplementedError; end
17
+
18
+ # defaults
19
+ #
20
+ def self.to(query=nil,country=nil); nil; end
21
+ def self.country_code(query=nil); nil; end
22
+ def self.convertable_formats; []; end
23
+
24
+ # is the query of this format?
25
+ #
26
+ def self.is?(query=nil)
27
+ raise ArgumentError unless query.is_a?(String)
28
+ return !(query =~ self.regex).nil?
29
+ end
30
+
31
+ # does the format support conversion from the given query?
32
+ #
33
+ def self.converts?(query=nil)
34
+ return false unless is_a_query?(query)
35
+ self.convertable_formats.include?(query.format)
36
+ end
37
+
38
+ # is the object a Barometer::Query?
39
+ #
40
+ def self.is_a_query?(object=nil)
41
+ return false unless object
42
+ object.is_a?(Barometer::Query)
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,51 @@
1
+ module Barometer
2
+ #
3
+ # Format: Geocode
4
+ # (not to be confused with the WebService geocode)
5
+ #
6
+ # eg. 123 Elm St, Mystery, Alaska, USA
7
+ #
8
+ # This class is used to determine if a query is a
9
+ # :geocode, how to convert to :geocode
10
+ #
11
+ class Query::Format::Geocode < Query::Format
12
+
13
+ def self.format; :geocode; end
14
+ def self.is?(query=nil); query.is_a?(String) ? true : false; end
15
+ def self.convertable_formats
16
+ [:short_zipcode, :zipcode, :coordinates, :weather_id, :icao]
17
+ end
18
+
19
+ # convert to this format, X -> :geocode
20
+ #
21
+ def self.to(original_query)
22
+ raise ArgumentError unless is_a_query?(original_query)
23
+ unless converts?(original_query)
24
+ return (original_query.format == format ? original_query.dup : nil)
25
+ end
26
+ converted_query = Barometer::Query.new
27
+
28
+ converted_query = (original_query.format == :weather_id ?
29
+ Query::Format::WeatherID.reverse(original_query) :
30
+ geocode(original_query))
31
+ converted_query
32
+ end
33
+
34
+ # geocode the query
35
+ #
36
+ def self.geocode(original_query)
37
+ raise ArgumentError unless is_a_query?(original_query)
38
+ converted_query = Barometer::Query.new
39
+
40
+ #converted_query.geo = _geocode(original_query)
41
+ converted_query.geo = WebService::Geocode.fetch(original_query)
42
+ if converted_query.geo
43
+ converted_query.country_code = converted_query.geo.country_code
44
+ converted_query.q = converted_query.geo.to_s
45
+ converted_query.format = format
46
+ end
47
+ converted_query
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,37 @@
1
+ module Barometer
2
+ #
3
+ # Format: ICAO (International Civil Aviation Organization)
4
+ #
5
+ # eg. KLAX (Los Angeles Airport)
6
+ #
7
+ # This class is used to determine if a query is a
8
+ # :icao and what the country_code is.
9
+ #
10
+ class Query::Format::Icao < Query::Format
11
+
12
+ @@codes_file = File.expand_path(
13
+ File.join(File.dirname(__FILE__), '..', 'translations', 'icao_country_codes.yml'))
14
+ @@codes = nil
15
+
16
+ def self.format; :icao; end
17
+
18
+ # call any 3-4 letter query, :icao ... obviously this will have a lot
19
+ # of false positives. So far this isn't an issue as all weather services
20
+ # that take :icao (which is just one, :wunderground) also take what
21
+ # this would have been if it was not called :icao.
22
+ #
23
+ def self.regex; /^[A-Za-z]{3,4}$/; end
24
+
25
+ # # in some cases the first letter can designate the country
26
+ # #
27
+ def self.country_code(query=nil)
28
+ return unless query && query.is_a?(String)
29
+ $:.unshift(File.dirname(__FILE__))
30
+ @@codes ||= YAML.load_file(@@codes_file)
31
+ return unless @@codes && @@codes['one_letter'] && @@codes['two_letter']
32
+ @@codes['one_letter'][query[0..0].upcase.to_s] ||
33
+ @@codes['two_letter'][query[0..1].upcase.to_s] || nil
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,22 @@
1
+ module Barometer
2
+ #
3
+ # Format: Postal Code
4
+ #
5
+ # eg. H0H 0H0
6
+ #
7
+ # This class is used to determine if a query is a
8
+ # :postalcode and what the country_code is.
9
+ #
10
+ class Query::Format::Postalcode < Query::Format
11
+
12
+ def self.format; :postalcode; end
13
+ def self.country_code(query=nil); "CA"; end
14
+ def self.regex
15
+ # Rules: no D, F, I, O, Q, or U anywhere
16
+ # Basic validation: ^[ABCEGHJ-NPRSTVXY]{1}[0-9]{1}[ABCEGHJ-NPRSTV-Z]{1}
17
+ # [ ]?[0-9]{1}[ABCEGHJ-NPRSTV-Z]{1}[0-9]{1}$
18
+ /^[A-Z]{1}[\d]{1}[A-Z]{1}[ ]?[\d]{1}[A-Z]{1}[\d]{1}$/
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module Barometer
2
+ #
3
+ # Format: Zip Code (short)
4
+ #
5
+ # eg. 90210
6
+ #
7
+ # This class is used to determine if a query is a
8
+ # :short_zipcode and what the country_code is.
9
+ #
10
+ class Query::Format::ShortZipcode < Query::Format
11
+
12
+ def self.format; :short_zipcode; end
13
+ def self.country_code(query=nil); "US"; end
14
+ def self.regex; /(^[0-9]{5}$)/; end
15
+
16
+ end
17
+ end
@@ -0,0 +1,107 @@
1
+ module Barometer
2
+ #
3
+ # Format: Weather ID (specific to weather.com)
4
+ #
5
+ # eg. USGA0028
6
+ #
7
+ # This class is used to determine if a query is a
8
+ # :weather_id, how to convert to and from :weather_id
9
+ # and what the country_code is.
10
+ #
11
+ class Query::Format::WeatherID < Query::Format
12
+
13
+ @@fixes_file = File.expand_path(
14
+ File.join(File.dirname(__FILE__), '..', 'translations', 'weather_country_codes.yml'))
15
+ @@fixes = nil
16
+
17
+ def self.format; :weather_id; end
18
+ def self.regex; /(^[A-Za-z]{4}[0-9]{4}$)/; end
19
+ def self.convertable_formats
20
+ [:short_zipcode, :zipcode, :coordinates, :geocode]
21
+ end
22
+
23
+ # the first two letters of the :weather_id is the country_code
24
+ #
25
+ def self.country_code(query=nil)
26
+ (query && query.size >= 2) ? _fix_country(query[0..1]) : nil
27
+ end
28
+
29
+ # convert to this format, X -> :weather_id
30
+ #
31
+ def self.to(original_query)
32
+ raise ArgumentError unless is_a_query?(original_query)
33
+ return nil unless converts?(original_query)
34
+ converted_query = Barometer::Query.new
35
+
36
+ # convert original query to :geocode, as that is the only
37
+ # format we can convert directly from to weather_id
38
+ converted_query = Query::Format::Geocode.to(original_query)
39
+ converted_query.q = _search(converted_query)
40
+ converted_query.format = format
41
+ converted_query.country_code = country_code(converted_query.q)
42
+ converted_query
43
+ end
44
+
45
+ # reverse lookup, :weather_id -> (:geocode || :coordinates)
46
+ #
47
+ def self.reverse(original_query)
48
+ raise ArgumentError unless is_a_query?(original_query)
49
+ return nil unless original_query.format == format
50
+ converted_query = Barometer::Query.new
51
+ converted_query.q = _reverse(original_query)
52
+ converted_query.format = Query::Format::Geocode.format
53
+ converted_query
54
+ end
55
+
56
+ private
57
+
58
+ # :geocode -> :weather_id
59
+ # search weather.com for the given query
60
+ #
61
+ def self._search(query=nil)
62
+ return nil unless query
63
+ raise ArgumentError unless is_a_query?(query)
64
+ response = WebService::WeatherID.fetch(query)
65
+ _parse_weather_id(response)
66
+ end
67
+
68
+ # :weather_id -> :geocode
69
+ # query yahoo with :weather_id and parse geo_data
70
+ #
71
+ def self._reverse(query=nil)
72
+ return nil unless query
73
+ raise ArgumentError unless is_a_query?(query)
74
+ response = WebService::WeatherID.reverse(query)
75
+ _parse_geocode(response)
76
+ end
77
+
78
+ # match the first :weather_id (from search results)
79
+ #
80
+ def self._parse_weather_id(text)
81
+ return nil unless text
82
+ match = text.match(/loc id=[\\]?['|""]([0-9a-zA-Z]*)[\\]?['|""]/)
83
+ match ? match[1] : nil
84
+ end
85
+
86
+ # parse the geo_data
87
+ #
88
+ def self._parse_geocode(text)
89
+ return nil unless text
90
+ output = [text["city"], text["region"], _fix_country(text["country"])]
91
+ output.delete("")
92
+ output.compact.join(', ')
93
+ end
94
+
95
+ # fix the country code
96
+ #
97
+ # weather.com uses non-standard two letter country codes that
98
+ # hinder the ability to determine the country or fetch geo_data.
99
+ # correct these "mistakes"
100
+ #
101
+ def self._fix_country(country_code)
102
+ @@fixes ||= YAML.load_file(@@fixes_file)
103
+ @@fixes[country_code.upcase.to_s] || country_code
104
+ end
105
+
106
+ end
107
+ end