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