forecast 0.0.7 → 0.0.8

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.
@@ -3,15 +3,14 @@ require 'rexml/document'
3
3
  require 'open-uri'
4
4
  require 'json'
5
5
 
6
+ require 'forecast/http.rb'
7
+
6
8
  class Forecast
7
9
  module Adapter
8
10
 
9
- @options = nil
11
+ attr_reader :options
10
12
 
11
13
  module ClassMethods
12
- def slug
13
- self.name.split('::').last.gsub(/Adapter$/, '').gsub(/(.)([A-Z])/,'\1_\2').downcase
14
- end
15
14
  end
16
15
 
17
16
  def self.included(base)
@@ -19,121 +18,146 @@ class Forecast
19
18
  end
20
19
 
21
20
  def self.instance
21
+ options = {}
22
22
  provider = Forecast.config.provider
23
- if provider.is_a?(Hash)
24
- adapter_name = provider['adapter'].to_s
23
+ if provider.is_a?(String) || provider.is_a?(Symbol)
24
+ adapter_name = provider.to_sym
25
+ elsif provider.is_a?(Hash)
26
+ adapter_name = provider[:adapter].to_s
25
27
  options = provider.clone
26
28
  options.delete('adapter')
27
- else
28
- adapter_name = provider
29
- options = {}
30
29
  end
31
30
  if adapter_name
31
+ if Forecast.config.adapters != nil && Forecast.config.adapters.has_key?(adapter_name)
32
+ options = options.merge(Forecast.config.adapters[adapter_name])
33
+ end
32
34
  adapter_classname = (adapter_name.to_s << "_adapter").split('_').collect!{ |w| w.capitalize }.join
33
35
  adapter_class = Object.const_get('Forecast').const_get("Adapters").const_get(adapter_classname)
34
36
  adapter_class.new(options)
35
37
  else
36
- puts 'no adapter provided'
38
+ puts 'Adapter not found'
37
39
  end
38
40
  end
39
41
 
40
42
  def initialize(options = {})
41
- @options = options
43
+ @options = ({cache: Forecast.config.cache}).merge(options)
44
+ @http = Http.new({cache: @options[:cache]})
45
+ end
46
+
47
+ def current(latitude, longitude)
42
48
  end
43
49
 
44
- def options
45
- @options
50
+ def hourly(latitude, longitude)
46
51
  end
47
52
 
48
- def config
49
- Forecast.config.adapters[self.class.slug] || {}
53
+ def daily(latitude, longitude)
50
54
  end
51
55
 
52
56
  protected
53
57
 
54
- def get_json(url, params = {})
55
- if params.keys.count > 0
56
- query_string = URI.encode_www_form(params)
57
- url = url + "?" + query_string
58
- end
59
- resp = Net::HTTP.get_response(URI.parse(url))
60
- data = resp.body
61
- result = JSON.parse(data)
62
- if result && result['cod'] != "404"
63
- return result
64
- end
65
- return nil
58
+ def options
59
+ @options
66
60
  end
67
-
68
- def get_doc(url, params = {})
69
- if params.keys.count > 0
70
- query_string = URI.encode_www_form(params)
71
- url = url + "?" + query_string
72
- end
73
- xml_data = Net::HTTP.get_response(URI.parse(url)).body
74
- doc = REXML::Document.new(xml_data)
75
- return doc
61
+
62
+ def get_json(url)
63
+ @http.get_json(url)
76
64
  end
77
65
 
78
- def metric(fahrenheit)
79
- ((fahrenheit - 32) / 1.800).round()
66
+ def get_dom(url)
67
+ @http.get_dom(url)
80
68
  end
81
69
 
