Source: lib/GeoPoint.js

'use strict';

/**
 * Creates a new GeoPoint instance
 * From latitude and longitude
 * From a json object
 * Or an tuple of latitude and longitude
 *
 * @alias GeoPoint
 */
class GeoPoint {
  /**
   * Creates a GeoPoint with the user's current location, if available.
   * @return {Promise<GeoPoint>} A promise that will be resolved with a GeoPoint
   */
  static current() {
    return new Promise(((resolve, reject) => {
      if (!navigator) {
        reject(new Error('This seems not to be a browser context.'));
      }
      if (!navigator.geolocation) {
        reject(new Error('This browser does not support geolocation.'));
      }

      navigator.geolocation.getCurrentPosition((location) => {
        resolve(new GeoPoint(location.coords.latitude, location.coords.longitude));
      }, (error) => {
        reject(error);
      });
    }));
  }

  /**
   * @param {string|number|Object|Array<number>} [latitude] A coordinate pair (latitude first),
   * a GeoPoint like object or the GeoPoint's latitude
   * @param {number=} longitude The GeoPoint's longitude
   */
  constructor(latitude, longitude) {
    let lat;
    let lng;
    if (Object(latitude) instanceof String) {
      const index = latitude.indexOf(';');
      lat = latitude.substring(0, index);
      lng = latitude.substring(index + 1);
    } else if (Object(latitude) instanceof Number) {
      lat = latitude;
      lng = longitude;
    } else if (Object(latitude) instanceof Array) {
      lat = latitude[0];
      lng = latitude[1];
    } else if (latitude instanceof Object) {
      lat = latitude.latitude;
      lng = latitude.longitude;
    } else {
      lat = 0;
      lng = 0;
    }

    /**
     * Longitude of the given point
     * @type {number}
     */
    this.longitude = lng;

    /**
     * Latitude of the given point
     * @type {number}
     */
    this.latitude = lat;

    if (this.latitude < -90 || this.latitude > 90) {
      throw new Error('Latitude ' + this.latitude + ' is not in bound of -90 <= latitude <= 90');
    }

    if (this.longitude < -180 || this.longitude > 180) {
      throw new Error('Longitude ' + this.longitude + ' is not in bound of -180 <= longitude <= 180');
    }
  }

  /**
   * Returns the distance from this GeoPoint to another in kilometers.
   * @param {GeoPoint} point another GeoPoint
   * @return {number} The distance in kilometers
   *
   * @see GeoPoint#radiansTo
   */
  kilometersTo(point) {
    return Number((GeoPoint.EARTH_RADIUS_IN_KILOMETERS * this.radiansTo(point)).toFixed(3));
  }

  /**
   * Returns the distance from this GeoPoint to another in miles.
   * @param {GeoPoint} point another GeoPoint
   * @return {number} The distance in miles
   *
   * @see GeoPoint#radiansTo
   */
  milesTo(point) {
    return Number((GeoPoint.EARTH_RADIUS_IN_MILES * this.radiansTo(point)).toFixed(3));
  }

  /**
   * Computes the arc, in radian, between two WGS-84 positions.
   *
   * The haversine formula implementation is taken from:
   * {@link http://www.movable-type.co.uk/scripts/latlong.html}
   *
   * Returns the distance from this GeoPoint to another in radians.
   * @param {GeoPoint} point another GeoPoint
   * @return {number} the arc, in radian, between two WGS-84 positions
   *
   * @see http://en.wikipedia.org/wiki/Haversine_formula
   */
  radiansTo(point) {
    const from = this;
    const to = point;
    const rad1 = from.latitude * GeoPoint.DEG_TO_RAD;
    const rad2 = to.latitude * GeoPoint.DEG_TO_RAD;
    const dLng = (to.longitude - from.longitude) * GeoPoint.DEG_TO_RAD;

    return Math.acos((Math.sin(rad1) * Math.sin(rad2)) + (Math.cos(rad1) * Math.cos(rad2) * Math.cos(dLng)));
  }

  /**
   * A String representation in latitude, longitude format
   * @return {string} The string representation of this class
   */
  toString() {
    return this.latitude + ';' + this.longitude;
  }

  /**
   * Returns a JSON representation of the GeoPoint
   * @return {json} A GeoJson object of this GeoPoint
   */
  toJSON() {
    return { latitude: this.latitude, longitude: this.longitude };
  }
}

/**
 * How many radians fit in one degree.
 * @type {number}
 */
GeoPoint.DEG_TO_RAD = Math.PI / 180;

/**
 * The Earth radius in kilometers used by {@link GeoPoint#kilometersTo}
 * @type {number}
 */
GeoPoint.EARTH_RADIUS_IN_KILOMETERS = 6371;

/**
 * The Earth radius in miles used by {@link GeoPoint#milesTo}
 * @type {number}
 */
GeoPoint.EARTH_RADIUS_IN_MILES = 3956;

module.exports = GeoPoint;