Exemplo n.º 1
0
double Hero::distance(const std::vector<double> &c1, const std::vector<double> &c2)
{
    dbg(1) << "XXXX c1.size = " << c1.size() << " c2.size = " << c2.size();
    dbg(1) << "XXXX c1 = " << c1;
    dbg(1) << "XXXX c2 = " << c2;

    // resize and pad with '1' to conform to naxis
    auto pix1 = c1; pix1.resize( naxes(), 1.0);
    auto pix2 = c2; pix2.resize( naxes(), 1.0);

    // if we don't have a frame, return cartesian distance
    if( m_ast.origWcsInfo == AST__NULL) {
        double sumSq = 0.0;
        for( int i = 0 ; i < naxes() ; i ++ ) {
            double diff = pix1[i] - pix2[i];
            sumSq += diff * diff;
        }
        return sqrt( sumSq);
    }

    dbg(1) << "XXXX fpix1 = " << pix1;
    dbg(1) << "XXXX fpix2 = " << pix2;

    pix1 = fits2world( pix1);
    pix2 = fits2world( pix2);

    dbg(1) << "XXXX wpix1 = " << pix1;
    dbg(1) << "XXXX wpix2 = " << pix2;

    AstErrorGuard errGuard( this);
    AstGCGuard gcGuard;

    // call ast to figure out the distance
    double res = astDistance( m_ast.currWcsInfo, & pix1.front(), & pix2.front());
    dbg(1) << "XXXX dist = " << res;

    astClearStatus;
    return res;
}
Exemplo n.º 2
0
void smurf_jsatileinfo( int *status ) {

/* Local Variables */
   AstCmpRegion *overlap;
   AstFitsChan *fc;
   AstFrameSet *fs;
   AstObject *obj;
   AstRegion *region;
   AstRegion *target;
   HDSLoc *cloc = NULL;
   HDSLoc *xloc = NULL;
   char *jcmt_tiles;
   char *tilendf = NULL;
   char text[ 200 ];
   double dec[ 8 ];
   double dist;
   double dlbnd[ 2 ];
   double dubnd[ 2 ];
   double gx[ 8 ];
   double gy[ 8 ];
   double maxdist;
   double norm_radec[2];
   double point1[ 2 ];
   double point2[ 2 ];
   double ra[ 8 ];
   double ra0;
   double dec0;
   int *ipntr;
   int axes[ 2 ];
   int create;
   int dirlen;
   int el;
   int exists;
   int flag;
   int i;
   int indf1;
   int indf2;
   int indf3;
   int itile;
   int iv;
   int jtile;
   int lbnd[ 2 ];
   int local_origin;
   int nc;
   int place;
   int tlbnd[ 2 ];
   int tubnd[ 2 ];
   int ubnd[ 2 ];
   smf_jsaproj_t proj;
   int xt;
   int yt;
   smfJSATiling skytiling;
   void *pntr;

/* Check inherited status */
   if( *status != SAI__OK ) return;

/* Start a new AST context. */
   astBegin;

/* Get the instrument to use abnd get the parameters describing the
   layout of its JSA tiles. */
   smf_jsainstrument( "INSTRUMENT", NULL, SMF__INST_NONE, &skytiling,
                      status );

/* Return the maximum tile index. */
   parPut0i( "MAXTILE", skytiling.ntiles - 1, status );

/* Abort if an error has occurred. */
   if( *status != SAI__OK ) goto L999;

/* Decide what sort of projection to use. */
   parChoic( "PROJ", "HPX", "HPX,HPX12,XPHN,XPHS", 1, text, sizeof(text),
             status );
   proj = smf_jsaproj_fromstr( text, 1, status );

/* If required, create an all-sky NDF in which each pixel covers the area
   of a single tile, and holds the integer tile index. The NDF has an
   initial size of 1x1 pixels, but is expanded later to the required size. */
   lbnd[ 0 ] = ubnd[ 0 ] = lbnd[ 1 ] = ubnd[ 1 ] = 1;
   ndfCreat( "ALLSKY", "_INTEGER", 2, lbnd, ubnd, &indf3, status );

/* If a null (!) value was supplied for parameter ALLSKY, annull the
   error and pass on. */
   if( *status == PAR__NULL ) {
      errAnnul( status );

/* Otherwise, create a FrameSet describing the whole sky in which each
   pixel corresponds to a single tile. */
   } else {
      smf_jsatile( -1, &skytiling, 0, proj, NULL, &fs, NULL, lbnd, ubnd,
                   status );

/* Change the bounds of the output NDF. */
      ndfSbnd( 2, lbnd, ubnd, indf3, status );

/* Store the FrameSet in the NDF. */
      ndfPtwcs( fs, indf3, status );

/* Map the data array. */
      ndfMap( indf3, "Data", "_INTEGER", "WRITE/BAD", (void **) &ipntr, &el,
              status );

/* Create all-sky map using XPH projection. */
      if( *status == SAI__OK ) {

/* Loop round every tile index. */
         for( jtile = 0; jtile < skytiling.ntiles; jtile++ ) {

/* Get the zero-based (x,y) indices of the tile within an HPX projection.
   This flips the bottom left half-facet up to the top right. */
            smf_jsatilei2xy( jtile, &skytiling, &xt, &yt, NULL, status );

/* Convert these HPX indices to the corresponding indices within the
   required projection. Note, the lower left facet is split by the above
   call to smf_jsatilei2xy tile (i.e. (xt,yt) indices are *not* in the
   "raw" mode). For instance, (0,0) is not a valid tile. */
            smf_jsatilexyconv( &skytiling, proj, xt, yt, 0, &xt, &yt, status );

/* Get the vector index of the corresponding element of the all-sky NDF. */
            if( proj == SMF__JSA_HPX || proj == SMF__JSA_HPX12 ) {
               iv = xt + 5*skytiling.ntpf*yt;
            } else {
               iv = xt + 4*skytiling.ntpf*yt;
            }

/* Report an error if this element has already been assigned a tile
   index. Otherwise, store the tile index. */
            if( ipntr[ iv ] == VAL__BADI ) {
               ipntr[ iv ] = jtile;
            } else if( *status == SAI__OK ) {
               *status = SAI__ERROR;
               errRepf( "", "%s projection assigns multiple tiles to "
                        "pixel (%d,%d).", status, text, xt, yt );
               break;
            }
         }
      }

/* Store NDF title. */
      sprintf( text, "JSA tile indices for %s data", skytiling.name );
      ndfCput( text, indf3, "TITLE", status );

/* Store the instrument as a component in the SMURF extension. */
      ndfXnew( indf3, "SMURF", "INSTRUMENT", 0, 0, &xloc, status );
      ndfXpt0c( skytiling.name, indf3, "SMURF", "INSTRUMENT", status );
      datAnnul( &xloc, status );

/* Close the NDF. */
      ndfAnnul( &indf3, status );
   }

/* Abort if an error has occurred. */
   if( *status != SAI__OK ) goto L999;

/* Get the zero-based index of the required tile. If a null value is
   supplied, annull the error and skip to the end. */
   parGdr0i( "ITILE", 0, 0, skytiling.ntiles - 1, 0, &itile, status );
   if( *status == PAR__NULL ) {
       errAnnul( status );
       goto L999;
   }

/* See if the pixel origin is to be at the centre of the tile. */
   parGet0l( "LOCAL", &local_origin, status );

/* Display the tile number. */
   msgBlank( status );
   msgSeti( "ITILE", itile );
   msgSeti( "MAXTILE", skytiling.ntiles - 1);
   msgOut( " ", "   Tile ^ITILE (from 0 to ^MAXTILE):", status );

/* Get the FITS header, FrameSet and Region defining the tile, and the tile
   bounds in pixel indices. */
   smf_jsatile( itile, &skytiling, local_origin,  proj, &fc, &fs, &region,
                lbnd, ubnd, status );

/* Write the FITS headers out to a file, annulling the error if the
   header is not required. */
   if( *status == SAI__OK ) {
      atlDumpFits( "HEADER", fc, status );
      if( *status == PAR__NULL ) errAnnul( status );
   }

/* If required, write the Region out to a text file. */
   if( *status == SAI__OK ) {
      atlCreat( "REGION", (AstObject *) region, status );
      if( *status == PAR__NULL ) errAnnul( status );
   }

/* Store the lower and upper pixel bounds of the tile. */
   parPut1i( "LBND", 2, lbnd, status );
   parPut1i( "UBND", 2, ubnd, status );

/* Display pixel bounds on the screen. */
   msgSeti( "XL", lbnd[ 0 ] );
   msgSeti( "XU", ubnd[ 0 ] );
   msgSeti( "YL", lbnd[ 1 ] );
   msgSeti( "YU", ubnd[ 1 ] );
   msgOut( " ", "      Pixel bounds: (^XL:^XU,^YL:^YU)", status );

/* Get the RA,Dec at the tile centre. */
   if( astTest( fs, "SkyRef" ) ) {
      ra0 = astGetD( fs, "SkyRef(1)" );
      dec0 = astGetD( fs, "SkyRef(2)" );

/* Format the central RA and Dec. and display. Call astNorm on the
   coordinates provided that the frame set has the correct number of
   axes (which it should as it comes from smf_jsatile). */
      norm_radec[0] = ra0;
      norm_radec[1] = dec0;
      if (astGetI(fs, "Naxes") == 2) astNorm(fs, norm_radec);
      msgSetc( "RACEN",  astFormat( fs, 1, norm_radec[ 0 ] ));
      msgSetc( "DECCEN",  astFormat( fs, 2, norm_radec[ 1 ] ));
      msgOut( " ", "      Centre (ICRS): RA=^RACEN DEC=^DECCEN", status );

/* Transform a collection of points on the edge of the region (corners and
   side mid-points) from GRID coords to RA,Dec. */
      point1[ 0 ] = 0.5;
      point1[ 1 ] = 0.5;
      point2[ 0 ] = ubnd[ 0 ] - lbnd[ 0 ] + 1;
      point2[ 1 ] = ubnd[ 1 ] - lbnd[ 1 ] + 1;

      gx[ 0 ] = point1[ 0 ];         /* Bottom left */
      gy[ 0 ] = point1[ 1 ];

      gx[ 1 ] = point1[ 0 ];         /* Centre left */
      gy[ 1 ] = gy[ 0 ];

      gx[ 2 ] = point1[ 0 ];         /* Top left */
      gy[ 2 ] = point2[ 1 ];

      gx[ 3 ] = gx[ 0 ];             /* Top centre */
      gy[ 3 ] = point2[ 1 ];

      gx[ 4 ] = point2[ 0 ];         /* Top right */
      gy[ 4 ] = point2[ 1 ];

      gx[ 5 ] = point2[ 0 ];         /* Centre right */
      gy[ 5 ] = gy[ 0 ];

      gx[ 6 ] = point2[ 0 ];         /* Bottom right */
      gy[ 6 ] = point1[ 1 ];

      gx[ 7 ] = gx[ 0 ];             /* Bottom centre */
      gy[ 7 ] = point1[ 1 ];

      astTran2( fs, 8, gx, gy, 1, ra, dec );

/* Find the arc-distance from the centre to the furthest point from the
   centre. */
      point1[ 0 ] = ra0;
      point1[ 1 ] = dec0;
      maxdist = -1.0;

      for( i = 1; i < 8; i++ ) {
         if( ra[ i ] != AST__BAD && dec[ i ] != AST__BAD ) {
            point2[ 0 ] = ra[ i ];
            point2[ 1 ] = dec[ i ];
            dist = astDistance( fs, point1, point2 );
            if( dist > maxdist ) maxdist = dist;
         }
      }

/* Convert from radius to diameter. */
      maxdist *= 2.0;

/* Format this size as a dec value (i.e. arc-distance) and display it. */
      if( maxdist > 0.0 ) {
         msgSetc( "SIZE",  astFormat( fs, 2, maxdist ) );
         msgOut( " ", "      Size: ^SIZE", status );
      } else {
         maxdist = AST__BAD;
         msgOut( " ", "      Size: <unknown>", status );
      }

/* If a discontinuity passes through the tile, the centre and size may be
   unknown. */
   } else {
      ra0 = AST__BAD;
      dec0 = AST__BAD;
      maxdist = AST__BAD;
      msgOut( " ", "      Centre (ICRS): RA=<unknown> DEC=<unknown>", status );
      msgOut( " ", "      Size: <unknown>", status );
   }

/* Write the tile centre ra and dec in radians to the output parameters. */
   parPut0d( "RACEN", norm_radec[ 0 ], status );
   parPut0d( "DECCEN", norm_radec[ 1 ], status );

/* Write the size to the output parameter as radians. */
   parPut0d( "SIZE", maxdist, status );

/* Get the translation of the environment variable JSA_TILE_DIR. */
   jcmt_tiles = getenv( "JSA_TILE_DIR" );

/* Initialise the path to the tile's NDF to hold the root directory.
   Use the current working directory if JSA_TILE_DIR is undefined. */
   if( jcmt_tiles ) {
      nc = 0;
      tilendf = astAppendString( tilendf, &nc, jcmt_tiles );

   } else {

      nc = 512;
      jcmt_tiles = astMalloc( nc );

      while( !getcwd( jcmt_tiles, nc ) ) {
         nc *= 2;
         jcmt_tiles = astRealloc( jcmt_tiles, nc );
      }

      nc = 0;
      tilendf = astAppendString( tilendf, &nc, jcmt_tiles );
      jcmt_tiles = astFree( jcmt_tiles );
   }

/* Complete the path to the tile's NDF. */
   tilendf = astAppendString( tilendf, &nc, "/" );
   tilendf = astAppendString( tilendf, &nc, skytiling.subdir );
   dirlen = nc;
   sprintf( text, "/tile_%d.sdf", itile );
   tilendf = astAppendString( tilendf, &nc, text );

/* Write it to the output parameter. */
   parPut0c( "TILENDF", tilendf, status );

/* See if the NDF exists, and store the flag in the output parameter. */
   exists = access( tilendf, F_OK ) ? 0 : 1;
   parPut0l( "EXISTS", exists, status );

/* If the NDF does not exist, create it if required. */
   parGet0l( "CREATE", &create, status );
   if( !exists && create && *status == SAI__OK ) {

/* Write the NDF info to the screen. */
      msgSetc( "NDF",  tilendf );
      msgOutif( MSG__NORM, " ", "      NDF: ^NDF (created)", status );

/* Temporarily terminate the NDF path at the end of the subdirectory. */
      tilendf[ dirlen ] = 0;

/* Create the required directory (does nothing if the directory
   already exists).  It is given read/write/search permissions for owner
   and group, and read/search permissions for others. */
      (void) mkdir( tilendf, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH );

/* Replace the character temporarily overwritten above. */
      tilendf[ dirlen ] = '/';

/* Now create the tile's NDF. */
      ndfPlace( NULL, tilendf, &place, status );
      ndfNew( skytiling.type, 2, lbnd, ubnd, &place, &indf1, status );

/* Fill its data array with zeros. */
      ndfMap( indf1, "Data", skytiling.type, "WRITE/ZERO", &pntr, &el,
              status );

/* Store the WCS FrameSet. */
      ndfPtwcs( fs, indf1, status );

/* If the instrument jsatiles.have variance, fill the variance array with zeros. */
      if( skytiling.var ) {
         ndfMap( indf1, "Variance", skytiling.type, "WRITE/ZERO", &pntr,
                 &el, status );
      }

/* Create a SMURF extension. */
      ndfXnew( indf1, SMURF__EXTNAME, SMURF__EXTTYPE, 0, NULL, &xloc, status );

/* Store the tile number and instrument name in the extension. */
      datNew0I( xloc, "TILE", status );
      datFind( xloc, "TILE", &cloc, status );
      datPut0I( cloc, itile, status );
      datAnnul( &cloc, status );

      datNew0C( xloc, "INSTRUMENT", strlen( skytiling.name ), status );
      datFind( xloc, "INSTRUMENT", &cloc, status );
      datPut0C( cloc, skytiling.name, status );
      datAnnul( &cloc, status );

/* Create a weights NDF within the SMURF extension, and fill its data
   array with zeros. */
      ndfPlace( xloc, "WEIGHTS", &place, status );
      ndfNew( skytiling.type, 2, lbnd, ubnd, &place, &indf2, status );
      ndfMap( indf2, "Data", skytiling.type, "WRITE/ZERO", &pntr, &el,
              status );
      ndfPtwcs( fs, indf2, status );
      ndfAnnul( &indf2, status );

/* Annul the extension locator and the main NDF identifier. */
      datAnnul( &xloc, status );
      ndfAnnul( &indf1, status );

/* Write the NDF info to the screen. */
   } else {
      msgSetc( "NDF",  tilendf );
      msgSetc( "E",  exists ? "exists" : "does not exist" );
      msgOut( " ", "      NDF: ^NDF (^E)", status );
   }

/* Initialise TBND and TLBND to indicate no overlap. */
   tlbnd[ 0 ] = 1;
   tlbnd[ 1 ] = 1;
   tubnd[ 0 ] = 0;
   tubnd[ 1 ] = 0;

/* Attempt to to get an AST Region (assumed to be in some 2D sky coordinate
   system) using parameter "TARGET". */
   if( *status == SAI__OK ) {
      kpg1Gtobj( "TARGET", "Region",
                 (void (*)( void )) F77_EXTERNAL_NAME(ast_isaregion),
                 &obj, status );

/* Annul the error if none was obtained. */
      if( *status == PAR__NULL ) {
         errAnnul( status );

/* Otherwise, use the supplied object. */
      } else {
         target = (AstRegion *) obj;

/* If the target Region is 3-dimensional, remove the third axis, which
   is assumed to be a spectral axis. */
         if( astGetI( target, "Naxes" ) == 3 ) {
            axes[ 0 ] = 1;
            axes[ 1 ] = 2;
            target = astPickAxes( target, 2, axes, NULL );
         }

/* See if there is any overlap between the target and the tile. */
         overlap = NULL;
         flag = astOverlap( region, target );

         if( flag == 0 ) {
            msgOut( "", "      Cannot convert between the coordinate system of the "
                    "supplied target and the tile.", status );

         } else if( flag == 1 || flag == 6 ) {
            msgOut( "", "      There is no overlap between the target and the tile.",
                    status );

         } else if( flag == 2 ) {
            msgOut( "", "      The tile is contained within the target.",
                    status );
            tlbnd[ 0 ] = lbnd[ 0 ];
            tlbnd[ 1 ] = lbnd[ 1 ];
            tubnd[ 0 ] = ubnd[ 0 ];
            tubnd[ 1 ] = ubnd[ 1 ];

         } else if( flag == 3 ) {
            overlap = astCmpRegion( region, target, AST__AND, " " );

         } else if( flag == 4 ) {
            overlap = astCmpRegion( region, target, AST__AND, " " );

         } else if( flag == 5 ) {
            msgOut( "", "      The target and tile are identical.",
                    status );
            tlbnd[ 0 ] = lbnd[ 0 ];
            tlbnd[ 1 ] = lbnd[ 1 ];
            tubnd[ 0 ] = ubnd[ 0 ];
            tubnd[ 1 ] = ubnd[ 1 ];

         } else if( *status == SAI__OK ) {
            *status = SAI__OK;
            errRepf( "", "Unexpected value %d returned by astOverlap "
                     "(programming error).", status, flag );
         }

/* If a region containing the intersection of the tile and target was
   created above, map it into the grid coordinate system of the tile. */
         if( overlap ) {
            overlap = astMapRegion( overlap, astGetMapping( fs, AST__CURRENT,
                                                            AST__BASE ),
                                    astGetFrame( fs, AST__BASE ) );

/* Get its GRID bounds. */
            astGetRegionBounds( overlap, dlbnd, dubnd );

/* Convert to integer. */
            tlbnd[ 0 ] = ceil( dlbnd[ 0 ] - 0.5 );
            tlbnd[ 1 ] = ceil( dlbnd[ 1 ] - 0.5 );
            tubnd[ 0 ] = ceil( dubnd[ 0 ] - 0.5 );
            tubnd[ 1 ] = ceil( dubnd[ 1 ] - 0.5 );

/* Convert to PIXEL indices within the tile. */
            tlbnd[ 0 ] += lbnd[ 0 ] - 1;
            tlbnd[ 1 ] += lbnd[ 1 ] - 1;
            tubnd[ 0 ] += lbnd[ 0 ] - 1;
            tubnd[ 1 ] += lbnd[ 1 ] - 1;

            msgOutf( "", "      The target overlaps section (%d:%d,%d:%d).",
                     status, tlbnd[ 0 ], tubnd[ 0 ], tlbnd[ 1 ], tubnd[ 1 ] );
         }
      }
   }

/* Store the pixel index bounds of the tiles overlap with the target. */
   parPut1i( "TLBND", 2, tlbnd, status );
   parPut1i( "TUBND", 2, tubnd, status );

/* Arrive here if an error occurs. */
   L999:;

/* Free resources. */
   tilendf = astFree( tilendf );

/* End the AST context. */
   astEnd;

/* Issue a status indication.*/
   msgBlank( status );
   if( *status == SAI__OK ) {
      msgOutif( MSG__VERB, "", "JSATILEINFO succeeded.", status);
   } else {
      msgOutif( MSG__VERB, "", "JSATILEINFO failed.", status);
   }
}
Exemplo n.º 3
0
void atlGetPixelParams( AstFrameSet *fset, int *dims, int degs,
                        double *crpix, double *crval, double *cdelt,
                        double *crota, int *status ){
/*
*+
*  Name:
*     atlGetPixelParams

*  Purpose:
*     Find typical values for "FITS-like" parameters describing a FrameSet.

*  Invocation:
*     void atlGetPixelParams( AstFrameSet *fset, int *dims, int degs,
*                             double *crpix, double *crval, double *cdelt,
*                             double *crota, int *status )

*  Description:
*     This function finds values that resemble the the FITS keywords
*     CRVAL1/2/3.., CRPIX1/2/3..., CRDELT1/2/3... and CROTA2, on the
*     assumption that the base Frame in the supplied FrameSet describe
*     GRID coords (i.e. FITS pixel coords), and the current Frame describe
*     the required WCS.  It is not restricted to 2D FrameSets.
*
*     If the FrameSet can be written to a FitsChan successfully using
*     FITS-WCS encoding, the the resulting keyword values are returned.
*     Otherwise, the values are estimated by transforming closely spaced
*     pixel positions along each axis. If the current Frame contains a
*     SkyFrame, and the SkyFrame has a defined reference position, then
*     this position specifies the returned CRVAL values. Otherwise, the
*     reference position is assumed to be at the central pixel.

*  Arguments:
*     fset
*        The FrameSet.
*     dims
*        Pointer to an array supplied holding the number of pixels along
*        each edge of the pixel array. The number of elements in this array
*        should match the number of axes in the base Frame of "fset".
*     degs
*        If non-zero, then the crval, cdelt and crota values for sky axes
*        are returned in units of degrees. Otherwise they are returned in
*        radians.
*     crpix
*        Pointer to an array returned holding the position of the
*        reference pixel in the base Frame of "fset". The number of
*        elements in this array should match the number of axes in the base
*        Frame of "fset".
*     crval
*        Pointer to an array returned holding the position of the
*        reference pixel in the current Frame of "fset". The number of
*        elements in this array should match the number of axes in the
*        current Frame of "fset".
*     cdelt
*        Pointer to an array returned holding the geodesic distance
*        along each edge of the reference pixel, measured within the
*        current Frame of "fset". The number of elements in this array
*        should match the number of axes in the base Frame of "fset".
*     crota
*        Pointer to a double in which to return the angle from north in
*        the current frame of "fset" to the second spatial pixel axis,
*        measured positive through east. This will be returned set to
*        AST__BAD if the current frame of "fset" does not contain a SkyFrame.
*     status
*        The global status.

*  Copyright:
*     Copyright (C) 2013 Science & Technology Facilities Council.
*     All Rights Reserved.

*  Licence:
*     This program is free software; you can redistribute it and/or
*     modify it under the terms of the GNU General Public License as
*     published by the Free Software Foundation; either version 2 of
*     the License, or (at your option) any later version.
*
*     This program is distributed in the hope that it will be
*     useful,but WITHOUT ANY WARRANTY; without even the implied
*     warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
*     PURPOSE. See the GNU General Public License for more details.
*
*     You should have received a copy of the GNU General Public License
*     along with this program; if not, write to the Free Software
*     Foundation, Inc., 51 Franklin Street,Fifth Floor, Boston, MA
*     02110-1301, USA

*  Authors:
*     DSB: David S. Berry (JAC, Hawaii)
*     {enter_new_authors_here}

*  History:
*     13-DEC-2013 (DSB):
*        Original version.
*     {enter_further_changes_here}
*-
*/


/* Local Variables: */
   AstFitsChan *fc;
   AstMapping *map;
   int npix;
   int nwcs;
   int lataxis;
   int lonaxis;
   char name[20];
   const char *cval;
   int ipix;
   double dval1;
   double dval2;
   int ival;
   int iwcs;
   double pixpos[ ATL__MXDIM ];
   double wcspos[ ATL__MXDIM ];
   int pixaxes[ ATL__MXDIM ];
   int wcsaxes[ ATL__MXDIM ];
   int skyaxis1;
   int skyaxis2;

/* Initialise returned values. */
   *crota = AST__BAD;

/* Check the inherited status. */
   if( *status != SAI__OK ) return;

/* Begin an AST context so that all AST objects created in this function
   are freed automatically when the context is ended. */
   astBegin;

/* Get the number of pixel axes (base frame) and wcs axes (current
   frame). */
   npix = astGetI( fset, "Nin" );
   if( npix > ATL__MXDIM && *status == SAI__OK ) {
      *status = SAI__ERROR;
      errRepf( "", "atlGetPixelParams: Too many pixel axes (%d). Must be "
               "no more than %d.", status, npix, ATL__MXDIM );
   }

   nwcs = astGetI( fset, "Nout" );
   if( nwcs > ATL__MXDIM && *status == SAI__OK ) {
      *status = SAI__ERROR;
      errRepf( "", "atlGetPixelParams: Too many WCS axes (%d). Must be "
               "no more than %d.", status, nwcs, ATL__MXDIM );
   }

/* Attempt to find a pair of sky axes in the current Frame by checking
   the "Domain" value for each WCS axis. */
   lataxis = -1;
   lonaxis = -1;
   skyaxis1 = -1;
   skyaxis2 = -1;

   for( iwcs = 0; iwcs < nwcs; iwcs++ ) {
      sprintf( name, "Domain(%d)", iwcs + 1 );
      cval = astGetC( fset, name );
      if( cval && !strcmp( cval, "SKY" ) ) {

/* Determine if this sky axis is a longitude or latitude axis, and record
   the zero-based indicies of the longitude and latitude axes. */
         sprintf( name, "IsLatAxis(%d)", iwcs + 1 );
         ival = astGetI( fset, name );
         if( ival ) {
            lataxis = iwcs;
         } else {
            lonaxis = iwcs;
         }
      }
   }

/* If a pair of sky axes were found in the current Frame, get the indices
   of the corresponding pair of pixel axes. */
   if( lonaxis >= 0 && lataxis >= 0 ) {

/* If there are only two pixel axes, they must be the sky axes. */
      if( nwcs == 2 ) {
         skyaxis1 = 0;
         skyaxis2 = 1;

/* If there are more than two pixel axes, we need to work harder. */
      } else {

/* Use astMapSplit to find the two pixel axes that feed the two sky axes.
   astMapSplit identifes outputs corresponding to specified mapping inputs,
   so we need to invert the FrameSet first so that the WCS Frame becomes
   the input (i.e. base Frame). Remember to un-invert the FrameSet
   afterwards. */
         astInvert( fset );
         wcsaxes[ 0 ] = lataxis + 1;
         wcsaxes[ 1 ] = lonaxis + 1;
         astMapSplit( fset, 2, wcsaxes, pixaxes, &map );
         astInvert( fset );

/* If the wcs->pixel mapping was split succesfully, the pixaxes array
   will contain the one-based pixel axes that feed the sky axes. Convert
   them to zero-based and note the lowest and highest. */
         if( map && astGetI( map, "Nout" ) == 2 ) {
            if( pixaxes[ 0 ] < pixaxes[ 1 ] ) {
               skyaxis1 = pixaxes[ 0 ] - 1;
               skyaxis2 = pixaxes[ 1 ] - 1;
            } else {
               skyaxis1 = pixaxes[ 1 ] - 1;
               skyaxis2 = pixaxes[ 0 ] - 1;
            }

/* If it could not be split, it means the spatial and non-spatial axes are
   tangle up by the pixel->wcs mapping to such an extent that they cannot
   be separated. */
         } else if( *status == SAI__OK ) {
            *status = SAI__ERROR;
            errRepf( "", "atlGetPixelParams: Cannot separate the spatial "
                     "axes from the non-spatial axes.", status );
         }
      }
   }

/* Attempt to write the supplied FrameSet to FitsChan using FITS-WCS
   encoding. If successful, retrieve the required values. Convert sky
   values from degrees to radians if required. */
   fc = astFitsChan( NULL, NULL, "Encoding=FITS-WCS" );
   if( astWrite( fc, fset ) == 1 ) {

      for( ipix = 0; ipix < npix; ipix++ ) {
         sprintf( name, "CRPIX%d", ipix + 1 );
         if( !astGetFitsF( fc, name, crpix + ipix ) && *status == SAI__OK ) {
            *status = SAI__ERROR;
            errRepf( "", "atlGetPixelParams: %s not found in FitsChan "
                     "(possible programming error).", status, name );
         }

         sprintf( name, "CDELT%d", ipix + 1 );
         if( !astGetFitsF( fc, name, cdelt + ipix ) && *status == SAI__OK ) {
            *status = SAI__ERROR;
            errRepf( "", "atlGetPixelParams: %s not found in FitsChan "
                     "(possible programming error).", status, name );
         }

         if( !degs && ( ipix == skyaxis1 || ipix == skyaxis2 ) ){
            cdelt[ ipix ] *= AST__DD2R;
         }

      }

      for( iwcs = 0; iwcs < nwcs; iwcs++ ) {

         sprintf( name, "CRVAL%d", iwcs + 1 );
         if( !astGetFitsF( fc, name, crval + iwcs ) && *status == SAI__OK ) {
            *status = SAI__ERROR;
            errRepf( "", "atlGetPixelParams: %s not found in FitsChan "
                     "(possible programming error).", status, name );
         }

         if( !degs && ( iwcs == lonaxis || iwcs == lataxis ) ){
            crval[ iwcs ] *= AST__DD2R;
         }

      }

/* Derive the position angle (within the sky frame) of the second spatial
   pixel axis, based on the PCi_j rotation matrix elements. Note, there is
   no assumption here that the latitude and longitude axes are orthogonal
   in the pixel frame. FITS-WCS allows for shear, so this value says
   nothing about the orientation of the first spatial pixel axis. But in
   practice images nearly always have no shear. */
      if( lataxis >= 0 && lonaxis >= 0 ) {
         sprintf( name, "PC%d_%d", lonaxis + 1, skyaxis1 + 1 );
         if( !astGetFitsF( fc, name, &dval1 ) && *status == SAI__OK ) {
            dval1 = ( lonaxis == skyaxis1 ) ? 1.0 : 0.0;
         }

         sprintf( name, "PC%d_%d", lonaxis + 1, skyaxis2 + 1 );
         if( !astGetFitsF( fc, name, &dval2 ) && *status == SAI__OK ) {
            dval2 = ( lonaxis == skyaxis2 ) ? 1.0 : 0.0;
         }

         *crota = atan2( dval2, dval1 );
         if( *crota < 0.0 ) *crota += 2*AST__DPI;
         if( degs ) *crota *= AST__DR2D;
      }

/* If the supplied FrameSet could not be converted to a set of
   FITS-WCS keywords, we derive similar values by looking at small
   increments of pixel position. */
   } else {

/* First job is to decide on the reference position. By default we use
   the central pixel. Store the corresponding pixel coords. */
      for( ipix = 0; ipix < npix; ipix++ ) {
         crpix[ ipix ] = ( 1.0 + dims[ ipix ] )/2.0;
      }

/* Convert this pixel position to the WCS Frame. */
      astTranN( fset, 1, npix, 1, crpix, 1, nwcs, 1, crval );

/* If the current Frame contains a pair of sky axes, then the associated
   SkyFrame may include a reference position. If so, we will use it
   instead of the central pixel. First test to see if the SkyFrame has a
   reference position. If so get the reference longitude and latitude in
   radians, and convert the new reference position back to pixel coords. */
      if( lonaxis >= 0 && lataxis >= 0 ) {
         sprintf( name, "SkyRef(%d)", lonaxis + 1 );
         if( astTest( fset, name ) ) {
            crval[ lonaxis ] = astGetD( fset, name );
            sprintf( name, "SkyRef(%d)", lataxis + 1 );
            crval[ lataxis ] = astGetD( fset, name );

            astTranN( fset, 1, nwcs, 1, crval, 0, npix, 1, crpix );

/* If we have sky axes but the skyframe has no reference position, we
   need to check that the central pixel is a good default. For instance,
   it may be off the edge of an all-sky map, in which case it is no good
   as a reference position. */
         } else if( ( crval[ lonaxis ] == AST__BAD ||
                      crval[ lataxis ] == AST__BAD ) && *status == SAI__OK ) {
            *status = SAI__ERROR;
            errRepf( "", "atlGetPixelParams: No reference position can be "
                     "determined.", status );
         }
      }

/* Normalize the reference position. */
      astNorm( fset, crval );

/* Now we find the pixel size on each pixel axis. First take a copy of
   the pixel reference position. */
      memcpy( pixpos, crpix, npix*sizeof( *pixpos ) );
      for( ipix = 0; ipix < npix; ipix++ ) {

/* Store a pixel position which is offset away from the reference position
   by one pixel along the current pixel axis, and then transform it into
   the WCS Frame. */
         pixpos[ ipix ] += 1.0;
         astTranN( fset, 1, npix, 1, pixpos, 1, nwcs, 1, wcspos );
         pixpos[ ipix ] -= 1.0;

/* Find the geodesic distance between this WCS position and the reference
   position. */
         cdelt[ ipix ] = astDistance( fset, crval, wcspos );
      }

/* Find the crota value if we have a pair of sky axes. */
      if( lonaxis >= 0 && lataxis >= 0 ){

/* Get a WCS position about one arc-second north of the reference position. */
         memcpy( wcspos, crval, nwcs*sizeof( *wcspos ) );
         wcspos[ lataxis ] += 5.0E-6;

/* Transform to pixel coordinates. */
         astTranN( fset, 1, nwcs, 1, wcspos, 0, npix, 1, pixpos );

/* Get the required angle. */
         *crota = atan2( pixpos[ skyaxis1 ] - crpix[ skyaxis1 ],
                         pixpos[ skyaxis1 ] - crpix[ skyaxis2 ] );
         if( *crota < 0.0 ) *crota += 2*AST__DPI;
         if( !degs ) *crota *= AST__DR2D;
      }

/* Convert the returned angles to degrees if required. */
      if( !degs && ( lonaxis >= 0 && lataxis >= 0 ) ){
         crval[ lataxis ] *= AST__DR2D;
         crval[ lonaxis ] *= AST__DR2D;
         cdelt[ skyaxis1 ] *= AST__DR2D;
         cdelt[ skyaxis2 ] *= AST__DR2D;
      }
   }

/* End the AST context. This will annull all AST objects created in this
   function. */
   astEnd;

}