Logo Search packages:      
Sourcecode: saods9 version File versions

proj.c

/*============================================================================
*
*   WCSLIB - an implementation of the FITS WCS proposal.
*   Copyright (C) 1995-2002, Mark Calabretta
*
*   This library is free software; you can redistribute it and/or modify it
*   under the terms of the GNU Library General Public License as published
*   by the Free Software Foundation; either version 2 of the License, or (at
*   your option) any later version.
*
*   This library 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 Library
*   General Public License for more details.
*
*   You should have received a copy of the GNU Library General Public License
*   along with this library; if not, write to the Free Software Foundation,
*   Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*   Correspondence concerning WCSLIB may be directed to:
*      Internet email: mcalabre@atnf.csiro.au
*      Postal address: Dr. Mark Calabretta,
*                      Australia Telescope National Facility,
*                      P.O. Box 76,
*                      Epping, NSW, 2121,
*                      AUSTRALIA
*
*
*=============================================================================
*
*  This version of proj.c is based on the version in wcslib-2.9, but has
*  been modified in the following ways by the Starlink project (e-mail: 
*  ussc@star.rl.ac.uk):
*     -  The copysign macro is now always defined within this file
*        instead of only being defined if the COPYSIGN macro has previously 
*        been defined.
*     -  Sine values which are slightly larger than 1.0 are now treated 
*        as 1.0 in function astCYPrev.
*     -  The maximum number of projection parameters has been changed from 
*        10 to 100.
*     -  The maximum number of projection parameters is given by the 
*        WCSLIB_MXPAR macro (defined in proj.h) instead of being hard-wired.
*     -  The names of all functions and structures have been chanegd to avoid 
*        clashes with wcslib. This involves adding "Ast" or "ast" at the
*        front and changing the capitalisation.
*     -  Include string.h (for strcpy and strcmp prototypes).
*     -  Include stdlib.h (for abs prototype).
*     -  Comment out declarations of npcode and pcodes variables (they
*        are not needed by AST) in order to avoid clash with similar names
*        in other modules imported as part of other software systems (e.g. 
*        SkyCat).
*     - astZPNfwd: Loop from prj->n to zero, not from MAXPAR to zero.
*     - astZPNfwd: Only return "2" if prj->n is larger than 2.

*=============================================================================
*
*   C implementation of the spherical map projections recognized by the FITS
*   "World Coordinate System" (WCS) convention.
*
*   Summary of routines
*   -------------------
*   Each projection is implemented via separate functions for the forward,
*   *fwd(), and reverse, *rev(), transformation.
*
*   Initialization routines, *set(), compute intermediate values from the
*   projection parameters but need not be called explicitly - see the
*   explanation of prj.flag below.
*
*      astPRJset astPRJfwd astPRJrev   Driver routines (see below).
*
*      astAZPset astAZPfwd astAZPrev   AZP: zenithal/azimuthal perspective
*      astSZPset astSZPfwd astSZPrev   SZP: slant zenithal perspective
*      astTANset astTANfwd astTANrev   TAN: gnomonic
*      astSTGset astSTGfwd astSTGrev   STG: stereographic
*      astSINset astSINfwd astSINrev   SIN: orthographic/synthesis
*      astARCset astARCfwd astARCrev   ARC: zenithal/azimuthal equidistant
*      astZPNset astZPNfwd astZPNrev   ZPN: zenithal/azimuthal polynomial
*      astZEAset astZEAfwd astZEArev   ZEA: zenithal/azimuthal equal area
*      astAIRset astAIRfwd astAIRrev   AIR: Airy
*      astCYPset astCYPfwd astCYPrev   CYP: cylindrical perspective
*      astCEAset astCEAfwd astCEArev   CEA: cylindrical equal area
*      astCARset astCARfwd astCARrev   CAR: Cartesian
*      astMERset astMERfwd astMERrev   MER: Mercator
*      astSFLset astSFLfwd astSFLrev   SFL: Sanson-Flamsteed
*      astPARset astPARfwd astPARrev   PAR: parabolic
*      astMOLset astMOLfwd astMOLrev   MOL: Mollweide
*      astAITset astAITfwd astAITrev   AIT: Hammer-Aitoff
*      astCOPset astCOPfwd astCOPrev   COP: conic perspective
*      astCOEset astCOEfwd astCOErev   COE: conic equal area
*      astCODset astCODfwd astCODrev   COD: conic equidistant
*      astCOOset astCOOfwd astCOOrev   COO: conic orthomorphic
*      astBONset astBONfwd astBONrev   BON: Bonne
*      astPCOset astPCOfwd astPCOrev   PCO: polyconic
*      astTSCset astTSCfwd astTSCrev   TSC: tangential spherical cube
*      astCSCset astCSCfwd astCSCrev   CSC: COBE quadrilateralized spherical cube
*      astQSCset astQSCfwd astQSCrev   QSC: quadrilateralized spherical cube
*
*
*   Driver routines; astPRJset(), astPRJfwd() & astPRJrev()
*   ----------------------------------------------
*   A set of driver routines are available for use as a generic interface to
*   the specific projection routines.  The interfaces to astPRJfwd() and astPRJrev()
*   are the same as those of the forward and reverse transformation routines
*   for the specific projections (see below).
*
*   The interface to astPRJset() differs slightly from that of the initialization
*   routines for the specific projections and unlike them it must be invoked
*   explicitly to use astPRJfwd() and astPRJrev().
*
*   Given:
*      pcode[4] const char
*                        WCS projection code.
*
*   Given and/or returned:
*      prj      AstPrjPrm*  Projection parameters (see below).
*
*   Function return value:
*               int      Error status
*                           0: Success.
*
*
*   Initialization routine; *set()
*   ------------------------------
*   Initializes members of a AstPrjPrm data structure which hold intermediate
*   values.  Note that this routine need not be called directly; it will be
*   invoked by astPRJfwd() and astPRJrev() if the "flag" structure member is
*   anything other than a predefined magic value.
*
*   Given and/or returned:
*      prj      AstPrjPrm*  Projection parameters (see below).
*
*   Function return value:
*               int      Error status
*                           0: Success.
*                           1: Invalid projection parameters.
*
*   Forward transformation; *fwd()
*   -----------------------------
*   Compute (x,y) coordinates in the plane of projection from native spherical
*   coordinates (phi,theta).
*
*   Given:
*      phi,     const double
*      theta             Longitude and latitude of the projected point in
*                        native spherical coordinates, in degrees.
*
*   Given and returned:
*      prj      AstPrjPrm*  Projection parameters (see below).
*
*   Returned:
*      x,y      double*  Projected coordinates.
*
*   Function return value:
*               int      Error status
*                           0: Success.
*                           1: Invalid projection parameters.
*                           2: Invalid value of (phi,theta).
*
*   Reverse transformation; *rev()
*   -----------------------------
*   Compute native spherical coordinates (phi,theta) from (x,y) coordinates in
*   the plane of projection.
*
*   Given:
*      x,y      const double
*                        Projected coordinates.
*
*   Given and returned:
*      prj      AstPrjPrm*  Projection parameters (see below).
*
*   Returned:
*      phi,     double*  Longitude and latitude of the projected point in
*      theta             native spherical coordinates, in degrees.
*
*   Function return value:
*               int      Error status
*                           0: Success.
*                           1: Invalid projection parameters.
*                           2: Invalid value of (x,y).
*                           1: Invalid projection parameters.
*
*   Projection parameters
*   ---------------------
*   The AstPrjPrm struct consists of the following:
*
*      int flag
*         This flag must be set to zero whenever any of p[] or r0 are set
*         or changed.  This signals the initialization routine to recompute
*         intermediaries.  flag may also be set to -1 to disable strict bounds
*         checking for the AZP, SZP, TAN, SIN, ZPN, and COP projections.
*
*      double r0
*         r0; The radius of the generating sphere for the projection, a linear
*         scaling parameter.  If this is zero, it will be reset to the default
*         value of 180/pi (the value for FITS WCS).
*
*      double p[WCSLIB_MXPAR]
*         The first WCSLIB_MXPAR elements contain projection parameters which 
*         correspond to the PROJPn keywords in FITS, so p[0] is PROJP0, and 
*         p[29] is PROJP29.  Many projections use p[1] (PROJP1) and some also 
*         use p[2] (PROJP2).  ZPN is the only projection which uses any of the 
*         others.
*
*   The remaining members of the AstPrjPrm struct are maintained by the
*   initialization routines and should not be modified.  This is done for the
*   sake of efficiency and to allow an arbitrary number of contexts to be
*   maintained simultaneously.
*
*      char code[4]
*         Three-letter projection code.
*
*      double phi0, theta0
*         Native longitude and latitude of the reference point, in degrees.
*
*      double w[10]
*      int n
*         Intermediate values derived from the projection parameters.
*
*      int (*astPRJfwd)()
*      int (*astPRJrev)()
*         Pointers to the forward and reverse projection routines.
*
*   Usage of the p[] array as it applies to each projection is described in
*   the prologue to each trio of projection routines.
*
*   Argument checking
*   -----------------
*   Forward routines:
*
*      The values of phi and theta (the native longitude and latitude)
*      normally lie in the range [-180,180] for phi, and [-90,90] for theta.
*      However, all forward projections will accept any value of phi and will
*      not normalize it.
*
*      The forward projection routines do not explicitly check that theta lies
*      within the range [-90,90].  They do check for any value of theta which
*      produces an invalid argument to the projection equations (e.g. leading
*      to division by zero).  The forward routines for AZP, SZP, TAN, SIN,
*      ZPN, and COP also return error 2 if (phi,theta) corresponds to the
*      overlapped (far) side of the projection but also return the
*      corresponding value of (x,y).  This strict bounds checking may be
*      relaxed by setting prj->flag to -1 (rather than 0) when these
*      projections are initialized.
*
*   Reverse routines:
*
*      Error checking on the projected coordinates (x,y) is limited to that
*      required to ascertain whether a solution exists.  Where a solution does
*      exist no check is made that the value of phi and theta obtained lie
*      within the ranges [-180,180] for phi, and [-90,90] for theta.
*
*   Accuracy
*   --------
*   Closure to a precision of at least 1E-10 degree of longitude and latitude
*   has been verified for typical projection parameters on the 1 degree grid
*   of native longitude and latitude (to within 5 degrees of any latitude
*   where the projection may diverge).
*
*   Author: Mark Calabretta, Australia Telescope National Facility
*   $Id: proj.c,v 1.1.1.1 2004/08/18 19:19:17 joye Exp $
*===========================================================================*/

#include <math.h>
#include <string.h>
#include <stdlib.h>
#include "wcsmath.h"
#include "wcstrig.h"
#include "proj.h"

/* Following variables are not needed in AST and are commented out to
   avoid name clashes with other software systems (e.g. SkyCat) which
   defines them.

int  npcode = 26;
char pcodes[26][4] =
      {"AZP", "SZP", "TAN", "STG", "SIN", "ARC", "ZPN", "ZEA", "AIR", "CYP",
       "CEA", "CAR", "MER", "COP", "COE", "COD", "COO", "SFL", "PAR", "MOL",
       "AIT", "BON", "PCO", "TSC", "CSC", "QSC"};
*/

const int WCS__AZP = 101;
const int WCS__SZP = 102;
const int WCS__TAN = 103;
const int WCS__STG = 104;
const int WCS__SIN = 105;
const int WCS__ARC = 106;
const int WCS__ZPN = 107;
const int WCS__ZEA = 108;
const int WCS__AIR = 109;
const int WCS__CYP = 201;
const int WCS__CEA = 202;
const int WCS__CAR = 203;
const int WCS__MER = 204;
const int WCS__SFL = 301;
const int WCS__PAR = 302;
const int WCS__MOL = 303;
const int WCS__AIT = 401;
const int WCS__COP = 501;
const int WCS__COE = 502;
const int WCS__COD = 503;
const int WCS__COO = 504;
const int WCS__BON = 601;
const int WCS__PCO = 602;
const int WCS__TSC = 701;
const int WCS__CSC = 702;
const int WCS__QSC = 703;

/* Map error number to error message for each function. */
const char *astPRJset_errmsg[] = {
   0,
   "Invalid projection parameters"};

const char *astPRJfwd_errmsg[] = {
   0,
   "Invalid projection parameters",
   "Invalid value of (phi,theta)"};

const char *astPRJrev_errmsg[] = {
   0,
   "Invalid projection parameters",
   "Invalid value of (x,y)"};


#define copysign(X, Y) ((Y) < 0.0 ? -fabs(X) : fabs(X))



/*==========================================================================*/

int astPRJset(pcode, prj)

const char pcode[4];
struct AstPrjPrm *prj;

