Fix issues sourcing data from weather.com by using OpenWeatherMaps
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
"moment-timezone": "^0.4.0",
|
"moment-timezone": "^0.4.0",
|
||||||
"mongoose": "^4.0.6",
|
"mongoose": "^4.0.6",
|
||||||
"suncalc": "^1.6.0",
|
"suncalc": "^1.6.0",
|
||||||
|
"timezoner": "^0.1.9",
|
||||||
"xml2js": "^0.4.9"
|
"xml2js": "^0.4.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ var http = require( "http" ),
|
|||||||
Cache = require( "../models/Cache" ),
|
Cache = require( "../models/Cache" ),
|
||||||
SunCalc = require( "suncalc" ),
|
SunCalc = require( "suncalc" ),
|
||||||
moment = require( "moment-timezone" ),
|
moment = require( "moment-timezone" ),
|
||||||
|
timezoner = require( "timezoner" ),
|
||||||
|
|
||||||
// Define regex filters to match against location
|
// Define regex filters to match against location
|
||||||
filters = {
|
filters = {
|
||||||
@@ -31,7 +32,7 @@ function resolveCoordinates( location, callback ) {
|
|||||||
if ( typeof data.RESULTS === "object" && data.RESULTS.length ) {
|
if ( typeof data.RESULTS === "object" && data.RESULTS.length ) {
|
||||||
|
|
||||||
// If it is, reply with an array containing the GPS coordinates
|
// If it is, reply with an array containing the GPS coordinates
|
||||||
callback( [ data.RESULTS[0].lat, data.RESULTS[0].lon ], moment().tz( data.RESULTS[0].tz ).utcOffset() );
|
callback( [ data.RESULTS[ 0 ].lat, data.RESULTS[ 0 ].lon ], moment().tz( data.RESULTS[ 0 ].tz ).utcOffset() );
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Otherwise, indicate no data was found
|
// Otherwise, indicate no data was found
|
||||||
@@ -55,7 +56,7 @@ function resolveWxLocation( location, callback ) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
callback( result.search.loc[0].$.id );
|
callback( result.search.loc[ 0 ].$.id );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
@@ -77,13 +78,13 @@ function getWeatherUndergroundData( location, weatherUndergroundKey, callback )
|
|||||||
timezone: data.current_observation.local_tz_offset,
|
timezone: data.current_observation.local_tz_offset,
|
||||||
sunrise: parseInt( data.sun_phase.sunrise.hour ) * 60 + parseInt( data.sun_phase.sunrise.minute ),
|
sunrise: parseInt( data.sun_phase.sunrise.hour ) * 60 + parseInt( data.sun_phase.sunrise.minute ),
|
||||||
sunset: parseInt( data.sun_phase.sunset.hour ) * 60 + parseInt( data.sun_phase.sunset.minute ),
|
sunset: parseInt( data.sun_phase.sunset.hour ) * 60 + parseInt( data.sun_phase.sunset.minute ),
|
||||||
maxTemp: parseInt( data.history.dailysummary[0].maxtempi ),
|
maxTemp: parseInt( data.history.dailysummary[ 0 ].maxtempi ),
|
||||||
minTemp: parseInt( data.history.dailysummary[0].mintempi ),
|
minTemp: parseInt( data.history.dailysummary[ 0 ].mintempi ),
|
||||||
temp: parseInt( data.current_observation.temp_f ),
|
temp: parseInt( data.current_observation.temp_f ),
|
||||||
humidity: ( parseInt( data.history.dailysummary[0].maxhumidity ) + parseInt( data.history.dailysummary[0].minhumidity ) ) / 2,
|
humidity: ( parseInt( data.history.dailysummary[ 0 ].maxhumidity ) + parseInt( data.history.dailysummary[ 0 ].minhumidity ) ) / 2,
|
||||||
precip: ( parseFloat( data.current_observation.precip_today_in ) || 0 ) + ( parseFloat( data.history.dailysummary[0].precipi ) || 0 ),
|
precip: ( parseFloat( data.current_observation.precip_today_in ) || 0 ) + ( parseFloat( data.history.dailysummary[ 0 ].precipi ) || 0 ),
|
||||||
solar: parseInt( data.current_observation.UV ),
|
solar: parseInt( data.current_observation.UV ),
|
||||||
wind: parseInt( data.history.dailysummary[0].meanwindspdi ),
|
wind: parseInt( data.history.dailysummary[ 0 ].meanwindspdi ),
|
||||||
elevation: parseInt( data.current_observation.observation_location.elevation )
|
elevation: parseInt( data.current_observation.observation_location.elevation )
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -115,16 +116,16 @@ function getWxWeatherData( location, callback ) {
|
|||||||
|
|
||||||
data = data.weather;
|
data = data.weather;
|
||||||
|
|
||||||
var tz = parseInt( data.loc[0].zone[0] ),
|
var tz = parseInt( data.loc[ 0 ].zone[ 0 ] ),
|
||||||
weather = {
|
weather = {
|
||||||
iconCode: parseInt( data.cc[0].icon[0] ),
|
iconCode: parseInt( data.cc[ 0 ].icon[ 0 ] ),
|
||||||
timezone: ( tz > 0 ? "+" : "" ) + pad( tz ) + "00",
|
timezone: ( tz > 0 ? "+" : "" ) + pad( tz ) + "00",
|
||||||
sunrise: parse12HourTime( data.loc[0].sunr[0] ),
|
sunrise: parse12HourTime( data.loc[ 0 ].sunr[ 0 ] ),
|
||||||
sunset: parse12HourTime( data.loc[0].suns[0] ),
|
sunset: parse12HourTime( data.loc[ 0 ].suns[ 0 ] ),
|
||||||
temp: parseInt( data.cc[0].tmp[0] ),
|
temp: parseInt( data.cc[ 0 ].tmp[ 0 ] ),
|
||||||
humidity: parseInt( data.cc[0].hmid[0] ),
|
humidity: parseInt( data.cc[ 0 ].hmid[ 0 ] ),
|
||||||
solar: parseInt( data.cc[0].uv[0].i[0] ),
|
solar: parseInt( data.cc[ 0 ].uv[ 0 ].i[ 0 ] ),
|
||||||
wind: parseInt( data.cc[0].wind[0].s[0] )
|
wind: parseInt( data.cc[ 0 ].wind[ 0 ].s[ 0 ] )
|
||||||
};
|
};
|
||||||
|
|
||||||
getCache( {
|
getCache( {
|
||||||
@@ -147,7 +148,7 @@ function getWeatherData( location, callback ) {
|
|||||||
var WSI_API_KEY = process.env.WSI_API_KEY,
|
var WSI_API_KEY = process.env.WSI_API_KEY,
|
||||||
|
|
||||||
// Generate URL using The Weather Company API v1 in Imperial units
|
// Generate URL using The Weather Company API v1 in Imperial units
|
||||||
url = "http://api.weather.com/v1/geocode/" + location[0] + "/" + location[1] +
|
url = "http://api.weather.com/v1/geocode/" + location[ 0 ] + "/" + location[ 1 ] +
|
||||||
"/observations/current.json?apiKey=" + WSI_API_KEY + "&language=en-US&units=e";
|
"/observations/current.json?apiKey=" + WSI_API_KEY + "&language=en-US&units=e";
|
||||||
|
|
||||||
// Perform the HTTP request to retrieve the weather data
|
// Perform the HTTP request to retrieve the weather data
|
||||||
@@ -200,7 +201,7 @@ function getYesterdayWeatherData( location, callback ) {
|
|||||||
|
|
||||||
// Generate URL using WSI Cleaned History API in Imperial units showing daily average values
|
// Generate URL using WSI Cleaned History API in Imperial units showing daily average values
|
||||||
url = "http://cleanedobservations.wsi.com/CleanedObs.svc/GetObs?ID=" + WSI_HISTORY_KEY +
|
url = "http://cleanedobservations.wsi.com/CleanedObs.svc/GetObs?ID=" + WSI_HISTORY_KEY +
|
||||||
"&Lat=" + location[0] + "&Long=" + location[1] +
|
"&Lat=" + location[ 0 ] + "&Long=" + location[ 1 ] +
|
||||||
"&Req=davg&startdate=" + yesterday + "&enddate=" + yesterday + "&TS=LST";
|
"&Req=davg&startdate=" + yesterday + "&enddate=" + yesterday + "&TS=LST";
|
||||||
|
|
||||||
// Perform the HTTP request to retrieve the weather data
|
// Perform the HTTP request to retrieve the weather data
|
||||||
@@ -211,11 +212,62 @@ function getYesterdayWeatherData( location, callback ) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
callback( result.WeatherResponse.WeatherRecords[0].WeatherData[0].$ );
|
callback( result.WeatherResponse.WeatherRecords[ 0 ].WeatherData[ 0 ].$ );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve weather data from Open Weather Map
|
||||||
|
function getOWMWeatherData( location, callback ) {
|
||||||
|
|
||||||
|
// Generate URL using The Weather Company API v1 in Imperial units
|
||||||
|
var url = "http://api.openweathermap.org/data/2.5/weather?units=imperial&lat=" + location[ 0 ] + "&lon=" + location[ 1 ];
|
||||||
|
|
||||||
|
// Perform the HTTP request to retrieve the weather data
|
||||||
|
httpRequest( url, function( data ) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
data = JSON.parse( data );
|
||||||
|
var sunrise = new Date( data.sys.sunrise * 1000 ),
|
||||||
|
sunset = new Date( data.sys.sunset * 1000 );
|
||||||
|
|
||||||
|
timezoner.getTimeZone(
|
||||||
|
location[ 0 ],
|
||||||
|
location[ 1 ],
|
||||||
|
function( err, timezone ) {
|
||||||
|
if ( err ) {
|
||||||
|
callback( false );
|
||||||
|
} else {
|
||||||
|
var weather = {
|
||||||
|
timezone: ( timezone.rawOffset + timezone.dstOffset ) / 60,
|
||||||
|
sunrise: ( sunrise.getHours() * 60 + sunrise.getMinutes() ),
|
||||||
|
sunset: ( sunset.getHours() * 60 + sunset.getMinutes() ),
|
||||||
|
temp: parseInt( data.main.temp ),
|
||||||
|
humidity: parseInt( data.main.humidity ),
|
||||||
|
wind: parseInt( data.wind.speed )
|
||||||
|
};
|
||||||
|
|
||||||
|
getCache( {
|
||||||
|
key: "yesterdayHumidity",
|
||||||
|
location: location,
|
||||||
|
weather: weather,
|
||||||
|
callback: callback
|
||||||
|
} );
|
||||||
|
|
||||||
|
updateCache( location, weather );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch ( err ) {
|
||||||
|
|
||||||
|
// Otherwise indicate the request failed
|
||||||
|
callback( false );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
// Retrieve cached record for a given location
|
// Retrieve cached record for a given location
|
||||||
// opt is defined as an object with two required items
|
// opt is defined as an object with two required items
|
||||||
// opt.location defines the location for the cache record
|
// opt.location defines the location for the cache record
|
||||||
@@ -352,7 +404,7 @@ exports.getWeather = function( req, res ) {
|
|||||||
// The adjustment method is encoded by the OpenSprinkler firmware and must be
|
// The adjustment method is encoded by the OpenSprinkler firmware and must be
|
||||||
// parsed. This allows the adjustment method and the restriction type to both
|
// parsed. This allows the adjustment method and the restriction type to both
|
||||||
// be saved in the same byte.
|
// be saved in the same byte.
|
||||||
var adjustmentMethod = req.params[0] & ~( 1 << 7 ),
|
var adjustmentMethod = req.params[ 0 ] & ~( 1 << 7 ),
|
||||||
adjustmentOptions = req.query.wto,
|
adjustmentOptions = req.query.wto,
|
||||||
location = req.query.loc,
|
location = req.query.loc,
|
||||||
weatherUndergroundKey = req.query.key,
|
weatherUndergroundKey = req.query.key,
|
||||||
@@ -373,7 +425,7 @@ exports.getWeather = function( req, res ) {
|
|||||||
rainDelay = -1;
|
rainDelay = -1;
|
||||||
|
|
||||||
// Check for any user-set restrictions and change the scale to 0 if the criteria is met
|
// Check for any user-set restrictions and change the scale to 0 if the criteria is met
|
||||||
if ( checkWeatherRestriction( req.params[0], weather ) ) {
|
if ( checkWeatherRestriction( req.params[ 0 ], weather ) ) {
|
||||||
scale = 0;
|
scale = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,7 +474,7 @@ exports.getWeather = function( req, res ) {
|
|||||||
|
|
||||||
// X-Forwarded-For header may contain more than one IP address and therefore
|
// X-Forwarded-For header may contain more than one IP address and therefore
|
||||||
// the string is split against a comma and the first value is selected
|
// the string is split against a comma and the first value is selected
|
||||||
remoteAddress = remoteAddress.split( "," )[0];
|
remoteAddress = remoteAddress.split( "," )[ 0 ];
|
||||||
|
|
||||||
// Parse weather adjustment options
|
// Parse weather adjustment options
|
||||||
try {
|
try {
|
||||||
@@ -455,10 +507,10 @@ exports.getWeather = function( req, res ) {
|
|||||||
|
|
||||||
// Handle GPS coordinates by storing each coordinate in an array
|
// Handle GPS coordinates by storing each coordinate in an array
|
||||||
location = location.split( "," );
|
location = location.split( "," );
|
||||||
location = [ parseFloat( location[0] ), parseFloat( location[1] ) ];
|
location = [ parseFloat( location[ 0 ] ), parseFloat( location[ 1 ] ) ];
|
||||||
|
|
||||||
// Continue with the weather request
|
// Continue with the weather request
|
||||||
getWeatherData( location, finishRequest );
|
getOWMWeatherData( location, finishRequest );
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@@ -476,7 +528,7 @@ exports.getWeather = function( req, res ) {
|
|||||||
var tzOffset = getTimezone( timezone, "minutes" ),
|
var tzOffset = getTimezone( timezone, "minutes" ),
|
||||||
|
|
||||||
// Calculate sunrise and sunset since Weather Underground does not provide it
|
// Calculate sunrise and sunset since Weather Underground does not provide it
|
||||||
sunData = SunCalc.getTimes( new Date(), location[0], location[1] );
|
sunData = SunCalc.getTimes( new Date(), location[ 0 ], location[ 1 ] );
|
||||||
|
|
||||||
sunData.sunrise.setUTCMinutes( sunData.sunrise.getUTCMinutes() + tzOffset );
|
sunData.sunrise.setUTCMinutes( sunData.sunrise.getUTCMinutes() + tzOffset );
|
||||||
sunData.sunset.setUTCMinutes( sunData.sunset.getUTCMinutes() + tzOffset );
|
sunData.sunset.setUTCMinutes( sunData.sunset.getUTCMinutes() + tzOffset );
|
||||||
@@ -499,9 +551,9 @@ function httpRequest( url, callback ) {
|
|||||||
url = url.match( filters.url );
|
url = url.match( filters.url );
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
host: url[1],
|
host: url[ 1 ],
|
||||||
port: url[2] || 80,
|
port: url[ 2 ] || 80,
|
||||||
path: url[3]
|
path: url[ 3 ]
|
||||||
};
|
};
|
||||||
|
|
||||||
http.get( options, function( response ) {
|
http.get( options, function( response ) {
|
||||||
@@ -532,9 +584,9 @@ function validateValues( keys, array ) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
key = keys[key];
|
key = keys[ key ];
|
||||||
|
|
||||||
if ( !array.hasOwnProperty( key ) || typeof array[key] !== "number" || isNaN( array[key] ) || array[key] === null || array[key] === -999 ) {
|
if ( !array.hasOwnProperty( key ) || typeof array[ key ] !== "number" || isNaN( array[ key ] ) || array[ key ] === null || array[ key ] === -999 ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -550,15 +602,15 @@ function getTimezone( time, format ) {
|
|||||||
var hour, minute, tz;
|
var hour, minute, tz;
|
||||||
|
|
||||||
if ( typeof time === "number" ) {
|
if ( typeof time === "number" ) {
|
||||||
hour = Math.floor( time / 60);
|
hour = Math.floor( time / 60 );
|
||||||
minute = time % 60;
|
minute = time % 60;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Match the provided time string against a regex for parsing
|
// Match the provided time string against a regex for parsing
|
||||||
time = time.match( filters.time ) || time.match( filters.timezone );
|
time = time.match( filters.time ) || time.match( filters.timezone );
|
||||||
|
|
||||||
hour = parseInt( time[7] + time[8] );
|
hour = parseInt( time[ 7 ] + time[ 8 ] );
|
||||||
minute = parseInt( time[9] );
|
minute = parseInt( time[ 9 ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( format === "minutes" ) {
|
if ( format === "minutes" ) {
|
||||||
@@ -582,7 +634,7 @@ function parseDayTime( time ) {
|
|||||||
time = time.match( filters.time );
|
time = time.match( filters.time );
|
||||||
|
|
||||||
// Values are converted to minutes from midnight for the controller
|
// Values are converted to minutes from midnight for the controller
|
||||||
return parseInt( time[4] ) * 60 + parseInt( time[5] );
|
return parseInt( time[ 4 ] ) * 60 + parseInt( time[ 5 ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to return the sunrise and sunset times from weather reply using 12 hour format
|
// Function to return the sunrise and sunset times from weather reply using 12 hour format
|
||||||
@@ -591,10 +643,10 @@ function parse12HourTime( time ) {
|
|||||||
// Time is parsed from string against a regex
|
// Time is parsed from string against a regex
|
||||||
time = time.match( filters.time12 );
|
time = time.match( filters.time12 );
|
||||||
|
|
||||||
var hour = parseInt( time[1] ),
|
var hour = parseInt( time[ 1 ] ),
|
||||||
minute = parseInt( time[2] );
|
minute = parseInt( time[ 2 ] );
|
||||||
|
|
||||||
if ( time[3].toLowerCase() === "pm" ) {
|
if ( time[ 3 ].toLowerCase() === "pm" ) {
|
||||||
hour += 12;
|
hour += 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,7 +666,7 @@ function pad( number ) {
|
|||||||
// Converts IP string to integer
|
// Converts IP string to integer
|
||||||
function ipToInt( ip ) {
|
function ipToInt( ip ) {
|
||||||
ip = ip.split( "." );
|
ip = ip.split( "." );
|
||||||
return ( ( ( ( ( ( +ip[0] ) * 256 ) + ( +ip[1] ) ) * 256 ) + ( +ip[2] ) ) * 256 ) + ( +ip[3] );
|
return ( ( ( ( ( ( +ip[ 0 ] ) * 256 ) + ( +ip[ 1 ] ) ) * 256 ) + ( +ip[ 2 ] ) ) * 256 ) + ( +ip[ 3 ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolves the Month / Day / Year of a Date object
|
// Resolves the Month / Day / Year of a Date object
|
||||||
|
|||||||
Reference in New Issue
Block a user