jw-weather
Advanced tools
Comparing version 1.2.0 to 2.0.0
@@ -15,5 +15,5 @@ { | ||
], | ||
"program": "${workspaceFolder}\\demo.js" | ||
"program": "${workspaceFolder}\\test.js" | ||
} | ||
] | ||
} |
82
demo.js
var Weather = require('./weather'); | ||
/** | ||
* Darksky is simple to use | ||
* Simply create the weather service and then run the update function | ||
*/ | ||
var DarkSky_Key = '0123456789abcdef9876543210fedcba'; //this is a fake key | ||
@@ -17,62 +13,42 @@ | ||
boston.update(function(err) { | ||
if (err) { | ||
console.log('ERROR: ' + err); | ||
} else { | ||
console.log('\r\n---Boston Weather (DarkSky)---'); | ||
printWeather(boston); | ||
} | ||
//In this example we're going to wait for the 'ready' event before running the first update. | ||
boston.on('ready', function() { | ||
boston.update(function(err) { | ||
if (err) { | ||
console.log('ERROR: ' + err); | ||
} else { | ||
console.log('\r\n---Boston Weather (DarkSky)---'); | ||
printWeather(boston); | ||
} | ||
}); | ||
}); | ||
boston.on('error', function(err) { | ||
console.log('ERROR with Boston weather lookup: ' + err); | ||
}); | ||
/** | ||
* AccuWeather requires a location Key, it can't run with | ||
* lat/long or zip codes directly. | ||
* If you know the code you can use it just like the DarkSky service. | ||
* | ||
* If not you need to use the helper function accuweatherLocationLookup() | ||
* to find the key based on lat/long or zip code. | ||
* | ||
* This example chains the creation of the service to the callback of the | ||
* location key lookup | ||
*/ | ||
var AccuWeather_Key = '0123456789abcdef9876543210fedcba'; //this is a fake key | ||
var AccuWeather_Houston; //create a reference outside the callback | ||
//run the lookup | ||
Weather.accuweatherLocationLookup({ | ||
var houston = new Weather.service({ | ||
provider: 'accuweather', | ||
key: AccuWeather_Key, | ||
latitude: 29.7604, | ||
longitude: -95.3698, | ||
//zipcode: 77001 //you can use a zip code or lat/long | ||
}, function(err, locationKey) { | ||
if (err) { | ||
console.log('ERROR: ' + err); | ||
} else { | ||
//we have a valid location key now, so create the service | ||
AccuWeather_Houston = new Weather.service({ | ||
provider: 'accuweather', | ||
key: AccuWeather_Key, | ||
locationKey: locationKey, | ||
celsius: false | ||
}); | ||
//run an update and print the result | ||
AccuWeather_Houston.update(function(err) { | ||
if (err) { | ||
console.log('ERROR: ' + err); | ||
} else { | ||
console.log('---Houston Weather (AccuWeather)---'); | ||
printWeather(AccuWeather_Houston); | ||
} | ||
}); | ||
} | ||
longitude: -95.3698, | ||
celsius: false | ||
}); | ||
//if you call the update function before the weather service has loaded it will store | ||
//a reference to the callback and automatically run it when the service is loaded | ||
//so even though we're calling the update() function before the ready event was fired | ||
//this will still work. | ||
houston.update(function(err) { | ||
if (err) { | ||
console.log('ERROR: ' + err); | ||
} else { | ||
console.log('---Houston Weather (AccuWeather)---'); | ||
printWeather(houston); | ||
} | ||
}); | ||
//This is just a simple function to print some information from a weather service | ||
@@ -79,0 +55,0 @@ function printWeather(weather) { |
{ | ||
"name": "jw-weather", | ||
"version": "1.2.0", | ||
"version": "2.0.0", | ||
"description": "Reader for various weather APIs", | ||
@@ -5,0 +5,0 @@ "main": "weather.js", |
@@ -8,4 +8,9 @@ # jw-weather | ||
The goal is to create a weather module that returns a standardized weather object regardless of the API used. | ||
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). | ||
2. Being able to switch to another service if costs or needs change. | ||
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. | ||
Because reason #1 is the main purpose of this module certain features that are nice to have but differ between services have been omitted. For example AccuWeather allows you to specify a zipcode for your location but DarkSky doesn't, so even though using the zipcode might be preferable vs. the latitude/longitude, that capability has been removed because it would make the usage incompatible between services. | ||
### Sample use in Node.js: | ||
@@ -40,8 +45,38 @@ ``` | ||
- An icon name/number | ||
- sunrise (DarkSky only) | ||
- sunset (DarkSky only) | ||
- A 5-day forecast (DarkSky only) | ||
- sunrise | ||
- sunset | ||
- A 5-day forecast (Accuweather does not forecast humidity) | ||
When using AccuWeather there is a helper function for looking up location keys since AccuWeather does not use latitude/longitude in it's API call. | ||
## Startup options: | ||
- provider: string, 'darksky' || 'accuweather' | ||
- key: string, provided by weather service | ||
- latitude: number | ||
- longitude: number | ||
- celsius: boolean (default=false, i.e. fahrenheit) | ||
## List of Functions: | ||
- ### update(callback(err)) | ||
Updates the weather object with the latest forecast. | ||
Returns an error message or null when the update is complete. | ||
- ### fullWeather() | ||
Returns an object representing the full standardized weather data | ||
## List of Properties: | ||
- raw: The raw API response from the provider | ||
- lastUpdate: The last time the weather object was updated | ||
- forecastTime: The time of the forecast | ||
- temp: The current temperature | ||
- humidity: The current humidity | ||
- currentCondition: A text description of the current condition | ||
- icon: An icon number | ||
- feelsLike: The "Real Feel" temperature | ||
- sunrise: Sunrise time | ||
- sunset: Sunset time | ||
- forecast: A 5-day forecast | ||
Review demo.js for example use. | ||
105
test.js
@@ -56,2 +56,3 @@ function formatDate(fullDate, formatString) { | ||
var darksky = new Weather.service({ | ||
@@ -63,98 +64,34 @@ provider: 'darksky', | ||
celsius: false | ||
}, function(err) { | ||
if (err) { | ||
console.log('Weather init ERR: ' + JSON.stringify(err)); | ||
} else { | ||
console.log('Weather started!'); | ||
} | ||
}); | ||
darksky.update(function(err) { | ||
if (err) { | ||
console.log('ERROR: ' + err); | ||
} else { | ||
console.log('DARKSKY FORECAST:'); | ||
console.log('Last Update: ' + formatDate(darksky.lastUpdate, 'short') + ', ' + formatTime(darksky.lastUpdate, 'short')); | ||
//console.log(JSON.stringify(weather.fullWeather())); | ||
console.log('forecastTime: ' + | ||
formatDate(darksky.forecastTime, 'short') + | ||
', ' + | ||
formatTime(darksky.forecastTime, 'short')); | ||
console.log('Temp: ' + darksky.temp); | ||
console.log('Feels like: ' + darksky.feelsLike); | ||
console.log('Current Condition: ' + darksky.currentCondition); | ||
console.log('Humidity: ' + darksky.humidity); | ||
console.log('Sunrise: ' + formatTime(darksky.sunrise, 'short')); | ||
console.log('Sunset: ' + formatTime(darksky.sunset, 'short')); | ||
console.log('icon: ' + darksky.icon); | ||
console.log('daily: '); | ||
console.log('5-Day Forecast:'); | ||
console.log(JSON.stringify(darksky.forecast)); | ||
} | ||
darksky.on('ready', function() { | ||
console.log('Darksky Ready'); | ||
}); | ||
darksky.on('error', function(err) { | ||
console.log('Weather Error: ' + err); | ||
}); | ||
/* | ||
var accuweather = null; | ||
Weather.accuweatherLocationLookup({ | ||
key: 'xA84Kwql8VY44jdfmrqnbrWOToU2hb3W', | ||
//latitude: 40.75855, | ||
//longitude: -73.76543 | ||
zipcode: 11361 | ||
}, function(err, locationKey) { | ||
if (err) { | ||
console.log('ERROR: ' + err); | ||
} else { | ||
console.log('LOCATION KEY: ' + locationKey); | ||
startAccuweather(locationKey); | ||
updateAccuweather(); | ||
} | ||
darksky.update(function() { | ||
console.log('DARKSKY:'); | ||
console.log(darksky.fullWeather().temp); | ||
}); | ||
function startAccuweather(locationKey) { | ||
accuweather = new Weather.service({ | ||
var accuweather = new Weather.service({ | ||
provider: 'accuweather', | ||
key: 'xA84Kwql8VY44jdfmrqnbrWOToU2hb3W', | ||
locationKey: locationKey, | ||
latitude: 40.758556, | ||
longitude: -73.765434, | ||
celsius: false | ||
}, function(err) { | ||
if (err) { | ||
console.log('Weather init ERR: ' + JSON.stringify(err)); | ||
} else { | ||
console.log('Weather started!'); | ||
} | ||
}); | ||
} | ||
}); | ||
function updateAccuweather() { | ||
accuweather.update(function() { | ||
console.log('ACCUWEATHER:'); | ||
console.log(accuweather.fullWeather()); | ||
//console.log(JSON.stringify(accuweather.raw)); | ||
}); | ||
accuweather.update(function(err) { | ||
if (err) { | ||
console.log('ERROR: ' + err); | ||
} else { | ||
console.log('ACCUWEATHER FORECAST:'); | ||
console.log('Last Update: ' + formatDate(accuweather.lastUpdate, 'short') + ', ' + formatTime(accuweather.lastUpdate, 'short')); | ||
//console.log(JSON.stringify(accuweather.fullWeather())); | ||
console.log('forecastTime: ' + | ||
formatDate(accuweather.forecastTime, 'short') + | ||
', ' + | ||
formatTime(accuweather.forecastTime, 'short')); | ||
console.log('Temp: ' + accuweather.temp); | ||
console.log('Feels like: ' + accuweather.feelsLike); | ||
console.log('Current Condition: ' + accuweather.currentCondition); | ||
console.log('Humidity: ' + accuweather.humidity); | ||
//console.log('Sunrise: ' + formatTime(weather.sunrise, 'short')); | ||
//console.log('Sunset: ' + formatTime(weather.sunset, 'short')); | ||
console.log('icon: ' + accuweather.icon) ; | ||
accuweather.on('ready', function() { | ||
console.log('accuweather ready'); | ||
}); | ||
console.log(JSON.stringify(accuweather.raw)); | ||
} | ||
}); | ||
} | ||
*/ |
249
weather.js
@@ -12,10 +12,8 @@ /* | ||
* @param {'darksky'|'accuweather'} options.provider - The weather provider to use | ||
* @param {Number} [options.latitude] - The latitude (Darksky) | ||
* @param {Number} [options.longitude] - the longitude (Darksky) | ||
* @param {String} [options.locationKey] - the location key (Accuweather) | ||
* @param {Number} [options.latitude] - The latitude | ||
* @param {Number} [options.longitude] - the longitude | ||
* @param {boolean} [options.celsius] - Temperature in celsius | ||
* | ||
* @param {Function} [callback] | ||
*/ | ||
function service(options, callback) { | ||
function service(options) { | ||
var _self = this; | ||
@@ -36,18 +34,57 @@ | ||
this.forecast = []; | ||
this.ready = false; //this is for weather services that need to do lookups | ||
this.runUpdateWhenReady = false; //this is in case the user requests an update before the weather object is ready | ||
this.updateWhenReadyFunc = null; //will hold the callback for the early update request | ||
this.locationKey = null; //this is for accuweather | ||
/******************* Custom Emitter Code **************************************************/ | ||
//this is for future browser compatibility | ||
var _events = {}; | ||
this.on = function(event, callback) { | ||
//attaches a callback function to an event | ||
_events[event] = callback; | ||
}; | ||
function emit(event, msg) { | ||
if (typeof _events[event] === 'function') { //the client has registered the event | ||
_events[event](msg); //run the event function provided | ||
} | ||
} | ||
/*******************************************************************************************/ | ||
(function startup() { | ||
var err = null; | ||
if (typeof options.key !== 'string' || | ||
typeof options.latitude !== 'number' || | ||
typeof options.longitude !== 'number') { | ||
setTimeout(function() { | ||
emit('error', 'Invalid startup options.'); | ||
}, 100); | ||
} | ||
// TODO: Actually test that darksky is working before ready? Maybe just ping the service? | ||
if (options.provider == 'darksky') { | ||
if (typeof options.key !== 'string' || | ||
typeof options.latitude !== 'number' || | ||
typeof options.longitude !== 'number') { | ||
err = 'Invalid startup options.'; | ||
_self.ready = true; | ||
if (_self.runUpdateWhenReady) { | ||
_self.update(_self.updateWhenReadyFunc); | ||
} | ||
setTimeout(function() { | ||
emit('ready', null); | ||
}, 100); | ||
} else if (options.provider == 'accuweather') { | ||
if (typeof options.key !== 'string' || typeof options.locationKey === 'undefined') { | ||
err = 'Invalid startup options.'; | ||
} | ||
accuweatherLocationLookup(options, function(err, locationKey) { | ||
_self.locationKey = locationKey; | ||
_self.ready = true; | ||
if (_self.runUpdateWhenReady) { | ||
_self.update(_self.updateWhenReadyFunc); | ||
} | ||
setTimeout(function() { | ||
emit('ready', null); | ||
}, 100); | ||
}); | ||
} | ||
if (typeof callback === 'function') { callback(err); } | ||
})(); | ||
@@ -62,33 +99,73 @@ | ||
this.update = function(callback) { | ||
var https = require('https'); | ||
var url = ''; | ||
if (options.provider == 'darksky') { //case insensitive | ||
url = 'https://api.darksky.net/forecast/[KEY]/[LAT],[LONG]?exclude=["minutely","flags","alerts"]'; | ||
} else if (options.provider == 'accuweather') { //case insensitive | ||
url = 'https://dataservice.accuweather.com/currentconditions/v1/[LOCATION]?apikey=[KEY]&details=true'; | ||
if (_self.ready) { | ||
if (options.provider == 'darksky') { //case insensitive | ||
getDarkSkyData(callback); | ||
} else if (options.provider == 'accuweather') { //case insensitive | ||
getAccuWeatherData(callback); | ||
} | ||
} else { | ||
_self.runUpdateWhenReady = true; | ||
_self.updateWhenReadyFunc = callback; | ||
} | ||
}; | ||
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) | ||
.replace('[LONG]', options.longitude) | ||
.replace('[LOCATION]', options.locationKey); | ||
.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); | ||
}); | ||
}); | ||
try { | ||
https.get(url, function(res){ | ||
var body = ''; | ||
res.on('data', function(chunk){ body += chunk; }); | ||
res.on('end', function(){ | ||
try { | ||
if (options.provider == 'darksky'){ parseDarkSky(body, callback); } | ||
else if (options.provider == 'accuweather'){ parseAccuweather(body, callback); } | ||
} catch (err) { | ||
if (typeof callback === 'function') { callback(err); } | ||
} | ||
}); | ||
}).on('error', function(err) { if (typeof callback === 'function') { callback(err); } }); | ||
} catch (err) { | ||
if (typeof callback === 'function') { callback(err); } | ||
} | ||
} | ||
}; | ||
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); | ||
} | ||
}); | ||
} | ||
/** | ||
@@ -98,3 +175,3 @@ * @returns {Object} - an object representing the top-level vars | ||
this.fullWeather = function() { | ||
console.log('returning full weather'); | ||
//console.log('returning full weather'); | ||
var weather = { | ||
@@ -118,2 +195,21 @@ lastUpdate: _self.lastUpdate, | ||
function getAPIData(url, callback) { | ||
var https = require('https'); | ||
try { | ||
https.get(url, function(res){ | ||
var body = ''; | ||
res.on('data', function(chunk){ body += chunk; }); | ||
res.on('end', function(){ | ||
try { | ||
body = JSON.parse(body); | ||
if (typeof callback === 'function') { callback(null, body); } | ||
} catch (err) { | ||
if (typeof callback === 'function') { callback(err); } | ||
} | ||
}); | ||
}).on('error', function(err) { if (typeof callback === 'function') { callback(err); } }); | ||
} catch (err) { | ||
if (typeof callback === 'function') { callback(err); } | ||
} | ||
} | ||
@@ -123,3 +219,3 @@ function parseDarkSky(weather, callback) { | ||
try { | ||
weather = JSON.parse(weather); | ||
//weather = JSON.parse(weather); | ||
if (weather.error) { | ||
@@ -174,19 +270,18 @@ if (typeof callback === 'function') {callback(weather.error); } | ||
function parseAccuweather(weather, callback) { | ||
function parseAccuweather(accuWeatherData, callback) { | ||
_self.lastUpdate = new Date().toString(); | ||
_self.raw = accuWeatherData; | ||
try { | ||
weather = JSON.parse(weather); | ||
weather = weather[0]; //break it out of the array | ||
var CurrentConditions = accuWeatherData.CurrentConditions[0]; //break it out of the array | ||
var Forecast = accuWeatherData.Forecast; | ||
_self.raw = weather; | ||
_self.temp = CurrentConditions.Temperature.Imperial.Value.toFixed(2); | ||
_self.feelsLike = CurrentConditions.RealFeelTemperature.Imperial.Value.toFixed(2); | ||
_self.currentCondition = CurrentConditions.WeatherText; | ||
_self.humidity = (CurrentConditions.RelativeHumidity/100).toFixed(2); | ||
_self.forecastTime = new Date(CurrentConditions.LocalObservationDateTime); | ||
_self.sunrise = new Date(Forecast.DailyForecasts[0].Sun.Rise); | ||
_self.sunset = new Date(Forecast.DailyForecasts[0].Sun.Set); | ||
_self.icon = CurrentConditions.WeatherIcon; | ||
_self.temp = weather.Temperature.Imperial.Value.toFixed(2); | ||
_self.feelsLike = weather.RealFeelTemperature.Imperial.Value.toFixed(2); | ||
_self.currentCondition = weather.WeatherText; | ||
_self.humidity = (weather.RelativeHumidity/100).toFixed(2); | ||
_self.forecastTime = new Date(weather.LocalObservationDateTime); | ||
_self.sunrise = null; | ||
_self.sunset = null; | ||
_self.icon = weather.WeatherIcon; | ||
//set celsius if desired | ||
@@ -198,2 +293,25 @@ if (options.celsius) { | ||
//create the forecast | ||
for (var i=0; i<5; i++) { | ||
var day = getDailyObject(); | ||
day.condition = Forecast.DailyForecasts[i].Day.ShortPhrase; | ||
day.feelsLikeHigh = Forecast.DailyForecasts[i].RealFeelTemperature.Maximum.Value.toFixed(2); | ||
day.feelsLikeLow = Forecast.DailyForecasts[i].RealFeelTemperature.Minimum.Value.toFixed(2); | ||
day.humidity = null; | ||
day.icon = Forecast.DailyForecasts[i].Day.Icon; | ||
day.sunrise = new Date(Forecast.DailyForecasts[i].Sun.Rise); | ||
day.sunset = new Date(Forecast.DailyForecasts[i].Sun.Set); | ||
day.tempHigh = Forecast.DailyForecasts[i].Temperature.Maximum.Value.toFixed(2); | ||
day.tempLow = Forecast.DailyForecasts[i].Temperature.Minimum.Value.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); | ||
} | ||
if (typeof callback === 'function') { callback(null); } | ||
@@ -207,3 +325,3 @@ | ||
} | ||
} | ||
} ///END OF service object | ||
@@ -217,3 +335,2 @@ /** | ||
* @param {number|string} [options.longitude] - the longitude | ||
* @param {number|string} [options.zipcode] - the zipcode | ||
* @param {function} callback | ||
@@ -234,7 +351,3 @@ */ | ||
var mode = null; | ||
if (options.zipcode) { | ||
mode = 'zipcode'; | ||
url = 'https://dataservice.accuweather.com/locations/v1/postalcodes/search?apikey=[KEY]&q=[ZIPCODE]'; | ||
} else if (options.latitude && options.longitude) { | ||
mode = 'lat/long'; | ||
if (options.latitude && options.longitude) { | ||
url = 'https://dataservice.accuweather.com/locations/v1/cities/geoposition/search?apikey=[KEY]&q=[LAT],[LONG]'; | ||
@@ -250,4 +363,3 @@ } else { | ||
.replace('[LAT]', options.latitude) | ||
.replace('[LONG]', options.longitude) | ||
.replace('[ZIPCODE]', options.zipcode); | ||
.replace('[LONG]', options.longitude); | ||
try { | ||
@@ -261,7 +373,3 @@ https.get(url, function(res){ | ||
var locationKey = ''; | ||
if (mode === 'zipcode') { | ||
locationKey = res[0].Key; | ||
} else if (mode === 'lat/long') { | ||
locationKey = res.Key; | ||
} | ||
locationKey = res.Key; | ||
if (typeof callback === 'function') { callback(null, locationKey); } | ||
@@ -300,2 +408,1 @@ } catch (err) { | ||
exports.service = service; | ||
exports.accuweatherLocationLookup = accuweatherLocationLookup; |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
98089
2140
81
2
10