{
   /* Set pointers to the forward and reverse projection routines. */
   if (strcmp(pcode, "AZP") == 0) {
      astAZPset(prj);
   } else if (strcmp(pcode, "SZP") == 0) {
      astSZPset(prj);
   } else if (strcmp(pcode, "TAN") == 0) {
      astTANset(prj);
   } else if (strcmp(pcode, "STG") == 0) {
      astSTGset(prj);
   } else if (strcmp(pcode, "SIN") == 0) {
      astSINset(prj);
   } else if (strcmp(pcode, "ARC") == 0) {
      astARCset(prj);
   } else if (strcmp(pcode, "ZPN") == 0) {
      astZPNset(prj);
   } else if (strcmp(pcode, "ZEA") == 0) {
      astZEAset(prj);
   } else if (strcmp(pcode, "AIR") == 0) {
      astAIRset(prj);
   } else if (strcmp(pcode, "CYP") == 0) {
      astCYPset(prj);
   } else if (strcmp(pcode, "CEA") == 0) {
      astCEAset(prj);
   } else if (strcmp(pcode, "CAR") == 0) {
      astCARset(prj);
   } else if (strcmp(pcode, "MER") == 0) {
      astMERset(prj);
   } else if (strcmp(pcode, "SFL") == 0) {
      astSFLset(prj);
   } else if (strcmp(pcode, "PAR") == 0) {
      astPARset(prj);
   } else if (strcmp(pcode, "MOL") == 0) {
      astMOLset(prj);
   } else if (strcmp(pcode, "AIT") == 0) {
      astAITset(prj);
   } else if (strcmp(pcode, "COP") == 0) {
      astCOPset(prj);
   } else if (strcmp(pcode, "COE") == 0) {
      astCOEset(prj);
   } else if (strcmp(pcode, "COD") == 0) {
      astCODset(prj);
   } else if (strcmp(pcode, "COO") == 0) {
      astCOOset(prj);
   } else if (strcmp(pcode, "BON") == 0) {
      astBONset(prj);
   } else if (strcmp(pcode, "PCO") == 0) {
      astPCOset(prj);
   } else if (strcmp(pcode, "TSC") == 0) {
      astTSCset(prj);
   } else if (strcmp(pcode, "CSC") == 0) {
      astCSCset(prj);
   } else if (strcmp(pcode, "QSC") == 0) {
      astQSCset(prj);
   } else {
      /* Unrecognized projection code. */
      return 1;
   }

   return 0;
}

/*--------------------------------------------------------------------------*/

int astPRJfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   return prj->astPRJfwd(phi, theta, prj, x, y);
}

/*--------------------------------------------------------------------------*/

int astPRJrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   return prj->astPRJrev(x, y, prj, phi, theta);
}

/*============================================================================
*   AZP: zenithal/azimuthal perspective projection.
*
*   Given:
*      prj->p[1]    Distance parameter, mu in units of r0.
*      prj->p[2]    Tilt angle, gamma in degrees.
*
*   Given and/or returned:
*      prj->flag    AZP, or -AZP if prj->flag is given < 0.
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "AZP"
*      prj->phi0     0.0
*      prj->theta0  90.0
*      prj->w[0]    r0*(mu+1)
*      prj->w[1]    tan(gamma)
*      prj->w[2]    sec(gamma)
*      prj->w[3]    cos(gamma)
*      prj->w[4]    sin(gamma)
*      prj->w[5]    asin(-1/mu) for |mu| >= 1, -90 otherwise
*      prj->w[6]    mu*cos(gamma)
*      prj->w[7]    1 if |mu*cos(gamma)| < 1, 0 otherwise
*      prj->astPRJfwd  Pointer to astAZPfwd().
*      prj->astPRJrev  Pointer to astAZPrev().
*===========================================================================*/

int astAZPset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "AZP");
   prj->flag   = copysign(WCS__AZP, prj->flag);
   prj->phi0   =  0.0;
   prj->theta0 = 90.0;

   if (prj->r0 == 0.0) prj->r0 = R2D;

   prj->w[0] = prj->r0*(prj->p[1] + 1.0);
   if (prj->w[0] == 0.0) {
      return 1;
   }

   prj->w[3] = astCosd(prj->p[2]);
   if (prj->w[3] == 0.0) {
      return 1;
   }

   prj->w[2] = 1.0/prj->w[3];
   prj->w[4] = astSind(prj->p[2]);
   prj->w[1] = prj->w[4] / prj->w[3];

   if (fabs(prj->p[1]) > 1.0) {
      prj->w[5] = astASind(-1.0/prj->p[1]);
   } else {
      prj->w[5] = -90.0;
   }

   prj->w[6] = prj->p[1] * prj->w[3];
   prj->w[7] = (fabs(prj->w[6]) < 1.0) ? 1.0 : 0.0;

   prj->astPRJfwd = astAZPfwd;
   prj->astPRJrev = astAZPrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astAZPfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double a, b, cphi, cthe, r, s, t;

   if (abs(prj->flag) != WCS__AZP) {
      if (astAZPset(prj)) return 1;
   }

   cphi = astCosd(phi);
   cthe = astCosd(theta);

   s = prj->w[1]*cphi;
   t = (prj->p[1] + astSind(theta)) + cthe*s;
   if (t == 0.0) {
      return 2;
   }

   r  =  prj->w[0]*cthe/t;
   *x =  r*astSind(phi);
   *y = -r*cphi*prj->w[2];

   /* Bounds checking. */
   if (prj->flag > 0) {
      /* Overlap. */
      if (theta < prj->w[5]) {
         return 2;
      }

      /* Divergence. */
      if (prj->w[7] > 0.0) {
         t = prj->p[1] / sqrt(1.0 + s*s);

         if (fabs(t) <= 1.0) {
            s = astATand(-s);
            t = astASind(t);
            a = s - t;
            b = s + t + 180.0;

            if (a > 90.0) a -= 360.0;
            if (b > 90.0) b -= 360.0;

            if (theta < ((a > b) ? a : b)) {
               return 2;
            }
         }
      }
   }

   return 0;
}

/*--------------------------------------------------------------------------*/

int astAZPrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double a, b, r, s, t, ycosg;
   const double tol = 1.0e-13;

   if (abs(prj->flag) != WCS__AZP) {
      if (astAZPset(prj)) return 1;
   }

   ycosg = y*prj->w[3];

   r = sqrt(x*x + ycosg*ycosg);
   if (r == 0.0) {
      *phi   =  0.0;
      *theta = 90.0;
   } else {
      *phi = astATan2d(x, -ycosg);

      s = r / (prj->w[0] + y*prj->w[4]);
      t = s*prj->p[1]/sqrt(s*s + 1.0);

      s = astATan2d(1.0, s);

      if (fabs(t) > 1.0) {
         t = copysign(90.0,t);
         if (fabs(t) > 1.0+tol) {
            return 2;
         }
      } else {
         t = astASind(t);
      }

      a = s - t;
      b = s + t + 180.0;

      if (a > 90.0) a -= 360.0;
      if (b > 90.0) b -= 360.0;

      *theta = (a > b) ? a : b;
   }

   return 0;
}

/*============================================================================
*   SZP: slant zenithal perspective projection.
*
*   Given:
*      prj->p[1]    Distance of the point of projection from the centre of the
*                   generating sphere, mu in units of r0.
*      prj->p[2]    Native longitude, phi_c, and ...
*      prj->p[3]    Native latitude, theta_c, on the planewards side of the
*                   intersection of the line through the point of projection
*                   and the centre of the generating sphere, phi_c in degrees.
*
*   Given and/or returned:
*      prj->flag    SZP, or -SZP if prj->flag is given < 0.
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "SZP"
*      prj->phi0     0.0
*      prj->theta0  90.0
*      prj->w[0]    1/r0
*      prj->w[1]    xp = -mu*cos(theta_c)*sin(phi_c)
*      prj->w[2]    yp =  mu*cos(theta_c)*cos(phi_c)
*      prj->w[3]    zp =  mu*sin(theta_c) + 1
*      prj->w[4]    r0*xp
*      prj->w[5]    r0*yp
*      prj->w[6]    r0*zp
*      prj->w[7]    (zp - 1)^2
*      prj->w[8]    asin(1-zp) if |1 - zp| < 1, -90 otherwise
*      prj->astPRJfwd  Pointer to astSZPfwd().
*      prj->astPRJrev  Pointer to astSZPrev().
*===========================================================================*/

int astSZPset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "SZP");
   prj->flag   = copysign(WCS__SZP, prj->flag);
   prj->phi0   =  0.0;
   prj->theta0 = 90.0;

   if (prj->r0 == 0.0) prj->r0 = R2D;

   prj->w[0] = 1.0/prj->r0;

   prj->w[3] = prj->p[1] * astSind(prj->p[3]) + 1.0;
   if (prj->w[3] == 0.0) {
      return 1;
   }

   prj->w[1] = -prj->p[1] * astCosd(prj->p[3]) * astSind(prj->p[2]);
   prj->w[2] =  prj->p[1] * astCosd(prj->p[3]) * astCosd(prj->p[2]);
   prj->w[4] =  prj->r0 * prj->w[1];
   prj->w[5] =  prj->r0 * prj->w[2];
   prj->w[6] =  prj->r0 * prj->w[3];
   prj->w[7] =  (prj->w[3] - 1.0) * prj->w[3] - 1.0;

   if (fabs(prj->w[3] - 1.0) < 1.0) {
      prj->w[8] = astASind(1.0 - prj->w[3]);
   } else {
      prj->w[8] = -90.0;
   }

   prj->astPRJfwd = astSZPfwd;
   prj->astPRJrev = astSZPrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astSZPfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double a, b, cphi, cthe, s, sphi, t;

   if (abs(prj->flag) != WCS__SZP) {
      if (astSZPset(prj)) return 1;
   }

   cphi = astCosd(phi);
   sphi = astSind(phi);
   cthe = astCosd(theta);
   s = 1.0 - astSind(theta);

   t = prj->w[3] - s;
   if (t == 0.0) {
      return 2;
   }

   *x =  (prj->w[6]*cthe*sphi - prj->w[4]*s)/t;
   *y = -(prj->w[6]*cthe*cphi + prj->w[5]*s)/t;

   /* Bounds checking. */
   if (prj->flag > 0) {
      /* Divergence. */
      if (theta < prj->w[8]) {
         return 2;
      }

      /* Overlap. */
      if (fabs(prj->p[1]) > 1.0) {
         s = prj->w[1]*sphi - prj->w[2]*cphi;
         t = 1.0/sqrt(prj->w[7] + s*s);

         if (fabs(t) <= 1.0) {
            s = astATan2d(s, prj->w[3] - 1.0);
            t = astASind(t);
            a = s - t;
            b = s + t + 180.0;

            if (a > 90.0) a -= 360.0;
            if (b > 90.0) b -= 360.0;

            if (theta < ((a > b) ? a : b)) {
               return 2;
            }
         }
      }
   }

   return 0;
}

/*--------------------------------------------------------------------------*/

int astSZPrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double a, b, c, d, r2, sth1, sth2, sthe, sxy, t, x1, xp, y1, yp, z;
   const double tol = 1.0e-13;

   if (abs(prj->flag) != WCS__SZP) {
      if (astSZPset(prj)) return 1;
   }

   xp = x*prj->w[0];
   yp = y*prj->w[0];
   r2 = xp*xp + yp*yp;

   x1 = (xp - prj->w[1])/prj->w[3];
   y1 = (yp - prj->w[2])/prj->w[3];
   sxy = xp*x1 + yp*y1;

   if (r2 < 1.0e-10) {
      /* Use small angle formula. */
      z = r2/2.0;
      *theta = 90.0 - R2D*sqrt(r2/(1.0 + sxy));

   } else {
      t = x1*x1 + y1*y1;
      a = t + 1.0;
      b = sxy - t;
      c = r2 - sxy - sxy + t - 1.0;
      d = b*b - a*c;

      /* Check for a solution. */
      if (d < 0.0) {
         return 2;
      }
      d = sqrt(d);

      /* Choose solution closest to pole. */
      sth1 = (-b + d)/a;
      sth2 = (-b - d)/a;
      sthe = (sth1 > sth2) ? sth1 : sth2;
      if (sthe > 1.0) {
         if (sthe-1.0 < tol) {
            sthe = 1.0;
         } else {
            sthe = (sth1 < sth2) ? sth1 : sth2;
         }
      }

      if (sthe < -1.0) {
         if (sthe+1.0 > -tol) {
            sthe = -1.0;
         }
      }

      if (sthe > 1.0 || sthe < -1.0) {
         return 2;
      }

      *theta = astASind(sthe);

      z = 1.0 - sthe;
   }

   *phi = astATan2d(xp - x1*z, -(yp - y1*z));

   return 0;
}

/*============================================================================
*   TAN: gnomonic projection.
*
*   Given and/or returned:
*      prj->flag    TAN, or -TAN if prj->flag is given < 0.
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "TAN"
*      prj->phi0     0.0
*      prj->theta0  90.0
*      prj->astPRJfwd  Pointer to astTANfwd().
*      prj->astPRJrev  Pointer to astTANrev().
*===========================================================================*/

int astTANset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "TAN");
   prj->flag   = copysign(WCS__TAN, prj->flag);
   prj->phi0   =  0.0;
   prj->theta0 = 90.0;

   if (prj->r0 == 0.0) prj->r0 = R2D;

   prj->astPRJfwd = astTANfwd;
   prj->astPRJrev = astTANrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astTANfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double r, s;

   if (abs(prj->flag) != WCS__TAN) {
      if(astTANset(prj)) return 1;
   }

   s = astSind(theta);
   if (s == 0.0) {
      return 2;
   }

   r =  prj->r0*astCosd(theta)/s;
   *x =  r*astSind(phi);
   *y = -r*astCosd(phi);

   if (prj->flag > 0 && s < 0.0) {
      return 2;
   }

   return 0;
}

/*--------------------------------------------------------------------------*/

int astTANrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double r;

   if (abs(prj->flag) != WCS__TAN) {
      if (astTANset(prj)) return 1;
   }

   r = sqrt(x*x + y*y);
   if (r == 0.0) {
      *phi = 0.0;
   } else {
      *phi = astATan2d(x, -y);
   }
   *theta = astATan2d(prj->r0, r);

   return 0;
}

/*============================================================================
*   STG: stereographic projection.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "STG"
*      prj->flag     STG
*      prj->phi0     0.0
*      prj->theta0  90.0
*      prj->w[0]    2*r0
*      prj->w[1]    1/(2*r0)
*      prj->astPRJfwd  Pointer to astSTGfwd().
*      prj->astPRJrev  Pointer to astSTGrev().
*===========================================================================*/

