class YrWeather

Constants

ARC
COMPASS_BEARINGS
YR_NO

Attributes

configuration[RW]

Public Class Methods

config() { |configuration| ... } click to toggle source
# File lib/yr_weather.rb, line 13
def self.config
  self.configuration ||= YrWeather::Configuration.new
  yield(configuration)
end
new(latitude:, longitude:, utc_offset: nil) click to toggle source

YrWeather.get(latitude: -33.9531096408383, longitude: 18.4806353422955) def self.get(sitename:, latitude:, longitude:, utc_offset: '+00:00', limit_to: nil)

YrWeather.new(latitude: latitude, longitude: longitude, utc_offset: utc_offset).process(limit_to)

end

# File lib/yr_weather.rb, line 104
def initialize(latitude:, longitude:, utc_offset: nil)
  @latitude     = latitude.round(4)   # yr developer page requests four decimals.
  @longitude    = longitude.round(4)
  @utc_offset   = utc_offset || YrWeather.configuration.utc_offset || '+00:00'
  @now          = Time.now.localtime(@utc_offset)
  @start_of_day = Time.local(@now.year, @now.month, @now.day)
  @start_of_day = @start_of_day + 24*60*60  if @now.hour >= 20
  raise 'yr.no reqiure a sitename and email. See readme for details'  unless YrWeather.configuration.sitename=~/@/
  params        = { latitude: @latitude,  longitude: @longitude, redis: YrWeather.configuration.redis }
  @cacher       = (YrWeather.configuration.redis ? YrWeather::RedisCache.new(params) : YrWeather::FileCache.new(params))
  @data         = load_forecast
end

Public Instance Methods

arrays() click to toggle source
# File lib/yr_weather.rb, line 207
def arrays
  nodes  = @data.dig(:properties, :timeseries)
  points = nodes.map do |node|
    {
      at:            node[:time],
      temperature:   node.dig(:data, :instant, :details, :air_temperature),
      wind_speed:    node.dig(:data, :instant, :details, :wind_speed),
      precipitation: node.dig(:data, :next_1_hours, :details, :precipitation_amount) || node.dig(:data, :next_6_hours, :details, :precipitation_amount),
      hours:         ( node.dig(:data, :next_1_hours, :details, :precipitation_amount) ? 1 : 6),
    }
  end
  results = {
    from:             [],
    to:               [],
    temperature:      [],
    wind_speed:       [],
    wind_speed_knots: [],
    precipitation:    [],
    hours:            [],
  }
  points.each do |point|
    point[:hours].times do |i|
      results[:from]             << point[:at] + i*60*60
      results[:to]               << point[:at] + (i+1)*60*60
      results[:temperature]      << point[:temperature]
      results[:wind_speed]       << point[:wind_speed]
      results[:wind_speed_knots] << to_knots(point[:wind_speed])
      results[:precipitation]    << ((point[:precipitation].to_f) / (point[:hours].to_f)).round(1)
    end
  end
  results
end
current() click to toggle source
# File lib/yr_weather.rb, line 138
def current
  time = @data.dig(:properties, :timeseries).map { |e| e[:time] }.reject { |e| e>@now }.sort.last
  node = @data.dig(:properties, :timeseries).select { |e| e[:time]==time }.first
  node.dig(:data, :instant, :details).merge({
    at:                   time,
    symbol_code:          node.dig(:data, :next_1_hours, :summary, :symbol_code),
    precipitation_amount: node.dig(:data, :next_1_hours, :details, :precipitation_amount),
    wind_direction:       degrees_to_bearing(node.dig(:data, :instant, :details, :wind_from_direction)),
    wind_description:     wind_description(node.dig(:data, :instant, :details, :wind_speed)),
    wind_speed_knots:     to_knots(node.dig(:data, :instant, :details, :wind_speed)),
  })
end
daily() click to toggle source
# File lib/yr_weather.rb, line 199
def daily
  8.times.map do |day|
    start = @start_of_day + day*24*60*60
    range = start..(start + 24*60*60)
    forecast(range).merge(from: start, to: start + 24*60*60)
  end
