forecast 0.0.7 → 0.0.8

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