int astSTGset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "STG");
   prj->flag   =  WCS__STG;
   prj->phi0   =  0.0;
   prj->theta0 = 90.0;

   if (prj->r0 == 0.0) {
      prj->r0 = R2D;
      prj->w[0] = 360.0/PI;
      prj->w[1] = PI/360.0;
   } else {
      prj->w[0] = 2.0*prj->r0;
      prj->w[1] = 1.0/prj->w[0];
   }

   prj->astPRJfwd = astSTGfwd;
   prj->astPRJrev = astSTGrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astSTGfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double r, s;

   if (prj->flag != WCS__STG) {
      if (astSTGset(prj)) return 1;
   }

   s = 1.0 + astSind(theta);
   if (s == 0.0) {
      return 2;
   }

   r =  prj->w[0]*astCosd(theta)/s;
   *x =  r*astSind(phi);
   *y = -r*astCosd(phi);

   return 0;
}

/*--------------------------------------------------------------------------*/

int astSTGrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double r;

   if (prj->flag != WCS__STG) {
      if (astSTGset(prj)) return 1;
   }

   r = sqrt(x*x + y*y);
   if (r == 0.0) {
      *phi = 0.0;
   } else {
      *phi = astATan2d(x, -y);
   }
   *theta = 90.0 - 2.0*astATand(r*prj->w[1]);

   return 0;
}

/*============================================================================
*   SIN: orthographic/synthesis projection.
*
*   Given:
*      prj->p[1:2]  Obliqueness parameters, xi and eta.
*
*   Given and/or returned:
*      prj->flag    SIN, or -SIN if prj->flag is given < 0.
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "SIN"
*      prj->phi0     0.0
*      prj->theta0  90.0
*      prj->w[0]    1/r0
*      prj->w[1]    xi**2 + eta**2
*      prj->w[2]    xi**2 + eta**2 + 1
*      prj->w[3]    xi**2 + eta**2 - 1
*      prj->astPRJfwd  Pointer to astSINfwd().
*      prj->astPRJrev  Pointer to astSINrev().
*===========================================================================*/

int astSINset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "SIN");
   prj->flag   = copysign(WCS__SIN, prj->flag);
   prj->phi0   =  0.0;
   prj->theta0 = 90.0;

   if (prj->r0 == 0.0) prj->r0 = R2D;

   prj->w[0] = 1.0/prj->r0;
   prj->w[1] = prj->p[1]*prj->p[1] + prj->p[2]*prj->p[2];
   prj->w[2] = prj->w[1] + 1.0;
   prj->w[3] = prj->w[1] - 1.0;

   prj->astPRJfwd = astSINfwd;
   prj->astPRJrev = astSINrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astSINfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double cphi, cthe, sphi, t, z;

   if (abs(prj->flag) != WCS__SIN) {
      if (astSINset(prj)) return 1;
   }

   t = (90.0 - fabs(theta))*D2R;
   if (t < 1.0e-5) {
      if (theta > 0.0) {
         z = t*t/2.0;
      } else {
         z = 2.0 - t*t/2.0;
      }
      cthe = t;
   } else {
      z =  1.0 - astSind(theta);
      cthe = astCosd(theta);
   }

   cphi = astCosd(phi);
   sphi = astSind(phi);
   *x =  prj->r0*(cthe*sphi + prj->p[1]*z);
   *y = -prj->r0*(cthe*cphi - prj->p[2]*z);

   /* Validate this solution. */
   if (prj->flag > 0) {
      if (prj->w[1] == 0.0) {
         /* Orthographic projection. */
         if (theta < 0.0) {
            return 2;
         }
      } else {
         /* "Synthesis" projection. */
         t = -astATand(prj->p[1]*sphi - prj->p[2]*cphi);
         if (theta < t) {
            return 2;
         }
      }
   }

   return 0;
}

/*--------------------------------------------------------------------------*/

int astSINrev (x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   const double tol = 1.0e-13;
   double a, b, c, d, r2, sth1, sth2, sthe, sxy, x0, x1, xp, y0, y1, yp, z;

   if (abs(prj->flag) != WCS__SIN) {
      if (astSINset(prj)) return 1;
   }

   /* Compute intermediaries. */
   x0 = x*prj->w[0];
   y0 = y*prj->w[0];
   r2 = x0*x0 + y0*y0;

   if (prj->w[1] == 0.0) {
      /* Orthographic projection. */
      if (r2 != 0.0) {
         *phi = astATan2d(x0, -y0);
      } else {
         *phi = 0.0;
      }

      if (r2 < 0.5) {
         *theta = astACosd(sqrt(r2));
      } else if (r2 <= 1.0) {
         *theta = astASind(sqrt(1.0 - r2));
      } else {
         return 2;
      }

   } else {
      /* "Synthesis" projection. */
      x1 = prj->p[1];
      y1 = prj->p[2];
      sxy = x0*x1 + y0*y1;

      if (r2 < 1.0e-10) {
         /* Use small angle formula. */
         z = r2/2.0;
         *theta = 90.0 - R2D*sqrt(r2/(1.0 + sxy));

      } else {
         a = prj->w[2];
         b = sxy - prj->w[1];
         c = r2 - sxy - sxy + prj->w[3];
         d = b*b - a*c;

         /* Check for a solution. */
         if (d < 0.0) {
            return 2;
         }
         d = sqrt(d);

         /* Choose solution closest to pole. */
         sth1 = (-b + d)/a;
         sth2 = (-b - d)/a;
         sthe = (sth1 > sth2) ? sth1 : sth2;
         if (sthe > 1.0) {
            if (sthe-1.0 < tol) {
               sthe = 1.0;
            } else {
               sthe = (sth1 < sth2) ? sth1 : sth2;
            }
         }

         if (sthe < -1.0) {
            if (sthe+1.0 > -tol) {
               sthe = -1.0;
            }
         }

         if (sthe > 1.0 || sthe < -1.0) {
            return 2;
         }

         *theta = astASind(sthe);
         z = 1.0 - sthe;
      }

      xp = -y0 + prj->p[2]*z;
      yp =  x0 - prj->p[1]*z;
      if (xp == 0.0 && yp == 0.0) {
         *phi = 0.0;
      } else {
         *phi = astATan2d(yp,xp);
      }
   }

   return 0;
}

/*============================================================================
*   ARC: zenithal/azimuthal equidistant projection.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "ARC"
*      prj->flag     ARC
*      prj->phi0     0.0
*      prj->theta0  90.0
*      prj->w[0]    r0*(pi/180)
*      prj->w[1]    (180/pi)/r0
*      prj->astPRJfwd  Pointer to astARCfwd().
*      prj->astPRJrev  Pointer to astARCrev().
*===========================================================================*/

int astARCset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "ARC");
   prj->flag   =  WCS__ARC;
   prj->phi0   =  0.0;
   prj->theta0 = 90.0;

   if (prj->r0 == 0.0) {
      prj->r0 = R2D;
      prj->w[0] = 1.0;
      prj->w[1] = 1.0;
   } else {
      prj->w[0] = prj->r0*D2R;
      prj->w[1] = 1.0/prj->w[0];
   }

   prj->astPRJfwd = astARCfwd;
   prj->astPRJrev = astARCrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astARCfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double r;

   if (prj->flag != WCS__ARC) {
      if (astARCset(prj)) return 1;
   }

   r =  prj->w[0]*(90.0 - theta);
   *x =  r*astSind(phi);
   *y = -r*astCosd(phi);

   return 0;
}

/*--------------------------------------------------------------------------*/

int astARCrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double r;

   if (prj->flag != WCS__ARC) {
      if (astARCset(prj)) return 1;
   }

   r = sqrt(x*x + y*y);
   if (r == 0.0) {
      *phi = 0.0;
   } else {
      *phi = astATan2d(x, -y);
   }
   *theta = 90.0 - r*prj->w[1];

   return 0;
}

/*============================================================================
*   ZPN: zenithal/azimuthal polynomial projection.
*
*   Given:
*      prj->p[0:WCSLIB_MXPAR-1]  Polynomial coefficients.
*
*   Given and/or returned:
*      prj->flag    ZPN, or -ZPN if prj->flag is given < 0.
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "ZPN"
*      prj->phi0     0.0
*      prj->theta0  90.0
*      prj->n       Degree of the polynomial, N.
*      prj->w[0]    Co-latitude of the first point of inflection (N > 2).
*      prj->w[1]    Radius of the first point of inflection (N > 2).
*      prj->astPRJfwd  Pointer to astZPNfwd().
*      prj->astPRJrev  Pointer to astZPNrev().
*===========================================================================*/

int astZPNset(prj)

struct AstPrjPrm *prj;

{
   int   i, j, k;
   double d, d1, d2, r, zd, zd1, zd2;
   const double tol = 1.0e-13;

   strcpy(prj->code, "ZPN");
   prj->flag   = copysign(WCS__ZPN, prj->flag);
   prj->phi0   =  0.0;
   prj->theta0 = 90.0;

   if (prj->r0 == 0.0) prj->r0 = R2D;

   /* Find the highest non-zero coefficient. */
   for (k = WCSLIB_MXPAR-1; k >= 0 && prj->p[k] == 0.0; k--);
   if (k < 0) return 1;

   prj->n = k;

   if (k >= 3) {
      /* Find the point of inflection closest to the pole. */
      zd1 = 0.0;
      d1  = prj->p[1];
      if (d1 <= 0.0) {
         return 1;
      }

      /* Find the point where the derivative first goes negative. */
      for (i = 0; i < 180; i++) {
         zd2 = i*D2R;
         d2  = 0.0;
         for (j = k; j > 0; j--) {
            d2 = d2*zd2 + j*prj->p[j];
         }

         if (d2 <= 0.0) break;
         zd1 = zd2;
         d1  = d2;
      }

      if (i == 180) {
         /* No negative derivative -> no point of inflection. */
         zd = PI;
      } else {
         /* Find where the derivative is zero. */
         for (i = 1; i <= 10; i++) {
            zd = zd1 - d1*(zd2-zd1)/(d2-d1);

            d = 0.0;
            for (j = k; j > 0; j--) {
               d = d*zd + j*prj->p[j];
            }

            if (fabs(d) < tol) break;

            if (d < 0.0) {
               zd2 = zd;
               d2  = d;
            } else {
               zd1 = zd;
               d1  = d;
            }
         }
      }

      r = 0.0;
      for (j = k; j >= 0; j--) {
         r = r*zd + prj->p[j];
      }
      prj->w[0] = zd;
      prj->w[1] = r;
   }

   prj->astPRJfwd = astZPNfwd;
   prj->astPRJrev = astZPNrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astZPNfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   int   j;
   double r, s;

   if (abs(prj->flag) != WCS__ZPN) {
      if (astZPNset(prj)) return 1;
   }

   s = (90.0 - theta)*D2R;

   r = 0.0;
   for (j = prj->n; j >= 0; j--) {
      r = r*s + prj->p[j];
   }
   r = prj->r0*r;

   *x =  r*astSind(phi);
   *y = -r*astCosd(phi);

   if (prj->flag > 0 && s > prj->w[0] && prj->n > 2 ) {
      return 2;
   }

   return 0;
}

/*--------------------------------------------------------------------------*/

int astZPNrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   int   i, j, k;
   double a, b, c, d, lambda, r, r1, r2, rt, zd, zd1, zd2;
   const double tol = 1.0e-13;

   if (abs(prj->flag) != WCS__ZPN) {
      if (astZPNset(prj)) return 1;
   }

   k = prj->n;

   r = sqrt(x*x + y*y)/prj->r0;

   if (k < 1) {
      /* Constant - no solution. */
      return 1;
   } else if (k == 1) {
      /* Linear. */
      zd = (r - prj->p[0])/prj->p[1];
   } else if (k == 2) {
      /* Quadratic. */
      a = prj->p[2];
      b = prj->p[1];
      c = prj->p[0] - r;

      d = b*b - 4.0*a*c;
      if (d < 0.0) {
         return 2;
      }
      d = sqrt(d);

      /* Choose solution closest to pole. */
      zd1 = (-b + d)/(2.0*a);
      zd2 = (-b - d)/(2.0*a);
      zd  = (zd1<zd2) ? zd1 : zd2;
      if (zd < -tol) zd = (zd1>zd2) ? zd1 : zd2;
      if (zd < 0.0) {
         if (zd < -tol) {
            return 2;
         }
         zd = 0.0;
      } else if (zd > PI) {
         if (zd > PI+tol) {
            return 2;
         }
         zd = PI;
      }
   } else {
      /* Higher order - solve iteratively. */
      zd1 = 0.0;
      r1  = prj->p[0];
      zd2 = prj->w[0];
      r2  = prj->w[1];

      if (r < r1) {
         if (r < r1-tol) {
            return 2;
         }
         zd = zd1;
      } else if (r > r2) {
         if (r > r2+tol) {
            return 2;
         }
         zd = zd2;
      } else {
         /* Disect the interval. */
         for (j = 0; j < 100; j++) {
            lambda = (r2 - r)/(r2 - r1);
            if (lambda < 0.1) {
               lambda = 0.1;
            } else if (lambda > 0.9) {
               lambda = 0.9;
            }

            zd = zd2 - lambda*(zd2 - zd1);

            rt = 0.0;
            for (i = k; i >= 0; i--) {
                rt = (rt * zd) + prj->p[i];
            }

            if (rt < r) {
                if (r-rt < tol) break;
                r1 = rt;
                zd1 = zd;
            } else {
                if (rt-r < tol) break;
                r2 = rt;
                zd2 = zd;
            }

            if (fabs(zd2-zd1) < tol) break;
         }
      }
   }

   if (r == 0.0) {
      *phi = 0.0;
   } else {
      *phi = astATan2d(x, -y);
   }
   *theta = 90.0 - zd*R2D;

   return 0;
}

