Source: get-weather-forecast.js

/**
 * @function getWeatherForecast
 * @summary
 * Get the Weather Forecst 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 weather forecast at the LAT/LON
 * 3.  Emits an event 'WEATHER_FORECAST_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.getWeatherForecast()
 * window.$b.on('WEATHER_FORECAST_RESULTS', function (data) {
 *   if (data.periods) {
 *     data.periods.forEach(function (period) {
 *       console.log(period.name + ': ' + period.shortForecast)
 *       console.log(period.detailedForecast)
 *     })
 *   }
 * }
 * ```
 * Example of data object returned:
 * ```
 * {
 *   "updated": "2018-09-06T23:47:02+00:00",
 *   "units": "us",
 *   "forecastGenerator": "BaselineForecastGenerator",
 *   "generatedAt": "2018-09-07T01:30:36+00:00",
 *   "updateTime": "2018-09-06T23:47:02+00:00",
 *   "validTimes": "2018-09-06T17:00:00+00:00/P7DT20H",
 *   "elevation": {
 *     "value": 39.9288,
 *     "unitCode": "unit:m"
 *   },
 *   "periods": [
 *     {
 *       "number": 1,
 *       "name": "Tonight",
 *       "startTime": "2018-09-06T18:00:00-07:00",
 *       "endTime": "2018-09-07T06:00:00-07:00",
 *       "isDaytime": false,
 *       "temperature": 54,
 *       "temperatureUnit": "F",
 *       "temperatureTrend": null,
 *       "windSpeed": "7 to 18 mph",
 *       "windDirection": "WSW",
 *       "icon": "https://api.weather.gov/icons/land/night/bkn?size=medium",
 *       "shortForecast": "Mostly Cloudy",
 *       "detailedForecast": "Mostly cloudy, with a low around 54. West southwest wind 7 to 18 mph, with gusts as high as 24 mph."
 *     },
 *     {
 *       "number": 2,
 *       "name": "Friday",
 *       "startTime": "2018-09-07T06:00:00-07:00",
 *       "endTime": "2018-09-07T18:00:00-07:00",
 *       "isDaytime": true,
 *       "temperature": 70,
 *       "temperatureUnit": "F",
 *       "temperatureTrend": "falling",
 *       "windSpeed": "7 to 18 mph",
 *       "windDirection": "WSW",
 *       "icon": "https://api.weather.gov/icons/land/day/sct?size=medium",
 *       "shortForecast": "Mostly Sunny",
 *       "detailedForecast": "Mostly sunny. High near 70, with temperatures falling to around 65 in the afternoon. West southwest wind 7 to 18 mph, with gusts as high as 25 mph."
 *     },
 *     {
 *       "number": 3,
 *       "name": "Friday Night",
 *       "startTime": "2018-09-07T18:00:00-07:00",
 *       "endTime": "2018-09-08T06:00:00-07:00",
 *       "isDaytime": false,
 *       "temperature": 55,
 *       "temperatureUnit": "F",
 *       "temperatureTrend": null,
 *       "windSpeed": "9 to 18 mph",
 *       "windDirection": "WSW",
 *       "icon": "https://api.weather.gov/icons/land/night/bkn?size=medium",
 *       "shortForecast": "Mostly Cloudy",
 *       "detailedForecast": "Mostly cloudy, with a low around 55. West southwest wind 9 to 18 mph, with gusts as high as 24 mph."
 *     },
 *     {
 *       "number": 4,
 *       "name": "Saturday",
 *       "startTime": "2018-09-08T06:00:00-07:00",
 *       "endTime": "2018-09-08T18:00:00-07:00",
 *       "isDaytime": true,
 *       "temperature": 68,
 *       "temperatureUnit": "F",
 *       "temperatureTrend": null,
 *       "windSpeed": "9 to 16 mph",
 *       "windDirection": "WSW",
 *       "icon": "https://api.weather.gov/icons/land/day/sct?size=medium",
 *       "shortForecast": "Mostly Sunny",
 *       "detailedForecast": "Mostly sunny, with a high near 68. West southwest wind 9 to 16 mph, with gusts as high as 22 mph."
 *     },
 *     {
 *       "number": 5,
 *       "name": "Saturday Night",
 *       "startTime": "2018-09-08T18:00:00-07:00",
 *       "endTime": "2018-09-09T06:00:00-07:00",
 *       "isDaytime": false,
 *       "temperature": 54,
 *       "temperatureUnit": "F",
 *       "temperatureTrend": null,
 *       "windSpeed": "5 to 16 mph",
 *       "windDirection": "WSW",
 *       "icon": "https://api.weather.gov/icons/land/night/bkn?size=medium",
 *       "shortForecast": "Mostly Cloudy",
 *       "detailedForecast": "Mostly cloudy, with a low around 54. West southwest wind 5 to 16 mph, with gusts as high as 22 mph."
 *     },
 *     {
 *       "number": 6,
 *       "name": "Sunday",
 *       "startTime": "2018-09-09T06:00:00-07:00",
 *       "endTime": "2018-09-09T18:00:00-07:00",
 *       "isDaytime": true,
 *       "temperature": 66,
 *       "temperatureUnit": "F",
 *       "temperatureTrend": null,
 *       "windSpeed": "5 to 14 mph",
 *       "windDirection": "W",
 *       "icon": "https://api.weather.gov/icons/land/day/sct?size=medium",
 *       "shortForecast": "Mostly Sunny",
 *       "detailedForecast": "Mostly sunny, with a high near 66."
 *     },
 *     {
 *       "number": 7,
 *       "name": "Sunday Night",
 *       "startTime": "2018-09-09T18:00:00-07:00",
 *       "endTime": "2018-09-10T06:00:00-07:00",
 *       "isDaytime": false,
 *       "temperature": 54,
 *       "temperatureUnit": "F",
 *       "temperatureTrend": null,
 *       "windSpeed": "8 to 14 mph",
 *       "windDirection": "W",
 *       "icon": "https://api.weather.gov/icons/land/night/sct?size=medium",
 *       "shortForecast": "Partly Cloudy",
 *       "detailedForecast": "Partly cloudy, with a low around 54."
 *     },
 *     {
 *       "number": 8,
 *       "name": "Monday",
 *       "startTime": "2018-09-10T06:00:00-07:00",
 *       "endTime": "2018-09-10T18:00:00-07:00",
 *       "isDaytime": true,
 *       "temperature": 66,
 *       "temperatureUnit": "F",
 *       "temperatureTrend": null,
 *       "windSpeed": "8 to 14 mph",
 *       "windDirection": "W",
 *       "icon": "https://api.weather.gov/icons/land/day/sct?size=medium",
 *       "shortForecast": "Mostly Sunny",
 *       "detailedForecast": "Mostly sunny, with a high near 66."
 *     },
 *     {
 *       "number": 9,
 *       "name": "Monday Night",
 *       "startTime": "2018-09-10T18:00:00-07:00",
 *       "endTime": "2018-09-11T06:00:00-07:00",
 *       "isDaytime": false,
 *       "temperature": 55,
 *       "temperatureUnit": "F",
 *       "temperatureTrend": null,
 *       "windSpeed": "9 to 14 mph",
 *       "windDirection": "WNW",
 *       "icon": "https://api.weather.gov/icons/land/night/sct?size=medium",
 *       "shortForecast": "Partly Cloudy",
 *       "detailedForecast": "Partly cloudy, with a low around 55."
 *     },
 *     {
 *       "number": 10,
 *       "name": "Tuesday",
 *       "startTime": "2018-09-11T06:00:00-07:00",
 *       "endTime": "2018-09-11T18:00:00-07:00",
 *       "isDaytime": true,
 *       "temperature": 65,
 *       "temperatureUnit": "F",
 *       "temperatureTrend": null,
 *       "windSpeed": "9 to 16 mph",
 *       "windDirection": "WNW",
 *       "icon": "https://api.weather.gov/icons/land/day/sct?size=medium",
 *       "shortForecast": "Mostly Sunny",
 *       "detailedForecast": "Mostly sunny, with a high near 65."
 *     },
 *     {
 *       "number": 11,
 *       "name": "Tuesday Night",
 *       "startTime": "2018-09-11T18:00:00-07:00",
 *       "endTime": "2018-09-12T06:00:00-07:00",
 *       "isDaytime": false,
 *       "temperature": 55,
 *       "temperatureUnit": "F",
 *       "temperatureTrend": null,
 *       "windSpeed": "10 to 16 mph",
 *       "windDirection": "WNW",
 *       "icon": "https://api.weather.gov/icons/land/night/sct?size=medium",
 *       "shortForecast": "Partly Cloudy",
 *       "detailedForecast": "Partly cloudy, with a low around 55."
 *     },
 *     {
 *       "number": 12,
 *       "name": "Wednesday",
 *       "startTime": "2018-09-12T06:00:00-07:00",
 *       "endTime": "2018-09-12T18:00:00-07:00",
 *       "isDaytime": true,
 *       "temperature": 65,
 *       "temperatureUnit": "F",
 *       "temperatureTrend": null,
 *       "windSpeed": "12 to 15 mph",
 *       "windDirection": "W",
 *       "icon": "https://api.weather.gov/icons/land/day/sct?size=medium",
 *       "shortForecast": "Mostly Sunny",
 *       "detailedForecast": "Mostly sunny, with a high near 65."
 *     },
 *     {
 *       "number": 13,
 *       "name": "Wednesday Night",
 *       "startTime": "2018-09-12T18:00:00-07:00",
 *       "endTime": "2018-09-13T06:00:00-07:00",
 *       "isDaytime": false,
 *       "temperature": 56,
 *       "temperatureUnit": "F",
 *       "temperatureTrend": null,
 *       "windSpeed": "8 to 15 mph",
 *       "windDirection": "W",
 *       "icon": "https://api.weather.gov/icons/land/night/few?size=medium",
 *       "shortForecast": "Mostly Clear",
 *       "detailedForecast": "Mostly clear, with a low around 56."
 *     },
 *     {
 *       "number": 14,
 *       "name": "Thursday",
 *       "startTime": "2018-09-13T06:00:00-07:00",
 *       "endTime": "2018-09-13T18:00:00-07:00",
 *       "isDaytime": true,
 *       "temperature": 66,
 *       "temperatureUnit": "F",
 *       "temperatureTrend": null,
 *       "windSpeed": "7 to 13 mph",
 *       "windDirection": "W",
 *       "icon": "https://api.weather.gov/icons/land/day/few?size=medium",
 *       "shortForecast": "Sunny",
 *       "detailedForecast": "Sunny, with a high near 66."
 *     }
 *   ],
 *   "geometry": {
 *     "type": "GeometryCollection",
 *     "geometries": [
 *       {
 *         "type": "Point",
 *         "coordinates": [
 *           -122.4097848,
 *           37.7725663
 *         ]
 *       },
 *       {
 *         "type": "Polygon",
 *         "coordinates": [
 *           [
 *             [
 *               -122.4263966,
 *               37.7812349
 *             ],
 *             [
 *               -122.42073930000001,
 *               37.759429600000004
 *             ],
 *             [
 *               -122.39317570000001,
 *               37.76389580000001
 *             ],
 *             [
 *               -122.39882750000001,
 *               37.78570150000001
 *             ],
 *             [
 *               -122.4263966,
 *               37.7812349
 *             ]
 *           ]
 *         ]
 *       }
 *     ]
 *   }
 * }
 * ```
 * @param {Object=} options
 * @param {string=} options.zipCode optional zip code to overrride value in Ad Parameters
 * @param {string=} options.temperatureUnit Sets the unit type that degrees will be measured.  Accepts 'F' or 'C'.  Default is 'F'.
 * @param {number=} timeout (default 30 seconds) maximum seconds to wait for api results
 * @returns {Promise}
 */
