barometer 0.6.7 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +1 -1
  4. data/README.rdoc +36 -8
  5. data/Rakefile +7 -28
  6. data/TODO +4 -0
  7. data/VERSION.yml +2 -2
  8. data/barometer.gemspec +20 -193
  9. data/bin/barometer +8 -5
  10. data/lib/barometer.rb +5 -1
  11. data/lib/barometer/data/local_datetime.rb +11 -7
  12. data/lib/barometer/formats.rb +2 -1
  13. data/lib/barometer/formats/coordinates.rb +15 -3
  14. data/lib/barometer/formats/format.rb +18 -0
  15. data/lib/barometer/formats/geocode.rb +12 -5
  16. data/lib/barometer/formats/weather_id.rb +0 -15
  17. data/lib/barometer/formats/woe_id.rb +150 -0
  18. data/lib/barometer/query.rb +20 -4
  19. data/lib/barometer/services.rb +2 -1
  20. data/lib/barometer/weather_services/service.rb +0 -4
  21. data/lib/barometer/weather_services/weather_bug.rb +2 -2
  22. data/lib/barometer/weather_services/yahoo.rb +14 -3
  23. data/lib/barometer/web_services/placemaker.rb +95 -0
  24. data/lib/barometer/web_services/web_service.rb +1 -4
  25. data/spec/barometer_spec.rb +14 -32
  26. data/spec/data/local_datetime_spec.rb +7 -2
  27. data/spec/data/zone_spec.rb +5 -2
  28. data/spec/fakeweb_helper.rb +158 -0
  29. data/spec/fixtures/services/placemaker/T5B4M9.xml +65 -0
  30. data/spec/fixtures/services/placemaker/atlanta.xml +65 -0
  31. data/spec/fixtures/services/placemaker/coords.xml +65 -0
  32. data/spec/fixtures/services/placemaker/ksfo.xml +65 -0
  33. data/spec/fixtures/services/placemaker/new_york.xml +65 -0
  34. data/spec/fixtures/services/placemaker/the_hills.xml +65 -0
  35. data/spec/fixtures/services/placemaker/w615702.xml +46 -0
  36. data/spec/fixtures/services/weather_bug/90210_forecast.xml +1 -1
  37. data/spec/formats/coordinates_spec.rb +16 -0
  38. data/spec/formats/format_spec.rb +9 -0
  39. data/spec/formats/woe_id_spec.rb +215 -0
  40. data/spec/query_spec.rb +38 -0
  41. data/spec/spec_helper.rb +7 -160
  42. data/spec/weather_services/google_spec.rb +0 -8
  43. data/spec/weather_services/services_spec.rb +1 -1
  44. data/spec/weather_services/weather_bug_spec.rb +0 -36
  45. data/spec/weather_services/weather_dot_com_spec.rb +0 -20
  46. data/spec/weather_services/wunderground_spec.rb +0 -30
  47. data/spec/weather_services/yahoo_spec.rb +1 -17
  48. data/spec/web_services/placemaker_spec.rb +46 -0
  49. metadata +136 -18
  50. data/lib/barometer/extensions/httparty.rb +0 -21
data/lib/barometer.rb CHANGED
@@ -17,7 +17,11 @@ module Barometer
17
17
 
18
18
  @@google_geocode_key = nil
19
19
  def self.google_geocode_key; @@google_geocode_key; end;
20
- def self.google_geocode_key=(key); @@google_geocode_key = key; end;
20
+ def self.google_geocode_key=(google_key); @@google_geocode_key = google_key; end;
21
+
22
+ @@yahoo_placemaker_app_id = nil
23
+ def self.yahoo_placemaker_app_id; @@yahoo_placemaker_app_id; end;
24
+ def self.yahoo_placemaker_app_id=(yahoo_key); @@yahoo_placemaker_app_id = yahoo_key; end;
21
25
 
22
26
  # sometimes a query is used as is and never gets geocoded (ie zipcode)
23
27
  # often, it is useful to have queries geocoded to know where in the
@@ -70,13 +70,17 @@ module Barometer
70
70
  month = string.mon
71
71
  day = string.day
72
72
  elsif string.is_a?(String)