/*============================================================================
*   ZEA: zenithal/azimuthal equal area projection.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "ZEA"
*      prj->flag     ZEA
*      prj->phi0     0.0
*      prj->theta0  90.0
*      prj->w[0]    2*r0
*      prj->w[1]    1/(2*r0)
*      prj->astPRJfwd  Pointer to astZEAfwd().
*      prj->astPRJrev  Pointer to astZEArev().
*===========================================================================*/

int astZEAset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "ZEA");
   prj->flag   =  WCS__ZEA;
   prj->phi0   =  0.0;
   prj->theta0 = 90.0;

   if (prj->r0 == 0.0) {
      prj->r0 = R2D;
      prj->w[0] = 360.0/PI;
      prj->w[1] = PI/360.0;
   } else {
      prj->w[0] = 2.0*prj->r0;
      prj->w[1] = 1.0/prj->w[0];
   }

   prj->astPRJfwd = astZEAfwd;
   prj->astPRJrev = astZEArev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astZEAfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double r;

   if (prj->flag != WCS__ZEA) {
      if (astZEAset(prj)) return 1;
   }

   r =  prj->w[0]*astSind((90.0 - theta)/2.0);
   *x =  r*astSind(phi);
   *y = -r*astCosd(phi);

   return 0;
}

/*--------------------------------------------------------------------------*/

int astZEArev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double r, s;
   const double tol = 1.0e-12;

   if (prj->flag != WCS__ZEA) {
      if (astZEAset(prj)) return 1;
   }

   r = sqrt(x*x + y*y);
   if (r == 0.0) {
      *phi = 0.0;
   } else {
      *phi = astATan2d(x, -y);
   }

   s = r*prj->w[1];
   if (fabs(s) > 1.0) {
      if (fabs(r - prj->w[0]) < tol) {
         *theta = -90.0;
      } else {
         return 2;
      }
   } else {
      *theta = 90.0 - 2.0*astASind(s);
   }

   return 0;
}

/*============================================================================
*   AIR: Airy's projection.
*
*   Given:
*      prj->p[1]    Latitude theta_b within which the error is minimized, in
*                   degrees.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "AIR"
*      prj->flag     AIR
*      prj->phi0     0.0
*      prj->theta0  90.0
*      prj->w[0]    2*r0
*      prj->w[1]    ln(cos(xi_b))/tan(xi_b)**2, where xi_b = (90-theta_b)/2
*      prj->w[2]    1/2 - prj->w[1]
*      prj->w[3]    2*r0*prj->w[2]
*      prj->w[4]    tol, cutoff for using small angle approximation, in
*                   radians.
*      prj->w[5]    prj->w[2]*tol
*      prj->w[6]    (180/pi)/prj->w[2]
*      prj->astPRJfwd  Pointer to astAIRfwd().
*      prj->astPRJrev  Pointer to astAIRrev().
*===========================================================================*/

int astAIRset(prj)

struct AstPrjPrm *prj;

{
   const double tol = 1.0e-4;
   double cxi;

   strcpy(prj->code, "AIR");
   prj->flag   =  WCS__AIR;
   prj->phi0   =  0.0;
   prj->theta0 = 90.0;

   if (prj->r0 == 0.0) prj->r0 = R2D;

   prj->w[0] = 2.0*prj->r0;
   if (prj->p[1] == 90.0) {
      prj->w[1] = -0.5;
      prj->w[2] =  1.0;
   } else if (prj->p[1] > -90.0) {
      cxi = astCosd((90.0 - prj->p[1])/2.0);
      prj->w[1] = log(cxi)*(cxi*cxi)/(1.0-cxi*cxi);
      prj->w[2] = 0.5 - prj->w[1];
   } else {
      return 1;
   }

   prj->w[3] = prj->w[0] * prj->w[2];
   prj->w[4] = tol;
   prj->w[5] = prj->w[2]*tol;
   prj->w[6] = R2D/prj->w[2];

   prj->astPRJfwd = astAIRfwd;
   prj->astPRJrev = astAIRrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astAIRfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double cxi, r, txi, xi;

   if (prj->flag != WCS__AIR) {
      if (astAIRset(prj)) return 1;
   }

   if (theta == 90.0) {
      r = 0.0;
   } else if (theta > -90.0) {
      xi = D2R*(90.0 - theta)/2.0;
      if (xi < prj->w[4]) {
         r = xi*prj->w[3];
      } else {
         cxi = astCosd((90.0 - theta)/2.0);
         txi = sqrt(1.0-cxi*cxi)/cxi;
         r = -prj->w[0]*(log(cxi)/txi + prj->w[1]*txi);
      }
   } else {
      return 2;
   }

   *x =  r*astSind(phi);
   *y = -r*astCosd(phi);

   return 0;
}

/*--------------------------------------------------------------------------*/

int astAIRrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   int   j;
   double cxi, lambda, r, r1, r2, rt, txi, x1, x2, xi;
   const double tol = 1.0e-12;

   if (prj->flag != WCS__AIR) {
      if (astAIRset(prj)) return 1;
   }

   r = sqrt(x*x + y*y)/prj->w[0];

   if (r == 0.0) {
      xi = 0.0;
   } else if (r < prj->w[5]) {
      xi = r*prj->w[6];
   } else {
      /* Find a solution interval. */
      x1 = 1.0;
      r1 = 0.0;
      for (j = 0; j < 30; j++) {
         x2 = x1/2.0;
         txi = sqrt(1.0-x2*x2)/x2;
         r2 = -(log(x2)/txi + prj->w[1]*txi);

         if (r2 >= r) break;
         x1 = x2;
         r1 = r2;
      }
      if (j == 30) return 2;

      for (j = 0; j < 100; j++) {
         /* Weighted division of the interval. */
         lambda = (r2-r)/(r2-r1);
         if (lambda < 0.1) {
            lambda = 0.1;
         } else if (lambda > 0.9) {
            lambda = 0.9;
         }
         cxi = x2 - lambda*(x2-x1);

         txi = sqrt(1.0-cxi*cxi)/cxi;
         rt = -(log(cxi)/txi + prj->w[1]*txi);

         if (rt < r) {
             if (r-rt < tol) break;
             r1 = rt;
             x1 = cxi;
         } else {
             if (rt-r < tol) break;
             r2 = rt;
             x2 = cxi;
         }
      }
      if (j == 100) return 2;

      xi = astACosd(cxi);
   }

   if (r == 0.0) {
      *phi = 0.0;
   } else {
      *phi = astATan2d(x, -y);
   }
   *theta = 90.0 - 2.0*xi;

   return 0;
}

/*============================================================================
*   CYP: cylindrical perspective projection.
*
*   Given:
*      prj->p[1]    Distance of point of projection from the centre of the
*                   generating sphere, mu, in units of r0.
*      prj->p[2]    Radius of the cylinder of projection, lambda, in units of
*                   r0.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "CYP"
*      prj->flag    CYP
*      prj->phi0    0.0
*      prj->theta0  0.0
*      prj->w[0]    r0*lambda*(pi/180)
*      prj->w[1]    (180/pi)/(r0*lambda)
*      prj->w[2]    r0*(mu + lambda)
*      prj->w[3]    1/(r0*(mu + lambda))
*      prj->astPRJfwd  Pointer to astCYPfwd().
*      prj->astPRJrev  Pointer to astCYPrev().
*===========================================================================*/

int astCYPset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "CYP");
   prj->flag   = WCS__CYP;
   prj->phi0   = 0.0;
   prj->theta0 = 0.0;

   if (prj->r0 == 0.0) {
      prj->r0 = R2D;

      prj->w[0] = prj->p[2];
      if (prj->w[0] == 0.0) {
         return 1;
      }

      prj->w[1] = 1.0/prj->w[0];

      prj->w[2] = R2D*(prj->p[1] + prj->p[2]);
      if (prj->w[2] == 0.0) {
         return 1;
      }

      prj->w[3] = 1.0/prj->w[2];
   } else {
      prj->w[0] = prj->r0*prj->p[2]*D2R;
      if (prj->w[0] == 0.0) {
         return 1;
      }

      prj->w[1] = 1.0/prj->w[0];

      prj->w[2] = prj->r0*(prj->p[1] + prj->p[2]);
      if (prj->w[2] == 0.0) {
         return 1;
      }

      prj->w[3] = 1.0/prj->w[2];
   }

   prj->astPRJfwd = astCYPfwd;
   prj->astPRJrev = astCYPrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astCYPfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double s;

   if (prj->flag != WCS__CYP) {
      if (astCYPset(prj)) return 1;
   }

   s = prj->p[1] + astCosd(theta);
   if (s == 0.0) {
         return 2;
      }

   *x = prj->w[0]*phi;
   *y = prj->w[2]*astSind(theta)/s;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astCYPrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double eta;
   double a;
   const double tol = 1.0e-13;

   if (prj->flag != WCS__CYP) {
      if (astCYPset(prj)) return 1;
   }

   *phi   = x*prj->w[1];
   eta    = y*prj->w[3];

   a = eta*prj->p[1]/sqrt(eta*eta+1.0);
   if( fabs( a ) < 1.0 ) {
      *theta = astATan2d(eta,1.0) + astASind( a );

   } else if( fabs( a ) < 1.0 + tol ) {
      if( a > 0.0 ){
         *theta = astATan2d(eta,1.0) + 90.0;
      } else {
         *theta = astATan2d(eta,1.0) - 90.0;
      }

   } else {
      return 2;
   }

   return 0;
}

/*============================================================================
*   CEA: cylindrical equal area projection.
*
*   Given:
*      prj->p[1]    Square of the cosine of the latitude at which the
*                   projection is conformal, lambda.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "CEA"
*      prj->flag    CEA
*      prj->phi0    0.0
*      prj->theta0  0.0
*      prj->w[0]    r0*(pi/180)
*      prj->w[1]    (180/pi)/r0
*      prj->w[2]    r0/lambda
*      prj->w[3]    lambda/r0
*      prj->astPRJfwd  Pointer to astCEAfwd().
*      prj->astPRJrev  Pointer to astCEArev().
*===========================================================================*/

int astCEAset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "CEA");
   prj->flag   = WCS__CEA;
   prj->phi0   = 0.0;
   prj->theta0 = 0.0;

   if (prj->r0 == 0.0) {
      prj->r0 = R2D;
      prj->w[0] = 1.0;
      prj->w[1] = 1.0;
      if (prj->p[1] <= 0.0 || prj->p[1] > 1.0) {
         return 1;
      }
      prj->w[2] = prj->r0/prj->p[1];
      prj->w[3] = prj->p[1]/prj->r0;
   } else {
      prj->w[0] = prj->r0*D2R;
      prj->w[1] = R2D/prj->r0;
      if (prj->p[1] <= 0.0 || prj->p[1] > 1.0) {
         return 1;
      }
      prj->w[2] = prj->r0/prj->p[1];
      prj->w[3] = prj->p[1]/prj->r0;
   }

   prj->astPRJfwd = astCEAfwd;
   prj->astPRJrev = astCEArev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astCEAfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   if (prj->flag != WCS__CEA) {
      if (astCEAset(prj)) return 1;
   }

   *x = prj->w[0]*phi;
   *y = prj->w[2]*astSind(theta);

   return 0;
}

/*--------------------------------------------------------------------------*/

int astCEArev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double s;
   const double tol = 1.0e-13;

   if (prj->flag != WCS__CEA) {
      if (astCEAset(prj)) return 1;
   }

   s = y*prj->w[3];
   if (fabs(s) > 1.0) {
      if (fabs(s) > 1.0+tol) {
         return 2;
      }
      s = copysign(1.0,s);
   }

   *phi   = x*prj->w[1];
   *theta = astASind(s);

   return 0;
}

/*============================================================================
*   CAR: Cartesian projection.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "CAR"
*      prj->flag    CAR
*      prj->phi0    0.0
*      prj->theta0  0.0
*      prj->w[0]    r0*(pi/180)
*      prj->w[1]    (180/pi)/r0
*      prj->astPRJfwd  Pointer to astCARfwd().
*      prj->astPRJrev  Pointer to astCARrev().
*===========================================================================*/

int astCARset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "CAR");
   prj->flag   = WCS__CAR;
   prj->phi0   = 0.0;
   prj->theta0 = 0.0;

   if (prj->r0 == 0.0) {
      prj->r0 = R2D;
      prj->w[0] = 1.0;
      prj->w[1] = 1.0;
   } else {
      prj->w[0] = prj->r0*D2R;
      prj->w[1] = 1.0/prj->w[0];
   }

   prj->astPRJfwd = astCARfwd;
   prj->astPRJrev = astCARrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astCARfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   if (prj->flag != WCS__CAR) {
      if (astCARset(prj)) return 1;
   }

   *x = prj->w[0]*phi;
   *y = prj->w[0]*theta;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astCARrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   if (prj->flag != WCS__CAR) {
      if (astCARset(prj)) return 1;
   }

   *phi   = prj->w[1]*x;
   *theta = prj->w[1]*y;

   return 0;
}

/*============================================================================
*   MER: Mercator's projection.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "MER"
*      prj->flag    MER
*      prj->phi0    0.0
*      prj->theta0  0.0
*      prj->w[0]    r0*(pi/180)
*      prj->w[1]    (180/pi)/r0
*      prj->astPRJfwd  Pointer to astMERfwd().
*      prj->astPRJrev  Pointer to astMERrev().
*===========================================================================*/

