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.
@@ -11,21 +11,13 @@ class Forecast
11
11
  forecast = nil
12
12
  doc = get_rss(latitude, longitude)
13
13
  if doc
14
- forecast = Forecast.new
14
+ hash = {}
15
15
  doc.elements.each('rss/channel/item/yweather:condition') do |elem|
16
16
  elem.attributes.each() do |attr|
17
- name = attr[0]
18
- value = attr[1]
19
- case name
20
- when 'date'
21
- forecast.date = DateTime.parse(value)
22
- when 'temp'
23
- forecast.temp = value.to_i
24
- when 'text'
25
- forecast.condition = get_condition(value)
26
- end
17
+ hash[attr[0].to_sym] = attr[1]
27
18
  end
28
19
  end
20
+ forecast = get_forecast({latitude: latitude, longitude: longitude}.merge(hash))
29
21
  end
30
22
  return forecast
31
23
  end
@@ -40,24 +32,11 @@ class Forecast
40
32
  forecasts = Forecast::Collection.new
41
33
  if doc
42
34
  doc.elements.each('rss/channel/item/yweather:forecast') do |elem|
43
- forecast = Forecast.new
35
+ hash = {}
44
36
  elem.attributes.each() do |attr|
45
- puts 'attr' + attr.to_s
46
- name = attr[0]
47
- value = attr[1]
48
- case name
49
- when 'date'
50
- forecast.date = DateTime.parse(value)
51
- when 'low'
52
- forecast.temp_min = get_temp(value)
53
- when 'high'
54
- forecast.temp_max = get_temp(value)
55
- when 'text'
56
- forecast.condition = get_condition(value)
57
- end
37
+ hash[attr[0].to_sym] = attr[1]
58
38
  end
59
- forecast.temp = (forecast.temp_min + forecast.temp_max) / 2
60
- forecasts << forecast
39
+ forecasts << get_forecast({latitude: latitude, longitude: longitude}.merge(hash))
61
40
  end
62
41
  end
63
42
  return forecasts
@@ -69,7 +48,7 @@ class Forecast
69
48
  woeid = nil
70
49
  query = "SELECT * FROM geo.placefinder WHERE text='#{latitude}, #{longitude}' and gflags='R'"
71
50
  url = URL_YQL + "?q=" + URI::encode(query)
72
- doc = get_doc(url)
51
+ doc = get_dom(url)
73
52
  doc.elements.each('query/results/Result/woeid') do |elem|
74
53
  woeid = elem.text
75
54
  end
@@ -79,12 +58,25 @@ class Forecast
79
58
  def get_rss(latitude, longitude)
80
59
  woeid = get_woeid(latitude, longitude)
81
60
  if woeid
82
- doc = get_doc(URL_RSS, {w: woeid})
61
+ doc = Forecast::Utils.get_doc(URL_RSS, {w: woeid})
83
62
  return doc
84
63
  end
85
64
  return nil
86
65
  end
87
66
 
67
+ def get_forecast(hash)
68
+ forecast = Forecast.new
69
+ forecast.latitude = hash[:latitude]
70
+ forecast.longitude = hash[:longitude]
71
+ forecast.time = get_time(hash[:date])
72
+ forecast.condition = get_condition(hash[:text])
73
+ forecast.text = get_text(hash[:text])
74
+ forecast.temperature_min = get_temperature(hash[:low])
75
+ forecast.temperature_max = get_temperature(hash[:high])
76
+ forecast.temperature = get_temperature(hash.has_key?(:temp) ? hash[:temp] : [hash[:low], hash[:high]])
77
+ return forecast
78
+ end
79
+
88
80
  end
89
81
  end
90
82
  end
@@ -1,15 +1,15 @@
1
1
  class Forecast
2
2
  class Collection < Array
3
- def select_date(date)
3
+ def select_time(time)
4
4
  result = nil
5
5
  date_forecasts = self.select do |obj|
6
- obj.date.to_date == date.to_date
6
+ obj.time.to_date == time.to_date
7
7
  end
8
8
  if date_forecasts.length == 0
9
9
  return nil
10
10
  else
11
11
  hour_forecasts = date_forecasts.select do |obj|
12
- obj.date.hour == obj.date.hour
12
+ obj.time.hour == obj.time.hour
13
13
  end
14
14
  if hour_forecasts.length > 0
15
15
  return hour_forecasts.first
@@ -17,10 +17,10 @@ class Forecast
17
17
  return date_forecasts.first
18
18
  end
19
19
  return nil
20
-
21
20
  end
22
21
 
23
22
  private