73
- datetime = Time.parse(string)
74
- year = datetime.year
75
- month = datetime.mon
76
- day = datetime.day
77
- hour = datetime.hour
78
- min = datetime.min
79
- sec = datetime.sec
73
+ begin
74
+ datetime = Time.parse(string)
75
+ year = datetime.year
76
+ month = datetime.mon
77
+ day = datetime.day
78
+ hour = datetime.hour
79
+ min = datetime.min
80
+ sec = datetime.sec
81
+ rescue ArgumentError
82
+ return nil
83
+ end
80
84
  end
81
85
  Data::LocalDateTime.new(year, month, day, hour, min, sec)
82
86
  end
@@ -9,4 +9,5 @@ require 'formats/postalcode'
9
9
  require 'formats/weather_id'
10
10
  require 'formats/coordinates'
11
11
  require 'formats/icao'
12
- require 'formats/geocode'
12
+ require 'formats/geocode'
13
+ require 'formats/woe_id'
@@ -12,16 +12,16 @@ module Barometer
12
12
  def self.format; :coordinates; end
13
13
  def self.regex; /^[-]?[0-9\.]+[,]{1}\s?[-]?[0-9\.]+$/; end
14
14
  def self.convertable_formats
15
- [:short_zipcode, :zipcode, :postalcode, :weather_id, :coordinates, :icao, :geocode]
15
+ [:short_zipcode, :zipcode, :postalcode, :weather_id, :coordinates, :icao, :geocode, :woe_id]
16
16
  end
17
17
 
18
18
  # convert to this format, X -> :coordinates
19
19
  #
20
20
  def self.to(original_query)
21
21
  raise ArgumentError unless is_a_query?(original_query)
22
- # return nil unless converts?(original_query)
22
+ return nil unless converts?(original_query)
23
23
  converted_query = Barometer::Query.new
24
-
24
+
25
25
  # pre-convert
26
26
  #
27
27
  pre_query = nil
@@ -30,6 +30,8 @@ module Barometer
30
30
  pre_query = Query::Format::WeatherID.reverse(original_query)
31
31
  original_query.post_conversion(pre_query)
32
32
  end
33
+ elsif original_query.format == :woe_id
34
+ pre_query = Query::Format::WoeID.reverse(original_query)
33
35
  end
34
36
 
35
37
  # convert & adjust
@@ -40,6 +42,16 @@ module Barometer
40
42
 
41
43
  converted_query
42
44
  end
45
+
46
+ def self.parse_latitude(query)
47
+ coordinates = query.to_s.split(',')
48
+ coordinates ? coordinates[0] : nil
49
+ end
50
+
51
+ def self.parse_longitude(query)
52
+ coordinates = query.to_s.split(',')
53
+ coordinates ? coordinates[1] : nil
54
+ end
43
55
 
44
56
  end
45
57
  end
@@ -10,6 +10,10 @@ module Barometer
10
10
  #
11
11
  class Query::Format
12
12
 
13
+ @@fixes_file = File.expand_path(
14
+ File.join(File.dirname(__FILE__), '..', 'translations', 'weather_country_codes.yml'))
15
+ @@fixes = nil
16
+
13
17
  # stubs
14
18
  #
15
19
  def self.regex; raise NotImplementedError; end
@@ -20,6 +24,7 @@ module Barometer
20
24
  def self.to(query=nil,country=nil); nil; end
21
25
  def self.country_code(query=nil); nil; end
22
26
  def self.convertable_formats; []; end
27
+ def self.convert_query(text); text; end
23
28
 
24
29
  # is the query of this format?
25
30
  #
@@ -41,6 +46,19 @@ module Barometer
41
46
  return false unless object
42
47
  object.is_a?(Barometer::Query)
43
48
  end
49
+
50
+ private
51
+
52
+ # fix the country code
53
+ #
54
+ # weather.com uses non-standard two letter country codes that
55
+ # hinder the ability to determine the country or fetch geo_data.
56
+ # correct these "mistakes"
57
+ #
58
+ def self._fix_country(country_code)
59
+ @@fixes ||= YAML.load_file(@@fixes_file)
60
+ @@fixes[country_code.upcase.to_s] || country_code
61
+ end
44
62
 
45
63
  end
46
64
  end
@@ -13,7 +13,7 @@ module Barometer
13
13
  def self.format; :geocode; end
