lib/distance/great_circle.ex

defmodule Distance.GreatCircle do
  @moduledoc ~S"""
  Calculate great circle distances (shortest travel distance on the surface of
  a spherical Earth) given a two longitude-latitude pairs.  This is an implementation
  of the [Haversine formula](https://en.wikipedia.org/wiki/Haversine_formula)
  and approximates using a spherical (non-ellipsoid) Earth with a
  mean radius of 6,371,008.8 meters derived from the WGS84 datum.

  The function accepts two tuples in the form of `{longitude, latitude}` and
  returns the distance in meters. It will also accept a List of tuples.
  """

  @type coords() :: {number(), number()}

  @pi_over_180 3.14159265359 / 180.0
  @radius_of_earth_meters 6_371_008.8

  @doc """
  Returns the great circle distance in meters between two points in the form of
  `{longitude, latitude}`.

  ## Examples
      iex> Distance.GreatCircle.distance({-105.343, 39.984}, {-105.534, 39.123})
      97129.22118968463
      iex> Distance.GreatCircle.distance({-74.00597, 40.71427}, {-70.56656, -33.42628})
      8251609.780265334
  """

  @spec distance(coords(), coords()) :: float()
  def distance({lon1, lat1}, {lon2, lat2}) do
    a = :math.sin((lat2 - lat1) * @pi_over_180 / 2)
    b = :math.sin((lon2 - lon1) * @pi_over_180 / 2)

    s = a * a + b * b * :math.cos(lat1 * @pi_over_180) * :math.cos(lat2 * @pi_over_180)
    2 * :math.atan2(:math.sqrt(s), :math.sqrt(1 - s)) * @radius_of_earth_meters
  end

  @doc """
  Returns the great circle distance in meters along a linestring defined by the
  List of `{longitude, latitude}` pairs.

  ## Examples
      iex> Distance.GreatCircle.distance([
      ...>  {-96.796667, 32.775833},
      ...>  {126.967583, 37.566776},
      ...>  {151.215158, -33.857406},
      ...>  {55.274180, 25.197229},
      ...>  {6.942661, 50.334057},
      ...>  {-97.635926, 30.134442}])
      44728827.84910666
  """
  @spec distance(list(coords())) :: float()
  def distance([]), do: 0.0
  def distance([_]), do: 0.0

  def distance([p1, p2 | tail]) do
    distance(p1, p2) + distance([p2 | tail])
  end
end