int astMERset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "MER");
   prj->flag   = WCS__MER;
   prj->phi0   = 0.0;
   prj->theta0 = 0.0;

   if (prj->r0 == 0.0) {
      prj->r0 = R2D;
      prj->w[0] = 1.0;
      prj->w[1] = 1.0;
   } else {
      prj->w[0] = prj->r0*D2R;
      prj->w[1] = 1.0/prj->w[0];
   }

   prj->astPRJfwd = astMERfwd;
   prj->astPRJrev = astMERrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astMERfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   if (prj->flag != WCS__MER) {
      if (astMERset(prj)) return 1;
   }

   if (theta <= -90.0 || theta >= 90.0) {
      return 2;
   }

   *x = prj->w[0]*phi;
   *y = prj->r0*log(astTand((90.0+theta)/2.0));

   return 0;
}

/*--------------------------------------------------------------------------*/

int astMERrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   if (prj->flag != WCS__MER) {
      if (astMERset(prj)) return 1;
   }

   *phi   = x*prj->w[1];
   *theta = 2.0*astATand(exp(y/prj->r0)) - 90.0;

   return 0;
}

/*============================================================================
*   SFL: Sanson-Flamsteed ("global sinusoid") projection.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "SFL"
*      prj->flag    SFL
*      prj->phi0    0.0
*      prj->theta0  0.0
*      prj->w[0]    r0*(pi/180)
*      prj->w[1]    (180/pi)/r0
*      prj->astPRJfwd  Pointer to astSFLfwd().
*      prj->astPRJrev  Pointer to astSFLrev().
*===========================================================================*/

int astSFLset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "SFL");
   prj->flag   = WCS__SFL;
   prj->phi0   = 0.0;
   prj->theta0 = 0.0;

   if (prj->r0 == 0.0) {
      prj->r0 = R2D;
      prj->w[0] = 1.0;
      prj->w[1] = 1.0;
   } else {
      prj->w[0] = prj->r0*D2R;
      prj->w[1] = 1.0/prj->w[0];
   }

   prj->astPRJfwd = astSFLfwd;
   prj->astPRJrev = astSFLrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astSFLfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   if (prj->flag != WCS__SFL) {
      if (astSFLset(prj)) return 1;
   }

   *x = prj->w[0]*phi*astCosd(theta);
   *y = prj->w[0]*theta;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astSFLrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double w;

   if (prj->flag != WCS__SFL) {
      if (astSFLset(prj)) return 1;
   }

   w = cos(y/prj->r0);
   if (w == 0.0) {
      *phi = 0.0;
   } else {
      *phi = x*prj->w[1]/cos(y/prj->r0);
   }
   *theta = y*prj->w[1];

   return 0;
}

/*============================================================================
*   PAR: parabolic projection.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "PAR"
*      prj->flag    PAR
*      prj->phi0    0.0
*      prj->theta0  0.0
*      prj->w[0]    r0*(pi/180)
*      prj->w[1]    (180/pi)/r0
*      prj->w[2]    pi*r0
*      prj->w[3]    1/(pi*r0)
*      prj->astPRJfwd  Pointer to astPARfwd().
*      prj->astPRJrev  Pointer to astPARrev().
*===========================================================================*/

int astPARset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "PAR");
   prj->flag   = WCS__PAR;
   prj->phi0   = 0.0;
   prj->theta0 = 0.0;

   if (prj->r0 == 0.0) {
      prj->r0 = R2D;
      prj->w[0] = 1.0;
      prj->w[1] = 1.0;
      prj->w[2] = 180.0;
      prj->w[3] = 1.0/prj->w[2];
   } else {
      prj->w[0] = prj->r0*D2R;
      prj->w[1] = 1.0/prj->w[0];
      prj->w[2] = PI*prj->r0;
      prj->w[3] = 1.0/prj->w[2];
   }

   prj->astPRJfwd = astPARfwd;
   prj->astPRJrev = astPARrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astPARfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double s;

   if (prj->flag != WCS__PAR) {
      if (astPARset(prj)) return 1;
   }

   s = astSind(theta/3.0);
   *x = prj->w[0]*phi*(1.0 - 4.0*s*s);
   *y = prj->w[2]*s;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astPARrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double s, t;

   if (prj->flag != WCS__PAR) {
      if (astPARset(prj)) return 1;
   }

   s = y*prj->w[3];
   if (s > 1.0 || s < -1.0) {
      return 2;
   }

   t = 1.0 - 4.0*s*s;
   if (t == 0.0) {
      if (x == 0.0) {
         *phi = 0.0;
      } else {
         return 2;
      }
   } else {
      *phi = prj->w[1]*x/t;
   }

   *theta = 3.0*astASind(s);

   return 0;
}

/*============================================================================
*   MOL: Mollweide's projection.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "MOL"
*      prj->flag    MOL
*      prj->phi0    0.0
*      prj->theta0  0.0
*      prj->w[0]    sqrt(2)*r0
*      prj->w[1]    sqrt(2)*r0/90
*      prj->w[2]    1/(sqrt(2)*r0)
*      prj->w[3]    90/r0
*      prj->astPRJfwd  Pointer to astMOLfwd().
*      prj->astPRJrev  Pointer to astMOLrev().
*===========================================================================*/

int astMOLset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "MOL");
   prj->flag   = WCS__MOL;
   prj->phi0   = 0.0;
   prj->theta0 = 0.0;

   if (prj->r0 == 0.0) prj->r0 = R2D;

   prj->w[0] = SQRT2*prj->r0;
   prj->w[1] = prj->w[0]/90.0;
   prj->w[2] = 1.0/prj->w[0];
   prj->w[3] = 90.0/prj->r0;
   prj->w[4] = 2.0/PI;

   prj->astPRJfwd = astMOLfwd;
   prj->astPRJrev = astMOLrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astMOLfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   int   j;
   double gamma, resid, u, v, v0, v1;
   const double tol = 1.0e-13;

   if (prj->flag != WCS__MOL) {
      if (astMOLset(prj)) return 1;
   }

   if (fabs(theta) == 90.0) {
      *x = 0.0;
      *y = copysign(prj->w[0],theta);
   } else if (theta == 0.0) {
      *x = prj->w[1]*phi;
      *y = 0.0;
   } else {
      u  = PI*astSind(theta);
      v0 = -PI;
      v1 =  PI;
      v  = u;
      for (j = 0; j < 100; j++) {
         resid = (v - u) + sin(v);
         if (resid < 0.0) {
            if (resid > -tol) break;
            v0 = v;
         } else {
            if (resid < tol) break;
            v1 = v;
         }
         v = (v0 + v1)/2.0;
      }

      gamma = v/2.0;
      *x = prj->w[1]*phi*cos(gamma);
      *y = prj->w[0]*sin(gamma);
   }

   return 0;
}

/*--------------------------------------------------------------------------*/

int astMOLrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double s, y0, z;
   const double tol = 1.0e-12;

   if (prj->flag != WCS__MOL) {
      if (astMOLset(prj)) return 1;
   }

   y0 = y/prj->r0;
   s  = 2.0 - y0*y0;
   if (s <= tol) {
      if (s < -tol) {
         return 2;
      }
      s = 0.0;

      if (fabs(x) > tol) {
         return 2;
      }
      *phi = 0.0;
   } else {
      s = sqrt(s);
      *phi = prj->w[3]*x/s;
   }

   z = y*prj->w[2];
   if (fabs(z) > 1.0) {
      if (fabs(z) > 1.0+tol) {
         return 2;
      }
      z = copysign(1.0,z) + y0*s/PI;
   } else {
      z = asin(z)*prj->w[4] + y0*s/PI;
   }

   if (fabs(z) > 1.0) {
      if (fabs(z) > 1.0+tol) {
         return 2;
      }
      z = copysign(1.0,z);
   }

   *theta = astASind(z);

   return 0;
}

/*============================================================================
*   AIT: Hammer-Aitoff projection.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "AIT"
*      prj->flag    AIT
*      prj->phi0    0.0
*      prj->theta0  0.0
*      prj->w[0]    2*r0**2
*      prj->w[1]    1/(2*r0)**2
*      prj->w[2]    1/(4*r0)**2
*      prj->w[3]    1/(2*r0)
*      prj->astPRJfwd  Pointer to astAITfwd().
*      prj->astPRJrev  Pointer to astAITrev().
*===========================================================================*/

int astAITset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "AIT");
   prj->flag   = WCS__AIT;
   prj->phi0   = 0.0;
   prj->theta0 = 0.0;

   if (prj->r0 == 0.0) prj->r0 = R2D;

   prj->w[0] = 2.0*prj->r0*prj->r0;
   prj->w[1] = 1.0/(2.0*prj->w[0]);
   prj->w[2] = prj->w[1]/4.0;
   prj->w[3] = 1.0/(2.0*prj->r0);

   prj->astPRJfwd = astAITfwd;
   prj->astPRJrev = astAITrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astAITfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double cthe, w;

   if (prj->flag != WCS__AIT) {
      if (astAITset(prj)) return 1;
   }

   cthe = astCosd(theta);
   w = sqrt(prj->w[0]/(1.0 + cthe*astCosd(phi/2.0)));
   *x = 2.0*w*cthe*astSind(phi/2.0);
   *y = w*astSind(theta);

   return 0;
}

/*--------------------------------------------------------------------------*/

int astAITrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double s, u, xp, yp, z;
   const double tol = 1.0e-13;

   if (prj->flag != WCS__AIT) {
      if (astAITset(prj)) return 1;
   }

   u = 1.0 - x*x*prj->w[2] - y*y*prj->w[1];
   if (u < 0.0) {
      if (u < -tol) {
         return 2;
      }

      u = 0.0;
   }

   z = sqrt(u);
   s = z*y/prj->r0;
   if (fabs(s) > 1.0) {
      if (fabs(s) > 1.0+tol) {
         return 2;
      }
      s = copysign(1.0,s);
   }

   xp = 2.0*z*z - 1.0;
   yp = z*x*prj->w[3];
   if (xp == 0.0 && yp == 0.0) {
      *phi = 0.0;
   } else {
      *phi = 2.0*astATan2d(yp, xp);
   }
   *theta = astASind(s);

   return 0;
}

/*============================================================================
*   COP: conic perspective projection.
*
*   Given:
*      prj->p[1]    sigma = (theta2+theta1)/2
*      prj->p[2]    delta = (theta2-theta1)/2, where theta1 and theta2 are the
*                   latitudes of the standard parallels, in degrees.
*
*   Given and/or returned:
*      prj->flag    COP, or -COP if prj->flag is given < 0.
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "COP"
*      prj->phi0     0.0
*      prj->theta0  sigma
*      prj->w[0]    C  = sin(sigma)
*      prj->w[1]    1/C
*      prj->w[2]    Y0 = r0*cos(delta)*cot(sigma)
*      prj->w[3]    r0*cos(delta)
*      prj->w[4]    1/(r0*cos(delta)
*      prj->w[5]    cot(sigma)
*      prj->astPRJfwd  Pointer to astCOPfwd().
*      prj->astPRJrev  Pointer to astCOPrev().
*===========================================================================*/

int astCOPset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "COP");
   prj->flag   = copysign(WCS__COP, prj->flag);
   prj->phi0   = 0.0;
   prj->theta0 = prj->p[1];

   if (prj->r0 == 0.0) prj->r0 = R2D;

   prj->w[0] = astSind(prj->p[1]);
   if (prj->w[0] == 0.0) {
      return 1;
   }

   prj->w[1] = 1.0/prj->w[0];

   prj->w[3] = prj->r0*astCosd(prj->p[2]);
   if (prj->w[3] == 0.0) {
      return 1;
   }

   prj->w[4] = 1.0/prj->w[3];
   prj->w[5] = 1.0/astTand(prj->p[1]);

   prj->w[2] = prj->w[3]*prj->w[5];

   prj->astPRJfwd = astCOPfwd;
   prj->astPRJrev = astCOPrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astCOPfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double a, r, s, t;

   if (abs(prj->flag) != WCS__COP) {
      if (astCOPset(prj)) return 1;
   }

   t = theta - prj->p[1];
   s = astCosd(t);
   if (s == 0.0) {
      return 2;
   }

   a = prj->w[0]*phi;
   r = prj->w[2] - prj->w[3]*astSind(t)/s;

   *x =             r*astSind(a);
   *y = prj->w[2] - r*astCosd(a);

   if (prj->flag > 0 && r*prj->w[0] < 0.0) {
      return 2;
   }

   return 0;
}

/*--------------------------------------------------------------------------*/

int astCOPrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double a, dy, r;

   if (abs(prj->flag) != WCS__COP) {
      if (astCOPset(prj)) return 1;
   }

   dy = prj->w[2] - y;
   r  = sqrt(x*x + dy*dy);
   if (prj->p[1] < 0.0) r = -r;

   if (r == 0.0) {
      a = 0.0;
   } else {
      a = astATan2d(x/r, dy/r);
   }

   *phi   = a*prj->w[1];
   *theta = prj->p[1] + astATand(prj->w[5] - r*prj->w[4]);

   return 0;
}

/*============================================================================
*   COE: conic equal area projection.
*
*   Given:
*      prj->p[1]    sigma = (theta2+theta1)/2
*      prj->p[2]    delta = (theta2-theta1)/2, where theta1 and theta2 are the
*                   latitudes of the standard parallels, in degrees.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "COE"
*      prj->flag    COE
*      prj->phi0    0.0
*      prj->theta0  sigma
*      prj->w[0]    C = (sin(theta1) + sin(theta2))/2
*      prj->w[1]    1/C
*      prj->w[2]    Y0 = chi*sqrt(psi - 2C*astSind(sigma))
*      prj->w[3]    chi = r0/C
*      prj->w[4]    psi = 1 + sin(theta1)*sin(theta2)
*      prj->w[5]    2C
*      prj->w[6]    (1 + sin(theta1)*sin(theta2))*(r0/C)**2
*      prj->w[7]    C/(2*r0**2)
*      prj->w[8]    chi*sqrt(psi + 2C)
*      prj->astPRJfwd  Pointer to astCOEfwd().
*      prj->astPRJrev  Pointer to astCOErev().
*===========================================================================*/