82
- def get_temp(fahrenheit)
83
- fahrenheit = fahrenheit.to_i.round()
84
- Forecast.config.temp_scale.to_sym == :metric ? metric(fahrenheit) : fahrenheit
70
+ def get_temperature(value, input = :fahrenheit )
71
+ if value == nil
72
+ value = 0
73
+ elsif value.is_a?(Array)
74
+ value = value.inject{ |sum, v| sum.to_f + v.to_f }.to_f() / value.size
75
+ elsif value.is_a?(String) || value.is_a?(Numeric)
76
+ value = value.to_f
77
+ end
78
+ if input == :fahrenheit && Forecast.config.scale.to_sym == :kelvin
79
+ value = Forecast::Utils.fahrenheit_to_kelvin(value)
80
+ elsif input == :fahrenheit && Forecast.config.scale.to_sym == :celsius
81
+ value = Forecast::Utils.fahrenheit_to_celsius(value)
82
+ elsif input == :kelvin && Forecast.config.scale.to_sym == :fahrenheit
83
+ value = Forecast::Utils.kelvin_to_fahrenheit(value)
84
+ elsif input == :kelvin && Forecast.config.scale.to_sym == :celsius
85
+ value = Forecast::Utils.kelvin_to_celsius(value)
86
+ elsif input == :celsius && Forecast.config.scale.to_sym == :fahrenheit
87
+ value = Forecast::Utils.celsius_to_fahrenheit(value)
88
+ elsif input == :celsius && Forecast.config.scale.to_sym == :kelvin
89
+ value = Forecast::Utils.celsius_to_kelvin(value)
90
+ end
91
+ value.round
85
92
  end
86
93
 
87
- def similar_words(first, second)
88
- similar_words = 0.0
89
- first_words = first.downcase.split(/\s+/)
90
- second_words = second.downcase.split(/\s+/)
91
- first_words.each do |first_word|
92
- second_words.each do |second_word|
93
- similar = 0.0
94
- if first_word == second_word
95
- similar = 1.0
96
- else
97
- l1 = levenshtein(first_word, second_word)
98
- if l1 > 0 && l1 < 3
99
- similar = 1.0
100
- end
101
- end
102
- similar_words+= similar
103
- end
94
+ def get_time(value)
95
+ if value.is_a?(Time) || value.is_a?(Date)
96
+ value.to_datetime
97
+ elsif value.is_a?(String) && value =~ /\A\d+\Z/ || value.is_a?(Numeric)
98
+ Time.at(value.to_i).to_datetime
99
+ elsif value.is_a?(String)
100
+ DateTime.parse(value.to_s)
101
+ elsif value.is_a?(DateTime)
102
+ value
104
103
  end
105
- count = first_words.concat(second_words).uniq.length
106
- similarity = similar_words / count
107
- return similarity
108
104
  end
109
105
 
110
- def levenshtein(first, second)
111
- matrix = [(0..first.length).to_a]
112
- (1..second.length).each do |j|
113
- matrix << [j] + [0] * (first.length)
114
- end
115
- (1..second.length).each do |i|
116
- (1..first.length).each do |j|
117
- if first[j-1] == second[i-1]
118
- matrix[i][j] = matrix[i-1][j-1]
119
- else
120
- matrix[i][j] = [
121
- matrix[i-1][j],
122
- matrix[i][j-1],
123
- matrix[i-1][j-1],
124
- ].min + 1
125
- end
126
- end
106
+ def get_text(value)
107
+ value.capitalize
108
+ end
109
+
110
+ def get_condition_synonyms(name)
111
+ result = []
112
+ synonyms = Forecast.config.synonyms
113
+ synonym_values = synonyms.flatten.select { |v|
114
+ Forecast::Utils.word_similarity(name, v) > 0.6
115
+ }
116
+ if synonym_values.size > 0
117
+ c = synonym_values.sort { |a, b|
118
+ as = Forecast::Utils.word_similarity(name, a)
119
+ bs = Forecast::Utils.word_similarity(name, b)
120
+ as <=> bs
121
+ }.reverse
122
+ match = c.first
123
+ result = synonyms.select do |v|
124
+ v.include?(match)
125
+ end.first
127
126
  end
128
- return matrix.last.last
127
+ return result
129
128
  end
130
129
 
131
- def get_condition_by_similarity(name)
130
+
131
+ def get_similar_condition(name)
132
132
  conditions = Forecast.config.conditions
