diff options
Diffstat (limited to 'inst/areaquad.m')
-rw-r--r-- | inst/areaquad.m | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/inst/areaquad.m b/inst/areaquad.m new file mode 100644 index 0000000..e90faae --- /dev/null +++ b/inst/areaquad.m @@ -0,0 +1,262 @@ +## Copyright (C) 2022 The Octave Project Developers +## +## 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 {} {@var{aq} =} areaquad (@var{lat1}, @var{lon1}, @var{lat2}, @var{lon2}) +## @deftypefnx {} {@var{aq} =} areaquad (@var{lat1}, @var{lon1}, @var{lat2}, @var{lon2}, @var{spheroid}) +## @deftypefnx {} {@var{aq} =} areaquad (@var{lat1}, @var{lon1}, @var{lat2}, @var{lon2}, @var{angleUnit}) +## @deftypefnx {} {@var{aq} =} areaquad (@var{lat1}, @var{lon1}, @var{lat2}, @var{lon2}, @var{spheroid}, @var{angleUnit}) +## Returns the area of a quadrilateral given two points. +## +## If no ellipsoid is given the result will be a fraction of a unit sphere +## with a radius of one meter. If an ellipsoid struct is supplied the result's +## unit will be the squared unit of that ellipsoid; otherwise, if just an +## ellipsoid name is given result will be in standard units squared (e.g., m^2). +## +## Input +## @itemize +## @item +## @var{lat1}, @var{lon1}: the first point of the quadrilateral. +## +## @item +## @var{lat2}, @var{lon2}: the second point of the quadrilateral. +## @end itemize +## +## @indentedblock +## These coordinate inputs be scalars, vectors or 2D arrays. If any of these +## is non-scalar, all other LAT/LON inputs must either have the same size or +## be scalars. +## @end indentedblock +## +## @itemize +## @item +## (optional) @var{spheroid}: referenceEllipsoid parameter struct; default +## is 'Unit Sphere'. A string value or EPSG code describing the +## spheroid is also accepted. Alternatively, a numerical vector +## @code{[semimajoraxis eccentricity]} can be supplied. If specified, +## @var{spheroid} must be the 5th input argument. +## +## @item +## (optional) @var{angleUnit}: string for angular units ('degrees' or +## 'radians', case-insensitive, just the first charachter will do). Default +## is 'degrees'. If specified, @var{angleUnit} must be the last input argument. +## @end itemize +## +## Output: +## @itemize +## @item +## @var{aq}: the area of the quadrilateral. If one or more of the inputs were +## vectors or arrays, areaquad's output will have the same size. +## @end itemize +## +## Example +## No ellipsoid (fraction of a sphere) +## @example +## aq = areaquad (0, 0, 90, 360) +## aq = 0.50000 +## @end example +## +## With radians +## @example +## aq = areaquad (0, 0, pi / 2 , 2 * pi, "radians") +## aq = 0.50000 +## @end example +## +## With ellipsoid in m^2 +## @example +## aq = areaquad (0, 0, 90, 360, "wgs84") +## aq = 2.5503e+14 +## @end example +## +## With vector +## @example +## aq = areaquad(-90,0,90,360,[6000 0]); +## aq = 4.5239e+08 +## @end example +## +## @seealso{referenceEllipsoid,referenceSphere} +## @end deftypefn + +function [aq] = areaquad (varargin) + + spheroid = ""; + angleUnit = "degrees"; + insq = 0; # When a spheriod is given the area is in units squared + + if (nargin < 4 || nargin > 6) + print_usage(); + elseif (nargin == 5) + if (isnumeric (varargin{5})) + ## EPSG spheroid code + spheroid = varargin{5}; + elseif (ischar (varargin{5})) + if (! isempty (varargin{5}) && ismember (lower (varargin{5}(1)), {"r", "d"})) + angleUnit = varargin{5}; + else + spheroid = varargin{5}; + endif + elseif (isstruct (varargin{5})) + spheroid = varargin{5}; + else + error ("areaquad: spheroid or angleUnit expected for arg. #5"); + endif + elseif (nargin == 6) + spheroid = varargin{5}; + angleUnit = varargin{6}; + endif + if (isnumeric (spheroid) && isscalar (spheroid)) + spheroid = num2str (spheroid); + endif + + if (! (all (cellfun ("isnumeric", varargin(1:4))) && ... + all (cellfun ("isreal", varargin(1:4))))) + error ("areaquad: numeric values expected for first four inputs"); + endif + + ism = ! cellfun ("isscalar", varargin(1:4)); + if (! isempty (find (ism))) + ## Some of the location inputs are vector or matrix. Check those sizes + sz = reshape (cell2mat (cellfun (@(x) size (x), varargin(ism), "uni", 0)), 2, [])'; + ## Check if matrix sizes are all identical + if (numel (find (ism)) > 1 && any (diff (sz))) + error ("areaquad: sizes of input location vectors/matrices must match."); + endif + ## Make sure all scalar inputs become matrices of same size + varargin(! ism) = cellfun (@(x) repmat (x, sz(ism(1), 1), sz(ism(1), 2)), ... + varargin(! ism), "uni", 0); + else + nv = 1; + endif + lat1 = varargin{1}; + lon1 = varargin{2}; + lat2 = varargin{3}; + lon2 = varargin{4}; + + if (! ischar (angleUnit)) + error ("areaquad: character value expected for 'angleUnit'"); + elseif (strncmpi (angleUnit, "degrees", min (length (angleUnit), 7))) + ## Latitude must be within [-90 ... 90] + if (any (abs ([lat1 lat2]) > 90)) + error ("areaquad: azimuth value(s) out of acceptable range [-90, 90]") + endif + lat1 = deg2rad (lat1); + lon1 = deg2rad (lon1); + lat2 = deg2rad (lat2); + lon2 = deg2rad (lon2); + elseif (strncmpi (angleUnit, "radians", min (length (angleUnit), 7))) + ## Latitude must be within [-pi/2 ... pi/2] as azimuth isn't defined outside + if (any (abs ([lat1 lat2]) > pi / 2)) + error("areaquad: azimuth value(s) out of acceptable range (-pi/2, pi/2)") + endif + else + error ("areaquad: illegal input for 'angleUnit'"); + endif + + if (isempty (spheroid)) + E = referenceSphere (); + insq = 1; # Returns the fraction of the surface area + else + E = sph_chk (spheroid); + endif + + a = E.SemimajorAxis; + e = E.Eccentricity; + s1 = sin (lat1); + s2 = sin (lat2); + del = lon1 - lon2; + + if (e < eps) + aq = abs ((del .* a .^ 2) .* (s2 - s1)); + else + ## From Earl Burkholder: 3d Global Spatial Data Model 2nd Ed. pg 168. + ## Make the equation more readable + e2 = e .^ 2; + f = 1 ./ (2 .* e); + e2m1 = 1 - e2; + + s21 = s1 .^ 2; + + s22 = s2 .^ 2; + se1 = 1 - e2 .* s21; + se2 = 1 - e2 .* s22; + + c = (del .* a .^2 .* e2m1) ./ 2; + t1 = 1 + e .* s1; + t2 = 1 + e .* s2; + + b1 = 1 - e .* s1; + b2 = 1 - e .* s2; + + g = f .* (log ( t2 ./ b2) - (log (t1 ./ b1))); + + aq = abs (c .* ((s2 ./ (se2)) - (s1 ./ (se1)) + g)); + endif + + if (insq == 1) + aq = aq ./ E.SurfaceArea; + endif + +endfunction + + +%!test +%! aq = areaquad (-90, 0, 90, 360); +%! assert (aq, 1, 1e-8); + +%!test +%! aq = areaquad (-pi / 2, 0, pi / 2, 2 * pi,"r"); +%! assert (aq, 1, 1e-8); + +%!test +%! aq = areaquad (0, 0, 90, 360, "wgs84"); +%! assert (aq, 2.5503281086204421875e+14, 1e-8); + +%!test +%! aq = areaquad(-90,0,90,360,[0 6000]); +%! assert (aq, 4 * pi * 6000 ^ 2, 1e-8) + +%!test +%! aq = areaquad(-90,0,90,360,[6000 0]); +%! assert (aq, 4 * pi * 6000 ^ 2, 1e-8) + +%!test +%! aq = areaquad(-90,0,90,360,[1 0]); +%! assert (aq, 4 * pi, 1e-8) + +%!test +%! aq = areaquad(-90,0,90,360,[0 1]); +%! assert (aq, 4 * pi, 1e-8) + +%!test +%! assert (areaquad ([-60 -45; -30 0], 0, 45, 180), ... +%! [0.393283046242, 0.353553390593; ... +%! 0.301776695296, 0.176776695296], 1e-11); + +%!error <numeric> areaquad ("s", 0, 90, 360) +%!error <numeric> areaquad (5i, 0, 90, 360) +%!error <numeric> areaquad (0, "s", 90, 360) +%!error <numeric> areaquad (0, 5i, 90, 360) +%!error <numeric> areaquad (0, 0, "s", 360) +%!error <numeric> areaquad (0, 0, 5i, 360) +%!error <numeric> areaquad (0, 0, 90, "s") +%!error <numeric> areaquad (0, 0, 90, 5i) + +%!error <ecc> areaquad (-90, 0, 90, 360, [2.9 6000]) +%!error <ellipsoid> areaquad (-90, 0, 90, 360, [2.9]) +%!error <element> areaquad (-90, 0, 90, 360, [2.9 6000 70]) +%!error <azimuth value> areaquad (-91, 0, 90, 360); +%!error <azimuth value> areaquad (-90, 0, 91, 360); +%!error <sizes of> areaquad ([1 2], 3, [4; 5], 6); + |