summaryrefslogtreecommitdiff
path: root/inst/vincenty.m
blob: d9423f213ec2364da19e62f7336212dfda00d46a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
## Copyright (C) 2014-2022 Alfredo Foltran <alfoltran@gmail.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 3 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, see <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn {Function File} {} @var{dist} = vincenty(@var{pt1}, @var{pt2})
## @deftypefnx {Function File} {} @var{dist} = vincenty(@var{pt1}, @var{pt2}, @var{ellipsoid})
## @deftypefnx {Function File} {[@var{dist}, @var{az}] = } {vincenty(@var{pt1}, @var{pt2})}
## @deftypefnx {Function File} {[@var{dist}, @var{az}] = } {vincenty(@var{pt1}, @var{pt2}, @var{ellipsoid})}
## Calculates the distance (in meters) between two (sets of) locations on
## an ellipsoid.
##
## The formula devised by Thaddeus Vincenty is used with an accurate
## ellipsoidal model of the earth (@var{ellipsoid}). The default ellipsoidal
## model is 'WGS84', which is the most globally accurate model.
##
## @var{pt1} and @var{pt2} are two-column matrices of the form [latitude longitude].
## The units for the input coordinates angles must be degrees.
## Optional argument @var{ellipsoid} defines the reference ellipsoid to use.
##
## Sample values for @var{ellipsoid} are the following:
##
## @multitable @columnfractions .7 .3
## @headitem Model @tab @var{ellipsoid}
## @item WGS 1984 (default) @tab referenceEllipsoid(7030)
## @item GRS 1980 @tab referenceEllipsoid(7019)
## @item G.B. Airy 1830 @tab referenceEllipsoid(7001)
## @item Internacional 1924 @tab referenceEllipsoid(7022)
## @item Clarke 1880 @tab referenceEllipsoid(7012)
## @item Australian Nat. @tab referenceEllipsoid(7003)
## @end multitable
##
## The sample model values are the following:
##
## @multitable @columnfractions .35 .20 .20 .25
## @headitem Model @tab Major (km) @tab Minor (km) @tab 1 / f
## @item WGS 1984 @tab 6378.137 @tab 6356.7523142 @tab 298.257223563
## @item GRS 1980 @tab 6378.137 @tab 6356.7523141 @tab 298.257222101
## @item G.B. Airy 1830 @tab 6377.563396 @tab 6356.256909 @tab 299.3249646
## @item Internacional 1924 @tab 6378.388 @tab 6356.911946 @tab 297.0
## @item Clarke 1880 @tab 6378.249145 @tab 6356.51486955 @tab 293.465
## @item Australian Nat. @tab 6378.1600 @tab 6356.774719 @tab 298.25
## @end multitable
##
## Usage:
## @example
## >> vincenty ([37, -76], [37, -9])
## ans = 5830.081
## >> vincenty ([37, -76], [67, -76], referenceEllipsoid (7019))
## ans = 3337.843
## @end example
##
## @seealso{distance, referenceEllipsoid}
## @end deftypefn

## Author: Alfredo Foltran <alfoltran@gmail.com>
##
## Octave style fixes and some re-coding to avoid sneaky errors by
## Philip Nienhuis <prnienhuis@users.sf.net>

