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.
- 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/)
|