barometer 0.6.7 → 0.7.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 (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/)