Packages
haversineEx
0.1.0
Provides some helpers functions to calculate the distance between two points on Earth using the Haversine formula. Also can find the bearing between two points, and get a point at a given distance and bearing from a given point.
Current section
Files
Jump to
Current section
Files
lib/haversineex.ex
defmodule HaversineEx do
@moduledoc """
Provides some helpers functions to calculate the distance between two points
on Earth using the Haversine formula. Also can find the bearing between two
points, and get a point at a given distance and bearing from a given point.
"""
alias HaversineEx.Point
@units [:km, :miles, :meters]
defmacrop is_unit(unit) do
quote do
unquote(unit) in unquote(@units)
end
end
@spec haversine(number) :: number
defp haversine(value) when is_number(value) do
:math.sin(value / 2)**2
end
@doc """
Calculates the distance between two points on Earth using the Haversine
formula. The result is returned in the given unit (miles, meters, km).
## Examples
iex> HaversineEx.distance(%HaversineEx.Point{lat: 40.7767644, lng: -73.9761399}, %HaversineEx.Point{lat: 40.771209, lng: -73.9673991}, :miles)
0.5971169354600706
"""
@spec distance(Point.t(), Point.t(), atom()) :: number
def distance(%Point{} = pointA, %Point{} = pointB, unit) when is_unit(unit) do
r = earth_radious(unit)
pointA_rad = Point.deg2rad(pointA)
pointB_rad = Point.deg2rad(pointB)
dLat = pointB_rad.lat - pointA_rad.lat
dLng = pointB_rad.lng - pointA_rad.lng
a = haversine(dLat) +
:math.cos(pointA_rad.lat) * :math.cos(pointB_rad.lat) *
haversine(dLng)
2 * r * :math.asin(:math.sqrt(a))
end
@doc """
Calculates the distance between all the points in the given list. The result
is returned in the given unit (miles, meters, km).
## Examples
iex> HaversineEx.distance([
...> %HaversineEx.Point{lat: 40.7767644, lng: -73.9761399},
...> %HaversineEx.Point{lat: 40.773987, lng: -73.971769},
...> %HaversineEx.Point{lat: 40.771209, lng: -73.9673991}
...> ], :miles)
0.597116937109039
"""
def distance([], _), do: 0.0
def distance([_], _), do: 0.0
@spec distance([Point.t()], atom()) :: number
def distance([pointA, pointB | tail], unit) when is_unit(unit) do
case tail do
[] -> distance(pointA, pointB, unit)
_ -> distance(pointA, pointB, unit) + distance([pointB | tail], unit)
end
end
@doc """
Calculates the bearing between two points.
## Examples
iex> HaversineEx.find_bearing(%HaversineEx.Point{lat: 40.7767644, lng: -73.9761399}, %HaversineEx.Point{lat: 40.771209, lng: -73.9673991})
130.0028272356484
"""
@spec find_bearing(Point.t(), Point.t()) :: number
def find_bearing(%Point{} = pointA, %Point{} = pointB) do
pointA_rad = Point.deg2rad(pointA)
pointB_rad = Point.deg2rad(pointB)
dLng = pointB_rad.lng - pointA_rad.lng
y = :math.sin(dLng) * :math.cos(pointB_rad.lat)
x = :math.cos(pointA_rad.lat) * :math.sin(pointB_rad.lat) -
:math.sin(pointA_rad.lat) * :math.cos(pointB_rad.lat) * :math.cos(dLng)
:math.atan2(y, x) * 180 / :math.pi
end
@doc """
Calculates a point at a given distance and bearing from a given point. The distance is given in the given unit (miles, meters, km).
## Examples
iex> HaversineEx.find_point(%HaversineEx.Point{lat: 40.7767644, lng: -73.9761399}, 0.597117, 130.002827, :miles)
%HaversineEx.Point{lat: 40.77120899942672, lng: -73.96739909902514}
"""
@spec find_point(Point.t(), number(), number(), atom()) :: %Point{}
def find_point(%Point{} = origin, distance, bearing, unit) when is_unit(unit) and is_number(distance) and is_number(bearing) do
r = earth_radious(unit)
point_rad = Point.deg2rad(origin)
bearing_rad = bearing * :math.pi / 180
c = distance / r
lat2 = :math.asin(
:math.sin(point_rad.lat) * :math.cos(c) +
:math.cos(point_rad.lat) * :math.sin(c) * :math.cos(bearing_rad)
)
lng2 = point_rad.lng + :math.atan2(
:math.sin(bearing_rad) * :math.sin(c) * :math.cos(point_rad.lat),
:math.cos(c) - :math.sin(point_rad.lat) * :math.sin(lat2)
)
%Point{lat: lat2, lng: lng2} |> Point.rad2deg()
end
@spec earth_radious(atom()) :: number()
defp earth_radious(unit) when is_unit(unit) do
case unit do
:km -> 6_371.0
:meters -> 6_371_000.0
:miles -> 3_959.0
end
end
end