function [dist, az] = vincenty (pt1, pt2, ellipsoid)

  if (nargin < 3)
    ellipsoid = referenceEllipsoid (7030);
  endif

  major = ellipsoid.SemimajorAxis;
  minor = ellipsoid.SemiminorAxis;
  f = ellipsoid.Flattening;
  ## Avoid confusion of length units impsed by ellipsoid, standardize on meters
  if (isfield (ellipsoid, "LengthUnit") && ! isempty (ellipsoid.LengthUnit))
    major *= unitsratio ("meters", ellipsoid.LengthUnit);
    minor *= unitsratio ("meters", ellipsoid.LengthUnit);
  endif

  iter_limit = 20;

  pt1 = deg2rad (pt1);
  pt2 = deg2rad (pt2);

  [lat1 lng1] = deal (pt1(1), pt1(2));
  [lat2 lng2] = deal (pt2(1), pt2(2));

  delta_lng = lng2 - lng1;

  reduced_lat1 = atan ((1 - f) * tan (lat1));
  reduced_lat2 = atan ((1 - f) * tan (lat2));

  [sin_reduced1 cos_reduced1] = deal (sin (reduced_lat1), cos (reduced_lat1));
  [sin_reduced2 cos_reduced2] = deal (sin (reduced_lat2), cos (reduced_lat2));

  lambda_lng = delta_lng;
  lambda_prime = 2 * pi;

  i = 0;
  while (abs (lambda_lng - lambda_prime) > 10e-12 && i <= iter_limit)
    i++;
    [sin_lambda_lng cos_lambda_lng] = deal (sin (lambda_lng), cos (lambda_lng));
    sin_sigma = sqrt ((cos_reduced2 * sin_lambda_lng) ^ 2 + ...
                      (cos_reduced1 * sin_reduced2 - ...
                      sin_reduced1 * cos_reduced2 * cos_lambda_lng) ^ 2);

    if (abs (sin_sigma < eps))
      dist = 0;
      return;
    endif

    cos_sigma = (sin_reduced1 * sin_reduced2 + ...
                 cos_reduced1 * cos_reduced2 * cos_lambda_lng);
    sigma = atan2 (sin_sigma, cos_sigma);
    sin_alpha = (cos_reduced1 * cos_reduced2 * sin_lambda_lng / sin_sigma);
    cos_sq_alpha = 1 - sin_alpha ^ 2;

    if (abs (cos_sq_alpha > eps))
      cos2_sigma_m = cos_sigma - 2 * (sin_reduced1 * sin_reduced2 / cos_sq_alpha);
    else
      cos2_sigma_m = 0.0;                       ## Equatorial line
    endif

    C = f / 16.0 * cos_sq_alpha * (4 + f * (4 - 3 * cos_sq_alpha));

    lambda_prime = lambda_lng;
    lambda_lng = (delta_lng + (1 - C) * f * sin_alpha * (sigma + ...
                  C * sin_sigma * (cos2_sigma_m + C * cos_sigma * ...
                  (-1 + 2 * cos2_sigma_m ^ 2))));
  endwhile

  if (i > iter_limit)
    error("Inverse Vincenty's formulae failed to converge!");
  endif

  u_sq = cos_sq_alpha * (major ^ 2 - minor ^ 2) / minor ^ 2;
  A = 1 + u_sq / 16384.0 * (4096 + u_sq * (-768 + u_sq * (320 - 175 * u_sq)));
  B = u_sq / 1024.0 * (256 + u_sq * (-128 + u_sq * (74 - 47 * u_sq)));
  delta_sigma = (B * sin_sigma * (cos2_sigma_m + B / 4. * (cos_sigma * ...
                (-1 + 2 * cos2_sigma_m ^ 2) - B / 6. * cos2_sigma_m * ...
                (-3 + 4 * sin_sigma ^ 2) * (-3 + 4 * cos2_sigma_m ^ 2))));
  dist = minor * A * (sigma - delta_sigma);

  if (nargout() > 1)
    alpha1 = atan2 (cos_reduced2 * sin_lambda_lng, ...
                    cos_reduced1 * sin_reduced2 - sin_reduced1 * ...
                    cos_reduced2 * cos_lambda_lng);
    alpha2 = atan2 (cos_reduced1 * sin_lambda_lng, ...
                    -sin_reduced1 * cos_reduced2 + cos_reduced1 * ...
                    sin_reduced2 * cos_lambda_lng);
    az = rad2deg ([alpha1 alpha2]);
  endif

endfunction


%!test
%! assert (vincenty ([37, -76], [37, -9]), 5830081.063, 1e-2);

%!test
%! assert (vincenty ([37, -76], [67, -76], referenceEllipsoid (7019)), ...
%!  3337842.871, 1e-2)