end
initialised?() click to toggle source
# File lib/yr_weather.rb, line 117
def initialised?
  !@data.nil?
end
metadata() click to toggle source
# File lib/yr_weather.rb, line 125
def metadata
  {
    forecast_updated_at: @data.dig(:properties, :meta, :updated_at),
    downloaded_at:       @data[:downloaded_at],
    expires_at:          @data[:expires],
    start_of_day:        @start_of_day,
    latitude:            @data.dig(:geometry, :coordinates)[1],
    longitude:           @data.dig(:geometry, :coordinates)[0],
    msl:                 @data.dig(:geometry, :coordinates)[2],
    units:               @data.dig(:properties, :meta, :units),
  }
end
next_12_hours() click to toggle source
# File lib/yr_weather.rb, line 151
def next_12_hours
  range = @now..(@now + 12*60*60)
  forecast(range).merge(symbol: symbol_code_hourly(range))
end
raw() click to toggle source
# File lib/yr_weather.rb, line 121
def raw
  @data
end
six_hourly() click to toggle source
# File lib/yr_weather.rb, line 171
def six_hourly
  t = @start_of_day
  loop do
    if (t + 6*60*60) > Time.now
      break
    else
      t = t + 6*60*60
    end
  end
  nodes = @data.dig(:properties, :timeseries).select { |e| e.dig(:data, :next_6_hours) }.map { |e| [e[:time], e] }.to_h
  nodes = 20.times.map do |i|
    nodes[t + i*6*60*60]
  end.compact.map do |node|
    {
      from:                 node.dig(:time),
      to:                   node.dig(:time) + 6*60*60,
      temperature_maximum:  node.dig(:data, :next_6_hours, :details, :air_temperature_max),
      temperature_minimum:  node.dig(:data, :next_6_hours, :details, :air_temperature_min),
      wind_speed_max:       node.dig(:data, :instant, :details, :wind_speed),
      wind_speed_max_knots: to_knots(node.dig(:data, :instant, :details, :wind_speed)),
      wind_direction:       degrees_to_bearing(node.dig(:data, :instant, :details, :wind_from_direction)),
      wind_description:     wind_description(node.dig(:data, :instant, :details, :wind_speed)),
      precipitation:        node.dig(:data, :next_6_hours, :details, :precipitation_amount),
      symbol_code:          node.dig(:data, :next_6_hours, :summary, :symbol_code),
    }
  end
end
three_days() click to toggle source
# File lib/yr_weather.rb, line 161
def three_days
  range = @now..(@now + 3*24*60*60)
  forecast(range).tap { |hs| hs.delete(:wind_description) }
end
tomorrow() click to toggle source
# File lib/yr_weather.rb, line 156
def tomorrow
  range = (@start_of_day + 24*60*60)..(@start_of_day + 2*24*60*60)
  forecast(range).tap { |hs| hs.delete(:wind_description) }
end
week() click to toggle source
# File lib/yr_weather.rb, line 166
def week
  range = @now..(@now + 7*24*60*60)
  forecast(range).tap { |hs| hs.delete(:wind_description) }
end

Private Instance Methods

degrees_to_bearing(degrees) click to toggle source
# File lib/yr_weather.rb, line 285
def degrees_to_bearing(degrees)
  COMPASS_BEARINGS[(degrees.to_f/ARC).round % COMPASS_BEARINGS.length]
end
forecast(range) click to toggle source
# File lib/yr_weather.rb, line 243
def forecast(range)
  nodes  = nodes_for_range(range)
  detail = nodes.map { |e| e.dig(:data, :instant, :details) }
  wind_directions = detail.map { |e| degrees_to_bearing(e[:wind_from_direction]) }
  {
    temperature_maximum:  detail.map { |e| e[:air_temperature] }.max,
    temperature_minimum:  detail.map { |e| e[:air_temperature] }.min,
    wind_speed_max:       detail.map { |e| e[:wind_speed] }.max,
    wind_speed_max_knots: to_knots(detail.map { |e| e[:wind_speed] }.max),
    wind_description:     wind_description(detail.map { |e| e[:wind_speed] }.max),
    wind_direction:       wind_directions.max_by { |e| wind_directions.count(e) },
    precipitation:        precipitation(range, nodes)
  }