133
- c = conditions.values.sort { |a, b| similar_words(name, a) <=> similar_words(name, b) }.reverse
134
- if c.first && similar_words(name, c.first) > 0
135
- return c.first
133
+ condition_synonyms = {}
134
+ conditions.each do |k, v|
135
+ condition_synonyms[v] = get_condition_synonyms(v) - [v]
136
+ end
137
+ condition_synonym_similarity = {}
138
+ conditions.each do |k, v|
139
+ synonyms = [v] + get_condition_synonyms(v)
140
+ condition_synonym_similarity[v] = get_condition_synonyms(v) - [v]
141
+ similarity = 0
142
+ synonyms.each do |synonym|
143
+ similarity = [similarity, Forecast::Utils.word_similarity(name, synonym)].max
136
144
  end
145
+ condition_synonym_similarity[v] = similarity
146
+ end
147
+ c = conditions.values.select { |condition|
148
+ condition_synonym_similarity[condition] > 0.1
149
+ }
150
+ c = c.sort { |a, b|
151
+ a_similarity = condition_synonym_similarity[a]
152
+ b_similarity = condition_synonym_similarity[b]
153
+ a_similarity <=> b_similarity
154
+ }.reverse
155
+ if c.first != nil
156
+ return {
157
+ condition: c.first,
158
+ similarity: condition_synonym_similarity[c.first] || 0
159
+ }
160
+ end
137
161
  end
138
162
 
139
163
  def get_condition_name(match)
@@ -141,7 +165,7 @@ class Forecast
141
165
  return nil
142
166
  end
143
167
  conditions = Forecast.config.conditions
144
- condition = "Unknown"
168
+ condition = nil
145
169
  if conditions.keys.include?(match)
146
170
  condition = conditions[match]
147
171
  elsif conditions.values.include?(match)
@@ -150,55 +174,34 @@ class Forecast
150
174
  return condition
151
175
  end
152
176
 
153
- def match_adapter_condition(api_condition)
154
- match = nil
155
- conditions = config['conditions']
156
- if conditions != nil
157
- conditions.each do |key, value|
158
- # puts "match key #{api_condition} -> #{key.to_s}, #{value.to_s}"
159
- if is_numeric?(api_condition) && key.is_a?(String)
160
- if key.include? ".."
161
- range = key.split(/\.{2}/)
162
- if api_condition.to_i >= range[0].to_i && api_condition.to_i <= range[1].to_i
163
- match = value
164
- end
165
- end
166
- elsif key.to_s == api_condition.to_s
167
- match = value;
168
- end
169
- end
170
- end
171
- return match
172
- end
173
-
174
177
  def get_condition(api_conditions)
175
178
  if !api_conditions.is_a?(Array)
176
179
  api_conditions = [api_conditions]
177
180
  end
178
181
  condition = nil
179
- api_conditions.each do |api_condition|
180
- match = match_adapter_condition(api_condition)
181
- if condition == nil
182
- condition = get_condition_by_similarity(api_condition)
182
+ if api_conditions.length > 0
183
+ similar_conditions = api_conditions.map { |api_condition|
184
+ get_similar_condition(api_condition)
185
+ }.select {|v|
186
+ v != nil
187
+ }
188
+ similar_condition = similar_conditions.sort { |a,b|
189
+ a[:similarity] <=> b[:similarity]
190
+ }.reverse.first
191
+ if similar_condition
192
+ condition = similar_condition[:condition]
193
+ condition_name = get_condition_name(condition)
194
+ if condition_name == nil
195
+ condition_name = api_conditions[0]
196
+ end
183
197
  end
184
- if condition
185
- break
198
+ if condition_name != nil
199
+ return condition_name
186
200
  end
187
201
  end
188
- get_condition_name(condition)
202
+ return "Unknown"
189
203
  end
190
204
 
191
- private
192
-
193
- def is_numeric?(s)
194
- begin
195
- Float(s)
196
- rescue
197
- false # not numeric
198
- else
199
- true # numeric
200
- end
201
- end
202
205
 
203
206
  end
204
207
  end