23
+ # Unused
24
24
  def seconds_between(date1, date2)
25
25
  ((Time.parse(date1.to_s) - Time.parse(date2.to_s)) / 3600).abs
26
26
  end
@@ -1,5 +1,5 @@
1
1
  forecast:
2
- conditions:
2
+ conditions:
3
3
  100: 'Clear'
4
4
  200: 'Partly Cloudy'
5
5
  210: 'Cloudy'
@@ -9,4 +9,4 @@ forecast:
9
9
  320: 'Heavy Rain'
10
10
  400: 'Light Snow'
11
11
  410: 'Snow'
12
- 500: 'Thunderstorm'
12
+ 501: 'Storm'
@@ -1,13 +1,13 @@
1
+ require "yaml"
1
2
  class Forecast
2
3
  class Config
3
4
 
4
- attr_accessor :adapters, :provider, :temp_scale, :conditions, :cache, :themes, :theme, :config_file
5
+ attr_accessor :adapters, :provider, :scale, :conditions, :synonyms, :cache, :themes, :theme, :config_file
5
6
 
6
7
  def initialize
7
8
 
8
9
  @config_file = nil
9
- #File.dirname(File.dirname(File.dirname(__FILE__))) + "/config/forecast.yml"
10
-
10
+ @provider||= :open_weather_map
11
11
  self.load(File.dirname(__FILE__) + '/**/*.yml')
12
12
 
13
13
  def theme
@@ -21,11 +21,10 @@ class Forecast
21
21
  end
22
22
  return @theme
23
23
  end
24
-
25
24
  end
26
25
 
26
+
27
27
  def load(pattern)
