require 'tlaw' module Reality module Describers class OpenWeatherMap < Abstract::Base def initialize(key) @api = API.new(appid: key, units: :metric) end DROP = %[weather.id sys.id sys.type sys.message cod base].freeze RENAME = {'weather.main' => 'weather', 'dt' => 'timestamp'}.freeze CONVERT = { 'temp' => Measure['°C'], 'temp_min' => Measure['°C'], 'temp_max' => Measure['°C'], 'pressure' => Measure['hPa'], 'humidity' => Measure['%'], 'wind_speed' => Measure['m/s'], 'wind_deg' => Measure['°'], }.freeze def perform_query(params) ENV.key?('OPEN_WEATHER_MAP_APPID') or fail "OPEN_WEATHER_MAP_APPID environment variable is missing" at = params.delete('at') lat, lng = case at when Array at when Geo::Coord at.latlng when Entity (at['coordinates'] || at['coordinate location'])&.latlng when /^\d+(?:\.\d+)?[,\| ;]\d+(?:\.\d+)?$/ at.split(/[,\| ;]/).map(&:to_f) end @api.current(lat, lng).yield_self { |response| eid = response.delete('id') response.reject { |key, _| DROP.include?(key) } .transform_keys { |key| RENAME.fetch(key, key.sub(/^(main|sys)\./, '').sub('.', '_')) } .map { |key, value| converter = CONVERT.fetch(key, :itself).to_proc obs(eid, key, converter.(value)) } }.yield_self(&method(:make_entities)) end private def prefix 'openweathermap' end class API < TLAW::API define do base 'http://api.openweathermap.org/data/2.5' param :appid, required: true param :lang, default: 'en' param :units, enum: %i[standard metric imperial], default: :metric endpoint :current, '/weather?lat={lat}&lon={lng}' do param :lat, :to_f, required: true, desc: 'Latitude' param :lng, :to_f, required: true, desc: 'Longitude' post_process { |e| e['coord'] = Geo::Coord.new(e.delete('coord.lat'), e.delete('coord.lon')) if e['coord.lat'] && e['coord.lon'] } post_process 'dt', &Time.method(:at) post_process 'sys.sunrise', &Time.method(:at) post_process 'sys.sunset', &Time.method(:at) end endpoint :forecast, '/forecast?lat={lat}&lon={lng}' do param :lat, :to_f, required: true, desc: 'Latitude' param :lng, :to_f, required: true, desc: 'Longitude' post_process { |e| e['coord'] = Geo::Coord.new(e['city.coord.lat'], e['city.coord.lon']) if e['city.coord.lat'] && e['city.coord.lon'] } end end end end end end Reality.describers['openweathermap'] = Reality.describers['open_weather_map'] = Reality::Describers::OpenWeatherMap.new(ENV['OPEN_WEATHER_MAP_APPID'])