View Raw SPL
/*****************************************************************************
*                                                                            *
*   SPH2CART.SPL  Copyright (C) 2022 DSP Development Corporation             *
*                               All Rights Reserved                          *
*                                                                            *
*   Author:      Randy Race                                                  *
*                                                                            *
*   Synopsis:    Spherical coordinates to XYZ                                *
*                                                                            *
*   Revisions:   15 May 2022  RRR  Creation                                  *
*                                                                            *
*****************************************************************************/

#if @HELP_SPH2CART

    SPH2CART

    Purpose: Convert spherical coordinates to Cartesian XYZ form.

    Syntax:  SPH2CART(az, el, r, "degrees")

                      az - A real or series, the azimuth in radians or
                           degrees

                      el - A real or series, the elevation in radians or
                           degrees.

                       r - Optional. A real or series, the radius. Defaults
                           to 1.0.

               "degrees" - Optional. A string, if "degrees", all input angles
                           are in degrees. If "radians" or not specified, the
                           input angles are in radians.

    Alternate Syntax:

             SPH2CART(sphcoords, "degrees")

               sphcoords - A 3 column series, where azimuth is the first
                           column, elevation the second column and radius
                           the third column.

               "degrees" - Optional. A string, if "degrees", all input angles
                           are in degrees. If "radians" or not specified, the
                           input angles are in radians.

    Returns: A three column series where the first column is X, the second
             column is Y and the third column is Z.

             (x, y, z) = sph2cart(az, el, r) returns the Cartesian coordinates
             as separate series.

    Example:
             W1: sph2cart(pi/4, pi/4, 1.0)

             W1 == {{0.5, 0.5, 0.707107}}

             W1 transforms the spherical coordinate with azimuth at
             pi/4 degrees, elevation of pi/4 degrees and unit length. 
             The result is a 1x3 array where the X value is 0.5, the
             Y value is 0.5 and the Z values is sqrt(2).

    Example:
             W1: sph2cart(45, 45, 1.0, "degrees")

             W1 == {{0.5, 0.5, 0.707107}}

             Same as above except the azimuth and elevation are specified
             in degrees.

    Example:
             W2: {{45, 45, 1.0}}
             W3: sph2cart(w2, "degrees")

             W3 == {{0.5, 0.5, 0.707107}}

             Same as above except the input spherical coordinates are
             contained in a single 3 column series.

    Example:
             W4: {{45, 45, 1.0}}
             (x, y, z) = sph2cart(w4, "degrees");

             x == {0.5}
             y == {0.5}
             z == {0.707107}

             Same as above except the output Cartesian coordinates are
             contained in three separate variables.

    Example:
             W1: {{pi/4, pi/4, 1.0}}
             W2: sph2cart(w1)
             W3: cart2sph(w2)

             W3 == {{0.785398, 0.785398, 1.0}}

             Demonstrates that SPH2CART and CART2SPH are inverse functions.

    Remarks:
             SPH2CART transforms azimuth, elevation and radius coordinates
             to Cartesian XYZ coordinates.

             AZIMUTH is measured counterclockwise from the positive X
             axis in the X-Y plane. AZIMUTH is in radians unless
             "degrees" is specified. AZIMUTH varies from 0 to 2*pi radians
             or 0 to 360 degrees.

             ELEVATION is measured from the X-Y plane. ELEVATION is in
             radians unless "degrees" is specified. ELEVATION varied from
             -pi/2 to pi/2 radians or -90 to 90 degrees.

             RADIUS is the optional length of the 3D vector. Defaults to
             unity.

             If "degrees" is specified, the input AZIMUTH and ELEVATION
             angles are in degrees. If "radians" or not specifed, AZIMUTH
             and ELEVATION are in radians.

    See Also:
             Cart2sph

#endif


/* spherical coords to XYZ */
sph2cart(argv)
{
        local x, y, z;

        /* parse args */
        (az, el, r, dflag) = sph2cart_parse_args(__FUNC__, argv);

        if (dflag == "degrees")
        {
                az *= pi / 180;
                el *= pi / 180;
        }

        /* transformation */
        x = r * cos(el) * cos(az);
        y = r * cos(el) * sin(az);
        z = r * sin(el);

        if (outargc <= 1)
        {
                /* force series for ravel */
                x = {x};
                y = {y};
                z = {z};
        }

        /* add comments */
        if (isarray(x))
        {
                setcomment(x, "X");
        }

        if (isarray(y))
        {
                setcomment(y, "Y");
        }

        if (isarray(z))
        {
                setcomment(z, "Z");
        }

        /* output */
        if (outargc > 1)
        {
                return(x, y, z);
        }
        else
        {
                return(ravel(x, y, z));
        }
}


/* parse args */
sph2cart_parse_args(func, argv)
{
        local az, el, r, dflag;

        az = el = r = dflag = {};

        loop (j = 1..(argc - 1))
        {
                if (isarray(getargv(j)) || isscalar(getargv(j)))
                {
                        if (isempty(az))
                        {
                                az = getargv(j);
                        }
                        else if (isempty(el))
                        {
                                el = getargv(j);
                        }
                        else if (isempty(r))
                        {
                                r = getargv(j);
                        }
                        else
                        {
                                error(sprintf("%s - too many input arguments", func));
                        }
                }
                else if (isstring(getargv(j)))
                {
                        if (isempty(dflag))
                        {
                                dflag = tolower(getargv(j));
                        }
                }
        }

        if (isempty(az))
        {
                error(sprintf("%s - input azimuth and elevation required", func));
        }
        else if (numcols(az) == 3)
        {
                /* az, el, r three column input */
                r  = col(az, 3);
                el = col(az, 2);
                az = col(az, 1);
        }
        else if (isempty(r))
        {
                r = ones(length(az), 1);
        }

        if (isempty(dflag))
        {
                dflag = "radians";
        }

        if (not(dflag == "radians" || dflag == "degrees"))
        {
                error(sprintf('%s - unknown flag "%s"', func, dflag));
        }

        return(az, el, r, dflag);
}