@@ -0,0 +1,57 @@
1
+ class Forecast
2
+ module Adapters
3
+ class ForecastIoAdapter
4
+
5
+ include Forecast::Adapter
6
+
7
+ def current(latitude, longitude)
8
+ result = get_json(get_action(latitude, longitude))
9
+ get_forecast({latitude: latitude, longitude: longitude}.merge(result['currently'])) unless nil
10
+ end
11
+
12
+ def hourly(latitude, longitude)
13
+ result = get_json(get_action(latitude, longitude))
14
+ forecasts = Forecast::Collection.new
15
+ result['hourly']['data'].each do |hash|
16
+ forecasts << get_forecast({latitude: latitude, longitude: longitude}.merge(hash))
17
+ end
18
+ return forecasts
19
+ end
20
+
21
+ def daily(latitude, longitude)
22
+ result = get_json(get_action(latitude, longitude))
23
+ forecasts = Forecast::Collection.new
24
+ result['daily']['data'].each do |hash|
25
+ forecasts << get_forecast({latitude: latitude, longitude: longitude}.merge(hash))
26
+ end
27
+ return forecasts
28
+ end
29
+
30
+ private
31
+
32
+ def get_action(latitude, longitude)
33
+ api_key = options[:api_key]
34
+ return "https://api.forecast.io/forecast/#{api_key}/#{latitude},#{longitude}"
35
+ end
36
+
37
+ def get_forecast(hash)
38
+ forecast = Forecast.new(hash)
39
+ forecast.latitude = hash[:latitude]
40
+ forecast.longitude = hash[:longitude]
41
+ forecast.time = get_time(hash['time'])
42
+ forecast.condition = get_condition(hash['summary'])
43
+ forecast.text = get_text(hash['summary'])
44
+ forecast.temperature_min = get_temperature(hash['temperatureMin'], :fahrenheit)
45
+ forecast.temperature_max = get_temperature(hash['temperatureMax'], :fahrenheit)
46
+ forecast.temperature = get_temperature(hash.has_key?('temperature') ? hash['temperature'] : [hash['temperatureMin'], hash['temperatureMax']], :fahrenheit)
47
+ return forecast
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+
54
+
55
+
56
+
57
+
@@ -5,70 +5,62 @@ class Forecast
5
5
  include Forecast::Adapter
6
6
 
7
7
  def current(latitude, longitude)
8
- forecast = nil
9
- result = get_json(api_url('weather', latitude, longitude))
10
- if result
11
- forecast = Forecast.new(latitude: latitude, longitude: longitude)
12
- forecast.date = Time.at(result['dt']).to_datetime
13
- forecast.temp = get_temp(kelvin_to_fahrenheit(result['main']['temp']))
14
- result['weather'].each do |obj|
15
- condition = get_condition(obj['description'])
16
- if condition != nil
17
- forecast.condition = condition
18
- break
19
- end
20
- end
8
+ hash = get_json(get_action('weather', latitude, longitude))
9
+ if hash
10
+ result = get_forecast({latitude: latitude, longitude: longitude}.merge(hash))
11
+ return result
21
12
  end
22
- return forecast
23
13
  end
24
14
 
25
15
  def hourly(latitude, longitude)
26
- forecasts = Forecast::Collection.new
27
- result = get_json(api_url('forecast', latitude, longitude))
28
- if result
29
- result['list'].each do |item|
30
- forecast = Forecast.new(latitude: latitude, longitude:longitude)
31
- forecast.date = Time.at(item['dt']).to_datetime
32
- forecast.temp = get_temp(kelvin_to_fahrenheit(item['main']['temp']))
33
- item['weather'].each do |obj|
34
- condition = get_condition([obj['description'], obj['id']])
35
- if condition != nil
36
- forecast.condition = condition
37
- break
38
- end
39
- end
40
- # forecast.temp_min = item['main']['temp_min']
41
- # forecast.temp_max = item['main']['temp_max']
42
- forecasts << forecast
16
+ json = get_json(get_action('forecast', latitude, longitude))
17
+ result = Forecast::Collection.new
18
+ if json && json.has_key?('list')
19
+ json['list'].each do |hash|
20
+ result << get_forecast({latitude: latitude, longitude: longitude}.merge(hash))
43
21
  end
44
22
  end
45
- return forecasts
23
+ return result
46
24
  end
47
25
 
48
26
  def daily(latitude, longitude)
