Logo Search packages:      
Sourcecode: saods9 version File versions

fitschan.c

/*
*class++
*  Name:
*     FitsChan

*  Purpose:
*     I/O Channel using FITS header cards to represent Objects.

*  Constructor Function:
c     astFitsChan
f     AST_FITSCHAN

*  Description:
*     A FitsChan is a specialised form of Channel which supports I/O
*     operations involving the use of FITS (Flexible Image Transport
*     System) header cards. Writing an Object to a FitsChan (using
c     astWrite) will, if the Object is suitable, generate a
f     AST_WRITE) will, if the Object is suitable, generate a
*     description of that Object composed of FITS header cards, and
*     reading from a FitsChan will create a new Object from its FITS
*     header card description.
*
*     While a FitsChan is active, it represents a buffer which may
*     contain zero or more 80-character "header cards" conforming to
*     FITS conventions. Any sequence of FITS-conforming header cards
*     may be stored, apart from the "END" card whose existence is
*     merely implied.  The cards may be accessed in any order by using
*     the FitsChan's integer Card attribute, which identifies a "current"
*     card, to which subsequent operations apply. Searches
c     based on keyword may be performed (using astFindFits), new
c     cards may be inserted (astPutFits, astPutCards) and existing ones may be
c     deleted (astDelFits).
f     based on keyword may be performed (using AST_FINDFITS), new
f     cards may be inserted (AST_PUTFITS, AST_PUTCARDS) and existing ones may be
f     deleted (AST_DELFITS).
*
*     When you create a FitsChan, you have the option of specifying
*     "source" and "sink" functions which connect it to external data
*     stores by reading and writing FITS header cards. If you provide
*     a source function, it is used to fill the FitsChan with header
*     cards when it is created. If you do not provide a source
*     function, the FitsChan remains empty until you explicitly enter
c     data into it (e.g. using astPutFits, astPutCards or astWrite). If you
f     data into it (e.g. using AST_PUTFITS, AST_PUTCARDS or AST_WRITE). If you
*     provide a sink function, it is used to deliver any remaining
*     contents of a FitsChan to an external data store when the
*     FitsChan is deleted. If you do not provide a sink function, any
*     header cards remaining when the FitsChan is deleted will be
*     lost, so you should arrange to extract them first if necessary
c     (e.g. using astFindFits or astRead).
f     (e.g. using AST_FINDFITS or AST_READ).
*     
*     Coordinate system information may be described using FITS header
*     cards using several different conventions, termed
*     "encodings". When an AST Object is written to (or read from) a
*     FitsChan, the value of the FitsChan's Encoding attribute
*     determines how the Object is converted to (or from) a
*     description involving FITS header cards. In general, different
*     encodings will result in different sets of header cards to
*     describe the same Object. Examples of encodings include the DSS
*     encoding (based on conventions used by the STScI Digitised Sky
*     Survey data), the FITS-WCS encoding (based on a proposed FITS
*     standard) and the NATIVE encoding (a near loss-less way of
*     storing AST Objects in FITS headers).
*
*     The available encodings differ in the range of Objects they can
*     represent, in the number of Object descriptions that can coexist
*     in the same FitsChan, and in their accessibility to other
*     (external) astronomy applications (see the Encoding attribute
*     for details). Encodings are not necessarily mutually exclusive
*     and it may sometimes be possible to describe the same Object in
*     several ways within a particular set of FITS header cards by
*     using several different encodings.
*
c     The detailed behaviour of astRead and astWrite, when used with
f     The detailed behaviour of AST_READ and AST_WRITE, when used with
*     a FitsChan, depends on the encoding in use. In general, however,
c     all use of astRead is destructive, so that FITS header cards
f     all use of AST_READ is destructive, so that FITS header cards
*     are consumed in the process of reading an Object, and are
*     removed from the FitsChan.
*
*     If the encoding in use allows only a single Object description
*     to be stored in a FitsChan (e.g. the DSS, FITS-WCS and FITS-IRAF
c     encodings), then write operations using astWrite will
f     encodings), then write operations using AST_WRITE will
*     over-write any existing Object description using that
*     encoding. Otherwise (e.g. the NATIVE encoding), multiple Object
*     descriptions are written sequentially and may later be read
*     back in the same sequence.

*  Inheritance:
*     The FitsChan class inherits from the Channel class.

*  Attributes:
*     In addition to those attributes common to all Channels, every
*     FitsChan also has the following attributes:
*
*     - AllWarnings: A list of the available conditions
*     - Card: Index of current FITS card in a FitsChan
*     - CarLin: Ignore spherical rotations on CAR projections?
*     - CDMatrix: Use a CD matrix instead of a PC matrix?
*     - Clean: Remove cards used whilst reading even if an error occurs?
*     - DefB1950: Use FK4 B1950 as default equatorial coordinates?
*     - Encoding: System for encoding Objects as FITS headers
*     - FitsDigits: Digits of precision for floating-point FITS values
*     - Iwc: Add a Frame describing Intermediate World Coords?
*     - Ncard: Number of FITS header cards in a FitsChan
*     - Warnings: Produces warnings about selected conditions

*  Functions:
c     In addition to those functions applicable to all Channels, the
c     following functions may also be applied to all FitsChans:
f     In addition to those routines applicable to all Channels, the
f     following routines may also be applied to all FitsChans:
*
c     - astDelFits: Delete the current FITS card in a FitsChan
f     - AST_DELFITS: Delete the current FITS card in a FitsChan
c     - astFindFits: Find a FITS card in a FitsChan by keyword
f     - AST_FINDFITS: Find a FITS card in a FitsChan by keyword
c     - astPutFits: Store a FITS header card in a FitsChan
f     - AST_PUTFITS: Store a FITS header card in a FitsChan
c     - astPutCards: Stores a set of FITS header card in a FitsChan
f     - AST_PUTCARDS: Stores a set of FITS header card in a FitsChan

*  Copyright:
*     Copyright (C) 2004 Central Laboratory of the Research Councils

*  Authors:
*     DSB: David Berry (Starlink)
*     RFWS: R.F. Warren-Smith (Starlink, RAL)

*  History:
*     11-DEC-1996 (DSB):
*        Original version.
*     20-MAR-1997 (DSB):
*        Made keyword setting and getting functions protected instead of
*        public. Renamed public methods. Added Ncard attribute.
*     20-MAY-1997 (RFWS):
*        Tidied public prologues.
*     30-JUN-1997 (DSB):
*        Added support for reading post-2000 DATE-OBS strings. Reading DSS
*        or FITS-WCS objects now returns NULL unless the FitsChan is
*        positioned at the start-of-file prior to the read. Bug fixed
*        which caused Ncard to be returned too large by one. Removed
*        dependancy on hard-wired header and footer text in Native
*        FitsChans.
*     18-AUG-1997 (DSB):
*        Bug fixed in WcsNative which caused incorrect CRVAL values
*        to be used if the axes needed permuting. Values assigned to the
*        Projection attribute fo the SkyFrames created by astRead.
*     2-SEP-1997 (DSB):
*        Added the IRAF convention that EPOCH=0.0 really means EPOCH=1950.0
*        (the EPOCH keyword is deprecated in the new FITS-WCS conventions
*        and is taken always as a Besselian epoch).
*     19-SEP-1997 (DSB):
*        Corrected interpretation of the FITS CD matrix. 
*     25-SEP-1997 (DSB):
*        o  Fix bug in LinearMap which caused it always to detect a linear
*        mapping. For instance, this allowed DssMaps to be erroneously 
*        written out using FITS-WCS encoding with a CAR projection.
*        o  Assign a full textual description to SkyFrame's Projection
*        attribute instead of a 3 letter acronym.
*        o  If DATE-OBS >= 1999.0 then DATE-OBS is now written in new 
*        Y2000 format. For DATE-OBS < 1999.0, the old format is written.
*        o  Add new attribute CDMatrix to determine whether PC or CD
*        matrices should be used when writing objects using FITS-WCS
*        encoding.
*        o  Modified the way floating point values are formatted to omit
*        unnecessary leading zeros from the exponent (i.e. E-5 instead of
*        E-05).
*        o  New-line characters at the end of supplied header cards are now 
*        ignored.
*        o  Cater for EQUINOX specified as a string prefixed by B or J
*        rather than as a floating point value (some HST data does this).
*        o  Corrected SetValue so that it always inserts comment cards 
*        rather than over-write existing comment cards. Previously,
*        writing a FrameSet to a DSS encoded FitsChan resulted in all
*        comments cards being stripped except for the last one.
*        o  Reading a FrameSet from a DSS-encoded FrameSet now only
*        removes the keywords actually required to construct the FrameSet.
*        Previously, all keywords were removed.
*        o  The EPOCH and EQUINOX keywords created when a FrameSet is
*        written to a DSS-encoded FitsChan are now determined from the 
*        epoch and equinox of the current Frame, instead of from a copy
*        of the original FitsChan stored within the DssMap.
*        o  The Encoding and CDMatrix attributes, and keyword types are 
*        now stored as strings externally instead of integers.
*     11-NOV-1997 (DSB):
*        o  Assume default of j2000 for DSS EQUINOX value.
*        o  Check for null object pointers in the interfaces for 
*        virtual functions which execute even if an error has previously
*        occurred. Otherwise, a segmentation violation can occur when 
*        trying to find the member function pointer.
*        o  Trailing spaces ignored in Encoding attribute.
*        o  Bugs fixed in FindWcs and SetValue which resulted in WCS cards
*        being written at the wrong place if the supplied FitsChan does not 
*        contain any WCS keywords.
*        o  Default for CDMatrix (if no axis rotation keywords can be found) 
*        changed to 2 (i.e. use "CDi_j" form keywords).
*        o  Write now leaves the current card unchanged if nothing is 
*        written to the FitsChan.
*     17-NOV-1997 (RFWS):
*        Disabled use of CDmatrix. Fixed initialisation problems in
*        astLoadFitsChan.
*     24-NOV-1997 (DSB):
*        Replace references to error code AST__OPT with AST__RDERR.
*     28-NOV-1997 (DSB):
*        o  Function WcsValues modified to prevent it from changing the 
*        current card. Previously, this could cause new cards to be 
*        written to the wrong place in a FITS-WCS encoded FitsChan.
*        o  Description of argument "value" corrected in prologue of
*        function FitsSet.
*        o  Argument "lastkey" removed from function SetValue since it
*        was never used (it was a relic from a previous method of
*        determining where to store new cards). Corresponding changes 
*        have been made to all the functions which create "lastkey" values 
*        or pass them on to SetValue (i.e DescWcs, WcsPrimary, WcsSecondary, 
*        WriteWcs and WriteDss).
*     10-DEC-1997 (DSB):
*        Bug fixed which caused the initial character designating the system 
*        within CTYPE value (eg E in ELON, G in GLON, etc) to be omitted.
*     1-JUN-1998 (DSB):
*        CDELT values of zero are now replaced by a small non-zero value
*        when creating the "pixel-to-relative physical" transformation
*        matrix. Previously, zero CDELT values could cause the matrix to
*        be non-invertable.
*     4-SEP-1998 (DSB):
*        - Indicate that SphMaps created by this class when using FITS-WCS
*        encoding all operate on the unit sphere. This aids simplification.
*        - Fix a bug in StoreFits which caused CD matrices to be indexed 
*        incorrectly (sometimes causing floating exceptions) if they do not
*        describe a celestial longitude/latitude system.
*        - Changed astFindFits to ignore trailing spaces in the keyword 
*        template.
*        - astSplit changed so that an error is not reported if a textual
*        keyword value ends before column 20.
*     7-OCT-1998 (DSB):
*        - Corrected test for linearity in LinearMap to include a factor
*        of the test vector length. Also LinearMap now uses a simplified 
*        Mapping.
*     5-NOV-1998 (DSB):
*        Added FITS-IRAF encoding.
*     9-NOV-1998 (DSB):
*        - Corrected values of macros DSS_ENCODING and MAX_ENCODING.
*        - Corrected erroneous success indication in IrafStore.
*        - Included checks for bad values in function LinearMap.
*     17-NOV-1998 (DSB):
*        The Domain name GRID is now given to the Base Frame in any FrameSets
*        created by astRead when using FitsChans with DSS, FITS-WCS or
*        FITS-IRAF encodings.
*     18-DEC-1998 (DSB):
*        Check for "D" exponents in floating point keyword strings.
*     12-FEB-1998 (DSB):
*        Modified EncodeFloat to avoid exceeding the 20 character FITS 
*        limit wherever possible if FitsDigits is positive.
*     10-MAY-1998 (DSB):
*        Bug fixed in astSplit which caused comments associated with string
*        keywords to be lost when storing the card in a FitsChan.
*     15-JUN-1999 (DSB):
*        Report an error if an unrecognised projection name is supplied.
*     9-DEC-1999 (DSB):
*        - Fixed bug in WcsNatPole which could result in longitude values
*        being out by 180 degrees for cylindrical projections such as CAR.
*        - Only report an "unrecognised projection" error for CTYPE values
*        which look like celestial longitude or latitude axes (i.e. if the 
*        first 4 characters are "RA--", "DEC-", "xLON" or "xLAT", and the
*        fifth character is "-").
*        - Added function SpecTrans to translated keywords related to the 
*        IRAF ZPX projection into keyword for the standard ZPN projection.
*        - Add ICRS as a valid value for the RADECSYS keyword. Since the
*        SkyFrame class does not yet support ICRS, an FK5 SkyFrame is
*        created if RADECSYS=ICRS.
*     16-DEC-1999 (DSB):
*        - Modified SpecTrans so that all keywords used to created a 
*        standard WCS representation from a non-standard one are consumed 
*        by the astRead operation.
*        - Changed the text of ASTWARN cards added to the FitsChan if an
*        IRAF ZPX projection is found to require unsupported corrections.
*        - Simplified the documentation describing the handling of the IRAF 
*        ZPX projection.
*        - Fixed code which assumed that the 10 FITS-WCS projection
*        parameters were PROJP1 -> PROJP10. In fact they are PROJP0 -
*        PROJP9. This could cause projection parameter values to be
*        incorrectly numbered when they are written out upon deletion of 
*        the FitsChan.
*     1-FEB-2000 (DSB):
*        Check that FITS_IRAF encoding is not being used before using a
*        PC matrix when reading WCS information from a header. This is
*        important if the header contains both PC and CD matrices.
*     8-FEB-2000 (DSB):
*        - Header cards are now only consumed by an astRead operation if the
*        operation succeeds (i.e. returns a non-null Object).
*        - The original FITS-WCS encoding has been renamed as FITS-PC (to
*        indicate the use of a PCiiijjj matrix), and a new FITS-WCS
*        encoding has been added.
*        - The disabled CDMatrix attribute has been removed.
*        - Bug in LinearMap corrected which prevented genuinely linear 
*        Mappings from being judged to be linear. This bug was previously
*        fudged (so it now appears) by the introduction of the test vector
*        length factor (see History entry for 7-OCT-1998). This test
*        vector length scale factor has consequently now been removed.
*        - Added FITS-AIPS encoding.
*        - The critical keywords used to select default encoding have been
*        changed.
*        - Support for common flavours of IRAF TNX projections added.
*        - The algorithm used to find a WcsMap in the supplied FrameSet 
*        has been improved so that compound Mappings which contain complex
*        mixtures of parallel and serial Mappings can be translated into
*        FITS-WCS encoding.
*        - Trailing white space in string keyword values is now retained
*        when using foreign encodings to enable correct concatenation where 
*        a string has been split over several keywords. E.g. if 2 string 
*        keywords contain a list of formatted numerical values (e.g. IRAF 
*        WAT... keywords), and the 1st one ends "0.123 " and the next one 
*        begins "1234.5 ", the trailing space at the end of the first keyword 
*        is needed to prevent the two numbers being merged into "0.1231234.5". 
*        Trailing spaces in native encodings is still protected by enclosing 
*        the whole string in double quotes. 
*        - The Channel methods WriteString and GetNextData can now save
*        and restore strings of arbitary length. This is done by storing
*        as much of the string as possible in the usual way, and then
*        storing any remaining characters in subsequent CONTINUE cards,
*        using the FITSIO conventions. This storage and retrieval of long
*        strings is only available for native encodings.
*     19-MAY-2000 (DSB):
*        Added attribute Warnings. Lowered DSS in the priority list
*        of encodings implemented by GetEncoding.
*     6-OCT-2000 (DSB):
*        Increased size of buffers used to store CTYPE values to take
*        account of the possiblity of lots of trailing spaces.
*     5-DEC-2000 (DSB):
*        Add support for the WCSNAME FITS keyword.
*     12-DEC-2000 (DSB):
*        Add a title to each physical, non-celestial coord Frame based on 
*        its Domain name (if any).
*     3-APR-2001 (DSB):
*        -  Use an "unknown" celestial coordinate system, instead of a
*        Cartesian coordinate system, if the CTYPE keywords specify an
*        unknown celestial coordinate system.
*        -  Do not report an error if there are no CTYPE keywords in the 
*        header (assume a unit mapping, like in La Palma FITS files).
*        -  Add a NoCTYPE warning condition.
*        -  Added AllWarnings attribute.
*        -  Ensure multiple copies of identical warnings are not produced.
*        -  Use the Object Ident attribute to store the identifier letter
*        associated with each Frame read from a secondary axis description,
*        so that they can be given the same letter when they are written
*        out to a new FITS file.
*     10-AUG-2001 (DSB):
*        - Corrected function value returned by SkySys to be 1 unless an
*        error occurs. This error resulted in CAR headers being produced
*        by astWrite with CRVAL and CD values till in radians rather than 
*        degrees.
*        - Introduced SplitMap2 in order to guard against producing
*        celestial FITS headers for a Mapping which includes more than
*        one WcsMap.
*     13-AUG-2001 (DSB):
*        - Modified FixNew so that it retains the current card index if possible.
*        This fixed a bug which could cause headers written out using Native 
*        encodings to be non-contiguous.
*        - Corrected ComBlock to correctly remove AST comment blocks in
*        native encoded fitschans.
*     14-AUG-2001 (DSB:
*        - Modified FixUsed so that it it does not set the current card
*        back to the start of file if the last card in the FitsChan is 
*        deleted.
*     16-AUG-2001 (DSB):
*        Modified WcsNative to limit reference point latitude to range
*        +/-90 degs (previously values outside this range were wrapped
*        round onto the opposite meridian). Also added new warning
*        condition "badlat".
*     23-AUG-2001 (DSB):
*        - Re-write LinearMap to use a least squares fit.
*        - Check that CDj_i is not AST__BAD within WcsWithWcs when
*        forming the increments along each physical axis.
*     28-SEP-2001 (DSB):
*        GoodWarns changed so that no error is reported if a blank list
*        of conditions is supplied.
*     12-OCT-2001 (DSB):
*        - Added DefB1950 attribute.
*        - Corrected equations which calculate CROTA when writing 
*        FITS-AIPS encodings.
*        - Corrected equations which turn a CROTA value into a CD matrix.
*     29-NOV-2001 (DSB):
*        Corrected use of "_" and "-" characters when referring to FK4-NO-E
*        system in function SkySys.
*     20-FEB-2002 (DSB)
*        Added CarLin attribute.
*     8-MAY-2002 (DSB):
*        Correct DSSToStore to ignore trailing blanks in the PLTDECSN
*        keyword value.
*     9-MAY-2002 (DSB):
*        Correct GetCard to avoid infinite loop if the current card has
*        been marked as deleted.
*     25-SEP-2002 (DSB):
*        AIPSFromStore: use larger of coscro and sincro when determining
*        CDELT values. Previously a non-zero coscro was always used, even
*        if it was a s small as 1.0E-17.
*     3-OCT-2002 (DSB):
*        - SkySys: Corrected calculation of longitude axis index for unknown
*        celestial systems.
*        - SpecTrans: Corrected check for latcor terms for ZPX projections.
*        - WcsFrame: Only store an explicit equinox value in a skyframe if 
*        it needs one (i.e. if the system is ecliptic or equatorial).
*        - WcsWithWcs: For Zenithal projections, always use the default
*        LONPOLE value, and absorb any excess rotation caused by this
*        into the CD matrix.
*        - WcsWithWcs: Improve the check that the native->celestial mapping 
*        is a pure rotation, allowing for rotations which change the
*        handed-ness of the system (if possible).
*        - WcsWithWcs: Avoid using LONPOLE keywords when creating headers
*        for a zenithal projection. Instead, add the corresponding rotation 
*        into the CD matrix.
*     22-OCT-2002 (DSB):
*        - Retain leading and trailing white space within COMMENT cards.
*        - Only use CTYPE comments as axis labels if all non-celestial
*          axes have a unique non-blank comment (otherwise use CTYPE
*          values as labels).
*        - Updated to use latest FITS-WCS projections. This means that the
*          "TAN with projection terms" is no longer a standard FITS
*          projection. It is now represented using the AST-specific TPN
*          projection (until such time as FITS-WCS paper IV is finished).
*        - Remove trailing "Z" from DATE-OBS values created by astWrite.
*     14-NOV-2002 (DSB):
*        - WcsWithWcs: Corrected to ignore longitude axis returned by
*        astPrimaryFrame since it does not take into account any axis
*        permutation.
*     26-NOV-2002 (DSB):
*        - SpecTrans: Corrected no. of characters copied from CTYPE to PRJ,
*        (from 5 to 4), and terminate PRJ correctly. 
*     8-JAN-2003 (DSB):
*        Changed private InitVtab method to protected astInitFitsChanVtab
*        method.
*     22-JAN-2003 (DSB):
*        Restructured the functions used for reading FITS_WCS headers to
*        make the distinction between the generic parts (pixel->intermediate 
*        world coordinates) and the specialised parts (e.g. celestial,
*        spectral, etc) clearer.
*     31-JAN-2003 (DSB)
*        - Added Clean attribute.
*        - Corrected initialisation and defaulting of CarLin and DefB1950 
*        attributes.
*        - Extensive changes to allow foreign encodings to be produced in
*        cases where the Base Frame has fewer axes than the Current Frame.
*     12-FEB-2003 (DSB)
*        - Modified FitsSet so that the existing card comment is retained
*        if the new data value equals the existing data value.
*     30-APR-2003 (DSB):
*        - Revert to standard "TAN" code for distorted tan projections,
*        rather than using the "TPN" code. Also recognise QVi_m (produced
*        by AUTOASTROM) as an alternative to PVi_m when reading distorted 
*        TAN headers. 
*     22-MAY-2003 (DSB):
*        Modified GetEncoding so that the presence of RADECSYS and/or
*        PROJPm is only considered significant if the modern equivalent 
*        keyword (REDESYS or PVi_m) is *NOT* present.
*     2-JUN-2003 (DSB):
*        - Added support for PCi_j kewwords within FITS-WCS encoding
*        - Added CDMatrix attribute
*        - Changed internal FitsStore usage to use PC/CDELT instead of CD
*        (as preparation for FITS-WCS paper IV).
*        - Added warning "BadMat".
*     11-JUN-2003 (DSB):
*        - Modified WcsNative to use the new SphMap PolarLong attribute 
*        in order to ensure correct propagation of the longitude CRVAL
*        value in cases where the fiducial point is coincident with a pole.
*        - Use PVi_3 and PVi_4 for longitude axis "i" (if present) in 
*        preference to LONPOLE and LATPOLE when reading a FITS-WCS header. 
*        Note, these projection values are never written out (LONPOLE and 
*        LATPOLE are written instead).
*        - Associate "RADESYS=ICRS" with SkyFrame( "System=ICRS" ), rather
*        than SkyFrame( "System=FK5" ).
*        - If DefB1950 is zero, use ICRS instead of FK5 as the default RADESYS 
*        if no EQUINOX is present.
*     1-SEP-2003 (DSB):
*        - Modify Dump so that it dumps all cards including those flagged as 
*        having been read.
*        - Added "reset" parameter to FixUsed.
*        - WcsMapFrm: store an Ident of ' ' for the primary coordinate 
*        description (previously Ident was left unset)
*        - Default value for DefB1950 attribute now depends on the value
*        of the Encoding attribute. 
*     15-SEP-2003 (DSB):
*        - Added Warnings "BadVal", "Distortion".
*        - Ignore FITS-WCS paper IV CTYPE distortion codes (except for
*          "-SIP" which is interpreted correctly on reading).
*     22-OCT-2003 (DSB):
*        - GetEncoding: If the header contains CDi_j but does not contain
*        any of the old IRAF keywords (RADECSYS, etc) then assume FITS-WCS
*        encoding. This allows a FITS-WCS header to have both CDi_j *and*
*        CROTA keywords.
*     5-JAN-2004 (DSB):
*        - SpecTrans: Use 1.0 (instead of the CDELT value) as the
*        diagonal PCi_j term for non-celestial axes with associated CROTA 
*        values.  
*     12-JAN-2004 (DSB):
*        - CelestialAxes: Initialise "tmap1" pointer to NULL in case of error 
*        (avoids a segvio happening in the case of an error).
*        - AddVersion: Do not attempt to add a Frame into the FITS header
*        if the mapping from grid to frame is not invertable.
*        - WorldAxes: Initialise the returned "perm" values to safe values, 
*        and return these values if no basis vectors cen be created.
*     19-JAN-2004 (DSB):
*        - When reading a FITS-WCS header, allow all keywords to be defaulted 
*        as decribed in paper I.
*     27-JAN-2004 (DSB):
*        - Modify FitLine to use correlation between actual and estimated
*        axis value as the test for linearity.
*        - Modify RoundFString to avoid writing beyond the end of the
*        supplied buffer if the supplied string contains a long list of 9's.
*     11-MAR-2004 (DSB):
*        - Modified SpecTrans to check all axis descriptions for keywords
*        to be translated.
*     19-MAR-2004 (DSB):
*        - Added astPutCards to support new fits_hdr2str function in
*        CFITSIO.
*     25-MAR-2004 (DSB):
*        - Corrected bug in astSplit which causes legal cards to be
*        rejected because characters beyond the 80 char limit are being
*        considered significant.
*        - Corrected bug in SpecTrans which caused QV keywords to be
*        ignored.
*     15-APR-2004 (DSB):
*        - SpecTrans modified to include translation of old "-WAV", "-FRQ"
*        and "-VEL" spectral algorithm codes to modern "-X2P" form.
*        - WcsFromStore modified to supress creation of WCSAXES keywords
*        for un-used axis versions.
*        - IsMapLinear modified to improve fit by doing a second least
*        squares fit to the residualleft by the first least squares fit.
*     16-APR-2004 (DSB):
*        - NonLinSpecWcs: Issue a warning if an illegal non-linear
*        spectral code is encountered.
*        - Add a BadCTYPE warning condition.
*        - Corrected default value for Clean so that it is zero (as
*        documented).
*     21-APR-2004 (DSB):
*        - FindWcs: Corrected to use correct OBSGEO template. This bug
*        caused OBSGEO keywords to be misplaced in written headers.
*     23-APR-2004 (DSB):
*        - SplitMap: Modified so that a Mapping which has celestial axes 
*        with constant values (such as produced by a PermMap) are treated 
*        as a valid sky coordinate Mapping.
*        - AddFrame modified so that WCS Frames with a different number
*        of axes ot the pixel Frame can be added into the FrameSet.
*        - IRAFFromStore and AIPSFromStore modified so that they do not
*        create any output keywords if the number of WCS axes is different 
*        to the number of pixel axes. 
*        - Handling of OBSGEO-X/Y/Z corrected again.
*        - WCSFromStore modified to avouid writing partial axis descriptions.
*     26-APR-2004 (DSB):
*        - Corrected text of output SPECSYS keyword values.
*     17-MAY-2004 (DSB):
*        - Added IWC attribute.
*     15-JUN-2004 (DSB):
*        - Ensure out-of-bounds longitude CRPIX values for CAR
*        projections are wrapped back into bounds.
*     21-JUN-2004 (DSB):
*        - Ensure primary MJD-OBS value is used when reading foreign FITS
*        headers.
*     7-JUL-2004 (DSB):
*        - Issue errors if an un-invertable PC/CD matrix is supplied in a
*        FITS-WCS Header.
*     11-JUL-2004 (DSB):
*        - Re-factor code for checking spectral axis CTYPE values into
*        new function IsSpectral.
*        - Modify AIPSFromSTore to create spectral axis keywords if
*        possible.
*        - Modify SpecTrans to recognize AIPS spectral axis keywords, and
*        to convert "HZ" to "Hz".
*        - Added FITS-AIPS++ encoding.

*class--
*/

/* Module Macros. */
/* ============== */
/* Set the name of the class we are implementing. This indicates to
   the header files that define class interfaces that they should make
   "protected" symbols available. */
#define astCLASS FitsChan

/* A macro which tests a character to see if it can be used within a FITS 
   keyword. We include lower case letters here, but they are considered
   as equivalent to upper case letter. */
#define isFits(a) ( islower(a) || isupper(a) || isdigit(a) || (a)=='-' || (a)=='_' )

/* Macros which return the maximum and minimum of two values. */
#define MAX(aa,bb) ((aa)>(bb)?(aa):(bb))
#define MIN(aa,bb) ((aa)<(bb)?(aa):(bb))

/* Macro which takes a pointer to a FitsCard and returns non-zero if the 
   card has been used and so should be ignored. */
#define CARDUSED(card)  ( \
             ( IgnoreUsed == 2 && \
                ( (FitsCard *) (card) )->flags & PROVISIONALLY_USED ) || \
             ( IgnoreUsed >= 1 && \
                ( (FitsCard *) (card) )->flags & USED ) )

/* Set of characters used to encode a "sequence number" at the end of
   FITS keywords in an attempt to make them unique.. */
#define SEQ_CHARS "_ABCDEFGHIJKLMNOPQRSTUVWXYZ"

/* A general tolerance for equality between floating point values. */
#define TOL1 10.0*DBL_EPSILON

/* A tolerance for equality between angular values in radians. */
#define TOL2 1.0E-10

/* Macro to check for equality of floating point values. We cannot
   compare bad values directory because of the danger of floating point
   exceptions, so bad values are dealt with explicitly. */
#define EQUAL(aa,bb) (((aa)==AST__BAD)?(((bb)==AST__BAD)?1:0):(((bb)==AST__BAD)?0:(fabs((aa)-(bb))<=1.0E5*MAX((fabs(aa)+fabs(bb))*DBL_EPSILON,DBL_MIN))))

/* Macro to check for equality of floating point angular values. We cannot
   compare bad values directory because of the danger of floating point
   exceptions, so bad values are dealt with explicitly. The smallest
   significant angle is assumed to be 1E-9 radians (0.0002 arc-seconds).*/
#define EQUALANG(aa,bb) (((aa)==AST__BAD)?(((bb)==AST__BAD)?1:0):(((bb)==AST__BAD)?0:(fabs((aa)-(bb))<=MAX(1.0E5*(fabs(aa)+fabs(bb))*DBL_EPSILON,1.0E-9))))

/* Macro to compare an angle in radians with zero, allowing some tolerance. */
#define ZEROANG(aa) (fabs(aa)<1.0E-9)

/* Constants: */
#define UNKNOWN_ENCODING  -1
#define NATIVE_ENCODING    0
#define FITSPC_ENCODING    1
#define DSS_ENCODING       2
#define FITSWCS_ENCODING   3
#define FITSIRAF_ENCODING  4
#define FITSAIPS_ENCODING  5
#define FITSAIPSPP_ENCODING 6
#define MAX_ENCODING       6
#define UNKNOWN_STRING     "UNKNOWN"
#define NATIVE_STRING      "NATIVE"
#define FITSPC_STRING      "FITS-PC"
#define FITSPC_STRING2     "FITS_PC"
#define DSS_STRING         "DSS"
#define FITSWCS_STRING     "FITS-WCS"
#define FITSWCS_STRING2    "FITS_WCS"
#define FITSIRAF_STRING    "FITS-IRAF"
#define FITSIRAF_STRING2   "FITS_IRAF"
#define FITSAIPS_STRING    "FITS-AIPS"
#define FITSAIPS_STRING2   "FITS_AIPS"
#define FITSAIPSPP_STRING  "FITS-AIPS++"
#define FITSAIPSPP_STRING2 "FITS_AIPS++"
#define INDENT_INC         3
#define PREVIOUS           0
#define NEXT               1
#define HEADER_TEXT        "Beginning of AST data for "
#define FOOTER_TEXT        "End of AST data for "
#define FITSNAMLEN         8
#define FITSSTCOL          20
#define FITSRLCOL          30
#define FITSIMCOL          50
#define FITSCOMCOL         32
#define FITSCARDLEN        80
#define NORADEC            0
#define FK4                1
#define FK4NOE             2
#define FK5                3
#define GAPPT              4
#define ICRS               5
#define NOCEL              0
#define RADEC              1
#define ECLIP              2
#define GALAC              3
#define SUPER              4
#define HECLIP             5
#define LONAX             -1
#define NONAX              0
#define LATAX              1
#define NDESC              9
#define MXCTYPELEN        81
#define ALLWARNINGS       " distortion noequinox noradesys nomjd-obs nolonpole nolatpole tnx zpx badcel noctype badlat badmat badval badctype "
#define NPFIT             10

#define FL  1.0/298.257  /*  Reference spheroid flattening factor */
#define A0  6378140.0    /*  Earth equatorial radius (metres) */

/* Each card in the fitschan has a set of flags associated with it,
   stored in different bits of the "flags" item within each FitsCard
   structure (note, in AST V1.4 these flags were stored in the "del"
   item... Dump and LoadFitsChan will need to be changed to use a
   correspondingly changed name for the external representation of this
   item). The following flags are currently defined: */

/* "USED" - This flag indicates that the the card has been used in the
   construction of an AST Object returned by astRead. Such cards should
   usually be treated as if they do not exist, i.e. they should not be 
   used again by subsequent calls to astRead, they should not be recognised
   by public FitsChan methods which search the FitsChan for specified
   cards, and they should not be written out when the FitsChan is deleted.
   This flag was the only flag available in AST V1.4, and was called
   "Del" (for "deleted"). Used cards are retained in order to give an
   indication of where abouts within the header new cards should be placed
   when astWrite is called (i.e. new cards should usually be placed at
   the same point within the header as the cards which they replace). */
#define USED      1

/* "PROVISIONALLY_USED" - This flag indicates that the the card is being
   considered as a candidate for inclusion in the construction of an AST 
   Object. If the Object is constructed succesfully, cards flagged as 
   "provisionally used" will be changed to be flagged as "definitely used"
   (using the USED flag). If the Object fails to be constructed
   succesfully (if some required cards are missing from the FitsChan
   for instance), then "provisionally used" cards will be returned to the
   former state which they had prior to the attempt to construct the
   object. */
#define PROVISIONALLY_USED 2

/* "NEW" - This flag indicates that the the card has just been added to
   the FitsChan and may yet proove to be unrequired. For instance if the
   supplied Object is not of an appropriate flavour to be stored using
   the requested encoding, all "new" cards which were added before the 
   inappropriateness was discovered will be removed from the FitsChan.
   Two different levels of "newness" are available. */
#define NEW1 4
#define NEW2 8

/* Include files. */
/* ============== */
/* Interface definitions. */
/* ---------------------- */
#include "channel.h"  
#include "cmpframe.h" 
#include "cmpmap.h"
#include "dssmap.h"   
#include "error.h"    
#include "fitschan.h" 
#include "frame.h" 
#include "frameset.h" 
#include "grismmap.h"
#include "mathmap.h"
#include "matrixmap.h"
#include "memory.h"   
#include "object.h"   
#include "permmap.h"
#include "pointset.h" 
#include "shiftmap.h" 
#include "skyframe.h" 
#include "slalib.h" 
#include "slamap.h"
#include "specframe.h"
#include "specmap.h"
#include "sphmap.h"
#include "unitmap.h"  
#include "polymap.h"  
#include "wcsmap.h"   
#include "winmap.h"
#include "zoommap.h"

/* Error code definitions. */
/* ----------------------- */
#include "ast_err.h"             /* AST error codes */

/* C header files. */
/* --------------- */
#include <ctype.h>
#include <float.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Type Definitions */
/* ================ */
/* This structure contains information describing a single FITS header card
   in a circular list of such structures. */

typedef struct FitsCard {
   char name[ FITSNAMLEN + 1 ];/* Keyword name (plus terminating null). */
   int type;                  /* Data type. */
   void *data;                /* Pointer to the keyword's data value. */
   char *comment;             /* Pointer to a comment for the keyword. */
   int flags;                 /* Flags for each card */
   size_t size;               /* Size of data value */
   struct FitsCard *next;     /* Pointer to next structure in list. */
   struct FitsCard *prev;     /* Pointer to previous structure in list. */
} FitsCard;   


typedef struct FitsKeySeq {   /* Associate a keyword with a sequence no. */
   char *key;                 /* Pointer to basic FITS keyword string */
   int seq;                   /* Sequence number last used */
   struct FitsKeySeq *next;   /* Pointer to next list element */
} FitsKeySeq;


/* Structure used to store information derived from the FITS WCS keyword 
   values in a form more convenient to further processing. Conventions
   for units, etc, for values in a FitsStore follow FITS-WCS (e.g. angular 
   values are stored in degrees, equinox is B or J depending on RADECSYS, 
   etc). */
typedef struct FitsStore {
   char ***cname;
   char ***ctype;
   char ***ctype_com;
   char ***cunit;
   char ***radesys;
   char ***wcsname;
   char ***specsys;
   double ***pc;
   double ***cdelt;
   double ***crpix;
   double ***crval;
   double ***equinox;
   double ***latpole;
   double ***lonpole;
   double ***mjdobs;
   double ***mjdavg;
   double ***pv;
   double ***wcsaxes;
   double ***obsgeox;
   double ***obsgeoy;
   double ***obsgeoz;
   double ***restfrq;
   double ***restwav;
   double ***vsource;
   double ***zsource;
   double ***asip;
   double ***bsip;
   double ***apsip;
   double ***bpsip;
   int naxis;
} FitsStore;


/* Module Variables. */
/* ================= */
/* Define the class virtual function table and its initialisation flag
   as static variables. */
static AstFitsChanVtab class_vtab; /* Virtual function table */
static int class_init = 0;       /* Virtual function table initialised? */

/* Strings to decribe each data type. These should be in the order implied
   by the corresponding macros (eg AST__FLOAT, etc). */
static const char *type_names[] = {"comment", "integer", "floating point",
                                   "string", "complex floating point",
                                   "complex integer", "logical",
                                   "continuation string" };

/* Pointers to parent class methods which are extended by this class. */
static const char *(* parent_getattrib)( AstObject *, const char * );
static int (* parent_getfull)( AstChannel * );
static int (* parent_getskip)( AstChannel * );
static int (* parent_testattrib)( AstObject *, const char * );
static void (* parent_clearattrib)( AstObject *, const char * );
static void (* parent_setattrib)( AstObject *, const char * );
static int (* parent_write)( AstChannel *, AstObject * );
static AstObject *(* parent_read)( AstChannel * );

/* Number of output items written since the last "Begin" or "IsA"
   output item, and level of Object nesting during recursive
   invocation of the astWrite method. */
static int items_written = 0;
static int write_nest = -1;

/* Indentation level for indented comments when writing Objects to a
   FitsChan. */
static int current_indent;

/* Text values used to represent Encoding values externally. */
static const char *xencod[7] = { NATIVE_STRING, FITSPC_STRING,
                                 DSS_STRING, FITSWCS_STRING, 
                                 FITSIRAF_STRING, FITSAIPS_STRING,
                                 FITSAIPSPP_STRING };

/* IgnoreUsed: If 2, then cards which have been marked as either "definitely 
   used" or "provisionally used" (see the USED flag above) will be ignored 
   when searching the FitsChan, etc (i.e. they will be treated as if they 
   have been removed from the FitsChan). If 1, then cards which have been 
   "definitely used" will be skipped over. If zero then no cards will be 
   skipped over. */
int IgnoreUsed;

/* MarkNew: If non-zero, then all cards added to the FitsChan will be
   marked with both the NEW1 and NEW2 flags (see above). If zero then 
   new cards will not be marked with either NEW1 or NEW2. */
int MarkNew;

/* External Interface Function Prototypes. */
/* ======================================= */
/* The following functions have public prototypes only (i.e. no
   protected prototypes), so we must provide local prototypes for use
   within this module. */
AstFitsChan *astFitsChanForId_( const char *(*)( void ), 
                           char *(*)( const char *(*)( void ) ), 
                           void (*)( const char * ), 
                           void (*)( void (*)( const char * ), const char * ),
                           const char *, ... );
AstFitsChan *astFitsChanId_( const char *(* source)( void ),
                             void (* sink)( const char * ),
                             const char *options, ... );

/* Prototypes for Private Member Functions. */
/* ======================================== */
static void ClearCard( AstFitsChan * );
static int GetCard( AstFitsChan * );
static int TestCard( AstFitsChan * );
static void SetCard( AstFitsChan *, int );

static void ClearEncoding( AstFitsChan * );
static int GetEncoding( AstFitsChan * );
static int TestEncoding( AstFitsChan * );
static void SetEncoding( AstFitsChan *, int );

static void ClearCDMatrix( AstFitsChan * );
static int GetCDMatrix( AstFitsChan * );
static int TestCDMatrix( AstFitsChan * );
static void SetCDMatrix( AstFitsChan *, int );

static void ClearFitsDigits( AstFitsChan * );
static int GetFitsDigits( AstFitsChan * );
static int TestFitsDigits( AstFitsChan * );
static void SetFitsDigits( AstFitsChan *, int );

static void ClearDefB1950( AstFitsChan * );
static int GetDefB1950( AstFitsChan * );
static int TestDefB1950( AstFitsChan * );
static void SetDefB1950( AstFitsChan *, int );

static void ClearCarLin( AstFitsChan * );
static int GetCarLin( AstFitsChan * );
static int TestCarLin( AstFitsChan * );
static void SetCarLin( AstFitsChan *, int );

static void ClearIwc( AstFitsChan * );
static int GetIwc( AstFitsChan * );
static int TestIwc( AstFitsChan * );
static void SetIwc( AstFitsChan *, int );

static void ClearClean( AstFitsChan * );
static int GetClean( AstFitsChan * );
static int TestClean( AstFitsChan * );
static void SetClean( AstFitsChan *, int );

static void ClearWarnings( AstFitsChan * );
static const char *GetWarnings( AstFitsChan * );
static int TestWarnings( AstFitsChan * );
static void SetWarnings( AstFitsChan *, const char * );

static int GetNcard( AstFitsChan * );
static const char *GetAllWarnings( AstFitsChan * );

static AstFitsChan *SpecTrans( AstFitsChan *, int, const char *, const char * );
static AstFrameSet *MakeFitsFrameSet( AstFrameSet *, int, int );
static AstGrismMap *ExtractGrismMap( AstMapping *, int, AstMapping ** );
static AstMapping *AddUnitMaps( AstMapping *, int, int );
static AstMapping *CelestialAxes( AstFrameSet *, double *, int *, char, FitsStore *, int *, const char *, const char * );
static AstMapping *GrismSpecWcs( char *, FitsStore *, int, char, AstSpecFrame *, const char *, const char * );
static AstMapping *LinearWcs( FitsStore *, int, char, const char *, const char * );
static AstMapping *LogAxis( AstMapping *, int, int, double *, double *, double );
static AstMapping *LogWcs( FitsStore *, int, char, const char *, const char * );
static AstMapping *NonLinSpecWcs( AstFitsChan *, char *, FitsStore *, int, char, AstSpecFrame *, const char *, const char * );
static AstMapping *OtherAxes( AstFrameSet *, double *, int *, char, FitsStore *, double *, int *, const char *, const char * );
static AstMapping *SIPMapping( FitsStore *, char, int, const char *, const char * );
static AstMapping *SpectralAxes( AstFrameSet *, double *, int *, char, FitsStore *, double *, int *, const char *, const char * );
static AstMapping *WcsCelestial( AstFitsChan *, FitsStore *, char, AstFrame **, AstFrame *, double *, double *, AstSkyFrame **, const char *, const char * );
static AstMapping *WcsIntWorld( AstFitsChan *, FitsStore *, char, int, const char *, const char *);
static AstMapping *WcsMapFrm( AstFitsChan *, FitsStore *, char, AstFrame **, const char *, const char * );
static AstMapping *WcsNative( AstFitsChan *, FitsStore *, char, AstWcsMap *, int, int, const char *, const char * );
static AstMapping *WcsOthers( AstFitsChan *, FitsStore *, char, AstFrame **, AstFrame *, const char *, const char * );
static AstMapping *WcsSpectral( AstFitsChan *, FitsStore *, char, AstFrame **, AstFrame *, double, double, AstSkyFrame *, const char *, const char * );
static AstMatrixMap *WcsCDeltMatrix( FitsStore *, char, int, const char *, const char * );
static AstMatrixMap *WcsPCMatrix( FitsStore *, char, int, const char *, const char * );
static AstObject *FsetFromStore( AstFitsChan *, FitsStore *, const char *, const char * );
static AstObject *Read( AstChannel * );
static AstSkyFrame *WcsSkyFrame( AstFitsChan *, FitsStore *, char, int, char *, int, int, const char *, const char *);
static AstWinMap *WcsShift( FitsStore *, char, int, const char *, const char * );
static FitsCard *GetLink( FitsCard *, int, const char *, const char * );
static FitsStore *FitsToStore( AstFitsChan *, int, const char *, const char * );
static FitsStore *FreeStore( FitsStore * );
static FitsStore *FsetToStore( AstFitsChan *, AstFrameSet *, int, double *, const char *, const char * );
static char *CardComm( AstFitsChan * );
static char *CardName( AstFitsChan * );
static char *FormatKey( char *, int, int, char );
static char *GetItemC( char ****, int, char, char *, const char *method, const char *class );
static char *SourceWrap( const char *(*)( void ) );
static char *UnPreQuote( const char * );
static char GetMaxS( double ****item );
static const char *GetAttrib( AstObject *, const char * );
static int IsAIPSSpectral( const char *, char **, char **);
static const char *IsSpectral( const char *, char[5], char[5] );
static double **OrthVectorSet( int, int, double ** );
static double *FitLine( AstMapping *, double *, double *, double *, double );
static double *OrthVector( int, int, double ** );
static double *ReadCrval( AstFitsChan *, AstFrame *, char, const char *, const char * );
static double DateObs( const char * );
static double GetItem( double ****, int, int, char, char *, const char *method, const char *class );
static double NearestPix( AstMapping *, double, int );
static int *CardFlags( AstFitsChan * );
static int AIPSFromStore( AstFitsChan *, FitsStore *, const char *, const char * );
static int AIPSPPFromStore( AstFitsChan *, FitsStore *, const char *, const char * );
static int AddVersion( AstFitsChan *, AstFrameSet *, int, int, FitsStore *, double *, char, const char *, const char * );
static int CardType( AstFitsChan * );
static int CheckFitsName( const char *, const char *, const char * );
static int ChrLen( const char * );
static int CnvType( int, void *, size_t, int, void *, const char *, const char *, const char * );
static int CnvValue( AstFitsChan *, int , void *, const char *);
static int ComBlock( AstFitsChan *, int, const char *, const char * );
static int CountFields( const char *, char, const char *, const char * );
static int DSSFromStore( AstFitsChan *, FitsStore *, const char *, const char * );
static int EncodeFloat( char *, int, int, int, double );
static int EncodeValue( AstFitsChan *, char *, int, int, const char * );
static int FindBasisVectors( AstMapping *, int, int, double *, AstPointSet *, AstPointSet * );
static int MakeBasisVectors( AstMapping *, int, int, double *, AstPointSet *, AstPointSet *);
static int FindKeyCard( AstFitsChan *, const char *, const char *, const char * );
static int FindLonLatSpecAxes( FitsStore *, char, int *, int *, int *, const char *, const char * );
static int FindString( int, const char *[], const char *, const char *, const char *, const char * );
static int FitsEof( AstFitsChan * );
static int FitOK( int, double *, double * );
static int FitsFromStore( AstFitsChan *, FitsStore *, int, const char *, const char * );
static int FitsGetCF( AstFitsChan *, const char *, double * );
static int FitsGetCI( AstFitsChan *, const char *, int * );
static int FitsGetCN( AstFitsChan *, const char *, char ** );
static int FitsGetCom( AstFitsChan *, const char *, char ** );
static int FitsGetF( AstFitsChan *, const char *, double * );
static int FitsGetI( AstFitsChan *, const char *, int * );
static int FitsGetL( AstFitsChan *, const char *, int * );
static int FitsGetS( AstFitsChan *, const char *, char ** );
static int FitsSet( AstFitsChan *, const char *, void *, int, const char *, int );
static int FullForm( const char *, const char *, int );
static int GetFiducialWCS( AstWcsMap *, AstMapping *, int, int, double *, double * );
static int GetFull( AstChannel * );
static int GetMaxJM( double ****item, char );
static int GetMaxI( double ****item, char );
static int GetSkip( AstChannel * );
static int GetValue( AstFitsChan *, char *, int, void *, int, int, const char *, const char * );
static int GetValue2( AstFitsChan *, AstFitsChan *, char *, int, void *, int, const char *, const char * );
static int GoodWarns( const char * );
static int HasAIPSSpecAxis( AstFitsChan *, const char *, const char * );
static int IRAFFromStore( AstFitsChan *, FitsStore *, const char *, const char * );
static int IsMapLinear( AstMapping *, const double [], const double [], int );
static int KeyFields( AstFitsChan *, const char *, int, int *, int * );
static int MakeIntWorld( AstMapping *, AstFrame *, int *, char, FitsStore *, double *, const char *, const char * );
static int Match( const char *, const char *, int, int *, int *, const char *, const char * );
static int MatchChar( char, char, const char *, const char *, const char * );
static int MatchFront( const char *, const char *, char *, int *, int *, int *, const char *, const char *, const char * );
static int MoveCard( AstFitsChan *, int, const char *, const char * );
static int PCFromStore( AstFitsChan *, FitsStore *, const char *, const char * );
static int SearchCard( AstFitsChan *, const char *, const char *, const char *);
static int Similar( const char *, const char * );
static int SkySys( AstSkyFrame *, int, FitsStore *, int, int, char c, const char *, const char * );
static int SplitMap( AstMapping *, int, int, int, AstMapping **, AstWcsMap **, AstMapping ** );
static int SplitMap2( AstMapping *, int, AstMapping **, AstWcsMap **, AstMapping ** );
static int SplitMat( int , double *, double * );
static int TestAttrib( AstObject *, const char * );
static int Use( AstFitsChan *, int, int );
static int Ustrcmp( const char *, const char * );
static int Ustrncmp( const char *, const char *, size_t );
static int WcsFromStore( AstFitsChan *, FitsStore *, const char *, const char * );
static int WcsNatPole( AstFitsChan *, AstWcsMap *, double, double, double, double *, double *, double * );
static int Write( AstChannel *, AstObject * );
static int astSplit_( const char *, char **, char **, char **, const char *, const char * );
static void *CardData( AstFitsChan *, size_t * );
static void AddFrame( AstFitsChan *, AstFrameSet *, int, int, FitsStore *, char, const char *, const char * );  
static void CheckZero( char *, double, int );
static void ClearAttrib( AstObject *, const char * );
static void Copy( const AstObject *, AstObject * );
static void CreateKeyword( AstFitsChan *, const char *, char [ FITSNAMLEN + 1 ] );
static void DistortMaps( AstFitsChan *, FitsStore *, char, int , AstMapping **, AstMapping **, AstMapping **, AstMapping **, const char *, const char * );
static void DSSToStore( AstFitsChan *, FitsStore *, const char *, const char * );
static void DelFits( AstFitsChan * );
static void Delete( AstObject * );
static void DeleteCard( AstFitsChan *, const char *, const char * );
static void Dump( AstObject *, AstChannel * );
static void Empty( AstFitsChan * );
static void FindWcs( AstFitsChan *, int, const char *, const char * );
static void FitsSetCF( AstFitsChan *, const char *, double *, const char *, int );
static void FitsSetCI( AstFitsChan *, const char *, int *, const char *, int );
static void FitsSetCN( AstFitsChan *, const char *, const char *, const char *, int );
static void FitsSetCom( AstFitsChan *, const char *, const char *, int );
static void FitsSetF( AstFitsChan *, const char *, double, const char *, int );
static void FitsSetI( AstFitsChan *, const char *, int, const char *, int );
static void FitsSetL( AstFitsChan *, const char *, int, const char *, int );
static void FitsSetS( AstFitsChan *, const char *, const char *, const char *, int );
static void FixNew( AstFitsChan *, int, int, const char *, const char * );
static void FixUsed( AstFitsChan *, int, int, int, const char *, const char * );
static void FormatCard( AstFitsChan *, char *, const char * );
static void FreeItem( double **** );
static void FreeItemC( char **** );
static void Geod( double[3], double *, double *, double * );
static void GetFiducialNSC( AstWcsMap *, double *, double * );
static void GetFiducialPPC( AstWcsMap *, double *, double * );
static void GetNextData( AstChannel *, int, char **, char ** );
static void InsCard( AstFitsChan *, int, const char *, int, void *, const char *, const char *, const char * );
static void MakeBanner( const char *, const char *, const char *, char [ FITSCARDLEN - FITSNAMLEN + 1 ] );
static void MakeIndentedComment( int, char, const char *, const char *, char [ FITSCARDLEN - FITSNAMLEN + 1] );
static void MakeIntoComment( AstFitsChan *, const char *, const char * );
static void MarkCard( AstFitsChan * );
static void NewCard( AstFitsChan *, const char *, int, const void *, const char *, int );
static void PreQuote( const char *, char [ FITSCARDLEN - FITSNAMLEN - 3 ] );
static void PutCards( AstFitsChan *, const char * );
static void PutFits( AstFitsChan *, const char [ FITSCARDLEN + 1 ], int );
static void ReadFromSource( AstFitsChan * );
static void RoundFString( char *, int );
static void SetAttrib( AstObject *, const char * );
static void SetItem( double ****, int, int, char, double );
static void SetItemC( char ****, int, char, const char * );
static void SetValue( AstFitsChan *, char *, void *, int, char * );
static void SinkWrap( void (*)( const char * ), const char * );
static void SkyPole( AstWcsMap *, AstMapping *, int, int, int *, char, FitsStore *, const char *, const char * );
static void Warn( AstFitsChan *, const char *, const char *, const char *, const char * );
static void WcsFcRead( AstFitsChan *, FitsStore *, const char *, const char * );
static void WcsToStore( AstFitsChan *, AstFitsChan *, FitsStore *, const char *, const char * );
static void WorldAxes( AstMapping *, double *, int * );
static void WriteBegin( AstChannel *, const char *, const char * );
static void WriteDouble( AstChannel *, const char *, int, int, double, const char * );
static void WriteEnd( AstChannel *, const char * );
static void WriteInt( AstChannel *, const char *, int, int, int, const char * );
static void WriteIsA( AstChannel *, const char *, const char * );
static void WriteObject( AstChannel *, const char *, int, int, AstObject *, const char * );
static void WriteString( AstChannel *, const char *, int, int, const char *, const char * );
static void WriteToSink( AstFitsChan * );

/* Member functions. */
/* ================= */
static void AddFrame( AstFitsChan *this, AstFrameSet *fset, int pixel, 
                      int npix, FitsStore *store, char s, const char *method, 
                      const char *class ){
/*
*  Name:
*     AddFrame

*  Purpose:
*     Create a Frame describing a set of axes with a given co-ordinate 
*     version, and add it to the supplied FrameSet.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void AddFrame( AstFitsChan *this, AstFrameSet *fset, int pixel, 
*                    int npix, FitsStore *store, char s, const char *method, 
*                    const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     A Frame is created describing axis with a specific co-ordinate
*     version character, reading information from the supplied FitsStore. 
*     A suitable Mapping is created to connect the new Frame to the pixel 
*     (GRID) Frame in the supplied FrameSet, and the Frame is added into 
*     the FrameSet using this Mapping.

*  Parameters:
*     this
*        The FitsChan from which the keywords were read. Warning messages
*        are added to this FitsChan if the celestial co-ordinate system is 
*        not recognized. 
*     fset
*        Pointer to the FrameSet to be extended.
*     pixel
*        The index of the pixel (GRID) Frame within fset.
*     npix
*        The number of pixel axes.
*     store
*        The FitsStore containing the required information extracted from 
*        the FitsChan.
*     s
*        The co-ordinate version character. A space means the primary
*        axis descriptions. Otherwise the supplied character should be 
*        an upper case alphabetical character ('A' to 'Z'). 
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.
*/

/* Local Variables: */
   AstFrame *frame;            /* Requested Frame */
   AstMapping *mapping;        /* Mapping from pixel to requested Frame */
   AstMapping *tmap;           /* Temporary Mapping pointer */
   AstPermMap *pmap;           /* PermMap pointer to add or remove axes */
   double con;                 /* Value to be assigned to missing axes */
   int *inperm;                /* Pointer to input axis permutation array */
   int *outperm;               /* Pointer to output axis permutation array */
   int i;                      /* Axis index */
   int nwcs;                   /* Number of wcs axes */

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

/* Get a Mapping between pixel coordinates and physical coordinates, using
   the requested axis descriptions. Also returns a Frame describing the
   physical coordinate system. */
   mapping = WcsMapFrm( this, store, s, &frame, method, class );

/* Add the Frame into the FrameSet, and annul the mapping and frame. If
   the new Frame has more axes than the pixel Frame, use a PermMap which
   assigns constant value 1.0 to the extra axes. If the new Frame has less 
   axes than the pixel Frame, use a PermMap which throws away the extra
   axes. */
   if( mapping != NULL ) {
      nwcs = astGetNin( mapping );
      if( nwcs != npix ) {
         inperm = astMalloc( sizeof(int)*(size_t)npix );
         outperm = astMalloc( sizeof(int)*(size_t)nwcs );
         if( astOK ) {
            for( i = 0; i < npix; i++ ) {
               inperm[ i ] = ( i < nwcs ) ? i : -1;
            }
            for( i = 0; i < nwcs; i++ ) {
               outperm[ i ] = ( i < npix ) ? i : -1;
            }
            con = 1.0;
            pmap = astPermMap( npix, inperm, nwcs, outperm, &con, "" );
            tmap = (AstMapping *) astCmpMap( pmap, mapping, 1, "" );
            pmap = astAnnul( pmap );
            astAnnul( mapping );
            mapping = tmap;
         }
         inperm = astFree( inperm );
         outperm = astFree( outperm );
      }

      astAddFrame( fset, pixel, mapping, frame );

/* Annul temporary resources. */
      frame = astAnnul( frame );
      mapping = astAnnul( mapping );
   }
}

static int AddVersion( AstFitsChan *this, AstFrameSet *fs, int ipix, int iwcs, 
                       FitsStore *store, double *dim, char s, 
                       const char *method, const char *class ){
/*
*  Name:
*     AddVersion

*  Purpose:
*     Add values to a FitsStore describing a specified Frame in a FrameSet.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int AddVersion( AstFitsChan *this, AstFrameSet *fs, int ipix, int iwcs, 
*                     FitsStore *store, double *dim, char s, 
*                     const char *method, const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Values are added to the supplied FitsStore describing the specified
*     WCS Frame, and its relationship to the specified pixel Frame. These 
*     values are based on the standard FITS-WCS conventions.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     fs
*        Pointer to the FrameSet.
*     ipix
*        The index of the pixel (GRID) Frame within fset.
*     iwcs
*        The index of the Frame within fset to use as the WCS co-ordinate 
*        Frame.
*     store
*        The FitsStore in which to store the information extracted from 
*        the FrameSet.
*     dim 
*        Pointer to an array of pixel axis dimensions. Individual elements 
*        will be AST__BAD if dimensions are not known. The number of
*        elements should equal the number of axes in the base Frame of the
*        supplied FrameSet. 
*     s
*        The co-ordinate version character. A space means the primary
*        axis descriptions. Otherwise the supplied character should be 
*        an upper case alphabetical character ('A' to 'Z'). 
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Retuned Value:
*     A value of 1 is returned if the WCS Frame was succesfully added to
*     the FitsStore. A value of zero is returned otherwise.

*/

/* Local Variables: */

   AstFrame *wcsfrm;        /* WCS Frame */
   AstFrameSet *fset;       /* Temporary FrameSet */
   AstMapping *iwcmap;      /* Mapping from WCS to IWC Frame */
   AstMapping *pixiwcmap;   /* Mapping from pixel to IWC Frame */
   AstMapping *mapping;     /* Mapping from pixel to WCS Frame */
   AstMapping *tmap2;       /* Temporary Mapping */
   AstMapping *tmap;        /* Temporary Mapping */
   double *crvals;          /* Pointer to array holding default CRVAL values */
   double cdelt2;           /* Sum of squared PC values */
   double cdelt;            /* CDELT value for axis */
   double crpix;            /* CRPIX value for axis */
   double crval;            /* CRVAL value for axis */
   double pc;               /* Element of the PC array */
   int *axis_done;          /* Flags indicating which axes have been done */
   int *wperm;              /* FITS axis for each Mapping output (Frame axis) */
   int fits_i;              /* FITS WCS axis index */
   int fits_j;              /* FITS pixel axis index */
   int iax;                 /* Frame axis index */
   int nwcs;                /* No. of axes in WCS frame */
   int ret;                 /* Returned value */

/* Initialise */
   ret = 0;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Construct a new FrameSet holding the pixel and WCS Frames from the 
   supplied FrameSet, but in which the current Frame is a copy of the 
   supplied WCS Frame, but optionally extended to include any extra axes 
   needed to conform to the FITS model. For instance, if the WCS Frame 
   consists of a single 1D SpecFrame with a defined celestial reference 
   position (SpecFrame attributes RefRA and RefDec), then FITS-WCS paper 
   III requires there to be a pair of celestial axes in the WCS Frame in 
   which the celestial reference point for the spectral axis is defined. */
   fset = MakeFitsFrameSet( fs, ipix, iwcs );

/* Abort if the FrameSet could not be produced. */
   if( !fset ) return ret;

/* Get the Mapping from base to current Frame and check its inverse is
   defined. Return if not. */
   mapping = astGetMapping( fset, AST__BASE, AST__CURRENT );
   if( !astGetTranInverse( mapping ) ) {
      mapping = astAnnul( mapping );
      fset = astAnnul( fset );
      return ret;
   }

/* We now need to choose the "FITS WCS axis" (i.e. the number that is included 
   in FITS keywords such as CRVAL2) for each axis of the output Frame. For 
   each WCS axis, we use the index of the pixel axis which is most closely 
   aligned with it. Allocate memory to store these indices, and then fill
   the memory. */
   mapping = astGetMapping( fset, AST__BASE, AST__CURRENT );
   nwcs= astGetNout( mapping );
   wperm = astMalloc( sizeof(int)*(size_t) nwcs );
   WorldAxes( mapping, dim, wperm );
   mapping = (AstMapping *) astAnnul( mapping );

/* Allocate an array of flags, one for each axis, which indicate if a
   description of the corresponding axis has yet been stored in the
   FitsStore. Initialise them to indicate that no axes have yet been
   described. */ 
   axis_done = astMalloc( sizeof(int)*(size_t) nwcs );
   if( astOK ) for( iax = 0; iax < nwcs; iax++ ) axis_done[ iax ] = 0;

/* Get the original reference point from the FitsChan and convert it into
   the require WCS Frame. This is used as the default reference point (some
   algorithms may choose to ignore this default reference point ). */
   wcsfrm = astGetFrame( fset, AST__CURRENT );
   crvals = ReadCrval( this, wcsfrm, s, method, class );

/* For each class of FITS conventions (celestial, spectral, others), 
   identify any corresponding axes within the WCS Frame and add
   descriptions of them to the FitsStore. These descriptions are in terms
   of the FITS keywords defined in the corresponding FITS-WCS paper. Note,
   the keywords which descirbed the pixel->IWC mapping (CRPIX, CD, PC,
   CDELT) are not stored by these functions, instead each function
   returns a Mapping from WCS to IWC coords (these Mappings 
   pass on axes of the wrong class without change). These Mappings are 
   combined in series to get the final WCS->IWC Mapping. First do 
   celestial axes. */
   iwcmap = CelestialAxes( fset, dim, wperm, s, store, axis_done, method, class );

/* Now look for spectral axes, and update the iwcmap. */
   tmap = SpectralAxes( fset, dim, wperm, s, store, crvals, axis_done, method, 
                        class );
   tmap2 = (AstMapping *) astCmpMap( iwcmap, tmap, 1, "" );
   tmap = astAnnul( tmap );
   astAnnul( iwcmap );
   iwcmap = tmap2;

/* Finally add descriptions of any axes not yet described (they are
   assumed to be linear), and update the iwcmap. */
   tmap = OtherAxes( fset, dim, wperm, s, store, crvals, axis_done, method, class );
   tmap2 = (AstMapping *) astCmpMap( iwcmap, tmap, 1, "" );
   tmap = astAnnul( tmap );
   astAnnul( iwcmap );
   iwcmap = tmap2;

/* The "iwcmap" Mapping found above converts from the WCS Frame to the IWC 
   Frame. Combine the pixel->WCS Mapping with this WCS->IWC Mapping to
   get the pixel->IWC Mapping. */
   mapping = astGetMapping( fset, AST__BASE, AST__CURRENT );
   pixiwcmap = (AstMapping *) astCmpMap( mapping, iwcmap, 1, "" );
   mapping = astAnnul( mapping );
   iwcmap = astAnnul( iwcmap );

/* Now attempt to store values for the keywords describing the pixel->IWC 
   Mapping (CRPIX, CD, PC, CDELT). This tests that the iwcmap is linear.
   Zero is returned if the test fails. */
   ret = MakeIntWorld( pixiwcmap, wcsfrm, wperm, s, store, dim, method, class );

/* If succesfull... */
   if( ret ) {

/* Store the Domain name as the WCSNAME keyword (if set). */
      if( astTestDomain( wcsfrm ) ) { 
         SetItemC( &(store->wcsname), 0, s, (char *) astGetDomain( wcsfrm ) );
      }

/* Set CRVAL values which are very small compared to the pixel size to
   zero. */
      for( iax = 0; iax < nwcs; iax++ ) {
         fits_i = wperm[ iax ];
         crval = GetItem( &(store->crval), fits_i, 0, s, NULL, method, class );
         if( crval != AST__BAD ) {   
   
            cdelt2 = 0.0;
            for( fits_j = 0; fits_j < nwcs; fits_j++ ){
               pc = GetItem( &(store->pc), fits_i, fits_j, s, NULL, method, class );
               if( pc == AST__BAD ) pc = ( fits_i == fits_j ) ? 1.0 : 0.0;
               cdelt2 += pc*pc;
            }
   
            cdelt = GetItem( &(store->cdelt), fits_i, 0, s, NULL, method, class );
            if( cdelt == AST__BAD ) cdelt = 1.0;
            cdelt2 *= ( cdelt*cdelt );
      
            if( fabs( crval ) < sqrt( DBL_EPSILON*cdelt2 ) ) {
               SetItem( &(store->crval), fits_i, 0, s, 0.0 );
            }   
         }
      }

/* Round CRPIX values to the nearest millionth of a pixel. */
      for( iax = 0; iax < nwcs; iax++ ) {
         crpix = GetItem( &(store->crpix), 0, iax, s, NULL, method, class );
         if( crpix != AST__BAD ) {   
            SetItem( &(store->crpix), 0, iax, s, 
                     ((int)( crpix*1.0E6 + 0.5 ))*1.0E-6);
         }
      }
   }



/* Free remaining resources. */
   if( crvals ) crvals = astFree( crvals );
   wcsfrm = astAnnul( wcsfrm );
   pixiwcmap = astAnnul( pixiwcmap );
   axis_done = astFree( axis_done );
   wperm = astFree( wperm );
   fset = astAnnul( fset );

/* If an error has occurred, return zero */
   return astOK ? ret : 0;

}

static AstMapping *AddUnitMaps( AstMapping *map, int iax, int nax ) {
/*
*  Name:
*     AddUnitMaps

*  Purpose:
*     Embed a Mapping within a pair of parallel UnitMaps.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     AstMapping *AddUnitMaps( AstMapping *map, int iax, int nax )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function returns a Mapping which consists of the supplied Mapping 
*     in parallel with a pair of UnitMaps so that he first axis of the
*     supplied Mapping is at a specified axis number in the returned Mapping.

*  Parameters:
*     map
*        Pointer to the Mapping. The Mapping must have equal numbers of
*        input and output coordinates.
*     iax
*        The index for the first input of "map" within the returned
*        Mapping.
*     nax
*        The number of axes for the returned Mapping.

*  Returned Value:
*     A Mapping which has "nax" axes, and in which the "iax" axis
*     corresponds to the first axis of "map". Axes lower than "iax" are
*     transformed using a UnitMap, and axes higher than the last axis of 
*     "map" are transformed using a UnitMap.

*/

/* Local Variables: */
   AstMapping *ret;      /* Returned Mapping */
   AstMapping *tmap0;    /* Temporary Mapping */
   AstMapping *tmap1;    /* Temporary Mapping */
   AstMapping *tmap2;    /* Temporary Mapping */
   int nmap;             /* Number of supplied Mapping inputs */

/* Initialise */
   ret = NULL;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Initialise the returned Mapping to be a clone of the supplied Mapping. */
   ret = astClone( map );

/* Note the number of inputs of the supplied Mapping (assumed to be equal 
   to the number of outputs). */
   nmap = astGetNin( map );

/* If necessary produce a parallel CmpMap which combines the Mapping with a 
   UnitMap representing the axes lower than "iax". */
   if( iax > 0 ) {
      tmap0 = (AstMapping *) astUnitMap( iax, "" );
      tmap1 = (AstMapping *) astCmpMap( tmap0, ret, 0, "" );
      ret = astAnnul( ret );
      tmap0 = astAnnul( tmap0 );
      ret = tmap1;
   }

/* If necessary produce a parallel CmpMap which combines the Mapping with a 
   UnitMap representing the axes higher than "iax+nmap". */
   if( iax + nmap < nax ) {
      tmap1 = (AstMapping *) astUnitMap( nax - iax - nmap, "" );
      tmap2 = (AstMapping *) astCmpMap( ret, tmap1, 0, "" );
      ret = astAnnul( ret );
      tmap1 = astAnnul( tmap1 );
      ret = tmap2;
   }

/* Return the result. */
   return ret;
}

static int AIPSFromStore( AstFitsChan *this, FitsStore *store, 
                          const char *method, const char *class ){
/*
*  Name:
*     AIPSFromStore

*  Purpose:
*     Store WCS keywords in a FitsChan using FITS-AIPS encoding.

*  Type:
*     Private function.

*  Synopsis:
*     int AIPSFromStore( AstFitsChan *this, FitsStore *store, 
*                        const char *method, const char *class )

*  Class Membership:
*     FitsChan

*  Description:
*     A FitsStore is a structure containing a generalised represention of
*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
*     from a set of FITS header cards (using a specified encoding), or
*     an AST FrameSet. In other words, a FitsStore is an encoding-
*     independant intermediary staging post between a FITS header and 
*     an AST FrameSet.
*
*     This function copies the WCS information stored in the supplied 
*     FitsStore into the supplied FitsChan, using FITS-AIPS encoding.
*
*     AIPS encoding is like FITS-WCS encoding but with the following
*     restrictions:
*
*     1) The celestial projection must not have any projection parameters
*     which are not set to their default values. The one exception to this 
*     is that SIN projections are acceptable if the associated projection 
*     parameter PV<axlat>_1 is zero and PV<axlat>_2 = cot( reference point 
*     latitude). This is encoded using the string "-NCP". The SFL projection 
*     is encoded using the string "-GLS". Note, the original AIPS WCS
*     system only recognised a small subset of the currently available
*     projections, but some more recent AIPS-like software recognizes some 
*     of the new projections included in the FITS-WCS encoding. The AIT, 
*     GLS and MER can only be written if the CRVAL keywords are zero for 
*     both longitude and latitude axes.
*
*     2) The celestial axes must be RA/DEC, galactic or ecliptic.   
*
*     3) LONPOLE and LATPOLE must take their default values.
*
*     4) Only primary axis descriptions are written out.
*
*     5) EPOCH is written instead of EQUINOX & RADECSYS, and uses the 
*        IAU 1984 rule ( EPOCH < 1984.0 is treated as a Besselian epoch 
*        and implies RADECSYS=FK4,  EPOCH >= 1984.0 is treated as a 
*        Julian epoch and implies RADECSYS=FK5). The RADECSYS & EQUINOX
*        values in the FitsStore must be consistent with this rule.
*
*     6) Any rotation produced by the PC matrix must be restricted to
*        the celestial plane, and must involve no shear. A CROTA keyword
*        with associated CDELT values are produced instead of the PC
*        matrix.
*     
*     7) ICRS is not supported.
*
*     8) Spectral axes can be created only for FITS-WCS CTYPE values of "FREQ"
*        "VRAD" and "VOPT-F2W" and with standards of rest of LSRK, LSRD, 
*        BARYCENT and GEOCENTR.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     store
*        Pointer to the FitsStore.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     A value of 1 is returned if succesfull, and zero is returned
*     otherwise.

*/

/* Local Variables: */
   char *comm;         /* Pointer to comment string */
   char *cval;         /* Pointer to string keyword value */
   char *specunit;     /* Pointer to corrected spectral units string */
   char combuf[80];    /* Buffer for FITS card comment */
   char lattype[MXCTYPELEN];/* Latitude axis CTYPE */
   char lontype[MXCTYPELEN];/* Longitude axis CTYPE */
   char s;             /* Co-ordinate version character */
   char sign[2];       /* Fraction's sign character */
   char spectype[MXCTYPELEN];/* Spectral axis CTYPE */
   double *cdelt;      /* Pointer to CDELT array */
   double cdl;         /* CDELT term */
   double cdlat_lon;   /* Off-diagonal CD element */
   double cdlon_lat;   /* Off-diagonal CD element */
   double coscro;      /* Cos( CROTA ) */
   double crota;       /* CROTA value to use */
   double epoch;       /* Epoch of reference equinox */
   double fd;          /* Fraction of a day */
   double latval;      /* CRVAL for latitude axis */
   double lonval;      /* CRVAL for longitude axis */
   double mjd99;       /* MJD at start of 1999 */
   double p1, p2;      /* Projection parameters */
   double rho_a;       /* First estimate of CROTA */
   double rho_b;       /* Second estimate of CROTA */
   double sincro;      /* Sin( CROTA ) */
   double specfactor;  /* Factor for converting internal spectral units */
   double val;         /* General purpose value */
   int axlat;          /* Index of latitude FITS WCS axis */
   int axlon;          /* Index of longitude FITS WCS axis */
   int axspec;         /* Index of spectral FITS WCS axis */
   int i;              /* Axis index */
   int ihmsf[ 4 ];     /* Hour, minute, second, fractional second */
   int iymdf[ 4 ];     /* Year, month, date, fractional day */
   int j;              /* Axis index */
   int jj;             /* SlaLib status */
   int naxis;          /* No. of axes */
   int ok;             /* Is FitsSTore OK for IRAF encoding? */
   int prj;            /* Projection type */

/* Check the inherited status. */
   if( !astOK ) return 0;

/* First check that the values in the FitsStore conform to the
   requirements of the AIPS encoding. Assume they do to begin with. */
   ok = 1;

/* Just do primary axes. */
   s = ' '; 

/* Look for the primary celestial axes. */
   FindLonLatSpecAxes( store, s, &axlon, &axlat, &axspec, method, class );

/* If both longitude and latitude axes are present ...*/
   if( axlon >= 0 && axlat >= 0 ) {

/* Get the CRVAL values for both axes. */
      latval = GetItem( &( store->crval ), axlat, 0, s, NULL, method, class );
      if( latval == AST__BAD ) ok = 0;
      
      lonval = GetItem( &( store->crval ), axlon, 0, s, NULL, method, class );
      if( lonval == AST__BAD ) ok = 0;
      
/* Get the CTYPE values for both axes. Extract the projection type as 
   specified by the last 4 characters in the latitude CTYPE keyword value. */
      cval = GetItemC( &(store->ctype), axlon, s, NULL, method, class );
      if( !cval ) {
         ok = 0;
      } else {
         strcpy( lontype, cval );
      }

      cval = GetItemC( &(store->ctype), axlat, s, NULL, method, class );
      if( !cval ) {
         ok = 0;
         prj = AST__WCSBAD;
      } else {
         strcpy( lattype, cval );
         prj = astWcsPrjType( cval + 4 );
      }

/* Check the projection type is OK. */
      if( prj != AST__SIN ){
   
/* There must be no projection parameters. */
         if( GetMaxJM( &(store->pv), ' ' ) >= 0 ) {
            ok = 0;

/* FITS-AIPS cannot handle the AST-specific TPN projection. */
         } else if( prj == AST__TPN ) {
            ok = 0;

/* For AIT, MER and GLS, check that the reference point is the origin of
   the celestial co-ordinate system. */
         } else if( prj == AST__MER ||
                    prj == AST__AIT ||
                    prj == AST__SFL ) {
            if( latval != 0.0 || lonval != 0.0 ){
               ok = 0;     

/* Change the new SFL projection code to to the older equivalent GLS */
            } else if( prj == AST__SFL ){
               (void) strcpy( lontype + 4, "-GLS" );
               (void) strcpy( lattype + 4, "-GLS" );
            }
         }

/* SIN projections are only acceptable if the associated projection
   parameters are both zero, or if the first is zero and the second 
   = cot( reference point latitude )  (the latter case is equivalent to 
   the old NCP projection). */
      } else {
         p1 = GetItem( &( store->pv ), axlat, 1, s, NULL, method, class );
         p2 = GetItem( &( store->pv ), axlat, 2, s, NULL, method, class );
         if( p1 == AST__BAD ) p1 = 0.0;   
         if( p2 == AST__BAD ) p2 = 0.0;   
         ok = 0;

         if( p1 == 0.0 ) {
            if( p2 == 0.0 ) {
               ok = 1;
   
            } else if( fabs( p2 ) >= 1.0E14 && latval == 0.0 ){
               ok = 1;
               (void) strcpy( lontype + 4, "-NCP" );
               (void) strcpy( lattype + 4, "-NCP" );
   
            } else if( fabs( p2*tan( AST__DD2R*latval ) - 1.0 ) 
                       < 0.01 ){
               ok = 1;
               (void) strcpy( lontype + 4, "-NCP" );
               (void) strcpy( lattype + 4, "-NCP" );
            }
         }
      }

/* Identify the celestial coordinate system from the first 4 characters of the
   longitude CTYPE value. Only RA, galactic longitude, and ecliptic
   longitude can be stored using FITS-AIPS. */
      if( ok && strncmp( lontype, "RA--", 4 ) &&
               strncmp( lontype, "GLON", 4 ) &&
               strncmp( lontype, "ELON", 4 ) ) ok = 0;

/* If the physical Frame requires a LONPOLE or LATPOLE keyword, it cannot
   be encoded using FITS-IRAF. */
      if( GetItem( &(store->latpole), 0, 0, s, NULL, method, class )
          != AST__BAD || 
          GetItem( &(store->lonpole), 0, 0, s, NULL, method, class )
          != AST__BAD ) ok = 0;
   }

/* If a spectral axis is present ...*/
   if( ok && axspec >= 0 ) {

/* Get the CTYPE values for the axis, and find the AIPS equivalent, if
   possible. */
      cval = GetItemC( &(store->ctype), axspec, s, NULL, method, class );
      if( !cval ) {
         ok = 0;
      } else {
         if( !strncmp( cval, "FREQ", astChrLen( cval ) ) ) {
            strcpy( spectype, "FREQ" );
         } else if( !strncmp( cval, "VRAD", astChrLen( cval ) ) ) {
            strcpy( spectype, "VELO" );
         } else if( !strncmp( cval, "VOPT-F2W", astChrLen( cval ) ) ) {
            strcpy( spectype, "FELO" );
         } else {
            ok = 0;
         }
      }

/* If OK, check the SPECSYS value and add the AIPS equivalent onto the
   end of the CTYPE value.*/
      cval = GetItemC( &(store->specsys), 0, s, NULL, method, class );
      if( !cval ) {
         ok = 0;
      } else if( ok ) {
         if( !strncmp( cval, "LSRK", astChrLen( cval ) ) ) {
            strcpy( spectype+4, "-LSR" );
         } else if( !strncmp( cval, "LSRD", astChrLen( cval ) ) ) {
            strcpy( spectype+4, "-LSD" );
         } else if( !strncmp( cval, "BARYCENT", astChrLen( cval ) ) ) {
            strcpy( spectype+4, "-HEL" );
         } else if( !strncmp( cval, "GEOCENTR", astChrLen( cval ) ) ) {
            strcpy( spectype+4, "-GEO" );
         } else {
            ok = 0;
         }
      }

/* If still OK, ensure the spectral axis units are Hz or m/s. */
      cval = GetItemC( &(store->cunit), axspec, s, NULL, method, class );
      if( !cval ) {
         ok = 0;
      } else if( ok ) {
         if( !strcmp( cval, "Hz" ) ) {
            specunit = "HZ";
            specfactor = 1.0;
         } else if( !strcmp( cval, "kHz" ) ) {
            specunit = "HZ";
            specfactor = 1.0E3;
         } else if( !strcmp( cval, "MHz" ) ) {
            specunit = "HZ";
            specfactor = 1.0E6;
         } else if( !strcmp( cval, "GHz" ) ) {
            specunit = "HZ";
            specfactor = 1.0E9;
         } else if( !strcmp( cval, "m/s" ) ) {
            specunit = "m/s";
            specfactor = 1.0;
         } else if( !strcmp( cval, "km/s" ) ) {
            specunit = "m/s";
            specfactor = 1.0E3;
         } else {
            ok = 0;
         }
      }
   }

/* Save the number of axes */
   naxis = GetMaxJM( &(store->crpix), ' ' ) + 1;

/* If this is different to the value of NAXIS abort since this encoding
   does not support WCSAXES keyword. */
   if( naxis != store->naxis ) ok = 0;

/* Allocate memory to store the CDELT values */
   if( ok ) {
      cdelt = (double *) astMalloc( sizeof(double)*naxis );
      if( !cdelt ) ok = 0;
   } else {
      cdelt = NULL;
   }

/* Check that rotation is restricted to the celestial plane, and extract
   the CDELT (diagonal) terms, etc. */
   cdlat_lon = 0.0;
   cdlon_lat = 0.0;
   for( i = 0; i < naxis && ok; i++ ){
      cdl = GetItem( &(store->cdelt), i, 0, s, NULL, method, class );
      if( cdl == AST__BAD ) cdl = 1.0;

      for( j = 0; j < naxis && ok; j++ ){
          val = GetItem( &(store->pc), i, j, s, NULL, method, class );
          if( val == AST__BAD ) val = ( i == j ) ? 1.0 : 0.0;
          val *= cdl;

          if( i == j ){
             cdelt[ i ] = val;

          } else if( i == axlat && j == axlon ){
             cdlat_lon = val;

          } else if( i == axlon && j == axlat ){
             cdlon_lat = val;

          } else if( val != 0.0 ){
             ok = 0;
          }
      }
   }

/* Find the CROTA and CDELT values for the celestial axes. */
   if( ok && axlon >= 0 && axlat >= 0 ) {

      if( cdlat_lon > 0.0 ) {
         rho_a = atan2( cdlat_lon, cdelt[ axlon ] );
      } else if( cdlat_lon == 0.0 ) {
         rho_a = 0.0;
      } else {
         rho_a = atan2( -cdlat_lon, -cdelt[ axlon ] );
      }

      if( cdlon_lat > 0.0 ) {
         rho_b = atan2( cdlon_lat, -cdelt[ axlat ] );
      } else if( cdlon_lat == 0.0 ) {
         rho_b = 0.0;
      } else {
         rho_b = atan2( -cdlon_lat, cdelt[ axlat ] );
      }

      if( fabs( slaDrange( rho_a - rho_b ) ) < 1.0E-2 ){
         crota = 0.5*( slaDranrm( rho_a ) + slaDranrm( rho_b ) );
         coscro = cos( crota );
         sincro = sin( crota );

         if( fabs( coscro ) > fabs( sincro ) ){
            cdelt[ axlat ] /= coscro;
            cdelt[ axlon ] /= coscro;
         } else {
            cdelt[ axlat ] = -cdlon_lat/sincro;
            cdelt[ axlon ] = cdlat_lon/sincro;
         }      
         crota *= AST__DR2D;

      } else {
         ok = 0;
      }

   } else {
      crota = 0.0;
   }

/* Get RADECSYS and the reference equinox (called EPOCH in FITS-AIPS). */
   cval = GetItemC( &(store->radesys), 0, s, NULL, method, class );
   epoch = GetItem( &(store->equinox), 0, 0, s, NULL, method, class );

/* If RADECSYS was available... */
   if( cval ){

/* ICRS is not supported in this encoding. */
      if( !strcmp( "ICRS", cval ) ) ok = 0;
      
/* If epoch was not available, set a default epoch. */
      if( epoch == AST__BAD ){

         if( !strcmp( "FK4", cval ) ){
            epoch = 1950.0;
         } else if( !strcmp( "FK5", cval ) ){
            epoch = 2000.0;
         } else {
            ok = 0;
         }

/* If an epoch was supplied, check it is consistent with the IAU 1984
   rule. */
      } else {
         if( !strcmp( "FK4", cval ) ){
            if( epoch >= 1984.0 ) ok = 0;
         } else if( !strcmp( "FK5", cval ) ){
            if( epoch < 1984.0 ) ok = 0;
         } else {
            ok = 0;
         }
      }
   }

/* Only create the keywords if the FitsStore conforms to the requirements
   of the FITS-AIPS encoding. */
   if( ok ) {

/* Get and save CRPIX for all pixel axes. These are required, so break
   if they are not available. */
      for( j = 0; j < naxis && ok; j++ ){
         val = GetItem( &(store->crpix), 0, j, s, NULL, method, class );
         if( val == AST__BAD ) {
            ok = 0;
         } else {
            sprintf( combuf, "Reference pixel on axis %d", j + 1 );
            SetValue( this, FormatKey( "CRPIX", j + 1, -1, s ), &val, 
                      AST__FLOAT, combuf );
         }
      }

/* Get and save CRVAL for all intermediate axes. These are required, so 
   break if they are not available. */
      for( i = 0; i < naxis && ok; i++ ){
         val = GetItem( &(store->crval), i, 0, s, NULL, method, class );
         if( val == AST__BAD ) {
            ok = 0;
         } else {
            if( i == axspec ) val *= specfactor;
            sprintf( combuf, "Value at ref. pixel on axis %d", i + 1 );
            SetValue( this, FormatKey( "CRVAL", i + 1, -1, s ), &val, 
                      AST__FLOAT, combuf );
         }
      }

/* Get and save CTYPE for all intermediate axes. These are required, so 
   break if they are not available. Use the potentially modified versions 
   saved above for the celestial axes. */
      for( i = 0; i < naxis && ok; i++ ){
         if( i == axlat ) {
            cval = lattype;
         } else if( i == axlon ) {
            cval = lontype;
         } else if( i == axspec ) {
            cval = spectype;
         } else {
            cval = GetItemC( &(store->ctype), i, s, NULL, method, class );
         }
         if( cval ){
            comm = GetItemC( &(store->ctype_com), i, s, NULL, method, class );
            if( !comm ) {            
               sprintf( combuf, "Type of co-ordinate on axis %d", i + 1 );
               comm = combuf;
            }
            SetValue( this, FormatKey( "CTYPE", i + 1, -1, s ), &cval, 
                      AST__STRING, comm );
         } else {
            ok = 0;
         }
      }

/* CDELT values */
      if( axspec != -1 ) cdelt[ axspec ] *= specfactor;
      for( i = 0; i < naxis; i++ ){
         SetValue( this, FormatKey( "CDELT", i + 1, -1, s ), cdelt + i, 
                   AST__FLOAT, "Pixel size" );
      }

/* CUNIT values. */
      for( i = 0; i < naxis; i++ ) {
         cval = GetItemC( &(store->cunit), i, s, NULL, method, class );
         if( cval ) {
            if( i == axspec ) cval = specunit;
            sprintf( combuf, "Units for axis %d", i + 1 );
            SetValue( this, FormatKey( "CUNIT", i + 1, -1, s ), &cval, AST__STRING, 
                      combuf );
         }
      }

/* CROTA */
      if( axlat != -1 ){
         SetValue( this, FormatKey( "CROTA", axlat + 1, -1, s ), &crota, 
                   AST__FLOAT, "Axis rotation" );
      } else if( ( axspec == -1 && naxis > 1 ) || 
                  ( axspec != -1 && naxis > 2 ) )  {
         SetValue( this, "CROTA1", &crota, AST__FLOAT, "Axis rotation" );
      }

/* Reference equinox */
      if( epoch != AST__BAD ) SetValue( this, "EPOCH", &epoch, AST__FLOAT, 
                                        "Epoch of reference equinox" );

/* Date of observation. */
      val = GetItem( &(store->mjdobs), 0, 0, s, NULL, method, class );
      if( val != AST__BAD ) {

/* The format used for the DATE-OBS keyword depends on the value of the
   keyword. For DATE-OBS < 1999.0, use the old "dd/mm/yy" format.
   Otherwise, use the new "ccyy-mm-ddThh:mm:ss[.ssss]" format. */
         slaCaldj( 99, 1, 1, &mjd99, &jj );
         if( val < mjd99 ) {
            slaDjcal( 0, val, iymdf, &jj );
            sprintf( combuf, "%2.2d/%2.2d/%2.2d", iymdf[ 2 ], iymdf[ 1 ], 
                     iymdf[ 0 ] - ( ( iymdf[ 0 ] > 1999 ) ? 2000 : 1900 ) ); 
         } else {
            slaDjcl( val, iymdf, iymdf+1, iymdf+2, &fd, &jj );
            slaDd2tf( 3, fd, sign, ihmsf );
            sprintf( combuf, "%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d.%3.3d",
                     iymdf[0], iymdf[1], iymdf[2], ihmsf[0], ihmsf[1],
                     ihmsf[2], ihmsf[3] ); 
         }

/* Now store the formatted string in the FitsChan. */
         cval = combuf;
         SetValue( this, "DATE-OBS", (void *) &cval, AST__STRING,
                   "Date of observation" );
      }

/* Spectral stuff.. */
      if( axspec >= 0 ) {

/* Rest frequency */
         val = GetItem( &(store->restfrq), 0, 0, s, NULL, method, class );
         if( val != AST__BAD ) SetValue( this, FormatKey( "RESTFREQ", -1, -1, s ),
                                         &val, AST__FLOAT, "[Hz] Rest frequency" );
      }
   }

/* Release CDELT workspace */
   if( cdelt ) cdelt = (double *) astFree( (void *) cdelt );

/* Return zero or ret depending on whether an error has occurred. */
   return astOK ? ok : 0;
}

static int AIPSPPFromStore( AstFitsChan *this, FitsStore *store, 
                            const char *method, const char *class ){
/*
*  Name:
*     AIPSPPFromStore

*  Purpose:
*     Store WCS keywords in a FitsChan using FITS-AIPS++ encoding.

*  Type:
*     Private function.

*  Synopsis:
*     int AIPSPPFromStore( AstFitsChan *this, FitsStore *store, 
*                        const char *method, const char *class )

*  Class Membership:
*     FitsChan

*  Description:
*     A FitsStore is a structure containing a generalised represention of
*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
*     from a set of FITS header cards (using a specified encoding), or
*     an AST FrameSet. In other words, a FitsStore is an encoding-
*     independant intermediary staging post between a FITS header and 
*     an AST FrameSet.
*
*     This function copies the WCS information stored in the supplied 
*     FitsStore into the supplied FitsChan, using FITS-AIPS++ encoding.
*
*     AIPS++ encoding is like FITS-WCS encoding but with the following
*     restrictions:
*
*     1) The celestial axes must be RA/DEC, galactic or ecliptic.   
*
*     2) Only primary axis descriptions are written out.
*
*     3) RADESYS is not written and so the RADECSYS & EQUINOX values in the 
*        FitsStore must be consistent with the "1984" rule.
*
*     4) Any rotation produced by the PC matrix must be restricted to
*        the celestial plane, and must involve no shear. A CROTA keyword
*        with associated CDELT values are produced instead of the PC
*        matrix.
*     
*     5) ICRS is not supported.
*
*     6) Spectral axes can be created only for FITS-WCS CTYPE values of "FREQ"
*        "VRAD" and "VOPT-F2W" and with standards of rest of LSRK, LSRD, 
*        BARYCENT and GEOCENTR.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     store
*        Pointer to the FitsStore.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     A value of 1 is returned if succesfull, and zero is returned
*     otherwise.

*/

/* Local Variables: */
   char *comm;         /* Pointer to comment string */
   char *cval;         /* Pointer to string keyword value */
   char *specunit;     /* Pointer to corrected spectral units string */
   char combuf[80];    /* Buffer for FITS card comment */
   char lattype[MXCTYPELEN];/* Latitude axis CTYPE */
   char lontype[MXCTYPELEN];/* Longitude axis CTYPE */
   char s;             /* Co-ordinate version character */
   char sign[2];       /* Fraction's sign character */
   char spectype[MXCTYPELEN];/* Spectral axis CTYPE */
   double *cdelt;      /* Pointer to CDELT array */
   double cdl;         /* CDELT term */
   double cdlat_lon;   /* Off-diagonal CD element */
   double cdlon_lat;   /* Off-diagonal CD element */
   double coscro;      /* Cos( CROTA ) */
   double crota;       /* CROTA value to use */
   double epoch;       /* Epoch of reference equinox */
   double fd;          /* Fraction of a day */
   double mjd99;       /* MJD at start of 1999 */
   double rho_a;       /* First estimate of CROTA */
   double rho_b;       /* Second estimate of CROTA */
   double sincro;      /* Sin( CROTA ) */
   double specfactor;  /* Factor for converting internal spectral units */
   double val;         /* General purpose value */
   int axlat;          /* Index of latitude FITS WCS axis */
   int axlon;          /* Index of longitude FITS WCS axis */
   int axspec;         /* Index of spectral FITS WCS axis */
   int i;              /* Axis index */
   int ihmsf[ 4 ];     /* Hour, minute, second, fractional second */
   int iymdf[ 4 ];     /* Year, month, date, fractional day */
   int j;              /* Axis index */
   int jj;             /* SlaLib status */
   int m;              /* Projection parameter index */
   int maxm;           /* Max projection parameter index */
   int naxis;          /* No. of axes */
   int ok;             /* Is FitsSTore OK for IRAF encoding? */
   int prj;            /* Projection type */

/* Check the inherited status. */
   if( !astOK ) return 0;

/* First check that the values in the FitsStore conform to the
   requirements of the AIPS++ encoding. Assume they do to begin with. */
   ok = 1;

/* Just do primary axes. */
   s = ' '; 

/* Save the number of axes */
   naxis = GetMaxJM( &(store->crpix), ' ' ) + 1;

/* Look for the primary celestial and spectral axes. */
   FindLonLatSpecAxes( store, s, &axlon, &axlat, &axspec, method, class );

/* If both longitude and latitude axes are present ...*/
   if( axlon >= 0 && axlat >= 0 ) {

/* Get the CTYPE values for both axes. Extract the projection type as 
   specified by the last 4 characters in the latitude CTYPE keyword value. */
      cval = GetItemC( &(store->ctype), axlon, s, NULL, method, class );
      if( !cval ) {
         ok = 0;
      } else {
         strcpy( lontype, cval );
      }

      cval = GetItemC( &(store->ctype), axlat, s, NULL, method, class );
      if( !cval ) {
         ok = 0;
         prj = AST__WCSBAD;
      } else {
         strcpy( lattype, cval );
         prj = astWcsPrjType( cval + 4 );
      }

/* FITS-AIPS++ cannot handle the AST-specific TPN projection. */
      if( prj == AST__TPN ) ok = 0;

/* Projection parameters. FITS-AIPS++ encoding ignores projection parameters 
   associated with the longitude axis. The number of parameters is limited to 
   10. */
      maxm = GetMaxJM( &(store->pv), ' ' );
      for( i = 0; i < naxis && ok; i++ ){
         if( i != axlon ) {
            for( m = 0; m <= maxm; m++ ){
               val = GetItem( &(store->pv), i, m, s, NULL, method, class );
               if( val != AST__BAD ) {
                  if( i != axlat || m >= 10 ){
                     ok = 0;
                     break;
                  }
               } 
            }
         }
      }

/* Identify the celestial coordinate system from the first 4 characters of the
   longitude CTYPE value. Only RA, galactic longitude, and ecliptic
   longitude can be stored using FITS-AIPS++. */
      if( ok && strncmp( lontype, "RA--", 4 ) &&
                strncmp( lontype, "GLON", 4 ) &&
                strncmp( lontype, "ELON", 4 ) ) ok = 0;

   }

/* If a spectral axis is present ...*/
   if( axspec >= 0 ) {

/* Get the CTYPE values for the axis, and find the AIPS equivalent, if
   possible. */
      cval = GetItemC( &(store->ctype), axspec, s, NULL, method, class );
      if( !cval ) {
         ok = 0;
      } else {
         if( !strncmp( cval, "FREQ", astChrLen( cval ) ) ) {
            strcpy( spectype, "FREQ" );
         } else if( !strncmp( cval, "VRAD", astChrLen( cval ) ) ) {
            strcpy( spectype, "VELO" );
         } else if( !strncmp( cval, "VOPT-F2W", astChrLen( cval ) ) ) {
            strcpy( spectype, "FELO" );
         } else {
            ok = 0;
         }
      }

/* If OK, check the SPECSYS value and add the AIPS equivalent onto the
   end of the CTYPE value.*/
      cval = GetItemC( &(store->specsys), 0, s, NULL, method, class );
      if( !cval ) {
         ok = 0;
      } else {
         if( !strncmp( cval, "LSRK", astChrLen( cval ) ) ) {
            strcpy( spectype+4, "-LSR" );
         } else if( !strncmp( cval, "LSRD", astChrLen( cval ) ) ) {
            strcpy( spectype+4, "-LSD" );
         } else if( !strncmp( cval, "BARYCENT", astChrLen( cval ) ) ) {
            strcpy( spectype+4, "-HEL" );
         } else if( !strncmp( cval, "GEOCENTR", astChrLen( cval ) ) ) {
            strcpy( spectype+4, "-GEO" );
         } else {
            ok = 0;
         }
      }

/* If still OK, ensure the spectral axis units are Hz or m/s. */
      cval = GetItemC( &(store->cunit), axspec, s, NULL, method, class );
      if( !cval ) {
         ok = 0;
      } else if( ok ) {
         if( !strcmp( cval, "Hz" ) ) {
            specunit = "HZ";
            specfactor = 1.0;
         } else if( !strcmp( cval, "kHz" ) ) {
            specunit = "HZ";
            specfactor = 1.0E3;
         } else if( !strcmp( cval, "MHz" ) ) {
            specunit = "HZ";
            specfactor = 1.0E6;
         } else if( !strcmp( cval, "GHz" ) ) {
            specunit = "HZ";
            specfactor = 1.0E9;
         } else if( !strcmp( cval, "m/s" ) ) {
            specunit = "m/s";
            specfactor = 1.0;
         } else if( !strcmp( cval, "km/s" ) ) {
            specunit = "m/s";
            specfactor = 1.0E3;
         } else {
            ok = 0;
         }
      }
   }

/* If this is different to the value of NAXIS abort since this encoding
   does not support WCSAXES keyword. */
   if( naxis != store->naxis ) ok = 0;

/* Allocate memory to store the CDELT values */
   if( ok ) {
      cdelt = (double *) astMalloc( sizeof(double)*naxis );
      if( !cdelt ) ok = 0;
   } else {
      cdelt = NULL;
   }

/* Check that rotation is restricted to the celestial plane, and extract
   the CDELT (diagonal) terms, etc. */
   cdlat_lon = 0.0;
   cdlon_lat = 0.0;
   for( i = 0; i < naxis && ok; i++ ){
      cdl = GetItem( &(store->cdelt), i, 0, s, NULL, method, class );
      if( cdl == AST__BAD ) cdl = 1.0;

      for( j = 0; j < naxis && ok; j++ ){
          val = GetItem( &(store->pc), i, j, s, NULL, method, class );
          if( val == AST__BAD ) val = ( i == j ) ? 1.0 : 0.0;
          val *= cdl;

          if( i == j ){
             cdelt[ i ] = val;

          } else if( i == axlat && j == axlon ){
             cdlat_lon = val;

          } else if( i == axlon && j == axlat ){
             cdlon_lat = val;

          } else if( val != 0.0 ){
             ok = 0;
          }
      }
   }

/* Find the CROTA and CDELT values for the celestial axes. */
   if( ok && axlon >= 0 && axlat >= 0 ) {

      if( cdlat_lon > 0.0 ) {
         rho_a = atan2( cdlat_lon, cdelt[ axlon ] );
      } else if( cdlat_lon == 0.0 ) {
         rho_a = 0.0;
      } else {
         rho_a = atan2( -cdlat_lon, -cdelt[ axlon ] );
      }

      if( cdlon_lat > 0.0 ) {
         rho_b = atan2( cdlon_lat, -cdelt[ axlat ] );
      } else if( cdlon_lat == 0.0 ) {
         rho_b = 0.0;
      } else {
         rho_b = atan2( -cdlon_lat, cdelt[ axlat ] );
      }

      if( fabs( slaDrange( rho_a - rho_b ) ) < 1.0E-2 ){
         crota = 0.5*( slaDranrm( rho_a ) + slaDranrm( rho_b ) );
         coscro = cos( crota );
         sincro = sin( crota );

         if( fabs( coscro ) > fabs( sincro ) ){
            cdelt[ axlat ] /= coscro;
            cdelt[ axlon ] /= coscro;
         } else {
            cdelt[ axlat ] = -cdlon_lat/sincro;
            cdelt[ axlon ] = cdlat_lon/sincro;
         }      
         crota *= AST__DR2D;

/* Use AST__BAD to indicate that CDi_j values shou;ld be produced
   instead of CROAT/CDELT. (I am told AIPS++ can understand CD matrices) */
      } else {
         crota = AST__BAD;
      }

   } else {
      crota = 0.0;
   }

/* Get RADECSYS and the reference equinox. */
   cval = GetItemC( &(store->radesys), 0, s, NULL, method, class );
   epoch = GetItem( &(store->equinox), 0, 0, s, NULL, method, class );

/* If RADECSYS was available... */
   if( cval ){

/* ICRS is not supported in this encoding. */
      if( !strcmp( "ICRS", cval ) ) ok = 0;
      
/* If epoch was not available, set a default epoch. */
      if( epoch == AST__BAD ){

         if( !strcmp( "FK4", cval ) ){
            epoch = 1950.0;
         } else if( !strcmp( "FK5", cval ) ){
            epoch = 2000.0;
         } else {
            ok = 0;
         }

/* If an equinox was supplied, check it is consistent with the IAU 1984
   rule. */
      } else {
         if( !strcmp( "FK4", cval ) ){
            if( epoch >= 1984.0 ) ok = 0;
         } else if( !strcmp( "FK5", cval ) ){
            if( epoch < 1984.0 ) ok = 0;
         } else {
            ok = 0;
         }
      }
   }

/* Only create the keywords if the FitsStore conforms to the requirements
   of the FITS-AIPS++ encoding. */
   if( ok ) {

/* Get and save CRPIX for all pixel axes. These are required, so break
   if they are not available. */
      for( j = 0; j < naxis && ok; j++ ){
         val = GetItem( &(store->crpix), 0, j, s, NULL, method, class );
         if( val == AST__BAD ) {
            ok = 0;
         } else {
            sprintf( combuf, "Reference pixel on axis %d", j + 1 );
            SetValue( this, FormatKey( "CRPIX", j + 1, -1, s ), &val, 
                      AST__FLOAT, combuf );
         }
      }

/* Get and save CRVAL for all intermediate axes. These are required, so 
   break if they are not available. */
      for( i = 0; i < naxis && ok; i++ ){
         val = GetItem( &(store->crval), i, 0, s, NULL, method, class );
         if( val == AST__BAD ) {
            ok = 0;
         } else {
            if( i == axspec ) val *= specfactor;
            sprintf( combuf, "Value at ref. pixel on axis %d", i + 1 );
            SetValue( this, FormatKey( "CRVAL", i + 1, -1, s ), &val, 
                      AST__FLOAT, combuf );
         }
      }

/* Get and save CTYPE for all intermediate axes. These are required, so 
   break if they are not available. Use the potentially modified versions 
   saved above for the celestial axes. */
      for( i = 0; i < naxis && ok; i++ ){
         if( i == axlat ) {
            cval = lattype;
         } else if( i == axlon ) {
            cval = lontype;
         } else if( i == axspec ) {
            cval = spectype;
         } else {
            cval = GetItemC( &(store->ctype), i, s, NULL, method, class );
         }
         if( cval ){
            comm = GetItemC( &(store->ctype_com), i, s, NULL, method, class );
            if( !comm ) {            
               sprintf( combuf, "Type of co-ordinate on axis %d", i + 1 );
               comm = combuf;
            }
            SetValue( this, FormatKey( "CTYPE", i + 1, -1, s ), &cval, 
                      AST__STRING, comm );
         } else {
            ok = 0;
         }
      }

/* CDELT values */
      if( axspec != -1 ) cdelt[ axspec ] *= specfactor;
      for( i = 0; i < naxis; i++ ){
         SetValue( this, FormatKey( "CDELT", i + 1, -1, s ), cdelt + i, 
                   AST__FLOAT, "Pixel size" );
      }

/* CUNIT values. [Spectral axis units should be upper-case] */
      for( i = 0; i < naxis; i++ ) {
         cval = GetItemC( &(store->cunit), i, s, NULL, method, class );
         if( cval ) {
            if( i == axspec ) cval = specunit;
            sprintf( combuf, "Units for axis %d", i + 1 );
            SetValue( this, FormatKey( "CUNIT", i + 1, -1, s ), &cval, AST__STRING, 
                      combuf );
         }
      }

/* CD matrix. Multiply the row of the PC matrix by the CDELT value. */
      if( crota == AST__BAD ) {

         for( i = 0; i < naxis; i++ ) {
            cdl = GetItem( &(store->cdelt), i, 0, s, NULL, method, class );
            if( cdl == AST__BAD ) cdl = 1.0;
       
            for( j = 0; j < naxis; j++ ){
               val = GetItem( &(store->pc), i, j, s, NULL, method, class );
               if( val == AST__BAD ) val = ( i == j ) ? 1.0 : 0.0;
               val *= cdl;

               if( val != 0.0 ) {
                   SetValue( this, FormatKey( "CD", i + 1, j + 1, s ), &val, 
                             AST__FLOAT, "Transformation matrix element" );
               }
            }
         }

/* CROTA */
      } else if( crota != 0.0 ) {
         if( axlat != -1 ){
            SetValue( this, FormatKey( "CROTA", axlat + 1, -1, s ), &crota, 
                      AST__FLOAT, "Axis rotation" );
         } else if( ( axspec == -1 && naxis > 1 ) || 
                    ( axspec != -1 && naxis > 2 ) ) {
            SetValue( this, "CROTA1", &crota, AST__FLOAT, "Axis rotation" );
         }
      }

/* Reference equinox */
      if( epoch != AST__BAD ) SetValue( this, "EPOCH", &epoch, AST__FLOAT, 
                                        "Epoch of reference equinox" );

/* Latitude of native north pole. */
      val = GetItem( &(store->latpole), 0, 0, s, NULL, method, class );
      if( val != AST__BAD ) SetValue( this, "LATPOLE", &val, AST__FLOAT, 
                                      "Latitude of native north pole" );

/* Longitude of native north pole. */
      val = GetItem( &(store->lonpole), 0, 0, s, NULL, method, class );
      if( val != AST__BAD ) SetValue( this, "LONPOLE", &val, AST__FLOAT, 
                                      "Longitude of native north pole" );

/* Date of observation. */
      val = GetItem( &(store->mjdobs), 0, 0, s, NULL, method, class );
      if( val != AST__BAD ) {

/* The format used for the DATE-OBS keyword depends on the value of the
   keyword. For DATE-OBS < 1999.0, use the old "dd/mm/yy" format.
   Otherwise, use the new "ccyy-mm-ddThh:mm:ss[.ssss]" format. */
         slaCaldj( 99, 1, 1, &mjd99, &jj );
         if( val < mjd99 ) {
            slaDjcal( 0, val, iymdf, &jj );
            sprintf( combuf, "%2.2d/%2.2d/%2.2d", iymdf[ 2 ], iymdf[ 1 ], 
                     iymdf[ 0 ] - ( ( iymdf[ 0 ] > 1999 ) ? 2000 : 1900 ) ); 
         } else {
            slaDjcl( val, iymdf, iymdf+1, iymdf+2, &fd, &jj );
            slaDd2tf( 3, fd, sign, ihmsf );
            sprintf( combuf, "%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d.%3.3d",
                     iymdf[0], iymdf[1], iymdf[2], ihmsf[0], ihmsf[1],
                     ihmsf[2], ihmsf[3] ); 
         }

/* Now store the formatted string in the FitsChan. */
         cval = combuf;
         SetValue( this, "DATE-OBS", (void *) &cval, AST__STRING,
                   "Date of observation" );
      }

/* Projection parameters. */
      for( m = 0; m <= maxm; m++ ){
         val = GetItem( &(store->pv), axlat, m, s, NULL, method, class );
         if( val != AST__BAD ) SetValue( this, FormatKey( "PROJP", m, -1, ' ' ), 
                                         &val, AST__FLOAT, "Projection parameter" );
      }

/* Spectral stuff.. */
      if( axspec >= 0 ) {

/* Rest frequency */
         val = GetItem( &(store->restfrq), 0, 0, s, NULL, method, class );
         if( val != AST__BAD ) SetValue( this, FormatKey( "RESTFREQ", -1, -1, s ),
                                         &val, AST__FLOAT, "[Hz] Rest frequency" );
      }
   }

/* Release CDELT workspace */
   if( cdelt ) cdelt = (double *) astFree( (void *) cdelt );

/* Return zero or ret depending on whether an error has occurred. */
   return astOK ? ok : 0;
}

static char *CardComm( AstFitsChan *this ){
/*
*  Name:
*     CardComm

*  Purpose:
*     Return the keyword comment from the current card.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     char *CardComm( AstFitsChan *this )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Returns a pointer to a string holding the keyword comment from the
*     current card.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     A pointer to the keyword comment, or NULL if the FitsChan is at
*     end-of-file, or does not have a comment.

*  Notes:
*     -  The current card is not changed by this function.
*     -  This function attempts to execute even if an error has occurred.
*/

/* Local Variables: */
   char *ret;

/* Check the supplied object. */
   if( !this ) return NULL;

/* If the current card is defined, store a pointer to its keyword comment. */
   if( this->card ){
      ret = ( (FitsCard *) this->card )->comment;

/* Otherwise store a NULL pointer. */
   } else {
      ret =  NULL;
   }

/* Return the answer. */
   return ret;

}

static void *CardData( AstFitsChan *this, size_t *size ){
/*
*  Name:
*     CardData

*  Purpose:
*     Return a pointer to the keyword data value for the current card.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void *CardData( AstFitsChan *this, size_t *size )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Returns a pointer to keyword data value from the current card.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     size
*        A pointer to a location at which to return the number of bytes
*        occupied by the data value. NULL can be supplied if this
*        information is not required.

*  Returned Value:
*     A pointer to the keyword data, or NULL if the FitsChan is at
*     end-of-file, or if the keyword does not have any data.

*  Notes:
*     -  For text data, the returned value for "size" includes the
*     terminating null character.
*     -  The current card is not changed by this function.
*     -  This function attempts to execute even if an error has occurred.
*/

/* Local Variables: */
   void *ret;

/* Check the supplied object. */
   if( !this ) return NULL;

/* If the current card is defined, store a pointer to its keyword data. */
   if( this->card ){
      ret = ( (FitsCard *) this->card )->data;
      if( size ) *size = ( (FitsCard *) this->card )->size;

/* Otherwise store a NULL pointer. */
   } else {
      ret =  NULL;
      if( size ) *size = 0;
   }

/* Return the answer. */
   return ret;

}

static int *CardFlags( AstFitsChan *this ){
/*
*  Name:
*     CardFlags

*  Purpose:
*     Return a pointer to the flags mask for the current card.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int *CardFlags( AstFitsChan *this )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Returns a pointer to the flags mask for the current card. 

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     The pointer to the flags mask.

*  Notes:
*     -  The current card is not changed by this function.
*     -  NULL is returned if the current card is not defined.
*     -  This function attempts to execute even if an error has occurred.
*/

/* Local Variables: */
   int *ret;

/* Check the supplied object. */
   if( !this ) return NULL;

/* If the current card is defined, store its deletion flag. */
   if( this->card ){
      ret = &( ( (FitsCard *) this->card )->flags );

/* Otherwise store zero. */
   } else {
      ret =  NULL;
   }

/* Return the answer. */
   return ret;

}

static char *CardName( AstFitsChan *this ){
/*
*  Name:
*     CardName

*  Purpose:
*     Return the keyword name from the current card.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     char *CardName( AstFitsChan *this )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Returns a pointer to a string holding the keyword name from the
*     current card.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     A pointer to the keyword name, or NULL if the FitsChan is at
*     end-of-file.

*  Notes:
*     -  The current card is not changed by this function.
*     -  This function attempts to execute even if an error has occurred.
*/

/* Local Variables: */
   char *ret;

/* Check the supplied object. */
   if( !this ) return NULL;

/* If the current card is defined, store a pointer to its keyword name. */
   if( this->card ){
      ret = ( (FitsCard *) this->card )->name;

/* Otherwise store a NULL pointer. */
   } else {
      ret =  NULL;
   }

/* Return the answer. */
   return ret;

}

static int CardType( AstFitsChan *this ){
/*
*  Name:
*     CardType

*  Purpose:
*     Return the keyword type from the current card.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int CardType( AstFitsChan *this )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Returns the keyword type from the current card.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     The keyword type.

*  Notes:
*     -  The current card is not changed by this function.
*     -  AST__NOTYPE is returned if the current card is not defined.
*     -  This function attempts to execute even if an error has occurred.
*/

/* Local Variables: */
   int ret;

/* Check the supplied object. */
   if( !this ) return AST__NOTYPE;

/* If the current card is defined, store the keyword type. */
   if( this->card ){
      ret = ( (FitsCard *) this->card )->type;

/* Otherwise store AST__NOTYPE. */
   } else {
      ret =  AST__NOTYPE;
   }

/* Return the answer. */
   return ret;

}

static AstMapping *CelestialAxes( AstFrameSet *fs, double *dim, int *wperm, 
                                  char s, FitsStore *store, int *axis_done, 
                                  const char *method, const char *class ){
/*
*  Name:
*     CelestialAxes

*  Purpose:
*     Add values to a FitsStore describing celestial axes in a Frame.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     AstMapping *CelestialAxes( AstFrameSet *fs, double *dim, int *wperm, 
*                                char s, FitsStore *store, int *axis_done, 
*                                const char *method, const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The current Frame of the supplied FrameSet is searched for celestial
*     axes. If any are found, FITS WCS keyword values describing the axis
*     are added to the supplied FitsStore, if possible (the conventions
*     of FITS-WCS paper II are used). Note, this function does not store
*     values for keywords which define the transformation from pixel
*     coords to Intermediate World Coords (CRPIX, PC and CDELT), but a
*     Mapping is returned which embodies these values. This Mapping is
*     from the current Frame in the FrameSet (WCS coords) to a Frame 
*     representing IWC. The IWC Frame has the same number of axes as the 
*     WCS Frame which may be greater than the number of base Frame (i.e. 
*     pixel) axes. 

*  Parameters:
*     fs
*        Pointer to the FrameSet. The base Frame should represent FITS pixel
*        coordinates, and the current Frame should represent FITS WCS
*        coordinates. The number of base Frame axes should not exceed the
*        number of current Frame axes.
*     dim
*        An array holding the image dimensions in pixels. AST__BAD can be 
*        supplied for any unknwon dimensions.
*     wperm
*        Pointer to an array of integers with one element for each axis of 
*        the current Frame. Each element holds the zero-based 
*        index of the FITS-WCS axis (i.e. the value of "i" in the keyword 
*        names "CTYPEi", "CRVALi", etc) which describes the Frame axis.
*     s
*        The co-ordinate version character. A space means the primary
*        axis descriptions. Otherwise the supplied character should be 
*        an upper case alphabetical character ('A' to 'Z'). 
*     store
*        The FitsStore in which to store the FITS WCS keyword values.
*     axis_done 
*        An array of flags, one for each Frame axis, which indicate if a
*        description of the corresponding axis has yet been stored in the
*        FitsStore. 
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     If celestial axes were found which can be described using the
*     conventions of FITS-WCS paper II, then a Mapping from the current Frame 
*     of the supplied FrameSet, to the IWC Frame is returned. Otherwise,
*     a UnitMap is returned. Note, the Mapping only defines the IWC
*     transformation for celestial axes. Any non-celestial axes are passed
*     unchanged by the returned Mapping.

*/

/* Local Variables: */
   AstFrame *pframe;       /* Primary Frame containing current WCS axis*/
   AstFrame *wcsfrm;       /* WCS Frame within FrameSet */
   AstMapping *map1;       /* Pointer to pre-WcsMap Mapping */
   AstMapping *map3;       /* Pointer to post-WcsMap Mapping */
   AstMapping *map;        /* Pixel -> WCS mapping */
   AstMapping *ret;        /* Returned Mapping */
   AstMapping *tmap0;      /* A temporary Mapping */
   AstMapping *tmap1;      /* A temporary Mapping */
   AstMapping *tmap2;      /* A temporary Mapping */
   AstMapping *tmap3;      /* A temporary Mapping */
   AstMapping *tmap4;      /* A temporary Mapping */
   AstSkyFrame *skyfrm;    /* The SkyFrame defining current WCS axis */
   AstWcsMap *map2;        /* Pointer to WcsMap */
   AstWcsMap *map2b;       /* Pointer to WcsMap with cleared lat/lonpole */
   char *cval;             /* Pointer to keyword value */
   char *temp;             /* Pointer to temporary string */
   double *ppcfid;         /* Pointer to array holding PPC at fiducial point */
   double *mat;            /* Pointer to matrix diagonal elements */
   double con;             /* Constant value for unassigned axes */
   double pv;              /* Projection parameter value */
   double skyfid[ 2 ];     /* Sky coords of fiducial point */
   double val;             /* Keyword value */
   int *inperm;            /* Input axis permutation array */
   int *outperm;           /* Output axis permutation array */
   int *tperm;             /* Pointer to new FITS axis numbering array */
   int axlat;              /* Index of latitude output from WcsMap */
   int axlon;              /* Index of longitude output from WcsMap */
   int fits_ilat;          /* FITS WCS axis index for latitude axis */
   int fits_ilon;          /* FITS WCS axis index for longitude axis */
   int i;                  /* Loop index */
   int iax;                /* Axis index */
   int ilat;               /* Index of latitude axis within total WCS Frame */
   int ilon;               /* Index of longitude axis within total WCS Frame */
   int m;                  /* Projection parameter index */
   int maxm;               /* Largest used "m" value */
   int npix;               /* Number of pixel axes */
   int nwcs;               /* Number of WCS axes */
   int nwcsmap;            /* Number of inputs/outputs for the WcsMap */
   int paxis;              /* Axis index within primary Frame */
   int skylataxis;         /* Index of latitude axis within SkyFrame */
   int skylonaxis;         /* Index of longitude axis within SkyFrame */
   int tpn;                /* Is the WCS projectiona TPN projection? */

/* Initialise */
   ret = NULL;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Get a pointer to the WCS Frame. */
   wcsfrm = astGetFrame( fs, AST__CURRENT );

/* Store the number of pixel and WCS axes. */
   npix = astGetNin( fs );
   nwcs = astGetNout( fs );

/* Check each axis in the WCS Frame to see if it is a celestial axis. */
   skyfrm = NULL;
   map = NULL;
   ilon = -1;
   ilat = -1;
   for( iax = 0; iax < nwcs; iax++ ) {

/* Obtain a pointer to the primary Frame containing the current WCS axis. */
      astPrimaryFrame( wcsfrm, iax, &pframe, &paxis );

/* If the current axis belongs to a SkyFrame, we have found a celestial
   axis. Keep a pointer to it, and note the indices of the celestial axes
   within the complete WCS Frame. The MakeFitsFrameSet function will have
   ensured that the WCS Frame only contains at most a single SkyFrame. */
      if( astIsASkyFrame( pframe ) ) {
         if( !skyfrm ) skyfrm = astClone( pframe );
         if( paxis == 0 ) {
            ilon = iax;
         } else {
            ilat = iax;
         }

/* Indicate that this axis has been classified. */
         axis_done[ iax ] = 1;
      }

/* Release resources. */
      pframe = astAnnul( pframe );
   }

/* Only proceed if we found celestial axes. */
   if( ilon != -1 && ilat != -1 ) {

/* Create an array to hold the Projection Plane Coords corresponding to the 
   CRVALi keywords. */
      ppcfid = (double *) astMalloc( sizeof( double )*nwcs );

/* Get the pixel->wcs Mapping. */
      map = astGetMapping( fs, AST__BASE, AST__CURRENT );

/* Some of the required FITS Keyword values are defined by the WcsMap
   contained within the Mapping. Split the mapping up into a list of serial 
   component mappings, and locate the first WcsMap in this list. The first 
   Mapping returned by this call is the result of compounding all the 
   Mappings up to (but not including) the WcsMap, the second returned Mapping 
   is the (inverted) WcsMap, and the third returned Mapping is anything 
   following the WcsMap. Only proceed if one and only one WcsMap is found. */
      if( SplitMap( map, astGetInvert( map ), ilon, ilat, &map1, &map2, &map3 ) ){

/* Get the indices of the latitude and longitude axes within the SkyFrame
   (not necessarily (1,0) because they may have been permuted). */
         skylataxis = astGetLatAxis( skyfrm );
         skylonaxis = astGetLonAxis( skyfrm );

/* The reference point in the celestial coordinate system is found by
   transforming the fiducial point in native spherical co-ordinates
   into WCS coordinates using map3. */
         if( GetFiducialWCS( map2, map3, ilon,  ilat, skyfid + skylonaxis, 
                         skyfid + skylataxis ) ){

/* We also need to find the indices of the longitude and latitude outputs 
   from the WcsMap. These may not be the same as ilat and ilon because of 
   axis permutations in "map3". */
            axlon = astGetWcsAxis( map2, 0 );
            axlat = astGetWcsAxis( map2, 1 );

/* Note the FITS WCS axis indices for the longitude and latitude axes */
            fits_ilon = wperm[ ilon ];
            fits_ilat = wperm[ ilat ];

/* Normalise the latitude and longitude values at the fiducial point. The
   longitude and latitude values found above will be in radians, but after
   normalization we convert them to degrees, as expected by other functions 
   which handle FitsStores. */
            if( skyfid[ skylonaxis ] == AST__BAD ) skyfid[ skylonaxis ] = 0.0;
            if( skyfid[ skylataxis ] == AST__BAD ) skyfid[ skylataxis ] = 0.0;

            if( ZEROANG( skyfid[ 0 ] ) ) skyfid[ 0 ] = 0.0;
            if( ZEROANG( skyfid[ 1 ] ) ) skyfid[ 1 ] = 0.0;

            astNorm( skyfrm, skyfid );          

            SetItem( &(store->crval), fits_ilon, 0, s, AST__DR2D*skyfid[ skylonaxis ] );
            SetItem( &(store->crval), fits_ilat, 0, s, AST__DR2D*skyfid[ skylataxis ] );

/* Set a flag if we have a TPN projection. This is an AST-specific
   projection which mimicks the old "TAN with correction terms" projection
   which was removed from the final version of the FITS-WCS paper II. */
            tpn = ( astGetWcsType( map2 ) == AST__TPN );

/* Store the WCS projection parameters. Except for TPN projections, always 
   exclude parameters 3 and 4 on the longitude axis since these are
   reserved to hold copies of LONPOLE and LATPOLE. */
            for( m = 0; m < WCSLIB_MXPAR; m++ ){
               if( astTestPV( map2, axlon, m ) ) {
                  if( m < 3 || m > 4 || tpn ) {
                     pv = astGetPV( map2, axlon, m );
                     if( pv != AST__BAD ) SetItem( &(store->pv), fits_ilon, m, 
                                                   s, pv );
                  }
               }
               if( astTestPV( map2, axlat, m ) ) {
                  pv = astGetPV( map2, axlat, m );
                  if( pv != AST__BAD ) SetItem( &(store->pv), fits_ilat, m, 
                                                s, pv );
               }
            }

/* If PVi_0 (for the longitude axis) is non-zero, the Cartesian coordinates 
   used by the WcsMap (Projection Plane Coordinates, PPC) need to be shifted
   to produce Intermediate World Coordinates (IWC). This shift results in 
   the pixel reference position specified by the CRPIXi values (and which
   corresponds to the origin of IWC) mapping on to the fiducial position 
   specified by the CRVALi values. The required shifts are just the PPC 
   coordinates of the fiducial point. The AST-specific "TPN" projection uses 
   longitude projection parameters to define correction terms, and so cannot 
   use the above convention (which is part of FITS-WCS paper II). Therefore 
   TPN projections always use zero shift between PPC and IWC. */
            for( iax = 0; iax < nwcs; iax++ ) ppcfid[ iax ] = 0.0;
            if( !tpn && astGetPV( map2, axlon, 0 ) != 0.0 ) {
               GetFiducialPPC( (AstWcsMap *) map2, ppcfid + ilon, ppcfid + ilat  );
               if( ppcfid[ ilon ] == AST__BAD ) ppcfid[ ilon ] = 0.0;
               if( ppcfid[ ilat ] == AST__BAD ) ppcfid[ ilat ] = 0.0;
               ppcfid[ ilon ] *= AST__DR2D;
               ppcfid[ ilat ] *= AST__DR2D;
            }

/* Store the CTYPE, CNAME, EQUINOX, MJDOBS, and RADESYS values. */
            SkySys( skyfrm, astGetWcsType( map2 ), store, fits_ilon, 
                    fits_ilat, s, method, class );

/* Store the LONPOLE and LATPOLE values in the FitsStore. */
            SkyPole( map2, map3, ilon, ilat, wperm, s, store, method, class );

/* The values of LONPOLE and LATPOLE stored above (in the FitsStore) will be 
   ignored by WcsNative if the WcsMap contains set values for projection 
   parameters PVi_3a and/or PVi_4a (these will be used in preference to
   the values in the FitsStore). To avoid this happening we take a copy
   of the WcsMap and clear the relevant parameters (but not if the WcsMap is
   for a TPN projection because TPN uses PVi_3a and PVi_4a for other
   purposes). */
            if( astGetWcsType( map2 ) != AST__TPN ) {
               map2b = astCopy( map2 );
               astClearPV( map2b, axlon, 3 );
               astClearPV( map2b, axlon, 4 );
            } else {
               map2b = astClone( map2 );
            }

/* We will now create the Mapping from WCS coords to IWC coords. In fact,
   we produce the Mapping from IWC to WCS and then invert it. Create the 
   first component of this Mapping which implements any shift of origin 
   from IWC to PPC. */
            tmap0 = (AstMapping *) astShiftMap( nwcs, ppcfid, "" );

/* The next component of this Mapping scales the PPC coords from degrees 
   to radians on the celestial axes. */
            mat = astMalloc( sizeof( double )*(size_t) nwcs );
            if( astOK ) {
               for( iax = 0; iax < nwcs; iax++ ) mat[ iax ] = 1.0;
               mat[ ilon ] = AST__DD2R;
               mat[ ilat ] = AST__DD2R;
               tmap1 = (AstMapping *) astMatrixMap( nwcs, nwcs, 1, mat, "" );
               mat = astFree( mat );
            } else {
               tmap1 = NULL;
            }

/* Now create the Mapping from Native Spherical Coords to WCS. */
            tmap2 = WcsNative( NULL, store, s, map2b, fits_ilon, fits_ilat, 
                               method, class );

/* Combine the WcsMap with the above Mapping, to get the Mapping from PPC
   to WCS. */
            tmap3 = (AstMapping *) astCmpMap( map2b, tmap2, 1, "" );
            tmap2 = astAnnul( tmap2 );

/* The pixel->wcs mapping may include a PermMap which selects some sub-set 
   or super-set of the orignal WCS axes. In this case the number of inputs 
   and outputs for "tmap3" created above may not equal "nwcs". To avoid this, 
   we embed "tmap3" between 2 PermMaps which select the required axes. */
            nwcsmap = astGetNin( map2b );
            if( nwcsmap != nwcs || ilon != axlon || ilat != axlat ) {
               inperm = astMalloc( sizeof( int )*(size_t) nwcs );
               outperm = astMalloc( sizeof( int )*(size_t) nwcsmap );
               if( astOK ) {

/* Indicate that no inputs of the PermMap have yet been assigned to any
   outputs */
                  for( i = 0; i < nwcs; i++ ) inperm[ i ] = -1;

/* Assign the WcsMap long/lat axes to the WCS Frame long/lat axes */
                  inperm[ ilon ] = axlon;
                  inperm[ ilat ] = axlat;

/* Assign the remaining inputs arbitrarily (doesn't matter how we do this
   since the WcsMap is effectively a UnitMap on all non-celestial axes). */
                  iax = 0;
                  for( i = 0; i < nwcs; i++ ) {
                     while( iax == axlon || iax == axlat ) iax++;
                     if( inperm[ i ] == -1 ) inperm[ i ] = iax++;
                  }

/* Do the same for the outputs. */
                  for( i = 0; i < nwcsmap; i++ ) outperm[ i ] = -1;
                  outperm[ axlon ] = ilon;
                  outperm[ axlat ] = ilat;
                  iax = 0;
                  for( i = 0; i < nwcsmap; i++ ) {
                     while( iax == ilon || iax == ilat ) iax++;
                     if( outperm[ i ] == -1 ) outperm[ i ] = iax++;
                  }

/* Create the PermMap. */
                  con = AST__BAD;
                  tmap2 = (AstMapping *) astPermMap( nwcs, inperm, nwcsmap,
                                                     outperm, &con, "" );

/* Sandwich the WcsMap between the PermMap and its inverse. */
                  tmap4 = (AstMapping *) astCmpMap( tmap2, tmap3, 1, "" );
                  tmap3 = astAnnul( tmap3 );
                  astInvert( tmap2 );
                  tmap3 = (AstMapping *) astCmpMap( tmap4, tmap2, 1, "" );
                  tmap2 = astAnnul( tmap2 );
                  tmap4 = astAnnul( tmap4 );

               }

               inperm = astFree( inperm );
               outperm = astFree( outperm );

            }

/* Combine these Mappings together. */
            tmap4 = (AstMapping *) astCmpMap( tmap0, tmap1, 1, "" );
            tmap0 = astAnnul( tmap0 );
            tmap1 = astAnnul( tmap1 );
            ret = (AstMapping *) astCmpMap( tmap4, tmap3, 1, "" );
            tmap3 = astAnnul( tmap3 );
            tmap4 = astAnnul( tmap4 );

/* Invert this Mapping to get the Mapping from WCS to IWC. */
            astInvert( ret );

/* The spherical rotation involved in converting WCS to IWC can result in
   in appropriate numbering of the FITS axes. For instance, a LONPOLE
   value of 90 degrees causes the IWC axes to be transposed. For this
   reason we re-asses the FITS axis numbers assigned to the celestial
   axes in order to make the IWC axes as close as possible to the pixel
   axes with the same number. To do this, we need the Mapping from pixel
   to IWC, which is formed by concatenating the pixel->WCS Mapping with the
   WCS->IWC Mapping. */
            tmap0 = (AstMapping *) astCmpMap( map, ret, 1, "" );

/* Find the outputs of this Mapping which should be associated with each
   input. */
            tperm = astMalloc( sizeof(int)*(size_t) nwcs );
            WorldAxes( tmap0, dim, tperm );

/* If the index associated with the celestial axes appear to have been 
   swapped... */
            if( astOK && fits_ilon == tperm[ ilat ] && 
                         fits_ilat == tperm[ ilon ] ) {

/* Swap the fits axis indices associated with each WCS axis to match. */
               wperm[ ilon ] = fits_ilat;
               wperm[ ilat ] = fits_ilon;

/* Swap the stored CRVAL value for the longitude and latitude axis. */
               val = GetItem( &(store->crval), fits_ilat, 0, s, NULL, method, class );
               SetItem( &(store->crval), fits_ilat, 0, s, 
                        GetItem( &(store->crval), fits_ilon, 0, s, NULL, 
                        method, class ) );
               SetItem( &(store->crval), fits_ilon, 0, s, val );

/* Swap the stored CTYPE value for the longitude and latitude axis. */
               cval = GetItemC( &(store->ctype), fits_ilat, s, NULL, method, class );
               if( cval ) {
                  temp = astStore( NULL, (void *) cval, strlen( cval ) + 1 );
                  cval = GetItemC( &(store->ctype), fits_ilon, s, NULL, method, class );
                  if( cval ) {
                     SetItemC( &(store->ctype), fits_ilat, s, cval );
                     SetItemC( &(store->ctype), fits_ilon, s, temp );
                  }
                  temp = astFree( temp );
               }

/* Swap the stored CNAME value for the longitude and latitude axis. */
               cval = GetItemC( &(store->cname), fits_ilat, s, NULL, method, class );
               if( cval ) {
                  temp = astStore( NULL, (void *) cval, strlen( cval ) + 1 );
                  cval = GetItemC( &(store->cname), fits_ilon, s, NULL, method, class );
                  if( cval ) {
                     SetItemC( &(store->cname), fits_ilat, s, cval );
                     SetItemC( &(store->cname), fits_ilon, s, temp );
                  }
                  temp = astFree( temp );
               }

/* Swap the projection parameters asociated with the longitude and latitude 
   axes. */
               maxm = GetMaxJM( &(store->pv), s );
               for( m = 0; m <= maxm; m++ ){
                  val = GetItem( &(store->pv), fits_ilat, m, s, NULL, method, class );
                  SetItem( &(store->pv), fits_ilat, m, s, 
                           GetItem( &(store->pv), fits_ilon, m, s, NULL, 
                           method, class ) );
                  SetItem( &(store->pv), fits_ilon, m, s, val );
               }
            }
             
/* Release resources. */
            map2b = astAnnul( map2b );
            tperm = astFree( tperm );

         }

/* Release resources. */
         map1 = astAnnul( map1 );
         map2 = astAnnul( map2 );
         map3 = astAnnul( map3 );
      }
      ppcfid = astFree( ppcfid );

   }

/* Release resources. */
   wcsfrm = astAnnul( wcsfrm );
   if( skyfrm ) skyfrm = astAnnul( skyfrm );
   if( map ) map = astAnnul( map );

/* If we have a Mapping to return, simplify it. Otherwise, create
   a UnitMap to return. */
   if( ret ) {
      tmap0 = ret;
      ret = astSimplify( tmap0 ); 
      tmap0 =  astAnnul( tmap0 );
   } else {
      ret = (AstMapping *) astUnitMap( nwcs, "" );
   }

/* Return the result. */
   return ret;

}

static int CheckFitsName( const char *name, const char *method, 
                          const char *class ){
/*
*  Name:
*     CheckFitsName

*  Purpose:
*     Check a keyword name conforms to FITS standards.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int CheckFitsName( const char *name, const char *method, 
*                        const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     FITS keywords must contain between 1 and 8 characters, and each
*     character must be an upper-case Latin alphabetic character, a digit,
*     an underscore, or a hyphen. Leading, trailing or embedded white space
*     is not allowed, with the exception of totally blank or null keyword 
*     names.

*  Parameters:
*     name
*        Pointer to a string holding the name to check. 
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     A value of 0 is returned if the supplied name was blank. A value of 1
*     is returned otherwise.

*  Notes:
*     -  An error is reported if the supplied keyword name does not
*     conform to FITS requirements, and zero is returned.

*/

/* Local Variables: */
   const char *c;     /* Pointer to next character in name */
   size_t n;          /* No. of characters in supplied name */
   int ret;           /* Returned value */

/* Check the global status. */
   if( !astOK ) return 0;

/* Initialise the returned value to indicate that the supplied name was
   blank. */
   ret = 0;

/* Check that the supplied pointer is not NULL. */
   if( name ){

/* Get the number of characters in the name. */
      n = strlen( name );

/* Report an error if the name has too many characters in it. */
      if( n > FITSNAMLEN ){
         astError( AST__BDFTS, "%s(%s): The supplied FITS keyword name ('%s') "
                   "has %d characters. FITS only allows up to %d.", method, 
                   class, name, n, FITSNAMLEN );                

/* If the name has no characters in it, then assume it is a legal blank
   keyword name. Otherwise, check that no illegal characters occur in the 
   name. */
      } else if( n != 0 ) {

/* Whitespace is only allowed in the special case of a name consisting
   entirely of whitespace. Such keywords are used to indicate that the rest 
   of the card is a comment. Find the first non-whitespace character in the 
   name. */
         c = name;
         while( isspace( ( int ) *(c++) ) );

/* If the name is filled entirely with whitespace, then the name is acceptable 
   as the special case. Otherwise, we need to do more checks. */
         if( c - name - 1 < n ){

/* Indicate that the supplied name is not blank. */
            ret = 1;

/* Loop round every character checking that it is one of the legal characters. 
   Report an error if any illegal characters are found. */
            c = name;
            while( *c ){

               if( !isFits( (int) *c ) ){

                  if( *c == '=' ){
                     astError( AST__BDFTS, "%s(%s): An equals sign ('=') was found "
                               "before column %d within a FITS keyword name or header "
                               "card.", method, class, FITSNAMLEN + 1 );                
                  } else {   
                     astError( AST__BDFTS, "%s(%s): The supplied FITS keyword "
                               "name ('%s') contains an illegal character ('%c').",
                               method, class, name, *c );                
                  }
                  break;
               }
               c++;
            }         
         }
   
      }
      
/* Report an error if no pointer was supplied. */
   } else if( astOK ){
      astError( AST__INTER, "CheckFitsName(fitschan): AST internal "
                "error; a NULL pointer was supplied for the keyword name. ", 
                method, class );                
   }

/* If an error has occurred, return 0. */
   if( !astOK ) ret = 0;

/* Return the answer. */
   return ret;

}

static void CheckZero( char *text, double value, int width ){
/*
*  Name:
*     CheckZero

*  Purpose:
*     Ensure that the formatted value zero has no minus sign.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void CheckZero( char *text, double value, int width )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     There is sometimes a problem (perhaps only on DEC UNIX) when formatting 
*     the floating-point value 0.0 using C. Sometimes it gives the string 
*     "-0". This function fixed this by checking the first character of
*     the supplied string (if the supplied value is zero), and shunting the
*     remaining text one character to the right if it is a minus sign. It
*     returns without action if the supplied value is not zero.
*
*     In addition, this function also rounds out long sequences of
*     adjacent zeros or nines in the number.

*  Parameters:
*     text
*        The formatted value.
*     value
*        The floating value which was formatted.
*     width
*        The minimum field width to use. The value is right justified in 
*        this field width. Ignored if zero.

*  Notes:
*     -  This function attempts to execute even if an error has occurred.
*/

/* Local Variables: */
   char *c;

/* Return if no text was supplied. */
   if( !text ) return;

/* If the numerical value is zero, check for the leading minus sign. */
   if( value == 0.0 ) {

/* Find the first non-space character. */
      c = text;
      while( *c && isspace( (int) *c ) ) c++;

/* If the first non-space character is a minus sign, replace it with a
      space. */
      if( *c == '-' ) *c = ' ';

/* Otherwise, round out sequences of zeros or nines. */
   } else {
      RoundFString( text, width );
   }
}

static int ChrLen( const char *string ){
/*
*  Name:
*     ChrLen

*  Purpose:
*     Return the length of a string excluding any trailing white space.

*  Type:
*     Private function.

*  Synopsis:
*     int ChrLen( const char *string )

*  Class Membership:
*     FitsChan

*  Description:
*     This function returns the length of a string excluding any trailing
*     white space, or non-printable characters.

*  Parameters:
*     string
*        Pointer to the string.

*  Returned Value:
*     The length of a string excluding any trailing white space and
*     non-printable characters.

*  Notes:
*     -  A value of zero is returned if a NULL pointer is supplied, or if an
*     error has already occurred.

*/

/* Local Variables: */
   const char *c;      /* Pointer to the next character to check */
   int ret;            /* The returned string length */

/* Check the global status. */
   if( !astOK ) return 0;

/* Initialise the returned string length. */
   ret = 0;

/* Check a string has been supplied. */
   if( string ){

/* Check each character in turn, starting with the last one. */
      ret = strlen( string );
      c = string + ret - 1;
      while( ret ){
         if( isprint( (int) *c ) && !isspace( (int) *c ) ) break;
         c--;
         ret--;
      }
   }

/* Return the answer. */
   return ret;

}

static void ClearAttrib( AstObject *this_object, const char *attrib ) {
/*
*  Name:
*     ClearAttrib

*  Purpose:
*     Clear an attribute value for a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void ClearAttrib( AstObject *this, const char *attrib )

*  Class Membership:
*     FitsChan member function (over-rides the astClearAttrib protected
*     method inherited from the Channel class).

*  Description:
*     This function clears the value of a specified attribute for a
*     FitsChan, so that the default value will subsequently be used.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     attrib
*        Pointer to a null-terminated string specifying the attribute
*        name.  This should be in lower case with no surrounding white
*        space.
*/

/* Local Variables: */
   AstFitsChan *this;            /* Pointer to the FitsChan structure */
   int len;                      /* Length of attrib string */

/* Check the global error status. */
   if ( !astOK ) return;

/* Obtain a pointer to the FitsChan structure. */
   this = (AstFitsChan *) this_object;

/* Obtain the length of the "attrib" string. */
   len = strlen( attrib );

/* Check the attribute name and clear the appropriate attribute. */

/* Card. */
/* ----- */
   if ( !strcmp( attrib, "card" ) ) {
      astClearCard( this );

/* Encoding. */
/* --------- */
   } else if ( !strcmp( attrib, "encoding" ) ) {
      astClearEncoding( this );

/* CDMatrix */
/* -------- */
   } else if ( !strcmp( attrib, "cdmatrix" ) ) {
      astClearCDMatrix( this );

/* FitsDigits. */
/* ----------- */
   } else if ( !strcmp( attrib, "fitsdigits" ) ) {
      astClearFitsDigits( this );

/* DefB1950 */
/* -------- */
   } else if ( !strcmp( attrib, "defb1950" ) ) {
      astClearDefB1950( this );

/* CarLin */
/* ------ */
   } else if ( !strcmp( attrib, "carlin" ) ) {
      astClearCarLin( this );

/* Iwc */
/* --- */
   } else if ( !strcmp( attrib, "iwc" ) ) {
      astClearIwc( this );

/* Clean */
/* ----- */
   } else if ( !strcmp( attrib, "clean" ) ) {
      astClearClean( this );

/* Warnings. */
/* -------- */
   } else if ( !strcmp( attrib, "warnings" ) ) {
      astClearWarnings( this );

/* If the name was not recognised, test if it matches any of the
   read-only attributes of this class. If it does, then report an
   error. */
   } else if ( astOK && ( !strcmp( attrib, "ncard" ) || 
                          !strcmp( attrib, "allwarnings" ) ) ){
      astError( AST__NOWRT, "astClear: Invalid attempt to clear the \"%s\" "
                "value for a %s.", attrib, astGetClass( this ) );
      astError( AST__NOWRT, "This is a read-only attribute." );

/* If the attribute is still not recognised, pass it on to the parent
   method for further interpretation. */
   } else {
      (*parent_clearattrib)( this_object, attrib );
   }
}

static void ClearCard( AstFitsChan *this ){
/*
*+
*  Name:
*     astClearCard

*  Purpose:
*     Clear the Card attribute.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "fitschan.h"
*     void astClearCard( AstFitsChan *this )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function clears the Card attribute for the supplied FitsChan by 
*     setting it to the index of the first un-used card in the FitsChan. 
*     This causes the next read operation performed on the FitsChan to 
*     read the first card. Thus, it is equivalent to "rewinding" the FitsChan.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Notes:
*     -  This function attempts to execute even if an error has occurred.
*-
*/

/* Check the supplied FitsChan. If its is empty, return. */
   if ( !this || !(this->head) ) return;

/* Set the pointer to the current card so that it points to the card at
   the head of the list. */
   this->card = this->head;

/* If the current card has been read into an AST object, move on to the 
   first card which has not, unless we are not skipping such cards. */

   if( CARDUSED(this->card) ){
      MoveCard( this, 1, "astClearCard", astGetClass( this ) );
   }

}

static int CnvValue( AstFitsChan *this, int type, void *buff, 
                      const char *method ){
/*
*
*  Name:
*     CnvValue

*  Purpose:
*     Convert a data value into a given FITS data type.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int CnvValue( AstFitsChan *this, int type, void *buff, 
*                   const char *method )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function produces a copy of the data value for the current card 
*     converted from its stored data type to the supplied data type.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     type
*        The FITS data type in which to return the data value of the
*        current card.
*     buf
*        A pointer to a buffer to recieve the converted value. It is the
*        responsibility of the caller to ensure that a suitable buffer is
*        supplied.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.

*  Returned Value:
*     Zero if the conversion was not possible (in which case NO error is
*     reported), one otherwise.

*  Notes: 
*     -  When converting from floating point to integer, the  floating
*     point value is truncated using a C cast.
*     -  Non-zero numerical values are considered TRUE, and zero
*     numerical values are considered FALSE. Any string starting with a
*     'T' or a 'Y' (upper or lower case) is considered TRUE, and anything 
*     starting with an 'F' or an 'N' (upper or lower case) is considered
*     FALSE. In addition, a dot ('.') may be placed in front of a 'T' or an
*     'F'.
*     -  A logical TRUE value is represented as a real numerical value of
*     one and the character string "Y". A logical FALSE value is represented 
*     by a real numerical value of zero and the character string "N".
*     -  When converting from a string to any numerical value, zero is
*     returned if the string is not a formatted value which can be converted 
*     into the corresponding type using astSscanf.
*     - Real and imaginary parts of a complex value should be separated by 
*     spaces within strings. If a string does contains only a single numerical 
*     value, it is assumed to be the real part, and the imaginary part is 
*     assumed to be zero.
*     -  When converting a complex numerical type to a non-complex numerical 
*     type, the returned value is derived from the real part only, the 
*     imaginary part is ignored.
*     -  Zero is returned if an error has occurred, or if this function
*     should fail for any reason.

*/

/* Local Variables: */
   int otype;               /* Stored data type */
   size_t osize;            /* Size of stored data */
   void *odata;             /* Pointer to stored data */

/* Check the global error status, and the supplied buffer. */
   if ( !astOK || !buff ) return 0; 

/* Get the type in which the data value is stored. */
   otype = CardType( this );

/* Get a pointer to the stored data value, and its size. */
   odata = CardData( this, &osize );

/* Do the conversion. */
   return CnvType( otype, odata, osize, type, buff, CardName( this ), 
                   method, astGetClass( this ) );
}

static int CnvType( int otype, void *odata, size_t osize, int type, 
                     void *buff, const char *name, const char *method, 
                     const char *class ){
/*
*
*  Name:
*     CnvType

*  Purpose:
*     Convert a data value into a given FITS data type.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int CnvType( int otype, void *odata, size_t osize, int type, 
*                   void *buff, const char *name, const char *method, 
*                   const char *class )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function produces a copy of the data value for the current card 
*     converted from its stored data type to the supplied data type.

*  Parameters:
*     otype
*        The type of the supplied data value.
*     odata
*        Pointer to a buffer holding the supplied data value.
*     osize
*        The size of the data value (in bytes - strings include the
*        terminating null).
*     type
*        The FITS data type in which to return the data value of the
*        current card.
*     buf
*        A pointer to a buffer to recieve the converted value. It is the
*        responsibility of the caller to ensure that a suitable buffer is
*        supplied.
*     name
*        A pointer to a string holding a keyword name to include in error
*        messages.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class
*        Pointer to a string holding the name of the object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     Zero if the conversion was not possible (in which case NO error is
*     reported), one otherwise.

*  Notes: 
*     -  When converting from floating point to integer, the  floating
*     point value is truncated using a C cast.
*     -  Non-zero numerical values are considered TRUE, and zero
*     numerical values are considered FALSE. Any string starting with a
*     'T' or a 'Y' (upper or lower case) is considered TRUE, and anything 
*     starting with an 'F' or an 'N' (upper or lower case) is considered
*     FALSE. In addition, a dot ('.') may be placed in front of a 'T' or an
*     'F'.
*     -  A logical TRUE value is represented as a real numerical value of
*     one and the character string "Y". A logical FALSE value is represented 
*     by a real numerical value of zero and the character string "N".
*     -  When converting from a string to any numerical value, zero is
*     returned if the string isn not a formatted value which can be converted 
*     into the corresponding type using astSscanf.
*     - Real and imaginary parts of a complex value should be separated by 
*     spaces within strings. If a string does contains only a single numerical 
*     value, it is assumed to be the real part, and the imaginary part is 
*     assumed to be zero.
*     -  When converting a complex numerical type to a non-complex numerical 
*     type, the returned value is derived from the real part only, the 
*     imaginary part is ignored.
*     -  Zero is returned if an error has occurred, or if this function
*     should fail for any reason.

*/

/* Local Variables: */
   const char *c;           /* Pointer to next character */
   const char *ostring;     /* String data value */
   double odouble;          /* Double data value */
   int oint;                /* Integer data value */
   int ival;                /* Integer value read from string */
   int len;                 /* Length of character string */
   int nc;                  /* No. of characetsr used */
   int ret;                 /* Returned success flag */
   static char text[ FITSCARDLEN + 1 ]; /* Buffer for returned text string */
   static char text0[ FITSCARDLEN + 1 ]; /* Buffer for real value */
   static char text1[ FITSCARDLEN + 1 ]; /* Buffer for imaginary value */

/* Check the global error status, and the supplied buffer. */
   if ( !astOK || !buff ) return 0; 

/* Assume success. */
   ret = 1;

/* If there is no data value and this is not a COMMENT keyword, or if
   there is a data value and this is a COMMENT card, conversion is not
   possible. */
   if( ( odata && type == AST__COMMENT ) ||
       ( !odata && type != AST__COMMENT ) ) {
      ret = 0;

/* If there is no data (and therefore this is a comment card), leave the 
   supplied buffers unchanged. */
   } else if( odata ) {

/* Do each possible combination of supplied and stored data types... */

/* Convert a AST__FLOAT data value to ... */
      if( otype == AST__FLOAT ){
         odouble = *( (double *) odata );

         if( type == AST__FLOAT ){
            (void) memcpy( buff, odata, osize );
   
         } else if( type == AST__STRING || type == AST__CONTINUE  ){
            (void) sprintf( text, "%.*g", DBL_DIG, odouble );
            CheckZero( text, odouble, 0 );
            *( (char **) buff ) = text;

         } else if( type == AST__INT      ){
            *( (int *) buff ) = (int) odouble;

         } else if( type == AST__LOGICAL  ){
            *( (int *) buff ) = ( odouble == 0.0 ) ? 0 : 1;

         } else if( type == AST__COMPLEXF ){
            ( (double *) buff )[ 0 ] = odouble;
            ( (double *) buff )[ 1 ] = 0.0;

         } else if( type == AST__COMPLEXI ){
            ( (int *) buff )[ 0 ] = (int) odouble;
            ( (int *) buff )[ 1 ] = 0;

         } else if( astOK ){
            ret = 0;
            astError( AST__INTER, "CnvType: AST internal programming error - "
                      "FITS data-type no. %d not yet supported.", type );
         }

/* Convert a AST__STRING data value to ... */
      } else if( otype == AST__STRING || type == AST__CONTINUE ){
         ostring = (char *) odata;
         len = (int) strlen( ostring );

         if( type == AST__FLOAT ){
            if( nc = 0, 
                     ( 1 != astSscanf( ostring, "%lf %n", (double *) buff, &nc ) )
                  || (nc < len ) ){
               ret = 0;
            }

         } else if( type == AST__STRING || type == AST__CONTINUE  ){
            strncpy( text, (char *) odata, FITSCARDLEN );
            *( (char **) buff ) = text;

         } else if( type == AST__INT      ){
            if( nc = 0, 
                     ( 1 != astSscanf( ostring, "%d %n", (int *) buff, &nc ) )
                  || (nc < len ) ){
               ret = 0;
            }

         } else if( type == AST__LOGICAL  ){
            if( nc = 0, 
                     ( 1 == astSscanf( ostring, "%d %n", &ival, &nc ) )
                  && (nc >= len ) ){
               *( (int *) buff ) = ival ? 1 : 0;               

            } else {
               c = ostring;
               while( *c && isspace( (int) *c ) ) c++;

               if( *c == 'y' || *c == 'Y' || *c == 't' || *c == 'T' ||
                   ( *c == '.' && ( c[1] == 't' || c[1] == 'T' ) ) ){
                  *( (int *) buff ) = 1;

               } else if( *c == 'n' || *c == 'N' || *c == 'f' || *c == 'F' ||
                   ( *c == '.' && ( c[1] == 'f' || c[1] == 'F' ) ) ){
                  *( (int *) buff ) = 0;
               } else {
                  ret = 0;
               }
            } 

         } else if( type == AST__COMPLEXF ){
            if( nc = 0, 
                     ( 1 != astSscanf( ostring, "%lf %lf %n", (double *) buff, 
                                    (double *) buff + 1, &nc ) )
                  || (nc < len ) ){

               if( nc = 0, 
                        ( 1 != astSscanf( ostring, "%lf %n", (double *) buff, 
                                       &nc ) )
                     || (nc < len ) ){
                  ret = 0;
               } else {
                  ( (double *) buff )[ 1 ] = 0.0;
               }

            }

         } else if( type == AST__COMPLEXI ){
            if( nc = 0, 
                    ( 1 != astSscanf( ostring, "%d %d %n", (int *) buff, 
                                   (int *) buff + 1, &nc ) )
                   || (nc < len ) ){

               if( nc = 0, 
                        ( 1 != astSscanf( ostring, "%d %n", (int *) buff, &nc ) )
                     || (nc < len ) ){

                  ret = 0;
               } else {
                  ( (int *) buff )[ 1 ] = 0;
               }

            }

         } else if( astOK ){
            ret = 0;
            astError( AST__INTER, "CnvType: AST internal programming error - "
                      "FITS data-type no. %d not yet supported.", type );
         }

/* Convert an AST__INT data value to ... */
      } else if( otype == AST__INT      ){
         oint = *( (int *) odata );

         if( type == AST__FLOAT ){
            *( (double *) buff ) = (double) oint;

         } else if( type == AST__STRING || type == AST__CONTINUE  ){
            (void) sprintf( text, "%d", oint );
            *( (char **) buff ) = text;

         } else if( type == AST__INT      ){
            (void) memcpy( buff, odata, osize );

         } else if( type == AST__LOGICAL  ){
            *( (int *) buff ) = oint ? 1 : 0;

         } else if( type == AST__COMPLEXF ){
            ( (double *) buff )[ 0 ] = (double) oint;
            ( (double *) buff )[ 1 ] = 0.0;

         } else if( type == AST__COMPLEXI ){
            ( (int *) buff )[ 0 ] = oint;
            ( (int *) buff )[ 1 ] = 0;

         } else if( astOK ){
            ret = 0;
            astError( AST__INTER, "CnvType: AST internal programming error - "
                      "FITS data-type no. %d not yet supported.", type );
         }

/* Convert a LOGICAL data value to ... */
      } else if( otype == AST__LOGICAL  ){
         oint = *( (int *) odata );
         
         if( type == AST__FLOAT ){
            *( (double *) buff ) = oint ? 1.0 : 0.0;

         } else if( type == AST__STRING || type == AST__CONTINUE  ){
            if( oint ){
               strcpy( text, "Y" );
            } else {
               strcpy( text, "N" );
            }
            *( (char **) buff ) = text;

         } else if( type == AST__INT      ){
            *( (int *) buff ) = oint;

         } else if( type == AST__LOGICAL  ){
            (void) memcpy( buff, odata, osize );

         } else if( type == AST__COMPLEXF ){
            ( (double *) buff )[ 0 ] = oint ? 1.0 : 0.0;
            ( (double *) buff )[ 1 ] = 0.0;

         } else if( type == AST__COMPLEXI ){
            ( (int *) buff )[ 0 ] = oint ? 1 : 0;
            ( (int *) buff )[ 1 ] = 0;

         } else if( astOK ){
            ret = 0;
            astError( AST__INTER, "CnvType: AST internal programming error - "
                      "FITS data-type no. %d not yet supported.", type );
         }

/* Convert a AST__COMPLEXF data value to ... */
      } else if( otype == AST__COMPLEXF ){
         odouble = ( (double *) odata )[ 0 ];

         if( type == AST__FLOAT ){
            *( (double *) buff ) = odouble;
   
         } else if( type == AST__STRING || type == AST__CONTINUE  ){
            (void) sprintf( text0, "%.*g", DBL_DIG, ( (double *) odata )[ 0 ] );
            CheckZero( text0, ( (double *) odata )[ 0 ], 0 );
            (void) sprintf( text1, "%.*g", DBL_DIG, ( (double *) odata )[ 1 ] );
            CheckZero( text1, ( (double *) odata )[ 1 ], 0 );
            (void) sprintf( text, "%s %s", text0, text1 );
            *( (char **) buff ) = text;

         } else if( type == AST__INT      ){
            *( (int *) buff ) = (int) odouble;

         } else if( type == AST__LOGICAL  ){
            *( (int *) buff ) = ( odouble == 0.0 ) ? 0 : 1;

         } else if( type == AST__COMPLEXF ){
            (void) memcpy( buff, odata, osize );

         } else if( type == AST__COMPLEXI ){
            ( (int *) buff )[ 0 ] = (int) odouble;
            ( (int *) buff )[ 1 ] = (int) ( (double *) odata )[ 1 ];

         } else if( astOK ){
            ret = 0;
            astError( AST__INTER, "CnvType: AST internal programming error - "
                      "FITS data-type no. %d not yet supported.", type );
         }

/* Convert a AST__COMPLEXI data value to ... */
      } else if( otype == AST__COMPLEXI ){
         oint = ( (int *) odata )[ 0 ];

         if( type == AST__FLOAT ){
            *( (double *) buff ) = (double) oint;

         } else if( type == AST__STRING || type == AST__CONTINUE  ){
            (void) sprintf( text, "%d %d", ( (int *) odata )[ 0 ],
                                           ( (int *) odata )[ 1 ] );
            *( (char **) buff ) = text;

         } else if( type == AST__INT      ){
            *( (int *) buff ) = oint;

         } else if( type == AST__LOGICAL  ){
            *( (int *) buff ) = oint ? 1 : 0;

         } else if( type == AST__COMPLEXF ){
            ( (double *) buff )[ 0 ] = (double) oint;
            ( (double *) buff )[ 1 ] = (double) ( (int *) odata )[ 1 ];

         } else if( type == AST__COMPLEXI ){
            (void) memcpy( buff, odata, osize );

         } else if( astOK ){
            ret = 0;
            astError( AST__INTER, "CnvType: AST internal programming error - "
                      "FITS data-type no. %d not yet supported.", type );
         }

      } else if( astOK ){
         ret = 0;
         astError( AST__INTER, "CnvType: AST internal programming error - "
                   "FITS data-type no. %d not yet supported.", type );
      }

   }

   return ret;

}

static int ComBlock( AstFitsChan *this, int incr, const char *method,
                     const char *class ){
/*
*  Name:
*     ComBlock

*  Purpose:
*     Delete a AST comment block in a Native-encoded FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int ComBlock( AstFitsChan *this, int incr, const char *method,
*                   const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function looks for a block of comment cards as defined below,
*     and deletes all the cards in the block, if a suitable block is found.
*
*     Comment blocks consist of a contiguous sequence of COMMENT cards. The 
*     text of each card should start and end with the 3 characters "AST".
*     The block is delimited above by a card containing all +'s (except
*     for the two "AST" strings), and below by a card containing all -'s.
*     
*     The block is assumed to start on the card which is adjacent to the
*     current card on entry.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     incr
*        This should be either +1 or -1, and is the increment between
*        adjacent cards in the comment block. A value of +1 means
*        that the card following the current card is taken as the first in 
*        the block, and subsequent cards are checked. The block must then
*        end with a line of -'s. If -1 is supplied, then the card
*        preceding the current card is taken as the first in the block,
*        and preceding cards are checked. The block must then end with
*        a row of +'s.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     1 if a block was found and deleted, 0 otherwise.

*  Notes:
*     -  The pointer to the current card is returned unchanged.
*/

/* Local Variables: */
   FitsCard *card0;              /* Pointer to current FitsCard on entry */
   char del;                     /* Delimiter character */
   char *text;                   /* Pointer to the comment text */
   int i;                        /* Card index within the block */
   int ncard;                    /* No. of cards in the block */
   int ret;                      /* The returned flag */   
   size_t len;                   /* Length of the comment text */

/* Check the global status. */
   if( !astOK ) return 0;

/* Save the pointer to the current card. */
   card0 = this->card;

/* Initialise the returned flag to indicate that we have not found a
   comment block. */
   ret = 0;

/* Move on to the first card in the block. If this is not possible (due to 
   us already being at the start or end of the FitsChan), then return. */
   if( MoveCard( this, incr, method, class ) == 1 ) {

/* Store the character which is used in the delimiter line for the
   comment block. */
      del = ( incr == 1 ) ? '-' : '+';

/* Initialise the number of cards in the comment block to zero. */
      ncard = 0;

/* Loop round until the end (or start) of the comment block is found.
   Leave the loop if an error occurs.  */
      while( astOK ) {

/* Is this card a comment card? If not, then we have failed to find a 
   complete comment block. Break out of the loop. */
         if( CardType( this ) != AST__COMMENT ) break;

/* Increment the number of cards in the comment block. */
         ncard++;

/* Get the text of the comment, and its length. */
         text = CardComm( this );
         if( text ){
            len = strlen( text );

/* Check the first 3 characters. Break out of the loop if they are not
   "AST". */
            if( strncmp( "AST", text, 3 ) ) break;

/* Check the last 3 characters. Break out of the loop if they are not
   "AST". */
            if( strcmp( "AST", text + len - 3 ) ) break;

/* If the comment is the appropriate block delimiter (a line of +'s or
   -'s depending on the direction), then set the flag to indicate that we
   have a complete comment block and leave the loop. Allow spaces to be
   included. Exclude the "AST" strings at begining and end from the check. */
            ret = 1;
            for( i = 3; i < len - 3; i++ ) {
               if( text[ i ] != del && text[ i ] != ' ' ) {
                  ret = 0;
                  break;
               }
            }
         }

         if( ret ) break;

/* Move on to the next card. If this is not possible (due to us already
   being at the start or end of the FitsChan), then break out of the loop. */
         if( MoveCard( this, incr, method, class ) == 0 ) break;

      }

/* Re-instate the original current card. */
      this->card = card0;

/* If we found a complete comment block, mark it (which is equivalent to
   deleting it except that memory of the cards location within the
   FitsChan is preserved for future use), and then re-instate the original 
   current card. */
      if( ret && astOK ) {

         for( i = 0; i < ncard; i++ ) {
            MoveCard( this, incr, method, class );
            MarkCard( this );
         }

         this->card = card0;

      }
   }

/* If an error occurred, indicate that coment block has been deleted. */
   if( !astOK ) ret = 0;

   return ret;

}

static int CountFields( const char *temp, char type, const char *method, 
                        const char *class ){
/*
*  Name:
*     CountFields

*  Purpose:
*     Count the number of field specifiers in a template string. 

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int CountFields( const char *temp, char type, const char *method, 
*                      const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function returns the number of fields which include the
*     specified character type in the supplied string.

*  Parameters:
*     temp
*        Pointer to a null terminated string holding the template.
*     type
*        A single character giving the field type to be counted (e.g.
*        'd', 'c' or 'f').
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     The number of fields.

*  Notes:
*     -  No error is reported if the parameter "type" is not a valid
*     field type specifier, but zero will be returned.
*     -  An error is reported if the template has any invalid field 
*     specifiers in it.
*     -  A value of zero is returned if an error has already occurred,
*     or if this function should fail for any reason.

*/
/* Local Variables: */
   const char *b;         /* Pointer to next template character */
   int nf;                /* No. of fields found so far */

/* Check global status. */
   if( !astOK ) return 0;

/* Initialise a pointer to the start of the template string. */
   b = temp;

/* Initialise the number of fields found so far. */
   nf = 0;

/* Go through the string. */
   while( *b && astOK ){

/* If the current character is a '%', a field is starting. */   
      if( *b == '%' ){

/* Skip over the field width (if supplied). */
         if( isdigit( (int) *(++b) ) ) b++;

/* Report an error if the end of the string occurs within the field. */
         if( !*b ) {
            astError( AST__BDFMT, "%s(%s): Incomplete field specifier found "
                      "at end of filter template '%s'.", method, class, 
                      temp );
            break;

/* Report an error if the field type is illegal. */
         } else if( *b != 'd' && *b != 'c' && *b != 'f' ) {
            astError( AST__BDFMT, "%s(%s): Illegal field type or width "
                      "specifier '%c' found in filter template '%s'.", 
                      method, class, *b, temp );
            break;
         }

/* Compare the field type with the supplied type, and increment the 
   number of fields found if it is the correct type. */
         if( *b == type ) nf++;

      }

/* Move on to the next character. */
      b++;   

   }

/* If an error has occurred, return 0. */
   if( !astOK ) nf = 0;

/* Return the answer. */
   return nf;

}

static void CreateKeyword( AstFitsChan *this, const char *name,
                           char keyword[ FITSNAMLEN + 1 ] ){
/*
*  Name:
*     CreateKeyword

*  Purpose:
*     Create a unique un-used keyword for a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void CreateKeyword( AstFitsChan *this, const char *name,
*                         char keyword[ FITSNAMLEN + 1 ] )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function takes a name which forms the basis of a FITS
*     keyword and appends a sequence number (encoded as a pair of
*     legal FITS keyword characters) so as to generate a unique FITS
*     keyword which has not previously been used in the FitsChan
*     supplied.
*
*     It is intended for use when several keywords with the same name
*     must be stored in a FitsChan, since to comply strictly with the
*     FITS standard keywords should normally be unique (otherwise
*     external software which processes the keywords might omit one or
*     other of the values).
*
*     An attempt is also made to generate keywords in a form that is
*     unlikely to clash with those from other sources (in as far as
*     this is possible with FITS). In any event, a keyword that
*     already appears in the FitsChan will not be re-used.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     name
*        Pointer to a constant null-terminated string containing the
*        name on which the new keyword should be based. This should be
*        a legal FITS keyword in itself, except that it should be at
*        least two characters shorter than the maximum length, in
*        order to accommodate the sequence number characters.
*
*        If this string is too long, it will be silently
*        truncated. Mixed case is permitted, as all characters
*        supplied are converted to upper case before use.
*     keyword
*        A character array in which the generated unique keyword will
*        be returned, null terminated.
*/

/* Local Variables: */
   const char *class;            /* Object clas */
   FitsKeySeq *keyseq;           /* Pointer to sequence number list entry */
   char *seq_chars = SEQ_CHARS;  /* Pointer to characters used for encoding */
   int found;                    /* Keyword entry found in list? */
   int icard;                    /* Index of current card on entry */
   int limit;                    /* Sequence number has reached limit? */
   int nc;                       /* Number of basic keyword characters */
   static int seq_nchars = -1;   /* Number of characters used for encoding */

/* Check the global error status. */
   if ( !astOK ) return;

/* Store the object class. */
   class = astGetClass( this );

/* Remember the index of the current card. */
   icard = astGetCard( this );

/* On the first invocation only, determine the number of characters
   being used to encode sequence number information and save this
   value. */
   if ( seq_nchars < 0 ) seq_nchars = (int) strlen( seq_chars );

/* Copy the name supplied into the output array, converting to upper
   case. Leave space for two characters to encode a sequence
   number. Terminate the resulting string. */
   for ( nc = 0; name[ nc ] && ( nc < ( FITSNAMLEN - 2 ) ); nc++ ) {
      keyword[ nc ] = toupper( name[ nc ] );
   }
   keyword[ nc ] = '\0';

/* We now search the list of sequence numbers already allocated to
   find the next one to use for this keyword. Obtain a pointer to the
   start of this list and loop until the end of the list is reached,
   or the keyword is found. */
   found = 0;
   keyseq = (FitsKeySeq *) this->keyseq;
   while ( keyseq && !found ) {

/* Compare each entry with the keyword we want to match. */
      if ( !strcmp( keyseq->key, keyword ) ) {
         found = 1;

/* Test the next list entry if the current one doesn't match. */
      } else {
         keyseq = keyseq->next;
      }
   }

/* If the keyword was not found in the list, create a new list entry
   to describe it. */
   if ( !found ) {
      keyseq = astMalloc( sizeof( FitsKeySeq ) );

/* If OK, store a copy of the keyword and initialise its sequence
   number (note that sequence number zero is not actually used for
   cosmetic reasons). */
      if ( astOK ) {
         keyseq->key = astString( keyword, nc );
         keyseq->seq = 0;

/* Prefix the new entry to the list. */
         if ( astOK ) {
            keyseq->next = (FitsKeySeq *) this->keyseq;
            this->keyseq = (void *) keyseq;

/* If an error occurred, clean up by freeing the memory allocated for
   the new list entry. */
         } else {
            keyseq = astFree( keyseq );
         }
      }
   }

/* If OK, loop to find a new sequence number which results in a FITS
   keyword that hasn't already been used to store data in the
   FitsChan. */
   if ( astOK ) {
      while ( 1 ) {

/* Determine if the sequence number just obtained has reached the
   upper limit. This is unlikely to happen in practice, but if it
   does, we simply re-use this maximum value. Otherwise, we increment
   the sequence number last used for this keyword to obtain a new
   one. */
         limit = ( keyseq->seq >= ( seq_nchars * seq_nchars - 1 ) );
         if ( !limit ) keyseq->seq++;

/* Encode the sequence number into two characters and append them to
   the original keyword (with a terminating null). */
         keyword[ nc ] = seq_chars[ keyseq->seq / seq_nchars ];
         keyword[ nc + 1 ] = seq_chars[ keyseq->seq % seq_nchars ];
         keyword[ nc + 2 ] = '\0';

/* If the upper sequence number limit has not been reached, try to
   look up the resulting keyword in the FitsChan to see if it has
   already been used. Quit searching when a suitable keyword is
   found. */
         if ( limit || !SearchCard( this, keyword, "astWrite", class ) ) break;
      }
   }

/* Reinstate the original current card. */
   astSetCard( this, icard );

}

static double DateObs( const char *dateobs ) {
/*
*  Name:
*     DateObs

*  Purpose:
*     Convert a FITS DATE-OBS keyword value to a MJD.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     double DateObs( const char *dateobs )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Extracts the date and time fields from the supplied string and converts 
*     them into a modified Julian Date. Supports both old "dd/mm/yy"
*     format, and the new "ccyy-mm-ddThh:mm:ss[.sss...]" format.

*  Parameters:
*     dateobs
*        Pointer to the DATE-OBS string.

*  Returned Value:
*     The Modified Julian Date corresponding to the supplied DATE-OBS
*     string.

*  Notes:
*     -  The value AST__BAD is returned (without error) if the supplied 
*     string does not conform to the requirements of a FITS DATE-OBS value,
*     or if an error has already occurred.
*/

/* Local Variables: */
   double days;               /* The hours, mins and secs as a fraction of a day */
   double ret;                /* The returned MJD value */
   double secs;               /* The total value of the two seconds fields */
   int dd;                    /* The day field from the supplied string */
   int fsc;                   /* The fractional seconds field from the supplied string */
   int hr;                    /* The hour field from the supplied string */
   int j;                     /* SLALIB status */
   int len;                   /* The length of the supplied string */
   int mm;                    /* The month field from the supplied string */
   int mn;                    /* The minute field from the supplied string */
   int nc;                    /* Number of characters used */
   int ok;                    /* Was the string of a legal format? */
   int rem;                   /* The least significant digit in fsc */
   int sc;                    /* The whole seconds field from the supplied string */
   int yy;                    /* The year field from the supplied string */


/* Check the global status. */
   if( !astOK ) return AST__BAD;

/* Initialise the returned value. */
   ret = AST__BAD;

/* Save the length of the supplied string. */
   len = (int) strlen( dateobs );

/* Extract the year, month, day, hour, minute, second and fractional
   seconds fields from the supplied string. Assume initially that the 
   string does not match any format. */
   ok = 0;   

/* First check for the old "dd/mm/yy" format. */
   if( nc = 0,
        ( astSscanf( dateobs, " %2d/%2d/%d %n", &dd, &mm, &yy, &nc ) == 3 ) &&
        ( nc >= len )  ){
      ok = 1;
      hr = 0;
      mn = 0;
      sc = 0;
      fsc = 0;

/* Otherwise, check for the new short format "ccyy-mm-dd". */
   } else if( nc = 0,
        ( astSscanf( dateobs, " %4d-%2d-%2d %n", &yy, &mm, &dd, &nc ) == 3 ) &&
        ( nc >= len )  ){
      ok = 1;
      hr = 0;
      mn = 0;
      sc = 0;
      fsc = 0;

/* Otherwise, check for the new format "ccyy-mm-ddThh:mm:ss" without a 
   fractional seconds field or the trailing Z. */
   } else if( nc = 0,
        ( astSscanf( dateobs, " %4d-%2d-%2dT%2d:%2d:%2d %n", &yy, &mm, &dd,
                  &hr, &mn, &sc, &nc ) == 6 ) && ( nc >= len )  ){
      ok = 1;
      fsc = 0;

/* Otherwise, check for the new format "ccyy-mm-ddThh:mm:ss.sss" with a 
   fractional seconds field but without the trailing Z. */
   } else if( nc = 0,
        ( astSscanf( dateobs, " %4d-%2d-%2dT%2d:%2d:%2d.%d %n", &yy, &mm, &dd,
                  &hr, &mn, &sc, &fsc, &nc ) == 7 ) && ( nc >= len )  ){
      ok = 1;

/* Otherwise, check for the new format "ccyy-mm-ddThh:mm:ssZ" without a 
   fractional seconds field but with the trailing Z. */
   } else if( nc = 0,
        ( astSscanf( dateobs, " %4d-%2d-%2dT%2d:%2d:%2dZ %n", &yy, &mm, &dd,
                  &hr, &mn, &sc, &nc ) == 6 ) && ( nc >= len )  ){
      ok = 1;
      fsc = 0;

/* Otherwise, check for the new format "ccyy-mm-ddThh:mm:ss.sssZ" with a 
   fractional seconds field and the trailing Z. */
   } else if( nc = 0,
        ( astSscanf( dateobs, " %4d-%2d-%2dT%2d:%2d:%2d.%dZ %n", &yy, &mm, &dd,
                  &hr, &mn, &sc, &fsc, &nc ) == 7 ) && ( nc >= len )  ){
      ok = 1;
   }

/* If the supplied string was legal, create a MJD from the separate fields. */
   if( ok ) { 

/* Get the MJD at the start of the day. */
      slaCaldj( yy, mm, dd, &ret, &j );

/* If succesful, convert the hours, minutes and seconds to a fraction of
    a day, and add it onto the MJD found above. */
      if( j == 0 ) {

/* Obtain a floating point representation of the fractional seconds
   field. */
         secs = 0.0;
         while ( fsc > 0 ) {
             rem = ( fsc % 10  );
             fsc /= 10;
             secs = 0.1 * ( secs + (double) rem );
         }

/* Add on the whole seconds field. */
         secs += (double) sc;

/*Convert the hours, minutes and seconds to a fractional day. */
         slaDtf2d( hr, mn, secs, &days, &j );

/* If succesful, add this onto the returned MJD. */
         if( j == 0 ) {
            ret = ret + days;         

/* If the conversion to MJD failed, return AST__BAD. */
         } else {
            ret = AST__BAD;
         }

      } else {
         ret = AST__BAD;
      }
   } 

/* Return the result. */
   return ret;   

}

static void DeleteCard( AstFitsChan *this, const char *method, 
                        const char *class ){
/*
*  Name:
*     DeleteCard

*  Purpose:
*     Delete the current card from a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void DeleteCard( AstFitsChan *this, const char *method,
*                      const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The current card is removed from the circular linked list of structures 
*     stored in the supplied FitsChan, and the memory used to store the 
*     structure is then freed.

*  Parameters:
*     this
*        Pointer to the FitsChan containing the list.
*     method
*        Name of calling method.
*     class
*        Object class.

*  Notes:
*     -  This function returns without action if the FitsChan is
*     currently at "end-of-file".
*     -  The next card becomes the current card.
*     -  This function attempts to execute even if an error has occurred.

*/

/* Local Variables: */
   FitsCard *card;            /* Pointer to the current card */
   FitsCard *next;            /* Pointer to next card in list */
   FitsCard *prev;            /* Pointer to previous card in list */

/* Return if the supplied object or current card is NULL. */
   if( !this || !this->card ) return;

/* Get a pointer to the card to be deleted (the current card). */
   card = (FitsCard *) this->card;

/* Move the current card on to the next card. */
   MoveCard( this, 1, method, class );

/* Save pointers to the previous and next cards in the list. */
   prev = GetLink( card, PREVIOUS, method, class );
   next = GetLink( card, NEXT, method, class );

/* If the backwards link points back to the supplied card, then it must
   be the only one left on the list. */
   if( prev == card ) prev = NULL;
   if( next == card ) next = NULL;

/* If the list head is to be deleted, store a value for the new list
   head. */
   if( this->head == (void *) card ) this->head = (void *) next;

/* Free the memory used to hold the data value. */
   (void) astFree( card->data );

/* Free the memory used to hold any comment. */
   if( card->comment ) (void) astFree( (void *) card->comment );

/* Free the memory used to hold the whole structure. */
   (void) astFree( (void *) card );

/* Fix up the links between the two adjacent cards in the list, unless the 
   supplied card was the last one in the list. */
   if( prev && next ){
      next->prev = prev;
      prev->next = next;

   } else {
      this->head = NULL;
      this->card = NULL;
   }

/* Return. */
   return;

}

static void DelFits( AstFitsChan *this ){
/*
*++
*  Name:
c     astDelFits
f     AST_DELFITS

*  Purpose:
*     Delete the current FITS card in a FitsChan.

*  Type:
*     Public virtual function.

*  Synopsis:
c     #include "fitschan.h"
c     void astDelFits( AstFitsChan *this )
f     CALL AST_DELFITS( THIS, STATUS )

*  Class Membership:
*     FitsChan method.

*  Description:
c     This function deletes the current FITS card from a FitsChan. The
f     This routine deletes the current FITS card from a FitsChan. The
*     current card may be selected using the Card attribute (if its index
c     is known) or by using astFindFits (if only the FITS keyword is
f     is known) or by using AST_FINDFITS (if only the FITS keyword is
*     known).
*
*     After deletion, the following card becomes the current card.

*  Parameters:
c     this
f     THIS = INTEGER (Given)
*        Pointer to the FitsChan.
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Notes:
*     - This function returns without action if the FitsChan is
*     initially positioned at the "end-of-file" (i.e. if the Card
*     attribute exceeds the number of cards in the FitsChan).
*     - If there are no subsequent cards in the FitsChan, then the
*     Card attribute is left pointing at the "end-of-file" after
*     deletion (i.e. is set to one more than the number of cards in
*     the FitsChan).
*--
*/

/* Check the global error status. */
   if ( !astOK ) return;

/* Delete the current card. The next card will be made the current card. */
   DeleteCard( this, "astDelFits", astGetClass( this ) );

}

static void DistortMaps( AstFitsChan *this, FitsStore *store, char s, 
                         int naxes, AstMapping **map1, AstMapping **map2, 
                         AstMapping **map3, AstMapping **map4, 
                         const char *method, const char *class ){
/*
*  Name:
*     DistortMap

*  Purpose:
*     Create a Mapping representing a FITS-WCS Paper IV distortion code.

*  Type:
*     Private function.

*  Synopsis:
*     void DistortMaps( AstFitsChan *this, FitsStore *store, char s, 
*                       int naxes, AstMapping **map1, AstMapping **map2, 
*                       AstMapping **map3, AstMapping **map4, 
*                       const char *method, const char *class )

*  Class Membership:
*     FitsChan

*  Description:
*     This function checks the CTYPE keywords in the supplied FitsStore to see 
*     if they contain a known distortion code (following the syntax described 
*     in FITS-WCS paper IV). If so, Mappings are returned which represent the 
*     distortions to be applied at each stage in the pixel->IWC chain. If
*     any distortion codes are found in the FitsStore CTYPE values, whether 
*     recognised or not, the CTYPE values in the FitsStore are modified to 
*     remove the distortion code. Warnings about any unknown or inappropriate 
*     distortion codes are added to the FitsChan.

*  Parameters:
*     this
*        The FitsChan. ASTWARN cards may be added to this FitsChan if any
*        anomalies are found in the keyword values in the FitsStore.
*     store
*        A structure containing information about the requested axis 
*        descriptions derived from a FITS header.
*     s
*        A character identifying the co-ordinate version to use. A space 
*        means use primary axis descriptions. Otherwise, it must be an 
*        upper-case alphabetical characters ('A' to 'Z').
*     naxes
*        The number of intermediate world coordinate axes (WCSAXES).
*     map1
*        Address of a location at which to store a pointer to a Mapping
*        which describes any distortion to be applied to pixel
*        coordinates, prior to performing the translation specified by the 
*        CRPIXj keywords. NULL is returned if no distortion is necessary.
*     map2
*        Address of a location at which to store a pointer to a Mapping
*        which describes any distortion to be applied to translated pixel
*        coordinates, prior to performing the PC matrix multiplication.
*        NULL is returned if no distortion is necessary.
*     map3
*        Address of a location at which to store a pointer to a Mapping
*        which describes any distortion to be applied to unscaled IWC 
*        coordinates, prior to performing the CDELT matrix multiplication.
*        NULL is returned if no distortion is necessary.
*     map4
*        Address of a location at which to store a pointer to a Mapping
*        which describes any distortion to be applied to scaled IWC 
*        coordinates, after performing the CDELT matrix multiplication.
*        NULL is returned if no distortion is necessary.
*     method
*        A pointer to a string holding the name of the calling method.
*        This is used only in the construction of error messages.
*     class
*        A pointer to a string holding the class of the object being
*        read. This is used only in the construction of error messages.

*/

/* Local Variables: */
   AstMapping *tmap1;        /* Mapping pointer */
   AstMapping *tmap2;        /* Mapping pointer */
   char msgbuf[ 250 ];       /* Buffer for warning message */
   int i;                    /* FITS axis index */
   char *ctype;              /* Pointer to CTYPE value */
   char type[ 5 ];           /* Axis type extracted from CTYPE */
   char code[ 4 ];           /* Projection code extracted from CTYPE */
   char dist[ 4 ];           /* Distortion code extracted from CTYPE */
   int warned;               /* Have any ASTWARN cards been issued? */

/* Initialise pointers to the returned Mappings. */
   *map1 = NULL;
   *map2 = NULL;
   *map3 = NULL;
   *map4 = NULL;

/* Check the global status. */
   if ( !astOK ) return;

/* First check each known distortion type... */

/* "-SIP": SIRTF (http://sirtf.caltech.edu/SSC/documents/WCSkeywords_v1.3.pdf)
   ============= */

/* SIRTF distortion is limited to 2D. Check the first two axes to see if
   they have "-SIP" codes in their CTYPE values. If they do, terminate the
   ctype string in order to exclude the distortion code (this is so that
   later functions do not need to allow for the possibility of a distortion
   code  being present in the CTYPE value)*/
   ctype = GetItemC( &(store->ctype), 0, s, NULL, method, class );
   if( ctype && 3 == astSscanf( ctype, "%4s-%3s-%3s", type, code, dist ) ){
      if( !strcmp( "SIP", dist ) ) {
         ctype[ 8 ] = 0;
         ctype = GetItemC( &(store->ctype), 1, s, NULL, method, class );
         if( ctype && 3 == astSscanf( ctype, "%4s-%3s-%3s", type, code, dist ) ){
            if( !strcmp( "SIP", dist ) ) {
               ctype[ 8 ] = 0;

/* Create a Mapping describing the distortion (other axes are passed
   unchanged by this Mapping), and add it in series with the returned map2 
   (SIRTF distortion is applied to the translated pixel coordinates). */
               tmap1 = SIPMapping( store, s, naxes, method, class );
               if( ! *map2 ) {
                  *map2 = tmap1;
               } else {
                  tmap2 = (AstMapping *) astCmpMap( *map2, tmap1, 1, "" );
                  *map2 = astAnnul( *map2 );                  
                  tmap1 = astAnnul( tmap1 );                  
                  *map2 = tmap2;
               }
            }
         }
      }      
   }

/* Check that the "-SIP" code is not included in any axes other than axes
   0 and 1. Issue a warning if it is, and remove it. */
   warned = 0;
   for( i = 2; i < naxes; i++ ){
      ctype = GetItemC( &(store->ctype), i, s, NULL, method, class );
      if( ctype && 3 == astSscanf( ctype, "%4s-%3s-%3s", type, code, dist ) ){
         if( !strcmp( "SIP", dist ) ){
            if( !warned ){
               warned = 1;
               sprintf( msgbuf, "The \"-SIP\" distortion code can only be "
                        "used on axes 1 and 2, but was found in keyword "
                        "%s (='%s'). The distortion will be ignored.", 
                        FormatKey( "CTYPE", i + 1, -1, ' ' ),  ctype );
               Warn( this, "distortion", msgbuf, method, class );
            }
            ctype[ 8 ] = 0;
         }
      }
   }   


/* (There are currently no other supported distortion codes.) */


/* Finally, check all axes looking for any remaining (and therefore
   unsupported) distortion codes. Issue a warning about them and remove 
   them. 
   =================================================================== */

/* Indicate that we have not yet issued a warning. */
   warned = 0;

/* Do each IWC axis. */
   for( i = 0; i < naxes; i++ ){

/* Get the CTYPE value for this axis. */
      ctype = GetItemC( &(store->ctype), i, s, NULL, method, class );
      if( ctype ) {

/* See if has the "4-3-3" form described in FITS-WCS paper IV. */
         if( 3 == astSscanf( ctype, "%4s-%3s-%3s", type, code, dist ) ){

/* Add an ASTWARN card to the FitsChan. Only issue one warning (this avoids 
   multiple warnings about the same distortion code in multiple CTYPE values). */
            if( !warned ){
               warned = 1;
               sprintf( msgbuf, "The header contains CTYPE values (e.g. "
                        "%s = '%s') which "
                        "include a distortion code \"-%s\". AST "
                        "currently ignores this distortion. The code "
                        "has been removed from the CTYPE values.",
                        FormatKey( "CTYPE", i + 1, -1, ' ' ),  ctype, dist );
               Warn( this, "distortion", msgbuf, method, class );
            }

/* Terminate the CTYPE value in the FitsStore in order to exclude the distortion 
   code. This means that later functions will not need to take account of
   distortion codes. */
            ctype[ 8 ] = 0;
         }
      }
   }   
}

static int DSSFromStore( AstFitsChan *this, FitsStore *store, 
                         const char *method, const char *class ){
/*
*  Name:
*     DSSFromStore

*  Purpose:
*     Store WCS keywords in a FitsChan using DSS encoding.

*  Type:
*     Private function.

*  Synopsis:
*     int DSSFromStore( AstFitsChan *this, FitsStore *store, 
*                       const char *method, const char *class )

*  Class Membership:
*     FitsChan

*  Description:
*     A FitsStore is a structure containing a generalised represention of
*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
*     from a set of FITS header cards (using a specified encoding), or
*     an AST FrameSet. In other words, a FitsStore is an encoding-
*     independant intermediary staging post between a FITS header and 
*     an AST FrameSet.
*
*     This function copies the WCS information stored in the supplied 
*     FitsStore into the supplied FitsChan, using DSS encoding.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     store
*        Pointer to the FitsStore.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     A value of 1 is returned if succesfull, and zero is returned
*     otherwise.

*/

/* Local Variables: */
   char *comm;         /* Pointer to comment string */
   char *cval;         /* Pointer to string keyword value */
   char *pltdecsn;     /* PLTDECSN keyword value */
   double amdx[20];    /* AMDXi keyword value */
   double amdy[20];    /* AMDYi keyword value */
   double cdelt;       /* CDELT element */
   double cnpix1;      /* CNPIX1 keyword value */
   double cnpix2;      /* CNPIX2 keyword value */
   double pc;          /* PC element */
   double pltdecd;     /* PLTDECD keyword value */
   double pltdecm;     /* PLTDECM keyword value */
   double pltdecs;     /* PLTDECS keyword value */
   double pltrah;      /* PLTRAH keyword value */
   double pltram;      /* PLTRAM keyword value */
   double pltras;      /* PLTRAS keyword value */
   double pltscl;      /* PLTSCL keyword value */
   double ppo1;        /* PPO1 keyword value */
   double ppo2;        /* PPO2 keyword value */
   double ppo3;        /* PPO3 keyword value */
   double ppo4;        /* PPO4 keyword value */
   double ppo5;        /* PPO5 keyword value */
   double ppo6;        /* PPO6 keyword value */
   double pvx[22];     /* X projection parameter values */
   double pvy[22];     /* Y projection parameter values */
   double val;         /* General purpose value */
   double xpixelsz;    /* XPIXELSZ keyword value */
   double ypixelsz;    /* YPIXELSZ keyword value */
   int i;              /* Loop count */
   int gottpn;         /* Is the projection a "TPN" projection? */
   int m;              /* Parameter index */
   int ret;            /* Returned value. */

/* Initialise */
   ret = 0;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Check the image is 2 dimensional. */
   if( GetMaxJM( &(store->crpix), ' ' ) != 1 ) return ret;

/* Check the first axis is RA with a TAN or TPN projection. */
   cval = GetItemC( &(store->ctype), 0, ' ', NULL, method, class );
   if( !cval ) return ret;
   gottpn = !strcmp( "RA---TPN", cval );
   if( strcmp( "RA---TAN", cval ) && !gottpn ) return ret;

/* Check the second axis is DEC with a TAN or TPN projection. */
   cval = GetItemC( &(store->ctype), 1, ' ', NULL, method, class );
   if( !cval ) return ret;
   if( gottpn ) {
      if( strcmp( "DEC--TPN", cval ) ) return ret;
   } else {
      if( strcmp( "DEC--TAN", cval ) ) return ret;
   }

/* Check that LONPOLE is undefined or is 180 degrees. */
   val = GetItem( &(store->lonpole), 0, 0, ' ', NULL, method, class );
   if( val != AST__BAD && val != 180.0 ) return ret;

/* Check that the RA/DEC system is FK5. */
   cval = GetItemC( &(store->radesys), 0, ' ', NULL, method, class );
   if( !cval || strcmp( "FK5", cval ) ) return ret;

/* Check that equinox is not defined or is 2000.0 */
   val = GetItem( &(store->equinox), 0, 0, ' ', NULL, method, class );
   if( val != AST__BAD && val != 2000.0 ) return ret;

/* Get the pixel sizes from the PC/CDELT keywords. They must be defined and 
   not be zero.  */
   cdelt = GetItem( &(store->cdelt), 0, 0, ' ', NULL, method, class );
   if( cdelt == AST__BAD ) return ret;

   pc = GetItem( &(store->pc), 0, 0, ' ', NULL, method, class );
   if( pc == AST__BAD ) pc = 1.0;

   xpixelsz = cdelt*pc;

   cdelt = GetItem( &(store->cdelt), 1, 0, ' ', NULL, method, class );
   if( cdelt == AST__BAD ) return ret;

   pc = GetItem( &(store->pc), 1, 1, ' ', NULL, method, class );
   if( pc == AST__BAD ) pc = 1.0;

   ypixelsz = cdelt*pc;

   if( xpixelsz == 0.0 || ypixelsz == 0.0 ) return ret;

   xpixelsz *= -1000.0;
   ypixelsz *= 1000.0;

/* Check the off-diagonal PC terms are zero. DSS does not allow any rotation. */
   val = GetItem( &(store->pc), 0, 1, ' ', NULL, method, class );
   if( val != AST__BAD && val != 0.0 ) return ret;
   
   val = GetItem( &(store->pc), 1, 0, ' ', NULL, method, class );
   if( val != AST__BAD && val != 0.0 ) return ret;
   
/* Get the required projection parameter values from the store, supplying
   appropriate values if a simple TAN projection is being used. */
   for( m = 0; m < 22; m++ ){
      pvx[ m ] = GetItem( &(store->pv), 0, m, ' ', NULL, method, class );
      if( pvx[ m ] == AST__BAD || !gottpn ) pvx[ m ] = ( m == 1 ) ? 1.0 : 0.0;

      pvy[ m ] = GetItem( &(store->pv), 1, m, ' ', NULL, method, class );
      if( pvy[ m ] == AST__BAD || !gottpn ) pvy[ m ] = ( m == 1 ) ? 1.0 : 0.0;
   }

/* Check that no other projection parameters have been set. */
   if( GetMaxJM( &(store->pv), ' ' ) > 21 ) return ret;

/* Check that specific parameters take their required zero value. */
   if( pvx[ 3 ] != 0.0 || pvy[ 3 ] != 0.0 ) return ret;

   for( m = 11; m < 17; m++ ){
      if( pvx[ m ] != 0.0 || pvy[ m ] != 0.0 ) return ret;
   }

   if( pvx[ 18 ] != 0.0 || pvy[ 18 ] != 0.0 ) return ret;
   if( pvx[ 20 ] != 0.0 || pvy[ 20 ] != 0.0 ) return ret;

/* Check that other projection parameters are related correctly. */
   if( !EQUAL( 2*pvx[ 17 ], pvx[ 19 ] ) ) return ret;
   if( !EQUAL( pvx[ 17 ], pvx[ 21 ] ) ) return ret;

   if( !EQUAL( 2*pvy[ 17 ], pvy[ 19 ] ) ) return ret;
   if( !EQUAL( pvy[ 17 ], pvy[ 21 ] ) ) return ret;

/* Initialise all polynomial co-efficients to zero. */
   for( m = 0; m < 20; m++ ){
      amdx[ m ] = 0.0;
      amdy[ m ] = 0.0;
   }

/* Polynomial co-efficients. There is redundancy here too, so we
   arbitrarily choose to leave AMDX/Y7 and AMDX/Y12 set to zero.  */
   amdx[ 0 ] = 3600.0*pvx[ 1 ];
   amdx[ 1 ] = 3600.0*pvx[ 2 ];
   amdx[ 2 ] = 3600.0*pvx[ 0 ];
   amdx[ 3 ] = 3600.0*pvx[ 4 ];
   amdx[ 4 ] = 3600.0*pvx[ 5 ];
   amdx[ 5 ] = 3600.0*pvx[ 6 ];
   amdx[ 7 ] = 3600.0*pvx[ 7 ];
   amdx[ 8 ] = 3600.0*pvx[ 8 ];
   amdx[ 9 ] = 3600.0*pvx[ 9 ];
   amdx[ 10 ] = 3600.0*pvx[ 10 ];
   amdx[ 12 ] = 3600.0*pvx[ 17 ];

   amdy[ 0 ] = 3600.0*pvy[ 1 ];
   amdy[ 1 ] = 3600.0*pvy[ 2 ];
   amdy[ 2 ] = 3600.0*pvy[ 0 ];
   amdy[ 3 ] = 3600.0*pvy[ 4 ];
   amdy[ 4 ] = 3600.0*pvy[ 5 ];
   amdy[ 5 ] = 3600.0*pvy[ 6 ];
   amdy[ 7 ] = 3600.0*pvy[ 7 ];
   amdy[ 8 ] = 3600.0*pvy[ 8 ];
   amdy[ 9 ] = 3600.0*pvy[ 9 ];
   amdy[ 10 ] = 3600.0*pvy[ 10 ];
   amdy[ 12 ] = 3600.0*pvy[ 17 ];

/* The plate scale is the mean of the first X and Y co-efficients. */
   pltscl = 0.5*( amdx[ 0 ] + amdy[ 0 ] );

/* There is redundancy in the DSS encoding. We can choose an arbitrary 
   pixel corner (CNPIX1, CNPIX2) so long as we use the corresponding origin 
   for the cartesian co-ordinate system in which the plate centre is 
   specified (PPO3, PPO6). Arbitrarily set CNPIX1 and CNPIX2 to one. */
   cnpix1 = 1.0;
   cnpix2 = 1.0;

/* Find the corresponding plate centre PPO3 and PPO6 (other co-efficients
   are set to zero). */
   ppo1 = 0.0;
   ppo2 = 0.0;

   val = GetItem( &(store->crpix), 0, 0, ' ', NULL, method, class );
   if( val == AST__BAD ) return ret;
   ppo3 = xpixelsz*( val + cnpix1 - 0.5 );

   ppo4 = 0.0;
   ppo5 = 0.0;

   val = GetItem( &(store->crpix), 0, 1, ' ', NULL, method, class );
   if( val == AST__BAD ) return ret;
   ppo6 = ypixelsz*( val + cnpix2 - 0.5 );

/* The reference RA. Get it in degrees. */
   val = GetItem( &(store->crval), 0, 0, ' ', NULL, method, class );
   if( val == AST__BAD ) return ret;

/* Convert to hours and ensure it is in the range 0 to 24 */
   val /= 15.0;
   while( val < 0 ) val += 24.0;
   while( val >= 24.0 ) val -= 24.0;

/* Split into hours, mins and seconds. */
   pltrah = (int) val;
   val = 60.0*( val - pltrah );
   pltram = (int) val;
   pltras = 60.0*( val - pltram );

/* The reference DEC. Get it in degrees. */
   val = GetItem( &(store->crval), 1, 0, ' ', NULL, method, class );
   if( val == AST__BAD ) return ret;

/* Ensure it is in the range -180 to +180 */
   while( val < -180.0 ) val += 360.0;
   while( val >= 180.0 ) val -= 360.0;

/* Save the sign. */
   if( val > 0.0 ){
      pltdecsn = "+";
   } else {
      pltdecsn = "-";
      val = -val;
   }

/* Split into degrees, mins and seconds. */
   pltdecd = (int) val;
   val = 60.0*( val - pltdecd );
   pltdecm = (int) val;
   pltdecs = 60.0*( val - pltdecm );

/* Store the DSS keywords in the FitsChan. */
   SetValue( this, "CNPIX1", &cnpix1, AST__FLOAT, "X corner (pixels)" );
   SetValue( this, "CNPIX2", &cnpix2, AST__FLOAT, "Y corner (pixels)" );
   SetValue( this, "PPO1", &ppo1, AST__FLOAT, "Orientation co-efficients" );
   SetValue( this, "PPO2", &ppo2, AST__FLOAT, "" );
   SetValue( this, "PPO3", &ppo3, AST__FLOAT, "" );
   SetValue( this, "PPO4", &ppo4, AST__FLOAT, "" );
   SetValue( this, "PPO5", &ppo5, AST__FLOAT, "" );
   SetValue( this, "PPO6", &ppo6, AST__FLOAT, "" );
   SetValue( this, "XPIXELSZ", &xpixelsz, AST__FLOAT, "X pixel size (microns)" );
   SetValue( this, "YPIXELSZ", &ypixelsz, AST__FLOAT, "Y pixel size (microns)" );
   SetValue( this, "PLTRAH", &pltrah, AST__FLOAT, "RA at plate centre" );
   SetValue( this, "PLTRAM", &pltram, AST__FLOAT, "" );
   SetValue( this, "PLTRAS", &pltras, AST__FLOAT, "" );
   SetValue( this, "PLTDECD", &pltdecd, AST__FLOAT, "DEC at plate centre" );
   SetValue( this, "PLTDECM", &pltdecm, AST__FLOAT, "" );
   SetValue( this, "PLTDECS", &pltdecs, AST__FLOAT, "" );
   SetValue( this, "PLTDECSN", &pltdecsn, AST__STRING, "" );
   SetValue( this, "PLTSCALE", &pltscl, AST__FLOAT, "Plate scale (arcsec/mm)" );

   comm = "Plate solution x co-efficients";
   for( i = 0; i < 20; i++ ){
      SetValue( this, FormatKey( "AMDX", i + 1, -1, ' ' ), amdx + i, 
                AST__FLOAT, comm );
      comm = NULL;
   }

   comm = "Plate solution y co-efficients";
   for( i = 0; i < 20; i++ ){
      SetValue( this, FormatKey( "AMDY", i + 1, -1, ' ' ), amdy + i, 
                AST__FLOAT, comm );
      comm = NULL;
   }

/* If no error has occurred, return one. */
   if( astOK ) ret = 1;

/* Return the answer. */
   return ret;

}

static void DSSToStore( AstFitsChan *this, FitsStore *store, 
                        const char *method, const char *class ){
/*
*  Name:
*     DSSToStore

*  Purpose:
*     Extract WCS information from the supplied FitsChan using a DSS
*     encoding, and store it in the supplied FitsStore.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void DSSToStore( AstFitsChan *this, FitsStore *store, 
                       const char *method, const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     A FitsStore is a structure containing a generalised represention of
*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
*     from a set of FITS header cards (using a specified encoding), or
*     an AST FrameSet. In other words, a FitsStore is an encoding-
*     independant intermediary staging post between a FITS header and 
*     an AST FrameSet.
*
*     This function extracts DSS keywords from the supplied FitsChan, and
*     stores the corresponding WCS information in the supplied FitsStore.
*     The conversion from DSS encoding to standard WCS encoding is
*     described in an ear;y draft of the Calabretta & Greisen paper 
*     "Representations of celestial coordinates in FITS" (A&A, in prep.),
*     and uses the now deprecated "TAN with polynomial corrections",
*     which is still supported by the WcsMap class as type AST__TPN.
*     Here we use "lambda=1" (i.e. plate co-ordinate are measured in mm, 
*     not degrees).
*
*     It is assumed that DSS images are 2 dimensional.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     store
*        Pointer to the FitsStore structure.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*/

/* Local Variables: */
   char *text;         /* Pointer to textual keyword value */
   char pltdecsn[11];  /* First 10 non-blank characters from PLTDECSN keyword */
   char keyname[10];   /* Buffer for keyword name */
   double amdx[20];    /* AMDXi keyword value */
   double amdy[20];    /* AMDYi keyword value */
   double cnpix1;      /* CNPIX1 keyword value */
   double cnpix2;      /* CNPIX2 keyword value */
   double crval2;      /* Equivalent CRVAL2 keyword value */
   double dummy;       /* Unused keyword value */
   double pltdecd;     /* PLTDECD keyword value */
   double pltdecm;     /* PLTDECM keyword value */
   double pltdecs;     /* PLTDECS keyword value */
   double pltrah;      /* PLTRAH keyword value */
   double pltram;      /* PLTRAM keyword value */
   double pltras;      /* PLTRAS keyword value */
   double ppo3;        /* PPO3 keyword value */
   double ppo6;        /* PPO6 keyword value */
   double pv;          /* Projection parameter value */
   double xpixelsz;    /* XPIXELSZ keyword value */
   double ypixelsz;    /* YPIXELSZ keyword value */
   int i;              /* Loop count */

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

/* Get the required DSS keywords. Report an error if any are missing. */
   GetValue( this, "CNPIX1", AST__FLOAT, &cnpix1, 1, 1, method, class );
   GetValue( this, "CNPIX2", AST__FLOAT, &cnpix2, 1, 1, method, class );
   GetValue( this, "PPO3", AST__FLOAT, &ppo3, 1, 1, method, class );
   GetValue( this, "PPO6", AST__FLOAT, &ppo6, 1, 1, method, class );
   GetValue( this, "XPIXELSZ", AST__FLOAT, &xpixelsz, 1, 1, method, class );
   GetValue( this, "YPIXELSZ", AST__FLOAT, &ypixelsz, 1, 1, method, class );
   GetValue( this, "PLTRAH", AST__FLOAT, &pltrah, 1, 1, method, class );
   GetValue( this, "PLTRAM", AST__FLOAT, &pltram, 1, 1, method, class );
   GetValue( this, "PLTRAS", AST__FLOAT, &pltras, 1, 1, method, class );
   GetValue( this, "PLTDECD", AST__FLOAT, &pltdecd, 1, 1, method, class );
   GetValue( this, "PLTDECM", AST__FLOAT, &pltdecm, 1, 1, method, class );
   GetValue( this, "PLTDECS", AST__FLOAT, &pltdecs, 1, 1, method, class );

/* Copy the first 10 non-blank characters from the PLTDECSN keyword. */
   GetValue( this, "PLTDECSN", AST__STRING, &text, 1, 1, method, class );
   text += strspn( text, " " );
   text[ strcspn( text, " " ) ] = 0;
   strncpy( pltdecsn, text, 10 );

/* Read other related keywords. We do not need these, but we read them
   so that they are not propagated to any output FITS file. */
   GetValue( this, "PLTSCALE", AST__FLOAT, &dummy, 0, 1, method, class );
   GetValue( this, "PPO1", AST__FLOAT, &dummy, 0, 1, method, class );
   GetValue( this, "PPO2", AST__FLOAT, &dummy, 0, 1, method, class );
   GetValue( this, "PPO4", AST__FLOAT, &dummy, 0, 1, method, class );
   GetValue( this, "PPO5", AST__FLOAT, &dummy, 0, 1, method, class );

/* Get the polynomial co-efficients. These can be defaulted if they are 
   missing, so do not report an error. */
   for( i = 0; i < 20; i++ ){
      (void) sprintf( keyname, "AMDX%d", i + 1 );
      amdx[i] = AST__BAD;
      GetValue( this, keyname, AST__FLOAT, amdx + i, 0, 1, method, class );

      (void) sprintf( keyname, "AMDY%d", i + 1 );
      amdy[i] = AST__BAD;
      GetValue( this, keyname, AST__FLOAT, amdy + i, 0, 1, method, class );
   }

/* Check the above went OK. */
   if( astOK ) {

/* Calculate and store the equivalent PV projection parameters. */
      if( amdx[2] != AST__BAD ) {
         pv = amdx[2]/3600.0;
         SetItem( &(store->pv), 0, 0, ' ', pv );
      }
      if( amdx[0] != AST__BAD ) {
         pv = amdx[0]/3600.0;
         SetItem( &(store->pv), 0, 1, ' ', pv );
      }
      if( amdx[1] != AST__BAD ) {
         pv = amdx[1]/3600.0;
         SetItem( &(store->pv), 0, 2, ' ', pv );
      }
      if( amdx[3] != AST__BAD && amdx[6] != AST__BAD ) {
         pv = ( amdx[3] + amdx[6] )/3600.0;
         SetItem( &(store->pv), 0, 4, ' ', pv );
      }
      if( amdx[4] != AST__BAD ) {
         pv = amdx[4]/3600.0;
         SetItem( &(store->pv), 0, 5, ' ', pv );
      }
      if( amdx[5] != AST__BAD && amdx[6] != AST__BAD ) {
         pv = ( amdx[5] + amdx[6] )/3600.0;
         SetItem( &(store->pv), 0, 6, ' ', pv );
      }
      if( amdx[7] != AST__BAD && amdx[11] != AST__BAD ) {
         pv = ( amdx[7] + amdx[11] )/3600.0;
         SetItem( &(store->pv), 0, 7, ' ', pv );
      }
      if( amdx[8] != AST__BAD ) {
         pv = amdx[8]/3600.0;
         SetItem( &(store->pv), 0, 8, ' ', pv );
      }
      if( amdx[9] != AST__BAD && amdx[11] != AST__BAD ) {
         pv = ( amdx[9] + amdx[11] )/3600.0;
         SetItem( &(store->pv), 0, 9, ' ', pv );
      }
      if( amdx[10] != AST__BAD ) {
         pv = amdx[10]/3600.0;
         SetItem( &(store->pv), 0, 10, ' ', pv );
      }
      if( amdx[12] != AST__BAD ) {
         pv = amdx[12]/3600.0;
         SetItem( &(store->pv), 0, 17, ' ', pv );
         SetItem( &(store->pv), 0, 19, ' ', 2*pv );
         SetItem( &(store->pv), 0, 21, ' ', pv );
      }
      
      if( amdy[2] != AST__BAD ) {
         pv = amdy[2]/3600.0;
         SetItem( &(store->pv), 1, 0, ' ', pv );
      }
      if( amdy[0] != AST__BAD ) {
         pv = amdy[0]/3600.0;
         SetItem( &(store->pv), 1, 1, ' ', pv );
      }
      if( amdy[1] != AST__BAD ) {
         pv = amdy[1]/3600.0;
         SetItem( &(store->pv), 1, 2, ' ', pv );
      }
      if( amdy[3] != AST__BAD && amdy[6] != AST__BAD ) {
         pv = ( amdy[3] + amdy[6] )/3600.0;
         SetItem( &(store->pv), 1, 4, ' ', pv );
      }
      if( amdy[4] != AST__BAD ) {
         pv = amdy[4]/3600.0;
         SetItem( &(store->pv), 1, 5, ' ', pv );
      }
      if( amdy[5] != AST__BAD && amdy[6] != AST__BAD ) {
         pv = ( amdy[5] + amdy[6] )/3600.0;
         SetItem( &(store->pv), 1, 6, ' ', pv );
      }
      if( amdy[7] != AST__BAD && amdy[11] != AST__BAD ) {
         pv = ( amdy[7] + amdy[11] )/3600.0;
         SetItem( &(store->pv), 1, 7, ' ', pv );
      }
      if( amdy[8] != AST__BAD ) {
         pv = amdy[8]/3600.0;
         SetItem( &(store->pv), 1, 8, ' ', pv );
      }
      if( amdy[9] != AST__BAD && amdy[11] != AST__BAD ) {
         pv = ( amdy[9] + amdy[11] )/3600.0;
         SetItem( &(store->pv), 1, 9, ' ', pv );
      }
      if( amdy[10] != AST__BAD ) {
         pv = amdy[10]/3600.0;
         SetItem( &(store->pv), 1, 10, ' ', pv );
      }
      if( amdy[12] != AST__BAD ) {
         pv = amdy[12]/3600.0;
         SetItem( &(store->pv), 1, 17, ' ', pv );
         SetItem( &(store->pv), 1, 19, ' ', 2*pv );
         SetItem( &(store->pv), 1, 21, ' ', pv );
      }

/* Calculate and store the equivalent CRPIX values. */
      if( xpixelsz != 0.0 ) {
         SetItem( &(store->crpix), 0, 0, ' ', 
                  ( ppo3/xpixelsz ) - cnpix1 + 0.5 );
      } else if( astOK ){      
         astError( AST__BDFTS, "%s(%s): FITS keyword XPIXELSZ has illegal "
                   "value 0.0", method, class );
      }   

      if( ypixelsz != 0.0 ) {
         SetItem( &(store->crpix), 0, 1, ' ', 
                  ( ppo6/ypixelsz ) - cnpix2 + 0.5 );
      } else if( astOK ){      
         astError( AST__BDFTS, "%s(%s): FITS keyword YPIXELSZ has illegal "
                   "value 0.0", method, class );
      }   

/* Calculate and store the equivalent CRVAL values. */
      SetItem( &(store->crval), 0, 0, ' ', 
               15.0*( pltrah + pltram/60.0 + pltras/3600.0 ) );
      crval2 = pltdecd + pltdecm/60.0 + pltdecs/3600.0;
      if( !strcmp( pltdecsn, "-") ) crval2 = -crval2;
      SetItem( &(store->crval), 1, 0, ' ', crval2 );

/* Calculate and store the equivalent PC matrix. */
      SetItem( &(store->pc), 0, 0, ' ', -0.001*xpixelsz );
      SetItem( &(store->pc), 1, 1, ' ', 0.001*ypixelsz );

/* Set values of 1.0 for the CDELT values. */
      SetItem( &(store->cdelt), 0, 0, ' ', 1.0 );
      SetItem( &(store->cdelt), 1, 0, ' ', 1.0 );

/* Store remaining constant items */
      SetItem( &(store->lonpole), 0, 0, ' ', 180.0 );
      SetItem( &(store->equinox), 0, 0, ' ', 2000.0 );
      SetItemC( &(store->radesys), 0, ' ', "FK5" );
      SetItem( &(store->wcsaxes), 0, 0, ' ', 2.0 );
      store->naxis = 2;
      SetItemC( &(store->ctype), 0, ' ', "RA---TAN" );
      SetItemC( &(store->ctype), 1, ' ', "DEC--TAN" );
   }
}

static void Empty( AstFitsChan *this ){
/*
*+
*  Name:
*     astEmpty

*  Purpose:
*     Remove all cards and related data from a FitsChan. 

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "fitschan.h"
*     void astEmpty( AstFitsChan *this )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function removes all cards and associated information from the 
*     supplied FitsChan.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Notes:
*     -  This function attempts to execute even if an error has occurred.

*-
*/

/* Local Variables: */
   const char *class;         /* Pointer to string holding object class */
   const char *method;        /* Pointer to string holding calling method */
   FitsKeySeq *tmp;           /* Temporary pointer to list element */
   FitsKeySeq *keyseq;        /* Temporary pointer to list head */
   int old_ignoreused;        /* Original setting of external IgnoreUsed variable */

/* Store the method and class strings. */
   method = "astEmpty";
   class = astGetClass( this );

/* Delete all cards from the circular linked list stored in the FitsChan,
   starting with the card at the head of the list. */
   old_ignoreused = IgnoreUsed;
   IgnoreUsed = 0;
   astClearCard( this );
   while( !astFitsEof( this ) ) DeleteCard( this, method, class );   
   IgnoreUsed = old_ignoreused;

/* Empty the list which holds keywords and the latest sequence number
   used by each of them, by repeatedly removing the first element. */

   keyseq = (FitsKeySeq *) this->keyseq;
   while ( keyseq ) {

/* Free the keyword string. */
      keyseq->key = astFree( keyseq->key );

/* Remove the first element from the list, retaining a pointer to
   it. */
      tmp = keyseq;
      keyseq = tmp->next;

/* Free the memory used by the first element. */
      tmp = astFree( tmp );
   }

/* Store a NULL pointer in the FitsChan. */
   this->keyseq = NULL;
}

static int EncodeFloat( char *buf, int digits, int width, int maxwidth,
                        double value ){
/*
*
*  Name:
*     EncodeFloat

*  Purpose:
*     Formats a floating point value.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int EncodeFloat( char *buf, int digits, int width, int maxwidth,
*                      double value )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function formats the value using a G format specified in order
*     to use the minimum field width (trailing zeros are not printed). 
*     However, the G specifier does not include a decimal point unless it
*     is necessary. FITS requires that floating point values always include
*     a decimal point, so this function inserts one, if necessary. 

*  Parameters:
*     buf
*        A character string into which the value is written. 
*     digits
*        The number of digits after the decimal point. If the supplied value
*        is negative, the number of digits actually used may be reduced if 
*        the string would otherwise extend beyond the number of columns 
*        allowed by the FITS standard. If the value is positive, the 
*        specified number of digits are always produced, even if it means 
*        breaking the FITS standard.
*     width
*        The minimum field width to use. The value is right justified in 
*        this field width.
*     maxwidth
*        The maximum field width to use. A value of zero is returned if 
*        the maximum field width is exceeded.
*     value
*        The value to format.

*  Returned Value:
*     The field width actually used, or zero if the value could not be
*     formatted. This does not include the trailing null character.

*  Notes:
*     -  If there is room, a trailing zero is also added following the 
*     inserted decimal point. 

*/

/* Local Variables: */
   char *c;
   char *w, *r;
   int i;
   int ldigits;
   int n;
   int ret;

/* Check the global error status. */
   if ( !astOK ) return 0; 

/* The supplied value of "digits" may be negative. Obtain the positive
   value giving the initial number of decimal digits to use. */   
   ldigits = ( digits > 0 ) ? digits : -digits;

/* Loop until a suitably encoded value has been obtained. */
   while( 1 ){

/* Write the value into the buffer.  Most are formatted with a G specifier.
   This will result in values between  -0.001 and -0.0001 being formatted
   without an exponent, and thus occupying (ldigits+6) characters. With
   an exponent, these values would be formatted in (ldigits+5) characters
   thus saving one character. This is important because the default value
   of ldigits is 15, resulting in 21 characters being used by the G
   specifier. This is one more than the maximum allowed by the FITS
   standard. Using an exponent instead would result in 20 characters
   being used without any loss of precision, thus staying within the FITS
   limit. Note, the precision used with the E specifier is one less than
   with the G specifier because the digit to the left of the decimal place
   is significant with the E specifier, and so we only need (ldigits-1)
   significant digits to the right of the decimal point. */
      if( value > -0.001 && value < -0.0001 ) {
         (void) sprintf( buf, "%*.*E", width, ldigits - 1, value );
      } else {
         (void) sprintf( buf, "%*.*G", width, ldigits, value );
      }

/* Check that the value zero is not encoded with a minus sign (e.g. "-0.").
   This also rounds out long sequences of zeros or nines.  */
      CheckZero( buf, value, width );

/* If the formatted value includes an exponent, it will have 2 digits.
   If the exponent includes a leading zero, remove it. */
      if( ( w = strstr( buf, "E-0" ) ) ) {
         w += 2;
      } else if( ( w = strstr( buf, "E+0" ) ) ){
         w += 2;
      } else if( ( w = strstr( buf, "E0" ) ) ){
         w += 1;
      } 

/* If a leading zero was found, shuffle everything down from the start of
   the string by one character, over-writing the redundant zero, and insert
   a space at the start of the string. */
      if( w ) {
         r = w - 1 ;
         while( w != buf ) *(w--) = *(r--);
         *w = ' ';
      }

/* If the used field width was too large, reduce it and try again, so
   long as we are allowed to change the number of digits being used. */
      ret = strlen( buf );
      if( ret > width && digits < 0 ){
         ldigits -= ( ret - width );
       
/* Otherwise leave the loop. Return zero field width if the maximum field 
   width was exceeded. */
      } else {
         if( ret > maxwidth ) ret = 0;
         break;
      }

   }

/* If a formatted value was obtained, we need to ensure that the it includes 
   a decimal point. */
   if( ret ){

/* Get a pointer to the first digit in the buffer. */
      c = strpbrk( buf, "0123456789" );

/* Something funny is going on if there are no digits in the buffer,
   so return a zero field width. */
      if( !c ){
         ret = 0;

/* Otherwise... */
      } else {

/* Find the number of digits following and including the first digit. */
         n = strspn( c, "0123456789" );

/* If the first non-digit character is a decimal point, do nothing. */
         if( c[ n ] != '.' ){

/* If there are two or more leading spaces, move the start of the string
   two character to the left, and insert ".0" in the gap created. This
   keeps the field right justified within the desired field width. */
            if( buf[ 0 ] == ' ' && buf[ 1 ] == ' ' ){
               for( i = 2; i < c - buf + n; i++ ) buf[ i - 2 ] = buf[ i ];
               c[ n - 2 ] = '.';
               c[ n - 1 ] = '0';

/* If there is just one leading space, move the start of the string
   one character to the left, and insert "." in the gap created. This
   keeps the field right justified within the desired field width. */
            } else if( buf[ 0 ] == ' ' ){
               for( i = 0; i < n; i++ ) c[ i - 1 ] = c[ i ];
               c[ n - 1 ] = '.';
  
/* If there are no leading spaces we need to move the end of the string
   to the right. This will result in the string no longer being right 
   justified in the required field width. Return zero if there is 
   insufficient room for an extra character. */
            } else {
               ret++;
               if( ret > maxwidth ){
                  ret = 0;

/* Otherwise, more the end of the string one place to the right and insert 
   the decimal point. */
               } else {
                  for( i = strlen( c ); i >= n; i-- ) c[ i + 1 ] = c[ i ];
                  c[ n ] = '.';
               }
            }
         }
      }
   }

/* Return the field width. */
   return ret;

}

static int EncodeValue( AstFitsChan *this, char *buf, int col, int digits,
                        const char *method ){
/*
*  Name:
*     EncodeValue

*  Purpose:
*     Encode the current card's keyword value into a string.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int EncodeValue( AstFitsChan *this, char *buf, int col, int digits,
*                      const char *method )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function encodes the keyword value defined in the current card
*     of the supplied FitsChan and stores it at the start of the supplied 
*     buffer. The number of characters placed in the buffer is returned
*     (not including a terminating null).

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     buf
*        The buffer to receive the formatted value. This should be at least
*        70 characters long.
*     col
*        The column number within the FITS header card corresponding to the
*        start of "buf".
*     digits
*        The number of digits to use when formatting floating point
*        values. If the supplied value is negative, the number of digits
*        actually used may be reduced if the string would otherwise extend
*        beyond the number of columns allowed by the FITS standard. If the
*        value is positive, the specified number of digits are always
*        produced, even if it means breaking the FITS standard.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.

*  Returned Value:
*     The number of columns used by the encoded value.

*  Notes:
*     -  The function returns 0 if an error has already occurred
*     or if an error occurs for any reason within this function.
*/

/* Local Variables: */
   char *c;         /* Pointer to next character */
   char *name;      /* Pointer to the keyword name */
   double dval;     /* Keyword value */
   void *data;      /* Pointer to keyword value */
   int i;           /* Loop count */
   int ilen;        /* Length of imaginary part */
   int len;         /* Returned length */
   int quote;       /* Quote character found? */
   int rlen;        /* Length of real part */
   int type;        /* Data type for keyword in current card */

/* Check the global status. */
   if( !astOK ) return 0;

/* Initialise returned length. */
   len = 0;

/* Return if there is no value associated with the keyword in the current
   card. */
   data = CardData( this, NULL );
   if( data ) {

/* Get the data type and name of the keyword. */
      type = CardType( this );
      name = CardName( this );

/* Go through each supported data type (roughly in the order of
   decreasing usage)... */

/* AST__FLOAT - stored internally in a variable of type "double".  Right 
   justified to column 30 in the header card. */
      if( type == AST__FLOAT ){
         dval = *( (double *) data );
   
         len = EncodeFloat( buf, digits, FITSRLCOL - FITSNAMLEN - 2, 
                            FITSCARDLEN - col + 1, dval );
   
         if( len <= 0 && astOK ) {
            astError( AST__BDFTS, "%s(%s): Cannot encode floating point value "
                      "%g into a FITS header card for keyword '%s'.", method,
                      astGetClass( this ), dval, name );
         }

/* AST__STRING & AST__CONTINUE - stored internally in a null terminated array of 
   type "char".  The encoded string is enclosed in single quotes, starting
   at FITS column 11 and ending in at least column 20. Single quotes
   in the string are replaced by two adjacent single quotes. */
      } else if( type == AST__STRING || type == AST__CONTINUE ){
         c = (char *) data;

/* Enter the opening quote. */
         len = 0;
         buf[ len++ ] = '\'';

/* Inspect each character, looking for quotes. */
         for ( i = 0; c[ i ]; ) {
            quote = ( c[ i ] == '\'' );

/* If it will not fit into the header card (allowing for doubled
   quotes), give up here. */
            if ( len + ( quote ? 2 : 1 ) > FITSCARDLEN - col ) break;

/* Otherwise, copy it into the output buffer and double any quotes. */
            buf[ len++ ] = c[ i ];
            if ( quote ) buf[ len++ ] = '\'';

/* Look at the next character. */
            i++;
         }

/* Pad the string out to the required minimum length with blanks and
   add the final quote. */
         while( len < FITSSTCOL - col ) buf[ len++ ] = ' ';
         buf[ len++ ] = '\'';

/* Inspect any characters that weren't used. If any are non-blank,
   report an error. */
         for ( ; c[ i ]; i++ ) {
            if ( !isspace( c[ i ] ) ) {
               astError( AST__BDFTS,
                         "%s(%s): Cannot encode string '%s' into a FITS "
                         "header card for keyword '%s'.", method, astGetClass( this ), 
                         (char *) data, name );
               break;
            }
         }

/* INTEGER - stored internally in a variable of type "int". Right justified
   to column 30 in the header card. */
      } else if( type == AST__INT ){
         len = sprintf(  buf, "%*d", FITSRLCOL - col + 1, 
                         *( (int *) data ) );
         if( len < 0 || len > FITSCARDLEN - col ) {
            astError( AST__BDFTS, "%s(%s): Cannot encode integer value %d into a "
                      "FITS header card for keyword '%s'.", method, astGetClass( this ), 
                      *( (int *) data ), name );
         }

/* LOGICAL - stored internally in a variable of type "int". Represented by
   a "T" or "F" in column 30 of the FITS header card. */
      } else if( type == AST__LOGICAL ){
         for( i = 0; i < FITSRLCOL - col; i++ ) buf[ i ] = ' ';
         if( *( (int *) data ) ){
            buf[ FITSRLCOL - col ] = 'T';
         } else {
            buf[ FITSRLCOL - col ] = 'F';
         }
         len = FITSRLCOL - col + 1;

/* AST__COMPLEXF - stored internally in an array of two "doubles". The real
   part is right justified to FITS column 30. The imaginary part is right
   justified to FITS column 50. */
      } else if( type == AST__COMPLEXF ){
         dval = ( (double *) data )[ 0 ];
   
         rlen = EncodeFloat( buf, digits, FITSRLCOL - FITSNAMLEN - 2, 
                             FITSCARDLEN - col + 1, dval );
         if( rlen <= 0 || rlen > FITSCARDLEN - col ) {
            astError( AST__BDFTS, "%s(%s): Cannot encode real part of a complex "
                      "floating point value [%g,%g] into a FITS header card "
                      "for keyword '%s'.", method, astGetClass( this ), dval, 
                      ( (double *) data )[ 1 ], name );
         } else {
   
            dval = ( (double *) data )[ 1 ];
            ilen = EncodeFloat( buf + rlen, digits, 
                                FITSIMCOL - FITSRLCOL, 
                                FITSCARDLEN - col - rlen, dval );
   
            if( ilen <= 0 ) {
               astError( AST__BDFTS, "%s(%s): Cannot encode imaginary part of a "
                         "complex floating point value [%g,%g] into a FITS header "
                         "card for keyword '%s'.", method, astGetClass( this ),
                         ( (double *) data )[ 0 ], dval, name );
            } else {
               len = ilen + rlen;   
            }
   
         }      

/* AST__COMPLEXI - stored internally in a an array of two "ints". */
      } else if( type == AST__COMPLEXI ){
         rlen = sprintf(  buf, "%*d", FITSRLCOL - col + 1, 
                          ( (int *) data )[ 0 ] );
         if( rlen < 0 || rlen > FITSCARDLEN - col ) {
            astError( AST__BDFTS, "%s(%s): Cannot encode real part of a complex "
                      "integer value [%d,%d] into a FITS header card "
                      "for keyword '%s'.", method, astGetClass( this ), 
                      ( (int *) data )[ 0 ], 
                      ( (int *) data )[ 1 ], name );
         } else {
   
            ilen = sprintf(  buf + rlen, "%*d",  FITSIMCOL - FITSRLCOL + 1,
                             ( (int *) data )[ 1 ] );
            if( ilen < 0 || ilen > FITSCARDLEN - col - rlen ) {
               astError( AST__BDFTS, "%s(%s): Cannot encode imaginary part of a "
                         "complex integer value [%d,%d] into a FITS header card "
                         "for keyword '%s'.", method, astGetClass( this ), 
                         ( (int *) data )[ 0 ],
                         ( (int *) data )[ 1 ], name );
            } else {
               len = ilen + rlen;   
   
            }
   
         }
   
/* Report an internal (ast) programming error if the keyword is of none of the
   above types. */
      } else if( astOK ){
         astError( AST__INTER, "EncodeValue: AST internal programming error - "
                   "FITS %s data-type not yet supported.", 
                   type_names[ type ] );
      }

   }

/* If an error has occurred, return zero length. */
   if( !astOK ) len = 0;

/* Return the answer. */
   return len;
}

static AstGrismMap *ExtractGrismMap( AstMapping *map, int iax, 
                                     AstMapping **new_map ){
/*
*  Name:
*     ExtractGrismMap

*  Purpose:
*     Extract a GrismMap from the end of the supplied Mapping.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     AstGrismMap *ExtractGrismMap( AstMapping *map, int iax, 
*                                   AstMapping **new_map )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function examines the supplied Mapping; if the specified output
*     coordinate of the Mapping is created directly by an un-inverted GrismMap,
*     then a pointer to the GrismMap is returned as the function value. A new 
*     Mapping is also returned via parameter "new_map" which is a copy of 
*     the supplied Mapping, except that the GrismMap is replaced with a 
*     UnitMap. If no GrismMap is found, NULL is returned for both Mappings.

*     The condition that "the specified output coordinate of the Mapping is 
*     created directly by an un-inverted GrismMap" means that the output
*     of the GrismMap is no subsequently modified by any further Mappings
*     before being returned as the "iax"th output of the supplied Mapping.
*     This means the GrismMap must be "at the end of" a CmpMap, not in
*     the middle of the CmpMap.

*  Parameters:
*     map
*        Pointer to the Mapping to check.
*     iax
*        The index for the output coordinate to be checked.
*     new_map
*        Pointer to a location at which to return a pointer to a new
*        Mapping which is a copy of "map" except that the GrismMap is 
*        replaced by a UnitMap. NULL is returned if the specified output 
*        was not created by a GrismMap.

*  Returned Value:
*     The extracted GrismMap, or NULL if the specified output was not
*     created by a GrismMap.

*/

/* Local Variables: */
   AstMapping *mapa;     /* First component Mapping */
   AstMapping *mapb;     /* Second component Mapping */
   AstMapping *new_mapa; /* Replacement for first component Mapping */
   AstMapping *new_mapb; /* Replacement for second component Mapping */
   AstGrismMap *ret;     /* Returned GrismMap */
   int inva;             /* Invert attribute for mapa within the CmpMap */
   int invb;             /* Invert attribute for mapb within the CmpMap */
   int na;               /* Number of outputs for mapa */
   int old_inva;         /* Current Invert attribute for mapa */
   int old_invb;         /* Current Invert attribute for mapb */
   int series;           /* Are component Mappings applied in series? */

/* Initialise */
   ret = NULL;
   *new_map = NULL;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* If the supplied Mapping is a GrismMap which has not been inverted, 
   return it as the function value and return a UnitMap as the new 
   Mapping. */
   if( astIsAGrismMap( map ) ) {
      if( !astGetInvert( map ) ) {
         ret = astClone( map );
         *new_map = (AstMapping *) astUnitMap( 1, "" );
      }

/* If the supplied Mapping is a CmpMap, get its two component Mappings,
   see if they are applied in parallel or series, and get the Invert
   attribute values which the component Mappings had at the time the 
   CmpMap was created. */
   } else if(  astIsACmpMap( map ) ) {
      astDecompose( map, &mapa, &mapb, &series, &inva, &invb );

/* Temporaily reset the Invert attributes of the component Mappings back to 
   the values they had when the CmpMap was created. */
      old_inva = astGetInvert( mapa );
      old_invb = astGetInvert( mapb );
      astSetInvert( mapa, inva );
      astSetInvert( mapb, invb );

/* If the supplied Mapping is a series CmpMap, attempt to extract a
   GrismMap from the second component Mapping ("mapb"). The first
   component Mapping ("mapa") is unchanged. We do not need to consdier
   the first component since we are only interested in GrismMaps which are
   at the end of the CmpMap. */
      if( series ) {
         ret = ExtractGrismMap( mapb, iax, &new_mapb );
         if( ret ) new_mapa = astClone( mapa );

/* If the supplied Mapping is a parallel CmpMap, attempt to extract a
   GrismMap from the component Mapping which produces output "iax". The
   other component Mapping is unchanged. */
      } else {
         na = astGetNout( mapa );
         if( iax < na ) {
            ret = ExtractGrismMap( mapa, iax, &new_mapa );
            if( ret ) new_mapb = astClone( mapb );
         } else {
            ret = ExtractGrismMap( mapb, iax - na, &new_mapb );
            if( ret ) new_mapa = astClone( mapa );
         }
      }

/* If succesful, create a new CmpMap to return. */
      if( ret ) {
         *new_map = (AstMapping *) astCmpMap( new_mapa, new_mapb, series, "" );
         new_mapa = astAnnul( new_mapa );                           
         new_mapb = astAnnul( new_mapb );                           
      } 

/* Re-instate the original Invert attributes of the component Mappings. */
      astSetInvert( mapa, old_inva );
      astSetInvert( mapb, old_invb );

/* Annul the component Mapping pointers. */
      mapa = astAnnul( mapa );
      mapb = astAnnul( mapb );

   }

/* Return the result. */
   return ret;

}

static int MakeBasisVectors( AstMapping *map, int nin, int nout, 
                             double *g0, AstPointSet *psetg, 
                             AstPointSet *psetw ){
/*
*  Name:
*     MakeBasisVectors

*  Purpose:
*     Create a set of basis vectors in grid coordinates

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int MakeBasisVectors( AstMapping *map, int nin, int nout, 
*                           double *g0, AstPointSet *psetg, 
*                           AstPointSet *psetw )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function returns a set of unit vectors in grid coordinates,
*     one for each grid axis. Each unit vector is parallel to the
*     corresponding grid axis, and rooted at a specified grid position
*     ("g0"). The IWC coordinates corresponding to "g0" and to the end of 
*     each of the unit vectors are also returned, together with a flag
*     indicating if all the IWC coordinate values are good.

*  Parameters:
*     map
*        A pointer to a Mapping which transforms grid coordinates into
*        intermediate world coordinates (IWC). The number of outputs must 
*        be greater than or equal to the number of inputs.
*     nin
*        The number of inputs for "map" (i.e. the number of grid axes).
*     nout
*        The number of outputs for "map" (i.e. the number of IWC axes).
*     g0
*        Pointer to an array of holding the grid coordinates at the
*        "root" position.
*     psetg
*        A pointer to a PointSet which can be used to hold the required
*        grid position. This should have room for nin+1 positions. On
*        return, the first position holds "g0", and the subsequent "nin" 
*        positions hold are offset from "g0" by unit vectors along the
*        corresponding grid axis. 
*     psetw
*        A pointer to a PointSet which can be used to hold the required
*        IWC position. This should also have room for nin+1 positions. On
*        return, the values are the IWC coordinates corresponding to the
*        grid positions returned in "psetg".

*  Returned Value:
*     A value of 1 is returned if all the axis values in "psetw" are good.
*     Zero is returned otherwise.

*  Notes:
*     -  Zero is returned if an error occurs.
*/

/* Local Variables: */
   double **ptrg;
   double **ptrw;
   double *c;
   int i;
   int ii;
   int j;
   int ret;               

/* Initialise */
   ret = 0;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Get pointers to the data in the two supplied PointSets. */
   ptrg = astGetPoints( psetg );
   ptrw = astGetPoints( psetw );

/* Check the pointers can be used safely. */
   if( astOK ) {

/* Assume success. */
      ret = 1;

/* Store the required grid positions in PointSet "pset1". The first
   position is the supplied root grid position, g0. The next "nin" 
   positions are offset from the root position by a unit vector along 
   each grid axis in turn. Store values for each grid axis in turn. */
      for( i = 0; i < nin; i++ ) {

/* Get a pointer to the first axis value for this grid axis. */
         c = ptrg[ i ];         

/* Initially set all values for this axis to the supplied root grid value. */
         for( ii = 0; ii < nin + 1; ii++ ) c[ ii ] = g0[ i ];

/* Modify the value corresponding to the vector along this grid axis. */
         c[ i + 1 ] += 1.0;
      }

/* Transform these grid positions in IWC positions using the supplied 
   Mapping. */
      astTransform( map, psetg, 1, psetw );

/* Check that all the transformed positions are good. */
      for( j = 0; j < nout; j++ ) {
         c = ptrw[ j ];
         for( ii = 0; ii < nin + 1; ii++, c++ ) {
            if( *c == AST__BAD ) {
               ret = 0;
               break;
            }            
         }
      }
   }

/* Return the result. */
   return ret;
}

static int FindBasisVectors( AstMapping *map, int nin, int nout, 
                             double *dim, AstPointSet *psetg, 
                             AstPointSet *psetw ){
/*
*  Name:
*     FindBasisVectors

*  Purpose:
*     Find the a set of basis vectors in grid coordinates

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int FindBasisVectors( AstMapping *map, int nin, int nout, 
*                           double *dim, AstPointSet *psetg, 
*                           AstPointSet *psetw )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function returns a set of unit vectors in grid coordinates,
*     one for each grid axis. Each unit vector is parallel to the
*     corresponding grid axis, and rooted at a specified grid position
*     ("g0"). The IWC coordinates corresponding to "g0" and to the end of 
*     each of the unit vectors are also returned, together with a flag
*     indicating if all the IWC coordinate values are good.

*  Parameters:
*     map
*        A pointer to a Mapping which transforms grid coordinates into
*        intermediate world coordinates (IWC). The number of outputs must 
*        be greater than or equal to the number of inputs.
*     nin
*        The number of inputs for "map" (i.e. the number of grid axes).
*     nout
*        The number of outputs for "map" (i.e. the number of IWC axes).
*     dim
*        Array dimensions, in pixels, if known (otherwise supplied a NULL
*        pointer to values of AST__BAD).
*     psetg
*        A pointer to a PointSet which can be used to hold the required
*        grid position. This should have room for nin+1 positions. On
*        return, the first position holds the "root" position and the 
*        subsequent "nin" positions hold are offset from root position 
*        by unit vectors along the corresponding grid axis. 
*     psetw
*        A pointer to a PointSet which can be used to hold the required
*        IWC position. This should also have room for nin+1 positions. On
*        return, the values are the IWC coordinates corresponding to the
*        grid positions returned in "psetg".

*  Returned Value:
*     A value of 1 is returned if a set of basis vectors was found
*     succesfully. Zero is returned otherwise.

*  Notes:
*     -  Zero is returned if an error occurs.
*/

/* Local Variables: */
   double *g0;
   double dd;
   double ddlim;
   int i;
   int ii;
   int ret;               

/* Initialise */
   ret = 0;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Allocate an array to store the candidate root position. */
   g0 = astMalloc( sizeof( double )*(size_t) nin );
   if( astOK ) {

/* First try the grid centre, if known. */
      ddlim = 0;
      ret = 0;
      if( dim ) {
         ret = 1;
         for( i = 0; i < nin; i++ ) {
            if( dim[ i ] != AST__BAD ) {
               g0[ i ] = 0.5*dim[ i ];
               if( dim[ i ] > ddlim ) ddlim = dim[ i ];               
            } else {
               ret = 0;
               break;
            }
         }
      }

      if( ret ) ret = MakeBasisVectors( map, nin, nout, g0, psetg, psetw );
       
/* If this did not produce a set of good IWC positions, try grid position
   (1,1,1...). */
      if( !ret ) {
         for( i = 0; i < nin; i++ ) g0[ i ] = 1.0;
         ret = MakeBasisVectors( map, nin, nout, g0, psetg, psetw );
      }       

/* If this did not produce a set of good IWC positions, try a sequence of
   grid positions which move an increasing distance along each grid axis
   from (1,1,1,...). Stop when we get further than "ddlim" from the
   origin. */
      dd = 10.0;
      if( ddlim == 0.0 ) ddlim = 10240.0;
      while( !ret && dd <= ddlim ) {

/* First try positions which extend across the middle of the data set.
   If the image dimensions are known, make the line go from the "bottom
   left corner" towards the "top right corner", taking the aspect ratio
   of the image into account. Otherise, just use a vector of (1,1,1,..) */
         for( i = 0; i < nin; i++ ) {
            if( dim && dim[ i ] != AST__BAD ) {
               g0[ i ] = dd*dim[ i ]/ddlim;
            } else {
               g0[ i ] = dd;
            }
         }

         ret = MakeBasisVectors( map, nin, nout, g0, psetg, psetw );

/* If the above didn't produce good positions, try moving out along each
   grid axis in turn. */
         for( ii = 0; !ret && ii < nin; ii++ ) {
            for( i = 0; i < nin; i++ ) g0[ i ] = 1.0;
            g0[ ii ] = dd;
            ret = MakeBasisVectors( map, nin, nout, g0, psetg, psetw );
         }

/* Go further out from the origin for the next set of tests (if any). */
         dd *= 2.0;
      }       

   }

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

/* Return the result. */
   return ret;
}

static int FindLonLatSpecAxes( FitsStore *store, char s, int *axlon, int *axlat, 
                           int *axspec, const char *method, const char *class ) {
/*
*  Name:
*     FindLonLatSpecAxes

*  Purpose:
*     Search the CTYPE values in a FitsStore for celestial and spectral axes.

*  Type:
*     Private function.

*  Synopsis:
*     int FindLonLatSpecAxes( FitsStore *store, char s, int *axlon, int *axlat,
*                             int *axspec, const char *method, const char *class )

*  Class Membership:
*     FitsChan

*  Description:
*     The supplied FitsStore is searched for axes with a specified axis
*     description character which describe celestial longitude or latitude
*     or spectral position.

*  Parameters:
*     store
*        A structure containing values for FITS keywords relating to 
*        the World Coordinate System.
*     s
*        A character identifying the co-ordinate version to use. A space 
*        means use primary axis descriptions. Otherwise, it must be an 
*        upper-case alphabetical characters ('A' to 'Z').
*     axlon
*        Address of a location at which to return the index of the
*        longitude axis (if found). This is the value of "i" within the
*        keyword name "CTYPEi". A value of -1 is returned if no longitude
*        axis is found.
*     axlat
*        Address of a location at which to return the index of the
*        latitude axis (if found). This is the value of "i" within the
*        keyword name "CTYPEi". A value of -1 is returned if no latitude
*        axis is found.
*     axspec
*        Address of a location at which to return the index of the
*        spectral axis (if found). This is the value of "i" within the
*        keyword name "CTYPEi". A value of -1 is returned if no spectral 
*        axis is found.
*     method
*        A pointer to a string holding the name of the calling method.
*        This is used only in the construction of error messages.
*     class
*        A pointer to a string holding the class of the object being
*        read. This is used only in the construction of error messages.

*  Returned Value:
*     One is returned if both celestial axes were found. Zero is returned if 
*     either axis was not found. The presence of a spectral axis does not
*     affect the returned value.

*  Notes:
*     -  If an error occurs, zero is returned.

*/

/* Local Variables: */
   char algcode[5];
   char stype[5];
   const char *ctype;
   double dval;
   int i;
   int wcsaxes;

/* Initialise */
   *axlon = -1;
   *axlat = -1;
   *axspec = -1;

/* Check the global status. */
   if ( !astOK ) return 0;

/* Obtain the number of FITS WCS axes in the header. If the WCSAXES header 
   was specified, use it. Otherwise assume it is the same as the number
   of pixel axes. */
   dval = GetItem( &(store->wcsaxes), 0, 0, s, NULL, method, class );
   if( dval != AST__BAD ) {
      wcsaxes = (int) dval + 0.5;
   } else {
      wcsaxes = store->naxis;
   }

/* Loop round the FITS WCS axes, getting each CTYPE value. */
   for( i = 0; i < wcsaxes && astOK; i++ ){
      ctype = GetItemC( &(store->ctype), i, s, NULL, method, class );

/* Check a value was found. */
      if( ctype ) {

/* First check for spectral axes. */
         if( IsSpectral( ctype, stype, algcode ) ) {
            *axspec = i;

/* Otherwise look for celestial axes. Celestial axes must have a "-" as the 
   fifth character in CTYPE. */
         } else if( ctype[4] == '-' ) {

/* See if this is a longitude axis (e.g. if the first 4 characters of CTYPE 
   are "RA--" or "xLON" or "yzLN" ). */
            if( !strncmp( ctype, "RA--", 4 ) ||
                !strncmp( ctype + 1, "LON", 3 ) ||
                !strncmp( ctype + 2, "LN", 2 ) ){
               *axlon = i;

/* Otherwise see if it is a latitude axis. */
            } else if( !strncmp( ctype, "DEC-", 4 ) ||
                       !strncmp( ctype + 1, "LAT", 3 ) ||
                       !strncmp( ctype + 2, "LT", 2 ) ){
               *axlat = i;
            }
         }
      }
   }

/* Indicate failure if an error occurred. */
   if( !astOK ) {
      *axlon = -1;
      *axlat = -1;
      *axspec = -1;
   }

/* Return the result. */
   return ( *axlat != -1 && *axlon != -1 );

}

static void FindWcs( AstFitsChan *this, int last, const char *method, const char *class ){
/*
*  Name:
*     FindWcs

*  Purpose:
*     Find the first or last FITS WCS related keyword in a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void FindWcs( AstFitsChan *this, int last, const char *method, const char *class  )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     A search is made through the FitsChan for the first or last card which
*     relates to a FITS WCS keyword (any encoding). The next card becomes 
*     the current card. Cards marked as having been read are included.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     last
*        If non-zero, the last WCS card is searched for. Otherwise, the
*        first WCS card is searched for.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Notes:
*     -  The FitsChan is left at end-of-file if no FITS-WCS keyword cards 
*     are found in the FitsChan.

*-
*/

/* Local Variables: */
   const char *keyname;     /* Keyword name from current card */
   int nfld;                /* Number of fields in keyword template */
   int old_ignoreused;      /* Original value of external variable IgnoreUsed */

/* Check the global status. */
   if( !astOK ) return;

/* Indicate that we should not skip over cards marked as having been
   read. */
   old_ignoreused = IgnoreUsed;
   IgnoreUsed = 0;

/* Set the FitsChan to start or end of file. */
   if( last ) {
      astSetCard( this, INT_MAX );
   } else {
      astClearCard( this );
   }

/* Check each card moving backwards from the end to the start, or
   forwards from the start to the end, until a WCS keyword is found, 
   or the other end of the FitsChan is reached. */
   while( astOK ){   

/* Get the keyword name from the current card. */
      keyname = CardName( this );

/* Save a pointer to the keyword if it is the first non-null, non-comment
   card. */
      if( keyname ) { 

/* If it matches any of the WCS keywords, move on one card 
   and break out of the loop. */
         if( Match( keyname, "CRVAL%d%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "CRPIX%d%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "CDELT%d%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "CROTA%d", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "CTYPE%d%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "CUNIT%d%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PC%3d%3d%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "CD%3d%3d%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "CD%1d_%1d%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PC%1d_%1d%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "LONGPOLE", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "LONPOLE%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "LATPOLE%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PROJP%d", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PV%d_%d%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PS%d_%d%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "EPOCH", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "EQUINOX%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "MJD-OBS%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "DATE-OBS", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "RADECSYS", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "RADESYS%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "C%1dVAL%d", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "C%1dPIX%d", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "C%1dELT%d", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "C%1dYPE%d", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "C%1dNIT%d", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "CNPIX1", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "CNPIX2", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PPO3", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PPO6", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "XPIXELSZ", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "YPIXELSZ", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PLTRAH", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PLTRAM", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PLTRAS", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PLTDECD", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PLTDECM", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PLTDECS", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PLTDECSN", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PLTSCALE", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PPO1", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PPO2", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PPO4", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "PPO5", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "WCSNAME%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "SPECSYS%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "VSOURCE%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "ZSOURCE%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "RESTFRQ%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "MJD_AVG%0c", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "OBSGEO-X", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "OBSGEO-Y", 0, NULL, &nfld, method, class ) ||
             Match( keyname, "OBSGEO-Z", 0, NULL, &nfld, method, class ) ) {

            if( last ) MoveCard( this, 1, method, class );
            break;
         }
      }

/* Leave the FitsChan at end-of-file if no WCS cards were found. */
      if( (last && astGetCard( this ) <= 1 ) ||
          (!last && astFitsEof( this ) ) ) {
         astSetCard( this, INT_MAX );
         break;
      } else {
         MoveCard( this, last?-1:1, method, class );
      }

   }

/* Re-instate the original flag indicating if cards marked as having been 
   read should be skipped over. */
   IgnoreUsed = old_ignoreused;

/* Return. */
   return;
}

static int FindString( int n, const char *list[], const char *test, 
                       const char *text, const char *method, 
                       const char *class ){
/*
*  Name:
*     FindString

*  Purpose:
*     Find a given string within an array of character strings.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int FindString( int n, const char *list[], const char *test, 
*                     const char *text, const char *method, const char *class )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function identifies a supplied string within a supplied
*     array of valid strings, and returns the index of the string within
*     the array. The test option may not be abbreviated, but case is
*     insignificant.

*  Parameters:
*     n
*        The number of strings in the array pointed to be "list".
*     list
*        A pointer to an array of legal character strings.
*     test
*        A candidate string.
*     text
*        A string giving a description of the object, parameter,
*        attribute, etc, to which the test value refers.
*        This is only for use in constructing error messages. It should
*        start with a lower case letter.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     The index of the identified string within the supplied array, starting
*     at zero.

*  Notes:
*     -  A value of -1 is returned if an error has already occurred, or
*     if this function should fail for any reason (for instance if the
*     supplied option is not specified in the supplied list). 

*/

/* Local Variables: */
   int ret;                /* The returned index */

/* Check global status. */
   if( !astOK ) return -1;

/* Compare the test string with each element of the supplied list. Leave
   the loop when a match is found. */
   for( ret = 0; ret < n; ret++ ) {
      if( !Ustrcmp( test, list[ ret ] ) ) break;
   }

/* Report an error if the supplied test string does not match any element
   in the supplied list. */
   if( ret >= n && astOK ) {
      astError( AST__RDERR, "%s(%s): Illegal value '%s' supplied for %s.",
                method, class, test, text );
      ret = -1;
   }

/* Return the answer. */
   return ret;
}

static int FitOK( int n, double *act, double *est ) {
/*
*  Name:
*     FitOK

*  Purpose:
*     See if a fit is usable.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int FitOK( int n, double *act, double *est )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function is supplied with a set of actual data values, and the
*     corresponding values estimated by some fitting process. It tests
*     the correlation between these two sets of data and returns 1 if the
*     correlation is very close to +1. Otherwise it returns zero.

*  Parameters:
*     n
*        Number of data points.
*     act
*        Pointer to the start of the actual data values.
*     est
*        Pointer to the start of the estimated data values.

*  Returned Value:
*     A value of 1 is returned if the two sets of values agree. Zero is
*     returned otherwise.

*  Notes:
*     -  Zero is returned if an error occurs.
*/

/* Local Variables: */
   int ret, i;   
   double s1, s2, s3, s4, s5, s6;
   double *px, *py, den1, den2, denom, r;

/* Initialise */
   ret = 0;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Form the sums needed to calculate the correlation coefficient between
   the actual and estimated values. */
   s1 = 0.0;
   s2 = 0.0;
   s3 = 0.0;
   s4 = 0.0;
   s5 = 0.0;
   s6 = 0.0;

   px = act;
   py = est;
   for( i = 0; i < n; i++, px++, py++ ){
      if( *px!= AST__BAD && *py != AST__BAD ) {
         s1 += *px;
         s2 += *py;
         s3 += (*px)*(*py);
         s4 += (*px)*(*px);
         s5 += (*py)*(*py);
         s6 += 1.0;
      }
   }

/* Normalise the values unless no good points were found. */
   if( s6 > 0 ) {
      s1 /= s6;
      s2 /= s6;
      s3 /= s6;
      s4 /= s6;
      s5 /= s6;

/* If the actual and estimated values are effectively constant, assume the
   fit is linear. */
      den1 = ( s4 - s1*s1 );
      den2 = ( s5 - s2*s2 );
      denom = den1*den2;
      if( den1 <= 1.0E-10*fabs( s1 ) && den2 <= 1.0E-10*fabs( s2 ) ) {
         ret = 1;

/* Otherwise, check the correlation coefficient between the actual and
   estimates values is sufficiently high. */
      } else if( denom > 0.0 ) {
         r = ( s3 - s1*s2 )/sqrt( denom );
         ret = ( r > 0.999999 );
      }
   } 

/* Return the result. */
   return ret;
}

static int FitsFromStore( AstFitsChan *this, FitsStore *store, int encoding, 
                          const char *method, const char *class ){
/*
*  Name:
*     FitsFromStore

*  Purpose:
*     Store WCS keywords in a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     int FitsFromStore( AstFitsChan *this, FitsStore *store, int encoding, 
*                        const char *method, const char *class )

*  Class Membership:
*     FitsChan

*  Description:
*     A FitsStore is a structure containing a generalised represention of
*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
*     from a set of FITS header cards (using a specified encoding), or
*     an AST FrameSet. In other words, a FitsStore is an encoding-
*     independant intermediary staging post between a FITS header and 
*     an AST FrameSet.
*
*     This function copies the WCS information stored in the supplied 
*     FitsStore into the supplied FitsChan, using a specified encoding.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     store
*        Pointer to the FitsStore.
*     encoding
*        The encoding to use.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     A value of 1 is returned if succesfull, and zero is returned
*     otherwise.

*/

/* Local Variables: */
   int ret;

/* Initialise */
   ret = 0;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Set the current card so that it points to the last WCS-related keyword
   in the FitsChan (whether previously read or not). Any new WCS related
   keywords either over-write pre-existing cards for the same keyword, or
   (if no pre-existing card exists) are inserted after the last WCS related
   keyword. */
   FindWcs( this, 1, method, class );

/* Do each non-standard FITS encoding... */
   if( encoding == DSS_ENCODING ){
      ret = DSSFromStore( this, store, method, class );

   } else if( encoding == FITSPC_ENCODING ){
      ret = PCFromStore( this, store, method, class );

   } else if( encoding == FITSIRAF_ENCODING ){
      ret = IRAFFromStore( this, store, method, class );

   } else if( encoding == FITSAIPS_ENCODING ){
      ret = AIPSFromStore( this, store, method, class );

   } else if( encoding == FITSAIPSPP_ENCODING ){
      ret = AIPSPPFromStore( this, store, method, class );

/* Standard FITS-WCS encoding */
   } else {
      ret = WcsFromStore( this, store, method, class );

   }

/* If an error has occurred, return zero. */
   if( !astOK ) ret = 0;

/* Return the answer. */
   return ret;

}

static FitsStore *FitsToStore( AstFitsChan *this, int encoding,
                               const char *method, const char *class ){
/*
*  Name:
*     FitsToStore

*  Purpose:
*     Return a pointer to a FitsStore structure containing WCS information
*     read from the supplied FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     FitsStore *FitsToStore( AstFitsChan *this, int encoding,
*                             const char *method, const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     A FitsStore is a structure containing a generalised represention of
*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
*     from a set of FITS header cards (using a specified encoding), or
*     an AST FrameSet. In other words, a FitsStore is an encoding-
*     independant intermediary staging post between a FITS header and 
*     an AST FrameSet.
*
*     This function creates a new FitsStore containing WCS information
*     read from the supplied FitsChan using the specified encoding. An
*     error is reported and a null pointer returned if the FitsChan does 
*     not contain usable WCS information with the specified encoding.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     encoding
*        The encoding to use.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     A pointer to a new FitsStore, or NULL if an error has occurred. The 
*     FitsStore should be released using FreeStore function when it is no 
*     longer needed.

*/

/* Local Variables: */
   AstFitsChan *trans;
   FitsStore *ret;

/* Initialise */
   ret = NULL;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Allocate memory for the new FitsStore, and store NULL pointers in it. */
   ret = (FitsStore *) astMalloc( sizeof(FitsStore) );
   if( ret ) {
      ret->cname = NULL;
      ret->ctype = NULL;
      ret->ctype_com = NULL;
      ret->cunit = NULL;
      ret->radesys = NULL;
      ret->wcsname = NULL;
      ret->wcsaxes = NULL;
      ret->pc = NULL;
      ret->cdelt = NULL;
      ret->crpix = NULL;
      ret->crval = NULL;
      ret->equinox = NULL;
      ret->latpole = NULL;
      ret->lonpole = NULL;
      ret->mjdobs = NULL;
      ret->mjdavg = NULL;
      ret->pv = NULL;
      ret->specsys = NULL;
      ret->obsgeox = NULL;
      ret->obsgeoy = NULL;
      ret->obsgeoz = NULL;
      ret->restfrq = NULL;
      ret->restwav = NULL;
      ret->vsource = NULL;
      ret->zsource = NULL;
      ret->asip = NULL;
      ret->bsip = NULL;
      ret->apsip = NULL;
      ret->bpsip = NULL;
      ret->naxis = 0;
   }

/* Call the routine apropriate to the encoding. */
   if( encoding == DSS_ENCODING ){
      DSSToStore( this, ret, method, class );

/* All other foreign encodings are treated as variants of FITS-WCS. */
   } else {

/* Create a new FitsChan containing standard translations for any
   non-standard keywords in the supplied FitsChan. The non-standard
   keywords are marked as provisionally read in the supplied FitsChan. */
      trans = SpecTrans( this, encoding, method, class );

/* Copy the required values to the FitsStore, using keywords in "trans"
   in preference to those in "this". */
      WcsToStore( this, trans, ret, method, class );

/* Delete the temporary FitsChan holding translations of non-standard
   keywords. */
      if( trans ) trans = (AstFitsChan *) astDelete( trans );

/* Store the number of pixel axes. This is taken as the highest index used
   in any primary CRPIX keyword. */
      ret->naxis = GetMaxJM( &(ret->crpix), ' ' ) + 1;
   }

/* If an error has occurred, free the returned FitsStore, and return a null 
   pointer. */
   if( !astOK ) ret = FreeStore( ret );

/* Return the answer. */
   return ret;

}

static void FreeItem( double ****item ){
/*
*  Name:
*     FreeItem

*  Purpose:
*     Frees all dynamically allocated memory associated with a specified
*     item in a FitsStore.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void FreeItem( double ****item );

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Frees all dynamically allocated memory associated with the specified
*     item in a FitsStore. A NULL pointer is stored in the FitsStore.

*  Parameters:
*     item
*        The address of the pointer within the FitsStore which locates the 
*        arrays of values for the required keyword (eg &(store->crval) ).
*        The array located by the supplied pointer contains a vector of
*        pointers. Each of these pointers is associated with a particular
*        co-ordinate version (s), and locates an array of pointers for that 
*        co-ordinate version. Each such array of pointers has an element
*        for each intermediate axis number (j), and the pointer locates an
*        array of axis keyword values. These arrays of keyword values have 
*        one element for every pixel axis (i) or projection parameter (m). 

*  Notes:
*    - This function attempt to execute even if an error has occurred.

*/

/* Local Variables: */
   int si;               /* Integer co-ordinate version index */
   int j;                /* Intermediate co-ordinate axis index */

/* Check the supplied pointer */
   if( item && *item ){

/* Loop round each coordinate version. */
      for( si = 0; si < astSizeOf( (void *) *item )/sizeof(double **);
           si++ ){

/* Check the pointer stored for this co-ordinate version is not null. */
         if( (*item)[si] ) {

/* Loop round the intermediate axes. */
            for( j = 0; j < astSizeOf( (void *) (*item)[si] )/sizeof(double *);
                 j++ ){

/* Free the pixel axis/parameter index pointer. */
               (*item)[si][j] = (double *) astFree( (void *) (*item)[si][j] );
            }

/* Free the intermediate axes pointer */
            (*item)[si] = (double **) astFree( (void *) (*item)[si] );
         }
      }

/* Free the co-ordinate versions pointer */
      *item = (double ***) astFree( (void *) *item );

   }

}

static void FreeItemC( char ****item ){
/*
*  Name:
*     FreeItemC

*  Purpose:
*     Frees all dynamically allocated memory associated with a specified
*     string item in a FitsStore.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void FreeItemC( char ****item );

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Frees all dynamically allocated memory associated with the specified
*     string item in a FitsStore. A NULL pointer is stored in the FitsStore.

*  Parameters:
*     item
*        The address of the pointer within the FitsStore which locates the 
*        arrays of values for the required keyword (eg &(store->crval) ).
*        The array located by the supplied pointer contains a vector of
*        pointers. Each of these pointers is associated with a particular
*        co-ordinate version (s), and locates an array of pointers for that 
*        co-ordinate version. Each such array of pointers has an element
*        for each intermediate axis number (j), and the pointer locates a
*        character string. 

*  Notes:
*    - This function attempt to execute even if an error has occurred.

*/

/* Local Variables: */
   int si;               /* Integer co-ordinate version index */
   int j;                /* Intermediate co-ordinate axis index */

/* Check the supplied pointer */
   if( item && *item ){

/* Loop round each coordinate version. */
      for( si = 0; si < astSizeOf( (void *) *item )/sizeof(char **);
           si++ ){

/* Check the pointer stored for this co-ordinate version is not null. */
         if( (*item)[si] ) {

/* Loop round the intermediate axes. */
            for( j = 0; j < astSizeOf( (void *) (*item)[si] )/sizeof(char *);
                 j++ ){

/* Free the character string pointer. */
               (*item)[si][j] = (char *) astFree( (void *) (*item)[si][j] );
            }

/* Free the intermediate axes pointer */
            (*item)[si] = (char **) astFree( (void *) (*item)[si] );
         }
      }

/* Free the co-ordinate versions pointer */
      *item = (char ***) astFree( (void *) *item );

   }

}

static FitsStore *FreeStore( FitsStore *store ){
/*
*  Name:
*     FreeStore

*  Purpose:
*     Free dynamic arrays stored in a FitsStore structure.

*  Type:
*     Private function.

*  Synopsis:
*     FitsStore *FreeStore( FitsStore *store )

*  Class Membership:
*     FitsChan

*  Description:
*     This function frees all dynamically allocated arrays stored in the
*     supplied FitsStore structure, and returns a NULL pointer.

*  Parameters:
*     store
*        Pointer to the structure to clean.

*  Notes:
*     - This function attempts to execute even if an error exists on entry.

*/

/* Return if no FitsStore was supplied. */
   if( !store ) return NULL;

/* Free each of the dynamic arrays stored in the FitsStore. */
   FreeItemC( &(store->cname) );
   FreeItemC( &(store->ctype) );
   FreeItemC( &(store->ctype_com) );
   FreeItemC( &(store->cunit) );
   FreeItemC( &(store->radesys) );
   FreeItemC( &(store->wcsname) );
   FreeItemC( &(store->specsys) );

   FreeItem( &(store->pc) );
   FreeItem( &(store->cdelt) );
   FreeItem( &(store->crpix) );
   FreeItem( &(store->crval) );
   FreeItem( &(store->equinox) );
   FreeItem( &(store->latpole) );
   FreeItem( &(store->lonpole) );
   FreeItem( &(store->mjdobs) );
   FreeItem( &(store->mjdavg) );
   FreeItem( &(store->pv) );
   FreeItem( &(store->wcsaxes) );
   FreeItem( &(store->obsgeox) );
   FreeItem( &(store->obsgeoy) );
   FreeItem( &(store->obsgeoz) );
   FreeItem( &(store->restfrq) );
   FreeItem( &(store->restwav) );
   FreeItem( &(store->vsource) );
   FreeItem( &(store->zsource) );
   FreeItem( &(store->asip) );
   FreeItem( &(store->bsip) );
   FreeItem( &(store->apsip) );
   FreeItem( &(store->bpsip) );

   return (FitsStore *) astFree( (void *) store );
}

static char *FormatKey( char *key, int c1, int c2, char s ){
/*
*  Name:
*     FormatKey

*  Purpose:
*     Format a keyword name with indices and co-ordinate version character.

*  Type:
*     Private function.

*  Synopsis:
*     char *FormatKey( char *key, int c1, int c2, char s )

*  Class Membership:
*     FitsChan

*  Description:
*     This function formats a keyword name by including the supplied
*     axis/parameter indices and co-ordinate version character.

*  Parameters:
*     key
*        The base name of the keyword (e.g. "CD", "CRVAL", etc).
*     c1
*        An integer value to append to the end of the keyword. Ignored if
*        less than zero.
*     c2
*        A second integer value to append to the end of the keyword. Ignored if
*        less than zero. This second integer is preceeded by an underscore.
*     s
*        The co-ordinate version character to append to the end of the
*        final string. Ignored if blank.

*  Returned Value;
*     A pointer to a static character buffer containing the final string.
*     NULL if an error occurs.

*/

/* Local Variables: */
   static char buff[10];
   char *ret;
   int len;
   int nc; 

/* Initialise */
   ret = NULL;

/* Check inherited status */
   if( !astOK ) return ret;   

/* No characters stored yet. A value of -1 is used to indicate that an 
   error has occurred. */
   len = 0;

/* Store the supplied keyword base name. */
   if( len >= 0 && ( nc = sprintf( buff + len, "%s", key ) ) >= 0 ){
      len += nc;
   } else {
      len = -1;
   }

/* If index c1 has been supplied, append it to the end of the string. */
   if( c1 >= 0 ) {
      if( len >= 0 && ( nc = sprintf( buff + len, "%d", c1 ) ) >= 0 ){
         len += nc;
      } else {
         len = -1;
      }

/* If index c2 has been supplied, append it to the end of the string,
   preceeded by an underscore. */
      if( c2 >= 0 ) {
         if( len >= 0 && ( nc = sprintf( buff + len, "_%d", c2 ) ) >= 0 ){
            len += nc;
         } else {
            len = -1;
         }
      }
   }

/* If a co-ordinate version character has been supplied, append it to the end 
   of the string. */
   if( s != ' ' ) {
      if( len >= 0 && ( nc = sprintf( buff + len, "%c", s ) ) >= 0 ){
         len += nc;
      } else {
         len = -1;
      }
   }

/* Report an error if necessary */
   if( len < 0 && astOK ) {
      astError( AST__INTER, "FormatKey(fitschan): AST internal error; failed "
                "to format the keyword %s with indices %d and %d, and "
                "co-ordinate version %c.", key, c1, c2, s );
      ret = NULL;

   } else {
      ret = buff;
   }

   return buff;
}

static AstObject *FsetFromStore( AstFitsChan *this, FitsStore *store, 
                                 const char *method, const char *class ){
/*
*  Name:
*     FsetFromStore

*  Purpose:
*     Create a FrameSet using the the information previously stored in
*     the suppllied FitsStore structure.

*  Type:
*     Private function.

*  Synopsis:
*     AstObject *FsetFromStore( AstFitsChan *this, FitsStore *store, 
*                               const char *method, const char *class )

*  Class Membership:
*     FitsChan

*  Description:
*     A FitsStore is a structure containing a generalised represention of
*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
*     from a set of FITS header cards (using a specified encoding), or
*     an AST FrameSet. In other words, a FitsStore is an encoding-
*     independant intermediary staging post between a FITS header and 
*     an AST FrameSet.
*
*     This function creates a new FrameSet containing WCS information
*     stored in the supplied FitsStore. A null pointer is returned and no
*     error is reported if this is not possible.

*  Parameters:
*     this
*        The FitsChan from which the keywords were read. Warning messages
*        are added to this FitsChan if the celestial co-ordinate system is 
*        not recognized. 
*     store
*        Pointer to the FitsStore.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     A pointer to the new FrameSet, or a null pointer if no FrameSet
*     could be constructed.

*  Notes:
*     -  The pixel Frame is given a title of "Pixel Coordinates", and
*     each axis in the pixel Frame is given a label of the form "Pixel
*     axis <n>", where <n> is the axis index (starting at one).
*     -  The FITS CTYPE keyword values are used to set the labels for any
*     non-celestial axes in the physical coordinate Frames, and the FITS 
*     CUNIT keywords are used to set the corresponding units strings.
*     -  On exit, the pixel Frame is the base Frame, and the physical
*     Frame derived from the primary axis descriptions is the current Frame.
*     - Extra Frames are added to hold any secondary axis descriptions. All
*     axes within such a Frame refer to the same coordinate version ('A',
*     'B', etc).

*/

/* Local Variables: */
   AstFrame *frame;   /* Pointer to pixel Frame */
   AstFrameSet *ret;  /* Pointer to returned FrameSet */
   char buff[ 20 ];   /* Buffer for axis label */
   char s;            /* Co-ordinate version character */
   int i;             /* Pixel axis index */
   int physical;      /* Index of primary physical co-ordinate Frame */
   int pixel;         /* Index of pixel Frame in returned FrameSet */
   int use;           /* Has this co-ordinate version been used? */

/* Initialise */
   ret = NULL;

/* Check the inherited status. */
   if( !astOK ) return (AstObject *) ret;

/* Only proceed if there are some axes. */
   if( store->naxis ) {

/* Create a Frame describing the pixel coordinate system. Give it the Domain 
   GRID. */
      frame = astFrame( store->naxis, "Title=Pixel Coordinates,Domain=GRID" );

/* Store labels for each pixel axis. */
      if( astOK ){
         for( i = 0; i < store->naxis; i++ ){
            sprintf( buff, "Pixel axis %d", i + 1 );
            astSetLabel( frame, i, buff );
         }
      }

/* Create the FrameSet initially holding just the pixel coordinate frame
   (this becomes the base Frame). */
      ret = astFrameSet( frame, "" );

/* Annul the pointer to the pixel coordinate Frame. */
      frame = astAnnul( frame );

/* Get the index of the pixel Frame in the FrameSet. */
      pixel = astGetCurrent( ret );

/* Produce the Frame describing the primary axis descriptions, and add it
   into the FrameSet. */
      AddFrame( this, ret, pixel, store->naxis, store, ' ', method, class );  

/* Get the index of the primary physical co-ordinate Frame in the FrameSet. */
      physical = astGetCurrent( ret );

/* Loop, producing secondary axis Frames for each of the co-ordinate 
   versions stored in the FitsStore. */
      for( s = 'A'; s <= GetMaxS( &(store->crval) ) && astOK; s++ ){      

/* Only use this co-ordinate version character if any of the required
   keywords (for any axis) are stored in the FitsStore. */
         use = 0;
         for( i = 0; i < store->naxis; i++ ){
            if( GetItem( &(store->crval), i, 0, s, NULL, method, class ) != AST__BAD ||
                GetItem( &(store->crpix), 0, i, s, NULL, method, class ) != AST__BAD ||
                GetItemC( &(store->ctype), i, s, NULL, method, class ) != NULL ){
               use = 1;
               break;
            }
         }

/* If this co-ordinate version has been used, add a Frame to the returned
   FrameSet holding this co-ordinate version. */
         if( use ) AddFrame( this, ret, pixel, store->naxis, store, s, method, class );  

      }

/* Ensure the pixel Frame is the Base Frame and the primary physical
   Frame is the Current Frame. */
      astSetBase( ret, pixel );
      astSetCurrent( ret, physical );
   }

/* If an error has occurred, free the returned FrameSet and return a null 
   pointer. */
   if( !astOK ) ret = astAnnul( ret );

/* Return the answer. */
   return (AstObject *) ret;

}

static FitsStore *FsetToStore( AstFitsChan *this, AstFrameSet *fset, int naxis,
                               double *dim, const char *class, 
                               const char *method ){
/*
*  Name:
*     FsetToStore

*  Purpose:
*     Fill a FitsStore structure with a description of the supplied
*     FrameSet.

*  Type:
*     Private function.

*  Synopsis:
*     FitsStore *FsetToStore( AstFitsChan *this, AstFrameSet *fset, int naxis,
*                             double *dim, const char *class, 
*                             const char *method )

*  Class Membership:
*     FitsChan

*  Description:
*     A FitsStore is a structure containing a generalised represention of
*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
*     from a set of FITS header cards (using a specified encoding), or
*     an AST FrameSet. In other words, a FitsStore is an encoding-
*     independant intermediary staging post between a FITS header and 
*     an AST FrameSet.
*
*     This function creates a new FitsStore containing WCS information
*     read from the supplied FitsChan using the specified encoding. An
*     error is reported and a null pointer returned if the FitsChan does 
*     not contain usable WCS information with the specified encoding.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     fset
*        Pointer to the FrameSet.
*     naxis
*        The number of axes in the Base Frame of the supplied FrameSet.
*     dim 
*        Pointer to an array of pixel axis dimensions. Individual elements 
*        will be AST__BAD if dimensions are not known.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     A pointer to a new FitsStore, or NULL if an error has occurred. The 
*     FitsStore should be released using FreeStore function when it is no 
*     longer needed.

*  Notes:
*     - A NULL pointer will be returned if this function is invoked
*     with the AST error status set, or if it should fail for any
*     reason.
*     - The Base Frame in the FrameSet is used as the pixel Frame, and
*     the Current Frame is used to create the primary axis descriptions.
*     Attempts are made to create secondary axis descriptions for any 
*     other Frames in the FrameSet (up to a total of 26).
*/

/* Local Variables: */
   AstFrame *frame;     /* A Frame */
   const char *id;      /* Frame Ident string */
   int nfrm;            /* Number of Frames in FrameSet */
   char *sid;           /* Pointer to array of version letters */
   int frms[ 'Z' + 1 ]; /* Array of Frame indices */
   FitsStore *ret;      /* Returned FitsStore */
   char s;              /* Co-ordinate version character */
   int ibase;           /* Base Frame index */
   int icurr;           /* Current Frame index */
   int ifrm;            /* Next Frame index */
   int primok;          /* Primary Frame stored succesfully? */
   int secok;           /* Secondary Frame stored succesfully? */

/* Initialise */
   ret = NULL;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Allocate memory for the new FitsStore, and store NULL pointers in it. */
   ret = (FitsStore *) astMalloc( sizeof(FitsStore) );
   if( astOK ) {
      ret->cname = NULL;
      ret->ctype = NULL;
      ret->ctype_com = NULL;
      ret->cunit = NULL;
      ret->radesys = NULL;
      ret->wcsname = NULL;
      ret->wcsaxes = NULL;
      ret->pc = NULL;
      ret->cdelt = NULL;
      ret->crpix = NULL;
      ret->crval = NULL;
      ret->equinox = NULL;
      ret->latpole = NULL;
      ret->lonpole = NULL;
      ret->mjdobs = NULL;
      ret->mjdavg = NULL;
      ret->pv = NULL;
      ret->specsys = NULL;
      ret->obsgeox = NULL;
      ret->obsgeoy = NULL;
      ret->obsgeoz = NULL;
      ret->restfrq = NULL;
      ret->restwav = NULL;
      ret->vsource = NULL;
      ret->zsource = NULL;
      ret->asip = NULL;
      ret->bsip = NULL;
      ret->apsip = NULL;
      ret->bpsip = NULL;
      ret->naxis = naxis;

/* Obtain the index of the Base Frame (i.e. the pixel frame ). */
      ibase = astGetBase( fset );

/* Obtain the index of the Current Frame (i.e. the Frame to use as the 
   primary physical coordinate frame). */
      icurr = astGetCurrent( fset );

/* Add a description of the primary axes to the FitsStore, based on the
   Current Frame in the FrameSet. */
      primok = AddVersion( this, fset, ibase, icurr, ret, dim, ' ', method, 
                           class );

/* Do not add any alternate axis descriptions if the primary axis
   descriptions could not be produced. */
      if( primok && astOK ) {

/* Get the number of Frames in the FrameSet. */
         nfrm = astGetNframe( fset );

/* We now need to allocate a version letter to each Frame. Allocate
   memory to hold the version letter assigned to each Frame. */
         sid = (char *) astMalloc( ( nfrm + 1 )*sizeof( char ) );

/* The frms array has an entry for each of the 26 possible version
   letters (starting at A and ending at Z). Each entry holds the index of 
   the Frame which has been assigned that version character. Initialise
   this array to indicate that no version letters have yet been assigned. */
         for( s = 'A'; s <= 'Z'; s++ ) {
            frms[ (int) s ] = 0;
         }

/* Loop round all frames (excluding the current and base and IWC Frames which 
   do not need version letters). If the Frame has an Ident attribute consisting 
   of a single upper case letter, use it as its version letter unless that
   letter has already been given to an earlier frame. IWC Frames are not
   written out - identify them by giving thema a "sid" value of 1 (an
   illegal FITS axis description character). */
         for( ifrm = 1; ifrm <= nfrm; ifrm++ ){
            sid[ ifrm ] = 0;
            if( ifrm != icurr && ifrm != ibase ) { 
               frame = astGetFrame( fset, ifrm );
               if( astChrMatchN( astGetDomain( frame ), "IWC", 3 ) ) {
                  sid[ ifrm ] = 1;
               } else {
                  id = astGetIdent( frame );
                  if( strlen( id ) == 1 && isupper( id[ 0 ] ) ) {
                     if( frms[ (int) id[ 0 ] ] == 0 ) {
                        frms[ (int) id[ 0 ] ] = ifrm;
                        sid[ ifrm ] = id[ 0 ];
                     }
                  }
               }
               astAnnul( frame );
            }
         }

/* Now go round all the Frames again, looking for Frames which did not
   get a version letter assigned to it on the previous loop. Assign them
   letters now, selected them from the letters not already assigned
   (lowest to highest). */
         s = 'A' - 1;
         for( ifrm = 1; ifrm <= nfrm; ifrm++ ){
            if( ifrm != icurr && ifrm != ibase && sid[ ifrm ] != 1 ) { 
               if( sid[ ifrm ] == 0 ){
                  while( frms[ (int) ++s ] != 0 );
                  if( s <= 'Z' ) { 
                     sid[ ifrm ] = s;
                     frms[ (int) s ] = ifrm;
                  }
               }
            }
         }

/* Now go through all the other Frames in the FrameSet, attempting to
   create alternate axis descriptions for each one. */
         for( ifrm = 1; ifrm <= nfrm; ifrm++ ){
            s = sid[ ifrm ];
            if( s != 0 && s != 1 ) {
               secok = AddVersion( this, fset, ibase, ifrm, ret, dim, 
                                   s, method, class );
            }
         }

/* Free memory holding version letters */
         sid = (char *) astFree( (void *) sid );

      }

/* If an error has occurred, or if the primary Frame could not be cerated, 
   free the returned FitsStore, and return a null pointer. */
      if( !astOK || !primok ) ret = FreeStore( ret );
   }

/* Return the answer. */
   return ret;

}

static void Geod( double pos[3], double *phi, double *h, double *lambda ){
/*
*  Name:
*     Geod

*  Purpose:
*     Convert a terrestrial Cartesian (x,y,z) position to geodetic lat/long 

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void Geod( double pos[3], double *phi, double *h, double *lambda )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function converts a position supplied as terrestrial Cartesian
*     (x,y,z) values into geodetic longitude, latitude and height above the
*     reference spheroid. The (x,y,z) system has origin at the centre of
*     the earth, Z axis going through the north pole, X axis at
*     (long,lat)=(0,0), and Y axis at (long,lat) = (E90,0).
*
*     The algorithm is due to Borkowski, and is described in the
*     Explanatory Supplement to the Astronomical Almanac (p206).

*  Parameters:
*     pos
*        Array holding the (x,y,z) values, in metres.
*     phi
*        Pointer at a location at which to return the geodetic latitude,
*        in radians.
*     h
*        Pointer at a location at which to return the height above the
*        reference spheroid (geodetic, metres).
*     lambda
*        Pointer at a location at which to return the geodetic longitude,
*        in radians.
*/

/* Local Variables... */
   double r, e, f, p, q, d, n, g, t, rp, rd, sn, b0, boa, ab2oa;

/* Initialise */
   *phi = 0.0;
   *h = 0.0;
   *lambda = 0.0;

/* Check the global status. */
   if( !astOK ) return;

/* Earth polar radius (metres) */
   b0 = A0*( 1.0 - FL );

/* Useful functions */
   boa = b0/A0;
   ab2oa = ( A0*A0 - b0*b0)/A0;

/* To obtain the proper sign and polynomial solution, the sign of b is
   set to that of z. Note the sign of z. */
   if( pos[ 2 ] > 0.0 ) {
      sn = 1.0;
   } else {
      sn = -1.0;
   }

/* If the supplied position is on the polar axis, the returned values are 
   trivial. We check this case because it corresponds to a singularity in
   the main algorithm. */
   r = sqrt( pos[ 0 ]*pos[ 0 ] + pos[ 1 ]*pos[ 1 ] );
   if( r == 0 ) {
      *lambda = 0.0;
      *phi = AST__DPIBY2;
      *h = pos[ 2 ] - sn*b0;

   } else {   

/* The longitude is simple. */
      *lambda = atan2( pos[ 1 ], pos[ 0 ] );

/* The equator is also a singularity in the main algorithm. If the
   supplied point is on the equator, the answers are trivial. */
      if( pos[ 2 ] == 0.0 ) {
         *phi = 0.0;
         *h = r - A0;

/* For all other cases, use the main Borkowski algorithm. */
      } else {
         e = ( sn*boa*pos[ 2 ] - ab2oa )/r;
         f = ( sn*boa*pos[ 2 ] + ab2oa )/r;
         p = 4.0*( e*f + 1.0 )/3.0;
         q = 2.0*( e*e - f*f );
         d = p*p*p + q*q;

         if( d < 0.0 ) {
            rp = sqrt( -p );
            n = 2.0*rp*cos( acos( q/(p*rp) )/3.0 );
         } else {
            rd = sqrt( d );
            n = pow( ( rd - q ), 1.0/3.0 ) - pow( (rd + q ), 1.0/3.0 );
         }

         g = 0.5* ( sqrt( e*e + n ) + e );
         t = sqrt( g*g + ( f - n*g )/( 2*g - e ) ) - g;

         *phi = atan( A0*( 1.0 - t*t  )/( 2.0*sn*b0*t ) );
         *h = ( r - A0*t )*cos( *phi ) + ( pos[ 2 ] - sn*b0 )*sin( *phi );

      }
   }
}

static int GetCDMatrix( AstFitsChan *this ){
/*
*  Name:
*     GetCDMatrix

*  Purpose:
*     Get the value of the CDMatrix attribute.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int GetCDMatrix( AstFitsChan *this )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     If the CDMatrix attribute has been set, then its value is returned. 
*     Otherwise, the supplied FitsChan is searched for keywords of the
*     form CDi_j. If any are found a non-zero value is returned. Otherwise 
*     a zero value is returned.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     The attribute value to use.

*  Notes:
*     -  A value of zero is returned if an error has already occurred
*     or if an error occurs for any reason within this function.
*/

/* Local Variables... */
   int ret;            /* Returned value */
   int icard;          /* Index of current card on entry */

/* Check the global status. */
   if( !astOK ) return 0;

/* If a value has been supplied for the CDMatrix attribute, use it. */
   if( astTestCDMatrix( this ) ) {
      ret = this->cdmatrix;

/* Otherwise, check for the existence of CDi_j keywords... */
   } else {

/* Save the current card index, and rewind the FitsChan. */
      icard = astGetCard( this );
      astClearCard( this );

/* If the FitsChan contains any keywords with the format "CDi_j" then return 
   1. Otherwise return zero. */
      ret = astKeyFields( this, "CD%1d_%1d", 0, NULL, NULL ) ? 1 : 0;

/* Reinstate the original current card index. */
      astSetCard( this, icard );

   }

/* Return  the result. */
   return astOK ? ret : 0;
}

static int GetEncoding( AstFitsChan *this ){
/*
*  Name:
*     GetEncoding

*  Purpose:
*     Get the value of the Encoding attribute.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int GetEncoding( AstFitsChan *this )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     If the Encoding attribute has been set, then its value is returned. 
*     Otherwise, an attempt is made to determine the encoding scheme by 
*     looking for selected keywords within the FitsChan. Checks are made
*     for the following keywords in the order specified, and the
*     corresponding encoding is adopted when the first one is found ( where
*     i, j and m are integers and s is a single upper case character):
*
*     1) Any keywords starting with "BEGAST" = Native encoding 
*     2) Any AIPS spectral CTYPE values:
*         Any of CDi_j, PROJP, LONPOLE, LATPOLE = FITS-AIPS++ encoding:
*         None of the above = FITS-AIPS encoding.
*     3) Any keywords matching PCiiijjj = FITS-PC encoding
*     4) Any keywords matching CDiiijjj = FITS-IRAF encoding
*     5) Any keywords matching CDi_j, AND at least one of RADECSYS, PROJPi
*        or CmVALi = FITS-IRAF encoding
*     6) Any keywords RADECSYS, PROJPi or CmVALi = FITS-PC encoding
*     7) Any keywords matching CROTAi = FITS-AIPS encoding
*     8) Keywords matching CRVALi = FITS-WCS encoding
*     9) The PLTRAH keyword = DSS encoding
*     10) If none of the above keywords are found, Native encoding is assumed.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     The encoding scheme identifier.

*  Notes:
*     -  The function returns UNKNOWN_ENCODING if an error has already occurred
*     or if an error occurs for any reason within this function.
*/

/* Local Variables... */
   int ret;            /* Returned value */
   int icard;          /* Index of current card on entry */

/* Check the global status. */
   if( !astOK ) return UNKNOWN_ENCODING;

/* If a value has been supplied for the Encoding attribute, use it. */
   if( astTestEncoding( this ) ) {
      ret = this->encoding;

/* Otherwise, check for the existence of certain critcal keywords... */
   } else {

/* Save the current card index, and rewind the FitsChan. */
      icard = astGetCard( this );
      astClearCard( this );

/* If the FitsChan contains any keywords starting with "BEGAST", then return
   "Native" encoding. */
      if( astKeyFields( this, "BEGAST%2f", 0, NULL, NULL ) ){
         ret = NATIVE_ENCODING;

/* Otherwise, if the FitsChan contains any CTYPE keywords which have the
   peculiar form used by AIPS, then use "FITS-AIPS" or "FITS-AIPS++" encoding. */
      } else if( HasAIPSSpecAxis( this, "astGetEncoding", "AstFitsChan" ) ){
         if( astKeyFields( this, "CD%1d_%1d", 0, NULL, NULL ) ||
             astKeyFields( this, "PROJP%d", 0, NULL, NULL ) ||
             astKeyFields( this, "LONPOLE", 0, NULL, NULL ) ||
             astKeyFields( this, "LATPOLE", 0, NULL, NULL ) ) {
            ret = FITSAIPSPP_ENCODING;
         } else {
            ret = FITSAIPS_ENCODING;
         }

/* Otherwise, if the FitsChan contains any keywords with the format 
   "PCiiijjj" then return "FITS-PC" encoding. */
      } else if( astKeyFields( this, "PC%3d%3d", 0, NULL, NULL ) ){
         ret = FITSPC_ENCODING;

/* Otherwise, if the FitsChan contains any keywords with the format 
   "CDiiijjj" then return "FITS-IRAF" encoding. */
      } else if( astKeyFields( this, "CD%3d%3d", 0, NULL, NULL ) ){
         ret = FITSIRAF_ENCODING;

/* Otherwise, if the FitsChan contains any keywords with the format 
   "CDi_j"  AND there is a RADECSYS. PROJPi or CmVALi keyword, then return 
   "FITS-IRAF" encoding. If "CDi_j" is present but none of the others
   are, return "FITS-WCS" encoding. */
      } else if( astKeyFields( this, "CD%1d_%1d", 0, NULL, NULL ) ) {

         if( (  astKeyFields( this, "RADECSYS", 0, NULL, NULL ) &&
               !astKeyFields( this, "RADESYS", 0, NULL, NULL ) ) ||

             ( astKeyFields( this, "PROJP%d", 0, NULL, NULL ) &&
              !astKeyFields( this, "PV%d_%d", 0, NULL, NULL ) ) ||

             ( astKeyFields( this, "C%1dVAL%d", 0, NULL, NULL )) ){
            ret = FITSIRAF_ENCODING;

         } else {
            ret = FITSWCS_ENCODING;
         }

/* Otherwise, if the FitsChan contains any keywords with the format 
   RADECSYS. PROJPi or CmVALi keyword, then return "FITS-PC" encoding. */
      } else if( ( astKeyFields( this, "RADECSYS", 0, NULL, NULL ) &&
                   !astKeyFields( this, "RADESYS", 0, NULL, NULL ) ) ||

                 ( astKeyFields( this, "PROJP%d", 0, NULL, NULL ) &&
                   !astKeyFields( this, "PV%d_%d", 0, NULL, NULL ) ) ||

                 astKeyFields( this, "C%1dVAL%d", 0, NULL, NULL ) ) {
         ret = FITSPC_ENCODING;

/* Otherwise, if the FitsChan contains any keywords with the format 
   "CROTAi" then return "FITS-AIPS" encoding. */
      } else if( astKeyFields( this, "CROTA%d", 0, NULL, NULL ) ){
         ret = FITSAIPS_ENCODING;

/* Otherwise, if the FitsChan contains any keywords with the format 
   "CRVALi" then return "FITS-WCS" encoding. */
      } else if( astKeyFields( this, "CRVAL%d", 0, NULL, NULL ) ){
         ret = FITSWCS_ENCODING;

/* Otherwise, if the FitsChan contains the "PLTRAH" keywords, use "DSS" 
   encoding. */
      } else if( astKeyFields( this, "PLTRAH", 0, NULL, NULL ) ){
         ret = DSS_ENCODING;

/* If none of these conditions is met, assume Native encoding. */
      } else {
         ret = NATIVE_ENCODING;
      }

/* Reinstate the original current card index. */
      astSetCard( this, icard );

   }

/* Return  the encoding scheme. */
   return astOK ? ret : UNKNOWN_ENCODING;
}

static void GetFiducialNSC( AstWcsMap *map, double *phi, double *theta ){
/*
*  Name:
*     GetFiducialNSC

*  Purpose:
*     Return the Native Spherical Coordinates at the fiducial point of a 
*     WcsMap projection.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void GetFiducialNSC( AstWcsMap *map, double *phi, double *theta )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function returns the native spherical coords corresponding at
*     the fiducial point of a WcsMap. 
*
*     The values of parameters 1 and 2 on the longitude axis of the WcsMap 
*     are usually used as the native spherical coordinates of the 
*     fiducial point. The default values for these parameters are equal
*     to the native spherical coordinates of the projection reference point.
*     The exception is that a TPN projection always uses the default
*     values, since the projection parameters are used to store polynomial
*     coefficients.

*  Parameters:
*     map
*        Pointer to the WcsMap.
*     phi
*        Address of a location at which to return the native spherical
*        longitude at the fiducial point (radians).
*     theta
*        Address of a location at which to return the native spherical
*        latitude at the fiducial point (radians).

*/

/* Local Variables: */
   int axlon;                /* Index of longitude axis */

/* Initialise */
   *phi = AST__BAD;
   *theta = AST__BAD;

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

/* If this is not a TPN projection get he value of the required
   projection parameters (the default values for these are equal to the
   fixed native shperical coordinates at the projection reference point). */
   if( astGetWcsType( map ) != AST__TPN ) {
      axlon = astGetWcsAxis( map, 0 );
      *phi = AST__DD2R*astGetPV( map, axlon, 1 );
      *theta = AST__DD2R*astGetPV( map, axlon, 2 );

/* If this is a TPN projection, the returned values are always the fixed 
   native shperical coordinates at the projection reference point). */
   } else {
      *phi = astGetNatLon( map );
      *theta = astGetNatLat( map );
   }
}

static void GetFiducialPPC( AstWcsMap *map, double *x0, double *y0 ){
/*
*  Name:
*     GetFiducialPPC

*  Purpose:
*     Return the IWC at the fiducial point of a WcsMap projection.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void GetFiducialPPC( AstWcsMap *map, double *x0, double *y0 )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function returns the projection plane coords corresponding to 
*     the native spherical coords of the fiducial point of a FITS-WCS
*     header. Note, projection plane coordinates (PPC) are equal to
*     Intermediate World Coordinates (IWC) except for cases where the
*     fiducial point does not correspond to the projection reference point.
*     In these cases, IWC and PPC will be connected by a translation
*     which ensures that the fiducial point corresponds to the origin of
*     IWC.
*
*     The values of parameters 1 and 2 on the longitude axis of 
*     the WcsMap are used as the native spherical coordinates of the 
*     fiducial point. The default values for these parameters are equal
*     to the native spherical coordinates of the projection reference point.

*  Parameters:
*     map
*        Pointer to the WcsMap.
*     x0
*        Address of a location at which to return the PPC X axis value at
*        the fiducial point (radians).
*     y0
*        Address of a location at which to return the PPC Y axis value at
*        the fiducial point (radians).

*/

/* Local Variables: */
   AstPointSet *pset1;       /* Pointer to the native spherical PointSet */
   AstPointSet *pset2;       /* Pointer to the intermediate world PointSet */
   double **ptr1;            /* Pointer to pset1 data */
   double **ptr2;            /* Pointer to pset2 data */
   int axlat;                /* Index of latitude axis */
   int axlon;                /* Index of longitude axis */
   int i;                    /* Loop count */
   int naxes;                /* Number of axes */

/* Initialise */
   *x0 = AST__BAD;
   *y0 = AST__BAD;

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

/* Save number of axes in the WcsMap. */
   naxes = astGetNin( map );

/* Allocate resources. */
   pset1 = astPointSet( 1, naxes, "" );
   ptr1 = astGetPoints( pset1 );
   
   pset2 = astPointSet( 1, naxes, "" );
   ptr2 = astGetPoints( pset2 );

/* Check pointers can be used safely. */
   if( astOK ) {

/* Get the indices of the longitude and latitude axes in WcsMap. */
      axlon = astGetWcsAxis( map, 0 );
      axlat = astGetWcsAxis( map, 1 );

/* Use zero on all non-celestial axes. */
      for( i = 0; i < naxes; i++ ) ptr1[ i ][ 0 ] = 0.0;

/* Get the native spherical coords at the fiducial point. */
      GetFiducialNSC( map, ptr1[ axlon ], ptr1[ axlat ] );

/* Use the inverse WcsMap to convert the native longitude and latitude of 
   the fiducial point into PPC (x,y). */
      astTransform( map, pset1, 0, pset2 );

/* Return the calculated PPC coords. */
      *x0 = ptr2[ axlon ][ 0 ];
      *y0 = ptr2[ axlat ][ 0 ];
   }

/* Free resources. */
   pset1 = astAnnul( pset1 );
   pset2 = astAnnul( pset2 );
}

static int GetFiducialWCS( AstWcsMap *wcsmap, AstMapping *map2, int colon,  
                           int colat, double *fidlon, double *fidlat ){
/*
*  Name:
*     GetFiducialWCS

*  Purpose:
*     Decide on the celestial coordinates of the fiducial point.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int GetFiducialWCS( AstWcsMap wcsmap, AstMapping map2, int colon,  
*                         int colat, double *fidlon, double *fidlat )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function returns the celestial longitude and latitude values
*     to use for the fiducial point. These are the values stored in FITS 
*     keywords CRVALi.

*  Parameters:
*     wcsmap
*        The WcsMap which converts Projection Plane Coordinates into 
*        native spherical coordinates. The number of outputs from this
*        Mapping should match the number of inputs to "map2".
*     map2
*        The Mapping which converts native spherical coordinates into WCS
*        coordinates. This Mapping should have the same number of inputs
*        as outputs.
*     colon
*        The index of the celestial longitude output from "map2".
*     colat
*        The index of the celestial latitude output from "map2".
*     fidlon
*        Pointer to a location at which to return the celestial longitude 
*        value at the fiducial point. The value is returned in radians.
*     fidlat
*        Pointer to a location at which to return the celestial latitude 
*        value at the fiducial point. The value is returned in radians.

*  Returned Value:
*     Zero if the fiducial point longitude or latitude could not be
*     determined. One otherwise.

*/

/* Local Variables: */
   AstPointSet *pset1;       /* Pointer to the native spherical PointSet */
   AstPointSet *pset2;       /* Pointer to the WCS PointSet */
   double **ptr1;            /* Pointer to pset1 data */
   double **ptr2;            /* Pointer to pset2 data */
   int axlat;                /* Index of latitude axis */
   int axlon;                /* Index of longitude axis */
   int iax;                  /* Axis index */
   int nax;                  /* Number of axes */
   int ret;                  /* The returned FrameSet */

/* Initialise */
   ret = 0;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Allocate resources. */
   nax = astGetNin( map2 );
   pset1 = astPointSet( 1, nax, "" );
   ptr1 = astGetPoints( pset1 );
   pset2 = astPointSet( 1, nax, "" );
   ptr2 = astGetPoints( pset2 );
   if( astOK ) {

/* Get the indices of the latitude and longitude outputs in the WcsMap.
   These are not necessarily the same as "colat" and "colon" because "map2"
   may contain a PermMap. */
      axlon = astGetWcsAxis( wcsmap, 0 );
      axlat = astGetWcsAxis( wcsmap, 1 );

/* Use zero on all non-celestial axes. */
      nax = astGetNout( wcsmap );
      for( iax = 0; iax < nax; iax++ ) ptr1[ iax ][ 0 ] = 0.0;

/* Get the native spherical coords at the fiducial point. */
      GetFiducialNSC( wcsmap, ptr1[ axlon ], ptr1[ axlat ] );

/* The fiducial point in the celestial coordinate system is found by
   transforming the fiducial point in native spherical co-ordinates
   into absolute physical coordinates using map2. */
      astTransform( map2, pset1, 1, pset2 );

/* Store the returned WCS values. */
      *fidlon = ptr2[ colon ][ 0 ];      
      *fidlat = ptr2[ colat ][ 0 ];      

/* Indicate if we have been succesfull. */
      if( astOK && *fidlon != AST__BAD && *fidlat != AST__BAD ) ret = 1;

   }

/* Free resources. */
   pset1 = astAnnul( pset1 );
   pset2 = astAnnul( pset2 );

/* Return the result. */
   return ret;

}

static double GetItem( double ****item, int i, int jm, char s, char *name,
                       const char *method, const char *class ){
/*
*  Name:
*     GetItem

*  Purpose:
*     Retrieve a value for a axis keyword value from a FitStore structure.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     double GetItem( double ****item, int i, int jm, char s, char *name,
*                     const char *method, const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The requested keyword value is retrieved from the specified array,
*     at a position indicated by the axis and co-ordinate version.
*     AST__BAD is returned if the array does not contain the requested
*     value. 

*  Parameters:
*     item
*        The address of the pointer within the FitsStore which locates the 
*        arrays of values for the required keyword (eg &(store->crval) ).
*        The array located by the supplied pointer contains a vector of
*        pointers. Each of these pointers is associated with a particular
*        co-ordinate version (s), and locates an array of pointers for that 
*        co-ordinate version. Each such array of pointers has an element
*        for each intermediate axis number (i), and the pointer locates an
*        array of axis keyword values. These arrays of keyword values have 
*        one element for every pixel axis (j) or projection parameter (m). 
*     i
*        The zero based intermediate axis index in the range 0 to 98. Set 
*        this to zero for keywords (e.g. CRPIX) which are not indexed by 
*        intermediate axis number.
*     jm
*        The zero based pixel axis index (in the range 0 to 98) or parameter 
*        index (in the range 0 to WCSLIB_MXPAR-1). Set this to zero for 
*        keywords (e.g. CRVAL) which are not indexed by either pixel axis or 
*        parameter number.
*     s
*        The co-ordinate version character (A to Z, or space), case
*        insensitive
*     name 
*        A string holding a name for the item of information. A NULL
*        pointer may be supplied, in which case it is ignored. If a
*        non-NULL pointer is supplied, an error is reported if the item
*        of information has not been stored, and the supplied name is
*        used to identify the information within the error message.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.


*  Returned Value:
*     The required keyword value, or AST__BAD if no value has previously
*     been stored for the keyword (or if an error has occurred).

*/

/* Local Variables: */
   double ret;           /* Returned keyword value */
   int si;               /* Integer co-ordinate version index */

/* Initialise */
   ret = AST__BAD;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Convert the character co-ordinate version into an integer index, and
   check it is within range. The primary axis description (s=' ') is
   given index zero. 'A' is 1, 'B' is 2, etc. */
   if( s == ' ' ) {
      si = 0;
   } else if( islower(s) ){
      si = (int) ( s - 'a' ) + 1;
   } else {
      si = (int) ( s - 'A' ) + 1;
   }

   if( si < 0 || si > 26 ) {
      astError( AST__INTER, "GetItem(fitschan): AST internal error; "
                "co-ordinate version '%c' ( char(%d) ) is invalid.", s, s );

/* Check the intermediate axis index is within range. */
   } else if( i < 0 || i > 98 ) {
      astError( AST__INTER, "GetItem(fitschan): AST internal error; "
                "intermediate axis index %d is invalid.", i );

/* Check the pixel axis or parameter index is within range. */
   } else if( jm < 0 || jm > 99 ) {
      astError( AST__INTER, "GetItem(fitschan): AST internal error; "
                "pixel axis or parameter index %d is invalid.", jm );

/* Otherwise, if the array holding the required keyword is not null, 
   proceed... */
   } else if( *item ){

/* Find the number of coordinate versions in the supplied array.
   Only proceed if it encompasses the requested co-ordinate
   version. */
      if( astSizeOf( (void *) *item )/sizeof(double **) > si ){

/* Find the number of intermediate axes in the supplied array.
   Only proceed if it encompasses the requested intermediate axis. */
         if( astSizeOf( (void *) (*item)[si] )/sizeof(double *) > i ){

/* Find the number of pixel axes or parameters in the supplied array.
   Only proceed if it encompasses the requested index. */
            if( astSizeOf( (void *) (*item)[si][i] )/sizeof(double) > jm ){

/* Return the required keyword value. */
               ret = (*item)[si][i][jm];
            }
         }
      }
   }

/* If required, report an error if the requested item of information has
   not been stored. */
   if( ret == AST__BAD && name && astOK ){
      astError( AST__NOFTS, "%s(%s): No value can be found for %s.",
                method, class, name );
   }

   return ret;
}

static int GetMaxJM( double ****item, char s ){
/*
*  Name:
*     GetMaxJM

*  Purpose:
*     Return the largest pixel axis or parameter index stored for an axis 
*     keyword value in a FitStore structure.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int GetMaxJM( double ****item, char s )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The number of pixel axis numbers or projection parameters stored for
*     a specified axis keyword is found and returned.

*  Parameters:
*     item
*        The address of the pointer within the FitsStore which locates the 
*        arrays of values for the required keyword (eg &(store->crpix) ).
*        The array located by the supplied pointer contains a vector of
*        pointers. Each of these pointers is associated with a particular
*        co-ordinate version (s), and locates an array of pointers for that 
*        co-ordinate version. Each such array of pointers has an element
*        for each intermediate axis number (i), and the pointer locates an
*        array of axis keyword values. These arrays of keyword values have 
*        one element for every pixel axis (j) or projection parameter (m). 
*     s
*        The co-ordinate version character (A to Z, or space), case
*        insensitive

*  Returned Value:
*     The maximum pixel axis number or projection parameter index (zero 
*     based).

*/

/* Local Variables: */
   int jm;               /* Number of parameters/pixel axes */
   int i;                /* Intermediate axis index */
   int ret;              /* Returned axis index */
   int si;               /* Integer co-ordinate version index */

/* Initialise */
   ret = -1;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* If the array holding the required keyword is not null, proceed... */
   if( *item ){

/* Convert the character co-ordinate version into an integer index, and
   check it is within range. The primary axis description (s=' ') is
   given index zero. 'A' is 1, 'B' is 2, etc. */
      if( s == ' ' ) {
         si = 0;
      } else if( islower(s) ){
         si = (int) ( s - 'a' ) + 1;
      } else {
         si = (int) ( s - 'A' ) + 1;
      }

      if( si < 0 || si > 26 ) {
         astError( AST__INTER, "GetMaxJM(fitschan): AST internal error; "
                   "co-ordinate version '%c' ( char(%d) ) is invalid.", s, s );
         return ret;
      }

/* Find the number of coordinate versions in the supplied array.
   Only proceed if it encompasses the requested co-ordinate
   version. */
      if( astSizeOf( (void *) *item )/sizeof(double **) > si ){

/* Check that the pointer to the array of intermediate axis values is not null. */
         if( (*item)[si] ){

/* Loop round each used element in this array. */
            for( i = 0; i < astSizeOf( (void *) (*item)[si] )/sizeof(double *);
                 i++ ){
               if( (*item)[si][i] ){

/* Get the size of the pixel axis/projection parameter array for the
   current intermediate axis, and subtract 1 to get the largest index. */
                  jm = astSizeOf( (void *) (*item)[si][i] )/sizeof(double) - 1;

/* Ignore any trailing unused (AST__BAD) values. */
                  while( jm >= 0 && (*item)[si][i][jm] == AST__BAD ) jm--;

/* Update the returned value if the current value is larger. */
                  if( jm > ret ) ret = jm;

               }
            }
         }
      }
   }

   return ret;

}

static int GetMaxI( double ****item, char s ){
/*
*  Name:
*     GetMaxI

*  Purpose:
*     Return the largest WCS axis index stored for an axis keyword value in 
*     a FitStore structure.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int GetMaxJM( double ****item, char s )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The number of Wcs axis numbers stored for a specified axis keyword is 
*     found and returned.

*  Parameters:
*     item
*        The address of the pointer within the FitsStore which locates the 
*        arrays of values for the required keyword (eg &(store->crval) ).
*        The array located by the supplied pointer contains a vector of
*        pointers. Each of these pointers is associated with a particular
*        co-ordinate version (s), and locates an array of pointers for that 
*        co-ordinate version. Each such array of pointers has an element
*        for each intermediate axis number (i), and the pointer locates an
*        array of axis keyword values. These arrays of keyword values have 
*        one element for every pixel axis (j) or projection parameter (m). 
*     s
*        The co-ordinate version character (A to Z, or space), case
*        insensitive

*  Returned Value:
*     The maximum WCS axis index (zero based).

*/

/* Local Variables: */
   int ret;              /* Returned axis index */
   int si;               /* Integer co-ordinate version index */

/* Initialise */
   ret = -1;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* If the array holding the required keyword is not null, proceed... */
   if( *item ){

/* Convert the character co-ordinate version into an integer index, and
   check it is within range. The primary axis description (s=' ') is
   given index zero. 'A' is 1, 'B' is 2, etc. */
      if( s == ' ' ) {
         si = 0;
      } else if( islower(s) ){
         si = (int) ( s - 'a' ) + 1;
      } else {
         si = (int) ( s - 'A' ) + 1;
      }

      if( si < 0 || si > 26 ) {
         astError( AST__INTER, "GetMaxI(fitschan): AST internal error; "
                   "co-ordinate version '%c' ( char(%d) ) is invalid.", s, s );
         return ret;
      }

/* Find the number of coordinate versions in the supplied array.
   Only proceed if it encompasses the requested co-ordinate
   version. */
      if( astSizeOf( (void *) *item )/sizeof(double **) > si ){

/* Check that the pointer to the array of intermediate axis values is not null. */
         if( (*item)[si] ){

/* Get the size of the intermediate axis array and subtract 1 to get the largest 
   index. */
            ret = astSizeOf( (void *) (*item)[si] )/sizeof(double *) - 1;

/* Ignore any trailing unused (NULL) values. */
            while( ret >= 0 && (*item)[si][ret] == NULL ) ret--;
         }
      }
   }

   return ret;

}

static char GetMaxS( double ****item ){
/*
*  Name:
*     GetMaxS

*  Purpose:
*     Return the largest (i.e. closest to Z) coordinate version character 
*     stored for a axis keyword value in a FitStore structure.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     char GetMaxS( double ****item )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The largest (i.e. closest to Z) coordinate version character 
*     stored for a axis keyword value in a FitStore structure is found
*     and returned.

*  Parameters:
*     item
*        The address of the pointer within the FitsStore which locates the 
*        arrays of values for the required keyword (eg &(store->crval) ).
*        The array located by the supplied pointer contains a vector of
*        pointers. Each of these pointers is associated with a particular
*        co-ordinate version (s), and locates an array of pointers for that 
*        co-ordinate version. Each such array of pointers has an element
*        for each intermediate axis number (i), and the pointer locates an
*        array of axis keyword values. These arrays of keyword values have 
*        one element for every pixel axis (j) or projection parameter (m). 

*  Returned Value:
*     The highest coordinate version character.

*/

/* Local Variables: */
   char ret;              /* Returned axis index */
   int si;                /* Integer index into alphabet */

/* Initialise */
   ret = ' ';

/* Check the inherited status. */
   if( !astOK ) return ret;

/* If the array holding the required keyword is not null, proceed... */
   if( *item ){

/* Find the length of this array, and subtract 1 to get the largest index
   in the array. */
      si = astSizeOf( (void *) *item )/sizeof(double **) - 1;

/* Ignore any trailing null (i.e. unused) values. */
      while( si >= 0 && !(*item)[si] ) si--;

/* Store the corresponding character */
      if( si == 0 ) {
         ret = ' ';
      } else {
         ret = 'A' + si - 1;
      }
   }

   return ret;

}

static char *GetItemC( char ****item, int i, char s, char *name,
                       const char *method, const char *class  ){
/*
*  Name:
*     GetItemC

*  Purpose:
*     Retrieve a string value for a axis keyword value from a FitStore 
*     structure.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     char *GetItemC( char ****item, int i, char s, char *name,
*                     const char *method, const char *class  )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The requested keyword string value is retrieved from the specified 
*     array, at a position indicated by the axis and co-ordinate version.
*     NULL is returned if the array does not contain the requested
*     value. 

*  Parameters:
*     item
*        The address of the pointer within the FitsStore which locates the 
*        arrays of values for the required keyword (eg &(store->crval) ).
*        The array located by the supplied pointer contains a vector of
*        pointers. Each of these pointers is associated with a particular
*        co-ordinate version (s), and locates an array of pointers for that 
*        co-ordinate version. Each such array of pointers has an element
*        for each intermediate axis number (i), and the pointer locates a
*        character string. 
*     i
*        The zero based intermediate axis index in the range 0 to 98. Set 
*        this to zero for keywords (e.g. CRPIX) which are not indexed by 
*        intermediate axis number.
*     s
*        The co-ordinate version character (A to Z, or space), case
*        insensitive
*     name 
*        A string holding a name for the item of information. A NULL
*        pointer may be supplied, in which case it is ignored. If a
*        non-NULL pointer is supplied, an error is reported if the item
*        of information has not been stored, and the supplied name is
*        used to identify the information within the error message.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     A pointer to the required keyword string value, or NULL if no value 
*     has previously been stored for the keyword (or if an error has 
*     occurred).

*/

/* Local Variables: */
   char *ret;            /* Returned keyword value */
   int si;               /* Integer co-ordinate version index */

/* Initialise */
   ret = NULL;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Convert the character co-ordinate version into an integer index, and
   check it is within range. The primary axis description (s=' ') is
   given index zero. 'A' is 1, 'B' is 2, etc. */
   if( s == ' ' ) {
      si = 0;
   } else if( islower(s) ){
      si = (int) ( s - 'a' ) + 1;
   } else {
      si = (int) ( s - 'A' ) + 1;
   }

   if( si < 0 || si > 26 ) {
      astError( AST__INTER, "GetItemC(fitschan): AST internal error; "
                "co-ordinate version '%c' ( char(%d) ) is invalid.", s, s );

/* Check the intermediate axis index is within range. */
   } else if( i < 0 || i > 98 ) {
      astError( AST__INTER, "GetItemC(fitschan): AST internal error; "
                "intermediate axis index %d is invalid.", i );

/* Otherwise, if the array holding the required keyword is not null, 
   proceed... */
   } else if( *item ){

/* Find the number of coordinate versions in the supplied array.
   Only proceed if it encompasses the requested co-ordinate
   version. */
      if( astSizeOf( (void *) *item )/sizeof(char **) > si ){

/* Find the number of intermediate axes in the supplied array.
   Only proceed if it encompasses the requested intermediate axis. */
         if( astSizeOf( (void *) (*item)[si] )/sizeof(char *) > i ){

/* Return the required keyword value. */
            ret = (*item)[si][i];
         }
      }
   }

/* If required, report an error if the requested item of information has
   not been stored. */
   if( !ret && name && astOK ){
      astError( AST__NOFTS, "%s(%s): No value can be found for %s.",
                method, class, name );
   }

   return ret;

}

static int GoodWarns( const char *value ){
/*
*  Name:
*     GoodWarns

*  Purpose:
*     Checks a string to ensure it is a legal list of warning conditions.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int GoodWarns( const char *value )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function checks the supplied string to ensure it contains a space
*     separated list of zero or more recognised warning conditions. An
*     error is reported if it does not.

*  Parameters:
*     value
*        The string to check.

*  Returned Value:
*     Zero is returned if the supplied string is not a legal list of
*     conditions, or if an error has already occurred. One is returned 
*     otherwise.

*/

/* Local Variables: */
   char *b;              /* Pointer to next buffer element */
   const char *c  ;      /* Pointer to next character */
   char buf[100];        /* Buffer for condition name */
   int inword;           /* Are we in a word? */
   int n;                /* Number of conditions supplied */
   int ret;              /* Returned value */

/* Initialise */
   ret = 0;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Report an error and return if the pointer is null. */
   if( !value ){
      astError( AST__ATTIN, "astSetWarnings(fitschan): Null pointer "
                "supplied for the Warnings attribute." );
      return ret;
   }

/* Initialise things */
   inword = 0;
   buf[ 0 ] = ' ';
   b = buf + 1;
   n = 0;
   ret = 1;

/* Loop round each character in the supplied string. */
   for( c = value ; c < value + strlen( value ) + 1; c++ ){

/* Have we found the first space or null following a word? */
      if( ( !(*c) || isspace( *c ) ) && inword ){

/* Add a space to the end of the buffer and terminate it. */
         *(b++) = ' ';
         *b = 0;

/* Check the word is legal by searching for it in the string of all
   conditions, which should be lower case and have spaces at start and end. 
   The word in the buffer is delimited by spaces and so it will not match 
   a substring within a condition. If it is legal increment the number of 
   conditions found. */
         if( strstr( ALLWARNINGS, buf ) ){         
            n++;

/* Otherwise, report an error and break. */
         } else {
            ret = 0;
            *(--b) = 0;
            astError( AST__ATTIN, "astSetWarnings(fitschan): Unknown "
                      "condition '%s' specified when setting the Warnings "
                      "attribute.", buf + 1 );
            break;
         }

/* Reset the pointer to the next character in the buffer, retaining the
   initial space in the buffer. */
         b = buf + 1;

/* Indicate we are no longer in a word. */
         inword = 0;

/* Have we found the first non-space, non-null character following a space? */
      } else if( *c && !isspace( *c ) && !inword ){

/* Note we are now in a word. */
         inword = 1;
         
      }

/* If we are in a word, copy the lowercase character to the buffer. */
      if( inword ) *(b++) = tolower( *c );
      
   }

   return ret;

}

static AstMapping *GrismSpecWcs( char *algcode, FitsStore *store, int i, 
                                 char s, AstSpecFrame *specfrm, 
                                 const char *method, const char *class   ) {
/*
*  Name:
*     GrismSpecWcs

*  Purpose:
*     Create a Mapping describing a FITS-WCS grism-dispersion algorithm

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     AstMapping *GrismSpecWcs( char *algcode, FitsStore *store, int i, char s, 
*                               AstSpecFrame *specfrm, const char *method, 
*                               const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function uses the contents of the supplied FitsStore to create
*     a Mapping which goes from Intermediate World Coordinate (known as "w" 
*     in the context of FITS-WCS paper III) to the spectral system
*     described by the supplied SpecFrame.
*
*     The returned Mapping implements the grism "GRA" and "GRI" algorithms
*     described in FITS-WCS paper III. 

*  Parameters:
*     algcode
*        Pointer to a string holding the code for the required algorithm
*        ("-GRA" or "-GRI").
*     store
*        Pointer to the FitsStore structure holding the values to use for 
*        the WCS keywords. 
*     i 
*        The zero-based index of the spectral axis within the FITS header
*     s
*        A character identifying the co-ordinate version to use. A space 
*        means use primary axis descriptions. Otherwise, it must be an 
*        upper-case alphabetical characters ('A' to 'Z').
*     specfrm
*        Pointer to the SpecFrame. This specifies the "S" system - the
*        system in which the CRVAL kewyords (etc) are specified.
*     method
*        A pointer to a string holding the name of the calling method.
*        This is used only in the construction of error messages.
*     class
*        A pointer to a string holding the class of the object being
*        read. This is used only in the construction of error messages.

*  Returned Value:
*     A pointer to a Mapping, or NULL if an error occurs.

*/

/* Local Variables: */
   AstFrameSet *fs;
   AstMapping *gmap;
   AstMapping *map1;
   AstMapping *map2;
   AstMapping *map2a;
   AstMapping *map2b;
   AstMapping *ret;
   AstMapping *smap;
   AstSpecFrame *wfrm;
   double crv;
   double dg;
   double gcrv;
   double pv;
   double wcrv;

/* Check the global status. */
   ret = NULL;
   if( !astOK ) return ret;

/* The returned Mapping will be a CmpMap including a GrismMap. This
   GrismMap will produced wavelength as output. We also need the Mapping 
   from wavelength to the system represented by the supplied SpecFrame. 
   To get this, we first create a copy of the supplied SpecFrame (in order 
   to inherit the standard of rest, epoch, etc), and set its System to
   wavlength in vacuum (for "-GRI") or air (for "-GRA"), and then use
   astConvert to get the Mapping from the SpecFrame system to relevant
   form of wavelength. */
   wfrm = astCopy( specfrm );
   astSetSystem( wfrm, strcmp( algcode, "-GRI" )?AST__AIRWAVE:AST__WAVELEN );
   astSetUnit( wfrm, 0, "m" );

   fs = astConvert( specfrm, wfrm, "" );
   if( fs ) {
      smap = astGetMapping( fs, AST__BASE, AST__CURRENT );
      fs = astAnnul( fs );

/* Get the CRVAL value for the spectral axis (this will be in the S system). */
      crv = GetItem( &(store->crval), i, 0, s, NULL, method, class );
      if( crv == AST__BAD ) crv = 0.0;

/* Convert it to the wavelength system (vacuum or air) in metres. */
      astTran1( smap, 1, &crv, 1, &wcrv );

/* Create a GrismMap, and then use the projection parameters stored in
   the FitsStore to set its attributes (convert degrees values to radians
   and supply the defaults specified in FITS-WCS paper III). The FITS
   paper specifies units in which these parameters should be stored in a
   FITS header - distances are in metres and angles in degrees. */
      gmap = (AstMapping *) astGrismMap( "" );

      pv = GetItem( &(store->pv), i, 0, s, NULL, method, class );
      astSetGrismG( gmap, ( pv != AST__BAD )?pv:0.0 );
   
      pv = GetItem( &(store->pv), i, 1, s, NULL, method, class );
      astSetGrismM( gmap, ( pv != AST__BAD )?(int) ( pv + 0.5 ):0);
   
      pv = GetItem( &(store->pv), i, 2, s, NULL, method, class );
      astSetGrismAlpha( gmap, ( pv != AST__BAD )?pv*AST__DD2R:0.0 );
   
      pv = GetItem( &(store->pv), i, 3, s, NULL, method, class );
      astSetGrismNR( gmap, ( pv != AST__BAD )?pv:1.0 );
   
      pv = GetItem( &(store->pv), i, 4, s, NULL, method, class );
      astSetGrismNRP( gmap, ( pv != AST__BAD )?pv:0.0 );
   
      pv = GetItem( &(store->pv), i, 5, s, NULL, method, class );
      astSetGrismEps( gmap, ( pv != AST__BAD )?pv*AST__DD2R:0.0 );
   
      pv = GetItem( &(store->pv), i, 6, s, NULL, method, class );
      astSetGrismTheta( gmap, ( pv != AST__BAD )?pv*AST__DD2R:0.0 );

/* Store the reference wavelength found above as an attribute of the
   GrismMap. */   
      astSetGrismWaveR( gmap, wcrv );

/* Invert the GrismMap to get the (Wavelength -> grism parameter) Mapping, and 
   then combine it with the (S -> Wavelength) Mapping to get the (S -> grism
   parameter) Mapping. */
      astInvert( gmap );
      map1 = (AstMapping *) astCmpMap( smap, gmap, 1, "" );
      
/* Convert the reference point value from wavelength to grism parameter. */
      astTran1( gmap, 1, &wcrv, 1, &gcrv );

/* Find the rate of change of grism parameter with respect to the S
   system at the reference point, dg/dS. */
      dg = astRate( map1, &crv, 0, 0, NULL );
      if( dg != AST__BAD && dg != 0.0 ) {

/* FITS-WCS paper II requires headers to be constructed so that dS/dw = 1.0 
   at the reference point. Therefore dg/dw = dg/dS. Create a WinMap which
   scales and shifts the "w" value to get the grism parameter value. */
         map2a = (AstMapping *) astZoomMap( 1, dg, "" );
         map2b = (AstMapping *) astShiftMap( 1, &gcrv, "" );
         map2 = (AstMapping *) astCmpMap( map2a, map2b, 1, "" );
         map2a = astAnnul( map2a );
         map2b = astAnnul( map2b );
         
/* The Mapping to be returned is the concatenation of the above Mapping
   (from w to g) with the Mapping from g to S. */
         astInvert( map1 );   
         ret = (AstMapping *) astCmpMap( map2, map1, 1, "" );
         map2 = astAnnul( map2 );

      }

      map1 = astAnnul( map1 );
      smap = astAnnul( smap );
      gmap = astAnnul( gmap );
   } 
   wfrm = astAnnul( wfrm );


/* Return the result */
   return ret;
}

static int KeyFields( AstFitsChan *this, const char *filter, int maxfld, 
                    int *ubnd, int *lbnd ){
/*
*+
*  Name:
*     astKeyFields

*  Purpose:
*     Find the ranges taken by integer fields within the keyword names 
*     in a FitsChan.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "fitschan.h"
*     int astKeyFields( AstFitsChan *this, const char *filter, int maxfld, 
*                       int *ubnd, int *lbnd )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function returns the number of cards within a FitsChan which
*     refer to keywords which match the supplied filter template. If the
*     filter contains any integer field specifiers (e.g. "%d", "%3d", etc),
*     it also returns the upper and lower bounds found for the integer 
*     fields.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     filter
*        The filter string. 
*     maxfld
*        The size of the "ubnd" and "lbnd" arrays.
*     ubnd
*        A pointer to an integer array in which to return the
*        upper bound found for each integer field in the filter.
*        They are stored in the order in which they occur in the filter.
*        If the filter contains too many fields to fit in the supplied
*        array, the excess trailing fields are ignored.
*     lbnd
*        A pointer to an integer array in which to return the
*        lower bound found for each integer field in the filter.

*  Returned Value:
*     astKeyFields()
*        The total number of cards matching the supplied filter in the
*        FitsChan.

*  Filter Syntax:
*     -  The criteria for a keyword name to match a filter template are
*     as follows:
*     -  All characters in the template other than "%" (and the field width
*     and type specifiers which follow a "%") must be matched by an 
*     identical character in the test string. 
      -  If a "%" occurs in the template, then the next character in the 
*     template should be a single digit specifying a field width. If it is 
*     zero, then the test string may contain zero or more matching characters. 
*     Otherwise, the test string must contain exactly the specified number 
*     of matching characters (i.e. 1 to 9). The field width digit may be 
*     omitted, in which case the test string must contain one or more matching 
*     characters. The next character in the template specifies the type of 
*     matching characters and must be one of "d", "c" or "f". Decimal digits 
*     are matched by "d", all upper (but not lower) case alphabetical 
*     characters are matched by "c", and all characters which may legally be 
*     found within a FITS keyword name are matched by "f".

*  Examples:
*     -  The filter "CRVAL1" accepts the single keyword CRVAL1.
*     -  The filter "CRVAL%1d" accepts the single keyword CRVAL0, CRVAL1,
*     CRVAL2, up to CRVAL9.
*     -  The filter "CRVAL%d" accepts any keyword consisting of the string
*     "CRVAL" followed by any integer value.
*     -  The filter "CR%0s1" accepts any keyword starting with the string "CR"
*     and ending with the character "1" (including CR1).

*  Notes:
*     -  The entire FitsChan is searched, irrespective of the setting of
*     the Card attribute.
*     -  If "maxfld" is supplied as zero, "ubnd" and "lbnd" are ignored,
*     but the number of matching cards is still returned as the function value.
*     -  If no matching cards are found in the FitsChan, or if there are no 
*     integer fields in the filter, then the lower and upper bounds are 
*     returned as zero and -1 (i.e. reversed). 
*     -  If an error has already occured, or if this function should fail 
*     for any reason, a value of zero is returned for the function value,
*     and the lower and upper bounds are set to zero and -1.

*-
*/

/* Local Variables: */
   const char *class;     /* Object class */
   const char *method;    /* Method name */
   int *fields;           /* Pointer to array of field values */
   int i;                 /* Field index */
   int icard;             /* Index of current card on entry */
   int nmatch;            /* No. of matching cards */
   int nf;                /* No. of integer fields in the filter */
   int nfld;              /* No. of integer fields in current keyword name */

/* Initialise the returned values. */
   nmatch = 0;
   for( i = 0; i < maxfld; i++ ){
      lbnd[ i ] = 0;
      ubnd[ i ] = -1;
   }
   nf = 0;

/* Check the global error status. */
   if ( !astOK || !filter ) return nf;

/* Store the method name and object class for use in error messages. */
   method = "astKeyFields";
   class = astGetClass( this );

/* Count the number of integer fields in the filter string. */
   nf = CountFields( filter, 'd', method, class );

/* If this is larger than the supplied arrays, use the size of the arrays 
   instead. */
   if( nf > maxfld ) nf = maxfld;

/* Allocate memory to hold the integer field values extracted from 
   each matching keyword. */
   fields = (int *) astMalloc( sizeof( int )*(size_t) nf );

/* Save the current card index, and rewind the FitsChan. */
   icard = astGetCard( this );
   astClearCard( this );

/* Check that the FitsChan is not empty and the pointer can be used. */
   if( !astFitsEof( this ) && astOK ){

/* Initialise the returned bounds. Any excess elements in the array are left
   at the previously initialised values. */
      for( i = 0; i < nf; i++ ){
         lbnd[ i ] = INT_MAX;
         ubnd[ i ] = -INT_MAX;
      }

/* Initialise the number of matching keywords. */
      nmatch = 0;
      
/* Loop round all the cards in the FitsChan. */
      while( !astFitsEof( this ) && astOK ){

/* If the current keyword name matches the filter, update the returned
   bounds and increment the number of matches. */
         if( Match( CardName( this ), filter, nf, fields, &nfld, 
                    method, class ) ){

            for( i = 0; i < nf; i++ ){
               if( fields[ i ] > ubnd[ i ] ) ubnd[ i ] = fields[ i ];
               if( fields[ i ] < lbnd[ i ] ) lbnd[ i ] = fields[ i ];
            }

            nmatch++;
                    
         }
                    
/* Move on to the next card. */
         MoveCard( this, 1, method, class );
         
      }

/* If bounds were not found, returned 0 and -1. */
      for( i = 0; i < nf; i++ ){
         if( lbnd[ i ] == INT_MAX ){
            lbnd[ i ] = 0;
            ubnd[ i ] = -1;
         }
      }

   }

/* Reinstate the original current card index. */
   astSetCard( this, icard );

/* Free the memory used to hold the integer field values extracted from 
   each matching keyword. */
   fields = (int *) astFree( (void *) fields );

/* If an error has occurred, returned no matches and reversed bounds. */
   if( !astOK ){
      nmatch = 0;
      for( i = 0; i < maxfld; i++ ){
         lbnd[ i ] = 0;
         ubnd[ i ] = -1;
      }
   }

/* Returned the answer. */
   return nmatch;

}

static int FindFits( AstFitsChan *this, const char *name, 
                     char card[ FITSCARDLEN + 1 ], int inc ){
/*
*++
*  Name:
c     astFindFits
f     AST_FINDFITS

*  Purpose:
*     Find a FITS card in a FitsChan by keyword.

*  Type:
*     Public virtual function.

*  Synopsis:
c     #include "fitschan.h"
c     int astFindFits( AstFitsChan *this, const char *name, char card[ 81 ],
c                      int inc )
f     RESULT = AST_FINDFITS( THIS, NAME, CARD, INC, STATUS )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function searches for a card in a FitsChan by keyword. The
*     search commences at the current card (identified by the Card
*     attribute) and ends when a card is found whose FITS keyword
*     matches the template supplied, or when the last card in the
*     FitsChan has been searched.
*
*     If the search is successful (i.e. a card is found which matches
c     the template), the contents of the card are (optionally)
f     the template), the contents of the card are
*     returned and the Card attribute is adjusted to identify the card
*     found or, if required, the one following it. If the search is
c     not successful, the function returns zero and the Card attribute
f     not successful, the function returns .FALSE. and the Card attribute
*     is set to the "end-of-file".

*  Parameters:
c     this
f     THIS = INTEGER (Given)
*        Pointer to the FitsChan.
c     name
f     NAME = CHARACTER * ( * ) (Given)
c        Pointer to a null-terminated character string containing a
f        A character string containing a
*        template for the keyword to be found. In the simplest case,
*        this should simply be the keyword name (the search is case
*        insensitive and trailing spaces are ignored). However, this
*        template may also contain "field specifiers" which are
*        capable of matching a range of characters (see the "Keyword
*        Templates" section for details). In this case, the first card
*        with a keyword which matches the template will be found. To
*        find the next FITS card regardless of its keyword, you should
*        use the template "%f".
c     card
f     CARD = CHARACTER * ( 80 ) (Returned)
c        An array of at least 81 characters (to allow room for a
c        terminating null)
f        A character variable with at least 80 characters
*        in which the FITS card which is found will be returned.  If
c        the search is not successful (or a NULL pointer is given), a
f        the search is not successful, a
*        card will not be returned.
c     inc
f     INC = LOGICAL (Given)
c        If this value is zero (and the search is successful), the
f        If this value is .FALSE. (and the search is successful), the
*        FitsChan's Card attribute will be set to the index of the card
c        that was found. If it is non-zero, however, the Card
f        that was found. If it is .TRUE., however, the Card
*        attribute will be incremented to identify the card which
*        follows the one found.
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Returned Value:
c     astFindFits()
f     AST_FINDFITS = LOGICAL
c        One if the search was successful, otherwise zero.
f        .TRUE. if the search was successful, otherwise .FALSE..

*  Notes:
*     - The search always starts with the current card, as identified
*     by the Card attribute. To ensure you search the entire contents
*     of a FitsChan, you should first clear the Card attribute (using
c     astClear). This effectively "rewinds" the FitsChan.
f     AST_CLEAR). This effectively "rewinds" the FitsChan.
*     - If a search is unsuccessful, the Card attribute is set to the
*     "end-of-file" (i.e. to one more than the number of cards in the
*     FitsChan). No error occurs.
c     - A value of zero will be returned if this function is invoked
f     - A value of .FALSE. will be returned if this function is invoked
*     with the AST error status set, or if it should fail for any
*     reason.

*  Examples:
c     result = astFindFits( fitschan, "%f", card, 1 );
f     RESULT = AST_FINDFITS( FITSCHAN, '%f', CARD, .TRUE., STATUS )
*        Returns the current card in a FitsChan and advances the Card
*        attribute to identify the card that follows (the "%f"
*        template matches any keyword).
c     result = astFindFits( fitschan, "BITPIX", card, 1 );
f     RESULT = AST_FINDFITS( FITSCHAN, 'BITPIX', CARD, .TRUE., STATUS )
*        Searches a FitsChan for a FITS card with the "BITPIX" keyword
*        and returns that card. The Card attribute is then incremented
*        to identify the card that follows it.
c     result = astFindFits( fitschan, "COMMENT", NULL, 0 );
f     RESULT = AST_FINDFITS( FITSCHAN, 'COMMENT', CARD, .FALSE., STATUS )
*        Sets the Card attribute of a FitsChan to identify the next
c        COMMENT card (if any). The card itself is not returned.
f        COMMENT card (if any) and returns that card.
c     result = astFindFits( fitschan, "CRVAL%1d", card, 1 );
f     RESULT = AST_FINDFITS( FITSCHAN, 'CRVAL%1d', CARD, .TRUE., STATUS )
*        Searches a FitsChan for the next card with a keyword of the
*        form "CRVALi" (for example, any of the keywords "CRVAL1",
*        "CRVAL2" or "CRVAL3" would be matched). The card found (if
*        any) is returned, and the Card attribute is then incremented
*        to identify the following card (ready to search for another
*        keyword with the same form, perhaps).

*  Keyword Templates:
*     The templates used to match FITS keywords are normally composed
*     of literal characters, which must match the keyword exactly
*     (apart from case). However, a template may also contain "field
*     specifiers" which can match a range of possible characters. This
*     allows you to search for keywords that contain (for example)
*     numbers, where the digits comprising the number are not known in
*     advance.
*
*     A field specifier starts with a "%" character. This is followed
*     by an optional single digit (0 to 9) specifying a field
*     width. Finally, there is a single character which specifies the
*     type of character to be matched, as follows:
*
*     - "c": matches all upper case letters,
*     - "d": matches all decimal digits,
*     - "f": matches all characters which are permitted within a FITS
*     keyword (upper case letters, digits, underscores and hyphens).
*
*     If the field width is omitted, the field specifier matches one
*     or more characters. If the field width is zero, it matches zero
*     or more characters. Otherwise, it matches exactly the number of
*     characters specified. In addition to this:
*
*     - The template "%f" will match a blank FITS keyword consisting
*     of 8 spaces (as well as matching all other keywords).
*     - A template consisting of 8 spaces will match a blank keyword
*     (only).
*
*     For example:
*
*     - The template "BitPix" will match the keyword "BITPIX" only.
*     - The template "crpix%1d" will match keywords consisting of
*     "CRPIX" followed by one decimal digit.
*     - The template "P%c" will match any keyword starting with "P"
*     and followed by one or more letters.
*     - The template "E%0f" will match any keyword beginning with "E".
*     - The template "%f" will match any keyword at all (including a
*     blank one).
*--
*/

/* Local Variables: */
   char *c;               /* Pointer to next character to check */
   char *lname;           /* Pointer to copy of name without trailing spaces */
   const char *class;     /* Object class */ 
   const char *method;    /* Calling method */ 
   int ret;               /* Was a card found? */

/* Check the global status, and supplied keyword name. */
   if( !astOK ) return 0;

/* Store the calling method and object class. */ 
   method = "astFindFits";
   class = astGetClass( this ); 

/* Get a local copy of the keyword template. */
   lname = (char *) astStore( NULL, (void *) name, strlen(name) + 1 );

/* Terminate it to exclude trailing spaces. */
   c = lname + strlen(lname) - 1;
   while( *c == ' ' && c >= lname ) *(c--) = 0;

/* Use the private FindKeyCard function to find the card and make it the
   current card. Always use the supplied current card (if any) if the 
   template is "%f" or "%0f". */
   if ( !strcmp( lname, "%f" ) || !strcmp( lname, "%0f" ) ){ 
      ret = astFitsEof( this ) ? 0 : 1;
   } else {
      ret = FindKeyCard( this, lname, method, class );
   }

/* Only proceed if the card was found. */
   if( ret && astOK ){

/* Format the current card if a destination string was supplied. */
      if( card ) FormatCard( this, card, method );

/* Increment the current card pointer if required. */
      if( inc ) MoveCard( this, 1, method, class );

/* Indicate that a card has been formatted. */
      ret = 1;

   }

/* Free the memory holding the local copy of the keyword template. */
   lname = (char *) astFree( (void *) lname );

/* If an errror has occurred, return zero. */
   if( !astOK ) ret = 0;

/* Return the answer. */
   return ret;

}

static int FindKeyCard( AstFitsChan *this, const char *name, 
                        const char *method, const char *class ){
/*
*  Name:
*     FindKeyCard

*  Purpose:
*     Find the next card refering to given keyword.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int FindKeyCard( AstFitsChan *this, const char *name, 
*                      const char *method, const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Finds the next card which refers to the supplied keyword and makes
*     it the current card. The search starts with the current card and ends 
*     when it reaches the last card.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     name
*        Pointer to a string holding the keyword template (using the
*        syntax expected by the Match function).
*     method
*        Pointer to string holding name of calling method.

*  Returned Value:
*     A value of 1 is returned if a card was found refering to the given
*     keyword. Otherwise zero is returned.

*  Notes:
*     -  If a NULL pointer is supplied for "name" then the current card
*     is left unchanged.
*     -  The current card is set to NULL (end-of-file) if no card can be
*     found for the supplied keyword.

*/

/* Local Variables: */
   int nfld;             /* Number of fields in keyword template */
   int ret;              /* Was a card found? */

/* Check the global status, and supplied keyword name. */
   if( !astOK || !name ) return 0;

/* Indicate that no card has been found yet. */
   ret = 0;

/* Search forward through the list until all cards have been checked. */
   while( !astFitsEof( this ) && astOK ){

/* Break out of the loop if the keyword name from the current card matches 
   the supplied keyword name. */
      if( Match( CardName( this ), name, 0, NULL, &nfld, method, class ) ){
         ret = 1;
         break;

/* Otherwise, move the current card on to the next card. */
      } else {
         MoveCard( this, 1, method, class );
      }

   }

/* Return. */
   return ret;

}

static double *FitLine( AstMapping *map, double *g, double *g0, double *w0, 
                        double dim ){
/*
*  Name:
*     FitLine

*  Purpose:
*     Check a Mapping for linearity.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     double *FitLine( AstMapping *map, double *g, double *g0, double *w0, 
*                      double dim )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function applies the supplied Mapping to a set of points along
*     a straight line in the input space. It checks to see if the transformed 
*     positions also lie on a straight line (in the output space). If so,
*     it returns the vector along this line in the output space which
*     corresponds to a unit vector along the line in the input space. If
*     not, a NULL pointer is returned.
*
*     The returned vector is found by doing a least squares fit.

*  Parameters:
*     map
*        A pointer to the Mapping to test. The number of outputs must be
*        greater than or equal to the number of inputs.
*     g
*        A pointer to an array holding a unit vector within the input space 
*        defining the straight line to be checked. The number of elements
*        within this array should equal the number of inputs for "map".
*     g0
*        A pointer to an array holding a position within the input space 
*        giving the central position of the vector "g". The number of elements
*        within this array should equal the number of inputs for "map".
*     w0
*        A pointer to an array holding a vector within the output space 
*        which corresponds to "g0". The number of elements within this array 
*        should equal the number of outputs for "map".
*     dim
*        The length of the pixel axis, or AST__BAD if unknown.

*  Returned Value:
*     A pointer to dynamically allocated memory holding the required vector
*     in the output space. The number of elements in this vector will equal
*     the number of outputs for "map". The memory should be freed using
*     astFree when no longer needed. If the Mapping is not linear, NULL
*     is returned.

*  Notes:
*     -  NULL is returned if an error occurs.
*/

/* Local Constants: */
#define NPO2 50
#define NP (2*NPO2+1)

/* Local Variables: */
   AstPointSet *pset1;
   AstPointSet *pset2;
   double **ptr1;
   double **ptr2;
   double *offset;
   double *pax;
   double *ret;
   double *voffset;
   double dax;
   double denom;
   double gap;
   double sd2;
   double sd;
   double sdw;
   double sw;
   int i;
   int j;
   int n;
   int nin;
   int nout;
   int ok;              

/* Initialise */
   ret = NULL;

/* Check the inherited status and supplied axis size. */
   if( !astOK || dim == 0.0 ) return ret;

/* Get the number of inputs and outputs for the Mapping. Return if the
   number of outputs is smaller than the number of inputs. */
   nin = astGetNin( map );
   nout = astGetNout( map );
   if( nout < nin ) return ret;

/* Check the supplied position is good on all axes. */
   for( j = 0; j < nout; j++ ) {
      if( w0[ j ] == AST__BAD ) return ret;
   }

/* We use NP points in the fit. If a value for "dim" has been supplied,
   we use points evenly distributed over one tenth of this size, If 
   not, we use a gap of 1.0 (corresponds to an axis length of 100 pixels). 
   Choose the gap. */
   gap = ( dim != AST__BAD ) ? 0.1*dim/NP : 1.0;

/* Create PointSets to hold the input and output positions. */
   pset1 = astPointSet( NP, nin, "" );
   ptr1 = astGetPoints( pset1 );
   pset2 = astPointSet( NP, nout, "" );
   ptr2 = astGetPoints( pset2 );
   
/* Allocate the returned array. */
   ret = astMalloc( sizeof( double )*(size_t) nout );
   
/* Allocate workspace to hold the constant offsets of the fit. */
   offset = astMalloc( sizeof( double )*(size_t) nout );
   voffset = astMalloc( sizeof( double )*(size_t) nout );

/* Indicate we have not yet got a usable returned vector. */
   ok = 0;
     
/* Check we can use the pointers safely. */
   if( astOK ) {

/* Set up the input positions: NP evenly spaced points along a line with
   unit direction vector given by "g", centred at position given by "g0". */
      for( j = 0; j < nin; j++ ) {
         pax = ptr1[ j ];
         dax = g[ j ]*gap;
         for( i = -NPO2; i <= NPO2; i++ ) *(pax++) = g0[ j ] + dax*i;
      }

/* Transform these positions into the output space. */
      astTransform( map, pset1, 1, pset2 );

/* Loop over all output axes, finding the component of the returned vector. */
      ok = 1;
      for( j = 0; j < nout; j++ ) {
         pax = ptr2[ j ];

/* Now loop over all the transformed points to form the other required
   sums. We also form the sums needed to estimate the variance in the 
   calculated offset. */
         sdw = 0.0;
         sw = 0.0;
         sd = 0.0;
         sd2 = 0.0;
         n = 0;
         
         for( i = -NPO2; i <= NPO2; i++, pax++ ) {
            if( *pax != AST__BAD ) {

/* Increment the required sums. */
               sdw += i*(*pax);           
               sw += (*pax);           
               sd += i;
               sd2 += i*i;
               n++;
            }
         }

/* If a reasonable number of good points were found, find the component of 
   the returned vector (excluding a scale factor of 1/gap). */
         denom = sd2*n - sd*sd;
         if( n > NP/4 && denom != 0.0 ) {

/* Find the constant scale factor to return for this axis. */
            ret[ j ] = (sdw*n - sw*sd)/denom;

/* Now find the constant offset for this axis. */
            offset[ j ] = (sw*sd2 - sdw*sd)/denom;

         } else {
            ok = 0;
            break;
         }
      }

/* Now check that the fit is good enough. Each axis is checked separately.
   All axes must be good. */
      if( ok ) {
         for( j = 0; j < nout; j++ ) {

/* Store the axis values implied by the linear fit in the now un-needed ptr1[0]
   array. */
            pax = ptr1[ 0 ];
            for( i = -NPO2; i <= NPO2; i++, pax++ ) {
               *pax = i*ret[ j ] + offset[ j ];
            }

/* Test the fit to see if we beleive that the mapping is linear. If
   it is, scale the returned value from units of "per gap" to units of 
   "per pixel". Otherwise,indicate that he returned vector is unusable. */
            if( FitOK( NP, ptr2[ j ], ptr1[ 0 ] ) ) {
               ret[ j ] /= gap;
            } else {
               ok = 0; 
               break;
            }
         }
      }
   }

/* Annul the PointSets. */
   pset1 = astAnnul( pset1 );
   pset2 = astAnnul( pset2 );

/* Free memory. */
   offset = astFree( offset );
   
/* If an error has occurred, or if the returned vector is unusable, 
   free any returned memory */
   if( !astOK || !ok ) ret = astFree( ret );

/* Return the answer. */
   return ret;

/* Undefine local constants: */
#undef NP 
#undef NPO2
}

static int FitsEof( AstFitsChan *this ){
/*
*+
*  Name:
*     astFitsEof

*  Purpose:
*     See if the FitsChan is at "end-of-file".

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "fitschan.h"
*     int astFitsEof( AstFitsChan *this )

*  Class Membership:
*     FitsChan method.

*  Description:
*     A value of zero is returned if any more cards remain to be read from the
*     FitsChan. Otherwise a value of 1 is returned. Thus, it is
*     equivalent to testing the FitsChan for an "end-of-file" condition.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     One if no more cards remain to be read, otherwise zero.

*  Notes:
*     - This function attempts to execute even if an error has already
*     occurred.

*-
*/

/* Check the supplied object. */
   if( !this ) return 1;

/* If no more cards remain to be read, the current card pointer in the
   FitsChan will be NULL. Return an appropriate integer value. */
   return  this->card ? 0 : 1;

}

/*
*+
*  Name:
*     astFitsGet<X>

*  Purpose:
*     Get a named keyword value from a FitsChan.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "fitschan.h"
*     int astFitsGet<X>( AstFitsChan *this, const char *name, <X>type *value )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This is a family of functions which gets a value for a named keyword 
*     from a FitsChan using one of several different data types. The data 
*     type of the returned value is selected by replacing <X> in the function 
*     name by one of the following strings representing the recognised FITS 
*     data types:
*
*     CF - Complex floating point values.
*     CI - Complex integer values.
*     F  - Floating point values.
*     I  - Integer values.
*     L  - Logical (i.e. boolean) values.
*     S  - String values.
*     CN  - CONTINUE values.
*
*     The "value" parameter should be a pointer with a data type depending on 
*     <X> as follows:
*
*     CF - "double *" (the pointer should point to a 2 element array to
*          receive the real and imaginary parts of the complex value).
*     CI - "int *" (the pointer should point to a 2 element array to
*          receive the real and imaginary parts of the complex value).
*     F  - "double *".
*     I  - "int *".
*     L  - "int *".
*     S  - "char **" (a pointer to a static "char" array is returned at the
*          location given by the "value" parameter, Note, the stored string
*          may change on subsequent invocations of astFitsGetS so a
*          permanent copy should be taken of the string if necessary). 
*     CN - Like "S".

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     name
*        A pointer to a string holding the keyword name. This may be a 
*        complete FITS header card, in which case the keyword to use is 
*        extracted from it. No more than 80 characters are read from this 
*        string.
*     value
*        A pointer to a location at which to return the keyword value.
*        the data type of this parameter depends on <X> as described above.
*        The supplied value is left unchanged if the keyword is not found.

*  Returned Value:
*     astFitsGet<X>()
*        A value of zero is returned if the keyword was not found
*        (no error is reported). 
*        Otherwise, a value of one is returned. 

*  Notes:
*     -  The card following the current card is checked first. If this is
*     not the required card, then the rest of the FitsChan is searched,
*     starting with the first card added to the FitsChan. Therefore cards
*     should be accessed in the order they are stored in the FitsChan (if
*     possible) as this will minimise the time spent searching for cards. 
*     -  If the requested card is found, it becomes the current card, 
*     otherwise the current card is left pointing at the "end-of-file".
*     -  If the stored keyword value is not of the requested type, it is
*     converted into the requested type.
*     -  An error will be reported if the keyword name does not conform
*     to FITS requirements.
*     -  Zero is returned as the function value 
*     if an error has already occurred, or if this function should fail for 
*     any reason.
*-
*/

/* Define a macro which expands to the implementation of the astFitsGet<X>
   routine for a given data type. */

#define MAKE_FGET(code,ctype,ftype) \
static int FitsGet##code( AstFitsChan *this, const char *name, ctype value ){ \
\
/* Local Variables: */ \
   const char *class;     /* Object class */ \
   const char *method;    /* Calling method */ \
   char *lcom;            /* Supplied keyword comment */ \
   char *lname;           /* Supplied keyword name */ \
   char *lvalue;          /* Supplied keyword value */ \
   int ret;               /* The returned value */ \
   size_t sz;                /* Data size */ \
\
/* Check the global error status. */ \
   if ( !astOK ) return 0; \
\
/* Store the calling method and object class. */ \
   method = "astFitsGet"#code; \
   class = astGetClass( this ); \
\
/* Initialise the returned value. */ \
   ret = 0; \
\
/* Extract the keyword name from the supplied string. */ \
   (void) astSplit( name, &lname, &lvalue, &lcom, method, class ); \
\
/* Attempt to find a card in the FitsChan refering to this keyword, \
   and make it the current card. Only proceed if a card was found. */ \
   if( SearchCard( this, lname, method, class ) ){ \
\
/* Convert the stored data value to the requested type, and store it in \
   the supplied buffer. */ \
      if( !CnvValue( this, ftype, value, method ) && astOK ) { \
         astError( AST__FTCNV, "%s(%s): Cannot convert FITS keyword " \
                   "'%s' (value '%s') to %s.", method, class, \
                   lname, CardData( this, &sz ), type_names[ ftype ] ); \
      } \
\
/* Indicate that a value is available. */ \
      if( astOK ) ret = 1; \
\
   } \
\
/* Release the memory used to hold keyword name, value and comment strings. */ \
   lname = (char *) astFree( (void *) lname ); \
   lvalue = (char *) astFree( (void *) lvalue ); \
   lcom = (char *) astFree( (void *) lcom ); \
\
/* Return the answer. */ \
   return ret; \
\
}

/* Use the above macro to give defintions for the astFitsGet<X> method
   for each FITS data type. */

MAKE_FGET(CF,double *,AST__COMPLEXF)
MAKE_FGET(CI,int *,AST__COMPLEXI)
MAKE_FGET(F,double *,AST__FLOAT)
MAKE_FGET(I,int *,AST__INT)
MAKE_FGET(L,int *,AST__LOGICAL)
MAKE_FGET(S,char **,AST__STRING)
MAKE_FGET(CN,char **,AST__CONTINUE)

#undef MAKE_FGET

static int FitsGetCom( AstFitsChan *this, const char *name,  
                       char **comment ){
/*
*+
*  Name:
*     astFitsGetCom

*  Purpose:
*     Get a keyword comment from a FitsChan.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "fitschan.h"
*     int astFitsGetCom( AstFitsChan *this, const char *name, 
*                        char **comment )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function gets the comment associated with the next occurrence of 
*     a named keyword in a FitsChan. 

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     name
*        A pointer to a 
*        string holding the keyword name. This may be a complete FITS
*        header card, in which case the keyword to use is extracted from 
*        it. No more than 80 characters are read from this string.
*     comment
*        A pointer to a location at which to return a pointer to a string
*        holding the keyword comment. Note, the stored string will change on 
*        subsequent invocations of astFitsGetCom so a permanent copy 
*        should be taken of the string if necessary. 

*  Returned Value:
*     astFitsGetCom()
*        A value of zero is returned if the keyword was not found before
*        the end of the FitsChan was reached (no error is reported). 
*        Otherwise, a value of one is returned. 

*  Notes:
*     -  If a NULL pointer is supplied for "name" then the comment from
*     the current card is returned.
*     -  The returned value is obtained from the next card refering to 
*     the required keyword, starting the search with the current card.
*     Any cards occuring before the current card are not seached. If
*     the entire contents of the FitsChan must be searched, then ensure
*     the current card is the first card in the FitsChan by clearing the Card
*     attribute. This effectively "rewinds" the FitsChan.
*     -  The current card is updated to become the card following the one 
*     read by this function. If the card read by this function is the
*     last one in the FitsChan, then the current card is left pointing at the
*     "end-of-file".
*     -  An error will be reported if the keyword name does not conform
*     to FITS requirements.
*     -  A NULL pointer is returned for the comment string if the keyword
*     has no comment.
*     -  Zero is returned as the function value if an error has already 
*     occurred, or if this function should fail for any reason.
*-
*/

/* Local Variables: */ 
   const char *method;    /* Calling method */
   const char *class;     /* Object class */
   char *lcom;            /* Supplied keyword comment */ 
   char *lname;           /* Supplied keyword name */ 
   char *lvalue;          /* Supplied keyword value */ 
   int ret;               /* The returned value */ 
   static char sval[ FITSCARDLEN + 1 ]; /* Static text buffer */ 

/* Check the global error status. */ 
   if ( !astOK ) return 0; 

/* Initialise the returned value. */ 
   ret = 0; 

/* Store the method name and object class. */
   method = "astFitsGetCom";
   class = astGetClass( this );

/* Extract the keyword name from the supplied string (if supplied). */ 
   if( name ){
      (void) astSplit( name, &lname, &lvalue, &lcom, method, class );
   } else {
      lname = NULL;
      lcom = NULL;
      lvalue = NULL;
   }

/* Find the next card in the FitsChan refering to this keyword. This will
   be the current card if no keyword name was supplied. The matching card
   is made the current card. Only proceed if a card was found. */ 
   if( FindKeyCard( this, lname, method, class ) ){ 

/* Copy the comment into a static buffer, and return a pointer to it. */ 
      if( CardComm( this ) ){
         (void) strncpy( sval, CardComm( this ), FITSCARDLEN ); 
         sval[ FITSCARDLEN ] = 0; 
         if( comment ) *comment = sval;
      } else {
         if( comment ) *comment = NULL;
      }        

/* Move on to the next card. */
      MoveCard( this, 1, method, class );

/* Indicate that a value is available. */ 
      if( astOK ) ret = 1; 

   } 

/* Release the memory used to hold keyword name, value and comment strings. */ 
   lname = (char *) astFree( (void *) lname ); 
   lvalue = (char *) astFree( (void *) lvalue ); 
   lcom = (char *) astFree( (void *) lcom ); 

/* Return the answer. */ 
   return ret; 

}

static int FitsSet( AstFitsChan *this, const char *keyname, void *value, 
                    int type, const char *comment, int overwrite ){
/*
*  Name:
*     FitsSet

*  Purpose:
*     Store a keyword value of any type in a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int FitsSet( AstFitsChan *this, const char *keyname, void *value, 
*                  int type, const char *comment, int overwrite )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function stores the supplied value for the supplied keyword 
*     in the supplied FitsChan, assuming it is of the supplied data type.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     name
*        A pointer to a string holding the keyword name. 
*     value
*        A pointer to a buffer holding the keyword value. For strings,
*        the buffer should hold the address of a pointer to the character 
*        string.
*     type 
*        The keyword type.
*     comment
*        A pointer to a string holding a comment to associated with the 
*        keyword. If a NULL pointer or a blank string is supplied, then
*        any comment included in the string supplied for the "name" parameter 
*        is used instead. If "name" contains no comment, then any existing 
*        comment in the card being over-written is retained, or a NULL
*        pointer is stored if a new card is being inserted. If the data
*        value being stored for the card is the same as the card being
*        over-written, then any existing comment is retained.
*     overwrite
*        If non-zero, the new card formed from the supplied keyword name,
*        value and comment string over-writes the current card, and the 
*        current card is incremented to refer to the next card. If zero, the 
*        new card is inserted in front of the current card and the current 
*        card is left unchanged. In either case, if the current card on 
*        entry points to the "end-of-file", the new card is appended to the 
*        end of the list. 

*  Returned Value:
*     A value of 0 is returned if the value could not be stored for any
*     reason. A value of 1 is returned otherwise.

*  Notes:
*     -  Nothing is stored in the FitsChan and a value of zero is returned 
*     (but no error is reported) if an AST__FLOAT value is supplied equal 
*     to AST__BAD.

*/

/* Local Variables: */
   const char *cval; 
   const char *ecval; 
   double dval;
   double ecdval[ 2 ];
   double edval;
   int ecival[ 2 ];
   int eival;
   int ival;
   int ret;          

/* Check the global status, and the supplied pointer. */
   if( !astOK || !value ) return 0;

/* Initialise the returned value to indicate that the supplied name was
   stored. */
   ret = 1;

/* Check each data type in turn. */
   if( type == AST__FLOAT ){
      dval = *( (double *) value );
      if( dval != AST__BAD ) {

/* If the data value has not changed, and the card has a coment, 
   set the comment pointer NULL so that the existing comment will be
   retained. */
         if( overwrite && CnvValue( this, type, &edval, "FitsSet" ) && 
             CardComm( this ) ) {
            if( EQUAL( edval, dval ) ) comment = NULL;
         }

         astFitsSetF( this, keyname, dval, comment, overwrite );
      } else {
         ret = 0;
      }

   } else if( type == AST__STRING ){
      cval = *( (char **) value);
      if( cval ){      

/* If the data value has not changed, retain the original comment. */
         if( overwrite && CnvValue( this, type, &ecval, "FitsSet" ) && 
             CardComm( this ) ) {
            if( Similar( ecval, cval ) ) comment = NULL;
         }

/* Ignore comments if they are identical to the keyword value. */
         if( comment && !strcmp( cval, comment ) ) comment = NULL;

         astFitsSetS( this, keyname, cval, comment, overwrite );
      } else {
         ret = 0;
      }

   } else if( type == AST__CONTINUE ){
      cval = *( (char **) value);
      if( cval ){      
         astFitsSetCN( this, keyname, cval, comment, overwrite );
      } else {
         ret = 0;
      }

   } else if( type == AST__COMMENT ){
      astFitsSetCom( this, keyname, comment, overwrite );

   } else if( type == AST__INT ){
      ival = *( (int *) value );

/* If the data value has not changed, retain the original comment. */
      if( overwrite && CnvValue( this, type, &eival, "FitsSet" ) && 
         CardComm( this ) ) {
         if( eival == ival ) comment = NULL;
      }

      astFitsSetI( this, keyname, ival, comment, overwrite );

   } else if( type == AST__COMPLEXF ){
      if( ( (double *) value )[0] != AST__BAD &&
          ( (double *) value )[1] != AST__BAD ) {

/* If the data value has not changed, retain the original comment. */
         if( overwrite && CnvValue( this, type, ecdval, "FitsSet" ) && 
             CardComm( this ) ) {
            if( EQUAL( ecdval[ 0 ], ( (double *) value )[ 0 ] ) &&
                EQUAL( ecdval[ 1 ], ( (double *) value )[ 1 ] ) ) comment = NULL;
         }

         astFitsSetCF( this, keyname, (double *) value, comment, overwrite );
      } else {
         ret = 0;
      }

   } else if( type == AST__COMPLEXI ){

/* If the data value has not changed, retain the original comment. */
      if( overwrite && CnvValue( this, type, ecival, "FitsSet" ) && 
          CardComm( this ) ) {
         if( ecival[ 0 ] == ( (int *) value )[ 0 ] &&
             ecival[ 1 ] == ( (int *) value )[ 1 ] ) comment = NULL;
      }

      astFitsSetCI( this, keyname, (int *) value, comment, overwrite );

   } else if( type == AST__LOGICAL ){
      ival = ( *( (int *) value ) != 0 );

/* If the data value has not changed, retain the original comment. */
      if( overwrite && CnvValue( this, type, &eival, "FitsSet" ) && 
          CardComm( this ) ) {
         if( eival == ival ) comment = NULL;
      }

      astFitsSetL( this, keyname, ival, comment, overwrite );

   } 

   return ret;
}

/*
*+
*  Name:
*     astFitsSet<X>

*  Purpose:
*     Store a keyword value in a FitsChan.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "fitschan.h"
*     void astFitsSet<X>( AstFitsChan *this, const char *name, <X>type value, 
*                         const char *comment, int overwrite )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This is a family of functions which store values for named keywords
*     within a FitsChan at the current card position. The supplied keyword 
*     value can either over-write an existing keyword value, or can be 
*     inserted as a new header card into the FitsChan.
*
*     The keyword data type is selected by replacing <X> in the function name 
*     by one of the following strings representing the recognised FITS data 
*     types:
*
*     CF - Complex floating point values.
*     CI - Complex integer values.
*     F  - Floating point values.
*     I  - Integer values.
*     L  - Logical (i.e. boolean) values.
*     S  - String values.
*     CN - A "CONTINUE" value, these are treated like string values, but
*          are encoded without an equals sign.
*
*     The data type of the "value" parameter depends on <X> as follows:
*
*     CF - "double *" (a pointer to a 2 element array holding the real and
*          imaginary parts of the complex value).
*     CI - "int *" (a pointer to a 2 element array holding the real and
*          imaginary parts of the complex value).
*     F  - "double".
*     I  - "int".
*     L  - "int".
*     S  - "const char *".
*     CN - "const char *".

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     name
*        A pointer to a 
*        string holding the keyword name. This may be a complete FITS
*        header card, in which case the keyword to use is extracted from 
*        it. No more than 80 characters are read from this string.
*     value
*        The keyword value to store with the named keyword. The data type
*        of this parameter depends on <X> as described above.
*     comment
*        A pointer to a string holding a comment to associated with the 
*        keyword. If a NULL pointer or a blank string is supplied, then
*        any comment included in the string supplied for the "name" parameter 
*        is used instead. If "name" contains no comment, then any existing 
*        comment in the card being over-written is retained, or a NULL
*        pointer is stored if a new card is being inserted.
*     overwrite
*        If non-zero, the new card formed from the supplied keyword name,
*        value and comment string over-writes the current card, and the 
*        current card is incremented to refer to the next card. If zero, the 
*        new card is inserted in front of the current card and the current 
*        card is left unchanged. In either case, if the current card on 
*        entry points to the "end-of-file", the new card is appended to the 
*        end of the list. 

*  Notes:
*     -  To assign a new value for an existing keyword within a FitsChan,
*     first find the card describing the keyword using astFindFits, and
*     then use one of the astFitsSet<X> family to over-write the old value.
*     -  If, on exit, there are no cards following the card written by
*     this function, then the current card is left pointing at the 
*     "end-of-file".
*     -  An error will be reported if the keyword name does not conform
*     to FITS requirements.
*-
*/

/* Define a macro which expands to the implementation of the astFitsSet<X>
   routine for a given data type. */

#define MAKE_FSET(code,ctype,ftype,valexp) \
static void FitsSet##code( AstFitsChan *this, const char *name, ctype value, const char *comment, int overwrite ) { \
\
/* Local variables: */ \
   const char *class;     /* Object class */ \
   const char *method;    /* Calling method */ \
   const char *com;       /* Comment to use */ \
   char *lcom;            /* Supplied keyword comment */ \
   char *lname;           /* Supplied keyword name */ \
   char *lvalue;          /* Supplied keyword value */ \
   int free_com;          /* Should com be freed before returned? */ \
\
/* Check the global error status. */ \
   if ( !astOK ) return; \
\
/* Store the object clas and calling method. */ \
   class = astGetClass( this ); \
   method = "astFitsSet"#code; \
\
/* Extract the keyword name from the supplied string. */ \
   (void) astSplit( name, &lname, &lvalue, &lcom, method, class ); \
\
/* Initialise a pointer to the comment to be stored. If the supplied \
   comment is blank, use the comment given with "name". */ \
   com = ChrLen( comment ) ? comment : lcom; \
\
/* If the comment is still blank, use the existing comment if we are \
   over-writing, or a NULL pointer otherwise. */ \
   free_com = 0; \
   if( !ChrLen( com ) ) { \
      com = NULL; \
      if( overwrite ) { \
         if( CardComm( this ) ){ \
            com = (const char *) astStore( NULL, (void *) CardComm( this), \
                                           strlen( CardComm( this ) ) + 1 ); \
            free_com = 1; \
         } \
      } \
   } \
\
/* Insert the new card. */ \
   InsCard( this, overwrite, lname, ftype, valexp, com, method, class ); \
\
/* Release the memory used to hold keyword name, value and comment strings. */ \
   lname = (char *) astFree( (void *) lname ); \
   lvalue = (char *) astFree( (void *) lvalue ); \
   lcom = (char *) astFree( (void *) lcom ); \
\
/* Release the memory holding the stored comment string, so long as it was \
   allocated within this function. */ \
   if( free_com ) com = (const char *) astFree( (void *) com ); \
\
}

/* Use the above macro to give defintions for the astFitsSet<X> method
   for each FITS data type. */

MAKE_FSET(I,int,AST__INT,(void *)&value)
MAKE_FSET(F,double,AST__FLOAT,(void *)&value)
MAKE_FSET(S,const char *,AST__STRING,(void *)value)
MAKE_FSET(CN,const char *,AST__CONTINUE,(void *)value)
MAKE_FSET(CF,double *,AST__COMPLEXF,(void *)value)
MAKE_FSET(CI,int *,AST__COMPLEXI,(void *)value)
MAKE_FSET(L,int,AST__LOGICAL,(void *)&value)

#undef MAKE_FSET


static void FitsSetCom( AstFitsChan *this, const char *name, 
                        const char *comment, int overwrite ){
/*
*+
*  Name:
*     astFitsSetCom

*  Purpose:
*     Store a comment for a keyword in a FitsChan.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "fitschan.h"
*     void astFitsSetCom( AstFitsChan *this, const char *name, 
*                         const char *comment, int overwrite ) 

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function replaces the comment within an existing card, or 
*     stores a new comment card within a FitsChan.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     name
*        A pointer to a 
*        string holding the keyword name. This may be a complete FITS
*        header card, in which case the keyword to use is extracted from 
*        it. No more than 80 characters are read from this string.
*     comment
*        A pointer to a 
*        string holding a comment to associated with the keyword.
*        If a NULL or
*        blank string is supplied, any existing comment associated with
*        the keyword is removed.
*     overwrite
*        If non-zero, the new comment replaces the comment in the current 
*        card, and the current card is then incremented to refer to the next 
*        card. If zero, a new comment card is inserted in front of the current 
*        card and the current card is left unchanged. In either case, if the 
*        current card on entry points to the "end-of-file", the new card is 
*        appended to the end of the list. 

*  Notes:
*     -  When replacing an existing comment, any existing keyword value is 
*     retained only if the supplied keyword name is the same as the keyword 
*     name in the current card. If the keyword names are different, then
*     the new name replaces the old name, and any existing keyword data value 
*     is deleted. The card thus becomes a comment card with the supplied 
*     keyword name and comment, but no data value.
*     -  If, on exit, there are no cards following the card written by
*     this function, then the current card is left pointing at the 
*     "end-of-file".
*     -  The current card can be set explicitly before calling this function
*     either by assigning a value to the Card attribute (if the index of the 
*     required card is already known), or using astFindFits (if only the 
*     keyword name is known).
*     -  An error will be reported if the keyword name does not conform
*     to FITS requirements.
*-
*/

/* Local variables: */ 
   const char *class;     /* Pointer to object class string */
   const char *method;    /* Pointer to calling method string */
   const char *cname;     /* The existing keyword name */ 
   const char *com;       /* The comment to use */ 
   char *lcom;            /* Supplied keyword comment */ 
   char *lname;           /* Supplied keyword name */ 
   char *lvalue;          /* Supplied keyword value */ 
   void *old_data;        /* Pointer to the old data value */
   void *data;            /* Pointer to data value to be stored */
   size_t size;           /* The size of the data value */

/* Check the global error status. */
   if ( !astOK ) return;

/* Store the calling method and object class. */
   method = "astFitsSetCom";
   class = astGetClass( this );

/* Extract the keyword name, etc, from the supplied string. */ 
   (void) astSplit( name, &lname, &lvalue, &lcom, method, class );

/* If a blank comment has been supplied, use NULL instead. */
   com = ChrLen( comment )? comment : NULL;

/* If we are inserting a new card, or over-writing an old card with a
   different name, create and store a comment card with the given keyword
   name and comment, but no data value. */
   cname = CardName( this );
   if( !overwrite || !cname || strcmp( lname, cname ) ){
      InsCard( this, overwrite, lname, AST__COMMENT, NULL, com, method, class );

/* If we are overwriting an existing keyword comment, use the data type
   and value from the existing current card. Note, we have to take a copy
   of the old data value because InsCard over-writes by deleting the old 
   card and then inserting a new one. */
   } else {
      old_data = CardData( this, &size );
      data = astStore( NULL, old_data, size );
      InsCard( this, 1, lname, CardType( this ), data, com, method, class );
      data = astFree( data );
   }

/* Release the memory used to hold keyword name, value and comment strings. */
   lname = (char *) astFree( (void *) lname );
   lvalue = (char *) astFree( (void *) lvalue );
   lcom = (char *) astFree( (void *) lcom );

}

static void FixNew( AstFitsChan *this, int flag, int remove, 
                    const char *method, const char *class ){
/*
*
*  Name:
*     FixNew

*  Purpose:
*     Remove "new" flags from the whole FitsChan, and optionally remove
*     "new" cards.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void FixNew( AstFitsChan *this, int flag, int remove, 
*                  const char *method, const char *class )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function searches the entire FitsChan for cards which are
*     marked as new using the supplied flag (NEW1 or NEW2). If "remove"
*     is non-zero, these cards are completely removed from the FitsChan 
*     (not just marked as used). If "remove" is zero, they are retained
*     and the specified flag is cleared.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     flag
*        The flag to use; NEW1 or NEW2.
*     remove
*        Remove flagged cards from the FitsChan?
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Notes:
*     - This function attempts to execute even if an error has occurred.
*     - If any cards are removed, the current Card is left at "end-of-file" 
*       on exit. If no cards are removed, the original current card is
*       retained.

*-
*/

/* Local Variables: */
   int *flags;             /* Pointer to flags mask for the current card */
   int icard;              /* Index of current card on entry */
   int ndeleted;           /* Number of cards deleted by this call */

/* Return if no FitsChan was supplied, or if the FitsChan is empty. */
   if ( !this || !this->head ) return;

/* Save the current card index, and rewind the FitsChan. */
   icard = astGetCard( this );
   astClearCard( this );

/* Indicate no cards have yet been deleted. */
   ndeleted = 0;

/* Loop through the list of FitsCards in the FitsChan until the final
   card is reached. */
   while( astOK && this->card ){

/* Get a pointer to the flags mask for this card. */
      flags = CardFlags( this );

/* See if the Card has been marked with the requeste new flag. */
      if( flags && ( (*flags) & flag ) ) {

/* If requested, remove the card. This will automatically move the
   current card on to the next card. */
         if( remove ){
            DeleteCard( this, method, class );
            ndeleted++;

/* Otherwise, clear the flag. */
         } else {
            *flags = (*flags) & ~flag;

/* Increment the card count and move on to the next card. */
            MoveCard( this, 1, method, class );

         }

/* Move on to the next card if this card is not marked with the requested 
   new flag. */
      } else {
         MoveCard( this, 1, method, class );
      }
   }

/* If no cards were removed, we can safely re-instate the original
   current card. Otherwise, the current card is left at "end-of-file". */
   if( ndeleted == 0 ) astSetCard( this, icard );

/* Return */
   return;

}

static void FixUsed( AstFitsChan *this, int reset, int used, int remove, 
                     const char *method, const char *class ){
/*
*
*  Name:
*     FixUsed

*  Purpose:
*     Remove "provisionally used" flags from the whole FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void FixUsed( AstFitsChan *this, int reset, int used, int remove, 
*                   const char *method, const char *class )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function searches the entire FitsChan for cards which are
*     marked as "provisionally used". The "provisionally used" flag is
*     cleared for each such card. In addition, if "used" is non-zero then
*     each such card is flagged as having been "definitely used". If
*     "remove" is non-zero, then all "provisionally used" cards are deleted 
*     from the FitsChan.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     reset
*        Set all cards so that they are neither provisionally used or
*        definitely used. In this case neither the "used" nor the
*        "remove" parameter are accssed.
*     used
*        Have the provisionally used cards definitely been used?
*     remove
*        Should provisionally used cards be deleted?
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Notes:
*     - This function attempts to execute even if an error has occurred.

*-
*/

/* Local Variables: */
   FitsCard *card0;        /* Pointer to current FitsCard */
   int *flags;             /* Pointer to flags mask for the current card */
   int old_ignoreused;     /* Original value of external variable IgnoreUsed */

/* Return if no FitsChan was supplied, or if the FitsChan is empty. */
   if ( !this || !this->head ) return;

/* Indicate that we should not skip over cards marked as having been
   read. */
   old_ignoreused = IgnoreUsed;
   IgnoreUsed = 0;

/* Save a pointer to the current card, and the reset the current card to
   be the first card. */
   card0 = this->card;
   astClearCard( this );

/* Loop through the list of FitsCards in the FitsChan until the final
   card is reached. */
   while( this->card ){

/* Get a pointer to the flags mask for this card. */
      flags = CardFlags( this );

/* Reset both used flags if required. */
      if( reset ) {
         *flags = (*flags) & ~PROVISIONALLY_USED;
         *flags = (*flags) & ~USED;
         MoveCard( this, 1, method, class );

/* Otherwise perform the actions indicated by parameters "used" and
   "remove". */
      } else {

/* See if the Card has been provisionally used. */
         if( flags && ( (*flags) & PROVISIONALLY_USED ) ) {

/* Clear the provisionally used flag. */
            *flags = (*flags) & ~PROVISIONALLY_USED;

/* If required, set the definitely used flag. */
            if( used ) *flags = (*flags) | USED;

/* If required, delete the card. The next card is made current. If we are
   about to delete the original current card, we need to update the
   pointer to the card to be made current at the end of this function.
   If we end up back at the head of the chain, indicate that we have
   reached the end of file by setting card0 NULL.  */
            if( remove ) {
               if( card0 == this->card && card0 ) {
                  card0 = ( (FitsCard *) this->card )->next;
                  if( (void *) card0 == this->head ) card0 = NULL;
               }
               DeleteCard( this, method, class );

/* Otherwise, just move on to the next card. */
            } else {
               MoveCard( this, 1, method, class );
            }

/* If this card has not bee provisionally used, move on to the next card. */
         } else {
            MoveCard( this, 1, method, class );
         }
      }
   }

/* Re-instate the original current card. */
   this->card = card0;

/* If this card is now flagged as definitely used, move forward to the
   next un-used card. */
   flags = CardFlags( this );
   if( flags && (*flags & USED ) ) {
      IgnoreUsed = 1;
      MoveCard( this, 1, method, class );
   }

/* Re-instate the original flag indicating if cards marked as having been 
   read should be skipped over. */
   IgnoreUsed = old_ignoreused;

/* Return */
   return;

}

static void FormatCard( AstFitsChan *this, char *buf, const char *method ){
/*
*
*  Name:
*     FormatCard

*  Purpose:
*     Formats the current card.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void FormatCard( AstFitsChan *this, char *buf, const char *method )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function write the current card into the supplied character 
*     buffer as a complete FITS header card.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     buf
*        A character string into which the header card is written. This 
*        should be at least 81 characters long. The returned string is 
*        padded with spaces upto column 80. A terminating null character 
*        is added. 
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.

*  Notes:
*     -  An error is reported if the requested header card does not conform to
*     FITS standards.
*
*/

/* Local Variables: */
   const char *com;            /* Pointer to comment string to use */
   int comlen;                 /* Length of comment string */
   int comstart;               /* Column in which to start comment */
   int i;                      /* Loop counter for characters */
   int len;                    /* Output string length */
   int digits;                 /* No. of digits to use when formatting floating point values */
   int type;                   /* Card data type */

/* Check the global error status, and check the current card is defined. */
   if ( !astOK || astFitsEof( this ) ) return; 

/* Get a pointer to the comment to use and determine its length. */
   com = CardComm( this );
   comlen = ChrLen( com );

/* Copy the keyword name to the start of the output buffer, and store
   its length. */
   len = (int) strlen( strcpy( buf, CardName( this ) ) );

/* Pad the name with spaces up to column 8. */
   while ( len < FITSNAMLEN ) buf[ len++ ] = ' ';

/* If the card contains a keyword value... */
   type = CardType( this );
   if( type != AST__COMMENT ){

/* Get the number of digits to use when formatting floating point values. */
      digits = astGetFitsDigits( this );

/* Put an equals sign in column 9 (or a space if the keyword is a CONTINUE
   card), followed by a space in column 10. */
      buf[ len++ ] = ( type == AST__CONTINUE ) ? ' ' : '=';
      buf[ len++ ] = ' ';

/* Format and store the keyword value, starting at column 11 and update the
   output string length. */
      len += EncodeValue( this, buf + len, FITSNAMLEN + 3, digits, 
                          method );

/* If there is a comment, determine which column it should start in so that
   it ends in column 80. */
      if( com ){
         comstart = FITSCARDLEN - ( comlen - 2 ) + 1;

/* Adjust the starting column to 32 if possible, avoiding over-writing
   the value, or running off the end of the card unless this is
   unavoidable. */
         if ( comstart > FITSCOMCOL ) comstart = FITSCOMCOL;
         if ( comstart < len + 1 ) comstart = len + 1;

/* Pad the output buffer with spaces up to the start of the comment. */
         while ( len < comstart - 1 ) buf[ len++ ] = ' ';

/* Then append "/ " to introduce the comment, truncating if the card
   length forces this. */
         for ( i = 0; ( i < 2 ) && ( len < FITSCARDLEN ); i++ ) {
            buf[ len++ ] = "/ "[ i ];
         }
      }
   }

/* Append any comment, truncating it if the card length forces
   this. */
   if ( com ) {
      for ( i = 0; com[ i ] && ( len < FITSCARDLEN ); i++ ) {
         buf[ len++ ] = com[ i ];
      }
   }

/* Pad with spaces up to the end of the card. */      
   while ( len < FITSCARDLEN ) buf[ len++ ] = ' ';

/* Terminate it. */
   buf[ FITSCARDLEN ] = 0;

}

static int FullForm( const char *list, const char *test, int abbrev ){
/*
*  Name:
*     FullForm

*  Purpose:
*     Identify the full form of an option string.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int FullForm( const char *list, const char *test, int abbrev )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function identifies a supplied test option within a supplied
*     list of valid options, and returns the index of the option within
*     the list. The test option may be abbreviated, and case is
*     insignificant.

*  Parameters:
*     list
*        A list of space separated option strings.
*     test
*        A candidate option string.
*     abbrev
*        1 if abbreviations are to be accepted. Zero otherwise.

*  Returned Value:
*     The index of the identified option within the supplied list, starting
*     at zero. -1 is returned if the option is not recognised, and (if
*     abbrev is 1 ) -2 if the option is ambiguous (no errors are reported 
*     in these cases). If abbrev is zero, the returned index will be the
*     index of the first matching string.
*    

*  Notes:
*     -  A value of -1 is returned if an error has already occurred, or
*     if this function should fail for any reason.

*/

/* Local Variables: */
   char *llist;            /* Pointer to a local copy of the options list */
   char *option;           /* Pointer to the start of the next option */
   int i;                  /* Current option index */
   int len;                /* Length of supplied option */
   int nmatch;             /* Number of matching options */
   int ret;                /* The returned index */

/* Initialise the answer to indicate that the option has not been
   identified. */
   ret = -1;

/* Check global status. */
   if( !astOK ) return ret;

/* Take a local copy of the supplied options list. This is necessary since
   "strtok" modified the string by inserting null characters. */
   llist = (char *) astStore( NULL, (void *) list, strlen(list) + 1 );
   if( astOK ){

/* Save the number of characters in the supplied test option (excluding
   trailing spaces). */
      len = ChrLen( test );

/* Compare the supplied test option against each of the known options in 
   turn. Count the number of matches. */
      nmatch = 0;
      option = strtok( llist, " " );
      i = 0;
      while( option ){
      
/* If every character in the supplied label matches the corresponding
   character in the current test label we have a match. Increment the 
   number of matches and save the current item index. If abbreviation is
   not allowed ensure that the lengths of the strings are equal. */
         if( !Ustrncmp( test, option, len ) && ( abbrev ||
             len == ChrLen( option ) ) ) {
            nmatch++;
            ret = i;
            if( !abbrev ) break;
         }

/* Get a pointer to the next option. */
         option = strtok( NULL, " " );
         i++;
      }

/* Return -1 if no match was found. */
      if( !nmatch ){
         ret = -1;

/* Return -2 if the option was ambiguous. */
      } else if( abbrev && nmatch > 1 ){
         ret = -2;
      }

/* Free the local copy of the options list. */
      llist = (char *) astFree( (void *) llist );
   }

/* Return the answer. */
   return ret;
}

static const char *GetAllWarnings( AstFitsChan *this ){
/*
*+
*  Name:
*     astGetAllWarnings

*  Purpose:
*     Return a list of all condition names.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "fitschan.h"
*     const char *GetAllWarnings( AstFitsChan *this )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function returns a space separated lits of the condition names
*     currently recognized by the Warnings attribute.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     A pointer to a static string holding the condition names.

*  Notes:
*     - This routine does not check the inherited status.

*-
*/

/* Return the result. */
   return ALLWARNINGS;

}

const char *GetAttrib( AstObject *this_object, const char *attrib ) {
/*
*  Name:
*     GetAttrib

*  Purpose:
*     Get the value of a specified attribute for a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     const char *GetAttrib( AstObject *this, const char *attrib )

*  Class Membership:
*     FitsChan member function (over-rides the protected astGetAttrib
*     method inherited from the Channel class).

*  Description:
*     This function returns a pointer to the value of a specified
*     attribute for a FitsChan, formatted as a character string.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     attrib
*        Pointer to a null-terminated string containing the name of
*        the attribute whose value is required. This name should be in
*        lower case, with all white space removed.

*  Returned Value:
*     - Pointer to a null-terminated string containing the attribute
*     value.

*  Notes:
*     - The returned string pointer may point at memory allocated
*     within the FitsChan, or at static memory. The contents of the
*     string may be over-written or the pointer may become invalid
*     following a further invocation of the same function or any
*     modification of the FitsChan. A copy of the string should
*     therefore be made if necessary.
*     - A NULL pointer will be returned if this function is invoked
*     with the global error status set, or if it should fail for any
*     reason.
*/

/* Local Constants: */
#define BUFF_LEN 50              /* Max. characters in result buffer */

/* Local Variables: */
   AstFitsChan *this;            /* Pointer to the FitsChan structure */
   const char *result;           /* Pointer value to return */
   static char buff[ BUFF_LEN + 1 ]; /* Buffer for string result */
   int ival;                     /* Integer attribute value */
   int len;                      /* Length of attrib string */

/* Initialise. */
   result = NULL;

/* Check the global error status. */   
   if ( !astOK ) return result;

/* Obtain a pointer to the FitsChan structure. */
   this = (AstFitsChan *) this_object;

/* Obtain the length of the attrib string. */
   len = strlen( attrib );

/* Card. */
/* ----- */
   if ( !strcmp( attrib, "card" ) ) {
      ival = astGetCard( this );
      if ( astOK ) {
         (void) sprintf( buff, "%d", ival );
         result = buff;
      }

/* Encoding. */
/* --------- */
   } else if ( !strcmp( attrib, "encoding" ) ) {
      ival = astGetEncoding( this );
      if ( astOK ) {
         if( ival == NATIVE_ENCODING ){
            result = NATIVE_STRING;

         } else if( ival == FITSPC_ENCODING ){
            result = FITSPC_STRING;

         } else if( ival == FITSIRAF_ENCODING ){
            result = FITSIRAF_STRING;

         } else if( ival == FITSAIPS_ENCODING ){
            result = FITSAIPS_STRING;

         } else if( ival == FITSAIPSPP_ENCODING ){
            result = FITSAIPSPP_STRING;

         } else if( ival == FITSWCS_ENCODING ){
            result = FITSWCS_STRING;

         } else if( ival == DSS_ENCODING ){
            result = DSS_STRING;

         } else {
            result = UNKNOWN_STRING;
         }
      }

/* CDMatrix */
/* -------- */
   } else if ( !strcmp( attrib, "cdmatrix" ) ) {
      ival = astGetCDMatrix( this );
      if ( astOK ) {
         (void) sprintf( buff, "%d", ival );
         result = buff;
      }

/* DefB1950 */
/* -------- */
   } else if ( !strcmp( attrib, "defb1950" ) ) {
      ival = astGetDefB1950( this );
      if ( astOK ) {
         (void) sprintf( buff, "%d", ival );
         result = buff;
      }

/* CarLin */
/* ------ */
   } else if ( !strcmp( attrib, "carlin" ) ) {
      ival = astGetCarLin( this );
      if ( astOK ) {
         (void) sprintf( buff, "%d", ival );
         result = buff;
      }

/* Iwc */
/* --- */
   } else if ( !strcmp( attrib, "iwc" ) ) {
      ival = astGetIwc( this );
      if ( astOK ) {
         (void) sprintf( buff, "%d", ival );
         result = buff;
      }

/* Clean */
/* ----- */
   } else if ( !strcmp( attrib, "clean" ) ) {
      ival = astGetClean( this );
      if ( astOK ) {
         (void) sprintf( buff, "%d", ival );
         result = buff;
      }

/* FitsDigits. */
/* ----------- */
   } else if ( !strcmp( attrib, "fitsdigits" ) ) {
      ival = astGetFitsDigits( this );
      if ( astOK ) {
         (void) sprintf( buff, "%d", ival );
         result = buff;
      }

/* Ncard. */
/* ------ */
   } else if ( !strcmp( attrib, "ncard" ) ) {
      ival = astGetNcard( this );
      if ( astOK ) {
         (void) sprintf( buff, "%d", ival );
         result = buff;
      }

/* AllWarnings */
/* ----------- */
   } else if ( !strcmp( attrib, "allwarnings" ) ) {
      result = astGetAllWarnings( this );

/* Warnings. */
/* -------- */
   } else if ( !strcmp( attrib, "warnings" ) ) {
      result = astGetWarnings( this );

/* If the attribute name was not recognised, pass it on to the parent
   method for further interpretation. */
   } else {
      result = (*parent_getattrib)( this_object, attrib );
   }

/* Return the result. */
   return result;

/* Undefine macros local to this function. */
#undef BUFF_LEN
}

static int GetCard( AstFitsChan *this ){
/*
*+
*  Name:
*     astGetCard

*  Purpose:
*     Get the value of the Card attribute.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "fitschan.h"
*     int astGetCard( AstFitsChan *this )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function returns the value of the Card attribute for the supplied 
*     FitsChan. This is the index of the next card to be read from the
*     FitsChan. The index of the first card is 1. If there are no more
*     cards to be read, a value one greater than the number of cards in the
*     FitsChan is returned. 

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     The index of the next card to be read.

*  Notes:
*     - A value of zero will be returned if the current card is not defined.
*     - This function attempts to execute even if an error has occurred.

*-
*/

/* Local Variables: */
   const char *class;      /* Pointer to class string */
   const char *method;     /* Pointer to method string */
   FitsCard *card0;        /* Pointer to current FitsCard */
   int index;              /* Index of next FitsCard */

/* Return if no FitsChan was supplied, or if the FitsChan is empty. */
   if ( !this || !this->head ) return 0;

/* Store the method and object class. */
   method = "astGetCard";
   class = astGetClass( this );

/* Save a pointer to the current card, and the reset the current card to
   be the first card. */
   card0 = this->card;
   astClearCard( this );

/* Count through the list of FitsCards in the FitsChan until the original
   current card is reached. If the current card is not found (for instance 
   if it has been marked as deleted and we are currently skipping such cards), 
   this->card will be left null (end-of-file). */
   index = 1;
   while( this->card != card0 && astOK && this->card ){

/* Increment the card count and move on to the next card. */
      index++;
      MoveCard( this, 1, method, class );

   }

/* Return the card index. */
   return index;

}

static int GetFull( AstChannel *this_channel ) {
/*
*  Name:
*     GetFull

*  Purpose:
*     Obtain the value of the Full attribute for a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int GetFull( AstChannel *this )

*  Class Membership:
*     FitsChan member function (over-rides the protected astGetFull
*     method inherited from the Channel class).

*  Description:
*     This function return the integer value of the Full attribute for
*     a FitsChan.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     The Full attribute value.

*  Notes:
*     - This function modifies the default Full value from 0 to -1 for
*     the benefit of the FitsChan class. This prevents non-essential
*     information being written by the astWrite method unless it is
*     requested by explicitlt setting a Full value.
*     - A value of zero will be returned if this function is invoked
*     with the global error status set, or if it should fail for any
*     reason.
*/

/* Local Variables: */
   AstFitsChan *this;            /* Pointer to the FitsChan structure */
   int result;                   /* Result value to return */

/* Check the global error status. */
   if ( !astOK ) return 0;

/* Obtain a pointer to the FitsChan structure. */
   this = (AstFitsChan *) this_channel;

/* If the Full attribute us set, obtain its value using the parent class
   method. */
   if ( astTestFull( this ) ) {
      result = (* parent_getfull)( this_channel );

/* Otherwise, supply a default value of -1. */
   } else {
      result = -1;
   }

/* Return the result. */
   return result;
}

static FitsCard *GetLink( FitsCard *card, int next, const char *method, 
                          const char *class ){
/*
*  Name:
*     GetLink

*  Purpose:
*     Get a pointer to the next or previous card in the list.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     FitsCard *GetLink( FitsCard *card, int next, const char *method, 
*                        const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Returns the a pointer to either the next or previous FitsCard
*     structure in the circular linked list of such structures stored in a
*     FitsChan. A check is performed to ensure that the forward and 
*     backward links from the supplied card are consistent and an error
*     is reported if they are not (so long as no previous error has been
*     reported). Memory corruption can result in inconsistent links
*     which can result in infinite loops if an attempt is made to scan the
*     list.

*  Parameters:
*     card
*        The current card.
*     next
*        If non-zero, a pointer to the "next" card is returned. Otherwise
*        a pointer to the "previous" card is returned.
*     method
*        Pointer to string holding the name of the calling method.
*     class
*        Pointer to string holding the object class.

*  Returned Value:
*     A pointer to the required card, or NULL if an error occurs.

*  Notes:
*     -  This function attempts to execute even if an error has occurred.
*/

/* Local Variables: */
   FitsCard *ret;               /* Pointer to the returned card */

/* Check that the "next" link from the previous card points back to
   the current card, and that the "prev" link from the next card points
   back to the current card. */
   if( card && ( card->prev->next != card || 
                 card->next->prev != card ) ){

/* Report an error so long as no previous error has been reported, and
   return a NULL pointer. */
      if( astOK ){
         astError( AST__FCRPT, "%s(%s): A corrupted %s object has been "
                   "supplied.", method, class, class );
      }
      ret = NULL;

/* If the links are good, return a pointer to the required card. */
   } else {
      ret = next ? card->next : card->prev;
   }

/* Return the result. */
   return ret;

}

static int GetNcard( AstFitsChan *this ){
/*
*+
*  Name:
*     astGetNcard

*  Purpose:
*     Get the value of the Ncard attribute.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "fitschan.h"
*     int astGetNcard( AstFitsChan *this )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function returns the value of the Ncard attribute for the supplied 
*     FitsChan. This is the number of cards currently in the FitsChan.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     The number of cards currently in the FitsChan.

*  Notes:
*     - A value of zero will be returned if an error has already
*     occurred, or if this function should fail for any reason.

*-
*/

/* Local Variables: */
   const char *class;      /* Pointer to class string */
   const char *method;     /* Pointer to method string */
   FitsCard *card0;        /* Pointer to current card on entry */
   int ncard;              /* Number of cards so far */

/* Return zero if an error has already occurred, or no FitsChan was supplied, 
   or the FitsChan is empty. */
   if ( !astOK || !this || !this->head ) return 0;

/* Store the method and object class. */
   method = "astGetNcard";
   class = astGetClass( this );

/* Save a pointer to the current card, and then reset the current card to
   be the first card. */
   card0 = this->card;
   astClearCard( this );

/* Count through the cards in the FitsChan until the end of file is reached. */
   ncard = 0;
   while( astOK && this->card ){

/* Increment the card count and move on to the next card. */
      ncard++;
      MoveCard( this, 1, method, class );

   }

/* Reset the current card to be the original current card. */
   this->card = card0;

/* Return the result. */
   return astOK ? ncard : 0;

}

static void GetNextData( AstChannel *this_channel, int skip, char **name,
                         char **val ) {
/*
*  Name:
*     GetNextData

*  Purpose:
*     Read the next item of data from a data source.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void GetNextData( AstChannel *this, int skip, char **name, char **val )

*  Class Membership:
*     FitsChan member function (over-rides the protected
*     astGetNextData method inherited from the Channel class).

*  Description:
*     This function reads the next item of input data from a data
*     source associated with a FitsChan and returns the result.  It
*     decodes the data item and returns name/value pairs ready for
*     use.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     skip
*        A non-zero value indicates that a new Object is to be read,
*        and that all input data up to the next "Begin" item are to be
*        skipped in order to locate it. This is useful if the data
*        source contains AST objects interspersed with other data (but
*        note that these other data cannot appear inside AST Objects,
*        only between them).
*
*        A zero value indicates that all input data are significant
*        and the next item will therefore be read and an attempt made
*        to interpret it whatever it contains. Any other data
*        inter-mixed with AST Objects will then result in an error.
*     name
*        An address at which to store a pointer to a null-terminated
*        dynamically allocated string containing the name of the next
*        item in the input data stream. This name will be in lower
*        case with no surrounding white space.  It is the callers
*        responsibilty to free the memory holding this string (using
*        astFree) when it is no longer required.
*
*        A NULL pointer value will be returned (without error) to
*        indicate when there are no further input data items to be
*        read.
*     val
*        An address at which to store a pointer to a null-terminated
*        dynamically allocated string containing the value associated
*        with the next item in the input data stream. No case
*        conversion is performed on this string and all white space is
*        potentially significant.  It is the callers responsibilty to
*        free the memory holding this string (using astFree) when it
*        is no longer required.
*
*        The returned pointer will be NULL if an Object data item is
*        read (see the "Data Representation" section).

*  Data Representation:
*     The returned data items fall into the following categories:
*
*     - Begin: Identified by the name string "begin", this indicates
*     the start of an Object definition. The associated value string
*     gives the class name of the Object being defined.
*
*     - IsA: Identified by the name string "isa", this indicates the
*     end of the data associated with a particular class structure
*     within the definiton of a larger Object. The associated value
*     string gives the name of the class whose data have just been
*     read.
*
*     - End: Identified by the name string "end", this indicates the
*     end of the data associated with a complete Object
*     definition. The associated value string gives the class name of
*     the Object whose definition is being ended.
*
*     - Non-Object: Identified by any other name string plus a
*     non-NULL "val" pointer, this gives the value of a non-Object
*     structure component (instance variable). The name identifies
*     which instance variable it is (within the context of the class
*     whose data are being read) and the value is encoded as a string.
*
*     - Object: Identified by any other name string plus a NULL "val"
*     pointer, this identifies the value of an Object structure
*     component (instance variable).  The name identifies which
*     instance variable it is (within the context of the class whose
*     data are being read) and the value is given by subsequent data
*     items (so the next item should be a "Begin" item).

*  Notes:
*     - NULL pointer values will be returned if this function is
*     invoked with the global error status set, or if it should fail
*     for any reason.
*/

/* Local Constants: */
#define BUFF_LEN 100             /* Length of formatting buffer */

/* Local Variables: */
   AstFitsChan *this;            /* Pointer to the FitsChan structure */
   char *keyword;                /* Pointer to current keyword string */
   char *newdata;                /* Pointer to stripped string value */
   char *upq;                    /* Pointer to unprequoted string */
   char buff[ BUFF_LEN + 1 ];    /* Buffer for formatting values */
   const char *class;            /* Pointer to object class */
   const char *method;           /* Pointer to method name */
   int cont;                     /* String ends with an ampersand? */
   int done;                     /* Data item found? */
   int freedata;                 /* Should the data pointer be freed? */
   int i;                        /* Loop counter for keyword characters */
   int len;                      /* Length of current keyword */
   int nc;                       /* Number of characters read by "astSscanf" */
   int nn;                       /* No. of characters after UnPreQuoting */
   int type;                     /* Data type code */
   void *data;                   /* Pointer to current data value */

/* Initialise the returned pointer values. */
   *name = NULL;
   *val = NULL;
   
/* Check the global error status. */
   if ( !astOK ) return;

/* Obtain a pointer to the FitsChan structure. */
   this = (AstFitsChan *) this_channel;

/* Store the method name and object class. */
   method = "astRead";
   class = astGetClass( this );

/* Loop to consider successive cards stored in the FitsChan (starting
   at the "current" card) until a valid data item is read or "end of
   file" is reached. Also quit the loop if an error occurs. */
   done = 0;
   newdata = NULL;
   while ( !done && !astFitsEof( this ) && astOK ){

/* Obtain the keyword string, data type code and data value pointer
   from the current card. */
      keyword = CardName( this );
      type = CardType( this );
      data = CardData( this, NULL );

/* Mark all cards as having been used unless we are skipping over cards which
   may not be related to AST. */
      if( !skip ) MarkCard( this );

/* Ignore comment cards. */
      if ( type != AST__COMMENT ) {

/* Native encoding requires trailing white space to be removed from
   string values (so that null strings can be distinguished from blank
   strings). Do this now. */
         freedata = 0;
         if ( ( type == AST__STRING || type == AST__CONTINUE ) && data ){
            newdata = (char *) astStore( NULL, data, strlen( (char *) data ) + 1 );
            if( newdata ){
               newdata[ ChrLen( data ) ] = 0;            
               data = (void *) newdata;
               freedata = 1;
            }
         }
/* Obtain the keyword length and test the card to identify the type of
   AST data item (if any) that it represents. */
         len = (int) strlen( keyword );

/* "Begin" item. */
/* ------------- */
/* This is identified by a string value and a keyword of the form
   "BEGASTxx", where "xx" are characters encoding a sequence
   number. */
         if ( ( type == AST__STRING ) &&
              ( nc = 0,
                ( 0 == astSscanf( keyword, "BEGAST"
                                        "%*1[" SEQ_CHARS "]"
                                        "%*1[" SEQ_CHARS "]%n", &nc ) )
                && ( nc >= len ) ) ) {

/* Note we have found a data item. */
            done = 1;

/* Set the returned name to "begin" and extract the associated class
   name from the string value. Store both of these in dynamically
   allocated strings. */
            *name = astString( "begin", 5 );
            *val = UnPreQuote( (const char *) data );

/* Indicate that the current card has been used. */
            MarkCard( this );

/* The "begin" item will be preceded by a header of COMMENT cards. Mark
   them as having been used. */
            ComBlock( this, -1, method, class );

/* "IsA" item. */
/* ----------- */
/* This is identified by a string value and a keyword of the form
   "ISAxx", where "xx" are characters encoding a sequence
   number. Don't accept the item if we are skipping over cards looking
   for a "Begin" item. */
         } else if ( !skip &&
                     ( type == AST__STRING ) &&
                     ( nc = 0,
                       ( 0 == astSscanf( keyword,
                                      "ISA"
                                      "%*1[" SEQ_CHARS "]"
                                      "%*1[" SEQ_CHARS "]%n", &nc ) )
                       && ( nc >= len ) ) ) {

/* Note we have found a data item. */
            done = 1;

/* Set the returned name to "isa" and extract the associated class
   name from the string value. Store both of these in dynamically
   allocated strings. */
            *name = astString( "isa", 3 );
            *val = UnPreQuote( (const char *) data );

/* "End" item. */
/* ----------- */
/* This is identified by a string value and a keyword of the form
   "ENDASTxx", where "xx" are characters encoding a sequence
   number. Don't accept the item if we are skipping over cards looking
   for a "Begin" item. */
         } else if ( !skip &&
                     ( type == AST__STRING ) &&
                     ( nc = 0,
                       ( 0 == astSscanf( keyword,
                                      "ENDAST"
                                      "%*1[" SEQ_CHARS "]"
                                      "%*1[" SEQ_CHARS "]%n", &nc ) )
                       && ( nc >= len ) ) ) {

/* Note we have found a data item. */
            done = 1;

/* Set the returned name to "end" and extract the associated class
   name from the string value. Store both of these in dynamically
   allocated strings. */
            *name = astString( "end", 3 );
            *val = UnPreQuote( (const char *) data );

/* The "end" item eill be followed by a footer of COMMENT cards. Mark
   these cards as having been used. */
            ComBlock( this, 1, method, class );

/* Object or data item. */
/* -------------------- */
/* These are identified by a string, int, or double value, and a
   keyword ending in two characters encoding a sequence number. Don't
   accept the item if we are skipping over cards looking for a "Begin"
   item. */
         } else if ( !skip &&
                     ( ( type == AST__STRING ) ||
                       ( type == AST__INT ) ||
                       ( type == AST__FLOAT ) ) &&
                     ( len > 2 ) &&
                     strchr( SEQ_CHARS, keyword[ len - 1 ] ) &&
                     strchr( SEQ_CHARS, keyword[ len - 2 ] ) ) {

/* Note we have found a data item. */
            done = 1;

/* Set the returned name by removing the last two characters from the
   keyword and converting to lower case. Store this in a dynamically
   allocated string. */
            *name = astString( keyword, len - 2 );
            for ( i = 0; ( *name )[ i ]; i++ ) {
               ( *name )[ i ] = tolower( ( *name )[ i ] );
            }

/* Classify the data type. */
            switch ( type ) {

/* If the value is a string, test if it is zero-length. If so, this
   "null" value indicates an Object data item (whose definition
   follows), so leave the returned value pointer as NULL. Otherwise,
   we have a string data item, so extract its value and store it in a
   dynamically allocated string. */
            case AST__STRING:
               if ( *( (char *) data ) ) {

/* A long string value may be continued on subsequent CONTINUE cards. See
   if the current string may be continued. This is the case if the final
   non-blank character (before UnPreQuoting) is an ampersand. */
                  cont = ( ((char *) data)[ ChrLen( data ) - 1 ] == '&' );

/* If the string does not end with an ampersand, just UnPreQUote it and
   return a copy. */
                  if( !cont ) {
                     *val = UnPreQuote( (const char *) data );

/* Otherwise, initialise the returned string to hold a copy of the keyword 
   value. */
                  } else {
                     nc = strlen( (const char *) data );
                     *val = astStore( NULL, (const char *) data, nc + 1 );

/* Loop round reading any subsequent CONTINUE cards. Leave the loop when
   the end-of-file is hit, or an error occurs. */
                     while( cont && MoveCard( this, 1, method, class ) &&
                            astOK ){

/* See if this is a CONTINUE card. If so, get its data pointer. */
                        if( CardType( this ) == AST__CONTINUE ){
                           data = CardData( this, NULL );

/* See if the CONTINUE card ends with an ampersand (i.e. if there is
   a possibility of there being any remaining CONTINUE cards). */
                           cont = ( ( (char *) data)[ ChrLen( data ) - 1 ] == '&' );

/* UnPreQUote it. */
                           upq = UnPreQuote( (const char *) data );
                           if( !astOK ) break;

/* Expand the memory for the returned string to hold the new string. */
                           nn = strlen( upq );
                           *val = astRealloc( *val, nc + nn );
                           if( !astOK ) break;
                           
/* Copy the new string into the expanded memory, so that the first
   character of the new string over-writes the trailing ampersand 
   currently in the buffer. */
                           strcpy( *val + nc - 1, upq );

/* Release the memory holding the UnPreQUoted string . */
                           upq = astFree( upq );

/* Update the current length of the returned string. */
                           nc += nn - 1;

/* Mark the current card as having been read. */
                           MarkCard( this );

/* Report an error if this is not a CONTINUE card. */
                        } else {
                           astError( AST__BADIN, "%s(%s): One or more "
                                     "FITS \"CONTINUE\" cards are missing "
                                     "after the card for keyword \"%s\".",
                                     method, class, keyword );
                        }
                     }
                  }
               }
               break;

/* If the value is an int, format it and store the result in a
   dynamically allocated string. */
            case AST__INT:
               (void) sprintf( buff, "%d", *( (int *) data ) );
               *val = astString( buff, (int) strlen( buff ) );
               break;

/* If the value is a double, format it and store the result in a
   dynamically allocated string. */
            case AST__FLOAT:
               (void) sprintf( buff, "%.*g", DBL_DIG, *( (double *) data ) );
               CheckZero( buff,  *( (double *) data ), 0 );
               *val = astString( buff, (int) strlen( buff ) );
               break;
            }

/* Anything else. */
/* -------------- */
/* If the input line didn't match any of the above and the "skip" flag
   is not set, then report an error.. */
         } else if ( !skip ) {
            astError( AST__BADIN,
                      "%s(%s): Cannot interpret the input data given by "
                      "FITS keyword \"%s\".", method, class, keyword );
         }

/* Free any memory used to hold stripped string data. */
         if( freedata ) newdata = (char *) astFree( (void *) newdata );

      }

/* Increment the current card. */
      MoveCard( this, 1, method, class );
   }

/* If an error occurred, ensure that any allocated memory is freed and
   that NULL pointer values are returned. */
   if ( !astOK ) {
      *name = astFree( *name );
      *val = astFree( *val );
   }

/* Undefine macros local to this function. */
#undef BUFF_LEN
}

static int GetSkip( AstChannel *this_channel ) {
/*
*  Name:
*     GetSkip

*  Purpose:
*     Obtain the value of the Skip attribute for a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int GetSkip( AstChannel *this )

*  Class Membership:
*     FitsChan member function (over-rides the protected astGetSkip
*     method inherited from the Channel class).

*  Description:
*     This function return the (boolean) integer value of the Skip
*     attribute for a FitsChan.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     The Skip attribute value.

*  Notes:
*     - This function modifies the default Skip value from 0 to 1 for
*     the benefit of the FitsChan class. This default value allows the
*     astRead method to skip over unrelated FITS keywords when
*     searching for the next Object to read.
*     - A value of zero will be returned if this function is invoked
*     with the global error status set, or if it should fail for any
*     reason.
*/

/* Local Variables: */
   AstFitsChan *this;            /* Pointer to the FitsChan structure */
   int result;                   /* Result value to return */

/* Check the global error status. */
   if ( !astOK ) return 0;

/* Obtain a pointer to the FitsChan structure. */
   this = (AstFitsChan *) this_channel;

/* If the Skip attribute us set, obtain its value using the parent class
   method. */
   if ( astTestSkip( this ) ) {
      result = (* parent_getskip)( this_channel );

/* Otherwise, supply a default value of 1. */
   } else {
      result = 1;
   }

/* Return the result. */
   return result;
}

static int GetValue( AstFitsChan *this, char *keyname, int type, 
                     void *value, int report, int mark, const char *method, 
                     const char *class ){
/*
*  Name:
*     GetValue

*  Purpose:
*     Obtain a FITS keyword value.

*  Type:
*     Private function.

*  Synopsis:
*     int GetValue( AstFitsChan *this, char *keyname, int type, void *value, 
*                   int report, int mark, const char *method, const char *class )

*  Class Membership:
*     FitsChan

*  Description:
*     This function gets a value for the specified keyword from the
*     supplied FitsChan, and stores it in the supplied buffer. Optionally, 
*     the keyword is marked as having been read into an AST object so that 
*     it is not written out when the FitsChan is deleted.

*  Parameters:
*     this
*        A pointer to the FitsChan containing the keyword values to be
*        read.
*     keyname
*        A pointer to a string holding the keyword name.
*     type
*        The FITS data type in which to return the keyword value. If the
*        stored value is not of the requested type, it is converted if
*        possible. 
*     value
*        A pointer to a buffer of suitable size to receive the keyword
*        value. The supplied value is left unchanged if the keyword is
*        not found.
*     report
*        Should an error be reported if the keyword cannot be found, or
*        cannot be converted to the requested type?
*     mark 
*        Should the card be marked as having been used?
*     method
*        A string holding the name of the calling method.
*     class
*        A string holding the object class.

*  Returned Value:
*     Zero if the keyword does not exist in "this", or cannot be
*     converted to the requested type. One is returned otherwise.

*  Notes:
*     -  A value of zero is returned if an error has already occurred,
*     or if an error occurs within this function.

*/

/* Local Variables: */
   int ret;                           /* Returned value */
   size_t sz;                         /* Data size in bytes */

/* Check the status */
   if( !astOK ) return 0;

/* Attempt to find the supplied keyword. */
   ret = SearchCard( this, keyname, method, class );

/* If the keyword was found, convert the current card's data value and copy 
   it to the supplied buffer. */
   if( ret ){
      if( CnvValue( this, type, value, method ) ) {

/* If required, mark it as having been read into an AST object. */
         if( mark ) MarkCard( this );
    
/* If the value could not be converted to the requested data, type report
   an error if reporting is enabled. */
      } else {
         ret = 0;
         if( report && astOK ){
            astError( AST__FTCNV, "%s(%s): Cannot convert FITS keyword " \
                      "'%s' (value '%s') to %s.", method, class, \
                      keyname, CardData( this, &sz ), type_names[ type ] ); \
         }
      }

/* If the keyword was not found, report an error if "report" is non-zero. */
   } else if( report && astOK ){
      astError( AST__BDFTS, "%s(%s): Unable to find a value for FITS "
                "keyword \"%s\".", method, class, keyname );
   }

/* If an error has occurred, return 0. */
   if( !astOK ) ret = 0;

/* Return the result. */
   return ret;

}

static int GetValue2( AstFitsChan *this1, AstFitsChan *this2, char *keyname, 
                      int type, void *value, int report, const char *method, 
                      const char *class ){
/*
*  Name:
*     GetValue2

*  Purpose:
*     Obtain a FITS keyword value from one of two FitsChans.

*  Type:
*     Private function.

*  Synopsis:
*     int GetValue2( AstFitsChan *this1, AstFitsChan *this2, char *keyname, 
*                    int type, void *value, int report, const char *method, 
*                    const char *class )

*  Class Membership:
*     FitsChan

*  Description:
*     This function attempts to get a value for the specified keyword from 
*     the first supplied FitsChan. If this fails (due to the FitsChan not
*     containing a value for the ketword) then an attempt is made to get
*     a value for the keyword from the second supplied FitsChan.

*  Parameters:
*     this1
*        A pointer to the first FitsChan to be used.
*     this2
*        A pointer to the second FitsChan to be used.
*     keyname
*        A pointer to a string holding the keyword name.
*     type
*        The FITS data type in which to return the keyword value. If the
*        stored value is not of the requested type, it is converted if
*        possible. 
*     value
*        A pointer to a buffer of suitable size to receive the keyword
*        value. The supplied value is left unchanged if the keyword is
*        not found.
*     report
*        Should an error be reported if the keyword cannot be found, or
*        cannot be converted to the requested type?
*     method
*        A string holding the name of the calling method.
*     class
*        A string holding the object class.

*  Returned Value:
*     Zero if the keyword does not exist in either FitsChan, or cannot be
*     converted to the requested type. One is returned otherwise.

*  Notes:
*     -  A value of zero is returned if an error has already occurred,
*     or if an error occurs within this function.
*     -  If the card is found in the first FitsChan, it is not marked as
*     having been used. If the card is found in the second FitsChan, it is 
*     marked as having been used.

*/

/* Local Variables: */
   int ret;                           /* Returned value */

/* Check the status */
   if( !astOK ) return 0;

/* Try the first FitsChan. If this fails try the second. Do not report
   an error if the keyword is not found in the first FitsChan (this will
   be done, if required, once the second FitsChan has been searched). */
   ret = GetValue( this1, keyname, type, value, 0, 0, method, class );
   if( ! ret ) {
      ret = GetValue( this2, keyname, type, value, report, 1, method, class );
   }

/* If an error has occurred, return 0. */
   if( !astOK ) ret = 0;

/* Return the result. */
   return ret;

}

static int HasAIPSSpecAxis( AstFitsChan *this, const char *method, 
                            const char *class  ){
/*
*  Name:
*     HasAIPSSpecAxis

*  Purpose:
*     Does the FitsChan contain an AIPS spectral CTYPE keyword?

*  Type:
*     Private function.

*  Synopsis:
*     int HasAIPSSpecAxis( AstFitsChan *this, const char *method, 
*                          const char *class  )

*  Class Membership:
*     FitsChan

*  Description:
*     This function returns a non-zero value if the FitsCHan contains a
*     CTYPE value which conforms to the non-standard system used by AIPS.

*  Parameters:
*     this
*        A pointer to the FitsChan to be used.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     Non-zero if an AIPS spectral CTYPE keyword was found.

*/

/* Local Variables: */
   char *assys;                   /* AIPS standard of rest type */
   char *astype;                  /* AIPS spectral type */
   char *cval;                    /* Pointer to character string */
   int j;                         /* Current axis index */
   int jhi;                       /* Highest axis index with a CTYPE */
   int jlo;                       /* Lowest axis index with a CTYPE */
   int ret;                       /* Returned value */

/* Initialise */
   ret = 0;

/* Check the status */
   if( !astOK ) return ret;

/* If the FitsChan contains any CTYPE values, convert the bounds from
   one-based to zero-based, and loop round them all. */   
   if( astKeyFields( this, "CTYPE%1d", 1, &jhi, &jlo ) ) {
      jlo--;
      jhi--;
      for( j = jlo; j <= jhi; j++ ) {

/* Get the next CTYPE value. If found, see if it is an AIPS spectral
   CTYPE value. */
         if( GetValue( this, FormatKey( "CTYPE", j + 1, -1, ' ' ),
                       AST__STRING, (void *) &cval, 0, 0, method, 
                       class ) ){
            if( IsAIPSSpectral( cval, &astype, &assys ) ) {
               ret = 1;
               break;
            }
         }
      }
   }

/* If an error has occurred, return 0. */
   if( !astOK ) ret = 0;

/* Return the result. */
   return ret;

}

void astInitFitsChanVtab_(  AstFitsChanVtab *vtab, const char *name ) {
/*
*+
*  Name:
*     astInitFitsChanVtab

*  Purpose:
*     Initialise a virtual function table for a FitsChan.

*  Type:
*     Protected function.

*  Synopsis:
*     #include "fitschan.h"
*     void astInitFitsChanVtab( AstFitsChanVtab *vtab, const char *name )

*  Class Membership:
*     FitsChan vtab initialiser.

*  Description:
*     This function initialises the component of a virtual function
*     table which is used by the FitsChan class.

*  Parameters:
*     vtab
*        Pointer to the virtual function table. The components used by
*        all ancestral classes will be initialised if they have not already
*        been initialised.
*     name
*        Pointer to a constant null-terminated character string which contains
*        the name of the class to which the virtual function table belongs (it 
*        is this pointer value that will subsequently be returned by the Object
*        astClass function).
*-
*/

/* Local Variables: */
   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
   AstChannelVtab *channel;      /* Pointer to Channel component of Vtab */

/* Check the local error status. */
   if ( !astOK ) return;

/* Initialize the component of the virtual function table used by the
   parent class. */
   astInitChannelVtab( (AstChannelVtab *) vtab, name );

/* Store a unique "magic" value in the virtual function table. This
   will be used (by astIsAFitsChan) to determine if an object belongs
   to this class.  We can conveniently use the address of the (static)
   class_init variable to generate this unique value. */
   vtab->check = &class_init;

/* Initialise member function pointers. */
/* ------------------------------------ */
/* Store pointers to the member functions (implemented here) that provide
   virtual methods for this class. */
   vtab->PutCards = PutCards;   
   vtab->PutFits = PutFits;   
   vtab->DelFits = DelFits;   
   vtab->FindFits = FindFits;   
   vtab->KeyFields = KeyFields;
   vtab->Empty = Empty;
   vtab->FitsEof = FitsEof;
   vtab->FitsGetCF = FitsGetCF;
   vtab->FitsGetCI = FitsGetCI;
   vtab->FitsGetF = FitsGetF;
   vtab->FitsGetI = FitsGetI;
   vtab->FitsGetL = FitsGetL;
   vtab->FitsGetS = FitsGetS;
   vtab->FitsGetCN = FitsGetCN;
   vtab->FitsGetCom = FitsGetCom;
   vtab->FitsSetCom = FitsSetCom;
   vtab->FitsSetCF = FitsSetCF;
   vtab->FitsSetCI = FitsSetCI;
   vtab->FitsSetF = FitsSetF;
   vtab->FitsSetI = FitsSetI;
   vtab->FitsSetL = FitsSetL;
   vtab->FitsSetS = FitsSetS;
   vtab->FitsSetCN = FitsSetCN;
   vtab->ClearCard = ClearCard;
   vtab->TestCard = TestCard;
   vtab->SetCard = SetCard;
   vtab->GetCard = GetCard;
   vtab->ClearFitsDigits = ClearFitsDigits;
   vtab->TestFitsDigits = TestFitsDigits;
   vtab->SetFitsDigits = SetFitsDigits;
   vtab->GetFitsDigits = GetFitsDigits;
   vtab->ClearDefB1950 = ClearDefB1950;
   vtab->TestDefB1950 = TestDefB1950;
   vtab->SetDefB1950 = SetDefB1950;
   vtab->GetDefB1950 = GetDefB1950;
   vtab->ClearCarLin = ClearCarLin;
   vtab->TestCarLin = TestCarLin;
   vtab->SetCarLin = SetCarLin;
   vtab->GetCarLin = GetCarLin;
   vtab->ClearIwc = ClearIwc;
   vtab->TestIwc = TestIwc;
   vtab->SetIwc = SetIwc;
   vtab->GetIwc = GetIwc;
   vtab->ClearWarnings = ClearWarnings;
   vtab->TestWarnings = TestWarnings;
   vtab->SetWarnings = SetWarnings;
   vtab->GetWarnings = GetWarnings;
   vtab->GetNcard = GetNcard;
   vtab->GetAllWarnings = GetAllWarnings;
   vtab->ClearEncoding = ClearEncoding;
   vtab->TestEncoding = TestEncoding;
   vtab->SetEncoding = SetEncoding;
   vtab->GetEncoding = GetEncoding;
   vtab->ClearClean = ClearClean;
   vtab->TestClean = TestClean;
   vtab->SetClean = SetClean;
   vtab->GetClean = GetClean;
   vtab->ClearCDMatrix = ClearCDMatrix;
   vtab->TestCDMatrix = TestCDMatrix;
   vtab->SetCDMatrix = SetCDMatrix;
   vtab->GetCDMatrix = GetCDMatrix;

/* Save the inherited pointers to methods that will be extended, and
   replace them with pointers to the new member functions. */
   object = (AstObjectVtab *) vtab;
   channel = (AstChannelVtab *) vtab;

   parent_clearattrib = object->ClearAttrib;
   object->ClearAttrib = ClearAttrib;
   parent_getattrib = object->GetAttrib;
   object->GetAttrib = GetAttrib;
   parent_setattrib = object->SetAttrib;
   object->SetAttrib = SetAttrib;
   parent_testattrib = object->TestAttrib;
   object->TestAttrib = TestAttrib;
 
   parent_write = channel->Write;
   channel->Write = Write;
   parent_read = channel->Read;
   channel->Read = Read;
   parent_getskip = channel->GetSkip;
   channel->GetSkip = GetSkip;
   parent_getfull = channel->GetFull;
   channel->GetFull = GetFull;

   channel->WriteBegin = WriteBegin;
   channel->WriteIsA = WriteIsA;
   channel->WriteEnd = WriteEnd;
   channel->WriteInt = WriteInt;
   channel->WriteDouble = WriteDouble;
   channel->WriteString = WriteString;
   channel->WriteObject = WriteObject;
   channel->GetNextData = GetNextData;

/* Declare the class dump, copy and delete functions.*/
   astSetDump( vtab, Dump, "FitsChan", "I/O channels to FITS files" );
   astSetCopy( (AstObjectVtab *) vtab, Copy );
   astSetDelete( (AstObjectVtab *) vtab, Delete );

/* Indicate that the private functions which navigate the circular linked
   list of FitsCard structures should ignore cards which have been 
   read into an AST object. */
   IgnoreUsed = 1;

/* Indicate that new cards added to the FitsChan should not be marked as
   new. This facility is only used when objects are written to the FitsChan
   using astWrite (so that in appropriately added cards can be identified
   and removed), and is explicitly switched on in astWrite. */
   MarkNew = 0;

}

static void InsCard( AstFitsChan *this, int overwrite, const char *name, 
                     int type, void *data, const char *comment, 
                     const char *method, const char *class ){
/*
*  Name:
*     InsCard

*  Purpose:
*     Inserts a card into a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void InsCard( AstFitsChan *this, int overwrite, const char *name, 
*                   int type, void *data, const char *comment, 
*                   const char *method, const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Either appends a new card to a FitsChan, or over-writes an existing 
*     card, holding the supplied keyword name, value and comment. 

*  Parameters:
*     this
*        Pointer to the FitsChan containing the filters to apply to the
*        keyword name. If a NULL pointer is supplied, no filtering is applied.
*     overwrite
*        If non-zero, the new card over-writes the current card given by 
*        the "Card" attribute, and the current card is incremented so
*        that it refers to the next card. Otherwise, the new card is 
*        inserted in front of the current card and the current card is
*        left unchanged.
*     name
*        Pointer to a string holding the keyword name of the new card.
*     type
*        An integer value representing the data type of the keyword.
*     data
*        Pointer to the data associated with the keyword. 
*     comment
*        Pointer to a null-terminated string holding a comment.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Notes:
*     -  An error is reported if an attempt is made to change the data type 
*     of an existing card.
*     -  If a type of AST__COMMENT is supplied, then any data value (of any 
*     type) associated with an existing card is left unchanged.

*/

/* Local Variables: */
   int flags;             /* Flags to assign to new card */

/* Check the global status. */
   if( !astOK ) return;

/* If the current card is to be over-written, delete the current card (the 
   next card in the list, if any, will become the new current card). */
   if( overwrite ) DeleteCard( this, method, class );

/* If requested, set both NEW flags for the new card. */
   flags = ( MarkNew ) ? ( NEW1 | NEW2 ): 0;

/* Insert the new card into the list, just before the current card. */
   NewCard( this, name, type, data, comment, flags );

}

static int IRAFFromStore( AstFitsChan *this, FitsStore *store, 
                          const char *method, const char *class ){
/*
*  Name:
*     IRAFFromStore

*  Purpose:
*     Store WCS keywords in a FitsChan using FITS-IRAF encoding.

*  Type:
*     Private function.

*  Synopsis:
*     int IRAFFromStore( AstFitsChan *this, FitsStore *store, 
*                        const char *method, const char *class )

*  Class Membership:
*     FitsChan

*  Description:
*     A FitsStore is a structure containing a generalised represention of
*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
*     from a set of FITS header cards (using a specified encoding), or
*     an AST FrameSet. In other words, a FitsStore is an encoding-
*     independant intermediary staging post between a FITS header and 
*     an AST FrameSet.
*
*     This function copies the WCS information stored in the supplied 
*     FitsStore into the supplied FitsChan, using FITS-IRAF encoding.
*
*     IRAF encoding is like FITS-WCS encoding but with the following
*     restrictions:
*
*     1) The celestial projection must not have any projection parameters
*     which are not set to their default values. The one exception to this 
*     is that SIN projections are acceptable if the associated projection 
*     parameter PV<axlat>_1 is zero and PV<axlat>_2 = cot( reference point 
*     latitude). This is encoded using the string "-NCP". The SFL projection 
*     is encoded using the string "-GLS". Note, the original IRAF WCS
*     system only recognised a small subset of the currently available
*     projections, but some more recent IRAF-like software recognizes some 
*     of the new projections included in the FITS-WCS encoding.
*
*     2) The celestial axes must be RA/DEC, galactic or ecliptic.   
*
*     3) LONPOLE and LATPOLE cannot be used. 
*
*     4) Only primary axis descriptions are written out.
*
*     5) RADECSYS is used in place of RADESYS.
*     
*     6) PC/CDELT keywords are not allowed (CD must be used)

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     store
*        Pointer to the FitsStore.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     A value of 1 is returned if succesfull, and zero is returned
*     otherwise.

*/

/* Local Variables: */
   char *comm;         /* Pointer to comment string */
   char *cval;         /* Pointer to string keyword value */
   char combuf[80];    /* Buffer for FITS card comment */
   char lattype[MXCTYPELEN];/* Latitude axis CTYPE */
   char lontype[MXCTYPELEN];/* Longitude axis CTYPE */
   char s;             /* Co-ordinate version character */
   char sign[2];       /* Fraction's sign character */
   double cdelt;       /* A CDELT value */
   double fd;          /* Fraction of a day */
   double mjd99;       /* MJD at start of 1999 */
   double p1, p2;      /* Projection parameters */
   double val;         /* General purpose value */
   int axlat;          /* Index of latitude FITS WCS axis */
   int axlon;          /* Index of longitude FITS WCS axis */
   int axspec;         /* Index of spectral FITS WCS axis */
   int i;              /* Axis index */
   int ihmsf[ 4 ];     /* Hour, minute, second, fractional second */
   int iymdf[ 4 ];     /* Year, month, date, fractional day */
   int j;              /* Axis index */
   int jj;             /* SlaLib status */
   int naxis;          /* No. of axes */
   int ok;             /* Is FitsSTore OK for IRAF encoding? */
   int prj;            /* Projection type */
   int ret;            /* Returned value. */

/* Initialise */
   ret = 0;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* First check that the values in the FitsStore conform to the
   requirements of the IRAF encoding. Assume they do to begin with. */
   ok = 1;

/* Just do primary axes. */
   s = ' '; 

/* Look for the primary celestial and spectral axes. */
   FindLonLatSpecAxes( store, s, &axlon, &axlat, &axspec, method, class );

/* If both longitude and latitude axes are present ...*/
   if( axlon >= 0 && axlat >= 0 ) {

/* Get the CTYPE values for both axes. */
      cval = GetItemC( &(store->ctype), axlon, s, NULL, method, class );
      if( !cval ) return ret;
      strcpy( lontype, cval );

      cval = GetItemC( &(store->ctype), axlat, s, NULL, method, class );
      if( !cval ) return ret;
      strcpy( lattype, cval );

/* Extract the projection type as specified by the last 4 characters 
   in the CTYPE keyword value. */
      prj = astWcsPrjType( lattype + 4 );

/* Check the projection type is OK. Assume not initially. */
      ok = 0;

/* FITS-IRAF cannot handle the AST-specific TPN projection. */
      if( prj == AST__TPN ) {
         ok = 0;

/* SIN projections are handled later. */
      } else if( prj != AST__SIN ){
   
/* There must be no projection parameters. */
         if( GetMaxJM( &(store->pv), ' ' ) == -1 ) ok = 1;

/* Change the new SFL projection code to to the older equivalent GLS */
         if( prj == AST__SFL ){
            (void) strcpy( lontype + 4, "-GLS" );
            (void) strcpy( lattype + 4, "-GLS" );
         }

/* SIN projections are only acceptable if the associated projection
   parameters are both zero, or if the first is zero and the second 
   = cot( reference point latitude )  (the latter case is equivalent to 
   the old NCP projection). */
      } else {
         p1 = GetItem( &( store->pv ), axlat, 1, s, NULL, method, class );
         p2 = GetItem( &( store->pv ), axlat, 2, s, NULL, method, class );
         if( p1 == AST__BAD ) p1 = 0.0;   
         if( p2 == AST__BAD ) p2 = 0.0;   

         val = GetItem( &( store->crval ), axlat, 0, s, NULL, method, class );
         if( val != AST__BAD ) {
            if( p1 == 0.0 ) {
               if( p2 == 0.0 ) {
                  ok = 1;
      
               } else if( fabs( p2 ) >= 1.0E14 && val == 0.0 ){
                  ok = 1;
                  (void) strcpy( lontype + 4, "-NCP" );
                  (void) strcpy( lattype + 4, "-NCP" );
      
               } else if( fabs( p2*tan( AST__DD2R*val ) - 1.0 ) 
                          < 0.01 ){
                  ok = 1;
                  (void) strcpy( lontype + 4, "-NCP" );
                  (void) strcpy( lattype + 4, "-NCP" );
               }
            }
         }
      }

/* Identify the celestial coordinate system from the first 4 characters of the
   longitude CTYPE value. Only RA, galactic longitude, and ecliptic
   longitude can be stored using FITS-IRAF. */
      if( strncmp( lontype, "RA--", 4 ) &&
          strncmp( lontype, "GLON", 4 ) &&
          strncmp( lontype, "ELON", 4 ) ) ok = 0;

/* If the physical Frame requires a LONPOLE or LATPOLE keyword, it cannot
   be encoded using FITS-IRAF. */
      if( GetItem( &(store->latpole), 0, 0, s, NULL, method, class )
          != AST__BAD || 
          GetItem( &(store->lonpole), 0, 0, s, NULL, method, class )
          != AST__BAD ) ok = 0;

/* If there are no celestial axes, the physical Frame can be written out
   using FITS-IRAF. */
   } else {
      ok = 1;
   }

/* Save the number of axes */
   naxis = GetMaxJM( &(store->crpix), ' ' ) + 1;

/* If this is different to the value of NAXIS abort since this encoding
   does not support WCSAXES keyword. */
   if( naxis != store->naxis ) ok = 0;

/* Return if the FitsStore does not conform to IRAF encoding. */
   if( !ok ) return ret;

/* Get and save CRPIX for all pixel axes. These are required, so return
   if they are not available. */
   for( i = 0; i < naxis; i++ ){
      val = GetItem( &(store->crpix), 0, i, s, NULL, method, class );
      if( val == AST__BAD ) return ret;
      sprintf( combuf, "Reference pixel on axis %d", i + 1 );
      SetValue( this, FormatKey( "CRPIX", i + 1, -1, s ), &val, AST__FLOAT, 
                combuf );
   }

/* Get and save CRVAL for all intermediate axes. These are required, so return
   if they are not available. */
   for( j = 0; j < naxis; j++ ){
      val = GetItem( &(store->crval), j, 0, s, NULL, method, class );
      if( val == AST__BAD ) return ret;
      sprintf( combuf, "Value at ref. pixel on axis %d", j + 1 );
      SetValue( this, FormatKey( "CRVAL", j + 1, -1, s ), &val, AST__FLOAT, 
                combuf );
   }

/* Get and save CTYPE for all intermediate axes. These are required, so return
   if they are not available. Use the potentially modified versions saved
   above for the celestial axes. */
   for( i = 0; i < naxis; i++ ){
      if( i == axlat ) {
         cval = lattype;
      } else if( i == axlon ) {
         cval = lontype;
      } else {
         cval = GetItemC( &(store->ctype), i, s, NULL, method, class );
         if( !cval ) return ret;
      }
      comm = GetItemC( &(store->ctype_com), i, s, NULL, method, class );
      if( !comm ) {            
         sprintf( combuf, "Type of co-ordinate on axis %d", i + 1 );
         comm = combuf;
      }
      SetValue( this, FormatKey( "CTYPE", i + 1, -1, s ), &cval, AST__STRING, 
                comm );
   }

/* CD matrix (the product of the CDELT and PC matrices). */
   for( i = 0; i < naxis; i++ ){
      cdelt = GetItem( &(store->cdelt), i, 0, s, NULL, method, class );
      if( cdelt == AST__BAD ) cdelt = 1.0;

      for( j = 0; j < naxis; j++ ){
         val = GetItem( &(store->pc), i, j, s, NULL, method, class );
         if( val == AST__BAD ) val = ( i == j ) ? 1.0 : 0.0;
         val *= cdelt;

         if( val != 0.0 ) {
             SetValue( this, FormatKey( "CD", i + 1, j + 1, s ), &val, 
                       AST__FLOAT, "Transformation matrix element" );
         }
      }
   }

/* Get and save CUNIT for all intermediate axes. These are NOT required, so 
   do not return if they are not available. */
   for( i = 0; i < naxis; i++ ){
      cval = GetItemC( &(store->cunit), i, s, NULL, method, class );
      if( cval ) {
         sprintf( combuf, "Units for axis %d", i + 1 );
         SetValue( this, FormatKey( "CUNIT", i + 1, -1, s ), &cval, AST__STRING, 
                   combuf );
      }
   }

/* Get and save RADECSYS. This is NOT required, so do not return if it is 
   not available. */
   cval = GetItemC( &(store->radesys), 0, s, NULL, method, class );
   if( cval ) SetValue( this, "RADECSYS", &cval, AST__STRING, 
                        "Reference frame for RA/DEC values" );

/* Reference equinox */
   val = GetItem( &(store->equinox), 0, 0, s, NULL, method, class );
   if( val != AST__BAD ) SetValue( this, "EQUINOX", &val, AST__FLOAT, 
                                   "Epoch of reference equinox" );

/* Date of observation */
   val = GetItem( &(store->mjdobs), 0, 0, s, NULL, method, class );
   if( val != AST__BAD ) {

/* The format used for the DATE-OBS keyword depends on the value of the
   keyword. For DATE-OBS < 1999.0, use the old "dd/mm/yy" format.
   Otherwise, use the new "ccyy-mm-ddThh:mm:ss[.ssss]" format. */
      slaCaldj( 99, 1, 1, &mjd99, &jj );
      if( val < mjd99 ) {

         slaDjcal( 0, val, iymdf, &jj );
         sprintf( combuf, "%2.2d/%2.2d/%2.2d", iymdf[ 2 ], iymdf[ 1 ], 
                  iymdf[ 0 ] - ( ( iymdf[ 0 ] > 1999 ) ? 2000 : 1900 ) ); 

      } else {

         slaDjcl( val, iymdf, iymdf+1, iymdf+2, &fd, &jj );
         slaDd2tf( 3, fd, sign, ihmsf );
         sprintf( combuf, "%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d.%3.3d",
                  iymdf[0], iymdf[1], iymdf[2], ihmsf[0], ihmsf[1],
                  ihmsf[2], ihmsf[3] ); 
      }

/* Now store the formatted string in the FitsChan. */
      cval = combuf;
      SetValue( this, "DATE-OBS", (void *) &cval, AST__STRING,
                "Date of observation" );
   }

/* If we get here we have succeeded. */
   ret = 1;

/* Return zero or ret depending on whether an error has occurred. */
   return astOK ? ret : 0;
}

static int IsMapLinear( AstMapping *map, const double lbnd_in[], 
                        const double ubnd_in[], int coord_out ) {
/*
*  Name:
*     IsMapLinear

*  Purpose:
*     See if a specified Mapping output is linearly related to the
*     Mapping inputs.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int IsMapLinear( AstMapping *map, const double lbnd_in[], 
*                      const double ubnd_in[], int coord_out ) 

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Returns a flag indicating if the specified output of the supplied 
*     Mapping is a linear function of the Mapping inputs. A set of output
*     positions are created which are evenly spaced along the specified
*     output coordinate. The spacing is chosen so that the entire range
*     of the output coordinate is covered in 20 steps. The other output
*     coordinates are held fixed at arbitrary values (actually, values
*     at which the specified output coordinate achieves its minimum value).
*     This set of output positions is transformed into the corresponding
*     set of input coordinates using the inverse of the supplied Mapping.
*     A least squares linear fit is then made which models each input 
*     coordinate as a linear function of the specified output coordinate.
*     The residual at every point in this fit must be less than some
*     small fraction of the total range of the corresponding input
*     coordinate for the Mapping to be considered linear.

*  Parameters:
*     map
*        Pointer to the Mapping.
*     lbnd_in
*        Pointer to an array of double, with one element for each
*        Mapping input coordinate. This should contain the lower bound
*        of the input box in each input dimension.
*     ubnd_in
*        Pointer to an array of double, with one element for each
*        Mapping input coordinate. This should contain the upper bound
*        of the input box in each input dimension.
*     coord_out
*        The zero-based index of the Mapping output which is to be checked.

*  Returned Value:
*     Non-zero if the specified Mapping output is linear. Zero otherwise.

*/

/* Local Constants: */
#define NP 20   

/* Local Variables: */
   AstPointSet *pset1;
   AstPointSet *pset2;
   double **ptr1;
   double **ptr2;
   double *p;
   double *s;
   double *xl;
   double c;
   double d;
   double delta;
   double in_lbnd;
   double in_ubnd;
   double lbnd_out;
   double m;
   double p0;
   double pv;
   double sn;
   double sp;
   double sps;
   double ss2;
   double ss;
   double sv;
   double tol;
   double ubnd_out;
   int i;
   int j;
   int nin;
   int nout;
   int ret;              /* Linear fit successful? */

/* Initialise */
   ret = 0;

/* Check inherited status */
   if( !astOK ) return ret;

/* Check the Mapping is defined in both directions. */
   if( astGetTranForward( map ) && astGetTranInverse( map ) ) {

/* Allocate resources. */
      nin = astGetNin( map );
      nout = astGetNout( map );
      xl = astMalloc( sizeof( double )*(size_t) nin );
      pset1 = astPointSet( NP, nin, "" );
      ptr1 = astGetPoints( pset1 );
      pset2 = astPointSet( NP, nout, "" );
      ptr2 = astGetPoints( pset2 );

/* Find the upper and lower bounds on the specified Mapping output. This also
   returns the input coords of a point at which the required output has its
   lowest value. */
      astMapBox( map, lbnd_in, ubnd_in, 1, coord_out, &lbnd_out, &ubnd_out,
                 xl, NULL );

/* If the bounds are equal, we cannot use them. In this case create new 
   bounds. */
      if( EQUAL( lbnd_out, ubnd_out ) ) {
         m = 0.5*( lbnd_out + ubnd_out );             
         if( fabs( m ) > 1.0E-15 ) {
            lbnd_out = 0.9*m;
            ubnd_out = 1.1*m;
         } else {
            lbnd_out = -1.0;
            ubnd_out = 1.0;
         }
      }

/* Check pointers can be used safely. */
      if( astOK ) {

/* Transform the input position returned by astMapBox using the supplied
   Mapping to get the corresponding output position. */
         for( i = 0; i < nin; i++ ) ptr1[ i ][ 0 ] = xl[ i ];
         astTransform( map, pset1, 1, pset2 );

/* Now create a set of NP points evenly spaced in output coordinates. The
   first point is at the output position found above. Each subsequent
   point is incremented by a fixed amount along the specified output
   coordinate (the values on all other output coordinates is held fixed). */
         delta = ( ubnd_out - lbnd_out )/ ( NP - 1 );
         for( i = 0; i < nout; i++ ){
            p = ptr2[ i ];
            if( i == coord_out ) {
               for( j = 0; j < NP; j++ ) *(p++) = lbnd_out + j*delta;
            } else {
               p0 = p[ 0 ];
               for( j = 0; j < NP; j++ ) *(p++) = p0;
            }
         }

/* Transform these output positions into input positions using the
   inverse Mapping. */
         astTransform( map, pset2, 0, pset1 );

/* Do a least squares fit to each input coordinate. Each fit gives the
   corresponding input coordinate value as a linear function of the
   specified output coordinate value. Note, linear function should never
   produce bad values so abort if a bad value is found. */
         ret = 1;
         s = ptr2[ coord_out ];
         for( i = 0; i < nin; i++ ) {
            p = ptr1[ i ];

/* Form the required sums. Also find the largest and smallest input
   coordinate value achieved. */
            sp = 0.0;
            ss = 0.0;
            sps = 0.0;
            sn = 0.0;
            ss2 = 0.0;
            in_lbnd = DBL_MAX;
            in_ubnd = DBL_MIN;

            for( j = 0; j < NP; j++ ) {
               sv = s[ j ];
               pv = p[ j ];
               if( pv != AST__BAD && sv != AST__BAD ) {
                  sp += pv;
                  ss += sv;
                  sps += pv*sv;
                  sn += 1.0;
                  ss2 += sv*sv;
                  if( pv < in_lbnd ) in_lbnd = pv;
                  if( pv > in_ubnd ) in_ubnd = pv;
               } else {
                  sn = 0.0;
                  break;
               }
            }

/* Ignore input axes which are independant of the output axis. */
            if( !EQUAL( in_lbnd, in_ubnd ) ) {

/* Calculate the constants "input coord = m*output coord + c" */
               d = ss*ss - sn*ss2;
               if( sn > 0.0 && d != 0.0 ) {
                  m = ( sp*ss - sps*sn )/d;
                  c = ( sps*ss - sp*ss2 )/d;

/* Subtract off the fit value form the "p" values to get the residuals of
   the fit. */
                  for( j = 0; j < NP; j++ ) p[ j ] -= m*s[ j ] + c;

/* We now do a least squares fit to the residuals. This second fit is done
   because the first least squares fit sometimes leaves the residuals with a
   distinct non-zero gradient. We do not need to worry about bad values
   here since we have checked above that there are no bad values. Also we
   do not need to recalculate sums which only depend on the "s" values since
   they have not changed. */
                  sp = 0.0;
                  sps = 0.0;
                  for( j = 0; j < NP; j++ ) {
                     pv = p[ j ];
                     sp += pv;
                     sps += pv*s[ j ];
                  }

/* Find the constants in "input residual = m*output coord + c" equation. */
                  m = ( sp*ss - sps*sn )/d;
                  c = ( sps*ss - sp*ss2 )/d;

/* Subtract off the fit value form the "p residuals" values to get the 
   residual redisuals of the fit. */
                  for( j = 0; j < NP; j++ ) p[ j ] -= m*s[ j ] + c;

/* The requirement for a linear relationship is that the absolute residual 
   between the input coord produced by the above linear fit and the input 
   coord produced by the actual Mapping should be less than some small
   fraction of the total range of input coord value, at every point. Test 
   this. */
                  tol = 1.0E-8*( in_ubnd - in_lbnd );
                  for( j = 0; j < NP; j++ ) {
                     if( fabs( p[ j ] ) > tol ) {
                        ret = 0;
                        break;
                     }
                  }
   
               } else {
                  ret = 0;
               }
            }
            if( !ret ) break;
         }
      }

/* Free resources. */
      pset1 = astAnnul( pset1 );
      pset2 = astAnnul( pset2 );
      xl = astFree( xl );
   }

/* Return the answer. */
   return ret;
}

static int IsAIPSSpectral( const char *ctype, char **wctype, char **wspecsys ){
/*
*  Name:
*     IsAIPSSpectral

*  Purpose:
*     See if a given CTYPE value describes a FITS-AIPS spectral axis.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int IsAIPSSpectral( const char *ctype, char **wctype, char **wspecsys )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The given CTYPE value is checked to see if it conforms to the
*     requirements of a spectral axis CTYPE value as specified by
*     FITS-AIPS encoding. If so, the equivalent FITS-WCS CTYPE and
*     SPECSYS values are returned.

*  Parameters:
*     ctype
*        Pointer to a null terminated string holding the CTYPE value to
*        check.
*     wctype
*        The address of a location at which to return a pointer to a
*        static string holding the corresponding FITS-WCS CTYPE value. A
*        NULL pointer is returned if the supplied CTYPE string is not an
*        AIPS spectral CTYPE value.
*     wspecsys
*        The address of a location at which to return a pointer to a
*        static string holding the corresponding FITS-WCS SPECSYS value. A
*        NULL pointer is returned if the supplied CTYPE string is not an
*        AIPS spectral CTYPE value.

*  Retuned Value:
*     Non-zero fi the supplied CTYPE was an AIPS spectral CTYPE value.

*/

/* Local Variables: */
   int ret;
   
/* Initialise */
   ret = 0;
   *wctype = NULL;
   *wspecsys = NULL;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Translate AIPS spectral CTYPE values to FITS-WCS paper III equivalents. 
   These are of the form AAAA-BBB, where "AAAA" can be "FREQ", "VELO" (=VRAD!) 
   or "FELO" (=VOPT-F2W), and BBB can be "LSR", "LSD", "HEL" (=*Bary*centric!) 
   or "GEO". */
   if( !strncmp( ctype, "FREQ", 4 ) ){
      *wctype = "FREQ    ";
   } else if( !strncmp( ctype, "VELO", 4 ) ){
      *wctype = "VRAD    ";
   } else if( !strncmp( ctype, "FELO", 4 ) ){
      *wctype = "VOPT-F2W";
   }

   if( !strcmp( ctype + 4, "-LSR" ) ){
      *wspecsys = "LSRK";
   } else if( !strcmp( ctype + 4, "LSRK" ) ){
      *wspecsys = "LSRK";
   } else if( !strcmp( ctype + 4, "-LSRK" ) ){
      *wspecsys = "LSRK";
   } else if( !strcmp( ctype + 4, "-LSD" ) ){
      *wspecsys = "LSRD";
   } else if( !strcmp( ctype + 4, "-HEL" ) ){
      *wspecsys = "BARYCENT";
   } else if( !strcmp( ctype + 4, "-GEO" ) ){
      *wspecsys = "GEOCENTR";
   }

   if( *wctype && *wspecsys ) {
      ret = 1;
   } else {
      *wctype = NULL;
      *wspecsys = NULL;
   }

/* Return the result. */
   return ret;
}

static const char *IsSpectral( const char *ctype, char stype[5], char algcode[5] ) {
/*
*  Name:
*     IsSpectral

*  Purpose:
*     See if a given FITS-WCS CTYPE value describes a spectral axis.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     char *IsSpectral( const char *ctype, char stype[5], char algcode[5] )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The given CTYPE value is checked to see if it conforms to the
*     requirements of a spectral axis CTYPE value as specified by
*     FITS-WCS paper 3. If so, the spectral system and algorithm codes
*     are extracted from it and returned, together with the default units 
*     for the spectral system.

*  Parameters:
*     ctype
*        Pointer to a null terminated string holding the CTYPE value to
*        check.
*     stype
*        An array in which to return the null-terminated spectral system type 
*        (e.g. "FREQ", "VELO", "WAVE", etc). A null string is returned if 
*        the CTYPE value does not describe a spectral axis.
*     algcode 
*        An array in which to return the null-terminated algorithm code
*        (e.g. "-LOG", "", "-F2W", etc). A null string is returned if the
*        spectral axis is linear. A null string is returned if the CTYPE 
*        value does not describe a spectral axis.

*  Retuned Value:
*     A point to a static string holding the default units associated
*     with the spectral system specified by the supplied CTYPE value.
*     NULL is returned if the CTYPE value does not describe a spectral
*     axis.

*  Notes: 
*     - The axis is considered to be a spectral axis if the first 4
*     characters form one of the spectral system codes listed in FITS-WCS
*     paper 3. The algorithm code is not checked, except to ensure that
*     it begins with a minus sign, or is blank.
*     - A NULL pointer is returned if an error has already occurred.

*/

/* Local Variables: */
   static const char *ret;
   
/* Initialise */
   ret = NULL;
   stype[ 0 ] = 0;
   algcode[ 0 ] = 0;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Copy the first 4 characters (the coordinate system described by the
   axis) into a null-terminated buffer. */
   strncpy( stype, ctype, 4 );
   stype[ 4 ] = 0;
   stype[ astChrLen( stype ) ] = 0;

/* Copy the next 4 characters in the CTYPE value (the algorithm code) into a 
   null-terminated buffer. */
   strncpy( algcode, ctype + 4, 4 );
   algcode[ 4 ] = 0;
   algcode[ astChrLen( algcode ) ] = 0;

/* See if the first 4 characters of the CTYPE value form one of the legal
   spectral coordinate type codes listed in FITS-WCS Paper III. Also note
   the default units associated with the system. */
   if( !strcmp( stype, "FREQ" ) ) {
      ret = "Hz";

   } else if( !strcmp( stype, "ENER" ) ) {
      ret = "J";

   } else if( !strcmp( stype, "WAVN" ) ) {
      ret = "/m";

   } else if( !strcmp( stype, "VRAD" ) ) {
      ret = "m/s";

   } else if( !strcmp( stype, "WAVE" ) ) {
      ret = "m";

   } else if( !strcmp( stype, "VOPT" ) ) {
      ret = "m/s";

   } else if( !strcmp( stype, "ZOPT" ) ) {
      ret = "";

   } else if( !strcmp( stype, "AWAV" ) ) {
      ret = "m";

   } else if( !strcmp( stype, "VELO" ) ) {
      ret = "m/s";

   } else if( !strcmp( stype, "BETA" ) ) {
      ret = "";
   }

/* Also check that the remaining part of CTYPE (the algorithm code) begins 
   with a minus sign or is blank. */
   if( algcode[ 0 ] != '-' && strlen( algcode ) > 0 ) ret = NULL;
   
/* Return null strings if the axis is not a spectral axis. */
   if( ! ret ) {
      stype[ 0 ] = 0;
      algcode[ 0 ] = 0;
   }

/* Return the result. */
   return ret;
}

static AstMapping *LinearWcs( FitsStore *store, int i, char s, 
                              const char *method, const char *class   ) {
/*
*  Name:
*     LinearWcs

*  Purpose:
*     Create a Mapping describing a FITS-WCS linear algorithm

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     AstMapping *LinearWcs( FitsStore *store, int i, char s, 
*                            const char *method, const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function uses the contents of the supplied FitsStore to create
*     a Mapping which goes from Intermediate World Coordinate (known as "w" 
*     in the context of FITS-WCS paper III) to a linearly related axis.
*
*     The returned Mapping is a ShiftMap which simply adds on the value of
*     CRVALi.

*  Parameters:
*     store
*        Pointer to the FitsStore structure holding the values to use for 
*        the WCS keywords. 
*     i 
*        The zero-based index of the spectral axis within the FITS header
*     s
*        A character identifying the co-ordinate version to use. A space 
*        means use primary axis descriptions. Otherwise, it must be an 
*        upper-case alphabetical characters ('A' to 'Z').
*     method
*        A pointer to a string holding the name of the calling method.
*        This is used only in the construction of error messages.
*     class
*        A pointer to a string holding the class of the object being
*        read. This is used only in the construction of error messages.

*  Returned Value:
*     A pointer to a Mapping, or NULL if an error occurs.

*/

/* Local Variables: */
   AstMapping *ret;
   double crv;

/* Check the global status. */
   ret = NULL;
   if( !astOK ) return ret;

/* Get the CRVAL value for the specified axis. */
   crv = GetItem( &(store->crval), i, 0, s, NULL, method, class );
   if( crv == AST__BAD ) crv = 0.0;

/* Create a 1D ShiftMap which adds this value onto the IWCS value. */
   if( crv != 0.0 ) {
      ret = (AstMapping *) astShiftMap( 1, &crv, "" );
   } else {
      ret = (AstMapping *) astUnitMap( 1, "" );
   }
   return ret;
}

static AstMapping *LogAxis( AstMapping *map, int iax, int nwcs, double *lbnd_p, 
                            double *ubnd_p, double crval ){
/*
*  Name:
*     LogAxes

*  Purpose:
*     Test a Frame axis to see if it logarithmically spaced in pixel coords.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     AstMapping *LogAxis( AstMapping *map, int iax, int nwcs, double *lbnd_p, 
*                          double *ubnd_p, double crval )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     A specified axis of the supplied Mappinhg is tested to see if it
*     corresponds to the form
*
*        S = Sr.exp( w/Sr )
*
*     where "w" is one of the Mapping inputs, "S" is the specified
*     Mapping output, and "Sr" is the supplied value of "crval". This
*     is the form for a FITS log axis as defined in FITS-WCS paper III.
*     
*     If the above test is passed, a Mapping is returned from "S" to "w"
*     (the inverseof the above expression).

*  Parameters:
*     map
*        Pointer to the Mapping. This will usually be a Mapping from
*        pixel coords to WCS coords.
*     iax
*        The index of the output of "map" which correspoinds to "S".
*     nwcs
*        The number of outputs from "map".
*     lbnd_p
*        Pointer to an array of double, with one element for each
*        Mapping input coordinate. This should contain the lower bound
*        of the input pixel box in each input dimension.
*     ubnd_p
*        Pointer to an array of double, with one element for each
*        Mapping input coordinate. This should contain the upper bound
*        of the input pixel box in each input dimension.
*     crval
*        The reference value ("Sr") to use. Must not be zero.

*  Returned Value:
*     If the specified axis is logarithmically spaced, a Mapping with
*     "nwcs" inputs and "nwcs" outputs is returned. This Mapping transforms
*     its "iax"th input using the transformation:
*
*        w = Sr.Log( S/Sr )
*
*     (where "S" is the Mapping is the "iax"th input and "w" is the
*     "iax"th output). Other inputs are copied to the corresponding
*     output without change. NULL is returned if the specified axis is 
*     not logarithmically spaced.
*/

/* Local Variables: */
   AstMapping *result;     /* Returned Mapping */
   AstMapping *tmap0;      /* A temporary Mapping */
   AstMapping *tmap1;      /* A temporary Mapping */
   AstMapping *tmap2;      /* A temporary Mapping */
   AstMapping *tmap3;      /* A temporary Mapping */
   AstMapping *tmap4;      /* A temporary Mapping */
   const char *fexps[ 1 ]; /* Forward MathMap expressions */
   const char *iexps[ 1 ]; /* Inverse MathMap expressions */

/* Initialise */
   result = NULL;

/* Check the inherited status and crval value. */
   if( !astOK || crval == 0.0 ) return result;

/* If the "log" algorithm is appropriate, the supplied axis (s) is related 
   to pixel coordinate (p) by s = Sr.EXP( a*p - b ). If this is the case, 
   then the log of s will be linearly related to pixel coordinates. To test 
   this, we create a CmpMap which produces log(s). */
   fexps[ 0 ] = "logs=log(s)";
   iexps[ 0 ] = "s=exp(logs)";
   tmap1 = (AstMapping *) astMathMap( 1, 1, 1, fexps, 1, iexps, 
                                      "simpfi=1,simpif=1" );
   tmap2 = AddUnitMaps( tmap1, iax, nwcs );
   
   tmap0 = (AstMapping *) astCmpMap( map, tmap2, 1, "" );
   tmap2 = astAnnul( tmap2 );

/* See if this Mapping is linear. */
   if( IsMapLinear( tmap0, lbnd_p, ubnd_p, iax ) ) {

/* Create the Mapping which defines the IWC axis. This is the Mapping from 
   WCS to IWCS - "W = Sr.log( S/Sr )". Other axes are left unchanged by the 
   Mapping. The IWC axis has the same axis index as the WCS axis. */
      tmap2 = (AstMapping *) astZoomMap( 1, 1.0/crval, "" );
      tmap3 = (AstMapping *) astCmpMap( tmap2, tmap1, 1, "" );
      tmap2 = astAnnul( tmap2 );
      tmap2 = (AstMapping *) astZoomMap( 1, crval, "" );
      tmap4 = (AstMapping *) astCmpMap( tmap3, tmap2, 1, "" );
      tmap3 = astAnnul( tmap3 );
      tmap2 = astAnnul( tmap2 );
      result = AddUnitMaps( tmap4, iax, nwcs );
      tmap4 = astAnnul( tmap4 );   
   }

/* Free resources. */
   tmap0 = astAnnul( tmap0 );
   tmap1 = astAnnul( tmap1 );

/* Return the result. */
   return result;
}

static AstMapping *LogWcs( FitsStore *store, int i, char s, 
                           const char *method, const char *class   ) {
/*
*  Name:
*     LogWcs

*  Purpose:
*     Create a Mapping describing a FITS-WCS logarithmic algorithm

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     AstMapping *LogWcs( FitsStore *store, int i, char s, 
*                         const char *method, const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function uses the contents of the supplied FitsStore to create
*     a Mapping which goes from Intermediate World Coordinate (known as "w" 
*     in the context of FITS-WCS paper III) to a logarthmic version of w
*     called "S" given by:
*
*     S = Sr.exp( w/Sr )
*
*     where Sr is the value of S corresponding to w=0.

*  Parameters:
*     store
*        Pointer to the FitsStore structure holding the values to use for 
*        the WCS keywords. 
*     i 
*        The zero-based index of the axis within the FITS header
*     s
*        A character identifying the co-ordinate version to use. A space 
*        means use primary axis descriptions. Otherwise, it must be an 
*        upper-case alphabetical characters ('A' to 'Z').
*     method
*        A pointer to a string holding the name of the calling method.
*        This is used only in the construction of error messages.
*     class
*        A pointer to a string holding the class of the object being
*        read. This is used only in the construction of error messages.

*  Returned Value:
*     A pointer to a Mapping, or NULL if an error occurs.

*/

/* Local Variables: */
   AstMapping *ret;
   char forexp[ 12 + DBL_DIG*2 ];
   char invexp[ 12 + DBL_DIG*2 ];
   const char *fexps[ 1 ];
   const char *iexps[ 1 ];
   double crv;

/* Check the global status. */
   ret = NULL;
   if( !astOK ) return ret;

/* Get the CRVAL value for the specified axis. Use a default of zero. */
   crv = GetItem( &(store->crval), i, 0, s, NULL, method, class );
   if( crv == AST__BAD ) crv = 0.0;

/* Create the MathMap, if possible. */
   if( crv != 0.0 ) {
      sprintf( forexp, "s=%.*g*exp(w/%.*g)", DBL_DIG, crv, DBL_DIG, crv );
      sprintf( invexp, "w=%.*g*log(s/%.*g)", DBL_DIG, crv, DBL_DIG, crv );
      fexps[ 0 ] = forexp;
      iexps[ 0 ] = invexp;
      ret = (AstMapping *) astMathMap( 1, 1, 1, fexps, 1, iexps, "simpfi=1,simpif=1" );
   }

/* Return the result */
   return ret;
}

static void MakeBanner( const char *prefix, const char *middle,
                        const char *suffix,
                        char banner[ FITSCARDLEN -
                                     FITSNAMLEN + 1 ] ) {
/*
*  Name:
*     MakeBanner

*  Purpose:
*     Create a string containing a banner comment.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void MakeBanner( const char *prefix, const char *middle,
*                      const char *suffix,
*                      char banner[ FITSCARDLEN - FITSNAMLEN + 1 ] )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function creates a string which can be written as a FITS
*     comment card to produce a banner heading (or tail) for an AST
*     Object when it is written to a FitsChan. The banner will occupy
*     the maximum permitted width for text in a FITS comment card.

*  Parameters:
*     prefix
*        A pointer to a constant null-terminated string containing the
*        first part of the text to appear in the banner.
*     middle
*        A pointer to a constant null-terminated string containing the
*        second part of the text to appear in the banner.
*     suffix
*        A pointer to a constant null-terminated string containing the
*        third part of the text to appear in the banner.
*     banner
*        A character array to receive the null-terminated result string.

*  Notes:
*     - The text to appear in the banner is constructed by
*     concatenating the three input strings supplied.
*/

/* Local Variables: */
   char token[] = "AST";         /* Identifying token */
   int i;                        /* Loop counter for input characters */
   int len;                      /* Number of output characters */
   int ltok;                     /* Length of token string */
   int mxlen;                    /* Maximum permitted output characters */
   int start;                    /* Column number where text starts */

/* Check the global error status. */
   if ( !astOK ) return;

/* Calculate the maximum number of characters that the output banner
   can hold and the length of the token string. */
   mxlen = FITSCARDLEN - FITSNAMLEN;
   ltok = (int) strlen( token );

/* Calculate the column in which to start the text, so that it is
   centred in the banner (with 4 non-text characters on each side). */
   start = ltok + 2 + ( mxlen - ltok - 1 -
                        (int) ( strlen( prefix ) +
                                strlen( middle ) +
                                strlen( suffix ) ) - 1 - ltok ) / 2;
   if ( start < ltok + 2 ) start = ltok + 2;

/* Start building the banner with the token string. */
   len = 0;
   for ( i = 0; token[ i ] && ( len < mxlen ); i++ ) {
      banner[ len++ ] = token[ i ];
   }

/* Then pad with spaces up to the start of the text. */
   while ( len < start - 1 ) banner[ len++ ] = ' ';

/* Insert the prefix data, truncating it if it is too long. */
   for ( i = 0; prefix[ i ] && ( len < mxlen - ltok - 1 ); i++ ) {
      banner[ len++ ] = prefix[ i ];
   }

/* Insert the middle data, truncating it if it is too long. */
   for ( i = 0; middle[ i ] && ( len < mxlen - ltok - 1 ); i++ ) {
      banner[ len++ ] = middle[ i ];
   }

/* Insert the suffix data, truncating it if it is too long. */
   for ( i = 0; suffix[ i ] && ( len < mxlen - ltok - 1 ); i++ ) {
      banner[ len++ ] = suffix[ i ];
   }

/* Pad the end of the text with spaces. */
   while ( len < mxlen - ltok ) banner[ len++ ] = ' ';

/* Finish the banner with the token string. */
   for ( i = 0; token[ i ] && ( len < mxlen ); i++ ) {
      banner[ len++ ] = token[ i ];
   }

/* Terminate the output string. */
   banner[ len ] = '\0';
}

static AstFrameSet *MakeFitsFrameSet( AstFrameSet *fset, int ipix, int iwcs ) {
/*
*  Name:
*     MakeFitsFrameSet

*  Purpose:
*     Create a FrameSet which conforms to the requirements of the FITS-WCS 
*     papers.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     AstFrameSet *MakeFitsFrameSet( AstFrameSet *fset, int ipix, int iwcs )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function constructs a new FrameSet holding the pixel and WCS 
*     Frames from the supplied FrameSet, but optionally extends the WCS
*     Frame to include any extra axes needed to conform to the FITS model. 
*     Currently, this function does the following:
*
*     - if the WCS Frame contains a spectral axis with a defined celestial 
*     reference position (SpecFrame attributes RefRA and RefDec), then 
*     it ensures that the WCS Frame also contains a pair of celestial
*     axes (such axes are added if they do not already exist within the 
*     supplied WCS Frame). The pixel->WCS Mapping is adjusted accordingly.
*
*     - if the WCS Frame contains a spectral axis and a pair of celestial 
*     axes, then the SpecFrame attributes RefRA and RefDec are set to the
*     reference position defined by the celestial axes. The pixel->WCS
*     Mapping is adjusted accordingly.
*
*     - NULL is returned if the WCS Frame contains more than one spectral
*     axis.
*
*     - NULL is returned if the WCS Frame contains more than one pair of
*     celestial axes.

*  Parameters:
*     fset
*        The FrameSet to check.
*     ipix
*        The index of the FITS pixel Frame within "fset".
*     iwcs
*        The index of the WCS Frame within "fset".

*  Returned Value:
*     A new FrameSet which confoms to the requirements of the FITS-WCS
*     papers. The base Frame in this FrameSet will be the FITS pixel
*     Frame, and the current Frame will be the WCS Frame. NULL is
*     returned if an error has already occurred, or if the FrameSet cannot 
*     be produced for any reason.

*/

/* Local Variables: */
   AstFitsChan *fc;        /* Pointer to temporary FitsChan */
   AstFrame *pframe;       /* Pointer to the primary Frame */
   AstFrame *pixfrm;       /* Pointer to the FITS pixel Frame */
   AstFrame *tfrm0;        /* Pointer to a temporary Frame */
   AstFrame *tfrm;         /* Pointer to a temporary Frame */
   AstFrame *wcsfrm;       /* Pointer to the FITS WCS Frame */
   AstFrameSet *ret;       /* The returned FrameSet */
   AstFrameSet *tfs;       /* Pointer to a temporary FrameSet */
   AstMapping *map1;       /* Pointer to pre-WcsMap Mapping */
   AstMapping *map3;       /* Pointer to post-WcsMap Mapping */
   AstMapping *map;        /* Pointer to the pixel->wcs Mapping */
   AstMapping *tmap0;      /* Pointer to a temporary Mapping */
   AstMapping *tmap1;      /* Pointer to a temporary Mapping */
   AstMapping *tmap2;      /* Pointer to a temporary Mapping */
   AstMapping *tmap;       /* Pointer to a temporary Mapping */
   AstSpecFrame *skyfrm;   /* Pointer to the SkyFrame within WCS Frame */
   AstSpecFrame *specfrm;  /* Pointer to the SpecFrame within WCS Frame */
   AstWcsMap *map2;        /* Pointer to WcsMap */
   char card[ FITSCARDLEN + 1 ]; /* A FITS header card */
   char equinox_attr[ 13 ];/* Name of Equinox attribute for sky axes */
   char system_attr[ 12 ]; /* Name of System attribute for sky axes */
   const char *eqn;              /* Pointer to original sky Equinox value */
   const char *skysys;     /* Pointer to original sky System value */
   double con;             /* Constant axis value */
   double reflat;          /* Celestial latitude at reference point */
   double reflon;          /* Celestial longitude at reference point */
   int *perm;              /* Pointer to axis permutation array */
   int iax;                /* Axis inex */
   int ilat;               /* Celestial latitude index within WCS Frame */
   int ilon;               /* Celestial longitude index within WCS Frame */
   int ispec;              /* SpecFrame axis index within WCS Frame */
   int npix;               /* Number of pixel axes */
   int nwcs;               /* Number of WCS axes */
   int ok;                 /* Is the supplied FrameSet usable? */
   int paxis;              /* Axis index within the primary Frame */

/* Initialise */
   ret = NULL;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Get copies of the pixel Frame, the WCS Frame and the Mapping. */
   tfrm = astGetFrame( fset, ipix );
   pixfrm = astCopy( tfrm );
   tfrm = astAnnul( tfrm );   

   tfrm = astGetFrame( fset, iwcs );
   wcsfrm = astCopy( tfrm );
   tfrm = astAnnul( tfrm );   

   tmap = astGetMapping( fset, ipix, iwcs );
   map = astCopy( tmap );
   tmap = astAnnul( tmap );   

/* Store the number of pixel and WCS axes. */
   npix = astGetNaxes( pixfrm );
   nwcs = astGetNaxes( wcsfrm );

/* Search the WCS Frame for SkyFrames and SpecFrames. */
   specfrm = NULL;
   skyfrm = NULL;
   ok = 1;
   ilat = -1;
   ilon = -1;
   for( iax = 0; iax < nwcs; iax++ ) {

/* Obtain a pointer to the primary Frame containing the current WCS axis. */
      astPrimaryFrame( wcsfrm, iax, &pframe, &paxis );

/* If the current axis is a SpecFrame, save a pointer to it, and its WCS
   index. If we have already found a SpecFrame, abort. */
      if( astIsASpecFrame( pframe ) ) {
         if( specfrm ) {
            ok = 0;
            break;
         }
         specfrm = astClone( pframe );
         ispec = iax;

/* If the current axis is a SkyFrame, save a pointer to it, and its WCS
   index. If we have already found a different SkyFrame, abort. */
      } else if( astIsASkyFrame( pframe ) ) {
         if( skyfrm ) {
            if( pframe != (AstFrame *) skyfrm ) {
               ok = 0;
               break;
            }
         } else {
            skyfrm = astClone( pframe );
         }

         if( paxis == 0 ) {
            ilon = iax;
         } else {
            ilat = iax;
         }

      }

/* Free resources. */
      pframe = astAnnul( pframe );

   }

/* If the supplied FrameSet is usable... */
   if( ok ) {

/* If we did not find a SpecFrame, return a FrameSet made from the base
   and current Frames in the supplied FrameSet. */
      if( !specfrm ) {
         ret = astFrameSet( pixfrm, "" );
         astAddFrame( ret, AST__BASE, map, wcsfrm );

/* If we have a SpecFrame, proceed. */
      } else {

/* Check that both the RefRA and RefDec attributes of the SpecFrame are set. 
   If not, return a FrameSet made from the base and current Frames in the 
   supplied FrameSet.*/
         if( !astTestRefRA( specfrm ) || !astTestRefDec( specfrm ) ) {
            ret = astFrameSet( pixfrm, "" );
            astAddFrame( ret, AST__BASE, map, wcsfrm );

/* If we have a celestial reference position for the spectral axis, ensure 
   it is descirbed correctly by a pair of celestial axes. */
         } else {

/* If the WCS Frame does not contain any celestial axes, we add some now. */
            if( !skyfrm ) {

/* The easiest way to create the required mapping from pixel to celestial
   to create a simple FITS header and read it in via a FitsChan to create a
   FrameSet. */
               fc = astFitsChan( NULL, NULL, "" );
               astPutFits( fc, "CRPIX1  = 0", 0 );
               astPutFits( fc, "CRPIX2  = 0", 0 );
               astPutFits( fc, "CDELT1  = 0.0003", 0 );
               astPutFits( fc, "CDELT2  = 0.0003", 0 );
               astPutFits( fc, "CTYPE1  = 'RA---TAN'", 0 );
               astPutFits( fc, "CTYPE2  = 'DEC--TAN'", 0 );
               astPutFits( fc, "RADESYS = 'FK5'", 0 );
               astPutFits( fc, "EQUINOX = 2000.0", 0 );
   
               sprintf( card, "CRVAL1  = %.*g", DBL_DIG, 
                        AST__DR2D*astGetRefRA( specfrm ) );
               astPutFits( fc, card, 0 );
   
               sprintf( card, "CRVAL2  = %.*g", DBL_DIG, 
                        AST__DR2D*astGetRefDec( specfrm ) );
               astPutFits( fc, card, 0 );
   
               sprintf( card, "MJD-OBS = %.*g", DBL_DIG, astGetEpoch( specfrm ) );
               astPutFits( fc, card, 0 );

               astClearCard( fc );
               tfs = astRead( fc );
               if( tfs ) {

/* Create the new pixel->wcs Mapping. First get the 2-input,2-output
   Mapping between pixel and sky coords from the above FrameSet. Then add
   this Mapping in parallel with the original pixel->wcs Mapping. */
                  tmap0 = astGetMapping( tfs, AST__BASE, AST__CURRENT );
                  tmap1 = (AstMapping *) astCmpMap( map, tmap0, 0, "" );
                  tmap0 = astAnnul( tmap0 );

/* We now have a (npix+2)-input,(nwcs+2)-output Mapping. We now add a
   PermMap in series with this which feeds the constant value 0.0 (the
   CRPIX value in the above set of FITS headers) into the 2 pixel axes
   corresponding to RA and Dec. This PermMap has npix-inputs and (npix+2)
   outputs. The total Mapping then has npix inputs and (nwcs+2) outputs. */
                  perm = astMalloc( sizeof( int )*(size_t) ( npix + 2 ) );
                  if( astOK ) {
                     for( iax = 0; iax < npix; iax++ ) perm[ iax ] = iax;
                     perm[ npix ] = -1;
                     perm[ npix + 1 ] = -1;
                     con = 0.0;
                     tmap0 = (AstMapping *) astPermMap( npix, perm, npix + 2, perm, &con, "" );
                     tmap2 = (AstMapping *) astCmpMap( tmap0, tmap1, 1, "" );
                     tmap0 = astAnnul( tmap0 );
                     tmap1 = astAnnul( tmap1 );

/* We now create the new WCS Frame with the extra RA and Dec axes. This
   is just a CmpFrame made up of the original WCS Frame and the new
   SkyFrame. */
                     tfrm = astGetFrame( tfs, AST__CURRENT );               
                     tfrm0 = (AstFrame *) astCmpFrame( wcsfrm, tfrm, "" );
                     tfrm = astAnnul( tfrm );

/* Construct the returned FrameSet. */
                     ret = astFrameSet( pixfrm, "" );
                     astAddFrame( ret, AST__BASE, tmap2, tfrm0 );
                     tmap2 = astAnnul( tmap2 );
                     tfrm0 = astAnnul( tfrm0 );

/* Free remaining resources. */
                     perm = astFree( perm );
                  }
   
                  tfs = astAnnul( tfs );            
               }
   
               fc = astAnnul( fc );

/* If the WCS Frame does contain celestial axes we make sure that the 
   SpecFrame uses the same reference point. */
            } else {

/* The returned FrameSet has no extra Frames (although some attributes
   may be changed) so just create a new FrameSet equaivalent to the supplied 
   FrameSet. */
               tfs = astFrameSet( pixfrm, "" );
               astAddFrame( tfs, AST__BASE, map, wcsfrm );

/* The RefRA and RefDec attributes of the SpecFrame must be set in FK5
   J2000. Therefore we need to know the celestial reference point in
   FK5 J2000. Modify the SkyFrame within the FrameSet to represent FK5
   J2000, noting the original sky system and equinox first so that they
   can be re-instated (if set) later on. */
               sprintf( system_attr, "System(%d)", ilon + 1 );
               if( astTest( tfs, system_attr ) ) {
                  skysys = astGetC( tfs, system_attr );
               } else {
                  skysys = NULL;
               }
               astSetC( tfs, system_attr, "FK5" );
   
               sprintf( equinox_attr, "Equinox(%d)", ilon + 1 );
               if( astTest( tfs, equinox_attr ) ) {
                  eqn = astGetC( tfs, equinox_attr );
               } else {
                  eqn = NULL;
               }
               astSetC( tfs, equinox_attr, "J2000" );
  
/* The reference point for the celestial axes is defined by the WcsMap
   contained within the Mapping. Split the mapping up into a list of serial 
   component mappings, and locate the first WcsMap in this list. The first 
   Mapping returned by this call is the result of compounding all the 
   Mappings up to (but not including) the WcsMap, the second returned Mapping 
   is the (inverted) WcsMap, and the third returned Mapping is anything 
   following the WcsMap. Only proceed if one and only one WcsMap is found. */
               tmap0 = astGetMapping( tfs, AST__BASE, AST__CURRENT );
               if( SplitMap( tmap0, astGetInvert( tmap0 ), ilon, ilat, &map1, &map2, &map3 ) ){

/* The reference point in the celestial coordinate system is found by
   transforming the fiducial point in native spherical co-ordinates
   into absolute physical coordinates using map3. */
                  if( GetFiducialWCS( map2, map3, ilon,  ilat, &reflon, &reflat ) ){

/* Use reflon and reflat (which represent FK5 J2000 RA and Dec) to set
   the values of the SpecFrame RefRA and RefDec attributes. Format the
   values first so that we can use the FrameSet astSetC method, and so
   maintain the FrameSet integrity. */
                     astSetC( tfs, "RefRA", astFormat( wcsfrm, ilon, reflon ) );
                     astSetC( tfs, "RefDec", astFormat( wcsfrm, ilat, reflat ) );

/* If succesfull, return a pointer to the FrameSet. */
                     if( astOK ) ret = astClone( tfs );
                  }

/* Release resources. */
                  map1 = astAnnul( map1 );
                  map2 = astAnnul( map2 );
                  map3 = astAnnul( map3 );
               }
               tmap0 = astAnnul( tmap0 );
            
/* Re-instate the original sky system and equinox. */
               if( skysys ) astSetC( tfs, system_attr, skysys );
               if( eqn ) astSetC( tfs, equinox_attr, eqn );

/* Release resources. */
               tfs = astAnnul( tfs );
            }
         }
      }
   }

/* Free resources. */
   if( specfrm ) specfrm = astAnnul( specfrm );
   if( skyfrm ) skyfrm = astAnnul( skyfrm );
   pixfrm = astAnnul( pixfrm );
   wcsfrm = astAnnul( wcsfrm );
   map = astAnnul( map );

/* Return NULL if an error has occurred. */
   if( !astOK && ret ) ret = astAnnul( ret );

/* Return the result. */
   return ret;
}

static void MakeIndentedComment( int indent, char token,
                                 const char *comment, const char *data,
                                 char string[ FITSCARDLEN -
                                              FITSNAMLEN + 1 ] ) {
/*
*  Name:
*     MakeIndentedComment

*  Purpose:
*     Create a comment string containing an indentation bar.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void MakeIndentedComment( int indent, char token,
*                               const char *comment, const char *data,
*                               char string[ FITSCARDLEN -
*                                            FITSNAMLEN + 1 ] )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function creates a string that may be used as text in a
*     FITS comment card. The string contains a textual comment
*     preceded by a bar (a line of characters) whose length can be
*     used to indicate a level of indentation (in the absence of any
*     way of indenting FITS keywords).

*  Parameters:
*     indent
*        The level of indentation, in characters.
*     token
*        The character used to form the indentation bar.
*     comment
*        A pointer to a constant null-terminated string containing the text
*        of the comment to be included.
*     data
*        A pointer to a constant null-terminated string containing any
*        textual data to be appended to the comment.
*     string
*        A character array to receive the output string.

*  Notes:
*    - The comment text that appears in the output string is formed by
*   concatenating the "comment" and "data" strings.
*/

/* Local Variables: */
   int i;                        /* Loop counter for input characters */
   int len;                      /* Number of output characters */
   int mxlen;                    /* Maximum length of output string */

/* Check the global error status. */
   if ( !astOK ) return;

/* Calculate the maximum number of characters that the output string
   can accommodate. */
   mxlen = FITSCARDLEN - FITSNAMLEN;

/* Start the string with "indent" copies of the token character, but
   without exceeding the output string length. */
   len = 0;
   while ( ( len < indent ) && ( len < mxlen ) ) string[ len++ ] = token;

/* Pad with spaces up to the start of the comment, if necessary. */
   while ( len < ( FITSCOMCOL - FITSNAMLEN - 1 ) ) {
      string[ len++ ] = ' ';
   }

/* Add "/ " to introduce the comment (strictly not necessary as the
   whole card will be a comment, but it matches the other non-comment
   cards). Truncate if necessary. */
   for ( i = 0; ( i < 2 ) && ( len < mxlen ); i++ ) {
      string[ len++ ] = "/ "[ i ];
   }

/* Append the comment string, truncating it if it is too long. */
   for ( i = 0; comment[ i ] && ( len < mxlen ); i++ ) {
      string[ len++ ] = comment[ i ];
   }

/* Append the data string, again truncating if too long. */
   for ( i = 0; data[ i ] && ( len < mxlen ); i++ ) {
      string[ len++ ] = data[ i ];
   }

/* Terminate the output string. */
   string[ len ] = '\0';
}

static void MakeIntoComment( AstFitsChan *this, const char *method, 
                             const char *class ){
/*
*  Name:
*     MakeIntoComment

*  Purpose:
*     Convert a card into a FITS COMMENT card.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void MakeIntoComment( AstFitsChan *this, const char *method,
*                           const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function formats the card stored just prior to the current card, 
*     and re-stores it as a COMMENT card. It is used (when writing an Object 
*     to a FitsChan) to output values that are not "set" and which are 
*     therefore provided for information only, and should not be read back. 
*     the COMMENT card has the effect of "commenting out" the value.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     method
*        Calling method.
*     class
*        Object class.

*/

/* Local Variables: */
   char card[ FITSCARDLEN + 1 ]; /* Character buffer for FITS card data */

/* Check the global error status. */
   if ( !astOK ) return;

/* Move the current card backwards by one card. */
   MoveCard( this, -1, method, class );    

/* Format the new current card. */
   FormatCard( this, card, method );

/* Write the resulting string to the FitsChan as the contents of a COMMENT 
   card, overwriting the existing card. The current card is incremented
   by this call so that it refers to the same card as on entry. */
   astFitsSetCom( this, "COMMENT", card, 1 );

}

static int MakeIntWorld( AstMapping *cmap, AstFrame *fr, int *wperm, char s, 
                         FitsStore *store, double *dim, 
                         const char *method, const char *class ){
/*
*  Name:
*     MakeIntWorld

*  Purpose:
*     Create FITS header values which map grid into intermediate world
*     coords.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int MakeIntWorld( AstMapping *cmap, AstFrame *fr, int *wperm, char s, 
*                       FitsStore *store, double *dim, 
*                       const char *method, const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function adds values to the supplied FitsStore which describe
*     the transformation from grid (pixel) coords to intermediate world
*     coords. The values added to the FitsStore correspond to the CRPIXj,
*     PCi_j, CDELTi and WCSAXES keywords, and are determined by examining the 
*     suppliedMapping, which must be linear with an optional shift of 
*     origin (otherwise a value of zero is returned).
*
*     Much of the complication in the algorithm arises from the need to
*     support cases where the supplied Mapping has more outputs than
*     inputs. In these case we add some "degenerate" axes to the grid
*     coord system, choosing their unit vectors to be orthogonal to all
*     the other grid axes. It is assumed that degenerate axes will never
*     be used to find a position other than at the axis value of 1.0.
*
*     NOTE, appropriate values for CRVAL keywords should have been stored 
*     in the FitsStore before calling this function (since this function may
*     modify them).

*  Parameters:
*     cmap
*        A pointer to a Mapping which transforms grid coordinates into
*        intermediate world coordinates. The number of outputs must be
*        greater than or equal to the number of inputs.
*     fr
*        Pointer to the final WCS coordinate Frame.
*     wperm
*        Pointer to an array of integers with one element for each axis of 
*        the "fr" Frame. Each element holds the zero-based index of the 
*        FITS-WCS axis (i.e. the value of "i" in the keyword names "CTYPEi", 
*        "CDi_j", etc) which describes the Frame axis.
*     s
*        The co-ordinate version character. A space means the primary
*        axis descriptions. Otherwise the supplied character should be 
*        an upper case alphabetical character ('A' to 'Z'). 
*     store
*        A pointer to the FitsStore into which the calculated CRPIX and 
*        CDi_j values are to be put.
*     dim
*        An array holding the image dimensions in pixels. AST__BAD can be 
*        supplied for any unknwon dimensions.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     A value of 1 is returned if the CRPIX and CDi_j values are
*     succesfully calculated. Zero is returned otherwise.

*  Notes:
*     -  Zero is returned if an error occurs.
*/

/* Local Variables: */
   AstFrame *pfrm;
   AstFrame *sfrm;
   AstMapping *map;
   AstPointSet *psetw;
   AstPointSet *psetg;
   double **fullmat;
   double **partmat;
   double **ptrg;
   double **ptrw;
   double *c;
   double *cdelt;
   double *cdmat;
   double *colvec;
   double *d;
   double *g;
   double *g0;
   double *m;
   double *mat;
   double *w0;
   double *y;
   double cd;
   double crp;
   double crv;
   double cv;
   double det;
   double k;
   double mxcv;
   double skydiag1;
   double skydiag0;
   double val; 
   int *iw;
   int *lin;
   int *pperm;
   int *skycol;
   int i;
   int ii;
   int j;
   int jax;
   int jj;
   int nin;
   int nout;
   int nwcs;
   int paxis;
   int ret;               
   int sing;   
   int skycol0;
   int skycol1;

/* Initialise */
   ret = 0;

/* Check the inherited status. */
   if( !astOK ) return ret;

/* Simplify the supplied Mapping to reduce rounding errors when
   transforming points. */
   map = astSimplify( cmap );

/* Get the number of inputs and outputs for the Mapping. Return if the
   number of outputs is smaller than the number of inputs. */
   nin = astGetNin( map );
   nout = astGetNout( map );
   if( nout < nin ) return ret;

/* Note the number of final World Coordinate axes (not necessarily the
   same as "nout", since some intermediate axes may be discarded by a
   later PermMap. */
   nwcs = astGetNaxes( fr );

/* Allocate work space. */
   g = astMalloc( sizeof(double)*(size_t) nin );
   g0 = astMalloc( sizeof(double)*(size_t) nin );
   w0 = astMalloc( sizeof(double)*(size_t) nout );
   partmat = astMalloc( sizeof(double *)*(size_t) nout );
   lin = astMalloc( sizeof(int)*(size_t) nout );
   pperm = astMalloc( sizeof(int)*(size_t) nout );
   skycol = astMalloc( sizeof(int)*(size_t) nout );
   cdmat = astMalloc( sizeof(double)*(size_t) (nout*nout) );
   cdelt = astMalloc( sizeof(double)*(size_t) nout );

/* For safety, initialise all other pointers. */
   if( partmat ) for( j = 0; j < nout; j++ ) partmat[ j ] = NULL;
   fullmat = NULL;

/* Create a PointSet to hold an input (grid) position for each axis, plus 
   an extra one. Create two other PointSets to hold corresponding
   output (IWC) coordinates. */
   psetg = astPointSet( nin + 1, nin, "" );
   ptrg = astGetPoints( psetg );
   psetw = astPointSet( nin + 1, nout, "" );
   ptrw = astGetPoints( psetw );

/* Check the pointers can be used safely. */
   if( astOK ) {

/* Assume success. */
      ret = 1;

/* The next section finds a 'root' grid position for which the 
   corresponding IWC coordinates are all good. It also finds these IWC 
   coordinates, together with the IWC coordinates of "nin" points which 
   are a unit distance away from the root grid position along each
   grid axis. It also finds an estimate of the rounding error in each
   Mapping output. 
   ================================================================= */
      ret = FindBasisVectors( map, nin, nout, dim, psetg, psetw );

/* Save the grid root position in "g0". */
      for( j = 0; j < nin; j++ ) g0[ j ] = ptrg[ j ][ 0 ];

/* Save the transformed root position in "w0". This is the grid root
   position represented as a vector within the Intermediate World
   Coordinate system. */
      for( j = 0; j < nout; j++ ) w0[ j ] = ptrw[ j ][ 0 ];


/* The next section finds the CD matrix. 
   ===================================== */

/* Initialise the CD matrix elements to "all missing". */
      for( i = 0; i < nout*nout; i++ ) cdmat[ i ] = AST__BAD;

/* The elements of column "j" of the CD matrix form a vector (in Intermediate 
   World Coords) which corresponds to a unit vector along grid axis "j". 
   We now find these vectors for all the grid axes represented by the
   inputs to the supplied Mapping. */
      for( i = 0; i < nin && ret; i++ ) {

/* Form a unit vector along the current input axis. */
         for( ii = 0; ii < nin; ii++ ) g[ ii ] = 0.0;
         g[ i ] = 1.0;

/* Fit a straight line (within IWC) to the current input axis of the Mapping. 
   The IWC vector corresponding to a unit vector along the current input axis 
   is returned if the Mapping is linear. A NULL pointer is returned if the 
   Mapping is not linear. */
         partmat[ i ] = FitLine( map, g, g0, w0, dim[ i ] );

/* If unsuccesful, indicate failure and break out of the loop. */
         if( !partmat[ i ] ) {
            ret = 0;
            break;
         }
      }

/* If the number of outputs for "map" is larger than the number of inputs,
   then we will still be missing some column vectors for the CDi_j matrix
   (which has to be square). We invent these such that the they are
   orthogonal to all the other column vectors. Only od this if the
   Mapping is linear. */
      if( ret ) {
         fullmat = OrthVectorSet( nout, nin, partmat );
         if( !fullmat ) ret = 0;
      }

/* Check everything is OK. */
      if( ret ) {

/* Set up an array holding index of the Mapping output corresponding to
   each IWC axis (the inverse of "wperm"). Also look for matching pairs of
   celestial WCS axes. For the first such pair, note the corresponding 
   column indices and the diagonal element of the matrix which gives the
   scaling for the axis (taking account of the permutation of WCS axes). 
   Also note if the Mapping from intermediate world coords to final world
   coords is linear for each axis (this is assumed to be the case if the
   axis is part of a simple Frame). */
         sfrm = NULL;
         skydiag0 = AST__BAD;
         skydiag1 = AST__BAD;
         skycol0 = -1;
         skycol1 = -1;
         for( i = 0; i < nout; i++ ) {
            pperm[ wperm[ i ] ] = i;

            astPrimaryFrame( fr, i, &pfrm, &paxis );
            if( astIsASkyFrame( pfrm ) ) {
               skycol[ wperm[ i ] ] = paxis + 1;
               lin[ i ] = 0;
               if( !sfrm ) {
                  sfrm = pfrm;
                  skycol0 = wperm[ i ];
                  skydiag0 = fullmat[ skycol0 ][ i ];
               } else if( sfrm == pfrm ) {
                  skycol1 = wperm[ i ];
                  skydiag1 = fullmat[ skycol1 ][ i ];
               } else {
                  pfrm = astAnnul( pfrm );                  
               }
            } else {
               skycol[ wperm[ i ] ] = 0;
               lin[ i ] = !strcmp( astGetClass( pfrm ), "Frame" );
               pfrm = astAnnul( pfrm );                  
            }
         }
         if( sfrm ) sfrm = astAnnul( sfrm );

/* We now have the complete CDi_j matrix. Now to find the CRPIX values.
   These are the grid coords of the reference point (which corresponds to
   the origin of Intermediate World Coords). The "w0" array currently holds 
   the position of the root position, as a position within IWC, and the
   "g0" array holds the corresponding position in grid coordinates. We 
   also have IWC vectors which correspond to unit vectors on each grid 
   axis. The CRPIX values are defined by the matrix equation

        w0 = fullmat*( g0 - crpix )

   The "g0" array only contains "nin" values. If nout>nin, then the
   missing g0 values will be assumed to be zero when we come to find the
   CRPIX values below. 

   We use slaDmat to solve this system of simultaneous equations to get
   crpix. The "y" array initially holds "w0" but is over-written to hold 
   "g0 - crpix". */
         mat = astMalloc( sizeof( double )*(size_t)( nout*nout ) );
         y = astMalloc( sizeof( double )*(size_t) nout );
         iw = astMalloc( sizeof( int )*(size_t) nout );
         if( astOK ) {
            m = mat;
            for( i = 0; i < nout; i++ ) {
               for( j = 0; j < nout; j++ ) *(m++) = fullmat[ j ][ i ];
               y[ i ] = w0[ i ];
            }               
            slaDmat( nout, mat, y, &det, &sing, iw );
         }
         mat = astFree( mat );
         iw = astFree( iw );

/* Loop round all axes, storing the column vector pointer. */
         for( j = 0; j < nout; j++ ) {
            colvec = fullmat[ j ];

/* Get the CRPIX values from the "y" vector created above by slaDmat.
   First deal with axes for which there are Mapping inputs. */
            if( j < nin ) {
               crp = g0[ j ] - y[ j ];

/* If this is a grid axis which has been created to represent a "missing" 
   input to the mapping, we need to add on 1.0 to the crpix value found
   above. This is because the "w0" vector corresponds to a value of zero
   on any missing axes, but the FITS grid value for any missing axes is
   1.0. */
            } else {
               crp = 1.0 - y[ j ];
            }

/* Store the CD and CRPIX values for axes which correspond to inputs 
   of "map". The CD matrix elements are stored in an array and are 
   converted later to the corresponding PC and CDELT values. */
            if( j < nin || crp == 0.0 ) {
               for( i = 0; i < nout; i++ ) {
                  cdmat[ wperm[ i ]*nout+j ] = colvec[ i ] ;
               }

               SetItem( &(store->crpix), 0, j, s, crp );


/* The length of the unit vector along any "degenerate" axes was fixed
   arbitrarily at 1.0 by the call to OrthVectorSet. We can probably
   choose a more appropriate vector length. The choice shouldn't make any
   difference to the transformation, but an appropriate value will look 
   more natural to human readers. */
            } else {

/* First, try to arrange for longitude/latitude axis pairs to have the same 
   scale. Do we have a matching pair of celestial axes? */
               k = AST__BAD;
               if( skydiag0 != AST__BAD && skydiag1 != AST__BAD ) {

/* Is the current column the one which corresponds to the first celestial
   axis, and does the other sky column correspond to a Mapping input? */
                  if( skycol0 == j && skycol1 < nin ) {

/* If so, scale this column so that its diagonal element is the negative
   of the diagonal element of the other axis. This is on the assumption that 
   the scales on the two axes should be equal, and that longitude increases 
   east whilst latitude increases north, and that the CD matrix does not 
   introduce an axis permutation. */
                     if( skydiag0 != 0.0 ) k = -skydiag1/skydiag0;

/* Now see if the current column the one which corresponds to the second 
   celestial axis. Do the same as above. */
                  } else if( skycol1 == j && skycol0 < nin ) {
                     if( skydiag1 != 0.0 ) k = -skydiag0/skydiag1;

/* If neither of the above conditions was met, assume a diagonal element 
   value of 1.0 degrees for latitude axes, and -1.0 degrees for longitude 
   axes. */
                  } 
               }

/* If this failed, the next choice is to arrange for diagonally opposite 
   elements to be equal and opposite in value. Look for the element of the 
   column which has the largest diagonally opposite element, and choose a 
   scaling factor which makes this column element equal to the negative value 
   of its diagonally opposite element. Be careful to take axis permutations 
   into account when finding the value of the diagonal element. */
               if( k == AST__BAD ) {
                  mxcv = 0.0;
                  ii = pperm[ j ];
                  for( i = 0; i < nout; i++ ) {
                     jj = wperm[ i ];
                     if( jj < nin ) {
                        cv = fullmat[ jj ][ ii ];
                        if( !EQUAL( colvec[ i ], 0.0 ) && fabs( cv ) > mxcv ) {
                           mxcv = fabs( cv );
                           k = -cv/colvec[ i ];
                        }
                     }
                  }
               }

/* If still no scaling factor is available, use a scaling factor which
   produces a diagonal element of 1.0 degree if the corresponding row is a
   sky latitude axis, -1.0 degree of sky longitude axes, and 1.0 for other 
   axes. */
               if( k == AST__BAD && colvec[ pperm[ j ] ] != 0.0 ) {
                  if( skycol[ j ] ) { 
                     k = AST__DD2R/colvec[ pperm[ j ] ];
                     if( skycol[ j ] == 1 ) k = -k;
                  } else {
                     k = 1.0/colvec[ pperm[ j ] ];
                  }
               }

/* If we still do not have a scaling, use 1.0 (no scaling). */
               if( k == AST__BAD ) k = 1.0;

/* Now scale and store the column elements. */
               for( i = 0; i < nout; i++ ) {
                  cdmat[ wperm[ i ]*nout+j ] = k*colvec[ i ];
               }

/* Find the corresponding modified CRPIX value and store it. */
               crp = 1.0 + ( crp - 1.0 )/k;
               SetItem( &(store->crpix), 0, j, s, crp );
            }

/* Free resources */
            if( pfrm ) pfrm = astAnnul( pfrm );

         }

/* Any "degenerate" axes added in the above process for which the
   intermediate->world mapping is linear, and which depend only on one
   pixel axis, can be adjusted so that the reference point is at grid 
   coord 1.0. */
         for( i = 0; i < nout; i++ ) {
            if( lin[ i ] ) {

/* Check only one pixel axis contributes to this intermediate world axis
   and find which one it is. */
               jax = -1;
               for( j = 0; j < nout; j++ ) {
                  if( !EQUAL( fullmat[ j ][ i ], 0.0 ) ) {
                     if( jax == -1 ) {
                        jax = j;
                     } else {
                        jax = -1;
                        break;
                     }
                  }
               }

/* We only adjust values for "degenerate" axes. */
               if( jax >= nin ) {

/* Check that this pixel axis only contributes to the single world axis
   currently being considered. */
                  for( ii = 0; ii < nout; ii++ ) { 
                     if( ii != i ) {
                        if( !EQUAL( fullmat[ jax ][ ii ], 0.0 ) ) {
                           jax = -1;
                           break;
                        }
                     }
                  }

                  if( jax != -1 ) {

/* Get the original CRVAL, CRPIX and CD values. Check they are defined.*/
                     crv = GetItem( &(store->crval), wperm[ i ], 0, s, NULL, 
                                    method, class );
                     cd = cdmat[ wperm[ i ]*nout + jax ];
                     crp = GetItem( &(store->crpix), 0, jax, s, NULL, method, class );
                     if( crv != AST__BAD && crp != AST__BAD && 
                         cd != AST__BAD ) {

/* Modify the CRPIX to be 1.0 and modify the CRVAL value accordingly. */
                        SetItem( &(store->crpix), 0, jax, s, 1.0 );
                        SetItem( &(store->crval), wperm[ i ], 0, s, 
                                 cd*( 1.0 - crp ) + crv );
                     }
                  }
               }
            }
         }

/* Finally, if there are fewer input axes than output axes, put a value for 
   the WCSAXES keyword into the store. */
         if( nin < nwcs ) SetItem( &(store->wcsaxes), 0, 0, s, nwcs );

/* Release resources. */
         y = astFree( y );
      }

/* Produce and store PC and CDELT values from the above CD matrix */
      SplitMat( nout, cdmat, cdelt );
      c = cdmat;
      d = cdelt;
      for( i = 0; i < nout; i++ ){
         for( j = 0; j < nout; j++ ){
            val = *(c++);
            if( i == j ){
               if( EQUAL( val, 1.0 ) ) val = AST__BAD;
            } else {
               if( EQUAL( val, 0.0 ) ) val = AST__BAD;
            }
            if( val != AST__BAD ) SetItem( &(store->pc), i, j, s, val );
         }
         SetItem( &(store->cdelt), i, 0, s, *(d++) );
      }
   }

/* Annul pointsets. */
   psetg = astAnnul( psetg );
   psetw = astAnnul( psetw );

/* Free other resources*/
   map = astAnnul( map );
   if( fullmat ) for( j = 0; j < nout; j++ ) fullmat[ j ] = astFree( fullmat[ j ] );
   if( partmat ) for( j = 0; j < nout; j++ ) partmat[ j ] = astFree( partmat[ j ] );
   fullmat = astFree( fullmat );
   partmat = astFree( partmat );
   cdmat = astFree( cdmat );
   cdelt = astFree( cdelt );
   g = astFree( g );
   g0 = astFree( g0 );
   w0 = astFree( w0 );
   lin = astFree( lin );
   skycol = astFree( skycol );
   pperm = astFree( pperm );

/* If an error has occurred, return zero. */
   if( !astOK ) ret = 0;

/* Return the answer. */
   return ret;

}

static int Match( const char *test, const char *temp, int maxfld, int *fields,
                  int *nfld, const char *method, const char *class ){
/*
*  Name:
*     Match

*  Purpose:
*     Sees if a test keyword name matches a template.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int Match( const char *test, const char *temp, int maxfld, int *fields,
*                int *nfld, const char *method, const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     All characters in the template other than "%" (and the field width
*     and type specifiers which follow a "%") must be matched by an 
*     identical character (ignoring case) in the test string. If a "%" occurs 
*     in the template, then the next character in the template should be a
*     single digit specifying a field width. If it is zero, then the test 
*     string may contain zero or more matching characters. Otherwise,
*     the test string must contain exactly the specified number of matching 
*     characters (i.e. 1 to 9). The field width digit may be omitted, in 
*     which case the test string must contain one or more matching 
*     characters. The next character in the template specifies the type of 
*     matching characters and must be one of "d", "c" or "f". Decimal digits 
*     are matched by "d", all upper (but not lower) case alphabetical 
*     characters are matched by "c", and all characters which are legal within 
*     a FITS keyword (i.e. upper case letters, digits, underscores and 
*     hyphens) are matched by "f".

*  Parameters:
*     test
*        Pointer to a null terminated string holding the keyword name to
*        be tested.
*     temp
*        Pointer to a null terminated string holding the template.
*     maxfld
*        The maximum number of integer field values which should be
*        returned in "fields".
*     fields
*        A pointer to an array of at least "maxfld" integers. This is
*        returned holding the values of any integer fields specified
*        in the template. The values are extracted from the test string,
*        and stored in the order they appear in the template string.
*     nfld
*        Pointer to a location at which is returned the total number of 
*        integer fields in the test string. This may be more than the 
*        number returned in "fields" if "maxfld" is smaller than "*nfld".
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.

*  Returned Value:
*     Zero is returned if the test string does not match the template
*     string, and one is returned if it does.

*/

/* Local Variables: */
   char type;             /* Field type specifier */
   const char *a;         /* Pointer to next test character */
   const char *b;         /* Pointer to next template character */
   int extend;            /* Can the width of the first field be extended? */
   int i;                 /* Field index */
   int match;             /* Does "test" match "temp"? */ 
   int nfret;             /* No. of fields returned */
   int tmp;               /* Field value */
   static char fmt[ 10 ]; /* Format specifier for reading an integer field */
   static const char *template; /* Pointer to start of template */
   static int *pa;        /* Pointer to first returned field value */
   static int *pb;        /* Pointer to last returned field value */
   static int na;         /* No. of characters read from the test string */
   static int nb;         /* No. of characters read from the template string */
   static int nentry = 0; /* Number of recursive entries into Match */

/* Check global status. */
   if( !astOK ) return 0;

/* On the first entry to this function, indicate that no integer fields 
   have yet been returned, and save a pointer to the start of the template
   string. */
   if( !nentry ) {
      *nfld = 0;
      template = temp;
   }

/* Increment the number of entries into this function. */
   nentry++;

/* Initialise pointers to the start of each string. */
   a = test;
   b = temp;

/* Initialise the returned flag to indicate that the two strings do not
   match. */
   match = 0;

/* Check that the initial part of the test string can match the first 
   field in the template. */
   if( MatchFront( a, b, &type, &extend, &na, &nb, method, class, template ) ){

/* If it does, increment the pointers to skip over the characters 
   used up in the comparison. */
      a += na;
      b += nb;

/* If the ends of both strings have been reached, they match. */
      if( *a == 0 && *b == 0 ){
         match = 1;

/* Otherwise, if the end of the template has been reached but there are 
   still characters to be read from the test string, we could still have 
   a match if all the remaining test characters match an extandable field. */
      } else if( *b == 0 && *a != 0 && extend ){

/* Loop until all the matching characters have been read from the end of
   the test string. */
         while( *a != 0 && MatchChar( *a, type, method, class, template ) ) a++;

/* If we reached the end of the test string, we have a match. */
         if( *a == 0 ) match = 1;

/* Otherwise, we need to carry on checking the remaining fields. */
      } else {

/* Call this function recursively to see if the remainder of the
   strings match. */
         if( Match( a, b, maxfld, fields, nfld, method, class ) ){
            match = 1;

/* If the remainder of the strings do not match, we may be able to make 
   them match by using up some extra test characters on the first field.
   This can only be done if the first field has an unspecified field width,
   and if the next test character if of a type which matches the first
   field in the template. */
         } else if( extend ){

/* Loop until all the suitable characters have been read from the 
   test string. Break out of the loop early if we find a field width
   which results in the whole string matching. */
            while( MatchChar( *a, type, method, class, template ) ){
               a++;

               if( Match( a, b, maxfld, fields, nfld, method, class ) ){
                  match = 1;
                  break;
               }

            }
         
         }
      
      }
   
   }

/* If the strings match and the leading field is an integer, decode
   the field and store it in the supplied array (if there is room). */
   if( match && type == 'd' && a > test ){
      if( *nfld < maxfld ){
         sprintf( fmt, "%%%dd", a - test );
         astSscanf( test, fmt, fields + *nfld );
      }
      (*nfld)++;
   }

/* Decrement the number of entries into this function. */
   nentry--;

/* If we are leaving this function for the last time, reverse the
   order of the returned integer fields so that they are returned
   in the same order that they occur in the template. */
   if( !nentry ){
      nfret = ( *nfld < maxfld ) ? (*nfld) : maxfld;
      pa = fields;
      pb = fields + nfret - 1;
      for( i = 0; i < nfret/2; i++ ){
         tmp = *pa;
         *(pa++) = *pb;
         *(pb--) = tmp;
      }
   }

/* Return the result. */
   return match;

}

static int MatchChar( char test, char type, const char *method, 
                      const char *class, const char *template ){
/*
*  Name:
*     MatchChar

*  Purpose:
*     See if a given character is of a specified type.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int MatchChar( char test, char type, const char *method, 
*                    const char *class, const char *template )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function checks that the supplied test character belongs
*     to the set of characters specified by the parameter "type".

*  Parameters:
*     test
*        The character to test.
*     type
*        The character specifying the set of acceptable characters. This 
*        should be one of the field type characters accepted by function 
*        Match (e.g. "d", "c" or "f").
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.
*     template
*        Pointer to the start of the whole template string, for use in error
*        messages.

*  Returned Value:
*     Zero is returned if the test character does not belongs to the 
*     specified character set, and one is returned if it does.

*  Notes:
*     -  An error is reported if the type specifier is not legal.
*     -  Zero is returned if an error has already occurred, or if ths
*     function fails for any reason.

*/

/* Local Variables: */
   int ret;            /* Returned flag */

/* Check global status. */
   ret = 0;
   if( !astOK ) return ret;

/* Check for "d" specifiers (digits). */
   if( type == 'd' ){
      ret = isdigit( (int) test );

/* Check for "c" specifiers (upper case letters). */
   } else if( type == 'c' ){
      ret = isupper( (int) test );

/* Check for "s" specifiers (any legal FITS keyword character). */
   } else if( type == 'f' ){
      ret = isFits( (int) test );

/* Report an error for any other specifier. */      
   } else if( astOK ){
      ret = 0;
      astError( AST__BDFMT, "%s(%s): Illegal field type or width "
                "specifier '%c' found in filter template '%s'.", 
                method, class, type, template );
   }

/* Return the answer. */
   return ret;

}

static int MatchFront( const char *test, const char *temp, char *type, 
                       int *extend, int *ntest, int *ntemp, 
                       const char *method, const char *class,
                       const char *template ){
/*
*  Name:
*     MatchFront

*  Purpose:
*     Sees if the start of a test string matches the start of a template.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int MatchFront( const char *test, const char *temp, char *type, 
*                     int *extend, int *ntest, int *ntemp, 
*                     const char *method, const char *class,
*                     const char *template )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function looks for a match between the first field in the 
*     template string and the string at the start of the test string,
*     using the syntax described in function Match.

*  Parameters:
*     test
*        Pointer to a null terminated string holding the keyword name to
*        be tested.
*     temp
*        Pointer to a null terminated string holding the template.
*     type
*        Pointer to a location at which to return a character specifying the
*        sort of field that was matched. This will be one of the legal field
*        types accepted by Match (e.g. "d", "c" or "f"), or null (zero) if
*        the first field in the template string was a literal character (i.e. 
*        did not start with a "%").
*     extend
*        Pointer to a location at which to return a flag which will be non-zero 
*        if the further test characters could be matched by the first field in 
*        the template. This will be the case if the template field only 
*        specifies a minimum number of matching characters (i.e. if the field 
*        width can be extended). For instance, "%d" can be extended, but "%1d" 
*        cannot.
*     ntest
*        Pointer to a location at which to return the number of characters 
*        matched in the test string. This will be the minimum number allowed 
*        by the template field.
*     ntemp
*        Pointer to a location at which to return the number of characters 
*        read from the template string (i.e. the number of characters in the 
*        field specification).
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     class 
*        Pointer to a string holding the name of the supplied object class.
*        This is only for use in constructing error messages.
*     template
*        Pointer to the start of the whole template string, for use in error
*        messages.

*  Returned Value:
*     Zero is returned if the test string starts with fewer than the 
*     minimum number of characters matching the template string, and one 
*     is returned if it does.

*  Notes:
*     -  Zero is returned if an error has already occurred, or if this
*     function fails for any reason.

*/

/* Local Variables: */
   const char *a;     /* Pointer to next test character */
   const char *b;     /* Pointer to next template character */
   int i;             /* Character index */
   int match;         /* Does "test" match "temp"? */

/* Check global status. */
   if( !astOK ) return 0;

/* Initialise pointers to the start of each string. */
   a = test;
   b = temp;

/* Initialise the returned value to indicate that the strings match. */
   match = 1;
   
/* If the current character in the template is not a % sign, it must 
   match the current character in the test string (except for case). */
   if( *b != '%' ){
      if( toupper( (int) *b ) != toupper( (int) *a ) ) {
         match = 0;

/* If the characters match, return all the required information. */
      } else {
         *type = 0;
         *extend = 0;
         *ntest = 1;
         *ntemp = 1;
      }

/* If the current character of the template is a %, we need to match
   a field. */
   } else {
      *ntemp = 3;

/* The next character in the template string determines the field width. 
   Get the lowest number of characters which must match in the test string,
   and set a flag indicating if this lowest limit can be extended. */
      b++;         
      if( *b == '0' ){
         *ntest = 0;
         *extend = 1;

      } else if( *b == '1' ){
         *ntest = 1;
         *extend = 0;

      } else if( *b == '2' ){
         *ntest = 2;
         *extend = 0;

      } else if( *b == '3' ){
         *ntest = 3;
         *extend = 0;

      } else if( *b == '4' ){
         *ntest = 4;
         *extend = 0;

      } else if( *b == '5' ){
         *ntest = 5;
         *extend = 0;

      } else if( *b == '6' ){
         *ntest = 6;
         *extend = 0;

      } else if( *b == '7' ){
         *ntest = 7;
         *extend = 0;

      } else if( *b == '8' ){
         *ntest = 8;
         *extend = 0;

      } else if( *b == '9' ){
         *ntest = 9;
         *extend = 0;

/* If no field width was given, one or more test characters are matched.
   Step back a character so that the current character will be re-used as
   the type specifier. */
      } else {
         *ntest = 1;
         *extend = 1;      
         b--;                
         (*ntemp)--;
      }

/* The next template character gives the type of character which should 
   be matched. */
      b++;
      *type = *b;

/* Report an error if the template string ended within the field 
   specifier. */
      if( !*b ){
         match = 0;
         astError( AST__BDFMT, "%s(%s): Incomplete field specifier found "
                   "at end of filter template '%s'.", method, class, 
                   template );

/* Otherwise, check that the test string starts with the minimum allowed 
   number of characters matching the specified type. */
      } else {

         for( i = 0; i < *ntest; i++ ){
            if( !MatchChar( *a, *type, method, class, template ) ){
               match = 0;
               break;
            }
            a++;
         }

      }

   }

/* Return the answer. */
   return match;

}

static void MarkCard( AstFitsChan *this ){
/*
*  Name:
*     MarkCard

*  Purpose:
*     Mark the current card as having been read into an AST object.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void MarkCard( AstFitsChan *this )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The current card is marked as having been "provisionally used" in
*     the construction of an AST object. If the Object is constructed 
*     succesfully, such cards are marked as havign been definitely used,
*     and they are then considered to have been removed from the FitsChan.

*  Parameters:
*     this
*        Pointer to the FitsChan containing the list of cards.

*  Notes:
*     -  The card remains the current card even though it is now marked
*     as having been read.

*/
   int flags;

/* Return if the global error status has been set, or the current card
   is not defined. */
   if( !astOK || !this->card ) return;

/* Set the PROVISIONALLY_USED flag in the current card. */
   flags = ( (FitsCard *) this->card )->flags;
   ( (FitsCard *) this->card )->flags = flags | PROVISIONALLY_USED;

}

static int MoveCard( AstFitsChan *this, int move, const char *method,
                      const char *class ){
/*
*  Name:
*     MoveCard

*  Purpose:
*     Move the current card a given number of cards forward or backwards.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int MoveCard( AstFitsChan *this, int move, const char *method,
*                    const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The current card is increment by the given number of cards, ignoring
*     cards which have been read into an AST object if the external 
*     IgnoreUsed flag is set non-zero.

*  Parameters:
*     this
*        Pointer to the FitsChan containing the list of cards.
*     move
*        The number of cards by which to move the current card. Positive
*        values move towards the end-of-file. Negative values move
*        towards the start of the file (i.e. the list head).
*     method
*        Pointer to string holding name of calling method.
*     class
*        Pointer to string holding object class.

*  Returned Value:
*     The number of cards actually moved. This may not always be equal to
*     the requested number (for instance, if the end or start of the
*     FitsChan is encountered first).

*  Notes:
*     -  If the end-of-file is reached before the required number of
*     cards have been skipped, the current card is set NULL, to indicate
*     an end-of-file condition.
*     -  If the start of the file is reached before the required number of
*     cards have been skipped, the current card is left pointing to the
*     first usable card.
*     -  This function attempts to execute even if an error has occurred.
*/

/* Local Variables: */
   FitsCard *card;         /* The current card */
   FitsCard *card0;        /* The previous non-deleted card */
   int moved;              /* The number of cards moved by so far */

/* Return if the supplied object is NULL or the FitsChan is
   empty, or zero movement is requested. */
   if( !this || !this->head || !move ) return 0;

/* Get a pointer to the current card. */
   card = (FitsCard *) this->card;

/* Initialise the number of cards moved so far. */
   moved = 0;

/* First deal with positive movements (towards the end-of-file). */
   if( move > 0 ){

/* Loop round moving on to the next card until the correct number of
   moves have been made, or the end-of-file is reached. */
      while( moved < move && card ){

/* Get a pointer to the next card in the list, reporting an error if the
   links are inconsistent. */
         card = GetLink( card, NEXT, method, class );

/* If we have moved past the last card and are now pointing back at the
   list head, then indicate that we are at end-of-file by setting the
   card pointer NULL. */
         if( (void *) card == this->head ){
            card = NULL;

/* Otherwise, increment the number of cards moved. We ignore cards which
   have been read into an AST object if the external "IgnoreUsed" flag is 
   set. */
         } else if( card ){
            if( !CARDUSED(card) ) moved++;
         }
      }

/* Now deal with negative movements (towards the list head), so long as
   we are not currently at the list head. */
   } else if( (void *) card != this->head ){

/* If we are currently at end-of-file, replace the NULL pointer for the
   current card with a pointer to the list head. The first step backwards
   will make the last card the current card. */
      if( !card ) card = (FitsCard *) this->head;

/* Loop round until the correct number of cards have been moved. */
      while( moved < -move && card ){

/* If cards which have been read into an AST object are to be included in the 
   count of moved cards, get a pointer to the previous card in the list, 
   reporting an error if the links are inconsistent. */
         if( !IgnoreUsed ){
            card = GetLink( card, PREVIOUS, method, class );

/* If cards which have been read into an AST object are to be ignored... */
         } else {

/* We need to find the previous card which has not been read into an AST
   object. We do not search beyond the start of the list. */
            card0 = GetLink( card, PREVIOUS, method, class );
            while( card0 && CARDUSED(card0) && (void *) card0 != this->head ){
               card0 = GetLink( card0, PREVIOUS, method, class );
            }

/* If no such card was found we leave the card where it is. */
            if( card0 && ( card0->flags & USED ) ) {
               break;

/* Otherwise, move back to card found above. */
            } else {
               card = card0;
            }

         }

/* Increment the number of cards moved. */
         moved++;

/* If the current card is the list head, break out of the loop. */
         if( (void *) card == this->head ) break;

      }

   }

/* Store the new current card. */
   this->card = (void *) card;

/* Return the answer. */
   return moved;

}

static double NearestPix( AstMapping *map, double val, int axis ){
/*
*  Name:
*     NearestPix

*  Purpose:
*     Find an axis value which corresponds to an integer pixel value.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     double NearestPix( AstMapping *map, double val, int axis )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The supplied axis value is transformed using the inverse of the
*     supplied Mapping (other axes are given the value AST__BAD). The
*     resulting axis values are rounded to the nearest whole number, and
*     then transformed back using the supplied Mapping in the forward
*     direction. If the nominated axis value is good, it is returned as
*     the function value, otherwise the supplied value is returned unchanged.

*  Parameters:
*     map
*        A Mapping (usually the input coordinates will correspond to
*        pixel coordinates).
*     val
*        A value for one of the outputs of the "map" Mapping.
*     axis
*        The index of the Mapping output to which "val" refers.

*  Retuned Value:
*     The modified output axis value.

*/

/* Local Variables: */
   AstPointSet *pset1;     /* Pixel coords PointSet */
   AstPointSet *pset2;     /* WCS coords PointSet */
   double **ptr1;          /* Pointer to data in pset1 */
   double **ptr2;          /* Pointer to data in pset2 */
   double result;           /* Returned value */
   int i;                   /* Loop count */
   int nin;                 /* Number of Mapping inputs */
   int nout;                /* Number of Mapping outputs */

/* Initialise. */
   result = val;

/* Check inherited status, and that the supplied value is good. */
   if( !astOK || result == AST__BAD ) return result;

/* Get the number of input and output coordinates. */
   nin = astGetNin( map );
   nout = astGetNout( map );

/* Create PointSets to hold a single input position and the corresponding 
   output position. */
   pset1 = astPointSet( 1, nin, "" );
   ptr1 = astGetPoints( pset1 );
   pset2 = astPointSet( 1, nout, "" );
   ptr2 = astGetPoints( pset2 );
   if( astOK ) {

/* Assign AST__BAD values to all output axes, except for the specified 
   axis, which is given the supplied axis value. */
      for( i = 0; i < nout; i++ ) ptr2[ i ][ 0 ] = AST__BAD;
      ptr2[ axis ][ 0 ] = val;

/* Transform this output position into an input position. */
      astTransform( map, pset2, 0, pset1 );

/* Round all good axis values in the resulting input position to the nearest 
   integer. */
      for( i = 0; i < nin; i++ ) {
         if( ptr1[ i ][ 0 ] != AST__BAD ) {
            ptr1[ i ][ 0 ] = (int) ( ptr1[ i ][ 0 ] + 0.5 );
         }
      }
     
/* Transform this input position back into output coords. */
      astTransform( map, pset1, 1, pset2 );

/* If the resulting axis value is good, return it. */
      if( ptr2[ axis ] [ 0 ] != AST__BAD ) result = ptr2[ axis ] [ 0 ];     

   }

/* Free resources. */
   pset1 = astAnnul( pset1 );
   pset2 = astAnnul( pset2 );

/* Return the result. */
   return result;
}

static void NewCard( AstFitsChan *this, const char *name, int type, 
                     const void *data, const char *comment, int flags ){
/*
*  Name:
*     NewCard

*  Purpose:
*     Insert a new card in front of the current card.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void NewCard( AstFitsChan *this, const char *name, int type, 
*                   const void *data, const char *comment, int flags )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The supplied keyword name, data type and value, and comment are
*     stored in a new FitsCard structure, and this structure is
*     inserted into the circular linked list stored in the supplied
*     FitsChan. It is inserted in front of the current card.

*  Parameters:
*     this
*        Pointer to the FitsChan containing the list of cards.
*     name
*        Pointer to a string holding the keyword name of the new card.
*     type
*        An integer value representing the data type of the keyword.
*     data
*        Pointer to the data associated with the keyword. 
*     comment
*        Pointer to a null-terminated string holding a comment. 
*     flags
*        The flags to assign to the card.

*  Notes:
*     -  The new card is inserted into the list in front of the current card,
*     so that the "next" link from the new card points to the current card. 
*     If the FitsChan is currently at end-of-file (indicated by a NULL
*     pointer being stored for the current card), then the card is appended
*     to the end of the list. The pointer to the current card is left 
*     unchanged.
*     -  Keyword names are converted to upper case before being stored.
*     -  Any trailing white space in a string value is saved as supplied.
*     -  Logical values are converted to zero or one before being stored.
*     -  The "comment" and/or "data" pointers may be supplied as NULL.

*/

/* Local Variables: */
   FitsCard *new;             /* Pointer to the new card */
   FitsCard *prev;            /* Pointer to the previous card in the list */
   char *b;                   /* Pointer to next stored character */
   const char *a;             /* Pointer to next supplied character */
   int lval;                  /* Logical data value restricted to 0 or 1 */
   int nc;                    /* No. of characters to store */

/* Check the global status. */
   if( !astOK ) return;

/* Get memory to hold the new FitsCard structure. */
   new = (FitsCard *) astMalloc( sizeof( FitsCard ) );

/* Check the pointer can be used. */
   if( astOK ){
     
/* Copy the keyword name, converting to upper case. */
      a = name;
      b = new->name;
      while( *a ) *(b++) = (char) toupper( (int) *(a++) );
      *b = 0;

/* Copy the data type. */
      new->type = type;

/* Copy any data. */
      if( data ){

/* Logical values are converted to zero or one before being stored. */
         if( type == AST__LOGICAL ){
            lval = *( (int *) data ) ? 1 : 0;
            new->size = sizeof( int );
            new->data = astStore( NULL, (void *) &lval, sizeof( int ) );

/* String values... */
         } else if( type == AST__STRING || type == AST__CONTINUE ){

/* Find the number of characters excluding the trailing null character. */
            nc = strlen( data );

/* Store the string, reserving room for a terminating null. */
            new->size = (size_t)( nc + 1 );
            new->data = astStore( NULL, (void *) data, (size_t)( nc + 1 ) );

/* Terminate it. */
            ( (char *) new->data)[ nc ] = 0;

/* Other types are stored as supplied. */
         } else if( type == AST__INT ){
            new->size = sizeof( int );
            new->data = astStore( NULL, (void *) data, sizeof( int ) );

         } else if( type == AST__FLOAT ){
            new->size = sizeof( double );
            new->data = astStore( NULL, (void *) data, sizeof( double ) );

         } else if( type == AST__COMPLEXF ){
            new->size = 2*sizeof( double );
            new->data = astStore( NULL, (void *) data, 2*sizeof( double ) );

         } else if( type == AST__COMPLEXI ){
            new->size = 2*sizeof( int );
            new->data = astStore( NULL, (void *) data, 2*sizeof( int ) );

         } else {
            new->size = 0;
            new->data = NULL;
         }

      } else {
         new->size = 0;
         new->data = NULL;
      }

/* Find the first non-blank character in the comment, and find the used
   length of the remaining string. We retain leading and trailing white
   space if the card is a COMMENT card. */
      if( comment ){
         a = comment;
         if( type != AST__COMMENT ) {
            while( isspace( *a ) ) a++;
            nc = ChrLen( a );
         } else {
            nc = strlen( a );
         }
      } else {
         nc = 0;
      }

/* Copy any comment, excluding leading and trailing white space unless
   this is a COMMENT card */
      if( nc > 0 ){
         new->comment = astStore( NULL, (void *) a, (size_t)( nc + 1 ) );
         ( (char *) new->comment)[ nc ] = 0;
      } else {
         new->comment = NULL;
      }
      
/* Set the supplied flag values. */
      new->