end
forecast_from_yr() click to toggle source

def parse

%w(hourly today tomorrow three_days week daily daily_objects hourly_objects).map(&:to_sym).map { |e| [e, self.send(e)] }

end

# File lib/yr_weather.rb, line 354
def forecast_from_yr
  url                     = URI("#{YR_NO}?lat=#{@latitude}&lon=#{@longitude}")
  https                   = Net::HTTP.new(url.host, url.port)
  https.use_ssl           = true
  request                 = Net::HTTP::Get.new(url)
  request["Content-Type"] = "application/json"
  request["User-Agent"]   = YrWeather.configuration.sitename
  response                = https.request(request)
  {
    expires:       Time.parse(response['expires']),
    last_modified: Time.parse(response['last-modified']),
    downloaded_at: Time.now,
  }.merge(parse_json(response.body))
end
load_forecast() click to toggle source
# File lib/yr_weather.rb, line 313
def load_forecast
  data  = @cacher.from_cache
  data = parse_json(data)  if !data.nil?
  if data.nil?
    data  = forecast_from_yr
    @cacher.to_cache(data)
  end
  data
end
nodes_for_range(range) click to toggle source
# File lib/yr_weather.rb, line 281
def nodes_for_range(range)
  @data.dig(:properties, :timeseries).select { |e| range.include?(e[:time]) }
end
parse_json(json) click to toggle source
# File lib/yr_weather.rb, line 323
def parse_json(json)
  parse_times(JSON.parse(json, symbolize_names: true))
end
parse_times(hash) click to toggle source
# File lib/yr_weather.rb, line 328
def parse_times(hash)
  if (hash.is_a?(Hash))
    hash.transform_values do |v|
      if v.is_a?(Hash)
        parse_times(v)
      elsif v.is_a?(Array)
        v.map { |e| parse_times(e) }
      elsif v.is_a?(String) && v=~/\d{4}-\d\d-\d\d[\sT]\d\d:\d\d:\d\d/
        Time.parse(v)
        # r = Time.parse(v) rescue nil
        # (r || v)
      else
        v
      end 
    end
  else
    hash
  end
end
precipitation(range, nodes) click to toggle source
# File lib/yr_weather.rb, line 258
def precipitation(range, nodes)
  next_time = range.first
  end_time  = range.last
  nodes.map do |node|
    mm = nil
    if node[:time] >= next_time
      [1,6,12].each do |i|
        mm = node.dig(:data, "next_#{i}_hours".to_sym, :details, :precipitation_amount)
        if mm
          next_time = next_time + i*60*60
          break
        end
      end
    end
    mm
  end.sum
end
symbol_code_hourly(range) click to toggle source
# File lib/yr_weather.rb, line 276
def symbol_code_hourly(range)
  symbols = nodes_for_range(@now..(@now + 12*60*60)).map { |e| e.dig(:data, :next_1_hours, :summary, :symbol_code) }
  symbols.max_by { |e| symbols.count(e) }
end
to_knots(ms) click to toggle source
# File lib/yr_weather.rb, line 289
def to_knots(ms)
  ( ms ? (ms*1.943844).round(1) : nil )
end
wind_description(speed) click to toggle source
# File lib/yr_weather.rb, line 293
def wind_description(speed)
  ms = speed.round(1)
  case ms
  when (0..(0.5))       then 'calm'
  when ((0.5)..(1.5))   then 'light air'
  when ((1.6)..(3.3))   then 'light breeze'
  when ((4)..(5.5))     then 'gentle breeze'
  when ((5.5)..(7.9))   then 'moderate breeze'
  when ((8)..(10.7))    then 'fresh breeze'
  when ((10.8)..(13.8)) then 'strong breeze'
  when ((13.9)..(17.1)) then 'high wind,'
  when ((17.2)..(20.7)) then 'gale'
  when ((20.8)..(24.4)) then 'strong gale'
  when ((24.5)..(28.4)) then 'storm'
  when ((28.5)..(32.6)) then 'violent storm'
  else 'hurricane force'
  end
end