49
- forecasts = Forecast::Collection.new
50
- result = get_json(api_url('forecast/daily', latitude, longitude))
51
- result['list'].each do |item|
52
- forecast = Forecast.new(latitude: latitude, longitude:longitude)
53
- forecast.date = Time.at(item['dt'])
54
- forecast.temp_min = get_temp(kelvin_to_fahrenheit(item['temp']['min']))
55
- forecast.temp_max = get_temp(kelvin_to_fahrenheit(item['temp']['max']))
56
- forecast.temp = (forecast.temp_min + forecast.temp_max) / 2
57
- item['weather'].each do |obj|
58
- condition = get_condition(obj['description'])
27
+ json = get_json(get_action('forecast/daily', latitude, longitude))
28
+ result = Forecast::Collection.new
29
+ if json && json.has_key?('list')
30
+ result = Forecast::Collection.new
31
+ json['list'].each do |hash|
32
+ result << get_forecast({latitude: latitude, longitude: longitude}.merge(hash))
33
+ end
34
+ end
35
+ return result
36
+ end
37
+
38
+ protected
39
+
40
+ def get_forecast(hash = {})
41
+ forecast = Forecast.new()
42
+ forecast.latitude = hash[:latitude]
43
+ forecast.longitude = hash[:longitude]
44
+ forecast.time = get_time(hash['dt'])
45
+ forecast.temperature = get_temperature(hash.has_key?('main') ? hash['main']['temp'] : hash['temp']['day'], :kelvin)
46
+ forecast.temperature_min = get_temperature(hash.has_key?('main') ? hash['main']['temp_min'] : hash['temp']['min'], :kelvin)
47
+ forecast.temperature_max = get_temperature(hash.has_key?('main') ? hash['main']['temp_max'] : hash['temp']['max'], :kelvin)
48
+ forecast.temperature = ((forecast.temperature_min + forecast.temperature_max) / 2).round
49
+ hash['weather'].each do |obj|
50
+ condition = get_condition([obj['description'], obj['main']])
59
51
  if condition != nil
52
+ forecast.text = get_text(obj['description'])
60
53
  forecast.condition = condition
61
54
  break
62
- end
55
+ end
63
56
  end
64
- forecasts << forecast
57
+ return forecast
65
58
  end
66
- return forecasts
67
- end
68
-
59
+
60
+
69
61
  private
70
62
 
71
- def api_url(action, latitude, longitude)
63
+ def get_action(action, latitude, longitude)
72
64
  url = "http://api.openweathermap.org/data/2.5/#{action}"
