barometer 0.6.7 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +3 -0
- data/LICENSE +1 -1
- data/README.rdoc +36 -8
- data/Rakefile +7 -28
- data/TODO +4 -0
- data/VERSION.yml +2 -2
- data/barometer.gemspec +20 -193
- data/bin/barometer +8 -5
- data/lib/barometer.rb +5 -1
- data/lib/barometer/data/local_datetime.rb +11 -7
- data/lib/barometer/formats.rb +2 -1
- data/lib/barometer/formats/coordinates.rb +15 -3
- data/lib/barometer/formats/format.rb +18 -0
- data/lib/barometer/formats/geocode.rb +12 -5
- data/lib/barometer/formats/weather_id.rb +0 -15
- data/lib/barometer/formats/woe_id.rb +150 -0
- data/lib/barometer/query.rb +20 -4
- data/lib/barometer/services.rb +2 -1
- data/lib/barometer/weather_services/service.rb +0 -4
- data/lib/barometer/weather_services/weather_bug.rb +2 -2
- data/lib/barometer/weather_services/yahoo.rb +14 -3
- data/lib/barometer/web_services/placemaker.rb +95 -0
- data/lib/barometer/web_services/web_service.rb +1 -4
- data/spec/barometer_spec.rb +14 -32
- data/spec/data/local_datetime_spec.rb +7 -2
- data/spec/data/zone_spec.rb +5 -2
- data/spec/fakeweb_helper.rb +158 -0
- data/spec/fixtures/services/placemaker/T5B4M9.xml +65 -0
- data/spec/fixtures/services/placemaker/atlanta.xml +65 -0
- data/spec/fixtures/services/placemaker/coords.xml +65 -0
- data/spec/fixtures/services/placemaker/ksfo.xml +65 -0
- data/spec/fixtures/services/placemaker/new_york.xml +65 -0
- data/spec/fixtures/services/placemaker/the_hills.xml +65 -0
- data/spec/fixtures/services/placemaker/w615702.xml +46 -0
- data/spec/fixtures/services/weather_bug/90210_forecast.xml +1 -1
- data/spec/formats/coordinates_spec.rb +16 -0
- data/spec/formats/format_spec.rb +9 -0
- data/spec/formats/woe_id_spec.rb +215 -0
- data/spec/query_spec.rb +38 -0
- data/spec/spec_helper.rb +7 -160
- data/spec/weather_services/google_spec.rb +0 -8
- data/spec/weather_services/services_spec.rb +1 -1
- data/spec/weather_services/weather_bug_spec.rb +0 -36
- data/spec/weather_services/weather_dot_com_spec.rb +0 -20
- data/spec/weather_services/wunderground_spec.rb +0 -30
- data/spec/weather_services/yahoo_spec.rb +1 -17
- data/spec/web_services/placemaker_spec.rb +46 -0
- metadata +136 -18
- 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=(
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
data/lib/barometer/formats.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
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
|
data/lib/barometer/query.rb
CHANGED
@@ -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
|
-
|
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 =
|
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
|
data/lib/barometer/services.rb
CHANGED
@@ -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'], "%
|
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/)
|