forecast 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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