/**
* @function getCurrentWeather
* @summary
* Get Current Weather from API.WEATHER.GOV
* @description
* Uses the zip code in the Ad Parameters (adParameters.geoInfo.postalCode) to
* 1. Get the Lat/Lon from pterodactlyus (or raptor) centered on the zip code
* 2. Make an asynchronous call to API.WEATHER.GOV for the current weather at the closest station returning data
* 3. Emits an event 'CURRENT_WEATHER_RESULTS' when the data is received
* 4. Returns a JavaScript Promise when can be used as an alternative way to receive data or catch an error
* 5. The zip code from the Ad Parameters can be superceded by passing in options.zipCode
*
* Example Usage:
* ```
* window.$b.snippets.getCurrentWeather()
* window.$b.on('CURRENT_WEATHER_RESULTS', function (data) {
* console.log(data.textDescription)
* }
* ```
* Example of data object returned:
* ```
* {
* "@id": "https://api.weather.gov/stations/KOAK/observations/2018-09-06T18:53:00+00:00",
* "@type": "wx:ObservationStation",
* "elevation": {
* "value": 3,
* "unitCode": "unit:m"
* },
* "station": "https://api.weather.gov/stations/KOAK",
* "timestamp": "2018-09-06T18:53:00+00:00",
* "rawMessage": "KOAK 061853Z 29008KT 10SM BKN010 16/13 A3010 RMK AO2 SLP191 T01610128",
* "textDescription": "Mostly Cloudy",
* "icon": "https://api.weather.gov/icons/land/day/bkn?size=medium",
* "presentWeather": [],
* "temperature": {
* "value": 16.100000000000023,
* "unitCode": "unit:degC",
* "qualityControl": "qc:V"
* },
* "dewpoint": {
* "value": 12.800000000000011,
* "unitCode": "unit:degC",
* "qualityControl": "qc:V"
* },
* "windDirection": {
* "value": 290,
* "unitCode": "unit:degree_(angle)",
* "qualityControl": "qc:V"
* },
* "windSpeed": {
* "value": 4.1,
* "unitCode": "unit:m_s-1",
* "qualityControl": "qc:V"
* },
* "windGust": {
* "value": null,
* "unitCode": "unit:m_s-1",
* "qualityControl": "qc:Z"
* },
* "barometricPressure": {
* "value": 101930,
* "unitCode": "unit:Pa",
* "qualityControl": "qc:V"
* },
* "seaLevelPressure": {
* "value": 101910,
* "unitCode": "unit:Pa",
* "qualityControl": "qc:V"
* },
* "visibility": {
* "value": 16090,
* "unitCode": "unit:m",
* "qualityControl": "qc:C"
* },
* "maxTemperatureLast24Hours": {
* "value": null,
* "unitCode": "unit:degC",
* "qualityControl": null
* },
* "minTemperatureLast24Hours": {
* "value": null,
* "unitCode": "unit:degC",
* "qualityControl": null
* },
* "precipitationLastHour": {
* "value": null,
* "unitCode": "unit:m",
* "qualityControl": "qc:Z"
* },
* "precipitationLast3Hours": {
* "value": null,
* "unitCode": "unit:m",
* "qualityControl": "qc:Z"
* },
* "precipitationLast6Hours": {
* "value": null,
* "unitCode": "unit:m",
* "qualityControl": "qc:Z"
* },
* "relativeHumidity": {
* "value": 80.79823362700911,
* "unitCode": "unit:percent",
* "qualityControl": "qc:C"
* },
* "windChill": {
* "value": null,
* "unitCode": "unit:degC",
* "qualityControl": "qc:V"
* },
* "heatIndex": {
* "value": null,
* "unitCode": "unit:degC",
* "qualityControl": "qc:V"
* },
* "cloudLayers": [
* {
* "base": {
* "value": 300,
* "unitCode": "unit:m"
* },
* "amount": "BKN"
* }
* ],
* "geometry": {
* "type": "Point",
* "coordinates": [
* -122.22,
* 37.72
* ]
* }
* }
* ```
* @param {Object=} options
* @param {string=} options.zipCode optional zip code to overrride value in Ad Parameters
* @param {number=} timeout (default 30 seconds) maximum seconds to wait for api results
* @returns {Promise}
*/
function getCurrentWeather (options) {
options = options || {}
var eventName = 'CURRENT_WEATHER_RESULTS'
var errorEvent = 'CURRENT_WEATHER_ERROR'
var blink = window.$b || {}
var adParameters = blink.adParameters || {}
var zipCode = options.zipCode
var isZipValid = function (zip) {
return /^\d{5,5}$/.test(zip)
}
var isLocationValid = function (locationObj) {
return typeof locationObj.latitude === 'number' && typeof locationObj.longitude === 'number'
}
if (!isZipValid(zipCode)) {
var message
if (adParameters.geoInfo && adParameters.geoInfo.postalCode) {
zipCode = adParameters.geoInfo.postalCode
if (!isZipValid(zipCode)) {
message = 'Invalid zip code: ' + zipCode
console.error(errorEvent, message)
try {
window.$b.emit(errorEvent, message)
} catch (err) {
console.error(err)
}
return Promise.reject(new Error(message))
}
} else {
message = 'Ad Parameter has no geoInfo.postalCode'
console.error(errorEvent, message)
try {
window.$b.emit(errorEvent, message)
} catch (err) {
console.error(err)
}
return Promise.reject(new Error(message))
}
}
var timeout = options.timeout
if ((typeof timeout !== 'number') || (timeout <= 0)) {
timeout = 30000
} else {
timeout *= 1000
}
/*
* Wrap emit in try catch in case user has faulty code in their error handler
*/
var emitErrorAndReject = function (options) {
var error = options.error
var reject = options.reject
try {
window.$b.emit(errorEvent, error)
} catch (err) {
console.error(err)
}
reject(error)
}
// var zipUrl = 'https://pterodactylus.tremorvideodsp.com/ws/zipcodes/ZIP.json'
var zipUrl = 'https://raptor.tremorvideodsp.com/ws/zipcodes/ZIP.json'
var stationUrl = 'https://api.weather.gov/points/LAT,LON/stations'
zipUrl = zipUrl.replace('ZIP', zipCode)
return new Promise(function (resolve, reject) {
$.ajax({
url: zipUrl,
method: 'GET',
timeout: timeout
})
.fail(function (err) {
var message = zipUrl + ': ' + err.statusText
console.error(errorEvent, message)
var error = new Error(message)
emitErrorAndReject({ error: error, reject: reject })
})
.then(function (json) {
if (!isLocationValid(json)) {
var message = 'Zipcode "' + zipCode + '" is invalid'
console.error(errorEvent, message)
var error = new Error(message)
emitErrorAndReject({ error: error, reject: reject })
return
}
stationUrl = stationUrl
.replace('LAT', json.latitude)
.replace('LON', json.longitude)
$.ajax({
url: stationUrl,
method: 'GET',
timeout: timeout
})
.fail(function (err) {
var message = stationUrl + ': ' + err.statusText
var error = new Error(message)
emitErrorAndReject({ error: error, reject: reject })
})
.done(function (data) {
// console.log('station: ', data)
var message
var i = 0
var station
if (!(data.features instanceof Array) || !data.features.length) {
message = 'URL: ' + stationUrl + ' did not return features array'
console.error(errorEvent, message)
var error = new Error(message)
emitErrorAndReject({ error: error, reject: reject })
} else {
var tryGetCurrentWeatherData = function (station) {
getCurrentWeatherData({ station: station, timeout: timeout })
.then(function (data) {
console.log(eventName, data)
resolve(data)
try {
window.$b.emit(eventName, data)
} catch (err) {
console.error(err)
}
})
.catch(function (err) {
console.log(err)
i += 1
if (i < data.features.length) {
station = data.features[i].id.replace(/^.*\/(.*)$/, '$1')
tryGetCurrentWeatherData(station)
} else {
message = 'No stations returned current weather data'
console.error(errorEvent, message)
var error = new Error(message)
emitErrorAndReject({ error: error, reject: reject })
}
})
}
station = data.features[i].id.replace(/^.*\/(.*)$/, '$1')
tryGetCurrentWeatherData(station)
}
})
})
})
}
function getCurrentWeatherData (options) {
var station = options.station
var timeout = options.timeout
return new Promise(function (resolve, reject) {
// DEPRECATED
// var weatherUrl = 'https://api.weather.gov/stations/STATION/observations/current'
var weatherUrl = 'https://api.weather.gov/stations/STATION/observations/latest'
var currentWeatherUrl = weatherUrl.replace('STATION', station)
var errorEvent = 'CURRENT_WEATHER_ERROR'
$.ajax({
url: currentWeatherUrl,
method: 'GET',
timeout: timeout
})
.done(function (data) {
if (!(data.properties instanceof Object)) {
var message = 'URL: ' + currentWeatherUrl + ' did not return data.properties'
console.error(errorEvent, message)
var error = new Error(message)
try {
window.$b.emit(errorEvent, error)
} catch (err) {
console.error(err)
}
reject(error)
} else {
data.properties.geometry = data.geometry
resolve(data.properties)
}
})
.fail(function () {
var message = 'URL: ' + currentWeatherUrl + ' did not return data'
console.error(errorEvent, message)
var error = new Error(message)
try {
window.$b.emit(errorEvent, error)
} catch (err) {
console.error(err)
}
reject(error)
})
})
}
window.$b = window.$b || {}
window.$b.snippets = window.$b.snippets || {}
var snippets = window.$b.snippets
snippets.getCurrentWeather = getCurrentWeather
if (window.module) {
window.module.exports = getCurrentWeather
}