14
14
  def self.is?(query=nil); query.is_a?(String) ? true : false; end
15
15
  def self.convertable_formats
16
- [:short_zipcode, :zipcode, :coordinates, :weather_id, :icao]
16
+ [:short_zipcode, :zipcode, :coordinates, :weather_id, :icao, :woe_id]
17
17
  end
18
18
 
19
19
  # convert to this format, X -> :geocode
@@ -26,9 +26,16 @@ module Barometer
26
26
 
27
27
  unless converted_query = original_query.get_conversion(format)
28
28
  converted_query = Barometer::Query.new
29
- converted_query = (original_query.format == :weather_id ?
30
- Query::Format::WeatherID.reverse(original_query) :
31
- geocode(original_query))
29
+
30
+ converted_query = case original_query.format
31
+ when :weather_id
32
+ Query::Format::WeatherID.reverse(original_query)
33
+ when :woe_id
34
+ Query::Format::WoeID.reverse(original_query)
35
+ else
36
+ geocode(original_query)
37
+ end
38
+
32
39
  original_query.post_conversion(converted_query) if converted_query
33
40
  end
34
41
  converted_query
@@ -40,7 +47,7 @@ module Barometer
40
47
  raise ArgumentError unless is_a_query?(original_query)
41
48
 
42
49
  converted_query = Barometer::Query.new
43
- converted_query.geo = WebService::Geocode.fetch(original_query)
50
+ converted_query.geo = Barometer::WebService::Geocode.fetch(original_query)
44
51
  if converted_query.geo
45
52
  converted_query.country_code = converted_query.geo.country_code
46
53
  converted_query.q = converted_query.geo.to_s
@@ -10,10 +10,6 @@ module Barometer
10
10
  #
11
11
  class Query::Format::WeatherID < Query::Format
12
12
 
13
- @@fixes_file = File.expand_path(
14
- File.join(File.dirname(__FILE__), '..', 'translations', 'weather_country_codes.yml'))
15
- @@fixes = nil
16
-
17
13
  def self.format; :weather_id; end
18
14
  def self.regex; /(^[A-Za-z]{4}[0-9]{4}$)/; end
19
15
  def self.convertable_formats
@@ -91,17 +87,6 @@ module Barometer
91
87
  output.delete("")
92
88
  output.compact.join(', ')
93
89
  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
90
 
106
91
  end
107
92
  end
