## Copyright (C) 2018-2020 Philip Nienhuis
##
## 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 PURPOSEll. 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; see the file COPYING.  If not, see
## <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn {Function File} {} referenceEllipsoid (@var{name}, @var{unit})
## Returns the parameters of an ellipsoid.
##
## @var{Name} can be the name (e.g., "wgs84") or (integer) EPSG code of a
## reference ellipoid.  Case is not important.  If the number or code 0
## (zero) is specified, referenceEllipsoid echoes a list of implemented
## ellipsoids to the screen.
##
## @var{unit} can be the name of any unit accepted by function 
## validateLengthUnit.m.  Also here case is not important.
##
## The output consists of a scalar struct with fields "Code" (EPSG code of the
## ellipsoid), "Name" (name of the ellipsoid), "LengthUnit", "SemimajorAxis",
## "SemiminorAxis", "InverseFlattening", "Eccentricity", "Flattening", 
## "ThirdFlattening", "MeanRadius", "SurfaceArea", and "Volume".
##
## Examples:
##
## @example
## >> E = referenceEllipsoid ("wgs84")
## E =
##
##  scalar structure containing the fields:
##
##    Code =  7030
##    Name = World Geodetic System 1984
##    LengthUnit = meter
##    SemimajorAxis =  6378137
##    SemiminorAxis =    6.3568e+06
##    InverseFlattening =  298.26
##    Eccentricity =  0.081819
##    Flattening =  0.0033528
##    ThirdFlattening =  0.0016792
##    MeanRadius =    6.3710e+06
##    SurfaceArea =    5.1007e+14
##    Volume =    1.0832e+21
## @end example
##
## The code number can be used:
##
## @example
## >> E = referenceEllipsoid (7019)
## E = 
##
##  scalar structure containing the fields:
##
##    Code =       7019
##    Name = Geodetic Reference System 1980
##    LengthUnit = meter
##    SemimajorAxis = 6.3781e+06
##    SemiminorAxis = 6.3568e+06
##    InverseFlattening =     298.26
##    Eccentricity =   0.081819
##    Flattening =  0.0033528
##    ThirdFlattening =  0.0016792
##    MeanRadius =  6.371e+06
##    SurfaceArea = 5.1007e+14
##    Volume = 1.0832e+21
## @end example
##
## @example
## >> E = referenceEllipsoid ("wgs84", "km")
## E =
##
##  scalar structure containing the fields:
##
##    Code =  7030
##    Name = World Geodetic System 1984
##    LengthUnit = km
##    SemimajorAxis =  6378.1
##    SemiminorAxis =  6356.8
##    InverseFlattening =  298.26
##    Eccentricity =  0.081819
##    Flattening =  0.0033528
##    ThirdFlattening =  0.0016792
##    MeanRadius =  6371.0
##    SurfaceArea =    5.1007e+08
##    Volume =    1.0832e+12
## @end example
##
## @seealso{validateLengthUnit, wgs84Ellipsoid}
## @end deftypefn

## Function supplied by anonymous contributor, see:
## https://savannah.gnu.org/patch/index.php?9634