73
65
  params = {
74
66
  lat: latitude,
@@ -81,15 +73,6 @@ class Forecast
81
73
  return url + "?" + query_string
82
74
  end
83
75
 
84
- def kelvin_to_fahrenheit(kelvin)
85
- return ((kelvin - 273.15) * 1.8000 + 32).round
86
- end
87
-
88
76
  end
89
77
  end
90
- end
91
-
92
-
93
-
94
-
95
-
78
+ end
@@ -6,29 +6,20 @@ class Forecast
6
6
 
7
7
  def current(latitude, longitude)
8
8
  forecast = nil
9
- result = get_json(api_url('conditions', latitude, longitude))
10
- if result
11
- item = result['current_observation']
12
- forecast = Forecast.new(latitude: latitude, longitude: longitude)
13
- forecast.date = Time.rfc822(item['observation_time_rfc822'])
14
- forecast.temp = get_temp(item['temp_f'])
15
- forecast.condition = get_condition([item['weather']])
16
- forecast.orig_condition = item['weather']
9
+ result = get_json(get_action('conditions', latitude, longitude))
10
+ if result.has_key?('current_observation')
11
+ forecast = get_current_forecast(result['current_observation'].merge({latitude: latitude, longitude: longitude}))
17
12
  end
18
13
  return forecast
19
14
  end
20
15
 
21
16
  def hourly(latitude, longitude)
22
17
  forecasts = Forecast::Collection.new
23
- result = get_json(api_url('hourly', latitude, longitude))
24
- if result
18
+ result = get_json(get_action('hourly', latitude, longitude))
19
+ if result.has_key?('hourly_forecast')
25
20
  items = result['hourly_forecast']
26
- items.each do |item|
27
- forecast = Forecast.new(latitude: latitude, longitude:longitude)
28
- forecast.date = Time.at(item['FCTTIME']['epoch'].to_i).to_datetime
29
- forecast.temp = get_temp(item['temp']['english'])
30
- forecast.condition = get_condition([item['condition']])
31
- forecast.orig_condition = item['condition']
21
+ items.each do |hash|
22
+ forecast = get_hourly_forecast(hash.merge({latitude: latitude, longitude: longitude}))
32
23
  forecasts << forecast
33
24
  end
34
25
  end
@@ -37,17 +28,11 @@ class Forecast
37
28
 
38
29
  def daily(latitude, longitude)
39
30
  forecasts = Forecast::Collection.new
40
- result = get_json(api_url('forecast', latitude, longitude))
41
- if result
31
+ result = get_json(get_action('forecast', latitude, longitude))
32
+ if result.has_key?('forecast')
42
33
  items = result['forecast']['simpleforecast']['forecastday']
43
- items.each do |item|
44
- forecast = Forecast.new(latitude: latitude, longitude:longitude)
45
- forecast.date = Time.at(item['date']['epoch'].to_i).to_datetime
46
- forecast.temp_min = get_temp(item['low']['fahrenheit'])
47
- forecast.temp_max = get_temp(item['high']['fahrenheit'])
48
- forecast.temp = (forecast.temp_min + forecast.temp_max) / 2
49
- forecast.condition = get_condition([item['conditions']])
50
- forecast.orig_condition = item['conditions']
34
+ items.each do |hash|
35
+ forecast = get_daily_forecast(hash.merge({latitude: latitude, longitude: longitude}))
51
36
  forecasts << forecast
52
37
  end
53
38
  end
@@ -55,10 +40,49 @@ class Forecast
55
40
  end
56
41
 
57
42
  private
58
-
59
- def api_url(action, latitude, longitude)
60
- url = "http://api.wunderground.com/api/#{options['api_key']}/#{action}/q/#{latitude},#{longitude}.json"
43
+
44
+ def get_action(action, latitude, longitude)
45
+ url = "http://api.wunderground.com/api/#{options[:api_key]}/#{action}/q/#{latitude},#{longitude}.json"
61
46
  end
47
+
48
+ def get_current_forecast(hash = {})
49
+ puts 'forecast hash: ' + hash.to_s
50
+ forecast = Forecast.new()
51
+ forecast.latitude = hash[:latitude]
52
+ forecast.longitude = hash[:longitude]
53
+ forecast.time = get_time(hash['observation_epoch'])
54
+ forecast.temperature = get_temperature(hash['temp_f'], :fahrenheit)
55
+ forecast.condition = get_condition(hash['weather'])
56
+ forecast.text = get_text(hash['weather'])
57
+ return forecast
58
+ end
59
+
60
+ def get_hourly_forecast(hash = {})
61
+ puts 'forecast hash: ' + hash.to_s
62
+ forecast = Forecast.new()
63
+ forecast.latitude = hash[:latitude]
64
+ forecast.longitude = hash[:longitude]
65
+ forecast.time = get_time(hash['FCTTIME']['epoch'])
66
+ forecast.temperature = get_temperature(hash['temp']['english'], :fahrenheit)
67
+ forecast.condition = get_condition([hash['condition']])
68
+ forecast.text = get_text(hash['condition'])
69
+ return forecast
70
+ end
71
+
72
+ def get_daily_forecast(hash = {})
73
+ puts 'forecast hash: ' + hash.to_s
74
+ forecast = Forecast.new()
75
+ forecast.latitude = hash[:latitude]
76
+ forecast.longitude = hash[:longitude]
77
+ forecast.time = get_time(hash['date']['epoch'])
78
+ forecast.temperature_min = get_temperature(hash['low']['fahrenheit'], :fahrenheit)
79
+ forecast.temperature_max = get_temperature(hash['high']['fahrenheit'], :fahrenheit)
80
+ forecast.temperature = get_temperature([hash['low']['fahrenheit'], hash['high']['fahrenheit']], :fahrenheit)
81
+ forecast.condition = get_condition(hash['conditions'])
82
+ forecast.text = get_text(hash['conditions'])
83
+ return forecast
84
+ end
85
+
62
86
 
63
87
  end
64
88
  end