@@ -0,0 +1,150 @@
1
+ module Barometer
2
+ #
3
+ # Format: WOEID
4
+ # "Where on Earth" ID (by Yahoo!)
5
+ #
6
+ # eg. 2459115, 615702 or w90210
7
+ #
8
+ # NOTE: zipcodes and WoeIDs can look exactly the same when the WoeID
9
+ # is 5 digits long. For now, a 5 digit number will be detected as
10
+ # zipcode. The way to override this is to prepend a number with the
11
+ # letter 'w'. Therefore 90210 will be a zipcode and w90210 will be
12
+ # a WoeID. In the future, if WoeIDs are moe popular then zipcodes,
13
+ # then I will reverse this ...
14
+ #
15
+ # This class is used to determine if a query is a
16
+ # :woe_id and how to convert to a :woe_id.
17
+ #
18
+ class Query::Format::WoeID < Query::Format
19
+
20
+ def self.format; :woe_id; end
21
+ def self.regex; /(^[0-9]{4}$)|(^[0-9]{6,7}$)|(^w[0-9]{4,7}$)/; end
22
+ def self.convertable_formats
23
+ [:short_zipcode, :zipcode, :weather_id, :coordinates, :icao, :geocode, :postalcode]
24
+ end
25
+
26
+ # remove the 'w' from applicable queries (only needed for detection)
27
+ #
28
+ def self.convert_query(text)
29
+ return nil unless text
30
+ text.delete('w')
31
+ end
32
+
33
+ # convert to this format, X -> :woeid
34
+ #
35
+ def self.to(original_query)
36
+ raise ArgumentError unless is_a_query?(original_query)
37
+ return nil unless converts?(original_query)
38
+
39
+ # pre-convert (:weather_id -> :geocode)
40
+ #
41
+ pre_query = nil
42
+ if original_query.format == :weather_id
43
+ pre_query = Query::Format::WeatherID.reverse(original_query)
44
+ end
45
+
46
+ # pre-convert ([:short_zipcode, :zipcode, :postalcode, :icao] -> :geocode)
47
+ #
48
+ unless pre_query
49
+ if [:short_zipcode, :zipcode, :icao].include?(original_query.format)
50
+ unless pre_query = original_query.get_conversion(Query::Format::Geocode.format)
51
+ pre_query = Query::Format::Geocode.to(original_query)
52
+ end
53
+ end
54
+ end
55
+
56
+ converted_query = Barometer::Query.new
57
+ converted_query.country_code = original_query.country_code if original_query
58
+
59
+ # TODO
60
+ # use Geomojo.com (when no Yahoo! appid)
61
+ #
62
+ # if [:coordinates].include?(pre_query ? pre_query.format : original_query.format) &&
63
+ # Barometer.yahoo_placemaker_app_id.nil?
64
+ # converted_query.q = _query_geomojo(pre_query || original_query)
65
+ # converted_query.format = format
66
+ # end
67
+
68
+ # use Yahoo! Placemaker
69
+ #
70
+ if [:coordinates, :geocode, :postalcode].include?(pre_query ? pre_query.format : original_query.format) &&
71
+ !Barometer.yahoo_placemaker_app_id.nil?
72
+ converted_query.q = _query_placemaker(pre_query || original_query)
73
+ converted_query.format = format
74
+ end
75
+
76
+ converted_query.geo = pre_query.geo if pre_query
77
+ converted_query.country_code = pre_query.country_code if pre_query
78
+ converted_query
79
+ end
80
+
81
+ # reverse lookup, :woe_id -> (:geocode || :coordinates)
82
+ #
83
+ def self.reverse(original_query)
84
+ raise ArgumentError unless is_a_query?(original_query)
85
+ return nil unless original_query.format == self.format
86
+ converted_query = Barometer::Query.new
87
+ converted_query.q = _reverse(original_query)
88
+ converted_query.format = Barometer::Query::Format::Geocode.format
89
+ converted_query
90
+ end
91
+
92
+ private
93
+
94
+ # Yahoo! Placemaker
95
+ # [:geocode,:coordinates] -> :woe_id
96
+ #
97
+ def self._query_placemaker(query=nil)
98
+ return nil unless query
99
+ raise ArgumentError unless is_a_query?(query)
100
+ doc = WebService::Placemaker.fetch(query)
101
+ _parse_woe_from_placemaker(doc)
102
+ end
103
+
104
+ # Geomojo.com
105
+ # [:coordinates] -> :woe_id
106
+ #
107
+ # def self._query_geomojo(query=nil)
108
+ # return nil unless query
109
+ # raise ArgumentError unless is_a_query?(query)
110
+ # doc = WebService::Geomojo.fetch(query)
111
+ # _parse_woe_from_geomojo(doc)
112
+ # end
113
+
114
+ # :woe_id -> :geocode
115
+ # query yahoo with :woe_id and parse geo_data
116
+ #
117
+ def self._reverse(query=nil)
118
+ return nil unless query
119
+ raise ArgumentError unless is_a_query?(query)
120
+ response = WebService::Placemaker.reverse(query)
121
+ _parse_geocode(response)
122
+ end
123
+
124
+ # match the first :woe_id (from search results)
125
+ # expects a Nokogiri doc
126
+ #
127
+ def self._parse_woe_from_placemaker(doc)
128
+ return nil unless doc
129
+ WebService::Placemaker.parse_woe_id(doc)
130
+ end
131
+
132
+ # match the first :woe_id (from search results)
133
+ # expects a Nokogiri doc
134
+ #
135
+ # def self._parse_woe_from_geomojo(doc)
136
+ # return nil unless doc
137
+ # WebService::Geomojo.parse_woe_id(doc)
138
+ # end
139
+
140
+ # parse the geo_data
141
+ #
142
+ def self._parse_geocode(text)
143
+ return nil unless text
144
+ output = [text["city"], text["region"], _fix_country(text["country"])]
145
+ output.delete("")
146
+ output.compact.join(', ')
147
+ end
148
+
149
+ end
150
+ end
@@ -21,16 +21,17 @@ module Barometer
21
21
  # This array defines the order to check a query for the format