int astCOEset(prj)

struct AstPrjPrm *prj;

{
   double theta1, theta2;

   strcpy(prj->code, "COE");
   prj->flag   = WCS__COE;
   prj->phi0   = 0.0;
   prj->theta0 = prj->p[1];

   if (prj->r0 == 0.0) prj->r0 = R2D;

   theta1 = prj->p[1] - prj->p[2];
   theta2 = prj->p[1] + prj->p[2];

   prj->w[0] = (astSind(theta1) + astSind(theta2))/2.0;
   if (prj->w[0] == 0.0) {
      return 1;
   }

   prj->w[1] = 1.0/prj->w[0];

   prj->w[3] = prj->r0/prj->w[0];
   prj->w[4] = 1.0 + astSind(theta1)*astSind(theta2);
   prj->w[5] = 2.0*prj->w[0];
   prj->w[6] = prj->w[3]*prj->w[3]*prj->w[4];
   prj->w[7] = 1.0/(2.0*prj->r0*prj->w[3]);
   prj->w[8] = prj->w[3]*sqrt(prj->w[4] + prj->w[5]);

   prj->w[2] = prj->w[3]*sqrt(prj->w[4] - prj->w[5]*astSind(prj->p[1]));

   prj->astPRJfwd = astCOEfwd;
   prj->astPRJrev = astCOErev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astCOEfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double a, r;

   if (prj->flag != WCS__COE) {
      if (astCOEset(prj)) return 1;
   }

   a = phi*prj->w[0];
   if (theta == -90.0) {
      r = prj->w[8];
   } else {
      r = prj->w[3]*sqrt(prj->w[4] - prj->w[5]*astSind(theta));
   }

   *x =             r*astSind(a);
   *y = prj->w[2] - r*astCosd(a);

   return 0;
}

/*--------------------------------------------------------------------------*/

int astCOErev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double a, dy, r, w;
   const double tol = 1.0e-12;

   if (prj->flag != WCS__COE) {
      if (astCOEset(prj)) return 1;
   }

   dy = prj->w[2] - y;
   r  = sqrt(x*x + dy*dy);
   if (prj->p[1] < 0.0) r = -r;

   if (r == 0.0) {
      a = 0.0;
   } else {
      a = astATan2d(x/r, dy/r);
   }

   *phi = a*prj->w[1];
   if (fabs(r - prj->w[8]) < tol) {
      *theta = -90.0;
   } else {
      w = (prj->w[6] - r*r)*prj->w[7];
      if (fabs(w) > 1.0) {
         if (fabs(w-1.0) < tol) {
            *theta = 90.0;
         } else if (fabs(w+1.0) < tol) {
            *theta = -90.0;
         } else {
            return 2;
         }
      } else {
         *theta = astASind(w);
      }
   }

   return 0;
}

/*============================================================================
*   COD: conic equidistant projection.
*
*   Given:
*      prj->p[1]    sigma = (theta2+theta1)/2
*      prj->p[2]    delta = (theta2-theta1)/2, where theta1 and theta2 are the
*                   latitudes of the standard parallels, in degrees.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "COD"
*      prj->flag    COD
*      prj->phi0    0.0
*      prj->theta0  sigma
*      prj->w[0]    C = r0*sin(sigma)*sin(delta)/delta
*      prj->w[1]    1/C
*      prj->w[2]    Y0 = delta*cot(delta)*cot(sigma)
*      prj->w[3]    Y0 + sigma
*      prj->astPRJfwd  Pointer to astCODfwd().
*      prj->astPRJrev  Pointer to astCODrev().
*===========================================================================*/

int astCODset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "COD");
   prj->flag   = WCS__COD;
   prj->phi0   = 0.0;
   prj->theta0 = prj->p[1];

   if (prj->r0 == 0.0) prj->r0 = R2D;

   if (prj->p[2] == 0.0) {
      prj->w[0] = prj->r0*astSind(prj->p[1])*D2R;
   } else {
      prj->w[0] = prj->r0*astSind(prj->p[1])*astSind(prj->p[2])/prj->p[2];
   }

   if (prj->w[0] == 0.0) {
      return 1;
   }

   prj->w[1] = 1.0/prj->w[0];
   prj->w[2] = prj->r0*astCosd(prj->p[2])*astCosd(prj->p[1])/prj->w[0];
   prj->w[3] = prj->w[2] + prj->p[1];

   prj->astPRJfwd = astCODfwd;
   prj->astPRJrev = astCODrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astCODfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double a, r;

   if (prj->flag != WCS__COD) {
      if (astCODset(prj)) return 1;
   }

   a = prj->w[0]*phi;
   r = prj->w[3] - theta;

   *x =             r*astSind(a);
   *y = prj->w[2] - r*astCosd(a);

   return 0;
}

/*--------------------------------------------------------------------------*/

int astCODrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double a, dy, r;

   if (prj->flag != WCS__COD) {
      if (astCODset(prj)) return 1;
   }

   dy = prj->w[2] - y;
   r  = sqrt(x*x + dy*dy);
   if (prj->p[1] < 0.0) r = -r;

   if (r == 0.0) {
      a = 0.0;
   } else {
      a = astATan2d(x/r, dy/r);
   }

   *phi   = a*prj->w[1];
   *theta = prj->w[3] - r;

   return 0;
}

/*============================================================================
*   COO: conic orthomorphic projection.
*
*   Given:
*      prj->p[1]    sigma = (theta2+theta1)/2
*      prj->p[2]    delta = (theta2-theta1)/2, where theta1 and theta2 are the
*                   latitudes of the standard parallels, in degrees.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "COO"
*      prj->flag    COO
*      prj->phi0    0.0
*      prj->theta0  sigma
*      prj->w[0]    C = ln(cos(theta2)/cos(theta1))/ln(tan(tau2)/tan(tau1))
*                       where tau1 = (90 - theta1)/2
*                             tau2 = (90 - theta2)/2
*      prj->w[1]    1/C
*      prj->w[2]    Y0 = psi*tan((90-sigma)/2)**C
*      prj->w[3]    psi = (r0*cos(theta1)/C)/tan(tau1)**C
*      prj->w[4]    1/psi
*      prj->astPRJfwd  Pointer to astCOOfwd().
*      prj->astPRJrev  Pointer to astCOOrev().
*===========================================================================*/

int astCOOset(prj)

struct AstPrjPrm *prj;

{
   double cos1, cos2, tan1, tan2, theta1, theta2;

   strcpy(prj->code, "COO");
   prj->flag   = WCS__COO;
   prj->phi0   = 0.0;
   prj->theta0 = prj->p[1];

   if (prj->r0 == 0.0) prj->r0 = R2D;

   theta1 = prj->p[1] - prj->p[2];
   theta2 = prj->p[1] + prj->p[2];

   tan1 = astTand((90.0 - theta1)/2.0);
   cos1 = astCosd(theta1);

   if (theta1 == theta2) {
      prj->w[0] = astSind(theta1);
   } else {
      tan2 = astTand((90.0 - theta2)/2.0);
      cos2 = astCosd(theta2);
      prj->w[0] = log(cos2/cos1)/log(tan2/tan1);
   }
   if (prj->w[0] == 0.0) {
      return 1;
   }

   prj->w[1] = 1.0/prj->w[0];

   prj->w[3] = prj->r0*(cos1/prj->w[0])/pow(tan1,prj->w[0]);
   if (prj->w[3] == 0.0) {
      return 1;
   }
   prj->w[2] = prj->w[3]*pow(astTand((90.0 - prj->p[1])/2.0),prj->w[0]);
   prj->w[4] = 1.0/prj->w[3];

   prj->astPRJfwd = astCOOfwd;
   prj->astPRJrev = astCOOrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astCOOfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double a, r;

   if (prj->flag != WCS__COO) {
      if (astCOOset(prj)) return 1;
   }

   a = prj->w[0]*phi;
   if (theta == -90.0) {
      if (prj->w[0] < 0.0) {
         r = 0.0;
      } else {
         return 2;
      }
   } else {
      r = prj->w[3]*pow(astTand((90.0 - theta)/2.0),prj->w[0]);
   }

   *x =             r*astSind(a);
   *y = prj->w[2] - r*astCosd(a);

   return 0;
}

/*--------------------------------------------------------------------------*/

int astCOOrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double a, dy, r;

   if (prj->flag != WCS__COO) {
      if (astCOOset(prj)) return 1;
   }

   dy = prj->w[2] - y;
   r  = sqrt(x*x + dy*dy);
   if (prj->p[1] < 0.0) r = -r;

   if (r == 0.0) {
      a = 0.0;
   } else {
      a = astATan2d(x/r, dy/r);
   }

   *phi = a*prj->w[1];
   if (r == 0.0) {
      if (prj->w[0] < 0.0) {
         *theta = -90.0;
      } else {
         return 2;
      }
   } else {
      *theta = 90.0 - 2.0*astATand(pow(r*prj->w[4],prj->w[1]));
   }

   return 0;
}

/*============================================================================
*   BON: Bonne's projection.
*
*   Given:
*      prj->p[1]    Bonne conformal latitude, theta1, in degrees.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "BON"
*      prj->flag    BON
*      prj->phi0    0.0
*      prj->theta0  0.0
*      prj->w[1]    r0*pi/180
*      prj->w[2]    Y0 = r0*(cot(theta1) + theta1*pi/180)
*      prj->astPRJfwd  Pointer to astBONfwd().
*      prj->astPRJrev  Pointer to astBONrev().
*===========================================================================*/

int astBONset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "BON");
   prj->flag   = WCS__BON;
   prj->phi0   = 0.0;
   prj->theta0 = 0.0;

   if (prj->r0 == 0.0) {
      prj->r0 = R2D;
      prj->w[1] = 1.0;
      prj->w[2] = prj->r0*astCosd(prj->p[1])/astSind(prj->p[1]) + prj->p[1];
   } else {
      prj->w[1] = prj->r0*D2R;
      prj->w[2] = prj->r0*(astCosd(prj->p[1])/astSind(prj->p[1]) + prj->p[1]*D2R);
   }

   prj->astPRJfwd = astBONfwd;
   prj->astPRJrev = astBONrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astBONfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double a, r;

   if (prj->p[1] == 0.0) {
      /* Sanson-Flamsteed. */
      return astSFLfwd(phi, theta, prj, x, y);
   }

   if (prj->flag != WCS__BON) {
      if (astBONset(prj)) return 1;
   }

   r = prj->w[2] - theta*prj->w[1];
   a = prj->r0*phi*astCosd(theta)/r;

   *x =             r*astSind(a);
   *y = prj->w[2] - r*astCosd(a);

   return 0;
}

/*--------------------------------------------------------------------------*/

int astBONrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double a, cthe, dy, r;

   if (prj->p[1] == 0.0) {
      /* Sanson-Flamsteed. */
      return astSFLrev(x, y, prj, phi, theta);
   }

   if (prj->flag != WCS__BON) {
      if (astBONset(prj)) return 1;
   }

   dy = prj->w[2] - y;
   r = sqrt(x*x + dy*dy);
   if (prj->p[1] < 0.0) r = -r;

   if (r == 0.0) {
      a = 0.0;
   } else {
      a = astATan2d(x/r, dy/r);
   }

   *theta = (prj->w[2] - r)/prj->w[1];
   cthe = astCosd(*theta);
   if (cthe == 0.0) {
      *phi = 0.0;
   } else {
      *phi = a*(r/prj->r0)/cthe;
   }

   return 0;
}

/*============================================================================
*   PCO: polyconic projection.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "PCO"
*      prj->flag    PCO
*      prj->phi0    0.0
*      prj->theta0  0.0
*      prj->w[0]    r0*(pi/180)
*      prj->w[1]    1/r0
*      prj->w[2]    2*r0
*      prj->astPRJfwd  Pointer to astPCOfwd().
*      prj->astPRJrev  Pointer to astPCOrev().
*===========================================================================*/

int astPCOset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "PCO");
   prj->flag   = WCS__PCO;
   prj->phi0   = 0.0;
   prj->theta0 = 0.0;

   if (prj->r0 == 0.0) {
      prj->r0 = R2D;
      prj->w[0] = 1.0;
      prj->w[1] = 1.0;
      prj->w[2] = 360.0/PI;
   } else {
      prj->w[0] = prj->r0*D2R;
      prj->w[1] = 1.0/prj->w[0];
      prj->w[2] = 2.0*prj->r0;
   }

   prj->astPRJfwd = astPCOfwd;
   prj->astPRJrev = astPCOrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astPCOfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   double a, cthe, cotthe, sthe;

   if (prj->flag != WCS__PCO) {
      if (astPCOset(prj)) return 1;
   }

   cthe = astCosd(theta);
   sthe = astSind(theta);
   a = phi*sthe;

   if (sthe == 0.0) {
      *x = prj->w[0]*phi;
      *y = 0.0;
   } else {
      cotthe = cthe/sthe;
      *x = prj->r0*cotthe*astSind(a);
      *y = prj->r0*(cotthe*(1.0 - astCosd(a)) + theta*D2R);
   }

   return 0;
}

/*--------------------------------------------------------------------------*/

int astPCOrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   int   j;
   double f, fneg, fpos, lambda, tanthe, theneg, thepos, w, xp, xx, ymthe, yp;
   const double tol = 1.0e-12;

   if (prj->flag != WCS__PCO) {
      if (astPCOset(prj)) return 1;
   }

   w = fabs(y*prj->w[1]);
   if (w < tol) {
      *phi = x*prj->w[1];
      *theta = 0.0;
   } else if (fabs(w-90.0) < tol) {
      *phi = 0.0;
      *theta = copysign(90.0,y);
   } else {
      /* Iterative solution using weighted division of the interval. */
      if (y > 0.0) {
         thepos =  90.0;
      } else {
         thepos = -90.0;
      }
      theneg = 0.0;

      xx = x*x;
      ymthe = y - prj->w[0]*thepos;
      fpos = xx + ymthe*ymthe;
      fneg = -999.0;

      for (j = 0; j < 64; j++) {
         if (fneg < -100.0) {
            /* Equal division of the interval. */
            *theta = (thepos+theneg)/2.0;
         } else {
            /* Weighted division of the interval. */
            lambda = fpos/(fpos-fneg);
            if (lambda < 0.1) {
               lambda = 0.1;
            } else if (lambda > 0.9) {
               lambda = 0.9;
            }
            *theta = thepos - lambda*(thepos-theneg);
         }

         /* Compute the residue. */
         ymthe = y - prj->w[0]*(*theta);
         tanthe = astTand(*theta);
         f = xx + ymthe*(ymthe - prj->w[2]/tanthe);

         /* Check for convergence. */
         if (fabs(f) < tol) break;
         if (fabs(thepos-theneg) < tol) break;

         /* Redefine the interval. */
         if (f > 0.0) {
            thepos = *theta;
            fpos = f;
         } else {
            theneg = *theta;
            fneg = f;
         }
      }

      xp = prj->r0 - ymthe*tanthe;
      yp = x*tanthe;
      if (xp == 0.0 && yp == 0.0) {
         *phi = 0.0;
      } else {
         *phi = astATan2d(yp, xp)/astSind(*theta);
      }
   }

   return 0;
}

/*============================================================================
*   TSC: tangential spherical cube projection.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "TSC"
*      prj->flag    TSC
*      prj->phi0    0.0
*      prj->theta0  0.0
*      prj->w[0]    r0*(pi/4)
*      prj->w[1]    (4/pi)/r0
*      prj->astPRJfwd  Pointer to astTSCfwd().
*      prj->astPRJrev  Pointer to astTSCrev().
*===========================================================================*/

int astTSCset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "TSC");
   prj->flag   = WCS__TSC;
   prj->phi0   = 0.0;
   prj->theta0 = 0.0;

   if (prj->r0 == 0.0) {
      prj->r0 = R2D;
      prj->w[0] = 45.0;
      prj->w[1] = 1.0/45.0;
   } else {
      prj->w[0] = prj->r0*PI/4.0;
      prj->w[1] = 1.0/prj->w[0];
   }

   prj->astPRJfwd = astTSCfwd;
   prj->astPRJrev = astTSCrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astTSCfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   int   face;
   double cthe, l, m, n, rho, x0, xf, y0, yf;
   const double tol = 1.0e-12;

   if (prj->flag != WCS__TSC) {
      if (astTSCset(prj)) return 1;
   }

   cthe = astCosd(theta);
   l = cthe*astCosd(phi);
   m = cthe*astSind(phi);
   n = astSind(theta);

   face = 0;
   rho  = n;
   if (l > rho) {
      face = 1;
      rho  = l;
   }
   if (m > rho) {
      face = 2;
      rho  = m;
   }
   if (-l > rho) {
      face = 3;
      rho  = -l;
   }
   if (-m > rho) {
      face = 4;
      rho  = -m;
   }
   if (-n > rho) {
      face = 5;
      rho  = -n;
   }

   if (face == 0) {
      xf =  m/rho;
      yf = -l/rho;
      x0 =  0.0;
      y0 =  2.0;
   } else if (face == 1) {
      xf =  m/rho;
      yf =  n/rho;
      x0 =  0.0;
      y0 =  0.0;
   } else if (face == 2) {
      xf = -l/rho;
      yf =  n/rho;
      x0 =  2.0;
      y0 =  0.0;
   } else if (face == 3) {
      xf = -m/rho;
      yf =  n/rho;
      x0 =  4.0;
      y0 =  0.0;
   } else if (face == 4) {
      xf =  l/rho;
      yf =  n/rho;
      x0 =  6.0;
      y0 =  0.0;
   } else if (face == 5) {
      xf =  m/rho;
      yf =  l/rho;
      x0 =  0.0;
      y0 = -2.0;
   }

   if (fabs(xf) > 1.0) {
      if (fabs(xf) > 1.0+tol) {
         return 2;
      }
      xf = copysign(1.0,xf);
   }
   if (fabs(yf) > 1.0) {
      if (fabs(yf) > 1.0+tol) {
         return 2;
      }
      yf = copysign(1.0,yf);
   }

   *x = prj->w[0]*(xf + x0);
   *y = prj->w[0]*(yf + y0);

   return 0;
}

/*--------------------------------------------------------------------------*/

int astTSCrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   double l, m, n, xf, yf;

   if (prj->flag != WCS__TSC) {
      if (astTSCset(prj)) return 1;
   }

   xf = x*prj->w[1];
   yf = y*prj->w[1];

   /* Check bounds. */
   if (fabs(xf) <= 1.0) {
      if (fabs(yf) > 3.0) return 2;
   } else {
      if (fabs(xf) > 7.0) return 2;
      if (fabs(yf) > 1.0) return 2;
   }

   /* Map negative faces to the other side. */
   if (xf < -1.0) xf += 8.0;

   /* Determine the face. */
   if (xf > 5.0) {
      /* face = 4 */
      xf = xf - 6.0;
      m  = -1.0/sqrt(1.0 + xf*xf + yf*yf);
      l  = -m*xf;
      n  = -m*yf;
   } else if (xf > 3.0) {
      /* face = 3 */
      xf = xf - 4.0;
      l  = -1.0/sqrt(1.0 + xf*xf + yf*yf);
      m  =  l*xf;
      n  = -l*yf;
   } else if (xf > 1.0) {
      /* face = 2 */
      xf = xf - 2.0;
      m  =  1.0/sqrt(1.0 + xf*xf + yf*yf);
      l  = -m*xf;
      n  =  m*yf;
   } else if (yf > 1.0) {
      /* face = 0 */
      yf = yf - 2.0;
      n  = 1.0/sqrt(1.0 + xf*xf + yf*yf);
      l  = -n*yf;
      m  =  n*xf;
   } else if (yf < -1.0) {
      /* face = 5 */
      yf = yf + 2.0;
      n  = -1.0/sqrt(1.0 + xf*xf + yf*yf);
      l  = -n*yf;
      m  = -n*xf;
   } else {
      /* face = 1 */
      l  =  1.0/sqrt(1.0 + xf*xf + yf*yf);
      m  =  l*xf;
      n  =  l*yf;
   }

   if (l == 0.0 && m == 0.0) {
      *phi = 0.0;
   } else {
      *phi = astATan2d(m, l);
   }
   *theta = astASind(n);

   return 0;
}

/*============================================================================
*   CSC: COBE quadrilateralized spherical cube projection.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "CSC"
*      prj->flag    CSC
*      prj->phi0    0.0
*      prj->theta0  0.0
*      prj->w[0]    r0*(pi/4)
*      prj->w[1]    (4/pi)/r0
*      prj->astPRJfwd  Pointer to astCSCfwd().
*      prj->astPRJrev  Pointer to astCSCrev().
*===========================================================================*/

int astCSCset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "CSC");
   prj->flag   = WCS__CSC;
   prj->phi0   = 0.0;
   prj->theta0 = 0.0;

   if (prj->r0 == 0.0) {
      prj->r0 = R2D;
      prj->w[0] = 45.0;
      prj->w[1] = 1.0/45.0;
   } else {
      prj->w[0] = prj->r0*PI/4.0;
      prj->w[1] = 1.0/prj->w[0];
   }

   prj->astPRJfwd = astCSCfwd;
   prj->astPRJrev = astCSCrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astCSCfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   int   face;
   float cthe, eta, l, m, n, rho, xi;
   const float tol = 1.0e-7;

   float a, a2, a2b2, a4, ab, b, b2, b4, ca2, cb2, x0, xf, y0, yf;
   const float gstar  =  1.37484847732;
   const float mm     =  0.004869491981;
   const float gamma  = -0.13161671474;
   const float omega1 = -0.159596235474;
   const float d0  =  0.0759196200467;
   const float d1  = -0.0217762490699;
   const float c00 =  0.141189631152;
   const float c10 =  0.0809701286525;
   const float c01 = -0.281528535557;
   const float c11 =  0.15384112876;
   const float c20 = -0.178251207466;
   const float c02 =  0.106959469314;

   if (prj->flag != WCS__CSC) {
      if (astCSCset(prj)) return 1;
   }

   cthe = astCosd(theta);
   l = cthe*astCosd(phi);
   m = cthe*astSind(phi);
   n = astSind(theta);

   face = 0;
   rho  = n;
   if (l > rho) {
      face = 1;
      rho  = l;
   }
   if (m > rho) {
      face = 2;
      rho  = m;
   }
   if (-l > rho) {
      face = 3;
      rho  = -l;
   }
   if (-m > rho) {
      face = 4;
      rho  = -m;
   }
   if (-n > rho) {
      face = 5;
      rho  = -n;
   }

   if (face == 0) {
      xi  =  m;
      eta = -l;
      x0  =  0.0;
      y0  =  2.0;
   } else if (face == 1) {
      xi  =  m;
      eta =  n;
      x0  =  0.0;
      y0  =  0.0;
   } else if (face == 2) {
      xi  = -l;
      eta =  n;
      x0  =  2.0;
      y0  =  0.0;
   } else if (face == 3) {
      xi  = -m;
      eta =  n;
      x0  =  4.0;
      y0  =  0.0;
   } else if (face == 4) {
      xi  =  l;
      eta =  n;
      x0  =  6.0;
      y0  =  0.0;
   } else if (face == 5) {
      xi  =  m;
      eta =  l;
      x0  =  0.0;
      y0  = -2.0;
   }

   a =  xi/rho;
   b = eta/rho;

   a2 = a*a;
   b2 = b*b;
   ca2 = 1.0 - a2;
   cb2 = 1.0 - b2;

   /* Avoid floating underflows. */
   ab   = fabs(a*b);
   a4   = (a2 > 1.0e-16) ? a2*a2 : 0.0;
   b4   = (b2 > 1.0e-16) ? b2*b2 : 0.0;
   a2b2 = (ab > 1.0e-16) ? a2*b2 : 0.0;

   xf = a*(a2 + ca2*(gstar + b2*(gamma*ca2 + mm*a2 +
          cb2*(c00 + c10*a2 + c01*b2 + c11*a2b2 + c20*a4 + c02*b4)) +
          a2*(omega1 - ca2*(d0 + d1*a2))));
   yf = b*(b2 + cb2*(gstar + a2*(gamma*cb2 + mm*b2 +
          ca2*(c00 + c10*b2 + c01*a2 + c11*a2b2 + c20*b4 + c02*a4)) +
          b2*(omega1 - cb2*(d0 + d1*b2))));

   if (fabs(xf) > 1.0) {
      if (fabs(xf) > 1.0+tol) {
         return 2;
      }
      xf = copysign(1.0,xf);
   }
   if (fabs(yf) > 1.0) {
      if (fabs(yf) > 1.0+tol) {
         return 2;
      }
      yf = copysign(1.0,yf);
   }

   *x = prj->w[0]*(x0 + xf);
   *y = prj->w[0]*(y0 + yf);

   return 0;
}

/*--------------------------------------------------------------------------*/

int astCSCrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   int   face;
   float l, m, n;

   float     a, b, xf, xx, yf, yy, z0, z1, z2, z3, z4, z5, z6;
   const float p00 = -0.27292696;
   const float p10 = -0.07629969;
   const float p20 = -0.22797056;
   const float p30 =  0.54852384;
   const float p40 = -0.62930065;
   const float p50 =  0.25795794;
   const float p60 =  0.02584375;
   const float p01 = -0.02819452;
   const float p11 = -0.01471565;
   const float p21 =  0.48051509;
   const float p31 = -1.74114454;
   const float p41 =  1.71547508;
   const float p51 = -0.53022337;
   const float p02 =  0.27058160;
   const float p12 = -0.56800938;
   const float p22 =  0.30803317;
   const float p32 =  0.98938102;
   const float p42 = -0.83180469;
   const float p03 = -0.60441560;
   const float p13 =  1.50880086;
   const float p23 = -0.93678576;
   const float p33 =  0.08693841;
   const float p04 =  0.93412077;
   const float p14 = -1.41601920;
   const float p24 =  0.33887446;
   const float p05 = -0.63915306;
   const float p15 =  0.52032238;
   const float p06 =  0.14381585;

   if (prj->flag != WCS__CSC) {
      if (astCSCset(prj)) return 1;
   }

   xf = x*prj->w[1];
   yf = y*prj->w[1];

   /* Check bounds. */
   if (fabs(xf) <= 1.0) {
      if (fabs(yf) > 3.0) return 2;
   } else {
      if (fabs(xf) > 7.0) return 2;
      if (fabs(yf) > 1.0) return 2;
   }

   /* Map negative faces to the other side. */
   if (xf < -1.0) xf += 8.0;

   /* Determine the face. */
   if (xf > 5.0) {
      face = 4;
      xf = xf - 6.0;
   } else if (xf > 3.0) {
      face = 3;
      xf = xf - 4.0;
   } else if (xf > 1.0) {
      face = 2;
      xf = xf - 2.0;
   } else if (yf > 1.0) {
      face = 0;
      yf = yf - 2.0;
   } else if (yf < -1.0) {
      face = 5;
      yf = yf + 2.0;
   } else {
      face = 1;
   }

   xx  =  xf*xf;
   yy  =  yf*yf;

   z0 = p00 + xx*(p10 + xx*(p20 + xx*(p30 + xx*(p40 + xx*(p50 + xx*(p60))))));
   z1 = p01 + xx*(p11 + xx*(p21 + xx*(p31 + xx*(p41 + xx*(p51)))));
   z2 = p02 + xx*(p12 + xx*(p22 + xx*(p32 + xx*(p42))));
   z3 = p03 + xx*(p13 + xx*(p23 + xx*(p33)));
   z4 = p04 + xx*(p14 + xx*(p24));
   z5 = p05 + xx*(p15);
   z6 = p06;

   a = z0 + yy*(z1 + yy*(z2 + yy*(z3 + yy*(z4 + yy*(z5 + yy*z6)))));
   a = xf + xf*(1.0 - xx)*a;

   z0 = p00 + yy*(p10 + yy*(p20 + yy*(p30 + yy*(p40 + yy*(p50 + yy*(p60))))));
   z1 = p01 + yy*(p11 + yy*(p21 + yy*(p31 + yy*(p41 + yy*(p51)))));
   z2 = p02 + yy*(p12 + yy*(p22 + yy*(p32 + yy*(p42))));
   z3 = p03 + yy*(p13 + yy*(p23 + yy*(p33)));
   z4 = p04 + yy*(p14 + yy*(p24));
   z5 = p05 + yy*(p15);
   z6 = p06;

   b = z0 + xx*(z1 + xx*(z2 + xx*(z3 + xx*(z4 + xx*(z5 + xx*z6)))));
   b = yf + yf*(1.0 - yy)*b;

   if (face == 0) {
      n =  1.0/sqrt(a*a + b*b + 1.0);
      l = -b*n;
      m =  a*n;
   } else if (face == 1) {
      l =  1.0/sqrt(a*a + b*b + 1.0);
      m =  a*l;
      n =  b*l;
   } else if (face == 2) {
      m =  1.0/sqrt(a*a + b*b + 1.0);
      l = -a*m;
      n =  b*m;
   } else if (face == 3) {
      l = -1.0/sqrt(a*a + b*b + 1.0);
      m =  a*l;
      n = -b*l;
   } else if (face == 4) {
      m = -1.0/sqrt(a*a + b*b + 1.0);
      l = -a*m;
      n = -b*m;
   } else if (face == 5) {
      n = -1.0/sqrt(a*a + b*b + 1.0);
      l = -b*n;
      m = -a*n;
   }

   if (l == 0.0 && m == 0.0) {
      *phi = 0.0;
   } else {
      *phi = astATan2d(m, l);
   }
   *theta = astASind(n);

   return 0;
}

/*============================================================================
*   QSC: quadrilaterilized spherical cube projection.
*
*   Given and/or returned:
*      prj->r0      r0; reset to 180/pi if 0.
*
*   Returned:
*      prj->code    "QSC"
*      prj->flag    QSC
*      prj->phi0    0.0
*      prj->theta0  0.0
*      prj->w[0]    r0*(pi/4)
*      prj->w[1]    (4/pi)/r0
*      prj->astPRJfwd  Pointer to astQSCfwd().
*      prj->astPRJrev  Pointer to astQSCrev().
*===========================================================================*/

int astQSCset(prj)

struct AstPrjPrm *prj;

{
   strcpy(prj->code, "QSC");
   prj->flag   = WCS__QSC;
   prj->phi0   = 0.0;
   prj->theta0 = 0.0;

   if (prj->r0 == 0.0) {
      prj->r0 = R2D;
      prj->w[0] = 45.0;
      prj->w[1] = 1.0/45.0;
   } else {
      prj->w[0] = prj->r0*PI/4.0;
      prj->w[1] = 1.0/prj->w[0];
   }

   prj->astPRJfwd = astQSCfwd;
   prj->astPRJrev = astQSCrev;

   return 0;
}

/*--------------------------------------------------------------------------*/

int astQSCfwd(phi, theta, prj, x, y)

const double phi, theta;
struct AstPrjPrm *prj;
double *x, *y;

{
   int   face;
   double cthe, eta, l, m, n, omega, p, rho, rhu, t, tau, x0, xf, xi, y0, yf;
   const double tol = 1.0e-12;

   if (prj->flag != WCS__QSC) {
      if (astQSCset(prj)) return 1;
   }

   if (fabs(theta) == 90.0) {
      *x = 0.0;
      *y = copysign(2.0*prj->w[0],theta);
      return 0;
   }

   cthe = astCosd(theta);
   l = cthe*astCosd(phi);
   m = cthe*astSind(phi);
   n = astSind(theta);

   face = 0;
   rho  = n;
   if (l > rho) {
      face = 1;
      rho  = l;
   }
   if (m > rho) {
      face = 2;
      rho  = m;
   }
   if (-l > rho) {
      face = 3;
      rho  = -l;
   }
   if (-m > rho) {
      face = 4;
      rho  = -m;
   }
   if (-n > rho) {
      face = 5;
      rho  = -n;
   }

   rhu = 1.0 - rho;

   if (face == 0) {
      xi  =  m;
      eta = -l;
      if (rhu < 1.0e-8) {
         /* Small angle formula. */
         t = (90.0 - theta)*D2R;
         rhu = t*t/2.0;
      }
      x0  =  0.0;
      y0  =  2.0;
   } else if (face == 1) {
      xi  =  m;
      eta =  n;
      if (rhu < 1.0e-8) {
         /* Small angle formula. */
         t = theta*D2R;
         p = fmod(phi,360.0);
         if (p < -180.0) p += 360.0;
         if (p >  180.0) p -= 360.0;
         p *= D2R;
         rhu = (p*p + t*t)/2.0;
      }
      x0  =  0.0;
      y0  =  0.0;
   } else if (face == 2) {
      xi  = -l;
      eta =  n;
      if (rhu < 1.0e-8) {
         /* Small angle formula. */
         t = theta*D2R;
         p = fmod(phi,360.0);
         if (p < -180.0) p += 360.0;
         p = (90.0 - p)*D2R;
         rhu = (p*p + t*t)/2.0;
      }
      x0  =  2.0;
      y0  =  0.0;
   } else if (face == 3) {
      xi  = -m;
      eta =  n;
      if (rhu < 1.0e-8) {
         /* Small angle formula. */
         t = theta*D2R;
         p = fmod(phi,360.0);
         if (p < 0.0) p += 360.0;
         p = (180.0 - p)*D2R;
         rhu = (p*p + t*t)/2.0;
      }
      x0  =  4.0;
      y0  =  0.0;
   } else if (face == 4) {
      xi  =  l;
      eta =  n;
      if (rhu < 1.0e-8) {
         /* Small angle formula. */
         t = theta*D2R;
         p = fmod(phi,360.0);
         if (p > 180.0) p -= 360.0;
         p *= (90.0 + p)*D2R;
         rhu = (p*p + t*t)/2.0;
      }
      x0  =  6;
      y0  =  0.0;
   } else if (face == 5) {
      xi  =  m;
      eta =  l;
      if (rhu < 1.0e-8) {
         /* Small angle formula. */
         t = (90.0 + theta)*D2R;
         rhu = t*t/2.0;
      }
      x0  =  0.0;
      y0  = -2;
   }

   if (xi == 0.0 && eta == 0.0) {
      xf  = 0.0;
      yf  = 0.0;
   } else if (-xi >= fabs(eta)) {
      omega = eta/xi;
      tau = 1.0 + omega*omega;
      xf  = -sqrt(rhu/(1.0-1.0/sqrt(1.0+tau)));
      yf  = (xf/15.0)*(astATand(omega) - astASind(omega/sqrt(tau+tau)));
   } else if (xi >= fabs(eta)) {
      omega = eta/xi;
      tau = 1.0 + omega*omega;
      xf  =  sqrt(rhu/(1.0-1.0/sqrt(1.0+tau)));
      yf  = (xf/15.0)*(astATand(omega) - astASind(omega/sqrt(tau+tau)));
   } else if (-eta > fabs(xi)) {
      omega = xi/eta;
      tau = 1.0 + omega*omega;
      yf  = -sqrt(rhu/(1.0-1.0/sqrt(1.0+tau)));
      xf  = (yf/15.0)*(astATand(omega) - astASind(omega/sqrt(tau+tau)));
   } else if (eta > fabs(xi)) {
      omega = xi/eta;
      tau = 1.0 + omega*omega;
      yf  =  sqrt(rhu/(1.0-1.0/sqrt(1.0+tau)));
      xf  = (yf/15.0)*(astATand(omega) - astASind(omega/sqrt(tau+tau)));
   }

   if (fabs(xf) > 1.0) {
      if (fabs(xf) > 1.0+tol) {
         return 2;
      }
      xf = copysign(1.0,xf);
   }
   if (fabs(yf) > 1.0) {
      if (fabs(yf) > 1.0+tol) {
         return 2;
      }
      yf = copysign(1.0,yf);
   }

   *x = prj->w[0]*(xf + x0);
   *y = prj->w[0]*(yf + y0);


   return 0;
}

/*--------------------------------------------------------------------------*/

int astQSCrev(x, y, prj, phi, theta)

const double x, y;
struct AstPrjPrm *prj;
double *phi, *theta;

{
   int   direct, face;
   double l, m, n, omega, rho, rhu, tau, xf, yf, w;
   const double tol = 1.0e-12;

   if (prj->flag != WCS__QSC) {
      if (astQSCset(prj)) return 1;
   }

   xf = x*prj->w[1];
   yf = y*prj->w[1];

   /* Check bounds. */
   if (fabs(xf) <= 1.0) {
      if (fabs(yf) > 3.0) return 2;
   } else {
      if (fabs(xf) > 7.0) return 2;
      if (fabs(yf) > 1.0) return 2;
   }

   /* Map negative faces to the other side. */
   if (xf < -1.0) xf += 8.0;

   /* Determine the face. */
   if (xf > 5.0) {
      face = 4;
      xf = xf - 6.0;
   } else if (xf > 3.0) {
      face = 3;
      xf = xf - 4.0;
   } else if (xf > 1.0) {
      face = 2;
      xf = xf - 2.0;
   } else if (yf > 1.0) {
      face = 0;
      yf = yf - 2.0;
   } else if (yf < -1.0) {
      face = 5;
      yf = yf + 2.0;
   } else {
      face = 1;
   }

   direct = (fabs(xf) > fabs(yf));
   if (direct) {
      if (xf == 0.0) {
         omega = 0.0;
         tau = 1.0;
         rho = 1.0;
         rhu = 0.0;
      } else {
         w = 15.0*yf/xf;
         omega = astSind(w)/(astCosd(w) - SQRT2INV);
         tau = 1.0 + omega*omega;
         rhu = xf*xf*(1.0 - 1.0/sqrt(1.0 + tau));
         rho = 1.0 - rhu;
      }
   } else {
      if (yf == 0.0) {
         omega = 0.0;
         tau = 1.0;
         rho = 1.0;
         rhu = 0.0;
      } else {
         w = 15.0*xf/yf;
         omega = astSind(w)/(astCosd(w) - SQRT2INV);
         tau = 1.0 + omega*omega;
         rhu = yf*yf*(1.0 - 1.0/sqrt(1.0 + tau));
         rho = 1.0 - rhu;
      }
   }

   if (rho < -1.0) {
      if (rho < -1.0-tol) {
         return 2;
      }

      rho = -1.0;
      rhu =  2.0;
      w   =  0.0;
   } else {
      w = sqrt(rhu*(2.0-rhu)/tau);
   }

   if (face == 0) {
      n = rho;
      if (direct) {
         m = w;
         if (xf < 0.0) m = -m;
         l = -m*omega;
      } else {
         l = w;
         if (yf > 0.0) l = -l;
         m = -l*omega;
      }
   } else if (face == 1) {
      l = rho;
      if (direct) {
         m = w;
         if (xf < 0.0) m = -m;
         n = m*omega;
      } else {
         n = w;
         if (yf < 0.0) n = -n;
         m = n*omega;
      }
   } else if (face == 2) {
      m = rho;
      if (direct) {
         l = w;
         if (xf > 0.0) l = -l;
         n = -l*omega;
      } else {
         n = w;
         if (yf < 0.0) n = -n;
         l = -n*omega;
      }
   } else if (face == 3) {
      l = -rho;
      if (direct) {
         m = w;
         if (xf > 0.0) m = -m;
         n = -m*omega;
      } else {
         n = w;
         if (yf < 0.0) n = -n;
         m = -n*omega;
      }
   } else if (face == 4) {
      m = -rho;
      if (direct) {
         l = w;
         if (xf < 0.0) l = -l;
         n = l*omega;
      } else {
         n = w;
         if (yf < 0.0) n = -n;
         l = n*omega;
      }
   } else if (face == 5) {
      n = -rho;
      if (direct) {
         m = w;
         if (xf < 0.0) m = -m;
         l = m*omega;
      } else {
         l = w;
         if (yf < 0.0) l = -l;
         m = l*omega;
      }
   }

   if (l == 0.0 && m == 0.0) {
      *phi = 0.0;
   } else {
      *phi = astATan2d(m, l);
   }
   *theta = astASind(n);

   return 0;
}

Generated by  Doxygen 1.6.0   Back to index