28
- # puts 'load forecast pattern ' + pattern.to_s
29
28
  Dir.glob(pattern).sort{ |a, b| a.split(/\//).length <=> b.split(/\//).length}.reverse.each do |f|
30
29
  obj = YAML.load_file(f)
31
30
  # puts 'load forecast config ' + f.to_s
@@ -1,6 +1,4 @@
1
1
  forecast:
2
- provider:
3
- adapter: open_weather_map
2
+ provider: open_weather_map
4
3
  theme: weather_icons
5
- temp_scale: fahrenheit
6
- cache: false
4
+ scale: celsius
@@ -0,0 +1,80 @@
1
+ require 'net/http'
2
+ require 'rexml/document'
3
+ require 'open-uri'
4
+ require 'json'
5
+ require "redis"
6
+
7
+ class Forecast
8
+
9
+ class Http
10
+
11
+ def initialize(options = {})
12
+ @options = {cache: nil}.merge options
13
+ if @options[:cache]
14
+ cache_options = @options[:cache].merge({
15
+ host: "127.0.0.1",
16
+ port: "6379"
17
+ })
18
+ if cache_options.has_key?(:url)
19
+ url = cache_options[:url]
20
+ uri = URI.parse(url)
21
+ #puts "Connecting to redis with url #{url}..."
22
+ @cache = Redis.new(host: uri.host, port: uri.port, password: uri.password)
23
+ elsif host != nil && port != nil
24
+ #puts "Connecting to redis on host #{host} at port #{port}..."
25
+ @cache = Redis.new(host: cache_options[:host], port: cache_options[:port])
26
+ end
27
+ end
28
+ end
29
+
30
+ def get(url, params = {})
31
+ if @cache && (!@options[:cache].has_key?(:invalidate) || !@options[:cache][:invalidate])
32
+ key = get_key(url)
33
+ data = @cache.get(key)
34
+ if data
35
+ #puts 'Read from cache... ' + url
36
+ return data
37
+ end
38
+ end
39
+ if params.keys.count > 0
40
+ query_string = URI.encode_www_form(params)
41
+ url = url + "?" + query_string
42
+ end
43
+ #puts 'Get url... ' + url
44
+ resp = Net::HTTP.get_response(URI.parse(url))
45
+ data = resp.body
46
+ if data
47
+ if @cache
48
+ #puts 'Write to cache... ' + url
49
+ key = get_key(url)
50
+ @cache.set(key, data)
51
+ if @options[:cache] && @options[:cache].has_key?(:expire)
52
+ @cache.expire(key, @options[:cache][:expire])
53
+ end
54
+ end
55
+ return data
56
+ end
57
+ end
58
+
59
+ def get_json(url, params = {})
60
+ data = get(url, params)
61
+ if data != nil
62
+ return JSON.parse(data)
63
+ end
64
+ end
65
+
66
+ def get_dom(url, params = {})
67
+ data = get(url, params)
68
+ if data != nil
69
+ return REXML::Document.new(data)
70
+ end
71
+ end
72
+
73
+
74
+ private
75
+ def get_key(url)
76
+ "Forecast::Http::" + url
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,32 @@
1
+ forecast:
2
+ synonyms:
3
+ -
4
+ - 'Clear'
5
+ - 'Sunny'
6
+ - 'Sunshine'
7
+ - 'No Clouds'
8
+ - 'Fair'
9
+ - 'Breezy'
10
+ -
11
+ - 'Light Rain'
12
+ - 'Drizzle'
13
+ - 'Light showers'
14
+ -
15
+ - 'Rain'
16
+ - 'Showers'
17
+ -
18
+ - 'Heavy Rain'
19
+ - 'Thundershowers'
20
+ -
21
+ - 'Partly Cloudy'
22
+ - 'Clouds Sun'
23
+ - 'Fog Sun'
24
+ - 'Few Clouds'
25
+ -
26
+ - 'Overcast'
27
+ - 'Cloudy'
28
+ - 'Clouds'
29
+ -
30
+ - 'Storm'
31
+ - 'Thunderstorm'
32
+ - 'Blizzard'
@@ -1,10 +1,12 @@
1
1
  forecast:
2
2
  themes:
3
- weather_icons:
4
- Clear: "wi wi-day-sunny"
5
- Partly Cloudy: "wi wi-day-sunny-overcast"
6
- Cloudy: "wi wi-day-cloudy"
7
- Mostly Cloudy: "wi wi-cloudy"
8
- Light Rain: "wi wi-showers"
9
- Rain: "wi wi-rain"
10
- Heavy Rain: "wi wi-rain"
3
+ weather_icons:
4
+ prefix: 'wi wi-'
5
+ conditions:
6
+ Clear: "day-sunny"
7
+ Partly Cloudy: "sunny-overcast"
8
+ Cloudy: "day-cloudy"
9
+ Mostly Cloudy: "cloudy"
10
+ Light Rain: "showers"
11
+ Rain: "rain"
12
+ Heavy Rain: "rain"
@@ -7,9 +7,50 @@ class Forecast
7
7
  module Utils
8
8
  class << self
9
9
 
10
- def get_json(url, params)
11
- query_string = URI.encode_www_form(params)
12
- url = url + "?" + query_string
10
+
11
+ def underscore(string)
12
+ if string.is_a?(String)
13
+ return string.gsub(/::/, '/').
14
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
15
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
16
+ tr("-", "_").
17
+ downcase
18
+ elsif string.is_a?(Hash)
19
+ return Hash[string.map { |k, v| [Forecast::Utils.underscore(k.to_s).to_sym, v.is_a?(Hash) ? Forecast::Utils.underscore(v) : v] }]
20
+ else
21
+ string
22
+ end
23
+ end
24
+
25
+ def fahrenheit_to_kelvin(fahrenheit)
26
+ ((fahrenheit - 32) / 1.8) - 273.15
27
+ end
28
+
29
+ def fahrenheit_to_celsius(fahrenheit)
30
+ ((fahrenheit - 32) / 1.8)
31
+ end
32
+
33
+ def kelvin_to_fahrenheit(kelvin)
34
+ return ((kelvin - 273.15) * 1.8 + 32)
35
+ end
36
+
37
+ def kelvin_to_celsius(kelvin)
38
+ kelvin - 273.15
39
+ end
40
+
41
+ def celsius_to_fahrenheit(celsius)
42
+ celsius * 1.8 + 32
43
+ end
44
+
45
+ def celsius_to_kelvin(celsius)
46
+ celsius + 273.15
47
+ end
48
+
49
+ def get_json(url, params = {})
50
+ if params.keys.count > 0
51
+ query_string = URI.encode_www_form(params)
52
+ url = url + "?" + query_string
53
+ end
13
54
  resp = Net::HTTP.get_response(URI.parse(url))
14
55
  data = resp.body
15
56
  result = JSON.parse(data)
@@ -19,14 +60,68 @@ class Forecast
19
60
  return nil
20
61
  end
21
62
 
22
- def get_doc(url, params)
23
- query_string = URI.encode_www_form(params)
24
- url = url + "?" + query_string
63
+ def get_doc(url, params = {})
64
+ if params.keys.count > 0
65
+ query_string = URI.encode_www_form(params)
66
+ url = url + "?" + query_string
67
+ end
25
68
  xml_data = Net::HTTP.get_response(URI.parse(url)).body
26
69
  doc = REXML::Document.new(xml_data)
27
70
  return doc
28
71
  end
29
72
 
73
+ def word_similarity(first, second)
74
+
75
+ if !first.is_a?(String) || !second.is_a?(String)
76
+ return 0
77
+ end
78
+ similar_words = 0.0
79
+ first_words = first.downcase.split(/\W+/)
80
+ second_words = second.downcase.split(/\W+/)
81
+
82
+ first_words.each do |first_word|
83
+ second_words.each do |second_word|
84
+ similar = 0.0
85
+ if first_word == second_word
86
+ similar = 1.0
87
+ else
88
+ l1 = levenshtein(first_word, second_word)
89
+ l = 1 - (l1.to_f / ([first_word.length, second_word.length].max))
90
+ l = [0, similar].max
91
+ l = [similar, 1].min
92
+ if l1 > 0.6
93
+ similar = 0.1
94
+ end
95
+ end
96
+ similar_words+= similar
97
+ end
98
+ end
99
+ count = first_words.concat(second_words).uniq.length
100
+ similarity = similar_words / count
101
+ return similarity
102
+ end
103
+
104
+ def levenshtein(first, second)
105
+ matrix = [(0..first.length).to_a]
106
+ (1..second.length).each do |j|
107
+ matrix << [j] + [0] * (first.length)
108
+ end
109
+ (1..second.length).each do |i|
110
+ (1..first.length).each do |j|
111
+ if first[j-1] == second[i-1]
112
+ matrix[i][j] = matrix[i-1][j-1]
113
+ else
114
+ matrix[i][j] = [
115
+ matrix[i-1][j],
116
+ matrix[i][j-1],
117
+ matrix[i-1][j-1],
118
+ ].min + 1
119
+ end
120
+ end
121
+ end
122
+ return matrix.last.last
123
+ end
124
+
30
125
  end
31
126
  end
32
127
  end
@@ -1,3 +1,3 @@
1
1
  class Forecast
2
- VERSION = "0.0.7"
2
+ VERSION = "0.0.8"
3
3
  end
data/lib/forecast.rb CHANGED
@@ -1,141 +1,73 @@
1
+ require 'ostruct'
1
2
  require "forecast/version"
2
3
  require "forecast/config"
3
- require "forecast/model"
4
+ require "forecast/utils"
4
5
  require "forecast/collection"
5
6
  require "forecast/adapter"
6
7
  require "forecast/adapters/yahoo_adapter"
7
8
  require "forecast/adapters/open_weather_map_adapter"
8
9
  require "forecast/adapters/wunderground_adapter"
9
- require "yaml"
10
- require "redis"
10
+ require "forecast/adapters/forecast_io_adapter"
11
+ require "forecast/http"
11
12
 
12
13
  class Forecast
13
14
 
14
- # instance
15
+ PROVIDERS = Dir.glob(File.expand_path(File.dirname(__FILE__) + '/forecast/adapters/*.*')).map{ |f| File.basename(f, '_adapter.rb') };
15
16
 
16
- include Forecast::Model
17
+ def method_missing(method, *args, &block)
18
+ @source.send(method, *args, &block)
19
+ end
20
+
21
+ def initialize(attrs = {})
22
+ @source = OpenStruct.new(attrs)
23
+ end
17
24
 
18
- attr_accessor :latitude, :longitude, :date, :temp, :temp_min, :temp_max, :condition, :orig_condition
25
+ def as_json(options = nil)
26
+ @source.table.as_json(options)
27
+ end
28
+
29
+ def to_json *a
30
+ self.marshal_dump.to_json a
31
+ end
32
+
33
+ def icon
34
+ # Pick icon from theme
35
+ if self.condition != nil && Forecast.config.theme.is_a?(Hash)
36
+ icon_prefix = Forecast.config.theme.has_key?('prefix') ? Forecast.config.theme['prefix'] : ''
37
+ icon_suffix = Forecast.config.theme.has_key?('suffix') ? Forecast.config.theme['suffix'] : ''
38
+ icon_name = Forecast.config.theme['conditions'].has_key?(self.condition) ? Forecast.config.theme['conditions'][self.condition] : self.condition
39
+ # Dasherize
40
+ icon_name = icon_name.to_s.gsub(/(.)([A-Z])/,'\1-\2').gsub(/\s*/, '').downcase
41
+ icon = icon_prefix + icon_name + icon_suffix
42
+ return icon != nil ? icon : self.icon
43
+ end
44
+ # Slugified condition as icon name
45
+ self.condition.is_a?(String) && self.condition.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
46
+ end
19
47
 
20
48
  # class
21
49
  class << self
22
50
 
23
51
  def current(latitude, longitude)
24
- cache_key = "current:#{latitude},#{longitude}"
25
- forecast = read_cache(cache_key)
26
- if forecast == nil
27
- forecast = adapter.current(latitude, longitude)
28
- write_cache(cache_key, forecast)
29
- end
30
- return forecast
52
+ return adapter.current(latitude, longitude)
31
53
  end
32
54
 
33
55
  def hourly(latitude, longitude)
34
- cache_key = "hourly:#{latitude},#{longitude}"
35
- forecasts = read_cache(cache_key)
36
- if forecasts == nil
37
- forecasts = adapter.hourly(latitude, longitude)
38
- write_cache(cache_key, forecasts)
39
- end
40
- return forecasts
56
+ return adapter.hourly(latitude, longitude)
41
57
  end
42
58
 
43
59
  def daily(latitude, longitude)
44
- cache_key = "daily:#{latitude},#{longitude}"
45
- forecasts = read_cache(cache_key)
46
- if forecasts == nil
47
- forecasts = adapter.daily(latitude, longitude)
48
- write_cache(cache_key, forecasts)
49
- end
50
- return forecasts
60
+ return adapter.daily(latitude, longitude)
51
61
  end
52
62
 
53
- private
54
-
55
- @adapter = nil
56
-
63
+ private
64
+
57
65
  def adapter
58
66
  if @adapter == nil
59
67
  @adapter = Forecast::Adapter.instance
60
68
  end
61
69
  return @adapter
62
70
  end
63
-
64
-
65
- @cache = nil
66
-
67
- def cache
68
- cache = Forecast.config.cache
69
- if @cache == nil && ( cache != nil || (!!cache == cache) && cache == true )
70
- if !!cache == cache
71
- Forecast.config.cache = {
72
- expire: 5,
73
- prefix: :forecast,
74
- host: "127.0.0.1",
75
- port: "6379",
76
- url: nil
77
- }
78
- end
79
- cache_config = Forecast.config.cache
80
- begin
81
- if cache_config['url'] != nil
82
- redis_url = cache_config['url']
83
- uri = URI.parse(redis_url)
84
- @cache = Redis.new(host: uri.host, port: uri.port, password: uri.password)
85
- else
86
- @cache = Redis.new(host: cache_config['host'], port: cache_config['port'])
87
- end
88
- @cache.ping
89
- rescue
90
- puts "error connecting to redis"
91
- end
92
- end
93
- return @cache
94
- end
95
-
96
- def cache_key(key)
97
- cache_prefix = Forecast.config.cache[:prefix]
98
- return "#{cache_prefix.to_s}:#{key}"
99
- end
100
-
101
- def cache_expire
102
- cache_expire = Forecast.config.cache[:expire]
103
- end
104
-
105
- def write_cache(key, data)
106
- if cache == nil
107
- return;
108
- end
109
- puts "WRITE TO CACHE... " + cache_key(key).to_s + ", cache_expire: " + cache_expire.to_s
110
- cache.set(cache_key(key), data.to_json)
111
- cache.expire(cache_key(key), cache_expire)
112
- end
113
-
114
- def read_cache(key)
115
- if cache == nil
116
- return nil;
117
- end
118
- cached_result = cache.get(cache_key(key))
119
- result = nil
120
- if cached_result != nil
121
- puts "READ FROM CACHE: " + cache_key(key).to_s + ", cache_expire: " + cache_expire.to_s
122
- json = JSON.parse(cached_result)
123
- if json.is_a?(Array)
124
- result = Forecast::Collection.new
125
- json.each do |obj|
126
- forecast = Forecast.new
127
- forecast.from_json(obj)
128
- result << forecast
129
- end
130
- elsif json.is_a?(Object)
131
- result = Forecast.new
132
- result.from_json(json)
133
- end
134
- end
135
- return result
136
- end
137
-
138
-
139
71
 
140
72
  end
141
73
 
@@ -0,0 +1,21 @@
1
+ New York:
2
+ latitude: 41.145495
3
+ longitude: -73.994901
4
+ Miami:
5
+ latitude: 25.775278
6
+ longitude: -80.208889
7
+ San Francisco:
8
+ latitude: 37.783333
9
+ longitude: -122.416667
10
+ London:
11
+ latitude: 51.50939
12
+ longitude: -0.11832
13
+ Johannesburg:
14
+ latitude: -26.204444
15
+ longitude: 28.045556
16
+ Sidney:
17
+ latitude: -33.865
18
+ longitude: 151.209444
19
+ Hamburg:
20
+ latitude: 53.55075
21
+ longitude: 9.93026