22
22
  #
23
23
  FORMATS = %w(
24
- ShortZipcode Zipcode Postalcode WeatherID Coordinates Icao Geocode
24
+ ShortZipcode Zipcode Postalcode WeatherID Coordinates Icao WoeID Geocode
25
25
  )
26
26
  FORMAT_MAP = {
27
27
  :short_zipcode => "ShortZipcode", :zipcode => "Zipcode",
28
28
  :postalcode => "Postalcode", :weather_id => "WeatherID",
29
29
  :coordinates => "Coordinates", :icao => "Icao",
30
- :geocode => "Geocode"
30
+ :woe_id => "WoeID", :geocode => "Geocode"
31
31
  }
32
32
 
33
- attr_accessor :format, :q, :country_code
33
+ attr_writer :q
34
+ attr_accessor :format, :country_code
34
35
  attr_accessor :geo, :timezone, :conversions
35
36
 
36
37
  def initialize(query=nil)
@@ -39,6 +40,10 @@ module Barometer
39
40
  self.analyze!
40
41
  @conversions = {}
41
42
  end
43
+
44
+ def q
45
+ format ? Barometer::Query::Format.const_get(FORMAT_MAP[format.to_sym].to_s).convert_query(@q) : @q
46
+ end
42
47
 
43
48
  # analyze the saved query to determine the format.
44
49
  # this delegates the detection to each formats class
@@ -81,13 +86,14 @@ module Barometer
81
86
  converted_query = Barometer::Query.new
82
87
  preferred_formats.each do |preferred_format|
83
88
  klass = FORMAT_MAP[preferred_format.to_sym]
89
+ # if we discover that the format we have is the preferred format, return it
84
90
  if preferred_format == @format
85
91
  converted = true
86
92
  converted_query = Barometer::Query.new(@q)
87
93
  end
88
94
  unless converted
89
95
  unless converted_query = get_conversion(preferred_format)
90
- converted_query = Query::Format.const_get(klass.to_s).to(self)
96
+ converted_query = Query::Format.const_get(klass.to_s).to(self)
91
97
  end
92
98
  converted = true if converted_query
93
99
  end
@@ -151,6 +157,16 @@ def get_conversion(format)
151
157
  nil
152
158
  end
153
159
  end
160
+
161
+ def latitude
162
+ return nil unless self.format == Query::Format::Coordinates.format
163
+ Query::Format::Coordinates.parse_latitude(self.q)
164
+ end
165
+
166
+ def longitude
167
+ return nil unless self.format == Query::Format::Coordinates.format
168
+ Query::Format::Coordinates.parse_longitude(self.q)
169
+ end
154
170
 
155
171
  end
156
172
  end
@@ -15,4 +15,5 @@ require 'weather_services/weather_bug'
15
15
  require 'web_services/web_service'
16
16
  require 'web_services/geocode'
17
17
  require 'web_services/weather_id'
18
- require 'web_services/timezone'
18
+ require 'web_services/timezone'
19
+ require 'web_services/placemaker'
@@ -1,10 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'httparty'
3
3
 
4
- $:.unshift(File.dirname(__FILE__))
5
- # load some changes to Httparty
6
- require 'extensions/httparty'
7
-
8
4
  module Barometer
9
5
  #
10
6
  # Service Class
@@ -127,7 +127,7 @@ module Barometer
127
127
  forecasts = Measurement::ResultArray.new
128
128
  # go through each forecast and create an instance
129
129
  if data && data["aws:forecast"]
130
- start_date = Date.strptime(data['date'], "%m/%d/%Y %H:%M:%S %p")
130
+ start_date = Date.strptime(data['date'], "%a, %d %b %Y %H:%M:%S %Z")
131
131
  i = 0
132
132
  data["aws:forecast"].each do |forecast|
133
133
  forecast_measurement = Measurement::Result.new
@@ -240,7 +240,7 @@ module Barometer
240
240
  }.merge(q),
241
241
  :format => :plain,
242
242
  :timeout => Barometer.timeout
243
- )
243
+ )
244
244
 
245
245
  # get icon
246
246
  icon_match = response.match(/cond(\d*)\.gif/)