Source: screensaver/geo.js

/*
 *  Copyright (c) 2015-2017, Michael A. Updike All rights reserved.
 *  Licensed under the BSD-3-Clause
 *  https://opensource.org/licenses/BSD-3-Clause
 *  https://github.com/opus1269/photo-screen-saver/blob/master/LICENSE.md
 */
window.app = window.app || {};

/**
 * Handle interaction the Google maps geocode API
 * @namespace
 */
app.Geo = (function() {
  'use strict';

  new ExceptionHandler();

  /**
   * Path to Google's geocode api
   * @type {string}
   * @const
   * @default
   * @private
   * @memberOf app.Geo
   */
  const _GEOCODE_API =
      'http://maps.googleapis.com/maps/api/geocode/json';

  /**
   * A Geo location
   * @typedef {Object} app.Geo.Location
   * @property {string} loc - descriptive location
   * @property {string} point - geo location 'lat lon'
   * @memberOf app.Geo
   */

  /**
   * Cache of Geo Locations
   * @typedef {Object} app.Geo.Cache
   * @property {app.Geo.Location[]} entries - Array of locations
   * @property {int} maxSize - max entries to cache
   * @memberOf app.Geo
   */

  /**
   * Location cache
   * @type {app.Geo.Cache}
   * @private
   * @memberOf app.Geo
   */
  const _LOC_CACHE = {
    entries: [],
    maxSize: 100,
  };

  /**
   * Try to get (@link app.Geo.Location} from cache
   * @param {string} point - a geolocation
   * @returns {app.Geo.Location|undefined} location, undefined if not cached
   * @memberOf app.Geo
   * @private
   */
  function _getFromCache(point) {
    return _LOC_CACHE.entries.find((element) => {
      return (element.point === point);
    });
  }

  /**
   * Try to get (@link app.Geo.Location} from cache
   * @param {string} point - a geolocation
   * @param {string} location - description
   * @private
   * @memberOf app.Geo
   */
  function _addToCache(point, location) {
    _LOC_CACHE.entries.push({
      loc: location,
      point: point,
    });
    if (_LOC_CACHE.entries.length > _LOC_CACHE.maxSize) {
      // FIFO
      _LOC_CACHE.entries.shift();
    }
  }

  /**
   * Make sure point is in fixed point notation
   * @param {string} point - 'lat lng' may have exponential notation
   * @returns {string} 'lat,lng' fixed point notation
   * @private
   * @memberOf app.Geo
   */
  function _cleanPoint(point) {
    let ret = point;
    try {
      const stringArray = point.split(' ');
      if (stringArray.length === 2) {
        const lat = parseFloat(stringArray[0]).toFixed(8);
        const lng = parseFloat(stringArray[1]).toFixed(8);
        ret = `${lat},${lng}`;
      }
    } catch (ex) {
      Chrome.Utils.noop();
    }
    return ret;
  }

  return {
    /**
     * Get the location string
     * @param {string} point - 'lat,long'
     * @returns {Promise<string>} geolocation as string
     * @memberOf app.Geo
     */
    get: function(point) {
      if (!Chrome.Storage.getBool('showLocation')) {
        return Promise.reject(new Error('showLocation is off'));
      } else if (Chrome.Utils.isWhiteSpace(point)) {
        return Promise.reject(new Error('point is empty or null'));
      }

      // replace any exponential notation
      const pt = _cleanPoint(point);

      // check cache
      const cache = _getFromCache(pt);
      if (cache) {
        // retrieve from cache
        return Promise.resolve(cache.loc);
      }

      // get from api - it will translate based on the browser language
      const url = `${_GEOCODE_API}?latlng=${pt}`;
      const conf = Chrome.JSONUtils.shallowCopy(Chrome.Http.conf);
      conf.maxRetries = 2;
      return Chrome.Http.doGet(url, conf).then((response) => {
        let location = '';
        if ((response.status === 'OK') && response.results
            && (response.results.length > 0)) {
          location = response.results[0].formatted_address;
          // cache it
          _addToCache(pt, location);
        }
        return Promise.resolve(location);
      });
    },
};
})();