Packages

Erlang library to calculate distance between 2 points with Vincenty's formulae.

Current section

Files

Jump to
vincenty src vincenty.erl
Raw

src/vincenty.erl

-module(vincenty).
-type coordinate() :: {float(), float()}.
-export([radians/1, distance/2, distance/3]).
-define(RADIUS_AT_EQUATOR, 6378137).
-define(FLATTENING_ELIPSOID, 1/298.257223563).
-define(RADIUS_AT_POLES, (1-?FLATTENING_ELIPSOID)*?RADIUS_AT_EQUATOR).
-define(MILES_PER_KILOMETER, 0.621371).
-define(MAX_ITERATIONS, 200).
-define(CONVERGENCE_THRESHOLD, 0.000000000001).
-spec distance(coordinate(), coordinate()) -> float().
distance(Point1, Point2) ->
distance(Point1, Point2, false).
-spec distance(coordinate(), coordinate(), boolean()) -> float().
distance(Point1, Point2, Miles) ->
ExactAns = case Miles of
true -> miles(vincenty_inverse(Point1, Point2));
false -> vincenty_inverse(Point1, Point2)
end,
{RoundedToSixDecimalPlaces, _Rest} = string:to_float(float_to_list(ExactAns, [{decimals, 6}])),
RoundedToSixDecimalPlaces.
-spec vincenty_inverse(coordinate(), coordinate()) -> float() | {error, fail_to_converge}.
vincenty_inverse({Lat1, Long1}, {Lat2, Long2}) ->
U1 = math:atan((1-?FLATTENING_ELIPSOID)*math:tan(radians(Lat1))),
U2 = math:atan((1-?FLATTENING_ELIPSOID)*math:tan(radians(Lat2))),
InitLambda = Lambda = radians(Long2 - Long1),
SinU1 = math:sin(U1),
CosU1 = math:cos(U1),
SinU2 = math:sin(U2),
CosU2 = math:cos(U2),
% recurse till ?MAX_ITERATIONS
approximate(InitLambda, Lambda, SinU1, CosU1, SinU2, CosU2, 0).
-spec approximate(float(), float(), float(), float(), float(), float(), non_neg_integer()) -> float() | {error, fail_to_converge}.
approximate(_InitLambda, _Lambda, _SinU1, _CosU1, _SinU2, _CosU2, ?MAX_ITERATIONS) ->
{error, fail_to_converge};
approximate(InitLambda, Lambda, SinU1, CosU1, SinU2, CosU2, Iteration) ->
SinLambda = math:sin(Lambda),
CosLambda = math:cos(Lambda),
SinSigma = math:sqrt(math:pow(CosU2*SinLambda, 2) + math:pow((CosU1 * SinU2 - SinU1 * CosU2 * CosLambda), 2)),
case SinSigma of
0.0 -> 0.0;
_ ->
CosSigma = SinU1 * SinU2 + CosU1 * CosU2 * CosLambda,
Sigma = math:atan2(SinSigma, CosSigma),
SinAlpha = CosU1 * CosU2 * SinLambda / SinSigma,
CosSqAlpha = 1 - math:pow(SinAlpha, 2),
Cos2SigmaM = case CosSqAlpha of
0.0 -> 0.0;
_ -> CosSigma - 2 * SinU1 * SinU2 / CosSqAlpha
end,
C = (?FLATTENING_ELIPSOID / 16) * CosSqAlpha * (4 + ?FLATTENING_ELIPSOID - 3 * CosSqAlpha),
NewLambda = InitLambda + (1 - C) * ?FLATTENING_ELIPSOID * SinAlpha * (Sigma + C * SinSigma * (Cos2SigmaM + C * CosSigma * (-1 + 2 * math:pow(Cos2SigmaM, 2)))),
case abs(NewLambda - Lambda) < ?CONVERGENCE_THRESHOLD of
true ->
% succesfful convergence
evaluate(CosSqAlpha, SinSigma, Cos2SigmaM, CosSigma, Sigma);
false -> approximate(InitLambda, NewLambda, SinU1, CosU1, SinU2, CosU2, Iteration + 1)
end
end.
-spec evaluate(float(), float(), float(), float(), float()) -> float().
evaluate(CosSqAlpha, SinSigma, Cos2SigmaM, CosSigma, Sigma) ->
Usq = CosSqAlpha * (math:pow(?RADIUS_AT_EQUATOR, 2) - math:pow(?RADIUS_AT_POLES, 2)) / math:pow(?RADIUS_AT_POLES, 2),
A = 1 + Usq / 16384 * (4096 + Usq * (-768 + Usq * (320 - 175 * Usq))),
B = (Usq / 1024) * (256 + Usq * (-128 + Usq * (74 - 47 * Usq))),
DeltaSigma = B * SinSigma * (Cos2SigmaM + (B / 4) * (CosSigma * (-1 + 2 * math:pow(Cos2SigmaM, 2)) - (B / 6) * Cos2SigmaM * (-3 + 4 * math:pow(SinSigma, 2)) * (-3 + 4 * math:pow(Cos2SigmaM, 2)))),
?RADIUS_AT_POLES * A * (Sigma - DeltaSigma)/1000.
-spec radians(float()) -> float().
radians(Degree) ->
Degree * math:pi()/180.
-spec miles(float()) -> float().
miles(DistanceInKm) ->
?MILES_PER_KILOMETER * DistanceInKm.