function Ell = referenceEllipsoid (name="wgs84", unit="meter")

  ## List of implemented codes. To be updated if codes are added.
  persistent codes;
  if (isempty (codes))
    codes = {"7001", "Airy30",       " "; ...
             "7002", "Airy49",       " "; ...
             "7003", "Australia",    " "; ...
             "7004", "Bessel",       " "; ...
             "7008", "Clarke66",     " "; ...
             "7012", "Bessel 1880",  " "; ...
             "7015", "Everest 1830", " "; ...
             "7019", "grs80",        "1980"; ...
             "7022", "int24",        "1924"; ...
             "7024", "Kras40",       "Krasovsky"; ...
             "7030", "Wgs84",        "1984"; ...
             "7043", "Wgs72",        "1972"; ...
             " ",    "Sun",          " "; ...
             " ",    "Mercury",      " "; ...
             " ",    "Venus",        " "; ...
             " ",    "Earth",        " "; ...
             " ",    "Moon",         " "; ...
             " ",    "Mars",         " "; ...
             " ",    "Jupiter",      " "; ...
             " ",    "Saturn",       " "; ...
             " ",    "Uranus",       " "; ...
             " ",    "Neptune",      " "; ...
             " ",    "Pluto",        " "};
  endif

  if (isnumeric (name) && isreal (name))
    name = num2str (fix (name));
  elseif (! ischar (name))
    error ("referenceEllipsoid: value must be a string or real number");
  elseif (strcmp (name, " "))
    error ("referenceEllipsoid: name required");
  endif

  if (! ischar (unit))
    error ("referenceEllipsoid: length name expected for input arg. #2");
  endif

  switch lower (name)
    ## Semimajor axis and Inverse flattening from
    ## USER's HANDBOOK ON DATUM TRANSFORMATIONS INVOLVING WGS 84
    ## 3rd Edition, July 2003, (Last correction August 2008),
    ## Special Publication No. 60
    ## Codenames are from https://epsg.io/
    ## Planet values from Report of the IAU Working Group on
    ## CartographicCoordinates and Rotational Elements: 2015

    case lower (codes(1, :))
      Code = 7001;
      Name = "Airy 1830";
      SemimajorAxis = 6377563.396;
      InverseFlattening = 299.3249646;
        
    case lower (codes (2, :))
      Code = 7002;
      Name = "Airy Modified 1849";
      SemimajorAxis = 6377340.189;
      InverseFlattening = 299.3249646;

    case lower (codes (3, :))
      Code = 7003;
      Name = "Australian National";
      SemimajorAxis = 6378160;
      InverseFlattening = 298.25;

    case lower (codes (4, :))
      Code = 7004;
      Name = "Bessel 1841";
      SemimajorAxis = 6377397.155;
      InverseFlattening = 299.1528128;

    case lower (codes (5, :))
      Code = 7008;
      Name = "Clarke 1866";
      SemimajorAxis = 6378206.4;
      InverseFlattening = 294.9786982;

    case lower (codes (6, :))
      Code = 7012;
      Name = "Bessel 1880";
      SemimajorAxis = 6378249.145;
      InverseFlattening = 293.465;

    case lower (codes (7, :))
      Code = 7015;
      Name = "Everest 1830";
      SemimajorAxis = 6377276.34518;
      InverseFlattening = 300.8017;

    case lower (codes (8, :))
      Code = 7019;
      Name = "Geodetic Reference System 1980";
      SemimajorAxis = 6378137;
      InverseFlattening = 298.257222101;

    case lower (codes (9, :))
      Code = 7022;
      Name = "International 1924";
      SemimajorAxis = 6378388;
      InverseFlattening = 297;

    case lower (codes (10, :))
      Code = 7024;
      Name = "Krasovsky 1940";
      SemimajorAxis = 6378245;
      InverseFlattening = 298.3;

    case lower (codes (11, :))
      Code = 7030;
      Name = "World Geodetic System 1984";
      SemimajorAxis = 6378137;
      InverseFlattening = 298.2572235630;

    case lower (codes (12, :))
      Code = 7043;
      Name = "World Geodetic System 1972";
      SemimajorAxis = 6378135;
      InverseFlattening = 298.26;

    case lower (codes (13, :))
      Code = " ";
      Name = "Sun";
      SemimajorAxis = 695700000;
      InverseFlattening = 111111;

    case lower (codes (14, :))
      Code = " ";
      Name = "Mercury";
      SemimajorAxis = 2440530;
      InverseFlattening = 1075;

    case lower (codes (15, :))
      Code = " ";
      Name = "Venus";
      SemimajorAxis = 6051800;
      InverseFlattening = Inf;

    case lower (codes (16, :))
      Code = " ";
      Name = "Earth";
      SemimajorAxis = 6378137;
      InverseFlattening = 298.2572235630;

    case lower (codes (17, :))
      Code = " ";
      Name = "Moon";
      SemimajorAxis = 1738100;
      InverseFlattening = 833.33;

    case lower (codes (18, :))
      Code = " ";
      Name = "Mars";
      SemimajorAxis = 3396190;
      InverseFlattening =  169.894;

    case lower (codes (19, :))
      Code = " ";
      Name = "Jupiter";
      SemimajorAxis = 71492000;
      InverseFlattening =  15.4144;

    case lower (codes (20, :))
      Code = " ";
      Name = "Saturn";
      SemimajorAxis = 60268000;
      InverseFlattening = 10.208;

    case lower (codes (21, :))
      Code = " ";
      Name = "Uranus";
      SemimajorAxis = 25559000;
      InverseFlattening = 43.616;

    case lower (codes (22, :))
      Code = " ";
      Name = "Neptune";
      SemimajorAxis = 24764000;
      InverseFlattening = 58.5437;

    case lower (codes (23, :))
      Code = " ";
      Name = "Pluto";
      SemimajorAxis = 1188300;
      InverseFlattening = INF;

    case "0"
      ## Show list of codes
      printf ("\n referenceEllipsoid.m:\n list of implemented ellipsoids:\n");
      printf (" Code          Alias 1          Alias 2\n");
      printf (" ====          =======          =======\n");
      for ii=1:size (codes, 1)
        printf ("%5s  %15s  %15s\n", codes (ii, :){:});
      endfor
      return

    otherwise
      error ("referenceEllipsoid: ellipsoid %s has not been implemented", name)

  endswitch

  ## Calculations
  Ell = param_calc (Code, Name, SemimajorAxis, InverseFlattening, unit);

endfunction


function ell = param_calc (Code, Name, SemimajorAxis, InvF, unit)

  ell.Code              = Code;
  ell.Name              = Name;
  ratio   = unitsratio (unit, "Meters");
  ell.LengthUnit        = unit;
  Major   = SemimajorAxis * ratio;
  ell.SemimajorAxis     = Major;
  Inverse = InvF;
  ell.InverseFlattening = InvF;
  Ecc     = flat2ecc (1 / InvF);
  ell.Eccentricity      = Ecc;
  Minor   = minaxis (Major, Ecc);
  ell.SemiminorAxis     = Minor;
  ell.Flattening        = 1 / InvF;
  ell.ThirdFlattening   = (Major - Minor) / (Major + Minor);
  ell.MeanRadius        = (2 * Major + Minor) / 3;
  ## From Knud Thomsen this results in a max error of 1.061 %:
  P       = 1.6075;
  Surface = 4 * pi * ((Major^(2*P) +  2 * (Major * Minor)^P) / 3 )^(1/P);
  ell.SurfaceArea       = Surface;
  ell.Volume            = (4 * pi) / 3 * Major^2 * Minor;

endfunction


%!test
%!
%! E = referenceEllipsoid ("wgs84");
%! assert ( E.SemiminorAxis, 6356752.314245, 10e-7 )
%! assert ( E.Eccentricity, 0.081819221456, 10e-8)
%! assert ( E.Flattening, 1 / 298.2572235630, 10e-8 )

%!error <value must be a string> referenceEllipsoid ( 7i )
%!error <not been implemented> referenceEllipsoid ( "yy" )
%!error <name required> referenceEllipsoid ( " " )
