jw-weather
Advanced tools
Comparing version 2.0.1 to 2.1.0
{ | ||
"name": "jw-weather", | ||
"version": "2.0.1", | ||
"version": "2.1.0", | ||
"description": "Reader for various weather APIs", | ||
@@ -14,3 +14,7 @@ "main": "weather.js", | ||
"keywords": [ | ||
"Weather", "DarkSky","AccuWeather","Forecast" | ||
"Weather", | ||
"DarkSky", | ||
"AccuWeather", | ||
"OpenWeatherMap", | ||
"Forecast" | ||
], | ||
@@ -22,3 +26,6 @@ "author": "Jonathan Wyett", | ||
}, | ||
"homepage": "https://github.com/jonwyett/weather#readme" | ||
"homepage": "https://github.com/jonwyett/weather#readme", | ||
"dependencies": { | ||
"jw-gate": "^1.0.0" | ||
} | ||
} |
@@ -5,7 +5,9 @@ # jw-weather | ||
Currently supports: | ||
- **DarkSky** | ||
- **DarkSky** (at least until Apple shuts it down...) | ||
- **AccuWeather** | ||
- **OpenWeatherMap** | ||
- ~~Weather UnderGround~~ *(API depriciated)* | ||
The goal is to create a weather module that returns a standardized weather object regardless of the API used. This serves a few purposes: | ||
1. Being able to seamlessly transition to another service in case the API is depreciated. (Which is why this project was started). | ||
1. Being able to seamlessly transition to another service in case the API is depreciated. *Which is why this project was started, and is already happening again with the DarkSky API.* | ||
2. Being able to switch to another service if costs or needs change. | ||
@@ -51,3 +53,3 @@ 3. Being able to use more then one service simultaneously to increase the frequency that you are able to query the APIs since the providers limit allowed queries for a given license. | ||
- provider: string, 'darksky' || 'accuweather' | ||
- provider: string, 'darksky' || 'accuweather' || 'openweathermap' | ||
- key: string, provided by weather service | ||
@@ -54,0 +56,0 @@ - latitude: number |
98
test.js
@@ -56,5 +56,5 @@ function formatDate(fullDate, formatString) { | ||
var darksky = new Weather.service({ | ||
provider: 'darksky', | ||
/* | ||
var openWeather = new Weather.service({ | ||
provider: 'OpenWeatherMap', | ||
key: '', | ||
@@ -66,32 +66,94 @@ latitude: 40.758556, | ||
darksky.on('ready', function() { | ||
console.log('Darksky Ready'); | ||
openWeather.on('ready', function() { | ||
console.log('openWeather Ready'); | ||
console.log('...update openWeather...'); | ||
openWeather.update(function() { | ||
console.log('openWeather listing:'); | ||
console.log(openWeather.fullWeather().temp); | ||
}); | ||
}); | ||
darksky.on('error', function(err) { | ||
openWeather.on('error', function(err) { | ||
console.log('Weather Error: ' + err); | ||
}); | ||
darksky.update(function() { | ||
console.log('DARKSKY:'); | ||
console.log(darksky.fullWeather().temp); | ||
*/ | ||
/* | ||
var accuWeather = new Weather.service({ | ||
provider: 'accuWeather', | ||
key: '', | ||
latitude: 40.758556, | ||
longitude: -73.765434, | ||
celsius: false | ||
}); | ||
var accuweather = new Weather.service({ | ||
provider: 'accuweather', | ||
accuWeather.on('ready', function() { | ||
console.log('accuWeather Ready'); | ||
console.log('...update accuWeather...'); | ||
accuWeather.update(function() { | ||
console.log('accuWeather listing:'); | ||
console.log(accuWeather.fullWeather().temp); | ||
}); | ||
}); | ||
accuWeather.on('error', function(err) { | ||
console.log('accuWeather Error: ' + err); | ||
}); | ||
*/ | ||
/* | ||
var darkSky = new Weather.service({ | ||
provider: 'darkSky', | ||
key: '', | ||
latitude: 40.758556, | ||
longitude: -73.765434, | ||
longitude: -73.765434, | ||
celsius: false | ||
}); | ||
accuweather.update(function() { | ||
console.log('ACCUWEATHER:'); | ||
console.log(accuweather.fullWeather()); | ||
//console.log(JSON.stringify(accuweather.raw)); | ||
darkSky.on('ready', function() { | ||
console.log('darkSky Ready'); | ||
console.log('...update darkSky...'); | ||
darkSky.update(function() { | ||
console.log('darkSky listing:'); | ||
console.log(darkSky.fullWeather().temp); | ||
}); | ||
}); | ||
accuweather.on('ready', function() { | ||
console.log('accuweather ready'); | ||
darkSky.on('error', function(err) { | ||
console.log('darkSky Error: ' + err); | ||
}); | ||
*/ | ||
var weatherbit = new Weather.service({ | ||
provider: 'weatherbit', | ||
key: '', | ||
latitude: 40.758556, | ||
longitude: -73.765434, | ||
celsius: false | ||
}); | ||
weatherbit.on('ready', function() { | ||
console.log('weatherbit Ready'); | ||
console.log('...update weatherbit...'); | ||
weatherbit.update(function() { | ||
console.log('weatherbit listing:'); | ||
console.log(weatherbit.fullWeather()); | ||
//console.log(formatTime(weatherbit.sunrise)); | ||
}); | ||
}); | ||
weatherbit.on('error', function(err) { | ||
console.log('weatherbit Error: ' + err); | ||
}); |
314
weather.js
/* | ||
ver 2.1.0 | ||
-Add OpenWetherMap support | ||
-Add WeatherBit support | ||
-Complete rewrite | ||
-require jw-gate | ||
ver 1.0.2 | ||
@@ -6,2 +12,4 @@ -includes celsius option | ||
var jwGate = require('jw-gate'); | ||
/** | ||
@@ -12,3 +20,3 @@ * Creates a service for various online weather APIs | ||
* @param {String} options.key - API Key | ||
* @param {'darksky'|'accuweather'} options.provider - The weather provider to use | ||
* @param {'darksky'|'accuweather'|'openweathermap'|weatherbit} options.provider - The weather provider to use | ||
* @param {Number} [options.latitude] - The latitude | ||
@@ -24,2 +32,6 @@ * @param {Number} [options.longitude] - the longitude | ||
this.raw = {}; //this is the raw API response from the provider | ||
//an object to hold the various data elements and urls to retrieve the data | ||
this.weatherData = {}; | ||
this.lastUpdate = null; | ||
@@ -66,5 +78,7 @@ this.forecastTime = null; | ||
} | ||
options.provider = options.provider.toLowerCase(); | ||
// TODO: Actually test that darksky is working before ready? Maybe just ping the service? | ||
if (options.provider == 'darksky') { | ||
if (options.provider === 'darksky' || | ||
options.provider === 'openweathermap' || | ||
options.provider === 'weatherbit') { | ||
@@ -80,3 +94,3 @@ _self.ready = true; | ||
} else if (options.provider == 'accuweather') { | ||
} else if (options.provider === 'accuweather') { | ||
accuweatherLocationLookup(options, function(err, locationKey) { | ||
@@ -103,7 +117,3 @@ _self.locationKey = locationKey; | ||
if (_self.ready) { | ||
if (options.provider == 'darksky') { //case insensitive | ||
getDarkSkyData(callback); | ||
} else if (options.provider == 'accuweather') { //case insensitive | ||
getAccuWeatherData(callback); | ||
} | ||
getWeatherData(callback); | ||
} else { | ||
@@ -115,62 +125,4 @@ _self.runUpdateWhenReady = true; | ||
function getDarkSkyData(callback) { | ||
var url = 'https://api.darksky.net/forecast/[KEY]/[LAT],[LONG]?exclude=["minutely","flags","alerts"]'; | ||
url = url.replace('[KEY]',options.key) | ||
.replace('[LAT]', options.latitude.toString()) | ||
.replace('[LONG]', options.longitude.toString()); | ||
getAPIData(url, function(err, weather) { | ||
if (err) { | ||
callback(err); | ||
} else { | ||
parseDarkSky(weather, callback); | ||
} | ||
}); | ||
} | ||
function getAccuWeatherData(callback) { | ||
var url = ''; | ||
//accuweather needs multiple calls, so we'll store them in an object | ||
var accuWeatherData = { | ||
CurrentConditions : {}, | ||
Forecast: {} | ||
}; | ||
getAccuWeatherCurrentConditions(accuWeatherData, function(accuWeatherData) { | ||
getAccuWeatherForecast(accuWeatherData, function(accuWeatherData) { | ||
parseAccuweather(accuWeatherData, callback); | ||
}); | ||
}); | ||
} | ||
function getAccuWeatherCurrentConditions(accuWeatherData, callback) { | ||
url = 'https://dataservice.accuweather.com/currentconditions/v1/[LOCATION]?apikey=[KEY]&details=true'; | ||
url = url.replace('[KEY]',options.key) | ||
.replace('[LOCATION]', _self.locationKey); | ||
getAPIData(url, function(err, weather) { | ||
if (err) { | ||
callback(err); | ||
} else { | ||
accuWeatherData.CurrentConditions = weather; | ||
callback(accuWeatherData); | ||
} | ||
}); | ||
} | ||
function getAccuWeatherForecast(accuWeatherData, callback) { | ||
url = 'https://dataservice.accuweather.com/forecasts/v1/daily/5day/[LOCATION]?apikey=[KEY]&details=true'; | ||
url = url.replace('[KEY]',options.key) | ||
.replace('[LOCATION]', _self.locationKey); | ||
getAPIData(url, function(err, weather) { | ||
if (err) { | ||
callback(err); | ||
} else { | ||
accuWeatherData.Forecast = weather; | ||
callback(accuWeatherData); | ||
} | ||
}); | ||
} | ||
/** | ||
/** | ||
* @returns {Object} - an object representing the top-level vars | ||
@@ -198,2 +150,98 @@ */ | ||
function getWeatherData(callback) { | ||
/* | ||
This function is perhaps to clever for it's own good. This is bad. I will attempt to explain the design: | ||
Each weather provider has 1 or more different API calls that are required to retrieve the standard data. | ||
1) Create a weather object with all of the urls needed. | ||
2) Create a jw-gate object using the weather object's keys as lock names | ||
jw-gate is a simple class for running sync events. When all the "locks" on the gate become unlocked | ||
the gate itself becomes unlocked and whatever needs to happen after all of the sync events are completed | ||
happens. | ||
3) Iterate through the weather object and get the data. This step is the confusing one because after the | ||
data is retrieved the url in the weather object will be replaced with the data itself, so in simple terms: | ||
weather { | ||
Forecast: 'https://API.com' | ||
} | ||
becomes: | ||
weather { | ||
ForeCast: { | ||
temp: 25.45, | ||
humidity: 48 | ||
} | ||
} | ||
4) Parse the weather data using a unique parser for that API | ||
5) Run the callback function | ||
*/ | ||
//will be null if the parser is successful or will contain an error | ||
var err = null; | ||
//fill the weatherData object with the needed API calls | ||
if (options.provider === 'openweathermap') { | ||
weatherData = { | ||
CurrentConditions: 'https://api.openweathermap.org/data/2.5/weather?units=imperial&lat=[LAT]&lon=[LONG]&appid=[KEY]', | ||
Forecast: 'https://api.openweathermap.org/data/2.5/forecast?units=imperial&lat=[LAT]&lon=[LONG]&appid=[KEY]' | ||
}; | ||
} else if (options.provider === 'accuweather') { | ||
weatherData = { | ||
CurrentConditions: 'https://dataservice.accuweather.com/currentconditions/v1/[LOCATION]?apikey=[KEY]&details=true', | ||
Forecast: 'https://dataservice.accuweather.com/forecasts/v1/daily/5day/[LOCATION]?apikey=[KEY]&details=true' | ||
}; | ||
} else if (options.provider === 'darksky') { | ||
weatherData = { | ||
Forecast: 'https://api.darksky.net/forecast/[KEY]/[LAT],[LONG]?exclude=["minutely","flags","alerts"]' | ||
}; | ||
} else if (options.provider === 'weatherbit') { | ||
weatherData = { | ||
Forecast: 'https://api.weatherbit.io/v2.0/forecast/daily?lat=[LAT]&lon=[LONG]&days=5&units=I&key=[KEY]', | ||
CurrentConditions: 'https://api.weatherbit.io/v2.0/current?lat=40.758556&lon=-73.765434&units=I&key=bd2295d6056e46a0adb4f2ff43569e03' | ||
}; | ||
} | ||
//get an array holding the weatherData keys | ||
var apiCalls = Object.keys(weatherData); | ||
//create a gate to allow for simultaneous API calls using the keys | ||
var apiGate = new jwGate.Gate(apiCalls, true); | ||
//this will fire when all the API calls are complete | ||
apiGate.on('unlocked', function() { | ||
if (options.provider === 'openweathermap') { | ||
err = parseOpenWeatherMap(); | ||
} else if (options.provider === 'accuweather') { | ||
err = parseAccuweather(); | ||
} else if (options.provider === 'darksky') { | ||
err = parseDarkSky(); | ||
} else if (options.provider === 'weatherbit') { | ||
err = parseWeatherBit(); | ||
} | ||
//emit the error if it has been set | ||
if (err) { emit('error', err); } | ||
//run the callback function | ||
if (typeof callback === 'function') { callback(err); } | ||
}); | ||
//replace the values in the url and get the data | ||
apiCalls.forEach(function(url) { | ||
weatherData[url] = weatherData[url].replace('[KEY]',options.key) | ||
.replace('[LAT]', options.latitude.toString()) | ||
.replace('[LONG]', options.longitude.toString()) | ||
.replace('[LOCATION]', _self.locationKey); | ||
//console.log(weatherData[url]); | ||
getAPIData(weatherData[url], function(err, weather) { | ||
if (err) { | ||
callback(err); | ||
} else { | ||
//this replaces the url with the weather data retrieved from the API | ||
weatherData[url] = weather; | ||
apiGate.lock(url, false); | ||
} | ||
}); | ||
}); | ||
} | ||
function getAPIData(url, callback) { | ||
@@ -219,6 +267,6 @@ var https = require('https'); | ||
function parseDarkSky(weather, callback) { | ||
function parseDarkSky() { | ||
_self.lastUpdate = new Date().toString(); | ||
try { | ||
//weather = JSON.parse(weather); | ||
var weather = weatherData.Forecast; | ||
if (weather.error) { | ||
@@ -265,16 +313,16 @@ if (typeof callback === 'function') {callback(weather.error); } | ||
if (typeof callback === 'function') { callback(null); } | ||
return null; | ||
} | ||
} catch (err) { | ||
_self.error = err; | ||
if (typeof callback === 'function') { callback(err); } | ||
return err; | ||
} | ||
} | ||
function parseAccuweather(accuWeatherData, callback) { | ||
function parseAccuweather() { | ||
_self.lastUpdate = new Date().toString(); | ||
_self.raw = accuWeatherData; | ||
_self.raw = weatherData; | ||
try { | ||
var CurrentConditions = accuWeatherData.CurrentConditions[0]; //break it out of the array | ||
var Forecast = accuWeatherData.Forecast; | ||
var CurrentConditions = weatherData.CurrentConditions[0]; //break it out of the array | ||
var Forecast = weatherData.Forecast; | ||
@@ -319,10 +367,120 @@ _self.temp = CurrentConditions.Temperature.Imperial.Value.toFixed(2); | ||
if (typeof callback === 'function') { callback(null); } | ||
return null; | ||
} catch (err) { | ||
_self.error = err; | ||
if (typeof callback === 'function') { callback(err); } | ||
return err; | ||
} | ||
} | ||
function parseOpenWeatherMap() { | ||
_self.lastUpdate = new Date().toString(); | ||
try { | ||
var CurrentConditions = weatherData.CurrentConditions; | ||
var Forecast = weatherData.Forecast; | ||
_self.raw = weatherData; | ||
_self.temp = CurrentConditions.main.temp.toFixed(2); | ||
_self.feelsLike = CurrentConditions.main.feels_like.toFixed(2); | ||
_self.currentCondition = CurrentConditions.weather[0].description; | ||
_self.humidity = CurrentConditions.main.humidity.toFixed(2); | ||
//_self.forecastTime = new Date(CurrentConditions * 1000); | ||
_self.sunrise = new Date(CurrentConditions.sys.sunrise * 1000); | ||
_self.sunset = new Date(CurrentConditions.sys.sunset * 1000); | ||
_self.icon = CurrentConditions.weather[0].icon; | ||
//set celsius if desired | ||
if (options.celsius) { | ||
_self.temp = toCelsius(_self.temp); | ||
_self.feelsLike = toCelsius(_self.feelsLike); | ||
} | ||
//create the forecast | ||
for (var i=0; i<5; i++) { | ||
var day = getDailyObject(); | ||
day.condition = Forecast.list[i].weather[0].description; | ||
day.feelsLikeHigh = Forecast.list[i].main.temp_max.toFixed(2); | ||
day.feelsLikeLow = Forecast.list[i].main.temp_min.toFixed(2); | ||
day.humidity = Forecast.list[i].main.humidity.toFixed(2); | ||
day.icon = null; | ||
day.sunrise = null; | ||
day.sunset = null; | ||
day.tempHigh = day.feelsLikeHigh; | ||
day.tempLow =day.feelsLikeLow; | ||
if (options.celsius) { | ||
day.feelsLikeHigh = toCelsius(day.feelsLikeHigh); | ||
day.feelsLikeLow = toCelsius(day.feelsLikeLow); | ||
day.tempHigh = toCelsius(day.tempHigh); | ||
day.tempLow = toCelsius(day.tempLow); | ||
} | ||
_self.forecast.push(day); | ||
} | ||
return null; | ||
} catch (err) { | ||
_self.error = err; | ||
return err; | ||
} | ||
} | ||
function parseWeatherBit() { | ||
_self.lastUpdate = new Date().toString(); | ||
try { | ||
_self.raw = weatherData; | ||
var CurrentConditions = weatherData.CurrentConditions.data[0]; | ||
var Forecast = weatherData.Forecast; | ||
_self.temp = CurrentConditions.temp.toFixed(2); | ||
_self.feelsLike = CurrentConditions.app_temp.toFixed(2); | ||
_self.currentCondition = CurrentConditions.weather.description; | ||
_self.humidity = CurrentConditions.rh.toFixed(2); | ||
_self.forecastTime = new Date(CurrentConditions.ts * 1000); | ||
_self.sunrise = new Date(Forecast.data[0].sunrise_ts * 1000); | ||
_self.sunset = new Date(Forecast.data[0].sunset_ts * 1000); | ||
_self.icon = CurrentConditions.weather.icon; | ||
//set celsius if desired | ||
if (options.celsius) { | ||
_self.temp = toCelsius(_self.temp); | ||
_self.feelsLike = toCelsius(_self.feelsLike); | ||
} | ||
//create the forecast | ||
for (var i=0; i<5; i++) { | ||
var day = getDailyObject(); | ||
day.condition = Forecast.data[i].weather.description; | ||
day.feelsLikeHigh = Forecast.data[i].app_max_temp.toFixed(2); | ||
day.feelsLikeLow = Forecast.data[i].app_min_temp.toFixed(2); | ||
day.humidity = Forecast.data[i].rh.toFixed(2); | ||
day.icon = Forecast.data[i].weather.icon; | ||
day.sunrise = new Date(Forecast.data[i].sunrise_ts * 1000); | ||
day.sunset = new Date(Forecast.data[i].sunset_ts * 1000); | ||
day.tempHigh = Forecast.data[i].max_temp.toFixed(2); | ||
day.tempLow = Forecast.data[i].low_temp.toFixed(2); | ||
if (options.celsius) { | ||
day.feelsLikeHigh = toCelsius(day.feelsLikeHigh); | ||
day.feelsLikeLow = toCelsius(day.feelsLikeLow); | ||
day.tempHigh = toCelsius(day.tempHigh); | ||
day.tempLow = toCelsius(day.tempLow); | ||
} | ||
_self.forecast.push(day); | ||
} | ||
return null; | ||
} catch (err) { | ||
_self.error = err; | ||
return err; | ||
} | ||
} | ||
} ///END OF service object | ||
@@ -329,0 +487,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
130307
12
3620
83
1
+ Addedjw-gate@^1.0.0
+ Addedjw-gate@1.0.0(transitive)