function getWeatherForecast (options) {
  options = options || {}
  var eventName = 'WEATHER_FORECAST_RESULTS'
  var errorEvent = 'WEATHER_FORECAST_ERROR'
  var temperatureUnit = options.temperatureUnit || 'F'
  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)
        var error = new Error(message)
        try {
          window.$b.emit(errorEvent, error)
        } catch (err) {
          console.error(err)
        }
        return Promise.reject(error)
      }
    } 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
  }
  // var zipUrl = 'https://pterodactylus.tremorvideodsp.com/ws/zipcodes/ZIP.json'
  var zipUrl = 'https://raptor.tremorvideodsp.com/ws/zipcodes/ZIP.json'
  var weatherUrl = 'https://api.weather.gov/points/LAT,LON/forecast'
  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)
        reject(error)
        try {
          window.$b.emit(errorEvent, error)
        } catch (err) {
          console.error(err)
        }
      })
      .then(function (json) {
        if (!isLocationValid(json)) {
          var message = 'Zipcode "' + zipCode + '" is invalid'
          console.error(errorEvent, message)
          var error = new Error(message)
          try {
            window.$b.emit(errorEvent, error)
          } catch (err) {
            console.error(err)
          }
          return reject(error)
        }
        weatherUrl = weatherUrl
          .replace('LAT', json.latitude)
          .replace('LON', json.longitude)
        $.ajax({
          url: weatherUrl,
          method: 'GET',
          timeout: timeout
        })
          .fail(function (err) {
            var message = weatherUrl + ': ' + err.statusText
            console.error(errorEvent, message)
            var error = new Error(message)
            reject(error)
            try {
              window.$b.emit(errorEvent, error)
            } catch (err) {
              console.error(err)
            }
          })
          .done(function (data) {
            if (data.properties instanceof Object) {
              data.properties.geometry = data.geometry
              data.properties.zipCode = zipCode
              if (temperatureUnit === 'C' && data.properties.periods) {
                data.properties.periods.forEach(function (element) {
                  element.temperature = Math.round((5 / 9) * (element.temperature - 32))
                  element.temperatureUnit = 'C'
                })
              }
              console.log(eventName, data.properties)
              resolve(data.properties)
              try {
                window.$b.emit(eventName, data.properties)
              } catch (err) {
                console.error(err)
              }
            } else {
              var message = 'Weather Forecast from ' + weatherUrl + ' did not return data.properties'
              console.error(errorEvent, message)
              var error = new Error(message)
              reject(error)
              try {
                window.$b.emit(errorEvent, error)
              } catch (err) {
                console.error(err)
              }
            }
          })
      })
  })
}

window.$b = window.$b || {}
window.$b.snippets = window.$b.snippets || {}
var snippets = window.$b.snippets
snippets.getWeatherForecast = getWeatherForecast

if (window.module) {
  window.module.exports = getWeatherForecast
}