/*
*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, astSetFits<X>) and
c     existing ones may be deleted (astDelFits), extracted (astGetFits<X>),
c     or changed (astSetFits<X>).
f     based on keyword may be performed (using AST_FINDFITS), new
f     cards may be inserted (AST_PUTFITS, AST_PUTCARDS, AST_SETFITS<X>) and
f     existing ones may be deleted (AST_DELFITS), extracted
f     (AST_GETFITS<X>) or changed (AST_SETFITS<X>).
*
*     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 accessed for the first time. 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, astWrite
f     data into it (e.g. using AST_PUTFITS, AST_PUTCARDS, AST_WRITE
*     or by using the SourceFile attribute to specifying a text file from
*     which headers should be read). When the FitsChan is deleted, any
*     remaining header cards in the FitsChan can be saved in either of
*     two ways: 1) by specifying a value for the SinkFile attribute (the
*     name of a text file to which header cards should be written), or 2)
*     by providing a sink function (used to to deliver header cards to an
*     external data store). If you do not provide a sink function or a
*     value for SinkFile, 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 successful use of astRead is destructive, so that FITS header cards
f     all successful 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 (this deletion can be prevented for
*     specific cards by calling the
c     astRetainFits function).
f     AST_RETAINFITS routine).
*     An unsuccessful call of
c     astRead
f     AST_READ
*     (for instance, caused by the FitsChan not containing the necessary
*     FITS headers cards needed to create an Object) results in the
*     contents of the FitsChan being left unchanged.
*
*     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
*     - AltAxes: Controls generation of FITS-WCS alternate axis descriptions
*     - Card: Index of current FITS card in a FitsChan
*     - CardComm: The comment of the current FITS card in a FitsChan
*     - CardName: The keyword name of the current FITS card in a FitsChan
*     - CardType: The data type of the 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
*     - FitsAxisOrder: Sets the order of WCS axes within new FITS-WCS headers
*     - FitsDigits: Digits of precision for floating-point FITS values
*     - FitsRounding: Controls rounding of floating-point FITS values
*     - ForceTab: Force use of the FITS "-TAB" algorithm?
*     - Iwc: Add a Frame describing Intermediate World Coords?
*     - Ncard: Number of FITS header cards in a FitsChan
*     - Nkey: Number of unique keywords in a FitsChan
*     - PolyTan: Use PVi_m keywords to define distorted TAN projection?
*     - SipReplace: Replace SIP inverse transformation?
*     - SipOK: Use Spitzer Space Telescope keywords to define distortion?
*     - SipReplace: Replace SIP inverse transformation?
*     - TabOK: Should the FITS "-TAB" algorithm be recognised?
*     - 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
c     - astEmptyFits: Delete all cards in a FitsChan
c     - astFindFits: Find a FITS card in a FitsChan by keyword
c     - astGetFits<X>: Get a keyword value from a FitsChan
c     - astGetTables: Retrieve any FitsTables from a FitsChan
c     - astPurgeWCS: Delete all WCS-related cards in a FitsChan
c     - astPutCards: Stores a set of FITS header card in a FitsChan
c     - astPutFits: Store a FITS header card in a FitsChan
c     - astPutTable: Store a single FitsTable in a FitsChan
c     - astPutTables: Store multiple FitsTables in a FitsChan
c     - astReadFits: Read cards in through the source function
c     - astRemoveTables: Remove one or more FitsTables from a FitsChan
c     - astRetainFits: Ensure current card is retained in a FitsChan
c     - astSetFits<X>: Store a new keyword value in a FitsChan
c     - astShowFits: Display the contents of a FitsChan on standard output
c     - astTableSource: Register a source function for FITS table access
c     - astTestFits: Test if a keyword has a defined value in a FitsChan
c     - astWriteFits: Write all cards out to the sink function
f     - AST_DELFITS: Delete the current FITS card in a FitsChan
f     - AST_EMPTYFITS: Delete all cards in a FitsChan
f     - AST_FINDFITS: Find a FITS card in a FitsChan by keyword
f     - AST_GETFITS<X>: Get a keyword value from a FitsChan
f     - AST_GETTABLES: Retrieve any FitsTables from a FitsChan
f     - AST_PURGEWCS: Delete all WCS-related cards in a FitsChan
f     - AST_PUTCARDS: Stores a set of FITS header card in a FitsChan
f     - AST_PUTFITS: Store a FITS header card in a FitsChan
f     - AST_PUTTABLE: Store a single FitsTables in a FitsChan
f     - AST_PUTTABLES: Store multiple FitsTables in a FitsChan
f     - AST_READFITS: Read cards in through the source function
f     - AST_REMOVETABLES: Remove one or more FitsTables from a FitsChan
f     - AST_RETAINFITS: Ensure current card is retained in a FitsChan
f     - AST_SETFITS<X>: Store a new keyword value in a FitsChan
c     - AST_SHOWFITS: Display the contents of a FitsChan on standard output
f     - AST_TABLESOURCE: Register a source function for FITS table access
f     - AST_TESTFITS: Test if a keyword has a defined value in a FitsChan
f     - AST_WRITEFITS: Write all cards out to the sink function

*  Copyright:
*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
*     Research Councils
*     Copyright (C) 2008-2011 Science & Technology Facilities Council.
*     All Rights Reserved.

*  Licence:
*     This program is free software: you can redistribute it and/or
*     modify it under the terms of the GNU Lesser General Public
*     License as published by the Free Software Foundation, either
*     version 3 of the License, or (at your option) any later
*     version.
*
*     This program is distributed in the hope that it will be useful,
*     but WITHOUT ANY WARRANTY; without even the implied warranty of
*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*     GNU Lesser General Public License for more details.
*
*     You should have received a copy of the GNU Lesser General
*     License along with this program.  If not, see
*     <http://www.gnu.org/licenses/>.

*  Authors:
*     DSB: David Berry (Starlink)
*     RFWS: R.F. Warren-Smith (Starlink, RAL)
*     TIMJ: Tim Jenness (JAC, Hawaii)

*  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 SetFits.
*        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 SetFits 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 to 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.
*     12-AUG-2004 (DSB):
*        - Convert GLS projection codes to equivalent SFL in SpecTrans.
*        - Added FITS-CLASS encoding.
*     16-AUG-2004 (DSB):
*        - Removed support for paper III keyword VSOURCE, and added
*        support for SSYSSRC keyword.
*        - Added initial support for CLASS encoding.
*        - In FitOK: Changed tolerance for detecting constant values
*        from 1.0E-10 to 1.0E-8.
*     17-AUG-2004 (DSB):
*        Correct GetFiducialNSC so that the stored values for longitude
*        parameters 1 and 2 are ignored unless the value of parameter 0 is
*        not zero.
*     19-AUG-2004 (DSB):
*        Modify SpecTrans to ignore any CDELT values if the header
*        includes some CDi_j values.
*     26-AUG-2004 (DSB):
*        Modify astSplit_ to allow floating point keyword values which
*        include an exponent to be specified with no decimal point
*        (e.g. "2E-4").
*     27-AUG-2004 (DSB):
*        Completed initial attempt at a FITS-CLASS encoding.
*     9-SEP-2004 (DSB):
*        Fixed usage of uninitialised values within ReadCrval.
*     13-SEP-2004 (DSB):
*        Check the "text" pointer can be used safely before using it in
*        DSSToStore.
*     27-SEP-2004 (DSB):
*        In SpecTrans, before creating new PCi_j values,  check that no
*        PCi_j values have been created via an earlier translation.
*     28-SEP-2004 (DSB):
*        In AIPSPPFromStore only get projection parameters values if there
*        are some celestialaxes. Also allow CROTA to describe rotation of
*        non-celestial axes (same for AIPSFromSTore).
*     4-OCT-2004 (DSB):
*        Correct rounding of CRPIX in AddVersion to avoid integer overflow.
*     11-NOV-2004 (DSB):
*        - WcsFcRead: Avoid issuing warnings about bad keywords which
*        have already been translated into equivalent good forms.
*        - SpecTrans: If both PROJP and PV keywords are present, use PV
*        in favour of PROJP only if the PV values look correct.
*     17-NOV-2004 (DSB):
*        - Make astSetFits<X> public.
*     16-MAR-2005 (DSB):
*        - Primary OBSGEO-X/Y/Z, MJD-AVG and MJDOBS keywords are associated
*        with all axis descriptions and should not have a trailing single
*        character indicating an alternate axis set.
*     9-AUG-2005 (DSB):
*        In WcsMapFrm, check reffrm is used before annulling it.
*     8-SEP-2005 (DSB):
*        - Change "if( a < b < c )" constructs to "if( a < b && b < c )"
*        - DSBSetup: correct test on FrameSet pointer state
*        - Ensure CLASS keywords written to a FitsChan do not come before
*        the final fixed position keyword.
*     9-SEP-2005 (DSB):
*        - Added "AZ--" and "EL--" as allowed axis types in FITS-WCS
*        ctype values.
*     12-SEP-2005 (DSB):
*        - Cast difference between two pointers to (int)
*        - CLASSFromStore:Check source velocity is defined before
*          storing it in the output header.
*     13-SEP-2005 (DSB):
*        - Corrected B1940 to B1950 in AddEncodingFrame. This bug
*        prevented some FrameSets being written out using FITS-CLASS.
*        - Rationalise the use of the "mapping" pointer in AddVersion.
*        - WcsCelestial: Modified so that the FITS reference point is
*        stored as the SkyFrame SkyRef attribute value.
*     7-OCT-2005 (DSB):
*        Make astGetFits<X> public.
*     30-NOV-2005 (DSB):
*        Add support for undefined FITS keyword values.
*     5-DEC-2005 (DSB):
*        - Include an IMAGFREQ keyword in the output when writing a
*        DSBSpecFrame out using FITS-WCS encoding.
*        - Correct test for constant values in FitOK.
*     7-DEC-2005 (DSB):
*        Free memory allocated by calls to astReadString.
*     30-JAN-2006 (DSB):
*        Modify astSplit so that it does no read the supplied card beyond
*        column 80.
*     14-FEB-2006 (DSB):
*        Override astGetObjSize.
*     28-FEB-2006 (DSB):
*        Correct documentation typo ("NCards" -> "Ncard").
*     5-APR-2006 (DSB):
*        Modify SpecTrans to convert CTYPE="LAMBDA" to CTYPE="WAVE".
*     26-MAY-2006 (DSB):
*        Guard against NULL comment pointer when converting RESTFREQ to
*        RESTFRQ in SpecTrans.
*     29-JUN-2006 (DSB):
*        - Added astRetainFits.
*        - Consume VELOSYS FITS-WCS keywords when reading an object.
*        - Write out VELOSYS FITS-WCS keywords when writing an object.
*     7-AUG-2006 (DSB):
*        Remove trailing spaces from the string returned by astGetFitsS
*        if the original string contains 8 or fewer characters.
*     16-AUG-2006 (DSB):
*        Document non-destructive nature of unsuccessful astRead calls.
*     17-AUG-2006 (DSB):
*        Fix bugs so that the value of the Clean attribute is honoured
*        even if an error has occurred.
*     4-SEP-2006 (DSB):
*        Modify GetClean so that it ignores the inherited status.
*     20-SEP-2006 (DSB):
*        Fix memory leak in WcsSpectral.
*     6-OCT-2006 (DSB):
*        Modify IsSpectral and IsAIPSSpectral to allow for CTYPE values that
*        are shorter than eight characters.
*     13-OCT-2006 (DSB):
*        - Ensure SpecFrames and SkyFrames created from a foreign FITS header
*        are consistent in their choice of Epoch.
*        - Convert MJD-OBS and MJD-AVG values from TIMESYS timescale to
*        TDB before using as the Epoch value in an AstFrame. Use UTC if
*        TIMESYS is absent.
*        - Convert Epoch values from TDB to UTC before storing as the
*        value of an MJD-OBS or MJD-AVG keyword (no TIMESYS keyword is
*        written).
*     23-OCT-2006 (DSB):
*        Prefer MJD-AVG over MJD-OBS.
*     30-OCT-2006 (DSB):
*        In FitOK: Changed lower limit on acceptbale correlation from
*        0.999999 to 0.99999.
*     1-NOV-2006 (DSB):
*        - When reading a foreign header that contains a DUT1 keyword,
*        use it to set the Dut1 attribute in the SkyFrame. Note, JACH
*        store DUT1 in units of days. This may clash with the FITS-WCS
*        standard (when its produced). Also note that DUT1 is not written
*        out as yet when writing a FrameSet to a foreign FITS header.
*        - Correct bug that prevented ZSOURCE keyword being added to the
*        output header if the source velocity was negative.
*     9-NOV-2006 (DSB):
*        Add STATUS argument to docs for F77 AST_SETx.
*     20-DEC-2006 (DSB):
*        Correct FK5 to ICRS in error message issued if no RADESYS or
*        EQUINOX is found.
*     16-JAN-2007 (DSB):
*        Cast ignored function return values to (void) to avoid compiler
*        warnings.
*     31-JAN-2007 (DSB):
*        Change SpecTrans to ignore blank unit strings (previously
*        converted them to "Hz").
*     16-APR-2007 (DSB):
*        In SplitMat, increase the allowed level of rounding erros from
*        1.0E-10 to 1.0E-7 (to avoid spurious low CDi_j values being
*        created that should be zero).
*     30-APR-2007 (DSB):
*        - Change DSBSetup so that the central DSBSpecFrame frequency is
*        CRVAL and the IF is the difference between CRVAL and LO.
*        - Change tolerance in FitOK from 0.99999 to 0.995 to handle data from Nicolas
*        Peretto.
*     1-MAY-2007 (DSB):
*        - In astSplit, if a keyword value looks like an int but is too long to
*         fit in an int, then treat it as a float instead.
*     18-MAY-2007 (DSB):
*        In CnvType, use input type rather than output type when checking
*        for a COMMENT card. Also, return a null data value buffer for a
*        COMMENT card.
*     4-JUN-2007 (DSB):
*        In CLASSFromStore, create a DELTAV header even if it is equal to
*        the spectral CDELT value. Also, convert spatial reference point
*        to (az,el) and write out as headers AZIMUTH and ELEVATIO.
*     9-JUL-2007 (DSB):
*        Fixed bug in DSBSetUp - previously, this function assumed that
*        the supplied DSBSpecFrame represented frequency, and so gave
*        incorrect values for IF and DSBCentre if the header described
*        velocity.
*     9-AUG-2007 (DSB):
*        Changed GetEncoding so that critcal keywords are ignored if
*        there are no CTYPE, CRPIX or CRVAL keywords in the header.
*     10-AUG-2007 (DSB):
*        - Changed GetEncoding so that FITS_PC is not returned if there are
*        any CDi_j or PCi_j keywords in the header.
*        - Added astPurgeWCS method.
*     13-AUG-2007 (DSB):
*        - Include the DSS keywords AMDX%d and AMDY%d in FindWCS.
*     16-AUG-2007 (DSB):
*        - Force all FITS-CLASS headers to contain frequency axes
*        (velocity axes seem not to be recognised properly by CLASS).
*        - Change the CLASS "VELO-LSR" header to be the velocity at the
*        reference channel, not the source velocity.
*     22-AUG-2007 (DSB):
*        - Remove debugging printf statements.
*     20-SEP-2007 (DSB):
*        Changed FitOK to check that the RMS residual is not more than
*        a fixed small fraction of the pixel size.
*     4-DEC-2007 (DSB):
*        Changed CreateKeyword so that it uses a KeyMap to search for
*        existing keywords. This is much faster than checking every
*        FitsCard in the FitsChan explicitly.
*     18-DEC-2007 (DSB):
*        Add keyword VLSR to the CLASS encoding. It holds the same value
*        as VELO-LSR, but different versions of class use different names.
*        Also write out the DELTAV keyword in the LSR rest frame rather
*        than the source rest frame.
*     31-JAN-2008 (DSB):
*        Correct calculation of redshift from radio velocity in ClassTrans.
*     25-FEB-2008 (DSB):
*        Ensure a SkyFrame represents absolute (rather than offset)
*        coords before writing it out in any non-native encoding.
*     28-FEB-2008 (DSB):
*        Test for existing of SkyRefIs attribute before accessing it.
*     2-APR-2008 (DSB):
*        In CLASSFromStore, adjust the spatial CRVAL and CRPIX values to be
*        the centre of the first pixel if the spatial axes are degenerate.
*     17-APR-2008 (DSB):
*        Ignore latitude axis PV terms supplied in a TAN header
*        (previously, such PV terms were used as polynomial correction
*        terms in a TPN projection).
*     30-APR-2008 (DSB):
*        SetValue changed so that new keywords are inserted before the
*        current card.
*     1-MAY-2008 (DSB):
*        Added UndefRead warning.
*     7-MAY-2008 (DSB):
*        Correct conversion of CDi_j to PCi_j/CDELT in SpecTrans.
*     8-MAY-2008 (DSB):
*        When writing out a FITS-WCS header, allow linear grid->WCS
*        mapping to be represented by a CAR projection.
*     9-MAY-2008 (DSB):
*        Make class variables IgnoreUsed and MarkNew static.
*     30-JUN-2008 (DSB):
*        Improve efficiency of FindWcs.
*     2-JUL-2008 (DSB):
*        FitsSof now returns non-zero if the FitsChan is empty.
*     16-JUL-2008 (DSB):
*        Plug memory leak caused by failure to free the Warnings
*        attribute string when a FitsChan is deleted.
*     24-JUL-2008 (TIMJ):
*        Fix buffer overrun in astGetFits when writing the keyword
*        to the buffer (occurred if the input string was 80 characters).
*     1-OCT-2008 (DSB):
*        When reading a FITS-WCS header, spurious PVi_j keywords no
*        longer generate an error. Instead they generate warnings via the
*        new "BadPV" warning type.
*     21-NOV-2008 (DSB):
*        Do not remove keywords from read headers if they may be of
*        relevance to things other than WCS (e.g. MJD-OBS, OBSGEO, etc).
*     2-DEC-2008 (DSB):
*        - astGetFits<X> now reports an error if the keyword value is undefined.
*        - Add new functions astTestFits and astSetFitsU.
*        - Remove use of AST__UNDEF<X> constants.
*        - Remove "undefread" warning.
*     16-JAN-2009 (DSB):
*        Use astAddWarning to store each warning in the parent Channel
*        structure.
*     4-MAR-2009 (DSB):
*        DATE-OBS and MJD-OBS cannot have an axis description character.
*     13-MAR-2009 (DSB):
*        The VELOSYS value read from the header is never used, so do not
*        report an error if VELOSYS has an undefined value.
*     11-JUN-2009 (DSB):
*        Delay reading cards from the source until they are actually
*        needed. Previously, the source function was called in the
*        FitsChan initialiser, but this means it is not possible for
*        application code to call astPutChannelData before the source
*        function is called. The ReadFromSource function is now called
*        at the start of each (nearly) public or protected function to
*        ensure the source function has been called (the source function
*        pointer in the FitsChan is then nullified to ensure it is not
*        called again).
*     18-JUN-2009 (DSB):
*        Include the effect of observer height (in the ObsAlt attribute)
*        when creating OBSGEO-X/Y/Z headers, and store a value for
*        ObsAlt when reading a set of OBSGEO-X/Y/Z headers.
*     2-JUL-2009 (DSB):
*        Check FitsChan is not empty at start of FindWcs.
*     7-JUL-2009 (DSB):
*        Add new function astSetFitsCM.
*     30-JUL-2009 (DSB):
*        Fix axis numbering in SkyPole.
*     12-FEB-2010 (DSB):
*        Use "<bad>" to represent AST__BAD externally.
*     25-JUN-2010 (DSB):
*        Fix problem rounding lots of 9's in RoundFString. The problem
*        only affected negative values, and could lead to an extra zero
*        being included in the integer part.
*     28-JUN-2010 (DSB):
*        Another problem in RoundFString! If the value has a series of
*        9's followed by a series of zeros, with no decimal point (e.g.
*        "260579999000"), then the trailing zeros were being lost.
*     16-JUL-2010 (DSB):
*        In SpecTrans, avoid over-writing the spatial projection code
*        with the spectral projection code.
*     20-JUL-2010 (DSB):
*        Correct interpretation of NCP projection code.
*     14-OCT-2010 (DSB):
*        - Correct loading of FitsChans that contain UNDEF keywords.
*        - Correct translation of spectral units with non-standard
*        capitalisation in SpecTrans.
*     10-JAN-2011 (DSB):
*        Fix memory leak in MakeIntWorld.
*     13-JAN-2011 (DSB):
*        Rename astEmpty ast astEmptyFits and make public.
*     20-JAN-2011 (DSB):
*        - Extensive changes to support -TAB algorithm
*        - Recovery from a major unrequested reformatting of whitespace by
*        my editor!
*     7-FEB-2011 (DSB):
*        Put a space between keyword value and slash that starts a comment
*        when formatting a FITS header card.
*     11-FEB-2011 (DSB):
*        Change meaning of TabOK attribute. It is no longer a simple
*        boolean indicating if the -TAB algorithm is supported. Instead
*        it gives the value to be used for the EXTVER header - i.e. the
*        version number to store with any binary table created as a
*        result of calling astWrite. If TabOK is zero or begative, then
*        the -TAB algorithm is not supported. This is so that there is
*        some way of having multiple binary table extensions with the same
*        name (but different EXTVER values).
*     14-FEB-2011 (DSB):
*        - Spectral reference point CRVAL records the obs. centre. So for -TAB
*        (when CRVAL is set to zero) we need to record the obs centre some
*        other way (use the AST-specific AXREF keywords, as for spatial axes).
*        - Whether to scale spatial axes from degs to rads depends on
*        whether the spatial axes are descirbed by -TAB or not.
*        - Relax the linearity requirement in IsMapLinear by a factor of
*        10 to prevent a change in rest frame resulting in a non-linear
*        mapping.
*     17-FEB-2011 (DSB):
*        Fix bug in axis linearity check (IsMapLinear).
*     22-FEB-2011 (DSB):
*        The translations of AIPS non-standard CTYPE values were always
*        stored as primary axis description keywords, even if the original
*        non-standard CTYPE values were read from an alternative axis
*        descriptions.
*     5-APR-2011 (DSB):
*        In SpecTrans, correct the MSX CAR projection translation. The
*        first pixel starts at GRID=0.5, not GRID=0.0. So the CRPIX value
*        needs to be reduced by 0.5 prior to normalisation, and then
*        increased by 0.5 after normalisation.
*     23-MAY-2011 (DSB):
*        Add support for TNX projections that use Chebyshev polynomials.
*     24-MAY-2011 (DSB):
*        - Add support for ZPX projections that include IRAF polynomial
*        corrections.
*        - Add PolyTan attribute.
*        - Fix interpretation of -SIP headers that have no inverse.
*     1-JUN-2011 (DSB):
*        In astInitFitsChanVtab, only create the two TimeFrames if they
*        have not already been created (fixes scuba2 trac ticket #666).
*     9-JUN-2011 (DSB):
*        In WCSFcRead, ignore trailing spaces when reading string values
*        for WCS keywords.
*     23-JUN-2011 (DSB):
*        - Override the parent astSetSourceFile method so that it reads
*        headers from the SourceFile and appends them to the end of the
*        FitsChan.
*        - On deletion, write out the FitsChan contents to the file
*        specified by the SinkFile attribute. If no file is specified,
*        use the sink function specified when the FitsChan was created.
*     30-AUG-2011 (DSB):
*        - Added astWriteFits and astReadFits.
*        - Move the deletion of tables and warnings from Delete to
*        EmptyFits.
*     21-SEP-2011 (DSB):
*        - In RoundFString, remember to update the pointer to the exponent.
*        This bug caused parts of the exponent to dissappear when
*        formatting a value that included some trailing zeros and a
*        series of adjacent 9's.
*        - Added Nkey attribute.
*     22-SEP-2011 (DSB):
*        - Added CardType attribute
*        - Allow GetFits to be used to get/set the value of the current
*        card.
*     4-OCT-2011 (DSB):
*        When reading a FITS-WCFS header, if the projection is TPV (as produced
*        by SCAMP), change to TPN (the internal AST code for a distorted
*        TAN projection).
*     22-NOV-2011 (DSB):
*        Allow the "-SIP" code to be used with non-celestial axes.
*     1-FEB-2012 (DSB):
*        Write out MJD-OBS in the timescale specified by any TIMESYS
*        keyword in the FitsChan, and ensure the TIMESYS value is included
*        in the output header.
*     23-FEB-2012 (DSB):
*        Use iauGd2gc in place of palGeoc where is saves some calculations.
*     24-FEB-2012 (DSB):
*        Move invocation of AddEncodingFrame from Write to end of
*        MakeFitsFrameSet. This is so that AddEncodingFrame can take
*        advantage of any standardisations (such as adding celestial axes)
*        performed by MakeFItsFrameSet. Without this, a FRameSet contain
*        a 1D SpecFrame (no celestial axes) would fail to be exported using
*        FITS-CLASS encoding.
*     29-FEB-2012 (DSB):
*        Fix bug in CLASSFromStore that caused spatial axes added by
*        MakeFitsFrameSet to be ignored.
*     2-MAR-2012 (DSB):
*        - In CLASSFromSTore, ensure NAXIS2/3 values are stored in teh FitsChan,
*        and cater for FrameSets that have only a apectral axis and no celestial
*        axes (this prevented the VELO_LSR keyword being created)..
*     7-MAR-2012 (DSB):
*        Use iauGc2gd in place of Geod.
*     22-JUN-2012 (DSB):
*        - Check for distorted TAN projections that have zero for all PVi_m
*        coefficients. Issue a warning and ignore the distortion in such
*        cases.
*        - Remove all set but unused variables.
*        - Convert SAO distorted TAN projections (which use COi_j keywords
*        for polynomial coeffs) to TPN.
*     26-JUN-2012 (DSB):
*        Correct call to astKeyFields in SAOTrans (thanks to Bill Joye
*        for pointing out this error).
*     8-AUG-2012 (DSB):
*        Correct assignment to lonpole within CLASSFromStore.
*     10-AUG-2012 (DSB):
*        Default DSS keywords CNPIX1 and CNPIX2 to zero if they are
*        absent, rather than reporting an error.
*     7-DEC-2012 (DSB):
*        - When writing out a FrameSet that uses an SkyFrame to describe a
*        generalised spherical coordinate system ("system=unknown"), ensure
*        that the generated FITS CTYPE values use FITS-compliant codes
*        for the axis type ( "xxLN/xxLT" or "xLON/xLAT" ).
*        - Add support for reading and writing offset SkyFrames to
*        FITS-WCS.
*     30-JAN-2013 (DSB):
*        When reading a FITS-CLASS header, use "VLSR" keyword if
*        "VELO-..." is not available.
*     15-APR-2013 (DSB):
*        Correct initialisation of missing coefficients When reading a
*        SAO plate solution header.
*     16-APR-2013 (DSB):
*        When determining default Encoding value, use "VLSR" keyword if
*        "VELO-..." is not available.
*     30-MAY-2013 (DSB):
*        Prevent seg fault caused by overrunning the coeffs array in
*        WATCoeffs in cases where the TNX/ZPX projection is found to be
*        of a form that cannot be implemented as a TPN projection.
*     11-JUN-2013 (DSB):
*        Fix support for reading GLS projections, and add support for
*        rotated GLS projections.
*     28-AUG-2013 (DSB):
*        In WcsCelestial, if celestial axes are found with no projection
*        code in CTYPE, assume an old-fashioned CAR projection (i.e. no
*        rotation from native to WCS coords). Before this change,
*        CTYPE = "RA" | "DEC" axes got treated as radians, not degrees.
*     16-SEP-2013 (DSB):
*        When exporting alternate offset SkyFrames to FITS-WCS headers,
*        correctly test the alternate Frame in the supplied FrameSet, rather
*        than the current Frame.
*     24-SEP-2013 (DSB):
*        Fix bug in choosing default value for PolyTan attribute.
*     19-OCT-2013 (DSB):
*        - In SIPMapping, always ignore any inverse polynomial supplied in
*        a SIP header as they seem often to be inaccurate. A new inverse is
*        created to replace it.
*        - In SIPMapping, only use a fit to the inverted SIP transformation
*        if an accuracy of 0.01 pixel can be achieved over an area three
*        times the dimensions of the image. Otherwise use an iterative
*        inverse for each point. People were seeing bad round-trip errors
*        when transforming points outside the image because the fit was
*        being used when it was not very accurate.
*     12-NOV-2013 (DSB):
*        Added CardName and CardComm attributes.
*     13-NOV-2013 (DSB):
*        Use a zero-length string for the CardComm attribute if the card
*        has no comment.
*     15-NOV-2013 (DSB):
*        - Added method astShowFits.
*        - Ensure PurgeWcs removes WCS cards even if an error occurs when
*        reading FrameSets from the FitsChan.
*        - Change IsMapTab1D to improve chances of a -TAB mapping being found.
*     6-JAN-2014 (DSB):
*        - Allow default options for newly created FitsChans to be
*        specified by the FITSCHAN_OPTIONS environment variable.
*        - Ensure the used CarLin value is not changed by a trailing frequency axis.
*     9-JUL-2014 (DSB):
*        Added attribute FitsAxisOrder, which allows an order to be
*        specified for WCS axis within FITS headers generated using astWrite.
*     9-SEP-2014 (DSB):
*        Modify Split so that any non-printing characters such as
*        newlines at the end of the string are ignored.
*     30-SEP-2014 (DSB):
*        Modify CnvType to indicate that comment cards cannot be
*        converted to any other data type. For instance, this causes
*        a warning to be issued if an equals sign is misplaced in a
*        WCS-related card (causing it to be treated as a comment card).
*     24-MAR-2015 (DSB):
*        Modify SpecTrans to avoid modifying the CRPIC value for CAR
*        projections when they do not need to be modified. The princiuple
*        is that the bulk of the array should be witin the native longitude
*        range [-180,+180]. Prompted by bug report from Bill Joye "yet
*        another CAR issue" on 24-MAR-2015 (file CHIPASS_Equ.head in
*        ast_tester).
*     27-APR-2015 (DSB):
*        Modify MakeFitsFrameSet so that isolated SkyAxes (e.g.
*        individual axes that have been oicked from a SkyFrame) are
*        re-mapped into degrees before being used.
*     20-APR-2015 (DSB):
*        In MakeIntWorld, relax tolerance for checking that each FITS-WCS IWC
*        axis is linear, from 0.01 of a pixel to 0.1 of a pixel.
*     6-JUL-2015 (DSB):
*        When checking a sub-string, ensure the whole string is at least as
*        long as the offset to the start of the sub-string. Without this, you
*        can get erroneous sub-string matches by chance, depending on what
*        characters happen to be present in memory after the end of the string.
*     11-AUG-2015 (DSB):
*        - Fix bug in CheckFitsName that prevented an error from being reported
*        if the FITS keyword name contained any illegal printable characters.
*        - Add new Warning "badkeyname", and issue such a warning instead
*        of an error if illegal characters are found in a keyword name.
*     31-AUG-2015 (DSB):
*        In FitLine, use the whole axis rather than 0.1 of the axis (if "dim"
*        is supplied). This is because non-linearity can become greater at
*        further distances along the axis. In practice, it meant that SIP
*        distortion were being treated as linear because the test did not
*        explore a large enough region of pixel space.
*     12-OCT-2015 (DSB):
*        Only add sky axes to a SpecFrame if the WCS Frame contains no
*        other axes other than the SpecFrame (MakeFitsFrameSet).
*     17-OCT-2015 (DSB):
*        - Add new Warning "badkeyvalue", and issue such a warning instead
*        of an error if the Split function cannot determine the keyword value.
*        - Move the check for PLTRAH (i.e. DSS encoding) higher up in GetEncoding.
*        This is because some DSS file slaos have CD and/or PC keywords.
*     5-NOV-2015 (DSB):
*        Fix bug in MakeFitsFrameSet that could cause an inappropriate
*        RefRA and RefDec values to be used when writing out a SpecFrame
*        using FITS-WCS. This bug was caused by the assumption that
*        changing the current Frame of a FRameSet also changes the Frame
*        that was added into the FRameSet. This used to be the  case as
*        astAddFrame took a clone of the supplied Frame pointer, but it
*        now takes a deep copy, so the original Frame and the FrameSet's
*        current Frame are now independent of each other.
*     28-JUN-2016 ((DSB):
*        IsAipsSpectral: Trailing spaces in CTYPE values are insignificant.
*     17-MAR-2017 (DSB):
*        Fix memory leak in MakeFitsFrameSet.
*     25-APR-2017 (DSB):
*        When reading foreign WCS, retain the TIMESYS keyword by default.
*     28-APR-2017 (DSB):
*        When reading a JCMT or UKIRT foreign header that contains a
*        DTAI keyword, use it to set the Dtai attribute in the WCS Frame.
*     11-SEP-2017 (DSB):
*        Allow a NULL keyword name to be supplied to astTestFits to
*        indicate that the current card should be used (as is also done in
*        astGetFits).
*     25-OCT-2017 (DSB):
*        Added attribute FitsTol.
*     27-OCT-2017 (DSB):
*        In RoundFString, only right justift the final string if a
*        minimum field width is given. Otherwise, leave the unchanged
*        characters in their original positions. Right justifying ccould
*        cause very long strings to be returned.
*     6-NOV-2017 (DSB):
*        In CelestialAxes, simplify the base->current mapping before
*        attempting to split it. This can cause multiple WcsMaps to cancel out,
*        which could otherwise prevent SplitMap from splitting the Mapping
*        successfully.
*     7-NOV-2017 (DSB):
*        If an IWC Frame is included in the FrameSet returned by astRead,
*        ensure it comes between the pixel and sky frames in the mapping chain.
*        Previously the order was PIXEL->SKY->IWC, Now it is PIXEL->IWC->SKY.
*        The inter-Frame Mappings required by the new arrangment are simpler
*        than for the old arrangement.
*     20-NOV-2017 (DSB)
*        Added SipReplace attribute.
*     20-NOV-2017 (DSB)
*        Added SipReplace attribute.
*     30-DEC-2017 (DSB):
*        Add the SipOK attribute, and support for writing SIP headers.
*     14-FEB-2018 (DSB):
*        In SipIntWorld, check linearity of mappings numerically rather
*        than on the basis of their class. This is because some linear
*        combinations contain non-linear mappings (eg. a spherical
*        rotation projected using a TAN projection).
*     2-NOV-2018 (DSB):
*        In SpecTrans, when renaming QV to PV, ensure the data type of
*        the FITS card is used rather than assuming AST__FLOAT. The previous
*        behaviour could cause spurious PV values for QV values that have no
*        decimal point.
*     27-FEB-2019 (DSB):
*        In GetEncoding, do not require CRPIX CRVAL and CTYPE all to be
*        present for the header to be considered FITS-WCS of some
*        flavour. These keywords have defined default values in FITS-WCS
*        paper 1 and so can be missing in a valid FITS-WCS header.
*     10-MAY-2019 (DSB):
*        Cater for reading FITS-WCS headers that have alternate axis
*        descriptions but do not have any primary axis descriptions
*        (see email from Bill Joye on 9/5/2019).
*     17-OCT-2019 (DSB):
*        Add ForceTab algorithm.
*     28-MAY-2020 (DSB):
*        - In SpecTrans, don't assume that the number of CRPIX kewords
*        defines the number of axes. Treat each keyword separately so
*        that no keywords are created that don't have corresponding keywords
*        in the original header. This avoids the final Mapping having (say)
*        3 inputs when there are are only two pixel axes speciofied in
*        the header (i.e. avoid adding degenerate axes).
*        - In AddFrame, feed a constant value of zero, instead of one,
*        into any Mapping inputs that have no corresponding pixel axis.
*        This is because the default value for CRPIX (specified in the
*        FITS-WCS papers) is 0.0, not 1.0.
*     12-JUN-2020 (DSB):
*        In SpecTrans ("Zero cdelt values:") use "CDELT", not "CTYPE", to
*        define the number of available CDELT values.
*     17-AUG-2020 (DSB):
*        Fix potential infinite loop in SpecTrans.
*     28-JAN-2021 (GSB):
*        Correct the use of offset coords axis indices in SkySys.
*     29-JAN-2021 (DSB):
*        - Check that the pointer returned by astGetC is not NULL before
*        attempting to use it.
*        - Fix sorting of vector magnitudes in OrthVector. This bug accounts for why
*        FITS_WCS keywords describing alternate axes were often not produced.
*        - Add AltAxes attribute, which controls the creation of FITS-WCS
*        alternate axis descriptions.
*     19-FEB-2021 (DSB):
*        - Fix bug in IsMapLinear for cases where the number of mapping
*        inputs and outputs differ and the Mapping can be split.
*     5-JAN-2022 (DSB):
*        Fix bug in SpecTrans that caused 'Unable to find a value for FITS keyword
*        "CRVAL2A"' error when reading a basic NCP projection header (i.e. a header
*        with no alternate axis keywords).
*     6-JAN-2022 (DSB):
*        - Added attribute FitsRounding and re-wrote RoundFString.
*        - Increase some buffer sizes to avoid compilation warnings.
*     6-JUN-2022 (DSB):
*        Avoid copying overlapping strings in RoundFString.
*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)=='_' )

/* A amacro to test if a Frame is a SkyFrame, and is used to describe the
   sky (skyframes could be used for other purposes - eg a POLANAL Frame). */
#define IsASkyFrame(frame) (astIsASkyFrame(frame)&&!strcmp("SKY",astGetDomain(frame)))

/* 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)  ( \
             ( ignore_used == 2 && \
                ( (FitsCard *) (card) )->flags & PROVISIONALLY_USED ) || \
             ( ignore_used >= 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 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))<=astMAX(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 ALTAXES_ALL          0
#define ALTAXES_IDENT        1
#define ALTAXES_NONE         2
#define ALTAXES_ALL_STRING   "ALL"
#define ALTAXES_IDENT_STRING "IDENT"
#define ALTAXES_NONE_STRING  "NONE"
#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 FITSCLASS_ENCODING 7
#define MAX_ENCODING       7
#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 FITSCLASS_STRING  "FITS-CLASS"
#define FITSCLASS_STRING2 "FITS_CLASS"
#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 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 AZEL               6
#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 badpv badkeyname badkeyvalue "
#define NPFIT             10
#define SPD               86400.0
#define FL  1.0/298.257  /*  Reference spheroid flattening factor */
#define A0  6378140.0    /*  Earth equatorial radius (metres) */





/* String used to represent AST__BAD externally. */
#define BAD_STRING "<bad>"

/* 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

/* "PROTECTED" - This flag indicates that the the card should not be
   removed form the FitsChan when an Object is read using astRead. If
   this flag is not set, then the card will dehave as if it has been
   deleted if it was used in the construction of the returned AST Object. */
#define PROTECTED 16

/* 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 "lutmap.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 "timeframe.h"
#include "keymap.h"
#include "pal.h"
#include "erfa.h"
#include "slamap.h"
#include "specframe.h"
#include "dsbspecframe.h"
#include "specmap.h"
#include "sphmap.h"
#include "unit.h"
#include "unitmap.h"
#include "polymap.h"
#include "wcsmap.h"
#include "winmap.h"
#include "zoommap.h"
#include "globals.h"
#include "fitstable.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>
#include <math.h>
#include <errno.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;

/* 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;
   char ****ssyssrc;
   char ****ps;
   char ****timesys;
   double ***pc;
   double ***cdelt;
   double ***crpix;
   double ***crval;
   double ***equinox;
   double ***latpole;
   double ***lonpole;
   double ***mjdobs;
   double ***dtai;
   double ***dut1;
   double ***mjdavg;
   double ***pv;
   double ***wcsaxes;
   double ***obsgeox;
   double ***obsgeoy;
   double ***obsgeoz;
   double ***restfrq;
   double ***restwav;
   double ***zsource;
   double ***velosys;
   double ***asip;
   double ***bsip;
   double ***apsip;
   double ***bpsip;
   double ***imagfreq;
   double ***axref;
   int naxis;
   AstKeyMap *tables;
   double ***skyref;
   double ***skyrefp;
   char ****skyrefis;
} FitsStore;

/* Module Variables. */
/* ================= */

/* Address of this static variable is used as a unique identifier for
   member of this class. */
static int class_check;

/* Pointers to parent class methods which are extended by this class. */
static void (* parent_setsourcefile)( AstChannel *, const char *, int * );
static size_t (* parent_getobjsize)( AstObject *, int * );
static const char *(* parent_getattrib)( AstObject *, const char *, int * );
static int (* parent_getfull)( AstChannel *, int * );
static int (* parent_getskip)( AstChannel *, int * );
static int (* parent_testattrib)( AstObject *, const char *, int * );
static void (* parent_clearattrib)( AstObject *, const char *, int * );
static void (* parent_setattrib)( AstObject *, const char *, int * );
static int (* parent_write)( AstChannel *, AstObject *, int * );
static AstObject *(* parent_read)( AstChannel *, int * );
#if defined(THREAD_SAFE)
static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
#endif

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

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

/* Text values used to represent AltAxes values externally. */
static const char *xaltax[3] = { ALTAXES_ALL_STRING,
                                 ALTAXES_IDENT_STRING,
                                 ALTAXES_NONE_STRING };

/* Define two variables to hold TimeFrames which will be used for converting
   MJD values between time scales. */
static AstTimeFrame *tdbframe = NULL;
static AstTimeFrame *timeframe = NULL;

/* Max number of characters in a formatted int */
static int int_dig;

/* Define macros for accessing each item of thread specific global data. */
#ifdef THREAD_SAFE

/* Define how to initialise thread-specific globals. */
#define GLOBAL_inits \
   globals->Class_Init = 0; \
   globals->GetAttrib_Buff[ 0 ] = 0; \
   globals->Items_Written = 0; \
   globals->Write_Nest = -1; \
   globals->Current_Indent = 0; \
   globals->Ignore_Used = 1; \
   globals->Mark_New = 0; \
   globals->CnvType_Text[ 0 ] = 0; \
   globals->CnvType_Text0[ 0 ] = 0; \
   globals->CnvType_Text1[ 0 ] = 0; \
   globals->CreateKeyword_Seq_Nchars = -1; \
   globals->FormatKey_Buff[ 0 ] = 0; \
   globals->FitsGetCom_Sval[ 0 ] = 0; \
   globals->IsSpectral_Ret = NULL; \
   globals->Match_Fmt[ 0 ] = 0; \
   globals->Match_Template = NULL; \
   globals->Match_PA = 0; \
   globals->Match_PB = 0; \
   globals->Match_NA = 0; \
   globals->Match_NB = 0; \
   globals->Match_Nentry = 0; \
   globals->WcsCelestial_Type[ 0 ] = 0; \
   globals->Ignore_Used = 1; \
   globals->Mark_New = 0;

/* Create the function that initialises global data for this module. */
astMAKE_INITGLOBALS(FitsChan)

/* Define macros for accessing each item of thread specific global data. */
#define class_init astGLOBAL(FitsChan,Class_Init)
#define class_vtab astGLOBAL(FitsChan,Class_Vtab)
#define getattrib_buff astGLOBAL(FitsChan,GetAttrib_Buff)
#define items_written astGLOBAL(FitsChan,Items_Written)
#define write_nest astGLOBAL(FitsChan,Write_Nest)
#define current_indent astGLOBAL(FitsChan,Current_Indent)
#define ignore_used astGLOBAL(FitsChan,Ignore_Used)
#define mark_new astGLOBAL(FitsChan,Mark_New)
#define cnvtype_text astGLOBAL(FitsChan,CnvType_Text)
#define cnvtype_text0 astGLOBAL(FitsChan,CnvType_Text0)
#define cnvtype_text1 astGLOBAL(FitsChan,CnvType_Text1)
#define createkeyword_seq_nchars astGLOBAL(FitsChan,CreateKeyword_Seq_Nchars)
#define formatkey_buff astGLOBAL(FitsChan,FormatKey_Buff)
#define fitsgetcom_sval astGLOBAL(FitsChan,FitsGetCom_Sval)
#define isspectral_ret astGLOBAL(FitsChan,IsSpectral_Ret)
#define match_fmt astGLOBAL(FitsChan,Match_Fmt)
#define match_template astGLOBAL(FitsChan,Match_Template)
#define match_pa astGLOBAL(FitsChan,Match_PA)
#define match_pb astGLOBAL(FitsChan,Match_PB)
#define match_na astGLOBAL(FitsChan,Match_NA)
#define match_nb astGLOBAL(FitsChan,Match_NB)
#define match_nentry  astGLOBAL(FitsChan,Match_Nentry)
#define wcscelestial_type astGLOBAL(FitsChan,WcsCelestial_Type)
static pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
#define LOCK_MUTEX2 pthread_mutex_lock( &mutex2 );
#define UNLOCK_MUTEX2 pthread_mutex_unlock( &mutex2 );
static pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;
#define LOCK_MUTEX3 pthread_mutex_lock( &mutex3 );
#define UNLOCK_MUTEX3 pthread_mutex_unlock( &mutex3 );
static pthread_mutex_t mutex4 = PTHREAD_MUTEX_INITIALIZER;
#define LOCK_MUTEX4 pthread_mutex_lock( &mutex4 );
#define UNLOCK_MUTEX4 pthread_mutex_unlock( &mutex4 );

/* If thread safety is not needed, declare and initialise globals at static
   variables. */
#else

/* Buffer returned by GetAttrib. */
static char getattrib_buff[ AST__FITSCHAN_GETATTRIB_BUFF_LEN + 1 ];

/* Buffer for returned text string in CnvType */
static char cnvtype_text[ 2*AST__FITSCHAN_FITSCARDLEN + 3 ];

/* Buffer for real value in CnvType */
static char cnvtype_text0[ AST__FITSCHAN_FITSCARDLEN + 1 ];

/* Buffer for imaginary value in CnvType */
static char cnvtype_text1[ AST__FITSCHAN_FITSCARDLEN + 1 ];

/* 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 = 0;

/* Ignore_Used: 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. */
static int ignore_used = 1;

/* Mark_New: 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. */
static int mark_new = 0;

/* Number of characters used for encoding */
static int createkeyword_seq_nchars = -1;

/* Buffer for value returned by FormatKey */
static char formatkey_buff[ 10 ];

/* Buffer for value returned by FitsGetCom */
static char fitsgetcom_sval[ AST__FITSCHAN_FITSCARDLEN + 1 ];

/* Pointer returned by IsSpectral */
static const char *isspectral_ret = NULL;

/* Format specifier for reading an integer field in Match */
static char match_fmt[ 10 ];

/* Pointer to start of template in Match */
static const char *match_template = NULL;

/* Pointer to first returned field value in Match */
static int *match_pa = 0;

/* Pointer to last returned field value in Match */
static int *match_pb = 0;

/* No. of characters read from the test string in Match */
static int match_na = 0;

/* No. of characters read from the template string in Match */
static int match_nb = 0;

/* Number of recursive entries into Match */
static int match_nentry = 0;

/* Buffer for celestial system in WcsCelestial */
static char wcscelestial_type[ 4 ];

/* 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? */
#define LOCK_MUTEX2
#define UNLOCK_MUTEX2
#define LOCK_MUTEX3
#define UNLOCK_MUTEX3
#define LOCK_MUTEX4
#define UNLOCK_MUTEX4
#endif

/* 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 ), int * ),
                           void (*)( const char * ),
                           void (*)( void (*)( const char * ), const char *, int * ),
                           const char *, ... );
AstFitsChan *astFitsChanId_( const char *(* source)( void ),
                             void (* sink)( const char * ),
                             const char *options, ... );

/* Prototypes for Private Member Functions. */
/* ======================================== */
static size_t GetObjSize( AstObject *, int * );
static void ClearCard( AstFitsChan *, int * );
static int GetCard( AstFitsChan *, int * );
static int TestCard( AstFitsChan *, int * );
static void SetCard( AstFitsChan *, int, int * );
static void ClearEncoding( AstFitsChan *, int * );
static int GetEncoding( AstFitsChan *, int * );
static int TestEncoding( AstFitsChan *, int * );
static void SetEncoding( AstFitsChan *, int, int * );
static void ClearCDMatrix( AstFitsChan *, int * );
static int GetCDMatrix( AstFitsChan *, int * );
static int TestCDMatrix( AstFitsChan *, int * );
static void SetCDMatrix( AstFitsChan *, int, int * );
static void ClearFitsDigits( AstFitsChan *, int * );
static int GetFitsDigits( AstFitsChan *, int * );
static int TestFitsDigits( AstFitsChan *, int * );
static void SetFitsDigits( AstFitsChan *, int, int * );
static void ClearFitsRounding( AstFitsChan *, int * );
static int GetFitsRounding( AstFitsChan *, int * );
static int TestFitsRounding( AstFitsChan *, int * );
static void SetFitsRounding( AstFitsChan *, int, int * );
static void ClearAltAxes( AstFitsChan *, int * );
static int GetAltAxes( AstFitsChan *, int * );
static int TestAltAxes( AstFitsChan *, int * );
static void SetAltAxes( AstFitsChan *, int, int * );
static void ClearFitsAxisOrder( AstFitsChan *, int * );
static const char *GetFitsAxisOrder( AstFitsChan *, int * );
static int TestFitsAxisOrder( AstFitsChan *, int * );
static void SetFitsAxisOrder( AstFitsChan *, const char *, int * );
static void ClearDefB1950( AstFitsChan *, int * );
static int GetDefB1950( AstFitsChan *, int * );
static int TestDefB1950( AstFitsChan *, int * );
static void SetDefB1950( AstFitsChan *, int, int * );
static void ClearTabOK( AstFitsChan *, int * );
static int GetTabOK( AstFitsChan *, int * );
static int TestTabOK( AstFitsChan *, int * );
static void SetTabOK( AstFitsChan *, int, int * );
static void ClearForceTab( AstFitsChan *, int * );
static int GetForceTab( AstFitsChan *, int * );
static int TestForceTab( AstFitsChan *, int * );
static void SetForceTab( AstFitsChan *, int, int * );
static void ClearCarLin( AstFitsChan *, int * );
static int GetCarLin( AstFitsChan *, int * );
static int TestCarLin( AstFitsChan *, int * );
static void SetCarLin( AstFitsChan *, int, int * );
static void ClearSipReplace( AstFitsChan *, int * );
static int GetSipReplace( AstFitsChan *, int * );
static int TestSipReplace( AstFitsChan *, int * );
static void SetSipReplace( AstFitsChan *, int, int * );
static void ClearPolyTan( AstFitsChan *, int * );
static int GetPolyTan( AstFitsChan *, int * );
static int TestPolyTan( AstFitsChan *, int * );
static void SetPolyTan( AstFitsChan *, int, int * );
static void ClearSipOK( AstFitsChan *, int * );
static int GetSipOK( AstFitsChan *, int * );
static int TestSipOK( AstFitsChan *, int * );
static void SetSipOK( AstFitsChan *, int, int * );
static void ClearIwc( AstFitsChan *, int * );
static int GetIwc( AstFitsChan *, int * );
static int TestIwc( AstFitsChan *, int * );
static void SetIwc( AstFitsChan *, int, int * );
static void ClearClean( AstFitsChan *, int * );
static int GetClean( AstFitsChan *, int * );
static int TestClean( AstFitsChan *, int * );
static void SetClean( AstFitsChan *, int, int * );
static void ClearWarnings( AstFitsChan *, int * );
static const char *GetWarnings( AstFitsChan *, int * );
static int TestWarnings( AstFitsChan *, int * );
static void SetWarnings( AstFitsChan *, const char *, int * );
static double GetFitsTol( AstFitsChan *, int * );
static int TestFitsTol( AstFitsChan *, int * );
static void ClearFitsTol( AstFitsChan *, int * );
static void SetFitsTol( AstFitsChan *, double, int * );


static AstFitsChan *SpecTrans( AstFitsChan *, int, const char *, const char *, int * );
static AstFitsTable *GetNamedTable( AstFitsChan *, const char *, int, int, int, const char *, int * );
static AstFrameSet *MakeFitsFrameSet( AstFitsChan *, AstFrameSet *, int, int, int, const char *, const char *, int * );
static AstGrismMap *ExtractGrismMap( AstMapping *, int, AstMapping **, int * );
static AstKeyMap *GetTables( AstFitsChan *, int * );
static AstMapping *AddUnitMaps( AstMapping *, int, int, int * );
static AstMapping *CelestialAxes( AstFitsChan *this, AstFrameSet *, double *, int *, char, FitsStore *, int *, int, const char *, const char *, int * );
static AstMapping *GrismSpecWcs( char *, FitsStore *, int, char, AstSpecFrame *, const char *, const char *, int * );
static AstMapping *IsMapTab1D( AstMapping *, double, const char *, AstFrame *, double *, int, int, AstFitsTable **, int *, int *, int *, int * );
static AstMapping *IsMapTab2D( AstMapping *, double, const char *, AstFrame *, double *, int, int, int, int, AstFitsTable **, int *, int *, int *, int *, int *, int *, int *, int *, int * );
static AstMapping *LinearWcs( FitsStore *, int, char, const char *, const char *, int * );
static AstMapping *LogAxis( AstMapping *, int, int, double *, double *, double, int * );
static AstMapping *LogWcs( FitsStore *, int, char, const char *, const char *, int * );
static AstMapping *MakeColumnMap( AstFitsTable *, const char *, int, int, const char *, const char *, int * );
static AstMapping *NonLinSpecWcs( AstFitsChan *, char *, FitsStore *, int, char, AstSpecFrame *, const char *, const char *, int * );
static AstMapping *OtherAxes( AstFitsChan *, AstFrameSet *, double *, int *, char, FitsStore *, double *, int *, const char *, const char *, int * );
static AstMapping *SIPIntWorld( AstMapping *, double, int, int, char, FitsStore *, double *, int[2], double[2], double[4], const char *, const char *, int * );
static AstMapping *SIPMapping( AstFitsChan *, double *, FitsStore *, char, int, const char *, const char *, int * );
static AstMapping *SpectralAxes( AstFitsChan *, AstFrameSet *, double *, int *, char, FitsStore *, double *, int *, const char *, const char *, int * );
static AstMapping *TabMapping( AstFitsChan *, FitsStore *, char, int **, const char *, const char *, int *);
static AstMapping *WcsCelestial( AstFitsChan *, FitsStore *, char, AstFrame **, AstFrame *, double *, double *, AstSkyFrame **, AstMapping **, int *, const char *, const char *, int * );
static AstMapping *WcsIntWorld( AstFitsChan *, FitsStore *, char, int, const char *, const char *, int * );
static AstMapping *WcsMapFrm( AstFitsChan *, FitsStore *, char, AstFrame **, const char *, const char *, int * );
static AstMapping *WcsNative( AstFitsChan *, FitsStore *, char, AstWcsMap *, int, int, const char *, const char *, int * );
static AstMapping *WcsOthers( AstFitsChan *, FitsStore *, char, AstFrame **, AstFrame *, const char *, const char *, int * );
static AstMapping *WcsSpectral( AstFitsChan *, FitsStore *, char, AstFrame **, AstFrame *, double, double, AstSkyFrame *, const char *, const char *, int * );
static AstMapping *ZPXMapping( AstFitsChan *, FitsStore *, char, int, int[2], const char *, const char *, int * );
static AstMatrixMap *WcsCDeltMatrix( FitsStore *, char, int, const char *, const char *, int * );
static AstMatrixMap *WcsPCMatrix( FitsStore *, char, int, const char *, const char *, int * );
static AstObject *FsetFromStore( AstFitsChan *, FitsStore *, const char *, const char *, int * );
static AstObject *Read( AstChannel *, int * );
static AstSkyFrame *WcsSkyFrame( AstFitsChan *, FitsStore *, char, int, char *, int, int, const char *, const char *, int * );
static AstTimeScaleType TimeSysToAst( AstFitsChan *, const char *, const char *, const char *, int * );
static AstWinMap *WcsShift( FitsStore *, char, int, const char *, const char *, int * );
static FitsCard *GetLink( FitsCard *, int, const char *, const char *, int * );
static FitsStore *FitsToStore( AstFitsChan *, int, const char *, const char *, int * );
static FitsStore *FreeStore( FitsStore *, int * );
static FitsStore *FsetToStore( AstFitsChan *, AstFrameSet *, int, double *, int, const char *, const char *, int * );
static char *CardComm( AstFitsChan *, int * );
static char *CardName( AstFitsChan *, int * );
static char *ConcatWAT( AstFitsChan *, int, const char *, const char *, int * );
static char *FormatKey( const char *, int, int, char, int * );
static char *GetItemC( char *****, int, int, char, char *, const char *method, const char *class, int * );
static char *SourceWrap( const char *(*)( void ), int * );
static char *UnPreQuote( const char *, int * );
static char GetMaxS( double ****item, int * );
static const char *GetAllWarnings( AstFitsChan *, int * );
static const char *GetAttrib( AstObject *, const char *, int * );
static const char *GetCardComm( AstFitsChan *, int * );
static const char *GetCardName( AstFitsChan *, int * );
static const char *GetFitsSor( const char *, int * );
static const char *IsSpectral( const char *, char[5], char[5], int * );
static double **OrthVectorSet( int, int, double **, int * );
static double *Cheb2Poly( double *, int, int, double, double, double, double, int * );
static double *FitLine( AstMapping *, double *, double *, double *, double, double *, int * );
static double *OrthVector( int, int, double **, int * );
static double *ReadCrval( AstFitsChan *, AstFrame *, char, const char *, const char *, int * );
static double ChooseEpoch( AstFitsChan *, FitsStore *, char, const char *, const char *, int * );
static double DateObs( const char *, int * );
static double GetItem( double ****, int, int, char, char *, const char *method, const char *class, int * );
static double NearestPix( AstMapping *, double, int, int * );
static double TDBConv( double, int, int, const char *, const char *, int * );
static int *CardFlags( AstFitsChan *, int * );
static int AIPSFromStore( AstFitsChan *, FitsStore *, const char *, const char *, int * );
static int AIPSPPFromStore( AstFitsChan *, FitsStore *, const char *, const char *, int * );
static int AddEncodingFrame( AstFitsChan *, AstFrameSet *, int, const char *, const char *, int * );
static int AddVersion( AstFitsChan *, AstFrameSet *, int, int, FitsStore *, double *, char, int, int, const char *, const char *, int * );
static int CLASSFromStore( AstFitsChan *, FitsStore *, AstFrameSet *, double *, const char *, const char *, int * );
static int CardType( AstFitsChan *, int * );
static int CheckFitsName( AstFitsChan *, const char *, const char *, const char *, int * );
static int ChrLen( const char *, int * );
static int CnvType( int, void *, size_t, int, int, int, void *, const char *, const char *, const char *, int * );
static int CnvValue( AstFitsChan *, int , int, void *, const char *, int * );
static int ComBlock( AstFitsChan *, int, const char *, const char *, int * );
static int CountFields( const char *, char, const char *, const char *, int * );
static int DSSFromStore( AstFitsChan *, FitsStore *, const char *, const char *, int * );
static int EncodeFloat( char *, int, int, int, int, double, int * );
static int EncodeValue( AstFitsChan *, char *, int, int, const char *, int * );
static int FindBasisVectors( AstMapping *, int, int, double *, AstPointSet *, AstPointSet *, int * );
static int FindFits( AstFitsChan *, const char *, char[ AST__FITSCHAN_FITSCARDLEN + 1 ], int, int * );
static int FindKeyCard( AstFitsChan *, const char *, const char *, const char *, int * );
static int FindLonLatSpecAxes( FitsStore *, char, int *, int *, int *, const char *, const char *, int * );
static int FindString( int, const char *[], const char *, const char *, const char *, const char *, int * );
static int FitOK( int, double *, double *, double, int * );
static int FitsAxisOrder( AstFitsChan *this, int nwcs, AstFrame *wcsfrm, int *perm, int *status );
static int FitsEof( AstFitsChan *, int * );
static int FitsFromStore( AstFitsChan *, FitsStore *, int, double *, AstFrameSet *, const char *, const char *, int * );
static int FitsGetCom( AstFitsChan *, const char *, char **, int * );
static int FitsSof( AstFitsChan *, int * );
static int FullForm( const char *, const char *, int, int * );
static int GetCardType( AstFitsChan *, int * );
static int GetFiducialWCS( AstWcsMap *, AstMapping *, int, int, double *, double *, int * );
static int GetFitsCF( AstFitsChan *, const char *, double *, int * );
static int GetFitsCI( AstFitsChan *, const char *, int *, int * );
static int GetFitsCN( AstFitsChan *, const char *, char **, int * );
static int GetFitsF( AstFitsChan *, const char *, double *, int * );
static int GetFitsI( AstFitsChan *, const char *, int *, int * );
static int GetFitsL( AstFitsChan *, const char *, int *, int * );
static int GetFitsS( AstFitsChan *, const char *, char **, int * );
static int GetFull( AstChannel *, int * );
static int GetMaxI( double ****item, char, int * );
static int GetMaxJM( double ****item, char, int * );
static int GetMaxJMC( char *****item, char, int * );
static int GetNcard( AstFitsChan *, int * );
static int GetNkey( AstFitsChan *, int * );
static int GetSkip( AstChannel *, int * );
static int GetUsedPolyTan( AstFitsChan *, AstFitsChan *, int, int, char, const char *, const char *, int * );
static int GetValue( AstFitsChan *, const char *, int, void *, int, int, const char *, const char *, int * );
static int GetValue2( AstFitsChan *, AstFitsChan *, const char *, int, void *, int, const char *, const char *, int * );
static int GoodWarns( const char *, int * );
static int HasAIPSSpecAxis( AstFitsChan *, const char *, const char *, int * );
static int HasCard( AstFitsChan *, const char *, const char *, const char *, int * );
static int IRAFFromStore( AstFitsChan *, FitsStore *, const char *, const char *, int * );
static int IsAIPSSpectral( const char *, char **, char **, int * );
static int IsMapLinear( AstMapping *, const double [], const double [], int, int *, int * );
static int IsSkyOff( AstFrameSet *, int, int * );
static int KeyFields( AstFitsChan *, const char *, int, int *, int *, int * );
static int LooksLikeClass( AstFitsChan *, const char *, const char *, int * );
static int MakeBasisVectors( AstMapping *, int, int, double *, AstPointSet *, AstPointSet *, int * );
static int MakeIntWorld( AstMapping *, AstFrame *, int *, char, FitsStore *, double *, double, int, const char *, const char *, int * );
static int Match( const char *, const char *, int, int *, int *, const char *, const char *, int * );
static int MatchChar( char, char, const char *, const char *, const char *, int * );
static int MatchFront( const char *, const char *, char *, int *, int *, int *, const char *, const char *, const char *, int * );
static int MoveCard( AstFitsChan *, int, const char *, const char *, int * );
static int PCFromStore( AstFitsChan *, FitsStore *, const char *, const char *, int * );
static int SAOTrans( AstFitsChan *, AstFitsChan *, const char *, const char *, int * );
static int SearchCard( AstFitsChan *, const char *, const char *, const char *, int * );
static int SetFits( AstFitsChan *, const char *, void *, int, const char *, int, int * );
static int Similar( const char *, const char *, int * );
static int SkySys( AstFitsChan *, AstSkyFrame *, int, int, FitsStore *, int, int, char c, int, const char *, const char *, int * );
static int Split( AstFitsChan *, const char *, char **, char **, char **, const char *, const char *, int * );
static int SplitMap( AstMapping *, int, int, int, AstMapping **, AstWcsMap **, AstMapping **, int * );
static int SplitMap2( AstMapping *, int, AstMapping **, AstWcsMap **, AstMapping **, int * );
static int SplitMat( int , double *, double *, int * );
static int TestAttrib( AstObject *, const char *, int * );
static int TestFits( AstFitsChan *, const char *, int *, int * );
static int Use( AstFitsChan *, int, int, int * );
static int Ustrcmp( const char *, const char *, int * );
static int Ustrncmp( const char *, const char *, size_t, int * );
static int WATCoeffs( const char *, int, double **, int **, int *, int * );
static int WcsFromStore( AstFitsChan *, FitsStore *, const char *, const char *, int * );
static int WcsNatPole( AstFitsChan *, AstWcsMap *, double, double, double, double *, double *, double *, int * );
static int WorldAxes( AstFitsChan *this, AstMapping *, double *, int *, int * );
static int Write( AstChannel *, AstObject *, int * );
static void *CardData( AstFitsChan *, size_t *, int * );
static void AdaptLut( AstMapping *, int, double, double, double, double, double, double **, double **, int *, int * );
static void AddFrame( AstFitsChan *, AstFrameSet *, int, int, FitsStore *, char, const char *, const char *, int * );
static void ChangePermSplit( AstMapping *, int * );
static void CheckZero( char *, double, int, int, int * );
static void Chpc1( double *, double *, int, int *, int *, int * );
static void ClassTrans( AstFitsChan *, AstFitsChan *, int, int, const char *, const char *, int * );
static void ClearAttrib( AstObject *, const char *, int * );
static void Copy( const AstObject *, AstObject *, int * );
static void CreateKeyword( AstFitsChan *, const char *, char [ FITSNAMLEN + 1 ], int * );
static void DSBSetUp( AstFitsChan *, FitsStore *, AstDSBSpecFrame *, char, double, const char *, const char *, int * );
static void DSSToStore( AstFitsChan *, FitsStore *, const char *, const char *, int * );
static void DelFits( AstFitsChan *, int * );
static void Delete( AstObject *, int * );
static void DeleteCard( AstFitsChan *, const char *, const char *, int * );
static void DistortMaps( AstFitsChan *, FitsStore *, char, int , AstMapping **, AstMapping **, AstMapping **, AstMapping **, const char *, const char *, int * );
static void Dump( AstObject *, AstChannel *, int * );
static void EmptyFits( AstFitsChan *, int * );
static void FindWcs( AstFitsChan *, int, int, int, const char *, const char *, int * );
static void FixNew( AstFitsChan *, int, int, const char *, const char *, int * );
static void FixUsed( AstFitsChan *, int, int, int, const char *, const char *, int * );
static void FormatCard( AstFitsChan *, char *, const char *, int * );
static void FreeItem( double ****, int * );
static void FreeItemC( char *****, int * );
static void GetFiducialNSC( AstWcsMap *, double *, double *, int * );
static void GetFiducialPPC( AstWcsMap *, double *, double *, int * );
static void GetNextData( AstChannel *, int, char **, char **, int * );
static void InsCard( AstFitsChan *, int, const char *, int, void *, const char *, const char *, const char *, int * );
static void MakeBanner( const char *, const char *, const char *, char [ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN + 1 ], int * );
static void MakeIndentedComment( int, char, const char *, const char *, char [ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN + 1], int * );
static void MakeIntoComment( AstFitsChan *, const char *, const char *, int * );
static void MakeInvertable( double **, int, double *, int * );
static void MarkCard( AstFitsChan *, int * );
static void NewCard( AstFitsChan *, const char *, int, const void *, const char *, int, int * );
static void PreQuote( const char *, char [ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN - 3 ], int * );
static void PurgeWCS( AstFitsChan *, int * );
static void PutCards( AstFitsChan *, const char *, int * );
static void PutFits( AstFitsChan *, const char [ AST__FITSCHAN_FITSCARDLEN + 1 ], int, int * );
static void PutTable( AstFitsChan *, AstFitsTable *, const char *, int * );
static void PutTables( AstFitsChan *, AstKeyMap *, int * );
static void ReadFits( AstFitsChan *, int * );
static void ReadFromSource( AstFitsChan *, int * );
static void RemoveTables( AstFitsChan *, const char *, int * );
static void RetainFits( AstFitsChan *, int * );
static void RoundFString( char *, int, int, int * );
static void SetAlgCode( char *, const char *, int * );
static void SetAttrib( AstObject *, const char *, int * );
static void SetFitsCF( AstFitsChan *, const char *, double *, const char *, int, int * );
static void SetFitsCI( AstFitsChan *, const char *, int *, const char *, int, int * );
static void SetFitsCM( AstFitsChan *, const char *, int, int * );
static void SetFitsCN( AstFitsChan *, const char *, const char *, const char *, int, int * );
static void SetFitsCom( AstFitsChan *, const char *, const char *, int, int * );
static void SetFitsF( AstFitsChan *, const char *, double, const char *, int, int * );
static void SetFitsI( AstFitsChan *, const char *, int, const char *, int, int * );
static void SetFitsL( AstFitsChan *, const char *, int, const char *, int, int * );
static void SetFitsS( AstFitsChan *, const char *, const char *, const char *, int, int * );
static void SetFitsU( AstFitsChan *, const char *, const char *, int, int * );
static void SetItem( double ****, int, int, char, double, int * );
static void SetItemC( char *****, int, int, char, const char *, int * );
static void SetSourceFile( AstChannel *, const char *, int * );
static void SetValue( AstFitsChan *, const char *, void *, int, const char *, int * );
static void ShowFits( AstFitsChan *, int * );
static void Shpc1( double, double, int, double *, double *, int * );
static void SinkWrap( void (*)( const char * ), const char *, int * );
static void SkyPole( AstWcsMap *, AstMapping *, int, int, int *, char, FitsStore *, const char *, const char *, int * );
static void TableSource( AstFitsChan *, void (*)( AstFitsChan *, const char *, int, int, int * ), int * );
static void TidyOffsets( AstFrameSet *, int * );
static void Warn( AstFitsChan *, const char *, const char *, const char *, const char *, int * );
static void WcsFcRead( AstFitsChan *, AstFitsChan *, FitsStore *, const char *, const char *, int * );
static void WcsToStore( AstFitsChan *, AstFitsChan *, FitsStore *, const char *, const char *, int * );
static void WriteBegin( AstChannel *, const char *, const char *, int * );
static void WriteDouble( AstChannel *, const char *, int, int, double, const char *, int * );
static void WriteEnd( AstChannel *, const char *, int * );
static void WriteFits( AstFitsChan *, int * );
static void WriteInt( AstChannel *, const char *, int, int, int, const char *, int * );
static void WriteIsA( AstChannel *, const char *, const char *, int * );
static void WriteObject( AstChannel *, const char *, int, int, AstObject *, const char *, int * );
static void WriteString( AstChannel *, const char *, int, int, const char *, const char *, int * );
static void WriteToSink( AstFitsChan *, int * );
static void SetTableSource( AstFitsChan *,
                            void (*)( void ),
                            void (*)( void (*)( void ),
                                      AstFitsChan *, const char *, int, int, int * ), int * );
static void TabSourceWrap( void (*)( void ),
                           AstFitsChan *, const char *, int, int, int * );
#if defined(THREAD_SAFE)
static int ManageLock( AstObject *, int, int, AstObject **, int * );
#endif

/* Member functions. */
/* ================= */

static void AdaptLut( AstMapping *map, int npos, double eps, double x0,
                      double x1, double v0, double v1, double **xtab,
                      double **vtab, int *nsamp, int *status ){
/*
*  Name:
*     AdaptLut

*  Purpose:
*     Create a table of optimally sampled values for a Mapping.

*  Type:
*     Private function.

*  Synopsis:
*     void AdaptLut( AstMapping *map, int npos, double eps, double x0,
*                    double x1, double v0, double v1, double **xtab,
*                    double **vtab, int *nsamp, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function returns a look-up table holding samples of the supplied
*     1D mapping. The input values at which the samples are taken are
*     returned in the "xtab" array, and the Mapping output values at
*     these input values are returned in the "vtab" array. The sample
*     spacing is smaller at positions where the output gradient is
*     changing more rapidly (i.e. where the output is more non-linear).

*  Parameters:
*     map
*        Pointer to the Mapping. Should have 1 input and 1 output.
*     npos
*        The minimum number of samples to place within the interval to be
*         sampled, excluding the two end points (which are always sampeld
*         anyway). These samples are placed evenly through the [x0,x1]
          interval. The interval between adjacent samples will be further
*         subdivided if necessary by calling this function recursively.
*     eps
*        The maximum error in X (i.e. the Mapping input) allowed before
*        the supplied interval is subdivided further by a recursive call
*        to this function.
*     x0
*        The Mapping input value at the start of the interval to be sampled.
*        It is assumed that this value is already stored in (*xtab)[0] on
*        entry.
*     x1
*        The Mapping input value at the end of the interval to be sampled.
*     v0
*        The Mapping output value at the start of the interval to be sampled.
*        It is assumed that this value is already stored in (*vtab)[0] on
*        entry.
*     v1
*        The Mapping output value at the end of the interval to be sampled.
*     xtab
*        Address of a pointer to the array in which to store the Mapping
*        input values at which samples were taken. The supplied pointer
*        may be changed on exit to point to a larger array. New values
*        are added to the end of this array. The initial size of the array
*        is given by the supplied value for "*nsamp"
*     vtab
*        Address of a pointer to the array in which to store the Mapping
*        output value at each sample. The supplied pointer may be changed
*        on exit to point to a larger array. New values are added to the
*        end of this array. The initial size of the array is given by the
*        supplied value for "*nsamp".
*     nsamp
*        Address of an int holding the number of values in the "*xtab"
*        and "*ytab" arrays. Updated on exit to include the new values
*        added to the arrays by this function.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     The size of the returned xtab and vtab arrays.
*/

/* Local Variables: */
   double *vv;               /* Pointer to Mapping output values */
   double *xx;               /* Pointer to Mapping input values */
   double dx;                /* Step between sample positions */
   double rg;                /* Reciprocal of gradient of (x0,v0)->(x1,v1) line */
   double xx0;               /* X at first new sample position */
   int ipos;                 /* Interior sample index */
   int isamp;                /* Index into extended xtab and vtab arrays. */
   int subdivide;            /* Subdivide each subinterval? */

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

/* Allocate work space. */
   xx = astMalloc( sizeof( double )*npos );
   vv = astMalloc( sizeof( double )*npos );
   if( astOK ) {

/* Set up the evenly spaced interior sample positions. */
      dx = ( x1 - x0 )/( npos + 1 );
      xx0 = x0 + dx;
      for( ipos = 0; ipos < npos; ipos++ ) {
         xx[ ipos ] = xx0 + ipos*dx;
      }

/* Find the Mapping output values at these input values. */
      astTran1( map, npos, xx, 1, vv );

/* See if any of these samples deviate significantly from the straight line
   defined by (x0,v0) and (x1,v1). If any such sample is found, we call
   this function recursively to sample the subdivided intervals. First
   handle cases where the straight line has zero gradient. */
      subdivide = 0;
      if( v0 == v1 ) {

/* Subdivide if any of the interior sample values are different to the
   end values. */
         for( ipos = 0; ipos < npos; ipos++ ) {
            if( vv[ ipos ] != v0 ) {
               subdivide = 1;
               break;
            }
         }

/* Now handle cases where the line has non-zero gradient. Subdivide if any
   of the interior sample input positions are further than "eps" from the
   input position that would give the same output value if the mapping was
   linear. */
      } else {
         rg = ( x1 - x0 )/( v1 - v0 );
         for( ipos = 0; ipos < npos; ipos++ ) {
            if( vv[ ipos ] == AST__BAD ||
                fabs( rg*( vv[ ipos ] - v0 ) - ( xx[ ipos ] - x0 ) ) > eps ) {
               subdivide = 1;
               break;
            }
         }
      }

/* If required, call this function recursively to subdivide each section
   of the supplied input interval, and append samples to the returned
   arrays. */
      if( subdivide ) {

/* Do each sub-interval, except the last one. The number of subintervals
   is one more than the number of interior samples. */
         for( ipos = 0; ipos < npos; ipos++ ) {

/* Append samples covering the current subinterval to the ends of the
   arrays. */
            AdaptLut( map, npos, eps, x0, xx[ ipos ], v0, vv[ ipos ],
                      xtab, vtab, nsamp, status );

/* Store the starting position for the next sub-interval. */
            x0 = xx[ ipos ];
            v0 = vv[ ipos ];
         }

/* Now do the final sub-interval. */
         AdaptLut( map, npos, eps, x0, x1, v0, v1, xtab, vtab, nsamp, status );

/* If we do not need to subdivide, store the samples in the returned
   array, together with the supplied final point. */
      } else {

/* Extend the arrays. */
         isamp = *nsamp;
         *nsamp += npos + 1;
         *xtab = astGrow( *xtab, *nsamp, sizeof( double ) );
         *vtab = astGrow( *vtab, *nsamp, sizeof( double ) );
         if( astOK ) {

/* Store the sample positions and values at the end of the extended
   arrays. */
            for( ipos = 0; ipos < npos; ipos++, isamp++ ) {
               (*xtab)[ isamp ] = xx[ ipos ];
               (*vtab)[ isamp ] = vv[ ipos ];
            }
            (*xtab)[ isamp ] = x1;
            (*vtab)[ isamp ] = v1;
         }
      }
   }

/* Free resources. */
   xx = astFree( xx );
   vv= astFree( vv );
}

static int AddEncodingFrame( AstFitsChan *this, AstFrameSet *fs, int encoding,
                             const char *method, const char *class, int *status ){

/*
*  Name:
*     AddEncodingFrame

*  Purpose:
*     Add a Frame which conforms to the requirements of the specified encoding.

*  Type:
*     Private function.

*  Synopsis:
*     int AddEncodingFrame( AstFitsChan *this, AstFrameSet *fs, int encoding,
*                           const char *method, const char *class, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function attempts to create a Frame based on the current Frame
*     of the supplied FrameSet, which conforms to the requirements of the
*     specified Encoding. If created, this Frame is added into the
*     FrameSet as the new current Frame, and the index of the original current
*     Frame is returned.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     fs
*        Pointer to the FrameSet.
*     encoding
*        The encoding in 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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     The index of the original current Frame in the FrameSet. A value of
*     AST__NOFRAME is returned if no new Frame is added to the FrameSet,
*     or if an error occurs.
*/

/* Local Variables: */
   AstCmpFrame *cmpfrm;   /* Pointer to spectral cube frame */
   AstFrame *cfrm;        /* Pointer to original current Frame */
   AstFrame *newfrm;      /* Frame describing coord system to be used */
   AstFrame *pfrm;        /* Pointer to primary Frame containing axis */
   AstFrameSet *fsconv;   /* FrameSet converting what we have to what we want */
   AstMapping *map;       /* Mapping from what we have to what we want */
   AstSkyFrame *skyfrm;   /* Pointer to SkyFrame */
   AstSpecFrame *specfrm; /* Pointer to SpecFrame */
   AstSystemType sys;     /* Frame coordinate system */
   int i;                 /* Axis index */
   int naxc;              /* No. of axes in original current Frame */
   int paxis;             /* Axis index in primary frame */
   int result;            /* Returned value */

/* Initialise */
   result = AST__NOFRAME;

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

/* Get a pointer to the current Frame and note how many axes it has. */
   cfrm = astGetFrame( fs, AST__CURRENT );
   naxc = astGetNaxes( cfrm );

/* FITS-CLASS */
/* ========== */
   if( encoding == FITSCLASS_ENCODING ) {

/* Try to locate a SpecFrame and a SkyFrame in the current Frame. */
      specfrm = NULL;
      skyfrm = NULL;
      for( i = 0; i < naxc; i++ ) {
         astPrimaryFrame( cfrm, i, &pfrm, &paxis );
         if( astIsASpecFrame( pfrm ) ) {
            if( !specfrm ) specfrm = astCopy( pfrm );
         } else if( IsASkyFrame( pfrm ) ) {
            if( !skyfrm ) skyfrm = astCopy( pfrm );
         }
         pfrm = astAnnul( pfrm );
      }

/* Cannot do anything if either is missing. */
      if( specfrm && skyfrm ) {

/* If the spectral axis is not frequency, set it to frequency. Also set
   spectral units of "Hz". */
         sys = astGetSystem( specfrm );
         if( sys != AST__FREQ ) {
            astSetSystem( specfrm, AST__FREQ );
            sys = AST__FREQ;
         }

/* Ensure the standard of rest is Source and units are "Hz". */
         astSetUnit( specfrm, 0, "Hz" );
         astSetStdOfRest( specfrm, AST__SCSOR );

/* The celestial axes must be either FK4, FK5 or galactic. */
         sys = astGetSystem( skyfrm );
         if( sys != AST__FK4 && sys != AST__FK5 && sys != AST__GALACTIC ) {
            astSetSystem( skyfrm, AST__FK5 );
            sys = AST__FK5;
         }

/* FK5 systems must be J2000, and FK4 must be B1950. */
         if( sys == AST__FK5 ) {
            astSetC( skyfrm, "Equinox", "J2000.0" );
         } else if( sys == AST__FK4 ) {
            astSetC( skyfrm, "Equinox", "B1950.0" );
         }

/* Combine the spectral and celestial Frames into a single CmpFrame with
   the spectral axis being the first axis. */
         cmpfrm = astCmpFrame( specfrm, skyfrm, "", status );

/* Attempt to obtain the current Frame of the supplied FrameSet to this
   new Frame. */
         fsconv = astConvert( cfrm, cmpfrm, "" );
         if( fsconv ) {

/* Get the Mapping and current Frame from the rconversion FrameSet. */
            newfrm = astGetFrame( fsconv, AST__CURRENT );
            map = astGetMapping( fsconv, AST__BASE, AST__CURRENT );

/* Save the original current Frame index. */
            result = astGetCurrent( fs );

/* Add the new Frame into the supplied FrameSet using the above Mapping
   to connect it to the original current Frame. The new Frame becomes the
   current Frame. */
            astAddFrame( fs, AST__CURRENT, map, newfrm );

/* Free resources */
            map = astAnnul( map );
            newfrm = astAnnul( newfrm );
            fsconv = astAnnul( fsconv );
         }

/* Free resources */
         cmpfrm = astAnnul( cmpfrm );
      }

/* Release resources. */
      if( specfrm ) specfrm = astAnnul( specfrm );
      if( skyfrm ) skyfrm = astAnnul( skyfrm );
   }

/* Free reources. */
   cfrm = astAnnul( cfrm );

/* Return the result */
   return result;
}

static void AddFrame( AstFitsChan *this, AstFrameSet *fset, int pixel,
                      int npix, FitsStore *store, char s, const char *method,
                      const char *class, int *status ){
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.
*/

/* 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 nf;                     /* Number of Frames originally in fset */
   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, status );

/* Add the Frame into the FrameSet, and annul the mapping and frame. If
   the mapping has more inputs than there are axes in the pixel Frame,
   use a PermMap which assigns constant value 0.0 to the extra axes. Zero
   is used rather than 1.0 because the default value for CRPIX is zero.
   If the new Frame has fewer 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 = 0.0;
            pmap = astPermMap( npix, inperm, nwcs, outperm, &con, "", status );
            tmap = (AstMapping *) astCmpMap( pmap, mapping, 1, "", status );
            pmap = astAnnul( pmap );
            (void) astAnnul( mapping );
            mapping = tmap;
         }
         inperm = astFree( inperm );
         outperm = astFree( outperm );
      }

/* Record the original number of Frames in the FrameSet. */
      nf = astGetNframe( fset );

/* Add in the Frame (which may be a FrameSet). */
      astAddFrame( fset, pixel, mapping, frame );

/* Ensure the WCS Frame is the current Frame within fset (it may not be if
   the frame returned by WcsMapFrm is actually a FrameSet containing a IWC
   Frame as well as a WCS Frame). The WCS Frame is always the first frame
   in the frame/frameset returned by WcsMapFrm. */
      astSetCurrent( fset, nf + 1 );

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

static int AddVersion( AstFitsChan *this, AstFrameSet *fs, int ipix, int iwcs,
                       FitsStore *store, double *dim, char s, int encoding,
                       int isoff, const char *method, const char *class,
                       int *status ){
/*
*  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, int encoding,
*                     int isoff, const char *method, const char *class,
*                     int *status )

*  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').
*     encoding
*        The encoding being used.
*     isoff
*        If greater than zero, the Frame is an offset SkyFrame and the
*        description added to the FitsStore should describe offset coordinates.
*        If less than than zero, the Frame is an offset SkyFrame and the
*        description added to the FitsStore should describe absolute coordinates.
*        If zero, the Frame is an absolute SkyFrame and the description added
*        to the FitsSTore should (by  necessity) describe absolute coordinates.
*     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.
*     status
*        Pointer to the inherited status variable.

*  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 *mapping;     /* Mapping from pixel to WCS Frame */
   AstMapping *pixiwcmap;   /* Mapping from pixel to IWC Frame */
   AstMapping *tmap2;       /* Temporary Mapping */
   AstMapping *tmap;        /* Temporary Mapping */
   const char *old_skyrefis;/* Old value of SkyRefIs attribute */
   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 fitstol;          /* Max departure from linearity, in pixels */
   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 icurr;               /* Index of current Frame */
   int nwcs;                /* No. of axes in WCS frame */
   int ret;                 /* Returned value */
   int sipok;               /* Should SIP headers be produced? */

/* Initialise */
   ret = 0;

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

/* If the frame is a SkyFrame describing offset coordinates, but the
   description added to the FitsStore should be for absolute coordinates,
   temporarily clear the SkyFrame SkyRefIs attribute. We need to make it
   the current Frame first so that we can use the FrameSet to clear the
   attribte, so that the SkyFrame will be re-mapped within the FrameSet
   to take account of the clearing. For negative isoff values, set the
   specific negative value to indicate the original SkyRefIs value. */
   if( isoff < 0 ) {
      icurr = astGetCurrent( fs );
      astSetCurrent( fs, iwcs );
      old_skyrefis = astGetC( fs, "SkyRefIs" );
      if( astOK ) {
         if( !Ustrcmp( old_skyrefis, "POLE", status ) ) {
            isoff = -1;
         } else if( !Ustrcmp( old_skyrefis, "ORIGIN", status ) ) {
            isoff = -2;
         } else {
            isoff = -3;
         }
      }
      astClear( fs, "SkyRefIs" );
      astSetCurrent( fs, icurr );
   } else {
      old_skyrefis = AST__BAD_REF;
   }

/* 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( this, fs, ipix, iwcs, encoding, method, class, status );

/* If required, re-instate the original value of the SkyRefIs attribute
   in the supplied FrameSet. */
   if( old_skyrefis != AST__BAD_REF ) {
      astSetCurrent( fs, iwcs );
      astSetC( fs, "SkyRefIs", old_skyrefis );
      astSetCurrent( fs, icurr );
   }

/* 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. Note, we can handle non-invertable Mappings if
   we are allowed to use the -TAB algorithm. */
   mapping = astGetMapping( fset, AST__BASE, AST__CURRENT );
   wcsfrm = astGetFrame( fset, AST__CURRENT );
   if( !astGetTranInverse( mapping ) && astGetTabOK( this ) <= 0 ) {
      mapping = astAnnul( mapping );
      wcsfrm = astAnnul( wcsfrm );
      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.
   Allocate memory to store these indices. */
   nwcs = astGetNout( mapping );
   wperm = astMalloc( sizeof(int)*(size_t) nwcs );

/* Attempt to use the FitsAxisOrder attribute to determine the order. If
   this is set to "<auto>", then for each WCS axis, we use the index of
   the pixel axis which is most closely aligned with it. */
   if( !FitsAxisOrder( this, nwcs, wcsfrm, wperm, status ) &&
       !WorldAxes( this, mapping, dim, wperm, status ) ) {
      wperm = astFree( wperm );
      mapping = astAnnul( mapping );
      wcsfrm = astAnnul( wcsfrm );
      fset = astAnnul( fset );
      return ret;
   }

/* 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 ). */
   crvals = ReadCrval( this, wcsfrm, s, method, class, status );

/* 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 describe 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( this, fset, dim, wperm, s, store, axis_done,
                           isoff, method, class, status );

/* Now look for spectral axes, and update the iwcmap. */
   tmap = SpectralAxes( this, fset, dim, wperm, s, store, crvals, axis_done,
                        method, class, status );
   tmap2 = (AstMapping *) astCmpMap( iwcmap, tmap, 1, "", status );
   tmap = astAnnul( tmap );
   (void) 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( this, fset, dim, wperm, s, store, crvals, axis_done,
                     method, class, status );
   tmap2 = (AstMapping *) astCmpMap( iwcmap, tmap, 1, "", status );
   tmap = astAnnul( tmap );
   (void) 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. */
   pixiwcmap = (AstMapping *) astCmpMap( mapping, iwcmap, 1, "", status );
   mapping = astAnnul( mapping );
   iwcmap = astAnnul( iwcmap );

/* Get the maximum departure from linearity, in pixels, for the pixiwcmap
   mapping to be considered linear. */
   fitstol = astGetFitsTol( this );

/* See if SIP headers are to be produced. */
   sipok = astGetSipOK( this );

/* Now attempt to store values for the keywords describing the pixel->IWC
   Mapping (CRPIX, CD, PC, CDELT). This tests that the iwcmap is linear.
   It can also include keywords describing distortion in the form of SIP
   headers, if appropriate. Zero is returned if the test fails. */
   ret = MakeIntWorld( pixiwcmap, wcsfrm, wperm, s, store, dim, fitstol,
                       sipok, method, class, status );

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

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

/* Store the UT1-UTC correction, if set, converting from seconds to days
   (as used by JACH). */
      if( astTestDut1( wcsfrm ) && s == ' ' ) {
         SetItem( &(store->dut1), 0, 0, ' ', astGetDut1( wcsfrm )/SPD, status );
      }

/* Store the TAI-UTC correction, if set. */
      if( astTestDtai( wcsfrm ) && s == ' ' ) {
         SetItem( &(store->dtai), 0, 0, ' ', astGetDtai( wcsfrm ), status );
      }

/* 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,
                          status );
         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, status );
               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, status );
            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, status );
            }
         }
      }

/* 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, status );
         if( crpix != AST__BAD ) {
            SetItem( &(store->crpix), 0, iax, s,
                     floor( crpix*1.0E6 + 0.5 )*1.0E-6, status );
         }
      }
   }

/* 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, int *status ) {
/*
*  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, int *status )

*  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 the 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.
*     status
*        Pointer to the inherited status variable.

*  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, "", status );
      tmap1 = (AstMapping *) astCmpMap( tmap0, ret, 0, "", status );
      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, "", status );
      tmap2 = (AstMapping *) astCmpMap( ret, tmap1, 0, "", status );
      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, int *status ){

/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

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

/* Local Variables: */
   char *comm;         /* Pointer to comment string */
   const char *cval;   /* Pointer to string keyword value */
   const 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 axrot1;         /* Index of first CROTA rotation axis */
   int axrot2;         /* Index of second CROTA rotation 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;

/* Initialise */
   specunit = "";
   specfactor = 1.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, status );

/* 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, status );
      if( latval == AST__BAD ) ok = 0;
      lonval = GetItem( &( store->crval ), axlon, 0, s, NULL, method, class, status );
      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, 0, s, NULL, method, class, status );
      if( !cval ) {
         ok = 0;
      } else {
         strcpy( lontype, cval );
      }
      cval = GetItemC( &(store->ctype), axlat, 0, s, NULL, method, class, status );
      if( !cval ) {
         ok = 0;
         prj = AST__WCSBAD;
      } else {
         strcpy( lattype, cval );
         prj = astWcsPrjType( cval + 4 );
      }

/* Check the projection type is OK. */
      if( prj == AST__WCSBAD ){
         ok = 0;
      } else if( prj != AST__SIN ){

/* There must be no projection parameters. */
         if( GetMaxJM( &(store->pv), ' ', status ) >= 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, status );
         p2 = GetItem( &( store->pv ), axlat, 2, s, NULL, method, class, status );
         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, status )
          != AST__BAD ||
          GetItem( &(store->lonpole), 0, 0, s, NULL, method, class, status )
          != 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, 0, s, NULL, method, class, status );
      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, 0, s, NULL, method, class, status );
      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, 0, s, NULL, method, class, status );
      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), ' ', status ) + 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. If there are no celestial
   axes, restrict rotation to the first two non-spectral axes. */
   if( axlat < 0 && axlon < 0 ) {
      if( axspec >= 0 && naxis > 2 ) {
         axrot2 = ( axspec == 0 ) ? 1 : 0;
         axrot1 = axrot2 + 1;
         if( axrot1 == axspec ) axrot1++;
      } else if( naxis > 1 ){
         axrot2 = 0;
         axrot1 = 1;
      } else {
         axrot2 = -1;
         axrot1 = -1;
      }
   } else {
      axrot1 = axlon;
      axrot2 = axlat;
   }
   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, status );
      if( cdl == AST__BAD ) cdl = 1.0;
      for( j = 0; j < naxis && ok; j++ ){
          val = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
          if( val == AST__BAD ) val = ( i == j ) ? 1.0 : 0.0;
          val *= cdl;
          if( i == j ){
             cdelt[ i ] = val;
          } else if( i == axrot2 && j == axrot1 ){
             cdlat_lon = val;
          } else if( i == axrot1 && j == axrot2 ){
             cdlon_lat = val;
          } else if( val != 0.0 ){
             ok = 0;
          }
      }
   }

/* Find the CROTA and CDELT values for the celestial axes. */
   if( ok && axrot1 >= 0 && axrot2 >= 0 ) {
      if( cdlat_lon > 0.0 ) {
         rho_a = atan2( cdlat_lon, cdelt[ axrot1 ] );
      } else if( cdlat_lon == 0.0 ) {
         rho_a = 0.0;
      } else {
         rho_a = atan2( -cdlat_lon, -cdelt[ axrot1 ] );
      }
      if( cdlon_lat > 0.0 ) {
         rho_b = atan2( cdlon_lat, -cdelt[ axrot2 ] );
      } else if( cdlon_lat == 0.0 ) {
         rho_b = 0.0;
      } else {
         rho_b = atan2( -cdlon_lat, cdelt[ axrot2 ] );
      }
      if( fabs( palDrange( rho_a - rho_b ) ) < 1.0E-2 ){
         crota = 0.5*( palDranrm( rho_a ) + palDranrm( rho_b ) );
         coscro = cos( crota );
         sincro = sin( crota );
         if( fabs( coscro ) > fabs( sincro ) ){
            cdelt[ axrot2 ] /= coscro;
            cdelt[ axrot1 ] /= coscro;
         } else {
            cdelt[ axrot2 ] = -cdlon_lat/sincro;
            cdelt[ axrot1 ] = 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, 0, s, NULL, method, class, status );
   epoch = GetItem( &(store->equinox), 0, 0, s, NULL, method, class, status );

/* 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, status );
         if( val == AST__BAD ) {
            ok = 0;
         } else {
            sprintf( combuf, "Reference pixel on axis %d", j + 1 );
            SetValue( this, FormatKey( "CRPIX", j + 1, -1, s, status ), &val,
                      AST__FLOAT, combuf, status );
         }
      }

/* 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, status );
         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, status ), &val,
                      AST__FLOAT, combuf, status );
         }
      }

/* 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, 0, s, NULL, method, class, status );
         }
         if( cval && ( strlen(cval) < 5 || strcmp( cval + 4, "-TAB" ) ) ) {
            comm = GetItemC( &(store->ctype_com), i, 0, s, NULL, method, class, status );
            if( !comm ) {
               sprintf( combuf, "Type of co-ordinate on axis %d", i + 1 );
               comm = combuf;
            }
            SetValue( this, FormatKey( "CTYPE", i + 1, -1, s, status ), &cval,
                      AST__STRING, comm, status );
         } 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, status ), cdelt + i,
                   AST__FLOAT, "Pixel size", status );
      }

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

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

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

/* Date of observation. */
      val = GetItem( &(store->mjdobs), 0, 0, ' ', NULL, method, class, status );
      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. */
         palCaldj( 99, 1, 1, &mjd99, &jj );
         if( val < mjd99 ) {
            palDjcal( 0, val, iymdf, &jj );
            sprintf( combuf, "%2.2d/%2.2d/%2.2d", iymdf[ 2 ], iymdf[ 1 ],
                     iymdf[ 0 ] - ( ( iymdf[ 0 ] > 1999 ) ? 2000 : 1900 ) );
         } else {
            palDjcl( val, iymdf, iymdf+1, iymdf+2, &fd, &jj );
            palDd2tf( 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", status );
      }

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

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

/* 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, int *status ){

/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

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

/* Local Variables: */
   char *comm;         /* Pointer to comment string */
   const char *cval;   /* Pointer to string keyword value */
   const 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 axrot1;         /* Index of first CROTA rotation axis */
   int axrot2;         /* Index of second CROTA rotation 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;

/* Initialise */
   specunit = "";
   specfactor = 1.0;
   maxm = 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), ' ', status ) + 1;

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

/* 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, 0, s, NULL, method, class, status );
      if( !cval ) {
         ok = 0;
      } else {
         strcpy( lontype, cval );
      }
      cval = GetItemC( &(store->ctype), axlat, 0, s, NULL, method, class, status );
      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 || prj == AST__WCSBAD ) 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), ' ', status );
      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, status );
               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, 0, s, NULL, method, class, status );
      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, 0, s, NULL, method, class, status );
      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, 0, s, NULL, method, class, status );
      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. If there are no celestial
   axes, restrict rotation to the first two non-spectral axes. */
   if( axlat < 0 && axlon < 0 ) {
      if( axspec >= 0 && naxis > 2 ) {
         axrot2 = ( axspec == 0 ) ? 1 : 0;
         axrot1 = axrot2 + 1;
         if( axrot1 == axspec ) axrot1++;
      } else if( naxis > 1 ){
         axrot2 = 0;
         axrot1 = 1;
      } else {
         axrot2 = -1;
         axrot1 = -1;
      }
   } else {
      axrot1 = axlon;
      axrot2 = axlat;
   }
   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, status );
      if( cdl == AST__BAD ) cdl = 1.0;
      for( j = 0; j < naxis && ok; j++ ){
          val = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
          if( val == AST__BAD ) val = ( i == j ) ? 1.0 : 0.0;
          val *= cdl;
          if( i == j ){
             cdelt[ i ] = val;
          } else if( i == axrot2 && j == axrot1 ){
             cdlat_lon = val;
          } else if( i == axrot1 && j == axrot2 ){
             cdlon_lat = val;
          } else if( val != 0.0 ){
             ok = 0;
          }
      }
   }

/* Find the CROTA and CDELT values for the celestial axes. */
   if( ok && axrot1 >= 0 && axrot2 >= 0 ) {
      if( cdlat_lon > 0.0 ) {
         rho_a = atan2( cdlat_lon, cdelt[ axrot1 ] );
      } else if( cdlat_lon == 0.0 ) {
         rho_a = 0.0;
      } else {
         rho_a = atan2( -cdlat_lon, -cdelt[ axrot1 ] );
      }
      if( cdlon_lat > 0.0 ) {
         rho_b = atan2( cdlon_lat, -cdelt[ axrot2 ] );
      } else if( cdlon_lat == 0.0 ) {
         rho_b = 0.0;
      } else {
         rho_b = atan2( -cdlon_lat, cdelt[ axrot2 ] );
      }
      if( fabs( palDrange( rho_a - rho_b ) ) < 1.0E-2 ){
         crota = 0.5*( palDranrm( rho_a ) + palDranrm( rho_b ) );
         coscro = cos( crota );
         sincro = sin( crota );
         if( fabs( coscro ) > fabs( sincro ) ){
            cdelt[ axrot2 ] /= coscro;
            cdelt[ axrot1 ] /= coscro;
         } else {
            cdelt[ axrot2 ] = -cdlon_lat/sincro;
            cdelt[ axrot1 ] = cdlat_lon/sincro;
         }
         crota *= AST__DR2D;

/* Use AST__BAD to indicate that CDi_j values should be produced
   instead of CROTA/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, 0, s, NULL, method, class, status );
   epoch = GetItem( &(store->equinox), 0, 0, s, NULL, method, class, status );

/* 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, status );
         if( val == AST__BAD ) {
            ok = 0;
         } else {
            sprintf( combuf, "Reference pixel on axis %d", j + 1 );
            SetValue( this, FormatKey( "CRPIX", j + 1, -1, s, status ), &val,
                      AST__FLOAT, combuf, status );
         }
      }

/* 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, status );
         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, status ), &val,
                      AST__FLOAT, combuf, status );
         }
      }

/* 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, 0, s, NULL, method, class, status );
         }
         if( cval && ( strlen(cval) < 5 || strcmp( cval + 4, "-TAB" ) ) ) {
            comm = GetItemC( &(store->ctype_com), i, 0, s, NULL, method, class, status );
            if( !comm ) {
               sprintf( combuf, "Type of co-ordinate on axis %d", i + 1 );
               comm = combuf;
            }
            SetValue( this, FormatKey( "CTYPE", i + 1, -1, s, status ), &cval,
                      AST__STRING, comm, status );
         } 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, status ), cdelt + i,
                   AST__FLOAT, "Pixel size", status );
      }

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

/* 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, status );
            if( cdl == AST__BAD ) cdl = 1.0;
            for( j = 0; j < naxis; j++ ){
               val = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
               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, status ), &val,
                             AST__FLOAT, "Transformation matrix element", status );
               }
            }
         }

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

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

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

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

/* Date of observation. */
      val = GetItem( &(store->mjdobs), 0, 0, ' ', NULL, method, class, status );
      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. */
         palCaldj( 99, 1, 1, &mjd99, &jj );
         if( val < mjd99 ) {
            palDjcal( 0, val, iymdf, &jj );
            sprintf( combuf, "%2.2d/%2.2d/%2.2d", iymdf[ 2 ], iymdf[ 1 ],
                     iymdf[ 0 ] - ( ( iymdf[ 0 ] > 1999 ) ? 2000 : 1900 ) );
         } else {
            palDjcl( val, iymdf, iymdf+1, iymdf+2, &fd, &jj );
            palDd2tf( 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", status );
      }

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

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

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

/* 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, int *status ){

/*
*  Name:
*     CardComm

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

*  Type:
*     Private function.

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

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, int *status ){

/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, int *status ){

/*
*  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, int *status )

*  Class Membership:
*     FitsChan member function.

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

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     status
*        Pointer to the inherited status variable.

*  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, int *status ){
/*
*  Name:
*     CardName

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

*  Type:
*     Private function.

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

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, int *status ){
/*
*  Name:
*     CardType

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

*  Type:
*     Private function.

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

*  Class Membership:
*     FitsChan member function.

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

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     status
*        Pointer to the inherited status variable.

*  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( AstFitsChan *this, AstFrameSet *fs, double *dim,
                                  int *wperm, char s, FitsStore *store, int *axis_done,
                                  int isoff, const char *method, const char *class, int *status ){

/*
*  Name:
*     CelestialAxes

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

*  Type:
*     Private function.

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

*  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:
*     this
*        Pointer to the FitsChan.
*     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 unknown 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.
*     isoff
*        If greater than zero, the description to add to the FitsStore
*        should describe offset coordinates. If less than zero, the
*        description to add to the FitsStore should describe absolute
*        coordinates but should include the SkyRefIs, SkyRef and SkyRefP
*        attributes. If zero, ignore all offset coordinate info. The
*        absolute value indicates the nature of the reference point:
*        1 == "pole", 2 == "origin", otherwise "ignored".
*     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.
*     status
*        Pointer to the inherited status variable.

*  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: */
   AstFitsTable *table;    /* Pointer to structure holding -TAB table info */
   AstFrame *pframe;       /* Primary Frame containing current WCS axis*/
   AstFrame *wcsfrm;       /* WCS Frame within FrameSet */
   AstMapping *map0;       /* Unsimplified Pixel -> WCS mapping */
   AstMapping *map1;       /* Pointer to pre-WcsMap Mapping */
   AstMapping *map3;       /* Pointer to post-WcsMap Mapping */
   AstMapping *map;        /* Simplified 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 *mat;            /* Pointer to matrix diagonal elements */
   double *ppcfid;         /* Pointer to array holding PPC at fiducial point */
   double con;             /* Constant value for unassigned axes */
   double crval[ 2 ];      /* Psi coords of reference point */
   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 extver;             /* Table version number for -TAB headers */
   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 icolindexlat;       /* Index of table column holding lat index vector */
   int icolindexlon;       /* Index of table column holding lon index vector */
   int icolmainlat;        /* Index of table column holding main lat coord array */
   int icolmainlon;        /* Index of table column holding main lon coord array */
   int interplat;          /* INterpolation method for latitude look-up tables */
   int interplon;          /* INterpolation method for longitude look-up tables */
   int ilat;               /* Index of latitude axis within total WCS Frame */
   int ilon;               /* Index of longitude axis within total WCS Frame */
   int j;                  /* Loop index */
   int m;                  /* Projection parameter index */
   int maxm;               /* Largest used "m" value */
   int mlat;               /* Index of latitude axis in main lat coord array */
   int mlon;               /* Index of longitude axis in main lon coord array */
   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;

/* Other initialisation to avoid compiler warnings. */
   mlon = 0;
   mlat = 0;

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

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

/* Store the number of WCS axes. */
   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( IsASkyFrame( 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 ) {

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

/* 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. */
      map0 = astGetMapping( fs, AST__BASE, AST__CURRENT );
      map = astSimplify( map0 );
      map0 = astAnnul( map0 );

/* Get the table version number to use if we end up using the -TAB
   algorithm. This is the set value of the TabOK attribute (if positive). */
      extver = astGetTabOK( this );

/* 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 the ForceTab attribute is used, then we attempt to use -TAB even if
   the Mapping can be split. */
      if( !astGetForceTab( this ) &&
          SplitMap( map, astGetInvert( map ), ilon, ilat, &map1, &map2, &map3,
                    status ) ){

/* 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, status ) ){

/* 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 );

/* 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 ], status );
            SetItem( &(store->crval), fits_ilat, 0, s, AST__DR2D*skyfid[ skylataxis ], status );

/* 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, status );
                  }
               }
               if( astTestPV( map2, axlat, m ) ) {
                  pv = astGetPV( map2, axlat, m );
                  if( pv != AST__BAD ) SetItem( &(store->pv), fits_ilat, m,
                                                s, pv, status );
               }
            }

/* 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, status );
               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( this, skyfrm, 1, astGetWcsType( map2 ), store, fits_ilon,
                    fits_ilat, s, isoff, method, class, status );

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

/* 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, "", status );

/* 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, "", status );
               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, status );

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

/* If there are more WCS axes than IWC axes, create a UnitMap for the extra
   WCS axes and add it in parallel with tmap3. */
            nwcsmap = astGetNin( map3 );
            if( nwcsmap < nwcs ) {
               tmap2 = (AstMapping *) astUnitMap( nwcs - nwcsmap, "", status );
               tmap4 = (AstMapping *) astCmpMap( tmap3, tmap2, 0, "", status );
               tmap3 = astAnnul( tmap3 );
               tmap2 = astAnnul( tmap2 );
               tmap3 = tmap4;
               nwcsmap = nwcs;
            }

/* 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. */
            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, "", status );

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

/* Combine these Mappings together. */
            tmap4 = (AstMapping *) astCmpMap( tmap0, tmap1, 1, "", status );
            tmap0 = astAnnul( tmap0 );
            tmap1 = astAnnul( tmap1 );
            ret = (AstMapping *) astCmpMap( tmap4, tmap3, 1, "", status );
            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
   inappropriate 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 (but only if the axis order is being
   determined automatically). 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. */
            if( astChrMatch( astGetFitsAxisOrder( this ), "<auto>" ) ) {
               tmap0 = (AstMapping *) astCmpMap( map, ret, 1, "", status );

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

/* If the index associated with the celestial axes appear to have been
   swapped... */
               if( ret && 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, status );
                  SetItem( &(store->crval), fits_ilat, 0, s,
                           GetItem( &(store->crval), fits_ilon, 0, s, NULL,
                           method, class, status ), status );
                  SetItem( &(store->crval), fits_ilon, 0, s, val, status );

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

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

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

/* Release resources. */
               tperm = astFree( tperm );
               tmap0 = astAnnul( tmap0 );
            }
            map2b = astAnnul( map2b );
         }

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

/* If no WcsMap was found in the pixel->WCS Mapping, it may be possible
   to describe the celestial axes using a tabular look-up table (i.e. the
   FITS-WCS "_TAB" algorithm). Only do this if the -TAB algorithm is to
   be supported. */
      } else if( extver > 0 ) {

/* Get any pre-existing FitsTable from the FitsStore. This is the table
   in which the tabular data will be stored (if the Mapping can be expressed
   in -TAB form). */
         if( !astMapGet0A( store->tables, AST_TABEXTNAME, &table ) ) table = NULL;

/* See if the transformations for the celestial axes can be expressed in -TAB
   form. The returned Mapping (if any) is the Mapping from (lon,lat)
   (rads) to (psi_lon,psi_lat) (pixels). See FITS-WCS paper III section 6.1.2
   for definition of psi. Scale the values stored in the table from radians
   to degrees. */
         tmap0 = IsMapTab2D( map, AST__DR2D, "deg", wcsfrm, dim, ilon, ilat,
                             fits_ilon, fits_ilat, &table, &icolmainlon,
                             &icolmainlat, &icolindexlon, &icolindexlat,
                             &mlon, &mlat, &interplon, &interplat, status );
         if( tmap0 ) {

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

/* If possible, choose the two CRVAL values (which are values on the psi
   axes) so that transforming them using the Mapping returned by
   IsMapTab2D gives the sky reference position stored in the SkyFrame.
   Check the SkyFrame has a defined reference position. */
            if( astTestSkyRef( skyfrm, 0 ) && astTestSkyRef( skyfrm, 1 ) ){

/* Get the longitude and latitude at the reference point in radians. */
               skyfid[ 0 ] = astGetSkyRef( skyfrm, astGetLonAxis( skyfrm ));
               skyfid[ 1 ] = astGetSkyRef( skyfrm, astGetLatAxis( skyfrm ));

/* We use the WCS->psi Mapping to convert the reference point WCS coords
   (rads) into psi coords (pixels). We can only do this if the WCS->psi
   Mapping has a defined forward transformation. */
               if( astGetTranForward( tmap0 ) ) {
                  astTran2( tmap0, 1, skyfid, skyfid + 1, 1, crval,
                            crval + 1 );

/* If the WCS->psi mapping has an undefined forward transformation, then
   just store the sky reference point coords (in degs) in keywords
   AXREFn, and use 1.0 for the CRVAL values, so that IWC becomes equal
   to (psi-1) i.e. (grid coords - 1). This means the reference point is
   at grid coords (1.0,1.0). Note this choice of 1.0 for CRVAL is not
   arbitrary since it is required by the trick used to create invertable CD
   matrix in function MakeInvertable. */
               } else {
                  SetItem( &(store->axref), fits_ilon, 0, s,
                           AST__DR2D*skyfid[ 0 ], status );
                  SetItem( &(store->axref), fits_ilat, 0, s,
                           AST__DR2D*skyfid[ 1 ], status );
                  crval[ 0 ] = 1.0;
                  crval[ 1 ] = 1.0;
               }

/* If the SkyFrame has no reference position, use 1.0 for the CRVAL values. */
            } else {
               crval[ 0 ] = 1.0;
               crval[ 1 ] = 1.0;
            }

/* Create a Mapping that describes the transformation from the lon and lat
   psi axes to the lon and lat IWC axes (i.e. a ShiftMap that just subtracts
   the CRVAL values from each axis). */
            crval[ 0 ] = -crval[ 0 ];
            crval[ 1 ] = -crval[ 1 ];
            tmap1 = (AstMapping *) astShiftMap( 2, crval, " ", status );
            crval[ 0 ] = -crval[ 0 ];
            crval[ 1 ] = -crval[ 1 ];

/* Create a series compound Mapping that applies the Mapping returned
   by IsMapTab2D first (the Mapping from WCS to psi), followed by the
   Mapping from psi to IWC created above. There-after, use this compound
   Mapping in place of the Mapping returned by IsMapTab2D. It maps WCS to
   IWC. */
            tmap2 = (AstMapping *) astCmpMap( tmap0, tmap1, 1, " ", status );
            (void) astAnnul( tmap0 );
            tmap1 = astAnnul( tmap1 );
            tmap0 = tmap2;

/* Store the CRVAL values */
            SetItem( &(store->crval), fits_ilon, 0, s, crval[ 0 ], status );
            SetItem( &(store->crval), fits_ilat, 0, s, crval[ 1 ], status );

/* Store TAB-specific values in the FitsStore. First the name of the
   FITS binary table extension holding the coordinate info. */
            SetItemC( &(store->ps), fits_ilon, 0, s, AST_TABEXTNAME, status );
            SetItemC( &(store->ps), fits_ilat, 0, s, AST_TABEXTNAME, status );

/* Next the table version number. This is the set (positive) value for the
   TabOK attribute. */
            SetItem( &(store->pv), fits_ilon, 1, s, extver, status );
            SetItem( &(store->pv), fits_ilat, 1, s, extver, status );

/* Also store the table version in the binary table header. */
            astSetFitsI( table->header, "EXTVER", extver, "Table version number",
                         0 );

/* Next the name of the table column containing the main coords array. */
            SetItemC( &(store->ps), fits_ilon, 1, s,
                      astColumnName( table, icolmainlon ), status );
            SetItemC( &(store->ps), fits_ilat, 1, s,
                      astColumnName( table, icolmainlat ), status );

/* Next the name of the column containing the index array. */
            if( icolindexlon >= 0 ) SetItemC( &(store->ps), fits_ilon, 2, s,
                             astColumnName( table, icolindexlon ), status );
            if( icolindexlat >= 0 ) SetItemC( &(store->ps), fits_ilat, 2, s,
                             astColumnName( table, icolindexlat ), status );

/* The one-based index of the axes within the coordinate array that
   describes FITS WCS axes "fits_ilon" and "fits_ilat". */
            SetItem( &(store->pv), fits_ilon, 3, s, mlon, status );
            SetItem( &(store->pv), fits_ilat, 3, s, mlat, status );

/* The interpolation method (an AST extension to the published -TAB
   algorithm, communicated through the QVi_4a keyword). */
            SetItem( &(store->pv), fits_ilon, 4, s, interplon, status );
            SetItem( &(store->pv), fits_ilat, 4, s, interplat, status );

/* Also store the FitsTable itself in the FitsStore. */
            astMapPut0A( store->tables, AST_TABEXTNAME, table, NULL );

/* Allocate space for the arrays that define the permutations required
   for the inputs and outputs of a PermMap. */
            inperm = astMalloc( sizeof( double )*nwcs );
            outperm = astMalloc( sizeof( double )*nwcs );
            if( astOK ) {

/* Create the WCS -> IWC Mapping. First create a parallel CmpMap that
   combines the Mapping returned by IsMapTab2D (which transforms the celestial
   axes), with a UnitMap which transforms the non-celestial axes. */
               if( nwcs > 2 ) {
                  tmap1 = (AstMapping *) astUnitMap( nwcs - 2, " ", status );
                  tmap2 = (AstMapping *) astCmpMap( tmap0, tmap1, 0, " ", status );
                  tmap1 = astAnnul( tmap1 );
               } else {
                  tmap2 = astClone( tmap0 );
               }

/* Now create a PermMap that permutes the inputs of this CmpMap into the
   order of the axes in the WCS Frame. */
               outperm[ 0 ] = ilon;
               outperm[ 1 ] = ilat;
               j = 0;
               for( i = 2; i < nwcs; i++ ) {
                  while( j == ilon || j == ilat ) j++;
                  outperm[ i ] = j++;
               }
               for( i = 0; i < nwcs; i++ ) inperm[ outperm[ i ] ] = i;
               tmap1 = (AstMapping *) astPermMap( nwcs, inperm, nwcs, outperm,
                                                  NULL, " ", status );

/* Use this PermMap (and its inverse) to permute the inputs (and outputs)
   of the parallel CmpMap created above. */
               tmap3 = (AstMapping *) astCmpMap( tmap1, tmap2, 1, " ", status );
               tmap2 = astAnnul( tmap2 );
               astInvert( tmap1 );
               tmap2 = (AstMapping *) astCmpMap( tmap3, tmap1, 1, " ", status );
               tmap1 = astAnnul( tmap1 );
               tmap3 = astAnnul( tmap3 );

/* Now create a PermMap that permutes the WCS axes into the FITS axis order. */
               for( i = 0; i < nwcs; i++ ) {
                  inperm[ i ] = wperm[ i ];
                  outperm[ wperm[ i ] ] = i;
               }
               tmap1 = (AstMapping *) astPermMap( nwcs, inperm, nwcs, outperm,
                                                  NULL, "", status );

/* Use this PermMap to permute the outputs of the "tmap2" Mapping. The
   resulting Mapping is the Mapping from the current Frame to IWC and is
   the Mapping to be returned as the function value. */
               ret = (AstMapping *) astCmpMap( tmap2, tmap1, 1, " ", status );
               tmap1 = astAnnul( tmap1 );
               tmap2 = astAnnul( tmap2 );
            }

/* Free remaining resources. */
            inperm = astFree( inperm );
            outperm = astFree( outperm );
            tmap0 = astAnnul( tmap0 );
         }
         if( table ) table = astAnnul( table );
      }

/* Release resources. */
      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, "", status );
   }

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

static void ChangePermSplit( AstMapping *map, int *status ){
/*
*  Name:
*     ChangePermSplit

*  Purpose:
*     Change all PermMaps in a Mapping to use the alternate
*     implementation of the astMapSplit method.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void ChangePermSplit( AstMapping *map, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The PemMap class provides two implementations of the astMapSplit
*     method. The implementation used by each PermMap is determined by
*     the value of the PermMap's "PermSplit" attribute. This function
*     searches the supplied Mapping for any PermMaps, and set their
*     PermSplit attribute to 1, indicating that the alternate
*     implementation of astMapSplit should be used.

*  Parameters:
*     map
*        Pointer to the Mapping. Modified on exit by setting all
*        PermSplit attributes to 1.
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   AstMapping *map1;
   AstMapping *map2;
   int series;
   int invert1;
   int invert2;

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

/* If the supplied Mapping is a PermMap, set its PermSplit attribute
   non-zero. */
   if( astIsAPermMap( map ) ) {
      astSetPermSplit( map, 1 );

/* If the supplied Mapping is not a PermMap, attempt to decompose the
   Mapping into two component Mappings. */
   } else {
      astDecompose( map, &map1, &map2, &series, &invert1, &invert2 );

/* If the Mapping could be decomposed, use this function recursively to
   set the PermSplit attributes in each component Mapping. */
      if( map1 && map2 ) {
         ChangePermSplit( map1, status );
         ChangePermSplit( map2, status );

/* Annul the component Mappings. */
         map1 = astAnnul( map1 );
         map2 = astAnnul( map2 );
      } else if( map1 ) {
         map1 = astAnnul( map1 );
      } else if( map2 ) {
         map2 = astAnnul( map2 );
      }
   }
}

static double *Cheb2Poly( double *c, int nx, int ny, double xmin, double xmax,
                          double ymin, double ymax, int *status ){
/*
*  Name:
*     Cheb2Poly

*  Purpose:
*     Converts a two-dimensional Chebyshev polynomial to standard form and
*     scale the arguments.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     double *Cheb2Poly( double *c, int nx, int ny, double xmin, double xmax,
*                        double ymin, double ymax, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     Given the coefficients of a two-dimensional Chebychev polynomial P(u,v),
*     find the coefficients of the equivalent standard two-dimensional
*     polynomial Q(x,y). The allowed range of u and v is assumed to be the
*     unit square, and this maps on to the rectangle in (x,y) given by
*     (xmin:xmax,ymin:ymax).

*  Parameters:
*     c
*        An array of (nx,ny) elements supplied holding the coefficients of
*        P, such that the coefficient of (Ti(u)*Tj(v)) is held in element
*        (i + j*nx), where "Ti(u)" is the Chebychev polynomial (of the
*        first kind) of order "i" evaluated at "u", and "Tj(v)" is the
*        Chebychev polynomial of order "j" evaluated at "v".
*     nx
*        One more than the maximum power of u within P.
*     ny
*        One more than the maximum power of v within P.
*     xmin
*        X value corresponding to u = -1
*     xmax
*        X value corresponding to u = +1
*     ymin
*        Y value corresponding to v = -1
*     ymax
*        Y value corresponding to v = +1
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     Pointer to a dynamically allocated array of (nx,ny) elements holding
*     the coefficients of Q, such that the coefficient of (x^i*y^j) is held
*     in element (i + j*nx). Free it using astFree when no longer needed.
*/

/* Local Variables: */
   double *d;
   double *pa;
   double *pw;
   double *work1;
   double *work2;
   double *work3;
   int *iw1;
   int *iw2;
   int i;
   int j;

/* Check the status and supplied value pointer. */
   if( !astOK ) return NULL;

/* Allocate returned array. */
   d = astMalloc( sizeof( *d )*nx*ny );

/* Allocate workspace. */
   work1 = astMalloc( sizeof( *work1 )*ny );
   work2 = astMalloc( sizeof( *work2 )*ny );
   work3 = astMalloc( sizeof( *work2 )*nx );
   iw1 = astMalloc( sizeof(int)*( nx > ny ? nx : ny ) );
   iw2 = astMalloc( sizeof(int)*( nx > ny ? nx : ny ) );
   if( astOK ) {

/* Thinking of P as a 1D polynomial in v, each coefficient would itself then
   be a 1D polynomial in u:

   P = (   c[0] +      c[1]*T1(u) +      c[2]*T2(u) + ... ) +
       (  c[nx] +   c[nx+1]*T1(u) +   c[nx+2]*T2(u) + ... )*T1(v) +
       (c[2*nx] + c[2*nx+1]*T1(u) + c[2*nx+2]*T2(u) + ... )*T2(v) +
       ...
       (c[(ny-1)*nx] + c[(ny-1)*nx+1]*T1(u) + c[(ny-1)*nx+2]*T2(u) + ... )T{ny-1}(v)

   Use Chpc1 to convert these "polynomial coefficients" to standard
   form, storing the result in the corresponding row of "d" . Also,
   convert them from u to x. */

      for( j = 0; j < ny; j++ ) {
         Chpc1( c + j*nx, work3, nx, iw1, iw2, status );
         Shpc1( xmin, xmax, nx, work3, d + j*nx, status );
      }

/* The polynomial value is now:

    (   d[0] +      d[1]*x +      d[2]*x*x + ... ) +
    (  d[nx] +   d[nx+1]*x +   d[nx+2]*x*x + ... )*T1(v) +
    (d[2*nx] + d[2*nx+1]*x + d[2*nx+2]*x*x + ... )*T2(v) +
    ...
    (d[(ny-1)*nx] + d[(ny-1)*nx+1]*x + d[(ny-1)*nx+2]*x*x + ... )*T{ny-1}(v)

   If we rearrange this expression to view it as a 1D polynomial in x,
   rather than v, each coefficient of the new 1D polynomial is then
   itself a polynomial in v:

    ( d[0] +   d[nx]*T1(v) +   d[2*nx]*T2(v) + ... d[(ny-1)*nx]*T{ny-1}(v) ) +
    ( d[1] + d[nx+1]*T1(v) + d[2*nx+1]*T2(v) + ... d[(ny-1)*nx+1]T{ny-1}(v)... )*x +
    ( d[2] + d[nx+2]*T1(v) + d[2*nx+2]*T2(v) + ... d[(ny-1)*nx+2]T{ny-1}(v)... )*x*x +
    ...
    ( d[nx-1] + d[2*nx-1]*T1(v) + d[3*nx-1]*T2(v) + ... d[ny*nx-1]*T{ny-1}(v) )*x*x*...


   Now use Chpc1 to convert each of these "polynomial coefficients"
   to standard form. We copy each column of the d array into a 1D work array,
   use Shpc1 to modify the values in the work array, and then write
   the modified values back into the current column of d. Also convert
   from v to y. */

      for( i = 0; i < nx; i++ ) {
         pa = d + i;
         pw = work1;
         for( j = 0; j < ny; j++ ) {
            *(pw++) = *pa;
            pa += nx;
         }

         Chpc1( work1, work2, ny, iw1, iw2, status );
         Shpc1( ymin, ymax, ny, work2, work1, status );

         pa = d + i;
         pw = work1;
         for( j = 0; j < ny; j++ ) {
            *pa = *(pw++);
            pa += nx;
         }
      }

/* So the polynomial is now:

    ( d[0] +   d[nx]*y +   d[2*nx]*y*y + ... d[(ny-1)*nx]*y*y*... ) +
    ( d[1] + d[nx+1]*y + d[2*nx+1]*y*y + ... d[(ny-1)*nx+1]*y*y*... )*x +
    ( d[2] + d[nx+2]*y + d[2*nx+2]*y*y + ... d[(ny-1)*nx+2]*y*y*... )*x*x +
    ...
    ( d[nx-1] + d[2*nx-1]*y + d[3*nx-1]*y*y + ... d[ny*nx-1]*y*y*... )*x*x*...

  Re-arranging, this is:

    (   d[0] +      d[1]*x +      d[2]*x*x + ... ) +
    (  d[nx] +   d[nx+1]*x +   d[nx+2]*x*x + ... )*y +
    (d[2*nx] + d[2*nx+1]*x + d[2*nx+2]*x*x + ... )*y*y +
    ...
    (d[(ny-1)*nx] + d[(ny-1)*nx+1]*x + d[(ny-1)*nx+2]*x*x + ... )*y*y*...

   as required. */

   }

/* Free the workspace. */
   work1 = astFree( work1 );
   work2 = astFree( work2 );
   work3 = astFree( work3 );
   iw1 = astFree( iw1 );
   iw2 = astFree( iw2 );

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

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

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

*  Type:
*     Private function.

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

*  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.
*
*     If the supplied keyword name is invalid, either a warning is issued
*     (for violations that can be handled - such as illegal characters in
*     keywords), or an error is reported (for more major violations such
*     as the keyname containing an equals sign).

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     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.
*     status
*        Pointer to the inherited status variable.

*  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: */
   char buf[100];     /* Buffer for warning text */
   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.", status, method,
                   class, name, (int) 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.", status, method, class, FITSNAMLEN + 1 );

                  } else if( *c < ' ' ) {
                     sprintf( buf, "The FITS keyword name ('%s') contains an "
                              "illegal non-printing character (ascii value "
                              "%d).", name, *c );
                     Warn( this, "badkeyname", buf, method, class, status );


                  } else if( *c > ' ' ) {
                     sprintf( buf, "The FITS keyword name ('%s') contains an "
                              "illegal character ('%c').", name, *c );
                     Warn( this, "badkeyname", buf, method, class, status );
                  }
                  break;
               }
               c++;
            }
         }
      }

/* Report an error if no pointer was supplied. */
   } else if( astOK ){
      astError( AST__INTER, "CheckFitsName(%s): AST internal error; a NULL "
                "pointer was supplied for the keyword name. ", status,
                astGetClass( this ) );
   }

/* 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, int fitsrnd,
                       int *status ){
/*
*  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, int fitsrnd,
*                     int *status )

*  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.
*     fitsrnd
*        The value of the FitsRounding attribute - the number of leading
*        digits to protect from rounding.
*     status
*        Pointer to the inherited status variable.

*  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, fitsrnd, status );
   }
}

static double ChooseEpoch( AstFitsChan *this, FitsStore *store, char s,
                           const char *method, const char *class, int *status ){
/*
*  Name:
*     ChooseEpoch

*  Purpose:
*     Choose a FITS keyword value to use for the AST Epoch attribute.

*  Type:
*     Private function.

*  Synopsis:
*     double ChooseEpoch( AstFitsChan *this, FitsStore *store, char s,
*                         const char *method, const char *class, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function returns an MJD value in the TDB timescale, which can
*     be used as the Epoch value in an AST Frame. It uses the following
*     preference order: secondary MJD-AVG, primary MJD-AVG, secondary MJD-OBS,
*     primary MJD-OBS. Note, DATE-OBS keywords are converted into MJD-OBS
*     keywords by the SpecTrans function before this function is called.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     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').
*     method
*        The calling method. Used only in error messages.
*     class
*        The object class. Used only in error messages.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     The MJD value.

*  Notes:
*     -  A value of AST__BAD is returned if an error occurs, or if none
*     of the required keywords can be found in the FitsChan.
*/

/* Local Variables: */
   const char *timesys;  /* The TIMESYS value in the FitsStore */
   double mjd;           /* The returned MJD */

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

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

/* Otherwise, try to get the secondary MJD-AVG value. */
   mjd = GetItem( &(store->mjdavg), 0, 0, s, NULL, method, class, status );

/* Otherwise, try to get the primary MJD-AVG value. */
   if( mjd == AST__BAD ) mjd = GetItem( &(store->mjdavg), 0, 0, ' ', NULL,
                                        method, class, status );

/* If the secondary MJD-OBS keyword is present in the FitsChan, gets its
   value. */
   if( mjd == AST__BAD ) mjd = GetItem( &(store->mjdobs), 0, 0, s, NULL,
                                        method, class, status );

/* Otherwise, try to get the primary MJD-OBS value. */
   if( mjd == AST__BAD ) mjd = GetItem( &(store->mjdobs), 0, 0, ' ', NULL,
                                        method, class, status );

/* Now convert the MJD value to the TDB timescale. */
   timesys = GetItemC( &(store->timesys), 0, 0, ' ', NULL, method, class, status );
   mjd = TDBConv( mjd, TimeSysToAst( this, timesys, method, class, status ),
                  0, method, class, status );

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

static void Chpc1( double *c, double *d, int n, int *w0, int *w1, int *status ){
/*
*  Name:
*     Chpc1

*  Purpose:
*     Converts a one-dimensional Chebyshev polynomial to standard form.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void Chpc1( double *c, double *d, int n, int *w0, int *w1, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     Given the coefficients of a one-dimensional Chebychev polynomial P(u),
*     find the coefficients of the equivalent standard 1D polynomial Q(u).
*     The allowed range of u is assumed to be the unit interval.

*  Parameters:
*     c
*        An array of n elements supplied holding the coefficients of
*        P, such that the coefficient of (Ti(u)) is held in element
*        (i), where "Ti(u)" is the Chebychev polynomial (of the
*        first kind) of order "i" evaluated at "u".
*     d
*        An array of n elements returned holding the coefficients of
*        Q, such that the coefficient of (u^i) is held in element (i).
*     n
*        One more than the highest power of u in P.
*     w0
*        Pointer to a work array of n elements.
*     w1
*        Pointer to a work array of n elements.
*     status
*        Inherited status value

*  Notes:
*    - Vaguely inspired by the Numerical Recipes routine "chebpc". But the
*    original had bugs, so I wrote this new version from first principles.

*/

/* Local Variables: */
   int sv;
   int j;
   int k;

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

/* Initialise the returned coefficients array. */
   for( j = 0; j < n; j++ ) d[ j ] = 0.0;

/* Use the recurrence relation

   T{k+1}(x) = 2.x.T{k}(x) - T{k-1}(x).

   w0[i] holds the coefficient of x^i in T{k-1}. w1[i] holds the
   coefficient of x^i in T{k}. Initialise them for T0 (="1") and
   T1 (="x"). */
   for( j = 0; j < n; j++ ) w0[ j ] = w1[ j ] = 0;
   w0[ 0 ] = 1;
   w1[ 1 ] = 1;

/* Update the returned coefficients array to include the T0 and T1 terms. */
   d[ 0 ] = c[ 0 ];
   d[ 1 ] = c[ 1 ];

/* Loop round using the above recurrence relation until we have found
   T{n-1}. */
   for( k = 1; k < n - 1; k++ ){

/* To get the coefficients of T{k+1} shift the contents of w1 up one
   element, introducing a zero at the low end, and then double all the
   values in w1. Finally subtract off the values in w0. This implements
   the above recurrence relationship. Starting at the top end and working
   down to the bottom, store a new value for each element of w1. */
      for( j = n - 1; j > 0; j-- ) {

/* First save the original element of w1 in w0 for use next time. But we
   also need the original w0 element later on so save it first. */
         sv = w0[ j ];
         w0[ j ] = w1[ j ];

/* Double the lower neighbouring w1 element and subtract off the w0
   element saved above. This forms the new value for w1. */
         w1[ j ] = 2*w1[ j - 1 ] - sv;
      }

/* Introduce a zero into the lowest element of w1, saving the original
   value first in w0. Then subtract off the original value of w0. */
      sv = w0[ 0 ];
      w0[ 0 ] = w1[ 0 ];
      w1[ 0 ] = -sv;

/* W1 now contains the coefficients of T{k+1} in w1, and the coefficients
   of T{k} in w0. Multiply these by the supplied coefficient for T{k+1},
   and add them into the returned array. */
      for( j = 0; j <= k + 1; j++ ){
         d[ j ] += c[ k + 1 ]*w1[ j ];
      }
   }
}

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

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

*  Type:
*     Private function.

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

*  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.
*     status
*        Pointer to the inherited status variable.

*  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 int CLASSFromStore( AstFitsChan *this, FitsStore *store,
                           AstFrameSet *fs, double *dim, const char *method,
                           const char *class, int *status ){

/*
*  Name:
*     CLASSFromStore

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

*  Type:
*     Private function.

*  Synopsis:

*     int CLASSFromStore( AstFitsChan *this, FitsStore *store,
*                         AstFrameSet *fs, double *dim, const char *method,
*                         const char *class, int *status )

*  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-CLASS encoding.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     store
*        Pointer to the FitsStore.
*     fs
*        Pointer to the FrameSet from which the values in the FitsStore
*        were derived.
*     dim
*        Pointer to an array holding the main array dimensions (AST__BAD
*        if a dimension is 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.
*     status
*        Pointer to the inherited status variable.

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

/* Local Variables: */
   AstFrame *azelfrm;  /* (az,el) frame */
   AstFrame *curfrm;   /* Current Frame in supplied FrameSet */
   AstFrame *freqfrm;  /* Frame for reference frequency value */
   AstFrame *radecfrm; /* Spatial frame for CRVAL values */
   AstFrame *velofrm;  /* Frame for reference velocity value */
   AstFrameSet *fsconv1;/* FrameSet connecting "curfrm" & "radecfrm" */
   AstFrameSet *fsconv2;/* FrameSet connecting "curfrm" & "azelfrm" */
   AstMapping *map1;   /* Axis permutation to get (lonaxis,lataxis) = (0,1) */
   AstMapping *map2;   /* Mapping from FITS CTYPE to (az,el) */
   AstMapping *map3;   /* Mapping from (lon,lat) to (az,el) */
   char *comm;         /* Pointer to comment string */
   char *cval;         /* Pointer to string keyword value */
   char attbuf[40];    /* Buffer for AST attribute name */
   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 aval[ 2 ];   /* General purpose array */
   double azel[ 2 ];   /* Reference (az,el) values */
   double cdl;         /* CDELT term */
   double crval[ 3 ];  /* CRVAL values converted to rads, etc */
   double delta;       /* Spectral axis increment */
   double equ;         /* Epoch of reference equinox */
   double fd;          /* Fraction of a day */
   double latval;      /* CRVAL for latitude axis */
   double lonpole;     /* LONPOLE value */
   double lonval;      /* CRVAL for longitude axis */
   double mjd99;       /* MJD at start of 1999 */
   double p1, p2;      /* Projection parameters */
   double radec[ 2 ];  /* Reference (lon,lat) values */
   double rf;          /* Rest freq (Hz) */
   double specfactor;  /* Factor for converting internal spectral units */
   double val;         /* General purpose value */
   double xin[ 3 ];    /* Grid coords at centre of first pixel */
   double xout[ 3 ];   /* WCS coords at centre of first pixel */
   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 naxis2;         /* Length of pixel axis 2 */
   int naxis3;         /* Length of pixel axis 3 */
   int naxis;          /* No. of axes */
   int ok;             /* Is FitsSTore OK for IRAF encoding? */
   int prj;            /* Projection type */

/* Other initialisation to avoid compiler warnings. */
   lonval = 0.0;
   latval = 0.0;

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

/* Initialise */
   specfactor = 1.0;

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

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

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

/* Get the current Frame from the supplied FrameSet. */
   curfrm = astGetFrame( fs, AST__CURRENT );

/* Spectral and celestial axes must be present in axes 1,2 and 3. */
   if( axspec >= 0 && axspec < 3 &&
       axlon >= 0 && axlon < 3 &&
       axlat >= 0 && axlat < 3 ) {
      ok = 1;

/* If the spatial pixel axes are degenerate (i.e. span only a single
   pixel), modify the CRPIX and CRVAL values in the FitsStore to put
   the reference point at the centre of the one and only spatial pixel. */
      if( store->naxis >= 3 && dim[ axlon ] == 1.0 && dim[ axlat ] == 1.0 ){
         xin[ 0 ] = 1.0;
         xin[ 1 ] = 1.0;
         xin[ 2 ] = 1.0;
         astTranN( fs, 1, 3, 1, xin, 1, 3, 1, xout );
         if( xout[ axlon ] != AST__BAD && xout[ axlat ] != AST__BAD ) {

/* The indices of the spatial axes in the FITS header may not be the same
   as the indices of the spatial axes in the WCS Frame of the supplied
   FrameSet. So search the current Frame for longitude and latitude axes,
   and store the corresponding elements of the "xout" array for later use. */
            for( i = 0; i < 3; i++ ) {
               sprintf( attbuf, "IsLonAxis(%d)", i + 1 );
               if( astHasAttribute( curfrm, attbuf ) ) {
                  if( astGetI( curfrm, attbuf ) ) {
                     lonval = xout[ i ];
                  } else {
                     latval = xout[ i ];
                  }
               }
            }

/* Store them in the FitsStore. */
            SetItem( &(store->crval), axlon, 0, ' ', lonval*AST__DR2D, status );
            SetItem( &(store->crval), axlat, 0, ' ', latval*AST__DR2D, status );
            SetItem( &(store->crpix), 0, axlon, ' ', 1.0, status );
            SetItem( &(store->crpix), 0, axlat, ' ', 1.0, status );
         }
      }

/* Get the CRVAL values for both spatial axes. */
      latval = GetItem( &( store->crval ), axlat, 0, s, NULL, method, class, status );
      if( latval == AST__BAD ) ok = 0;
      lonval = GetItem( &( store->crval ), axlon, 0, s, NULL, method, class, status );
      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, 0, s, NULL, method, class, status );
      if( !cval ) {
         ok = 0;
      } else {
         strcpy( lontype, cval );
      }
      cval = GetItemC( &(store->ctype), axlat, 0, s, NULL, method, class, status );
      if( !cval ) {
         ok = 0;
         prj = AST__WCSBAD;
      } else {
         strcpy( lattype, cval );
         prj = astWcsPrjType( cval + 4 );
      }

/* Check the projection type is OK. */
      if( prj == AST__WCSBAD ){
         ok = 0;
      } else if( prj != AST__SIN ){

/* Check the projection code is OK. */
         ok = 0;
         if( prj == AST__TAN ||
             prj == AST__ARC ||
             prj == AST__STG ||
             prj == AST__AIT ||
             prj == AST__SFL ) {
            ok = 1;

/* For AIT, and SFL, check that the reference point is the origin of
   the celestial co-ordinate system. */
            if( 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" );

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

/* SIN projections are only acceptable if the associated projection
   parameters are both zero. */
      } else {
         p1 = GetItem( &( store->pv ), axlat, 1, s, NULL, method, class, status );
         p2 = GetItem( &( store->pv ), axlat, 2, s, NULL, method, class, status );
         if( p1 == AST__BAD ) p1 = 0.0;
         if( p2 == AST__BAD ) p2 = 0.0;
         ok = ( p1 == 0.0 && p2 == 0.0 );
      }

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

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

/* If OK, check the SPECSYS value is SOURCE. */
      cval = GetItemC( &(store->specsys), 0, 0, s, NULL, method, class, status );
      if( !cval ) {
         ok = 0;
      } else if( ok ) {
         if( strncmp( cval, "SOURCE", astChrLen( cval ) ) ) ok = 0;
      }

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

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

/* If this is larger than 3, ignore the surplus WCS axes. Note, the
   above code has checked that the spatial and spectral axes are
   WCS axes 0, 1 and 2. */
   if( naxis > 3 ) naxis = 3;

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

/* Check that there is no rotation, and extract the CDELT (diagonal) terms,
   etc. If the spatial axes are degenerate (i.e. cover only a single pixel)
   then ignore any rotation. */
   if( !GetValue( this, FormatKey( "NAXIS", axlon + 1, -1, s, status ), AST__INT,
                  &naxis2, 0, 0, method, class, status ) ) {
      naxis2 = 0;
   }
   if( !GetValue( this, FormatKey( "NAXIS", axlat + 1, -1, s, status ), AST__INT,
                  &naxis3, 0, 0, method, class, status ) ) {
      naxis3 = 0;
   }
   for( i = 0; i < naxis && ok; i++ ){
      cdl = GetItem( &(store->cdelt), i, 0, s, NULL, method, class, status );
      if( cdl == AST__BAD ) cdl = 1.0;
      for( j = 0; j < naxis && ok; j++ ){
          val = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
          if( val == AST__BAD ) val = ( i == j ) ? 1.0 : 0.0;
          val *= cdl;
          if( i == j ){
             cdelt[ i ] = val;
          } else if( val != 0.0 ){
             if( naxis2 != 1 || naxis3 != 1 ) ok = 0;
          }
      }
   }

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

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

/* Only FK4 and FK5 are supported in this encoding. */
      if( strcmp( "FK4", cval ) && strcmp( "FK5", cval ) ) ok = 0;

/* If epoch was not available, set a default epoch. */
      if( equ == AST__BAD ){
         if( !strcmp( "FK4", cval ) ){
            equ = 1950.0;
         } else if( !strcmp( "FK5", cval ) ){
            equ = 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( equ >= 1984.0 ) ok = 0;
         } else if( !strcmp( "FK5", cval ) ){
            if( equ < 1984.0 ) ok = 0;
         } else {
            ok = 0;
         }
      }

/* Check we have a rest frequency */
      rf = GetItem( &(store->restfrq), 0, 0, s, NULL, method, class, status );
      if( rf == AST__BAD ) ok = 0;
   }

/* If the spatial Frame covers more than a single Frame and requires a LONPOLE
   or LATPOLE keyword, it cannot be encoded using FITS-CLASS. However since
   FITS-CLASS imposes a no rotation restriction, it can tolerate lonpole
   values of +/- 180 degrees. */
   if( ok && ( naxis2 != 1 || naxis3 != 1 ) ) {
      lonpole =  GetItem( &(store->lonpole), 0, 0, s, NULL, method, class, status );
      if( lonpole != AST__BAD && lonpole != -180.0 && lonpole == 180 ) ok = 0;
      if( GetItem( &(store->latpole), 0, 0, s, NULL, method, class, status )
          != AST__BAD ) ok = 0;
   }

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

/* If celestial axes were added by MakeFitsFrameSet, we need to ensure
   the header contains 3 main array axes. This is because the CLASS
   encoding does not support the WCSAXES keyword. */
      if( store->naxis == 1 ) {

/* Update the "NAXIS" value to 3 or put a new card in at the start. */
         astClearCard( this );
         i = 3;
         SetValue( this, "NAXIS", &i, AST__INT, NULL, status );

/* Put NAXIS2/3 after NAXIS1, or after NAXIS if the FitsChan does not contain
   NAXIS1. These are set to 1 since the spatial axes are degenerate. */
         if( FindKeyCard( this, "NAXIS1",  method, class, status ) ) {
            MoveCard( this, 1, method, class, status );
         }
         i = 1;
         SetValue( this, "NAXIS2", &i, AST__INT, NULL, status );
         SetValue( this, "NAXIS3", &i, AST__INT, NULL, status );
      }

/* Find the last WCS related card. */
      FindWcs( this, 1, 1, 0, method, class, status );

/* 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, status );
         if( val == AST__BAD ) {
            ok = 0;
         } else {
            sprintf( combuf, "Reference pixel on axis %d", j + 1 );
            SetValue( this, FormatKey( "CRPIX", j + 1, -1, s, status ), &val,
                      AST__FLOAT, combuf, status );
         }
      }

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

/* 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, 0, s, NULL, method, class, status );
         }
         if( cval && ( strlen(cval) < 5 || strcmp( cval + 4, "-TAB" ) ) ) {
            comm = GetItemC( &(store->ctype_com), i, 0, s, NULL, method, class, status );
            if( !comm ) {
               sprintf( combuf, "Type of co-ordinate on axis %d", i + 1 );
               comm = combuf;
            }
            SetValue( this, FormatKey( "CTYPE", i + 1, -1, s, status ), &cval,
                      AST__STRING, comm, status );
         } 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, status ), cdelt + i,
                   AST__FLOAT, "Pixel size", status );
      }

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

/* Date of observation. */
      val = GetItem( &(store->mjdobs), 0, 0, ' ', NULL, method, class, status );
      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. */
         palCaldj( 99, 1, 1, &mjd99, &jj );
         if( val < mjd99 ) {
            palDjcal( 0, val, iymdf, &jj );
            sprintf( combuf, "%2.2d/%2.2d/%2.2d", iymdf[ 2 ], iymdf[ 1 ],
                     iymdf[ 0 ] - ( ( iymdf[ 0 ] > 1999 ) ? 2000 : 1900 ) );
         } else {
            palDjcl( val, iymdf, iymdf+1, iymdf+2, &fd, &jj );
            palDd2tf( 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", status );
      }

/* Rest frequency */
      SetValue( this, "RESTFREQ", &rf, AST__FLOAT, "[Hz] Rest frequency", status );

/* The image frequency corresponding to the rest frequency (only used for
   double sideband data). */
      val = GetItem( &(store->imagfreq), 0, 0, s, NULL, method, class, status );
      if( val != AST__BAD ) {
         SetValue( this, "IMAGFREQ", &val, AST__FLOAT, "[Hz] Image frequency", status );
      }

/* Ensure the FitsChan contains OBJECT and LINE headers */
      if( !HasCard( this, "OBJECT", method, class, status ) ) {
         cval = " ";
         SetValue( this, "OBJECT", &cval, AST__STRING, NULL, status );
      }
      if( !HasCard( this, "LINE", method, class, status ) ) {
         cval = " ";
         SetValue( this, "LINE", &cval, AST__STRING, NULL, status );
      }

/* CLASS expects the VELO-LSR keyword to hold the radio velocity of the
   reference channel (NOT of the source as I was told!!) with respect to
   the LSRK rest frame. The "crval" array holds the frequency of the
   reference channel in the source rest frame, so we need to convert this
   to get the value for VELO-LSR. Create a SpecFrame describing the
   required frame (other attributes such as Epoch etc are left unset and
   so will be picked up from the supplied FrameSet). We set MinAxes
   and MaxAxes so that the Frame can be used as a template to match the
   1D or 3D current Frame in the supplied FrameSet. */
      velofrm = (AstFrame *) astSpecFrame( "System=vrad,StdOfRest=lsrk,"
                                           "Unit=m/s,MinAxes=1,MaxAxes=3", status );

/* Find the spectral axis within the current Frame of the supplied
   FrameSet, using the above "velofrm" as a template. */
      fsconv1 = astFindFrame( curfrm, velofrm, "" );

/* If OK, extract the SpecFrame from the returned FraneSet (this will
   have the attribute values that were assigned explicitly to "velofrm"
   and will have inherited all unset attributes from the supplied
   FrameSet). */
      if( fsconv1 ) {
         velofrm = astAnnul( velofrm );
         velofrm = astGetFrame( fsconv1, AST__CURRENT );
         fsconv1 = astAnnul( fsconv1 );

/* Take a copy of the velofrm and modify its attributes so that it
   describes frequency in the sources rest frame in units of Hz. This is
   the system that CLASS expects for the CRVAL3 keyword. */
         freqfrm = astCopy( velofrm );
         astSet( freqfrm, "System=freq,StdOfRest=Source,Unit=Hz", status );

/* Get a Mapping from frequency to velocity. */
         fsconv1 = astConvert( freqfrm, velofrm, "" );
         if( fsconv1 ) {

/* Use this Mapping to convert the spectral crval value from frequency to
   velocity. Also convert the value for the neighbouring channel. */
            aval[ 0 ] = crval[ axspec ]*specfactor;
            aval[ 1 ] = aval[ 0 ] + cdelt[ axspec ]*specfactor;
            astTran1( fsconv1, 2, aval, 1, aval );

/* Store the value. Also store it as VLSR since this keyword seems to be
   used for the same thing. */
            SetValue( this, "VELO-LSR", aval, AST__FLOAT, "[m/s] Reference velocity", status );
            SetValue( this, "VLSR", aval, AST__FLOAT, "[m/s] Reference velocity", status );

/* The DELTAV keyword holds the radio velocity channel spacing in the
   LSR. */
            delta = aval[ 1 ] - aval[ 0 ];
            SetValue( this, "DELTAV", &delta, AST__FLOAT, "[m/s] Velocity resolution", status );

/* Free remaining resources. */
            fsconv1 = astAnnul( fsconv1 );
         }
      }
      velofrm = astAnnul( velofrm );

/* AZIMUTH and ELEVATIO - the (az,el) equivalent of CRVAL. We need a
   Mapping from the CTYPE spatial system to (az,el). This depends on all
   the extra info like telescope position, epoch, etc.  This info is in
   the current Frame in the supplied FrameSet. First get a conversion
   from a sky frame with default axis ordering to the supplied Frame. All
   the extra info is picked up from the supplied Frame since it is not set
   in the template. */
      radecfrm = (AstFrame *) astSkyFrame( "Permute=0,MinAxes=3,MaxAxes=3", status );
      fsconv1 = astFindFrame( curfrm, radecfrm, "" );

/* Now get conversion from the an (az,el) Frame to the supplied Frame. */
      azelfrm = (AstFrame *) astSkyFrame( "System=AZEL,Permute=0,MinAxes=3,MaxAxes=3", status );
      fsconv2 = astFindFrame( curfrm, azelfrm, "" );

/* If both conversions werew possible, concatenate their Mappings to get
   a Mapping from (lon,lat) in the CTYPE system, to (az,el). */
      if( fsconv1 && fsconv2 ) {
         map1 = astGetMapping( fsconv1, AST__CURRENT, AST__BASE );
         map2 = astGetMapping( fsconv2, AST__BASE, AST__CURRENT );
         map3 = (AstMapping *) astCmpMap( map1, map2, 1, "", status );

/* Store the CRVAL (ra,dec) values in the default order. */
         radec[ 0 ] = crval[ axlon ]*AST__DD2R;
         radec[ 1 ] = crval[ axlat ]*AST__DD2R;

/* Transform to (az,el), normalise, convert to degrees and store. */
         astTranN( map3, 1, 2, 1, radec, 1, 2, 1, azel );
         if( azel[ 0 ] != AST__BAD && azel[ 1 ] != AST__BAD ) {
            astNorm( azelfrm, azel );
            azel[ 0 ] *= AST__DR2D;
            azel[ 1 ] *= AST__DR2D;
            SetValue( this, "AZIMUTH", azel, AST__FLOAT, "[Deg] Telescope azimuth", status );
            SetValue( this, "ELEVATIO", azel + 1, AST__FLOAT, "[Deg] Telescope elevation", status );
         }

/* Free resources */
         map1 = astAnnul( map1 );
         map2 = astAnnul( map2 );
         map3 = astAnnul( map3 );
         fsconv1 = astAnnul( fsconv1 );
         fsconv2 = astAnnul( fsconv2 );
      }
      radecfrm = astAnnul( radecfrm );
      azelfrm = astAnnul( azelfrm );
   }
   curfrm = astAnnul( curfrm );

/* 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 void ClassTrans( AstFitsChan *this, AstFitsChan *ret, int axlat,
                        int axlon, const char *method, const char *class, int *status ){

/*
*  Name:
*     ClassTrans

*  Purpose:
*     Translated non-standard FITS-CLASS headers into equivalent standard
*     ones.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void ClassTrans( AstFitsChan *this, AstFitsChan *ret, int axlat,
*                      int axlon, const char *method, const char *class )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function extends the functionality of the SpecTrans function,
*     by converting non-standard WCS keywords into standard FITS-WCS
*     keywords, using the conventions of the FITS-CLASS encoding.

*  Parameters:
*     this
*        Pointer to the FitsChan containing the original header cards.
*     ret
*        Pointer to a FitsChan in which to return the standardised header
*        cards.
*     axlat
*        Zero-based index of the celestial latitude axis.
*     axlon
*        Zero-based index of the celestial longitude axis.
*     method
*        Pointer to string holding name of calling method.
*     class
*        Pointer to a string holding the name of the supplied object class.
*/

/* Local Variables: */
   char *cval;                    /* Pointer to character string */
   char newtype[ 10 ];            /* New CTYPE value */
   const char *keyname;           /* Pointer to keyword name */
   const char *ssyssrc;           /* Pointer to SSYSSRC keyword value string */
   double crval;                  /* CRVAL value */
   double restfreq;               /* Rest frequency (Hz) */
   double v0;                     /* Ref channel velocity in source frame */
   double vref;                   /* Ref channel velocity in LSR or whatever */
   double vsource;                /* Source velocity */
   double zsource;                /* Source redshift */
   int axspec;                    /* Index of spectral axis */

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

/* Get the rest frequency. */
   restfreq = AST__BAD;
   if( !GetValue2( ret, this, "RESTFRQ", AST__FLOAT, (void *) &restfreq, 0,
                  method, class, status ) ){
      GetValue2( ret, this, "RESTFREQ", AST__FLOAT, (void *) &restfreq, 0,
                  method, class, status );
   }
   if( restfreq == AST__BAD ) {
      astError( AST__BDFTS, "%s(%s): Keyword RESTFREQ not found in CLASS "
                "FITS header.", status, method, class );
   }

/* Get the index of the spectral axis. */
   if( axlat + axlon == 1 ) {
      axspec = 2;
   } else if( axlat + axlon == 3 ) {
      axspec = 0;
   } else {
      axspec = 1;
   }

/* Get the spectral CTYPE value */
   if( GetValue2( ret, this, FormatKey( "CTYPE", axspec + 1, -1, ' ', status ),
                  AST__STRING, (void *) &cval, 0, method, class, status ) ){

/* We can only handle frequency axes at the moment. */
      if( !astChrMatch( "FREQ", cval ) ) {
         astError( AST__BDFTS, "%s(%s): FITS-CLASS keyword %s has value "
                   "\"%s\" - CLASS support in AST only includes \"FREQ\" axes.", status,
                   method, class, FormatKey( "CTYPE", axspec + 1, -1, ' ', status ),
                   cval );

/* CRVAL for the spectral axis needs to be incremented by RESTFREQ if the
   axis represents frequency. */
      } else {
         keyname = FormatKey( "CRVAL", axspec + 1, -1, ' ', status );
         if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &crval, 1,
                        method, class, status ) ) {
            crval += restfreq;
            SetValue( ret, keyname, (void *) &crval, AST__FLOAT, NULL, status );
         }
      }

/* CLASS frequency axes describe source frame frequencies. */
      cval = "SOURCE";
      SetValue( ret, "SPECSYS", (void *) &cval, AST__STRING, NULL, status );
   }

/* If no projection code is supplied for the longitude and latitude axes,
   use "-GLS". This will be translated to "-SFL" by SpecTrans. */
   keyname = FormatKey( "CTYPE", axlon + 1, -1, ' ', status );
   if( GetValue2( ret, this, keyname, AST__STRING, (void *) &cval, 0, method,
                  class, status ) ){
      if( strlen(cval) > 4 && !strncmp( "    ", cval + 4, 4 ) ) {
         strncpy( newtype, cval, 4 );
         strcpy( newtype + 4, "-GLS" );
         cval = newtype;
         SetValue( ret, keyname, (void *) &cval, AST__STRING, NULL, status );
      }
   }
   keyname = FormatKey( "CTYPE", axlat + 1, -1, ' ', status );
   if( GetValue2( ret, this, keyname, AST__STRING, (void *) &cval, 0, method,
                  class, status ) ){
      if( strlen(cval) > 4 && !strncmp( "    ", cval + 4, 4 ) ) {
         strncpy( newtype, cval, 4 );
         strcpy( newtype + 4, "-GLS" );
         cval = newtype;
         SetValue( ret, keyname, (void *) &cval, AST__STRING, NULL, status );
      }
   }

/* Look for a keyword with name "VELO-...". This specifies the radio velocity
   at the reference channel, in a standard of rest specified by the "..."
   in the keyword name. If "VELO-..." is not found, look for "VLSR",
   which is the same as "VELO-LSR". */
   if( GetValue2( ret, this, "VELO-%3c", AST__FLOAT, (void *) &vref, 0,
                  method, class, status ) ||
       GetValue2( ret, this, "VLSR", AST__FLOAT, (void *) &vref, 0,
                  method, class, status ) ){

/* Calculate the radio velocity (in the rest frame of the source) corresponding
   to the frequency at the reference channel. */
      v0 = AST__C*( restfreq - crval )/restfreq;

/* Assume that the source velocity is the difference between this velocity
   and the reference channel velocity given by "VELO-..." */
      vsource = vref - v0;

/* Get the keyword name and find the corresponding SSYSSRC keyword value. */
      keyname = CardName( this, status );
      if( !strcmp( keyname, "VELO-HEL" ) ) {
         ssyssrc = "BARYCENT";
      } else if( !strcmp( keyname, "VELO-OBS" ) || !strcmp( keyname, "VELO-TOP" ) ) {
         ssyssrc = "TOPOCENT";
      } else if( !strcmp( keyname, "VELO-EAR" ) || !strcmp( keyname, "VELO-GEO" ) ) {
         ssyssrc = "GEOCENTR";
      } else {
         ssyssrc = "LSRK";
      }
      SetValue( ret, "SSYSSRC", (void *) &ssyssrc, AST__STRING, NULL, status );

/* Convert from radio velocity to redshift and store as ZSOURCE */
      zsource = ( AST__C / (AST__C - vsource) ) - 1.0;
      SetValue( ret, "ZSOURCE", (void *) &zsource, AST__FLOAT, NULL, status );
   }
}

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

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

*  Type:
*     Private function.

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

*  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.
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   AstFitsChan *this;            /* Pointer to the FitsChan structure */

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

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

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

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

/* AltAxes. */
/* -------- */
   } else if ( !strcmp( attrib, "altaxes" ) ) {
      astClearAltAxes( this );

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

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

/* FitsAxisOrder. */
/* ----------- */
   } else if ( !strcmp( attrib, "fitsaxisorder" ) ) {
      astClearFitsAxisOrder( this );

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

/* FitsRounding. */
/* ------------- */
   } else if ( !strcmp( attrib, "fitsrounding" ) ) {
      astClearFitsRounding( this );

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

/* TabOK */
/* ----- */
   } else if ( !strcmp( attrib, "tabok" ) ) {
      astClearTabOK( this );

/* ForceTab */
/* -------- */
   } else if ( !strcmp( attrib, "forcetab" ) ) {
      astClearForceTab( this );

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

/* SipReplace */
/* ---------- */
   } else if ( !strcmp( attrib, "sipreplace" ) ) {
      astClearSipReplace( this );

/* FitsTol */
/* ------- */
   } else if ( !strcmp( attrib, "fitstol" ) ) {
      astClearFitsTol( this );

/* PolyTan */
/* ------- */
   } else if ( !strcmp( attrib, "polytan" ) ) {
      astClearPolyTan( this );

/* SipOK */
/* ------- */
   } else if ( !strcmp( attrib, "sipok" ) ) {
      astClearSipOK( 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.", status, attrib, astGetClass( this ) );
      astError( AST__NOWRT, "This is a read-only attribute." , status);

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

static void ClearCard( AstFitsChan *this, int *status ){

/*
*+
*  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.
*-
*/

/* Local Variables; */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */

/* Ensure the source function has been called */
   ReadFromSource( this, status );

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* 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 ), status );
   }
}

static int CnvValue( AstFitsChan *this, int type, int undef, void *buff,
                     const char *method, int *status ){

/*
*
*  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, int undef, void *buff,
*                   const char *method, int *status )

*  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.
*     undef
*        Determines what happens if the current card has an undefined
*        value. If "undef" is zero, an error will be reported identifying
*        the undefined keyword value. If "undef" is non-zero, no error is
*        reported and the contents of the output buffer are left unchanged.
*     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.
*     status
*        Pointer to the inherited status variable.

*  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.
*     - If the supplied value is undefined an error will be reported.
*/

/* 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, status );

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

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

static int CnvType( int otype, void *odata, size_t osize, int type, int undef,
                    int fitsrnd, void *buff, const char *name, const char *method,
                    const char *class, int *status ){
/*
*
*  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, int undef,
*                  int fitsrnd, void *buff, const char *name, const char *method,
*                  const char *class, int *status )

*  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.
*     undef
*        Determines what happens if the supplied data value type is
*        undefined If "undef" is zero, an error will be reported identifying
*        the undefined keyword value. If "undef" is non-zero, no error is
*        reported and the contents of the output buffer are left unchanged.
*     fitsrnd
*        The value of the FitsRounding attribute - the number of leading
*        digits to protect from rounding.
*     buff
*        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.
*     status
*        Pointer to the inherited status variable.

*  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: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   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 */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(NULL);

/* Assume success. */
   ret = 1;

/* If the supplied data type is undefined, report an error unless the
   returned data type is also undefined or an undefined value is
   acceptable for the keyword. */
   if( otype == AST__UNDEF ) {
      if( type != AST__UNDEF && !undef ) {
         ret = 0;
         astError( AST__FUNDEF, "The FITS keyword '%s' has an undefined "
                   "value.", status, name );
      }

/* If the returned data type is undefined, the returned value is
   immaterial, so leave the buffer contents unchanged. */
   } else if( type == AST__UNDEF ) {

/* 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. */
   } else if( ( odata && otype == AST__COMMENT ) ||
              ( !odata && otype != AST__COMMENT ) ) {
      ret = 0;

/* If there is no data value (and therefore this is a comment card),
   conversion is only possible if the output type is also a comment. */
   } else if( !odata ) {
      if( type != AST__COMMENT ) ret = 0;

/* Otherwise we have a data value, so do each possible combination of
   supplied and stored data types... */
   } else {

/* 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  ){
            if( odouble != AST__BAD ) {
               (void) sprintf( cnvtype_text, "%.*g", AST__DBL_DIG, odouble );
               CheckZero( cnvtype_text, odouble, 0, fitsrnd, status );
            } else {
               strcpy( cnvtype_text, BAD_STRING );
            }
            *( (char **) buff ) = cnvtype_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.", status, 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,
                    ( 0 == astSscanf( ostring, BAD_STRING " %n", &nc ) )
                  && (nc >= len ) ){
               *( (double *) buff ) = AST__BAD;
            } else if( nc = 0,
                     ( 1 != astSscanf( ostring, "%lf %n", (double *) buff, &nc ) )
                  || (nc < len ) ){
               ret = 0;
            }
         } else if( type == AST__STRING || type == AST__CONTINUE  ){
            strncpy( cnvtype_text, (char *) odata, AST__FITSCHAN_FITSCARDLEN );
            *( (char **) buff ) = cnvtype_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.", status, 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( cnvtype_text, "%d", oint );
            *( (char **) buff ) = cnvtype_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.", status, 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( cnvtype_text, "Y" );
            } else {
               strcpy( cnvtype_text, "N" );
            }
            *( (char **) buff ) = cnvtype_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.", status, 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( cnvtype_text0, "%.*g", AST__DBL_DIG, ( (double *) odata )[ 0 ] );
            CheckZero( cnvtype_text0, ( (double *) odata )[ 0 ], 0, fitsrnd, status );
            (void) sprintf( cnvtype_text1, "%.*g", AST__DBL_DIG, ( (double *) odata )[ 1 ] );
            CheckZero( cnvtype_text1, ( (double *) odata )[ 1 ], 0, fitsrnd, status );
            (void) sprintf( cnvtype_text, "%s %s", cnvtype_text0, cnvtype_text1 );
            *( (char **) buff ) = cnvtype_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.", status, 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( cnvtype_text, "%d %d", ( (int *) odata )[ 0 ],
                                           ( (int *) odata )[ 1 ] );
            *( (char **) buff ) = cnvtype_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.", status, type );
         }
      } else if( astOK ){
         ret = 0;
         astError( AST__INTER, "CnvType: AST internal programming error - "
                   "FITS data-type no. %d not yet supported.", status, type );
      }
   }
   return ret;
}

static int ComBlock( AstFitsChan *this, int incr, const char *method,
                     const char *class, int *status ){

/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, status ) == 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, status ) != 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, status );
         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, status ) == 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, status );
            MarkCard( this, status );
         }
         this->card = card0;
      }
   }

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

static char *ConcatWAT( AstFitsChan *this, int iaxis, const char *method,
                        const char *class, int *status ){
/*
*  Name:
*     ConcatWAT

*  Purpose:
*     Concatenate all the IRAF "WAT" keywords for an axis.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     char *ConcatWAT( AstFitsChan *this, int iaxis, const char *method,
*                      const char *class, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function searches the supplied FitsChan for any keywords of
*     the form "WATi_j", where i and j are integers and i is equal to the
*     supplied "iaxis" value plus one, and concatenates their string
*     values into a single string. Such keywords are created by IRAF to
*     describe their non-standard ZPX and TNX projections.

*  Parameters:
*     this
*        The FistChan.
*     iaxis
*        The zero-based index of the axis to be retrieved.
*     method
*         The name of the calling method to include in error messages.
*     class
*         The object type to include in error messages.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to a dynamically allocated, null terminated string
*     containing a copy of the concatentated WAT values. This string must
*     be freed by the caller (using astFree) when no longer required.
*
*     A NULL pointer will be returned if there are no WAT kewyords for
*     the requested axis in the FitsChan.

*  Notes:
*     - A NULL pointer value will be returned if this function is
*     invoked with the global error status set or if it should fail
*     for any reason.
*/

/* Local Variables: */
   char keyname[ FITSNAMLEN + 20 ];/* Keyword name */
   char *wat;                     /* Pointer to a single WAT string */
   char *result;                  /* Returned string */
   int watlen;                    /* Length of total WAT string (inc. term null)*/
   int j;                         /* WAT index */
   size_t size;                   /* Length of string value */

/* Initialise returned value. */
   result = NULL;

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

/* Rewind the FitsChan. */
   astClearCard( this );

/* Concatenate all the IRAF "WAT" keywords together for this axis. These
   keywords are marked as having been used, so that they are not written
   out when the FitsChan is deleted. */
   watlen = 1;
   j = 1;
   size = 0;
   sprintf( keyname, "WAT%d_%.3d", iaxis + 1, j );
   while( astOK ) {

/* Search forward from the current card for the next WAT card. If no
   found, try searching again from the start of the FitsChan. If not found
   evenm then, break. */
      if( ! FindKeyCard( this, keyname, method, class, status ) ) {
         astClearCard( this );
         if( ! FindKeyCard( this, keyname, method, class, status ) ) break;
      }

      wat = (char *) CardData( this, &size, status );
      result = (char *) astRealloc( (void *) result,
                                    watlen - 1 + size );
      if( result ) {
         strcpy( result + watlen - 1, wat );
         watlen += size - 1;
         MarkCard( this, status );
         MoveCard( this, 1, method, class, status );
         j++;
         sprintf( keyname, "WAT%d_%.3d", iaxis + 1, j );
      } else {
         break;
      }
   }

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

static int CountFields( const char *temp, char type, const char *method,
                        const char *class, int *status ){
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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'.", status, 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'.", status,
                      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 ], int *status ){

/*
*  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 ], int *status )

*  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.
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   const char *seq_chars = SEQ_CHARS;/* Pointer to characters used for encoding */
   char seq_char;                /* The first sequence character */
   const char *class;            /* Object clas */
   int found;                    /* Keyword entry found in list? */
   int limit;                    /* Sequence number has reached limit? */
   int nc;                       /* Number of basic keyword characters */
   int seq;                      /* The sequence number */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

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

/* On the first invocation only, determine the number of characters
   being used to encode sequence number information and save this
   value. */
   if( createkeyword_seq_nchars < 0 ) createkeyword_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; ( nc < ( FITSNAMLEN - 2 ) ) && name[ nc ]; 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. */
   if( this->keyseq ) {
      found = astMapGet0I( this->keyseq, keyword, &seq );
   } else {
      found = 0;
      this->keyseq = astKeyMap( " ", status );
   }

/* If the keyword was not found in the list, create a new list entry
   to describe it. */
   if( !found ) seq = 0;

/* 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 = ( seq >= ( createkeyword_seq_nchars * createkeyword_seq_nchars - 1 ) );
         if( !limit ) seq++;

/* Encode the sequence number into two characters and append them to
   the original keyword (with a terminating null). */
         seq_char = seq_chars[ seq / createkeyword_seq_nchars ];
         keyword[ nc ] = seq_char;
         keyword[ nc + 1 ] = seq_chars[ seq % createkeyword_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 || !HasCard( this, keyword, "astWrite", class, status ) ) break;
      }

/* Store the update sequence number in the keymap. The keys into this
   keymap are the base keyword name without the appended sequence string, so
   temporaily terminate the returned keyword name to exclude the sequence
   string. */
      keyword[ nc ] = '\0';
      astMapPut0I( this->keyseq, keyword, seq, NULL );
      keyword[ nc ] = seq_char;
   }
}

static double DateObs( const char *dateobs, int *status ) {
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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. */
      palCaldj( 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. */
         palDtf2d( 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, int *status ){
/*
*  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;

/* Remove it from the KeyMap holding all keywords. */
   astMapRemove( this->keywords, card->name );

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

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

/* 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, int *status ){

/*
*++
*  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;

/* Ensure the source function has been called */
   ReadFromSource( this, status );

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

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, int *status ){
/*
*  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 *ctype;              /* Pointer to CTYPE value */
   char code[ 4 ];           /* Projection code extracted from CTYPE */
   char dist[ 4 ];           /* Distortion code extracted from CTYPE */
   char msgbuf[ 250 ];       /* Buffer for warning message */
   char type[ 5 ];           /* Axis type extracted from CTYPE */
   double *dim;              /* Array holding array dimensions */
   int found_axes[ 2 ];      /* Index of axes with the distortion code */
   int i;                    /* FITS axis index */
   int nc;                   /* No. of characters in CTYPE without "-SIP" */
   int nfound;               /* No. of axes with the distortion code */
   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;

/* Allocate memory to hold the image dimensions. */
   dim = (double *) astMalloc( sizeof(double)*naxes );
   if( dim ){

/* Note the image dimensions, if known. If not, store AST__BAD values. */
      for( i = 0; i < naxes; i++ ){
         if( !astGetFitsF( this, FormatKey( "NAXIS", i + 1, -1, ' ', status ),
                           dim + i ) ) dim[ i ] = AST__BAD;
      }

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

/* "-SIP": Spitzer (http://irsa.ipac.caltech.edu/data/SPITZER/docs/files/spitzer/shupeADASS.pdf)
   ============= */

/* Spitzer distortion is limited to 2D. Check the first two axes to see if
   they have "-SIP" codes at the end of 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, 0, s, NULL, method, class, status );
      if( ctype ){
         nc = astChrLen( ctype ) - 4;
         if( nc >= 0 && !strcmp( ctype + nc, "-SIP" ) ) {
            ctype[ nc ] = 0;
            ctype = GetItemC( &(store->ctype), 1, 0, s, NULL, method, class, status );
            if( ctype ) {
               nc = astChrLen( ctype ) - 4;
               if( nc >= 0 && !strcmp( ctype + nc, "-SIP" ) ) {
                  ctype[ nc ] = 0;

/* Create a Mapping describing the distortion (other axes are passed
   unchanged by this Mapping), and add it in series with the returned map2
   (Spitzer distortion is applied to the translated pixel coordinates). */
                  tmap1 = SIPMapping( this, dim, store, s, naxes, method, class, status );
                  if( ! *map2 ) {
                     *map2 = tmap1;
                  } else {
                     tmap2 = (AstMapping *) astCmpMap( *map2, tmap1, 1, "", status );
                     *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, 0, s, NULL, method, class, status );
         if( ctype ){
            nc = astChrLen( ctype ) - 4;
            if( nc >= 0 && !strcmp( ctype + nc, "-SIP" ) ) {
               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, ' ', status ),  ctype );
                  Warn( this, "distortion", msgbuf, method, class, status );
               }
               ctype[ nc ] = 0;
            }
         }
      }

/* "-ZPX": IRAF (http://iraf.noao.edu/projects/ccdmosaic/zpx.html)
   ============= */

/* An IRAF ZPX header uses a ZPX projection within each CTYPE value in place
   of the basic ZPN projection. The SpecTrans function converts -ZPX" to
   "-ZPN-ZPX" (i.e. a basic projection of ZPN with a distortion code of
   "-ZPX"). This function then traps and processes the "-ZPX" distortion
   code. */

/* Look for axes that have the "-ZPX" code in their CTYPE values. If any
   are found, check that there are exactly two such axes, and terminate the
   ctype strings 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)*/
      nfound = 0;
      for( i = 0; i < naxes; i++ ){
         ctype = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
         if( ctype && 3 == astSscanf( ctype, "%4s-%3s-%3s", type, code, dist ) ){
            if( !strcmp( "ZPX", dist ) ){
               if( nfound < 2 ) found_axes[ nfound ] = i;
               nfound++;
               ctype[ 8 ] = 0;
            }
         }
      }

/* Issue a warning if more than two ZPX axes were found. */
      if( nfound > 2 ) {
         Warn( this, "distortion", "More than two axes were found "
               "with the \"-ZPX\" projection code. A ZPN projection "
               "will be used instead.", method, class, status );

/* Otherwise, create a Mapping describing the distortion (other axes are passed
   unchanged by this Mapping), and add it in series with the returned map4
   (ZPX distortion is applied to the translated, rotated, scaled IWC
   coordinates). */
      } else if( nfound == 2 ){
         tmap1 = ZPXMapping( this, store, s, naxes,  found_axes, method,
                             class, status );
         if( ! *map4 ) {
            *map4 = tmap1;
         } else {
            tmap2 = (AstMapping *) astCmpMap( *map4, tmap1, 1, "", status );
            *map4 = astAnnul( *map4 );
            tmap1 = astAnnul( tmap1 );
            *map4 = tmap2;
         }
      }

/* (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, 0, s, NULL, method, class, status );
         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, ' ', status ),  ctype, dist );
                  Warn( this, "distortion", msgbuf, method, class, status );
               }

/* 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;
            }
         }
      }
   }

/* Free resources. */
   dim = astFree( dim );
}

static void DSBSetUp( AstFitsChan *this, FitsStore *store,
                      AstDSBSpecFrame *dsb, char s, double crval,
                      const char *method, const char *class, int *status ){

/*
*  Name:
*     DSBSetUp

*  Purpose:
*     Modify an AstDSBSpecFrame object to reflect the contents of a FitsStore.

*  Type:
*     Private function.

*  Synopsis:

*     void DSBSetUp( AstFitsChan *this, FitsStore *store,
*                    AstDSBSpecFrame *dsb, char s, double crval,
*                    const char *method, const char *class, int *status  )

*  Class Membership:
*     FitsChan

*  Description:
*     This function sets the attributes of the supplied DSBSpecFrame to
*     reflect the values in the supplied FitsStore.

*  Parameters:
*     this
*        The FitsChan.
*     store
*        A structure containing information about the requested axis
*        descriptions derived from a FITS header.
*     dsb
*        Pointer to the DSBSpecFrame.
*     s
*        Alternate axis code.
*     crval
*        The spectral CRVAL value, in the spectral system represented by
*        the supplied DSBSPecFrame.
*     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.
*     status
*        Pointer to the inherited status variable.

*  Notes:
*     - This implementation follows the conventions of the FITS-CLASS encoding.
*/

/* Local Variables: */
   AstDSBSpecFrame *dsb_src; /* New DSBSpecFrame in which StdOfRest is source */
   AstDSBSpecFrame *dsb_topo;/* New DSBSpecFrame in which StdOfRest is topo */
   AstFrameSet *fs;        /* FrameSet connecting two standards of rest */
   double dsbcentre;       /* Topocentric reference (CRVAL) frequency */
   double in[2];           /* Source rest and image frequencies */
   double lo;              /* Topocentric Local Oscillator frequency */
   double out[2];          /* Topocentric rest and image frequencies */

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

/* In order to determine the topocentric IF, we need the topocentric
   frequencies corresponding to the RESTFREQ and IMAGFREQ values in the
   FITS header. The values stored in the FITS header are measured in Hz,
   in the source's rest frame, so we need a mapping from frequency in the
   source rest frame to topocentric frequency. Take a copy of the supplied
   DSBSpecFrame and then set its attributes to represent frequency in the
   sources rest frame. */
   dsb_src = astCopy( dsb );
   astSetStdOfRest( dsb_src, AST__SCSOR );
   astSetSystem( dsb_src, AST__FREQ );
   astSetUnit( dsb_src, 0, "Hz" );

/* Take a copy of this DSBSpecFrame and set its standard of rest to
   topocentric. */
   dsb_topo = astCopy( dsb_src );
   astSetStdOfRest( dsb_topo, AST__TPSOR );

/* Now get the Mapping between these. */
   fs = astConvert( dsb_src, dsb_topo, "" );
   dsb_src = astAnnul( dsb_src );
   dsb_topo = astAnnul( dsb_topo );

/* Check a conversion was found. */
   if( fs != NULL ) {

/* Use this Mapping to transform the rest frequency and the image
   frequency from the standard of rest of the source to that of the
   observer. */
      in[ 0 ] = astGetRestFreq( dsb );
      in[ 1 ] = GetItem( &(store->imagfreq), 0, 0, s, NULL, method, class, status );
      astTran1( fs, 2, in, 1, out );

/* The intermediate frequency is half the distance between these two
   frequencies. Note, the IF value is signed so as to put the rest
   frequency in the observed sideband. */
      if( out[ 0 ] != AST__BAD && out[ 1 ] != AST__BAD ) {

/* Store the spectral CRVAL value as the centre frequency of the
   DSBSpecFrame. The public astSetD method interprets the supplied value
   as a value in the spectral system described by the other SpecFrame
   attributes. */
         astSetD( dsb, "DSBCentre", crval );

/* To calculate the topocentric IF we need the topocentric frequency
   equivalent of CRVAL. So take a copy of the DSBSpecFrame, then set it to
   represent topocentric frequency, and read back the DSBCentre value. */
         dsb_topo = astCopy( dsb );
         astSetStdOfRest( dsb_topo, AST__TPSOR );
         astSetSystem( dsb_topo, AST__FREQ );
         astSetUnit( dsb_topo, 0, "Hz" );
         dsbcentre = astGetD( dsb_topo, "DSBCentre" );
         dsb_topo = astAnnul( dsb_topo );

/* We also need the topocentric Local Oscillator frequency. This is
   assumed to be half way between the topocentric IMAGFREQ and RESTFREQ
   values. */
         lo = 0.5*( out[ 1 ] + out[ 0 ] );

/* Set the IF to be the difference between the Local Oscillator frequency
   and the CRVAL frequency. */
         astSetIF( dsb, lo - dsbcentre );

/* Set the DSBSpecFrame to represent the observed sideband */
         astSetC( dsb, "SideBand", "observed" );
      }

/* Free resources. */
      fs = astAnnul( fs );
   }
}

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

/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

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

/* Local Variables: */
   const char *comm;   /* Pointer to comment string */
   char *cval;         /* Pointer to string keyword value */
   const 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), ' ', status ) != 1 ) return ret;

/* Check the first axis is RA with a TAN or TPN projection. */
   cval = GetItemC( &(store->ctype), 0, 0, ' ', NULL, method, class, status );
   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, 0, ' ', NULL, method, class, status );
   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, status );
   if( val != AST__BAD && val != 180.0 ) return ret;

/* Check that the RA/DEC system is FK5. */
   cval = GetItemC( &(store->radesys), 0, 0, ' ', NULL, method, class, status );
   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, status );
   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, status );
   if( cdelt == AST__BAD ) return ret;
   pc = GetItem( &(store->pc), 0, 0, ' ', NULL, method, class, status );
   if( pc == AST__BAD ) pc = 1.0;
   xpixelsz = cdelt*pc;
   cdelt = GetItem( &(store->cdelt), 1, 0, ' ', NULL, method, class, status );
   if( cdelt == AST__BAD ) return ret;
   pc = GetItem( &(store->pc), 1, 1, ' ', NULL, method, class, status );
   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, status );
   if( val != AST__BAD && val != 0.0 ) return ret;
   val = GetItem( &(store->pc), 1, 0, ' ', NULL, method, class, status );
   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, status );
      if( pvx[ m ] == AST__BAD || !gottpn ) pvx[ m ] = ( m == 1 ) ? 1.0 : 0.0;
      pvy[ m ] = GetItem( &(store->pv), 1, m, ' ', NULL, method, class, status );
      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), ' ', status ) > 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( !astEQUAL( 2*pvx[ 17 ], pvx[ 19 ] ) ) return ret;
   if( !astEQUAL( pvx[ 17 ], pvx[ 21 ] ) ) return ret;
   if( !astEQUAL( 2*pvy[ 17 ], pvy[ 19 ] ) ) return ret;
   if( !astEQUAL( 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, status );
   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, status );
   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, status );
   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, status );
   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)", status );
   SetValue( this, "CNPIX2", &cnpix2, AST__FLOAT, "Y corner (pixels)", status );
   SetValue( this, "PPO1", &ppo1, AST__FLOAT, "Orientation co-efficients", status );
   SetValue( this, "PPO2", &ppo2, AST__FLOAT, "", status );
   SetValue( this, "PPO3", &ppo3, AST__FLOAT, "", status );
   SetValue( this, "PPO4", &ppo4, AST__FLOAT, "", status );
   SetValue( this, "PPO5", &ppo5, AST__FLOAT, "", status );
   SetValue( this, "PPO6", &ppo6, AST__FLOAT, "", status );
   SetValue( this, "XPIXELSZ", &xpixelsz, AST__FLOAT, "X pixel size (microns)", status );
   SetValue( this, "YPIXELSZ", &ypixelsz, AST__FLOAT, "Y pixel size (microns)", status );
   SetValue( this, "PLTRAH", &pltrah, AST__FLOAT, "RA at plate centre", status );
   SetValue( this, "PLTRAM", &pltram, AST__FLOAT, "", status );
   SetValue( this, "PLTRAS", &pltras, AST__FLOAT, "", status );
   SetValue( this, "PLTDECD", &pltdecd, AST__FLOAT, "DEC at plate centre", status );
   SetValue( this, "PLTDECM", &pltdecm, AST__FLOAT, "", status );
   SetValue( this, "PLTDECS", &pltdecs, AST__FLOAT, "", status );
   SetValue( this, "PLTDECSN", &pltdecsn, AST__STRING, "", status );
   SetValue( this, "PLTSCALE", &pltscl, AST__FLOAT, "Plate scale (arcsec/mm)", status );
   comm = "Plate solution x co-efficients";
   for( i = 0; i < 20; i++ ){
      SetValue( this, FormatKey( "AMDX", i + 1, -1, ' ', status ), amdx + i,
                AST__FLOAT, comm, status );
      comm = NULL;
   }
   comm = "Plate solution y co-efficients";
   for( i = 0; i < 20; i++ ){
      SetValue( this, FormatKey( "AMDY", i + 1, -1, ' ', status ), amdy + i,
                AST__FLOAT, comm, status );
      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, int *status ){

/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.
*/

/* 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 optional DSS keywords, supplying defaults for any missing keywords. */
   cnpix1 = 0.0;
   cnpix2 = 0.0;
   GetValue( this, "CNPIX1", AST__FLOAT, &cnpix1, 0, 1, method, class, status );
   GetValue( this, "CNPIX2", AST__FLOAT, &cnpix2, 0, 1, method, class, status );

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

/* Copy the first 10 non-blank characters from the PLTDECSN keyword. */
   GetValue( this, "PLTDECSN", AST__STRING, &text, 1, 1, method, class, status );
   if( astOK ) {
      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, status );
   GetValue( this, "PPO1", AST__FLOAT, &dummy, 0, 1, method, class, status );
   GetValue( this, "PPO2", AST__FLOAT, &dummy, 0, 1, method, class, status );
   GetValue( this, "PPO4", AST__FLOAT, &dummy, 0, 1, method, class, status );
   GetValue( this, "PPO5", AST__FLOAT, &dummy, 0, 1, method, class, status );

/* 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, status );
      (void) sprintf( keyname, "AMDY%d", i + 1 );
      amdy[i] = AST__BAD;
      GetValue( this, keyname, AST__FLOAT, amdy + i, 0, 1, method, class, status );
   }

/* 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, status );
      }
      if( amdx[0] != AST__BAD ) {
         pv = amdx[0]/3600.0;
         SetItem( &(store->pv), 0, 1, ' ', pv, status );
      }
      if( amdx[1] != AST__BAD ) {
         pv = amdx[1]/3600.0;
         SetItem( &(store->pv), 0, 2, ' ', pv, status );
      }
      if( amdx[3] != AST__BAD && amdx[6] != AST__BAD ) {
         pv = ( amdx[3] + amdx[6] )/3600.0;
         SetItem( &(store->pv), 0, 4, ' ', pv, status );
      }
      if( amdx[4] != AST__BAD ) {
         pv = amdx[4]/3600.0;
         SetItem( &(store->pv), 0, 5, ' ', pv, status );
      }
      if( amdx[5] != AST__BAD && amdx[6] != AST__BAD ) {
         pv = ( amdx[5] + amdx[6] )/3600.0;
         SetItem( &(store->pv), 0, 6, ' ', pv, status );
      }
      if( amdx[7] != AST__BAD && amdx[11] != AST__BAD ) {
         pv = ( amdx[7] + amdx[11] )/3600.0;
         SetItem( &(store->pv), 0, 7, ' ', pv, status );
      }
      if( amdx[8] != AST__BAD ) {
         pv = amdx[8]/3600.0;
         SetItem( &(store->pv), 0, 8, ' ', pv, status );
      }
      if( amdx[9] != AST__BAD && amdx[11] != AST__BAD ) {
         pv = ( amdx[9] + amdx[11] )/3600.0;
         SetItem( &(store->pv), 0, 9, ' ', pv, status );
      }
      if( amdx[10] != AST__BAD ) {
         pv = amdx[10]/3600.0;
         SetItem( &(store->pv), 0, 10, ' ', pv, status );
      }
      if( amdx[12] != AST__BAD ) {
         pv = amdx[12]/3600.0;
         SetItem( &(store->pv), 0, 17, ' ', pv, status );
         SetItem( &(store->pv), 0, 19, ' ', 2*pv, status );
         SetItem( &(store->pv), 0, 21, ' ', pv, status );
      }
      if( amdy[2] != AST__BAD ) {
         pv = amdy[2]/3600.0;
         SetItem( &(store->pv), 1, 0, ' ', pv, status );
      }
      if( amdy[0] != AST__BAD ) {
         pv = amdy[0]/3600.0;
         SetItem( &(store->pv), 1, 1, ' ', pv, status );
      }
      if( amdy[1] != AST__BAD ) {
         pv = amdy[1]/3600.0;
         SetItem( &(store->pv), 1, 2, ' ', pv, status );
      }
      if( amdy[3] != AST__BAD && amdy[6] != AST__BAD ) {
         pv = ( amdy[3] + amdy[6] )/3600.0;
         SetItem( &(store->pv), 1, 4, ' ', pv, status );
      }
      if( amdy[4] != AST__BAD ) {
         pv = amdy[4]/3600.0;
         SetItem( &(store->pv), 1, 5, ' ', pv, status );
      }
      if( amdy[5] != AST__BAD && amdy[6] != AST__BAD ) {
         pv = ( amdy[5] + amdy[6] )/3600.0;
         SetItem( &(store->pv), 1, 6, ' ', pv, status );
      }
      if( amdy[7] != AST__BAD && amdy[11] != AST__BAD ) {
         pv = ( amdy[7] + amdy[11] )/3600.0;
         SetItem( &(store->pv), 1, 7, ' ', pv, status );
      }
      if( amdy[8] != AST__BAD ) {
         pv = amdy[8]/3600.0;
         SetItem( &(store->pv), 1, 8, ' ', pv, status );
      }
      if( amdy[9] != AST__BAD && amdy[11] != AST__BAD ) {
         pv = ( amdy[9] + amdy[11] )/3600.0;
         SetItem( &(store->pv), 1, 9, ' ', pv, status );
      }
      if( amdy[10] != AST__BAD ) {
         pv = amdy[10]/3600.0;
         SetItem( &(store->pv), 1, 10, ' ', pv, status );
      }
      if( amdy[12] != AST__BAD ) {
         pv = amdy[12]/3600.0;
         SetItem( &(store->pv), 1, 17, ' ', pv, status );
         SetItem( &(store->pv), 1, 19, ' ', 2*pv, status );
         SetItem( &(store->pv), 1, 21, ' ', pv, status );
      }

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

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

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

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

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

static void EmptyFits( AstFitsChan *this, int *status ){

/*
*++
*  Name:
c     astEmptyFits
f     AST_EMPTYFITS

*  Purpose:
*     Delete all cards in a FitsChan.

*  Type:
*     Public virtual function.

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

*  Class Membership:
*     FitsChan method.

*  Description:
c     This function
f     This routine
*     deletes all cards and associated information from a FitsChan.

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

*  Notes:
*     - This method simply deletes the cards currently in the FitsChan.
c     Unlike astWriteFits,
f     Unlike AST_WRITEFITS,
*     they are not first written out to the sink function or sink file.
*     - Any Tables or warnings stored in the FitsChan are also deleted.
*     - This method attempt to execute even if an error has occurred
*     previously.
*--
*/

/* Local Variables: */
   astDECLARE_GLOBALS         /* Declare the thread specific global data */
   const char *class;         /* Pointer to string holding object class */
   const char *method;        /* Pointer to string holding calling method */
   int old_ignore_used;       /* Original setting of ignore_used variable */

/* Check a FitsChan was supplied. */
   if( !this ) return;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* 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_ignore_used = ignore_used;
   ignore_used = 0;
   astClearCard( this );
   while( !astFitsEof( this ) ) DeleteCard( this, method, class, status );
   ignore_used = old_ignore_used;

/* Delete the KeyMap which holds keywords and the latest sequence number
   used by each of them. */
   if( this->keyseq ) this->keyseq = astAnnul( this->keyseq );

/* Delete the KeyMap holding the keyword names. */
   if( this->keywords ) this->keywords = astAnnul( this->keywords );

/* Free any memory used to hold the Warnings attribute value. */
   this->warnings = astFree( this->warnings );

/* Other objects in the FitsChan structure. */
   if( this->tables ) this->tables = astAnnul( this->tables );
}

static int EncodeFloat( char *buf, int digits, int width, int maxwidth,
                        int fitsrnd, double value, int *status ){
/*
*
*  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,
*                      int fitsrnd, double value, int *status )

*  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.
*     fitsrnd
*        The value of the FitsRounding attribute - the number of leading
*        digits to protect from rounding.
*     value
*        The value to format.
*     status
*        Pointer to the inherited status variable.

*  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, fitsrnd, status );

/* 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, int *status ){

/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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;

/* Get the data type of the keyword. */
   type = CardType( this, status );

/* Get a pointer to the data value in the current card. */
   data = CardData( this, NULL, status );

/* Return if there is no defined value associated with the keyword in the
   current card. */
   if( type != AST__UNDEF ) {

/* Get the name of the keyword. */
      name = CardName( this, status );

/* 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,
                            AST__FITSCHAN_FITSCARDLEN - col + 1,
                            astGetFitsRounding( this ), dval, status );
         if( len <= 0 && astOK ) {
            astError( AST__BDFTS, "%s(%s): Cannot encode floating point value "
                      "%g into a FITS header card for keyword '%s'.", status, 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 ) > AST__FITSCHAN_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'.", status, 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 > AST__FITSCHAN_FITSCARDLEN - col ) {
            astError( AST__BDFTS, "%s(%s): Cannot encode integer value %d into a "
                      "FITS header card for keyword '%s'.", status, 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,
                             AST__FITSCHAN_FITSCARDLEN - col + 1,
                             astGetFitsRounding( this ), dval, status );
         if( rlen <= 0 || rlen > AST__FITSCHAN_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'.", status, method, astGetClass( this ), dval,
                      ( (double *) data )[ 1 ], name );
         } else {
            dval = ( (double *) data )[ 1 ];
            ilen = EncodeFloat( buf + rlen, digits,
                                FITSIMCOL - FITSRLCOL,
                                AST__FITSCHAN_FITSCARDLEN - col - rlen,
                                astGetFitsRounding( this ), dval, status );
            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'.", status, 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 > AST__FITSCHAN_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'.", status, 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 > AST__FITSCHAN_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'.", status, 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.", status,
                   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, int *status ){
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, "", status );
      }

/* 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, status );
         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, status );
            if( ret ) new_mapb = astClone( mapb );
         } else {
            ret = ExtractGrismMap( mapb, iax - na, &new_mapb, status );
            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, "", status );
         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, int *status ){
/*
*  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, int *status )

*  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 positions. 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".
*     status
*        Pointer to the inherited status variable.

*  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. */
      (void) 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, int *status ){
/*
*  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, int *status )

*  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".
*     status
*        Pointer to the inherited status variable.

*  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*( 1 + dim[ i ] );
               if( dim[ i ] > ddlim ) ddlim = dim[ i ];
            } else {
               ret = 0;
               break;
            }
         }
      }
      if( ret ) ret = MakeBasisVectors( map, nin, nout, g0, psetg, psetw, status );

/* 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, status );
      }

/* 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, status );

/* 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, status );
         }

/* 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, int *status ) {
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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 *assys;
   char *astype;
   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, status );
   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, 0, s, NULL, method, class, status );

/* Check a value was found. */
      if( ctype ) {

/* First check for spectral axes, either FITS-WCS or AIPS-like. */
         if( IsSpectral( ctype, stype, algcode, status ) ||
             IsAIPSSpectral( ctype, &astype, &assys, status ) ) {
            *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, "AZ--", 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, "EL--", 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, int all, int rewind,
                     const char *method, const char *class, int *status ){

/*
*  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, int all, int rewind,
*                   const char *method, const char *class, int *status )

*  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). If "last" is
*     non-zero, the next card becomes the current card. If "last" is
*     zero, the WCS card is left as the current card. Cards marked as
*     having been read are included or not, as specified by "all".

*  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.
*     all
*        If non-zero, then cards marked as having been read are included
*        in the search. Otherwise such cards are ignored.
*     rewind
*        Only used if "last" is zero (i.e. the first card is being
*        searched for). If "rewind" is non-zero, then the search starts
*        from the first card in the FitsChan. If zero, the search starts
*        from the current card.
*     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.
*     status
*        Pointer to the inherited status variable.

*  Notes:
*     -  The FitsChan is left at end-of-file if no FITS-WCS keyword cards
*     are found in the FitsChan.
*-
*/

/* Local Variables: */
   astDECLARE_GLOBALS       /* Declare the thread specific global data */
   const char *keyname;     /* Keyword name from current card */
   int nfld;                /* Number of fields in keyword template */
   int old_ignore_used;     /* Original value of variable ignore_used */

/* Check the global status. Also check the FitsChan is not empty. */
   if( !astOK || !this->head ) return;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Indicate that we should, or should not, skip over cards marked as having
   been read. */
   old_ignore_used = ignore_used;
   ignore_used = all ? 0 : 1;

/* If required, set the FitsChan to start or end of file. */
   if( last ) {
      astSetCard( this, INT_MAX );
   } else if( rewind ) {
      astClearCard( this );
   }

/* If the current card is marked as used, and we are skipping used cards,
   move on to the next unused card */
   if( CARDUSED( this->card ) ) MoveCard( this, last?-1:1, method, class, status );

/* 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, status );

/* 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, status ) ||
             Match( keyname, "CRPIX%d%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "CDELT%d%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "CROTA%d", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "CTYPE%d%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "CUNIT%d%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PC%3d%3d%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "CD%3d%3d%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "CD%1d_%1d%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PC%1d_%1d%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "LONGPOLE", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "LONPOLE%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "LATPOLE%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PROJP%d", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PV%d_%d%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PS%d_%d%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "EPOCH", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "EQUINOX%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "MJD-OBS",  0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "DATE-OBS", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "TIMESYS", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "RADECSYS", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "RADESYS%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "C%1dVAL%d", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "C%1dPIX%d", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "C%1dELT%d", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "C%1dYPE%d", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "C%1dNIT%d", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "CNPIX1", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "CNPIX2", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PPO%d", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "AMDX%d", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "AMDY%d", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "XPIXELSZ", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "YPIXELSZ", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PLTRAH", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PLTRAM", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PLTRAS", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PLTDECD", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PLTDECM", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PLTDECS", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PLTDECSN", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PLTSCALE", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PPO1", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PPO2", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PPO4", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "PPO5", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "WCSNAME%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "SPECSYS%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "SSYSSRC%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "ZSOURCE%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "VELOSYS%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "RESTFRQ%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "MJD_AVG%0c", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "OBSGEO-X", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "OBSGEO-Y", 0, NULL, &nfld, method, class, status ) ||
             Match( keyname, "OBSGEO-Z", 0, NULL, &nfld, method, class, status ) ) {
            if( last ) MoveCard( this, 1, method, class, status );
            break;
         }
      }

/* Leave the FitsChan at end-of-file if no WCS cards were found. */
      if( (last && FitsSof( this, status ) ) ||
          (!last && astFitsEof( this ) ) ) {
         astSetCard( this, INT_MAX );
         break;
      } else {
         MoveCard( this, last?-1:1, method, class, status );
      }
   }

/* Re-instate the original flag indicating if cards marked as having been
   read should be skipped over. */
   ignore_used = old_ignore_used;

/* Return. */
   return;
}

static int FindString( int n, const char *list[], const char *test,
                       const char *text, const char *method,
                       const char *class, int *status ){
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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 ], status ) ) 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.", status,
                method, class, test, text );
      ret = -1;
   }

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

static int FitOK( int n, double *act, double *est, double tol, int *status ) {
/*
*  Name:
*     FitOK

*  Purpose:
*     See if a fit is usable.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int FitOK( int n, double *act, double *est, double tol, int *status )

*  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
*     that the RMS residual between them is no more than "tol".

*  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.
*     tol
*        The largest acceptable RMS error between "act" and "est".
*     status
*        Pointer to the inherited status variable.

*  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;
   double *px, *py, diff, mserr;

/* Initialise */
   ret = 0;

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

/* Initialise the sum of the squared residuals, and the number summed. */
   s1 = 0.0;
   s2 = 0.0;

/* Initialise pointers to the next actual and estimated values to use. */
   px = act;
   py = est;

/* Loop round all pairs of good actual and estimate value. */
   for( i = 0; i < n; i++, px++, py++ ){
      if( *px != AST__BAD && *py != AST__BAD ) {

/* Increment the sums need to find the RMS residual between the actual
   and estimated values. */
         diff = *px - *py;
         s1 += diff*diff;
         s2 += 1.0;
      }
   }

/* If the sums are usable... */
   if( s2 > 0.0 ) {

/* Form the mean squared residual, and check if it is less than the
   squared error limit. */
      mserr = s1/s2;
      if( mserr < tol*tol ) ret = 1;
   }

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

static int FitsAxisOrder( AstFitsChan *this, int nwcs, AstFrame *wcsfrm,
                          int *perm, int *status ){
/*
*  Name:
*     FitsAxisOrder

*  Purpose:
*     Return the order of WCS axes specified by attribute FitsAxisOrder.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int FitsAxisOrder( AstFitsChan *this, int nwcs, AstFrame *wcsfrm,
*                        int *perm, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function returns an array indicating the order of the WCS axes
*     within the output FITS header, as specified by the FitsAxisOrder
*     attribute.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     nwcs
*        The number of axes in "wcsfrm".
*     wcsfrm
*        The Frame containing the output WCS axes.
*     perm
*        Pointer to an array of "nwcs" integers. On exit, element "k"
*        of this array holds the zero-based index of the FITS-WCS axis
*        (i.e. one less than the value of "i" in the keyword names
*        "CTYPEi", "CRVALi", etc) that describes the k'th axis in "wcsfrm".
*        In other words, "perm[ast_index] = fits_index". The order is
*        determined by the FitsAxisOrder attribute. If this attribute is
*        "<copy>" or "<auto>", then "perm[k]=k" for all k on exit (i.e.
*        a unit mapping between axes in "wcsfrm" and the FITS header).
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     Returns zero if the FitsAxisOrder attribute is "<auto">, and
*     non-zero otherwise. This is a flag indicating if the returned
*     values in "perm" can be used s they are.

*/

/* Local Variables: */
   AstKeyMap *km;    /* KeyMap holding axis indices keyed by axis symbols */
   char **words;     /* Pointer to array of words from FitsAxisOrder */
   char attr_name[50];/* Attribute name */
   const char *attr; /* Pointer to a string holding the FitsAxisOrder value */
   int i;            /* Loop count */
   int j;            /* Zero-based axis index */
   int k;            /* Zero-based axis index */
   int nword;        /* Number of words in FitsAxisOrder */
   int result;       /* Retrned value */

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

/* Initialise the returned array to a unit mapping from Frame axis to
   FITS axis. */
   for( i = 0; i < nwcs; i++ ) perm[ i ] = i;

/* Get the FitsAxisOrder attribute value, and set the returned value to
   indicate if it is "<auto>". */
   attr = astGetFitsAxisOrder( this );
   result = !astChrMatch( attr, "<auto>" );

/* Return immediately if it is "<auto>" or "<copy>". */
   if( result && !astChrMatch( attr, "<copy>" ) ) {

/* Create a KeyMap in which each key is the Symbol for an axis and the
   associated value is the zero based index of the axis within "wcsfrm". */
      km = astKeyMap( "KeyCase=0", status );
      for( i = 0; i < nwcs; i++ ){
         sprintf( attr_name, "Symbol(%d)", i + 1 );
         astMapPut0I( km, astGetC( wcsfrm, attr_name ), i, NULL );
      }

/* Split the FitsAxisOrder value into a collection of space-separated words. */
      words = astChrSplit( attr, &nword );

/* Loop round them all. */
      k = 0;
      for( i = 0; i < nword; i++ ) {

/* Get the zero based index within "wcsfrm" of the axis that has a Symbol
   equal to the current word from FitsAxisOrder. */
         if( astMapGet0I( km, words[ i ], &j ) ) {

/* If this "wcsfrm" axis has already been used, report an error. */
            if( j < 0 ) {
               if( astOK ) astError( AST__ATTIN, "astWrite(fitschan): "
                           "attribute FitsAxisOrder (%s) refers to axis "
                           "%s more than once.", status, attr, words[ i ] );

/* Otherwise, set the corresponding element of the returned array, and
   ensure this axis cannot be used again by assigning it an index of -1
   in the KeyMap. */
            } else {
               perm[ j ] = k++;
               astMapPut0I( km, words[ i ], -1, NULL );
            }
         }

/* Free the memory holding the copy of the word. */
         words[ i ] = astFree( words[ i ] );
      }

/* Report an error if any wcsfrm axes were not included in FitsAxisOrder. */
      if( astOK ) {
         for( i = 0; i < nwcs; i++ ){
            sprintf( attr_name, "Symbol(%d)", i + 1 );
            if( astMapGet0I( km, astGetC( wcsfrm, attr_name ), &j ) ) {
               if( j >= 0 ) {
                  astError( AST__ATTIN, "astWrite(fitschan): attribute FitsAxisOrder "
                      "(%s) does not specify a position for WCS axis '%s'.",
                      status, attr, astGetC( wcsfrm, attr_name ) );
                  break;
               }
            }
         }
      }

/* Free resources. */
      words = astFree( words );
      km = astAnnul( km );
   }

   return result;
}

static int FitsFromStore( AstFitsChan *this, FitsStore *store, int encoding,
                          double *dim, AstFrameSet *fs, const char *method,
                          const char *class, int *status ){

/*
*  Name:
*     FitsFromStore

*  Purpose:
*     Store WCS keywords in a FitsChan.

*  Type:
*     Private function.

*  Synopsis:

*     int FitsFromStore( AstFitsChan *this, FitsStore *store, int encoding,
*                        double *dim, AstFrameSet *fs, const char *method,
*                        const char *class, int *status )

*  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.
*     dim
*        Pointer to an array holding the array dimensions (AST__BAD
*        indicates that the dimenson is 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.
*     status
*        Pointer to the inherited status variable.

*  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, 1, 0, method, class, status );

/* Do each non-standard FITS encoding... */
   if( encoding == DSS_ENCODING ){
      ret = DSSFromStore( this, store, method, class, status );
   } else if( encoding == FITSPC_ENCODING ){
      ret = PCFromStore( this, store, method, class, status );
   } else if( encoding == FITSIRAF_ENCODING ){
      ret = IRAFFromStore( this, store, method, class, status );
   } else if( encoding == FITSAIPS_ENCODING ){
      ret = AIPSFromStore( this, store, method, class, status );
   } else if( encoding == FITSAIPSPP_ENCODING ){
      ret = AIPSPPFromStore( this, store, method, class, status );
   } else if( encoding == FITSCLASS_ENCODING ){
      ret = CLASSFromStore( this, store, fs, dim, method, class, status );

/* Standard FITS-WCS encoding */
   } else {
      ret = WcsFromStore( this, store, method, class, status );
   }

/* If there are any Tables in the FitsStore move the KeyMap that contains
   them from the FitsStore to the FitsChan, from where they can be
   retrieved using the public astGetTables method. */
   if( astMapSize( store->tables ) > 0 ) {
      if( !this->tables ) this->tables = astKeyMap( " ", status );
      astMapCopy( this->tables, store->tables );
      (void) astAnnul( store->tables );
      store->tables = astKeyMap( " ", status );
   }

/* 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, int *status ){

/*
*  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;
   char s;
   char smax;
   int naxis;

/* 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->ps = 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->dut1 = NULL;
      ret->dtai = NULL;
      ret->pv = NULL;
      ret->specsys = NULL;
      ret->ssyssrc = NULL;
      ret->obsgeox = NULL;
      ret->obsgeoy = NULL;
      ret->obsgeoz = NULL;
      ret->restfrq = NULL;
      ret->restwav = NULL;
      ret->zsource = NULL;
      ret->velosys = NULL;
      ret->asip = NULL;
      ret->bsip = NULL;
      ret->apsip = NULL;
      ret->bpsip = NULL;
      ret->imagfreq = NULL;
      ret->axref = NULL;
      ret->naxis = 0;
      ret->timesys = NULL;
      ret->tables = astKeyMap( " ", status );
      ret->skyref = NULL;
      ret->skyrefp = NULL;
      ret->skyrefis = NULL;
   }

/* Call the routine apropriate to the encoding. */
   if( encoding == DSS_ENCODING ){
      DSSToStore( this, ret, method, class, status );

/* 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, status );

/* Copy the required values to the FitsStore, using keywords in "trans"
   in preference to those in "this". */
      WcsToStore( this, trans, ret, method, class, status );

/* 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 or alternate CRPIX keyword. */
      ret->naxis = GetMaxJM( &(ret->crpix), ' ', status ) + 1;
      smax = GetMaxS( &(ret->crval), status );
      for( s = 'A'; s <= smax && astOK; s++ ){
         naxis = GetMaxJM( &(ret->crpix), s, status ) + 1;
         if( naxis > ret->naxis ) ret->naxis = naxis;
      }
   }

/* If an error has occurred, free the returned FitsStore, and return a null
   pointer. */
   if( !astOK ) ret = FreeStore( ret, status );

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

static void FreeItem( double ****item, int *status ){
/*
*  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, int *status );

*  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).
*     status
*        Pointer to the inherited status variable.

*  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 */
   int oldstatus;        /* Old error status value */
   int oldreport;        /* Old error reporting value */

/* Other initialisation to avoid compiler warnings. */
   oldreport = 0;

/* Check the supplied pointer */
   if( item && *item ){

/* Start a new error reporting context. */
      oldstatus = astStatus;
      if( !astOK ) {
         oldreport = astReporting( 0 );
         astClearStatus;
      }

/* 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 );

/* If there was an error status on entry to this function, re-instate it.
   Otherwise, allow any new error status to remain. */
      if( oldstatus ){
         if( !astOK ) astClearStatus;
         astSetStatus( oldstatus );
         astReporting( oldreport );
      }
   }
}

static void FreeItemC( char *****item, int *status ){
/*
*  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, int *status )

*  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->ctype) ).
*        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 (a char pointyer) for every pixel axis (i) or
*        projection parameter (m).
*     status
*        Pointer to the inherited status variable.

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

/* Local Variables: */
   int si;               /* Integer co-ordinate version index */
   int i;                /* Intermediate co-ordinate axis index */
   int jm;               /* Pixel co-ordinate axis or parameter index */
   int oldstatus;        /* Old error status value */
   int oldreport;        /* Old error reporting value */

/* Other initialisation to avoid compiler warnings. */
   oldreport = 0;

/* Check the supplied pointer */
   if( item && *item ){

/* Start a new error reporting context. */
      oldstatus = astStatus;
      if( !astOK ) {
         oldreport = astReporting( 0 );
         astClearStatus;
      }

/* 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( i = 0; i < astSizeOf( (void *) (*item)[si] )/sizeof(char **);
                 i++ ){

/* Check the pointer stored for this intermediate axis is not null. */
               if( (*item)[si][i] ) {

/* Loop round the pixel axes or parameter values. */
                  for( jm = 0; jm < astSizeOf( (void *) (*item)[si][i] )/sizeof(char *);
                       jm++ ){

/* Free the string. */
                     (*item)[si][i][jm] = (char *) astFree( (void *) (*item)[si][i][jm] );
                  }

/* Free the pixel axes/parameter pointer */
                  (*item)[si][i] = (char **) astFree( (void *) (*item)[si][i] );
               }
            }

/* Free the intermediate axes pointer */
            (*item)[si] = (char ***) astFree( (void *) (*item)[si] );
         }
      }

/* Free the co-ordinate versions pointer */
      *item = (char ****) astFree( (void *) *item );

/* If there was an error status on entry to this function, re-instate it.
   Otherwise, allow any new error status to remain. */
      if( oldstatus ){
         if( !astOK ) astClearStatus;
         astSetStatus( oldstatus );
         astReporting( oldreport );
      }
   }
}

static FitsStore *FreeStore( FitsStore *store, int *status ){
/*
*  Name:
*     FreeStore

*  Purpose:
*     Free dynamic arrays stored in a FitsStore structure.

*  Type:
*     Private function.

*  Synopsis:
*     FitsStore *FreeStore( FitsStore *store, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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), status );
   FreeItemC( &(store->ctype), status );
   FreeItemC( &(store->ctype_com), status );
   FreeItemC( &(store->cunit), status );
   FreeItemC( &(store->radesys), status );
   FreeItemC( &(store->wcsname), status );
   FreeItemC( &(store->specsys), status );
   FreeItemC( &(store->ssyssrc), status );
   FreeItemC( &(store->ps), status );
   FreeItemC( &(store->timesys), status );
   FreeItem( &(store->pc), status );
   FreeItem( &(store->cdelt), status );
   FreeItem( &(store->crpix), status );
   FreeItem( &(store->crval), status );
   FreeItem( &(store->equinox), status );
   FreeItem( &(store->latpole), status );
   FreeItem( &(store->lonpole), status );
   FreeItem( &(store->mjdobs), status );
   FreeItem( &(store->dut1), status );
   FreeItem( &(store->dtai), status );
   FreeItem( &(store->mjdavg), status );
   FreeItem( &(store->pv), status );
   FreeItem( &(store->wcsaxes), status );
   FreeItem( &(store->obsgeox), status );
   FreeItem( &(store->obsgeoy), status );
   FreeItem( &(store->obsgeoz), status );
   FreeItem( &(store->restfrq), status );
   FreeItem( &(store->restwav), status );
   FreeItem( &(store->zsource), status );
   FreeItem( &(store->velosys), status );
   FreeItem( &(store->asip), status );
   FreeItem( &(store->bsip), status );
   FreeItem( &(store->apsip), status );
   FreeItem( &(store->bpsip), status );
   FreeItem( &(store->imagfreq), status );
   FreeItem( &(store->axref), status );
   store->tables = astAnnul( store->tables );
   FreeItem( &(store->skyref), status );
   FreeItem( &(store->skyrefp), status );
   FreeItemC( &(store->skyrefis), status );
   return (FitsStore *) astFree( (void *) store );
}

static char *FormatKey( const char *key, int c1, int c2, char s, int *status ){
/*
*  Name:
*     FormatKey

*  Purpose:
*     Format a keyword name with indices and co-ordinate version character.

*  Type:
*     Private function.

*  Synopsis:
*     char *FormatKey( const char *key, int c1, int c2, char s, int *status )

*  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 preceded by an underscore.
*     s
*        The co-ordinate version character to append to the end of the
*        final string. Ignored if blank.
*     status
*        Pointer to the inherited status variable.
*  Returned Value;
*     A pointer to a static character buffer containing the final string.
*     NULL if an error occurs.
*/

/* Local Variables: */
   astDECLARE_GLOBALS
   char *ret;
   int len;
   int nc;

/* Initialise */
   ret = NULL;

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(NULL);

/* 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( formatkey_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( formatkey_buff + len, "%d", c1 ) ) >= 0 ){
         len += nc;
      } else {
         len = -1;
      }

/* If index c2 has been supplied, append it to the end of the string,
   preceded by an underscore. */
      if( c2 >= 0 ) {
         if( len >= 0 && ( nc = sprintf( formatkey_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( formatkey_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.", status, key, c1, c2, s );
      ret = NULL;
   } else {
      ret = formatkey_buff;
   }
   return formatkey_buff;
}

static AstObject *FsetFromStore( AstFitsChan *this, FitsStore *store,
                                 const char *method, const char *class, int *status ){
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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[ 40 ];   /* 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", status );

/* 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, "", status );

/* 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. Only do this if there are some primary axis
   descriptions. */
      if( GetMaxJM( &(store->crpix), ' ', status ) >= 0 ) {
         AddFrame( this, ret, pixel, store->naxis, store, ' ', method, class, status );
      }

/* 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), status ) && 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, status ) != AST__BAD ||
                GetItem( &(store->crpix), 0, i, s, NULL, method, class, status ) != AST__BAD ||
                GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status ) != 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, status );
      }

/* Ensure the pixel Frame is the Base Frame and the primary physical
   Frame is the Current Frame. */
      astSetBase( ret, pixel );
      astSetCurrent( ret, physical );

/* Remove any unneeded Frames that hold a FITS representation of offset
   coordinates. */
      TidyOffsets( ret, status );

/* 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, int encoding, const char *class,
                               const char *method, int *status ){

/*
*  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, int encoding, const char *class,
*                             const char *method, int *status )

*  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.
*     encoding
*        The encoding being 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.
*     status
*        Pointer to the inherited status variable.

*  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;              /* Next available co-ordinate version character */
   char s0;             /* Co-ordinate version character */
   int altaxes;         /* Value of AltAxes  attribute */
   int ibase;           /* Base Frame index */
   int icurr;           /* Current Frame index */
   int ifrm;            /* Next Frame index */
   int isoff;           /* Is the Frame an offset SkyFrame? */
   int primok;          /* Primary 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->ps = 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->dut1 = NULL;
      ret->dtai = NULL;
      ret->mjdobs = NULL;
      ret->mjdavg = NULL;
      ret->pv = NULL;
      ret->specsys = NULL;
      ret->ssyssrc = NULL;
      ret->obsgeox = NULL;
      ret->obsgeoy = NULL;
      ret->obsgeoz = NULL;
      ret->restfrq = NULL;
      ret->restwav = NULL;
      ret->zsource = NULL;
      ret->velosys = NULL;
      ret->asip = NULL;
      ret->bsip = NULL;
      ret->apsip = NULL;
      ret->bpsip = NULL;
      ret->imagfreq = NULL;
      ret->axref = NULL;
      ret->naxis = naxis;
      ret->timesys = NULL;
      ret->tables = astKeyMap( " ", status );
      ret->skyref = NULL;
      ret->skyrefp = NULL;
      ret->skyrefis = NULL;

/* 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 );

/* Does the current Frame contain a SkyFrame that describes offset
   coordinates? */
      isoff = IsSkyOff( fset, icurr, status );

/* 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, ' ',
                           encoding, isoff, method, class, status );

/* Get the value of the FitsChan AltAxes attribute. This controls which
   Frames are used to generate alternate axis descriptions. */
      altaxes =astGetAltAxes( this );

/* Do not add any alternate axis descriptions if the primary axis
   descriptions could not be produced or the AltAxes attribute indicates
   that no alternate axis descriptions should be created. */
      if( primok && astOK && altaxes != ALTAXES_NONE ) {

/* 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 them 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 ];
                     }
                  }
               }
               (void) 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). Do not do this if the AltAxes attribute
   indicates that only Frames with suitable Ident values should be used to
   create alternate axis descriptions. */
         s = 'A' - 1;
         if( altaxes != ALTAXES_IDENT ){
            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;
                     }
                  }
               }
            }
         }

/* If the primary headers describe offset coordinates, create an alternate
   axis description for the correspondsing absolute coordinate system. */
         if( isoff && ++s <= 'Z' ) {
            (void) AddVersion( this, fset, ibase, icurr, ret, dim,
                               s, encoding, -1, method, class, status );
         }

/* 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++ ){
            s0 = sid[ ifrm ];
            if( s0 != 0 && s0 != 1 ) {

/* Does it contain an offset sky frame? */
               isoff = IsSkyOff( fset, ifrm, status );

/* Write out the Frame - offset if it is offset, absolute otherwise. */
               (void) AddVersion( this, fset, ibase, ifrm, ret, dim,
                                  s0, encoding, isoff, method, class, status );

/* If the Frame is offset, create an extra alternate axis description for
   the correspondsing absolute coordinate system. */
               if( isoff && ++s <= 'Z' ) {
                  (void) AddVersion( this, fset, ibase, ifrm, ret, dim,
                                     s, encoding, -1, method, class, status );
               }
            }
         }

/* 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, status );
   }

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

static int GetClean( AstFitsChan *this, int *status ) {

/*
*  Name:
*     GetClean

*  Purpose:
*     Return the value of the Clean attribute.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"

*     int GetClean( AstFitsChan *this, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function returns the value of the Clean attribute. Since this
*     attribute controls the behaviour of the FitsChan in the event of an
*     error condition, it is is necessary to ignore any inherited error
*     condition when getting the attribute value. This is why the
*     astMAKE_GET macro is not used.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     The Clean value to use.
*/

/* Return if no FitsChan pointer was supplied. */
   if ( !this ) return 0;

/* Return the attribute value, supplying a default value of 0 (false). */
   return ( this->clean == -1 ) ? 0 : (this->clean ? 1 : 0 );
}

static size_t GetObjSize( AstObject *this_object, int *status ) {
/*
*  Name:
*     GetObjSize

*  Purpose:
*     Return the in-memory size of an Object.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     size_t GetObjSize( AstObject *this, int *status )

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

*  Description:
*     This function returns the in-memory size of the supplied FitsChan,
*     in bytes.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     The Object size, in bytes.

*  Notes:
*     - A value of zero will be returned if this function is invoked
*     with the global status set, or if it should fail for any reason.
*/

/* Local Variables: */
   AstFitsChan *this;         /* Pointer to FitsChan structure */
   FitsCard *card;            /* Pointer to next FitsCard */
   size_t result;             /* Result value to return */

/* Initialise. */
   result = 0;

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

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

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* Invoke the GetObjSize method inherited from the parent class, and then
   add on any components of the class structure defined by thsi class
   which are stored in dynamically allocated memory. */
   result = (*parent_getobjsize)( this_object, status );
   result += astTSizeOf( this->warnings );
   result += astGetObjSize( this->keyseq );
   result += astGetObjSize( this->keywords );
   result += astGetObjSize( this->tables );
   card = (FitsCard *) ( this->head );
   while( card ) {
      result += astTSizeOf( card );
      result += card->size;
      result += astTSizeOf( card->comment );
      card = GetLink( card, NEXT, "astGetObjSize", "FitsChan", status );
      if( (void *) card == this->head ) break;
   }

/* If an error occurred, clear the result value. */
   if ( !astOK ) result = 0;

/* Return the result, */
   return result;
}

static int GetCDMatrix( AstFitsChan *this, int *status ){

/*
*  Name:
*     GetCDMatrix

*  Purpose:
*     Get the value of the CDMatrix attribute.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"

*     int GetCDMatrix( AstFitsChan *this, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, int *status ){

/*
*  Name:
*     GetEncoding

*  Purpose:
*     Get the value of the Encoding attribute.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int GetEncoding( AstFitsChan *this, int *status )

*  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) DELTAV and VELO-xxx (or VLSR) keywords = FITS-CLASS.
*     3) Any AIPS spectral CTYPE values:

*         Any of CDi_j, PROJP, LONPOLE, LATPOLE = FITS-AIPS++ encoding:
*         None of the above = FITS-AIPS encoding.
*     4) Any keywords matching PCiiijjj = FITS-PC encoding
*     5) Any keywords matching CDiiijjj = FITS-IRAF encoding
*     6) Any keywords matching CDi_j, AND at least one of RADECSYS, PROJPi
*        or CmVALi = FITS-IRAF encoding
*     7) Any keywords RADECSYS, PROJPi or CmVALi, and no CDi_j or PCi_j
*        keywords, = FITS-PC encoding
*     8) Any keywords matching CROTAi = FITS-AIPS encoding
*     9) Keywords matching CRVALi = FITS-WCS encoding
*     10) The PLTRAH keyword = DSS encoding
*     11) If none of the above keywords are found, Native encoding is assumed.
*
*     For cases 2) to 9), a check is also made that the header contains
*     at least one of the keywords CTYPE, CRPIX and CRVAL. If not, then
*     the checking process continues to the next case.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     status
*        Pointer to the inherited status variable.

*  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 hascd;          /* Any CDi_j keywords found? */
   int haspc;          /* Any PCi_j keywords found? */
   int haswcs;         /* Any CRVAL, CTYPE and CRPIX found? */
   int icard;          /* Index of current card on entry */
   int ret;            /* Returned value */

/* 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 {

/* See if the header contains some CTYPE, CRPIX or CRVAL keywords. Note,
   the FITS-WCS standard provides defaults for these keywords and so we
   cannot rely on them all being present. Check alternate axis descriptions
   as well as primary axis descriptions. */
      haswcs = astKeyFields( this, "CTYPE%d%0c", 0, NULL, NULL ) ||
               astKeyFields( this, "CRPIX%d%0c", 0, NULL, NULL ) ||
               astKeyFields( this, "CRVAL%d%0c", 0, NULL, NULL );

/* See if there are any CDi_j keywords. */
      hascd = astKeyFields( this, "CD%1d_%1d%0c", 0, NULL, NULL );

/* See if there are any PCi_j keywords. */
      haspc = astKeyFields( this, "PC%1d_%1d%0c", 0, NULL, NULL );

/* 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, look for a FITS-CLASS signature... */
      } else if( haswcs && LooksLikeClass( this, "astGetEncoding", "AstFitsChan", status ) ){
         ret = FITSCLASS_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( haswcs && HasAIPSSpecAxis( this, "astGetEncoding", "AstFitsChan", status ) ){
         if( hascd ||
             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 the "PLTRAH" keywords, use "DSS"
   encoding. */
      } else if( astKeyFields( this, "PLTRAH", 0, NULL, NULL ) ){
         ret = DSS_ENCODING;

/* Otherwise, if the FitsChan contains any keywords with the format
   "PCiiijjj" then return "FITS-PC" encoding. */
      } else if( haswcs && 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( haswcs && 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( haswcs && hascd ) {
         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,
   so long as there are no FITS-WCS equivalent keywords. */
      } else if( haswcs && !haspc && !hascd && (
                   ( 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( haswcs && 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( haswcs && astKeyFields( this, "CRVAL%d%0c", 0, NULL, NULL ) ){
         ret = FITSWCS_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, int *status ){
/*
*  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, int *status )

*  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).
*     status
*        Pointer to the inherited status variable.
*/

/* 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 );
      if( astGetPV( map, axlon, 0 ) != 0.0 ) {
         *phi = AST__DD2R*astGetPV( map, axlon, 1 );
         *theta = AST__DD2R*astGetPV( map, axlon, 2 );
      } else {
         *phi = astGetNatLon( map );
         *theta = astGetNatLat( map );
      }

/* 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, int *status ){
/*
*  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, int *status )

*  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).
*     status
*        Pointer to the inherited status variable.
*/

/* 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, "", status );
   ptr1 = astGetPoints( pset1 );
   pset2 = astPointSet( 1, naxes, "", status );
   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 ], status );

/* Use the inverse WcsMap to convert the native longitude and latitude of
   the fiducial point into PPC (x,y). */
      (void) 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, int *status ){
/*
*  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, int *status )

*  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.
*     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.
*     status
*        Pointer to the inherited status variable.

*  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 naxin;                /* Number of IWC axes */
   int naxout;               /* Number of WCS axes */
   int ret;                  /* The returned FrameSet */

/* Initialise */
   ret = 0;

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

/* Allocate resources. */
   naxin = astGetNin( map2 );
   naxout = astGetNout( map2 );
   pset1 = astPointSet( 1, naxin, "", status );
   ptr1 = astGetPoints( pset1 );
   pset2 = astPointSet( 1, naxout, "", status );
   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. */
      for( iax = 0; iax < naxin; iax++ ) ptr1[ iax ][ 0 ] = 0.0;

/* Get the native spherical coords at the fiducial point. */
      GetFiducialNSC( wcsmap, ptr1[ axlon ], ptr1[ axlat ], status );

/* 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. */
      (void) 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 const char *GetFitsSor( const char *string, int *status ) {
/*
*  Name:
*     GetFitsSor

*  Purpose:
*     Get the string used to represent an AST spectral standard of rest.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     const char *GetFitsSor( const char *string, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function returns a pointer to a static string which is the
*     FITS equivalent to a given SpecFrame StdOfRest value.

*  Parameters:
*     string
*        Pointer to a constant null-terminated string containing the
*        SpecFrame StdOfRest value.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     Pointer to a static null-terminated string containing the FITS
*     equivalent to the supplied string. NULL is returned if the supplied
*     string has no FITS equivalent.

*  Notes:
*     - A NULL pointer value will be returned if this function is
*     invoked wth the global error status set, or if it should fail
*     for any reason.
*/

/* Local Variables: */
   const char *result;           /* Pointer value to return */

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

/* Compare the supplied string with SpecFrame value for which there is a
   known FITS equivalent. */
   if( !strcmp( string, "Topocentric" ) ){
      result = "TOPOCENT";
   } else if( !strcmp( string, "Geocentric" )){
      result = "GEOCENTR";
   } else if( !strcmp( string, "Barycentric" )){
      result = "BARYCENT";
   } else if( !strcmp( string, "Heliocentric" )){
      result = "HELIOCEN";
   } else if( !strcmp( string, "LSRK" )){
      result = "LSRK";
   } else if( !strcmp( string, "LSRD" )){
      result = "LSRD";
   } else if( !strcmp( string, "Galactic" )){
      result = "GALACTOC";
   } else if( !strcmp( string, "Local_group" )){
      result = "LOCALGRP";
   } else if( !strcmp( string, "Source" )){
      result = "SOURCE";
   } else {
      result = NULL;
   }

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

static double GetItem( double ****item, int i, int jm, char s, char *name,
                       const char *method, const char *class, int *status ){
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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.", status, 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.", status, 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.", status, 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.", status,
                method, class, name );
   }
   return ret;
}

static int GetMaxJM( double ****item, char s, int *status ){
/*
*  Name:
*     GetMaxJM

*  Purpose:
*     Return the largest pixel axis or parameter index stored for an
*     numerical axis keyword value in a FitStore structure.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int GetMaxJM( double ****item, char s, int *status)

*  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
*     status
*        Pointer to the inherited status variable.

*  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.", status, 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 GetMaxJMC( char *****item, char s, int *status ){
/*
*  Name:
*     GetMaxJMC

*  Purpose:
*     Return the largest pixel axis or parameter index stored for an
*     character-valued axis keyword value in a FitStore structure.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int GetMaxJMC( char *****item, char s, int *status)

*  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->ctype) ).
*        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 string pointers. These arrays of keyword
*        string pointers 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
*     status
*        Pointer to the inherited status variable.

*  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, "GetMaxJMC(fitschan): AST internal error; "
                   "co-ordinate version '%c' ( char(%d) ) is invalid.", status, 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(char ***) > 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(char **);
                 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(char *) - 1;

/* Ignore any trailing unused (NULL) values. */
                  while( jm >= 0 && (*item)[si][i][jm] == NULL ) 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, int *status ){
/*
*  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.", status, 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, int *status ){
/*
*  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, int *status)

*  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).
*     status
*        Pointer to the inherited status variable.

*  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, int jm, char s, char *name,
                       const char *method, const char *class, int *status ){
/*
*  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, int jm, char s, char *name,
*                     const char *method, const char *class, int *status  )

*  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->ctype) ).
*        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 string pointers. These arrays of keyword
*        string pointers 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. CTYPE) 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.
*     status
*        Pointer to the inherited status variable.

*  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.", status, 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.", status, 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.", status, 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(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 ){

/* 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(char *) > 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 && name && astOK ){
      astError( AST__NOFTS, "%s(%s): No value can be found for %s.", status,
                method, class, name );
   }
   return ret;
}

static AstFitsTable *GetNamedTable( AstFitsChan *this, const char *extname,
                                    int extver, int extlevel, int report,
                                    const char *method, int *status ){

/*
*  Name:
*     GetNamedTable

*  Purpose:
*     Return a FitsTable holding the contents of a named FITS binary table.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"

*     AstFitsTable *GetNamedTable( AstFitsChan *this, const char *extname,
*                                  int extver, int extlevel, int report,
*                                  const char *method, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     If a table source function has been registered with FitsChan (using
*     astTableSource), invoke it to read the required table from the external
*     FITS file. If the extension is available in the FITS file, this will
*     put a FitsTable into the "tables" KeyMap in the FitsChan structure,
*     using the FITS extension name as the key - this will replace any
*     FitsTable already present in the KeyMap with the same key. Finally,
*     return a pointer to the FitsTable stored in the KeyMap - if any.
*
*     This strategy allows the astPutTables or astPutTable method to be used
*     as an alternative to registering a table source function with the
*     FitsChan.  Note, any table read using the source function is used
*     in preference to any table stored in the FitsChan by an earlier call
*     to astPutTables/astPutTable.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     extname
*        The key associated with the required table - should be the name
*        of the FITS extension containing the binary table.
*     extver
*        The FITS "EXTVER" value for the required table.
*     extlevel
*        The FITS "EXTLEVEL" value for the required table.
*     report
*        If non-zero, report an error if the named table is not available.
*     method
*        Pointer to a string holding the name of the calling method.
*        This is only for use in constructing error messages.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     Pointer to the FitsTable, or NULL if the table is not avalable.
*/

/* Local Variables: */
   AstFitsTable *ret;

/* Initialise */
   ret = NULL;

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

/* First attempt to read the required table from the external FITS file.
   Only proceed if table source function and wrapper have been supplied
   using astTableSource. */
   if( this->tabsource && this->tabsource_wrap ){

/* Invoke the table source function asking it to place the required FITS
   table in the FitsChan. This is an externally supplied function which may
   not be thread-safe, so lock a mutex first. Note, a cloned FitsChan pointer
   is sent to the table source function since the table source function will
   annul the supplied FitsChan pointer. Also store the channel data
   pointer in a global variable so that it can be accessed in the source
   function using macro astChannelData. */
      astStoreChannelData( this );
      LOCK_MUTEX2;
      ( *this->tabsource_wrap )( this->tabsource, astClone( this ), extname,
                                 extver, extlevel, status );
      UNLOCK_MUTEX2;
   }

/* Now get a pointer to the required FitsTable, stored as an entry in the
   "tables" KeyMap. Report an error if required. */
   if( ! (this->tables) || !astMapGet0A( this->tables, extname, &ret ) ){
      if( report && astOK ) {
         astError( AST__NOTAB, "%s(%s): Failed to read FITS binary table "
                   "from extension '%s' (extver=%d, extlevel=%d).", status,
                   method, astGetClass( this ), extname, extver, extlevel );
      }
   }

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

static AstKeyMap *GetTables( AstFitsChan *this, int *status ) {

/*
*++
*  Name:
c     astGetTables
f     AST_GETTABLES

*  Purpose:
*     Retrieve any FitsTables currently in a FitsChan.

*  Type:
*     Public virtual function.

*  Synopsis:
c     #include "fitschan.h"
c     AstKeyMap *astGetTables( AstFitsChan *this )
f     RESULT = AST_GETTABLES( THIS, STATUS )

*  Class Membership:
*     FitsChan method.

*  Description:
*     If the supplied FitsChan currently contains any tables, then this
*     function returns a pointer to a KeyMap. Each entry in the KeyMap
*     is a pointer to a FitsTable holding the data for a FITS binary
*     table. The key used to access each entry is the FITS extension
*     name in which the table should be stored.
*
*     Tables can be present in a FitsChan as a result either of using the
c     astPutTable (or astPutTables)
f     AST_PUTTABLE (or AST_PUTTABLES)
*     method to store existing tables in the FitsChan, or of using the
c     astWrite
f     AST_WRITE
*     method to write a FrameSet to the FitsChan. For the later case, if
*     the FitsChan "TabOK" attribute is positive and the FrameSet requires
*     a look-up table to describe one or more axes, then the "-TAB"
*     algorithm code described in FITS-WCS paper III is used and the table
*     values are stored in the FitsChan in the form of a FitsTable object
*     (see the documentation for the "TabOK" attribute).

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

*  Returned Value:
c     astGetTables()
f     AST_GETTABLES = INTEGER
*        A pointer to a deep copy of the KeyMap holding the tables currently
*        in the FitsChan, or
c        NULL
f        AST__NULL
*        if the FitsChan does not contain any tables. The returned
*        pointer should be annulled using
c        astAnnul
f        AST_ANNUL
*        when no longer needed.

*  Notes:
*     - A null Object pointer (AST__NULL) will be returned if this
c     function is invoked with the AST error status set, or if it
f     function is invoked with STATUS set to an error value, or if it
*     should fail for any reason.
*--
*/

/* Local Variables: */
   AstKeyMap *result;      /* Pointer value to return */

/* Initialise. */
   result = NULL;

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

/* If the FitsChan contains any tables, return a pointer to a copy of
   the KeyMap containing them. Otherwise, return a NULL pointer. */
   if( this->tables && astMapSize( this->tables ) > 0 ) {
      result = astCopy( this->tables );
   }

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

static int GetUsedPolyTan( AstFitsChan *this, AstFitsChan *out, int latax,
                           int lonax, char s, const char *method,
                           const char *class, int *status ){
/*
*  Name:
*     GetUsedPolyTan

*  Purpose:
*     Get the value to use for the PolyTan attribute.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int GetUsedPolyTan( AstFitsChan *this, AstFitsChan *out, int latax,
*                         int lonax, char s, const char *method,
*                         const char *class, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     If the PolyTan attribute is zero or positive, then its value is
*     returned. If it is negative, the supplied FitsChan is searched for
*     keywords of the form PVi_m. If any are found on the latitude axis,
*     or if any are found on the longitude axis with "m" > 4, +1 is
*     returned (meaning "use the distorted TAN conventio"). Otherwise 0
*     is returned (meaning "use the standard TAN convention").
*
*     If all the PVi_m values for m > 0 on either axis are zero, a warning is
*     issued and zero is returned.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     out
*        Pointer to a secondary FitsChan. If the PV values in "this" are
*        found to be unusable, they will be marked as used in both "this"
*        and "out".
*     latax
*        The one-based index of the latitude axis within the FITS header.
*     lonax
*        The one-based index of the longitude 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.
*     status
*        Pointer to the inherited status variable.

*  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... */
   char template[ 50 ];
   double pval;
   int lbnd_lat;
   int lbnd_lon;
   int m;
   int nfound1;
   int nfound2;
   int ok;
   int ret;
   int ubnd_lat;
   int ubnd_lon;

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

/* Get the value of the PolyTan attribute. */
   ret = astGetPolyTan( this );

/* If it is negative, we examine the FitsChan to see which convention to
   use. */
   if( ret < 0 ) {
      ret = 0;

/* Search the FitsChan for latitude PV cards. */
      if( s != ' ' ) {
         sprintf( template, "PV%d_%%d%c", latax, s );
      } else {
         sprintf( template, "PV%d_%%d", latax );
      }
      nfound1 = astKeyFields( this, template, 1, &ubnd_lat, &lbnd_lat );

/* Search the FitsChan for longitude PV cards. */
      if( s != ' ' ) {
         sprintf( template, "PV%d_%%d%c", lonax, s );
      } else {
         sprintf( template, "PV%d_%%d", lonax );
      }
      nfound2 = astKeyFields( this, template, 1, &ubnd_lon, &lbnd_lon );

/* If any were found with "m" value greater than 4, assume the distorted
   TAN convention is in use. Otherwise assume the stdanrd TAN convention is
   in use. */
      if( nfound1 || ( nfound2 && ubnd_lon > 4 ) ) ret = 1;

/* If the distorted TAN convention is to be used, check that at least one
   of the PVi_m values is non-zero on each axis. We ignore the PVi_0
   (constant) terms in this check. */
      if( ret > 0 ) {

/* Do the latitude axis first, skipping the first (constant) term. Assume
   that all latitude pV values are zero until we find one that is not. */
         ok = 0;
         for( m = 1; m <= ubnd_lat && !ok; m++ ) {

/* Form the PVi_m keyword name. */
            if( s != ' ' ) {
               sprintf( template, "PV%d_%d%c", latax, m, s );
            } else {
               sprintf( template, "PV%d_%d", latax, m );
            }

/* Get it's value. */
            if( ! GetValue( this, template, AST__FLOAT, &pval, 0, 0,
                            method,  class, status ) ) {

/* If the PVi_m header is not present in the FitsChan, use a default value. */
               pval = ( m == 1 ) ? 1.0 : 0.0;
            }

/* If the PVi_m header has a non-zero value, we can leave the loop. */
            if( pval != 0.0 ) ok = 1;
         }

/* If all the latitude PVi_m values are zero, issue a warning and return
   zero, indicating that a simple undistorted TAN projection should be used. */
         if( !ok ) {
            Warn( this, "badpv", "This FITS header describes a distorted TAN "
                  "projection, but all the distortion coefficients (the "
                  "PVi_m headers) on the latitude axis are zero.", method,
                  class, status );
            ret = 0;


/* Also, delete the PV keywords so that no attempt is made to use them. */
            for( m = 1; m <= ubnd_lat; m++ ) {
               if( s != ' ' ) {
                  sprintf( template, "PV%d_%d%c", latax, m, s );
               } else {
                  sprintf( template, "PV%d_%d", latax, m );
               }
               astClearCard( this );
               if( FindKeyCard( this, template, method, class, status ) ) {
                  DeleteCard( this, method, class, status );
               }
            }

/* Otherwise, do the same check for the longitude axis. */
         } else {
            ok = 0;
            for( m = 1; m <= ubnd_lon && !ok; m++ ) {

               if( s != ' ' ) {
                  sprintf( template, "PV%d_%d%c", lonax, m, s );
               } else {
                  sprintf( template, "PV%d_%d", lonax, m );
               }

               if( ! GetValue( this, template, AST__FLOAT, &pval, 0, 0,
                               method, class, status ) ) {

                  pval = ( m == 1 ) ? 1.0 : 0.0;
               }

               if( pval != 0.0 ) ok = 1;
            }

            if( !ok ) {
               Warn( this, "badpv", "This FITS header describes a distorted TAN "
                     "projection, but all the distortion coefficients (the "
                     "PVi_m headers) on the longitude axis are zero.", method,
                     class, status );
               ret = 0;

               for( m = 1; m <= ubnd_lon; m++ ) {
                  if( s != ' ' ) {
                     sprintf( template, "PV%d_%d%c", lonax, m, s );
                  } else {
                     sprintf( template, "PV%d_%d", lonax, m );
                  }
                  astClearCard( this );
                  if( FindKeyCard( this, template, method, class, status ) ) {
                     DeleteCard( this, method, class, status );
                  }
               }
            }
         }
      }
   }

/* Return  the result. */
   return astOK ? ret : 0;
}

static int GoodWarns( const char *value, int *status ){
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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." , status);
      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.", status, 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, int *status ) {
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, status );
      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( "", status );
      pv = GetItem( &(store->pv), i, 0, s, NULL, method, class, status );
      astSetGrismG( gmap, ( pv != AST__BAD )?pv:0.0 );
      pv = GetItem( &(store->pv), i, 1, s, NULL, method, class, status );
      astSetGrismM( gmap, ( pv != AST__BAD )?(int) ( pv + 0.5 ):0);
      pv = GetItem( &(store->pv), i, 2, s, NULL, method, class, status );
      astSetGrismAlpha( gmap, ( pv != AST__BAD )?pv*AST__DD2R:0.0 );
      pv = GetItem( &(store->pv), i, 3, s, NULL, method, class, status );
      astSetGrismNR( gmap, ( pv != AST__BAD )?pv:1.0 );
      pv = GetItem( &(store->pv), i, 4, s, NULL, method, class, status );
      astSetGrismNRP( gmap, ( pv != AST__BAD )?pv:0.0 );
      pv = GetItem( &(store->pv), i, 5, s, NULL, method, class, status );
      astSetGrismEps( gmap, ( pv != AST__BAD )?pv*AST__DD2R:0.0 );
      pv = GetItem( &(store->pv), i, 6, s, NULL, method, class, status );
      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, "", status );

/* 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 );
      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, "", status );
         map2b = (AstMapping *) astShiftMap( 1, &gcrv, "", status );
         map2 = (AstMapping *) astCmpMap( map2a, map2b, 1, "", status );
         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, "", status );
         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, int *status ){

/*
*+
*  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;

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* 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, status );

/* 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, status ), filter, nf, fields, &nfld,
                    method, class, status ) ){
            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, status );
      }

/* 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[ AST__FITSCHAN_FITSCARDLEN + 1 ], int inc, int *status ){

/*
*++
*  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;

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* 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, status );
   }

/* 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, status );

/* Increment the current card pointer if required. */
      if( inc ) MoveCard( this, 1, method, class, status );

/* 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, int *status ){
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, status ), name, 0, NULL, &nfld, method, class, status ) ){
         ret = 1;
         break;

/* Otherwise, move the current card on to the next card. */
      } else {
         MoveCard( this, 1, method, class, status );
      }
   }

/* Return. */
   return ret;
}

static double *FitLine( AstMapping *map, double *g, double *g0, double *w0,
                        double dim, double *tol, int *status ){
/*
*  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, double *tol, int *status )

*  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.
*     tol
*        Pointer to an array holding the tolerance for equality on each
*        output axis.
*     status
*        Pointer to the inherited status variable.

*  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;
   double wmax;
   double wmin;
   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 the whole axis. If not, we use
   a gap of 1.0 (corresponds to an axis length of 100 pixels).
   Choose the gap. */
   gap = ( dim != AST__BAD ) ? dim/NP : 1.0;

/* Create PointSets to hold the input and output positions. */
   pset1 = astPointSet( NP, nin, "", status );
   ptr1 = astGetPoints( pset1 );
   pset2 = astPointSet( NP, nout, "", status );
   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. */
      (void) 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;
         wmax = -DBL_MAX;
         wmin = DBL_MAX;
         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( *pax > wmax ) wmax = *pax;
               if( *pax < wmin ) wmin = *pax;
            }
         }

/* 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. If the axis
   value is constant, return zero. */
            if( wmax > wmin ) {
               ret[ j ] = (sdw*n - sw*sd)/denom;
            } else {
               ret[ j ] = 0.0;
            }

/* 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 ], tol[ j ], status ) ) {
               ret[ j ] /= gap;
            } else {
               ok = 0;
               break;
            }
         }
      }
   }

/* Annul the PointSets. */
   pset1 = astAnnul( pset1 );
   pset2 = astAnnul( pset2 );

/* Free memory. */
   offset = astFree( offset );
   voffset = astFree( voffset );

/* 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, int *status ){

/*
*+
*  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;

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* 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;
}

static int FitsSof( AstFitsChan *this, int *status ){

/*
*+
*  Name:
*     FitsSof

*  Purpose:
*     See if the FitsChan is at "start-of-file".

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"

*     int FitsSof( AstFitsChan *this, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     A value of 1 is returned if the current card is the first card in
*     the FitsChan. Otherwise a value of zero is returned.  This function
*     is much more efficient than "astGetCard(this) <= 1" .

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     Zero if the current card is the first card.

*  Notes:
*     - This function attempts to execute even if an error has already
*     occurred.
*     - A non-zero value is returned if the FitsChan is empty.
*-
*/

/* Return if no FitsChan was supplied, or if the FitsChan is empty. */
   if ( !this || !this->head ) return 1;

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* If the current card is at the head of the linked list, it is the first
   card. */
   return  this->card == this->head;
}

/*
*++
*  Name:
c     astGetFits<X>
f     AST_GETFITS<X>

*  Purpose:
*     Get a named keyword value from a FitsChan.

*  Type:
*     Public virtual function.

*  Synopsis:
c     #include "fitschan.h"

c     int astGetFits<X>( AstFitsChan *this, const char *name, <X>type *value )
f     RESULT = AST_GETFITS<X>( THIS, NAME, VALUE, STATUS )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This is a family of functions which gets a value for a named keyword,
*     or the value of the current card, 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 - A "CONTINUE" value, these are treated like string values, but
*            are encoded without an equals sign.
*
*     The data type of the "value"
c     parameter
f     argument

*     depends on <X> as follows:
*
c     - CF - "double *" (a pointer to a 2 element array to hold the real and
c            imaginary parts of the complex value).
c     - CI - "int *" (a pointer to a 2 element array to hold the real and
c            imaginary parts of the complex value).
c     - F  - "double *".
c     - I  - "int *".
c     - L  - "int *".
c     - S  - "char **" (a pointer to a static "char" array is returned at the
c            location given by the "value" parameter, Note, the stored string
c            may change on subsequent invocations of astGetFitsS so a
c            permanent copy should be taken of the string if necessary).
c     - CN - Like"S".
f     - CF - DOUBLE PRECISION(2) (a 2 element array to hold the real and
f            imaginary parts of the complex value).
f     - CI - INTEGER(2) (a 2 element array to hold the real and imaginary
f            parts of the complex value).
f     - F  - DOUBLE PRECISION.
f     - I  - INTEGER
f     - L  - LOGICAL
f     - S  - CHARACTER
f     - CN - CHARACTER

*  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
f        A character string
*        containing the FITS 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. If
c        NULL
f        a single dot '.'
*        is supplied, the value of the current card is returned.
c     value
f     VALUE = <X>type (Returned)
c        A pointer to a
f        A
*        buffer to receive the keyword value. The data type depends on <X>
*        as described above. The conents of the buffer on entry are left
*        unchanged if the keyword is not found.
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Returned Value:
c     astGetFits<X><X>()
f     AST_GETFITS<X> = LOGICAL
c        A value of zero
f        .FALSE.
*        is returned if the keyword was not found in the FitsChan (no error
*        is reported). Otherwise, a value of
c        one
f        .TRUE.
*        is returned.

*  Notes:
*     -  If a name is supplied, 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.
*     -  If the keyword is found in the FitsChan, but has no associated
*     value, an error is reported. If necessary, the
c     astTestFits
f     AST_TESTFITS
*     function can be used to determine if the keyword has a defined
*     value in the FitsChan prior to calling this function.
*     -  An error will be reported if the keyword name does not conform
*     to FITS requirements.
c     -  Zero
*     -  .FALSE.
*     is returned as the function value if an error has already occurred,
*     or if this function should fail for any reason.
*     - The FITS standard says that string keyword values should be
*     padded with trailing spaces if they are shorter than 8 characters.
*     For this reason, trailing spaces are removed from the string
*     returned by
c     astGetFitsS
f     AST_GETFITSS
*     if the original string (including any trailing spaces) contains 8
*     or fewer characters. Trailing spaces are not removed from longer
*     strings.
*--
*/

/* Define a macro which expands to the implementation of the astGetFits<X>
   routine for a given data type. */
#define MAKE_FGET(code,ctype,ftype) \
static int GetFits##code( AstFitsChan *this, const char *name, ctype value, int *status ){ \
\
/* 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 */ \
   char *string;          /* Pointer to returned string value */ \
   char *c;               /* Pointer to next character */ \
   int cl;                /* Length of string value */ \
   int ret;               /* The returned value */ \
\
/* Check the global error status. */ \
   if ( !astOK ) return 0; \
\
/* Ensure the source function has been called */ \
   ReadFromSource( this, status ); \
\
/* Store the calling method and object class. */ \
   method = "astGetFits"#code; \
   class = astGetClass( this ); \
\
/* Initialise the returned value. */ \
   ret = 0; \
\
/* Extract the keyword name from the supplied string. */ \
   if( name ) { \
      (void) Split( this, name, &lname, &lvalue, &lcom, method, class, status ); \
   } else { \
      lname = NULL; \
      lvalue = NULL; \
      lcom = NULL; \
   } \
\
/* 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. No \
   need to do the search if the value of the current card is required. */ \
   if( !lname || SearchCard( this, lname, method, class, status ) ){ \
\
/* Convert the stored data value to the requested type, and store it in \
   the supplied buffer. */ \
      if( !CnvValue( this, ftype, 0, value, method, status ) && astOK ) { \
         astError( AST__FTCNV, "%s(%s): Cannot convert FITS keyword " \
                   "'%s' to %s.", status, method, class, \
                   CardName( this, status ), type_names[ ftype ] ); \
      } \
\
/* If the returned value is a string containing 8 or fewer characters, \
   replace trailing spaces with null characters. */ \
      if( astOK ) { \
         if( ftype == AST__STRING ) { \
            string = *( (char **) value ); \
            if( string ) { \
               cl =strlen( string ); \
               if( cl <= 8 ) { \
                  c = string + cl - 1; \
                  while( *c == ' ' && c > string ) { \
                     *c = 0; \
                     c--; \
                  } \
               } \
            } \
         } \
\
/* Indicate that a value is available. */ \
         ret = 1; \
      } \
\
   } \
\
/* Context error message. */ \
   if( !astOK && lname && *lname ) { \
      astError( astStatus, "%s(%s): Cannot get value for FITS keyword " \
                   "'%s'.", status, method, class, lname ); \
   } \
\
/* 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 astGetFits<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, int *status ){

/*
*+
*  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: */
   astDECLARE_GLOBALS     /* Declare the thread specific global data */
   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 */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* 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) Split( this, name, &lname, &lvalue, &lcom, method, class, status );
   } 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, status ) ){

/* Copy the comment into a static buffer, and return a pointer to it. */
      if( CardComm( this, status ) ){
         (void) strncpy( fitsgetcom_sval, CardComm( this, status ), AST__FITSCHAN_FITSCARDLEN );
         fitsgetcom_sval[ AST__FITSCHAN_FITSCARDLEN ] = 0;
         if( comment ) *comment = fitsgetcom_sval;
      } else {
         if( comment ) *comment = NULL;
      }

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

/* 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 SetFits( AstFitsChan *this, const char *keyname, void *value,
                    int type, const char *comment, int overwrite, int *status ){

/*
*  Name:
*     SetFits

*  Purpose:
*     Store a keyword value of any type in a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"

*     int SetFits( AstFitsChan *this, const char *keyname, void *value,
*                  int type, const char *comment, int overwrite, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, 0, &edval, "SetFits",
                                    status ) &&
             CardComm( this, status ) ) {
            if( astEQUAL( edval, dval ) ) comment = NULL;
         }
         astSetFitsF( 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, 0, &ecval, "SetFits",
                                    status ) &&
             CardComm( this, status ) ) {
            if( Similar( ecval, cval, status ) ) comment = NULL;
         }

/* Ignore comments if they are identical to the keyword value. */
         if( comment && !strcmp( cval, comment ) ) comment = NULL;
         astSetFitsS( this, keyname, cval, comment, overwrite );
      } else {
         ret = 0;
      }
   } else if( type == AST__CONTINUE ){
      cval = *( (char **) value);
      if( cval ){
         astSetFitsCN( this, keyname, cval, comment, overwrite );
      } else {
         ret = 0;
      }
   } else if( type == AST__COMMENT ){
      astSetFitsCom( 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, 0, &eival, "SetFits",
                                 status ) &&
         CardComm( this, status ) ) {
         if( eival == ival ) comment = NULL;
      }
      astSetFitsI( 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, 0, ecdval, "SetFits",
                                    status ) &&
             CardComm( this, status ) ) {
            if( astEQUAL( ecdval[ 0 ], ( (double *) value )[ 0 ] ) &&
                astEQUAL( ecdval[ 1 ], ( (double *) value )[ 1 ] ) ) comment = NULL;
         }
         astSetFitsCF( 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, 0, ecival, "SetFits",
                                 status ) &&
          CardComm( this, status ) ) {
         if( ecival[ 0 ] == ( (int *) value )[ 0 ] &&
             ecival[ 1 ] == ( (int *) value )[ 1 ] ) comment = NULL;
      }
      astSetFitsCI( 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, 0, &eival, "SetFits",
                                 status ) &&
          CardComm( this, status ) ) {
         if( eival == ival ) comment = NULL;
      }
      astSetFitsL( this, keyname, ival, comment, overwrite );
   } else if( type == AST__UNDEF ){
      if( overwrite && CardType( this, status ) == AST__UNDEF && CardComm( this, status ) ) {
         comment = NULL;
      }
      astSetFitsU( this, keyname, comment, overwrite );
   }
   return ret;
}

/*
*++
*  Name:
c     astSetFits<X>
f     AST_SETFITS<X>

*  Purpose:
*     Store a keyword value in a FitsChan.

*  Type:
*     Public virtual function.

*  Synopsis:
c     #include "fitschan.h"

c     void astSetFits<X>( AstFitsChan *this, const char *name, <X>type value,
c                         const char *comment, int overwrite )
f     CALL AST_SETFITS<X>( THIS, NAME, VALUE, COMMENT, OVERWRITE, STATUS )

*  Class Membership:
*     FitsChan method.

*  Description:
c     This is a family of functions which store values for named keywords
f     This is a family of routines 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.
*
c     The keyword data type is selected by replacing <X> in the function name
f     The keyword data type is selected by replacing <X> in the routine 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:
*
c     - CF - "double *" (a pointer to a 2 element array holding the real and
c            imaginary parts of the complex value).
c     - CI - "int *" (a pointer to a 2 element array holding the real and
c            imaginary parts of the complex value).
c     - F  - "double".
c     - I  - "int".
c     - L  - "int".
c     - S  - "const char *".
c     - CN - "const char *".
*
f     - CF - DOUBLE PRECISION(2) (a 2 element array holding the real and
f            imaginary parts of the complex value).
f     - CI - INTEGER(2) (a 2 element array holding the real and imaginary
f            parts of the complex value).
f     - F  - DOUBLE PRECISION.
f     - I  - INTEGER
f     - L  - LOGICAL
f     - S  - CHARACTER
f     - CN - CHARACTER

*  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
f        A character string
*        containing the FITS 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.
c     value
f     VALUE = <X>type (Given)
*        The keyword value to store with the named keyword. The data type
*        of this parameter depends on <X> as described above.
c     comment
f     COMMENT = CHARACTER * ( * ) (Given)
c        A pointer to a null terminated string
f        A string
*        holding a comment to associated with the keyword.
c        If a NULL pointer or
f        If
*        a blank string is supplied, then any comment included in the string
*        supplied for the
c        "name" parameter is used instead. If "name"
f        NAME parameter is used instead. If NAME
*        contains no comment, then any existing comment in the card being
*        over-written is retained. Otherwise, no comment is stored with
*        the card.
c     overwrite
f     OVERWRITE = LOGICAL (Given)
c        If non-zero,
f        If .TRUE.,
*        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 (see the "Card" attribute). If
c        zero,
f        .FALSE.,
*        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.
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Notes:
*     - The
c     function astSetFitsU
f     routine AST_SETFITSU
*     can be used to indicate that no value is associated with a keyword.
*     - The
c     function astSetFitsCM
f     routine AST_SETFITSCM
*     can be used to store a pure comment card (i.e. a card with a blank
*     keyword).
*     -  To assign a new value for an existing keyword within a FitsChan,
c     first find the card describing the keyword using astFindFits, and
c     then use one of the astSetFits<X> family to over-write the old value.
f     first find the card describing the keyword using AST_FINDFITS, and
f     then use one of the AST_SETFITS<X> family to over-write the old value.
*     -  If, on exit, there are no cards following the card written by
c     this function, then the current card is left pointing at the
f     this routine, 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 astSetFits<X>
   routine for a given data type. */
#define MAKE_FSET(code,ctype,ftype,valexp) \
static void SetFits##code( AstFitsChan *this, const char *name, ctype value, const char *comment, int overwrite, int *status ) { \
\
/* 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; \
\
/* Ensure the source function has been called */ \
   ReadFromSource( this, status ); \
\
/* Store the object clas and calling method. */ \
   class = astGetClass( this ); \
   method = "astSetFits"#code; \
\
/* Extract the keyword name from the supplied string. */ \
   (void) Split( this, name, &lname, &lvalue, &lcom, method, class, status ); \
\
/* Initialise a pointer to the comment to be stored. If the supplied \
   comment is blank, use the comment given with "name". */ \
   com = ChrLen( comment, status ) ? 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, status ) ) { \
      com = NULL; \
      if( overwrite ) { \
         if( CardComm( this, status ) ){ \
            com = (const char *) astStore( NULL, (void *) CardComm( this, status ), \
                                           strlen( CardComm( this, status ) ) + 1 ); \
            free_com = 1; \
         } \
      } \
   } \
\
/* Insert the new card. */ \
   InsCard( this, overwrite, lname, ftype, valexp, com, method, class, status ); \
\
/* 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 astSetFits<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 SetFitsU( AstFitsChan *this, const char *name, const char *comment,
                      int overwrite, int *status ) {

/*
*++
*  Name:
c     astSetFitsU
f     AST_SETFITSU

*  Purpose:
*     Store an undefined keyword value in a FitsChan.

*  Type:
*     Public virtual function.

*  Synopsis:
c     #include "fitschan.h"

c     void astSetFitsU( AstFitsChan *this, const char *name,
c                       const char *comment, int overwrite )
f     CALL AST_SETFITSU( THIS, NAME, COMMENT, OVERWRITE, STATUS )

*  Description:
*     This
c     function
f     routine
*     stores an undefined value for a named keyword within
*     a FitsChan at the current card position. The new undefined value
*     can either over-write an existing keyword value, or can be inserted
*     as a new header card into the FitsChan.

*  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
f        A character string
*        containing the FITS 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.
c     comment
f     COMMENT = CHARACTER * ( * ) (Given)
c        A pointer to a null terminated string
f        A string
*        holding a comment to associated with the keyword.
c        If a NULL pointer or
f        If
*        a blank string is supplied, then any comment included in the string
*        supplied for the
c        "name" parameter is used instead. If "name"
f        NAME parameter is used instead. If NAME
*        contains no comment, then any existing comment in the card being
*        over-written is retained. Otherwise, no comment is stored with
*        the card.
c     overwrite
f     OVERWRITE = LOGICAL (Given)
c        If non-zero,
f        If .TRUE.,
*        the new card formed from the supplied keyword name and comment
*        string over-writes the current card, and the current card is
*        incremented to refer to the next card (see the "Card" attribute). If
c        zero,
f        .FALSE.,
*        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.
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Notes:
*     -  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.
*--
*/

/* 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;

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* Store the object clas and calling method. */
   class = astGetClass( this );
   method = "astSetFitsU";

/* Extract the keyword name from the supplied string. */
   (void) Split( this, name, &lname, &lvalue, &lcom, method, class, status );

/* Initialise a pointer to the comment to be stored. If the supplied
   comment is blank, use the comment given with "name". */
   com = ChrLen( comment, status ) ? 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, status ) ) {
      com = NULL;
      if( overwrite ) {
         if( CardComm( this, status ) ){
            com = (const char *) astStore( NULL, (void *) CardComm( this, status ),
                                           strlen( CardComm( this, status ) ) + 1 );
            free_com = 1;
         }
      }
   }

/* Insert the new card. */
   InsCard( this, overwrite, lname, AST__UNDEF, NULL, com, method, class,
            status );

/* 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 );
}

static void SetFitsCM( AstFitsChan *this, const char *comment,
                       int overwrite, int *status ) {

/*
*++
*  Name:
c     astSetFitsCM
f     AST_SETFITSCM

*  Purpose:
*     Store a comment card in a FitsChan.

*  Type:
*     Public virtual function.

*  Synopsis:
c     #include "fitschan.h"

c     void astSetFitsCM( AstFitsChan *this, const char *comment,
c                        int overwrite )
f     CALL AST_SETFITSCM( THIS, COMMENT, OVERWRITE, STATUS )

*  Description:
*     This
c     function
f     routine
*     stores a comment card ( i.e. a card with no keyword name or equals
*     sign) within a FitsChan at the current card position. The new card
*     can either over-write an existing card, or can be inserted as a new
*     card into the FitsChan.

*  Parameters:
c     this
f     THIS = INTEGER (Given)
*        Pointer to the FitsChan.
c     comment
f     COMMENT = CHARACTER * ( * ) (Given)
c        A pointer to a null terminated string
f        A string
*        holding the text of the comment card.
c        If a NULL pointer or
f        If
*        a blank string is supplied, then a totally blank card is produced.
c     overwrite
f     OVERWRITE = LOGICAL (Given)
c        If non-zero,
f        If .TRUE.,
*        the new card over-writes the current card, and the current card is
*        incremented to refer to the next card (see the "Card" attribute). If
c        zero,
f        .FALSE.,
*        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.
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Notes:
*     -  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".
*--
*/

/* Just call astSetFitsCom with a blank keyword name. */
   astSetFitsCom( this, "", comment, overwrite );
}

static void SetFitsCom( AstFitsChan *this, const char *name,
                        const char *comment, int overwrite, int *status ){

/*
*+
*  Name:
*     astSetFitsCom

*  Purpose:
*     Store a comment for a keyword in a FitsChan.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "fitschan.h"

*     void astSetFitsCom( 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;

/* Initialisation */
   size = 0;

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* Store the calling method and object class. */
   method = "astSetFitsCom";
   class = astGetClass( this );

/* Extract the keyword name, etc, from the supplied string. */
   (void) Split( this, name, &lname, &lvalue, &lcom, method, class, status );

/* If a blank comment has been supplied, use NULL instead. */
   com = ChrLen( comment, status )? 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, status );
   if( !overwrite || !cname || strcmp( lname, cname ) ){
      InsCard( this, overwrite, lname, AST__COMMENT, NULL, com, method, class, status );

/* 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, status );
      data = astStore( NULL, old_data, size );
      InsCard( this, 1, lname, CardType( this, status ), data, com, method, class, status );
      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, int *status ){

/*
*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, status );

/* 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, status );
            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, status );
         }

/* Move on to the next card if this card is not marked with the requested
   new flag. */
      } else {
         MoveCard( this, 1, method, class, status );
      }
   }

/* 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, int *status ){

/*
*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

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

/* Local Variables: */
   astDECLARE_GLOBALS      /* Declare the thread specific global data */
   FitsCard *card0;        /* Pointer to current FitsCard */
   int *flags;             /* Pointer to flags mask for the current card */
   int old_ignore_used;    /* Original value of variable ignore_used */
   int old_status;         /* Original inherited status value */
   int rep;                /* Original error reporting flag */

/* Return if no FitsChan was supplied, or if the FitsChan is empty. */
   if ( !this || !this->head ) return;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Temporarily clear any bad status value and supress error reporting in
   this function. */
   old_status = astStatus;
   astClearStatus;
   rep = astReporting( 0 );

/* Indicate that we should not skip over cards marked as having been
   read. */
   old_ignore_used = ignore_used;
   ignore_used = 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, status );

/* Reset both used flags if required. */
      if( reset ) {
         *flags = (*flags) & ~PROVISIONALLY_USED;
         *flags = (*flags) & ~USED;
         MoveCard( this, 1, method, class, status );

/* 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, status );

/* Otherwise, just move on to the next card. */
            } else {
               MoveCard( this, 1, method, class, status );
            }

/* If this card has not bee provisionally used, move on to the next card. */
         } else {
            MoveCard( this, 1, method, class, status );
         }
      }
   }

/* 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, status );
   if( flags && (*flags & USED ) ) {
      ignore_used = 1;
      MoveCard( this, 1, method, class, status );
   }

/* Re-instate the original flag indicating if cards marked as having been
   read should be skipped over. */
   ignore_used = old_ignore_used;

/* Re-instate the original status value and error reporting condition. */
   astReporting( rep );
   astSetStatus( old_status );
}

static void FormatCard( AstFitsChan *this, char *buf, const char *method, int *status ){

/*
*
*  Name:
*     FormatCard

*  Purpose:
*     Formats the current card.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"

*     void FormatCard( AstFitsChan *this, char *buf, const char *method, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, status );
   comlen = ChrLen( com, status );

/* Copy the keyword name to the start of the output buffer, and store
   its length. */
   len = (int) strlen( strcpy( buf, CardName( this, status ) ) );

/* Pad the name with spaces up to column 8. */
   while ( len < FITSNAMLEN ) buf[ len++ ] = ' ';

/* If the card contains a keyword value... */
   type = CardType( this, status );
   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, status );

/* If there is a comment, determine which column it should start in so that
   it ends in column 80. */
      if( com ){
         comstart = AST__FITSCHAN_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 + 2 ) comstart = len + 2;

/* 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 < AST__FITSCHAN_FITSCARDLEN ); i++ ) {
            buf[ len++ ] = "/ "[ i ];
         }
      }
   }

/* Append any comment, truncating it if the card length forces
   this. */
   if ( com ) {
      for ( i = 0; com[ i ] && ( len < AST__FITSCHAN_FITSCARDLEN ); i++ ) {
         buf[ len++ ] = com[ i ];
      }
   }

/* Pad with spaces up to the end of the card. */
   while ( len < AST__FITSCHAN_FITSCARDLEN ) buf[ len++ ] = ' ';

/* Terminate it. */
   buf[ AST__FITSCHAN_FITSCARDLEN ] = 0;
}

static int FullForm( const char *list, const char *test, int abbrev, int *status ){
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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 *context;          /* Context used by strtok_r */
   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;

/* Avoid compiler warnings. */
   context = NULL;

/* 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, status );

/* Compare the supplied test option against each of the known options in
   turn. Count the number of matches. */
      nmatch = 0;
#if HAVE_STRTOK_R
      option = strtok_r( llist, " ", &context );
#else
      option = strtok( llist, " " );
#endif
      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, status ) && ( abbrev ||
             len == ChrLen( option, status ) ) ) {
            nmatch++;
            ret = i;
            if( !abbrev ) break;
         }

/* Get a pointer to the next option. */
#if HAVE_STRTOK_R
         option = strtok_r( NULL, " ", &context );
#else
         option = strtok( NULL, " " );
#endif
         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, int *status ){

/*
*+
*  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, int *status ) {

/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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 Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   AstFitsChan *this;            /* Pointer to the FitsChan structure */
   const char *result;           /* Pointer value to return */
   double dval;                  /* Double attribute value */
   int ival;                     /* Integer attribute value */

/* Initialise. */
   result = NULL;

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this_object);

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

/* Card. */
/* ----- */
   if ( !strcmp( attrib, "card" ) ) {
      ival = astGetCard( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", ival );
         result = getattrib_buff;
      }

/* CardComm. */
/* --------- */
   } else if ( !strcmp( attrib, "cardcomm" ) ) {
      result = astGetCardComm( this );

/* CardName. */
/* --------- */
   } else if ( !strcmp( attrib, "cardname" ) ) {
      result = astGetCardName( this );

/* CardType. */
/* --------- */
   } else if ( !strcmp( attrib, "cardtype" ) ) {
      ival = astGetCardType( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", ival );
         result = getattrib_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 == FITSCLASS_ENCODING ){
            result = FITSCLASS_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( getattrib_buff, "%d", ival );
         result = getattrib_buff;
      }

/* DefB1950 */
/* -------- */
   } else if ( !strcmp( attrib, "defb1950" ) ) {
      ival = astGetDefB1950( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", ival );
         result = getattrib_buff;
      }

/* TabOK */
/* ----- */
   } else if ( !strcmp( attrib, "tabok" ) ) {
      ival = astGetTabOK( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", ival );
         result = getattrib_buff;
      }

/* ForceTab */
/* -------- */
   } else if ( !strcmp( attrib, "forcetab" ) ) {
      ival = astGetForceTab( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", ival );
         result = getattrib_buff;
      }

/* CarLin */
/* ------ */
   } else if ( !strcmp( attrib, "carlin" ) ) {
      ival = astGetCarLin( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", ival );
         result = getattrib_buff;
      }

/* SipReplace */
/* ---------- */
   } else if ( !strcmp( attrib, "sipreplace" ) ) {
      ival = astGetSipReplace( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", ival );
         result = getattrib_buff;
      }

/* FitsTol. */
/* -------- */
   } else if ( !strcmp( attrib, "fitstol" ) ) {
      dval = astGetFitsTol( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%.*g", AST__DBL_DIG, dval );
         result = getattrib_buff;
      }

/* PolyTan */
/* ------- */
   } else if ( !strcmp( attrib, "polytan" ) ) {
      ival = astGetPolyTan( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", ival );
         result = getattrib_buff;
      }

/* SipOK */
/* ------- */
   } else if ( !strcmp( attrib, "sipok" ) ) {
      ival = astGetSipOK( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", ival );
         result = getattrib_buff;
      }

/* Iwc */
/* --- */
   } else if ( !strcmp( attrib, "iwc" ) ) {
      ival = astGetIwc( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", ival );
         result = getattrib_buff;
      }

/* Clean */
/* ----- */
   } else if ( !strcmp( attrib, "clean" ) ) {
      ival = astGetClean( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", ival );
         result = getattrib_buff;
      }

/* FitsAxisOrder. */
/* -------------- */
   } else if ( !strcmp( attrib, "fitsaxisorder" ) ) {
      result = astGetFitsAxisOrder( this );

/* FitsDigits. */
/* ----------- */
   } else if ( !strcmp( attrib, "fitsdigits" ) ) {
      ival = astGetFitsDigits( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", ival );
         result = getattrib_buff;
      }

/* FitsRounding. */
/* ------------- */
   } else if ( !strcmp( attrib, "fitsrounding" ) ) {
      ival = astGetFitsRounding( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", ival );
         result = getattrib_buff;
      }

/* AltAxes. */
/* -------- */
   } else if ( !strcmp( attrib, "altaxes" ) ) {
      ival = astGetAltAxes( this );
      if ( astOK ) {
         if( ival == ALTAXES_NONE ){
            result = ALTAXES_NONE_STRING;
         } else if( ival == ALTAXES_IDENT ){
            result = ALTAXES_IDENT_STRING;
         } else if( ival == ALTAXES_ALL ){
            result = ALTAXES_ALL_STRING;
         } else if( astOK ) {
            astError( AST__INTER, "astGet(%s): Illegal AltAxes value %d "
                      "encountered (internal AST programming error).", status,
                      astGetClass( this ), ival );
         }
      }

/* Ncard. */
/* ------ */
   } else if ( !strcmp( attrib, "ncard" ) ) {
      ival = astGetNcard( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", ival );
         result = getattrib_buff;
      }

/* Nkey. */
/* ----- */
   } else if ( !strcmp( attrib, "nkey" ) ) {
      ival = astGetNkey( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", ival );
         result = getattrib_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, status );
   }

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

static int GetCard( AstFitsChan *this, int *status ){

/*
*+
*  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 */

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* 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, status );
   }

/* Return the card index. */
   return index;
}

static const char *GetCardComm( AstFitsChan *this, int *status ){
/*
*+
*  Name:
*     GetCardComm

*  Purpose:
*     Get the value of the CardComm attribute.

*  Type:
*     Protected virtual function.

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

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function returns the value of the CardComm attribute for the
*     supplied FitsChan. This is the comment for the current card.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     A pointer to a static string holding the comment. A zero-length
*     string is returned if the card has no comment.

*  Notes:
*     - A value of NULL will be returned if an error has already
*     occurred, or if this function should fail for any reason.
*-
*/

/* Local Variables */
   const char *result = NULL;

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

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* Get the comment for the current card. */
   result = CardComm( this, status );

/* Return a zero-length string if the card has no comment. */
   if( astOK && !result ) result = "";

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

static const char *GetCardName( AstFitsChan *this, int *status ){
/*
*+
*  Name:
*     GetCardName

*  Purpose:
*     Get the value of the CardName attribute.

*  Type:
*     Protected virtual function.

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

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function returns the value of the CardName attribute for the
*     supplied FitsChan. This is the keyword name for the current card.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     A pointer to a static string holding the keyword name.

*  Notes:
*     - A value of NULL will be returned if an error has already
*     occurred, or if this function should fail for any reason.
*-
*/

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* Return the keyword name of the current card. */
   return CardName( this, status );
}

static int GetCardType( AstFitsChan *this, int *status ){
/*
*+
*  Name:
*     GetCardType

*  Purpose:
*     Get the value of the CardType attribute.

*  Type:
*     Protected virtual function.

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

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function returns the value of teh CardType attribute for the supplied
*     FitsChan. This is the data type of the keyword value for the current card.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     An integer representing the data type of the current card.

*  Notes:
*     - A value of AST__NOTYPE will be returned if an error has already
*     occurred, or if this function should fail for any reason.
*-
*/

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* Return the data type of the current card. */
   return CardType( this, status );
}

static int GetFull( AstChannel *this_channel, int *status ) {
/*
*  Name:
*     GetFull

*  Purpose:
*     Obtain the value of the Full attribute for a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int GetFull( AstChannel *this, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, status );

/* 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, int *status ){
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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.", status, 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, int *status ){

/*
*+
*  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 */

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* 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, status );
   }

/* Reset the current card to be the original current card. */
   this->card = card0;

/* Return the result. */
   return astOK ? ncard : 0;
}

static int GetNkey( AstFitsChan *this, int *status ){

/*
*+
*  Name:
*     astGetNkey

*  Purpose:
*     Get the value of the Nkey attribute.

*  Type:
*     Protected virtual function.

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

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function returns the value of the Nkey attribute for the supplied
*     FitsChan. This is the number of unique keywords currently in the
*     FitsChan.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     The number of  unique keywords 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: */
   AstKeyMap *km;          /* KeyMap holding unique keyword names */
   FitsCard *card0;        /* Pointer to current card on entry */
   const char *class;      /* Pointer to class string */
   const char *method;     /* Pointer to method string */
   int nkey;               /* Returned Nkey value */

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* 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 = "astGetNkey";
   class = astGetClass( this );

/* Create an empty KeyMap to hold the unused keyword names */
   km = astKeyMap( " ", status );

/* Save a pointer to the current card, and then reset the current card to
   be the first card. */
   card0 = this->card;
   astClearCard( this );

/* Loop through the cards in the FitsChan until the end of file is reached. */
   while( astOK && this->card ){

/* Get the keyword name for the current card and add it to the keymap. */
      astMapPut0I( km, CardName( this, status ), 0, NULL );

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

/* Reset the current card to be the original current card. */
   this->card = card0;

/* Get the number of keywords. */
   nkey = astMapSize( km );

/* Annull the KeyMap . */
   km = astAnnul( km );

/* Return the result. */
   return astOK ? nkey : 0;
}

static void GetNextData( AstChannel *this_channel, int skip, char **name,
                         char **val, int *status ) {
/*
*  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 fitsrnd;                  /* Value of FitsRounding attribute */
   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 );

/* Get the value of the FitsRounding attribute */
   fitsrnd = astGetFitsRounding( 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, status );
      type = CardType( this, status );
      data = CardData( this, NULL, status );

/* Mark all cards as having been used unless we are skipping over cards which
   may not be related to AST. */
      if( !skip ) MarkCard( this, status );

/* 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, status ) ] = 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, status );

/* Indicate that the current card has been used. */
            MarkCard( this, status );

/* The "begin" item will be preceded by a header of COMMENT cards. Mark
   them as having been used. */
            ComBlock( this, -1, method, class, status );

/* "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, status );

/* "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, status );

/* The "end" item eill be followed by a footer of COMMENT cards. Mark
   these cards as having been used. */
            ComBlock( this, 1, method, class, status );

/* 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, status ) - 1 ] == '&' );

/* If the string does not end with an ampersand, just UnPreQUote it and
   return a copy. */
                  if( !cont ) {
                     *val = UnPreQuote( (const char *) data, status );

/* 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, status ) &&
                            astOK ){

/* See if this is a CONTINUE card. If so, get its data pointer. */
                        if( CardType( this, status ) == AST__CONTINUE ){
                           data = CardData( this, NULL, status );

/* 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, status ) - 1 ] == '&' );

/* UnPreQUote it. */
                           upq = UnPreQuote( (const char *) data, status );
                           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, status );

/* 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\".", status,
                                     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", AST__DBL_DIG, *( (double *) data ) );
               CheckZero( buff,  *( (double *) data ), 0, fitsrnd, status );
               *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\".", status, 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, status );
   }

/* 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, int *status ) {
/*
*  Name:
*     GetSkip

*  Purpose:
*     Obtain the value of the Skip attribute for a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int GetSkip( AstChannel *this, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, status );

/* Otherwise, supply a default value of 1. */
   } else {
      result = 1;
   }

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

static int GetValue( AstFitsChan *this, const char *keyname, int type,
                     void *value, int report, int mark, const char *method,
                     const char *class, int *status ){
/*
*  Name:
*     GetValue

*  Purpose:
*     Obtain a FITS keyword value.

*  Type:
*     Private function.

*  Synopsis:
*     int GetValue( AstFitsChan *this, const char *keyname, int type, void *value,
*                   int report, int mark, const char *method, const char *class, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     Zero if the keyword does not exist in "this", or cannot be
*     converted to the requested type. One is returned otherwise.

*  Notes:
*     - An error is reported if the keyword value is undefined.
*     - A value of zero is returned if an error has already occurred,
*     or if an error occurs within this function.
*/

/* Local Variables: */
   int icard;                         /* Current card index */
   int ret;                           /* Returned value */

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

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

/* Attempt to find the supplied keyword. */
   ret = SearchCard( this, keyname, method, class, status );

/* 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, 0, value, method, status ) ) {

/* If required, mark it as having been read into an AST object. */
         if( mark ) MarkCard( this, status );

/* If the value is undefined, report an error if "report" is non-zero. */
         if( type == AST__UNDEF && report && astOK ) {
            ret = 0;
            astError( AST__FUNDEF, "%s(%s): FITS keyword \"%s\" has no value.",
                      status, method, class, keyname );
         }

/* 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' to %s.",
                      status, method, class, keyname, 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\".", status, method, class, keyname );
   }

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

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

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

static int GetValue2( AstFitsChan *this1, AstFitsChan *this2, const char *keyname,
                      int type, void *value, int report, const char *method,
                      const char *class, int *status ){
/*
*  Name:
*     GetValue2

*  Purpose:
*     Obtain a FITS keyword value from one of two FitsChans.

*  Type:
*     Private function.

*  Synopsis:
*     int GetValue2( AstFitsChan *this1, AstFitsChan *this2, const char *keyname,
*                    int type, void *value, int report, const char *method,
*                    const char *class, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, status );
   if( ! ret ) {
      ret = GetValue( this2, keyname, type, value, report, 1, method, class, status );
   }

/* 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, int *status ){

/*
*  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, int *status  )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, ' ', status ),
                       AST__STRING, (void *) &cval, 0, 0, method,
                       class, status ) ){
            if( IsAIPSSpectral( cval, &astype, &assys, status ) ) {
               ret = 1;
               break;
            }
         }
      }
   }

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

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

static int HasCard( AstFitsChan *this, const char *name,
                    const char *method, const char *class, int *status ){

/*
*  Name:
*     HasCard

*  Purpose:
*     Check if the FitsChan contains a specified keyword.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"

*     int HasCard( AstFitsChan *this, const char *name,
*                  const char *method, const char *class, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Returns a non-zero value if the FitsChan contains the given keyword,
*     and zero otherwise. The current card is unchanged.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     name
*        Pointer to a string holding the keyword name.
*     method
*        Pointer to string holding name of calling method.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A value of 1 is returned if a card was found refering to the given
*     keyword. Otherwise zero is returned.
*/

/* Check the supplied pointers (we can rely on astMapHasKey to check the
   inherited status). */
   if( !name || !this || !this->keywords ) return 0;

/* Search the KeyMap holding the keywords currently in the FitsChan,
   returning non-zero if the keyword was found. A KeyMap is used because
   it uses a hashing algorithm to find the entries and is therefore a lot
   quicker than searching through the list of linked FitsCards. */
   return astMapHasKey( this->keywords, name );
}
void astInitFitsChanVtab_(  AstFitsChanVtab *vtab, const char *name, int *status ) {

/*
*+
*  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: */
   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
   AstChannelVtab *channel;      /* Pointer to Channel component of Vtab */
   char buf[ 100 ];              /* Buffer large enough to store formatted INT_MAX */

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

/* Get a pointer to the thread specific global data structure. */
   astGET_GLOBALS(NULL);

/* 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_check variable to generate this unique value. */
   vtab->id.check = &class_check;
   vtab->id.parent = &(((AstChannelVtab *) vtab)->id);

/* 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->GetTables = GetTables;
   vtab->PutTables = PutTables;
   vtab->PutTable = PutTable;
   vtab->TableSource = TableSource;
   vtab->SetTableSource = SetTableSource;
   vtab->RemoveTables = RemoveTables;
   vtab->PurgeWCS = PurgeWCS;
   vtab->RetainFits = RetainFits;
   vtab->FindFits = FindFits;
   vtab->KeyFields = KeyFields;
   vtab->ReadFits = ReadFits;
   vtab->ShowFits = ShowFits;
   vtab->WriteFits = WriteFits;
   vtab->EmptyFits = EmptyFits;
   vtab->FitsEof = FitsEof;
   vtab->GetFitsCF = GetFitsCF;
   vtab->GetFitsCI = GetFitsCI;
   vtab->GetFitsF = GetFitsF;
   vtab->GetFitsI = GetFitsI;
   vtab->GetFitsL = GetFitsL;
   vtab->TestFits = TestFits;
   vtab->GetFitsS = GetFitsS;
   vtab->GetFitsCN = GetFitsCN;
   vtab->FitsGetCom = FitsGetCom;
   vtab->SetFitsCom = SetFitsCom;
   vtab->SetFitsCF = SetFitsCF;
   vtab->SetFitsCI = SetFitsCI;
   vtab->SetFitsF = SetFitsF;
   vtab->SetFitsI = SetFitsI;
   vtab->SetFitsL = SetFitsL;
   vtab->SetFitsU = SetFitsU;
   vtab->SetFitsS = SetFitsS;
   vtab->SetFitsCN = SetFitsCN;
   vtab->SetFitsCM = SetFitsCM;
   vtab->ClearCard = ClearCard;
   vtab->TestCard = TestCard;
   vtab->SetCard = SetCard;
   vtab->GetCard = GetCard;
   vtab->ClearAltAxes = ClearAltAxes;
   vtab->TestAltAxes = TestAltAxes;
   vtab->SetAltAxes = SetAltAxes;
   vtab->GetAltAxes = GetAltAxes;
   vtab->ClearFitsDigits = ClearFitsDigits;
   vtab->TestFitsDigits = TestFitsDigits;
   vtab->SetFitsDigits = SetFitsDigits;
   vtab->GetFitsDigits = GetFitsDigits;
   vtab->ClearFitsRounding = ClearFitsRounding;
   vtab->TestFitsRounding = TestFitsRounding;
   vtab->SetFitsRounding = SetFitsRounding;
   vtab->GetFitsRounding = GetFitsRounding;
   vtab->ClearFitsAxisOrder = ClearFitsAxisOrder;
   vtab->TestFitsAxisOrder = TestFitsAxisOrder;
   vtab->SetFitsAxisOrder = SetFitsAxisOrder;
   vtab->GetFitsAxisOrder = GetFitsAxisOrder;
   vtab->ClearDefB1950 = ClearDefB1950;
   vtab->TestDefB1950 = TestDefB1950;
   vtab->SetDefB1950 = SetDefB1950;
   vtab->GetDefB1950 = GetDefB1950;
   vtab->ClearTabOK = ClearTabOK;
   vtab->TestTabOK = TestTabOK;
   vtab->SetTabOK = SetTabOK;
   vtab->GetTabOK = GetTabOK;
   vtab->ClearForceTab = ClearForceTab;
   vtab->TestForceTab = TestForceTab;
   vtab->SetForceTab = SetForceTab;
   vtab->GetForceTab = GetForceTab;
   vtab->ClearCarLin = ClearCarLin;
   vtab->TestCarLin = TestCarLin;
   vtab->SetCarLin = SetCarLin;
   vtab->GetCarLin = GetCarLin;
   vtab->ClearSipReplace = ClearSipReplace;
   vtab->TestSipReplace = TestSipReplace;
   vtab->SetSipReplace = SetSipReplace;
   vtab->GetSipReplace = GetSipReplace;
   vtab->ClearFitsTol = ClearFitsTol;
   vtab->TestFitsTol = TestFitsTol;
   vtab->SetFitsTol = SetFitsTol;
   vtab->GetFitsTol = GetFitsTol;
   vtab->ClearPolyTan = ClearPolyTan;
   vtab->TestPolyTan = TestPolyTan;
   vtab->SetPolyTan = SetPolyTan;
   vtab->GetPolyTan = GetPolyTan;
   vtab->ClearSipOK = ClearSipOK;
   vtab->TestSipOK = TestSipOK;
   vtab->SetSipOK = SetSipOK;
   vtab->GetSipOK = GetSipOK;
   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->GetCardType = GetCardType;
   vtab->GetCardName = GetCardName;
   vtab->GetCardComm = GetCardComm;
   vtab->GetNcard = GetNcard;
   vtab->GetNkey = GetNkey;
   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_getobjsize = object->GetObjSize;
   object->GetObjSize = GetObjSize;
#if defined(THREAD_SAFE)
   parent_managelock = object->ManageLock;
   object->ManageLock = ManageLock;
#endif
   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;
   parent_setsourcefile = channel->SetSourceFile;
   channel->SetSourceFile = SetSourceFile;

/* 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 );

/* Max number of characters needed to format an int. */
   LOCK_MUTEX4
   sprintf( buf, "%d", INT_MAX );
   int_dig = strlen( buf );

/* Create a pair of MJD TimeFrames which will be used for converting to and
   from TDB. */
   astBeginPM;
   if( !tdbframe ) tdbframe = astTimeFrame( "system=MJD,timescale=TDB", status );
   if( !timeframe ) timeframe = astTimeFrame( "system=MJD", status );
   astEndPM;
   UNLOCK_MUTEX4

/* If we have just initialised the vtab for the current class, indicate
   that the vtab is now initialised, and store a pointer to the class
   identifier in the base "object" level of the vtab. */
   if( vtab == &class_vtab ) {
      class_init = 1;
      astSetVtabClassIdentifier( vtab, &(vtab->id) );
   }
}

static void InsCard( AstFitsChan *this, int overwrite, const char *name,
                     int type, void *data, const char *comment,
                     const char *method, const char *class, int *status ){

/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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: */
   astDECLARE_GLOBALS     /* Declare the thread specific global data */
   int flags;             /* Flags to assign to new card */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* 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, status );

/* If requested, set both NEW flags for the new card. */
   flags = ( mark_new ) ? ( NEW1 | NEW2 ): 0;

/* Insert the new card into the list, just before the current card. */
   NewCard( this, name, type, data, comment, flags, status );
}

static int IRAFFromStore( AstFitsChan *this, FitsStore *store,
                          const char *method, const char *class, int *status ){

/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, status );

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

/* Get the CTYPE values for both axes. */
      cval = GetItemC( &(store->ctype), axlon, 0, s, NULL, method, class, status );
      if( !cval ) return ret;
      strcpy( lontype, cval );
      cval = GetItemC( &(store->ctype), axlat, 0, s, NULL, method, class, status );
      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 ||  prj == AST__WCSBAD ) {
         ok = 0;

/* SIN projections are handled later. */
      } else if( prj != AST__SIN ){

/* There must be no projection parameters. */
         if( GetMaxJM( &(store->pv), ' ', status ) == -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, status );
         p2 = GetItem( &( store->pv ), axlat, 2, s, NULL, method, class, status );
         if( p1 == AST__BAD ) p1 = 0.0;
         if( p2 == AST__BAD ) p2 = 0.0;
         val = GetItem( &( store->crval ), axlat, 0, s, NULL, method, class, status );
         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, status )
          != AST__BAD ||
          GetItem( &(store->lonpole), 0, 0, s, NULL, method, class, status )
          != 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), ' ', status ) + 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, status );
      if( val == AST__BAD ) return ret;
      sprintf( combuf, "Reference pixel on axis %d", i + 1 );
      SetValue( this, FormatKey( "CRPIX", i + 1, -1, s, status ), &val, AST__FLOAT,
                combuf, status );
   }

/* 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, status );
      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, status ), &val, AST__FLOAT,
                combuf, status );
   }

/* 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, 0, s, NULL, method, class, status );
         if( !cval ) return ret;
      }
      if( strlen(cval) > 4 && !strcmp( cval + 4, "-TAB" ) ) return ret;
      comm = GetItemC( &(store->ctype_com), i, 0, s, NULL, method, class, status );
      if( !comm ) {
         sprintf( combuf, "Type of co-ordinate on axis %d", i + 1 );
         comm = combuf;
      }
      SetValue( this, FormatKey( "CTYPE", i + 1, -1, s, status ), &cval, AST__STRING,
                comm, status );
   }

/* 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, status );
      if( cdelt == AST__BAD ) cdelt = 1.0;
      for( j = 0; j < naxis; j++ ){
         val = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
         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, status ), &val,
                       AST__FLOAT, "Transformation matrix element", status );
         }
      }
   }

/* 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, 0, s, NULL, method, class, status );
      if( cval ) {
         sprintf( combuf, "Units for axis %d", i + 1 );
         SetValue( this, FormatKey( "CUNIT", i + 1, -1, s, status ), &cval, AST__STRING,
                   combuf, status );
      }
   }

/* Get and save RADECSYS. This is NOT required, so do not return if it is
   not available. */
   cval = GetItemC( &(store->radesys), 0, 0, s, NULL, method, class, status );
   if( cval ) SetValue( this, "RADECSYS", &cval, AST__STRING,
                        "Reference frame for RA/DEC values", status );

/* Reference equinox */
   val = GetItem( &(store->equinox), 0, 0, s, NULL, method, class, status );
   if( val != AST__BAD ) SetValue( this, "EQUINOX", &val, AST__FLOAT,
                                   "Epoch of reference equinox", status );

/* Date of observation */
   val = GetItem( &(store->mjdobs), 0, 0, ' ', NULL, method, class, status );
   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. */
      palCaldj( 99, 1, 1, &mjd99, &jj );
      if( val < mjd99 ) {
         palDjcal( 0, val, iymdf, &jj );
         sprintf( combuf, "%2.2d/%2.2d/%2.2d", iymdf[ 2 ], iymdf[ 1 ],
                  iymdf[ 0 ] - ( ( iymdf[ 0 ] > 1999 ) ? 2000 : 1900 ) );
      } else {
         palDjcl( val, iymdf, iymdf+1, iymdf+2, &fd, &jj );
         palDd2tf( 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", status );
   }

/* 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 *smap, const double lbnd_in[],
                        const double ubnd_in[], int coord_out,
                        int *coord_in, int *status ) {
/*
*  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 *smap, const double lbnd_in[],
*                      const double ubnd_in[], int coord_out,
*                      int *coord_in, int *status )

*  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:
*     smap
*        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.
*     coord_in
*        Returned holding the zero-based index of the Mapping input that
*        feeds output "coord_out". Returned as -1 if the output does not
*        correspond to a single input. May be NULL.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     Non-zero if the specified Mapping output is linear. Zero otherwise.
*/

/* Local Constants: */
#define NP 20

/* Local Variables: */
   AstMapping *map;
   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 *ins;
   int boxok;
   int i;
   int j;
   int nin;
   int nout;
   int oldrep;
   int ret;

/* Initialise */
   ret = 0;
   if( coord_in ) *coord_in = -1;

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

/* Attempt to split off the required output (in case any of the other
   outputs are associated with Mappings that do not have an inverse). */
   astInvert( smap );
   ins = astMapSplit( smap, 1, &coord_out, &map );
   astInvert( smap );

/* If successful, check that the supplied output is fed by only one input.
   At the moment "map" goes form wcs to pixel, so we check its Nout
   attribute. If required, return the index of the input that feeds the
   requested output. */
   if( ins ) {
      if( astGetNout( map ) == 1 ) {
         if( coord_in ) *coord_in = ins[ 0 ];

/* If so, invert the map so that it goes from pixel to wcs. Here on we
   use "map" in place of the supplied Mapping "smap", so modify the other
   supplied arguments so that they refer to the single output of "map". */
         astInvert( map );
         lbnd_in += ins[ 0 ];
         ubnd_in += ins[ 0 ];
         coord_out = 0;

/* If the output was fed by more than one input, annul the split mapping
   and use the supplied nmapping. */
      } else {
         (void) astAnnul( map );
         map = astClone( smap );
      }
      ins = astFree( ins );

/* If the supplied Mapping could not be split, use the supplied nmapping. */
   } else {
      map = astClone( smap );
   }

/* 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, "", status );
      ptr1 = astGetPoints( pset1 );
      pset2 = astPointSet( NP, nout, "", status );
      ptr2 = astGetPoints( pset2 );

/* Call astMapBox in a new error reporting context. */
      boxok = 0;
      if( astOK ) {

/* Temporarily switch off error reporting so that no report is made if
   astMapBox cannot find a bounding box (which can legitimately happen with
   some non-linear Mappings). */
         oldrep = astReporting( 0 );

/* 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 box could not be found, clear the error status and pass on. */
         if( !astOK ) {
            astClearStatus;

/* If the box was found OK, flag this and check if the bounds are equal.
   If so we cannot use them. In this case create new bounds. */
         } else {
            boxok = 1;
            if( astEQUAL( 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;
               }
            }
         }

/* Re-instate error reporting. */
         astReporting( oldrep );
      }

/* Check pointers can be used safely and a box was obtained. */
      if( astOK && boxok ) {

/* Transform the input position returned by astMapBox using the supplied
   Mapping to get the corresponding output position. Fill all unused
   elements of the PointSet with AST__BAD. */
         for( i = 0; i < nin; i++ ){
            p = ptr1[ i ];
            *(p++) = xl[ i ];
            for( j = 1; j < NP; j++ ) *(p++) = AST__BAD;
         }
         (void) 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. */
         (void) 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( !astEQUAL( 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-7*( 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 );
   }
   map = astAnnul( map );

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

static AstMapping *IsMapTab1D( AstMapping *map, double scale, const char *unit,
                               AstFrame *wcsfrm, double *dim, int iax,
                               int iwcs, AstFitsTable **table, int *icolmain,
                               int *icolindex, int *interp, int *status ){
/*
*  Name:
*     IsMapTab1D

*  Purpose:
*     See if a specified Mapping output is related to a single Mapping input
*     via a FITS -TAB algorithm.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     AstMapping *IsMapTab1D( AstMapping *map, double scale, const char *unit,
*                             AstFrame *wcsfrm, double *dim, int iax,
*                             int iwcs, AstFitsTable **table, int *icolmain,
*                             int *icolindex, int *interp, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     A specified axis of the supplied Mapping is tested to see if it
*     can be represented by the -TAB alogirithm described in FITS-WCS
*     paper III. If the test is passed, a Mapping is returned from the
*     specified WCS axis to the corresponding psi axis. A FitsTable is
*     also created holding the information to be stored in the
*     corresponding FITS binary table.
*
*     Note, when creating a -TAB header, AST uses grid coords for the psi
*     axis. See FITS-WCS paper III section 6.1.2 for a definition of the
*     psi axes.

*  Parameters:
*     map
*        Pointer to the Mapping from pixel coords to WCS coords.
*     scale
*        A scale factor by which to multiply the axis values stored in the
*        returned FitsTable. Note, the returned Mapping is unaffected by
*        this scaling factor.
*     unit
*        Pointer to the unit string to store with the coords column. If
*        NULL, the unit string is extracted form the supplied WCS Frame.
*     wcsfrm
*        Pointer to a Frame describing WCS coords.
*     dim
*        An array holding the array dimensions in pixels. AST__BAD should
*        be supplied for any unknown dimensions.
*     iax
*        The zero-based index of the Mapping output which is to be checked.
*     iwcs
*        The zero-based index of the corresponding FITS WCS axis.
*     table
*        Pointer to a location holding a pointer to the FitsTable describing
*        the -TAB look-up table. If "*table" is NULL on entry, a new
*        FitsTable will be created and returned, otherwise the supplied
*        FitsTable is used.
*     icolmain
*        The one-based index of the column within "*table" that holds the
*        main data array.
*     icolindex
*        The one-based index of the column within "*table" that holds the
*        index vector. Returned equal to -1 if no index is added to the
*        table (i.e. if the index is a unt index).
*     interp
*        The interpolation method (0=linear, other=nearest neighbour).
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     If the specified "map" output can be described using the -TAB
*     algorithm of FITS-WCS paper III, then a 1-input/1-output Mapping
*     from the specified WCS axis to the corresponding psi axis (which is
*     assumed to be equal to grid coords) is returned. NULL is returned
*     otherwise, of if an error occurs.
*/

/* Local Variables: */
   AstCmpMap *cm;          /* CmpMap pointer */
   AstMapping **map_list;  /* Mapping array pointer */
   AstMapping *postmap;    /* Total Mapping after LutMap */
   AstMapping *premap;     /* Total Mapping before LutMap */
   AstMapping *ret;        /* Returned WCS axis Mapping */
   AstMapping *tmap;       /* Temporary Mapping */
   AstPermMap *pm;         /* PermMap pointer */
   char cellname[ 21 ];    /* Buffer for cell name */
   char colname[ 20 ];     /* Buffer for column name */
   double *lut;            /* Pointer to table of Y values */
   double *work1;          /* Pointer to work array */
   double *work2;          /* Pointer to work array */
   double inc;             /* X increment between table entries */
   double start;           /* X value at first table entry */
   double v[ 2 ];          /* Y values at start and end of interval */
   double x[ 2 ];          /* X values at start and end of interval */
   int *ins;               /* Array of "map" input indices */
   int *invert_list;       /* Invert array pointer */
   int *outs;              /* Array of "map" output indices */
   int dims[ 2 ];          /* Dimensions of the tab coords array */
   int iin;                /* Index of Mapping input */
   int ilut;               /* Index of the LutMap within the mappings list */
   int imap;               /* Index of current Mapping in list */
   int iout;               /* Index of Mapping output */
   int jout;               /* Index of Mapping output */
   int nin;                /* Number of Mapping inputs */
   int nlut;               /* Number of elements in "lut" array */
   int nmap;               /* Number of Mappings in the list */
   int nout;               /* Number of Mapping outputs */
   int ok;                 /* Were columns added to the table? */
   int old_invert;         /* Original value for Mapping's Invert flag */
   int outperm;            /* Index of input that feeds the single output */

/* Initialise */
   ret = NULL;
   *icolmain = -1;
   *icolindex = -1;
   *interp = 0;

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

/* Ensure we have aunit string. */
   if( !unit ) unit = astGetUnit( wcsfrm, iax );

/* Check that the requested mapping output is fed by only one mapping
   input, identify that input, and extract the input->output mapping from
   the total mapping. Since astMapSplit splits off a specified input, we
   need to invert the Mapping first so we can split off a specified output.  */
   astInvert( map );
   ins = astMapSplit( map, 1, &iax, &ret );
   astInvert( map );

/* If the Mapping could not be split, try a different approach in which
   each input is checked in turn to see if it feeds the specified output. */
   if( !ins ) {

/* Loop round each input of "map". */
      nin = astGetNin( map );
      for( iin = 0; iin < nin && !ins; iin++ ) {

/* Attempt to find a group of outputs (of "map") that are fed by just
   this one input. */
         outs = astMapSplit( map, 1, &iin, &ret );

/* If successful, "ret" will be a Mapping with one input corresponding to
   input "iin" of "map, and one or more outputs. We loop round these
   outputs to see if any of them correspond to output "iax" of "map". */
         if( outs ) {
            nout = astGetNout( ret );
            for( iout = 0; iout < nout; iout++ ) {
               if( outs[ iout ] == iax ) break;
            }

/* Did input "iin" feed the output "iax" (and possibly other outputs)? */
            if( iout < nout ) {

/* The "ret" Mapping is now a 1-input (pixel) N-output (WCS) Mapping in which
   output "iout" corresponds to output "iax" of Mapping. To be compatible
   with the previous approach, we want "ret" to be a 1-input  (WCS) to
   1-output (pixel) Mapping in which the input corresponds to output
   "iax" of Mapping. To get "ret" into this form, we first append a PermMap
   to "ret" that selects a single output ("iout"), and then invert the
   whole CmpMap. */
               for( jout = 0; jout < nout; jout++ ) {
                  outs[ jout ] = -1;
               }
               outs[ iout ] = 0;
               outperm = iout;

               pm = astPermMap( nout, outs, 1, &outperm, NULL, "", status );
               cm = astCmpMap( ret, pm, 1, " ", status );
               (void) astAnnul( ret );
               pm = astAnnul( pm );
               ret = (AstMapping *) cm;
               astInvert( ret );

/* The earlier approach leves ins[ 0 ] holding the index of the input to
   "map" that feeds output iax. Ensure we have this too. */
               ins = outs;
               ins[ 0 ] = iin;

/* Free resources if the current input did not feed the required output. */
            } else {
               outs = astFree( outs );
               ret = astAnnul( ret );
            }
         }
      }
   }

/* If the Mapping still could not be split, try again on a copy of the
   Mapping in which all PermMaps provide an alternative implementation of
   the astMapSplit method. */
   if( !ins ) {
      astInvert( map );
      tmap = astCopy( map );
      ChangePermSplit( tmap, status );
      ins = astMapSplit( tmap, 1, &iax, &ret );
      tmap = astAnnul( tmap );
      astInvert( map );
   }

/* Assume the Mapping cannot be represented by -TAB */
   ok = 0;

/* Check a Mapping was returned by astMapSplit. If so, it will be the
   mapping from the requested output of "map" (the WCS axis) to the
   corresponding input(s) (grid axes). Check only one "map" input feeds the
   requested output. */
   if( ins && ret && astGetNout( ret ) == 1 ) {

/* Invert the Mapping so that the input is grid coord and the output is
   WCS coord. */
      astInvert( ret );

/* We now search the "ret" mapping for a non-inverted LutMap, splitting ret
   up into three serial components: 1) the mappings before the LutMap, 2) the
   LutMap itself, and 3) the mappings following the LutMap. First, decompose
   the mapping into a list of series mappings. */
      map_list = NULL;
      invert_list = NULL;
      nmap = 0;
      astMapList( ret, 1, astGetInvert( ret ), &nmap, &map_list,
                  &invert_list );

/* Search the list for a non-inverted LutMap. */
      ilut = -1;
      for( imap = 0; imap < nmap; imap++ ) {
         if( astIsALutMap( map_list[ imap ] ) && !(invert_list[ imap ]) ) {
            ilut = imap;
            break;
         }
      }

/* If a LutMap was found, combine all Mappings before the LutMap into a
   single Mapping. Remember to set the Mapping Invert flags temporarily to
   the values used within the CmpMap. */
      if( ilut >= 0 ) {
         premap = (AstMapping *) astUnitMap( 1, " ", status );
         for( imap = 0; imap < ilut; imap++ ) {
            old_invert = astGetInvert( map_list[ imap ] );
            astSetInvert( map_list[ imap ], invert_list[ imap ] );
            tmap = (AstMapping *) astCmpMap( premap,  map_list[ imap ], 1,
                                             " ", status );
            astSetInvert( map_list[ imap ], old_invert );
            (void) astAnnul( premap );
            premap = tmap;
         }

/* Also combine all Mappings after the LutMap into a single Mapping, setting
   the Mapping Invert flags temporarily to the values used within the
   CmpMap. */
         postmap = (AstMapping *) astUnitMap( 1, " ", status );
         for( imap = ilut + 1; imap < nmap; imap++ ) {
            old_invert = astGetInvert( map_list[ imap ] );
            astSetInvert( map_list[ imap ], invert_list[ imap ] );
            tmap = (AstMapping *) astCmpMap( postmap,  map_list[ imap ], 1,
                                             " ", status );
            astSetInvert( map_list[ imap ], old_invert );
            (void) astAnnul( postmap );
            postmap = tmap;
         }

/* Get the table of values, and other attributes, from the LutMap. */
         lut = astGetLutMapInfo( map_list[ ilut ], &start, &inc, &nlut );
         *interp = astGetLutInterp(  map_list[ ilut ] );

/* If required, create a FitsTable to hold the returned table info. */
         if( ! *table ) *table = astFitsTable( NULL, "", status );
         ok = 1;

/* Define the properties of the column in the FitsTable that holds the main
   coordinate array. Points on a WCS axis are described by a single value
   (wavelength, frequency, or whatever), but the coords array has to be
   2-dimensional, with an initial degenerate axis, as required by FITS-WCS
   paper III. */
         dims[ 0 ] = 1;
         dims[ 1 ] = nlut;
         sprintf( colname, "COORDS%d", iwcs + 1 );
         astAddColumn( *table, colname, AST__DOUBLETYPE, 2, dims, unit );

/* Get the one-based index of the column just added to the table. */
         *icolmain = astGetNcolumn( *table );

/* Get workspace. */
         work1 = astMalloc( nlut*sizeof( double ) );
         if( astOK ) {

/* Transform the LutMap table values using the post-lutmap mapping to
   get the list of WCS values in AST units. */
            astTran1( postmap, nlut, lut, 1, work1 );

/* Convert them to FITS units (e.g. celestial axis values should be
   converted from radians to degrees). */
            for( ilut = 0; ilut < nlut; ilut++ ) work1[ ilut ] *= scale;

/* Store them in row 1, column COORDS, in the FitsTable. */
            sprintf( cellname, "COORDS%d(1)", iwcs + 1 );
            astMapPut1D( *table, cellname, nlut, work1, NULL );

/* Create an array holding the LutMap input value at the centre of each
   table entry. Re-use the "lut" array since we no longer need it. */
            for( ilut = 0; ilut < nlut; ilut++ ) {
               lut[ ilut ] = start + ilut*inc;
            }

/* Transform this array using the inverted pre-lutmap mapping to get the
   list of grid coord. */
            astTran1( premap, nlut, lut, 0, work1 );

/* Test this list to see if they form a unit index (i.e. index(i) == i+1 ).
   (not the "+1" is due to the fact that "i" is zero based). */
            for( ilut = 0; ilut < nlut; ilut++ ) {
               if( fabs( work1[ ilut ] - ilut - 1.0 ) > 1.0E-6 ) break;
            }

/* if it is not a unit index, we add the index to the table. */
            if( ilut < nlut ) {

/* Define the properties of the column in the FitsTable that holds the
   indexing vector. */
               sprintf( colname, "INDEX%d", iwcs + 1 );
               astAddColumn( *table, colname, AST__DOUBLETYPE, 1, &nlut, " " );

/* Get the one-based index of the column just added to the table. */
               *icolindex = astGetNcolumn( *table );

/* Store the values in the column. */
               sprintf( cellname, "INDEX%d(1)", iwcs + 1 );
               astMapPut1D( *table, cellname, nlut, work1, NULL );
            }
         }

/* Free resources. */
         work1 = astFree( work1 );
         lut = astFree( lut );
         premap = astAnnul( premap );
         postmap = astAnnul( postmap );

/* If no LutMap was found in the Mapping, then we can create a FitsTable
   by sampling the full WCS Mapping at selected input (i.e. grid)
   positions. But we can only do this if we know the number of pixels
   along the WCS axis. */
      } else if( dim[ ins[ 0 ] ] != AST__BAD ) {

/* Create two works array each holding a single value. The first holds
   the grid coords at which the samples are taken. The second holds the
   WCS coords at the sampled positions. These arrays are expanded as
   required within function AdaptLut. */
         work1 = astMalloc( sizeof( double ) );
         work2 = astMalloc( sizeof( double ) );
         if( astOK ) {

/* Get the WCS values at the centres of the first and last pixel on
   the WCS axis. */
            x[ 0 ] = 1.0;
            x[ 1 ] = dim[ ins[ 0 ] ];
            astTran1( ret, 2, x, 1, v );

/* Put the lower limit into the work arrays. */
            work1[ 0 ] = x[ 0 ];
            work2[ 0 ] = v[ 0 ];
            nlut = 1;

/* Expand the arrays by sampling the WCS axis adaptively so that
   more samples occur where the WCS value is changing most rapidly.
   We require the maximum error introduced by the table to be 0.25 pixels. */
            AdaptLut( ret, 3, 0.25, x[ 0 ], x[ 1 ], v[ 0 ], v[ 1 ],
                      &work1, &work2, &nlut, status );

/* Create a FitsTable to hold the returned table info. */
            if( ! *table ) *table = astFitsTable( NULL, "", status );
            ok = 1;

/* Define the properties of the column in the FitsTable that holds the main
   coordinate array. */
            sprintf( colname, "COORDS%d", iwcs + 1 );
            dims[ 0 ] = 1;
            dims[ 1 ] = nlut;
            astAddColumn( *table, colname, AST__DOUBLETYPE, 2, dims, unit );
            *icolmain = astGetNcolumn( *table );

/* Convert the axis values to FITS units (e.g. celestial axis values should be
   converted from radians to degrees). */
            for( ilut = 0; ilut < nlut; ilut++ ) work2[ ilut ] *= scale;

/* Store the scaled axis values in row 1 of the column. */
            sprintf( cellname, "COORDS%d(1)", iwcs + 1 );
            astMapPut1D( *table, cellname, nlut, work2, NULL );

/* Test the index vector to see if they form a unit index (i.e. index(i) ==
   i+1 ). If not the "+1" is due to the fact that "i" is zero based). If not, store
   them as the index vector in the FitsTable. */
            for( ilut = 0; ilut < nlut; ilut++ ) {
               if( fabs( work1[ ilut ] - ilut - 1.0 ) > 1.0E-6 ) break;
            }

/* If the index vector is not a unit index, define the properties of the
   column in the FitsTable that holds the indexing vector. Then store values
   in row 1 of the column. */
            if( ilut < nlut ) {
               sprintf( colname, "INDEX%d", iwcs + 1 );
               astAddColumn( *table, colname, AST__DOUBLETYPE, 1, &nlut, " " );
               *icolindex = astGetNcolumn( *table );
               sprintf( cellname, "INDEX%d(1)", iwcs + 1 );
               astMapPut1D( *table, cellname, nlut, work1, NULL );
            }
         }

/* Free resources */
         work1 = astFree( work1 );
         work2 = astFree( work2 );
      }

/* If columns were added to the table, invert the returned Mapping again
   so that the input is wcs coord and the output is grid coord. Otherwise,
   annul the returned Mapping. */
      if( ok ) {
         astInvert( ret );
      } else {
         ret = astAnnul( ret );
      }

/* Loop to annul all the Mapping pointers in the list. */
      for ( imap = 0; imap < nmap; imap++ ) map_list[ imap ] = astAnnul( map_list[ imap ] );

/* Free the dynamic arrays. */
      map_list = astFree( map_list );
      invert_list = astFree( invert_list );
   }

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

/* If an error occurred, free the returned Mapping. */
   if( !astOK ) ret = astAnnul( ret );

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

static AstMapping *IsMapTab2D( AstMapping *map, double scale, const char *unit,
                               AstFrame *wcsfrm, double *dim, int iax1,
                               int iax2, int iwcs1, int iwcs2,
                               AstFitsTable **table, int *icolmain1,
                               int *icolmain2, int *icolindex1,
                               int *icolindex2, int *max1, int *max2,
                               int *interp1, int *interp2, int *status ){
/*
*  Name:
*     IsMapTab2D

*  Purpose:
*     See if a specified pair of Mapping outputs are related to a pair of
*     Mapping inputs via a FITS -TAB algorithm.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     AstMapping *IsMapTab2D( AstMapping *map, double scale, const char *unit,
*                             AstFrame *wcsfrm, double *dim, int iax1,
*                             int iax2, int iwcs1, int iwcs2,
*                             AstFitsTable **table, int *icolmain1,
*                             int *icolmain2, int *icolindex1,
*                             int *icolindex2, int *max1, int *max2,
*                             int *interp1, int *interp2, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     A specified pair of outputs axes of the supplied Mapping are tested
*     to see if they can be represented by the -TAB alogirithm described in
*     FITS-WCS paper III. If the test is passed, a Mapping is returned from
*     the specified WCS axes to the corresponding psi axes. A FitsTable is
*     also created holding the information to be stored in the corresponding
*     FITS binary table. Note, when creating a header, AST assumes a unit
*     transformaton between psi axes and grid axes (psi axes are defined
*     in FITS-WCS paper III section 6.1.2).

*  Parameters:
*     map
*        Pointer to the Mapping from pixel coords to WCS coords.
*     scale
*        A scale factor by which to multiply the axis values stored in the
*        returned FitsTable. Note, the returned Mapping is unaffected by
*        this scaling factor.
*     unit
*        A unit string for the axis values. If supplied, the same
*        string is stored for both axes. If NULL, the unit strings are
*        extracted from the relavent axes of the supplied WCS Frame.
*     wcsfrm
*        Pointer to a Frame describing WCS coords.
*     dim
*        An array holding the array dimensions in pixels. AST__BAD should
*        be supplied for any unknown dimensions.
*     iax1
*        The zero-based index of the first Mapping output which is to be
*        checked.
*     iax2
*        The zero-based index of the second Mapping output which is to be
*        checked.
*     iwcs1
*        The zero-based index of the FITS WCS axis corresponding to "iax1".
*     iwcs2
*        The zero-based index of the FITS WCS axis corresponding to "iax2".
*     table
*        Pointer to a location holding a pointer to the FitsTable describing
*        the -TAB look-up table. If "*table" is NULL on entry, a new
*        FitsTable will be created and returned, otherwise the supplied
*        FitsTable is used.
*     icolmain1
*        The one-based index of the column within "*table" that holds the
*        main coord array for the first Mapping output.
*     icolmain2
*        The one-based index of the column within "*table" that holds the
*        main coord array for the second Mapping output.
*     icolindex1
*        The one-based index of the column within "*table" that holds the
*        index vector for the first Mapping output. Returned equal to -1
*        if no index is added to the table (e.g. because the index is a
*        unit index).
*     icolindex2
*        The one-based index of the column within "*table" that holds the
*        index vector for the second Mapping output. Returned equal to -1
*        if no index is added to the table (e.g. because the index is a
*        unit index).
*     max1
*        The one-based index of the dimension describing the first Mapping
*        output within the main coord array specified by "icolmain1".
*     max2
*        The one-based index of the dimension describing the second Mapping
*        output within the main coord array specified by "icolmain1".
*     interp1
*        The interpolation method (0=linear, other=nearest neighbour) for
*        the first mapping output.
*     interp2
*        The interpolation method (0=linear, other=nearest neighbour) for
*        the second mapping output.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     If the specified "map" outputs can be described using the -TAB
*     algorithm of FITS-WCS paper III, then a 2-input/2-output Mapping
*     from the specified WCS axes to the corresponding psi axes (i.e.
*     grid axes) is returned. NULL is returned otherwise, of if an error
*     occurs.
*/

/* Local Variables: */
   AstMapping *ret1;       /* WCS->IWC Mapping for first output */
   AstMapping *ret2;       /* WCS->IWC Mapping for second output */
   AstMapping *ret;        /* Returned WCS axis Mapping */
   AstMapping *tmap;
   AstPermMap *pm;
   int *pix_axes;          /* Zero-based indices of corresponding pixel axes */
   int wcs_axes[ 2 ];      /* Zero-based indices of selected WCS axes */
   int inperm[ 1 ];
   int outperm[ 2 ];

/* Initialise */
   ret = NULL;

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

/* First see if the two required Mapping outputs are separable, in which case
   they can be described by two 1D tables. */
   ret1 = IsMapTab1D( map, scale, unit, wcsfrm, dim, iax1, iwcs1, table, icolmain1,
                      icolindex1, interp1, status );
   ret2 = IsMapTab1D( map, scale, unit, wcsfrm, dim, iax2, iwcs2, table, icolmain2,
                      icolindex2, interp2, status );

/* If both outputs are seperable... */
   if( ret1 && ret2 ) {

/* Both axes are stored as the first dimension in the corresponding main
   coords array. */
      *max1 = 1;
      *max2 = 1;

/* Get a Mapping from the required pair of WCS axes to the corresponding
   pair of grid axes. First try to split the supplied grid->wcs mapping. */
      wcs_axes[ 0 ] = iax1;
      wcs_axes[ 1 ] = iax2;

      astInvert( map );
      pix_axes = astMapSplit( map, 2, wcs_axes, &ret );
      astInvert( map );

      if( pix_axes ) {
         pix_axes = astFree( pix_axes );
         if( astGetNout( ret ) > 2 ) {
            ret = astAnnul( ret );

/* If the two output WCS axes are fed by the same grid axis, we need to
   add another pixel axis to form the pair. */
         } else if( astGetNout( ret ) == 1 ) {
            inperm[ 0 ] = 0;
            outperm[ 0 ] = 0;
            outperm[ 1 ] = 0;
            pm = astPermMap( 1, inperm, 2, outperm, NULL, " ", status );
            tmap = (AstMapping *) astCmpMap( ret, pm, 1, " ", status );
            ret = astAnnul( ret );
            pm = astAnnul( pm );
            ret = tmap;
         }
      }

/* If this was unsuccessful, combine the Mappings returned by IsMapTab1D.
   We only do this if the above astMapSplit call failed, since the IsMapTab1D
   mappings may well not be independent of each other, and we may end up
   sticking together in parallel two mappings that are basically the same
   except for ending with PermMapa that select different axes. Is is hard
   then to simplify such a parallel CmpMap back into the simpler form
   that uses only one of the two identical mappings, without a PermMap. */
      if( !ret ) {
         ret = (AstMapping *) astCmpMap( ret1, ret2, 0, " ", status );
      }

/* Free resources. */
      ret1 = astAnnul( ret1 );
      ret2 = astAnnul( ret2 );

/* If only one output is separable, remove the corresponding columns from
   the returned table. */
   } else if( ret1 ) {
      ret1 = astAnnul( ret1 );
      astRemoveColumn( *table, astColumnName( *table, *icolmain1 ) );
      if( icolindex1 >= 0 ) astRemoveColumn( *table, astColumnName( *table, *icolindex1 ) );
   } else if( ret2 ) {
      ret2 = astAnnul( ret2 );
      astRemoveColumn( *table, astColumnName( *table, *icolmain2 ) );
      if( icolindex1 >= 0 ) astRemoveColumn( *table, astColumnName( *table, *icolindex2 ) );
   }

/* If the required Mapping outputs were not separable, create a single
   2D coords array describing both outputs. */
   if( !ret ) {

/* TO BE DONE... Until then non-separable Mappings will result in a
   failure to create a -TAB header. No point in doing this until AST has
   an N-dimensional LutMap class (otherwise AST could never read the
   resulting FITS header). */
   }

/* If an error occurred, free the returned Mapping. */
   if( !astOK ) ret = astAnnul( ret );

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

static int IsAIPSSpectral( const char *ctype, char **wctype, char **wspecsys, int *status ){
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  Retuned Value:
*     Non-zero fi the supplied CTYPE was an AIPS spectral CTYPE value.

*  Note:
*     - These translations are also used by the FITS-CLASS encoding.
*/

/* Local Variables: */
   int ret;

/* Initialise */
   ret = 0;
   *wctype = NULL;
   *wspecsys = NULL;

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

/* If the used length of the string is not 8, then it is not an AIPS spectral axis. */
   if( astChrLen( ctype ) == 8 ) {

/* 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";
      } else if( !strncmp( ctype, "WAVELENG", 8 ) ){
         *wctype = "WAVE    ";
      }
      if( !strncmp( ctype + 4, "-LSR", 4 ) ){
         *wspecsys = "LSRK";
      } else if( !strncmp( ctype + 4, "LSRK", 4 ) ){
         *wspecsys = "LSRK";
      } else if( !strncmp( ctype + 4, "-LSD", 4 ) ){
         *wspecsys = "LSRD";
      } else if( !strncmp( ctype + 4, "-HEL", 4 ) ){
         *wspecsys = "BARYCENT";
      } else if( !strncmp( ctype + 4, "-EAR", 4 ) || !strncmp( ctype + 4, "-GEO", 4 ) ){
         *wspecsys = "GEOCENTR";
      } else if( !strncmp( ctype + 4, "-OBS", 4 ) || !strncmp( ctype + 4, "-TOP", 4 ) ){
         *wspecsys = "TOPOCENT";
      }
      if( *wctype && *wspecsys ) {
         ret = 1;
      } else {
         *wctype = NULL;
         *wspecsys = NULL;
      }
   }

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

static int IsSkyOff( AstFrameSet *fset, int iframe, int *status ){
/*
*  Name:
*     IsSkyOff

*  Purpose:
*     See if a given Frame contains an offset SkyFrame.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int IsSkyOff( AstFrameSet *fset, int iframe, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Returns a flag indicating if the specified Frame within the
*     supplied FrameSet is, or contains, a SkyFrame that represents
*     offset coordinates. This is the case if the Frame is a SkyFrame
*     and its SkyRefIs attribute is "Pole" or "Origin", or is a CmpFrame
*     containing such a SkyFrame.

*  Parameters:
*     fset
*        The FrameSet.
*     iframe
*        Index of the Frame to check within "fset"
*     status
*        Pointer to the inherited status variable.

*  Retuned Value:
*     +1 if the Frame is an offset SkyFrame. Zero otherwise.

*  Notes:
*     - Zero is returned if an error has already occurred.
*/

/* Local Variables: */
   AstFrame *frm;
   const char *skyrefis;
   int oldrep;
   int result;

/* Initialise. */
   result = 0;

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

/* Get a pointer to the required Frame in the FrameSet */
   frm = astGetFrame( fset, iframe );

/* Since the current Frame may not contain a SkyFrame, we temporarily
   switch off error reporting. */
   oldrep = astReporting( 0 );

/* Get the SkyRefIs attribute value. */
   skyrefis = astGetC( frm, "SkyRefIs" );

/* If it is "Pole" or "Origin", return 1. */
   if( skyrefis && ( !Ustrcmp( skyrefis, "POLE", status ) ||
                     !Ustrcmp( skyrefis, "ORIGIN", status ) ) ) result = 1;

/* Cancel any error and switch error reporting back on again. */
   astClearStatus;
   astReporting( oldrep );

/* Annul the Frame pointer. */
   frm = astAnnul( frm );

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

static const char *IsSpectral( const char *ctype, char stype[5], char algcode[5], int *status ) {
/*
*  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], int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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: */
   astDECLARE_GLOBALS
   int ctype_len;

/* Initialise */
   stype[ 0 ] = 0;
   algcode[ 0 ] = 0;

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(NULL);

/* Initialise more stuff */
   isspectral_ret = NULL;

/* If the length of the string is less than 4, then it is not a spectral
   axis. */
   ctype_len = strlen( ctype );
   if( ctype_len >= 4 ) {

/* 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 any remaining characters (the algorithm code) into a null-terminated
   buffer. Only copy a maximum of 4 characters. */
      if( ctype_len > 4 ) {
         if( ctype_len <= 8 ) {
            strcpy( algcode, ctype + 4 );
         } else {
            strncpy( algcode, ctype + 4, 4 );
            algcode[ 4 ] = 0;
         }
         algcode[ astChrLen( algcode ) ] = 0;
      } else {
         algcode[ 0 ] = 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" ) ) {
         isspectral_ret = "Hz";
      } else if( !strcmp( stype, "ENER" ) ) {
         isspectral_ret = "J";
      } else if( !strcmp( stype, "WAVN" ) ) {
         isspectral_ret = "/m";
      } else if( !strcmp( stype, "VRAD" ) ) {
         isspectral_ret = "m/s";
      } else if( !strcmp( stype, "WAVE" ) ) {
         isspectral_ret = "m";
      } else if( !strcmp( stype, "VOPT" ) ) {
         isspectral_ret = "m/s";
      } else if( !strcmp( stype, "ZOPT" ) ) {
         isspectral_ret = "";
      } else if( !strcmp( stype, "AWAV" ) ) {
         isspectral_ret = "m";
      } else if( !strcmp( stype, "VELO" ) ) {
         isspectral_ret = "m/s";
      } else if( !strcmp( stype, "BETA" ) ) {
         isspectral_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 ) isspectral_ret = NULL;
   }

/* Return null strings if the axis is not a spectral axis. */
   if( ! isspectral_ret ) {
      stype[ 0 ] = 0;
      algcode[ 0 ] = 0;
   }

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

static AstMapping *LinearWcs( FitsStore *store, int i, char s,
                              const char *method, const char *class, int *status ) {
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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, status );
   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, "", status );
   } else {
      ret = (AstMapping *) astUnitMap( 1, "", status );
   }
   return ret;
}

static AstMapping *LogAxis( AstMapping *map, int iax, int nwcs, double *lbnd_p,
                            double *ubnd_p, double crval, int *status ){
/*
*  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", status );
   tmap2 = AddUnitMaps( tmap1, iax, nwcs, status );
   tmap0 = (AstMapping *) astCmpMap( map, tmap2, 1, "", status );
   tmap2 = astAnnul( tmap2 );

/* See if this Mapping is linear. */
   if( IsMapLinear( tmap0, lbnd_p, ubnd_p, iax, NULL, status ) ) {

/* 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, "", status );
      tmap3 = (AstMapping *) astCmpMap( tmap2, tmap1, 1, "", status );
      tmap2 = astAnnul( tmap2 );
      tmap2 = (AstMapping *) astZoomMap( 1, crval, "", status );
      tmap4 = (AstMapping *) astCmpMap( tmap3, tmap2, 1, "", status );
      tmap3 = astAnnul( tmap3 );
      tmap2 = astAnnul( tmap2 );
      result = AddUnitMaps( tmap4, iax, nwcs, status );
      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, int *status ) {
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to a Mapping, or NULL if an error occurs.
*/

/* Local Variables: */
   AstMapping *ret;
   char forexp[ 12 + AST__DBL_WIDTH*2 ];
   char invexp[ 12 + AST__DBL_WIDTH*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, status );
   if( crv == AST__BAD ) crv = 0.0;

/* Create the MathMap, if possible. */
   if( crv != 0.0 ) {
      sprintf( forexp, "s=%.*g*exp(w/%.*g)", AST__DBL_DIG, crv, AST__DBL_DIG, crv );
      sprintf( invexp, "w=%.*g*log(s/%.*g)", AST__DBL_DIG, crv, AST__DBL_DIG, crv );
      fexps[ 0 ] = forexp;
      iexps[ 0 ] = invexp;
      ret = (AstMapping *) astMathMap( 1, 1, 1, fexps, 1, iexps, "simpfi=1,simpif=1", status );
   }

/* Return the result */
   return ret;
}

static int LooksLikeClass( AstFitsChan *this, const char *method,
                           const char *class, int *status ){

/*
*  Name:
*     LooksLikeClass

*  Purpose:
*     Does the FitsChan seem to use FITS-CLASS encoding?

*  Type:
*     Private function.

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

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Returns non-zero if the supplied FitsChan probably uses FITS-CLASS
*     encoding. This is the case if it contains a DELTAV keyword and a
*     keyword of the form VELO-xxx", where xxx is one of the accepted
*     standards of rest, or "VLSR".

*  Parameters:
*     this
*        Pointer to 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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     Non-zero if the encoding in use lookslike FITS-CLASS.
*/

/* Local Variables... */
   int ret;            /* Returned value */

/* Initialise */
   ret = 0;

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

/* See if there is a "DELTAV" card, and a "VELO-xxx" or "VLSR" card. */
   if( astKeyFields( this, "DELTAV", 0, NULL, NULL ) && (
          astKeyFields( this, "VLSR", 0, NULL, NULL ) ||
          astKeyFields( this, "VELO-OBS", 0, NULL, NULL ) ||
          astKeyFields( this, "VELO-HEL", 0, NULL, NULL ) ||
          astKeyFields( this, "VELO-EAR", 0, NULL, NULL ) ||
          astKeyFields( this, "VELO-LSR", 0, NULL, NULL ) ) ) {
      ret = 1;
   }

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

static void MakeBanner( const char *prefix, const char *middle,
                        const char *suffix,
                        char banner[ AST__FITSCHAN_FITSCARDLEN -
                                     FITSNAMLEN + 1 ], int *status ) {
/*
*  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[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN + 1 ], int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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 = AST__FITSCHAN_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 AstMapping *MakeColumnMap( AstFitsTable *table, const char *col,
                                  int isindex, int interp, const char *method,
                                  const char *class, int *status ){
/*
*  Name:
*     MakeColumnMap

*  Purpose:
*     Create a Mapping describing a look-up table supplied in a cell of a
*     FITS binary table.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     AstMapping *MakeColumnMap( AstFitsTable *table, const char *col,
*                                int isindex, int interp, const char *method,
*                                const char *class, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function returns a Mapping representing the array of values
*     stored in row 1 of a named column of a supplied FitsTable. The
*     array of values is treated as a look-up table following the prescription
*     of FITS-WCS paper III (the "-TAB" algorithm). If the array has (N+1)
*     dimensions (where N is one or more), the returned Mapping has N
*     inputs and N outputs. The inputs correspond to FITS GRID coords
*     within the array. FITS-WCS paper III requires that the first dimension
*     in the array has a length of "N" and contains the N output values
*     at each input values.

*  Parameters:
*     table
*        Pointer to the Fitstable.
*     col
*        A string holding the name of the column to use.
*     isindex
*        Non-zero if the column hold an index array, zero if it holds a
*        coordinate array.
*     interp
*        The value to use for the Interp attribute of the LutMap. A value
*        of zero tells the LutMap class to use linear interpolation. Other
*        values tell the LutMap class to use nearest neighbour interpolation.
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to the Mapping, or NULL if an error occurs.
*/

/* Local Variables: */
   AstMapping *result;
   char *key;
   double *lut;
   int *dims;
   int ndim;
   int nel;

/* Initialise */
   result = NULL;

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

/* Get the number of dimensions spanned by the value in the named column. */
   ndim = astGetColumnNdim( table, col );

/* First deal with index vectors. */
   if( isindex ) {

/* FITS-WCS paper II mandates that index arrays must be 1-dimensional. */
      if( ndim != 1 && astOK ) {
         astError( AST__BADTAB, "%s(%s): Column '%s' has %d dimensions but it "
                   "holds an index vector and should therefore be 1-dimensional.",
                   status, method, class, col, ndim );
      }

/* Get the length of the index vector. */
      nel = astGetColumnLength( table, col );

/* Allocate memory to hold the array values, and to hold the cell key. */
      lut = astMalloc( nel*sizeof( double ) );
      key = astMalloc( strlen( col ) + 5 );
      if( astOK ) {

/* Create the key for the table cell holding the required array. FITS-WCS
   paper III mandates that tables always occur in the first row of the
   table (and that the table only has one row). Ignore trailing spaces in
   the column name. */
         sprintf( key, "%.*s(1)", (int) astChrLen( col ), col );

/* Copy the array values into the above memory. */
         if( astMapGet1D( table, key, nel, &nel, lut ) ) {

/* Create a 1D LutMap. FITS-WCS paper III (sec 6.1.2) mandates that the input
   corresponds to FITS grid coord (i.e. 1.0 at the centre of the first entry).
   Ensure the LutMap uses linear interpolation. */
            result = (AstMapping *) astLutMap( nel, lut, 1.0, 1.0,
                                               "LutInterp=%d", status, interp );

/* Report an error if the table cell was empty. */
         } else if( astOK ) {
            astError( AST__BADTAB, "%s(%s): Row 1 of the binary table "
                      "contains no value for column '%s'.", status, method,
                      class, col );
         }
      }

/* Free memory. */
      lut = astFree( lut );
      key = astFree( key );

/* Now deal with coordinate arrays. */
   } else {

/* Get the shape of the array. */
      dims = astMalloc( sizeof( int )*ndim );
      astColumnShape( table, col, ndim, &ndim, dims );

/* For coordinate arrays, check the length of the first axis is "ndim-1", as
   required by FITS-WCS paper III. */
      if( astOK && dims[ 0 ] != ndim - 1 && !isindex ) {
         astError( AST__BADTAB, "%s(%s): The first dimension of the coordinate "
                   "array has length %d (should be %d since the array has %d "
                   "dimensions).", status, method, class, dims[ 0 ], ndim - 1,
                   ndim );
      }

/* We can currently only handle 1D look-up tables. These are stored in
   notionally two-dimensional arrays in which the first dimension is
   degenarate (i.e. spans only a single element). */
      if( ndim > 2 ) {
         if( astOK ) astError( AST__INTER, "%s(%s): AST can currently only "
                               "handle 1-dimensional coordinate look-up tables "
                               "(the supplied table has %d dimensions).", status,
                               method, class, ndim - 1 );

/* Handle 1-dimensional  look-up tables. */
      } else if( astOK ){

/* Allocate memory to hold the array values, and to hold the cell key. */
         lut = astMalloc( dims[ 1 ]*sizeof( double ) );
         key = astMalloc( strlen( col ) + 5 );
         if( astOK ) {

/* Create the key for the table cell holding the required array. FITS-WCS
   paper III mandates that tables always occur in the first row of the
   table (and that the table only has one row). Ignore trailing spaces in
   the column name. */
            sprintf( key, "%.*s(1)", (int) astChrLen( col ), col );

/* Copy the array values into the above memory. */
            if( astMapGet1D( table, key, dims[ 1 ], dims, lut ) ) {

/* Create a 1D LutMap. FITS-WCS paper III (sec 6.1.2) mandates that the input
   corresponds to FITS grid coord (i.e. 1.0 at the centre of the first entry).
   Ensure the LutMap uses linear interpolation. */
               result = (AstMapping *) astLutMap( dims[ 1 ], lut, 1.0, 1.0,
                                                  "LutInterp=%d", status,
                                                  interp );

/* Report an error if the table cell was empty. */
            } else if( astOK ) {
               astError( AST__BADTAB, "%s(%s): Row 1 of the binary table "
                         "contains no value for column '%s'.", status, method,
                         class, col );
            }
         }

/* Free memory. */
         lut = astFree( lut );
         key = astFree( key );
      }
      dims = astFree( dims );
   }

/* Issue a context message and annul the returned Mapping if an error
   has occurred. */
   if( !astOK ) {
      astError( astStatus, "%s(%s): Cannot read a look-up table for a "
                "tabular WCS axis from column '%s' of a FITS binary table.",
                status, method, class, col );
      result = astAnnul( result );
   }

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

static AstFrameSet *MakeFitsFrameSet( AstFitsChan *this, AstFrameSet *fset,
                                      int ipix, int iwcs, int encoding,
                                      const char *method, const char *class,
                                      int *status ) {
/*
*  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( AstFitsChan *this, AstFrameSet *fset,
*                                    int ipix, int iwcs, int encoding,
*                                    const char *method, const char *class,
*                                    int *status )

*  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 1D spectral Frame 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.
*
*     - Any isolated sky axes (i.e. not contained within a SkyFrame) are
*     re-mapped from radians into degrees.

*  Parameters:
*     this
*        The FitsChan.
*     fset
*        The FrameSet to check.
*     ipix
*        The index of the FITS pixel Frame within "fset".
*     iwcs
*        The index of the WCS Frame within "fset".
*     encoding
*        The encoding in 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.
*     status
*        Pointer to the inherited status variable.

*  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 *remap;      /* Total Mapping from internal to external units */
   AstMapping *smap;       /* Simplified 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 */
   AstMapping *umap;       /* 1D Mapping from internal to external units */
   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[ AST__FITSCHAN_FITSCARDLEN + 1 ]; /* A FITS header card */
   char equinox_attr[ 40 ];/* Name of Equinox attribute for sky axes */
   char system_attr[ 40 ]; /* Name of System attribute for sky axes */
   const char *eqn;        /* Pointer to original sky Equinox value */
   const char *extunit;    /* External units string */
   const char *intunit;    /* Internal units string */
   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 icurr;              /* Index of original current Frame in returned FrameSet */
   int ilat;               /* Celestial latitude index within WCS Frame */
   int ilon;               /* Celestial longitude 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 */
   int rep;                /* Was error reporting switched on? */

/* 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. */
   umap = NULL;
   remap = NULL;
   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. If we have already
   found a SpecFrame, abort. */
      if( astIsASpecFrame( pframe ) ) {
         if( specfrm ) {
            ok = 0;
            break;
         }
         specfrm = astClone( pframe );

/* 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( IsASkyFrame( pframe ) ) {
         if( skyfrm ) {
            if( pframe != (AstFrame *) skyfrm ) {
               ok = 0;
               break;
            }
         } else {
            skyfrm = astClone( pframe );
         }
         if( paxis == 0 ) {
            ilon = iax;
         } else {
            ilat = iax;
         }

/* If the internal and external units differ, attempt to remap the axis
   into its external units. */
      } else {

/* Get the string describing the external units (the "Unit" attribute). */
         extunit = astGetUnit( pframe, paxis );

/* Get the string describing the internal units (the "InternalUnit"
   attribute). */
         intunit = astGetInternalUnit( pframe, paxis );

/* If they are the same, we do not need to modify this axis. */
         if( astOK && strcmp( extunit, intunit ) ){

/* Otherwise, get the mapping from the internal units to the external
   units, if possible. Ignore any error reported by unitmapper. */
            rep = astReporting( 0 );
            umap = astUnitMapper( intunit, extunit, NULL, NULL );
            if( !astOK ) astClearStatus;
            astReporting( rep );

            if( !umap ) {

/* If the above failed, ensure that the external units are the same as
   the internal units (except that internal radians are converted to
   external degrees). */
               if( !strcmp( intunit, "rad" ) ) {
                  umap = (AstMapping *) astZoomMap( 1, AST__DR2D, " ", status );
                  extunit = "deg";
               } else {
                  extunit = intunit;
               }

               astSetUnit( wcsfrm, iax, extunit );
            }
         }
      }

/* If no change is needed for the mapping for this axis, use a UnitMap. */
      if( !umap ) umap = (AstMapping *) astUnitMap( 1, " ", status );

/* Extend the parallel CmpMap to encompass the current axis. */
      if( remap ) {
         tmap = (AstMapping *) astCmpMap( remap, umap, 0, " ", status );
         (void) astAnnul( remap );
         remap = tmap;
      } else {
         remap = astClone( umap );
      }

/* Free resources. */
      umap = astAnnul( umap );
      pframe = astAnnul( pframe );
   }

/* See if the pixel->wcs mapping needs to be modified to take account of
   any changes to axis units. */
   smap = astSimplify( remap );
   if( ! astIsAUnitMap( smap ) ) {
      tmap = (AstMapping *) astCmpMap( map, remap, 1, " ", status );
      (void) astAnnul( map );
      map = tmap;
   }
   remap = astAnnul( remap );
   smap = astAnnul( smap );

/* 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, "", status );
         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. Also do this if the original WCS Frame contains 3
   or more axes (since it is almost always inappropriate to add extra sky
   axes in such circumestances). But if the other axes form a skyfram,
   then we need to make sure they use the right refrence point. */
         if( !astTestRefRA( specfrm ) || !astTestRefDec( specfrm ) ||
             ( nwcs > 2 && !skyfrm ) ) {
            ret = astFrameSet( pixfrm, "", status );
            astAddFrame( ret, AST__BASE, map, wcsfrm );

/* If we have a celestial reference position for the spectral axis, ensure
   it is described 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, "", status );
               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", AST__DBL_DIG,
                        AST__DR2D*astGetRefRA( specfrm ) );
               astPutFits( fc, card, 0 );
               sprintf( card, "CRVAL2  = %.*g", AST__DBL_DIG,
                        AST__DR2D*astGetRefDec( specfrm ) );
               astPutFits( fc, card, 0 );
               sprintf( card, "MJD-OBS = %.*g", AST__DBL_DIG,
                        TDBConv( astGetEpoch( specfrm ), AST__UTC, 1,
                                 "astWrite", "FitsChan", status ) );
               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, "", status );
                  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, "", status );
                     tmap2 = (AstMapping *) astCmpMap( tmap0, tmap1, 1, "", status );
                     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, "", status );
                     tfrm = astAnnul( tfrm );

/* Construct the returned FrameSet. */
                     ret = astFrameSet( pixfrm, "", status );
                     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, "", status );
               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, status ) ){

/* 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, status ) ){

/* 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. Use "tfs" rather than "wcsfrm" when
   calling astFormat, as "wcsfrm" is not affected by the above change
   to the current frame of "tfs" (i.e. astAddFrame takes a deep copy of the
   supplied Frame). */
                     astSetC( tfs, "RefRA", astFormat( tfs, ilon, reflon ) );
                     astSetC( tfs, "RefDec", astFormat( tfs, 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 );

/* If no WcsMap was found, the celestial axes have no reference point and
   so we can retain the original spectral reference point, so just return
   the temporary FrameSet. */
               } else if( astOK ) {
                  ret = astClone( tfs );
               }
               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 );
            }
         }
      }
   }

/* Add a new current Frame into the FrameSet which increases the chances of
   the requested encoding being usable. The index of the original current
   Frame is returned, or AST__NOFRAME if no new Frame was added. */
   icurr = AddEncodingFrame( this, ret, encoding, method, class, status );

/* If a new Frame was added, remove the original current Frame. */
   if( icurr != AST__NOFRAME ) astRemoveFrame( ret, icurr );

/* 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[ AST__FITSCHAN_FITSCARDLEN -
                                              FITSNAMLEN + 1 ], int *status ) {
/*
*  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[ AST__FITSCHAN_FITSCARDLEN -
*                                            FITSNAMLEN + 1 ], int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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 = AST__FITSCHAN_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, int *status ){

/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   char card[ AST__FITSCHAN_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, status );

/* Format the new current card. */
   FormatCard( this, card, method, status );

/* 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. */
   astSetFitsCom( this, "COMMENT", card, 1 );
}

static int MakeIntWorld( AstMapping *cmap, AstFrame *fr, int *wperm, char s,
                         FitsStore *store, double *dim, double fitstol,
                         int sipok, const char *method, const char *class,
                         int *status ){
/*
*  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, double fitstol,
*                       int sipok, const char *method, const char *class,
*                       int *status )

*  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
*     supplied Mapping, which must be linear with an optional shift of
*     origin, otherwise a value of zero is returned. The exception to
*     this rule is that if the Mapping contains a PolyMap, and the "sipok"
*     argument is non-zero, an attempt is made to create a set of SIP
*     headers to describe the non-linear transformation.
*
*     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.
*     fitstol
*        The maximum departure from linearity that can be introduced by
*        "cmap" on any axis for it to be considered linear. Expressed as
*        a fraction of a grid pixel.
*     sipok
*        Flag indicating if SIP headers should be produced if there is
*        a suitable PolyMap in the Mapping chain.
*     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.
*     status
*        Pointer to the inherited status variable.

*  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;
   AstMapping *sipmap;
   AstPointSet *psetg;
   AstPointSet *psetw;
   double **fullmat;
   double **partmat;
   double **ptrw;
   double **ptrg;
   double *c;
   double *cdelt;
   double *cdmat;
   double *colvec;
   double *d;
   double *g0;
   double *g;
   double *m;
   double *mat;
   double *tol;
   double *w0;
   double *y;
   double cd;
   double cd_sip[4];
   double crp;
   double crpix_sip[2];
   double crv;
   double cv;
   double det;
   double err;
   double k;
   double mxcv;
   double skydiag0;
   double skydiag1;
   double val;
   int *iw;
   int *lin;
   int *pperm;
   int *skycol;
   int havesip;
   int i;
   int ii;
   int j;
   int jax;
   int jj;
   int lonax;
   int latax;
   int nin;
   int nout;
   int nwcs;
   int paxis;
   int ret;
   int sipax[2];
   int sing;
   int skycol0;
   int skycol1;

/* Initialise */
   ret = 0;

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

/* Avoid compiler warnings. */
   sipax[ 0 ] = 0;
   sipax[ 1 ] = 0;

/* 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( cmap );
   nout = astGetNout( cmap );
   if( nout < nin ) return ret;

/* Simplify the supplied Mapping to reduce rounding errors when
   transforming points. */
   map = astSimplify( cmap );

/* See if the WCS Frame contains a SkyFrame, and if so get the indices of
   the Mapping outputs that correspond to the longitude and latitude
   axes. */
   lonax = -1;
   latax = -1;
   for( i = 0; i < nout; i++ ) {
      astPrimaryFrame( fr, i, &pfrm, &paxis );
      if( astIsASkyFrame( pfrm ) ) {
         if( paxis == 0 ) {
            lonax = i;
         } else {
            latax = i;
         }
      }
      pfrm = astAnnul( pfrm );
   }

/* If there is a pair of celestial axes in the WCS Frame, and if the
   celestial axes can be described using SIP distortion, then put the
   headers describing the SIP distortion into the FitsStore. This also
   returns the CRPIX and CD values to use with the celestial axes. */
   havesip = 0;
   if( sipok && lonax != -1 ){
      sipmap = SIPIntWorld( map, fitstol, lonax, latax, s, store, dim, sipax,
                            crpix_sip, cd_sip, method, class, status );

/* If SIP headers were stored successfully, use the modified mapping from
   now on. This is the same as "map" but does not include the PolyMap
   from which the SIP headers were determined. This is done so that he
   PolyMap does not break the linearity test performed below in order to
   determine CRPIX and CD values for any other non-celestial axes. */
      if( sipmap ) {
         (void) astAnnul( map );
         map = sipmap;
         havesip = 1;
      }
   }

/* 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 );
   tol = 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, "", status );
   ptrg = astGetPoints( psetg );
   psetw = astPointSet( nin + 1, nout, "", status );
   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, status );

/* 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 ];

/* Find the tolerance for positions on the j'th IWC axis. This is a
   fraction "fitstol" of the largest change in the j'th IWC axis value
   caused by moving out 1 pixel along any grid axis. */
         tol[ j ] = 0.0;
         for( i = 0; i < nin; i++ ) {
            err = fabs( ptrw[ j ][ i + 1 ] - w0[ j ] );
            if( err > tol[ j ] ) tol[ j ] = err;
         }
         tol[ j ] *= fitstol;

/* If the tolerance is zero (e.g. as is produced for degenerate axes),
   then use a tolerance equal to a very small fraction of hte degenerate
   axis value. If the axis value is zero use a fixed small value. */
         if( tol[ j ] == 0.0 ) tol[ j ] = w0[ j ]*DBL_EPSILON*1.0E5;
         if( tol[ j ] == 0.0 ) tol[ j ] = sqrt( DBL_MIN );
      }

/* 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 ], tol, status );

/* If unsuccesful, indicate failure and break out of the loop. */
         if( !partmat[ i ] ) {
            ret = 0;
            break;
         }
      }

/* If we are using SIP distortion, replace the values for the celestial
   axes found above with the values found by SIPIntWorld. */
      if( havesip ) {
         partmat[ sipax[0] ][ lonax ] = cd_sip[ 0 ];
         partmat[ sipax[1] ][ lonax ] = cd_sip[ 1 ];
         partmat[ sipax[0] ][ latax ] = cd_sip[ 2 ];
         partmat[ sipax[1] ][ latax ] = cd_sip[ 3 ];
      }

/* 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 do this if the
   Mapping is linear. */
      if( ret ) {
         fullmat = OrthVectorSet( nout, nin, partmat, status );
         if( !fullmat ) ret = 0;
      }

/* Check everything is OK. */
      if( ret ) {

/* Check that the full matrix is invertable, and if not, see if there is
   any way to make it invertable. */
         MakeInvertable( fullmat, nout, dim, status );

/* 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( IsASkyFrame( pfrm ) ) {
               skycol[ wperm[ i ] ] = paxis + 1;
               lin[ i ] = 0;
               if( !sfrm ) {
                  sfrm = astClone( pfrm );
                  skycol0 = wperm[ i ];
                  skydiag0 = fullmat[ skycol0 ][ i ];
               } else if( sfrm == pfrm ) {
                  skycol1 = wperm[ i ];
                  skydiag1 = fullmat[ skycol1 ][ i ];
               }
            } 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 palDmat 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 ];
            }
            palDmat( 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 palDmat.
   First deal with axes for which there are Mapping inputs. If we are
   using SIP distortion, replace the crpix values for the celestial
   axes found above with the values found by SIPIntWorld. */
            if( j < nin ) {
               crp = g0[ j ] - y[ j ];
               if( havesip ) {
                  if( j == sipax[0] ){
                     crp = crpix_sip[0];
                  } else if( j == sipax[1] ){
                     crp = crpix_sip[1];
                  }
               }

/* 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, status );

/* 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( !astEQUAL( 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, status );
            }

/* 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( !astEQUAL( 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( !astEQUAL( 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, status );
                     cd = cdmat[ wperm[ i ]*nout + jax ];
                     crp = GetItem( &(store->crpix), 0, jax, s, NULL, method, class, status );
                     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, status );
                        SetItem( &(store->crval), wperm[ i ], 0, s,
                                 cd*( 1.0 - crp ) + crv, status );
                     }
                  }
               }
            }
         }

/* 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, status );

/* Release resources. */
         y = astFree( y );
      }

/* Produce and store PC and CDELT values from the above CD matrix */
      SplitMat( nout, cdmat, cdelt, status );
      c = cdmat;
      d = cdelt;
      for( i = 0; i < nout; i++ ){
         for( j = 0; j < nout; j++ ){
            val = *(c++);
            if( i == j ){
               if( astEQUAL( val, 1.0 ) ) val = AST__BAD;
            } else {
               if( astEQUAL( val, 0.0 ) ) val = AST__BAD;
            }
            if( val != AST__BAD ) SetItem( &(store->pc), i, j, s, val, status );
         }
         SetItem( &(store->cdelt), i, 0, s, *(d++), status );
      }
   }

/* 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 );
   tol = astFree( tol );
   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 void MakeInvertable( double **fullmat, int n, double *dim, int *status ){
/*
*  Name:
*     MakeInvertable

*  Purpose:
*     Modify a supplied square CD matrix if possible to make it invertable.

*  Type:
*     Private function.

*  Synopsis:
*     void MakeInvertable( double **fullmat, int n, double *dim, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     A search is made for matrix inputs that have no effect on any
*     matrix outputs. if any such matrix inputs are associated with
*     degenerate pixel axes (i.e. pixel axes that span only a single
*     pixel), then the matrix input should always have the value zero and
*     so the corresponding diagonal element of the matrix can be set to
*     1.0 without changing and of the outputs.

*  Parameters:
*     fullmat
*        A pointer to an array with "n" elements corresponding to the n
*        inputs of the matrix, each element being a pointer to an array
*        with "n" elements corresponding to the n outputs of the matrix.
*     n
*        The number of inputs and outputs for the square matrix.
*     dim
*        Pointer to an array of "n" input (i.e. pixel) axis dimensions.
*        Individual elements will be AST__BAD if dimensions are not known.
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   int i;          /* Input index */
   int j;          /* Output index */
   int unused;     /* Does the current input have no effect on any output? */

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

/* Look for any inputs that have no effect on any of the outputs. If such
   an input is associated with a degenerate grid axis (i.e. a grid axis
   with a dimension of 1.0), then the input value will always be zero and
   so the corresponding diagonal element of the matrix can eb set to 1.0
   without affecting the output value (which will always be zero since
   zero times anything is zero). Loop over all inputs. */
   for( i = 0; i < n; i++ ) {

/* Assume this input has no effect on any output. */
      unused = 1;

/* Loop over all outputs. */
      for( j = 0; j < n; j++ ) {

/* If the corresponding matrix term is non-zero, the the input will have
   an effect on the output, so set the unused flag false and break out of
   the output loop. */
         if( fullmat[ i ][ j ] != 0.0 ) {
            unused = 0;
            break;
         }
      }

/* If the input is unused, and it is associated with a degenerate pixel
   axis, we can set the corresponding diagonal element of the matrix to
   1.0. */
      if( unused && dim[ i ] == 1.0 ) fullmat[ i ][ i ] = 1.0;
   }
}
#if defined(THREAD_SAFE)

static int ManageLock( AstObject *this_object, int mode, int extra,
                       AstObject **fail, int *status ) {
/*
*  Name:
*     ManageLock

*  Purpose:
*     Manage the thread lock on an Object.

*  Type:
*     Private function.

*  Synopsis:
*     #include "object.h"
*     AstObject *ManageLock( AstObject *this, int mode, int extra,
*                            AstObject **fail, int *status )

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

*  Description:
*     This function manages the thread lock on the supplied Object. The
*     lock can be locked, unlocked or checked by this function as
*     deteremined by parameter "mode". See astLock for details of the way
*     these locks are used.

*  Parameters:
*     this
*        Pointer to the Object.
*     mode

*        An integer flag indicating what the function should do:
*
*        AST__LOCK: Lock the Object for exclusive use by the calling
*        thread. The "extra" value indicates what should be done if the
*        Object is already locked (wait or report an error - see astLock).
*
*        AST__UNLOCK: Unlock the Object for use by other threads.
*
*        AST__CHECKLOCK: Check that the object is locked for use by the
*        calling thread (report an error if not).
*     extra
*        Extra mode-specific information.
*     fail
*        If a non-zero function value is returned, a pointer to the
*        Object that caused the failure is returned at "*fail". This may
*        be "this" or it may be an Object contained within "this". Note,
*        the Object's reference count is not incremented, and so the
*        returned pointer should not be annulled. A NULL pointer is
*        returned if this function returns a value of zero.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:

*    A local status value:
*        0 - Success
*        1 - Could not lock or unlock the object because it was already
*            locked by another thread.
*        2 - Failed to lock a POSIX mutex
*        3 - Failed to unlock a POSIX mutex
*        4 - Bad "mode" value supplied.

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

/* Local Variables: */
   AstFitsChan *this;         /* Pointer to FitsChan structure */
   int result;                /* Returned status value */

/* Initialise */
   result = 0;

/* Check the supplied pointer is not NUL. */
   if( ! this_object ) return result;

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

/* Invoke the ManageLock method inherited from the parent class. */
   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
                                                fail, status );

/* Invoke the astManageLock method on any Objects contained within
   the supplied Object. */
   if( !result ) result = astManageLock( this->keyseq, mode, extra, fail );
   if( !result ) result = astManageLock( this->keywords, mode, extra, fail );
   return result;
}
#endif

static int Match( const char *test, const char *temp, int maxfld, int *fields,
                  int *nfld, const char *method, const char *class, int *status ){
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     Zero is returned if the test string does not match the template
*     string, and one is returned if it does.
*/

/* Local Variables: */
   astDECLARE_GLOBALS     /* Declare the thread specific global data */
   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 */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(NULL);

/* 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( !match_nentry ) {
      *nfld = 0;
      match_template = temp;
   }

/* Increment the number of entries into this function. */
   match_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, &match_na, &match_nb, method, class, match_template, status ) ){

/* If it does, increment the pointers to skip over the characters
   used up in the comparison. */
      a += match_na;
      b += match_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, match_template, status ) ) 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, status ) ){
            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, match_template, status ) ){
               a++;
               if( Match( a, b, maxfld, fields, nfld, method, class, status ) ){
                  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( match_fmt, "%%%dd", (int) ( a - test ) );
         astSscanf( test, match_fmt, fields + *nfld );
      }
      (*nfld)++;
   }

/* Decrement the number of entries into this function. */
   match_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( !match_nentry ){
      nfret = ( *nfld < maxfld ) ? (*nfld) : maxfld;
      match_pa = fields;
      match_pb = fields + nfret - 1;
      for( i = 0; i < nfret/2; i++ ){
         tmp = *match_pa;
         *(match_pa++) = *match_pb;
         *(match_pb--) = tmp;
      }
   }

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

static int MatchChar( char test, char type, const char *method,
                      const char *class, const char *template, int *status ){
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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'.", status,
                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, int *status ){
/*
*  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'.", status, 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, status ) ){
               match = 0;
               break;
            }
            a++;
         }
      }
   }

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

static void MarkCard( AstFitsChan *this, int *status ){

/*
*  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, int *status )

*  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 having 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.
*     status
*        Pointer to the inherited status variable.

*  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, but only if the
   PROTECTED flag is not set. */
   flags = ( (FitsCard *) this->card )->flags;
   if( !( flags & PROTECTED ) ) {
      ( (FitsCard *) this->card )->flags = flags | PROVISIONALLY_USED;
   }
}

static int MoveCard( AstFitsChan *this, int move, const char *method,
                      const char *class, int *status ){

/*
*  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, int *status )

*  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 ignore_used 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.
*     status
*        Pointer to the inherited status variable.

*  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: */
   astDECLARE_GLOBALS      /* Declare the thread specific global data */
   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 structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* 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, status );

/* 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 "ignore_used" 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( !ignore_used ){
            card = GetLink( card, PREVIOUS, method, class, status );

/* 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, status );
            while( card0 && CARDUSED(card0) && (void *) card0 != this->head ){
               card0 = GetLink( card0, PREVIOUS, method, class, status );
            }

/* 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, int *status ){
/*
*  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, int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  Retuned Value:
*     The modified output axis value.
*/

/* Local Variables: */
   AstMapping *tmap;       /* Mapping to be used */
   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 *ins;               /* Array holding input axis indices */
   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;

/* If the supplied Mapping has no inverse, trying splitting off the
   transformation for the required axis, which may have an inverse.
   If succesful, use the 1-in,1-out Mapping returned by astMapSPlit
   instead of the supplied Mapping, and adjust the axis index accordingly. */
   if( !astGetTranInverse( map ) ) {
      astInvert( map );
      ins = astMapSplit( map, 1, &axis, &tmap );
      if( tmap ) {
         astInvert( tmap );
         axis = 0;
      } else {
         tmap = astClone( map );
      }
      ins = astFree( ins );
      astInvert( map );
   } else {
      tmap = astClone( map );
   }

/* If the Mapping still has no inverse, return the supplied value
   unchanged. */
   if( astGetTranInverse( tmap ) ) {

/* Get the number of input and output coordinates. */
      nin = astGetNin( tmap );
      nout = astGetNout( tmap );

/* Create PointSets to hold a single input position and the corresponding
   output position. */
      pset1 = astPointSet( 1, nin, "", status );
      ptr1 = astGetPoints( pset1 );
      pset2 = astPointSet( 1, nout, "", status );
      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. */
         (void) astTransform( tmap, 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. */
         (void) astTransform( tmap, 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 );
   }
   tmap = astAnnul( tmap );

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

static void NewCard( AstFitsChan *this, const char *name, int type,
                     const void *data, const char *comment, int flags,
                     int *status ){

/*
*  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,
*                   int *status )

*  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.
*     status
*        Pointer to the inherited status variable.

*  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;

/* Ensure that a KeyMap exists to hold the keywords currently in the
   FitsChan. */
      if( !this->keywords ) this->keywords = astKeyMap( " ", status );

/* Add the keyword name to the KeyMap. The value associated with the
   KeyMap entry is not used and is set arbitrarily to zero. */
      astMapPut0I( this->keywords, new->name, 0, NULL );

/* Copy the data type. */
      new->type = type;

/* Copy any data (ignore any data supplied for an UNDEF value). */
      if( data && type != AST__UNDEF ){

/* 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 ){
            if( *( (double *) data ) != AST__BAD ) {
               new->size = 2*sizeof( double );
               new->data = astStore( NULL, (void *) data, 2*sizeof( double ) );
            } else {
               nc = strlen( BAD_STRING );
               new->size = (size_t)( nc + 1 );
               new->data = astStore( NULL, BAD_STRING, (size_t)( nc + 1 ) );
               ( (char *) new->data)[ nc ] = 0;
            }
         } 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, status );
         } 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->flags = flags;

/* Insert the copied card into the list, in front of the current card. If
   the current card is the list head, make the new card the list head. */
      if( this->card ){
         prev = ( ( FitsCard *) this->card )->prev;
         ( ( FitsCard *) this->card )->prev = new;
         new->prev = prev;
         prev->next = new;
         new->next = (FitsCard *) this->card;
         if( this->card == this->head ) this->head = (void *) new;

/* If the FitsChan is at end-of-file, append the new card to the end of
   the list (i.e. insert it just before the list head). */
      } else {
         if( this->head ){
            prev = ( (FitsCard *) this->head )->prev;
            ( (FitsCard *) this->head )->prev = new;
            new->prev = prev;
            prev->next = new;
            new->next = (FitsCard *) this->head;

/* If there are no cards in the list, start a new list. */
         } else {
            new->prev = new;
            new->next = new;
            this->head = (void *) new;
            this->card = NULL;
         }
      }
   }

/* Return. */
   return;
}

static AstMapping *NonLinSpecWcs( AstFitsChan *this, char *algcode,
                                  FitsStore *store, int i, char s,
                                  AstSpecFrame *specfrm, const char *method,
                                  const char *class, int *status ) {

/*
*  Name:
*     NonLinSpecWcs

*  Purpose:
*     Create a Mapping describing a FITS-WCS non-linear spectral algorithm

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"

*     AstMapping *NonLinSpecWcs( AstFitsChan *this, char *algcode,
*                                FitsStore *store, int i, char s,
*                                AstSpecFrame *specfrm, const char *method,
*                                const char *class, int *status )

*  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 non-linear "X2P" algorithms
*     described in FITS-WCS paper III. The axis is linearly sampled in
*     system "X" but expressed in some other system (specified by the
*     supplied SpecFrame).

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     algcode
*        Pointer to a string holding the non-linear "-X2P" code for the
*        required algorithm. This includes aleading "-" character.
*     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 specified 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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to a Mapping, or NULL if an error occurs.
*/

/* Local Variables: */
   AstFrameSet *fs;
   AstMapping *map1;
   AstMapping *ret;
   AstSpecFrame *xfrm;
   AstMapping *map2;
   char buf[ 100 ];
   char pc;
   double crv;
   double ds;
   double in_a;
   double in_b;
   double out_a;
   double out_b;
   int ok;
   int s_sys;

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

/* Identify the spectral "X" system within the "X2P" algorithm code, and
   create a SpecFrame describing the X system ("X" is the system in
   which the axis is linearly sampled). This is done by copying the
   supplied SpecFrame and then setting its System attribute. Copying
   the supplied SpecFrame ensures that all the other attributes (RestFreq,
   etc.) are set correctly. */
   ok = 1;
   xfrm = astCopy( specfrm );
   if( algcode[ 1 ] == 'F' ) {
      astSetSystem( xfrm, AST__FREQ );
      astSetUnit( xfrm, 0, "Hz" );
   } else if( algcode[ 1 ] == 'W' ) {
      astSetSystem( xfrm, AST__WAVELEN );
      astSetUnit( xfrm, 0, "m" );
   } else if( algcode[ 1 ] == 'V' ) {
      astSetSystem( xfrm, AST__VREL );
      astSetUnit( xfrm, 0, "m/s" );
   } else if( algcode[ 1 ] == 'A' ) {
      astSetSystem( xfrm, AST__AIRWAVE );
      astSetUnit( xfrm, 0, "m" );
   } else {
      ok = 0;
   }

/* If the X system was identified, find a Mapping from the "S" (specfrm)
   system to the X system. */
   map1 = NULL;
   if( ok ) {
      ok = 0;
      fs = astConvert( specfrm, xfrm, "" );
      if( fs ) {
         map1 = astGetMapping( fs, AST__BASE, AST__CURRENT );
         fs = astAnnul( fs );
         ok = 1;
      }

/* Issue a warning if the "P" system is not the correct one for the given
   "S" system. We can however continue, sine AST interprets illegal "P"
   systems correctly. */
      pc = 0;
      s_sys = astGetSystem( specfrm );
      if( s_sys == AST__FREQ || s_sys == AST__ENERGY ||
          s_sys == AST__WAVENUM ||  s_sys == AST__VRADIO ) {
         pc = 'F';
      } else if( s_sys == AST__WAVELEN || s_sys == AST__VOPTICAL ||
                 s_sys == AST__REDSHIFT ){
         pc = 'W';
      } else if( s_sys == AST__AIRWAVE ) {
         pc = 'A';
      } else if( s_sys == AST__BETA || s_sys == AST__VREL ) {
         pc = 'V';
      } else if( astOK ) {
         pc = algcode[ 3 ];
         astError( AST__INTER, "%s: Function NonLinSpecWcs does not yet "
                   "support spectral axes of type %s (internal AST "
                   "programming error).", status, method,
                   astGetC( specfrm, "System" ) );
      }
      if( algcode[ 3 ] != pc ) {
         const char *text = astGetC( specfrm, "System" );
         if( text ) {
            sprintf( buf, "The spectral CTYPE value %s%s is not legal - using"
                     " %s%.3s%c instead.", text, algcode, text, algcode, pc );
            Warn( this, "badctype", buf, method, class, status );
         }
      }
   }

/* If succesfull, use this Mapping to find the reference value (CRVAL)
   in the "X" system. */
   if( ok ) {

/* 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, status );
      if( crv == AST__BAD ) crv = 0.0;

/* Convert it to the X system. */
      astTran1( map1, 1, &crv, 1, &crv );

/* Invert this Mapping so that it forward transformation goes from X to S. */
      astInvert( map1 );

/* Find the rate of change of S with respect to X (dS/dX) at the reference
   point (x = crv). */
      ds = astRate( map1, &crv, 0, 0 );
      if( ds != AST__BAD && ds != 0.0 ) {

/* FITS-WCS paper III says that dS/dw must be 1.0 at the reference point.
   Therefore dX/dw = dX/dS at the reference point. Also, since the spectral
   axis is linear in X, dX/dw must be constant. Therefore the Mapping from
   IWC to X is a WinMap which scales the IWC axis ("w") by dX/dw and adds
   on the X value at the reference point. */
         if( crv != 0.0 ) {
            in_a = 0.0;
            out_a = crv;
            in_b = crv*ds;
            out_b = 2.0*crv;
            map2 = (AstMapping *) astWinMap( 1, &in_a, &in_b, &out_a, &out_b, "", status );
         } else {
            map2 = (AstMapping *) astZoomMap( 1, 1.0/ds, "", status );
         }

/* The Mapping to be returned is the concatenation of the above Mapping
   (from w to X) with the Mapping from X to S. */
         ret = (AstMapping *) astCmpMap( map2, map1, 1, "", status );
         map1 = astAnnul( map1 );
         map2 = astAnnul( map2 );
      }
   }
   xfrm = astAnnul( xfrm );

/* Return the result */
   return ret;
}

static double *OrthVector( int n, int m, double **in, int *status ){
/*
*  Name:
*     OrthVector

*  Purpose:
*     Find a unit vector which is orthogonal to a set of supplied vectors.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     double *OrthVector( int n, int m, double **in, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     A set of M vectors is supplied, each vector being N-dimensional.
*     It is assumed that M < N and that the supplied vectors span M
*     axes within the N dimensional space. An N-dimensional unit vector is
*     returned which is orthogonal to all the supplied vectors.
*
*     The required vector is orthogonal to all the supplied vectors.
*     Therefore the dot product of the required vector with each of the
*     supplied vectors must be zero. This gives us M equations of the

*     form:
*
*     a1*r1 + a2*r2 + a3*r3 + .... + aN*rN = 0.0
*     b1*r1 + b2*r2 + b3*r3 + .... + bN*rN = 0.0
*     ...
*
*     where (a1,a2,..,aN), (b1,b2,..,bN), ... are the supplied vectors
*     and (r1,r2,...,rN) is the required vector. Since M is less
*     than N the system of linear simultaneous equations is under
*     specified and we need to assign arbitrary values to some of the
*     components of the required vector in order to allow the equations
*     to be solved. We arbitrarily assume that 1 element of the required
*     vector has value 1.0 and (N-M-1) have value zero. The selection of
*     *which* elements to set constant is based on the magnitudes of the
*     columns of coefficients (a1,b1...), (a2,b2,...), etc. The M components
*     of the required vector which are *not* set constant are the ones which
*     have coefficient columns with the *largest* magnitude. This choice is
*     made in order to minimise the risk of the remaining matrix of
*     coefficients being singular (for instance, if a component of the
*     required vector has a coefficient of zero in every supplied vector
*     then the column magnitude will be zero and that component will be
*     set to 1.0). After choosing the M largest columns, the largest
*     remaining column is assigned a value of 1.0 in the required vector,
*     and all other columns are assigned the value zero in the required

*     vector. This means that the above equations becomes:
*
*     a1*r1 + a2*r2 + a3*r3 + .... + aM*rM = -aM+1
*     b1*r1 + b2*r2 + b3*r3 + .... + bM*rM = -bM+1
*     ...
*
*     Where the indices are now not direct indices into the supplied and
*     returned vectors, but indices into an array of indices which have
*     been sorted into column magnitude order. This is now a set of MxM

*     simultaneous linear equations which we can solve using palDmat:
*
*     MAT.R = V
*
*     where MAT is the the matrix of columns (coefficients) on the left
*     hand side of the above set of simultaneous equations, R is the
*     required vector (just the components which have *not* been set
*     constant), and V is a constant vector equal to the column of values
*     on the right hand side in the above set of simultaneous equations.
*     The palDmat function solves this equation to obtain R.

*  Parameters:
*     n
*        The number of dimensions
*     m
*        The number of supplied vectors.
*     in
*        A pointer to an array with "m" elements, each element being a
*        pointer to an array with "n" elements. Each of these "n" element
*        array holds one of the supplied vectors.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     The pointer to some newly allocated memory holding the returned N
*     dimensional unit vector. The memory should be freed using astFree when
*     no longer needed.

*  Notes:
*     -  NULL is returned if an error occurs.
*     -  NULL is returned (without error) if the required vector cannot
*     be found (.e.g becuase the supplied M vectors span less than M axes).
*/

/* Local Variables: */
   double *colmag;
   double *d;
   double *e;
   double *mat;
   double *mel;
   double *ret;
   double *rhs;
   double det;
   double sl;
   int *colperm;
   int *iw;
   int done;
   int i;
   int ih;
   int ii;
   int il;
   int j;
   int sing;

/* Initialise */
   ret = NULL;

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

/* Return if any of the M supplied vectors are NULL. */
   for( i = 0; i < m; i++ ) {
      if( !in[ i ] ) return ret;
   }

/* Allocate rquired memory. */
   ret = astMalloc( sizeof( double )*(size_t) n );
   rhs = astMalloc( sizeof( double )*(size_t) m );
   mat = astMalloc( sizeof( double )*(size_t) m*m );
   iw = astMalloc( sizeof( int )*(size_t) m );
   colmag = astMalloc( sizeof( double )*(size_t) n );
   colperm = astMalloc( sizeof( int )*(size_t) n );

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

/* Find the magnitude of each column of coefficients in the full set of
   simultaneous linear equations (before setting any components of the
   required vector constant). Also initialise the column permutation array
   to indicate that the columns are in their original order. The outer
   loop loops through the columns and the inner loop loops through rows
   (i.e. equations). */
      for( i = 0; i < n; i++ ) {
         colperm[ i ] = i;
         colmag[ i ] = 0.0;
         for( j = 0; j < m; j++ ) {
            colmag[ i ] += in[ j ][ i ]*in[ j ][ i ];
         }
      }

/* Now re-arrange the column indices within the permutation array so that
   they are in order of decreasing ciolumn magnitude (i.e. colperm[0] will
   be left holding the index of the column with the largest magnitude). A
   simple bubble sort is used. */
      ii = n;
      done = 0;
      while( !done ) {
         done = 1;
         for( i = 1; i < ii; i++ ) {
            ih = colperm[ i ];
            il = colperm[ i - 1 ];
            if( colmag[ ih ] > colmag[ il ] ) {
               colperm[ i ] = il;
               colperm[ i - 1 ] = ih;
               done = 0;
            }
         }
         ii--;
      }

/* The first M elements in "colperm" now hold the indices of the
   columns which are to be used within the MAT matrix, the next element
   of "colperm" hold the index of the column which is to be included in the
   V vector (other elements hold the indices of the columns which are
   being ignored because they will be mutiplied by a value of zero - the
   assumed value of the corresponding components of the returned vector). We
   now copy the these values into arrays which can be passed to palDmat.
   First, initialise a pointer used to step through the mat array. */
      mel = mat;

/* Loop through all the supplied vectors. Get a pointer to the first
   element of the vector. */
      for( i = 0; i < m; i++ ) {
         d = in[ i ];

/* Copy the required M elements of this supplied vector into the work array
   which will be passed to palDmat. */
         for( j = 0; j < m; j++ ) *(mel++) = d[ colperm[ j ] ];

/* Put the next right-hand side value into the "rhs" array. */
         rhs[ i ] = -d[ colperm[ m ] ];
      }

/* Use palDmat to find the first M elements of the returned array. These
   are stored in "rhs", over-writing the original right-hand side values. */
      palDmat( m, mat, rhs, &det, &sing, iw );

/* If the supplied vectors span fewer than M axes, the above call will fail.
   In this case, annul the returned vector. */
      if( sing != 0 ) {
         ret = astFree( ret );

/* If succesful, copy the M elements of the solution vector into the
   required M elements of the returned vector. Also find the squared length
   of the vector. */
      } else {
         sl = 0.0;
         e = rhs;
         for( j = 0; j < m; j++ ) {
            sl += (*e)*(*e);
            ret[ colperm[ j ] ] = *(e++);
         }

/* Put 1.0 into the next element of the returned vector. */
         sl += 1.0;
         ret[ colperm[ m ] ] = 1.0;

/* Fill up the rest of the returned vector with zeros. */
         for( j = m + 1; j < n; j++ ) ret[ colperm[ j ] ] = 0.0;

/* Normalise the returned vector so that it is a unit vector.Also ensure
   that any zeros are "+0.0" insteasd of "-0.0". */
         e = ret;
         sl = sqrt( sl );
         for( j = 0; j < n; e++,j++ ) {
            *e /= sl;
            if( *e == 0.0 ) *e = 0.0;
         }
      }
   }

/* Free workspace. */
   rhs = astFree( rhs );
   mat = astFree( mat );
   iw = astFree( iw );
   colmag = astFree( colmag );
   colperm = astFree( colperm );

/* Free the returned vector if an error has occurred. */
   if( !astOK ) ret = astFree( ret );

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

static double **OrthVectorSet( int n, int m, double **in, int *status ){
/*
*  Name:
*     OrthVectorSet

*  Purpose:
*     Find a set of mutually orthogonal vectors.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     double **OrthVectorSet( int n, int m, double **in, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     A set of M vectors is supplied, each vector being N-dimensional.
*     It is assumed that the supplied vectors span M axes within the
*     N dimensional space. A pointer to a set of N vectors is returned.
*     The first M returned vectors are copies of the M supplied vectors.
*     The remaining returned vectors are unit vectors chosen to be
*     orthogonal to all other vectors in the returned set.

*  Parameters:
*     n
*        The number of dimensions
*     m
*        The number of supplied vectors.
*     in
*        A pointer to an array with "m" elements, each element being a
*        pointer to an array with "n" elements. Each of these "n" element
*        array holds one of the supplied vectors.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     The pointer to some newly allocated memory holding the returned N
*     vectors. The pointer locates an array of N elements, each of which
*     is a pointer to an array holding the N elements of a single vector.
*     The memory (including the inner pointers) should be freed using
*     astFree when no longer needed.

*  Notes:
*     -  NULL is returned if an error occurs.
*     -  NULL is returned (without error) if the required vectors cannot
*     be found (e.g. becuase the supplied M vectors span less than M axes).
*/

/* Local Variables: */
   double **ret;
   int i;
   int bad;

/* Initialise */
   ret = NULL;

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

/* Allocate required memory. */
   ret = astMalloc( sizeof( double * )*(size_t) n );

/* Check memory can be used safely. */
   bad = 0;
   if( astOK ) {

/* Copy the supplied vectors into the returned array. */
      for( i = 0; i < m; i++ ) {
         ret[ i ] = astStore( NULL, in[ i ], sizeof( double )*n );
      }

/* For the remaining vectors, find a vector which is orthogonal to all
   the vectors currently in the returned set. */
      for( ; i < n; i++ ) {
         ret[ i ] = OrthVector( n, i, ret, status );
         if( !ret[ i ] ) bad = 1;
      }
   }

/* Free the returned vectors if an error has occurred. */
   if( bad || !astOK ) {
      for( i = 0; ret && i < n; i++ ) ret[ i ] = astFree( ret[ i ] );
      ret = astFree( ret );
   }

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

static AstMapping *OtherAxes( AstFitsChan *this, AstFrameSet *fs, double *dim,
                              int *wperm, char s, FitsStore *store,
                              double *crvals, int *axis_done,
                              const char *method, const char *class,
                              int *status ){

/*
*  Name:
*     OtherAxes

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

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"

*     AstMapping *OtherAxes( AstFitsChan *this, AstFrameSet *fs, double *dim,
*                            int *wperm, char s, FitsStore *store,
*                            double *crvals, int *axis_done,
*                            const char *method, const char *class,
*                            int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     FITS WCS keyword values are added to the supplied FitsStore which
*     describe any as yet undescribed axes in the supplied FrameSet. These
*     axes are assumed to be linear and to follow the conventions
*     of FITS-WCS paper I (if in fact they are not linear, then the
*     grid->iwc mapping determined by MakeIntWorld will not be linear and
*     so the axes will be rejected).
*
*     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:
*     this
*        Pointer to the FitsChan.
*     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.
*     crvals
*        Pointer to an array holding the default CRVAL value for each
*        axis in the WCS Frame.
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     If any axis descriptions were added to the FitsStore, 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 the described axes. Any other (previously
*     described) axes are passed unchanged by the returned Mapping.
*/

/* Local Variables: */
   AstFitsTable *table;    /* Pointer to structure holding -TAB table info */
   AstFrame *wcsfrm;       /* WCS Frame within FrameSet */
   AstMapping *axmap;      /* Mapping from WCS to IWC */
   AstMapping *map;        /* FITS pixel->WCS Mapping */
   AstMapping *ret;        /* Returned Mapping */
   AstMapping *tmap0;      /* Pointer to a temporary Mapping */
   AstMapping *tmap1;      /* Pointer to a temporary Mapping */
   AstPermMap *pm;         /* PermMap pointer */
   AstPointSet *pset1;     /* PointSet holding central pixel position */
   AstPointSet *pset2;     /* PointSet holding reference WCS position */
   char buf[80];           /* Text buffer */
   const char *lab;        /* Pointer to axis Label */
   const char *sym;        /* Pointer to axis Symbol */
   double **ptr1;          /* Pointer to data for pset1 */
   double **ptr2;          /* Pointer to data for pset2 */
   double *lbnd_p;         /* Pointer to array of lower pixel bounds */
   double *ubnd_p;         /* Pointer to array of upper pixel bounds */
   double crval;           /* The value for the FITS CRVAL keyword */
   int *inperm;            /* Pointer to permutation array for input axes */
   int *outperm;           /* Pointer to permutation array for output axes */
   int extver;             /* Table version number for -TAB headers */
   int fits_i;             /* FITS WCS axis index */
   int force_tab;          /* Use TAB even if another code could be used? */
   int i;                  /* Loop count */
   int iax;                /* WCS Frame axis index */
   int icolindex;          /* Index of table column holding index vector */
   int icolmain;           /* Index of table column holding main coord array */
   int interp;             /* Interpolation method for look-up tables */
   int log_axis;           /* Is the axis logarithmically spaced? */
   int nother;             /* Number of axes still to be described */
   int npix;               /* Number of pixel axes */
   int nwcs;               /* Number of WCS axes */
   int tab_axis;           /* Can the axis be described by the -TAB algorithm? */

/* Initialise */
   ret = NULL;

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

/* Get the number of WCS axes. */
   nwcs = astGetNaxes( fs );

/* Count the number of WCS axes which have not yet been described. */
   nother = 0;
   for( iax = 0; iax < nwcs; iax++ ) {
      if( ! axis_done[ iax ] ) nother++;
   }

/* Only proceed if there are some axes to described. */
   axmap = NULL;
   if( nother ) {

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

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

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

/* Store the upper and lower pixel bounds. */
      lbnd_p = astMalloc( sizeof( double )*(size_t) npix );
      ubnd_p = astMalloc( sizeof( double )*(size_t) npix );
      if( astOK ) {
         for( iax = 0; iax < npix; iax++ ) {
            lbnd_p[ iax ] = 1.0;
            ubnd_p[ iax ] = ( dim[ iax ] != AST__BAD ) ? dim[ iax ] : 500;
         }
      }

/* Transform the central pixel coords into WCS coords */
      pset1 = astPointSet( 1, npix, "", status );
      ptr1 = astGetPoints( pset1 );
      pset2 = astPointSet( 1, nwcs, "", status );
      ptr2 = astGetPoints( pset2 );
      if( astOK ) {
         for( iax = 0; iax < npix; iax++ ) {
            ptr1[ iax ][ 0 ] = ( dim[ iax ] != AST__BAD ) ? floor( 0.5*dim[ iax ] ) : 1.0;
         }
         (void) astTransform( map, pset1, 1, pset2 );
      }

/* Loop round all WCS axes, producing descriptions of any axes which have not
   yet been described. */
      for( iax = 0; iax < nwcs && astOK; iax++ ) {
         if( ! axis_done[ iax ] ) {

/* Get the (one-based) FITS WCS axis index to use for this Frame axis. */
            fits_i = wperm[ iax ];

/* Use the supplied default CRVAL value. If bad, use the WCS value
   corresponding to the central pixel found above (if this value is bad,
   abort). */
            crval = crvals ? crvals[ iax ] : AST__BAD;
            if( crval == AST__BAD ) crval = ptr2[ iax ][ 0 ];
            if( crval == AST__BAD ) {
               break;
            } else {
               SetItem( &(store->crval), fits_i, 0, s, crval, status );
            }

/* Initialise flags indicating the type of the axis. */
            log_axis = 0;
            tab_axis = 0;

/* Get the table version number to use if we end up using the -TAB
   algorithm. This is the set value of the TabOK attribute (if positive). */
            extver = astGetTabOK( this );

/* If the ForceTab attribute is set, we jump on immediately to using
   -TAB regardless of whether some other algorithm could be used. */
            force_tab = astGetForceTab( this );

/* See if the axis is linear. If so, create a ShiftMap which subtracts off
   the CRVAL value. */
            if( !force_tab && IsMapLinear( map, lbnd_p, ubnd_p, iax, NULL, status ) ) {
               crval = -crval;
               tmap0 = (AstMapping *) astShiftMap( 1, &crval, "", status );
               axmap = AddUnitMaps( tmap0, iax, nwcs, status );
               tmap0 = astAnnul( tmap0 );
               crval = -crval;

/* If it is not linear, see if it is logarithmic. If the "log" algorithm is
   appropriate (as defined in FITS-WCS paper III), the supplied Frame (s) is
   related to pixel coordinate (p) by
      s = Sr.EXP( a*p - b ). If this
   is the case, the log of s will be linearly related to pixel coordinates.
   Test this. If the test is passed a Mapping is returned from WCS to IWC. */
            } else if( !force_tab && (axmap = LogAxis( map, iax, nwcs, lbnd_p,
                                                 ubnd_p, crval, status ) ) ) {
               log_axis = 1;

/* If it is not linear or logarithmic, and the TabOK attribute is
   non-zero, describe it using the -TAB algorithm. */
            } else if( extver > 0 ){

/* Get any pre-existing FitsTable from the FitsStore. This is the table
   in which the tabular data will be stored (if the Mapping can be expressed
   in -TAB form). */
               if( !astMapGet0A( store->tables, AST_TABEXTNAME, &table ) ) table = NULL;

/* See if the Mapping can be expressed in -TAB form. */
               tmap0 = IsMapTab1D( map, 1.0, NULL, wcsfrm, dim, iax, fits_i,
                                   &table, &icolmain, &icolindex, &interp,
                                   status );
               if( tmap0 ) {
                  tab_axis = 1;

/* The values stored in the table index vector are GRID coords. So we
   need to ensure that IWC are equivalent to GRID coords. So set CRVAL
   to zero. */
                  crval = 0.0;

/* Store TAB-specific values in the FitsStore. First the name of the
   FITS binary table extension holding the coordinate info. */
                  SetItemC( &(store->ps), fits_i, 0, s, AST_TABEXTNAME, status );

/* Next the table version number. This is the set (positive) value for the
   TabOK attribute. */
                  SetItem( &(store->pv), fits_i, 1, s, extver, status );

/* Also store the table version in the binary table header. */
                  astSetFitsI( table->header, "EXTVER", extver,
                               "Table version number", 0 );

/* Next the name of the table column containing the main coords array. */
                  SetItemC( &(store->ps), fits_i, 1, s,
                            astColumnName( table, icolmain ), status );

/* Next the name of the column containing the index array */
                  if( icolindex >= 0 ) SetItemC( &(store->ps), fits_i, 2, s,
                                  astColumnName( table, icolindex ), status );

/* The interpolation method (an AST extension to the published -TAB
   algorithm, communicated through the QVi_4a keyword). */
                  SetItem( &(store->pv), fits_i, 4, s, interp, status );

/* Also store the FitsTable itself in the FitsStore. */
                  astMapPut0A( store->tables, AST_TABEXTNAME, table, NULL );

/* Create the WCS -> IWC Mapping (AST uses grid coords as IWC coords for
   the -TAB algorithm). First, get a Mapping that combines the TAB axis
   Mapping( tmap0) in parallel with one or two UnitMaps in order to put
   the TAB axis at the required index. */
                  tmap1 = AddUnitMaps( tmap0, iax, nwcs, status );

/* Now get a PermMap that permutes the WCS axes into the FITS axis order. */
                  inperm = astMalloc( sizeof( double )*nwcs );
                  outperm = astMalloc( sizeof( double )*nwcs );
                  if( astOK ) {
                     for( i = 0; i < nwcs; i++ ) {
                        inperm[ i ] = wperm[ i ];
                        outperm[ wperm[ i ] ] = i;
                     }
                  }
                  pm = astPermMap( nwcs, inperm, nwcs, outperm, NULL, "",
                                   status );

/* Combine these two Mappings in series, to get the Mapping from WCS to
   IWC. */
                  axmap = (AstMapping *) astCmpMap( pm, tmap1, 1, " ",
                                                    status );

/* Free resources. */
                  inperm = astFree( inperm );
                  outperm = astFree( outperm );
                  pm = astAnnul( pm );
                  tmap0 = astAnnul( tmap0 );
                  tmap1 = astAnnul( tmap1 );
               }
               if( table ) table = astAnnul( table );
            }

/* If the axis cannot be described by any of the above methods, we
   pretend it is linear. This will generate a non-linear PIXEL->IWC
   mapping later (in MakeIntWorld) which will cause the write operation
   to fail. */
            if( !axmap ) {
               crval = -crval;
               tmap0 = (AstMapping *) astShiftMap( 1, &crval, "", status );
               axmap = AddUnitMaps( tmap0, iax, nwcs, status );
               tmap0 = astAnnul( tmap0 );
               crval = -crval;
            }

/* Combine the Mapping for this axis in series with those of earlier axes. */
            if( ret ) {
               tmap0 = (AstMapping *) astCmpMap( ret, axmap, 1, "", status );
               (void) astAnnul( ret );
               ret = tmap0;
            } else {
               ret = astClone( axmap );
            }

/* Get axis label and symbol. */
            sym =  astGetSymbol( wcsfrm, iax );
            lab =  astGetLabel( wcsfrm, iax );

/* The axis symbols are taken as the CTYPE values. Append "-LOG" or "-TAB" if
   the axis is logarithmic or tabular. */
            if( sym && strlen( sym ) ) {
               (void) sprintf( buf, "%s", sym );
            } else {
               (void) sprintf( buf, "AXIS%d", iax + 1 );
            }
            if( log_axis ) {
               SetAlgCode( buf, "-LOG", status );
            } else if( tab_axis ) {
               SetAlgCode( buf, "-TAB", status );
            }
            SetItemC( &(store->ctype), fits_i, 0, s, buf, status );

/* The axis labels are taken as the comment for the CTYPE keywords and as
   the CNAME keyword (but only if a label has been set and is different to
   the symbol). */
            if( lab && lab[ 0 ] && astTestLabel( wcsfrm, iax ) && strcmp( sym, lab ) ) {
               SetItemC( &(store->ctype_com), fits_i, 0, s, (char *) lab, status );
               SetItemC( &(store->cname), fits_i, 0, s, (char *) lab, status );
            } else {
               sprintf( buf, "Type of co-ordinate on axis %d", iax + 1 );
               SetItemC( &(store->ctype_com), fits_i, 0, s, buf, status );
            }

/* If a value has been set for the axis units, use it as CUNIT. */
            if( astTestUnit( wcsfrm, iax ) ){
               SetItemC( &(store->cunit), fits_i, 0, s, (char *) astGetUnit( wcsfrm, iax ), status );
            }

/* Indicate this axis has now been described. */
            axis_done[ iax ] = 1;

/* Release Resources. */
            axmap = astAnnul( axmap );
         }
      }

/* Release Resources. */
      wcsfrm = astAnnul( wcsfrm );
      map = astAnnul( map );
      pset1 = astAnnul( pset1 );
      pset2 = astAnnul( pset2 );
      lbnd_p = astFree( lbnd_p );
      ubnd_p = astFree( ubnd_p );
   }

/* 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, "", status );
   }

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

static int PCFromStore( AstFitsChan *this, FitsStore *store,
                        const char *method, const char *class, int *status ){

/*
*  Name:
*     PCFromStore

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

*  Type:
*     Private function.

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

*  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-PC encoding.
*
*     Zero is returned if the primary axis descriptions cannot be produced.
*     Whether or not secondary axis descriptions can be produced does not
*     effect the returned value (i.e. failure to produce a specific set of
*     secondary axes does not prevent other axis descriptions from being
*     produced).

*  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.
*     status
*        Pointer to the inherited status variable.

*  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 keyname[40];   /* Buffer for keyword name string */
   char primsys[20];   /* Buffer for primnary RADECSYS value */
   char type[MXCTYPELEN];/* Buffer for CTYPE value */
   char s;             /* Co-ordinate version character */
   char sign[2];       /* Fraction's sign character */
   char sup;           /* Upper limit on s */
   double *c;          /* Pointer to next array element */
   double *d;          /* Pointer to next array element */
   double *matrix;     /* Pointer to Frame PC/CD matrix */
   double *primpc;     /* Pointer to primary PC/CD matrix */
   double fd;          /* Fraction of a day */
   double mjd99;       /* MJD at start of 1999 */
   double primdt;      /* Primary mjd-obs value */
   double primeq;      /* Primary equinox value */
   double primln;      /* Primary lonpole value */
   double primlt;      /* Primary latpole value */
   double primpv[10];  /* Primary projection parameter values */
   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 is;             /* Co-ordinate version index */
   int iymdf[ 4 ];     /* Year, month, date, fractional day */
   int j;              /* Axis index */
   int jj;             /* SlaLib status */
   int m;              /* Parameter index */
   int maxm;           /* Upper limit on m */
   int naxis;          /* No. of axes */
   int nc;             /* Length of string */
   int ok;             /* Frame written out succesfully? */
   int prj;            /* Projection type */
   int ret;            /* Returned value. */

/* Initialise */
   ret = 0;

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

/* Find the number of co-ordinate versions in the FitsStore. FITS-PC
   can only encode 10 axis descriptions (including primary). */
   sup = GetMaxS( &(store->crval), status );
   if( sup > 'I' ) return ret;

/* Initialise */
   primdt = AST__BAD;
   primeq = AST__BAD;
   primln = AST__BAD;
   primlt = AST__BAD;

/* Loop round all co-ordinate versions (0-9) */
   primpc = NULL;
   for( s = ' '; s <= sup && astOK; s++ ){
      is = s - 'A' + 1;

/* Assume the Frame can be created succesfully. */
      ok = 1;

/* Save the number of wcs axes */
      val = GetItem( &(store->wcsaxes), 0, 0, s, NULL, method, class, status );
      if( val != AST__BAD ) {
         naxis = (int) ( val + 0.5 );
         SetValue( this, FormatKey( "WCSAXES", -1, -1, s, status ),
                   &naxis, AST__INT, "Number of WCS axes", status );
      } else {
         naxis = GetMaxJM( &(store->crpix), s, status ) + 1;
      }

/* PC matrix:
   --------- */

/* This encoding does not allow the PC matrix to be specified for each
   version - instead they all share the primary PC matrix. Therefore we
   need to check that all versions can use the primary PC matrix. Allocate
   memory to hold the PC matrix for this version. */
      matrix = (double *) astMalloc( sizeof(double)*naxis*naxis );
      if( matrix ){

/* Fill these array with the values supplied in the FitsStore. */
         c = matrix;
         for( i = 0; i < naxis; i++ ){
            for( j = 0; j < naxis; j++ ){
               *c = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
               if( *c == AST__BAD ) *c = ( i == j ) ? 1.0 : 0.0;
               c++;
            }
         }

/* If we are currently processing the primary axis description, take
   a copy of the PC matrix. */
         if( s == ' ' ) {
            primpc = (double *) astStore(  NULL, (void *) matrix,
                                           sizeof(double)*naxis*naxis );

/* Store each matrix element in turn. */
            c = matrix;
            for( i = 0; i < naxis; i++ ){
               for( j = 0; j < naxis; j++ ){

/* Set the element bad if it takes its default value. */
                  val = *(c++);
                  if( i == j ){
                     if( astEQUAL( val, 1.0 ) ) val = AST__BAD;
                  } else {
                     if( astEQUAL( val, 0.0 ) ) val = AST__BAD;
                  }

/* Only store elements which do not take their default values. */
                  if( val != AST__BAD ){
                     sprintf( keyname, "PC%.3d%.3d", i + 1, j + 1 );
                     SetValue( this, keyname, &val, AST__FLOAT, NULL, status );
                  }
               }
            }

/* For secondary axis descriptions, a check is made that the PC values are
   the same as the primary PC values stored earlier. If not, the current
   Frame cannot be stored as a secondary axis description so continue on
   to the next Frame. */
         } else {
            if( primpc ){
               c = matrix;
               d = primpc;
               for( i = 0; i < naxis; i++ ){
                  for( j = 0; j < naxis; j++ ){
                     if( !astEQUAL( *c, *d ) ){
                        ok = 0;
                     } else {
                        c++;
                        d++;
                     }
                  }
               }

/* Continue with the next Frame if the PC matrix for this Frame is different
   to the primary PC matrix. */
               if( !ok ) goto next;
            }
         }
         matrix = (double *) astFree( (void *) matrix );
      }

/* CDELT:
   ------ */
      for( i = 0; i < naxis; i++ ){
         val = GetItem( &(store->cdelt), i, 0, s, NULL, method, class, status );
         if( val == AST__BAD ) {
            ok = 0;
            goto next;
         }
         sprintf( combuf, "Pixel scale on axis %d", i + 1 );
         if( s == ' ' ) {
            sprintf( keyname, "CDELT%d", i + 1 );
         } else {
            sprintf( keyname, "C%dELT%d", is, i + 1 );
         }
         SetValue( this, keyname, &val, AST__FLOAT, combuf, status );
      }

/* CRPIX:
   ------ */
      for( j = 0; j < naxis; j++ ){
         val = GetItem( &(store->crpix), 0, j, s, NULL, method, class, status );
         if( val == AST__BAD ) {
            ok = 0;
            goto next;
         }
         sprintf( combuf, "Reference pixel on axis %d", j + 1 );
         if( s == ' ' ) {
            sprintf( keyname, "CRPIX%d", j + 1 );
         } else {
            sprintf( keyname, "C%dPIX%d", is, j + 1 );
         }
         SetValue( this, keyname, &val, AST__FLOAT, combuf, status );
      }

/* CRVAL:
   ------ */
      for( i = 0; i < naxis; i++ ){
         val = GetItem( &(store->crval), i, 0, s, NULL, method, class, status );
         if( val == AST__BAD ) {
            ok = 0;
            goto next;
         }
         sprintf( combuf, "Value at ref. pixel on axis %d", i + 1 );
         if( s == ' ' ) {
            sprintf( keyname, "CRVAL%d", i + 1 );
         } else {
            sprintf( keyname, "C%dVAL%d", is, i + 1 );
         }
         SetValue( this, keyname, &val, AST__FLOAT, combuf, status );
      }

/* CTYPE:
   ------ */
      for( i = 0; i < naxis; i++ ){
         cval = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
         nc = strlen( cval );
         if( !cval || ( nc > 4 && !strcmp( cval + 4, "-TAB" ) ) ) {
            ok = 0;
            goto next;
         }
         comm = GetItemC( &(store->ctype_com), i, 0, s, NULL, method, class, status );
         if( !comm ) {
            sprintf( combuf, "Type of co-ordinate on axis %d", i + 1 );
            comm = combuf;
         }
         if( s == ' ' ) {
            sprintf( keyname, "CTYPE%d", i + 1 );
         } else {
            sprintf( keyname, "C%dYPE%d", is, i + 1 );
         }

/* FITS-PC cannot handle celestial axes of type "xxLT" or "xxLN".
   Neither can it handle the "-TAB". */
         if( ( nc > 2 && !strncmp( cval + 2, "LT-", 3 ) ) ||
             ( nc > 2 && !strncmp( cval + 2, "LN-", 3 ) ) ||
             ( nc > 4 && !strncmp( cval + 4, "-TAB", 4 ) ) ){
            ok = 0;
            goto next;
         }

/* Extract the projection type as specified by the last 4 characters
   in the CTYPE keyword value. This will be AST__WCSBAD for non-celestial
   axes. */
         prj = astWcsPrjType( cval + 4 );

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

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

/* Store the CTYPE value */
         SetValue( this, keyname, &cval, AST__STRING, comm, status );
      }

/* Get and save CUNIT for all intermediate axes. These are NOT required, so
   do not pass on if they are not available. */
      for( i = 0; i < naxis; i++ ){
         cval = GetItemC( &(store->cunit), i, 0, s, NULL, method, class, status );
         if( cval ) {
            sprintf( combuf, "Units for axis %d", i + 1 );
            if( s == ' ' ) {
               sprintf( keyname, "CUNIT%d", i + 1 );
            } else {
               sprintf( keyname, "C%dNIT%d", is, i + 1 );
            }
            SetValue( this, keyname, &cval, AST__STRING, combuf, status );
         }
      }

/* Get and save RADESYS. This is NOT required, so do not pass on if it is
   not available. If RADECSYS is provided for a secondary axis, it must
   be the same as the primary axis RADECSYS value. If it is not, pass on to
   the next Frame. */
      cval = GetItemC( &(store->radesys), 0, 0, s, NULL, method, class, status );
      if( cval ) {
         if( s == ' ' ) {
            strcpy( primsys, cval );
            SetValue( this, "RADECSYS", &cval, AST__STRING,
                      "Reference frame for RA/DEC values", status );
         } else if( strcmp( cval, primsys ) ) {
            ok = 0;
            goto next;
         }
      }

/* Reference equinox. This is NOT required, so do not pass on if it is
   not available. If equinox is provided for a secondary axis, it must
   be the same as the primary axis equinox value. If it is not, pass on to
   the next Frame. */
      val = GetItem( &(store->equinox), 0, 0, s, NULL, method, class, status );
      if( s == ' ' ) {
         primeq = val;
         if( val != AST__BAD ) SetValue( this, "EQUINOX", &val, AST__FLOAT,
                                         "Epoch of reference equinox", status );
      } else if( !astEQUAL( val, primeq ) ){
         ok = 0;
         goto next;
      }

/* Latitude of native north pole. This is NOT required, so do not pass on
   if it is not available. If latpole is provided for a secondary axis, it
   must be the same as the primary axis value. If it is not, pass on to
   the next Frame. */
      val = GetItem( &(store->latpole), 0, 0, s, NULL, method, class, status );
      if( s == ' ' ) {
         primlt = val;
         if( val != AST__BAD ) SetValue( this, "LATPOLE", &val, AST__FLOAT,
                                         "Latitude of native north pole", status );
      } else if( !EQUALANG( val, primlt ) ){
         ok = 0;
         goto next;
      }

/* Longitude of native north pole. This is NOT required, so do not pass on
   if it is not available. If lonpole is provided for a secondary axis, it
   must be the same as the primary axis value. If it is not, pass on to
   the next Frame. */
      val = GetItem( &(store->lonpole), 0, 0, s, NULL, method, class, status );
      if( s == ' ' ) {
         primln = val;
         if( val != AST__BAD ) SetValue( this, "LONGPOLE", &val, AST__FLOAT,
                                         "Longitude of native north pole", status );
      } else if( !EQUALANG( val, primln ) ){
         ok = 0;
         goto next;
      }

/* Date of observation. This is NOT required, so do not pass on if it is
   not available. If mjd-obs is provided for a secondary axis, it must be
   the same as the primary axis value. If it is not, pass on to the next
   Frame. */
      val = GetItem( &(store->mjdobs), 0, 0, s, NULL, method, class, status );
      if( s == ' ' ) {
         primdt = val;
         if( val != AST__BAD ) {
            SetValue( this, "MJD-OBS", &val, AST__FLOAT,
                      "Modified Julian Date of observation", status );

/* 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. */
            palCaldj( 99, 1, 1, &mjd99, &jj );
            if( val < mjd99 ) {
               palDjcal( 0, val, iymdf, &jj );
               sprintf( combuf, "%2.2d/%2.2d/%2.2d", iymdf[ 2 ], iymdf[ 1 ],
                        iymdf[ 0 ] - ( ( iymdf[ 0 ] > 1999 ) ? 2000 : 1900 ) );
            } else {
               palDjcl( val, iymdf, iymdf+1, iymdf+2, &fd, &jj );
               palDd2tf( 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", &cval, AST__STRING,
                      "Date of observation", status );
         }
      } else if( !astEQUAL( val, primdt ) ){
         ok = 0;
         goto next;
      }

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

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

/* Get the CTYPE values for the latitude axis. */
         cval = GetItemC( &(store->ctype), axlat, 0, s, NULL, method, class, status );

/* Extract the projection type as specified by the last 4 characters
   in the CTYPE keyword value. */
         prj = ( cval ) ? astWcsPrjType( cval + 4 ) : AST__WCSBAD;

/* Projection parameters. If provided for a secondary axis, they must be
   the same as the primary axis value. If it is not, pass on to the next
   Frame. PC encoding ignores parameters associated with the longitude
   axis. The old PC TAN projection did not have any parameters.
   Pass on if a TAN projection with parameters is found.  The number of
   parameters was limited to 10. Pass on if more than 10 are supplied. */
         maxm = GetMaxJM( &(store->pv), ' ', status );
         for( i = 0; i < naxis; i++ ){
            if( i != axlon ) {
               for( m = 0; m <= maxm; m++ ){
                  val = GetItem( &(store->pv), i, m, s, NULL, method, class, status );
                  if( s == ' ' ){
                     if( val != AST__BAD ) {
                        if( i != axlat || prj == AST__TAN || m >= 10 ){
                           ok = 0;
                           goto next;
                        } else {
                           SetValue( this, FormatKey( "PROJP", m, -1, ' ', status ), &val,
                                     AST__FLOAT, "Projection parameter", status );
                        }
                     }
                     if( i == axlat && m < 10 ) primpv[m] = val;
                  } else {
                     if( ( ( i != axlat || m >= 10 ) && val != AST__BAD ) ||
                         ( i == axlat && m < 10 && !astEQUAL( val, primpv[m] ) ) ){
                        ok = 0;
                        goto next;
                     }
                  }
               }
            }
         }
      }

/* See if a Frame was sucessfully written to the FitsChan. */
next:
      ok = ok && astOK;

/* If so, indicate we have something to return. */
      if( ok ) ret = 1;

/* Clear any error status so we can continue to produce the next Frame.
   Retain the error if the primary axes could not be produced. After the
   primary axes, do the A axes. */
      if( s != ' ' ) {
         astClearStatus;
      } else {
         s = 'A' - 1;
      }

/* Remove the secondary "new" flags from the FitsChan. This flag is
   associated with cards which have been added to the FitsChan during
   this pass through the main loop in this function. If the Frame was
   written out succesfully, just clear the flags. If anything went wrong
   with this Frame, remove the flagged cards from the FitsChan. */
      FixNew( this, NEW2, !ok, method, class, status );

/* Set the current card so that it points to the last WCS-related keyword
   in the FitsChan (whether previously read or not). */
      FindWcs( this, 1, 1, 0, method, class, status );
   }

/* Annul the array holding the primary PC matrix. */
   primpc = (double *) astFree( (void *) primpc );

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

static void PreQuote( const char *value,
                      char string[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN - 3 ], int *status ) {

/*
*  Name:
*     PreQuote

*  Purpose:
*     Pre-quote FITS character data.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void PreQuote( const char *value,
*                    char string[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN - 3 ] )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function processes a string value in such a way that it can
*     be stored as a FITS character value (associated with a keyword)
*     and later retrieved unchanged, except for possible truncation.
*
*     This pre-processing is necessary because FITS does not regard
*     trailing white space as significant, so it is lost. This
*     function adds double quote (") characters around the string if
*     it is necessary in order to prevent this loss. These quotes are
*     also added to zero-length strings and to strings that are
*     already quoted (so that the original quotes are not lost when
*     they are later un-quoted).
*
*     This function will silently truncate any string that is too long
*     to be stored as a FITS character value, but will ensure that the
*     maximum number of characters are retained, taking account of any
*     quoting required.

*  Parameters:
*     value
*        Pointer to a constant null-terminated string containing the
*        input character data to be quoted. All white space is
*        significant.
*     string
*        A character array into which the result string will be
*        written, with a terminating null. The maximum number of
*        characters from the input string that can be accommodated in
*        this is (AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN - 4), but this
*        will be reduced if quoting is necessary.

*  Notes:
*     - The UnPreQuote function should be used to reverse the effect
*     of this function on a string (apart from any truncation).
*/

/* Local Variables: */
   int dq;                       /* Number of double quotes needed */
   int dquotes;                  /* Final number of double quotes */
   int i;                        /* Loop counter for input characters */
   int j;                        /* Counter for output characters */
   int nc;                       /* Number of characters to be accommodated */
   int sq;                       /* Number of single quotes needed */

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

/* Initialise, setting the default number of double quotes (which
   applies to a zero-length string) to 2. */
   dquotes = 2;
   nc = 0;
   sq = 0;

/* Loop to consider each input character to see if it will fit into
   the result string. */
   for ( i = 0; value[ i ]; i++ ) {

/* If a single quote character is to be included, count it. When the
   string is encoded as FITS character data, these quotes will be
   doubled, so will increase the overall string length by one. */
      if ( value[ i ] == '\'' ) sq++;

/* See how many double quotes are needed around the string (0 or
   2). These are needed if there is trailing white space that needs
   protecting (this is not significant in FITS and will be removed),
   or if the string already has quotes at either end (in which case an
   extra set is needed to prevent the original ones being removed when
   it is later un-quoted). Note we do not need to double existing
   double quote characters within the string, because the position of
   the ends of the string are known (from the quoting supplied by
   FITS) so only the first and last characters need be inspected when
   un-quoting the string.
   In assessing the number of double quotes, assume the string will be
   truncated after the current character. */
      dq = ( isspace( value[ i ] ) ||
             ( ( value[ 0 ] == '"' ) && ( value[ i ] == '"' ) ) ) ? 2 : 0;

/* See if the length of the resulting string, including the current
   character and all necessary quotes, is too long. If so, give up
   here. */
      if ( ( nc + 1 + dq + sq ) >
           ( AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN - 4 ) ) break;

/* If the string is not too long, accept the character and note the
   number of double quotes needed. */
      nc = i + 1;
      dquotes = dq;
   }

/* If double quotes are needed, insert the opening quote into the
   output string. */
   j = 0;
   if ( dquotes ) string[ j++ ] = '"';

/* Follow this with the maximum number of input string characters that
   can be accommodated. */
   for ( i = 0; i < nc; i++ ) string[ j++ ] = value[ i ];

/* Append the closing quote if necessary and terminate the output
   string. */
   if ( dquotes ) string[ j++ ] = '"';
   string[ j ] = '\0';
}

static void PurgeWCS( AstFitsChan *this, int *status ){

/*
*++
*  Name:
c     astPurgeWCS
f     AST_PURGEWCS

*  Purpose:
*     Delete all cards in the FitsChan describing WCS information.

*  Type:
*     Public virtual function.

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

*  Class Membership:
*     FitsChan method.

*  Description:
c     This function
f     This routine
*     deletes all cards in a FitsChan that relate to any of the recognised
*     WCS encodings. On exit, the current card is the first remaining card
*     in the FitsChan.

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

/* Local Variables: */
   AstObject *obj;
   int oldclean;

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

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* Ensure the Clean attribute is set so that WCS keywords are removed
   even if an error occurs. */
   if( astTestClean( this ) ) {
      oldclean = astGetClean( this );
      astSetClean( this, 1 );
   } else {
      astSetClean( this, 1 );
      oldclean = -1;
   }

/* Loop round attempting to read AST objects form the FitsChan. This will
   flag cards as used that are involved in the creation of these object
   (including NATIVE encodings). Ignore any error that ocurs whilst doing
   this. */
   astClearCard( this );
   if( astOK ) {
      int oldreporting = astReporting( 0 );
      obj = astRead( this );
      while( obj ) {
         obj = astAnnul( obj );
         astClearCard( this );
         obj = astRead( this );
      }
      if( !astOK ) astClearStatus;
      astReporting( oldreporting );
   }

/* We now loop round to remove any spurious WCS-related cards left in the
   FitsChan that did not form part of a complete WCS encoding. Find the
   first WCS-related card left in the FitsChan. */
   FindWcs( this, 0, 0, 1, "DeleteWcs", "FitsChan", status );

/* Loop round marking each WCS-related card as used until none are left */
   while( this->card && astOK ) {

/* Mark the current card as having been read. */
      ( (FitsCard*) this->card )->flags = USED;

/* Find the next WCS-related card. */
      FindWcs( this, 0, 0, 0, "DeleteWcs", "FitsChan", status );
   }

/* Rewind the FitsChan. */
   astClearCard( this );

/* Reset the Clean attribute. */
   if( oldclean == -1 ) {
      astClearClean( this );
   } else {
      astSetClean( this, oldclean );
   }

}

static void PutCards( AstFitsChan *this, const char *cards, int *status ) {

/*
*++
*  Name:
c     astPutCards
f     AST_PUTCARDS

*  Purpose:
*     Store a set of FITS header cards in a FitsChan.

*  Type:
*     Public virtual function.

*  Synopsis:
c     #include "fitschan.h"

c     void astPutCards( AstFitsChan *this, const char *cards )
f     CALL AST_PUTCARDS( THIS, CARDS, STATUS )

*  Class Membership:
*     FitsChan method.

*  Description:
c     This function
f     This routine
*     stores a set of FITS header cards in a FitsChan. The cards are
*     supplied concatenated together into a single character string.
*     Any existing cards in the FitsChan are removed before the new cards
*     are added. The FitsChan is "re-wound" on exit by clearing its Card
*     attribute. This means that a subsequent invocation of
c     astRead
f     AST_READ
*     can be made immediately without the need to re-wind the FitsChan
*     first.

*  Parameters:
c     this
f     THIS = INTEGER (Given)
*        Pointer to the FitsChan.
c     cards
f     CARDS = CHARACTER * ( * ) (Given)
c        Pointer to a null-terminated character string
f        A character string
*        containing the FITS cards to be stored. Each individual card
*        should occupy 80 characters in this string, and there should be
*        no delimiters, new lines, etc, between adjacent cards. The final
*        card may be less than 80 characters long.
c        This is the format produced by the fits_hdr2str function in the
c        CFITSIO library.
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Notes:
*     - An error will result if the supplied string contains any cards
*     which cannot be interpreted.
*--
*/

/* Local Variables: */
   const char *a;         /* Pointer to start of next card */
   int clen;              /* Length of supplied string */
   int i;                 /* Card index */
   int ncard;             /* No. of cards supplied */

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

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* Empty the FitsChan. */
   astEmptyFits( this );

/* Loop round the supplied string in 80 character segments, inserting
   each segment into the FitsChan as a header card. Allow the last card
   to be less than 80 characters long. */
   clen = strlen( cards );
   ncard = clen/80;
   if( ncard*80 < clen ) ncard++;
   a = cards;
   for( i = 0; i < ncard; i++, a += 80 ) astPutFits( this, a, 1 );

/* Rewind the FitsChan. */
   astClearCard( this );
}

static void PutFits( AstFitsChan *this, const char card[ AST__FITSCHAN_FITSCARDLEN + 1 ],
                     int overwrite, int *status ){

/*
*++
*  Name:
c     astPutFits
f     AST_PUTFITS

*  Purpose:
*     Store a FITS header card in a FitsChan.

*  Type:
*     Public virtual function.

*  Synopsis:
c     #include "fitschan.h"

c     void astPutFits( AstFitsChan *this, const char card[ 80 ],
c                      int overwrite )
f     CALL AST_PUTFITS( THIS, CARD, OVERWRITE, STATUS )

*  Class Membership:
*     FitsChan method.

*  Description:
c     This function stores a FITS header card in a FitsChan. The card
f     This routine stores a FITS header card in a FitsChan. The card
*     is either inserted before the current card (identified by the
*     Card attribute), or over-writes the current card, as required.

*  Parameters:
c     this
f     THIS = INTEGER (Given)
*        Pointer to the FitsChan.
c     card
f     CARD = CHARACTER * ( 80 ) (Given)
c        Pointer to a possibly null-terminated character string
c        containing the FITS card to be stored. No more than 80
c        characters will be used from this string (or fewer if a null
c        occurs earlier).
f        A character string string containing the FITS card to be
f        stored. No more than 80 characters will be used from this
f        string.
c     overwrite
f     OVERWRITE = LOGICAL (Given)
c        If this value is zero, the new card is inserted in front of
f        If this value is .FALSE., the new card is inserted in front of
*        the current card in the FitsChan (as identified by the
c        initial value of the Card attribute). If it is non-zero, the
f        initial value of the Card attribute). If it is .TRUE., the
*        new card replaces the current card. In either case, the Card
*        attribute is then incremented by one so that it subsequently
*        identifies the card following the one stored.
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Notes:
*     - If the Card attribute initially points at the "end-of-file"
*     (i.e. exceeds the number of cards in the FitsChan), then the new
*     card is appended as the last card in the FitsChan.
*     - An error will result if the supplied string cannot be interpreted
*     as a FITS header card.
*--
*/

/* Local Variables: */
   char *comment;         /* The keyword comment */
   char *name;            /* The keyword name */
   char *value;           /* The keyword value */
   const char *class;     /* Object class */
   const char *method;    /* Current method */
   double cfval[2];       /* Complex floating point keyword value */
   double fval;           /* floating point keyword value */
   int cival[2];          /* Complex integer keyword value */
   int ival;              /* Integer keyword value */
   int len;               /* No. of characters to read from the value string */
   int nc;                /* No. of characters read from value string */
   int type;              /* Keyword data type */

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

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* Store the current method, and the class of the supplied object for use
   in error messages.*/
   method = "astPutFits";
   class = astGetClass( this );

/* Split the supplied card up into name, value and commment strings, and
   get pointers to local copies of them. The data type associated with the
   keyword is returned. */
   type = Split( this, card, &name, &value, &comment, method, class, status );

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

/* Initialise the number of characters read from the value string. */
      nc = 0;

/* Store the number of characters in the value string. */
      len = strlen( value );

/* Read and store floating point values from the value string. NB, this
   list is roughly in the order of descreasing frequency of use (i.e.
   most FITS keywords are simple floating point values, the next most
   common are strings, etc). */
      if( type == AST__FLOAT ){
         if( 1 == astSscanf( value, " %lf %n", &fval, &nc ) && nc >= len ){
            astSetFitsF( this, name, fval, comment, overwrite );
         } else {
            astError( AST__BDFTS, "%s(%s): Unable to read a floating point "
                      "FITS keyword value.", status, method, class );
         }

/* Read and store string values from the value string. */
      } else if( type == AST__STRING ){
         astSetFitsS( this, name, value, comment, overwrite );

/* Read and store string values from the value string. */
      } else if( type == AST__CONTINUE ){
         astSetFitsCN( this, name, value, comment, overwrite );

/* Store comment card. */
      } else if( type == AST__COMMENT ){
         astSetFitsCom( this, name, comment, overwrite );

/* Read and store integer values from the value string. */
      } else if( type == AST__INT ){
         if( 1 == astSscanf( value, " %d %n", &ival, &nc ) && nc >= len ){
            astSetFitsI( this, name, ival, comment, overwrite );
         } else {
            astError( AST__BDFTS, "%s(%s): Unable to read an integer FITS "
                      "keyword value.", status, method, class );
         }

/* Read and store logical values from the value string. */
      } else if( type == AST__LOGICAL ){
         astSetFitsL( this, name, (*value == 'T'), comment, overwrite );

/* Read and store undefined values from the value string. */
      } else if( type == AST__UNDEF ){
         astSetFitsU( this, name, comment, overwrite );

/* Read and store complex floating point values from the value string. */
      } else if( type == AST__COMPLEXF ){
         if( 2 == astSscanf( value, " %lf %lf %n", cfval, cfval + 1, &nc ) &&
             nc >= len ){
            astSetFitsCF( this, name, cfval, comment, overwrite );
         } else {
            astError( AST__BDFTS, "%s(%s): Unable to read a complex pair "
                      "of floating point FITS keyword values.", status, method, class );
         }

/* Read and store complex integer values from the value string. */
      } else if( type == AST__COMPLEXI ){
         if( 2 == astSscanf( value, " %d %d %n", cival, cival + 1, &nc ) &&
             nc >= len ){
            astSetFitsCI( this, name, cival, comment, overwrite );
         } else {
            astError( AST__BDFTS, "%s(%s): Unable to read a complex pair "
                      "of integer FITS keyword values.", status, method, class );
         }

/* Report an error for any other type. */
      } else {
         astError( AST__INTER, "%s: AST internal programming error - "
                   "FITS data-type '%d' not yet supported.", status, method, type );
      }

/* Give a context message if an error occurred. */
      if( !astOK ){
         astError( astStatus, "%s(%s): Unable to store the following FITS "
                   "header card:\n%s\n", status, method, class, card );
      }
   }

/* Free the memory used to hold the keyword name, comment and value
   strings. */
   (void) astFree( (void *) name );
   (void) astFree( (void *) comment );
   (void) astFree( (void *) value );
}

static void PutTable( AstFitsChan *this, AstFitsTable *table,
                      const char *extnam, int *status ) {

/*
*++
*  Name:
c     astPutTable
f     AST_PUTTABLE

*  Purpose:
*     Store a single FitsTable in a FitsChan.

*  Type:
*     Public virtual function.

*  Synopsis:
c     #include "fitschan.h"

c     void astPutTable( AstFitsChan *this, AstFitsTable *table,
c                       const char *extnam )
f     CALL AST_PUTTABLE( THIS, TABLE, EXTNAM, STATUS )

*  Class Membership:
*     FitsChan method.

*  Description:
c     This function
f     This routine
*     allows a representation of a single FITS binary table to be
*     stored in a FitsChan. For instance, this may provide the coordinate
*     look-up tables needed subequently when reading FITS-WCS headers
*     for axes described using the "-TAB" algorithm. Since, in general,
*     the calling application may not know which tables will be needed -
*     if any - prior to calling
c     astRead, the astTablesSource function
f     AST_READ, the AST_TABLESOURCE routine
*     provides an alternative mechanism in which a caller-supplied
*     function is invoked to store a named table in the FitsChan.

*  Parameters:
c     this
f     THIS = INTEGER (Given)
*        Pointer to the FitsChan.
c     table
f     TABLE = INTEGER (Given)
*        Pointer to a FitsTable to be added to the FitsChan. If a FitsTable
*        with the associated extension name already exists in the FitsChan,
*        it is replaced with the new one. A deep copy of the FitsTable is
*        stored in the FitsChan, so any subsequent changes made to the
*        FitsTable will have no effect on the behaviour of the FitsChan.
c     extnam
f     EXTNAM = CHARACTER * ( * ) (Given)
*        The name of the FITS extension associated with the table.
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Notes:
*     - Tables stored in the FitsChan may be retrieved using
c     astGetTables.
f     AST_GETTABLES.
c     - The astPutTables method can add multiple FitsTables in a single call.
f     - The AST_PUTTABLES method can add multiple FitsTables in a single call.
*--
*/

/* Local Variables: */
   AstObject *ft;

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

/* Create a KeyMap to hold the tables within the FitsChan, if this has not
   already been done. */
   if( !this->tables ) this->tables = astKeyMap( " ", status );

/* Store a copy of the FitsTable in the FitsChan. */
   ft = astCopy( table );
   astMapPut0A( this->tables, extnam, ft, NULL );
   ft = astAnnul( ft );
}

static void PutTables( AstFitsChan *this, AstKeyMap *tables, int *status ) {

/*
*++
*  Name:
c     astPutTables
f     AST_PUTTABLES

*  Purpose:
*     Store one or more FitsTables in a FitsChan.

*  Type:
*     Public virtual function.

*  Synopsis:
c     #include "fitschan.h"

c     void astPutTables( AstFitsChan *this, AstKeyMap *tables )
f     CALL AST_PUTTABLES( THIS, TABLES, STATUS )

*  Class Membership:
*     FitsChan method.

*  Description:
c     This function
f     This routine
*     allows representations of one or more FITS binary tables to be
*     stored in a FitsChan. For instance, these may provide the coordinate
*     look-up tables needed subequently when reading FITS-WCS headers
*     for axes described using the "-TAB" algorithm. Since, in general,
*     the calling application may not know which tables will be needed -
*     if any - prior to calling
c     astRead, the astTablesSource function
f     AST_READ, the AST_TABLESOURCE routine
*     provides an alternative mechanism in which a caller-supplied
*     function is invoked to store a named table in the FitsChan.

*  Parameters:
c     this
f     THIS = INTEGER (Given)
*        Pointer to the FitsChan.
c     tables
f     TABLES = INTEGER (Given)
*        Pointer to a KeyMap holding the tables that are to be added
*        to the FitsChan. Each entry should hold a scalar value which is a
*        pointer to a FitsTable to be added to the FitsChan. Any unusable
*        entries are ignored. The key associated with each entry should be
*        the name of the FITS binary extension from which the table was
*        read. If a FitsTable with the associated key already exists in the
*        FitsChan, it is replaced with the new one. A deep copy of each
*        usable FitsTable is stored in the FitsChan, so any subsequent
*        changes made to the FitsTables will have no effect on the
*        behaviour of the FitsChan.
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Notes:
*     - Tables stored in the FitsChan may be retrieved using
c     astGetTables.
f     AST_GETTABLES.
*     - The tables in the supplied KeyMap are added to any tables already
*     in the FitsChan.
c     - The astPutTable
f     - The AST_PUTTABLE
*     method provides a simpler means of adding a single table to a FitsChan.
*--
*/

/* Local Variables: */
   AstObject *obj;
   const char *key;
   int ientry;
   int nentry;

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

/* Loop through all entries in the supplied KeyMap. */
   nentry = astMapSize( tables );
   for( ientry = 0; ientry < nentry; ientry++ ) {
      key = astMapKey( tables, ientry );

/* Ignore entries that do not contain AST Object pointers, or are not
   scalar. */
      if( astMapType( tables, key ) == AST__OBJECTTYPE &&
          astMapLength( tables, key ) == 1 ) {

/* Get the pointer, amd ignore it if it is not a FitsTable. */
         astMapGet0A( tables, key, &obj );
         if( astIsAFitsTable( obj ) ) {

/* Store it in the FitsChan. */
            astPutTable( this, (AstFitsTable *) obj, key );
         }

/* Annul the Object pointer. */
         obj = astAnnul( obj );
      }
   }
}

static AstObject *Read( AstChannel *this_channel, int *status ) {
/*
*  Name:
*     Read

*  Purpose:
*     Read an Object from a Channel.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     AstObject *Read( AstChannel *this_channel, int *status )

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

*  Description:
*     This function reads an Object from a FitsChan.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to the new Object. This will always be a FrameSet.

*  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).
*     -  For foreign encodings, the first card in the FitsChan must be
*     the current card on entry (otherwise a NULL pointer is returned),
*     and the FitsChan is left at end-of-file on exit.
*     -  For the Native encoding, reading commences from the current card
*     on entry (which need not be the first in the FitsChan), and the
*     current Card on exit is the first card following the last one read
*     (or end-of-file).
*/

/* Local Variables: */
   AstObject *new;               /* Pointer to returned Object */
   AstFitsChan *this;            /* Pointer to the FitsChan structure */
   FitsStore *store;             /* Intermediate storage for WCS information */
   const char *method;           /* Pointer to string holding calling method */
   const char *class;            /* Pointer to string holding object class */
   int encoding;                 /* The encoding scheme */
   int remove;                   /* Remove used cards? */

/* Initialise. */
   new = NULL;

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

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

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* Store the calling method, and object class. */
   method = "astRead";
   class = astGetClass( this );

/* Get the encoding scheme used by the FitsChan. */
   encoding = astGetEncoding( this );

/* If we are reading from a FitsChan in which AST objects are encoded using
   native AST-specific keywords, use the Read method inherited from the
   Channel class. */
   if( encoding == NATIVE_ENCODING ){
      new = (*parent_read)( this_channel, status );

/* Indicate that used cards should be removed from the FitsChan. */
      remove = 1;

/* If we are reading from a FitsChan in which AST objects are encoded using
   any of the other supported encodings, the header may only contain a
   single FrameSet. */
   } else {
      remove = 0;

/* Only proceed if the FitsChan is at start-of-file. */
      if( !astTestCard( this ) && astOK ){

/* Extract the required information from the FITS header into a standard
   intermediary structure called a FitsStore. */
         store = FitsToStore( this, encoding, method, class, status );

/* Now create a FrameSet from this FitsStore. */
         new = FsetFromStore( this, store, method, class, status );

/* Release the resources used by the FitsStore. */
         store = FreeStore( store, status );

/* Indicate that used cards should be retained in the FitsChan. */
         remove = 0;

/* If no object is being returned, rewind the fitschan in order to
   re-instate the original current Card. */
         if( !new ) {
            astClearCard( this );

/*  Otherwise, ensure the current card is at "end-of-file". */
         } else {
            astSetCard( this, INT_MAX );
         }
      }
   }

/* If an error occurred, clean up by deleting the new Object and
   return a NULL pointer. */
   if ( !astOK ) new = astDelete( new );

/* If no object is being returned, clear the "provisionally used" flags
   associated with cards which were read. We do not do this if the user
   wants to clean WCS cards from the FitsChan even if an error occurs. */
   if( !new && !astGetClean( this ) ) {
      FixUsed( this, 0, 0, 0, method, class, status );

/*  Otherwise, indicate that all the "provisionally used" cards have been
    "definitely used". If native encoding was used, these cards are
    totally removed from the FitsChan. */
   } else {
      FixUsed( this, 0, 1, remove, method, class, status );
   }

/* Return the pointer to the new Object. */
   return new;
}

static double *ReadCrval( AstFitsChan *this, AstFrame *wcsfrm, char s,
                          const char *method, const char *class, int *status ){

/*
*  Name:
*     ReadCrval

*  Purpose:
*     Obtain the reference point from the supplied FitsChan  in the
*     supplied WCS Frame.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"

*     double *ReadCrval( AstFitsChan *this, AstFrame *wcsfrm, char s,
*                        const char *method, const char *class, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The original reference point in the "s" coordinate description is read
*     from the CRVAL keywords in the supplied FitsChan, and the original
*     FrameSet is re-read from the FitsChan. If possible, the reference
*     position is then converted from the "s" coordinate description to the
*     supplied WCS Frame, and a pointer to an array holding the axis
*     values for the transformed reference point is returned.

*  Parameters:
*     this
*        The FitsChan.
*     wcsfrm
*        The WCS Frame in the FitsChan being written to.
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to a dynamically allocated array holding the reference
*     point in the supplied WCS Frame. NULL is returned if is is not
*     possible to determine the reference point for any reason (for
*     instance, if the FitsChan does not contain values for the CRVAL
*     keywords).
*/

/* Local Variables: */
   AstFitsChan *fc;          /* A copy of the supplied FitsChan */
   AstFrame *tfrm;           /* Temporary Frame pointer */
   AstFrameSet *fs;          /* The FITS FrameSet */
   AstFrameSet *tfs;         /* FrameSet connecting FITS and supplied WCS Frame */
   const char *id;           /* Pointer to Object "Id" string */
   char buf[ 11 ];           /* FITS keyword template buffer */
   double *crval;            /* CRVAL keyword values in supplied FitsChan */
   double *ret;              /* Returned array */
   int hii;                  /* Highest found FITS axis index */
   int iax;                  /* Axis index (zero based) */
   int ifr;                  /* Frames index */
   int loi;                  /* Lowest found FITS axis index */
   int nax;                  /* Axis count */
   int nfr;                  /* No. of Frames in FITS FrameSet */
   int ok;                   /* Were CRVAL values found? */

/* Initialise */
   ret = NULL;

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

/* We want to re-create the original FrameSet represented by the original
   contents of the supplied FitsChan. Some of the contents of the
   FitsChan will already have been marked as "having been read" and so
   will be ignored if we attempt to read a FrameSet directly from the
   supplied FitsChan. Therefore we take a deep copy of the supplied
   FitsChan and clear all the "previusly read" flags in the copy. */
   fc = astCopy( this );
   astClearEncoding( fc );
   FixUsed( fc, 1, 0, 0, method, class, status );

/* Copy the CRVAL values for the "s" axis descriptions into a dynamically
   allocated array ("crval"). */
   if( s == ' ' ) {
      strcpy( buf, "CRVAL%d" );
   } else {
      sprintf( buf, "CRVAL%%d%c", s );
   }
   if( astKeyFields( fc, buf, 1, &hii, &loi ) > 0 ) {
      crval = astMalloc( sizeof( double )*(size_t) hii );
      ok = 1;
      for( iax = 0; iax < hii; iax++ ){
         ok = ok && GetValue( fc, FormatKey( "CRVAL", iax + 1, -1, s, status ),
                              AST__FLOAT, (void *) (crval + iax), 0, 0, method,
                              class, status );
      }
   } else {
      crval = NULL;
      ok = 0;
   }

/* If the CRVAL values were obtained succesfully, attempt to read a FrameSet
   from the FitsChan copy. Do it in a new error report context so that we
   can annull any error when the FrameSet is read. */
   if( ok ) {
      int oldreporting = astReporting( 0 );
      astClearCard( fc );
      fs = astRead( fc );
      if( fs ) {

/* We want to find a conversion from the Frame in this FrameSet which
   represents the FITS-WCS "s" coordinate descriptions and the supplied WCS
   Frame. So first find the Frame which has its Ident attribute set to
   "s" and make it the current Frame. */
         nfr = astGetNframe( fs );
         for( ifr = 1; ifr <= nfr; ifr++ ) {
            astSetCurrent( fs, ifr );
            tfrm = astGetFrame( fs, ifr );
            id = astTestIdent( tfrm ) ? astGetIdent( tfrm ) : NULL;
            tfrm = astAnnul( tfrm );
            if( id && strlen( id ) == 1 && id[ 0 ] == s ) break;
         }

/* Check a Frame was found, and that we have CRVAL values for all axes in
   the Frame. */
         if( ifr <= nfr && astGetNaxes( fs ) == hii ) {

/* Attempt to find a conversion route from the Frame found above to the
   supplied WCS Frame. */
            tfs = astConvert( fs, wcsfrm, astGetDomain( wcsfrm ) );
            if( tfs ) {

/* Allocate memory to hold the returned reference point. */
               nax = astGetNaxes( wcsfrm );
               ret = astMalloc( sizeof( double )*(size_t) nax );

/* Transform the original reference position from the "s" Frame to the
   supplied WCS Frame using the Mapping returned by astConvert. */
               astTranN( tfs, 1, hii, 1, crval, 1, nax, 1, ret );

/* Free resources. */
               tfs = astAnnul( tfs );
            }
         }

/* Free resources. */
         fs = astAnnul( fs );

/* Annul any error that occurred reading the FitsChan. */
      } else if( !astOK ) {
         astClearStatus;
      }

/* Re-instate error reporting. */
      astReporting( oldreporting );
   }

/* Free resources. */
   if( crval ) crval = astFree( crval );
   fc = astAnnul( fc );

/* If an error occurred, free the returned array. */
   if( !astOK ) ret = astFree( ret );

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

static void ReadFits( AstFitsChan *this, int *status ){

/*
*++
*  Name:
c     astReadFits
f     AST_READFITS

*  Purpose:
*     Read cards into a FitsChan from the source function.

*  Type:
*     Public virtual function.

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

*  Class Membership:
*     FitsChan method.

*  Description:
c     This function
f     This routine
*     reads cards from the source function that was specified when the
*     FitsChan was created, and stores them in the FitsChan. This
*     normally happens once-only, when the FitsChan is accessed for the
*     first time.
c     This function
f     This routine
*     provides a means of forcing a re-read of the external source, and
*     may be useful if (say) new cards have been deposited into the
*     external source. Any newcards read from the source are appended to
*     the end of the current contents of the FitsChan.

*  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 no source function was
*     specified when the FitsChan was created.
*     - The SourceFile attribute is ignored by this
c     function.
f     routine.
*     New cards are read from the source file whenever a new value is
*     assigned to the SourceFile attribute.

*--
*/

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

/* If no source function is available, re-instate any saved source
   function pointer. */
   if( !this->source ) {
      this->source = this->saved_source;
      this->saved_source = NULL;
   }

/* Call the source function. */
   ReadFromSource( this, status );
}

static void ReadFromSource( AstFitsChan *this, int *status ){

/*
*  Name:
*     ReadFromSource

*  Purpose:
*     Fill the FitsChan by reading cards from the source function.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void ReadFromSource( AstFitsChan *this, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The source function specified when the FitsChan was created is
*     called repeatedly until it returns a NULL pointer. The string
*     returned by each such call is assumed to be a FITS header card,
*     and is stored in the FitsChan using astPutFits.
*
*     If no source function was provided, the FitsChan is left as supplied.
*     This is different to a standard Channel, which tries to read data
*     from standard input if no source function is provided.
*
*     This function should be called at the start of most public or protected
*     FitsChan functions, and most private functions that are used to override
*     methods inherited form the Channel class. Previously, this function
*     was called only once, from the FitsChan initialiser (astInitFitschan).
*     However, calling it from astInitFitsChan means that application code
*     cannot use the astPutChannelData function with a FitsChan, since the
*     source function would already have been called by the time the
*     FitsChan constructor returned (and thus before astPutChannelData
*     could have been called). In order to ensure that the source
*     function is called only once, this function now nullifies the source
*     function pointer after its first use.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     status
*        Pointer to the inherited status variable.

*  Notes:
*     -  The new cards are appended to the end of the FitsChan.
*     -  The first of the new cards is made the current card on exit. If no
*     source function is supplied, the current card is left unchanged.
*/

/* Local Variables: */
   const char *(* source)( void ); /* Pointer to source function */
   const char *card;               /* Pointer to externally-read header card */
   int icard;                      /* Current card index on entry */

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

/* Only proceed if source function and wrapper were supplied when the FitsChan
   was created and are still available. */
   if( this->source && this->source_wrap ){

/* Save the source function pointer and then nullify the pointer in the
   FitsChan structure. This avoids infinte loops. */
      source = this->source;
      this->source = NULL;

/* Save the source fubnction pointer in the FitsChan so that it can be
   re-instated if required (e.g. by astReadFits). */
      this->saved_source = source;

/* Ensure the FitsChan is at end-of-file. This will result in the
   new cards being appended to the end of the FitsChan. */
      astSetCard( this, INT_MAX );

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

/* Obtain the first header card from the source function. This is an
   externally supplied function which may not be thread-safe, so lock a
   mutex first. Also store the channel data pointer in a global variable
   so that it can be accessed in the source function using macro
   astChannelData. */
      astStoreChannelData( this );
      LOCK_MUTEX2;
      card = ( *this->source_wrap )( source, status );
      UNLOCK_MUTEX2;

/* Loop until a NULL pointer is returned by the source function, or an
   error occurs. */
      while( card && astOK ){

/* Store the card in the FitsChan. */
         astPutFits( this, card, 0 );

/* Free the memory holding the header card. */
         card = (char *) astFree( (void *) card );

/* Obtain the next header card. Also store the channel data pointer in a
   global variable so that it can be accessed in the source function using
   macro astChannelData. */
         astStoreChannelData( this );
         LOCK_MUTEX2;
         card = ( *this->source_wrap )( source, status );
         UNLOCK_MUTEX2;
      }

/* Set the current card index so that the first of the new cards will be the
   next card to be read from the FitsChan. */
      astSetCard( this, icard );
   }
}

static void RemoveTables( AstFitsChan *this, const char *key, int *status ){

/*
*++
*  Name:
c     astRemoveTables
f     AST_REMOVETABLES

*  Purpose:
*     Remove one or more tables from a FitsChan.

*  Type:
*     Public virtual function.

*  Synopsis:
c     #include "fitschan.h"

c     void astRemoveTables( AstFitsChan *this, const char *key )
f     CALL AST_REMOVETABLES( THIS, KEY, STATUS )

*  Class Membership:
*     FitsChan method.

*  Description:
c     This function
f     This routine
*     removes the named tables from the FitsChan, it they exist (no error
*     is reported if any the tables do not exist).

*  Parameters:
c     this
f     THIS = INTEGER (Given)
*        Pointer to the FitsChan.
c     key
f     KEY = CHARACTER * ( * ) (Given)
*        The key indicating which tables to exist. A single key or a
*        comma-separated list of keys can be supplied. If a blank string
*        is supplied, all tables are removed.
f     STATUS = INTEGER (Given and Returned)
f        The global status.
*--
*/

/* Local variables: */
   char **words;
   int itable;
   int ntable;

/* Return if the global error status has been set, or the FitsChan
   contains no tables KeyMap. */
   if( !astOK || !this->tables ) return;

/* If the string is blank, remove all tables. */
   if( astChrLen( key ) == 0 ) {
      ntable = astMapSize( this->tables );
      for( itable = 0; itable < ntable; itable++ ) {
         astMapRemove( this->tables, astMapKey( this->tables, itable ) );
      }

/* Otherwise, split the supplied comma-separated string up into individual
   items. */
   } else {
      words = astChrSplitC( key, ',', &ntable );

/* Attempt to remove each one, and then free the string. */
      if( astOK ) {
         for( itable = 0; itable < ntable; itable++ ) {
            astMapRemove( this->tables, words[ itable ] );
            words[ itable ] = astFree( words[ itable ] );
         }
      }

/* Free the list. */
      words = astFree( words );
   }
}

static void RetainFits( AstFitsChan *this, int *status ){

/*
*++
*  Name:
c     astRetainFits
f     AST_RETAINFITS

*  Purpose:
*     Indicate that the current card in a FitsChan should be retained.

*  Type:
*     Public virtual function.

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

*  Class Membership:
*     FitsChan method.

*  Description:
c     This function
f     This routine
*     stores a flag with the current card in the FitsChan indicating that
*     the card should not be removed from the FitsChan when an Object is
*     read from the FitsChan using
c     astRead.
f     AST_READ.
*
*     Cards that have not been flagged in this way are removed when a
*     read operation completes succesfully, but only if the card was used
*     in the process of creating the returned AST Object. Any cards that
*     are irrelevant to the creation of the AST Object are retained whether
*     or not they are flagged.

*  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).
*     - The current card is not changed by this function.
*--
*/

/* Local variables: */
   int flags;

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* Return if the global error status has been set, or the current card
   is not defined. */
   if( !astOK || !this->card ) return;

/* Set the PROTECTED flag in the current card. */
   flags = ( (FitsCard *) this->card )->flags;
   ( (FitsCard *) this->card )->flags = flags | PROTECTED;
}

static void RoundFString( char *text, int width, int fitsrnd, int *status ){
/*
*  Name:
*     RoundString

*  Purpose:
*     Modify a formatted floating point number to round out long
*     sequences of zeros or nines.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void RoundFString( char *text, int width, int fitsrnd )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The supplied string is assumed to be a valid decimal representation of
*     a floating point number. It is searched for sub-strings consisting
*     of NSEQ or more adjacent zeros, or NSEQ or more adjacent nines. If
*     found, and if the sub-string extends beyond the number of protected
*     digits specified by "fitsrnd", the string is modified to represent the
*     result of rounding the number to remove the sequence of zeros or nines.

*  Parameters:
*     text
*        The formatted number. Modified on exit to round out long
*        sequences of zeros or nines. The returned string is right justified.
*     width
*        The minimum field width to use. The value is right justified in
*        this field width. Ignored if zero.
*     fitsrnd
*        The value of the FitsRounding attribute - the number of leading
*        digits to protect from rounding.
*/

/* Local Constants: */
#define NSEQ  4    /* No. of adjacent 0's or 9's to produce rounding */

/* Local Variables: */
   char *a;
   char *c;
   char *d;
   char *dot;
   char *e;
   char *end;
   char *exp;
   char *ltext;
   char *prnd;
   char *seq0;
   char *seq9;
   int bu;
   int first;
   int i;
   int len0;
   int len;
   int lexp;
   int nls;
   int nsig;
   int seqlen;
   int started;

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

/* Save the original length of the text. */
   len0 = strlen( text );

/* Allocate a buffer and store a copy of the text with an extra leading
   space. */
   len = len0 + 1;
   ltext = astMalloc( len + 1 );
   if( ltext ) {
      *ltext = ' ';
      strcpy( ltext + 1, text );

/* Locate the start of any exponent string in the local copy. ALso get
   its length. */
      exp = strpbrk( ltext, "dDeE" );
      lexp = exp ? strlen(exp) : 0;

/* Get a pointer to the terminator (either the exponent or the null at the end of
   the string). */
      end = exp ? exp : ltext + len;

/* Values of "fitsrnd" that are less than 1 are equivalebnt to 1. */
      if( fitsrnd < 1 ) fitsrnd = 1;

/* Get a pointer (prnd) to the character that defines the first position
   at which rounding can occur. Step through the text from the start
   until "fitsrnd" digits have been found (ignoring leading zeros) or
   the terminator is reached. Also note if the string includes a decimal
   point. */
      started = 0;
      nsig = 0;
      prnd = NULL;
      dot = NULL;

      c = ltext - 1;
      while( ++c < end ){
         if( isdigit( *c ) ){
            if( *c != '0' ) started = 1;
            if( started ) {
               if( ++nsig == fitsrnd ) prnd = c;
            }
         } else if( *c == '.' ){
            dot = c;
         }
      }

/* If no non-zero digits were found, just return the most simple zero
   value. */
      if( !started ) {
         if( dot ){
            strcpy( ltext, "0.0" );
         } else {
            strcpy( ltext, "0" );
         }

/* If the "fitsrnd" value is larger than the number of significant
   figures in the value, "prnd" will be NULL, in which case we can do
   no rounding. If "prnd" is not NULL, we round any sequences by modifying
   the contents of the local text buffer. */
      } else if( prnd ) {

/* Initialise the current character pointer to point to the character immediately
   before the terminator (either the exponent or the null at the end of the
   string). */
         c = end - 1;

/* Loop backwards over any trailing spaces */
         while( c >= ltext && *c == ' ' ) c--;

/* Initialise flags to show we are not currently in any sequence. */
         seq9 = NULL;
         seq0 = NULL;
         seqlen = 0;

/* Loop backwards over the remaining characters, from end to start of the
   string. */
          while( c >= ltext ) {

/* If we are in a sequence of 9's (i.e. the previous character was a 9)... */
            if( seq9 ){

/* If the current character is a 9, increment the length of the sequence. */
               if( *c == '9' ) {
                  seqlen++;

/* If the current character is not a 9 but is a digit, the previous digit was
   the start of the sequence. */
               } else if( isdigit( *c ) ){

/* If the sequence ends beyond the rounding limit and has a length of at
   least NSEQ, replace it with a rounded value. */
                  if( seq9 > prnd && seqlen >= NSEQ ){

/* Replace all digits in or after the sequence with 0's. */
                     d = c;
                     while( ++d < end ) {
                        if( isdigit(*d) ) *d = '0';
                     }

/* Increment the current digit (we know it's not a 9 and so it can
   always be incremented to a higher digit value). */
                     (*c)++;
                  }

/* If the current digit was incremented above to a 9 (i.e. the original
   value was 8), we need to start a new sequence of 9's. */
                  if( *c == '9' ) {
                     seq9 = c;
                     seqlen = 1;

/* If the current digit is a 0, we need to start a new sequence of 0's. */
                  } else if( *c == '0' ) {
                     seq0 = c;
                     seq9 = NULL;
                     seqlen = 1;

/* Otherwise, indicate we are no longer in a sequence of 9's. */
                  } else {
                     seq9 = NULL;
                     seqlen = 0;
                  }
               }

/* Otherwise, if we are in a sequence of 0's (i.e. the previous character
   was a 0)... */
            } else if( seq0 ){

/* If the current character is a 0, increment the length of the sequence. */
               if( *c == '0' ) {
                  seqlen++;

/* If the current character is not a 0 but is a digit, the previous digit
   was the start of the sequence. */
               } else if( isdigit( *c ) ){

/* If the sequence ends beyond the rounding limit and has a length of at
   least NSEQ, replace it with a rounded value. */
                  if( seq0 > prnd && seqlen >= NSEQ ){

/* Replace all digits in or after the sequence with 0's. */
                     d = c;
                     while( ++d < end ) {
                        if( isdigit(*d) ) *d = '0';
                     }
                  }

/* If the current digit is a 9, we need to start a new sequence of 9's. */
                  if( *c == '9' ) {
                     seq9 = c;
                     seq0 = NULL;
                     seqlen = 1;

/* Otherwise, indicate we are no longer in a sequence of 0's. */
                  } else {
                     seq0 = NULL;
                     seqlen = 0;
                  }
               }

/* Otherwise we are not in a sequence. */
            } else {

/* If we are beyond the point at which any further rounding can occur, we
   can leave the loop. */
               if( c < prnd ) {
                  break;

/* If the current character is a 9, we are at the start of a possible
   sequence of 9's. */
               } else if( *c == '9' ) {
                  seq9 = c;
                  seqlen = 1;

/* If the current character is a 0, we are at the start of a possible
   sequence of 0's. */
               } else if( *c == '0' ) {
                  seq0 = c;
                  seqlen = 1;
               }
            }

/* Move the current character pointer to the next more significant
   character in the formatted number. */
            c--;
         }

/* If "seq9" is still set at the end of the loop, it must mean that the first
   digit in the string is the start of a sequence of 9's. If the sequence ends
   beyond the rounding limit and has a length of at least NSEQ, replace it with
   a rounded value. */
         if( seq9 && seq9 > prnd && seqlen >= NSEQ ){

/* Replace all digits in or after the sequence with 0's. */
            first = 1;
            c = ltext - 1;
            while( ++c < end ) {
               if( isdigit( *c ) ) {
                  *c = '0';

/* If this is the first digit, move any characters (eg "+" or "-" ) that
   occur before the digit one space to the left (we added a leading space
   to the local buffer so that we could do this), and insert a "1"
   immediately before the current zero. */
                  if( first ) {
                     e = ltext + 1;
                     while( e < c ) {
                        e[ -1 ] = e[ 0 ];
                        e++;
                     }
                     e[ -1 ] = '1';
                     first = 0;
                  }
               }
            }
         }
      }

/* Remove any trailing zeros which occur to the right of a decimal point. */
      if( dot ) {

/* Initialise the current character pointer to point to the character
   immediately before the terminator (either the exponent or the null at
   the end of the string). */
         c = end - 1;

/* Loop backwards over any trailing spaces or zeros. */
         while( c >= ltext && ( *c == ' ' || *c == '0' ) ) c--;

/* Ensure one space is left after a decimal point, if there is room for it. */
         if( *c == '.' && c < end - 1 ) *(++c) = '0';

/* Move the terminator to the following character. We are sure not to
   overrun the text buffer since "c" is always less than "exp". */
         if( exp ) {
            for( i = 0; i <= lexp; i++ ) c[ i + 1 ] = exp[ i ];
         } else {
            c[ 1 ] = 0;
         }
      }

/* Copy the rounded string into the supplied text string, if there is room. */
      if( astChrLen( ltext ) <= len0 ) strcpy( text, ltext );

/* Free local resources. */
      ltext = astFree( ltext );

/* If a minimum field width has been given, move the text to the right
   hand end of the supplied buffer. */
      if( width ) {
         end = text + len0;
         c = text + astChrLen( text );
         *end = 0;
         if( c != end ) {
            while( c > text ) *(--end) = *(--c);
            while( end > text ) *(--end) = ' ';
         }
      }

/* If a minimum field width was given, shunt the text to the left in
   order to reduce the used field width to the specified value. This
   requires there to be some leading spaces (because we do not want to
   loose any non-blank characters from the left hand end of the string).
   If there are insufficient leading spaces to allow the field width to
   be reduced to the specified value, then reduce the field width as far
   as possible. First find the number of spaces we would like to remove
   from the front of the string (in order to reduce the used width to the
   specified value). */
      bu = len0 - width;

/* If we need to remove any leading spaces... */
      if( width > 0 && bu > 0 ) {

/* Find the number of leading spaces which are available to be removed. */
         c = text - 1;
         while( *(++c) == ' ' );
         nls = c - text;

/* If there are insufficient leading spaces, just use however many there
   are. */
         if( bu > nls ) bu = nls;

/* Shift the string. */
         c = text;
         a = c + bu;
         while( ( *(c++) = *(a++) ) );
      }
   }

/* Undefine local constants. */
#undef NSEQ
}

static int SAOTrans( AstFitsChan *this, AstFitsChan *out, const char *method,
                     const char *class, int *status ){
/*
*  Name:
*     SAOTrans

*  Purpose:
*     Translate an SAO encoded header into a TPN encoded header.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int SAOTrans( AstFitsChan *this, AstFitsChan *out, const char *method,
*                   const char *class, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Search "this" for keywords that give a description of a distorted
*     TAN projection using the SAO representation and, if found, write
*     keywords to "out" that describe an equivalent projection using TPN
*     representation. The definition of the SAO polynomial is taken from
*     the platepos.c file included in Doug Mink's WCSTools.

*  Parameters:
*     this
*        Pointer to the FitsChan to read.
*     out
*        Pointer to a FitsCHan in which to store translated keywords.
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     Non-zero if "this" contained an SAO encoded header. Zero otherwise.

*/

#define NC 13

/* Local Variables: */
   char keyname[10];
   double co[ 2 ][ NC ];
   double pv;
   int i;
   int is_sao;
   int m;
   int ok;
   int result;

/* Initialise */
   result = 0;

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

/* Check there are exactly two CTYPE keywords in the header. */
   if( 2 == astKeyFields( this, "CTYPE%d", 0, NULL, NULL ) ){

/* Initialise all cooefficients. */
      memset( co, 0, sizeof( co ) );

/* Get the required SAO keywords. */
      is_sao = 1;
      ok = 1;
      for( i = 0; i < 2 && ok && is_sao; i++ ) {

         ok = 0;
         for( m = 0; m < NC; m++ ) {

/* Get the value of the next "COi_j" keyword. If any of the first 3 values
   are missing on either axis, we assume this is not an SAO header. */
            sprintf( keyname, "CO%d_%d", i + 1, m + 1 );
            if( !GetValue( this, keyname, AST__FLOAT, &co[ i ][ m ], 0, 1, method,
                           class, status ) ) {
               if( m < 3 ) is_sao = 0;
               break;
            }

/* Check that we have at least one non-zero coefficient (excluding the
   first constant term ). */
            if( co[ i ][ m ] != 0.0 && m > 0 ) ok = 1;
         }
      }

/* If this is an SAO header..  */
      if( is_sao ) {

/* Issue a warning if all coefficients for this axis are zero. */
         if( !ok ) {
            Warn( this, "badpv", "This FITS header describes an SAO encoded "
                  "distorted TAN projection, but all the distortion "
                  "coefficients for at least one axis are zero.", method, class,
                  status );

/* Otherwise, calculate and store the equivalent PV projection parameters. */
         } else {
            pv = co[ 0 ][ 0 ];
            if( pv != AST__BAD ) SetValue( out, "PV1_0", &pv,
                                           AST__FLOAT, NULL, status );

            pv = co[ 0 ][ 1 ];
            if( pv != AST__BAD ) SetValue( out, "PV1_1", &pv,
                                           AST__FLOAT, NULL, status );

            pv = co[ 0 ][ 2 ];
            if( pv != AST__BAD ) SetValue( out, "PV1_2", &pv,
                                           AST__FLOAT, NULL, status );

            pv = 0.0;
            if( co[ 0 ][ 3 ] != AST__BAD ) pv += co[ 0 ][ 3 ];
            if( co[ 0 ][ 10 ] != AST__BAD ) pv += co[ 0 ][ 10 ];
            if( pv != AST__BAD ) SetValue( out, "PV1_4", &pv,
                                           AST__FLOAT, NULL, status );

            pv = co[ 0 ][ 5 ];
            if( pv != AST__BAD ) SetValue( out, "PV1_5", &pv,
                                           AST__FLOAT, NULL, status );

            pv = 0.0;
            if( co[ 0 ][ 4 ] != AST__BAD ) pv += co[ 0 ][ 4 ];
            if( co[ 0 ][ 10 ] != AST__BAD ) pv += co[ 0 ][ 10 ];
            if( pv != AST__BAD ) SetValue( out, "PV1_6", &pv,
                                           AST__FLOAT, NULL, status );

            pv = 0.0;
            if( co[ 0 ][ 6 ] != AST__BAD ) pv += co[ 0 ][ 6 ];
            if( co[ 0 ][ 11 ] != AST__BAD ) pv += co[ 0 ][ 11 ];
            if( pv != AST__BAD ) SetValue( out, "PV1_7", &pv,
                                           AST__FLOAT, NULL, status );

            pv = 0.0;
            if( co[ 0 ][ 8 ] != AST__BAD ) pv += co[ 0 ][ 8 ];
            if( co[ 0 ][ 12 ] != AST__BAD ) pv += co[ 0 ][ 12 ];
            if( pv != AST__BAD ) SetValue( out, "PV1_8", &pv,
                                           AST__FLOAT, NULL, status );

            pv = 0.0;
            if( co[ 0 ][ 9 ] != AST__BAD ) pv += co[ 0 ][ 9 ];
            if( co[ 0 ][ 11 ] != AST__BAD ) pv += co[ 0 ][ 11 ];
            if( pv != AST__BAD ) SetValue( out, "PV1_9", &pv,
                                           AST__FLOAT, NULL, status );

            pv = 0.0;
            if( co[ 0 ][ 7 ] != AST__BAD ) pv += co[ 0 ][ 7 ];
            if( co[ 0 ][ 12 ] != AST__BAD ) pv += co[ 0 ][ 12 ];
            if( pv != AST__BAD ) SetValue( out, "PV1_10", &pv,
                                           AST__FLOAT, NULL, status );

            pv = co[ 1 ][ 0 ];
            if( pv != AST__BAD ) SetValue( out, "PV2_0", &pv,
                                           AST__FLOAT, NULL, status );

            pv = co[ 1 ][ 2 ];
            if( pv != AST__BAD ) SetValue( out, "PV2_1", &pv,
                                           AST__FLOAT, NULL, status );

            pv = co[ 1 ][ 1 ];
            if( pv != AST__BAD ) SetValue( out, "PV2_2", &pv,
                                           AST__FLOAT, NULL, status );

            pv = 0.0;
            if( co[ 1 ][ 4 ] != AST__BAD ) pv += co[ 1 ][ 4 ];
            if( co[ 1 ][ 10 ] != AST__BAD ) pv += co[ 1 ][ 10 ];
            if( pv != AST__BAD ) SetValue( out, "PV2_4", &pv,
                                           AST__FLOAT, NULL, status );

            pv = co[ 1 ][ 5 ];
            if( pv != AST__BAD ) SetValue( out, "PV2_5", &pv,
                                           AST__FLOAT, NULL, status );

            pv = 0.0;
            if( co[ 1 ][ 3 ] != AST__BAD ) pv += co[ 1 ][ 3 ];
            if( co[ 1 ][ 10 ] != AST__BAD ) pv += co[ 1 ][ 10 ];
            if( pv != AST__BAD ) SetValue( out, "PV2_6", &pv,
                                           AST__FLOAT, NULL, status );

            pv = 0.0;
            if( co[ 1 ][ 7 ] != AST__BAD ) pv += co[ 1 ][ 7 ];
            if( co[ 1 ][ 12 ] != AST__BAD ) pv += co[ 1 ][ 12 ];
            if( pv != AST__BAD ) SetValue( out, "PV2_7", &pv,
                                           AST__FLOAT, NULL, status );

            pv = 0.0;
            if( co[ 1 ][ 9 ] != AST__BAD ) pv += co[ 1 ][ 9 ];
            if( co[ 1 ][ 11 ] != AST__BAD ) pv += co[ 1 ][ 11 ];
            if( pv != AST__BAD ) SetValue( out, "PV2_8", &pv,
                                           AST__FLOAT, NULL, status );

            pv = 0.0;
            if( co[ 1 ][ 8 ] != AST__BAD ) pv += co[ 1 ][ 8 ];
            if( co[ 1 ][ 12 ] != AST__BAD ) pv += co[ 1 ][ 12 ];
            if( pv != AST__BAD ) SetValue( out, "PV2_9", &pv,
                                           AST__FLOAT, NULL, status );

            pv = 0.0;
            if( co[ 1 ][ 6 ] != AST__BAD ) pv += co[ 1 ][ 6 ];
            if( co[ 1 ][ 11 ] != AST__BAD ) pv += co[ 1 ][ 11 ];
            if( pv != AST__BAD ) SetValue( out, "PV2_10", &pv,
                                           AST__FLOAT, NULL, status );

/* From an example header provided by Bill Joye, it seems that the SAO
   polynomial includes the rotation and scaling effects of the CD matrix.
   Therefore we mark as read all CDi_j, CDELT and CROTA values. Without
   this, the rotation and scaling would be applied twice. First, mark the
   original values as having been used, no matter which FitsChan they are
   in. */
            GetValue( this, "CD1_1", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( this, "CD1_2", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( this, "CD2_1", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( this, "CD2_2", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( this, "PC1_1", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( this, "PC1_2", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( this, "PC2_1", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( this, "PC2_2", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( this, "CDELT1", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( this, "CDELT2", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( this, "CROTA1", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( this, "CROTA2", AST__FLOAT, &pv, 0, 1, method, class, status );

            GetValue( out, "CD1_1", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( out, "CD1_2", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( out, "CD2_1", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( out, "CD2_2", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( out, "PC1_1", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( out, "PC1_2", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( out, "PC2_1", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( out, "PC2_2", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( out, "CDELT1", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( out, "CDELT2", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( out, "CROTA1", AST__FLOAT, &pv, 0, 1, method, class, status );
            GetValue( out, "CROTA2", AST__FLOAT, &pv, 0, 1, method, class, status );

/* Now store new default values in the returned FitsChan. */
            pv = 1.0;
            SetValue( out, "PC1_1", &pv, AST__FLOAT, NULL,
                      status );
            SetValue( out, "PC2_2", &pv, AST__FLOAT, NULL,
                      status );
            SetValue( out, "CDELT1", &pv, AST__FLOAT, NULL,
                      status );
            SetValue( out, "CDELT2", &pv, AST__FLOAT, NULL,
                      status );

            pv = 0.0;
            SetValue( out, "PC1_2", &pv, AST__FLOAT, NULL,
                      status );
            SetValue( out, "PC2_1", &pv, AST__FLOAT, NULL,
                      status );

/* Indicate we have converted an SAO header. */
            result = 1;
         }
      }
   }

/* Return a flag indicating if an SAO header was found. */
   return result;
}
#undef NC

static int SearchCard( AstFitsChan *this, const char *name,
                       const char *method, const char *class, int *status ){

/*
*  Name:
*     SearchCard

*  Purpose:
*     Search the whole FitsChan for a card refering to given keyword.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"

*     int SearchCard( AstFitsChan *this, const char *name,
*                     const char *method, const char *class, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Searches the whole FitsChan for a card refering to the supplied keyword,
*     and makes it the current card. The card following the current card is
*     checked first. If this is not the required card, then a search is
*     performed starting with the first keyword in the FitsChan.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     name
*        Pointer to a string holding the keyword name.
*     method
*        Pointer to string holding name of calling method.
*     status
*        Pointer to the inherited status variable.

*  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 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;

/* The required card is very often the next card in the FitsChan, so check the
   next card, and only search the entire FitsChan if the check fails. */
   MoveCard( this, 1, method, class, status );
   if( !astFitsEof( this ) &&
       !Ustrncmp( CardName( this, status ), name, FITSNAMLEN, status ) ){
      ret = 1;

/* If the next card is not the required card, rewind the FitsChan back to
   the first card. */
   } else {
      astClearCard( this );

/* Attempt to find the supplied keyword, searching from the first card. */
      ret = FindKeyCard( this, name, method, class, status );
   }

/* Return. */
   return ret;
}

static void SetAlgCode( char *buf, const char *algcode, int *status ){
/*
*  Name:
*     SetAlgCode

*  Purpose:
*    Create a non-linear CTYPE string from a system code and an algorithm
*    code.

*  Type:
*     Private function.

*  Synopsis:
*     void SetAlgCode( char *buf, const char *algcode, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     FITS-WCS paper 1 says that non-linear axes must have a CTYPE of the
*     form "4-3" (e.g. "VRAD-TAB"). This function handles the truncation
*     of long system codes, or the padding of short system codes.

*  Parameters:
*     buf
*        A buffer in which is stored the system code. Modified on exit to
*        hold the combined CTYPE value. It should have a length long
*        enough to hold the system code and the algorithm code.
*     algcode
*        Pointer to a string holding the algorithm code (with a leading
*        "-", e.g. "-TAB").
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   int nc;

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

/* Pad the supplied string to at least 4 characters using "-" characters. */
   nc = strlen( buf );
   while( nc < 4 ) buf[ nc++ ] = '-';

/* Insert the null-terminated code at position 4. */
   strcpy( buf + 4, algcode );
}

static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
/*
*  Name:
*     SetAttrib

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

*  Type:
*     Private function.

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

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

*  Description:
*     This function assigns an attribute value for a FitsChan, the
*     attribute and its value being specified by means of a string of

*     the form:
*
*        "attribute= value "
*
*     Here, "attribute" specifies the attribute name and should be in
*     lower case with no white space present. The value to the right
*     of the "=" should be a suitable textual representation of the
*     value to be assigned and this will be interpreted according to
*     the attribute's data type.  White space surrounding the value is
*     only significant for string attributes.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     setting
*        Pointer to a null-terminated string specifying the new attribute
*        value.
*/

/* Local Variables: */
   AstFitsChan *this;            /* Pointer to the FitsChan structure */
   const char *class;            /* Object class */
   double dval;                  /* Double attribute value */
   int ival;                     /* Integer attribute value */
   int len;                      /* Length of setting string */
   int nc;                       /* Number of characters read by astSscanf */
   int offset;                   /* Offset of attribute string */
   int warn;                     /* Offset of Warnings 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 setting string. */
   len = (int) strlen( setting );

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

/* Card. */
/* ----- */
   if ( nc = 0,
        ( 1 == astSscanf( setting, "card= %d %n", &ival, &nc ) )
        && ( nc >= len ) ) {
      astSetCard( this, ival );

/* Encoding. */
/* --------- */
   } else if( nc = 0,
        ( 0 == astSscanf( setting, "encoding=%n%*[^\n]%n", &ival, &nc ) )
        && ( nc >= len ) ) {
      nc = ChrLen( setting + ival, status );
      if( !Ustrncmp( setting + ival, NATIVE_STRING, nc, status ) ){
         astSetEncoding( this, NATIVE_ENCODING );
      } else if( !Ustrncmp( setting + ival, FITSPC_STRING, nc, status ) ){
         astSetEncoding( this, FITSPC_ENCODING );
      } else if( !Ustrncmp( setting + ival, FITSPC_STRING2, nc, status ) ){
         astSetEncoding( this, FITSPC_ENCODING );
      } else if( !Ustrncmp( setting + ival, FITSWCS_STRING, nc, status ) ){
         astSetEncoding( this, FITSWCS_ENCODING );
      } else if( !Ustrncmp( setting + ival, FITSWCS_STRING2, nc, status ) ){
         astSetEncoding( this, FITSWCS_ENCODING );
      } else if( !Ustrncmp( setting + ival, FITSIRAF_STRING, nc, status ) ){
         astSetEncoding( this, FITSIRAF_ENCODING );
      } else if( !Ustrncmp( setting + ival, FITSIRAF_STRING2, nc, status ) ){
         astSetEncoding( this, FITSIRAF_ENCODING );
      } else if( !Ustrncmp( setting + ival, FITSAIPS_STRING, nc, status ) ){
         astSetEncoding( this, FITSAIPS_ENCODING );
      } else if( !Ustrncmp( setting + ival, FITSAIPS_STRING2, nc, status ) ){
         astSetEncoding( this, FITSAIPS_ENCODING );
      } else if( !Ustrncmp( setting + ival, FITSAIPSPP_STRING, nc, status ) ){
         astSetEncoding( this, FITSAIPSPP_ENCODING );
      } else if( !Ustrncmp( setting + ival, FITSAIPSPP_STRING2, nc, status ) ){
         astSetEncoding( this, FITSAIPSPP_ENCODING );
      } else if( !Ustrncmp( setting + ival, FITSCLASS_STRING, nc, status ) ){
         astSetEncoding( this, FITSCLASS_ENCODING );
      } else if( !Ustrncmp( setting + ival, FITSCLASS_STRING2, nc, status ) ){
         astSetEncoding( this, FITSCLASS_ENCODING );
      } else if( !Ustrncmp( setting + ival, DSS_STRING, nc, status ) ){
         astSetEncoding( this, DSS_ENCODING );
      } else {
         astError( AST__BADAT, "astSet(%s): Unknown encoding system '%s' "
                   "requested for a %s.", status, class, setting + ival, class );
      }

/* AltAxes. */
/* -------- */
   } else if ( nc = 0,
        ( 1 == astSscanf( setting, "altaxes= %d %n", &ival, &nc ) )
        && ( nc >= len ) ) {
      nc = ChrLen( setting + ival, status );
      if( !Ustrncmp( setting + ival, ALTAXES_NONE_STRING, nc, status ) ){
         astSetAltAxes( this, ALTAXES_NONE );
      } else if( !Ustrncmp( setting + ival, ALTAXES_IDENT_STRING, nc, status ) ){
         astSetAltAxes( this, ALTAXES_IDENT );
      } else if( !Ustrncmp( setting + ival, ALTAXES_ALL_STRING, nc, status ) ){
         astSetAltAxes( this, ALTAXES_ALL );
      } else {
         astError( AST__BADAT, "astSet(%s): Illegal value '%s' supplied for "
                   "attribute AltAxes.", status, class, setting + ival );
      }

/* FitsDigits. */
/* ----------- */
   } else if ( nc = 0,
        ( 1 == astSscanf( setting, "fitsdigits= %d %n", &ival, &nc ) )
        && ( nc >= len ) ) {
      astSetFitsDigits( this, ival );

/* FitsRounding. */
/* ------------- */
   } else if ( nc = 0,
        ( 1 == astSscanf( setting, "fitsrounding= %d %n", &ival, &nc ) )
        && ( nc >= len ) ) {
      astSetFitsRounding( this, ival );

/* FitsAxisOrder. */
/* -------------- */
   } else if ( nc = 0,
               ( 0 == astSscanf( setting, "fitsaxisorder=%n%*[^\n]%n",
                                 &offset, &nc ) )
               && ( nc >= len ) ) {
      astSetFitsAxisOrder( this, setting + offset );

/* CDMatrix */
/* -------- */
   } else if ( nc = 0,
        ( 1 == astSscanf( setting, "cdmatrix= %d %n", &ival, &nc ) )
        && ( nc >= len ) ) {
      astSetCDMatrix( this, ival );

/* DefB1950 */
/* -------- */
   } else if ( nc = 0,
        ( 1 == astSscanf( setting, "defb1950= %d %n", &ival, &nc ) )
        && ( nc >= len ) ) {
      astSetDefB1950( this, ival );

/* TabOK */
/* ----- */
   } else if ( nc = 0,
        ( 1 == astSscanf( setting, "tabok= %d %n", &ival, &nc ) )
        && ( nc >= len ) ) {
      astSetTabOK( this, ival );

/* ForceTab */
/* -------- */
   } else if ( nc = 0,
        ( 1 == astSscanf( setting, "forcetab= %d %n", &ival, &nc ) )
        && ( nc >= len ) ) {
      astSetForceTab( this, ival );

/* CarLin */
/* ------ */
   } else if ( nc = 0,
        ( 1 == astSscanf( setting, "carlin= %d %n", &ival, &nc ) )
        && ( nc >= len ) ) {
      astSetCarLin( this, ival );

/* SipReplace */
/* ---------- */
   } else if ( nc = 0,
        ( 1 == astSscanf( setting, "sipreplace= %d %n", &ival, &nc ) )
        && ( nc >= len ) ) {
      astSetSipReplace( this, ival );

/* FitsTol. */
/* -------- */
   } else if ( nc = 0,
        ( 1 == astSscanf( setting, "fitstol= %lg %n", &dval, &nc ) )
        && ( nc >= len ) ) {
      astSetFitsTol( this, dval );

/* PolyTan */
/* ------- */
   } else if ( nc = 0,
        ( 1 == astSscanf( setting, "polytan= %d %n", &ival, &nc ) )
        && ( nc >= len ) ) {
      astSetPolyTan( this, ival );

/* SipOK */
/* ------- */
   } else if ( nc = 0,
        ( 1 == astSscanf( setting, "sipok= %d %n", &ival, &nc ) )
        && ( nc >= len ) ) {
      astSetSipOK( this, ival );

/* Iwc */
/* --- */
   } else if ( nc = 0,
        ( 1 == astSscanf( setting, "iwc= %d %n", &ival, &nc ) )
        && ( nc >= len ) ) {
      astSetIwc( this, ival );

/* Clean */
/* ----- */
   } else if ( nc = 0,
        ( 1 == astSscanf( setting, "clean= %d %n", &ival, &nc ) )
        && ( nc >= len ) ) {
      astSetClean( this, ival );

/* Warnings. */
/* -------- */
   } else if ( nc = 0,
               ( 0 == astSscanf( setting, "warnings=%n%*[^\n]%n", &warn, &nc ) )
               && ( nc >= len ) ) {
      astSetWarnings( this, setting + warn );

/* Define a macro to see if the setting string matches any of the
   read-only attributes of this class. */
#define MATCH(attrib) \
        ( nc = 0, ( 0 == astSscanf( setting, attrib "=%*[^\n]%n", &nc ) ) && \
                  ( nc >= len ) )

/* If the attribute was not recognised, use this macro to report an error
   if a read-only attribute has been specified. */
   } else if ( MATCH( "ncard" ) ||
               MATCH( "cardtype" ) ||
               MATCH( "cardcomm" ) ||
               MATCH( "cardname" ) ||
               MATCH( "nkey" ) ||
               MATCH( "allwarnings" ) ){
      astError( AST__NOWRT, "astSet: The setting \"%s\" is invalid for a %s.", status,
                setting, astGetClass( this ) );
      astError( AST__NOWRT, "This is a read-only attribute." , status);

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

static void SetCard( AstFitsChan *this, int icard, int *status ){

/*
*+
*  Name:
*     astSetCard

*  Purpose:
*     Set the value of the Card attribute.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "fitschan.h"

*     void astSetCard( AstFitsChan *this, int icard )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function sets the value of the Card attribute for the supplied
*     FitsChan. This is the index of the next card to be read from the
*     FitsChan. If a value of 1 or less is supplied, the first card in
*     the FitsChan will be read next. If a value greater than the number
*     of cards in the FitsChan is supplied, the FitsChan will be left in an
*     "end-of-file" condition, in which no further read operations can be
*     performed.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     icard
*        The index of the next card to read.

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

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

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* Rewind the FitsChan. */
   astClearCard( this );

/* Move forward the requested number of cards. */
   MoveCard( this, icard - 1, "astSetCard", astGetClass( this ), status );

/* Return. */
   return;
}

static void SetItem( double ****item, int i, int jm, char s, double val, int *status ){
/*
*  Name:
*     SetItem

*  Purpose:
*     Store a value for a axis keyword value in a FitStore structure.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void SetItem( double ****item, int i, int jm, char s, double val, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The supplied keyword value is stored in the specified array,
*     at a position indicated by the axis and co-ordinate version.
*     The array is created or extended as necessary to make room for
*     the new value. Any old value is over-written.

*  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.
*     val
*        The keyword value to store.
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   int el;               /* Array index */
   int nel;              /* Number of elements in array */
   int si;               /* Integer co-ordinate version index */

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

/* 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, "SetItem(fitschan): AST internal error; "
                "co-ordinate version '%c' ( char(%d) ) is invalid.", status, s, s );

/* Check the intermediate axis index is within range. */
   } else if( i < 0 || i > 98 ) {
      astError( AST__INTER, "SetItem(fitschan): AST internal error; "
                "intermediate axis index %d is invalid.", status, i );

/* Check the pixel axis or parameter index is within range. */
   } else if( jm < 0 || jm > 99 ) {
      astError( AST__INTER, "SetItem(fitschan): AST internal error; "
                "pixel axis or parameter index %d is invalid.", status, jm );

/* Otherwise proceed... */
   } else {

/* Store the current number of coordinate versions in the supplied array */
      nel = astSizeOf( (void *) *item )/sizeof(double **);

/* If required, extend the array located by the supplied pointer so that
   it is long enough to hold the specified co-ordinate version. */
      if( nel < si + 1 ){
         *item = (double ***) astGrow( (void *) *item, si + 1,
                                      sizeof(double **) );

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

/* Initialise the new elements to hold NULL. Note, astGrow may add more
   elements to the array than is actually needed, so use the actual current
   size of the array as implied by astSize rather than the index si. */
            for( el = nel;
                 el < astSizeOf( (void *) *item )/sizeof(double **);
                 el++ ) (*item)[el] = NULL;
         }
      }

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

/* Store the currrent number of intermediate axes in the supplied array */
         nel = astSizeOf( (void *) (*item)[si] )/sizeof(double *);

/* If required, extend the array so that it is long enough to hold the
   specified intermediate axis. */
         if( nel < i + 1 ){
            (*item)[si] = (double **) astGrow( (void *) (*item)[si], i + 1,
                                      sizeof(double *) );

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

/* Initialise the new elements to hold NULL. */
               for( el = nel;
                    el < astSizeOf( (void *) (*item)[si] )/sizeof(double *);
                    el++ ) (*item)[si][el] = NULL;
            }
         }

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

/* Store the current number of pixel axis or parameter values in the array. */
            nel = astSizeOf( (void *) (*item)[si][i] )/sizeof(double);

/* If required, extend the array so that it is long enough to hold the
   specified axis. */
            if( nel < jm + 1 ){
               (*item)[si][i] = (double *) astGrow( (void *) (*item)[si][i],
                                                    jm + 1, sizeof(double) );

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

/* Initialise the new elements to hold AST__BAD. */
                  for( el = nel;
                       el < astSizeOf( (void *) (*item)[si][i] )/sizeof(double);
                       el++ ) (*item)[si][i][el] = AST__BAD;
               }
            }

/* If the above went OK, store the supplied keyword value. */
            if( astOK ) (*item)[si][i][jm] = val;
         }
      }
   }
}

static void SetItemC( char *****item, int i, int jm, char s, const char *val,
                      int *status ){
/*
*  Name:
*     SetItemC

*  Purpose:
*     Store a character string for an axis keyword value in a FitStore
*     structure.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void SetItemC( char *****item, int i, int jm, char s, const char *val,
*                    int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The supplied keyword string value is stored in the specified array,
*     at a position indicated by the axis and co-ordinate version.
*     The array is created or extended as necessary to make room for
*     the new value. Any old value is over-written.

*  Parameters:
*     item
*        The address of the pointer within the FitsStore which locates the
*        arrays of values for the required keyword (eg &(store->ctype) ).
*        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 string pointers. These arrays of keyword
*        string pointers 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. RADESYS) 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. CTYPE) which are not indexed by either pixel axis or
*        parameter number.
*     val
*        The keyword string value to store. A copy of the supplied string
*        is taken.
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   int el;               /* Array index */
   int nel;              /* Number of elements in array */
   int si;               /* Integer co-ordinate version index */

/* Check the inherited status and the supplied pointer. */
   if( !astOK || !val ) return;

/* 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, "SetItemC(fitschan): AST internal error; "
                "co-ordinate version '%c' ( char(%d) ) is invalid.", status, s, s );

/* Check the intermediate axis index is within range. */
   } else if( i < 0 || i > 98 ) {
      astError( AST__INTER, "SetItemC(fitschan): AST internal error; "
                "intermediate axis index %d is invalid.", status, i );

/* Check the pixel axis or parameter index is within range. */
   } else if( jm < 0 || jm > 99 ) {
      astError( AST__INTER, "SetItemC(fitschan): AST internal error; "
                "pixel axis or parameter index %d is invalid.", status, jm );

/* Otherwise proceed... */
   } else {

/* Store the current number of coordinate versions in the supplied array */
      nel = astSizeOf( (void *) *item )/sizeof(char ***);

/* If required, extend the array located by the supplied pointer so that
   it is long enough to hold the specified co-ordinate version. */
      if( nel < si + 1 ){
         *item = (char ****) astGrow( (void *) *item, si + 1,
                                      sizeof(char ***) );

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

/* Initialise the new elements to hold NULL. Note, astGrow may add more
   elements to the array than is actually needed, so use the actual current
   size of the array as implied by astSize rather than the index si. */
            for( el = nel;
                 el < astSizeOf( (void *) *item )/sizeof(char ***);
                 el++ ) (*item)[el] = NULL;
         }
      }

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

/* Store the currrent number of intermediate axes in the supplied array */
         nel = astSizeOf( (void *) (*item)[si] )/sizeof(char **);

/* If required, extend the array so that it is long enough to hold the
   specified intermediate axis. */
         if( nel < i + 1 ){
            (*item)[si] = (char ***) astGrow( (void *) (*item)[si], i + 1,
                                              sizeof(char **) );

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

/* Initialise the new elements to hold NULL. */
               for( el = nel;
                    el < astSizeOf( (void *) (*item)[si] )/sizeof(char **);
                    el++ ) (*item)[si][el] = NULL;
            }
         }

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

/* Store the current number of pixel axis or parameter values in the array. */
            nel = astSizeOf( (void *) (*item)[si][i] )/sizeof(char *);

/* If required, extend the array so that it is long enough to hold the
   specified axis. */
            if( nel < jm + 1 ){
               (*item)[si][i] = (char **) astGrow( (void *) (*item)[si][i],
                                                    jm + 1, sizeof(char *) );

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

/* Initialise the new elements to hold NULL. */
                  for( el = nel;
                       el < astSizeOf( (void *) (*item)[si][i] )/sizeof(char *);
                       el++ ) (*item)[si][i][el] = NULL;
               }
            }

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

/* Store a copy of the supplied string, using any pre-allocated memory. */
               (*item)[si][i][jm] = (char *) astStore( (void *) (*item)[si][i][jm],
                                                       (void *) val,
                                                       strlen( val ) + 1 );
            }
         }
      }
   }
}

static void SetSourceFile( AstChannel *this_channel, const char *source_file,
                           int *status ) {
/*
*  Name:
*     SetSourceFile

*  Purpose:
*     Set a new value for the SourceFile attribute.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void SetSourceFile( AstChannel *this, const char *source_file,
*                         int *status )

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

*  Description:
*     This function stores the supplied string as the new value for the
*     SourceFile attribute. In addition, it also attempts to open the
*     file, read FITS headers from it and append them to the end of the
*     FitsChan. It then closes the SourceFile.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     source_file
*        The new attribute value. Should be the path to an existing text
*        file, holding FITS headers (one per line)
*     status
*        Inherited status pointer.

*/

/* Local Constants: */
#define ERRBUF_LEN 80

/* Local Variables: */
   AstFitsChan *this;            /* Pointer to the FitsChan structure */
   FILE *fd;                     /* Descriptor for source file */
   char *errstat;                /* Pointer for system error message */
   char card[ AST__FITSCHAN_FITSCARDLEN + 2 ]; /* Buffer for source line */
   char errbuf[ ERRBUF_LEN ];    /* Buffer for system error message */

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

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

/* Invoke the parent astSetSourceFile method to store  the supplied
   string in the Channel structure. */
   (*parent_setsourcefile)( this_channel, source_file, status );

/* Attempt to open the file. */
   fd = NULL;
   if( astOK ) {
      fd = fopen( source_file, "r" );
      if( !fd ) {
         if ( errno ) {
#if HAVE_STRERROR_R
            strerror_r( errno, errbuf, ERRBUF_LEN );
            errstat = errbuf;
#else
            errstat = strerror( errno );
#endif
            astError( AST__RDERR, "astSetSourceFile(%s): Failed to open input "
                      "SourceFile '%s' - %s.", status, astGetClass( this ),
                      source_file, errstat );
         } else {
            astError( AST__RDERR, "astSetSourceFile(%s): Failed to open input "
                      "SourceFile '%s'.", status, astGetClass( this ),
                      source_file );
         }
      }
   }

/* Move the FitsChan to EOF */
   astSetCard( this, INT_MAX );

/* Read each line from the file, remove trailing space, and append to the
   FitsChan. */
   while( astOK && fgets( card, AST__FITSCHAN_FITSCARDLEN + 2, fd ) ) {
      card[ astChrLen( card ) ] = 0;
      astPutFits( this, card, 0 );
   }

/* Close the source file. */
   if( fd ) fclose( fd );

}

static void SetTableSource( AstFitsChan *this,
                            void (*tabsource)( void ),
                            void (*tabsource_wrap)( void (*)( void ),
                                                    AstFitsChan *, const char *,
                                                    int, int, int * ),
                            int *status ){

/*
*+
*  Name:
*     astSetTableSource

*  Purpose:
*     Register source and wrapper function for accessing tables in FITS files.

*  Type:
*     Protected function.

*  Synopsis:
*     #include "fitschan.h"
*     void astSetTableSource( AstFitsChan *this,
*                             void (*tabsource)( void ),
*                             void (*tabsource_wrap)( void (*)( void ),
*                                                     AstFitsChan *, const char *,
*                                                     int, int, int * ),
*                             int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function registers a table source function and its wrapper. A
*     wrapper function exists to adapt the API of the table source
*     function to the needs of different languages. The wrapper is called
*     from the FitsChan code. The wrapper then adjusts the arguments as
*     required and then calls the actualy table source function.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     tabsource
*        Pointer to the table source function. The API for this function
*        will depend on the language, and so is cast to void here. It
*        should be cast to the required form within the wrapper function.
*     tabsource_wrap
*        The wrapper function.
*-
*/

/* Local Variables: */

/* Check the global error status. */
   if ( !astOK ) return;
   this->tabsource = tabsource;
   this->tabsource_wrap = tabsource_wrap;
}

static void SetValue( AstFitsChan *this, const char *keyname, void *value,
                      int type, const char *comment, int *status ){

/*
*  Name:
*     SetValue

*  Purpose:
*     Save a FITS keyword value, over-writing any existing keyword value.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void SetValue( AstFitsChan *this, char *keyname, void *value,
*                    int type, const char *comment, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function saves a keyword value as a card in the supplied
*     FitsChan. Comment cards are always inserted in-front of the current
*     card. If the keyword is not a comment card, any existing value
*     for the keyword is over-written with the new value (even if it is
*     marked as having been read). Otherwise, (i.e. if it is not a comment
*     card, and no previous value exists) it is inserted in front
*     of the current card.

*  Parameters:
*     this
*        A pointer to the FitsChan.
*     keyname
*        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 a pointer to the character string.
*     type
*        The FITS data type of the supplied keyword value.
*     comment
*        A comment to store with the keyword.
*     status
*        Pointer to the inherited status variable.

*   Notes:
*     -  Nothing is stored if a NULL pointer is supplied for "value".
*     -  If the keyword has a value of AST__BAD then nothing is stored,
*     and an error is reported.
*/

/* Local Variables: */
   astDECLARE_GLOBALS     /* Declare the thread specific global data */
   FitsCard *card;        /* Pointer to original current card */
   const char *class;     /* Class name to include in error messages */
   const char *method;    /* Method name to include in error messages */
   int newcard;           /* Has the original current card been deleted? */
   int old_ignore_used;   /* Original setting of external ignore_used variable */
   int stored;            /* Has the keyword been stored? */

/* Check the status and supplied value pointer. */
   if( !astOK || !value ) return;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Set up the method and class names for inclusion in error mesages. */
   method = "astWrite";
   class = astGetClass( this );

/* Comment card are always inserted in-front of the current card. */
   if ( type == AST__COMMENT ) {
      SetFits( this, keyname, value, type, comment, 0, status );

/* Otherwise... */
   } else {

/* Report an error if a bad value is stored for a keyword. */
      if( type == AST__FLOAT ){
         if( *( (double *) value ) == AST__BAD && astOK ) {
            astError( AST__BDFTS, "%s(%s): The required FITS keyword "
                      "\"%s\" is indeterminate.", status, method, class, keyname );
         }
      }

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

/* Indicate that we should not skip over cards marked as having been
   read. */
      old_ignore_used = ignore_used;
      ignore_used = 0;

/* Indicate that we have not yet stored the keyword value. */
      stored = 0;

/* Attempt to find a card refering to the supplied keyword. If one is
   found, it becomes the current card. */
      if( SearchCard( this, keyname, "astWrite", astGetClass( this ), status ) ){

/* If the card which was current on entry to this function will be
   over-written, we will need to take account of this when re-instating the
   original current card. Make a note of this. */
         newcard = ( card == (FitsCard *) this->card );

/* Replace the current card with a card holding the supplied information. */
         SetFits( this, keyname, value, type, comment, 1, status );
         stored = 1;

/* If we have just replaced the original current card, back up a card
   so that the replacement card becomes the current card. */
         if( newcard ) {
            MoveCard( this, -1, "astWrite", astGetClass( this ), status );

/* Otherwise, re-instate the original current card. */
         } else {
            this->card = (void *) card;
         }
      }

/* If the keyword has not yet been stored (i.e. if it did not exist in the
   FitsChan), re-instate the original current card and insert the new card
   before the original current card, leaving the current card unchanged. */
      if( !stored ) {
         this->card = (void *) card;
         SetFits( this, keyname, value, type, comment, 0, status );
      }

/* Re-instate the original flag indicating if cards marked as having been
   read should be skipped over. */
      ignore_used = old_ignore_used;
   }
}

static void Shpc1( double xmin, double xmax, int n, double *d, double *w,
                   int *status ){
/*
*  Name:
*     Shpc1

*  Purpose:
*     Modifies a one-dimensional polynomial to scale the polynomial argument.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void Shpc1( double xmin, double xmax, int n, double *d, double *w,
*                 int *status )

*  Description:
*     Given the coefficients of a one-dimensional polynomial P(u) defined on a
*     unit interval (i.e. -1 <= u <= +1 ), find the coefficients of another
*     one-dimensional polynomial Q(x) where:
*
*       Q(x) = P(u)
*       u = ( 2*x - ( xmax + xmin ) ) / ( xmax - xmin )
*
*     That is, u is a scaled version of x, such that the unit interval in u
*     maps onto (xmin:xmax) in x.

*  Parameters:
*     xmin
*        X value corresponding to u = -1
*     xmax
*        X value corresponding to u = +1
*     n
*        One more than the maximum power of u within P.
*     d
*        An array of n elements supplied holding the coefficients of P such
*        that the coefficient of (u^i) is held in element (i).
*     w
*        An array of n elements returned holding the coefficients of Q such
*        that the coefficient of (x^i) is held in element (i).
*     status
*        Pointer to the inherited status variable.

*  Notes:
*    - Vaguely inspired by the Numerical Recipes routine "pcshft". But the
*    original had bugs, so I wrote this new version from first principles.

*/

/* Local Variables: */
   double b;
   double a;
   int j;
   int i;

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

/* Get the scale and shift terms so that u = a*x + b */
   a = 2.0/( xmax - xmin );
   b = ( xmin + xmax )/( xmin - xmax );

/* Initialise the returned coeffs */
   for( i = 0; i < n; i++ ) w[ i ] = 0.0;

/* The supplied Polynomial is

   P(u) = d0 + d1*u + d2*u^2 + ...

        = d0 + u*( d1 + u*( d2 + ... u*( d{n-1} ) ) )  . . . . . (1)

        = d0 + (a*x+b)*( d1 + (a*x+b)*( d2 + ... (a*x+b)*( d[n-1] ) ) )

   The inner-most parenthesised expression is a polynomial of order zero
   (a constant - d[n-1]). Store the coefficients of this zeroth order
   polynomial in the returned array. The "w" array is used to hold the
   coefficients of Q, i.e. coefficients of powers of "x", not "u", but
   since the inner-most polynomial is a constant, it makes no difference
   (x^0 == u^0 == 1). */
   w[ 0 ] = d[ n - 1 ];

/* Now loop through each remaining level of parenthetic nesting in (1). At
   each level, the parenthesised expression represents a polynomial of order
   "i". At the end of each pass though this loop, the returned array "w"
   holds the coefficients of this "i"th order polynomial. So on the last
   loop, i = n-1, "w" holds the required coefficients of Q. */
   for( i = 1; i < n; i++ ) {

/* If "R" is the polynomial at the "i-1"th level of nesting (the
   coefficiemts of which are currently held in "w"), and "S" is the
   polynomial at the "i"th level of nesting, we can see from (1) that:

   S = d[ n - 1 - i ] + u*R

   Substituting for "u", this becomes

   S = d[ n - 1 - i ] + ( a*x + b )*R
     = d[ n - 1 - i ] + a*R*x + b*R

   Looking at each of these three terms in reverse order:

   1) The "b*R" term is implemented by simply scaling the current contents
   of the "w" array by "b"; in the "a*R*x" term.

   2) In "a*R*x", the effect of multiplying by "x" is to move the existing
   coefficients in "w" up one element. We then multiply the shifted
   coefficients by "a" and add them onto the coefficients produced at
   step 1) above.

   We know that "w" still contains the initial zeros at indices higher than
   "i" so we only need to scale the bottom "i" elements. We do not do the
   zeroth term in this loop since there is no lower term to shift up into
   it. */

      for( j = i; j > 0; j-- ){
         w[ j ] = b*w[ j ] + a*w[ j - 1 ];
      }

/* Now do the zeroth term. Scale the existing zeroth term by "b" as
   required by step 1) and add on the first term, the constant
   "d[ n - 1 - i ]". Step 2) is a no-op, since in effect the value of
   "w[-1]" is zero. */
      w[ 0 ] = d[ n - i - 1 ] + b*w[ 0 ];
   }

}

static void ShowFits( AstFitsChan *this, int *status ){

/*
*++
*  Name:
c     astShowFits
f     AST_SHOWFITS

*  Purpose:
*     Display the contents of a FitsChan on standard output.

*  Type:
*     Public virtual function.

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

*  Class Membership:
*     FitsChan method.

*  Description:
c     This function
f     This routine
*     formats and displays all the cards in a FitsChan on standard output.

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

*--
*/

/* Local Variables: */
   astDECLARE_GLOBALS           /* Declare the thread specific global data */
   char card[ AST__FITSCHAN_FITSCARDLEN + 1]; /* Buffer for header card */
   int icard;                   /* Current card index on entry */
   int old_ignore_used;         /* Original value of external variable ignore_used */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

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

/* Indicate that cards which have been read into an AST object should skipped
   over by the functions which navigate the linked list of cards. */
   old_ignore_used = ignore_used;
   ignore_used = 1;

/* Ensure that the first card in the FitsChan will be the next one to be
   read. */
   astSetCard( this, 1 );

/* Loop round obtaining and writing out each card, until all cards have been
   processed. */
   while( !astFitsEof( this ) && astOK ){

/* Get the current card, and display it. The call to astFindFits increments
   the current card. */
      if( astFindFits( this, "%f", card, 1 ) ) printf( "%s\n", card );
   }

/* Re-instate the original flag indicating if cards marked as having been
   read should be skipped over. */
   ignore_used = old_ignore_used;

/* Set the current card index back to what it was on entry. */
   astSetCard( this, icard );

}

static int Similar( const char *str1, const char *str2, int *status ){
/*
*  Name:
*     Similar

*  Purpose:
*     Are two string effectively the same to human readers?

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void Similar( const char *str1, const char *str2, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function returns a non-zero value if the two supplied strings
*     are equivalent to a human reader. This is assumed to be the case if
*     the strings are equal apart from leading and trailing white space,
*     multiple embedded space, and case.

*  Parameters:
*     str1
*        The first string
*     str2
*        The second string
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     Non-zero if the two supplied strings are equivalent, and zero
*     otherwise.
*/

/* Local Variables: */
   const char *ea;         /* Pointer to end of string a */
   const char *eb;         /* Pointer to end of string b */
   const char *a;          /* Pointer to next character in string a */
   const char *b;          /* Pointer to next character in string b */
   int result;             /* Are the two strings equivalent? */
   int ss;                 /* Skip subsequent spaces? */

/* Initialise */
   result = 0;

/* Check the status and supplied value pointer. */
   if( !astOK ) return result;

/* Initialise pointers into the two strings. */
   a = str1;
   b = str2;

/* Get a pointer to the character following the last non-blank character in
   each string. */
   ea = a + ChrLen( a, status ) - 1;
   eb = b + ChrLen( b, status ) - 1;

/* Set a flag indicating that spaces before the next non-blank character
   should be ignored. */
   ss = 1;

/* Compare the strings. */
   while( 1 ){

/* Move on to the next significant character in both strings. */
      while( a < ea && *a == ' ' && ss ) a++;
      while( b < eb && *b == ' ' && ss ) b++;

/* If one string has been exhausted but the other has not, the strings
   are not equivalent. */
      if( ( a < ea && b == eb ) || ( a == ea && b < eb ) ) {
         break;

/* If both strings have been exhausted simultaneously, the strings
   are equivalent. */
      } else if( b == eb && a == ea ) {
         result = 1;
         break;

/* If neither string has been exhausted, compare the current character
   for equality, ignoring case. Break if they are different. */
      } else if( tolower( *a ) != tolower( *b ) ){
         break;

/* If the two characters are both spaces, indicate that subsequent spaces
   should be skipped. */
      } else if( *a == ' ' ) {
         ss = 1;

/* If the two characters are not spaces, indicate that subsequent spaces
   should not be skipped. */
      } else {
         ss = 0;
      }

/* Move on to the next characters. */
      a++;
      b++;
   }

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

static void SinkWrap( void (* sink)( const char * ), const char *line, int *status ) {
/*
*  Name:
*     SinkWrap

*  Purpose:
*     Wrapper function to invoke a C FitsChan sink function.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void SinkWrap( void (* sink)( const char * ), const char *line, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function invokes the sink function whose pointer is
*     supplied in order to write an output line to an external data
*     store.

*  Parameters:
*     sink
*        Pointer to a sink function, whose single parameter is a
*        pointer to a const, null-terminated string containing the
*        text to be written, and which returns void. This is the form
*        of FitsChan sink function employed by the C language interface
*        to the AST library.
*     status
*        Pointer to the inherited status variable.
*/

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

/* Invoke the sink function. */
   ( *sink )( line );
}

static AstMapping *SIPIntWorld( AstMapping *map, double tol, int lonax,
                                int latax, char s, FitsStore *store, double *dim,
                                int inaxes[2], double crpix[2], double cd[4],
                                const char *method, const char *class,
                                int *status ){
/*
*  Name:
*     SIPIntWorld

*  Purpose:
*     Create FITS header values which map grid into intermediate world
*     coords for celestial axes that include SIP distortion.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     AstMapping *SIPIntWorld( AstMapping *map, double tol, int lonax,
*                              int latax, char s, FitsStore *store, double *dim,
*                              int inaxes[2], double crpix[2], double cd[4],
*                              const char *method, const char *class,
*                              int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function finds and returns values for the CRPIX and CDi_j
*     keywords for sky axes that can be described using the SIP
*     distortion scheme. These values are determined by examining the
*     supplied pixel->IWCS Mapping. Values for SIP headers are also stored
*     in the supplied FitsSTore.
*
*     The celestial axes are first identified and the supplied Mapping
*     split to create a (2-in,2-out) Mapping that describes them. This
*     Mapping is then searched for a PolyMap. If found, the Mapping prior
*     to the PolyMap is checked to ensure it is a simple shift of origin.
*     The Mapping following the PolyMap is checked to ensure it is a
*     linear transformation with no shift of origin. The PolyMap itself
*     is checked to see if it conforms to the requirements of the SIP
*     conventions. If any of these conditions are not met, NULL is
*     returned as the function value. Otherwise, CRPIX values are created
*     from the Mapping prior to the PolyMap, and CDi_j values from the
*     Mapping following the PolyMap. The keywords describing the SIP
*     distortion itself (the PolyMap) are stored in the supplied FitsStore.
*     A Mapping is retuned that is identical to the supplied Mapping but
*     without the PolyMap.

*  Parameters:
*     map
*        A pointer to a Mapping which transforms grid coordinates into
*        intermediate world coordinates.
*     tol
*        The tolerance, in pixels, used to determine if the pre-PolyMap and
*        post-PolyMap Mappings are sufficiently linear.
*     lonax
*        The zero-based index of the output of "map" corresponding to
*        celestial longitude.
*     latax
*        The zero-based index of the output of "map" corresponding to
*        celestial latitude.
*     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 SIP headers
*        are stored.
*     dim
*        An array holding the image dimensions in pixels. AST__BAD can be
*        supplied for any unknown dimensions, in which case a default
*        value of 1000 pixel is used..
*     inaxes
*        Returned holding the indices of the two Mapping inputs that generate
*        the returned "crpix" and "cd" values.
*     crpix
*        If SIP headers are stored successfully in the FitsStore, then
*        this array is returned holding the CRPIX values. The first
*        element refers to the Mapping input given by the first element
*        of "inaxes". The second element refers to the Mapping input given
*        by the second element of "inaxes".
*     cd
*        If SIP headers are stored successfully in the FitsStore, then
*        this array is returned holding the CD values in the order
*        (CDlonax_j1,CDlonax_j2,CDlatax_j1,CDlatax_j2). Where "lonax" and
*        "latax" are the indices of the lon and lat Mapping outputs
*        (note, these may be different to the corresponding FITS "i" axis
*        indices), and "j1" and "j2" are the indices of the Mapping inputs
*        returned in "inaxes" (i.e. j1 = inaxes[0] and j2 = inaxes[1]).
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A Mapping is returned if SIP headers have been stored in the
*     FitsStore successfully. NULL is returned otherwise. The returned
*     Mapping is a copy of the supplied mapping "map", but without the
*     PolyMap.

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

/* Local Variables: */
   AstMapping **map_list;
   AstMapping *map_upper;
   AstMapping *map_lower;
   AstMapping *result;
   AstMapping *smap;
   AstMapping *tmap;
   AstMapping *tmap2;
   AstMapping *tmap1;
   AstPolyMap *polymap;
   AstPermMap *pm;
   const char *cval;
   char buf[30];
   double ****item;
   double *coeffs;
   double *pc;
   double fit[ 6 ];
   double iwcxin;
   double iwcyin;
   double lbnd[ 2 ];
   double ubnd[ 2 ];
   double val;
   int *inax1;
   int *inax2;
   int *inperm1;
   int *inperm2;
   int *invert_list;
   int *outperm1;
   int *outperm2;
   int *outrem;
   int fwd;
   int i;
   int icoeff;
   int iin;
   int imap;
   int imap_pm;
   int iout;
   int ioutrem;
   int jm;
   int ncoeff;
   int nin;
   int nmap;
   int nout;
   int noutrem;
   int ok;
   int old_invert;
   int outax[ 2 ];
   int aimax;
   int ajmmax;
   int bimax;
   int bjmmax;

/* Initialise */
   result = NULL;

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

/* Avoid compiler warnings */
   noutrem = 0;

/* Get the number of inputs and outputs for the Mapping. */
   nin = astGetNin( map );
   nout = astGetNin( map );

/* Check both transformations are defined in the supplied Mapping. */
   if( astGetTranForward( map ) && astGetTranInverse( map ) ) {

/* Attempt to split the supplied Mapping to generate a (2-input,2-output)
   Mapping that goes from grid coords to celestial longitude and latitude.
   Since we want to specify the retained output, we need to invert the
   Mapping, because astMapSplit only allows us to specify the retained
   inputs. */
      astInvert( map );
      outax[ 0 ] = lonax;
      outax[ 1 ] = latax;
      inax1 = astMapSplit( map, 2, outax, &tmap1 );
      astInvert( map );

/* Check the Mapping could be split, and that the mapping that generates
   lonax/latax has exactly two inputs (use the NBout attribute since
   "tmap1" is inverted). Then invert "tmap1" so that it is in the same
   direction as the supplied mapping. */
      if( inax1 && tmap1 && astGetNout( tmap1 ) == 2 ) {
         astInvert( tmap1 );
         inaxes[ 0 ] = inax1[ 0 ];
         inaxes[ 1 ] = inax1[ 1 ];

/* Search this list of Mappings for a PolyMap. First simplify it, then
   expand it as a list of Mappings in series. Then look through the list
   for a PolyMap. */
         polymap = NULL;
         smap = astSimplify( tmap1 );
         nmap = 0;
         map_list = NULL;
         invert_list = NULL;
         (void) astMapList( smap, 1, astGetInvert(smap), &nmap, &map_list,
                            &invert_list );
         for( imap = 0; imap < nmap; imap++ ) {
            if( astIsAPolyMap( map_list[ imap ] ) ) {
               imap_pm = imap;
               polymap = astCopy( map_list[ imap ] );
               astSetInvert( polymap, invert_list[ imap ] );
               break;
            }
         }

/* If a PolyMap is found, check it conforms to the requirements of the
   SIP convention. */
         if( polymap ){
            if( astGetNin( polymap ) == 2 && astGetNout( polymap ) == 2 ){

/* Accumulate each Mapping before the PolyMap into a single CmpMap. */
               map_lower = NULL;
               for( imap = 0; imap < imap_pm; imap++ ) {
                  old_invert = astGetInvert(  map_list[ imap ] );
                  astSetInvert(  map_list[ imap ], invert_list[ imap ] );

                  if( map_lower ) {
                     tmap = (AstMapping *) astCmpMap( map_lower, map_list[ imap ], 1, " ", status );
                     (void) astAnnul( map_lower );
                     map_lower = tmap;
                  } else {
                     map_lower = astCopy( map_list[ imap ] );
                  }

                  astSetInvert(  map_list[ imap ], old_invert );
               }

/* Accumulate each Mapping after the PolyMap into a single CmpMap. */
               map_upper = NULL;
               for( imap = imap_pm + 1; imap < nmap; imap++ ) {
                  old_invert = astGetInvert(  map_list[ imap ] );
                  astSetInvert(  map_list[ imap ], invert_list[ imap ] );

                  if( map_upper ) {
                     tmap = (AstMapping *) astCmpMap( map_upper, map_list[ imap ], 1, " ", status );
                     (void) astAnnul( map_upper );
                     map_upper = tmap;
                  } else {
                     map_upper = astCopy( map_list[ imap ] );
                  }

                  astSetInvert(  map_list[ imap ], old_invert );
               }

/* Use UnitMaps if no Mappings were found. */
               if( !map_lower ) map_lower = (AstMapping *) astUnitMap( 2, " ", status );
               if( !map_upper ) map_upper = (AstMapping *) astUnitMap( 2, " ", status );

/* Check that both Mappings have 2 inputs and 2 outputs */
               ok = ( astGetNin( map_lower ) == 2 &&
                      astGetNout( map_lower ) == 2 &&
                      astGetNin( map_upper ) == 2 &&
                      astGetNout( map_upper ) == 2 );

/* Check that the lower Mapping is a shift of origin with no scaling or
   rotation. */
               if( ok ) {
                  lbnd[ 0 ] = 0.0;
                  lbnd[ 1 ] = 0.0;
                  ubnd[ 0 ] = dim[ 0 ];
                  ubnd[ 1 ] = dim[ 1 ];
                  if( ubnd[ 0 ] == AST__BAD ) ubnd[ 0 ] = 1000.0;
                  if( ubnd[ 1 ] == AST__BAD ) ubnd[ 1 ] = 1000.0;
                  ok = astLinearApprox( map_lower, lbnd, ubnd, tol, fit );
                  if( fabs( fit[ 2 ] - 1.0 ) > 1.0E-7  ||
                      fabs( fit[ 3 ] ) > 1.0E-7 ||
                      fabs( fit[ 4 ] ) > 1.0E-7 ||
                      fabs( fit[ 5 ] - 1.0 ) > 1.0E-7  ) ok = 0;
               }

/* Check that the upper Mapping is a linear Mapping with no shift of origin.
   Retain the fit coefficients for later use. */
               if( ok ) {
                  lbnd[ 0 ] = -ubnd[ 0 ];
                  lbnd[ 1 ] = -ubnd[ 1 ];
                  ok = astLinearApprox( map_upper, lbnd, ubnd, tol, fit );
                  if( fabs( fit[ 0 ] ) > 1.0E-7 ||
                      fabs( fit[ 1 ] ) > 1.0E-7 ) ok = 0;
               }

/* Split the supplied Mapping to generate the Mapping that gives
   any remaining non-celestial output axes. We only need to do this if
   the supplied Mapping has any surplus inputs or outputs. */
               inax2 = NULL;
               tmap2 = NULL;
               outrem = NULL;

               if( nout > 2 && ok ) {
                  noutrem = nout - 2;
                  outrem = astMalloc( noutrem*sizeof(int) );
                  if( astOK ) {
                     ioutrem = 0;
                     for( iout = 0; iout < nout; iout++ ) {
                        if( iout != lonax && iout != latax ) outrem[ ioutrem++ ] = iout;
                     }

                     astInvert( map );
                     inax2 = astMapSplit( map, noutrem, outrem, &tmap2 );
                     astInvert( map );
                     if( tmap2 ) {
                        astInvert( tmap2 );
                     } else {
                        ok = 0;
                     }
                  }

               } else if( nout != 2 || nin != 2 ) {
                  ok = 0;
               }

/* If the above tests were passed, transform the origin of IWC (the total map
   output space) into grid coords. This gives CRPIX. */
               if( ok ) {
                  iwcxin = 0.0;
                  iwcyin = 0.0;
                  astTran2( smap, 1, &iwcxin, &iwcyin, 0, crpix, crpix + 1 );

/* The "fit" array currently contains the coefficients of a linear
   approximation to the upper Mapping. These give us the CD matrix.
   Store the matrix elements in the required order. */
                  cd[ 0 ] = fit[ 2 ];
                  cd[ 1 ] = fit[ 3 ];
                  cd[ 2 ] = fit[ 4 ];
                  cd[ 3 ] = fit[ 5 ];

/* Store SIP headers describing first the forward then the inverse
   transformation of the PolyMap in the FitsStore. Note, the axis indices
   returned by astPolyCoeffs are 1-based. */
                  for( fwd = 1; fwd >= 0; fwd-- ) {
                     if( ( fwd && astGetTranForward( polymap ) ) ||
                         ( !fwd && astGetTranInverse( polymap ) ) ) {
                        astPolyCoeffs( polymap, fwd, 0, NULL, &ncoeff );
                        coeffs = astMalloc( 4*ncoeff*sizeof(*coeffs) );
                        if( astOK ) {
                           astPolyCoeffs( polymap, fwd, 4*ncoeff, coeffs, &ncoeff );

/* Find the maximum used power on each input axis. */
                           aimax = 0;
                           ajmmax = 0;
                           bimax = 0;
                           bjmmax = 0;
                           pc = coeffs;
                           for( icoeff = 0; icoeff < ncoeff; icoeff++ ) {
                              if( inaxes[ 0 ] < inaxes [ 1 ] ) {
                                 i = (int) ( pc[ 2 ] + 0.5 );
                                 jm = (int) ( pc[ 3 ] + 0.5 );
                                 if( pc[ 1 ] == 1 ) {
                                    if( i > aimax ) aimax = i;
                                    if( jm > ajmmax ) ajmmax = jm;
                                 } else {
                                    if( i > bimax ) bimax = i;
                                    if( jm > bjmmax ) bjmmax = jm;
                                 }
                              } else {
                                 i = (int) ( pc[ 3 ] + 0.5 );
                                 jm = (int) ( pc[ 2 ] + 0.5 );
                                 if( pc[ 1 ] == 1 ) {
                                    if( i > bimax ) bimax = i;
                                    if( jm > bjmmax ) bjmmax = jm;
                                 } else {
                                    if( i > aimax ) aimax = i;
                                    if( jm > ajmmax ) ajmmax = jm;
                                 }
                              }
                              pc += 4;
                           }

/* Initialise the arrays with bad values so that unused powers are not
   included in the header. */

                           for( i = 0; i <= aimax; i++ ){
                              for( jm = 0; jm <= ajmmax; jm++ ){
                                 SetItem( fwd? &(store->asip) : &(store->apsip),
                                          i, jm, s, AST__BAD, status );
                              }
                           }

                           for( i = 0; i <= bimax; i++ ){
                              for( jm = 0; jm <= bjmmax; jm++ ){
                                 SetItem( fwd? &(store->bsip) : &(store->bpsip),
                                          i, jm, s, AST__BAD, status );
                              }
                           }

/* Over-write the bad values with real values for the powers that are
   actually used. Reduce the coefficients of the linear terms by 1.0
   since the SIP distortion is an additive correction, rather than a direct
   transformation. */
                           pc = coeffs;
                           for( icoeff = 0; icoeff < ncoeff; icoeff++ ) {
                              if( inaxes[ 0 ] < inaxes [ 1 ] ) {
                                 if( pc[ 1 ] == 1 ) {
                                    item = fwd ? &(store->asip) : &(store->apsip);
                                 } else {
                                    item = fwd ? &(store->bsip) : &(store->bpsip);
                                 }
                                 i = (int) ( pc[ 2 ] + 0.5 );
                                 jm = (int) ( pc[ 3 ] + 0.5 );
                              } else {
                                 if( pc[ 1 ] == 1 ) {
                                    item = fwd ? &(store->bsip) : &(store->bpsip);
                                 } else {
                                    item = fwd ? &(store->asip) : &(store->apsip);
                                 }
                                 i = (int) ( pc[ 3 ] + 0.5 );
                                 jm = (int) ( pc[ 2 ] + 0.5 );
                              }

                              val = pc[ 0 ];
                              if( ( pc[ 1 ] == 1 && i == 1 && jm == 0 ) ||
                                  ( pc[ 1 ] == 2 && i == 0 && jm == 1 ) ){
                                 val -= 1.0;
                              }
                              if( val != 0.0 && val != AST__BAD ) {
                                 SetItem( item, i, jm, s, val, status );
                              }
                              pc += 4;
                           }
                        }
                        coeffs = astFree( coeffs );
                     }
                  }

/* Change the CTYPE value to indicate SIP distortion is in use. */
                  cval = GetItemC( &(store->ctype), latax, 0, s, NULL, method,
                                   class, status );
                  if( cval ){
                     strcpy( buf, cval );
                     strcpy( buf + 8, "-SIP" );
                     SetItemC( &(store->ctype), latax, 0, s, buf, status );
                  }

                  cval = GetItemC( &(store->ctype), lonax, 0, s, NULL, method,
                                   class, status );
                  if( cval ){
                     strcpy( buf, cval );
                     strcpy( buf + 8, "-SIP" );
                     SetItemC( &(store->ctype), lonax, 0, s, buf, status );
                  }

/* Construct the returned Mapping. This is equivalent to the supplied
   Mapping, but without the PolyMap. Use PermMaps at beginning and end to
   take account of any axis permutations introduced by the operation of
   astMapSplit. First put the 2D Mapping preceding the PolyMap in series
   with the 2D Mapping following the PolyMap. */
                  result = (AstMapping *) astCmpMap( map_lower, map_upper, 1, " ", status );

/* Now put the above Mapping in parallel with the mMapping that
   transforms any additional axes. */
                  if( tmap2 ) {
                     tmap = (AstMapping *) astCmpMap( result, tmap2, 0, " ", status );
                     (void) astAnnul( result );
                     result = tmap;
                  }

/* Create a PermMap that permutes the outputs of the above Mapping back
   into their original order. */
                  inperm1 = astMalloc( nout*sizeof(int) );
                  outperm1 = astMalloc( nout*sizeof(int) );
                  inperm2 = astMalloc( nin*sizeof(int) );
                  outperm2 = astMalloc( nin*sizeof(int) );
                  if( astOK ) {
                     inperm1[ 0 ] = lonax;
                     inperm1[ 1 ] = latax;
                     outperm1[ lonax ] = 0;
                     outperm1[ latax ] = 1;
                     if( tmap2 ) {
                        for( iout = 0; iout < noutrem; iout++ ) {
                           inperm1[ iout + 2 ] = outrem[ iout ];
                           outperm1[ outrem[ iout ] ] = iout + 2;
                        }
                     }
                     pm = astPermMap( nout, inperm1, nout, outperm1,
                                      NULL, " ", status );

/* Put this PermMap in series with (following) the main Mapping created
   above. */
                     tmap = (AstMapping *) astCmpMap( result, pm, 1, " ", status );
                     (void) astAnnul( result );
                     pm = astAnnul( pm );
                     result = tmap;

/* Create a PermMap that permutes the inputs of the above Mapping back
   into their original order. */
                     outperm2[ 0 ] = inax1[ 0 ];
                     outperm2[ 1 ] = inax1[ 1 ];
                     inperm2[ inax1[ 0 ] ] = 0;
                     inperm2[ inax1[ 1 ] ] = 1;

                     if( tmap2 ) {
                        for( iin = 0; iin < nin - 2; iin++ ) {
                           outperm2[ iin + 2 ] = inax2[ iin ];
                           inperm2[ inax2[ iin ] ] = iin + 2;
                        }
                     }

                     pm = astPermMap( nin, inperm2, nin, outperm2,
                                      NULL, " ", status );

/* Put this PermMap in series with (preceding) the main Mapping created
   above. */
                     tmap = (AstMapping *) astCmpMap( pm, result, 1, " ", status );
                     (void) astAnnul( result );
                     pm = astAnnul( pm );
                     result = tmap;
                  }

/* Free resources. */
                  inperm1 = astFree( inperm1 );
                  inperm2 = astFree( inperm2 );
                  outperm1 = astFree( outperm1 );
                  outperm2 = astFree( outperm2 );
               }

               inax2 = astFree( inax2 );
               outrem = astFree( outrem );
               if( tmap2 ) tmap2 = astAnnul( tmap2 );
               if( map_lower ) map_lower = astAnnul( map_lower );
               if( map_upper ) map_upper = astAnnul( map_upper );
            }

            polymap = astAnnul( polymap );
         }

         for( imap = 0; imap < nmap; imap++ ) {
            map_list[ imap ] = astAnnul( map_list[ imap ] );
         }

         invert_list = astFree( invert_list );
         map_list = astFree( map_list );
         smap = astAnnul( smap );
      }
      inax1 = astFree( inax1 );
      if( tmap1 ) tmap1 = astAnnul( tmap1 );
   }

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

static AstMapping *SIPMapping( AstFitsChan *this, double *dim,  FitsStore *store,
                               char s, int naxes, const char *method,
                               const char *class, int *status ){
/*
*  Name:
*     SIPMapping

*  Purpose:
*     Create a Mapping descriping "-SIP" (Spitzer) distortion.

*  Type:
*     Private function.

*  Synopsis:
*     AstMapping *SIPMapping( AstFitsChan *this, double *dim, FitsStore *store,
*                             char s, int naxes, const char *method,
*                             const char *class, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function uses the values in the supplied FitsStore to create a
*     Mapping which implements the "-SIP" distortion code. This is the

*     code used by the Spitzer project and is described in:
*
*     http://irsa.ipac.caltech.edu/data/SPITZER/docs/files/spitzer/shupeADASS.pdf
*
*     SIP distortion can only be applied to axes 0 and 1. Other axes are
*     passed unchanged by the returned Mapping.

*  Parameters:
*     this
*        The FitsChan.
*     dim
*        The dimensions of the array in pixels. AST__BAD is stored for
*        each value if dimensions are not known.
*     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).
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to the Mapping.
*/

/* Local Variables: */
   AstMapping   *ret;        /* Pointer to the returned Mapping */
   AstPolyMap *pmap;         /* PolyMap describing the distortion */
   AstPolyMap *pmap2;        /* New PolyMap describing the distortion */
   double ****item;          /* Address of FitsStore item to use */
   double *c;                /* Pointer to start of coefficient description */
   double *coeff_f;          /* Array of coeffs. for forward transformation */
   double *coeff_i;          /* Array of coeffs. for inverse transformation */
   double cof;               /* Coefficient value */
   double lbnd[ 2 ];         /* Lower bounds of fitted region */
   double ubnd[ 2 ];         /* Upper bounds of fitted region */
   int def;                  /* Is transformation defined? */
   int iin;                  /* Input (u or v) index */
   int iout;                 /* Output (U or V) index */
   int ncoeff_f;             /* No. of coeffs. for forward transformation */
   int ncoeff_i;             /* No. of coeffs. for inverse transformation */
   int p;                    /* Power of u or U */
   int pmax;                 /* Max power of u or U */
   int q;                    /* Power of v or V */
   int qmax;                 /* Max power of v or V */

/* Initialise the pointer to the returned Mapping. */
   ret = NULL;

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

/* Store coefficients of the forward transformation:
   ================================================ */

/* Indicate that we have as yet no coefficients for the forward polynomials. */
   ncoeff_f = 0;

/* Indicate that we do not yet have any evidence that the forward
   transformation is defined. */
   def = 0;

/* Allocate workspace to hold descriptions of (initially) 20 coefficients used
   within the forward polynomials. */
   coeff_f = astMalloc( sizeof( double )*20 );

/* Store the coefficients of the polynomial which produces each output
   axis (U or V) in turn. */
   for( iout = 0; iout < 2; iout++ ){

/* Get a pointer to the FitsStore item holding the values defining this
   output. */
      item = ( iout == 0 ) ? &(store->asip) : &(store->bsip);

/* Get the largest powers used of u and v. */
      pmax = GetMaxI( item, s, status );
      qmax = GetMaxJM( item, s, status );

/* Loop round all combination of powers. */
      for( p = 0; p <= pmax; p++ ){
         for( q = 0; q <= qmax; q++ ){

/* Get the polynomial coefficient for this combination of powers. */
            cof = GetItem( item, p, q, s, NULL, method, class, status );

/* If there is no coefficient for this combination of powers, use a value
   of zero. Otherwise indicate we have found at least one coefficient. */
            if( cof == AST__BAD ) {
               cof = 0.0;
            } else {
               def = 1;
            }

/* The distortion polynomial gives a correction to be added on to the
   input value. On the other hand, the returned Mapping is a direct
   transformation from input to output. Therefore increment the coefficient
   value by 1 for the term which corresponds to the current output axis. */
            if( p == ( 1 - iout ) && q == iout ) cof += 1.0;

/* If the coefficient is not zero, store it in the array of coefficient
   descriptions. */
            if( cof != 0.0 ) {

/* Increment the number of coefficients for the forward polynomials. */
               ncoeff_f++;

/* Ensure the "coeff_f" array is large enough to hold the new coefficient. */
               coeff_f = astGrow( coeff_f, sizeof( double )*4, ncoeff_f );
               if( astOK ) {

/* Store it. Each coefficient is described by 4 values (since we have 2
   inputs to the Mapping). The first is the coefficient value, the second
   is the (1-based) index of the output to which the coefficient relates.
   The next is the power of input 0, and the last one is the power of input 1. */
                  c = coeff_f + 4*( ncoeff_f - 1 );
                  c[ 0 ] = cof;
                  c[ 1 ] = iout + 1;
                  c[ 2 ] = p;
                  c[ 3 ] = q;
               }
            }
         }
      }
   }

/* If no coefficients were supplied in the FitsStore, the forward
   transformation is undefined. */
   if( !def ) ncoeff_f = 0;

/* Store coefficients of the inverse transformation:
   ================================================ */

/* Indicate that we have as yet no coefficients for the inverse polynomials. */
   ncoeff_i = 0;

/* Indicate that we do not yet have any evidence that the forward
   transformation is defined. */
   def = 0;

/* Allocate workspace to hold descriptions of (initially) 20 coefficients used
   within the inverse polynomials. */
   coeff_i = astMalloc( sizeof( double )*20 );

/* Store the coefficients of the polynomial which produces each input
   axis (u or v) in turn. */
   for( iin = 0; iin < 2; iin++ ){

/* Get a pointer to the FitsStore item holding the values defining this
   output. */
      item = ( iin == 0 ) ? &(store->apsip) : &(store->bpsip);

/* Get the largest powers used of U and V. */
      pmax = GetMaxI( item, s, status );
      qmax = GetMaxJM( item, s, status );

/* Loop round all combination of powers. */
      for( p = 0; p <= pmax; p++ ){
         for( q = 0; q <= qmax; q++ ){

/* Get the polynomial coefficient for this combination of powers. */
            cof = GetItem( item, p, q, s, NULL, method, class, status );

/* If there is no coefficient for this combination of powers, use a value
   of zero. Otherwise indicate we have found at least one coefficient. */
            if( cof == AST__BAD ) {
               cof = 0.0;
            } else {
               def = 1;
            }

/* The distortion polynomial gives a correction to be added on to the
   output value. On the other hand, the returned Mapping is a direct
   transformation from output to input. Therefore increment the coefficient
   value by 1 for the term which corresponds to the current input axis. */
            if( p == ( 1 - iin ) && q == iin ) cof += 1.0;

/* If the coefficient is not zero, store it in the array of coefficient
   descriptions. */
            if( cof != 0.0 ) {

/* Increment the number of coefficients for the inverse polynomials. */
               ncoeff_i++;

/* Ensure the "coeff_i" array is large enough to hold the new coefficient. */
               coeff_i = astGrow( coeff_i, sizeof( double )*4, ncoeff_i );
               if( astOK ) {

/* Store it. Each coefficient is described by 4 values (since we have 2
   outputs to the Mapping). The first is the coefficient value, the second
   is the (1-based) index of the input to which the coefficient relates. The
   next is the power of output 0, and the last one is the power of output 1. */
                  c = coeff_i + 4*( ncoeff_i - 1 );
                  c[ 0 ] = cof;
                  c[ 1 ] = iin + 1;
                  c[ 2 ] = p;
                  c[ 3 ] = q;
               }
            }
         }
      }
   }

/* If no coefficients were supplied in the FitsStore, the forward
   transformation is undefined. */
   if( !def ) ncoeff_i = 0;

/* Create the returned Mapping:
   ============================ */

/* If neither transformation is defined, create a UnitMap. */
   if( ncoeff_f == 0 && ncoeff_i == 0 ){
      ret = (AstMapping *) astUnitMap( naxes, "", status );

/* Otherwise, create a PolyMap to describe axes 0 and 1. */
   } else {
      pmap = astPolyMap( 2, 2, ncoeff_f, coeff_f, ncoeff_i, coeff_i, "", status );

/* The inverse transformations supplied within SIP headers are often
   inaccurate. So replace any existing inverse by sampling the supplied
   transformation, and fitting a polynomial to the sampled positions. If
   the fit fails to reach 0.01 pixel accuracy, forget it and rely on the
   (slower) iterative inverse provided by the PolyMap class. Do the fit
   over an area three times the size of the image to provide accurate
   values outside the image. Only do this if it has not been disabled
   using attribute SipReplace. */
      if( astGetSipReplace( this ) ) {
         lbnd[ 0 ] = ( dim[ 0 ] != AST__BAD ) ? -dim[ 0 ] : -1000.0;
         lbnd[ 1 ] = ( dim[ 1 ] != AST__BAD ) ? -dim[ 1 ] : -1000.0;
         ubnd[ 0 ] = ( dim[ 0 ] != AST__BAD ) ? 2*dim[ 0 ] : 2000.0;
         ubnd[ 1 ] = ( dim[ 1 ] != AST__BAD ) ? 2*dim[ 1 ] : 2000.0;
         pmap2 = astPolyTran( pmap, (ncoeff_f == 0), 0.0001, 0.01, 7, lbnd,
                              ubnd );
         if( pmap2 ) {
            (void) astAnnul( pmap );
            pmap = pmap2;
         } else {
            astSet( pmap, "IterInverse=1,NiterInverse=6,TolInverse=1.0E-8",
                    status );
         }
      }

/* Add the above Mapping in parallel with a UnitMap which passes any
   other axes unchanged. */
      ret = AddUnitMaps( (AstMapping *) pmap, 0, naxes, status );
      pmap = astAnnul( pmap );
   }

/* Free resources. */
   coeff_f = astFree( coeff_f );
   coeff_i = astFree( coeff_i );

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

static void SkyPole( AstWcsMap *map2, AstMapping *map3, int ilon, int ilat,
                     int *wperm, char s, FitsStore *store, const char *method,
                     const char *class, int *status ){
/*
*  Name:
*     SkyPole

*  Purpose:
*     Put values for FITS keywords LONPOLE and LATPOLE into a FitsStore.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void SkyPole( AstWcsMap *map2, AstMapping *map3, int ilon, int ilat,
*                   int *wperm, char s, FitsStore *store, const char *method,
*                   const char *class, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function calculates values for the LONPOLE and LATPOLE FITS
*     keywords and stores them in the supplied FitsStore. LONPOLE and
*     LATPOLE are the longitude and latitude of the celestial north pole
*     in native spherical coordinates.

*  Parameters:
*     map2
*        Pointer to the Mapping from Intermediate World Coordinates to Native
*        Spherical Coordinates.
*     map3
*        Pointer to the Mapping from Native Spherical Coordinates to celestial
*        coordinates.
*     ilon
*        Zero-based index of longitude output from "map3".
*     ilat
*        Zero-based index of latitude output from "map3".
*     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.
*     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.
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   AstPointSet *pset1;      /* PointSet holding intermediate wcs coords */
   AstPointSet *pset2;      /* PointSet holding final WCS coords */
   double **ptr1;           /* Pointer to coordinate data */
   double **ptr2;           /* Pointer to coordinate data */
   double alpha0;           /* Long. of fiducial point in standard system */
   double alphap;           /* Celestial longitude of native north pole */
   double deflonpole;       /* Default value for lonpole */
   double delta0;           /* Lat. of fiducial point in standard system */
   double latpole;          /* Native latitude of celestial north pole */
   double lonpole;          /* Native longitude of celestial north pole */
   double phi0;             /* Native longitude at fiducial point */
   double theta0;           /* Native latitude at fiducial point */
   int axlat;               /* Index of latitude output from "map2" */
   int axlon;               /* Index of longitude output from "map2" */
   int fits_ilat;           /* FITS WCS axis index for latitude axis */
   int fits_ilon;           /* FITS WCS axis index for longitude axis */
   int iax;                 /* Axis index */
   int nax;                 /* Number of IWC axes */
   int nax2;                /* Number of WCS axes */

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

/* Store the indices of the native longitude and latitude outputs of the
   WcsMap. */
   axlon = astGetWcsAxis( map2, 0 );
   axlat = astGetWcsAxis( map2, 1 );

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

/* To find the longitude and latitude of the celestial north pole in native
   spherical coordinates, we will transform the coords of the celestial north
   pole into spherical cords using the inverse of "map2", and if the resulting
   native spherical coords differ from the default values of LONPOLE and
   LATPOLE, we store them in the FitsStore. However, for zenithal projections,
   any value can be used simply by introducing an extra rotation into the
   (X,Y) projection plane. If values have been set in the WcsMap (as
   projection parameters PVi_3 and PVi_4 for longitude axis "i") uses
   them. Otherwise, set the values bad to indicate that the default values
   should be used. Note, these projection parameters are used for other
   purposes in a TPN projection. */
   lonpole = AST__BAD;
   latpole = AST__BAD;
   if( astIsZenithal( map2 ) ) {
      if( astGetWcsType( map2 ) != AST__TPN ) {
         lonpole = astTestPV( map2, axlon, 3 ) ? astGetPV( map2, axlon, 3 )
                                                : AST__BAD;
         latpole = astTestPV( map2, axlon, 4 ) ? astGetPV( map2, axlon, 4 )
                                                : AST__BAD;
      }

/* For non-zenithal projections, do the full calculation. */
   } else {

/* Allocate resources. */
      nax = astGetNin( map2 );
      pset1 = astPointSet( 1, nax, "", status );
      ptr1 = astGetPoints( pset1 );
      nax2 = astGetNout( map3 );
      pset2 = astPointSet( 1, nax2, "", status );
      ptr2 = astGetPoints( pset2 );
      if( astOK ) {

/* Calculate the longitude and latitude of the celestial north pole
   in native spherical coordinates (using the inverse of map3). These
   values correspond to the LONPOLE and LATPOLE keywords. */
         for( iax = 0; iax < nax2; iax++ ) ptr2[ iax ][ 0 ] = 0.0;
         ptr2[ ilat ][ 0 ] = AST__DPIBY2;
         (void) astTransform( map3, pset2, 0, pset1 );

/* Retrieve the latitude and longitude (in the standard system) of the
   fiducial point (i.e. CRVAL), in radians. */
         delta0 = GetItem( &(store->crval), fits_ilat, 0, s, NULL, method, class, status );
         if( delta0 == AST__BAD ) delta0 = 0.0;
         delta0 *= AST__DD2R;
         alpha0 = GetItem( &(store->crval), fits_ilon, 0, s, NULL, method, class, status );
         if( alpha0 == AST__BAD ) alpha0 = 0.0;
         alpha0 *= AST__DD2R;

/* The default value of the LATPOLE is defined by equation 8 of FITS-WCS
   paper II (taking the +ve signs). Find this value. */
         if( WcsNatPole( NULL, map2, alpha0, delta0, 999.0, ptr1[ axlon ],
                         &alphap, &latpole, status ) ){

/* If the default value is defined, compare it to the latitude of the
   north pole found above. If they are equal use a bad value instead to
   prevent an explicit keyword from being added to the FitsChan. */
            if( EQUALANG( ptr1[ axlat ][ 0 ], latpole ) ) {
               latpole = AST__BAD;
            } else {
               latpole = ptr1[ axlat ][ 0 ];
            }

/* If the default value is not defined, always store an explicit LATPOLE
   value. */
         } else {
            latpole = ptr1[ axlat ][ 0 ];
         }

/* The default LONPOLE value is zero if the celestial latitude at the
   fiducial point is greater than or equal to the native latitude at the
   fiducial point. Otherwise, the default is (+ or -) 180 degrees. If LONPOLE
   takes the default value, replace it with AST__BAD to prevent an explicit
   keyword being stored in the FitsChan. */
         GetFiducialNSC( map2, &phi0, &theta0, status );
         lonpole = palDranrm( ptr1[ axlon ][ 0 ] );
         if( delta0 >= theta0 ){
            deflonpole = 0.0;
         } else {
            deflonpole = AST__DPI;
         }
         if( EQUALANG( lonpole, deflonpole ) ) lonpole = AST__BAD;
      }

/* Convert from radians to degrees. */
      if( lonpole != AST__BAD ) lonpole *= AST__DR2D;
      if( latpole != AST__BAD ) latpole *= AST__DR2D;

/* Free resources. */
      pset1 = astAnnul( pset1 );
      pset2 = astAnnul( pset2 );
   }

/* Store these values. */
   SetItem( &(store->lonpole), 0, 0, s, lonpole, status );
   SetItem( &(store->latpole), 0, 0, s, latpole, status );

/* FITS-WCS paper 2 recommends putting a copy of LONPOLE and LATPOLE in
   projection parameters 3 and 4 associated with the longitude axis. Only do
   this if the projection is not TPN (since this projection uses these
   parameters for other purposes). */
   if( astGetWcsType( map2 ) != AST__TPN ) {
      SetItem( &(store->pv), fits_ilon, 3, s, lonpole, status );
      SetItem( &(store->pv), fits_ilon, 4, s, latpole, status );
   }
}

static int SkySys( AstFitsChan *this, AstSkyFrame *skyfrm, int wcstype,
                   int wcsproj, FitsStore *store, int axlon, int axlat, char s,
                   int isoff, const char *method, const char *class, int *status ){
/*
*  Name:
*     SkySys

*  Purpose:
*     Return FITS-WCS values describing a sky coordinate system.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int SkySys( AstFitsChan *this, AstSkyFrame *skyfrm, int wcstype,
*                 int wcsproj, FitsStore *store, int axlon, int axlat, char s,
*                 int isoff, const char *method, const char *class, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function sets values for the following FITS-WCS keywords
*     within the supplied FitsStore structure: CTYPE, CNAME, RADESYS, EQUINOX,
*     MJDOBS, CUNIT, OBSGEO-X/Y/Z. The values are derived from the supplied
*     SkyFrame and WcsMap.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     skyfrm
*        A pointer to the SkyFrame to be described.
*     wcstype
*        The type of WCS: 0 = TAB, 1 = WcsMap projection.
*     wcsproj
*        An identifier for the type of WCS projection to use. Should be
*        one of the values defined by the WcsMap class. Only used if "wcstype"
*        is 1.
*     store
*        A pointer to the FitsStore structure in which to store the
*        results.
*     axlon
*        The index of the FITS WCS longitude axis (i.e. the value of "i"
*        in "CTYPEi").
*     axlat
*        The index of the FITS WCS latitude axis (i.e. the value of "i"
*        in "CTYPEi").
*     s
*        Co-ordinate version character.
*     isoff
*        If greater than zero, the description to add to the FitsStore
*        should describe offset coordinates. If less than zero, the
*        description to add to the FitsStore should describe absolute
*        coordinates but should include the SkyRefIs, SkyRef and SkyRefP
*        attributes. If zero, ignore all offset coordinate info. The
*        absolute value indicates the nature of the reference point:
*        1 == "pole", 2 == "origin", otherwise "ignored".
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     Are the keywords values in the FitsStore usable?
*/

/* Local Variables: */
   astDECLARE_GLOBALS     /* Declare the thread specific global data */
   char *label;             /* Pointer to axis label string */
   char attr[40];           /* Buffer for AST attribute name */
   char com[80];            /* Buffer for keyword comment */
   char lattype[MXCTYPELEN+4];/* Latitude axis CTYPE value */
   char lontype[MXCTYPELEN+4];/* Longitude axis CTYPE value */
   const char *latsym;      /* SkyFrame latitude axis symbol */
   const char *lonsym;      /* SkyFrame longitude axis symbol */
   const char *prj_name;    /* Pointer to projection name string */
   const char *skyref;      /* Formatted SkyRef position */
   const char *skyrefis;    /* SkyRefIs value */
   const char *sys;         /* Celestal coordinate system */
   const char *timesys;     /* Timescale specified in FitsChan */
   double ep;               /* Epoch of observation in required timescale (MJD) */
   double ep_tdb;           /* Epoch of observation in TDB timescale (MJD) */
   double ep_utc;           /* Epoch of observation in UTC timescale (MJD) */
   double eq;               /* Epoch of reference equinox (MJD) */
   double geolat;           /* Geodetic latitude of observer (radians) */
   double geolon;           /* Geodetic longitude of observer (radians) */
   double h;                /* Geodetic altitude of observer (metres) */
   double skyref_lat;       /* SkyRef latitude value (rads) */
   double skyrefp_lat;      /* SkyRefP latitude value (rads) */
   double skyref_lon;       /* SkyRef longitude value (rads) */
   double skyrefp_lon;      /* SkyRefP longitude value (rads) */
   double xyz[3];           /* Geocentric position vector (in m) */
   int defdate;             /* Can the date keywords be defaulted? */
   int i;                   /* Character count */
   int isys;                /* Celestial coordinate system */
   int latax;               /* Index of latitude axis in SkyFrame */
   int lonax;               /* Index of longitude axis in SkyFrame */
   int ok;                  /* Do axis symbols conform to FITS-WCS CTYPE form? */
   int old_ignore_used;     /* Original setting of external ignore_used variable */
   int ret;                 /* Returned flag */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Check we have a SkyFrame. */
   if( !IsASkyFrame( skyfrm ) ) return 0;

/* Initialise */
   ret = 1;

/* Get the equinox, epoch of observation, and system of the SkyFrame. The epoch
   is in TDB. It is assumed the Equinox is in UTC. */
   eq = astGetEquinox( skyfrm );
   sys = astGetC( skyfrm, "system" );
   ep_tdb = astTestEpoch( skyfrm ) ? astGetEpoch( skyfrm ) : AST__BAD;

/* Convert the epoch to UTC. */
   ep_utc = TDBConv( ep_tdb, AST__UTC, 1, method, class, status );

/* See if the FitsChan contains a value for the TIMESYS keyword (include
   previously used cards in the search). If so, and if it is not UTC, convert
   the epoch to the specified time scale, and store a TIMESYS value in the
   FitsStore. */
   old_ignore_used = ignore_used;
   ignore_used = 0;
   if( GetValue( this, "TIMESYS", AST__STRING, (void *) &timesys, 0, 0, method,
                 class, status ) && strcmp( timesys, "UTC" ) ) {
      ep = TDBConv( ep_tdb, TimeSysToAst( this, timesys, method, class,
                                          status ),
                    1, method, class, status );
      SetItemC( &(store->timesys), 0, 0, s, timesys, status );

/* If no TIMESYS keyword was found in the FitsChan, or the timesys was
   UTC, we use the UTC epoch value found above. In this case no TIMESYS value
   need be stored in the FitsSTore since UTC is the default for TIMESYS. */
   } else {
      ep = ep_utc;
   }

/* Reinstate the original value for the flag that indicates whether keywords
   in the FitsChan that have been used previously should be ignored. */
   ignore_used = old_ignore_used;

/* The MJD-OBS and DATE-OBS keywords default to the epoch of the
   reference equinox if not supplied. Therefore MJD-OBS and DATE-OBS do
   not need to be stored in the FitsChan if the epoch of observation is
   the same as the epoch of the reference equinox. This can avoid
   producing FITS headers which say unlikely things like
   DATE-OBS = "01/01/50". Set a flag indicating if MJD-OBS and DATE-OBS
   can be defaulted. */
   defdate = astEQUAL( ep_utc, eq );

/* Convert the equinox to a Julian or Besselian epoch. Also get the
   reference frame and standard system. */
   if( !Ustrcmp( sys, "FK4", status ) ){
      eq = palEpb( eq );
      isys = RADEC;
      SetItemC( &(store->radesys), 0, 0, s, "FK4", status );
   } else if( !Ustrcmp( sys, "FK4_NO_E", status ) || !Ustrcmp( sys, "FK4-NO-E", status ) ){
      eq = palEpb( eq );
      isys = RADEC;
      SetItemC( &(store->radesys), 0, 0, s, "FK4-NO-E", status );
   } else if( !Ustrcmp( sys, "FK5", status ) ){
      eq = palEpj( eq );
      isys = RADEC;
      SetItemC( &(store->radesys), 0, 0, s, "FK5", status );
   } else if( !Ustrcmp( sys, "ICRS", status ) ){
      eq = AST__BAD;
      isys = RADEC;
      SetItemC( &(store->radesys), 0, 0, s, "ICRS", status );
   } else if( !Ustrcmp( sys, "GAPPT", status ) ||
              !Ustrcmp( sys, "Apparent", status ) ||
              !Ustrcmp( sys, "Geocentric", status ) ){
      eq = AST__BAD;
      isys = RADEC;
      SetItemC( &(store->radesys), 0, 0, s, "GAPPT", status );
   } else if( !Ustrcmp( sys, "Helioecliptic", status ) ){
      eq = AST__BAD;
      isys = HECLIP;
   } else if( !Ustrcmp( sys, "Galactic", status ) ){
      eq = AST__BAD;
      isys = GALAC;
   } else if( !Ustrcmp( sys, "Supergalactic", status ) ){
      eq = AST__BAD;
      isys = SUPER;
   } else if( !Ustrcmp( sys, "AzEl", status ) ){
      eq = AST__BAD;
      isys = AZEL;
   } else {
      eq = AST__BAD;
      isys = NOCEL;
   }

/* Store these values. Only store the date if it does not take its
   default value. */
   SetItem( &(store->equinox), 0, 0, s, eq, status );
   if( !defdate ) SetItem( &(store->mjdobs), 0, 0, ' ', ep, status );

/* Only proceed if we have usable values */
   if( astOK ) {

/* Get the indices of the latitude and longitude axes within the
   SkyFrame. */
      latax = astGetLatAxis( skyfrm );
      lonax = 1 - latax;

/* The first 4 characters in CTYPE are determined by the celestial coordinate
   system and the second 4 by the projection type. If we are describing
   offset coordinates, then use "OFLN" and "OFLT. Otherwise use the
   standard FITS-WCS name of the system. */
      if( isoff > 0 ){
         strcpy( lontype, "OFLN" );
         strcpy( lattype, "OFLT" );
      } else if( isys == RADEC ){
         strcpy( lontype, "RA--" );
         strcpy( lattype, "DEC-" );
      } else if( isys == ECLIP ){
         strcpy( lontype, "ELON" );
         strcpy( lattype, "ELAT" );
      } else if( isys == HECLIP ){
         strcpy( lontype, "HLON" );
         strcpy( lattype, "HLAT" );
      } else if( isys == GALAC ){
         strcpy( lontype, "GLON" );
         strcpy( lattype, "GLAT" );
      } else if( isys == SUPER ){
         strcpy( lontype, "SLON" );
         strcpy( lattype, "SLAT" );
      } else if( isys == AZEL ){
         strcpy( lontype, "AZ--" );
         strcpy( lattype, "EL--" );

/* For unknown systems, use the axis symbols within CTYPE if they conform
   to the requirement of FITS-WCS (i.e. "xxLN/xxLT" or "xLON/xLAT") or use
   "UNLN/UNLT" otherwise. */
      } else {
         latsym = astGetSymbol( skyfrm, latax );
         lonsym = astGetSymbol( skyfrm, lonax );
         if( astOK ) {

            ok = 0;
            if( strlen( latsym ) == 4 && strlen( lonsym ) == 4 ) {
               if( !strcmp( latsym + 2, "LT" ) &&
                   !strcmp( lonsym + 2, "LN" ) &&
                   !strncmp( latsym, lonsym, 2 ) ) {
                  ok = 1;
               } else if( !strcmp( latsym + 1, "LAT" ) &&
                   !strcmp( lonsym + 1, "LON" ) &&
                   !strncmp( latsym, lonsym, 1 ) ) {
                  ok = 1;
               }
            }

            if( !ok ) {
               latsym = "UNLT";
               lonsym = "UNLN";
            }

            strncpy( lontype, lonsym, 4 );
            for( i = strlen( lonsym ); i < 4; i++ ) {
               lontype[ i ] = '-';
            }
            strncpy( lattype, latsym, 4 );
            for( i = strlen( latsym ); i < 4; i++ ) {
               lattype[ i ] = '-';
            }
         }
      }

/* Store the projection strings. */
      prj_name = ( wcstype == 0 ) ? "-TAB" : astWcsPrjName( wcsproj );
      if( astOK ) {
         strcpy( lontype + 4, prj_name );
         strcpy( lattype + 4, prj_name );
      }

/* Store the total CTYPE strings */
      SetItemC( &(store->ctype), axlon, 0, s, lontype, status );
      SetItemC( &(store->ctype), axlat, 0, s, lattype, status );

/* Store offset coord information. */
      if( isoff ) {

/* If the description is for offset coords store suitable comments for
   the CTYPE keywords. */
         if( isoff > 0 ) {
            skyref = astGetC( skyfrm, "SkyRef" );

            sprintf( attr, "Symbol(%d)", lonax + 1 );
            const char *text = astGetC( skyfrm, attr );
            if( skyref && text ) {
               sprintf( com, "%s offset from %s", text + 1, skyref );
               SetItemC( &(store->ctype_com), axlon, 0, s, com, status );
            }

            sprintf( attr, "Symbol(%d)", latax + 1 );
            text = astGetC( skyfrm, attr );
            if( skyref && text ) {
               sprintf( com, "%s offset from %s", text + 1, skyref );
               SetItemC( &(store->ctype_com), axlat, 0, s, com, status );
            }

/* If the description is for absolute coords store the SkyFrame attribute
   values in AST-specific keywords. */
         } else {
            sprintf( attr, "SkyRef(%d)", lonax + 1 );
            skyref_lon = astGetD( skyfrm, attr );
            sprintf( attr, "SkyRef(%d)", latax + 1 );
            skyref_lat = astGetD( skyfrm, attr );

            sprintf( attr, "SkyRefP(%d)", lonax + 1 );
            skyrefp_lon = astGetD( skyfrm, attr );
            sprintf( attr, "SkyRefP(%d)", latax + 1 );
            skyrefp_lat = astGetD( skyfrm, attr );

            skyrefis = (isoff < -2) ? "IGNORED" :
                       ( (isoff < -1) ? "ORIGIN" : "POLE" );

            SetItemC( &(store->skyrefis), 0, 0, s, skyrefis, status );
            if( astTest( skyfrm, "SkyRef(1)" ) ) {
               SetItem( &(store->skyref), axlon, 0, s, skyref_lon, status );
               SetItem( &(store->skyref), axlat, 0, s, skyref_lat, status );
            }
            if( astTest( skyfrm, "SkyRefP(1)" ) ) {
               SetItem( &(store->skyrefp), axlon, 0, s, skyrefp_lon, status );
               SetItem( &(store->skyrefp), axlat, 0, s, skyrefp_lat, status );
            }
         }
      }

/* If the Label attribute  has been set for an axis, use it as the CTYPE
   comment and CNAME value. */
      if( astTestLabel( skyfrm, latax ) ) {
         label = (char *) astGetLabel( skyfrm, latax );
         SetItemC( &(store->ctype_com), axlat, 0, s, label, status );
         SetItemC( &(store->cname), axlat, 0, s, label, status );
      }
      if( astTestLabel( skyfrm, lonax ) ) {
         label = (char *) astGetLabel( skyfrm, lonax );
         SetItemC( &(store->ctype_com), axlon, 0, s, label, status );
         SetItemC( &(store->cname), axlon, 0, s, label, status );
      }

/* Nullify any CUNITS strings for the longitude and latitude axes (they
   always take the default value of degrees). */
      SetItemC( &(store->cunit), axlat, 0, s, NULL, status );
      SetItemC( &(store->cunit), axlon, 0, s, NULL, status );
   }

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

/* Store the observer's position if set (needed for definition of AzEl
   systems). */
   if( astTestObsLon( skyfrm ) && astTestObsLat( skyfrm ) && s == ' ' ) {
      geolon = astGetObsLon( skyfrm );
      geolat = astGetObsLat( skyfrm );
      h = astGetObsAlt( skyfrm );
      if( geolat != AST__BAD && geolon != AST__BAD && h != AST__BAD ) {
         eraGd2gc( 1, geolon, geolat, h, xyz );
         SetItem( &(store->obsgeox), 0, 0, ' ', xyz[0], status );
         SetItem( &(store->obsgeoy), 0, 0, ' ', xyz[1], status );
         SetItem( &(store->obsgeoz), 0, 0, ' ', xyz[2], status );
      }
   }
   if( !astOK ) ret = 0;
   return ret;
}

static char *SourceWrap( const char *(* source)( void ), int *status ) {
/*
*  Name:
*     SourceWrap

*  Purpose:
*     Wrapper function to invoke a C FitsChan source function.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     char *SourceWrap( const char *, int *status(* source)( void ) )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function invokes the source function whose pointer is
*     supplied in order to read the next input line from an external
*     data store. It then returns a pointer to a dynamic string
*     containing a copy of the text that was read.

*  Parameters:
*     source
*        Pointer to a source function, with no parameters, that
*        returns a pointer to a const, null-terminated string
*        containing the text that it read. This is the form of FitsChan
*        source function employed by the C language interface to the
*        AST library.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to a dynamically allocated, null terminated string
*     containing a copy of the text that was read. This string must be
*     freed by the caller (using astFree) when no longer required.
*
*     A NULL pointer will be returned if there is no more input text
*     to read.

*  Notes:
*     - A NULL pointer value will be returned if this function is
*     invoked with the global error status set or if it should fail
*     for any reason.
*/

/* Local Variables: */
   char *result;                 /* Pointer value to return */
   const char *line;             /* Pointer to input line */

/* Initialise. */
   result = NULL;

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

/* Invoke the source function to read the next input line and return a
   pointer to the resulting string. */
   line = ( *source )();

/* If a string was obtained, make a dynamic copy of it and save the
   resulting pointer. */
   if ( line ) result = astString( line, (int) strlen( line ) );

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

static AstMapping *SpectralAxes( AstFitsChan *this, AstFrameSet *fs,
                                 double *dim, int *wperm,
                                 char s, FitsStore *store, double *crvals,
                                 int *axis_done, const char *method,
                                 const char *class, int *status ){

/*
*  Name:
*     SpectralAxes

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

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"

*     AstMapping *SpectralAxes( AstFitsChan *this, AstFrameSet *fs,
*                               double *dim, int *wperm,
*                               char s, FitsStore *store, double *crvals,
*                               int *axis_done, const char *method,
*                               const char *class, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The current Frame of the supplied FrameSet is searched for spectral
*     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 III 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.
*
*     If a spectral axis is found, the RafRA and RefDec attributes of the
*     SpecFrame describing the axis are ignored: it is assumed that the
*     WCS Frame also contains a pair of celestial axes which will result
*     in appropriate celestial reference values being stored in the
*     FitsStore (this asumption should be enforced by calling function
*     MakeFitsFrameSet prior to calling this function).

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     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. The spectral Unit in the returned
*        FrameSet will always be linearly related to the default Units for
*        the spectral System in use by the axis. If this requires a
*        change to the existing spectral Unit, the integrity of the
*        FrameSet will be maintained by suitable adjustments to the Mappings
*        within the FrameSet.
*     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. one les than 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.
*     crvals
*        Pointer to an array holding the default CRVAL value for each
*        axis in the WCS Frame.
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     If a spectral axis was found which can be described using the
*     conventions of FITS-WCS paper III, 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 spectral axes. Any non-spectral axes are passed
*     unchanged by the returned Mapping.
*/

/* Local Variables: */
   AstFitsTable *table;    /* Pointer to structure holding -TAB table info */
   AstFrame *pframe;       /* Primary Frame containing current WCS axis*/
   AstFrame *tfrm1;        /* A temporary Frame */
   AstFrame *tfrm;         /* A temporary Frame */
   AstFrame *wcsfrm;       /* WCS Frame within FrameSet */
   AstFrameSet *tfs;       /* A temporary FrameSet */
   AstGrismMap *gmap;      /* GrismMap defining the spectral axis */
   AstMapping *axmap;      /* Mapping from WCS to IWC */
   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 */
   AstMapping *tmap5;      /* A temporary Mapping */
   AstMapping *tmap6;      /* A temporary Mapping */
   AstPermMap *pm;         /* PermMap pointer */
   AstSpecFrame *specfrm;  /* The SpecFrame defining current WCS axis */
   char *cname;            /* Pointer to CNAME value */
   char ctype[ MXCTYPELEN ]; /* The value for the FITS CTYPE keyword */
   char lin_unit[ 20 ];    /* Linear spectral Units being used */
   char orig_system[ 40 ]; /* Value of System attribute for current WCS axis */
   char system_attr[ 20 ]; /* Name of System attribute for current WCS axis */
   char unit_attr[ 20 ];   /* Name of Unit attribute for current WCS axis */
   const char *cval;       /* Pointer to temporary character string */
   const char *x_sys[ 4 ]; /* Basic spectral systems */
   double *lbnd_p;         /* Pointer to array of lower pixel bounds */
   double *ubnd_p;         /* Pointer to array of upper pixel bounds */
   double crval;           /* The value for the FITS CRVAL keyword */
   double dgbyds;          /* Rate of change of grism parameter wrt "S" at ref. point */
   double dsbydx;          /* Rate of change of "S" wrt "X" at ref. point */
   double geolat;          /* Geodetic latitude of observer (radians) */
   double geolon;          /* Geodetic longitude of observer (radians) */
   double gval;            /* Value of grism parameter at reference point  */
   double h;               /* Geodetic altitude of observer (metres) */
   double imagfreq;        /* Image sideband equivalent to the rest frequency (Hz) */
   double lbnd_s;          /* Lower bound on spectral axis */
   double pv;              /* Value of projection parameter */
   double restfreq;        /* Rest frequency (Hz) */
   double ubnd_s;          /* Upper bound on spectral axis */
   double vsource;         /* Rel.vel. of source (m/s) */
   double xval;            /* Value of "X" system at reference point  */
   double xyz[3];          /* Geocentric position vector (in m) */
   double zsource;         /* Redshift of source */
   int *inperm;            /* Pointer to permutation array for input axes */
   int *outperm;           /* Pointer to permutation array for output axes */
   int extver;             /* Table version number for -TAB headers */
   int fits_i;             /* FITS WCS axis index for current WCS axis */
   int iax;                /* Axis index */
   int icolindex;          /* Index of table column holding index vector */
   int icolmain;           /* Index of table column holding main coord array */
   int interp;             /* INterpolation method for look-up tables */
   int ix;                 /* System index */
   int j;                  /* Loop count */
   int npix;               /* Number of pixel axes */
   int nwcs;               /* Number of WCS axes */
   int pax;                /* Pixel axis corresponding to iax */
   int paxis;              /* Axis index within primary Frame */
   int sourcevrf;          /* Rest Frame in which SourceVel is accesed */

/* Initialise */
   ret = NULL;

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

/* Every supported spectral system is linearly related to one of the
   following four systems. */
   x_sys[ 0 ] = "FREQ";
   x_sys[ 1 ] = "WAVE";
   x_sys[ 2 ] = "AWAV";
   x_sys[ 3 ] = "VELO";

/* 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 );

/* Store the upper and lower pixel bounds. */
   lbnd_p = astMalloc( sizeof( double )*(size_t) npix );
   ubnd_p = astMalloc( sizeof( double )*(size_t) npix );
   if( astOK ) {
      for( iax = 0; iax < npix; iax++ ) {
         lbnd_p[ iax ] = 1.0;
         ubnd_p[ iax ] = ( dim[ iax ] != AST__BAD ) ? dim[ iax ] : 500;
      }
   }

/* Check each axis in the WCS Frame to see if it is a spectral axis. */
   axmap = NULL;
   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 SpecFrame, we have found a spectral
   axis. */
      if( astIsASpecFrame( pframe ) ) {
         specfrm = (AstSpecFrame *) pframe;

/* Note the (zero-based) FITS WCS axis index to be used for the current
   Frame axis. */
         fits_i = wperm[ iax ];

/* Note the name and original value of the System attribute for the spectral
   axis within the FrameSet current Frame. */
         sprintf( system_attr, "System(%d)", iax + 1 );
         cval = astGetC( wcsfrm, system_attr );
         if( cval ) strcpy( orig_system, cval );

/* Note the name of the Unit attribute for the spectral axis within the
   FrameSet current Frame. */
         sprintf( unit_attr, "Unit(%d)", iax + 1 );

/* Get a pointer to the Mapping from FITS pixel coordinates to SpecFrame. */
         map = astGetMapping( fs, AST__BASE, AST__CURRENT );

/* Find the bounds of the Spectral axis over the volume of the pixel grid. */
         astMapBox( map, lbnd_p, ubnd_p, 1, iax, &lbnd_s, &ubnd_s,
                    NULL, NULL );

/* The Unit attribute of a SpecFrame can be set to arbitrary non-linear
   functions of standard linear spectral units.  FITS-WCS paper III requires
   CRVAL etc to be given in linear units. So first we ensure that we have a
   SpecFrame with linear Units. Create a copy of the SpecFrame and clear
   its Unit attribute (this ensures the copy has the default linear units).
   Then find a Mapping from the original spectral units to the default
   linear units. If the conversion is possible, see if the Mapping
   between the units is linear. If it is, then the original Unit attribute
   of the SpecFrame is OK (i.e. the units are linear). If not, clear
   the Unit attribute of the spectral axis in the FrameSet so that it
   uses the default linear units (retaining the original value so that it
   can be re-instated later). Using the clear method on the FrameSet
   pointer rather than the SpecFrame pointer causes the SpecFrame to be
   re-mapped within the FrameSet to maintain its correct relationship with
   the other Frames in the FrameSet. Also update the pixel->spectrum
   Mapping to take account of the change in units and re-calculate the new
   bounds on the spectral axis. Also update any supplied CRVAL value for
   the spectral axis. */
         tfrm = astCopy( specfrm );
         astClearUnit( tfrm, 0 );
         tfs = astConvert( specfrm, tfrm, "" );
         tfrm = astAnnul( tfrm );
         if( tfs ) {
            crval = crvals ? crvals[ iax ] : AST__BAD;
            tmap1 = astGetMapping( tfs, AST__BASE, AST__CURRENT );
            tfs = astAnnul( tfs );
            if( !IsMapLinear( tmap1, &lbnd_s, &ubnd_s, 0, &pax, status ) ) {
               astClear( fs, unit_attr );
               (void) astAnnul( map );
               map = astGetMapping( fs, AST__BASE, AST__CURRENT );
               astMapBox( map, lbnd_p, ubnd_p, 1, iax, &lbnd_s, &ubnd_s,
                          NULL, NULL );
               astTran1( tmap1, 1, &crval, 1, &crval );
            }
            tmap1 = astAnnul( tmap1 );

/* Note the linear spectral Unit currently in use. */
            cval = astGetUnit( specfrm, 0 );
            if( cval ) strcpy( lin_unit, cval );

/* For some of the algorithms, the reference value CRVAL is arbitrary.
   For these algorithms we choose to use the supplied default CRVAL value.
   If no default CRVAL value was suppllied, we use the mid spectral value
   if the size of the spectral axis was given, or the lower bound (i.e.
   pixel 1) if the size of the spectral axis was not given. */
            if( crval == AST__BAD ) {
               if( pax >= 0 && dim[ pax ] != AST__BAD ) {
                  crval = 0.5*( lbnd_s + ubnd_s );
               } else {
                  crval = lbnd_s;
               }
            }

/* Modify this crval value so that it correpsonds to an integer pixel
   coordinate. */
            crval = NearestPix( map, crval, iax, status );

/* We now check to see if the Mapping from pixel coords -> linear spectral
   coords corresponds to one of the algorithms supported in FITS-WCS paper
   III. First check for the "linear" algorithm in which the linear spectral
   coordinate given by the SpecFrame is related linearly to the pixel
   coords. If the ForceTab attribute is set, we jump on immediately to
   using -TAB regardless of whether some other algorithm could be used. */
            ctype[ 0 ] = 0;
            if( !astGetForceTab( this ) ) {
               if( IsMapLinear( map, lbnd_p, ubnd_p, iax, NULL, status ) ) {

/* The CTYPE value is just the spectral system. */
                  strcpy( ctype, orig_system );

/* Create the Mapping which defines the spectral IWC axis. This is
   initially the Mapping from WCS to IWCS - it subtracts the CRVAL value
   from the spectral WCS value to get the spectral IWC value (other
   non-spectral axes are left unchanged by this Mapping). This results
   in the spectral IWC axis having the same axis index as the spectral
   WCS axis. */
                  crval = -crval;
                  tmap0 = (AstMapping *) astShiftMap( 1, &crval, "", status );
                  crval = -crval;
                  axmap = AddUnitMaps( tmap0, iax, nwcs, status );
                  tmap0 = astAnnul( tmap0 );
               }

/* If the "linear" algorithm above is inappropriate, see if the "non-linear"
   algorithm defined in FITS-WCS paper III can be used, in which pixel
   coords are linearly related to some spectral system (called "X") other
   than the one represented by the supplied SpecFrame (called "S"). */
               if( !ctype[ 0 ] ) {

/* Loop round each of the 4 allowed X systems. All other spectral systems
   are linearly related to one of these 4 systems and so do not need to be
   tested. */
                  for( ix = 0; ix < 4 && !ctype[ 0 ]; ix++ ) {

/* Set the system of the spectral WCS axis to the new trial X system. Clear
   the Unit attribute to ensure we are using the default linear units.
   Using the FrameSet pointer "fs" ensures that the Mappings within the
   FrameSet are modified to maintain the correct inter-Frame relationships. */
                     astSetC( fs, system_attr, x_sys[ ix ] );
                     astClear( fs, unit_attr );

/* Now we check to see if the current X system is linearly related to
   pixel coordinates. */
                     tmap3 = astGetMapping( fs, AST__BASE, AST__CURRENT );
                     if( IsMapLinear( tmap3, lbnd_p, ubnd_p, iax, NULL, status ) ) {

/* CTYPE: First 4 characters specify the "S" system. */
                        strcpy( ctype, orig_system );

/* The non-linear algorithm code to be appended to the "S" system is of the
   form "-X2P" ("P" is the system which is linearly related to "S"). */
                        if( !strcmp( x_sys[ ix ], "FREQ" ) ) {
                           strcpy( ctype + 4, "-F2" );
                        } else if( !strcmp( x_sys[ ix ], "WAVE" ) ) {
                           strcpy( ctype + 4, "-W2" );
                        } else if( !strcmp( x_sys[ ix ], "AWAV" ) ) {
                           strcpy( ctype + 4, "-A2" );
                        } else {
                           strcpy( ctype + 4, "-V2" );
                        }
                        if( !strcmp( orig_system, "FREQ" ) ||
                            !strcmp( orig_system, "ENER" ) ||
                            !strcmp( orig_system, "WAVN" ) ||
                            !strcmp( orig_system, "VRAD" ) ) {
                           strcpy( ctype + 7, "F" );
                        } else if( !strcmp( orig_system, "WAVE" ) ||
                                   !strcmp( orig_system, "VOPT" ) ||
                                   !strcmp( orig_system, "ZOPT" ) ) {
                           strcpy( ctype + 7, "W" );
                        } else if( !strcmp( orig_system, "AWAV" ) ) {
                           strcpy( ctype + 7, "A" );
                        } else {
                           strcpy( ctype + 7, "V" );
                        }

/* Create a Mapping which gives S as a function of X. */
                        tfrm = astCopy( specfrm );
                        astSetC( tfrm, "System(1)", orig_system );
                        astSetC( tfrm, "Unit(1)", lin_unit );
                        tfs = astConvert( specfrm, tfrm, "" );
                        tmap5 = astGetMapping( tfs, AST__BASE, AST__CURRENT );
                        tfs = astAnnul( tfs );
                        tfrm = astAnnul( tfrm );

/* Use the inverse of this Mapping to get the X value at the reference S
   value. */
                        astTran1( tmap5, 1, &crval, 0, &xval );

/* Also use it to get the rate of change of S with respect to X at the
   reference point. */
                        dsbydx = astRate( tmap5, &xval, 0, 0 );

/* Create the Mapping which defines the spectral IWC axis. This is the
   Mapping from WCS to IWC - it first converts from S to X, then subtracts
   the X reference value value, and then scales the axis to ensure that
   the rate of change of S with respect to IWC is unity (as required by
   FITS-WCS paper III). Other non-spectral axes are left unchanged by
   the Mapping. The spectral IWC axis has the same axis index as the
   spectral WCS axis. */
                        xval = -xval;
                        tmap2 = (AstMapping *) astShiftMap( 1, &xval, "", status );
                        astInvert( tmap5 );
                        tmap0 = (AstMapping *) astCmpMap( tmap5, tmap2, 1, "", status );
                        tmap5 = astAnnul( tmap5 );
                        tmap2 = astAnnul( tmap2 );
                        tmap2 = (AstMapping *) astZoomMap( 1, dsbydx, "", status );
                        tmap1 = (AstMapping *) astCmpMap( tmap0, tmap2, 1, "", status );
                        tmap0 = astAnnul( tmap0 );
                        tmap2 = astAnnul( tmap2 );
                        axmap = AddUnitMaps( tmap1, iax, nwcs, status );
                        tmap1 = astAnnul( tmap1 );
                     }
                     tmap3 = astAnnul( tmap3 );

/* Re-instate the original system and unit attributes for the spectral axis. */
                     astSetC( fs, system_attr, orig_system );
                     astSetC( fs, unit_attr, lin_unit );
                  }
               }

/* If the "non-linear" algorithm above is inappropriate, see if the
   "log-linear" algorithm defined in FITS-WCS paper III can be used, in
   which the spectral axis is logarithmically spaced in the spectral
   system given by the SpecFrame. */
               if( !ctype[ 0 ] ) {

/* If the "log-linear" algorithm is appropriate, the supplied SpecFrame (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. Test this. If the test is passed a Mapping is returned from
   WCS to IWC. */
                  axmap = LogAxis( map, iax, nwcs, lbnd_p, ubnd_p, crval, status );

/* If the axis is logarithmic... */
                  if( axmap ) {

/* CTYPE: First 4 characters specify the "S" system. */
                     strcpy( ctype, orig_system );

/* The rest is "-LOG". */
                     strcpy( ctype + 4, "-LOG" );
                  }
               }

/* If the "log-linear" algorithm above is inappropriate, see if the "grism"
   algorithm defined in FITS-WCS paper III can be used, in which pixel
   coords are related to wavelength using a grism dispersion function,
   implemented in AST by a GrismMap. GrismMaps produce either vacuum
   wavelength or air wavelength as output. Temporarily set the SpecFrame
   to these two systems in turn before we do the check for a GrismMap. */
               for( ix = 0; ix < 2 && !ctype[ 0 ]; ix++ ) {
                  astSetC( fs, system_attr, ( ix == 0 ) ? "WAVE" : "AWAV" );
                  astSetC( fs, unit_attr, "m" );

/* Get the simplified Mapping from pixel to wavelength. If the Mapping is
   a CmpMap containing a GrismMap, and if the output of the GrismMap is
   scaled by a neighbouring ZoomMap (e.g. into different wavelength units),
   then the GrismMap will be modified to incorporate the effect of the
   ZoomMap, and the ZoomMap will be removed. */
                  tmap2 = astGetMapping( fs, AST__BASE, AST__CURRENT );
                  tmap1 = astSimplify( tmap2 );
                  tmap2 = astAnnul( tmap2 );

/* Analyse this Mapping to see if the iax'th output is created directly by a
   GrismMap (i.e. the output of theGrismMap must not subsequently be
   modified by some other Mapping). If so, ExtractGrismMap returns a pointer
   to the GrismMap as its function value, and also returns "tmap2" as a copy
   of tmap1 in which the GrismMap has been replaced by a UnitMap. */
                  gmap = ExtractGrismMap( tmap1, iax, &tmap2, status );
                  if( gmap ) {

/* The Mapping without the GrismMap must be linear on the spectral axis. */
                     if( IsMapLinear( tmap2, lbnd_p, ubnd_p, iax, NULL, status ) ) {

/* Get the reference wavelength (in "m") stored in the GrismMap. */
                        crval = astGetGrismWaveR( gmap );

/* Save a copy of the current Wavelength (in "m") SpecFrame. */
                        tfrm1 = astCopy( specfrm );

/* Re-instate the original System and Unit attributes for the SpecFrame. */
                        astSetC( fs, system_attr, orig_system );
                        astSetC( fs, unit_attr, lin_unit );

/* Find the Mapping from the original "S" system to wavelength (in "m"). */
                        tfs = astConvert( specfrm, tfrm1, "" );
                        tfrm1 = astAnnul( tfrm1 );
                        if( tfs ) {
                           tmap3 = astGetMapping( tfs, AST__BASE, AST__CURRENT );
                           tfs = astAnnul( tfs );

/* Use the inverse of this Mapping to convert the reference value from
   wavelength to the "S" system. */
                           astTran1( tmap3, 1, &crval, 0, &crval );

/* Concatenate the "S"->wavelength Mapping with the inverse GrismMap (from
   wavelength to grism parameter), to get the "S" -> "grism parameter"
   Mapping. */
                           astInvert( gmap );
                           tmap4 = (AstMapping *) astCmpMap( tmap3, gmap, 1, "", status );
                           tmap3 = astAnnul( tmap3 );

/* Use this Mapping to find the grism parameter at the reference point. */
                           astTran1( tmap4, 1, &crval, 1, &gval );

/* Also use it to find the rate of change of grism parameter with respect
   to "S" at the reference point. */
                           dgbyds = astRate( tmap4, &crval, 0, 0 );

/* FITS-WCS paper III required ds/dw to be unity at the reference point.
   Therefore the rate of change of grism parameter with respect to IWC ("w")
   is equal to the rate of change of grism parameter with respect to "S"
   (at the reference point). The mapping from "w" to grism parameter is a
   ZoomMap which scales "w" by "dgbyds" followed by a ShiftMap which adds
   on "gval". */
                           tmap5 = (AstMapping *) astZoomMap( 1, dgbyds, "", status );
                           tmap6 = (AstMapping *) astShiftMap( 1, &gval, "", status );
                           tmap3 = (AstMapping *) astCmpMap( tmap5, tmap6, 1, "", status );
                           tmap5 = astAnnul( tmap5 );
                           tmap6 = astAnnul( tmap6 );

/* Create the Mapping which defines the spectral IWC axis. This is the
   Mapping from WCS "S" to IWCS "w", formed by combining the Mapping from
   "S" to grism parameter (tmap4), with the Mapping from grism parameter to
   "w" (inverse of tmap3). Other non-spectral axes are left unchanged by the
   Mapping. The spectral IWC axis has the same axis index as the spectral
   WCS axis. */
                           astInvert( tmap3 );
                           tmap5 = (AstMapping *) astCmpMap( tmap4, tmap3, 1, "", status );
                           tmap3 = astAnnul( tmap3 );
                           tmap4 = astAnnul( tmap4 );
                           axmap = AddUnitMaps( tmap5, iax, nwcs, status );
                           tmap5 = astAnnul( tmap5 );

/* CTYPE: First 4 characters specify the "S" system. */
                           strcpy( ctype, orig_system );

/* Last 4 characters are "-GRA" or "-GRI". */
                           strcpy( ctype + 4, ( ix == 0 ) ? "-GRI" : "-GRA"  );

/* Store values for the projection parameters in the FitsStore. Ignore
   parameters which are set to the default values defined in FITS-WCS
   paper III. */
                           pv = astGetGrismG( gmap );
                           if( pv != 0 ) SetItem( &(store->pv), fits_i, 0, s, pv, status );
                           pv = (double) astGetGrismM( gmap );
                           if( pv != 0 ) SetItem( &(store->pv), fits_i, 1, s, pv, status );
                           pv = astGetGrismAlpha( gmap );
                           if( pv != 0 ) SetItem( &(store->pv), fits_i, 2, s, pv*AST__DR2D, status );
                           pv = astGetGrismNR( gmap );
                           if( pv != 1.0 ) SetItem( &(store->pv), fits_i, 3, s, pv, status );
                           pv = astGetGrismNRP( gmap );
                           if( pv != 0 ) SetItem( &(store->pv), fits_i, 4, s, pv, status );
                           pv = astGetGrismEps( gmap );
                           if( pv != 0 ) SetItem( &(store->pv), fits_i, 5, s, pv*AST__DR2D, status );
                           pv = astGetGrismTheta( gmap );
                           if( pv != 0 ) SetItem( &(store->pv), fits_i, 6, s, pv*AST__DR2D, status );
                        }
                     }

/* Release resources. */
                     tmap2 = astAnnul( tmap2 );
                     gmap = astAnnul( gmap );
                  }

/* Release resources. */
                  tmap1 = astAnnul( tmap1 );

/* Re-instate the original System and Unit attributes for the SpecFrame. */
                  astSetC( fs, system_attr, orig_system );
                  astSetC( fs, unit_attr, lin_unit );
               }
            }

/* If none of the above algorithms are appropriate, we must resort to
   using the -TAB algorithm, in which the Mapping is defined by a look-up
   table. Check the TabOK attribute to see -TAB is to be supported. */
            extver = astGetTabOK( this );
            if( !ctype[ 0 ] && extver > 0 ) {

/* Get any pre-existing FitsTable from the FitsStore. This is the table
   in which the tabular data will be stored (if the Mapping can be expressed
   in -TAB form). */
               if( !astMapGet0A( store->tables, AST_TABEXTNAME, &table ) ) table = NULL;

/* See if the Mapping can be expressed in -TAB form. */
               tmap0 = IsMapTab1D( map, 1.0, NULL, wcsfrm, dim, iax, fits_i, &table,
                                   &icolmain, &icolindex, &interp, status );
               if( tmap0 ) {

/* CTYPE: First 4 characters specify the "S" system. Last 4 characters are
   "-TAB". */
                  strcpy( ctype, orig_system );
                  strcpy( ctype + 4, "-TAB" );

/* The values stored in the table index vector are GRID coords. So we
   need to ensure that IWC are equivalent to GRID coords. So set CRVAL
   to zero. First store the original CRVAL value (which gives the
   observation centre) in AXREF. */
                  SetItem( &(store->axref), fits_i, 0, s, crval, status );
                  crval = 0.0;

/* Store TAB-specific values in the FitsStore. First the name of the
   FITS binary table extension holding the coordinate info. */
                  SetItemC( &(store->ps), fits_i, 0, s, AST_TABEXTNAME, status );

/* Next the table version number. This is the set (positive) value for the
   TabOK attribute. */
                  SetItem( &(store->pv), fits_i, 1, s, extver, status );

/* Also store the table version in the binary table header. */
                  astSetFitsI( table->header, "EXTVER", extver,
                               "Table version number", 0 );

/* Next the name of the table column containing the main coords array. */
                  SetItemC( &(store->ps), fits_i, 1, s,
                            astColumnName( table, icolmain ), status );

/* Next the name of the column containing the index array */
                  if( icolindex >= 0 ) SetItemC( &(store->ps), fits_i, 2, s,
                                  astColumnName( table, icolindex ), status );

/* The interpolation method (an AST extension to the published -TAB
   algorithm, communicated through the QVi_4a keyword). */
                  SetItem( &(store->pv), fits_i, 4, s, interp, status );

/* Also store the FitsTable itself in the FitsStore. */
                  astMapPut0A( store->tables, AST_TABEXTNAME, table, NULL );

/* Create the WCS -> IWC Mapping (AST uses grid coords as IWC coords for
   the -TAB algorithm). First, get a Mapping that combines the TAB axis
   Mapping( tmap0) in parallel with one or two UnitMaps in order to put
   the TAB axis at the required index. */
                  tmap1 = AddUnitMaps( tmap0, iax, nwcs, status );

/* Now get a PermMap that permutes the WCS axes into the FITS axis order. */
                  inperm = astMalloc( sizeof( double )*nwcs );
                  outperm = astMalloc( sizeof( double )*nwcs );
                  if( astOK ) {
                     for( j = 0; j < nwcs; j++ ) {
                        inperm[ j ] = wperm[ j ];
                        outperm[ wperm[ j ] ] = j;
                     }
                  }
                  pm = astPermMap( nwcs, inperm, nwcs, outperm, NULL, "",
                                   status );

/* Combine these two Mappings in series, to get the Mapping from WCS to
   IWC. */
                  axmap = (AstMapping *) astCmpMap( pm, tmap1, 1, " ",
                                                    status );

/* Free resources. */
                  inperm = astFree( inperm );
                  outperm = astFree( outperm );
                  pm = astAnnul( pm );
                  tmap0 = astAnnul( tmap0 );
                  tmap1 = astAnnul( tmap1 );
               }
               if( table ) table = astAnnul( table );
            }

/* If this axis is a usable spectral axis... */
            if( ctype[ 0 ] ) {

/* Add the Mapping for this axis in series with any existing result Mapping. */
               if( ret ) {
                  tmap0 = (AstMapping *) astCmpMap( ret, axmap, 1, "", status );
                  (void) astAnnul( ret );
                  ret = tmap0;
               } else {
                  ret = astClone( axmap );
               }
               axmap = astAnnul( axmap );

/* Store values for CTYPE, CRVAL and CUNIT in the FitsStore. */
               SetItemC( &(store->ctype), fits_i, 0, s, ctype, status );
               SetItem( &(store->crval), fits_i, 0, s, crval, status );
               SetItemC( &(store->cunit), fits_i, 0, s, lin_unit, status );

/* If the axis label has been set, use it as the CTYPE comment and CNAME
   value. */
               if( astTestLabel( specfrm, 0 ) ) {
                  cname = (char *) astGetLabel( specfrm, 0 );
                  SetItemC( &(store->ctype_com), fits_i, 0, s, cname, status );
                  SetItemC( &(store->cname), fits_i, 0, s, cname, status );
               }

/* Store values for the other FITS-WCS keywords which describe the
   spectral system. Only store values which have been explicitly set in
   the SpecFrame, which are different to the default values defined by
   FITS-WCS paper III (if any), and which are not bad. */
               if( astTestObsLon( specfrm ) && astTestObsLat( specfrm ) &&
                   s == ' ' ) {
                  geolon = astGetObsLon( specfrm );
                  geolat = astGetObsLat( specfrm );
                  h = astGetObsAlt( specfrm );
                  if( geolat != AST__BAD && geolon != AST__BAD && h != AST__BAD ) {
                     eraGd2gc( 1, geolon, geolat, h, xyz );
                     SetItem( &(store->obsgeox), 0, 0, ' ', xyz[0], status );
                     SetItem( &(store->obsgeoy), 0, 0, ' ', xyz[1], status );
                     SetItem( &(store->obsgeoz), 0, 0, ' ', xyz[2], status );
                  }
               }
               if( astTestRestFreq( specfrm ) ) {
                  restfreq = astGetRestFreq( specfrm );
                  if( restfreq != AST__BAD ) {
                     if( !strcmp( orig_system, "WAVE" ) ||
                         !strcmp( orig_system, "VOPT" ) ||
                         !strcmp( orig_system, "ZOPT" ) ||
                         !strcmp( orig_system, "AWAV" ) ) {
                        SetItem( &(store->restwav), 0, 0, s, AST__C/restfreq, status );
                     } else {
                        SetItem( &(store->restfrq), 0, 0, s, restfreq, status );
                     }
                  }
                  if( astIsADSBSpecFrame( specfrm ) ) {
                     imagfreq = astGetImagFreq( (AstDSBSpecFrame *) specfrm );
                     if( imagfreq != AST__BAD ) {
                        SetItem( &(store->imagfreq), 0, 0, s, imagfreq, status );
                     }
                  }
               }
               cval = GetFitsSor( astGetC( specfrm, "StdOfRest" ), status );
               if( cval ) SetItemC( &(store->specsys), 0, 0, s, cval, status );
               if( astTestSourceVel( specfrm ) ) {
                  vsource = astGetSourceVel( specfrm );
                  if( vsource != AST__BAD && fabs( vsource ) < AST__C ) {
                     zsource = sqrt( (AST__C + vsource)/
                                     (AST__C - vsource) ) - 1.0;
                     SetItem( &(store->zsource), 0, 0, s, zsource, status );
                     cval = GetFitsSor( astGetC( specfrm, "SourceVRF" ), status );
                     if( cval ) SetItemC( &(store->ssyssrc), 0, 0, s, cval, status );
                  }
               } else {
                  vsource = AST__BAD;
               }

/* Store the VELOSYS value (not strictly needed since it can be
   determined from the other values, but FITS-WCS paper III says it can be
   useful). We temporarily change the source velocity to be zero m/s
   in the main rest frame (StdOfRest) (unless the main rest frame is
   already the source rest frame). We then change the source rest
   frame to topocentric and get the source velocity (i.e. the velocity of
   the main rest Frame) in the topocentric system. We then re-instate the
   original attribute values if they were set. */
               if( astGetStdOfRest( specfrm ) != AST__SCSOR ) {
                  sourcevrf = astGetSourceVRF( specfrm );
                  astSetSourceVRF( specfrm, astGetStdOfRest( specfrm ) );
                  astSetSourceVel( specfrm, 0.0 );
               } else {
                  vsource = AST__BAD;
                  sourcevrf = AST__NOSOR;
               }
               astSetSourceVRF( specfrm, AST__TPSOR );
               SetItem( &(store->velosys), 0, 0, s,
                        astGetSourceVel( specfrm ), status );
               if( vsource != AST__BAD ){
                  astSetSourceVRF( specfrm, sourcevrf );
                  astSetSourceVel( specfrm, vsource );
               }

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

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

/* Release resources. */
   lbnd_p = astFree( lbnd_p );
   ubnd_p = astFree( ubnd_p );
   wcsfrm = astAnnul( wcsfrm );

/* 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, "", status );
   }

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

static AstFitsChan *SpecTrans( AstFitsChan *this, int encoding,
                               const char *method, const char *class, int *status ){
/*
*  Name:
*     SpecTrans

*  Purpose:
*     Translated non-standard WCS FITS headers into equivalent standard
*     ones.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     AstFitsChan *SpecTrans( AstFitsChan *this, int encoding,
*                             const char *method, const char *class, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function checks the supplied FitsChan for selected
*     non-standard WCS keywords and, if found, stores equivalent
*     standard keywords in a newly created FitsChan which is returned as
*     the function value. All the original keywords are marked
*     as having been used, so that they are not written out when the
*     FitsChan is deleted.
*

*     At the moment, the non-standard keywords checked for are:
*
*     1) RADECSYS is renamed as RADESYS
*
*     2) LONGPOLE is renamed as LONPOLE
*
*     3) CDjjjiii and CDj_i are converted to PCi_j (with unit CDELT)
*
*     4) CROTAj are converted to PCi_j
*
*     5) PROJPi are converted to PV<axlat>_i
*
*     6) CmVALi are converted to CRVALis (s=A,B,,, for m=1,2...). This
*        is also done for CmPIXi, CmYPEi, and CmNITi. CmELTi is converted
*        to a CDj_is array.
*
*     7) EQUINOX keywords with string values equal to a date preceded
*        by the letter B or J (eg "B1995.0"). These are converted to the
*        corresponding Julian floating point value without any epoch
*        specifier.
*
*     8) EPOCH values are converted into Julian EQUINOX values (but only
*        if the FitsChan does not already contain an EQUINOX value).
*
*     9) DATE-OBS values are converted into MJD-OBS values (but only
*        if the FitsChan does not already contain an MJD-OBS value).
*
*     10) EQUINOX or EPOCH keywords with value zero  are converted to
*         B1950.
*
*     11) The AIPS NCP and GLS projections are converted into equivalent SIN
*         or SFL projections.
*
*     12) The IRAF "ZPX" projection. If the last 4 chacaters of CTYPEi

*       (i = 1, naxis) are "-ZPX", then:
*	- "ZPX" is replaced by "ZPN" within the CTYPEi value
*	- A distortion code of "-ZPX" is appended to the end of the CTYPEi
*       value (this is used later by the DistortMaps function).
*       - If the FitsChan contains no PROJP keywords, then projection
*       parameter valus are read from any WATi_nnn keywords, and
*       corresponding PV keywords are added to the FitsChan.
*
*     13) The IRAF "TNX" projection. If the last 4 chacaters of CTYPEi

*       (i = 1, naxis) are "-TNX", then:
*	- "TNX" is replaced by "TAN" within the CTYPEi value (the distorted
*       TAN projection included in a pre-final version of FITS-WCS is still
*       supported by AST using the WcsMap AST__TPN projection).
*       - If the FitsChan contains no PROJP keywords, then projection
*       parameter valus are read from any WATi_nnn keywords, and
*       corresponding PV keywords are added to the FitsChan.
*       - If the TNX projection cannot be converted exactly into a TAN
*       projection, ASTWARN keywords are added to the FitsChan
*       containing a warning message. The calling application can (if it
*       wants to) check for this keyword, and report its contents to the
*       user.
*
*     14) Keywords relating to the IRAF "mini-WCS" system are removed.
*       This is the IRAF equivalent of the AST native encoding. Mini-WCS
*       keywords are removed in order to avoid confusion arising between
*       potentially inconsistent encodings.
*
*     15) "QV" parameters for TAN projections (as produced by AUTOASTROM)
*       or "-TAB" (as produced by FitsChan) are renamed to "PV".
*
*     16) RESTFREQ is converted to RESTFRQ.
*
*     17) the "-WAV", "-FRQ" and "-VEL" CTYPE algorithms included in an
*       early draft of FITS-WCS paper III are translated to the
*       corresponding modern "-X2P" form.
*
*     18) AIPS spectral CTYPE values are translated to FITS-WCS paper III
*     equivalents.
*
*     19) AIPS spectral keywords OBSRA and OBSDEC are used to create a
*     pair of celestial axes with reference point at the specified
*     (OBSRA,OBSDEC) position. This is only done if the header does not
*     already contain a pair of celestial axes.
*
*     20) Common case insensitive CUNIT values: "Hz", "Angstrom", "km/s",
*     "M/S"
*
*     21) Various translations specific to the FITS-CLASS encoding.
*
*     22) SAO distorted TAN projections (uses COi_j keywords to store
*     polynomial coefficients) are converted to TPN projections.

*     23) CTYPE == "LAMBDA" changed to CTYPE = "WAVE"
*
*     24) if the projection is TAN and the PolyTan attribute is non-zero,
*     or if the projection is TPV (produced by SCAMP), the projection is
*     changed to TPN (the AST code for the draft FITS-WCS paper II
*     conventions for a distorted TAN projection).

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     encoding
*        The FitsChan encoding in use.
*     method
*        Pointer to string holding name of calling method.
*     class
*        Pointer to a string holding the name of the supplied object class.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to the new FitsChan containing the keywords which
*     constitute the standard equivalents to any non-standard keywords in
*     the supplied FitsChan. A NULL pointer is returned if there are no
*     non-standard keywords in the supplied FitsChan.
*/

/* Local Variables: */
   AstFitsChan *ret;              /* The returned FitsChan */
   char *assys;                   /* AIPS standad of rest type */
   char *astype;                  /* AIPS spectral type */
   char *comm;                    /* Pointer to comment string */
   char *cval;                    /* Pointer to character string */
   char *start;                   /* Pointer to start of projp term */
   char *watmem;                  /* Pointer to total WAT string */
   char bj;                       /* Besselian/Julian indicator */
   char format[ 50 ];             /* scanf format string */
   char keyname[ FITSNAMLEN + 20 ];/* General keyword name + formats */
   char lattype[MXCTYPELEN];      /* CTYPE value for latitude axis */
   char lontype[MXCTYPELEN];      /* CTYPE value for longitude axis */
   char prj[6];                   /* Spatial projection string */
   char s;                        /* Co-ordinate version character */
   char spectype[MXCTYPELEN + 20];/* CTYPE value for spectral axis */
   char sprj[10];                  /* Spectral projection string */
   char ss;                       /* Co-ordinate version character */
   char template[ FITSNAMLEN + 1 ];/* General keyword name template */
   double *cvals;                 /* PVi_m values for TPN projection */
   double cdelti;                 /* CDELT for longitude axis */
   double cdeltj;                 /* CDELT for latitude axis */
   double cosrota;                /* Cos( CROTA ) */
   double crota;                  /* CROTA Value */
   double dval;                   /* General floating value */
   double lambda;                 /* Ratio of CDELTs */
   double projp;                  /* Projection parameter value */
   double rowsum2;                /* Sum of squared CDi_j row elements */
   double sinrota;                /* Sin( CROTA ) */
   double sinval;                 /* Sin( dec ref ) */
   int *mvals;                    /* "m" index of each PVi_m value */
   int axlat;                     /* Index of latitude axis */
   int axlon;                     /* Index of longitude axis */
   int diag;                      /* Sign of diagonal CDi_j element */
   int dim;                       /* Length of pixel axis */
   int gotpcij;                   /* Does FitsChan contain any PCi_j keywords? */
   int i,j;                       /* Indices */
   int iaxis;                     /* Axis index */
   int icoeff;                    /* Index of next PVi_m value */
   int iproj;                     /* Projection parameter index */
   int jhi;                       /* Highest axis index */
   int jlo;                       /* Lowest axis index */
   int lbnd[ 2 ];                 /* Lower index bounds */
   int m;                         /* Co-ordinate version index */
   int naxis;                     /* Number of axes */
   int nc;                        /* Length of string */
   int ncoeff;                    /* Number of PVi_m values */
   int ok;                        /* Can projection be represented in FITS-WCS?*/
   int shifted;                   /* Non-zero if there is an origin shift */
   int tlbnd[ 2 ];                /* Lower index bounds */
   int tubnd[ 2 ];                /* Upper index bounds */
   int ubnd[ 2 ];                 /* Upper index bounds */
   int use_projp;                 /* Use PROJP keywors in favour of PV keywords? */
   size_t size;                   /* Length of string value */

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

/* Initialise to avoid compiler warnings. */
   size = 0;
   prj[ 0 ] = 0;

/* Create the returned FitsChan. */
   ret = astFitsChan( NULL, NULL, "", status );

/* Loop round all axis descriptions, starting with primary (' '). */
   for( s = 'A' - 1; s <= 'Z' && astOK; s++ ){
      if( s == 'A' - 1 ) s = ' ';

/* Indicate that no celestial axes have yet been found for the current
   axis description. */
      axlon = -1;
      axlat = -1;
      prj[ 0 ] = 0;

/* Find the highest axis index in a CTYPE keyword. */
      if( s != ' ' ) {
         sprintf( template, "CTYPE%%d%c", s );
      } else {
         strcpy( template, "CTYPE%d" );
      }
      if( astKeyFields( this, template, 1, &naxis, lbnd ) ) {

/* Find the longitude and latitude axes by examining the CTYPE values.
   They are marked as read. Such markings are only provisional, and they
   can be read again any number of times until the current astRead
   operation is completed. Also note the projection type. */
         j = 0;
         while( j < naxis && astOK ){
            if( GetValue2( ret, this, FormatKey( "CTYPE", j + 1, -1, s, status ),
                          AST__STRING, (void *) &cval, 0, method,
                          class, status ) ){
               nc = strlen( cval );
               if( !strncmp( cval, "RA--", 4 ) ||
                   !strncmp( cval, "AZ--", 4 ) ||
                   ( nc > 1 && !strncmp( cval + 1, "LON", 3 ) ) ||
                   ( nc > 2 && !strncmp( cval + 2, "LN", 2 ) ) ) {
                  axlon = j;
                  strncpy( prj, cval + 4, 4 );
                  strncpy( lontype, cval, 10 );
                  prj[ 4 ] = 0;
               } else if( !strncmp( cval, "DEC-", 4 ) ||
                   !strncmp( cval, "EL--", 4 ) ||
                   ( nc > 1 && !strncmp( cval + 1, "LAT", 3 ) ) ||
                   ( nc > 2 && !strncmp( cval + 2, "LT", 2 ) ) ) {
                  axlat = j;
                  strncpy( prj, cval + 4, 4 );
                  strncpy( lattype, cval, 10 );
                  prj[ 4 ] = 0;

/* Check for spectral algorithms from early drafts of paper III */
               } else {
                  sprj[ 0 ] = '-';
                  if( ( nc > 4 && !strncmp( cval + 4, "-WAV", 4 ) ) ) {
                     sprj[ 1 ] = 'W';
                  } else if( ( nc > 4 && !strncmp( cval + 4, "-FRQ", 4 ) ) ) {
                     sprj[ 1 ] = 'F';
                  } else if( ( nc > 4 && !strncmp( cval + 4, "-VEL", 4 ) ) ) {
                     sprj[ 1 ] = 'V';
                  } else {
                     sprj[ 0 ] = 0;
                  }
                  if( *sprj ) {
                     sprj[ 2 ] = '2';
                     if( !strncmp( cval, "WAVE", 4 ) ) {
                        sprj[ 3 ] = 'W';
                     } else if( !strncmp( cval, "FREQ", 4 ) ) {
                        sprj[ 3 ] = 'F';
                     } else if( !strncmp( cval, "VELO", 4 ) ) {
                        sprj[ 3 ] = 'V';
                     } else if( !strncmp( cval, "VRAD", 4 ) ) {
                        sprj[ 3 ] = 'F';
                     } else if( !strncmp( cval, "VOPT", 4 ) ) {
                        sprj[ 3 ] = 'W';
                     } else if( !strncmp( cval, "ZOPT", 4 ) ) {
                        sprj[ 3 ] = 'W';
                     } else if( !strncmp( cval, "ENER", 4 ) ) {
                        sprj[ 3 ] = 'F';
                     } else if( !strncmp( cval, "WAVN", 4 ) ) {
                        sprj[ 3 ] = 'F';
                     } else if( !strncmp( cval, "BETA", 4 ) ) {
                        sprj[ 3 ] = 'V';
                     } else {
                        sprj[ 0 ] = 0;
                     }
                  }
                  if( *sprj ) {
                     strcpy( spectype, cval );
                     if( sprj[ 1 ] == sprj[ 3 ] ) {
                        strcpy( sprj, strlen( cval ) > 8 ? "----" : "    " );
                     } else {
                        sprj[ 4 ] = 0;
                     }
                     strncpy( spectype + 4, sprj, 4 );
                     cval = spectype;
                     SetValue( ret, FormatKey( "CTYPE", j + 1, -1, s, status ),
                              (void *) &cval, AST__STRING, NULL, status );
                  }
               }
            }
            j++;
         }
      }

/* RADECSYS keywords
   ----------------- */
      if( s == ' ' ) {
         if( GetValue2( ret, this, "RADECSYS", AST__STRING, (void *) &cval, 0, method,
                       class, status ) ){
            if( encoding == FITSPC_ENCODING || encoding == FITSIRAF_ENCODING ){
               SetValue( ret, "RADESYS", (void *) &cval, AST__STRING,
                         CardComm( this, status ), status );
            }
         }

/* LONGPOLE keywords
   ----------------- */
         if( GetValue2( ret, this, "LONGPOLE", AST__FLOAT, (void *) &dval, 0, method,
                       class, status ) ){
            if( encoding == FITSPC_ENCODING || encoding == FITSIRAF_ENCODING ){
               SetValue( ret, "LONPOLE", (void *) &dval, AST__FLOAT,
                         CardComm( this, status ), status );
            }
         }
      }

/* Zero CDELT values.
   ----------------- */

/* Find the highest axis index in a CDELT keyword. Pass on if there are
   none. */
      if( s != ' ' ) {
         sprintf( template, "CDELT%%d%c", s );
      } else {
         strcpy( template, "CDELT%d" );
      }
      if( astKeyFields( this, template, 1, &naxis, lbnd ) ){

/* Do each row in the matrix. */
         for( j = 0; j < naxis; j++ ){

/* Get the CDELT value for this row. */
            GetValue2( ret, this, FormatKey( "CDELT", j + 1, -1, s, status ), AST__FLOAT,
                       (void *) &cdeltj, 0, method, class, status );

/* If CDELT is zero, use 1.0E-6 of the corresponding CRVAL value
   instead, or 1.0 if CRVAL is zero. Otherwise, the zeros could cause the
   matrix to be non-invertable. The Mapping could then not be simplified
   or used by a Plot. CDELT values of zero are usually used to indicate
   "redundant" axes. For instance, a 2D image may be stored as a 3D cube
   with a single plane with the "redundant" 3rd axis used to specify the
   wavelength of the filter. The actual value used for CDELT shouldn't
   matter since the axis only spans a single pixel anyway. */
            if( cdeltj == 0.0 ){
               GetValue2( ret, this, FormatKey( "CDELT", j + 1, -1, s, status ), AST__FLOAT,
                         (void *) &dval, 1, method, class, status );
               cdeltj = 1.0E-6*dval;
               if( cdeltj == 0.0 ) cdeltj = 1.0;
               SetValue( ret, FormatKey( "CDELT", j + 1, -1, s, status ), (void *) &cdeltj,
                         AST__FLOAT, NULL, status );
            }
         }
      }

/* Following conversions produce PCi_j keywords. Only do them if there
   are currently no PCi_j keywords in the header. */
      if( s != ' ' ) {
         sprintf( template, "PC%%d_%%d%c", s );
      } else {
         strcpy( template, "PC%d_%d" );
      }
      gotpcij = astKeyFields( this, template, 1, &naxis, lbnd );
      if( !gotpcij ){

/* CDjjjiii
   -------- */
         if( s == ' ' && astKeyFields( this, "CD%3d%3d", 1, &naxis, lbnd ) ){

/* Do each row in the matrix. */
            for( j = 0; j < naxis; j++ ){

/* Do each column in the matrix. */
               for( i = 0; i < naxis; i++ ){

/* Get the CDjjjiii matrix element */
                  sprintf( keyname, "CD%.3d%.3d", j + 1, i + 1 );
                  if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0,
                                method, class, status ) ){

/* If found, save it with name PCj_i, and ensure the default value of 1.0
   is used for CDELT. */
                     if( encoding == FITSIRAF_ENCODING ){
                        SetValue( ret, FormatKey( "PC", j + 1, i + 1, ' ', status ),
                                  (void *) &dval, AST__FLOAT, NULL, status );
                        dval = 1.0;
                        SetValue( ret, FormatKey( "CDELT", j + 1, -1, s, status ),
                                  (void *) &dval, AST__FLOAT, NULL, status );
                        gotpcij = 1;
                     }
                  }
               }
            }
         }

/* CDj_i
   ---- */
         if( s != ' ' ) {
            sprintf( template, "CD%%d_%%d%c", s );
         } else {
            strcpy( template, "CD%d_%d" );
         }
         if( !gotpcij && astKeyFields( this, template, 1, &naxis, lbnd ) ){

/* Do each row in the matrix. */
            for( j = 0; j < naxis; j++ ){

/* First find the sum of the squared elements in the row. and note the
   sign of the diagonal element. */
               rowsum2 = 0.0;
               diag = +1;
               for( i = 0; i < naxis; i++ ){
                  if( GetValue2( ret, this, FormatKey( "CD", j + 1, i + 1, s, status ),
                                  AST__FLOAT, (void *) &dval, 0, method, class, status ) ){
                     rowsum2 += dval*dval;
                     if( i == j ) diag = ( dval >= 0.0 ) ? +1 : -1;
                  }
               }

/* The CDELT value for this row will be the length of the row vector. This means that
   each row will be a unit vector when converted to PCi_j form, and the CDELT will
   give a real indication of the pixel size. Ensure that the diagonal
   PCi+j element has a positive sign. */
               cdelti = sqrt( rowsum2 )*diag;
               SetValue( ret, FormatKey( "CDELT", j + 1, -1, s, status ),
                         (void *) &cdelti, AST__FLOAT, NULL, status );

/* Do each column in the matrix. */
               for( i = 0; i < naxis; i++ ){

/* Get the CDj_i matrix element (note default value for all CD elements
   is zero (even diagonal elements!). */
                  if( !GetValue2( ret, this, FormatKey( "CD", j + 1, i + 1, s, status ),
                                  AST__FLOAT, (void *) &dval, 0, method, class, status ) ){
                     dval = 0.0;
                  }

/* Divide by the rows cdelt value and save it with name PCj_i. */
                  if( cdelti != 0.0 ) dval /= cdelti;
                  SetValue( ret, FormatKey( "PC", j + 1, i + 1, s, status ),
                            (void *) &dval, AST__FLOAT, NULL, status );
                  gotpcij = 1;
               }
            }
         }

/* PCjjjiii and CROTAi keywords
   ---------------------------- */

/* Check there are some CDELT keywords... */
         if( s != ' ' ) {
            sprintf( template, "CDELT%%d%c", s );
         } else {
            strcpy( template, "CDELT%d" );
         }
         if( !gotpcij && astKeyFields( this, template, 1, &naxis, lbnd ) ){

/* See if there is a CROTA keyword. Try to read values for both axes
   since they are sometimes both included. This ensures they will not be
   included in the output when the FitsChan is deleted. Read the latitude
   axis second in order to give it priority in cases where both are
   present. */
            crota = AST__BAD;
            GetValue2( ret, this, FormatKey( "CROTA", axlon + 1, -1, s, status ),
                       AST__FLOAT, (void *) &crota, 0, method, class, status );
            GetValue2( ret, this, FormatKey( "CROTA", axlat + 1, -1, s, status ),
                       AST__FLOAT, (void *) &crota, 0, method, class, status );

/* If there are any PCjjjiii keywords, rename them as PCj_i. */
            if( s == ' ' && astKeyFields( this, "PC%3d%3d", 0, NULL, NULL ) ){

/* Do each row in the matrix. */
               for( j = 0; j < naxis; j++ ){

/* Do each column in the matrix. */
                  for( i = 0; i < naxis; i++ ){

/* Get the PCiiijjj matrix element */
                     sprintf( keyname, "PC%.3d%.3d", j + 1, i + 1 );
                     if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0,
                                   method, class, status ) ){
                     } else if( i == j ) {
                        dval = 1.0;
                     } else {
                        dval = 0.0;
                     }

/* Store it as PCi_j */
                     SetValue( ret, FormatKey( "PC", j + 1, i + 1, ' ', status ),
                               (void *) &dval, AST__FLOAT, NULL, status );
                     gotpcij = 1;
                  }
               }

/* If there is a CROTA value and no PCjjjii keywords, create a PCj_i
   matrix from the CROTA values. We need to have latitude and longitude
   axes for this.  */
            } else if( s == ' ' && axlat != -1 && axlon != -1 && crota != AST__BAD ){

/* Get the sin and cos of CROTA */
               cosrota = cos( crota*AST__DD2R );
               sinrota = sin( crota*AST__DD2R );

/* Get the CDELT values for the longitude and latitude axes. */
               if( GetValue2( ret, this, FormatKey( "CDELT", axlat + 1, -1, ' ', status ),
                             AST__FLOAT, (void *) &cdeltj, 1, method,
                             class, status ) &&
                   GetValue2( ret, this, FormatKey( "CDELT", axlon + 1, -1, ' ', status ),
                             AST__FLOAT, (void *) &cdelti, 1, method,
                             class, status ) ){

/* Save the ratio, needed below. */
                  lambda = cdeltj/cdelti;

/* Save a corresponding set of PCi_j keywords in the FitsChan. First do
   the diagonal terms. */
                  for( i = 0; i < naxis; i++ ){
                     if( i == axlat ) {
                        dval = cosrota;
                     } else if( i == axlon ) {
                        dval = cosrota;
                     } else {
                        dval = 1.0;
                     }
                     SetValue( ret, FormatKey( "PC", i + 1, i + 1, ' ', status ),
                               (void *) &dval, AST__FLOAT, NULL, status );
                     gotpcij = 1;
                  }

/* Now do the non-zero off-diagonal terms. */
                  dval = sinrota/lambda;
                  SetValue( ret, FormatKey( "PC", axlat + 1, axlon + 1, ' ', status ),
                            (void *) &dval, AST__FLOAT, NULL, status );
                  dval = -sinrota*lambda;
                  SetValue( ret, FormatKey( "PC", axlon + 1, axlat + 1, ' ', status ),
                            (void *) &dval, AST__FLOAT, NULL, status );
               }
            }
         }
      }

/* Conversion of old PROJP, etc, is done once on the "primary" pass. */
      if( s == ' ' ) {

/* PROJP keywords
   -------------- */
         if( astKeyFields( this, "PROJP%d", 1, ubnd, lbnd ) && axlat != -1 ) {

/* Some people produce headers with both PROJP and PV. Even worse, the
   PROJP and PV values are sometimes inconsistent. In this case we trust
   the PV values rather than the PROJP values, but only if the PV values
   are not obviously incorrect for some reason. In particularly, we check
   that, *if* either PVi_1 or PVi_2 (where i=longitude axis) is present,
   then PVi_0 is also present. Conversely we check that if PVi_0 is
   present then at least one of PVi_1 or PVi_2 is present. */
            use_projp = 1;
            if( axlat != -1 &&
                astKeyFields( this, "PV%d_%d", 2, tubnd, tlbnd ) ){
               use_projp = 0;

/* Are there any PV values for the longitude axis? */
               if( tlbnd[ 0 ] <= axlon + 1 && axlon + 1 <= tubnd[ 0 ] ) {

/* Are either PVi_1 or PVi_2 available? */
                  if( HasCard( this, FormatKey( "PV", axlon + 1, 1, ' ', status ),
                               method, class, status ) ||
                      HasCard( this, FormatKey( "PV", axlon + 1, 2, ' ', status ),
                               method, class, status ) ) {

/* If so use PROJP if PVi_0 is not also available. */
                     if( !HasCard( this, FormatKey( "PV", axlon + 1, 0, ' ', status ),
                                   method, class, status ) ) use_projp = 1;

/* If neither PVi_1 or PVi_2 are available, use PROJP if PVi_0 is
   available. */
                  } else if( HasCard( this, FormatKey( "PV", axlon + 1, 0, ' ', status ),
                                      method, class, status ) ) {
                     use_projp = 1;
                  }
               }
            }

/* Translate PROJP to PV if required. */
            if( use_projp ) {
               for( i = lbnd[ 0 ]; i <= ubnd[ 0 ]; i++ ){
                  if( GetValue2( ret, this, FormatKey( "PROJP", i, -1, ' ', status ),
                                AST__FLOAT, (void *) &dval, 0, method, class, status ) &&
                      ( encoding == FITSPC_ENCODING ||
                        encoding == FITSIRAF_ENCODING ) ){
                     SetValue( ret, FormatKey( "PV", axlat + 1, i, ' ', status ),
                               (void *) &dval, AST__FLOAT, CardComm( this, status ), status );
                  }
               }
            }
         }

/* CmVALi keywords
   --------------- */
         if( astKeyFields( this, "C%1dVAL%d", 2, ubnd, lbnd ) ){
            ss = 'A';
            for( m = lbnd[ 0 ]; m <= ubnd[ 0 ]; m++ ){
               for( i = lbnd[ 1 ]; i <= ubnd[ 1 ]; i++ ){
                  sprintf( keyname, "C%dVAL%d", m, i );
                  if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0,
                                method, class, status ) &&
                      ( encoding == FITSPC_ENCODING ||
                        encoding == FITSIRAF_ENCODING ) ){
                     sprintf( keyname, "CRVAL%d%c", i, ss );
                     SetValue( ret, keyname, (void *) &dval, AST__FLOAT,
                               CardComm( this, status ), status );
                  }
               }
               ss++;
            }
         }

/* CmPIXi keywords
   --------------- */
         if( astKeyFields( this, "C%1dPIX%d", 2, ubnd, lbnd ) ){
            ss = 'A';
            for( m = lbnd[ 0 ]; m <= ubnd[ 0 ]; m++ ){
               for( i = lbnd[ 1 ]; i <= ubnd[ 1 ]; i++ ){
                  sprintf( keyname, "C%dPIX%d", m, i );
                  if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0,
                                method, class, status ) &&
                      ( encoding == FITSPC_ENCODING ||
                        encoding == FITSIRAF_ENCODING ) ){
                     sprintf( keyname, "CRPIX%d%c", i, ss );
                     SetValue( ret, keyname, (void *) &dval, AST__FLOAT,
                               CardComm( this, status ), status );
                  }
               }
               ss++;
            }
         }

/* CmYPEi keywords
   --------------- */
         if( astKeyFields( this, "C%1dYPE%d", 2, ubnd, lbnd ) ){
            ss = 'A';
            for( m = lbnd[ 0 ]; m <= ubnd[ 0 ]; m++ ){
               for( i = lbnd[ 1 ]; i <= ubnd[ 1 ]; i++ ){
                  sprintf( keyname, "C%dYPE%d", m, i );
                  if( GetValue2( ret, this, keyname, AST__STRING, (void *) &cval, 0,
                                method, class, status ) &&
                      ( encoding == FITSPC_ENCODING ||
                        encoding == FITSIRAF_ENCODING ) ){
                     sprintf( keyname, "CTYPE%d%c", i, ss );
                     SetValue( ret, keyname, (void *) &cval, AST__STRING,
                               CardComm( this, status ), status );
                  }
               }
               ss++;
            }
         }

/* CmNITi keywords
   --------------- */
         if( astKeyFields( this, "C%1dNIT%d", 2, ubnd, lbnd ) ){
            ss = 'A';
            for( m = lbnd[ 0 ]; m <= ubnd[ 0 ]; m++ ){
               for( i = lbnd[ 1 ]; i <= ubnd[ 1 ]; i++ ){
                  sprintf( keyname, "C%dNIT%d", m, i );
                  if( GetValue2( ret, this, keyname, AST__STRING, (void *) &cval, 0,
                                method, class, status ) &&
                      ( encoding == FITSPC_ENCODING ||
                        encoding == FITSIRAF_ENCODING ) ){
                     sprintf( keyname, "CUNIT%d%c", i, ss );
                     SetValue( ret, keyname, (void *) &cval, AST__STRING,
                               CardComm( this, status ), status );
                  }
               }
               ss++;
            }
         }

/* CmELTi keywords
   --------------- */
         if( astKeyFields( this, "C%1dELT%d", 2, ubnd, lbnd ) ){
            naxis = ubnd[ 1 ];
            ss = 'A';
            for( m = lbnd[ 0 ]; m <= ubnd[ 0 ]; m++ ){

/* Create a PCj_is matrix by copying the PCjjjiii values and rename CmELTi as
   CDELTis. */

/* Do each row in the matrix. */
               for( j = 0; j < naxis; j++ ){

/* Get the CDELT value for this row. Report an error if not present. */
                  sprintf( keyname, "C%dELT%d", m, j + 1 );
                  GetValue2( ret, this, keyname, AST__FLOAT, (void *) &cdeltj, 1,
                             method, class, status );

/* If CDELT is zero, use one hundredth of the corresponding CRVAL value
   instead, or 1.0 if CRVAL is zero. Otherwise, the zeros could cause the
   matrix to be non-invertable. The Mapping could then not be simplified
   or used by a Plot. CDELT values of zero are usually used to indicate
   "redundant" axes. For instance, a 2D image may be stored as a 3D cube
   with a single plane with the "redundant" 3rd axis used to specify the
   wavelength of the filter. The actual value used for CDELT shouldn't
   matter since the axis only spans a single pixel anyway. */
                  if( cdeltj == 0.0 ){
                     GetValue2( ret, this, FormatKey( "CRVAL", j + 1, -1, ss, status ), AST__FLOAT,
                               (void *) &dval, 1, method, class, status );
                     cdeltj = 0.01*dval;
                     if( cdeltj == 0.0 ) cdeltj = 1.0;
                  }

/* Save it as CDELTis */
                  sprintf( keyname, "CDELT%d%c", j + 1, ss );
                  SetValue( ret, keyname, (void *) &cdeltj, AST__FLOAT,
                            CardComm( this, status ), status );

/* Do each column in the matrix. */
                  for( i = 0; i < naxis; i++ ){

/* Get the PCiiijjj matrix element */
                     sprintf( keyname, "PC%.3d%.3d", j + 1, i + 1 );
                     if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0,
                                   method, class, status ) ){
                     } else if( i == j ) {
                        dval = 1.0;
                     } else {
                        dval = 0.0;
                     }

/* Store it as PCi_js. */
                     SetValue( ret, FormatKey( "PC", j + 1, i + 1, ss, status ),
                               (void *) &dval, AST__FLOAT, NULL, status );
                  }
               }
               ss++;
            }
         }

/* EPOCH keywords
   ------------ */

/* Get any EPOCH card, marking it as read. */
         if( GetValue2( ret, this, "EPOCH", AST__FLOAT, (void *) &dval, 0, method,
                          class, status ) ){

/* Convert values of zero to B1950. */
            if( dval == 0.0 ) dval = 1950.0;

/* Save a new EQUINOX card in the FitsChan, so long as there is not
   already one there. */
            if( !GetValue2( ret, this, "EQUINOX", AST__STRING, (void *) &cval, 0,
                            method, class, status ) ){
               SetValue( ret, "EQUINOX", (void *) &dval, AST__FLOAT,
                         "Reference equinox", status );
            }
         }

/* String EQUINOX values
   ---------------------
   If found, EQUINOX will be used in favour of any EPOCH value found
   above. */
         if( GetValue2( ret, this, "EQUINOX", AST__STRING, (void *) &cval, 0, method,
                        class, status ) ){

/* Note the first character. */
            bj = cval[ 0 ];

/* If it is "B" or "J", read a floating value from the rest */
            if( bj == 'B' || bj == 'J' ) {
               if( 1 == astSscanf( cval + 1, " %lf ", &dval ) ){

/* If it is a Besselian epoch, convert to Julian. */
                  if( bj == 'B' ) dval = palEpj( palEpb2d( dval ) );

/* Replace the original EQUINOX card. */
                  SetValue( ret, "EQUINOX", (void *) &dval, AST__FLOAT,
                            CardComm( this, status ), status );
               }
            }
         }

/* EQUINOX = 0.0 keywords
   ---------------------- */
         if( GetValue2( ret, this, "EQUINOX", AST__FLOAT, (void *) &dval, 0, method,
                       class, status ) ){
            if( dval == 0.0 ){
               dval = 1950.0;
               SetValue( ret, "EQUINOX", (void *) &dval, AST__FLOAT,
                         CardComm( this, status ), status );
            }
         }
      }

/* DATE-OBS keywords
   ---------------- */

/* Read any DATE-OBS card. This prevents it being written out when the
   FitsChan is deleted.  */
      if( s == ' ' ) {
         strcpy( keyname, "DATE-OBS" );
         if( GetValue2( ret, this, keyname, AST__STRING, (void *) &cval, 0, method,
                       class, status ) ){

/* Ignore DATE-OBS values if the header contains an MJD-OBS value */
            strcpy( keyname, "MJD-OBS" );
            if( !GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0,
                           method, class, status ) ){

/* Get the corresponding mjd-obs value, checking that DATE-OBS is valid. */
               dval = DateObs( cval, status );
               if( dval != AST__BAD ){
                  SetValue( ret, keyname, (void *) &dval, AST__FLOAT,
                            "Date of observation", status );
               }
            }
         }
      }

/* Things specific to the CLASS encoding
   ------------------------------------- */
      if( encoding == FITSCLASS_ENCODING ) ClassTrans( this, ret, axlat,
                                                       axlon, method, class, status );

/* Convert SAO distorted TAN headers to TPN distorted TAN headers.
   -------------------------------------------------------------- */
      if( s == ' ' && !Ustrcmp( prj, "-TAN", status ) ){

/* Translate the COi_m keywords into PV i+m keywords. */
         if( SAOTrans( this, ret, method, class, status ) ) {

/* Change the CTYPE projection form TAN to TPV. */
            strcpy( prj, "-TPN" );
            strcpy( lontype + 4, "-TPN" );
            cval = lontype;
            SetValue( ret, FormatKey( "CTYPE", axlon + 1, -1, s, status ),
                         (void *) &cval, AST__STRING, NULL, status );
            strcpy( lattype + 4, "-TPN" );
            cval = lattype;
            SetValue( ret, FormatKey( "CTYPE", axlat + 1, -1, s, status ),
                      (void *) &cval, AST__STRING, NULL, status );
         }
      }

/* AIPS "NCP" projections
   --------------------- */

/* Compare the projection type with "-NCP" */
      if( !Ustrcmp( prj, "-NCP", status ) ) {

/* Get the latitude reference value, and take is cot. */
         GetValue2( ret, this, FormatKey( "CRVAL", axlat + 1, -1, s, status ),
                   AST__FLOAT, (void *) &dval, 1, method, class, status );
         sinval = sin( dval*AST__DD2R );
         if( sinval != 0.0 ) {
            dval = cos( dval*AST__DD2R )/sinval;

/* Replace NCP with SIN in the CTYPE values. */
            strcpy( lontype + 4, "-SIN" );
            cval = lontype;
            SetValue( ret, FormatKey( "CTYPE", axlon + 1, -1, s, status ),
                      (void *) &cval, AST__STRING, NULL, status );
            strcpy( lattype + 4, "-SIN" );
            cval = lattype;
            SetValue( ret, FormatKey( "CTYPE", axlat + 1, -1, s, status ),
                      (void *) &cval, AST__STRING, NULL, status );

/* Store the new projection parameters using names suitable to FITS_WCS
   encoding. */
            SetValue( ret, FormatKey( "PV", axlat + 1, 2, s, status ),
                      (void *) &dval, AST__FLOAT, NULL, status );
            dval = 0.0;
            SetValue( ret, FormatKey( "PV", axlat + 1, 1, s, status ),
                      (void *) &dval, AST__FLOAT, NULL, status );
         }
      }

/* CLASS "ATF" projections
   ---------------------- */

/* Replace ATF with AIT in the CTYPE values. */
      if( !Ustrcmp( prj, "-ATF", status ) ) {
         strcpy( lontype + 4, "-AIT" );
         cval = lontype;
         SetValue( ret, FormatKey( "CTYPE", axlon + 1, -1, s, status ),
                   (void *) &cval, AST__STRING, NULL, status );
         strcpy( lattype + 4, "-AIT" );
         cval = lattype;
         SetValue( ret, FormatKey( "CTYPE", axlat + 1, -1, s, status ),
                   (void *) &cval, AST__STRING, NULL, status );
      }

/* AIPS "GLS" projections
   --------------------- */

/* Compare the projection type with "-GLS" */
      if( !Ustrcmp( prj, "-GLS", status ) ) {

/* Convert to "-SFL" */
         strcpy( lontype + 4, "-SFL" );
         cval = lontype;
         SetValue( ret, FormatKey( "CTYPE", axlon + 1, -1, s, status ),
                   (void *) &cval, AST__STRING, NULL, status );
         strcpy( lattype + 4, "-SFL" );
         cval = lattype;
         SetValue( ret, FormatKey( "CTYPE", axlat + 1, -1, s, status ),
                   (void *) &cval, AST__STRING, NULL, status );

/* FITS-WCS paper 2 (sec. 6.1.4) describes how to handle AIPS GLS
   projections, but requires that the axes are not rotated. Instead, we
   modify the native latitude at the fiducial point, theta_0, as is done
   in wcslib function celfix in file wcsfix.c (see also FITS-WCS paper
   II sec. 2.5). We only need to change theta_0 if the CRVAL position is
   not the celestial origin. */
         shifted = 0;
         sprintf( keyname, "CRVAL%d", axlon + 1 );
         if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0,
                        method, class, status ) ){
            if( dval != 0.0 ) shifted = 1;
         }
         sprintf( keyname, "CRVAL%d", axlat + 1 );
         if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0,
                        method, class, status ) ){
            if( dval != 0.0 ) shifted = 1;
         }

         if( 0 && shifted ) {
            SetValue( ret, FormatKey( "PV", axlon + 1, 2, s, status ),
                      (void *) &dval, AST__FLOAT, NULL, status );
            dval = 0.0;
            SetValue( ret, FormatKey( "PV", axlon + 1, 1, s, status ),
                      (void *) &dval, AST__FLOAT, NULL, status );
            dval = 1.0;
            SetValue( ret, FormatKey( "PV", axlon + 1, 0, s, status ),
                      (void *) &dval, AST__FLOAT, NULL, status );
         }
      }

/* Rename any "QV" projection parameters to "PV" (such as used by
   -TAB to indicate the interpolation method, or by the internal
   -TPN projection to indicate distortion coefficients).
   ------------------------------------------------------------ */

/* Rewind the FitsChan. */
      astClearCard( this );

/* Search the FitsChan for QV cards. */
      if( s != ' ' ) {
         sprintf( template, "QV%%d_%%d%c", s );
      } else {
         strcpy( template, "QV%d_%d" );
      }
      while( FindKeyCard( this, template, method, class, status ) && astOK ) {

/* If the projection name is "TAN", replace TAN with TPN in the CTYPE values. */
         if( !Ustrcmp( prj, "-TAN", status ) ){
            strcpy( prj, "-TPN" );
            strcpy( lontype + 4, "-TPN" );
            cval = lontype;
            SetValue( ret, FormatKey( "CTYPE", axlon + 1, -1, s, status ),
                      (void *) &cval, AST__STRING, NULL, status );
            strcpy( lattype + 4, "-TPN" );
            cval = lattype;
            SetValue( ret, FormatKey( "CTYPE", axlat + 1, -1, s, status ),
                      (void *) &cval, AST__STRING, NULL, status );
         }

/* Indicate that the QV card has been consumed. */
         MarkCard( this, status );

/* Get the keyword name and change it from QV to PV. */
         strcpy( keyname, CardName( this, status ) );
         keyname[ 0 ] ='P';

/* Store the new PV card so long as there it is not already present in the
   FitsChan. */
         if( !GetValue2( ret, this, keyname, AST__FLOAT, (void *) &cval, 0,
                        method, class, status ) ){
            SetValue( ret, keyname, CardData( this, &size, status ),
                      CardType( this, status ), CardComm( this, status ),
                      status );
         }

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



/* Change any TAN projection to TPN projection if the PolyTan attribute
   is non-zero. Also change any TPV projection to TPN projection.
   --------------------------------------------------- */
      if( ( !Ustrcmp( prj, "-TAN", status ) &&
            GetUsedPolyTan( this, ret, axlat + 1, axlon + 1, s, method, class, status ) ) ||
          !Ustrcmp( prj, "-TPV", status ) ){
         strcpy( prj, "-TPN" );
         strcpy( lontype + 4, "-TPN" );
         cval = lontype;
         SetValue( ret, FormatKey( "CTYPE", axlon + 1, -1, s, status ),
                   (void *) &cval, AST__STRING, NULL, status );
         strcpy( lattype + 4, "-TPN" );
         cval = lattype;
         SetValue( ret, FormatKey( "CTYPE", axlat + 1, -1, s, status ),
                   (void *) &cval, AST__STRING, NULL, status );
      }



/* IRAF "ZPX" projections
   --------------------- */
      if( s == ' ' && !Ustrcmp( prj, "-ZPX", status ) ) {

/* Replace "ZPX" with "ZPN-ZPX" (i.e. ZPN projection with ZPX distortion
   code) in the CTYPE values. */
         strcpy( lontype + 4, "-ZPN-ZPX" );
         cval = lontype;
         SetValue( ret, FormatKey( "CTYPE", axlon + 1, -1, ' ', status ),
                   (void *) &cval, AST__STRING, NULL, status );
         strcpy( lattype + 4, "-ZPN-ZPX" );
         cval = lattype;
         SetValue( ret, FormatKey( "CTYPE", axlat + 1, -1, ' ', status ),
                   (void *) &cval, AST__STRING, NULL, status );

/* Check latitude then longitude axes */
         for( i = 0; i < 2; i++ ){
            iaxis = i ? axlat : axlon;

/* Concatenate all the IRAF "WAT" keywords together for this axis. These
   keywords are marked as having been used, so that they are not written
   out when the FitsChan is deleted. */
            watmem = ConcatWAT( this, iaxis, method, class, status );

/* Search the total WAT string for any projp terms. */
            if( watmem ){
               for( iproj = 0; iproj < 10 && astOK; iproj++ ) {
                  sprintf( format, "projp%d=", iproj );
                  start = strstr( watmem, format );
                  if( start ) {
                     sprintf( format, "projp%d=%%lf", iproj );
                     if( astSscanf( start, format, &projp ) ){
                        SetValue( ret, FormatKey( "PV", axlat + 1, iproj, ' ', status ),
                                  (void *) &projp, AST__FLOAT,
                                  "ZPN projection parameter", status );
                     }
                  }
               }

/*  Release the memory used to hold the concatenated WAT keywords. */
               watmem = (char *) astFree( (void *) watmem );
            }
         }

/* IRAF "TNX" projections
   --------------------- */
      } else if( s == ' ' && !Ustrcmp( prj, "-TNX", status ) ) {

/* Replace TNX with TPN in the CTYPE values. */
         strcpy( lontype + 4, "-TPN" );
         cval = lontype;
         SetValue( ret, FormatKey( "CTYPE", axlon + 1, -1, ' ', status ),
                   (void *) &cval, AST__STRING, NULL, status );
         strcpy( lattype + 4, "-TPN" );
         cval = lattype;
         SetValue( ret, FormatKey( "CTYPE", axlat + 1, -1, ' ', status ),
                   (void *) &cval, AST__STRING, NULL, status );

/* Check latitude then longitude axes */
         for( i = 0; i < 2; i++ ){
            iaxis = i ? axlat : axlon;

/* Concatenate all the IRAF "WAT" keywords together for this axis. These
   keywords are marked as having been used, so that they are not written
   out when the FitsChan is deleted. */
            watmem = ConcatWAT( this, iaxis, method, class, status );

/* Extract the polynomial coefficients from the concatenated WAT string.
   These are returned in the form of a list of PVi_m values for a TPN
   projection. */
            ncoeff = WATCoeffs( watmem, i, &cvals, &mvals, &ok, status );

/* If we can handle the TNX projection, store the PV values in the FitsChan. */
            if( ok ) {
               for( icoeff = 0; icoeff < ncoeff; icoeff++ ) {
                  SetValue( ret, FormatKey( "PV", iaxis + 1, mvals[ icoeff ],
                                            ' ', status ),
                           (void *) (cvals + icoeff), AST__FLOAT,
                           "TAN projection parameter", status );
               }

/* If the TNX cannot be represented in FITS-WCS (within our restrictions), add
   warning keywords to the FitsChan. */
            } else {
               Warn( this, "tnx", "This FITS header includes, or was "
                     "derived from, a TNX projection which requires "
                     "unsupported IRAF-specific corrections. The WCS "
                     "information may therefore be incorrect.", method, class, status );
            }

/*  Release the memory used to hold the concatenated WAT keywords. */
            watmem = (char *) astFree( (void *) watmem );
         }
      }

/* MSX CAR projections.
   ------------------- */
      if( s == ' ' && !Ustrcmp( prj, "-CAR", status ) && !astGetCarLin( this ) ) {

/* The CAR projection has valid projection plane points only for native
   longitudes in the range [-180,+180]. The reference pixel (CRPIX) is at
   native longitude zero.  We need to check that the reference point is not
   so far outside the image that the entire image lies outside the range
   [-180,+180]. If it is, we modify the CRPIX value by the number of
   pixels corresponding to 360 degres of longitude in order to bring the
   array into the valid domain ([-180,+180]) of the projection. */
         if( GetValue2( ret, this, FormatKey( "CDELT", axlon + 1, -1, s, status ),
                        AST__FLOAT, (void *) &cdelti, 1, method, class, status ) &&
             GetValue2( ret, this, FormatKey( "CRPIX", axlon + 1, -1, s, status ),
                        AST__FLOAT, (void *) &dval, 0, method, class, status ) ) {

/* We check if the mid point of the array is in the valiud longitude range. Use the
   bottom left corner as a fallback if the image size is unknown. */
            if( !GetValue( this, FormatKey( "NAXIS", axlon + 1, -1, ' ', status ),
                           AST__INT, &dim, 0, 0, method, class, status ) ) {
               dim = 0;
            }

            if( cdelti != 0.0 ) {
               double offset = 0.5*( dim + 1 );
               dval = offset + AST__DR2D*palDrange( AST__DD2R*( dval - offset )*cdelti )/cdelti;
               SetValue( ret, FormatKey( "CRPIX", axlon + 1, -1, s, status ),
                         (void *) &dval, AST__FLOAT, CardComm( this, status ), status );
            }
         }
      }

/* Replace RESTFREQ by RESTFRQ.
   ---------------------------- */

/* Get any RESTFREQ card, marking it as read. */
      if( s != ' ' ) {
         sprintf( keyname, "RESTFREQ%c", s );
      } else {
         strcpy( keyname, "RESTFREQ" );
      }
      if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0, method,
                     class, status ) ){

/* Look for "MHz" and "GHz" within the comment. If found scale the value
   into Hz. */
         comm = CardComm( this, status );
         if( comm ) {
            if( strstr( comm, "GHz" ) ) {
               dval *= 1.0E9;
               comm = "[Hz] Rest Frequency";
            } else if( strstr( comm, "MHz" ) ) {
               dval *= 1.0E6;
               comm = "[Hz] Rest Frequency";
            }
         }

/* Save a new RESTFRQ card in the FitsChan, so long as there is not
   already one there. */
         if( s != ' ' ) {
            sprintf( keyname, "RESTFRQ%c", s );
         } else {
            strcpy( keyname, "RESTFRQ" );
         }
         if( !GetValue2( ret, this, keyname, AST__STRING, (void *) &cval, 0,
                         method, class, status ) ){
            SetValue( ret, keyname, (void *) &dval, AST__FLOAT, comm, status );
         }
      }

/* 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". Also convert "LAMBDA" to "WAVE". */

      if( s != ' ' ) {
         sprintf( template, "CTYPE%%d%c", s );
      } else {
         strcpy( template, "CTYPE%d" );
      }
      if( astKeyFields( this, template, 1, &naxis, lbnd ) ){
         for( j = 0; j < naxis; j++ ) {
            if( GetValue2( ret, this, FormatKey( "CTYPE", j + 1, -1, s, status ),
                          AST__STRING, (void *) &cval, 0, method,
                          class, status ) ){
               if( IsAIPSSpectral( cval, &astype, &assys, status ) ) {
                  SetValue( ret, FormatKey( "CTYPE", j + 1, -1, s, status ),
                            (void *) &astype, AST__STRING, NULL, status );
                  SetValue( ret, "SPECSYS", (void *) &assys, AST__STRING, NULL, status );
                  break;
               } else if( !strcmp( cval, "LAMBDA  " ) ) {
                  cval = "WAVE";
                  SetValue( ret, FormatKey( "CTYPE", j + 1, -1, s, status ),
                            (void *) &cval, AST__STRING, NULL, status );
                  break;
               }
            }
         }
      }

/* Common case insensitive CUNIT values: "Hz", "Angstrom", "km/s", "M/S" */
      if( s != ' ' ) {
         sprintf( template, "CUNIT%%d%c", s );
      } else {
         strcpy( template, "CUNIT%d" );
      }
      if( astKeyFields( this, template, 1, &jhi, &jlo ) ){

/* Convert keyword indices from 1-based to 0-base, and loop round them all. */
         jhi--;
         jlo--;
         for( j = jlo; j <= jhi; j++ ){
            char *keynam;
            keynam =  FormatKey( "CUNIT", j + 1, -1, s, status );
            if( GetValue2( ret, this, keynam, AST__STRING, (void *) &cval, 0,
                           method, class, status ) ){
               size_t nc = astChrLen( cval );
               if( nc == 0 ) {
                  cval = NULL;
               } else if( !Ustrcmp( cval, "Hz", status ) ) {
                  cval = "Hz";
               } else if( !Ustrcmp( cval, "Angstrom", status ) ) {
                  cval = "Angstrom";
               } else if( !Ustrcmp( cval, "km/s", status ) ) {
                  cval = "km/s";
               } else if( !Ustrcmp( cval, "m/s", status ) ) {
                  cval = "m/s";
               } else {
                  cval = NULL;
               }
               if( cval ) SetValue( ret, keynam, (void *) &cval, AST__STRING, NULL, status );
            }
         }
      }

/* After doing the primary axis descriptions, prepare to do the "A"
   description. */
      if( s == ' ' ) s = 'A' - 1;
   }

/* IRAF mini-WCS keywords
   ---------------------- */

/* Rewind the FitsChan to search from the first card. */
   astClearCard( this );

/* Search forward through until all cards have been checked. */
   while( !astFitsEof( this ) && astOK ){

/* Check to see if the keyword name from the current card matches
   any of the known mini-WCS keywords. If so, mark the card as read. */
      if( Match( CardName( this, status ), "WAT%d_%d", 0, NULL, &m, method, class, status ) ||
          Match( CardName( this, status ), "LTM%d_%d", 0, NULL, &m, method, class, status ) ||
          Match( CardName( this, status ), "LTV%d", 0, NULL, &m, method, class, status ) ||
          Match( CardName( this, status ), "WSV%d_LEN", 0, NULL, &m, method, class, status ) ||
          Match( CardName( this, status ), "WSV%d_%d", 0, NULL, &m, method, class, status ) ){
          MarkCard( this, status );
      }

/* Now move the current card on to the next card. */
      MoveCard( this, 1, method, class, status );
   }

/* Delete the returned FitsChan if it is empty. */
   if( ret && !astGetNcard( ret ) ) ret = (AstFitsChan *) astDelete( ret );

/* Return. */
   return ret;
}

int Split( AstFitsChan *this, const char *card, char **name, char **value,
           char **comment, const char *method, const char *class, int *status ){
/*
*  Name:
*     Split

*  Purpose:
*     Extract the keyword name, value and comment from a FITS header card.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int Split( AstFitsChan *this, const char *card, char **name, char **value,
*                char **comment, const char *method, const char *class, int *status  )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     The name, value and comment (if present) are extracted from the
*     supplied card text and returned.

*  Parameters:
*     this
*        Pointer to the FitsCHan.
*     card
*        Pointer to a string holding the FITS header card.
*     name
*        Pointer to a location at which to return the pointer to a string
*        holding the keyword name.
*     value
*        Pointer to a location at which to return the pointer to a string
*        holding the keyword value.
*     comment
*        Pointer to a location at which to return the pointer to a string
*        holding the keyword 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.
*     status
*        Pointer to the inherited status variable.

*  Returned value:
*     -  An integer identifying the data type of the keyword value. This
*     will be one of the values AST__UNDEF, AST__COMMENT, AST__INT,
*     AST__STRING, AST__CONTINUE, AST__FLOAT, AST__COMPLEXI or AST__COMPLEXF
*     defined in fitschan.h.

*  Notes:
*     -  If the keyword value is a string, then the returned value does not
*     include the delimiting quotes, and pairs of adjacent quotes within the
*     string are replaced by single quotes.
*     -  A maximum of 80 characters are read from the supplied card, so the
*     string does not need to be null terminated unless less than 80
*     characters are to be read.
*     -  The memory holding the three strings "name", "value" and "comment"
*     should be released when no longer needed using astFree.
*     -  NULL pointers and a data type of AST__COMMENT are returned if an
*     error has already occurred, or if this function fails for any reason.
*/

/* Local Variables: */
   char *c;                   /* Pointer to returned comment string */
   char *dd;                  /* Pointer to intermediate character */
   char *slash;               /* Pointer to comment character */
   char *v;                   /* Pointer to returned value string */
   char buf[255];             /* Buffer for warning text */
   const char *d;             /* Pointer to first comment character */
   const char *v0;            /* Pointer to first non-blank value character */
   double fi, fr;             /* Values read from value string */
   int badval;                /* Is the keyword value illegal? */
   int blank_name;            /* Is keyword name blank? */
   int cont;                  /* Is this a continuation card? */
   int i;                     /* Character index */
   int ii, ir;                /* Values read from value string */
   int iopt;                  /* Index of option within list */
   int len;                   /* Used length of value string */
   int lq;                    /* Was previous character an escaping quote? */
   int nch;                   /* No. of characters used */
   int ndig;                  /* No. of digits in the formatted integer */
   int type;                  /* Keyword data type */
   size_t nc;                 /* Number of character in the supplied card */
   size_t ncc;                /* No. of characters in the comment string */
   size_t ncv;                /* No. of characters in the value string */

/* Initialise the returned pointers. */
   *name = NULL;
   *value = NULL;
   *comment = NULL;
   type = AST__COMMENT;

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

/* Assume initially that the keyword value is legal. */
   badval = 0;

/* Store the number of characters to be read from the supplied card. This
   is not allowed to be more than the length of a FITS header card. */
   nc = 0;
   while( nc < AST__FITSCHAN_FITSCARDLEN && card[ nc ] ) nc++;

/* Reduce the number of characters to read so that any non-printing
   characters such as new-lines at the end of the string are ignored. */
   while( nc > 0 && !isprint( card[ nc - 1 ] ) ) nc--;

/* Allocate memory for a copy of the keyword name plus a terminating
   null character. */
   *name = (char *) astMalloc( ( 1 + FITSNAMLEN )*sizeof(char) );

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

/* Initialise the name string by filling it with spaces, and terminating it. */
      for( i = 0; i < FITSNAMLEN; i++ ) (*name)[ i ] = ' ';
      (*name)[ FITSNAMLEN ] = 0;

/* Copy the the keyword name, ensuring that no more than FITSNAMLEN (8)
   characters are copied. */
      strncpy( *name, card, ( nc > FITSNAMLEN ) ? FITSNAMLEN : nc );

/* If there is no keyword name, flag that we have a blank name which will
   be treated as a comment card. */
      if( strspn( *name, " " ) == strlen( *name ) ){
         blank_name = 1;

/* If the card contains a keyword name, replace any white space with
   nulls. */
      } else {
         blank_name = 0;
         dd = *name + strlen( *name ) - 1;
         while( isspace( *dd ) ) *(dd--) = 0;
      }

/* Check the keyword name is legal. */
      CheckFitsName( this, *name, method, class, status );

/* Allocate memory to hold the keyword value and comment strings. */
      *value = (char *) astMalloc( sizeof(char)*( 2 + nc ) );
      *comment = (char *) astMalloc( sizeof(char)*( 1 + nc ) );

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

/* Check for CONTINUE cards. These have keyword CONTINUE but have a space
   instead of an equals sign in column 9. They must also have a single quote
   in column 11. */
         cont = ( !Ustrcmp( *name, "CONTINUE", status ) &&
                  nc > FITSNAMLEN + 3 &&
                  card[ FITSNAMLEN ] == ' ' &&
                  card[ FITSNAMLEN + 2 ] == '\'' );

/* If column 9 does not contain an equals sign (but is not a CONTINUE card), or if
   the keyword is "HISTORY", "COMMENT" or blank, then columns 9 to the end are
   comment characters, and the value string is null. */
         if( ( nc <= FITSNAMLEN || card[ FITSNAMLEN ] != '='
                                || !Ustrcmp( *name, "HISTORY", status )
                                || !Ustrcmp( *name, "COMMENT", status )
                                || blank_name ) && !cont ){
            (*value)[ 0 ] = 0;
            if( nc > FITSNAMLEN ){
               (void) strncpy( *comment, card + FITSNAMLEN,
                               nc - FITSNAMLEN );
               (*comment)[ nc - FITSNAMLEN ] = 0;
            } else {
               (*comment)[ 0 ] = 0;
            }

/* Otherwise there is a value field. */
         } else {

/* Find the first non-blank character in the value string. */
            v0 = card + FITSNAMLEN + 1;
            while( (size_t)(v0 - card) < nc &&
                   isspace( (int) *v0 ) ) v0++;

/* Store pointers to the start of the returned value and comment strings. */
            v = *value;
            c = *comment;

/* If the first character in the value string is a single quote, the value is
   a string. In this case the value ends at the first non-escaped single
   quote. */
            if( *v0 == '\''){
               type = cont ? AST__CONTINUE : AST__STRING;

/* We want to copy the string value, without the delimiting quotes, to the
   returned value string. Single quotes within the string are represented
   by two adjacent quotes, so we also need to check for these and replace
   them by one quote in the returned string. First initialise a pointer
   to the first character after the opening quote, and set a flag
   indicating that (for the purposes of identifying pairs of adjacent
   quotes within the string) the previous character was not a quote. */
               d = v0 + 1;
               lq = 0;

/* Loop round each remaining character in the supplied card. */
               while( (size_t)(d - card) < nc ){

/* If the current character is a single quote... */
                  if( *d == '\'' ){

/* If the previous character was also a single quote then the quote does
   not mark the end of the string, but is a quote to be included literally
   in the value. Copy the quote to the returned string and clear the flag
   to indicate that the pair of adjacent quotes is now complete. */
                    if( lq ){
                       *(v++) = '\'';
                       lq = 0;

/* If the last character was not a quote, then set the flag for the next
   pass through the loop, but do not copy the quote to the returned string
   since it will either be a quote escaping a following adjacent quote, or
   a quote to mark the end of the string. */
                    } else {
                       lq = 1;
                    }

/* If the current character is not a quote... */
                  } else {

/* If the previous character was a quote, then we have found a single
   isolated quote which therefore marks the end of the string value.
   The pointer "d" is left pointing to the first character
   after the terminating quote. */
                     if( lq ){
                        break;

/* If the last character was not a quote, copy it to the returned string. */
                     } else {
                        *(v++) = *d;
                     }
                  }
                  d++;
               }

/* Terminate the returned value string. */
               *v = 0;

/* Now deal with logical and numerical values. */
            } else {

/* The end of the value field is marked by the first "/". Find the number
   of characters in the value field. Pointer "d" is left pointing to the
   first character in the comment (if any). Only use "/" characters which
   occur within the first nc characters, and do not occur wiuthin the
   keyword name (not strictly legal, but a warning will have been issued
   by CheckFitsName in such cases). */
               d = strchr( card + FITSNAMLEN, '/' );
               if( !d || ( d - card ) >= nc ){
                  ncv = nc - FITSNAMLEN - 1;
                  d = NULL;
               } else {
                  ncv = (size_t)( d - card ) - FITSNAMLEN - 1;
               }

/* Copy the value string to the returned string. */
               if( ncv == 0 ){
                  *v = 0;
               } else {
                  strncpy( v, card + FITSNAMLEN + 1, ncv );
                  v[ ncv ] = ' ';
                  v[ ncv + 1 ] = 0;
               }

/* Find the first non-blank character in the value string. */
               v0 = v;
               while( *v0 && isspace( (int) *v0 ) ) v0++;

/* See if the value string is one of the following strings (optionally
   abbreviated and case insensitive): YES, NO, TRUE, FALSE. */
               iopt = FullForm( "YES NO TRUE FALSE", v0, 1, status );

/* Return the single character "T" or "F" at the start of the value string
   if the value matches one of the above strings. */
               if( iopt == 0 || iopt == 2 ) {
                  type = AST__LOGICAL;
                  strcpy ( v, "T" );
               } else if( iopt == 1 || iopt == 3 ) {
                  type = AST__LOGICAL;
                  strcpy ( v, "F" );

/* If it does not match, see if the value is numerical. */
               } else {

/* Save the length of the value string excluding trailing blanks. */
                  len = ChrLen( v, status );

/* If the entire string is blank, the value type is UNDEF. */
                  if( len == 0 ) {
                     type = AST__UNDEF;

/* If there are no dots (decimal points) or exponents (D or E) in the value... */
                  } else if( !strpbrk( v, ".EeDd" ) ){

/* First attempt to read two integers from the string (separated by white
   space). */
                     if( nch = 0,
                         ( 2 == astSscanf( v, " %d %d%n", &ir, &ii, &nch ) ) &&
                         ( nch >= len ) ) {
                        type = AST__COMPLEXI;

/* If that failed, attempt to read a single integer from the string. */
                     } else if( nch = 0,
                         ( 1 == astSscanf( v, " %d%n", &ir, &nch ) ) &&
                         ( nch >= len ) ) {
                        type = AST__INT;
                     }

/* If there are dots (decimal points) in the value... */
                  } else {

/* First attempt to read two doubles from the string (separated by white
   space). */
                     if( nch = 0,
                         ( 2 == astSscanf( v, " %lf %lf%n", &fr, &fi, &nch ) ) &&
                         ( nch >= len ) ) {
                        type = AST__COMPLEXF;

/* If that failed, attempt to read a single double from the string. */
                     } else if( nch = 0,
                         ( 1 == astSscanf( v, " %lf%n", &fr, &nch ) ) &&
                         ( nch >= len ) ) {
                        type = AST__FLOAT;
                     }

/* If both the above failed, it could be because the string contains a
   "D" exponent (which is probably valid FITS) instead of an "E" exponent.
   Replace any "D" in the string with "e" and try again. */
                     if( type == AST__COMMENT && astOK ) {

/* Replace "d" and "D" by "e" (if this doesn't produce a readable floating
   point value then the value string will not be used, so it is safe to
   do the replacement in situ). */
                        for( i = 0; i < len; i++ ) {
                           if( v[ i ] == 'd' || v[ i ] == 'D' ) v[ i ] = 'e';
                        }

/* Attempt to read two doubles from the edited string (separated by white
   space). */
                        if( nch = 0,
                          ( 2 == astSscanf( v, " %lf %lf%n", &fr, &fi, &nch ) ) &&
                          ( nch >= len ) ) {
                           type = AST__COMPLEXF;

/* If that failed, attempt to read a single double from the edited string. */
                        } else if( nch = 0,
                            ( 1 == astSscanf( v, " %lf%n", &fr, &nch ) ) &&
                            ( nch >= len ) ) {
                           type = AST__FLOAT;
                        }
                     }
                  }
               }

/* If the value type could not be determined, indicate that a warning
   should be issued. */
               if( type == AST__COMMENT && astOK ) {
                  badval = 1;
                  (*value)[ 0 ] = 0;
                  (*comment)[ 0 ] = 0;
                  d = NULL;
               }
            }

/* Find the number of characters in the comment. Pointer "d" should point to
   the first character following the value string. */
            if( d ){
               ncc = nc - (size_t)( d - card );
            } else {
               ncc = 0;
            }

/* Copy the remainder of the card to the returned comment string. */
            if( astOK && ncc > 0 ){
               strncpy( c, d, ncc );
               c[ ncc ] = 0;

/* Find the start of the comment (indicated by the first "/" after the
   value string). */
               slash = strchr( c, '/' );

/* Temporarily terminate the string at the slash. */
               if( slash ) *slash = 0;

/* Shuffle the characters following the slash down to the
   start of the returned string. */
               if( slash ){
                  ncc -= (size_t)( slash - c ) + 1;
                  d = slash + 1;
                  for( i = 0; i < 1 + (int) ncc; i++ ) *(c++) = *(d++);
               }

/* If there is no comment string, return a null string. */
            } else {
               *c = 0;
            }
         }
      }
   }

/* Truncate the returned string to avoid wasting space. */
   if( *name ) *name = (char *) astRealloc( (void *) *name, strlen( *name ) + 1 );
   if( *comment ) *comment = (char *) astRealloc( (void *) *comment, strlen( *comment ) + 1 );
   if( *value ) *value = (char *) astRealloc( (void *) *value, strlen( *value ) + 1 );

/* If the value is deemed to be integer, check that the number of digits
   in the formatted value does not exceed the capacity of an int. This may
   be the case if there are too many digits in the integer for an "int" to
   hold. In this case, change the data type to float. */
   if( *value && type == AST__INT ) {
      ndig = 0;
      c = *value;
      while( *c ) {
         if( isdigit( *(c++) ) ) ndig++;
      }
      if( ndig >= int_dig ) type = AST__FLOAT;
   }

/* If an error occurred, free the returned strings and issue a context message. */
   if( !astOK ){
      *name = (char *) astFree( (void *) *name );
      *value = (char *) astFree( (void *) *value );
      *comment = (char *) astFree( (void *) *comment );
      type = AST__COMMENT;
      astError( astStatus, "%s(%s): Unable to store the following FITS "
                "header card:\n%.*s\n", status, method, class,
                AST__FITSCHAN_FITSCARDLEN, card );

/* If a bad keyword value was encountered, issue a warning. Remember that
   "card" may not be null terminated, so ensure that only one header is
   included from "card". */
   } else if( badval ){
      snprintf( buf, sizeof(buf), "The keyword value is illegal in "
                "'%.*s'", AST__FITSCHAN_FITSCARDLEN, card );
      Warn( this, "badkeyvalue", buf, method, class, status );
   }

/* Return the data type. */
   return type;
}

static int SplitMap( AstMapping *map, int invert, int ilon, int ilat,
                     AstMapping **map1, AstWcsMap **map2, AstMapping **map3, int *status ){
/*
*  Name:
*     SplitMap

*  Purpose:
*     Locate a WCS projection within a Mapping.

*  Type:
*     Private function.

*  Synopsis:
*     int SplitMap( AstMapping *map, int invert, int ilon, int ilat,
*                   AstMapping **map1, AstWcsMap **map2, AstMapping **map3, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     If possible, the supplied Mapping is decomposed into three component
*     mappings to be compounded in series. To be acceptable, the second of
*     these three Mappings must be an inverted WcsMap with a non-zero
*     FITSProj attribute value, and there must not be such a WcsMap in
*     either of the other two Mappings. If it is not possible to produce
*     such a group of three Mappings, then a zero function value is returned,
*     together with three NULL Mapping pointers. All the mappings before the
*     WcsMap are compounded together and returned as "map1". The inverse of
*     the WcsMap itself is returned as "map2", and any remaining Mappings
*     are compounded together and returned as "map3".
*
*     The search algorithm allows for an arbitrary combination of series and
*     parallel CmpMaps.

*  Parameters:
*     map
*        A pointer to the Mapping from pixel to physical coordinates.
*     invert
*        The value of the Invert attribute to use with "map" (the value
*        returned by astGetInvert is not used).
*     ilon
*        Index of mapping output which is connected to the longitude axis.
*     ilat
*        Index of mapping output which is connected to the latitude axis.
*     map1
*        A location at which to return a pointer to the Mapping from pixel
*        to intermediate world coordinates.
*     map2
*        A location at which to return a pointer to the Mapping from
*        intermediate world coordinates to native spherical coordinates. This
*        will be an inverted WcsMap with non-zero FITSProj attribute value.
*     map3
*        A location at which to return a pointer to the Mapping from
*        native spherical coordinates to physical coordinates.
*     dep
*        The address of an integer holding the current depth of recursion
*        into this function.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     One if a suitable WcsMap was found, zero otherwise.

*  Notes:
*     -  The returned Mappings contain independant copies of the relevant
*     components of the supplied Mapping and can be modified without
*     changing the supplied Mapping.
*     -  NULL pointers will be returned for all Mappings if no WcsMap
*     can be found in the supplied Mapping.
*     -  A pointer to a UnitMap will be returned for map1 if no mappings
*     exist before the WcsMap.
*     -  A pointer to a UnitMap will be returned for map3 if no mappings
*     exist after the WcsMap.
*     -  NULL pointers will be returned for all Mappings and a function
*     value of zero will be returned if an error has occurred, or if this
*     function should fail for any reason.
*/

/* Local Variables */
   AstFitsChan *fc;        /* Pointer to temporary FitsChan */
   AstFrameSet *tfs;       /* Temporary FrameSet */
   AstMapping *mapa;       /* Pre-wcs Mapping */
   AstMapping *mapc;       /* Post-wcs Mapping */
   AstMapping *tmap1;      /* Temporary Mapping */
   AstMapping *tmap2;      /* Temporary Mapping */
   AstPointSet *pset1;     /* Pixel positions */
   AstPointSet *pset2;     /* WCS positions */
   AstWcsMap  *mapb;       /* WcsMap */
   char card[ AST__FITSCHAN_FITSCARDLEN + 1 ]; /* Buffer for header card */
   double **ptr1;          /* Pointer to pixel axis values */
   double **ptr2;          /* Pointer to WCS axis values */
   double *iwc_origin;     /* Array holding IWC at pixel origin */
   double *pix_origin;     /* Array holding pixel coords at pixel origin */
   double *w1;             /* Pointer to work space */
   int i;                  /* Loop index */
   int npix;               /* Number of pixel axes */
   int nwcs;               /* Number of WCS axes */
   int ret;                /* Was a non-linear Mapping found? */

/* Initialise */
   *map1 = NULL;
   *map2 = NULL;
   *map3 = NULL;
   ret = 0;

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

/* Call SplitMap2 to do the work. SplitMap2 does not check that the
   WcsMap is an *inverted* WcsMap, neither does it check that there
   are no WcsMaps in either map1 or map3. */
   if( SplitMap2( map, invert, map1, map2, map3, status ) ) {

/* Check that the WcsMap is inverted. */
      if( astGetInvert( *map2 ) ) {

/* Check that map 1 does not contain a WcsMap with non-zero FITSProj
   attribute. */
         if( !SplitMap2( *map1, astGetInvert( *map1 ), &mapa, &mapb, &mapc,
                         status ) ) {

/* Check that map 3 does not contain a WcsMap with non-zero FITSProj
   attribute. */
            if( !SplitMap2( *map3, astGetInvert( *map3 ), &mapa, &mapb, &mapc,
                            status ) ) {

/* If so, the three Mappings are OK. */
               ret = 1;
            } else {
               mapa = astAnnul( mapa );
               mapb = astAnnul( mapb );
               mapc = astAnnul( mapc );
            }
         } else {
            mapa = astAnnul( mapa );
            mapb = astAnnul( mapb );
            mapc = astAnnul( mapc );
         }
      }
   }

/* If the above failed to find a suitable WcsMap, we now consider cases
   where the pixel->WCS mapping is linear. We can invent a CAR projection
   WcsMap for such cases. We use a ShiftMap to move the origin of the
   longitude IWC axis to a sensible value (it is left at zero otherwise).
   We cannot do this with the latitude axis since pre-FITS-WCS fits
   readers could not handle the resulting rotation from native to celestial
   coords. */
   if( !ret && astGetIsLinear( map ) ) {
      nwcs = astGetNout( map );
      npix = astGetNin( map );
      iwc_origin = astMalloc( sizeof( double )*nwcs );
      pix_origin = astMalloc( sizeof( double )*npix );
      if( astOK ) {
         for( i = 0; i < npix; i++ ) pix_origin[ i ] = 0.0;
         astTranN( map, 1, npix, 1, pix_origin, 1, nwcs, 1, iwc_origin );
         for( i = 0; i < nwcs; i++ ) {
            if( i != ilon ) {
               iwc_origin[ i ] = 0.0;
            } else {
               iwc_origin[ i ] *= -1;
            }
         }
         mapa = (AstMapping *) astShiftMap( nwcs, iwc_origin, "", status );
         *map1 = (AstMapping *) astCmpMap( map, mapa, 1, "", status );
         *map2 = astWcsMap( nwcs, AST__CAR, ilon + 1, ilat + 1, "Invert=1", status );
         astInvert( mapa );
         *map3 = astClone( mapa );
         mapa = astAnnul( mapa );
         ret = 1;
      }
      iwc_origin = astFree( iwc_origin );
      pix_origin = astFree( pix_origin );
   }

/* If the above failed to find a suitable WcsMap, we now consider cases
   where the output (long,lat) values are constants supplied by a
   final PermMap. We can invent a WcsMap for such cases. */
   if( !ret ) {

/* Transform two arbitrary pixel positions into the WCS Frame. */
      npix = astGetNin( map );
      nwcs = astGetNout( map );
      pset1 = astPointSet( 2, npix, "", status );
      pset2 = astPointSet( 2, nwcs, "", status );
      ptr1 = astGetPoints( pset1 );
      ptr2 = astGetPoints( pset2 );
      w1 = astMalloc( sizeof( double )*(size_t) nwcs );
      if( astOK ) {
         for( i = 0; i < npix; i++ ) {
            ptr1[ i ][ 0 ] = 1.0;
            ptr1[ i ][ 1 ] = 1000.0;
         }
         (void) astTransform( map, pset1, 1, pset2 );

/* If the two wcs positions have equal longitude and latitude values,
   assume that the output longitude and latitude axes are assigned
   constant values by the Mapping. */
         if( ptr2[ ilon ][ 0 ] == ptr2[ ilon ][ 1 ] &&
             ptr2[ ilon ][ 0 ] != AST__BAD &&
             ptr2[ ilat ][ 0 ] == ptr2[ ilat ][ 1 ] &&
             ptr2[ ilat ][ 0 ] != AST__BAD ) {

/* Create a set of Mappings to return, including a WcsMap, which result in
   these constant latitude and longitude values. We do this by creating a
   FITS-WCS header and reading the FrameSet from it. Keywords which are not
   important to the final mappings are given arbitrary values. */
            fc = astFitsChan( NULL, NULL, "", status );
            for( i = 0; i < nwcs; i++ ) {
               sprintf( card, "CRPIX%d  = 0", i + 1 );
               astPutFits( fc, card, 0 );
               sprintf( card, "CDELT%d  = 0.0003", i + 1 );
               astPutFits( fc, card, 0 );
               if( i == ilon ) {
                  sprintf( card, "CTYPE%d  = 'RA---TAN'", i + 1 );
               } else if( i == ilat ) {
                  sprintf( card, "CTYPE%d  = 'DEC--TAN'", i + 1 );
               } else {
                  sprintf( card, "CTYPE%d  = 'DUMMY'", i + 1 );
               }
               astPutFits( fc, card, 0 );
               if( i == ilon ) {
                  sprintf( card, "CRVAL%d  = %.*g", i + 1, AST__DBL_DIG, AST__DR2D*ptr2[ ilon ][ 0 ] );
               } else if( i == ilat ) {
                  sprintf( card, "CRVAL%d  = %.*g", i + 1, AST__DBL_DIG, AST__DR2D*ptr2[ ilat ][ 0 ] );
               } else {
                  sprintf( card, "CRVAL%d  = 0.0", i + 1 );
               }
               astPutFits( fc, card, 0 );
            }
            astClearCard( fc );
            tfs = astRead( fc );
            if( tfs ) {

/* Use SplitMap to get the required Mapings from the FrameSet. */
               tmap2 = astGetMapping( tfs, AST__BASE, AST__CURRENT );
               SplitMap( tmap2, astGetInvert( tmap2 ), 0, 1, &tmap1, map2,
                         map3, status );
               tmap1 = astAnnul( tmap1 );
               tmap2 = astAnnul( tmap2 );

/* Create a ShiftMap which subtract the constant longitude and latitude
   values off the inputs. */
               for( i = 0; i < nwcs; i++ ) w1[ i ] = 0.0;
               w1[ ilon ] = -ptr2[ ilon ][ 0 ];
               w1[ ilat ] = -ptr2[ ilat ][ 0 ];
               tmap1 = (AstMapping *) astShiftMap( nwcs, w1, "", status );

/* Compose this with the supplied Mapping. This results in the celestial
   outputs being zero. This gives the required "map1". */
               *map1 = (AstMapping *) astCmpMap( map, tmap1, 1, "", status );

/* Indicate success.*/
               ret = 1;

/* Free resources. */
               tmap1 = astAnnul( tmap1 );
               tfs = astAnnul( tfs );
            }
            fc = astAnnul( fc );
         }
      }

/* Free resources */
      pset1 = astAnnul( pset1 );
      pset2 = astAnnul( pset2 );
      w1 = astFree( w1 );
   }
   if( !ret ) {
      if( *map1 ) *map1 = astAnnul( *map1 );
      if( *map2 ) *map2 = astAnnul( *map2 );
      if( *map3 ) *map3 = astAnnul( *map3 );
   }
   return ret;
}

static int SplitMap2( AstMapping *map, int invert, AstMapping **map1,
                      AstWcsMap **map2, AstMapping **map3, int *status ){
/*
*  Name:
*     SplitMap2

*  Purpose:
*     Locate a WCS projection within a Mapping.

*  Type:
*     Private function.

*  Synopsis:
*     int SplitMap2( AstMapping *map, int invert, AstMapping **map1,
*                    AstWcsMap **map2, AstMapping **map3, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     If possible, the supplied Mapping is decomposed into three component
*     mappings to be compounded in series. To be acceptable, the second of
*     these three Mappings must be a WcsMap with a non-zero FITSProj value.
*     If it is not possible to produce such a group of three Mappings, then a
*     zero function value is returned, together with three NULL Mapping
*     pointers. All the mappings before the WcsMap are compounded together
*     and returned as "map1". The WcsMap itself is returned as "map2", and
*     any remaining Mappings are compounded together and returned as "map3".
*
*     The search algorithm allows for an arbitrary combination of series and
*     parallel CmpMaps.

*  Parameters:
*     map
*        A pointer to the Mapping from pixel to physical coordinates.
*     invert
*        The value of the Invert attribute to use with "map" (the value
*        returned by astGetInvert is not used).
*     map1
*        A location at which to return a pointer to the Mapping from pixel
*        to intermediate world coordinates.
*     map2
*        A location at which to return a pointer to the Mapping from relative
*        physical coordinates to native spherical coordinates. This will
*        be a WcsMap, and it will have a non-zero FITSProj value.
*     map3
*        A location at which to return a pointer to the Mapping from
*        native spherical coordinates to physical coordinates.
*     dep
*        The address of an integer holding the current depth of recursion
*        into this function.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     One if a suitable WcsMap was found, zero otherwise.

*  Notes:
*     -  The returned Mappings contain independant copies of the relevant
*     components of the supplied Mapping and can be modified without
*     changing the supplied Mapping.
*     -  NULL pointers will be returned for all Mappings if no WcsMap
*     with anon-zero FITSProj value can be found in the supplied Mapping.
*     -  A pointer to a UnitMap will be returned for map1 if no mappings
*     exist before the WcsMap.
*     -  A pointer to a UnitMap will be returned for map3 if no mappings
*     exist after the WcsMap.
*     -  NULL pointers will be returned for all Mappings and a function
*     value of zero will be returned if an error has occurred, or if this
*     function should fail for any reason.
*     - "*map1" and "*map3" may contain WcsMaps, but they will have zero
*     values for their FITSProj values.
*/

/* Local Variables */
   AstMapping **map_list;  /* Mapping array pointer */
   AstMapping *mapa;       /* Pre-wcs Mapping */
   AstWcsMap *mapb;        /* WcsMap */
   AstMapping *mapc;       /* Post-wcs Mapping */
   AstMapping *temp;       /* Intermediate Mapping */
   const char *class;      /* Pointer to class of supplied Mapping */
   double pv;              /* Projection parameter value */
   int *invert_list;       /* Invert array pointer */
   int axis;               /* No. of axes in whole Mapping */
   int axlat;              /* Index of latitude axis */
   int axlon;              /* Index of longitude axis */
   int haswcs;             /* Was a usable inverted WcsMap found? */
   int imap;               /* Index of current Mapping in list */
   int i;                  /* axis index */
   int m;                  /* Parameter index */
   int nax;                /* No. of axes in Mapping */
   int nmap;               /* Number of Mappings in the list */
   int ret;                /* Was a non-linear Mapping found? */
   int wcsaxis;            /* Index of first WcsMap axis */

/* Initialise */
   *map1 = NULL;
   *map2 = NULL;
   *map3 = NULL;
   ret = 0;

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

/* Get the class of the Mapping. */
   class = astGetClass( map );

/* If the supplied Mapping is a CmpMap... */
   wcsaxis = -1;
   if( !strcmp( class, "CmpMap" ) ){

/* Decompose the Mapping into a sequence of Mappings to be applied in
   series and an associated list of Invert flags. */
      map_list = NULL;
      invert_list = NULL;
      nmap = 0;
      astMapList( map, 1, invert, &nmap, &map_list, &invert_list );

/* If there is more than one Mapping, this must be a series CmpMap. */
      if( nmap > 1 && astOK ){

/* Initialise the returned pre-wcs Mapping to be a UnitMap. */
         if( invert == astGetInvert( map ) ){
            *map1 = (AstMapping *) astUnitMap( astGetNin( map ), "", status );
         } else {
            *map1 = (AstMapping *) astUnitMap( astGetNout( map ), "", status );
         }

/* Indicate we have not yet found  a WcsMap. */
         ret = 0;

/* Process each series Mapping. */
         for( imap = 0; imap < nmap; imap++ ){

/* If we have not yet found a WcsMap... */
            if( !ret ){

/* Search this Mapping for a WcsMap. */
               ret = SplitMap2( map_list[ imap ], invert_list[ imap ], &mapa,
                                map2, map3, status );

/* If no WcsMap was found, use the whole mapping as part of the
   pre-wcs Mapping. */
               if( !ret ){
                  mapa = astCopy( map_list[ imap ] );
                  astSetInvert( mapa, invert_list[ imap ] );
               }

/* Add the pre-wcs mapping to the cumulative pre-wcs CmpMap. */
               temp = (AstMapping *) astCmpMap( *map1, mapa, 1, "", status );
               *map1 = astAnnul( *map1 );
               mapa = astAnnul( mapa );
               *map1 = temp;

/* If we have previously found a WcsMap, use the whole mapping
   as part of the post-wcs mapping. */
            } else {
               mapc = astCopy( map_list[ imap ] );
               astSetInvert( mapc, invert_list[ imap ] );
               temp = (AstMapping *) astCmpMap( *map3, mapc, 1, "", status );
               *map3 = astAnnul( *map3 );
               mapc = astAnnul( mapc );
               *map3 = temp;
            }
         }

/* If there is only one Mapping, this must be a parallel CmpMap. */
      } else {

/* Annul the Mapping pointer in the series list created above, and free the
   dynamic arrays. */
         map_list[ 0 ] = astAnnul( map_list[ 0 ] );
         map_list = astFree( map_list );
         invert_list = astFree( invert_list );
         nmap = 0;

/* Decompose the Mapping into a sequence of Mappings to be applied in
   parallel and an associated list of Invert flags. */
         astMapList( map, 0, invert, &nmap, &map_list, &invert_list );

/* Process each parallel Mapping. */
         axis = 0;
         for( imap = 0; imap < nmap && astOK; imap++ ){

/* See if this Mapping contains a usable WcsMap. Only do the search
   if no such WcsMap has already been found, since only the first is usable. */
            if( !ret ) {

/* Search this Mapping for a WcsMap. */
               haswcs = SplitMap2( map_list[ imap ], invert_list[ imap ], &mapa,
                                  &mapb, &mapc, status );

/* Note if we have found a usable WcsMap, and its first axis index. */
               if( haswcs ){
                  ret = 1;
                  wcsaxis = axis;
               }

/* If a WcsMap has already been found, the mapping cannot contain a
   usable WcsMap. */
            } else {
               haswcs = 0;
            }

/* If the Mapping did not contain a usable WcsMap, use the whole mapping as
   part of the pre-wcs Mapping, and create a UnitMap as part of the post-wcs
   mapping. */
            if( !haswcs ){
               mapa = astCopy( map_list[ imap ] );
               astSetInvert( mapa, invert_list[ imap ] );
               nax = astGetNout( mapa );
               mapc = (AstMapping *) astUnitMap( nax, "", status );
            }

/* Increment the index of the first axis in the next Mapping. */
            axis += astGetNout( mapa );

/* Add the pre-wcs mapping in parallel with the cumulative pre-wcs CmpMap. */
            if( *map1 ){
               temp = (AstMapping *) astCmpMap( *map1, mapa, 0, "", status );
               *map1 = astAnnul( *map1 );
               mapa = astAnnul( mapa );
               *map1 = temp;
            } else {
               *map1 = mapa;
            }

/* Add the post-wcs mapping in parallel with the cumulative post-wcs CmpMap. */
            if( *map3 ){
               temp = (AstMapping *) astCmpMap( *map3, mapc, 0, "", status );
               *map3 = astAnnul( *map3 );
               mapc = astAnnul( mapc );
               *map3 = temp;
            } else {
               *map3 = mapc;
            }
         }

/* If a usable WcsMap was found, create a new one which has all the same
   properties, but with enough axes to join the pre and post wcs Mappings
   together. Ensure the correct axes are used for longitude and latitude,
   and copy the projection parameters. */
         if( ret ){
            axlat = astGetWcsAxis( mapb, 1 );
            axlon = astGetWcsAxis( mapb, 0 );
            *map2 = astWcsMap( axis, astGetWcsType( mapb ),
                               axlon + wcsaxis + 1,
                               axlat + wcsaxis + 1, "", status );
            for( i = 0; i < astGetNin( mapb ); i++ ){
               for( m = 0; m < WCSLIB_MXPAR; m++ ){
                  if( astTestPV( mapb, i, m ) ) {
                     pv = astGetPV( mapb, i, m );
                     if( pv != AST__BAD ) astSetPV( *map2, i + wcsaxis, m, pv );
                  }
               }
            }
            astInvert( *map2 );
            mapb = astAnnul( mapb );
         }
      }

/* Loop to annul all the Mapping pointers in the list. */
      for ( imap = 0; imap < nmap; imap++ ) map_list[ imap ] = astAnnul( map_list[ imap ] );

/* Free the dynamic arrays. */
      map_list = astFree( map_list );
      invert_list = astFree( invert_list );

/* If the supplied Mapping is not a CmpMap, see if it is a WcsMap with a
   non-zero FITSProj value. If so, take a copy and set its invert attribute
   correctly. Also create UnitMaps for the pre and post wcs mappings. */
   } else if( astOK && !strcmp( class, "WcsMap" ) && astGetFITSProj( map ) ){
      ret = 1;
      nax = astGetNin( map );
      *map1 = (AstMapping *) astUnitMap( nax, "", status );
      *map2 = astCopy( map );
      astSetInvert( *map2, invert );
      *map3 = (AstMapping *) astUnitMap( nax, "", status );
   }

/* If an error has occurred, or if no suitable WcsMap was found, annul any
   Mappings. */
   if( !astOK || !ret ){
      ret = 0;
      if( *map1 ) *map1 = astAnnul( *map1 );
      if( *map2 ) *map2 = astAnnul( *map2 );
      if( *map3 ) *map3 = astAnnul( *map3 );
   }

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

static int SplitMat( int naxis, double *matrix, double *cdelt, int *status ){
/*
*  Name:
*     SplitMat

*  Purpose:
*     Factorises a single "CD"-style matrix into a diagonal CDELT matrix
*     and a "PC"-style matrix.

*  Type:
*     Private function.

*  Synopsis:
*     int SplitMat( int naxis, double *matrix, double *cdelt, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function splits up the supplied CD matrix into separate PC and
*     CDELT matrices. The product of the returned matrices (CDELT.PC)
*     equals the supplied CD matrix. The CDELT values are chosen so that
*     the corresponding row of the PC matrix represents a unit vector.
*     The signs of the CDELT values are chosen so that the diagonal terms
*     of the PC matrix are all positive.
*

*  Parameters:
*     naxis
*        The number of axes.
*     matrix
*        A pointer to an array of naxis*naxis elements. On entry this holds
*        the "CD" matrix. On exit, it is modified to represent the "PC"
*        matrix.
*     cdelt
*        A pointer to an array of naxis elements. On exit this holds the CDELT
*        values for each axis (i.e. the diagonal terms of the CDELT matrix).
*     status
*        Pointer to the inherited status variable.

* Returned Value:
*     Zero is returned if any bad values are found in the supplied
*     matrix, or if an error has already occurred. One is returned otherwise.
*/

/* Local Variables: */
   int i;
   int j;
   int ok;
   double *a;
   int dineg;
   double s2;
   double cdlt;

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

/* Assume success. */
   ok = 1;

/* Loop round every row in the matrix. Get a pointer to the first element
   in the row. */
   for( i = 0; i < naxis; i++ ){
      a = matrix + i*naxis;

/* Note the sign of the diagonal term (i.e. the i'th element) of this row. */
      dineg = ( a[ i ] < 0.0 );

/* Get the magnitude of the vector represented by this row. This is the
   CDELT value for the row. BAD values cause the whole function to return. */
      s2 = 0.0;
      for( j = 0; j < naxis; j++ ){
         if( *a == AST__BAD )  {
            ok = 0;
            break;
         }
         s2 += (*a)*(*a);
         a++;
      }
      if( !ok ) break;
      cdlt = sqrt( astMAX( 0.0, s2 ) );

/* If the diagonal term for this row of the matrix is negative, make
   the CDELT value negative instead. This means that the diagonal term in
   the final PC matrix will be positive. */
      if( dineg ) cdlt = -cdlt;

/* Store the CDELT value. */
      cdelt[ i ] = cdlt;

/* The row of the PC matrix is obtained by dividing the original row by
   the CDELT value. Set to zero any PC values which are less than 1.0E-7
   (such values may be produced by rounding errors). */
      a = matrix + i*naxis;
      for( j = 0; j < naxis; j++ ) {
         if( cdlt != 0.0 ){
            *a /= cdlt;
            if( fabs( *a ) < 1.E-7 ) *a = 0.0;
         } else {
            *a = 0.0;
         }
         a++;
      }
   }
   return ok;
}
static void TableSource( AstFitsChan *this,
                         void (* tabsource)( AstFitsChan *, const char *,
                                             int, int, int * ),
                         int *status ){

/*
*++
*  Name:
c     astTableSource
f     AST_TABLESOURCE

*  Purpose:
c     Register a source function for accessing tables in FITS files.
f     Register a source routine for accessing tables in FITS files.

*  Type:
*     Public function.

*  Synopsis:
c     #include "fitschan.h"
c     void astTableSource( AstFitsChan *this,
c                          void (* tabsource)( AstFitsChan *, const char *,
c                                              int, int, int * ) )
f     CALL AST_TABLESOURCE( THIS, TABSOURCE, STATUS )

*  Class Membership:
*     FitsChan member function.

*  Description:
c     This function can be used to register a call-back function
f     This routine can be used to register a call-back routine
*     with a FitsChan. The registered
c     function
f     routine
*     is called when-ever the FitsChan needs to read information from a
*     binary table contained within a FITS file. This occurs if the
c     astRead
f     AST_READ
*     function is invoked to read a FrameSet from a set of FITS headers
*     that use the "-TAB" algorithm to describe one or more axes. Such
*     axes use a FITS binary table to store a look-up table of axis values.
*     The FitsChan will fail to read such axes unless the "TabOK" attribute
*     is set to a non-zero positive integer value. The table containing the
*     axis values must be made available to the FitsChan either by storing
*     the table contents in the FitsChan (using
c     astPutTables or astPutTable) prior to invoking astRead
f     AST_PUTTABLES or AST_PUTTABLE) prior to invoking AST_READ
*     or by registering a call-back
c     function using astTableSource.
f     routine using AST_TABLESOURCE.
*     The first method is possibly simpler, but requires that the name of
*     the extension containing the table be known in advance. Since the
*     table name is embedded in the FITS headers, the name is often not
*     known in advance. If a call-back is registered, the FitsChan will
*     determine the name of the required table and invoke the call-back
c     function
f     routine
*     to supply the table at the point where it is needed (i.e. within
c     the astRead method).
f     the AST_READ method).

*  Parameters:
c     this
f     THIS = INTEGER (Given)
*        Pointer to the FitsChan.
c     tabsource
f     TABSOURCE = SUBROUTINE (Given)
c        Pointer to the table source function to use.
f        The table source routine to use.
*        It takes five arguments - the first is a pointer to the
*        FitsChan, the second is a string holding the name of the
*        FITS extension containing the required binary table ("EXTNAME"),
*        the third is the integer FITS "EXTVER" header value for the
*        required extension, the fourth is the integer FITS "EXTLEVEL"
*        header value for the required extension, and the fifth is
c        a pointer to an integer status value.
f        the usual inherited status value.
*
*        The call-back should read the entire contents (header and data)
*        of the binary table in the named extension of the external FITS
*        file, storing the contents in a newly created FitsTable object. It
*        should then store this FitsTable in the FitsChan using the
c        astPutTables or astPutTable
f        AST_PUTTABLES or AST_PUTTABLE
*        method, and finally annull its local copy of the FitsTable pointer.
*        If the table cannot be read for any reason, or if any other
*        error occurs, it should return
c        zero for the final (third) argument (otherwise any non-zero integer
f        a non-zero integer for the final (third) argument (otherwise zero
*        should be returned).
*
c        If "tabsource" is NULL,
f        If TABSOURCE is AST_NULL,
*        any registered call-back function will be removed.
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Notes:
c     - Application code can pass arbitrary data (such as file
c     descriptors, etc) to the table source function using the
c     astPutChannelData function. The source function should use
c     the astChannelData macro to retrieve this data.
f     - The name  of the routine supplied for the TABSOURCE
f     argument should appear in an EXTERNAL statement in the Fortran
f     routine which invokes AST_TABLESOURCE. However, this is not generally
f     necessary for the null routine AST_NULL (so long as the AST_PAR
f     include file has been used).
f     - Note that the null routine AST_NULL (one underscore) is
f     different to AST__NULL (two underscores), which is the null Object
f     pointer.
*--
*/

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

/* Register the supplied source function, using the wrapper function
   appropriate for calling C table source functions. */
   astSetTableSource( this, (void (*)( void )) tabsource, TabSourceWrap );
}

static AstMapping *TabMapping( AstFitsChan *this, FitsStore *store, char s,
                               int **tabaxis, const char *method,
                               const char *class, int *status ) {

/*
*  Name:
*     TabMapping

*  Purpose:
*     Create a Mapping that performs any -TAB look-ups for all WCS axes.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     AstMapping *TabMapping( AstFitsChan *this, FitsStore *store, char s,
*                             int **tabaxis, const char *method,
*                             const char *class, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function returns a Mapping that has "nwcs" inputs and outputs,
*     where "nwcs" is the number of FITS WCS axes defined in the supplied
*     FitsStore. The inputs and outputs are in the same order as the
*     CTYPEi keywords in the FitsStore. The forward transformation of the
*     Mapping converts positions from the axes defined by the CRVALi keywords
*     to the WCS axes. This transformation will be a UnitMap except for
*     any axes that are described using the "-TAB" algorithm. For "-TAB"
*     axes, the transformation will implement the relevant coordinate
*     look-up function.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     store
*        Pointer to the FitsStore structure holding the values to use for
*        the WCS keywords.
*     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').
*     tabaxis
*        Address of a location at which to store a pointer to an array of
*        flags, one for each output of the returned Mapping. A flag will
*        be non-zero if the corresponding output of the returned Mapping
*        corresponds to a -TAB axis. A NULL pointer is returned if the
*        returned Mapping is NULL.
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to a Mapping. A NULL pointer is returned if the FitsChan does
*     not support the -TAB algorithm (i.e. if the value of the TabOK
*     attribute is zero or negative), or if no axes use the "-TAB" algorithm.
*/

/* Local Variables: */
   AstFitsTable *table;
   AstKeyMap *used_tables;
   AstMapping *tmap1;
   AstMapping *tmap2;
   AstMapping *indexmap;
   AstMapping *tmap0;
   AstMapping *ret;
   AstPermMap *pm;
   char name[21];
   const char *indexcol;
   const char *extname;
   const char *cval;
   const char *ctype;
   const char *coordscol;
   double dval;
   int *marray;
   int *permin;
   int *permout;
   int extlevel;
   int extver;
   int iaxis;
   int iiaxis;
   int ikey;
   int interp;
   int ival;
   int maxis;
   int mdim;
   int nkey;
   int nperm;
   int unit;
   int wcsaxes;

/* Initialise */
   ret = NULL;
   *tabaxis = NULL;
   extname = NULL;
   tmap0 = NULL;
   tmap2 = NULL;

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

/* Obtain the number of physical 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, status );
   if( dval != AST__BAD ) {
      wcsaxes = (int) dval + 0.5;
   } else {
      wcsaxes = store->naxis;
   }

/* If the FitsChan does not support the -TAB algorithm, return a NULL
   pointer. */
   if( astGetTabOK( this ) > 0 ) {

/* Create a KeyMap to hold a list of the used extension names. */
      used_tables = astKeyMap( " ", status );

/* Allocate memory to indicate if each WCS axis is described by a -TAB
   algorithm or not. Initialiss it to zero. */
      *tabaxis = astCalloc( wcsaxes, sizeof( int ) );

/* Allocate memory to hold the FITS-WCS axis index corresponding to each
   input of the "tmap0" Mapping. Indicate that as yet, not values are
   stored in this array. Also allocate memory for the inverse of this
   permutation array. */
      permout = astMalloc( wcsaxes*sizeof( int ) );
      permin = astMalloc( wcsaxes*sizeof( int ) );
      nperm = 0;
      if( astOK ) {

/* Initialise the permutation arrays. */
         for( iaxis = 0; iaxis < wcsaxes; iaxis++ ) {
            permout[ iaxis ] = permin[ iaxis ] = -1;
         }

/* Otherwise, loop round all FITS WCS axis indices present in the FitsStore. */
         for( iaxis = 0; iaxis < wcsaxes; iaxis++ ) {

/* If the current FITS WCS axis is already included in the returned
   Mapping, skip it. This will be the case if the axis uses the same
   coordinate array as an earlier axis since all FITS WCS axes associated
   with a coordinate array are processed together. */
            if( permin[ iaxis ] == -1 ) {

/* See if this WCS axis uses the -TAB algorithm. */
               ctype = GetItemC( &(store->ctype), iaxis, 0, s, NULL, method,
                                 class, status );
               if( ctype && strlen(ctype) > 4 && !strncmp( ctype + 4, "-TAB", 4 ) ) {

/* Get the name of the FITS binary table extension holding the coordinate
   info. No default, so report an error if not present. */
                  sprintf( name, "PS%d_0%c", iaxis + 1, s );
                  extname = GetItemC( &(store->ps), iaxis, 0, s, name, method,
                                      class, status );

/* Get the extension version and level. */
                  dval = GetItem( &(store->pv), iaxis, 1, s, NULL, method,
                                    class, status );
                  extver = ( dval != AST__BAD ) ? (int) dval : 1;
                  dval = GetItem( &(store->pv), iaxis, 2, s, NULL, method,
                                    class, status );
                  extlevel = ( dval != AST__BAD ) ? (int) dval : 1;

/* Get the FITS binary table. This will invoke any supplied table source
   function, and put a copy of the table into the FitsChan structure.
   Report an error if the table can not be obtained. */
                  table = GetNamedTable( this, extname, extver, extlevel, 1,
                                         method, status );

/* Add this extension name to a list of used extensions. */
                  astMapPut0I( used_tables, extname, 1, NULL );

/* Get the name of the table column containing the main coords array. No
   default so report error if not present. Report an error if the column
   is not present in the table. */
                  sprintf( name, "PS%d_1%c", iaxis + 1, s );
                  coordscol = GetItemC( &(store->ps), iaxis, 1, s, name, method,
                                        class, status );
                  if( !astHasColumn( table, coordscol ) && astOK ) {
                     astError( AST__BADTAB, "%s(%s): Unable to find the "
                               "coordinate array for FITS-WCS axis %d (type %s): "
                               "column '%s' cannot be found in table '%s'.", status,
                               method, class, iaxis + 1, ctype, coordscol, extname );
                  }

/* Get the number of dimensions spanned by the coordinate array. Report
   an error if the coordinate array has only one axis (FITS-WCS paper III
   requires it to have at leats two axes). */
                  mdim = astGetColumnNdim( table, coordscol );
                  if( mdim == 1 && astOK ) {
                     astError( AST__BADTAB, "%s(%s): Unable to use the "
                               "coordinate array for FITS-WCS axis %d (type %s): "
                               "column '%s' in table '%s' has one axis but at "
                               "least two are required.", status, method, class,
                                iaxis + 1, ctype, coordscol, extname );
                  }

/* Allocate memory to hold the FITS-WCS axis corresponding to each dimension
   of the coordinate array. Initialise it to hold -1 (i.e. "no matching
   FITS-WCS axis yet found") at every element. */
                  marray = astMalloc( mdim*sizeof( int ) );
                  if( astOK ) {
                     for( maxis = 0; maxis < mdim; maxis++ ) {
                        marray[ maxis ] = -1;
                     }

/* Loop round each dimension of the coordinate array, storing the index
   of the corresponding FITS-WCS axis in "marray". We omit the first axis
   (axis 0) since FITS-WCS Paper III defines it is a "conventional" axis
   used to enumerate the planes of coordinate values. */
                     for( maxis = 1; maxis < mdim && astOK ; maxis++ ) {

/* Each axis of the coordinate array (except axis 0) must have one, and only
   one, corresponding FITS-WCS axis. Check each FITS-WCS axis to find one
   that uses the same table and column as the "iaxis" axis, and which
   corresponds to axis "maxis" of the coordinate array. */
                        for( iiaxis = 0; iiaxis < wcsaxes; iiaxis++ ) {
                           cval = GetItemC( &(store->ps), iiaxis, 0, s, NULL,
                                            method, class, status );
                           if( cval && !strcmp( cval, extname ) ) {
                              cval= GetItemC( &(store->ps), iiaxis, 1, s, NULL,
                                              method, class, status );
                              if( cval && !strcmp( cval, coordscol ) ) {
                                 dval = GetItem( &(store->pv), iiaxis, 3, s,
                                                 NULL, method, class, status );
                                 if( dval != AST__BAD ) {
                                    ival = (int)( dval + 0.5 );
                                 } else {
                                    ival = 1;
                                 }
                                 if( ival == maxis ) {

/* Arrive here if the "iiaxis" FITS-WCS axis uses the same table and column
   as "iaxis", and corresponds to the "maxis" axis in the coordinate
   array. If this is the first matching FITS-WCS axis, store its index. */
                                    if( marray[ maxis ] == -1 ) {
                                       marray[ maxis ] = iiaxis;

/* If a matching FITS-WCS axis has already been found, report an error. */
                                    } else if( astOK ) {
                                       astError( AST__BADTAB, "%s(%s): Unable to use "
                                                 "the coordinate array for FITS-WCS "
                                                 "axis %d (type %s): more than one "
                                                 "intermediate WCS axis is mapped onto "
                                                 " dimension %d of the coordinate "
                                                 "array in column '%s' of table '%s'.",
                                                 status, method, class, iaxis + 1,
                                                 ctype, maxis, coordscol, extname );
                                    }
                                 }
                              }
                           }
                        }
                     }

/* Check that every dimension of the coordinate array (except the first) has
   a corresponding FITS-WCS axis. */
                     for( maxis = 1; maxis < mdim && astOK ; maxis++ ) {
                        if( marray[ maxis ] == -1 ) {
                           astError( AST__BADTAB, "%s(%s): Unable to use the "
                                     "coordinate array for FITS-WCS axis %d (type "
                                     "%s): no intermediate WCS axis is mapped onto "
                                     " dimension %d of the coordinate array in column "
                                     " '%s' of table '%s'.", status, method, class,
                                     iaxis + 1, ctype, maxis, coordscol, extname );
                        }
                     }

/* Now we know which FITS-WCS axis corresponds to each dimension of the
   coordinate array. We now need to form a parallel CmpMap (compound Mapping)
   by gathering together the indexing vectors for each dimension of the
   coordinates array. Each indexing vector is represented by an inverted
   1D LutMap - dimensions that do not have an indexing vector are
   represented using a UnitMap. */
                     indexmap = NULL;
                     unit = 1;
                     for( maxis = 1; maxis < mdim && astOK ; maxis++ ) {

/* Get the name of the column containing the index array. Defaults is to
   use a unit index, so do not report an error if not present. */
                        indexcol = GetItemC( &(store->ps), marray[ maxis ], 2,
                                             s, NULL, method, class, status );

/* If the table contains an index vector, create a LutMap from it, then
   invert it. */
                        if( indexcol ) {
                           tmap1 = MakeColumnMap( table, indexcol, 1, 0,
                                                  method, class, status );
                           astInvert( tmap1 );
                           unit = 0;

/* If the table does not contain an index vector, use a UnitMap. */
                        } else {
                           tmap1 = (AstMapping *) astUnitMap( 1, " ", status );
                        }

/* Combine the index Mapping for this dimension in parallel with the
   Mapping for all earlier dimensions. */
                        if( indexmap ) {
                           tmap2 = (AstMapping *) astCmpMap( indexmap, tmap1,
                                                             0, " ", status );
                           indexmap = astAnnul( indexmap );
                           tmap1 = astAnnul( tmap1 );
                           indexmap = tmap2;
                        } else {
                           indexmap = tmap1;
                        }
                     }

/* Get the interpolation method to use for the main coordinate array.
   This is an extension to the published -TAB algorithm in which the
   QVi_4a keyword is assumed to hold zero for linear interpolation (the
   default) and non-zero for nearest neighbour interpolation. The QVi_4a
   keyword will be translated to PVi_4a by the SpecTrans function. */
                     dval = GetItem( &(store->pv), iaxis, 4, s,
                                     NULL, method, class, status );
                     if( dval != AST__BAD ) {
                        interp = (int)( dval + 0.5 );
                     } else {
                        interp = 0;
                     }

/* Make a Mapping from the main coordinate array, and then if required
   append it in series to the end of the index Mapping created above. */
                     tmap1 = MakeColumnMap( table, coordscol, 0, interp,
                                            method, class, status );
                     if( ! unit ) {
                        tmap2 = (AstMapping *) astCmpMap( indexmap, tmap1, 1,
                                                          " ", status );
                     } else {
                        tmap2 = astClone( tmap1 );
                     }
                     indexmap = astAnnul( indexmap );
                     tmap1 = astAnnul( tmap1 );

/* Extend the array that holds the zero-based FITS-WCS axis index
   corresponding to each input of the extended "tmap0" mapping. Also create
   the inverse permutation (i.e. zero-based "tmap0" input indexed by
   zero-based FITS-WCS axis index). */
                     for( maxis = 1; maxis < mdim; maxis++ ) {
                        permout[ nperm ] = marray[ maxis ];
                        permin[ marray[ maxis ] ] = nperm++;
                     }

/*  Free resources. */
                     marray = astFree( marray );
                  }

/* Annul the table pointer. */
                  table = astAnnul( table );

/* Clear the CTYPE algorithm code to indicate that the axis should be
   considered to be linear from now on. This means that the following
   functions will create a Mapping from pixel to psi (the system in which
   the CRVAL values are defined when using -TAB). The psi axes will then
   be mapping into the CS axes using the Mappign returned by this function. */
                  strncpy( name, ctype, 4 );
                  strcpy( name + 4, ctype + 8 );
                  SetItemC( &(store->ctype), iaxis, 0, s, name, status );

/* Set the returned flag for this axis. */
                  (*tabaxis)[ iaxis ] = 1;

/* If the FITS WCS axis "iaxis" does not use a -TAB algorithm, describe
   it in the returned Mapping using a 1D UnitMap. */
               } else {
                  tmap2 = (AstMapping *) astUnitMap( 1, " ", status );

/* Extend the array that holds the zero-based FITS-WCS axis index
   corresponding to each input of the extended "tmap0" mapping. Also create
   the inverse permutation (i.e. zero-based "tmap0" input indexed by
   zero-based FITS-WCS axis index). */
                  permout[ nperm ] = iaxis;
                  permin[ iaxis ] = nperm++;
               }

/* Append the Mapping describing the FITS WCS axis "iaxis" in parallel to any
   Mappings created for earlier "iaxis" axes. */
               if( tmap0 ) {
                  tmap1 = (AstMapping *) astCmpMap( tmap0, tmap2, 0, " ", status );
                  tmap0 = astAnnul( tmap0 );
                  tmap2 = astAnnul( tmap2 );
                  tmap0 = tmap1;
               } else {
                  tmap0 = tmap2;
               }
            }
         }

/* If no -TAB axes were found, just return a NULL pointer. */
         if( extname && astOK ) {

/* Do a sanity check on the permutation arrays. */
            for( iaxis = 0; iaxis < wcsaxes; iaxis++ ) {
               if( permin[ iaxis ] < 0 || permin[ iaxis ] >= wcsaxes ||
                   permout[ permin[ iaxis ] ] != iaxis ) {
                  astError( AST__INTER, "%s(%s): Invalid permutation "
                            "arrays in function TabMapping (internal AST "
                            "progranmming error).", status, method, class );
                  break;
               }
            }

/* Sandwich the "tmap0" Mapping in series between two PermMaps to create a
   Mapping in which the inputs and outputs correspond to FITS WCS axis
   numbering. */
            pm = astPermMap( wcsaxes, permin, wcsaxes, permout, NULL, " ",
                             status  );
            tmap1 = (AstMapping *) astCmpMap( pm, tmap0, 1, " ", status );
            astInvert( pm );
            tmap2 = (AstMapping *) astCmpMap( tmap1, pm, 1, " ", status );
            pm = astAnnul( pm );
            tmap1 = astAnnul( tmap1 );

/* Simplify the returned Mapping. */
            ret = astSimplify( tmap2 );
            tmap2 = astAnnul( tmap2 );
         }

/* Free remaining resources */
         tmap0 = astAnnul( tmap0 );
      }
      permout = astFree( permout );
      permin = astFree( permin );

/* Remove all used tables from the FitsChan now that they have been used. */
      nkey = astMapSize( used_tables );
      for( ikey = 0; ikey < nkey; ikey++ ) {
         astRemoveTables( this, astMapKey( used_tables, ikey ) );
      }

/* Delete the KeyMap holding the used table names. */
      used_tables = astAnnul( used_tables );

/* If we are not returning a Mapping, ensure we do not return any axis
   flags either. */
      if( !ret ) *tabaxis = astFree( *tabaxis );
   }

/* Return the result */
   return ret;
}
static void TabSourceWrap( void (*tabsource)( void  ),
                           AstFitsChan *this, const char *extname,
                           int extver, int extlevel, int *status ){

/*
*  Name:
*     TabSourceWrap

*  Purpose:
*     Wrapper function to invoke the C table source function.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void TabSourceWrap( void (*tabsource)( void ),
*                         AstFitsChan *this, const char *extname,
*                         int extver, int extlevel, int *status )

*  Class Membership:
*     Channel member function.

*  Description:
*     This function invokes the table source function whose pointer is
*     supplied in order to read a named FITS binary table from an external
*     FITS file.

*  Parameters:
*     tabsource
*        Pointer to the C tab source function.
*     this
*        Pointer to the FitsChan. The reference count for the FitsChan is
*        decremented by this function (this behaviour is imposed by
*        restrictions in the equivalent Fortran wrapper function).
*     extname
*        Pointer to the string holding the name of the FITS extension
*        from which a table is to be read.
*     extver
*        The integer "EXTVER" value for the required extension.
*     extlevel
*        The integer "EXTLEVEL" value for the required extension.
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   AstFitsChan *this_id;
   int lstat;

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

/* Get an external identifier for the FitsChan. Could use astClone here
   to avoid this function anulling the supplied pointer, but the F77 wrapper
   cannot use the protected version of astClone, so for consistency we do
   not use it here either. */
   this_id = astMakeId( this );

/* Invoke the table source function (casting it to the C API first) to
   read the table, and store it in the FitsChan. */
   ( *( void (*)( struct AstFitsChan *, const char *, int, int, int * ) )tabsource )( this_id, extname, extver, extlevel, &lstat );

/* Free the FitsChan identifier (this annuls the supplied "this" pointer). */
   this_id = astAnnulId( this_id );

/* Report an error if the source function failed. */
   if( !lstat ) {
      astError( AST__NOTAB, "astRead(%s): The table source function failed to read "
                "a binary table from extension %s in an external FITS file.",
                status, astGetClass( this ), extname );
   }
}

static double TDBConv( double mjd, int timescale, int fromTDB,
                       const char *method, const char *class, int *status ){
/*
*  Name:
*     TDBConv

*  Purpose:
*     Convert an MJD between the TDB time scale and another timescale.

*  Type:
*     Private function.

*  Synopsis:
*     double TDBConv( double mjd, int timescale, int fromTDB,
*                     const char *method, const char *class, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function converts the supplied mjd value to or from the TDB
*     timescale.

*  Parameters:
*     mjd
*        The input MJD value.
*     timescale
*        The other timescale.
*     fromTDB
*        Indicates the direction of the required conversion. If non-zero,
*        the supplied "mjd" value should be in the TDB timescale, and the
*        returned value will be in the timescale specified by "timescale".
*        If zero, the supplied "mjd" value should be in the timescale
*        specified by "timescale", and the returned value will be in the
*        TDB timescale.
*     method
*        The calling method. Used only in error messages.
*     class
*        The object class. Used only in error messages.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     The converted MJD value, or AST__BAD if an error occurs.
*/

/* Local Variables: */
   AstFrameSet *fs;    /* Mapping from supplied timescale to TDB */
   double ret;         /* The returned value */

/* Initialise */
   ret = AST__BAD;

/* Check inherited status and supplied TDB value. */
   if( !astOK || mjd == AST__BAD ) return ret;

/* Return the supplied value if no conversion is needed. */
   if( timescale == AST__TDB ) {
      ret = mjd;

/* Otherwise, do the conversion. */
   } else {

/* Lock the timeframes for use by the current thread, waiting if they are
   currently locked by another thread. */
      astManageLock( timeframe, AST__LOCK, 1, NULL );
      astManageLock( tdbframe, AST__LOCK, 1, NULL );

/* Set the required timescale. */
      astSetTimeScale( timeframe, timescale );

/* Get the Mapping between the two timescales, and use it to convert the
   suipplied value. */
      fs = astConvert( tdbframe, timeframe, "" );
      astTran1( fs, 1, &mjd, fromTDB, &ret );
      fs = astAnnul( fs );

/* Unlock the timeframes. */
      astManageLock( timeframe, AST__UNLOCK, 1, NULL );
      astManageLock( tdbframe, AST__UNLOCK, 1, NULL );
   }

/* Return the result */
   return ret;
}

static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
/*
*  Name:
*     TestAttrib

*  Purpose:
*     Test if a specified attribute value is set for a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int TestAttrib( AstObject *this, const char *attrib, int *status )

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

*  Description:
*     This function returns a boolean result (0 or 1) to indicate whether
*     a value has been set for one of a FitsChan's attributes.

*  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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     One if a value has been set, otherwise zero.

*  Notes:
*     - A value of zero will be returned if this function is invoked
*     with the global 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 */

/* Initialise. */
   result = 0;

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

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

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

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

/* FitsAxisOrder. */
/* -------------- */
   } else if ( !strcmp( attrib, "fitsaxisorder" ) ) {
      result = astTestFitsAxisOrder( this );

/* AltAxes. */
/* -------- */
   } else if ( !strcmp( attrib, "altaxes" ) ) {
      result = astTestAltAxes( this );

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

/* FitsRounding. */
/* ------------- */
   } else if ( !strcmp( attrib, "fitsrounding" ) ) {
      result = astTestFitsRounding( this );

/* DefB1950. */
/* --------- */
   } else if ( !strcmp( attrib, "defb1950" ) ) {
      result = astTestDefB1950( this );

/* TabOK. */
/* ------ */
   } else if ( !strcmp( attrib, "tabok" ) ) {
      result = astTestTabOK( this );

/* ForceTab. */
/* --------- */
   } else if ( !strcmp( attrib, "forcetab" ) ) {
      result = astTestForceTab( this );

/* CDMatrix. */
/* --------- */
   } else if ( !strcmp( attrib, "cdmatrix" ) ) {
      result = astTestCDMatrix( this );

/* CarLin. */
/* --------- */
   } else if ( !strcmp( attrib, "carlin" ) ) {
      result = astTestCarLin( this );

/* SipReplace. */
/* ----------- */
   } else if ( !strcmp( attrib, "sipreplace" ) ) {
      result = astTestSipReplace( this );

/* FitsTol. */
/* -------- */
   } else if ( !strcmp( attrib, "fitstol" ) ) {
      result = astTestFitsTol( this );

/* PolyTan */
/* ------- */
   } else if ( !strcmp( attrib, "polytan" ) ) {
      result = astTestPolyTan( this );

/* SipOK */
/* ----- */
   } else if ( !strcmp( attrib, "sipok" ) ) {
      result = astTestSipOK( this );

/* Iwc. */
/* ---- */
   } else if ( !strcmp( attrib, "iwc" ) ) {
      result = astTestIwc( this );

/* Clean. */
/* ------ */
   } else if ( !strcmp( attrib, "clean" ) ) {
      result = astTestClean( this );

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

/* If the name is not recognised, test if it matches any of the
   read-only attributes of this class. If it does, then return
   zero. */
   } else if ( !strcmp( attrib, "ncard" ) ||
               !strcmp( attrib, "nkey" ) ||
               !strcmp( attrib, "cardtype" ) ||
               !strcmp( attrib, "cardcomm" ) ||
               !strcmp( attrib, "cardname" ) ||
               !strcmp( attrib, "allwarnings" ) ){
      result = 0;

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

/* Return the result, */
   return result;
}

static int TestCard( AstFitsChan *this, int *status ){

/*
*+
*  Name:
*     astTestCard

*  Purpose:
*     Test the Card attribute.

*  Type:
*     Protected virtual function.

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

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function tests the Card attribute for the supplied FitsChan.

*  Parameters:
*     this
*        Pointer to the FitsChan.

*  Returned Value:
*     If the Card attribute has its "cleared" value (i.e. if the first card
*     in the FitsChan will be the next one to be read), then zero is returned,
*     otherwise 1 is returned.
*-
*/

/* Local Variables: */
   int card;               /* The original value of Card */
   int ret;                /* The returned flag */

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* Get the current value of Card. */
   card = astGetCard( this );

/* Temporarily clear Card. */
   astClearCard( this );

/* See if the original Card is equal to the cleared card, and set the
   returned flag appropriately. Re-instate the original value of card is
   required.*/
   if( astGetCard( this ) == card ) {
      ret = 0;
   } else {
      astSetCard( this, card );
      ret = 1;
   }

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

static int TestFits( AstFitsChan *this, const char *name, int *there,
                     int *status ){

/*
*++
*  Name:
c     astTestFits
f     AST_TESTFITS

*  Purpose:
*     See if a named keyword has a defined value in a FitsChan.

*  Type:
*     Public virtual function.

*  Synopsis:
c     #include "fitschan.h"

c     int astTestFits( AstFitsChan *this, const char *name, int *there )
f     RESULT = AST_TESTFITS( THIS, NAME, THERE, STATUS )

*  Class Membership:
*     FitsChan method.

*  Description:
*     This function serches for a named keyword in a FitsChan. If found,
*     and if the keyword has a value associated with it, a
c     non-zero
f     .TRUE.
*     value is returned. If the keyword is not found, or if it does not
*     have an associated value, a
c     zero
f     .FALSE.
*     value is returned.

*  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
f        A character string
*        containing the FITS 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. If
c        NULL
f        a single dot '.'
*        is supplied, the current card is tested.
c     there
f     THERE = LOGICAL (Returned)
c        Pointer to an integer which will be returned holding a non-zero
c        value if the keyword was found in the header, and zero otherwise.
f        A value of .TRUE. will be returned if the keyword was found in the
f        header, and .FALSE. otherwise.
*        This parameter allows a distinction to be made between the case
*        where a keyword is not present, and the case where a keyword is
*        present but has no associated value.
c        A NULL pointer may be supplied if this information is not
c        required.
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Returned Value:
c     astTestFits()
f     AST_TESTFITS = LOGICAL
*        A value of zero
f        .FALSE.
*        is returned if the keyword was not found in the FitsChan or has
*        no associated value. Otherwise, a value of
c        one
f        .TRUE.
*        is returned.

*  Notes:
*     -  The current card is left unchanged by this function.
*     -  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.
*     -  An error will be reported if the keyword name does not conform
*     to FITS requirements.
c     -  Zero
f     -  .FALSE.
*     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 *class;     /* Object class */
   const char *method;    /* Calling method */
   char *lcom;            /* Supplied keyword comment */
   char *lname;           /* Supplied keyword name */
   char *lvalue;          /* Supplied keyword value */
   int icard;             /* Current card index on entry */
   int ret;               /* The returned value */
   int type;              /* The card's type */

/* Initialise */
   if( there ) *there = 0;

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

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* Store the calling method and object class. */
   method = "astTestFits";
   class = astGetClass( this );

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

/* Extract the keyword name from the supplied string. */
   if( name ) {
      (void) Split( this, name, &lname, &lvalue, &lcom, method, class, status );
   } else {
      lname = NULL;
      lvalue = NULL;
      lcom = NULL;
   }

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

/* 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. No
   need to do the search if the value of the current card is required. */
   if( !lname || SearchCard( this, lname, method, class, status ) ){

/* Get the card type. */
      type = CardType( this, status );

/* Check the card exists. */
      if( type != AST__NOTYPE ) {

/* If the cards data type is not undefined, return 1. */
         if( CardType( this, status ) != AST__UNDEF ) ret = 1;

/* Indicate the card has been found. */
         if( there ) *there = 1;
      }
   }

/* Re-instate the original current card index. */
   astSetCard( this, icard );

/* 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 void TidyOffsets( AstFrameSet *fset, int *status ) {
/*
*  Name:
*     TidyOffsets

*  Purpose:
*     Remove un-needed offset coordinate Frames.

*  Type:
*     Private function.

*  Synopsis:
*     void TidyOffsets( AstFrameSet *fset, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     A FITS header stores offset sky coordinates as two alternaive axis
*     descriptions - one giving the offset axes and one giving the absolute
*     axes. But AST can hold both forms in a single SkyFrame. This function
*     removes the FITS Frames describing offset axes from the FrameSet.
*     The remaining absolute Frame is then used to describe both absolute
*     and offset.

*  Parameters:
*     fset
*        A FrameSet holding the Frames read from a FITS-WCS Header.
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   AstFrame *frm;
   AstFrame *pfrm;
   const char *dom;
   const char *skyrefis;
   int hasabs;
   int hasoff;
   int iax;
   int icurr;
   int icurr_is_offset;
   int ifrm;
   int nax;
   int nfrm;
   int pax;
   int remove;

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

/* Note the original current Frame index. */
   icurr = astGetCurrent( fset );

/* Assume the current Frame is not an offset frame until proven
   otherwise. */
   icurr_is_offset = 0;

/* Does the FrameSet contain any Frames holding sky offsets? Such Frames
   should have been given a Domain of SKY_OFFSETS within function
   WcsSkyFrame. Loop round all Frames, checking each one. Also note if
   the FrameSet contains any (absolute) SKY frames. Also set the SkyRefIs
   attribute for any absolute SkyFrames that were marked with domains
   SKY_POLE or SKY_OFFSET in WcsSkyFrame. */
   hasabs = 0;
   hasoff = 0;
   nfrm = astGetNframe( fset );
   for( ifrm = 1; ifrm <= nfrm; ifrm++ ){
      skyrefis = NULL;
      frm = astGetFrame( fset, ifrm );
      nax = astGetNaxes( frm );
      for( iax = 0; iax < nax; iax++ ) {
         astPrimaryFrame( frm, iax, &pfrm, &pax );
         if( IsASkyFrame( pfrm ) ) {
            dom = astGetDomain( pfrm );
            if( dom ) {
               if( !strcmp( dom, "SKY_OFFSETS" ) ){
                  hasoff = 1;
                  if( ifrm == icurr ) icurr_is_offset = 1;
                  iax = nax;
               } else if( !strcmp( dom, "SKY" ) ){
                  hasabs = 1;
                  iax = nax;
               } else if( !strcmp( dom, "SKY_POLE" ) ){
                  hasabs = 1;
                  skyrefis = "POLE";
                  iax = nax;
               } else if( !strcmp( dom, "SKY_ORIGIN" ) ){
                  hasabs = 1;
                  skyrefis = "ORIGIN";
                  iax = nax;
               }
            }
         }
         pfrm = astAnnul( pfrm );
      }
      frm = astAnnul( frm );

      if( skyrefis ) {
         astSetI( fset, "Current", ifrm);
         astSetC( fset, "SkyRefIs", skyrefis );
         astSetI( fset, "Current", icurr );
      }
   }

/* If one or more absolute sky frames were found, then remove any offset
   sky frames. Clear the Ident attribute (that holds the FITS-WCS alternate
   axis description character) for any absoute Frames. */
   if( hasabs && hasoff ) {

      for( ifrm = nfrm; ifrm > 0; ifrm-- ) {
         remove = 0;
         frm = astGetFrame( fset, ifrm );
         nax = astGetNaxes( frm );
         for( iax = 0; iax < nax; iax++ ) {
            astPrimaryFrame( frm, iax, &pfrm, &pax );
            if( IsASkyFrame( pfrm ) ) {
               dom = astGetDomain( pfrm );
               if( dom ) {
                  if( !strcmp( dom, "SKY_OFFSETS" ) ){
                     remove = 1;
                     iax = nax;

                  } else if( !strcmp( dom, "SKY_POLE" ) ||
                             !strcmp( dom, "SKY_ORIGIN" ) ){
                     astClearIdent( frm );
                     astClearDomain( pfrm );

/* If we will be deleting the original current Frame (because it is an
   offset Frame), then mark the first absolute Frame as the new current
   Frame. */
                     if( icurr_is_offset ) {
                        astSetCurrent( fset, ifrm );
                        icurr_is_offset = 0;
                     }
                     iax = nax;
                  }
               }
            }
            pfrm = astAnnul( pfrm );
         }
         frm = astAnnul( frm );

         if( remove ) astRemoveFrame( fset, ifrm );
      }
   }
}

static AstTimeScaleType TimeSysToAst( AstFitsChan *this, const char *timesys,
                                      const char *method, const char *class, int *status ){
/*
*  Name:
*     TimeSysToAst

*  Purpose:
*     Convert a FITS TIMESYS value to an AST TimeFrame timescale value.

*  Type:
*     Private function.

*  Synopsis:
*     AstTimeScaleType TimeSysToAst( AstFitsChan *this, const char *timesys,
*                                    const char *method, const char *class, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function returns the value used by the AST TimeFrame class to
*     represent the timescale specified by the "timesys" parameter, which
*     should hold the value of a FITS TIMESYS keyword. The TIMESYS
*     convention was introduced as part of the Y2K DATE-OBS changes, and
*     is not currently part of the published FITS-WCS conventions.
*
*     If the requested timescale is not supported by AST, then a warning is
*     added to the FitsChan and a value of AST__UTC is returned (but no
*     error is reported).

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     timesys
*        Pointer to the string holding the TIMESYS value. A NULL pointer
*        returns the default timescale of UTC.
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     The equivalent AstTimeScaleType value.
*/

/* Local Variables: */
   AstTimeScaleType result;  /* The returned timescale */
   char buf[ 200 ];          /* Buffer for warning message */

/* Initialise */
   result = AST__UTC;

/* Check the inherited status. */
   if( !astOK ) return result;
   if( !timesys ) {
      result = AST__UTC;
   } else if( !strcmp( timesys, "UTC" ) ) {
      result = AST__UTC;
   } else if( !strcmp( timesys, "UT" ) ) {
      result = AST__UTC;
      Warn( this, "badval", "The original FITS header contained a value of UT "
            "for keyword TIMESYS which is being interpreted as UTC.", method,
            class, status );
   } else if( !strcmp( timesys, "TAI" ) ) {
      result = AST__TAI;
   } else if( !strcmp( timesys, "IAT" ) ) {
      result = AST__TAI;
   } else if( !strcmp( timesys, "ET" ) ) {
      result = AST__TT;
      Warn( this, "badval", "The original FITS header contained a value of ET "
            "for keyword TIMESYS. TT will be used instead.", method, class, status );
   } else if( !strcmp( timesys, "TT" ) ) {
      result = AST__TT;
   } else if( !strcmp( timesys, "TDT" ) ) {
      result = AST__TT;
   } else if( !strcmp( timesys, "TDB" ) ) {
      result = AST__TDB;
   } else if( !strcmp( timesys, "TCG" ) ) {
      result = AST__TCG;
   } else if( !strcmp( timesys, "TCB" ) ) {
      result = AST__TCB;
   } else {
      result = AST__UTC;
      sprintf( buf, "The original FITS header contained a value of %s for "
               "keyword TIMESYS. AST does not support this timescale so "
               "UTC will be used instead.", timesys );
      Warn( this, "badval", buf, method, class, status );
   }

/* Return the result */
   return result;
}

static char *UnPreQuote( const char *string, int *status ) {
/*
*  Name:
*     UnPreQuote

*  Purpose:
*     Reverse the pre-quoting of FITS character data.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     char *UnPreQuote( const char *string, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function reverses the effect of the PreQuote function on a
*     string (apart from any loss of data due to truncation). It
*     should be used to recover the original character data from the
*     pre-quoted version of a string retrieved from a FITS character
*     value associated with a keyword.

*  Parameters:
*     string
*        Pointer to a constant null-terminated string containing the
*        pre-quoted character data.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     Pointer to a dynamically allocated null-terminated string
*     containing the un-quoted character data. The memory holding this
*     string should be freed by the caller (using astFree) when no
*     longer required.

*  Notes:
*     - A NULL pointer value will be returned if this function is
*     invoked wth the global error status set, or if it should fail
*     for any reason.
*/

/* Local Variables: */
   char *result;                 /* Pointer value to return */
   int i1;                       /* Offset of first useful character */
   int i2;                       /* Offest of last useful character */

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

/* Initialise to use the first and last characters in the input
   string. */
   i1 = 0;
   i2 = strlen( string ) - 1;

/* If the string contains at least 2 characters, check if the first
   and last characters are double quotes ("). If so, adjust the
   offsets to exclude them. */
   if ( ( i2 > i1 ) &&
        ( string[ i1 ] == '"' ) && ( string[ i2 ] == '"' ) ) {
      i1++;
      i2--;
   }

/* Make a dynamically allocated copy of the useful part of the
   string. */
   result = astString( string + i1, i2 - i1 + 1 );

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

static int Use( AstFitsChan *this, int set, int helpful, int *status ) {

/*
*  Name:
*     Use

*  Purpose:
*     Decide whether to write a value to a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"

*     int Use( AstFitsChan *this, int set, int helpful, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     This function decides whether a value supplied by a class "Dump"
*     function, via a call to one of the astWrite... protected
*     methods, should actually be written to a FitsChan.
*
*     This decision is based on the settings of the "set" and
*     "helpful" flags supplied to the astWrite... method, plus the
*     attribute settings of the FitsChan.

*  Parameters:
*     this
*        A pointer to the FitsChan.
*     set
*        The "set" flag supplied.
*     helpful
*        The "helpful" value supplied.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     One if the value should be written out, otherwise zero.

*  Notes:
*     - 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: */
   int full;                     /* Full attribute value */
   int result;                   /* Result value to be returned */

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

/* If "set" is non-zero, then so is the result ("set" values must
   always be written out). */
   result = ( set != 0 );

/* Otherwise, obtain the value of the FitsChan's Full attribute. */
   if ( !set ) {
      full = astGetFull( this );

/* If Full is positive, display all values, if zero, display only
   "helpful" values, if negative, display no (un-"set") values. */
      if ( astOK ) result = ( ( helpful && ( full > -1 ) ) || ( full > 0 ) );
   }

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

static int Ustrcmp( const char *a, const char *b, int *status ){
/*
*  Name:
*     Ustrcmp

*  Purpose:
*     A case blind version of strcmp.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int Ustrcmp( const char *a, const char *b, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Returns 0 if there are no differences between the two strings, and 1
*     otherwise. Comparisons are case blind.

*  Parameters:
*     a
*        Pointer to first string.
*     b
*        Pointer to second string.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     Zero if the strings match, otherwise one.

*  Notes:
*     -  This function does not consider the sign of the difference between
*     the two strings, whereas "strcmp" does.
*     -  This function attempts to execute even if an error has occurred.
*/

/* Local Variables: */
   const char *aa;         /* Pointer to next "a" character */
   const char *bb;         /* Pointer to next "b" character */
   int ret;                /* Returned value */

/* Initialise the returned value to indicate that the strings match. */
   ret = 0;

/* Initialise pointers to the start of each string. */
   aa = a;
   bb = b;

/* Loop round each character. */
   while( 1 ){

/* We leave the loop if either of the strings has been exhausted. */
      if( !(*aa ) || !(*bb) ){

/* If one of the strings has not been exhausted, indicate that the
   strings are different. */
         if( *aa || *bb ) ret = 1;

/* Break out of the loop. */
         break;

/* If neither string has been exhausted, convert the next characters to
   upper case and compare them, incrementing the pointers to the next
   characters at the same time. If they are different, break out of the
   loop. */
      } else {
         if( toupper( (int) *(aa++) ) != toupper( (int) *(bb++) ) ){
            ret = 1;
            break;
         }
      }
   }

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

static int Ustrncmp( const char *a, const char *b, size_t n, int *status ){
/*
*  Name:
*     Ustrncmp

*  Purpose:
*     A case blind version of strncmp.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int Ustrncmp( const char *a, const char *b, size_t n, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     Returns 0 if there are no differences between the first "n"
*     characters of the two strings, and 1 otherwise. Comparisons are
*     case blind.

*  Parameters:
*     a
*        Pointer to first string.
*     b
*        Pointer to second string.
*     n
*        The maximum number of characters to compare.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     Zero if the strings match, otherwise one.

*  Notes:
*     -  This function does not consider the sign of the difference between
*     the two strings, whereas "strncmp" does.
*     -  This function attempts to execute even if an error has occurred.
*/

/* Local Variables: */
   const char *aa;         /* Pointer to next "a" character */
   const char *bb;         /* Pointer to next "b" character */
   int i;                  /* Character index */
   int ret;                /* Returned value */

/* Initialise the returned value to indicate that the strings match. */
   ret = 0;

/* Initialise pointers to the start of each string. */
   aa = a;
   bb = b;

/* Compare up to "n" characters. */
   for( i = 0; i < (int) n; i++ ){

/* We leave the loop if either of the strings has been exhausted. */
      if( !(*aa ) || !(*bb) ){

/* If one of the strings has not been exhausted, indicate that the
   strings are different. */
         if( *aa || *bb ) ret = 1;

/* Break out of the loop. */
         break;

/* If neither string has been exhausted, convert the next characters to
   upper case and compare them, incrementing the pointers to the next
   characters at the same time. If they are different, break out of the
   loop. */
      } else {
         if( toupper( (int) *(aa++) ) != toupper( (int) *(bb++) ) ){
            ret = 1;
            break;
         }
      }
   }

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

static void Warn( AstFitsChan *this, const char *condition, const char *text,
                  const char*method, const char *class, int *status ){
/*
*  Name:
*     Warn

*  Purpose:
*     Store warning cards in a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int Warn( AstFitsChan *this, const char *condition, const char *text,
*               const char*method, const char *class, int *status );

*  Class Membership:
*     FitsChan member function.

*  Description:
*     If the Warnings attribute indicates that occurences of the specified
*     condition should be reported, the supplied text is split into lines
*     and stored in the FitsChan as a series of ASTWARN cards, in front
*     of the current card. If the specified condition is not being reported,
*     this function returns without action.

*  Parameters:
*     this
*        The FitsChan. If NULL, this function returns without action.
*     condition
*        Pointer to a string holding a lower case condition name.
*     text
*        Pointer to a string holding the text of the warning.
*     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.
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   char buff[ AST__FITSCHAN_FITSCARDLEN + 1 ]; /* Buffer for new card text */
   const char *a;        /* Pointer to 1st character in next card */
   const char *b;        /* Pointer to terminating null character */
   const char *c;        /* Pointer to last character in next card */
   int exists;           /* Has the supplied warning already been issued? */
   int icard;            /* Index of original card */
   int nc;               /* No. of characters in next card */

/* Check the inherited status, warning text, FitsChan and Clean attribute. */
   if( !astOK || !text || !text[0] || !this || astGetClean( this ) ) return;

/* Ignore the warning if the supplied condition is not contained within
   the list of conditions to be reported in this way (given by the
   Warnings attribute). */
   if( FullForm( astGetWarnings( this ), condition, 0, status ) >= 0 ){

/* If found, store the warning in the parent Channel structure. */
      astAddWarning( this, 1, "%s", method, status, text );

/* For historical reasons, warnings are also stored in the FitsChan as a
   set of FITS cards... First save the current card index, and rewind the
   FitsChan. */
      icard = astGetCard( this );
      astClearCard( this );

/* Break the supplied text into lines and check the FitsChan to see if
   a block of adjacent ASTWARN cards with these lines already exist
   within the FitsChan. Assume they do until proven otherwise. */
      exists = 1;
      a = text;
      b = a + strlen( text );
      while( a < b ){

/* Each card contains about 60 characters of the text. Get a pointer to
   the nominal last character in the next card. */
         c = a + 60;

/* If this puts the last character beyond the end of the text, use the
   last character before the null as the last character in the card. */
         if( c >= b ) {
            c = b - 1;

/* Otherwise, if the last character is not a space, move the last
   character backwards to the first space. This avoids breaking words
   across cards. */
         } else {
            while( !isspace( *c ) && c > a ) c--;
         }

/* Copy the text into a null terminated buffer. */
         nc = c - a + 1;
         strncpy( buff, a, nc );
         buff[ nc ] = 0;

/* If this is the first line, search the entire FitsChan for an ASTWARN card
   with this text. If not, indiate that the supplied text needs to be
   stored in the FitsChan, and break out of the loop. */
         if( a == text ) {
            exists = 0;
            while( !exists &&
                   FindKeyCard( this, "ASTWARN", method, class, status ) ) {
               if( !strcmp( (const char *) CardData( this, NULL, status ), buff ) ) {
                  exists = 1;
               }
               MoveCard( this, 1, method, class, status );
            }
            if( !exists ) break;

/* If this is not the first line, see if the next card in the FitsChan is
   an ASTWARN card with this text. If not, indiate that the supplied text
   needs to be stored in the FitsChan, and break out of the loop. */
         } else {
            if( !strcmp( CardName( this, status ), "ASTWARN" ) &&
                !strcmp( (const char *) CardData( this, NULL, status ), buff ) ) {
               MoveCard( this, 1, method, class, status );
            } else {
               exists = 0;
               break;
            }
         }

/* Set the start of the next bit of the text. */
         a = c + 1;
      }

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

/* We only add new cards to the FitsChan if they do not already exist. */
      if( !exists ) {

/* Break the text into lines using the same algorithm as above, and store
   each line as a new ASTWARN card. Start with a blank ASTWARN card. */
         astSetFitsS( this, "ASTWARN", " ", NULL, 0 );

/* Loop until the entire text has been written out. */
         a = text;
         b = a + strlen( text );
         while( a < b ){

/* Each card contains about 60 characters of the text. Get a pointer to
   the nominal last character in the next card. */
            c = a + 60;

/* If this puts the last character beyond the end of the text, use the
   last character before the null as the last character in the card. */
            if( c >= b ) {
               c = b - 1;

/* Otherwise, if the last character is not a space, move the last
   character backwards to the first space. This avoids breaking words
   across cards. */
            } else {
               while( !isspace( *c ) && c > a ) c--;
            }

/* Copy the text into a null terminated buffer. */
            nc = c - a + 1;
            strncpy( buff, a, nc );
            buff[ nc ] = 0;

/* Store the buffer as the next card. */
            astSetFitsS( this, "ASTWARN", buff, NULL, 0 );

/* Set the start of the next bit of the text. */
            a = c + 1;
         }

/* Include a final blank card. */
         astSetFitsS( this, "ASTWARN", " ", NULL, 0 );
      }
   }
}

static int WATCoeffs( const char *watstr, int iaxis, double **cvals,
                      int **mvals, int *ok, int *status ){
/*
*  Name:
*     WATCoeffs

*  Purpose:
*     Get the polynomial coefficients from the lngcor or latcor component
*     of an IRAF WAT string.

*  Type:
*     Private function.

*  Synopsis:
*     int WATCoeffs( const char *watstr, int iaxis, double **cvals,
*                    int **mvals, int *ok, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function extracts the polynomial coefficients from a supplied
*     string containing the concatenated values of a set of IRAF "WAT"
*     keywords, such as used for the IRAF-specific TNX and ZPX projections.
*     The coefficients are returned in the form of a set of PVi_m values
*     for a TPN projection.

*  Parameters:
*     watstr
*        The concatentated WAT keyword values.
*     iaxis
*        Zero based index of the axis to which the WAT keywords refer (0
*        or 1).
*     cvals
*        Location at which to return a pointer to a dynamically allocated
*        list of coefficient values, or NULL if no lngcor/latcor values
*        were found in the WAT string. Free using astFree.
*     mvals
*        Location at which to return a pointer to a dynamically allocated
*        list of coefficient indices, or NULL if no lngcor/latcor values
*        were found in the WAT string. Free using astFree.
*     ok
*        Pointer to an in which is returned set to zero if the polynomial
*        in the supplied WAT string cannot be represented using TPN form.
*        Non-zero otherwise.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     The size of the returned cvals and mvals arrays.

*/

/* Local Variables: */
   char **w1;
   char **w2;
   double *coeff;
   double *pc;
   int result;
   double dval;
   double etamax;
   double etamin;
   double ximax;
   double ximin;
   int cheb;
   int etaorder;
   int iword;
   int m;
   int mn;
   int nword;
   int order;
   int porder;
   int xiorder;
   int ires;

/* The number of lngcor/latcor values needed for each order. */
   static const int nab[] = {1,3,6,10,15,21,28,36};

/* Initialise the pointer to the returned Mapping. */
   result = 0;
   *mvals = NULL;
   *cvals = NULL;
   *ok = 1;

/* Other initialisation to avoid compiler warnings. */
   etamin = 0.0;
   etamax = 0.0;
   ximax = 0.0;
   ximin = 0.0;
   order = 0;

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

/* Look for cor = "..." and extract the "..." string. */
   w1 = astChrSplitRE( watstr, "cor *= *\"(.*)\"", &nword, NULL );
   if( w1 ) {

/* Split the "..." string into words. */
      w2 = astChrSplit( w1[ 0 ], &nword );
      if( w2 ) {

/* Initialise flags. */
         cheb = 0;
         xiorder = 0;
         etaorder = 0;
         coeff = NULL;
         porder = -1;

/* Loop round each word. Break early if we find that the projection
   cannot be represented as a TPN projection. */
         for( iword = 0; iword < nword && *ok; iword++ ) {

/* Convert the word to double. */
            dval = astChr2Double( w2[ iword ] );
            if( dval == AST__BAD ) {
               astError( AST__BDFTS, "astRead(FitsChan): Failed to read a "
                         "numerical value from sub-string \"%s\" found in "
                         "an IRAF \"WAT...\" keyword.", status,  w2[ iword ] );
               break;
            }

/* The first value gives the correction surface type. We can only handle type
   1 (chebyshev) or 3 (simple polynomial). */
            if( iword == 0 ){
               if( dval == 1.0 ) {
                  cheb = 1;
               } else if( dval == 2.0 ) {
                  *ok = 0;
               }

/* The second and third numbers gives the orders of the polynomial in X
   and Y. We can only handle cases in which the orders are the same on
   both axes, and greater than 0 and less than 8. Store a pointer to the
   first TAN projection parameter index to use. */
            } else if( iword == 1 ){
               order = dval;
               porder = order - 1;

            } else if( iword == 2 ){
               if( dval - 1 != porder || dval < 0 || dval > 7 ) *ok = 0;

/* The fourth number defines the type of cross-terms. We can only handle
   type 2 (half-cross terms). */
            } else if( iword == 3 ){
               if( dval != 2.0 ) *ok = 0;

/* We now know the maximum number of co-efficients that may be needed.
   Allocate memory to hold them, and fill it with zeros. They are
   stored in this array as if full cross-terms have been supplied (the
   unspecified coefficients retain their initialised value of zero).  */
               coeff = astCalloc( order*order, sizeof( double ) );
               if( !astOK ) break;

/* The next 4 numbers describe the region of validity of the fits in IRAF's
   xi and eta space, e.g. ximin, ximax, etamin, etamax. We only uses
   these if we have a chebyshev polynomial. */
            } else if( iword == 4 ) {
               ximin = dval;

            } else if( iword == 5 ) {
               ximax = dval;

            } else if( iword == 6 ) {
               etamin = dval;

            } else if( iword == 7 ) {
               etamax = dval;

/* The remaining terms are the coefficients of the polynomial terms. */
            } else if( iword > 7 ){

/* Store the coefficient in the array. They are stored so that power of
   xi increases fastest. */
               coeff[ xiorder + order*etaorder ] = dval;

/* Increment the powers of the next coefficient. We know we only have half
   cross-terms, so the maximum power of xi decreases from order to zero
   as we move through the list of coefficients. */
               if( ++xiorder == order - etaorder ) {
                  xiorder = 0;
                  etaorder++;
               }
            }
         }

/* Check that all the required co-efficients were found */
         if( porder == -1 || nword != 8 + nab[ porder ] ) *ok = 0;

/* If we can handle the projection, proceed. */
         if( *ok && astOK ) {

/* If the coefficients were supplied in chebyshev form, convert to simple
   form. */
            if( cheb ) {
               double *tcoeff = coeff;
               coeff = Cheb2Poly( tcoeff, order, order, ximin,
                                  ximax, etamin, etamax, status );
               tcoeff = astFree( tcoeff );
            }

/* The polynomials provide a "correction* to be added to the supplied X and
   Y values. Therefore increase the linear co-efficients by 1 on the axis
   that is being calculated. */
            coeff[ iaxis ? order : 1 ] += 1.0;

/* Loop round all coefficients, keeping track of the power of xi and eta
   for the current coefficient. */
            pc = coeff;
            for( etaorder = 0; etaorder < order; etaorder++ ) {
               for( xiorder = 0; xiorder < order; xiorder++,pc++ ) {

/* Skip coefficients that have their default values (zero, except for the
   linear coefficients which default to 1.0). */
                  mn = xiorder + etaorder;
                  if( *pc != ( mn == 1 ? 1.0 : 0.0 ) ) {

/* Find the "m" index of the PVi_m FITS keyword for the current
   coefficient. */
                     m = mn*( 1 + mn )/2 + mn/2;
                     m += iaxis ? xiorder : etaorder;

/* Append the PV and m values to the ends of the returned arrays. */
                     ires = result++;
                     *cvals = astGrow( *cvals, sizeof( double ), result );
                     *mvals = astGrow( *mvals, sizeof( int ), result );
                     if( astOK ) {
                        (*cvals)[ ires ] = *pc;
                        (*mvals)[ ires ] = m;
                     }
                  }
               }
            }

/* Free coefficients arrays */
            coeff = astFree( coeff );
         }

/* Free resources */
         w2 = astFree( w2 );
      }
      w1 = astFree( w1 );
   }

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

static AstMatrixMap *WcsCDeltMatrix( FitsStore *store, char s, int naxes,
                                     const char *method, const char *class, int *status ){
/*
*  Name:
*     WcsCDeltMatrix

*  Purpose:
*     Create a MatrixMap representing the CDELT scaling.

*  Type:
*     Private function.

*  Synopsis:
*     AstMatrixMap *WcsCDeltMatrix( FitsStore *store, char s, int naxes,
*                                   const char *method, const char *class, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     A diagonal MatrixMap representing the FITS "CDELT" keywords is
*     returned.

*  Parameters:
*     store
*        A structure containing values for FITS keywords relating to
*        the World Coordinate System.
*     s
*        A character s 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).
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to the created MatrixMap or a NULL pointer if an
*     error occurred.
*/

/* Local Variables: */
   AstMatrixMap *new;       /* The created MatrixMap */
   double *el;              /* Pointer to next matrix element */
   double *mat;             /* Pointer to matrix array */
   int i;                   /* Pixel axis index */

/* Initialise/ */
   new = NULL;

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

/* Allocate memory for the diagonal matrix elements. */
   mat = (double *) astMalloc( sizeof(double)*naxes );
   if( astOK ){

/* Fill the matrix diagonal with values from the FitsStore. */
      el = mat;
      for( i = 0; i < naxes; i++ ){

/* Get the CDELTi value for this axis. Missing terms can be defaulted so
   do not report an error if the required value is not present in the
   FitsStore. */
         *el = GetItem( &(store->cdelt), i, 0, s, NULL, method, class, status );

/* Missing terms default to to 1.0. */
         if( *el == AST__BAD ) *el = 1.0;

/* Move on to the next matrix element. */
         el++;
      }

/* Create the diagional matrix. */
      new = astMatrixMap( naxes, naxes, 1, mat, "", status );

/* Report an error if the inverse transformation is undefined. */
      if( !astGetTranInverse( new ) && astOK ) {
        astError( AST__BDFTS, "%s(%s): Unusable CDELT values found "
                  "in the FITS-WCS header - one or more values are zero.", status, method, class );
      }

/* Release the memory used to hold the matrix. */
      mat = (double *) astFree( (void *) mat );
   }

/* If an error has occurred, attempt to annul the returned MatrixMap. */
   if( !astOK ) new = astAnnul( new );

/* Return the MatrixMap. */
   return new;
}

static AstMapping *WcsCelestial( AstFitsChan *this, FitsStore *store, char s,
                                 AstFrame **frm, AstFrame *iwcfrm, double *reflon, double *reflat,
                                 AstSkyFrame **reffrm, AstMapping **tabmap,
                                 int *tabaxis, const char *method,
                                 const char *class, int *status ){
/*
*  Name:
*     WcsCelestial

*  Purpose:
*     Create a Mapping from intermediate world coords to celestial coords
*     as described in a FITS header.

*  Type:
*     Private function.

*  Synopsis:
*     AstMapping *WcsCelestial( AstFitsChan *this, FitsStore *store, char s,
*                               AstFrame **frm, AstFrame *iwcfrm, double *reflon, double *reflat,
*                               AstSkyFrame **reffrm, , AstMapping **tabmap,
*                               int *tabaxis, const char *method,
*                               const char *class, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function interprets the contents of the supplied FitsStore
*     structure, looking for world coordinate axes which describe positions
*     on the sky. If a pair of such longitude/latitude axes is found, a
*     Mapping is returned which transforms the corresponding intermediate
*     world coordinates to celestial world coordinates (this mapping leaves
*     any other axes unchanged). It also, modifies the supplied Frame to
*     describe the axes (again, other axes are left unchanged). If no
*     pair of celestial axes is found, a UnitMap is returned, and the
*     supplied Frame is left unchanged.

*  Parameters:
*     this
*        The FitsChan.
*     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').
*     frm
*        The address of a location at which to store a pointer to the
*        Frame describing the world coordinate axes.
*     iwcfrm
*        A pointer to the Frame describing the intermediate world coordinate
*        axes. The properties of this Frame may be changed on exit.
*     reflon
*        Address of a location at which to return the celestial longitude
*        at the reference point. It is returned as AST__BAD if no
*        celestial coordinate frame is found.
*     reflat
*        Address of a location at which to return the celestial latitude
*        at the reference point. It is returned as AST__BAD if no
*        celestial coordinate frame is found.
*     reffrm
*        Address of a location at which to return a pointer to a SkyFrame
*        which define the reference values returned in reflon and reflat.
*        It is returned as NULL if no celestial coordinate frame is found.
*     tabmap
*        Address of a pointer to a Mapping describing any -TAB
*        transformations to be applied to the results of the Mapping returned
*        by this function. If any celestial axes are found, the supplied
*        Mapping is modified so that the celestial axes produce values in
*        radians rather than degrees. NULL if no axes are described by -TAB.
*     tabaxis
*        Pointer to an array of flags, one for each WCS axis, indicating
*        if the corresponding WCS axis is described by the -TAB algorithm.
*        NULL if no axes are described by -TAB.
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to the Mapping.
*/

/* Local Variables: */
   astDECLARE_GLOBALS        /* Declare the thread specific global data */
   AstFrame *ofrm;           /* Pointer to a Frame */
   AstMapping *map1;         /* Pointer to a Mapping */
   AstMapping *map2;         /* Pointer to a Mapping */
   AstMapping *map3;         /* Pointer to a Mapping */
   AstMapping *map4;         /* Pointer to a Mapping */
   AstMapping *ret;          /* Pointer to the returned Mapping */
   AstMapping *newmap;       /* Modified PIXEL->IWC Mapping */
   AstMapping *shiftmap;     /* ShiftMap from IWC to PPC */
   AstSkyFrame *sfrm;        /* Pointer to a SkyFrame */
   char *ctype;              /* Pointer to CTYPE string */
   char *keyname;            /* Pointer to keyword name string */
   char buf[300];            /* Text buffer */
   char latctype[MXCTYPELEN];/* Latitude CTYPE keyword value */
   char latkey[10];          /* Latitude CTYPE keyword name */
   char lattype[4];          /* Buffer for celestial system */
   char lonctype[MXCTYPELEN];/* Longitude CTYPE keyword value */
   char lonkey[10];          /* Longitude CTYPE keyword name */
   char lontype[4];          /* Buffer for celestial system */
   double *shifts;           /* Array holding axis shifts */
   double *ina;              /* Pointer to memory holding input position A */
   double *inb;              /* Pointer to memory holding input position B */
   double *mat;              /* Pointer to data for deg->rad scaling matrix */
   double *outa;             /* Pointer to memory holding output position A */
   double *outb;             /* Pointer to memory holding output position B */
   double latval;            /* CRVAL for latitude axis */
   double lonval;            /* CRVAL for longitude axis */
   double pv;                /* Projection parameter value */
   double x0;                /* IWC X at the projection fiducial point */
   double y0;                /* IWC Y at the projection fiducial point */
   int *axes;                /* Point to a list of axis indices */
   int axlat;                /* Index of latitude physical axis */
   int axlon;                /* Index of longitude physical axis */
   int carlin;               /* Assume native and WCS axes are the same? */
   int ctlen;                /* Length of CTYPE string */
   int gotax;                /* Celestial axis found? */
   int i;                    /* Loop count */
   int j;                    /* Axis index */
   int latprj;               /* Latitude projection type identifier */
   int lonprj;               /* Longitude projection type identifier */
   int m;                    /* Parameter index */
   int mxpar_lat;            /* Max. projection parameter index on lat axis */
   int mxpar_lon;            /* Max. projection parameter index on lon axis */
   int naxes;                /* Number of axes */
   int nc;                   /* String length */
   int np;                   /* Max parameter index */
   int prj;                  /* Projection type identifier */

/* Initialise the returned values. */
   ret = NULL;
   *reflon = AST__BAD;
   *reflat = AST__BAD;
   *reffrm = NULL;

/* Other initialisation to avoid compiler warnings. */
   map1 = NULL;

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Get the number of physical axes. */
   naxes = astGetNaxes( *frm );

/* See if CAR projections should be interpreted in the old fashioned way
   (i.e native coords are always the same as WCS coords, so no need for
   any rotation). */
   carlin = astGetCarLin( this );

/* The first major section sees if the physical axes include a pair of
   longitude/latitude celestial axes.
   ================================================================= */

/* We have not yet found any celestial axes. */
   axlon = -1;
   axlat = -1;
   latprj = AST__WCSBAD;
   lonprj = AST__WCSBAD;
   prj = AST__WCSBAD;

/* First, we examine the CTYPE values in the FitsStore to determine
   which axes are the longitude and latitude axes, and what the celestial
   co-ordinate system and projection are. Loop round the physical axes,
   getting each CTYPE value. */
   for( i = 0; i < naxes && astOK; i++ ){
      keyname =  FormatKey( "CTYPE", i + 1, -1, s, status );
      ctype = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );

/* Issue a warning if no CTYPE value was found. */
      if( !ctype ) {
         sprintf( buf, "Axis type keywords (CTYPE, etc) were not found "
                  "for one or more axes in the original FITS header. These "
                  "axes will be assumed to be linear." );
         Warn( this, "noctype", buf, method, class, status );
      } else {

/* See if this is a longitude axis (e.g. if the first 4 characters of CTYPE
   are "RA--" or "xLON" or "yzLN" ). If so, store the value of "x" or "yz"
   (or "EQU" for equatorial coordinates) in variable "type" to indicate which
   coordinate system is being used. */
         nc = strlen( ctype );
         gotax = 0;
         if( !strcmp( ctype, "RA" ) || !strncmp( ctype, "RA--", 4 ) ){
            strcpy( wcscelestial_type, "EQU" );
            gotax = 1;
         } else if( !strcmp( ctype, "AZ" ) || !strncmp( ctype, "AZ--", 4 ) ){
            strcpy( wcscelestial_type, "AZL" );
            gotax = 1;
         } else if( nc > 1 && ( !strcmp( ctype + 1, "LON" ) ||
                                !strncmp( ctype + 1, "LON-", 4 ) ) ){
            wcscelestial_type[ 0 ] = ctype[ 0 ];
            wcscelestial_type[ 1 ] = 0;
            gotax = 1;
         } else if( nc > 2 && ( !strcmp( ctype + 2, "LN" ) ||
                                !strncmp( ctype + 2, "LN-", 3 ) ) ){
            wcscelestial_type[ 0 ] = ctype[ 0 ];
            wcscelestial_type[ 1 ] = ctype[ 1 ];
            wcscelestial_type[ 2 ] = 0;
            gotax = 1;
         }

/* If this is a longitude axis... */
         if( gotax ){

/* Check that this is the first longitude axis to be found. */
            if( axlon == -1 ){

/* Find the projection type as specified by the last 4 characters
   in the CTYPE keyword value. AST__WCSBAD is stored in "prj" if the
   last 4 characters do not specify a known WCS projection, but no error
   is reported. Assume simple linear axes if no projection code is
   supplied. Note, AST__WCSBAD is used to indicate a TAB header. */
               ctlen = strlen( ctype );
               if( ctlen > 4 ) {
                  prj = astWcsPrjType( ctype + ctlen - 4 );
               } else if( tabmap && *tabmap ) {
                  prj = AST__WCSBAD;
               } else {
                  prj = AST__CAR;
                  carlin = 1;
               }

/* Report an error if the projection is unknown. */
               if( prj == AST__WCSBAD && ctlen > 4 ){
                  astError( AST__BDFTS, "%s(%s): FITS keyword '%s' refers to "
                        "an unknown projection type '%s'.", status, method, class,
                         keyname, ctype + ctlen - 4 );
                  break;
               }

/* Store the index of the longitude axis, type of longitude, etc. */
               axlon = i;
               strcpy( lontype, wcscelestial_type );
               strcpy( lonkey, keyname );
               strcpy( lonctype, ctype );
               lonprj = prj;

/* If another longitude axis has already been found, report an error. */
            } else {
               astError( AST__BDFTS, "%s(%s): FITS keywords '%s' (='%s') "
                  "and '%s' (='%s') both describe celestial longitude axes.", status,
                  method, class, keyname, ctype, lonkey, lonctype );
               break;
            }
         }

/* Do the same for the latitude axis, checking for "DEC-" and "xLAT" and
  "yzLT". */
         gotax = 0;
         if( !strcmp( ctype, "DEC" ) || !strncmp( ctype, "DEC-", 4 ) ){
            strcpy( wcscelestial_type, "EQU" );
            gotax = 1;
         } else if( !strcmp( ctype, "EL" ) || !strncmp( ctype, "EL--", 4 ) ){
            strcpy( wcscelestial_type, "AZL" );
            gotax = 1;
         } else if( !strcmp( ctype + 1, "LAT" ) || !strncmp( ctype + 1, "LAT-", 4 ) ){
            wcscelestial_type[ 0 ] = ctype[ 0 ];
            wcscelestial_type[ 1 ] = 0;
            gotax = 1;
         } else if( !strcmp( ctype + 2, "LT" ) || !strncmp( ctype + 2, "LT-", 3 ) ){
            wcscelestial_type[ 0 ] = ctype[ 0 ];
            wcscelestial_type[ 1 ] = ctype[ 1 ];
            wcscelestial_type[ 2 ] = 0;
            gotax = 1;
         }
         if( gotax ){
            if( axlat == -1 ){
               ctlen = strlen( ctype );
               if( ctlen > 4 ) {
                  prj = astWcsPrjType( ctype + ctlen - 4 );
               } else if( tabmap && *tabmap ) {
                  prj = AST__WCSBAD;
               } else {
                  prj = AST__CAR;
                  carlin = 1;
               }

               if( prj == AST__WCSBAD && ctlen > 4 ){
                  astError( AST__BDFTS, "%s(%s): FITS keyword '%s' refers to "
                        "an unknown projection type '%s'.", status, method, class,
                         keyname, ctype + ctlen - 4 );
                  break;
               }
               axlat = i;
               strcpy( lattype, wcscelestial_type );
               strcpy( latkey, keyname );
               strcpy( latctype, ctype );
               latprj = prj;
            } else {
               astError( AST__BDFTS, "%s(%s): FITS keywords '%s' (='%s') "
                  "and '%s' (='%s') both describe celestial latitude axes.", status,
                  method, class, keyname, ctype, latkey, latctype );
               break;
            }
         }
      }
   }

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

/* If both longitude and latitude axes were found... */
      if( axlat != -1 && axlon != -1 ){

/* Report an error if they refer to different celestial coordinate systems. */
         if( strcmp( lattype, lontype ) ){
            astError( AST__BDFTS, "%s(%s): FITS keywords '%s' and '%s' "
                      "indicate different celestial coordinate systems "
                      "('%s' and '%s').", status, method, class, latkey, lonkey,
                      latctype, lonctype );

/* Otherwise report an error if longitude and latitude axes use different
   projections. */
         } else if( lonprj != latprj ){
            astError( AST__BDFTS, "%s(%s): FITS keywords '%s' and '%s' "
                      "indicate different projections ('%s' and '%s').", status,
                      method, class, latkey, lonkey, latctype, lonctype );
         }

/* If only one axis has been provided without the other (e.g. longitude but no
   latitude), report an error. */
      } else if( axlat != -1 && prj != AST__WCSBAD ){
         astError( AST__BDFTS, "%s(%s): A latitude axis ('%s') was found "
                   "without a corresponding longitude axis.", status, method, class,
                   latctype );
      } else if( axlon != -1 && prj != AST__WCSBAD ){
         astError( AST__BDFTS, "%s(%s): A longitude axis ('%s') was found "
                   "without a corresponding latitude axis.", status, method, class,
                   lonctype );
      }
   }

/* If a pair of matching celestial axes was not found, return a UnitMap
   and leave the Frame unchanged.
   ===================================================================== */
   if( axlat == -1 || axlon == -1 ) {
      ret = (AstMapping *) astUnitMap( naxes, "", status );

/* The rest of this function deals with creating a Mapping from
   intermediate world coords to celestial coords, and modifying the
   Frame appropriately.
   ===================================================================== */
   } else if( astOK ) {

/* Create a MatrixMap which scales the intermediate world coordinate axes
   corresponding to the longitude and latitude axes from degrees to radians.
   Only do this if a projection was supplied. */
      if( latprj != AST__WCSBAD ) {
         mat = (double *) astMalloc( sizeof(double)*naxes );
         if( mat ){
            for( i = 0; i < naxes; i++ ){
               if( i == axlat || i == axlon ){
                  mat[ i ] = AST__DD2R;
               } else {
                  mat[ i ] = 1.0;
               }
            }
            map1 = (AstMapping *) astMatrixMap( naxes, naxes, 1, mat, "", status );
            mat = (double *) astFree( (void *) mat );
         }
      } else {
         map1 = (AstMapping *) astUnitMap( naxes, " ", status );
      }

/* If the projection is a CAR projection, but the CarLin attribute is
   set, then we consider the CAR projection to be a simple linear mapping
   of pixel coords to celestial coords. Do this by using a WcsMap with no
   projection. All axes will then be treated as linear and non-celestial.
   If no projection was specified (i.e. if prj == AST__WCSBAD, as is the
   case when using -TAB for instance) then do the same but use a UnitMap
   instead of a WcsMap. */
      map3 = NULL;
      if( ( latprj == AST__CAR && carlin ) || latprj == AST__WCSBAD ) {
         if( latprj == AST__CAR ) {
            map2 = (AstMapping *) astWcsMap( naxes, AST__WCSBAD, axlon + 1,
                                             axlat + 1, "", status );
         } else {
            map2 = (AstMapping *) astUnitMap( naxes, "", status );
         }

/* Now create a WinMap which adds on the CRVAL values to each axis. */
         ina = astMalloc( sizeof(double)*naxes );
         inb = astMalloc( sizeof(double)*naxes );
         outa = astMalloc( sizeof(double)*naxes );
         outb = astMalloc( sizeof(double)*naxes );
         if( astOK ) {
            for( i = 0; i < naxes; i++ ) {
               ina[ i ] = 0.0;
               inb[ i ] = 1.0;
               outa[ i ] = 0.0;
               outb[ i ] = 1.0;
            }
            lonval = GetItem( &(store->crval), axlon, 0, s, NULL, method, class, status );
            if( lonval != AST__BAD ) {

/* For recognised projections the CRVAL value is required to be degrees,
   so convert to radians. For other algorithms (e.g. -TAB) the CRVAL
   values are in unknown units so retain their original scaling. */
               *reflon = ( latprj == AST__CAR ) ? lonval*AST__DD2R : lonval;

               outa[ axlon ] += *reflon;
               outb[ axlon ] += *reflon;
            } else {
               outa[ axlon ] = AST__BAD;
               outb[ axlon ] = AST__BAD;
            }

            latval = GetItem( &(store->crval), axlat, 0, s, NULL, method, class, status );
            if( latval != AST__BAD ) {
               *reflat = ( latprj == AST__CAR ) ? latval*AST__DD2R : latval;
               outa[ axlat ] += *reflat;
               outb[ axlat ] += *reflat;
            } else {
               outa[ axlat ] = AST__BAD;
               outb[ axlat ] = AST__BAD;
            }

            map3 = (AstMapping *) astWinMap( naxes, ina, inb, outa, outb, "", status );

         }
         ina = astFree( ina );
         inb = astFree( inb );
         outa = astFree( outa );
         outb = astFree( outb );

/* Otherwise, create a WcsMap with the specified projection. The WcsMap
   is equivalent to a unit mapping for all axes other than "axlat" and
   "axlon". */
      } else {

/* Get the highest index ("m" value) of any supplied PVi_m projection
   parameters (on any axes). */
         np = GetMaxJM( &(store->pv), s, status );

/* Create the WcsMap */
         map2 = (AstMapping *) astWcsMap( naxes, latprj, axlon + 1,
                                          axlat + 1, "", status );

/* If the FITS header contains any projection parameters, store them in
   the WcsMap. */
         mxpar_lat = astGetPVMax( map2, axlat );
         mxpar_lon = astGetPVMax( map2, axlon );
         for( m = 0; m <= np; m++ ){
            pv = GetItem( &(store->pv), axlat, m, s, NULL, method, class, status );
            if( pv != AST__BAD ) {
               if( m <= mxpar_lat ) {
                  astSetPV( map2, axlat, m, pv );
               } else {
                  sprintf( buf, "Projection parameter PV%d_%d found, "
                           "but is not used by %s projections.", axlat + 1,
                           m, astWcsPrjName( astGetWcsType( map2 ) ) );
                  Warn( this, "badpv", buf, method, class, status );
               }
            }
            pv = GetItem( &(store->pv), axlon, m, s, NULL, method, class, status );
            if( pv != AST__BAD ) {
               if( m <= mxpar_lon ) {
                  astSetPV( map2, axlon, m, pv );
               } else {
                  sprintf( buf, "Projection parameter PV%d_%d found, "
                           "but is not used by %s projections.", axlon + 1,
                           m, astWcsPrjName( astGetWcsType( map2 ) ) );
                  Warn( this, "badpv", buf, method, class, status );
               }
            }
         }

/* Invert the WcsMap to get a DEprojection. */
         astInvert( map2 );

/* Now produce a Mapping which converts the axes holding "Native Spherical
   Coords" into "Celestial Coords", leaving all other axes unchanged. */
         map3 = WcsNative( this, store, s, (AstWcsMap *) map2, -1, -1,
                           method, class, status );

/* Retrieve and store the reference longitude and latitude. */
         *reflon = GetItem( &(store->crval), axlon, 0, s, NULL, method, class, status );
         if( *reflon != AST__BAD ) *reflon *= AST__DD2R;
         *reflat = GetItem( &(store->crval), axlat, 0, s, NULL, method, class, status );
         if( *reflat != AST__BAD ) *reflat *= AST__DD2R;
      }

/* If projection parameter PVi_0a for the longitude axis "i" is non-zero,
   then there is a shift of origin between Intermediate World Coords, IWC,
   (the CRPIXi values correspond to the origin of IWC), and Projection Plane
   Coords, PPC (these are the cartesian coordinates used by the WcsMap).
   This shift of origin results in the fiducial point specified by the
   CRVALi values mapping onto the pixel reference point specified by the
   CRPIXj values. In this case we need to add a Mapping which implements
   the shift of origin. Note, the AST-specific "TPN" projection cannot use
   this convention since it uses PVi_0 to hold a polynomial correction term. */
      if( latprj != AST__WCSBAD && astGetWcsType( map2 ) != AST__TPN &&
          astGetPV( map2, axlon, 0 ) != 0.0 ) {

/* Find the projection plane coords corresponding to the fiducial point
   of the projection. This is done by using the inverse WcsMap to convert
   the native spherical coords at the fiducial point into PPC (x,y), which
   are returned in units of radians (not degrees). */
         GetFiducialPPC( (AstWcsMap *) map2, &x0, &y0, status );
         if( x0 != AST__BAD && y0 != AST__BAD ) {

/* Allocate resources. */
            shifts = astMalloc( sizeof( double )*(size_t) naxes );

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

/* Create a Mapping (a ShiftMap) from IWC to PPC. */
               for( i = 0; i < naxes; i++ ) shifts[ i ] = 0.0;
               shifts[ axlon ] = x0;
               shifts[ axlat ] = y0;
               shiftmap = (AstMapping *) astShiftMap( naxes, shifts, "", status );

/* Produce a CmpMap which combines "map1" (which converts degrees to
   radians on the celestial axes) with the above ShiftMap. */
               newmap = (AstMapping *) astCmpMap( map1, shiftmap, 1, "", status );

/* Annul the component Mappings and use the new one in place of map1. */
               shiftmap = astAnnul( shiftmap );
               map1 = astAnnul( map1 );
               map1 = newmap;
            }

/* Free resources. */
            shifts = astFree( shifts );
         }
      }

/* Now concatenate the Mappings to produce the returned Mapping. */
      map4 = (AstMapping *) astCmpMap( map1, map2, 1, "", status );
      ret = (AstMapping *) astCmpMap( map4, map3, 1, "", status );

/* Annul the component Mappings. */
      map1 = astAnnul( map1 );
      map2 = astAnnul( map2 );
      map3 = astAnnul( map3 );
      map4 = astAnnul( map4 );

/* We now make changes to the supplied Frame so that the longitude and
   latitude axes are described by a SkyFrame. First create an appropriate
   SkyFrame. */
      sfrm = WcsSkyFrame( this, store, s, prj, wcscelestial_type, axlon,
                          axlat, method, class, status );

/* The values currently stored in *reflat and *reflon are the CRVAL
   values. In some circumstances, these may not be the original values in
   the supplied header but may have been translated within the SpecTrans
   function as part of the process of translating an old unsupported
   projection into a new supported projection. Since the returned RefLat
   and RefLon values may be used to set the reference position for a
   SpecFrame, we should return the original values rather than the
   translated values. The original values will have been stored (within
   SpecTrans) in the FitsChan as keywords RFVALi. If such keywords can
   be found, use their values in preference to the currently stored CRVAL
   values.*/
      if( GetValue( this, FormatKey( "RFVAL", axlon + 1, -1, s, status ),
                    AST__FLOAT, (void *) &lonval, 0, 0, method, class, status ) &&
          GetValue( this, FormatKey( "RFVAL", axlat + 1, -1, s, status ),
                    AST__FLOAT, (void *) &latval, 0, 0, method, class, status ) ) {
         *reflon = lonval*AST__DD2R;
         *reflat = latval*AST__DD2R;
      }

/* Store the reflon and reflat values as the SkyRef position in the
   SkyFrame, and set SkyRefIs to "ignore" so that the SkyFrame continues
   to represent absolute celestial coords. Do not change the SkyFrame if
   it already had a set reference posiiton. */
      if( ! astTestSkyRef( sfrm, 0 ) ) {
         if( *reflon != AST__BAD && *reflat != AST__BAD ) {
            astSetSkyRef( sfrm, 0, *reflon );
            astSetSkyRef( sfrm, 1, *reflat );
            astSet( sfrm, "SkyRefIs=Ignored", status );
         }
      }

/* Return a clone of this SkyFrame as the reference Frame. */
      *reffrm = astClone( sfrm );

/* Create a Frame by picking all the other (non-celestial) axes from the
   supplied Frame. */
      axes = astMalloc( naxes*sizeof( int ) );
      if( axes ) {
         j = 0;
         for( i = 0; i < naxes; i++ ) {
            if( i != axlat && i != axlon ) axes[ j++ ] = i;
         }

/* If there were no other axes, replace the supplied Frame with the skyframe. */
         if( j == 0 ) {
            (void) astAnnul( *frm );
            *frm = (AstFrame *) astClone( sfrm );

/* Otherwise pick the other axes from the supplied Frame */
         } else {
            ofrm = astPickAxes( *frm, j, axes, NULL );

/* Replace the supplied Frame with a CmpFrame made up of this Frame and
   the SkyFrame. */
            (void) astAnnul( *frm );
            *frm = (AstFrame *) astCmpFrame( ofrm, sfrm, "", status );
            ofrm = astAnnul( ofrm );
         }

/* Permute the axis order to put the longitude and latitude axes back in
   their original position. The SkyFrame will have the default axis
   ordering (lon=axis 0, lat = axis 1). */
         j = 0;
         for( i = 0; i < naxes; i++ ) {
            if( i == axlat ) {
               axes[ i ] = naxes - 1;
            } else if( i == axlon ) {
               axes[ i ] = naxes - 2;
            } else {
               axes[ i ] = j++;
            }
         }
         astPermAxes( *frm, axes );

/* Free the axes array. */
         axes= astFree( axes );
      }

/* Set the units in the supplied IWC Frame for the longitude and latitude
   axes. Unless using -TAB, these are degrees (the conversion from degs to
   rads is part of the Mapping from IWC to WCS). If using -TAB the units
   are unknown. */
      if( !tabaxis || !tabaxis[ axlon ] ) astSetUnit( iwcfrm, axlon, "deg" );
      if( !tabaxis || !tabaxis[ axlat ] ) astSetUnit( iwcfrm, axlat, "deg" );

/* Modify any supplied tabmap so that the celestial outputs create
   radians rather than degrees (but only if the celestial axes are
   generated by the -TAB algorithm). */
      if( tabaxis && tabaxis[ axlon ] && tabaxis[ axlat ] ) {
         mat = (double *) astMalloc( sizeof(double)*naxes );
         if( mat ){
            for( i = 0; i < naxes; i++ ){
               if( i == axlat || i == axlon ){
                  mat[ i ] = AST__DD2R;
               } else {
                  mat[ i ] = 1.0;
               }
            }
            map1 = (AstMapping *) astMatrixMap( naxes, naxes, 1, mat, "", status );
            mat = (double *) astFree( (void *) mat );
            map2 = (AstMapping *) astCmpMap( *tabmap, map1, 1, " ", status );
            map1 = astAnnul( map1 );
            (void) astAnnul( *tabmap );
            *tabmap = map2;
         }

/* Also modify the returned reflon and reflat values to transform them
   using the tabmap. Also transform the reference position in the SkyFrame. */
         if( *reflon != AST__BAD && *reflat != AST__BAD ) {
            ina = astMalloc( sizeof(double)*naxes );
            outa = astMalloc( sizeof(double)*naxes );
            if( astOK ) {
               for( i = 0; i < naxes; i++ ) ina[ i ] = 0.0;
               ina[ axlat ] = *reflat;
               ina[ axlon ] = *reflon;
               astTranN( *tabmap, 1, naxes, 1, ina, 1, naxes, 1, outa );
               *reflon = outa[ axlon ];
               *reflat = outa[ axlat ];
            }
            ina = astFree( ina );
            outa = astFree( outa );

/* Store this transformed reference position in the SkyFrame. */
            astSetSkyRef( sfrm, 0, *reflon );
            astSetSkyRef( sfrm, 1, *reflat );
            astSet( sfrm, "SkyRefIs=Ignored", status );
         }
      }

/* If the header contains AXREF values for both lon and lat axes, use
   them as the sky reference position in preferences to the values
   derived form the CRVAL values. AXREF keywords are created by the
   astWrite method for axes described by -TAB algorithm that have no inverse
   transformation. */
      if( GetValue( this, FormatKey( "AXREF", axlon + 1, -1, s, status ),
                    AST__FLOAT, (void *) &lonval, 0, 0, method, class, status ) &&
          GetValue( this, FormatKey( "AXREF", axlat + 1, -1, s, status ),
                    AST__FLOAT, (void *) &latval, 0, 0, method, class, status ) ) {
         *reflon = lonval*AST__DD2R;
         *reflat = latval*AST__DD2R;
         astSetSkyRef( sfrm, 0, *reflon );
         astSetSkyRef( sfrm, 1, *reflat );
         astSet( sfrm, "SkyRefIs=Ignored", status );
      }

/* Free resources. */
      sfrm = astAnnul( sfrm );
   }

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

static void WcsFcRead( AstFitsChan *fc, AstFitsChan *fc2, FitsStore *store,
                       const char *method, const char *class, int *status ){
/*
*  Name:
*     WcsFcRead

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

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void WcsFcRead( AstFitsChan *fc, AstFitsChan *fc2, FitsStore *store,
*                     const char *method, const char *class, int *status )

*  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 FITSWCS keywords from the supplied FitsChan,
*     and stores the corresponding WCS information in the supplied FitsStore.

*  Parameters:
*     fc
*        Pointer to the FitsChan containing the cards read from the
*        original FITS header. This should not include any un-used
*        non-standard keywords.
*     fc2
*        Pointer to a second FitsChan. If a card read from "fc" fails to
*        be converted to its correct data type, a warning is only issued
*        if there is no card for this keyword in "fc2". "fc2" may be NULL
*        in which case a warning is always issued.
*     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.
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   char buf[200];     /* Buffer for warning message */
   char *cval;        /* String keyword value */
   char *keynam;      /* Pointer to current keyword name */
   char s;            /* Co-ordinate version character */
   char *telescop;    /* Pointer to TELESCOP keyword value */
   double dval;       /* Floating point keyword value */
   int fld[2];        /* Integer field values from keyword name */
   int jm;            /* Pixel axis or projection parameter index */
   int i;             /* Intermediate axis index */
   int mark;          /* Non-zero if card should be removed once used */
   int nfld;          /* Number of integer fields in test string */
   int ok;            /* Was value converted succesfully? */
   int type;          /* Keyword data type */
   int undef;         /* Is an undefined keyword value acceptable? */
   void *item;        /* Pointer to item to get/put */

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

/* Ensure the FitsChan is re-wound. */
   astClearCard( fc );

/* Loop round all the cards in the FitsChan obtaining the keyword name for
   each card. Note, the single "=" is correct in the following "while"
   statement. */
   s = 0;
   jm = -1;
   i = -1;
   type = AST__NOTYPE;
   while( (keynam = CardName( fc, status )) ){
      item = NULL;

/* Assume the card is to be consumed by the reading process. This means
   the card will be marked as used and effectively excluded from the header.
   Keywords which supply observation details that do not depend on the
   mapping from pixel to WCS axes, or on the nature of the WCS axes,
   are not removed as they may be needed for other, non-WCS related,
   purposes. */
      mark = 1;

/* For most keywords, if the keyword is present in the header it must
   have a definded value. However, some keywords are read from the header
   but not actually used for anything. This is done to ensure that the
   keyword is stripped from the header. It is acceptable for such
   keywords to have an undefined value. Initialise a flag indicating that
   the next keyword read is not allowed to have an undefined value. */
      undef = 0;

/* Is this a primary CRVAL keyword? */
      if( Match( keynam, "CRVAL%d", 1, fld, &nfld, method, class, status ) ){
         item = &(store->crval);
         type = AST__FLOAT;
         i = fld[ 0 ] - 1;
         jm = 0;
         s = ' ';

/* Is this a secondary CRVAL keyword? */
      } else if( Match( keynam, "CRVAL%d%1c", 1, fld, &nfld, method, class, status ) ){
         item = &(store->crval);
         type = AST__FLOAT;
         i = fld[ 0 ] - 1;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary CRPIX keyword? */
      } else if( Match( keynam, "CRPIX%d", 1, fld, &nfld, method, class, status ) ){
         item = &(store->crpix);
         type = AST__FLOAT;
         i = 0;
         jm = fld[ 0 ] - 1;
         s = ' ';

/* Is this a secondary CRPIX keyword? */
      } else if( Match( keynam, "CRPIX%d%1c", 1, fld, &nfld, method, class, status ) ){
         item = &(store->crpix);
         type = AST__FLOAT;
         i = 0;
         jm = fld[ 0 ] - 1;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary CDELT keyword? */
      } else if( Match( keynam, "CDELT%d", 1, fld, &nfld, method, class, status ) ){
         item = &(store->cdelt);
         type = AST__FLOAT;
         i = fld[ 0 ] - 1;
         jm = 0;
         s = ' ';

/* Is this a secondary CDELT keyword? */
      } else if( Match( keynam, "CDELT%d%1c", 1, fld, &nfld, method, class, status ) ){
         item = &(store->cdelt);
         type = AST__FLOAT;
         i = fld[ 0 ] - 1;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary CTYPE keyword? If so, store the associated comment. */
      } else if( Match( keynam, "CTYPE%d", 1, fld, &nfld, method, class, status ) ){
         item = &(store->ctype);
         type = AST__STRING;
         i = fld[ 0 ] - 1;
         jm = 0;
         s = ' ';
         SetItemC( &(store->ctype_com), i, 0, ' ', CardComm( fc, status ), status );

/* Is this a secondary CTYPE keyword? If so, store the associated comment. */
      } else if( Match( keynam, "CTYPE%d%1c", 1, fld, &nfld, method, class, status ) ){
         item = &(store->ctype);
         type = AST__STRING;
         i = fld[ 0 ] - 1;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];
         SetItemC( &(store->ctype_com), i, 0, s, CardComm( fc, status ), status );

/* Is this a primary CNAME keyword? */
      } else if( Match( keynam, "CNAME%d", 1, fld, &nfld, method, class, status ) ){
         item = &(store->cname);
         type = AST__STRING;
         i = fld[ 0 ] - 1;
         jm = 0;
         s = ' ';

/* Is this a secondary CNAME keyword? */
      } else if( Match( keynam, "CNAME%d%1c", 1, fld, &nfld, method, class, status ) ){
         item = &(store->cname);
         type = AST__STRING;
         i = fld[ 0 ] - 1;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary CUNIT keyword? */
      } else if( Match( keynam, "CUNIT%d", 1, fld, &nfld, method, class, status ) ){
         item = &(store->cunit);
         type = AST__STRING;
         i = fld[ 0 ] - 1;
         jm = 0;
         s = ' ';

/* Is this a secondary CUNIT keyword? */
      } else if( Match( keynam, "CUNIT%d%1c", 1, fld, &nfld, method, class, status ) ){
         item = &(store->cunit);
         type = AST__STRING;
         i = fld[ 0 ] - 1;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary PC keyword? */
      } else if( Match( keynam, "PC%d_%d", 2, fld, &nfld, method, class, status ) ){
         item = &(store->pc);
         type = AST__FLOAT;
         i = fld[ 0 ] - 1;
         jm = fld[ 1 ] - 1;
         s = ' ';

/* Is this a secondary PC keyword? */
      } else if( Match( keynam, "PC%d_%d%1c", 2, fld, &nfld, method, class, status ) ){
         item = &(store->pc);
         type = AST__FLOAT;
         i = fld[ 0 ] - 1;
         jm = fld[ 1 ] - 1;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary PV keyword? */
      } else if( Match( keynam, "PV%d_%d", 2, fld, &nfld, method, class, status ) ){
         item = &(store->pv);
         type = AST__FLOAT;
         i = fld[ 0 ] - 1;
         jm = fld[ 1 ];
         s = ' ';

/* Is this a secondary PV keyword? */
      } else if( Match( keynam, "PV%d_%d%1c", 2, fld, &nfld, method, class, status ) ){
         item = &(store->pv);
         type = AST__FLOAT;
         i = fld[ 0 ] - 1;
         jm = fld[ 1 ];
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary PS keyword? */
      } else if( Match( keynam, "PS%d_%d", 2, fld, &nfld, method, class, status ) ){
         item = &(store->ps);
         type = AST__STRING;
         i = fld[ 0 ] - 1;
         jm = fld[ 1 ];
         s = ' ';

/* Is this a secondary PS keyword? */
      } else if( Match( keynam, "PS%d_%d%1c", 2, fld, &nfld, method, class, status ) ){
         item = &(store->ps);
         type = AST__STRING;
         i = fld[ 0 ] - 1;
         jm = fld[ 1 ];
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary RADESYS keyword? */
      } else if( Match( keynam, "RADESYS", 0, fld, &nfld, method, class, status ) ){
         item = &(store->radesys);
         type = AST__STRING;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a secondary RADESYS keyword? */
      } else if( Match( keynam, "RADESYS%1c", 0, fld, &nfld, method, class, status ) ){
         item = &(store->radesys);
         type = AST__STRING;
         i = 0;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary EQUINOX keyword? */
      } else if( Match( keynam, "EQUINOX", 0, fld, &nfld, method, class, status ) ){
         item = &(store->equinox);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a secondary EQUINOX keyword? */
      } else if( Match( keynam, "EQUINOX%1c", 0, fld, &nfld, method, class, status ) ){
         item = &(store->equinox);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary LATPOLE keyword? */
      } else if( Match( keynam, "LATPOLE", 0, fld, &nfld, method, class, status ) ){
         item = &(store->latpole);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a secondary LATPOLE keyword? */
      } else if( Match( keynam, "LATPOLE%1c", 0, fld, &nfld, method, class, status ) ){
         item = &(store->latpole);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary LONPOLE keyword? */
      } else if( Match( keynam, "LONPOLE", 0, fld, &nfld, method, class, status ) ){
         item = &(store->lonpole);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a secondary LONPOLE keyword? */
      } else if( Match( keynam, "LONPOLE%1c", 0, fld, &nfld, method, class, status ) ){
         item = &(store->lonpole);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary WXSAXES keyword? */
      } else if( Match( keynam, "WCSAXES", 0, fld, &nfld, method, class, status ) ){
         item = &(store->wcsaxes);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a secondary WCSAXES keyword? */
      } else if( Match( keynam, "WCSAXES%1c", 0, fld, &nfld, method, class, status ) ){
         item = &(store->wcsaxes);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary DUT1 keyword? */
      } else if( Match( keynam, "DUT1", 0, fld, &nfld, method, class, status ) ){
         mark = 0;
         item = &(store->dut1);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a primary DTAI keyword? Only handle if the telescope is JCMT
   or UKIRT, since other telescopes may use DTAI for different purposes. */
      } else if( Match( keynam, "DTAI", 0, fld, &nfld, method, class, status ) ){
         if( GetValue( fc, "TELESCOP", AST__STRING, &telescop, 0, 0, method,
                       class, status ) && ( !strncmp( telescop, "JCMT", 4)
                                         || !strncmp( telescop, "UKIRT", 5 ) ) ){
            mark = 0;
            item = &(store->dtai);
            type = AST__FLOAT;
            i = 0;
            jm = 0;
            s = ' ';
         }

/* Is this a primary MJD-OBS keyword? */
      } else if( Match( keynam, "MJD-OBS", 0, fld, &nfld, method, class, status ) ){
         mark = 0;
         item = &(store->mjdobs);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a primary WCSNAME keyword? */
      } else if( Match( keynam, "WCSNAME", 0, fld, &nfld, method, class, status ) ){
         item = &(store->wcsname);
         type = AST__STRING;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a secondary WCSNAME keyword? */
      } else if( Match( keynam, "WCSNAME%1c", 0, fld, &nfld, method, class, status ) ){
         item = &(store->wcsname);
         type = AST__STRING;
         i = 0;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary SPECSYS keyword? */
      } else if( Match( keynam, "SPECSYS", 0, fld, &nfld, method, class, status ) ){
         item = &(store->specsys);
         type = AST__STRING;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a secondary SPECSYS keyword? */
      } else if( Match( keynam, "SPECSYS%1c", 0, fld, &nfld, method, class, status ) ){
         item = &(store->specsys);
         type = AST__STRING;
         i = 0;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary SSYSSRC keyword? */
      } else if( Match( keynam, "SSYSSRC", 0, fld, &nfld, method, class, status ) ){
         item = &(store->ssyssrc);
         type = AST__STRING;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a secondary SSYSSRC keyword? */
      } else if( Match( keynam, "SSYSSRC%1c", 0, fld, &nfld, method, class, status ) ){
         item = &(store->ssyssrc);
         type = AST__STRING;
         i = 0;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary ZSOURCE keyword? */
      } else if( Match( keynam, "ZSOURCE", 0, fld, &nfld, method, class, status ) ){
         item = &(store->zsource);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a secondary ZSOURCE keyword? */
      } else if( Match( keynam, "ZSOURCE%1c", 0, fld, &nfld, method, class, status ) ){
         item = &(store->zsource);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary VELOSYS keyword? */
      } else if( Match( keynam, "VELOSYS", 0, fld, &nfld, method, class, status ) ){
         item = &(store->velosys);
         type = AST__FLOAT;
         undef = 1;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a secondary VELOSYS keyword? */
      } else if( Match( keynam, "VELOSYS%1c", 0, fld, &nfld, method, class, status ) ){
         item = &(store->velosys);
         type = AST__FLOAT;
         undef = 1;
         i = 0;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary RESTFRQ keyword? */
      } else if( Match( keynam, "RESTFRQ", 0, fld, &nfld, method, class, status ) ){
         item = &(store->restfrq);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a secondary RESTFRQ keyword? */
      } else if( Match( keynam, "RESTFRQ%1c", 0, fld, &nfld, method, class, status ) ){
         item = &(store->restfrq);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary RESTWAV keyword? */
      } else if( Match( keynam, "RESTWAV", 0, fld, &nfld, method, class, status ) ){
         item = &(store->restwav);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a secondary RESTWAV keyword? */
      } else if( Match( keynam, "RESTWAV%1c", 0, fld, &nfld, method, class, status ) ){
         item = &(store->restwav);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary IMAGFREQ keyword? */
      } else if( Match( keynam, "IMAGFREQ", 0, fld, &nfld, method, class, status ) ){
         item = &(store->imagfreq);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a primary SKYREF keyword? */
      } else if( Match( keynam, "SREF%d", 1, fld, &nfld, method, class, status ) ){
         item = &(store->skyref);
         type = AST__FLOAT;
         i = fld[ 0 ] - 1;
         jm = 0;
         s = ' ';

/* Is this a secondary SKYREF keyword? */
      } else if( Match( keynam, "SREF%d%1c", 1, fld, &nfld, method, class, status ) ){
         item = &(store->skyref);
         type = AST__FLOAT;
         i = fld[ 0 ] - 1;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary SKYREFP keyword? */
      } else if( Match( keynam, "SREFP%d", 1, fld, &nfld, method, class, status ) ){
         item = &(store->skyrefp);
         type = AST__FLOAT;
         i = fld[ 0 ] - 1;
         jm = 0;
         s = ' ';

/* Is this a secondary SKYREFP keyword? */
      } else if( Match( keynam, "SREFP%d%1c", 1, fld, &nfld, method, class, status ) ){
         item = &(store->skyrefp);
         type = AST__FLOAT;
         i = fld[ 0 ] - 1;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary SKYREFIS keyword? */
      } else if( Match( keynam, "SREFIS", 0, fld, &nfld, method, class, status ) ){
         item = &(store->skyrefis);
         type = AST__STRING;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a secondary SKYREFIS keyword? */
      } else if( Match( keynam, "SREFIS%1c", 0, fld, &nfld, method, class, status ) ){
         item = &(store->skyrefis);
         type = AST__STRING;
         i = 0;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary AXREF keyword? */
      } else if( Match( keynam, "AXREF%d", 1, fld, &nfld, method, class, status ) ){
         item = &(store->axref);
         type = AST__FLOAT;
         i = fld[ 0 ] - 1;
         jm = 0;
         s = ' ';

/* Is this a secondary AXREF keyword? */
      } else if( Match( keynam, "AXREF%d%1c", 1, fld, &nfld, method, class, status ) ){
         item = &(store->axref);
         type = AST__FLOAT;
         i = fld[ 0 ] - 1;
         jm = 0;
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a MJD-AVG keyword? */
      } else if( Match( keynam, "MJD-AVG", 0, fld, &nfld, method, class, status ) ){
         mark = 0;
         item = &(store->mjdavg);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a OBSGEO-X keyword? */
      } else if( Match( keynam, "OBSGEO-X", 0, fld, &nfld, method, class, status ) ){
         mark = 0;
         item = &(store->obsgeox);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a OBSGEO-Y keyword? */
      } else if( Match( keynam, "OBSGEO-Y", 0, fld, &nfld, method, class, status ) ){
         mark = 0;
         item = &(store->obsgeoy);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a OBSGEO-Z keyword? */
      } else if( Match( keynam, "OBSGEO-Z", 0, fld, &nfld, method, class, status ) ){
         mark = 0;
         item = &(store->obsgeoz);
         type = AST__FLOAT;
         i = 0;
         jm = 0;
         s = ' ';

/* Is this a TIMESYS keyword? */
      } else if( Match( keynam, "TIMESYS", 0, fld, &nfld, method, class, status ) ){
         mark = 0;
         item = &(store->timesys);
         type = AST__STRING;
         i = 0;
         jm = 0;
         s = ' ';

/* Following keywords are used to describe "-SIP" distortion as used by
   the Spitzer project... */

/* Is this a primary A keyword? */
      } else if( Match( keynam, "A_%d_%d", 2, fld, &nfld, method, class, status ) ){
         item = &(store->asip);
         type = AST__FLOAT;
         i = fld[ 0 ];
         jm = fld[ 1 ];
         s = ' ';

/* Is this a secondary A keyword? */
      } else if( Match( keynam, "A_%d_%d%1c", 2, fld, &nfld, method, class, status ) ){
         item = &(store->asip);
         type = AST__FLOAT;
         i = fld[ 0 ];
         jm = fld[ 1 ];
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary B keyword? */
      } else if( Match( keynam, "B_%d_%d", 2, fld, &nfld, method, class, status ) ){
         item = &(store->bsip);
         type = AST__FLOAT;
         i = fld[ 0 ];
         jm = fld[ 1 ];
         s = ' ';

/* Is this a secondary B keyword? */
      } else if( Match( keynam, "B_%d_%d%1c", 2, fld, &nfld, method, class, status ) ){
         item = &(store->bsip);
         type = AST__FLOAT;
         i = fld[ 0 ];
         jm = fld[ 1 ];
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary AP keyword? */
      } else if( Match( keynam, "AP_%d_%d", 2, fld, &nfld, method, class, status ) ){
         item = &(store->apsip);
         type = AST__FLOAT;
         i = fld[ 0 ];
         jm = fld[ 1 ];
         s = ' ';

/* Is this a secondary AP keyword? */
      } else if( Match( keynam, "AP_%d_%d%1c", 2, fld, &nfld, method, class, status ) ){
         item = &(store->apsip);
         type = AST__FLOAT;
         i = fld[ 0 ];
         jm = fld[ 1 ];
         s = keynam[ strlen( keynam ) - 1 ];

/* Is this a primary BP keyword? */
      } else if( Match( keynam, "BP_%d_%d", 2, fld, &nfld, method, class, status ) ){
         item = &(store->bpsip);
         type = AST__FLOAT;
         i = fld[ 0 ];
         jm = fld[ 1 ];
         s = ' ';

/* Is this a secondary BP keyword? */
      } else if( Match( keynam, "BP_%d_%d%1c", 2, fld, &nfld, method, class, status ) ){
         item = &(store->bpsip);
         type = AST__FLOAT;
         i = fld[ 0 ];
         jm = fld[ 1 ];
         s = keynam[ strlen( keynam ) - 1 ];
      }

/* If this keyword was recognized, store it in the FitsStore, and mark it
   as having been read. */
      if( item ){
         ok = 1;
         if( type == AST__FLOAT ){
            if( CnvValue( fc, AST__FLOAT, undef, &dval, method, status ) ) {
               SetItem( (double ****) item, i, jm, s, dval, status );
               if( mark ) MarkCard( fc, status );
            } else {
               ok = 0;
            }
         } else {
            if( CnvValue( fc, AST__STRING, undef, &cval, method, status ) ) {
               cval[ astChrLen( cval ) ] = 0;  /* Exclude trailing spaces */
               SetItemC( (char *****) item, i, jm, s, cval, status );
               if( mark ) MarkCard( fc, status );
            } else {
              ok = 0;
            }
         }

/* Issue a warning if the value could not be converted to the expected
   type. */
         if( !ok ) {

/* First check that the keyword is not included in "fc2". */
            if( !HasCard( fc2, keynam, method, class, status ) ) {
               sprintf( buf, "The original FITS header contained a value for "
                        "keyword %s which could not be converted to a %s.",
                        keynam, ( type==AST__FLOAT ? "floating point number":
                        "character string" ) );
               Warn( fc, "badval", buf, "astRead", "FitsChan", status );
            }
         }
      }

/* Move on to the next card. */
      MoveCard( fc, 1, method, class, status );
   }
}

static int WcsFromStore( AstFitsChan *this, FitsStore *store,
                         const char *method, const char *class, int *status ){

/*
*  Name:
*     WcsFromStore

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

*  Type:
*     Private function.

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

*  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-WCS 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.
*     status
*        Pointer to the inherited status variable.

*  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 parprefix[4];  /* Prefix for projection parameter keywords */
   char s;             /* Co-ordinate version character */
   char sign[2];       /* Fraction's sign character */
   char sup;           /* Upper limit on s */
   char type[MXCTYPELEN];/* Buffer for CTYPE value */
   const char *order_kwd; /* Name for SIP max order keyword */
   double ****item;    /* Address of FitsStore item to use */
   double cdl;         /* CDELT value */
   double fd;          /* Fraction of a day */
   double mjd99;       /* MJD at start of 1999 */
   double val;         /* General purpose value */
   int *tabaxis;       /* Flags WCS axes that use -TAB algorithm */
   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;              /* Parameter index */
   int maxm;           /* Upper limit on m */
   int naxis;          /* Value of NAXIS keyword */
   int nc;             /* Length of STYPE string */
   int nwcs;           /* No. of WCS axes */
   int ok;             /* Frame created succesfully? */
   int order;          /* Max SIP polynomial order */
   int p;              /* Power of u or U */
   int pmax;           /* Max power of u or U */
   int prj;            /* Projection type */
   int q;              /* Power of v or V */
   int qmax;           /* Max power of v or V */
   int ret;            /* Returned value */

/* Initialise */
   ret = 0;

/* Other initialisation to avoid compiler warnings. */
   tabaxis = NULL;

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

/* If the FitsChan contains a value for the NAXIS keyword, note it.
   Otherwise store -1. */
   if( !astGetFitsI( this, "NAXIS", &naxis ) ) naxis = -1;

/* Find the last WCS related card. */
   FindWcs( this, 1, 1, 0, method, class, status );

/* Loop round all co-ordinate versions */
   sup = GetMaxS( &(store->crval), status );
   for( s = ' '; s <= sup && astOK; s++ ){

/* For alternate axes, skip this axis description if there is no CRPIX1 or
   CRVAL1 value. This avoids partial axis descriptions being written out. */
      if( s != ' ' ) {
         if( GetItem( &(store->crpix), 0, 0, s, NULL, method, class, status ) ==
             AST__BAD ||
             GetItem( &(store->crval), 0, 0, s, NULL, method, class, status ) ==
             AST__BAD ) {
            ok = 0;
            goto next;
         }
      }

/* Assume the Frame can be created succesfully. */
      ok = 1;

/* Save the number of wcs axes. If a value for WCSAXES has been set, or
   if the number of axes is not the same as specified in the NAXIS keyword,
   store a WCSAXES keyword. */
      val = GetItem( &(store->wcsaxes), 0, 0, s, NULL, method, class, status );
      if( val != AST__BAD ) {
         nwcs = (int) ( val + 0.5 );
      } else {
         nwcs = GetMaxJM( &(store->crpix), s, status ) + 1;
         if( nwcs != 0 && nwcs != naxis ) val = (double) nwcs;
      }
      if( val != AST__BAD ) {
         SetValue( this, FormatKey( "WCSAXES", -1, -1, s, status ),
                   &nwcs, AST__INT, "Number of WCS axes", status );
      }

/* Get and save WCSNAME. This is NOT required, so do not return if it is
   not available. If the WCS is 1-d, only store WCSNAME if its value is
   different to the CTYPE1 value. */
      cval = GetItemC( &(store->wcsname), 0, 0, s, NULL, method, class, status );
      if( cval && nwcs == 1 ) {
         comm = GetItemC( &(store->ctype), 0, 0, s, NULL, method, class, status );
         if( comm && Similar( comm, cval, status ) ) cval = NULL;
      }
      if( cval ) SetValue( this, FormatKey( "WCSNAME", -1, -1, s, status ), &cval,
                           AST__STRING, "Reference name for the coord. frame", status );

/* The prefix for numerical projection parameters is usually "PV". */
      strcpy( parprefix, "PV" );

/* Keywords common to all axis types... */

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

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

/* Allocate memory to indicate if each WCS axis is described by a -TAB
   algorithm or not. Initialiss it to zero. */
      tabaxis = astCalloc( nwcs, sizeof( int ) );

/* Get and save CTYPE for all WCS axes. These are required, so
   pass on if they are not available. */
      for( i = 0; i < nwcs; i++ ) {
         cval = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
         if( !cval ) {
            ok = 0;
            goto next;
         }
         comm = GetItemC( &(store->ctype_com), i, 0, s, NULL, method, class, status );
         if( !comm ) {
            sprintf( combuf, "Type of co-ordinate on axis %d", i + 1 );
            comm = combuf;
         }

/* Extract the projection type as specified by the last 4 characters
   in the CTYPE keyword value. This will be AST__WCSBAD for non-celestial
   axes. Note, CTYPE can be more than 8 characters long. */
         nc = strlen( cval );
         prj = ( nc > 4 ) ? astWcsPrjType( cval + nc - 4 ) : AST__WCSBAD;

/* If the projection type is "TPN" (an AST-specific code) convert it to
   standard FITS-WCS code "TAN" and change the prefix for projection
   parameters from "PV" to "QV". AST will do the inverse conversions when
   reading such a header. Non-AST software will simply ignore the QV
   terms and interpret the header as a simple TAN projection. */
         if( prj == AST__TPN ) {
            strcpy( parprefix, "QV" );
            strcpy( type, cval );
            (void) strcpy( type + nc - 4, "-TAN" );
            cval = type;
         }

/* Note if the axis is described by the -TAB algorithm. */
         tabaxis[ i ] = ( prj == AST__WCSBAD && strlen( cval ) >= 8 &&
                          !strncmp( cval + 4, "-TAB", 4 ) );

/* Store the (potentially modified) CTYPE value. */
         SetValue( this, FormatKey( "CTYPE", i + 1, -1, s, status ), &cval, AST__STRING,
                   comm, status );
      }

/* Get and save CNAME for all WCS axes. These are NOT required, so
   do not pass on if they are not available. Do not include a CNAME
   keyword if its value equals the commen or value of the corresponding
   CTYPE keyword. */
      for( i = 0; i < nwcs; i++ ) {
         cval = GetItemC( &(store->cname), i, 0, s, NULL, method, class, status );
         if( cval ) {
            comm = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
            if( !comm || strcmp( comm, cval ) ) {
               comm = GetItemC( &(store->ctype_com), i, 0, s, NULL, method, class, status );
               if( !comm || strcmp( comm, cval ) ) {
                  sprintf( combuf, "Description of axis %d", i + 1 );
                  SetValue( this, FormatKey( "CNAME", i + 1, -1, s, status ), &cval,
                            AST__STRING, combuf, status );
               }
            }
         }
      }

/* Now choose whether to produce CDi_j or CDELT/PCi_j keywords. */
      if( astGetCDMatrix( this ) ) {

/* CD matrix. Multiply the row of the PC matrix by the CDELT value. */
         for( i = 0; i < nwcs; i++ ) {
            cdl = GetItem( &(store->cdelt), i, 0, s, NULL, method, class, status );
            if( cdl == AST__BAD ) cdl = 1.0;
            for( j = 0; j < nwcs; j++ ){
               val = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
               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, status ), &val,
                             AST__FLOAT, "Transformation matrix element", status );
               }
            }
         }

/* If producing PC/CDELT keywords... */
      } else {

/* CDELT keywords. */
         for( i = 0; i < nwcs; i++ ) {
            val = GetItem( &(store->cdelt), i, 0, s, NULL, method, class, status );
            if( val == AST__BAD ) {
               ok = 0;
               goto next;
            }
            sprintf( combuf, "Pixel size on axis %d", i + 1 );
            SetValue( this, FormatKey( "CDELT", i + 1, -1, s, status ), &val, AST__FLOAT,
                      combuf, status );
         }

/* PC matrix. */
         for( i = 0; i < nwcs; i++ ) {
            for( j = 0; j < nwcs; j++ ){
               val = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
               if( val != AST__BAD ) {
                  if( i == j ) {
                     if( astEQUAL( val, 1.0 ) ) val = AST__BAD;
                  } else {
                     if( astEQUAL( val, 0.0 ) ) val = AST__BAD;
                  }
               }
               if( val != AST__BAD ) {
                  SetValue( this, FormatKey( "PC", i + 1, j + 1, s, status ), &val,
                            AST__FLOAT, "Transformation matrix element", status );
               }
            }
         }
      }

/* Get and save CUNIT for all WCS axes. These are NOT required, so
   do not pass on if they are not available. */
      for( i = 0; i < nwcs; i++ ) {
         cval = GetItemC( &(store->cunit), i, 0, s, NULL, method, class, status );
         if( cval ) {
            sprintf( combuf, "Units for axis %d", i + 1 );
            SetValue( this, FormatKey( "CUNIT", i + 1, -1, s, status ), &cval, AST__STRING,
                      combuf, status );
         }
      }

/* Get and save AXREF for all WCS axes. These are NOT required, so do not
   pass on if they are not available. Note, AXREF is a non-standard keyword
   used by AST to communicate the reference position on any axes described
   by the -TAB algorithm and which has no inverse transformation. For all
   other cases, the reference position corresponds to the values of CRVAL. */
      for( i = 0; i < nwcs; i++ ) {
         val = GetItem( &(store->axref), i, 0, s, NULL, method, class, status );
         if( val != AST__BAD ) {
            sprintf( combuf, "Reference WCS value on axis %d", i + 1 );
            SetValue( this, FormatKey( "AXREF", i + 1, -1, s, status ), &val, AST__FLOAT,
                      combuf, status );
         }
      }

/* Get and save SREFIS. This is NOT required, so do not return if it is
   not available. Note, SREFIS is a non-standard keyword used by AST to
   communicate the SkyRefIs attribute in the original SkyFrame. */
      cval = GetItemC( &(store->skyrefis), 0, 0, s, NULL, method, class, status );
      if( cval ) SetValue( this, FormatKey( "SREFIS", -1, -1, s, status ), &cval,
                           AST__STRING, "Is SkyRef used as pole or origin?", status );

/* Get and save SREF for all WCS axes. These are NOT required, so do not
   pass on if they are not available. Note, SREF is a non-standard keyword
   used by AST to communicate the SkyRef position on any axes described
   by a offset SkyFrame. */
      for( i = 0; i < nwcs; i++ ) {
         val = GetItem( &(store->skyref), i, 0, s, NULL, method, class, status );
         if( val != AST__BAD ) {
            sprintf( combuf, "Sky reference position on axis %d", i + 1 );
            SetValue( this, FormatKey( "SREF", i + 1, -1, s, status ), &val, AST__FLOAT,
                      combuf, status );
         }
      }

/* Get and save SREFP for all WCS axes. These are NOT required, so do not
   pass on if they are not available. Note, SREFP is a non-standard keyword
   used by AST to communicate the SkyRefP position on any axes described
   by a offset SkyFrame. */
      for( i = 0; i < nwcs; i++ ) {
         val = GetItem( &(store->skyrefp), i, 0, s, NULL, method, class, status );
         if( val != AST__BAD ) {
            sprintf( combuf, "Sky primary meridian position on axis %d", i + 1 );
            SetValue( this, FormatKey( "SREFP", i + 1, -1, s, status ), &val, AST__FLOAT,
                      combuf, status );
         }
      }

/* Date of observation (only allowed for primary axis descriptions). */
      if( s == ' ' ) {
         val = GetItem( &(store->mjdobs), 0, 0, s, NULL, method, class, status );
         if( val != AST__BAD ) {
            SetValue( this, FormatKey( "MJD-OBS", -1, -1, s, status ),
                      &val, AST__FLOAT, "Modified Julian Date of observation", status );

/* 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. */
            palCaldj( 99, 1, 1, &mjd99, &jj );
            if( val < mjd99 ) {
               palDjcal( 0, val, iymdf, &jj );
               sprintf( combuf, "%2.2d/%2.2d/%2.2d", iymdf[ 2 ], iymdf[ 1 ],
                        iymdf[ 0 ] - ( ( iymdf[ 0 ] > 1999 ) ? 2000 : 1900 ) );
            } else {
               palDjcl( val, iymdf, iymdf+1, iymdf+2, &fd, &jj );
               palDd2tf( 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", status );
         }
         val = GetItem( &(store->mjdavg), 0, 0, ' ', NULL, method, class, status );
         if( val != AST__BAD ) SetValue( this, "MJD-AVG", &val, AST__FLOAT,
                                         "Average Modified Julian Date of observation", status );

/* Store the timescale in TIMESYS. */
         cval = GetItemC( &(store->timesys), 0, 0, s, NULL, method, class, status );
         if( cval ) SetValue( this, "TIMESYS", &cval, AST__STRING,
                              "Timescale for MJD-OBS/MJD-AVG values", status );
      }

/* Numerical projection parameters */
      maxm = GetMaxJM( &(store->pv), s, status );
      for( i = 0; i < nwcs; i++ ){
         for( m = 0; m <= maxm; m++ ){
            val = GetItem( &(store->pv), i, m, s, NULL, method, class, status );
            if( val != AST__BAD ) {

/* If the axis uses the "TAB" algorithm, there may be a PVi_4a parameter
   in the FitsStore. This is an AST extension to the published -TAB
   algorithm, and is used to hold the interpolation method. To avoid
   clashing with any standard use of PV1_4a, rename it to QVi_4a. The
   default is zero (linear interpolation) so do not write the QV value
   if it zero. */
               if( m == 4 && tabaxis[ i ] ) {
                  if( val != 0.0 ) {
                     SetValue( this, FormatKey( "QV", i + 1, m, s, status ),
                               &val, AST__FLOAT, "Use nearest neighbour "
                               "interpolation", status );
                  }

/* Just store the parameters for other type of axes. */
               } else {
                  SetValue( this, FormatKey( parprefix, i + 1, m, s, status ), &val,
                            AST__FLOAT, "Projection parameter", status );
               }
            }
         }
      }

/* String projection parameters */
      maxm = GetMaxJMC( &(store->ps), s, status );
      for( i = 0; i < nwcs; i++ ){
         for( m = 0; m <= maxm; m++ ){
            cval = GetItemC( &(store->ps), i, m, s, NULL, method, class, status );
            if( cval ) {
               SetValue( this, FormatKey( "PS", i + 1, m, s, status ), &cval,
                         AST__STRING, "Projection parameter", status );
            }
         }
      }

/* Keywords specific to celestial axes... */

/* Get and save RADESYS. This is NOT required, so do not return if it is
   not available. */
      cval = GetItemC( &(store->radesys), 0, 0, s, NULL, method, class, status );
      if( cval ) SetValue( this, FormatKey( "RADESYS", -1, -1, s, status ), &cval,
                           AST__STRING, "Reference frame for RA/DEC values", status );

/* Reference equinox */
      val = GetItem( &(store->equinox), 0, 0, s, NULL, method, class, status );
      if( val != AST__BAD ) SetValue( this, FormatKey( "EQUINOX", -1, -1, s, status ),
                                      &val, AST__FLOAT,
                                      "[yr] Epoch of reference equinox", status );

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

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

/* Keywords specific to spectral axes... */

/* SPECSYS - the standard of rest for the spectral axis */
      cval = GetItemC( &(store->specsys), 0, 0, s, NULL, method, class, status );
      if( cval ) SetValue( this, FormatKey( "SPECSYS", -1, -1, s, status ), &cval,
                           AST__STRING, "Standard of rest for spectral axis", status );

/* SSYSSRC - the standard of rest in which ZSOURCE is stored. */
      cval = GetItemC( &(store->ssyssrc), 0, 0, s, NULL, method, class, status );
      if( cval ) SetValue( this, FormatKey( "SSYSSRC", -1, -1, s, status ), &cval,
                           AST__STRING, "Standard of rest for source redshift", status );

/* ZSOURCE - topocentric optical velocity of source */
      val = GetItem( &(store->zsource), 0, 0, s, NULL, method, class, status );
      if( val != AST__BAD ) SetValue( this, FormatKey( "ZSOURCE", -1, -1, s, status ),
                                      &val, AST__FLOAT, "[] Redshift of source", status );

/* VELOSYS - topocentric apparent radial velocity of the standard of rest. */
      val = GetItem( &(store->velosys), 0, 0, s, NULL, method, class, status );
      if( val != AST__BAD ) SetValue( this, FormatKey( "VELOSYS", -1, -1, s, status ),
                                      &val, AST__FLOAT, "[m/s] Topo. apparent velocity of rest frame", status );

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

/* RESTWAV - rest wavelength */
      val = GetItem( &(store->restwav), 0, 0, s, NULL, method, class, status );
      if( val != AST__BAD ) SetValue( this, FormatKey( "RESTWAV", -1, -1, s, status ),
                                      &val, AST__FLOAT, "[m] Rest wavelength", status );

/* The image frequency corresponding to the rest frequency (only used for
   double sideband data). This is not part of the FITS-WCS standard but
   is added for the benefit of JACH. */
      val = GetItem( &(store->imagfreq), 0, 0, s, NULL, method, class, status );
      if( val != AST__BAD ) {
         SetValue( this, "IMAGFREQ", &val, AST__FLOAT, "[Hz] Image frequency", status );
      }

/* OBSGEO-X/Y/Z - observer's geocentric coords. Note, these always refer
   to the primary axes. */
      if( s == ' ' ) {
         val = GetItem( &(store->obsgeox), 0, 0, s, NULL, method, class, status );
         if( val != AST__BAD ) SetValue( this, "OBSGEO-X", &val, AST__FLOAT, "[m] Observatory geocentric X", status );
         val = GetItem( &(store->obsgeoy), 0, 0, s, NULL, method, class, status );
         if( val != AST__BAD ) SetValue( this, "OBSGEO-Y", &val, AST__FLOAT, "[m] Observatory geocentric Y", status );
         val = GetItem( &(store->obsgeoz), 0, 0, s, NULL, method, class, status );
         if( val != AST__BAD ) SetValue( this, "OBSGEO-Z", &val, AST__FLOAT, "[m] Observatory geocentric Z", status );
      }


/* Forward SIP distortion keywords. Loop over the two spatial axes. My reading
   of the SIP paper is that the SIP distortion *must* be attached to the first
   two pixel axes defined by the FITS header. */
      for( i = 0; i < 2; i++ ) {

/* Get a pointer to the FitsStore item holding the values defining this
   output. */
         if( i == 0 ) {
            item = &(store->asip);
            strcpy( parprefix, "A_" );
            order_kwd = "A_ORDER";
         } else {
            item = &(store->bsip);
            strcpy( parprefix, "B_" );
            order_kwd = "B_ORDER";
         }

/* Get the largest powers used of u and v. */
         pmax = GetMaxI( item, s, status );
         qmax = GetMaxJM( item, s, status );

/* Loop round all combination of powers. */
         order = 0;
         for( p = 0; p <= pmax; p++ ){
            for( q = 0; q <= qmax; q++ ){

/* Get the polynomial coefficient for this combination of powers. If it
   is good, format the keyword name and store it in the header. */
               val = GetItem( item, p, q, s, NULL, method, class, status );
               if( val != AST__BAD ) {
                  SetValue( this, FormatKey( parprefix, p, q, s, status ), &val,
                            AST__FLOAT, "SIP forward distortion coeff", status );
                  if( p + q > order ) order = p + q;
               }
            }
         }
         if( order > 0 ) SetValue( this, order_kwd, &order, AST__INT,
                                   "SIP max order", status );
      }

/* Inverse SIP distortion keywords. Loop over the two spatial axes. */
      for( i = 0; i < 2; i++ ) {

/* Get a pointer to the FitsStore item holding the values defining this
   output. */
         if( i == 0 ) {
            item = &(store->apsip);
            strcpy( parprefix, "AP_" );
            order_kwd = "AP_ORDER";
         } else {
            item = &(store->bpsip);
            strcpy( parprefix, "BP_" );
            order_kwd = "BP_ORDER";
         }

/* Get the largest powers used of u and v. */
         pmax = GetMaxI( item, s, status );
         qmax = GetMaxJM( item, s, status );

/* Loop round all combination of powers. */
         order = 0;
         for( p = 0; p <= pmax; p++ ){
            for( q = 0; q <= qmax; q++ ){

/* Get the polynomial coefficient for this combination of powers. If it
   is good, format the keyword name and store it in the header. */
               val = GetItem( item, p, q, s, NULL, method, class, status );
               if( val != AST__BAD ) {
                  SetValue( this, FormatKey( parprefix, p, q, s, status ), &val,
                            AST__FLOAT, "SIP inverse distortion coeff", status );
                  if( p + q > order ) order = p + q;
               }
            }
         }
         if( order > 0 ) SetValue( this, order_kwd, &order, AST__INT,
                                   "SIP inverse max order", status );
      }

/* See if a Frame was sucessfully written to the FitsChan. */
next:
      ok = ok && astOK;

/* If so, indicate we have something to return. */
      if( ok ) ret = 1;

/* If we are producing secondary axes, clear any error status so we can
   continue to produce the next Frame. Retain the error if the primary axes
   could not be produced. After the primary axes, do the A axes. */
      if( s != ' ' ) {
         astClearStatus;
      } else {
         s = 'A' - 1;
      }

/* Remove the secondary "new" flags from the FitsChan. This flag is
   associated with cards which have been added to the FitsChan during
   this pass through the main loop in this function. If the Frame was
   written out succesfully, just clear the flags. If anything went wrong
   with this Frame, remove the flagged cards from the FitsChan. */
      FixNew( this, NEW2, !ok, method, class, status );

/* Set the current card so that it points to the last WCS-related keyword
   in the FitsChan (whether previously read or not). */
      FindWcs( this, 1, 1, 0, method, class, status );

/* Free resources. */
      tabaxis = astFree( tabaxis );
   }

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

static AstMapping *WcsIntWorld( AstFitsChan *this, FitsStore *store, char s,
                                int naxes, const char *method, const char *class, int *status ){
/*
*  Name:
*     WcsIntWorld

*  Purpose:
*     Create a Mapping from pixel coords to intermediate world coords.

*  Type:
*     Private function.

*  Synopsis:
*     AstMapping *WcsIntWorld( AstFitsChan *this, FitsStore *store, char s,
*                              int naxes, const char *method, const char *class, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function interprets the contents of the supplied FitsStore
*     structure, and creates a Mapping which describes the transformation
*     from pixel coordinates to intermediate world coordinates, using the
*     FITS World Coordinate System conventions. This is a general linear
*     transformation described by the CRPIXj, PCi_j and CDELTi keywords.

*  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).
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to the Mapping.
*/

/* Local Variables: */
   AstMapping   *mapd1;      /* Pointer to first distortion Mapping */
   AstMapping   *mapd2;      /* Pointer to second distortion Mapping */
   AstMapping   *mapd3;      /* Pointer to third distortion Mapping */
   AstMapping   *mapd4;      /* Pointer to fourth distortion Mapping */
   AstMapping   *map0;       /* Pointer to a Mapping */
   AstMapping   *map1;       /* Pointer to a Mapping */
   AstMapping   *ret;        /* Pointer to the returned Mapping */

/* Initialise the pointer to the returned Mapping. */
   ret = NULL;

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

/* First of all, check the CTYPE keywords to see if they contain any known
   distortion codes (following the syntax described in FITS-WCS paper IV).
   If so, Mappings are returned which represents the distortions to be
   applied at each point in the chain of Mappings produced by this function.
   Any distortion codes are removed from the CTYPE values in the FitsStore. */
   DistortMaps( this, store, s, naxes, &mapd1, &mapd2, &mapd3, &mapd4, method,
                class, status );

/* If distortion is to be applied now, initialise the returned Mapping to
   be the distortion. */
   if( mapd1 ) ret = mapd1;

/* Try to create a WinMap which translates the pixel coordinates so
   that they are refered to an origin at the reference pixel. This
   subtracts the value of CRPIXi from axis i. */
   map1 = (AstMapping *) WcsShift( store, s, naxes, method, class, status );

/* Combine this with any previous Mapping. */
   if( ret ) {
      map0 = (AstMapping *) astCmpMap( ret, map1, 1, "", status );
      ret = astAnnul( ret );
      map1 = astAnnul( map1 );
      ret = map0;
   } else {
      ret = map1;
   }

/* If distortion is to be applied now, combine the two Mappings. */
   if( mapd2 ) {
      map0 = (AstMapping *) astCmpMap( ret, mapd2, 1, "", status );
      ret = astAnnul( ret );
      mapd2 = astAnnul( mapd2 );
      ret = map0;
   }

/* Now try to create a MatrixMap to implement the PC matrix. Combine it
   with the above Mapping. Add a Warning if this mapping cannot be inverted. */
   map1 = (AstMapping *) WcsPCMatrix( store, s, naxes, method, class, status );
   if( !astGetTranInverse( map1 ) ) {
      Warn( this, "badmat", "The pixel rotation matrix in the original FITS "
            "header (specified by CD or PC keywords) could not be inverted. "
            "This may be because the matrix contains rows or columns which "
            "are entirely zero.", method, class, status );
   }
   map0 = (AstMapping *) astCmpMap( ret, map1, 1, "", status );
   ret = astAnnul( ret );
   map1 = astAnnul( map1 );
   ret = map0;

/* If distortion is to be applied now, combine the two Mappings. */
   if( mapd3 ) {
      map0 = (AstMapping *) astCmpMap( ret, mapd3, 1, "", status );
      ret = astAnnul( ret );
      mapd3 = astAnnul( mapd3 );
      ret = map0;
   }

/* Now try to create a diagonal MatrixMap to implement the CDELT scaling.
   Combine it with the above Mapping.  */
   map1 = (AstMapping *) WcsCDeltMatrix( store, s, naxes, method, class, status );
   map0 = (AstMapping *) astCmpMap( ret, map1, 1, "", status );
   ret = astAnnul( ret );
   map1 = astAnnul( map1 );
   ret = map0;

/* If distortion is to be applied now, combine the two Mappings. */
   if( mapd4 ) {
      map0 = (AstMapping *) astCmpMap( ret, mapd4, 1, "", status );
      ret = astAnnul( ret );
      mapd4 = astAnnul( mapd4 );
      ret = map0;
   }

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

static AstMapping *WcsMapFrm( AstFitsChan *this, FitsStore *store, char s,
                              AstFrame **frm, const char *method,
                              const char *class, int *status ){
/*
*  Name:
*     WcsMapFrm

*  Purpose:
*     Create a Mapping and Frame for the WCS transformations described in a
*     FITS header.

*  Type:
*     Private function.

*  Synopsis:
*     AstMapping *WcsMapFrm( AstFitsChan *this, FitsStore *store, char s,
*                            AstFrame **frm, const char *method,
*                            const char *class, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function interprets the contents of the supplied FitsStore
*     structure, and creates a Mapping which describes the transformation
*     from pixel coordinates to world coordinates, using the FITS World
*     Coordinate System conventions. It also creates a Frame describing
*     the world coordinate axes.

*  Parameters:
*     this
*        The FitsChan.
*     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').
*     frm
*        The address of a location at which to store a pointer to the
*        Frame describing the world coordinate axes. If the Iwc attribute
*        is non-zero, then this is actually a FrameSet in which Frame 1
*        is the base Frame and describes the required WCS system. Frame
*        2 is the current Frame and describes the FITS IWC system.
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to the Mapping. If the Iwc attribute is non-zero, this
*     will be the Mapping from pixel to IWC coordinates. Otherwise it
*     will be the mapping from pixel to WCS coordinates.
*/

/* Local Variables: */
   AstFrame *iwcfrm;         /* Frame defining IWC system */
   AstFrameSet *fs;          /* Pointer to returned FrameSet */
   AstMapping *map10;        /* Pointer to a Mapping */
   AstMapping *map1;         /* Pointer to a Mapping */
   AstMapping *map2;         /* Pointer to a Mapping */
   AstMapping *map3;         /* Pointer to a Mapping */
   AstMapping *map4;         /* Pointer to a Mapping */
   AstMapping *map5;         /* Pointer to a Mapping */
   AstMapping *map6;         /* Pointer to a Mapping */
   AstMapping *map7;         /* Pointer to a Mapping */
   AstMapping *map8;         /* Pointer to a Mapping */
   AstMapping *map9;         /* Pointer to a Mapping */
   AstMapping *ret;          /* Pointer to the returned Mapping */
   AstMapping *tabmap;       /* Mapping from psi to WCS (paper III - 6.1.2) */
   AstSkyFrame *reffrm;      /* SkyFrame defining reflon and reflat */
   char id[2];               /* ID string for returned Frame */
   char iwc[5];              /* Domain name for IWC Frame */
   const char *cc;           /* Pointer to Domain */
   double dtai;              /* TAI-UTC correction in seconds */
   double dut1;              /* UT1-UTC correction in days */
   double dval;              /* Temporary double value */
   double reflat;            /* Reference celestial latitude */
   double reflon;            /* Reference celestial longitude */
   int *tabaxis;             /* Flags indicating -TAB axes */
   int wcsaxes;              /* Number of physical axes */

/* Initialise the pointer to the returned Mapping. */
   ret = NULL;

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

/* Identify any axes that use the -TAB algoritm code described in FITS-WCS
   paper III, and convert their CTYPE values to describe linear axes
   (i.e. just remove "-TAB" from the CTYPE value). This also returns a
   Mapping (which includes one or more LutMaps) that should be applied to
   the resulting linear axis values in order to generate the final WCS
   axis values. A NULL pointer is returned if no axes use -TAB. */
   tabmap = TabMapping( this, store, s, &tabaxis, method, class, status );

/* Obtain the number of physical 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, status );
   if( dval != AST__BAD ) {
      wcsaxes = (int) dval + 0.5;
   } else {
      wcsaxes = store->naxis;
   }

/* Create a simple Frame to represent IWC coords. */
   iwcfrm = astFrame( wcsaxes, "Title=FITS Intermediate World Coordinates", status );
   strcpy( iwc, "IWC" );
   iwc[ 3 ]= s;
   iwc[ 4 ]= 0;
   astSetDomain( iwcfrm, iwc );

/* Create a simple Frame which will be used as the initial representation
   for the physical axes. This Frame will be changed later (or possibly
   replaced by a Frame of another class) when we know what type of
   physical axes we are dealing with. Set its Domain to "AST_FITSCHAN"
   This value is used to identify axes which have not been changed,
   and will be replaced before returning the final FrameSet. */
   *frm = astFrame( wcsaxes, "Domain=AST_FITSCHAN", status );

/* Store the coordinate version character as the Ident attribute for the
   returned Frame. */
   id[ 0 ] = s;
   id[ 1 ] = 0;
   astSetIdent( *frm, id );

/* Create a Mapping which goes from pixel coordinates to what FITS-WCS
   paper I calls "intermediate world coordinates". This stage is the same
   for all axes. It uses the CRPIXj, PCi_j and CDELTi headers (and
   distortion codes from the CTYPE keywords). */
   map1 = WcsIntWorld( this, store, s, wcsaxes, method, class, status );

/* The conversion from intermediate world coordinates to the final world
   coordinates depends on the type of axis being converted (as specified
   by its CTYPE keyword). Check for each type of axis for which known
   conventions exist... */

/* Celestial coordinate axes. The following call returns a Mapping which
   transforms any celestial coordinate axes from intermediate world
   coordinates to the final celestial coordinates. Other axes are left
   unchanged by the Mapping. It also modifies the Frame so that a
   SkyFrame is used to describe the celestial axes. */
   map2 = WcsCelestial( this, store, s, frm, iwcfrm, &reflon, &reflat,
                        &reffrm, &tabmap, tabaxis, method, class, status );

/* Spectral coordinate axes. The following call returns a Mapping which
   transforms any spectral coordinate axes from intermediate world
   coordinates to the final spectral coordinates. Other axes are left
   unchanged by the Mapping. It also modifies the Frame so that a
   SpecFrame is used to describe the spectral axes. */
   map3 = WcsSpectral( this, store, s, frm, iwcfrm, reflon, reflat, reffrm,
                       method, class, status );

/* Any axes which were not recognized by the above calls are assumed to
   be linear. Create a Mapping which adds on the reference value for such
   axes, and modify the Frame to desribe the axes. */
   map4 = WcsOthers( this, store, s, frm, iwcfrm, method, class, status );

/* If the Frame still has the Domain "AST_FITSCHAN", clear it. */
   cc = astGetDomain( *frm );
   if( cc && !strcmp( cc, "AST_FITSCHAN" ) ) astClearDomain( *frm );

/* Concatenate the Mappings and simplify the result. */
   map5 = (AstMapping *) astCmpMap( map1, map2, 1, "", status );
   map6 = (AstMapping *) astCmpMap( map5, map3, 1, "", status );
   map7 = (AstMapping *) astCmpMap( map6, map4, 1, "", status );
   if( tabmap ) {
      map8 = (AstMapping *) astCmpMap( map7, tabmap, 1, "", status );
   } else {
      map8 = astClone( map7 );
   }

   ret = astSimplify( map8 );

/* Ensure that the coordinate version character is stored as the Ident
   attribute for the returned Frame (the above calls may have changed it). */
   astSetIdent( *frm, id );

/* Set the DUT1 value. Note, the JACH store DUT1 in units of days in their
   FITS headers, so convert from days to seconds. May need to do somthing
   about this if the forthcoming FITS-WCS paper 5 (time axes) defines DUT1
   to be in seconds. */
   dut1 = GetItem( &(store->dut1), 0, 0, s, NULL, method, class, status );
   if( dut1 != AST__BAD ) astSetDut1( *frm, dut1*SPD );

/* Set the DTAI value. */
   dtai = GetItem( &(store->dtai), 0, 0, s, NULL, method, class, status );
   if( dtai != AST__BAD ) astSetDtai( *frm, dtai );

/* The returned Frame is actually a FrameSet in which the base (first) Frame
   is the required WCS Frame. The FrameSet contains one other Frame,
   which is the Frame representing IWC and is always frame 2, the current
   Frame. Create a FrameSet containing these two Frames. */
   if( astGetIwc( this ) ) {
      fs = astFrameSet( *frm, "", status );
      astInvert( ret );
      map9 = (AstMapping *) astCmpMap( ret, map1, 1, "", status );
      astInvert( ret );
      map10 = astSimplify( map9 );
      astAddFrame( fs, AST__BASE, map10, iwcfrm );

/* Return this FrameSet instead of the Frame. */
      *frm = astAnnul( *frm );
      *frm = (AstFrame *) fs;

/* Free resources */
      map9 = astAnnul( map9 );
      map10 = astAnnul( map10 );

/* Also modify the returned mapping so that it goes from pixel to iwc
   instead of to wcs. */
      ret = astAnnul( ret );
      ret = astClone( map1 );
   }

/* Annull temporary resources. */
   if( reffrm ) reffrm = astAnnul( reffrm );
   if( tabmap ) tabmap = astAnnul( tabmap );
   tabaxis = astFree( tabaxis );
   iwcfrm = astAnnul( iwcfrm );
   map1 = astAnnul( map1 );
   map2 = astAnnul( map2 );
   map3 = astAnnul( map3 );
   map4 = astAnnul( map4 );
   map5 = astAnnul( map5 );
   map6 = astAnnul( map6 );
   map7 = astAnnul( map7 );
   map8 = astAnnul( map8 );

/* Annul thre returned objects if an error has occurred. */
   if( !astOK ) {
      ret = astAnnul( ret );
      *frm = astAnnul( *frm );
   }

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

static AstMatrixMap *WcsPCMatrix( FitsStore *store, char s, int naxes,
                                  const char *method, const char *class, int *status ){
/*
*  Name:
*     WcsPCMatrix

*  Purpose:
*     Create a MatrixMap representing the PC matrix.

*  Type:
*     Private function.

*  Synopsis:
*     AstMatrixMap *WcsPCMatrix( FitsStore *store, char s, int naxes,
*                                const char *method, const char *class, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     A MatrixMap representing the FITS "PC" matrix is returned.

*  Parameters:
*     store
*        A structure containing values for FITS keywords relating to
*        the World Coordinate System.
*     s
*        A character s 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).
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to the created MatrixMap or a NULL pointer if an
*     error occurred.
*/

/* Local Variables: */
   AstMatrixMap *new;       /* The created MatrixMap */
   double *el;              /* Pointer to next matrix element */
   double *mat;             /* Pointer to matrix array */
   int i;                   /* Pixel axis index */
   int j;                   /* Intermediate axis index. */

/* Initialise/ */
   new = NULL;

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

/* Allocate memory for the matrix. */
   mat = (double *) astMalloc( sizeof(double)*naxes*naxes );
   if( astOK ){

/* Fill the matrix with values from the FitsStore. */
      el = mat;
      for( i = 0; i < naxes; i++ ){
         for( j = 0; j < naxes; j++ ){

/* Get the PCj_i value for this axis. Missing terms can be defaulted so
   do not report an error if the required value is not present in the
   FitsStore. */
            *el = GetItem( &(store->pc), i, j, s, NULL, method, class, status );

/* Diagonal terms default to to 1.0, off-diagonal to zero. */
            if( *el == AST__BAD ) *el = ( i == j ) ? 1.0: 0.0;

/* Move on to the next matrix element. */
            el++;
         }
      }

/* Create the matrix. */
      new = astMatrixMap( naxes, naxes, 0, mat, "", status );

/* Report an error if the inverse transformation is undefined. */
      if( !astGetTranInverse( new ) && astOK ) {
        astError( AST__BDFTS, "%s(%s): Unusable rotation matrix (PC or CD) found "
                  "in the FITS-WCS header - the matrix cannot be inverted.", status, method, class );
      }

/* Release the memory used to hold the matrix. */
      mat = (double *) astFree( (void *) mat );
   }

/* If an error has occurred, attempt to annul the returned MatrixMap. */
   if( !astOK ) new = astAnnul( new );

/* Return the MatrixMap. */
   return new;
}

static AstMapping *WcsNative( AstFitsChan *this, FitsStore *store, char s,
                              AstWcsMap *wcsmap, int fits_ilon, int fits_ilat,
                              const char *method, const char *class, int *status ){
/*
*  Name:
*     WcsNative

*  Purpose:
*     Create a CmpMap which transforms Native Spherical Coords to
*     Celestial Coords.

*  Type:
*     Private function.

*  Synopsis:
*     AstMapping *WcsNative( AstFitsChan *this, FitsStore *store, char s,
*                            AstWcsMap *wcsmap, int fits_ilon, int fits_ilat,
*                            const char *method, const char *class, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     A CmpMap is created which rotates the supplied Native Spherical Coords
*     into Celestial Coords in the standard system specified by the CTYPE
*     keywords. Any non-celestial axes are left unchanged.
*
*     At the highest level, the returned CmpMap is made up of the following

*     Mappings in series (if celestial long/lat axes are present):
*        1 - A PermMap which rearranges the axes so that the longitude axis is
*            axis 0, the latitude axis is axis 1, and all other axes are
*            stored at higher indices, starting at axis 2.
*        2 - A CmpMap which converts the values on axes 0 and 1 from Native
*            Spherical to Celestial coordinates, leaving all other axes
*            unchanged.
*        3 - A PermMap which rearranges the axes to put the longitude and
*            latitude axes back in their original places. This is just the
*            inverse of the PermMap used at stage 1 above.
*
*     The CmpMap used at stage 2 above, is made up of two Mappings in

*     parallel:
*         4 - A CmpMap which maps axes 0 and 1 from Native Spherical to
*             Celestial coordinates.
*         5 - A UnitMap which passes on the values to axes 2, 3, etc,
*             without change.
*
*     The CmpMap used at stage 4 above, is made up of the following Mappings

*     in series:
*         6 - A SphMap which converts the supplied spherical coordinates into
*             Cartesian Coordinates.
*         7 - A MatrixMap which rotates the Cartesian coordinates from the
*             Native to the Celestial system.
*         8 - A SphMap which converts the resulting Cartesian coordinates back
*             to spherical coordinates.

*  Parameters:
*     this
*        The FitsChan in which to store any warning cards. If NULL, no
*        warnings are stored.
*     store
*        A structure containing values for FITS keywords relating to
*        the World Coordinate System.
*     s
*        Co-ordinate version character to use (space means primary axes).
*     wcsmap
*        A mapping describing the deprojection which is being used. This is
*        needed in order to be able to locate the fiducial point within the
*        Native Speherical Coordinate system, since it varies from projection
*        to projection.
*     fits_ilon
*        The zero-based FITS WCS axis index corresponding to celestial
*        longitude (i.e. one less than the value of "i" in the keyword
*        names "CTYPEi", "CRVALi", etc). If -1 is supplied, the index of
*        the longitude axis in the supplied WcsMap is used.
*     fits_ilat
*        The zero-based FITS WCS axis index corresponding to celestial
*        latitude (i.e. one less than the value of "i" in the keyword
*        names "CTYPEi", "CRVALi", etc). If -1 is supplied, the index of
*        the latitude axis in the supplied WcsMap is used.
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to the created CmpMap or a NULL pointer if an error occurred.

*  Notes:
*     -  The local variable names correspond to the notation in the papers
*     by Greisen & Calabretta describing the FITS WCS system.
*/

/* Local Variables: */
   AstCmpMap *cmpmap;         /* A CmpMap */
   AstMapping *new;           /* The returned CmpMap */
   AstMatrixMap *matmap2;     /* Another MatrixMap */
   AstMatrixMap *matmap;      /* A MatrixMap */
   AstPermMap *permmap;       /* A PermMap */
   AstSphMap *sphmap;         /* A SphMap */
   AstUnitMap *unitmap;       /* A UnitMap */
   char buf[150];             /* Message buffer */
   double alpha0;             /* Long. of fiduaicl point in standard system */
   double alphap;             /* Long. of native nth pole in standard system */
   double axis[3];            /* Vector giving the axis of rotation */
   double delta0;             /* Lat. of fiducial point in standard system */
   double deltap;             /* Lat. of native nth pole in standard system */
   double latpole;            /* Lat. of native nth pole in standard system if deltap undefined */
   double phip;               /* Long. of standard nth pole in native system */
   double phi0;               /* Native longitude at fiducial point */
   double theta0;             /* Native latitude at fiducial point */
   int *inperm;               /* Pointer to array of output axis indices */
   int *outperm;              /* Pointer to array of input axis indices */
   int axlat;                 /* Index of latitude physical axis */
   int axlon;                 /* Index of longitude physical axis */
   int i;                     /* Loop count */
   int nax_rem;               /* No. of non-astrometric axes */
   int naxis;                 /* No. of axes. */
   int new_axlat;             /* Index of lat. physical axis after perming */
   int tpn;                   /* Is this a TPN projection? */

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

/* Initialise the returned CmpMap pointer. */
   new = NULL;

/* Store the number of axes in a local variable. */
   naxis = astGetNin( wcsmap );

/* Get the indices of the celestial axes. */
   axlon = astGetWcsAxis( wcsmap, 0 );
   axlat = astGetWcsAxis( wcsmap, 1 );

/* If the corresponding FITS axis indices were not supplied, use the
   WcsMap axes found above. */
   if( fits_ilon == -1 ) fits_ilon = axlon;
   if( fits_ilat == -1 ) fits_ilat = axlat;

/* If there is no longitude or latitude axis, or if we have a
   non-celestial projection, just return a UnitMap. */
   if( axlon == axlat || astGetWcsType( wcsmap ) == AST__WCSBAD ){
      new = (AstMapping *) astUnitMap( naxis, "", status );

/* If there is a lon/lat axis pair, create the inperm and outperm arrays
   which will be needed later to create the PermMap which reorganises
   the axes so that axis zero is the longitude axis and axis 1 is the
   latitude axis. */
   } else {

/* Get storage for the two arrays. */
      inperm = (int *) astMalloc( sizeof( int )*(size_t)naxis );
      outperm = (int *) astMalloc( sizeof( int )*(size_t)naxis );
      if( astOK ){

/* Initialise an array holding the indices of the input axes which are copied
   to each output axis. Initially assume that there is no re-arranging of
   the axes. */
         for( i = 0; i < naxis; i++ ) outperm[ i ] = i;

/* Swap the longitude axis and axis 0. */
         i = outperm[ axlon ];
         outperm[ axlon ] = outperm[ 0 ];
         outperm[ 0 ] = i;

/* If axis 0 was originally the latitude axis, the latitude axis will now
   be where the longitude axis was originally (because of the above axis
   swap). */
         if( axlat == 0 ) {
            new_axlat = axlon;
         } else {
            new_axlat = axlat;
         }

/* Swap the latitude axis and axis 1. */
         i = outperm[ new_axlat ];
         outperm[ new_axlat ] = outperm[ 1 ];
         outperm[ 1 ] = i;

/* Create the array holding the output axis index corresponding to
   each input axis. */
         for( i = 0; i < naxis; i++ ) inperm[ outperm[ i ] ] = i;
      }

/* Store the latitude and longitude (in the standard system) of the fiducial
   point, in radians. */
      delta0 = GetItem( &(store->crval), fits_ilat, 0, s, NULL, method, class, status );
      if( delta0 == AST__BAD ) delta0 = 0.0;
      delta0 *= AST__DD2R;
      alpha0 = GetItem( &(store->crval), fits_ilon, 0, s, NULL, method, class, status );
      if( alpha0 == AST__BAD ) alpha0 = 0.0;
      alpha0 *= AST__DD2R;

/* Limit the latitude to the range +/- PI/2, issuing a warning if the
   supplied CRVAL value is outside this range. The "alphap" variable is used
   as workspace here. */
      alphap = palDrange( delta0 );
      delta0 = alphap;
      if ( delta0 > AST__DPIBY2 ){
         delta0 = AST__DPIBY2;
      } else if ( delta0 < -AST__DPIBY2 ){
         delta0 = -AST__DPIBY2;
      }
      if( alphap != delta0 ) {
         sprintf( buf, "The original FITS header specified a fiducial "
                  "point with latitude %.*g. A value of %.*g is being used "
                  "instead. ", AST__DBL_DIG, alphap*AST__DR2D, AST__DBL_DIG,
                  delta0*AST__DR2D );
         Warn( this, "badlat", buf, method, class, status );
      }

/* Set a flag indicating if we have a TPN projection. The handling or
   projection parameters  is different for TPN projections.  */
      tpn = ( astGetWcsType( wcsmap ) == AST__TPN );

/* Store the radian values of the FITS keywords LONPOLE and LATPOLE. Defaults
   will be used if either of these items was not supplied. These keyword
   values may be stored in projection parameters PVi_3a and PVi_4a for
   longitude axis "i" - in which case the "PV" values take precedence over
   the "LONPOLE" and "LATPOLE" values. Do not do this for TPN projections
   since they use these projection parameters to specify correcton terms. */
      if( astTestPV( wcsmap, axlon, 3 ) && !tpn ) {
         phip = astGetPV( wcsmap, axlon, 3 );
      } else {
         phip = GetItem( &(store->lonpole), 0, 0, s, NULL, method, class, status );
         if( phip != AST__BAD && !tpn ) astSetPV( wcsmap, axlon, 3, phip );
      }
      if( phip != AST__BAD ) phip *= AST__DD2R;
      if( astTestPV( wcsmap, axlon, 4 ) && !tpn ) {
         latpole = astGetPV( wcsmap, axlon, 4 );
      } else {
         latpole = GetItem( &(store->latpole), 0, 0, s, NULL, method, class, status );
         if( latpole != AST__BAD && !tpn ) astSetPV( wcsmap, axlon, 4, latpole );
      }
      if( latpole != AST__BAD ) latpole *= AST__DD2R;

/* Find the standard Celestial Coordinates of the north pole of the Native
   Spherical Coordinate system. Report an error if the position was not
   defined. */
      if( !WcsNatPole( this, wcsmap, alpha0, delta0, latpole, &phip, &alphap,
                       &deltap, status ) && astOK ){
         astError( AST__BDFTS, "%s(%s): Conversion from FITS WCS native "
                   "coordinates to celestial coordinates is ill-conditioned.", status,
                   method, class );
      }

/* Create the SphMap which converts spherical coordinates to Cartesian
   coordinates (stage 6 in the prologue). This asumes that axis 0 is the
   longitude axis, and axis 1 is the latitude axis. This will be ensured
   by a PermMap created later. Indicate that the SphMap will only be used
   to transform points on a unit sphere. This enables a forward SphMap
   to be combined with an inverse SphMap into a UnitMap, and thus aids
   simplification. */
      sphmap = astSphMap( "UnitRadius=1", status );
      astInvert( sphmap );

/* Set the PolarLong attribute of the SphMap so that a longitude of phi0 (the
   native longitude of the fiducial point) is returned by the inverse
   transformation (cartesian->spherical) at either pole. */
      GetFiducialNSC( wcsmap, &phi0, &theta0, status );
      astSetPolarLong( sphmap, phi0 );

/* Create a unit MatrixMap to be the basis of the MatrixMap which rotates
   Native Spherical Coords to Celestial Coords (stage 7 in the prologue). */
      matmap = astMatrixMap( 3, 3, 2, NULL, "", status );

/* Modify the above MatrixMap so that it rotates the Cartesian position vectors
   by -phip (i.e. LONPOLE) about the Z axis. This puts the north pole of the
   standard system at zero longitude in the rotated system. Then annul the
   original MatrixMap and use the new one instead. */
      axis[ 0 ] = 0;
      axis[ 1 ] = 0;
      axis[ 2 ] = 1;
      matmap2 = astMtrRot( matmap, -phip, axis );
      matmap = astAnnul( matmap );
      matmap = matmap2;

/* Now modify the above MatrixMap so that it rotates the Cartesian position
   vectors by -(PI/2-deltap) about the Y axis. This puts the north pole of
   the standard system as 90 degrees latitude in the rotated system. Then annul
   the original MatrixMap and use the new one instead. */
      axis[ 0 ] = 0;
      axis[ 1 ] = 1;
      axis[ 2 ] = 0;
      matmap2 = astMtrRot( matmap, deltap - AST__DPIBY2, axis );
      matmap = astAnnul( matmap );
      matmap = matmap2;

/* Finally modify the above MatrixMap so that it rotates the Cartesian position
   vectors (PI+alphap) about the Z axis. This puts the primary meridian of the
   standard system at zero longitude in the rotated system. This results in the
   rotated system being coincident with the standard system. */
      axis[ 0 ] = 0;
      axis[ 1 ] = 0;
      axis[ 2 ] = 1;
      matmap2 = astMtrRot( matmap, AST__DPI + alphap, axis );
      matmap = astAnnul( matmap );
      matmap = matmap2;

/* Combine the SphMap (stage 6) and MatrixMap (stage 7) in series. */
      cmpmap = astCmpMap( sphmap, matmap, 1, "", status );
      sphmap = astAnnul( sphmap );
      matmap = astAnnul( matmap );

/* Create a new SphMap which converts Cartesian coordinates to spherical
   coordinates (stage 8 in the prologue). Indicate that the SphMap will
   only be used to transform points on a unit sphere. */
      sphmap = astSphMap( "UnitRadius=1", status );

/* Set the PolarLong attribute of the SphMap so that a longitude of alpha0
   (the celestial longitude of the fiducial point) is returned by the
   forward transformation (cartesian->spherical) at either pole. */
      astSetPolarLong( sphmap, alpha0 );

/* Add it to the compound mapping. The CmpMap then corresponds to stage 4
   in the prologue. Annul the constituent mappings. */
      new = (AstMapping *) astCmpMap( cmpmap, sphmap, 1, "", status );
      cmpmap = astAnnul( cmpmap );
      sphmap = astAnnul( sphmap );

/* If there are any remaining axes (i.e. axes which do not describe a
   Celestial coordinate system), create a UnitMap which passes on their
   values unchanged (stage 5 in the prologue), and add it the CmpMap,
   putting it in parallel with the earlier mappings. The resulting CmpMap
   then corresponds to stage 2 in the prologue. Note, the axis numbering
   used by this UnitMap needs to take account of the fact that it is only
   applied to the non-celestial axes. The axes are re-ordered by the
   PermMap described at stage 1 in the prologue. */
      nax_rem = naxis - 2;
      if( nax_rem > 0 ){
         unitmap = astUnitMap( nax_rem, "", status );
         cmpmap = astCmpMap( new, unitmap, 0, "", status );
         new = astAnnul( new );
         unitmap = astAnnul( unitmap );
         new = (AstMapping *) cmpmap;
      }

/* Now we need to ensure that axes 0 and 1 correspond to longitude and
   latitude. If this is already the case, then the CmpMap can be returned
   as it is. Otherwise, a PermMap needs to be created to rearrange the
   axes. */
      if( axlon != 0 || axlat != 1 ){

/* Create the PermMap using the inperm and outperm arrays created earlier.
   This is the mapping described as stage 1 in the prologue. */
         permmap = astPermMap( naxis, inperm, naxis, outperm, NULL, "", status );

/* Compound this PermMap and the CmpMap corresponding to stage 2 (created
   earlier) in series. */
         cmpmap = astCmpMap( permmap, new, 1, "", status );
         new = astAnnul( new );
         new = (AstMapping *) cmpmap;

/* Now invert the PermMap, so that it re-arranges the axes back into
   their original order. This is the mapping described as stage 3 in
   the prologue. */
         astInvert( permmap );

/* And finally.... add this inverted PermMap onto the end of the CmpMap. */
         cmpmap = astCmpMap( new, permmap, 1, "", status );
         permmap = astAnnul( permmap );
         new = astAnnul( new );
         new = (AstMapping *) cmpmap;
      }

/* Free the temporary arrays. */
      inperm = (int *) astFree( (void *) inperm );
      outperm = (int *) astFree( (void *) outperm );
   }

/* If an error has occurred, attempt to annul the new CmpMap. */
   if( !astOK ) new = astAnnul( new );

/* Return the CmpMap. */
   return new;
}

static int WcsNatPole( AstFitsChan *this, AstWcsMap *wcsmap, double alpha0,
                       double delta0, double latpole, double *phip,
                       double *alphap, double *deltap, int *status ){

/*
*  Name:
*     WcsNatPole

*  Purpose:
*     Find the celestial coordinates of the north pole of the Native Spherical
*     Coordinate system.

*  Type:
*     Private function.

*  Synopsis:

*     int WcsNatPole( AstFitsChan *this, AstWcsMap *wcsmap, double alpha0,
*                     double delta0, double latpole, double *phip,
*                     double *alphap, double *deltap, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     The supplied WcsMap converts projected positions given in
*     "Projection Plane Coords" to positions in the "Native Spherical
*     Coordinate" system. This function finds the pole of this spherical
*     coordinate system in terms of the standard celestial coordinate
*     system to which the CRVALi, LONPOLE and LATPOLE keywords refer (this
*     system should be identified by characters 5-8 of the CTYPEi
*     keywords). It also supplies a default value for LONPOLE if no value
*     has been supplied explicitly in the FITS header.
*
*     This function implements equations 8, 9 and 10 from the FITS-WCS paper
*     II by Calabretta & Greisen (plus the associated treatment of special
*     cases). The paper provides more detailed documentation for the
*     mathematics implemented by this function.

*  Parameters:
*     this
*        The FitsChan in which to store any warning cards. If NULL, no
*        warnings are stored.
*     wcsmap
*        A mapping describing the deprojection being used (i.e. the
*        mapping from Projection Plane Coords to Native Spherical Coords).
*     alpha0
*        The longitude of the fiducial point in the standard celestial
*        coordinate frame (in radians). Note, this fiducial point does
*        not necessarily correspond to the point given by keywords CRPIXj.
*     delta0
*        The celestial latitude (radians) of the fiducial point.
*     latpole
*        The value of FITS keyword LATPOLE, converted to radians, or the
*        symbolic constant AST__BAD if the keyword was not supplied.
*     phip
*        Pointer to a location at which is stored the longitude of the north
*        pole of the standard Celestial coordinate system, as measured in
*        the Native Spherical Coordinate system, in radians. This should be
*        supplied holding the radian equivalent of the value of the FITS
*        keyword LONPOLE, or the symbolic constant AST__BAD if the keyword was
*        not supplied (in which case a default value will be returned at the
*        given location).
*     alphap
*        Pointer to a location at which to store the calculated longitude
*        of the Native North Pole, in radians.
*     deltap
*        Pointer to a location at which to store the calculated latitude
*        of the Native North Pole, in radians.
*     class
*        A pointer to a string holding the class of the object being
*        read. This is used only in the construction of error messages.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A status: non-zero for success, zero if the position of the native
*     north pole is undefined.

*  Notes:
*     -  Certain combinations of keyword values result in the latitude of
*     the Native North Pole being undefined. In these cases, a value of
*     0 is returned for the function value, but no error is reported.
*     -  All angular values used by this function are in radians.
*     -  A value of 0 is returned if an error has already occurred.
*/

/* Local Variables: */
   char buf[150];                  /* Buffer for warning message */
   double cos_theta0;              /* Cosine of theta0 */
   double cos_phip;                /* Cosine of (phip - phi0) */
   double cos_delta0;              /* Cosine of delta0 */
   double cos_deltap;              /* Cosine of deltap */
   double deltap_1;                /* First possible value for deltap */
   double deltap_2;                /* Second possible value for deltap */
   double sin_theta0;              /* Sine of theta0 */
   double sin_phip;                /* Sine of (phip - phi0) */
   double sin_delta0;              /* Sine of delta0 */
   double sin_deltap;              /* Sine of deltap */
   double t0, t1, t2, t3, t4;      /* Intermediate values */
   double phi0, theta0;            /* Native coords of fiducial point */

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

/* Get the longitude and latitude of the fiducial point in the native
   spherical coordinate frame (in radians). */
   GetFiducialNSC( wcsmap, &phi0, &theta0, status );

/* If no value was supplied for the FITS keyword LONPOLE, set up a default
   value such that the celestial latitude increases in the same direction
   as the native latitude at the fiducial; point. */
   if( *phip == AST__BAD ){
      if( delta0 >= theta0 ){
         *phip = 0.0;
      } else {
         *phip = AST__DPI;
      }

/* Issue a warning that a default lonpole value has been adopted. */
      sprintf( buf, "The original FITS header did not specify the "
               "longitude of the native north pole. A default value "
               "of %.8g degrees was assumed.", (*phip)*AST__DR2D );
      Warn( this, "nolonpole", buf, "astRead", "FitsChan", status );
   }

/* If the fiducial point is coincident with the Native North Pole, then the
   Native North Pole must have the same coordinates as the fiducial
   point. Tests for equality include some tolerance to allow for rounding
   errors. */
   sin_theta0 = sin( theta0 );
   if( astEQUAL( sin_theta0, 1.0 ) ){
      *alphap = alpha0;
      *deltap = delta0;

/* If the fiducial point is concident with the Native South Pole, then the
   Native North Pole must have the coordinates of the point diametrically
   opposite the fiducial point. */
   } else if( astEQUAL( sin_theta0, -1.0 ) ){
      *alphap = alpha0 + AST__DPI;
      *deltap = -delta0;

/* For all other cases, go through the procedure described in the WCS paper
   by Greisen & Calabretta, to find the position of the Native North Pole.
   First store some useful values. */
   } else {
      cos_theta0 = cos( theta0 );
      cos_delta0 = cos( delta0 );
      cos_phip = cos( *phip - phi0 );
      sin_delta0 = sin( delta0 );
      sin_phip = sin( *phip - phi0 );

/* Next, find the two possible values for the latitude of the Native
   North Pole (deltap). If any stage of this transformation is
   indeterminate, return zero (except for the single special case noted
   in item 6 para. 2 of the WCS paper, for which LATPOLE specifies the
   values to be used). */
      t0 = cos_theta0*cos_phip;
      if( fabs( t0 ) < TOL2 && fabs( sin_theta0 ) < TOL2 ){
         if( latpole != AST__BAD ) {
            *deltap = latpole;
         } else {
            return 0;
         }
      } else {
         t1 = atan2( sin_theta0, t0 );
         t2 = cos_theta0*cos_phip;
         t2 *= t2;
         t2 += sin_theta0*sin_theta0;
         if( t2 <= DBL_MIN ){
            return 0;
         } else {
            t3 = sin_delta0/sqrt( t2 );
            if( fabs( t3 ) > 1.0 + TOL1 ){
               return 0;
            } else {
               if( t3 < -1.0 ){
                  t4 = AST__DPI;
               } else if( t3 > 1.0 ){
                  t4 = 0.0;
               } else {
                  t4 = acos( t3 );
               }
               deltap_1 = palDrange( t1 + t4 );
               deltap_2 = palDrange( t1 - t4 );

/* Select which of these two values of deltap to use. Values outside the
   range +/- PI/2 cannot be used. If both values are within this range
   use the value which is closest to the supplied value of latpole (or
   use the northern most value if the LATPOLE keyword was not supplied. */
               if( fabs( deltap_1 ) > AST__DPIBY2 + TOL2 ){
                  *deltap = deltap_2;
               } else if( fabs( deltap_2 ) > AST__DPIBY2 + TOL2 ){
                  *deltap = deltap_1;
               } else {
                  if( latpole != AST__BAD ){
                     if( fabs( deltap_1 - latpole ) <
                         fabs( deltap_2 - latpole ) ){
                        *deltap = deltap_1;
                     } else {
                        *deltap = deltap_2;
                     }
                  } else {
                     if( deltap_1 > deltap_2 ){
                        *deltap = deltap_1;
                     } else {
                        *deltap = deltap_2;
                     }

/* Issue a warning that a default latpole value has been adopted. */
                     sprintf( buf, "The original FITS header did not specify "
                              "the latitude of the native north pole. A "
                              "default value of %.8g degrees was assumed.",
                              (*deltap)*AST__DR2D );
                     Warn( this, "nolatpole", buf, "astRead", "FitsChan", status );
                  }
               }
               if( fabs( *deltap  ) > AST__DPIBY2 + TOL2 ) {
                  return 0;
               } else if( *deltap < -AST__DPIBY2 ){
                  *deltap = -AST__DPIBY2;
               } else if( *deltap > AST__DPIBY2 ){
                  *deltap = AST__DPIBY2;
               }
            }
         }
      }

/* If a valid value for the latitude (deltap) has been found, find the
   longitude of the Native North Pole. */
      if( *deltap != AST__BAD ) {
         if( fabs( cos_delta0) > TOL2 ){
            cos_deltap = cos( *deltap );
            sin_deltap = sin( *deltap );
            if( fabs( cos_deltap ) > TOL2 ){
               t1 = sin_phip*cos_theta0/cos_delta0;
               t2 = ( sin_theta0 - sin_deltap*sin_delta0 )
                    /( cos_delta0*cos_deltap );
               if( ( fabs( t1 ) > TOL2 ) || ( fabs( t2 ) > TOL2 ) ){
                  *alphap = alpha0 - atan2( t1, t2 );
               } else {
                  *alphap = alpha0;
               }
            } else if( sin_deltap > 0.0 ){
               *alphap = alpha0 + (*phip - phi0) - AST__DPI;
            } else {
               *alphap = alpha0 - (*phip - phi0);
            }
         } else {
            *alphap = alpha0;
         }
      } else {
         *alphap = AST__BAD;
      }
   }

/* Return a success status if valid latitude and longitude values were
   found. */
   return (*deltap) != AST__BAD && (*alphap) != AST__BAD ;
}

static AstMapping *WcsOthers( AstFitsChan *this, FitsStore *store, char s,
                              AstFrame **frm,  AstFrame *iwcfrm, const char *method,
                              const char *class, int *status ){

/*
*  Name:
*     WcsOthers

*  Purpose:
*     Create a Mapping from intermediate world coords to any axes
*     which are not covered by specialised conventions.

*  Type:
*     Private function.

*  Synopsis:

*     AstMapping *WcsOthers( AstFitsChan *this, FitsStore *store, char s,
*                            AstFrame **frm,  AstFrame *iwcfrm, const char *method,
*                            const char *class, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function interprets the contents of the supplied FitsStore
*     structure, looking for world coordinate axes for which no
*     description has yet been added to the supplied Frame . It is
*     assumed that any such axes are simple linear axes. It returns a
*     Mapping which simply adds on the CRVAL values to such axes.
*     It also modifies the supplied Frame to describe the axes.

*  Parameters:
*     this
*        The FitsChan.
*     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').
*     frm
*        The address of a location at which to store a pointer to the
*        Frame describing the world coordinate axes.
*     iwcfrm
*        A pointer to the Frame describing the intermediate world coordinate
*        axes. The properties of this Frame may be changed on exit.
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to the Mapping.
*/

/* Local Variables: */
   AstFrame *pfrm;           /* Pointer to primary Frame */
   AstFrame *pfrm2;          /* Pointer to primary Frame */
   AstMapping *map1;         /* Pointer to a Mapping */
   AstMapping *map2;         /* Pointer to a Mapping */
   AstMapping *ret;          /* The returned Mapping */
   char **comms;             /* Pointer to array of CTYPE commments */
   char buf[ 500 ];          /* Buffer for textual attribute value */
   char buf2[ 100 ];         /* Buffer for textual attribute value */
   char buf3[ 20 ];          /* Buffer for default CTYPE value */
   char *newdom;             /* Pointer to new Domain value */
   const char *ckeyval;      /* Pointer to character keyword value */
   int i;                    /* Axis index */
   int j;                    /* Axis index */
   int len;                  /* Used length of string */
   int naxes;                /* no. of axes in Frame */
   int nother;               /* The number of "other" axes */
   int paxis;                /* Primary axis index */
   int usecom;               /* Use CTYPE comments as axis Labels? */

/* Initialise the pointer to the returned Mapping. */
   ret = NULL;

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

/* Get the number of physical axes. */
   naxes = astGetNaxes( *frm );

/* Assume we will use CTYPE comments as the axis labels. */
   usecom = 1;

/* Initialise the count of "other" axes. */
   nother = 0;

/* Get the comments associated with the CTYPE keywords for all "other"
   axes. */
   comms = astMalloc( naxes*sizeof( char * ) );
   if( comms ) {

/* Loop round all axes in the Frame, and initialise the pointer to its
   comment. */
      for( i = 0; i < naxes; i++ ){
         comms[ i ] = NULL;

/* Get the Domain for the primary frame containing the axis. This will be
   "AST_FITSCHAN" if the axis has not yet been recognised (this Domain is
   set up by WcsMapFrm). Only consider the axis further if the Domain has
   not been changed. */
         astPrimaryFrame( *frm, i, &pfrm, &paxis );
         if( !strcmp( astGetDomain( pfrm ), "AST_FITSCHAN" ) ) {

/* Increment the count of "other" axes. */
            nother++;

/* Get the comment associated with the CTYPE header. */
            ckeyval = GetItemC( &(store->ctype_com), i, 0, s, NULL, method, class, status );

/* If this axis has no CTYPE comment, we will use CTYPE values as axis
   labels (if given, the CNAME keyword take precedence). */
            if( !ckeyval || astChrLen( ckeyval ) == 0  ) {
               usecom = 0;

/* If the CTYPE comment for this axis is the same as any other comment, we
   will use CTYPE values as axis labels. */
            } else {
               for( j = 0; j < nother - 1; j++ ) {
                  if( comms[ j ]  && !strcmp( ckeyval, comms[ j ] ) ) {
                     usecom = 0;
                     break;
                  }
               }
            }

/* If we are still using comments as axis labels, store a copy of it in the
   workspace. */
            if( usecom ) comms[ i ] = astStore( NULL, ckeyval,
                                                strlen( ckeyval ) + 1 );
         }
         pfrm = astAnnul( pfrm );
      }

/* Free the workspace holding comments. */
      for( i = 0; i < naxes; i++ ) comms[ i ] = astFree( comms[ i ] );
      comms = astFree( comms );
   }

/* If there are no "other" axes, just return a UnitMap. */
   if( nother == 0 ) {
      ret = (AstMapping *) astUnitMap( naxes, "", status );

/* Otherwise... */
   } else {

/* If we have only a single other axis, use CTYPE value instead of
   comment. */
      if( nother == 1 ) usecom = 0;

/* Not yet started a new Domain value to replace "AST_FITSCHAN". */
      newdom = NULL;
      pfrm2 = NULL;

/* Check each axis of the Frame looking for axes which have not yet been
   recognised. */
      for( i = 0; i < naxes; i++ ) {

/* Get the Domain for the primary frame containing the axis. This will be
   "AST_FITSCHAN" if the axis has not yet been recognised (this Domain is
   set up by WcsMapFrm). Only consider the axis further if the Domain has
   not been changed. */
         astPrimaryFrame( *frm, i, &pfrm, &paxis );
         if( !strcmp( astGetDomain( pfrm ), "AST_FITSCHAN" ) ) {

/* Save a pointer to the primary Frame which we will use to set the
   Domain of the primary Frame. */
            if( !pfrm2 ) pfrm2 = astClone( pfrm );

/* Get the CTYPE value. Use a default of "AXISn". */
            ckeyval = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
            if( !ckeyval ) {
               sprintf( buf3, "AXIS%d", i + 1 );
               ckeyval = buf3;
            }

/* If the CTYPE value ends with "-LOG", assume it is a logarithmically spaced
   axis. Get the Mapping from IWC to WCS. Reduce the used length of the
   CTYPE string to exlude any trailing "-LOG" string. */
            len = strlen( ckeyval );
            if( len > 3 && !strcmp( ckeyval + len - 4, "-LOG" ) ){
               map1 = LogWcs( store, i, s, method, class, status );
               sprintf( buf2, "%.*s", len - 4, ckeyval );

/* Otherwise, assume the axis is linearly spaced. */
            } else {
               map1 = LinearWcs( store, i, s, method, class, status );
               sprintf( buf2, "%.*s", len, ckeyval );
            }

/* Append the CTYPE value to the final Domain value for the primary Frame. */
            if( ckeyval && astChrLen( ckeyval ) > 0 ) {
               if( newdom ) {
                  sprintf( buf, "%s-%s", newdom, buf2 );
               } else {
                  sprintf( buf, "%s", buf2 );
                  newdom = buf;
               }
            }

/* Now modify the axis in the Frame to have appropriate values for the
   Unit, Label and Symbol attributes. Also set the Unit attribute for
   the corresponding axis in the IWC Frame. */
            if( ckeyval ) astSetSymbol( *frm, i, buf2 );
            ckeyval = GetItemC( &(store->cname), i, 0, s, NULL, method, class, status );
            if( !ckeyval && usecom ) ckeyval = GetItemC( &(store->ctype_com),
                                                   i, 0, s, NULL, method, class, status );
            if( !ckeyval ) ckeyval = buf2;
            if( ckeyval ) astSetLabel( *frm, i, ckeyval );
            ckeyval = GetItemC( &(store->cunit), i, 0, s, NULL, method, class, status );
            if( ckeyval ) {
               astSetUnit( *frm, i, ckeyval );
               astSetUnit( iwcfrm, i, ckeyval );
            }

/* If this axis has been described by an earlier function (because it
   uses specialised conventions such as those described in FITS-WCS papers
   II or III), then create a UnitMap for this axis. */
         } else {
            map1 = (AstMapping *) astUnitMap( 1, "", status );
         }

/* Annul the pointer to the primary Frame containing the current axis. */
         pfrm = astAnnul( pfrm );

/* Add the Mapping for this axis in parallel with the current "running sum"
   Mapping (if any). */
         if( ret ) {
            map2 = (AstMapping *) astCmpMap( ret, map1, 0, "", status );
            ret = astAnnul( ret );
            map1 = astAnnul( map1 );
            ret = map2;
         } else {
            ret = map1;
         }
      }

/* Set the Domain name for the primary Frame. It is currently set to
   AST_FITSCHAN. We replace it with a value formed by concatenating the
   CTYPE values of its axes. */
      if( pfrm2 ) {
         if( newdom && astChrLen( newdom ) > 0 ) {
            astSetDomain( pfrm2, newdom );
         } else {
            astClearDomain( pfrm2 );
         }
         pfrm2 = astAnnul( pfrm2 );
      }

/* If the header contained a WCSNAME keyword, use it as the Domain name for
   the Frame. Also use it to create a title. */
      ckeyval = GetItemC( &(store->wcsname), 0, 0, s, NULL, method, class, status );
      if( ckeyval ){
         astSetDomain( *frm, ckeyval );
         sprintf( buf, "%s coordinates", ckeyval );
         astSetTitle( *frm, buf );
      }
   }

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

static AstWinMap *WcsShift( FitsStore *store, char s, int naxes,
                            const char *method, const char *class, int *status ){
/*
*  Name:
*     WcsShift

*  Purpose:
*     Create a WinMap which shifts pixels coordinates so that their origin
*     is at the reference pixel.

*  Type:
*     Private function.

*  Synopsis:
*     AstWinMap *WcsShift( FitsStore *store, char s, int naxes,
*                          const char *method, const char *class, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     A WinMap is created which implements a shift of origin by subtracting
*     the reference pixel coordinates (CRPIXi) from the input pixel
*     coordinates.

*  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').
*     naxes
*        The number of intermediate world coordinate axes (WCSAXES).
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to the created WinMap or a NULL pointer if an
*     error occurred.

*  Notes:
*     -  If an error occurs, a NULL pointer is returned.
*/

/* Local Variables: */
   AstWinMap *new;                 /* The created WinMap */
   int j;                          /* Pixel axis index */
   double crpix;                   /* CRPIX keyword value */
   double *c1_in;                  /* Input corner 1 */
   double *c2_in;                  /* Input corner 2 */
   double *c1_out;                 /* Output corner 1 */
   double *c2_out;                 /* Output corner 2 */

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

/* Initialise the returned WinMap pointer. */
   new = NULL;

/* Allocate memory to hold the two corners, in both input and output
   coordinates. */
   c1_in = (double *) astMalloc( sizeof( double )*(size_t) naxes );
   c1_out = (double *) astMalloc( sizeof( double )*(size_t) naxes );
   c2_in = (double *) astMalloc( sizeof( double )*(size_t) naxes );
   c2_out = (double *) astMalloc( sizeof( double )*(size_t) naxes );

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

/* Set up two arbitrary corners in the input coordinate system, and the
   corresponding values with the CRPIX values subtracted off. */
      for( j = 0; j < naxes; j++ ){

/* Get the CRPIX value for this axis. */
         crpix = GetItem( &(store->crpix), 0, j, s, NULL, method, class, status );
         if( crpix == AST__BAD ) crpix = 0.0;

/* Store the corner co-ordinates. */
         c1_in[ j ] = 0.0;
         c2_in[ j ] = 1.0;
         c1_out[ j ] = -crpix;
         c2_out[ j ] = 1.0 - crpix;
      }

/* Create the WinMap. */
      new = astWinMap( naxes, c1_in, c2_in, c1_out, c2_out, "", status );

/* If an error has occurred, attempt to annul the new WinMap. */
      if( !astOK ) new = astAnnul( new );
   }

/* Free the memory holding the corners. */
   c1_in = (double *) astFree( (void *) c1_in );
   c1_out = (double *) astFree( (void *) c1_out );
   c2_in = (double *) astFree( (void *) c2_in );
   c2_out = (double *) astFree( (void *) c2_out );

/* Return the WinMap. */
   return new;
}

static AstSkyFrame *WcsSkyFrame( AstFitsChan *this, FitsStore *store, char s,
                                 int prj, char *sys, int axlon, int axlat,
                                 const char *method, const char *class, int *status ){

/*
*  Name:
*     WcsSkyFrame

*  Purpose:
*     Create a SkyFrame to describe a WCS celestial coordinate system.

*  Type:
*     Private function.

*  Synopsis:
*     AstSkyFrame *WcsSkyFrame( AstFitsChan this, FitsStore *store, char s, int prj,
*                               char *sys, int axlon, int axlat, const char *method,
*                               const char *class, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     A SkyFrame is returned describing the celestial coordinate system
*     described by a FITS header. The axes are *not* permuted in the
*     returned Frame (that is, axis 0 is longitude and axis 1 is latitude
*     in the returned SkyFrame, no matter what values are supplied for
*     "axlat" and "axlon").

*  Parameters:
*     this
*        The FitsChan from which the keywords were read. Warning messages
*        may be added to this FitsChan.
*     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').
*     prj
*        An integer code for the WCS projection being used.
*     sys
*        A pointer to a string identifying the celestial co-ordinate system
*        implied by the CTYPE values in the FitsStore. This will be "EQU" (for
*        equatorial), or a one or two character code extracted from the
*        CTYPE values.
*     axlon
*        Zero based index of the longitude axis in the FITS header.
*     axlat
*        Zero based index of the latitude axis in the FITS header.
*     method
*        The calling method. Used only in error messages.
*     class
*        The object class. Used only in error messages.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to the SkyFrame.

*  Notes:
*     -  A NULL pointer is returned if an error has already occurred, or
*     if this function should fail for any reason.
*/

/* Local Variables: */
   AstSkyFrame *ret;              /* Returned Frame */
   char *ckeyval;                 /* Pointer to string item value */
   char *lattype;                 /* Pointer to latitude CTYPE value */
   char *lontype;                 /* Pointer to longitude CTYPE value */
   char bj;                       /* Besselian/Julian selector */
   char buf[300];                 /* Text buffer */
   char sym[10];                  /* Axis symbol */
   double dval;                   /* Floating point attribute value */
   double eqmjd;                  /* MJD equivalent of equinox */
   double equinox;                /* EQUINOX value */
   double geolat;                 /* Observer's geodetic latitude */
   double geolon;                 /* Observer's geodetic longitude */
   double h;                      /* Observer's geodetic height */
   double mjdobs;                 /* MJD-OBS value */
   double obsgeo[ 3 ];            /* Observer's Cartesian position */
   int radesys;                   /* RADESYS value */
   int report;                    /* Report unknown lon/lat system? */

/* Initialise. */
   ret = NULL;

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

/* Get the RADESYS keyword from the header, and identify the value.
   Store a integer value identifying the system. Report an error if an
   unrecognised system is supplied. Store NORADEC if the keyword was
   not supplied. */
   ckeyval = GetItemC( &(store->radesys), 0, 0, s, NULL, method, class, status );
   radesys = NORADEC;
   if( ckeyval ){
      if( !strncmp( ckeyval, "FK4 ", 4 ) ||
          !strcmp( ckeyval, "FK4" ) ){
         radesys = FK4;
      } else if( !strncmp( ckeyval, "FK4-NO-E", 8 ) ){
         radesys = FK4NOE;
      } else if( !strncmp( ckeyval, "FK5 ", 4 ) ||
                 !strcmp( ckeyval, "FK5" ) ){
         radesys = FK5;
      } else if( !strncmp( ckeyval, "ICRS ", 5 ) ||
                 !strcmp( ckeyval, "ICRS" ) ){
         radesys = ICRS;
      } else if( !strncmp( ckeyval, "GAPPT ", 6 ) ||
                 !strcmp( ckeyval, "GAPPT" ) ){
         radesys = GAPPT;
      } else if( astOK ){
         astError( AST__BDFTS, "%s(%s): FITS keyword '%s' has the "
                   "unrecognised value '%s'.", status, method, class,
                   FormatKey( "RADESYS", -1, -1, s, status ), ckeyval );
      }
   } else {
      radesys = NORADEC;
   }

/* Get the value of the EQUINOX keyword. */
   equinox = GetItem( &(store->equinox), 0, 0, s, NULL, method, class, status );

/* For FK4 and FK4-NO-E any supplied equinox value is Besselian. For all
   other systems, the equinox value is Julian. */
   bj = 0;
   if( equinox != AST__BAD ){
      if( radesys == FK4 || radesys == FK4NOE ){
         bj = 'B';
      } else if( radesys != NORADEC ) {
         bj = 'J';

/* If no RADESYS was suppied, but an equinox was, use the IAU 1984 rule
   to determine the default RADESYS and equinox type. */
      } else {
         if( equinox < 1984.0 ){
            radesys = FK4;
            bj = 'B';
         } else {
            radesys = FK5;
            bj = 'J';
         }

/* If an equatorial system is being used, give a warning that a default RADESYS
   value is being used. */
         if( !strcmp( sys, "EQU" ) ){
            sprintf( buf, "The original FITS header did not specify the "
                     "RA/DEC reference frame. A default value of %s was "
                     "assumed.", ( radesys == FK4 ) ? "FK4" : "FK5" );
            Warn( this, "noradesys", buf, method, class, status );
         }
      }

/* If no equinox was supplied, use a default equinox value depending
   on the frame of reference. For FK4-based systems, use B1950. */
   } else {
      if( radesys == FK4 || radesys == FK4NOE ){
         equinox = 1950.0;
         bj = 'B';

/* For FK5-based systems, use J2000. */
      } else if( radesys == FK5 ){
         equinox = 2000.0;
         bj = 'J';

/* If no RADESYS or EQUINOX was supplied, assume either FK4 B1950 or ICRS -
   as decided by attribute DefB1950 (GAPPT and ICRS do not use EQUINOX). */
      } else if( radesys == NORADEC ) {
         if( astGetDefB1950( this ) ) {
            equinox = 1950.0;
            bj = 'B';
            radesys = FK4;
         } else {
            radesys = ICRS;
         }
         if( !strcmp( sys, "EQU" ) ){
            sprintf( buf, "The original FITS header did not specify the "
                     "RA/DEC reference frame. A default value of %s was "
                     "assumed.", ( radesys == FK4 ) ? "FK4" : "ICRS" );
            Warn( this, "noradesys", buf, method, class, status );
         }
      }

/* If we have an equatorial or ecliptic system, issue a warning that a default
   equinox has been adopted. */
      if( ( !strcmp( sys, "EQU" ) && radesys != ICRS && radesys != GAPPT ) ||
          !strcmp( sys, "ECL" ) ){
         sprintf( buf, "The original FITS header did not specify the "
                  "reference equinox. A default value of %c%.8g was "
                  "assumed.", bj, equinox );
         Warn( this, "noequinox", buf, method, class, status );
      }
   }

/* Convert the equinox to a Modified Julian Date. */
   if( equinox != AST__BAD ) {
      if( bj == 'B' ) {
         eqmjd = palEpb2d( equinox );
      } else {
         eqmjd = palEpj2d( equinox );
      }
   } else {
      eqmjd = AST__BAD;
   }

/* Get a value for the Epoch attribute. If no value is available, use
   EQUINOX and issue a warning. */
   mjdobs = ChooseEpoch( this, store, s, method, class, status );
   if( mjdobs == AST__BAD ) {
      mjdobs = eqmjd;
      if( mjdobs != AST__BAD ) {
         sprintf( buf, "The original FITS header did not specify the "
                  "date of observation. A default value of %c%.8g was "
                  "assumed.", bj, equinox );
         Warn( this, "nomjd-obs", buf, method, class, status );
      }
   }

/* Create a SkyFrame for the specified system. */
   if( !strcmp( sys, "E" ) ){
      ret = astSkyFrame( "System=Ecliptic", status );
   } else if( !strcmp( sys, "H" ) ){
      ret = astSkyFrame( "System=Helioecliptic", status );
   } else if( !(strcmp( sys, "G" ) ) ){
      ret = astSkyFrame( "System=Galactic", status );
   } else if( !(strcmp( sys, "S" ) ) ){
      ret = astSkyFrame( "System=Supergalactic", status );
   } else if( !(strcmp( sys, "AZL" ) ) ){
      ret = astSkyFrame( "System=AzEl", status );
   } else if( !(strcmp( sys, "EQU" ) ) ){

/* For equatorial systems, the specific system is given by the RADESYS
   value. */
      if( radesys == FK4 ){
         ret = astSkyFrame( "System=FK4", status );
      } else if( radesys == FK4NOE ){
         ret = astSkyFrame( "System=FK4-NO-E", status );
      } else if( radesys == FK5 ){
         ret = astSkyFrame( "System=FK5", status );
      } else if( radesys == ICRS ){
         ret = astSkyFrame( "System=ICRS", status );
      } else if( radesys == GAPPT ){
         ret = astSkyFrame( "System=GAPPT", status );
      } else if( astOK ){
         astError( AST__INTER, "%s(%s): Internal AST programming "
                   "error - FITS equatorial coordinate system type %d "
                   "not yet supported in WcsSkyFrame.", status, method, class, radesys );
      }

/* If an unknown celestial co-ordinate system was specified by the CTYPE
   keywords, add warning messages to the FitsChan and treat the axes as
   a general spherical coordinate system. */
   } else if( astOK ){
      report = 1;
      ret = astSkyFrame( "System=UNKNOWN", status );
      strcpy( sym, sys );
      if( strlen( sys ) == 1 ) {
         strcpy( sym + 1, "LON" );
         astSetSymbol( ret, 0, sym );
         strcpy( sym + 1, "LAT" );
         astSetSymbol( ret, 1, sym );
      } else {
         strcpy( sym + 2, "LN" );
         astSetSymbol( ret, 0, sym );
         strcpy( sym + 2, "LT" );
         astSetSymbol( ret, 1, sym );

/* The code "OF" is used by AST to describe offset sky coordinates. Set
   the Domain to SKY_OFFSETS in these cases, so that we can identify
   these Frames later. */
         if( !strcmp( sys, "OF" ) ) {
            astSetDomain( ret, "SKY_OFFSETS" );
            report = 0;
         }
      }

      if( report ) {
         lontype = GetItemC( &(store->ctype), axlon, 0, s, NULL, method, class, status );
         lattype = GetItemC( &(store->ctype), axlat, 0, s, NULL, method, class, status );
         if( lontype && lattype ){
            sprintf( buf, "This FITS header contains references to an unknown "
                     "spherical co-ordinate system specified in the values "
                     "%s and %s. It may not be possible to convert to "
                     "other standard co-ordinate systems.", lontype, lattype );
            Warn( this, "badcel", buf, method, class, status );
         }
      }
   }

/* If a skyFrame was created... */
   if( ret ){

/* Store the projection description. */
      if( prj != AST__WCSBAD ) astSetProjection( ret, astWcsPrjDesc( prj )  );

/* Store the epoch of the observation in the SkyFrame. */
      if( mjdobs != AST__BAD ) astSetEpoch( ret, mjdobs );

/* For equatorial and ecliptic systems, store the epoch of the reference
   equinox in the SkyFrame. */
      if( ( !strcmp( sys, "EQU" ) || !strcmp( sys, "ECL" ) ) &&
          equinox != AST__BAD ) astSetEquinox( ret, eqmjd );

/* If either of the CNAME keywords is set, use it as the axis label. */
      ckeyval = GetItemC( &(store->cname), axlon, 0, s, NULL, method, class, status );
      if( ckeyval ) astSetLabel( ret, 0, ckeyval );
      ckeyval = GetItemC( &(store->cname), axlat, 0, s, NULL, method, class, status );
      if( ckeyval ) astSetLabel( ret, 1, ckeyval );

/* Observer's position (from primary axis descriptions). Get the OBSGEO-X/Y/Z
   keywords, convert to geodetic longitude and latitude and store as the
   SpecFrame's ObsLat, ObsLon and ObsAlt attributes. */
      obsgeo[ 0 ] = GetItem( &(store->obsgeox), 0, 0, ' ', NULL, method, class, status );
      obsgeo[ 1 ] = GetItem( &(store->obsgeoy), 0, 0, ' ', NULL, method, class, status );
      obsgeo[ 2 ] = GetItem( &(store->obsgeoz), 0, 0, ' ', NULL, method, class, status );
      if( obsgeo[ 0 ] != AST__BAD &&
          obsgeo[ 1 ] != AST__BAD &&
          obsgeo[ 2 ] != AST__BAD ) {
         eraGc2gd( 1, obsgeo, &geolon, &geolat, &h );
         astSetObsLat( ret, geolat );
         astSetObsLon( ret, geolon );
         astSetObsAlt( ret, h );
      }

/* Store values for the reference point in the SkyFrame. */
      dval = GetItem( &(store->skyref), axlon, 0, s, NULL, method, class, status );
      if( dval != AST__BAD ) astSetSkyRef( ret, 0, dval );
      dval = GetItem( &(store->skyref), axlat, 0, s, NULL, method, class, status );
      if( dval != AST__BAD ) astSetSkyRef( ret, 1, dval );

      dval = GetItem( &(store->skyrefp), axlon, 0, s, NULL, method, class, status );
      if( dval != AST__BAD ) astSetSkyRefP( ret, 0, dval );
      dval = GetItem( &(store->skyrefp), axlat, 0, s, NULL, method, class, status );
      if( dval != AST__BAD ) astSetSkyRefP( ret, 1, dval );

/* We cannot store the SkyRefIs value yet since this needs to be done
   after the SkyFrame has been added into the FrameSet, so that the Frame
   will be remapped to represent the intended offsets. SO instance, mark
   the Frame by setting the domain to "SKY_POLE" or "SKY_ORIGIN". This
   odd Domain value will be cleared later in TidyOffsets. */
      ckeyval = GetItemC( &(store->skyrefis), 0, 0, s, NULL, method, class, status );
      if( ckeyval ) {
         if( !Ustrcmp( "POLE", ckeyval, status ) ) {
            astSetDomain( ret, "SKY_POLE" );
         } else if( !Ustrcmp( "ORIGIN", ckeyval, status ) ) {
            astSetDomain( ret, "SKY_ORIGIN" );
         }
      }
   }

/* If an error has occurred, annul the Frame. */
   if( !astOK ) ret = astAnnul( ret );

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

static AstMapping *WcsSpectral( AstFitsChan *this, FitsStore *store, char s,
                                AstFrame **frm, AstFrame *iwcfrm, double reflon, double reflat,
                                AstSkyFrame *reffrm, const char *method,
                                const char *class, int *status ){

/*
*  Name:
*     WcsSpectral

*  Purpose:
*     Create a Mapping from intermediate world coords to spectral coords
*     as described in a FITS header.

*  Type:
*     Private function.

*  Synopsis:

*     AstMapping *WcsSpectral( AstFitsChan *this, FitsStore *store, char s,
*                              AstFrame **frm, AstFrame *iwcfrm, double reflon,
*                              double reflat, AstSkyFrame *reffrm,
*                              const char *method, const char *class, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function interprets the contents of the supplied FitsStore
*     structure, looking for world coordinate axes which describe positions
*     in a spectrum. If such an axis is found, a Mapping is returned which
*     transforms the corresponding intermediate world coordinates to
*     spectral world coordinates (this mapping leaves any other axes
*     unchanged). It also, modifies the supplied Frame to describe the
*     axis (again, other axes are left unchanged). If no spectral axis
*     is found, a UnitMap is returned, and the supplied Frame is left
*     unchanged.

*  Parameters:
*     this
*        The FitsChan.
*     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').
*     frm
*        The address of a location at which to store a pointer to the
*        Frame describing the world coordinate axes.
*     iwcfrm
*        A pointer to the Frame describing the intermediate world coordinate
*        axes. The properties of this Frame may be changed on exit.
*     reflon
*        The reference celestial longitude, in the frame given by reffrm.
*     reflat
*        The reference celestial latitude, in the frame given by reffrm.
*     reffrm
*        The SkyFrame defining reflon and reflat.
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to the Mapping.
*/

/* Local Variables: */
   AstFrame *ofrm;        /* Pointer to a Frame */
   AstMapping *map1;      /* Pointer to Mapping */
   AstMapping *map2;      /* Pointer to Mapping */
   AstMapping *ret;       /* Pointer to the returned Mapping */
   AstSpecFrame *specfrm; /* Pointer to a SpecFrame */
   char algcode[ 5 ];     /* Displayed spectral type string */
   char stype[ 5 ];       /* Displayed spectral type string */
   const char *cname;     /* Pointer to CNAME value */
   const char *ctype;     /* Pointer to CTYPE value */
   const char *cunit;     /* Pointer to CUNIT value */
   const char *defunit;   /* Default unit string */
   const char *specsys;   /* Pointer to SPECSYS value */
   const char *ssyssrc;   /* Pointer to SSYSSRC value */
   double geolat;         /* Observer's geodetic latitude */
   double geolon;         /* Observer's geodetic longitude */
   double h;              /* Observer's geodetic height */
   double mjd;            /* Modified Julian Date */
   double obscentre;      /* Spectral value at observation centre */
   double obsgeo[ 3 ];    /* Observer's Cartesian position */
   double restfrq;        /* RESTFRQ keyword value */
   double vsource;        /* Source velocity */
   int *axes;             /* Pointer to axis permutation array */
   int i;                 /* Axis index */
   int j;                 /* Loop count */
   int k;                 /* Loop count */
   int kk;                /* Loop count */
   int naxes;             /* No. of axes in Frame */

/* Initialise the pointer to the returned Mapping. */
   ret = NULL;

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

/* Get the number of physical axes. */
   naxes = astGetNaxes( *frm );

/* An array to hold a list of axis selections. */
   axes = astMalloc( naxes*sizeof( int ) );

/* Loop round checking each axis. */
   defunit = NULL;
   map1 = NULL;
   for( i = 0; i < naxes && astOK; i++ ) {

/* Get the CTYPE value. Pass on to the next axis if no CTYPE is available. */
      ctype = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
      if( ctype ) {

/* See if this CTYPE describes a spectral axis, and if so, extract the
   system code, the algorithm code and get the default units. */
         defunit = IsSpectral( ctype, stype, algcode, status );

/* Skip to the next axis if the system type was not a spectral system
   type. */
         if( defunit ) {

/* Create a SpecFrame or DSBSpecFrame with this system (the FITS type codes
   are also legal SpecFrame System values). We use astSetC rather than
   astSetSystem because astSetC translates string values into the
   corresponding integer system identifiers. */
            if( GetItem( &(store->imagfreq), 0, 0, s, NULL, method,
                         class, status ) == AST__BAD ) {
               specfrm = astSpecFrame( "", status );
            } else {
               specfrm = (AstSpecFrame *) astDSBSpecFrame( "", status );
            }
            astSetC( specfrm, "System", stype );

/* Set the reference position (attributes RefRA and RefDec), if known. */
            if( reffrm ) astSetRefPos( specfrm, reffrm, reflon, reflat );

/* Set the SpecFrame units. Use the value of the CUNIT FITS keyword for this
   axis if available, otherwise use the default units for the system, noted
   above. */
            cunit = GetItemC( &(store->cunit), i, 0, s, NULL, method, class, status );
            if( !cunit ) cunit = defunit;
            astSetUnit( specfrm, 0, cunit );

/* Set the axis unit in the IWC Frame. */
            astSetUnit( iwcfrm, i, cunit );

/* Get a value for the Epoch attribute (the date of observation). */
            mjd = ChooseEpoch( this, store, s, method, class, status );
            if( mjd != AST__BAD ) astSetEpoch( specfrm, mjd );

/* Set the rest frequency. Use the RESTFRQ keyword (assumed to be in Hz),
   or (if RESTFRQ is not available), RESTWAV (assumes to be in m). */
            restfrq = GetItem( &(store->restfrq), 0, 0, s, NULL, method, class, status );
            if( restfrq == AST__BAD ) {
               restfrq = GetItem( &(store->restwav), 0, 0, s, NULL, method, class, status );
               if( restfrq != AST__BAD ) restfrq = AST__C/restfrq;
            }
            astSetRestFreq( specfrm, restfrq );

/* Observer's position (from primary axis descriptions). Get the OBSGEO-X/Y/Z
   keywords, convert to geodetic longitude and latitude and store as the
   SpecFrame's ObsLat, ObsLon and ObsAlt attributes. */
            obsgeo[ 0 ] = GetItem( &(store->obsgeox), 0, 0, ' ', NULL, method, class, status );
            obsgeo[ 1 ] = GetItem( &(store->obsgeoy), 0, 0, ' ', NULL, method, class, status );
            obsgeo[ 2 ] = GetItem( &(store->obsgeoz), 0, 0, ' ', NULL, method, class, status );
            if( obsgeo[ 0 ] != AST__BAD &&
                obsgeo[ 1 ] != AST__BAD &&
                obsgeo[ 2 ] != AST__BAD ) {
               eraGc2gd( 1, obsgeo, &geolon, &geolat, &h );
               astSetObsLat( specfrm, geolat );
               astSetObsLon( specfrm, geolon );
               astSetObsAlt( specfrm, h );
            }

/* Source velocity rest frame */
            ssyssrc = GetItemC( &(store->ssyssrc), 0, 0, s, NULL, method, class, status );
            if( ssyssrc ) astSetC( specfrm, "SourceVRF", ssyssrc );

/* Source velocity. Use the ZSOURCE keyword and convert from redshift to
   velocity. */
            vsource = GetItem( &(store->zsource), 0, 0, s, NULL, method, class, status );
            if( vsource != AST__BAD ) {
               vsource += 1.0;
               vsource *= vsource;
               vsource = AST__C*( vsource - 1.0  )/( vsource + 1.0 );
               astSetSourceVel( specfrm, vsource );
            }

/* Reference frame. If the SPECSYS keyword is set, use it (the FITS codes
   are also legal SpecFrame StdOfRest values). We use astSetC rather than
   astSetSystem because astSetC translates string values into the
   corresponding integer system identifiers. */
            specsys = GetItemC( &(store->specsys), 0, 0, s, NULL, method, class, status );
            if( specsys ) astSetC( specfrm, "StdOfRest", specsys );

/* Axis label. If the CNAME keyword is set, use it as the axis label. */
            cname = GetItemC( &(store->cname), i, 0, s, NULL, method, class, status );
            if( cname ) astSetLabel( specfrm, 0, cname );

/* If the header contains an AXREF value for the spectral axis, use it as the
   observation centre in preferences to the CRVAL value. AXREF keywords are
   created by the astWrite method for axes described by -TAB algorithm that
   have no inverse transformation. */
            obscentre = GetItem( &(store->axref), i, 0, s, NULL, method,
                                  class, status );
            if( obscentre == AST__BAD ) {
               obscentre = GetItem( &(store->crval), i, 0, s, NULL, method,
                                    class, status );
            }

/* Now do the extra stuff needed if we are creating a dual sideband
   SpecFrame. */
            if( astIsADSBSpecFrame( specfrm ) ) {
               DSBSetUp( this, store, (AstDSBSpecFrame *) specfrm, s,
                         obscentre, method, class, status );
            }

/* Now branch for each type of algorithm code. Each case returns a 1D
   Mapping which converts IWC value into the specified Spectral system. */

/* Linear */
            if( strlen( algcode ) == 0 ) {
               map1 = LinearWcs( store, i, s, method, class, status );

/* Log-Linear */
            } else if( !strcmp( "-LOG", algcode ) ) {
               map1 = LogWcs( store, i, s, method, class, status );

/* Non-Linear */
            } else if( algcode[ 0 ] == '-' && algcode[ 2 ] == '2' ) {
               map1 = NonLinSpecWcs( this, algcode, store, i, s, specfrm, method, class, status );

/* Grism */
            } else if( !strcmp( "-GRI", algcode ) ||
                       !strcmp( "-GRA", algcode ) ) {
               map1 = GrismSpecWcs( algcode, store, i, s, specfrm, method, class, status );
            } else {
               map1 = NULL;
            }
            if( map1 == NULL && astOK ) {
               specfrm = astAnnul( specfrm );
               astError( AST__BDFTS, "%s(%s): Cannot implement spectral "
                         "algorithm code '%s' specified in FITS keyword '%s'.", status,
                         method, class, ctype + 4, FormatKey( "CTYPE", i + 1, -1, s, status ) );
               astError( AST__BDFTS, "%s(%s): Unknown algorithm code or "
                         "unusable parameter values.", status, method, class );
               break;
            }

/* Create a Frame by picking all the other (non-spectral) axes from the
   supplied Frame. */
            j = 0;
            for( k = 0; k < naxes; k++ ) {
               if( k != i ) axes[ j++ ] = k;
            }

/* If there were no other axes, replace the supplied Frame with the
   specframe. */
            if( j == 0 ) {
               (void) astAnnul( *frm );
               *frm = (AstFrame *) specfrm;

/* Otherwise pick the other axes from the supplied Frame */
            } else {
               ofrm = astPickAxes( *frm, j, axes, NULL );

/* Replace the supplied Frame with a CmpFrame made up of this Frame and
   the SpecFrame. */
               (void) astAnnul( *frm );
               *frm = (AstFrame *) astCmpFrame( ofrm, specfrm, "", status );
               ofrm = astAnnul( ofrm );
               specfrm = astAnnul( specfrm );
            }

/* Permute the axis order to put the spectral axis back in its original
   position. */
            j = 0;
            for( kk = 0; kk < naxes; kk++ ) {
               if( kk == i ) {
                  axes[ kk ] = naxes - 1;
               } else {
                  axes[ kk ] = j++;
               }
            }
            astPermAxes( *frm, axes );
         }
      }

/* If this axis is not a spectral axis, create a UnitMap (the Frame is left
   unchanged). */
      if( !map1 && astOK ) map1 = (AstMapping *) astUnitMap( 1, "", status );

/* Add the Mapping for this axis in parallel with the Mappings for
   previous axes. */
      if( ret ) {
         map2 = (AstMapping *) astCmpMap( ret, map1, 0, "", status );
         ret = astAnnul( ret );
         map1 = astAnnul( map1 );
         ret = map2;
      } else {
         ret = map1;
         map1 = NULL;
      }
   }

/* Free the axes array. */
   axes= astFree( axes );

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

static void WcsToStore( AstFitsChan *this, AstFitsChan *trans,
                        FitsStore *store, const char *method,
                        const char *class, int *status ){

/*
*  Name:
*     WcsToStore

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

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"

*     void WcsToStore( AstFitsChan *this, AstFitsChan *trans,
*                      FitsStore *store, const char *method,
*                      const char *class, int *status )

*  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 FITSWCS keywords from the supplied FitsChan(s),
*     and stores the corresponding WCS information in the supplied FitsStore.
*     Keywords will be searched for first in "trans", and then, if they
*     are not found in "trans", they will be searched for in "this".

*  Parameters:
*     this
*        Pointer to the FitsChan containing the cards read from the
*        original FITS header. This may include non-standard keywords.
*     trans
*        Pointer to a FitsChan containing cards representing standard
*        translations of any non-standard keywords in "this". A NULL
*        pointer indicates that "this" contains no non-standard keywords.
*     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.
*     status
*        Pointer to the inherited status variable.
*/

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

/* Read all usable cards out of the main FitsChan, into the FitsStore. */
   WcsFcRead( this, trans, store, method, class, status );

/* If a FitsChan containing standard translations was supplied, read all
   cards out of it, into the FitsStore, potentially over-writing the
   non-standard values stored in the previous call to WcsFcRead. */
   if( trans ) WcsFcRead( trans, NULL, store, method, class, status );
}

static int WorldAxes( AstFitsChan *this, AstMapping *cmap, double *dim, int *perm,
                      int *status ){

/*
*  Name:
*     WorldAxes

*  Purpose:
*     Associate final world axes with pixel axes.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"

*     int WorldAxes( AstFitsChan *this, AstMapping *cmap, double *dim, int *perm,
*                    int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function finds the association between the axes of the final
*     world coordinate system, and those of the pixel coordinate
*     system. This may not simply be a 1-to-1 association because the
*     Mapping may include a PermMap. Each output axis is associated with
*     the input axis which is most nearly aligned with it.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     cmap
*        Pointer to the Mapping from pixel coordinates to final world
*        coordinates.
*     dim
*        Pointer to an array with one element for each input of "map",
*        supplied holding the no. of pixels in the data cube along the axis, or
*        AST__BAD If unknown.
*     perm
*        Pointer to an array with one element for each output of "map".
*        On exit, each element of this array holds the zero-based index of the
*        "corresponding" (i.e. most nearly parallel) pixel axis.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     Non-zero for success - zero for failure.
*/

/* Local Variables: */
   AstMapping *smap;
   AstMapping *map;
   AstPointSet *pset1;
   AstPointSet *pset2;
   double **ptr2;
   double **ptr1;
   double *dw;
   double *g0;
   double *nwt;
   double *ntn;
   double *tn;
   double *wt;
   double *w0;
   double dg;
   double s;
   double sj;
   double tnmin;
   double wtmax;
   int *outs;
   int i2;
   int i;
   int imin;
   int j2;
   int j;
   int jmin;
   int nin;
   int nout;
   int nouts;
   int nused;
   int ret;
   int retain;
   int used;

/* Initialise returned value */
   ret = 0;

/* Other initialisation to avoid compiler warnings. */
   retain = 0;

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

/* Simplfy the Mapping. */
   map = astSimplify( cmap );

/* Get the number of inputs and outputs for the Mapping. */
   nin = astGetNin( map );
   nout = astGetNout( map );

/* Initialise "perm". */
   for( i = 0; i < nout; i++ ) perm[ i ] = i;

/* First deal with Mappings that are defined in both directions. */
   if( astGetTranForward( map ) && astGetTranInverse( map ) ) {

/* Use FindBasisVectors to find an input position which coresponds to a
   good output position. Store it in a dynamic array pointed to by "g0". */
      pset1 = astPointSet( nin+1, nin, "", status );
      pset2 = astPointSet( nin+1, nout, "", status );
      if( FindBasisVectors( map, nin, nout, dim, pset1, pset2, status ) ) {
         g0 = astMalloc( sizeof(double)*nin );
         ptr1 = astGetPoints( pset1 );
         if( astOK ) {
            for( j = 0; j < nin; j++ ) g0[ j ] = ptr1[ j ][ 0 ];
         }
         pset1 = astAnnul( pset1 );
         pset2 = astAnnul( pset2 );

/* If no basis vectors found, return. */
      } else {
         pset1 = astAnnul( pset1 );
         pset2 = astAnnul( pset2 );
         return ret;
      }

/* Create Pointset to hold two input (pixel) points. */
      pset1 = astPointSet( 2, nin, "", status );
      ptr1 = astGetPoints( pset1 );

/* Create a Pointset to hold the same number of output (world) points. */
      pset2 = astPointSet( 2, nout, "", status );
      ptr2 = astGetPoints( pset2 );

/* Allocate memory to use as work space */
      w0 = astMalloc( sizeof(double)*nout );
      dw = astMalloc( sizeof(double)*nout );
      tn = astMalloc( sizeof(double)*nout*nin );
      wt = astMalloc( sizeof(double)*nout*nin );

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

/* Transform the grid position found above, plus a position 1 pixel away
   along all pixel axes, into world coords. Also set up "dw" to hold
   "a small increment" along each world axis. */
         for( j = 0; j < nin; j++ ) {
            ptr1[ j ] [ 0 ] = g0[ j ];
            ptr1[ j ] [ 1 ] = g0[ j ] + 1.0;
         }
         (void) astTransform( map, pset1, 1, pset2 );
         for( i = 0; i < nout; i++ ) {
            w0[ i ] = ptr2[ i ] [ 0 ];
            if( w0[ i ] != AST__BAD && ptr2[ i ] [ 1 ] != AST__BAD ) {
               dw[ i ] = fabs( 0.1*( ptr2[ i ] [ 1 ] - w0[ i ] ) );
               if( dw[ i ] <= fabs( 0.001*w0[ i ] ) ) {
                  if( w0[ i ] != 0.0 ) {
                     dw[ i ] = fabs( 0.001*w0[ i ] );
                  } else {
                     dw[ i ] = 1.0;
                  }
               }
            } else {
               dw[ i ] = AST__BAD;
            }
         }

/* Any PermMap in the mapping may result in the the "inverse transformation"
   not being a true inverse of the forward transformation (for instance,
   constant values fed in for degenerate axis would have this effect). To
   ensure that "g0" and "w0" are corresponding positions, transform the
   "w0" position back into grid coords and use the resulting grid position
   as "g0". */
         (void) astTransform( map, pset2, 0, pset1 );
         for( j = 0; j < nin; j++ ) {
            g0[ j ] = ptr1[ j ] [ 0 ];
         }

/* In the next loop we find the tan of the angle between each WCS axis and
   each of the pixel axes. Loop round each WCS axis. */
         for( i = 0; i < nout; i++ ) {

/* Initialise the tan values for this WCS axis to AST__BAD. */
            ntn = tn + i*nin;
            nwt = wt + i*nin;
            for( j = 0; j < nin; j++ ) ntn[ j ] = AST__BAD;

/* As a side issue, initialise the pixel axis assigned to each WCS axis
   to -1, to indicate that no grid axis has yet been associated with this
   WCS axis. */
            perm[ i ] = -1;

/* Skip over this axis if the increment is bad. */
            if( dw[ i ] != AST__BAD ) {

/* Store a WCS position which is offset from the "w0" position by a small
   amount along the current WCS axis. The first position in "ptr2" is
   currently "w0". */
               ptr2[ i ][ 0 ] += dw[ i ];

/* Transform this position into grid coords. */
               (void) astTransform( map, pset2, 0, pset1 );

/* Re-instate the original "w0" values within "ptr2", ready for the next
   WCS axis. */
               ptr2[ i ][ 0 ] = w0[ i ];

/* Consider each pixel axis in turn as a candidate for being assigned to
   the current WCS axis. */
               for( j = 0; j < nin; j++ ) {

/* Find the tan of the angle between the current ("i"th) WCS axis and the
   current ("j"th) pixel axis. This gets stored in tn[j+nin*i]. A
   corresponding weight for each angle is stored in nwt[j+nin*i]. This
   is the length of the projection of the vector onto the "j"th pixel
   axis.  */
                  s = 0.0;
                  sj = 0.0;
                  for( j2 = 0; j2 < nin; j2++ ) {
                     if( ptr1[ j2 ][ 0 ] != AST__BAD ) {
                         dg = ptr1[ j2 ][ 0 ] - g0[ j2 ];
                         if( j2 != j ) {
                            s += dg*dg;
                         } else {
                            sj = fabs( dg );
                         }
                     } else {
                         s = AST__BAD;
                         break;
                     }
                  }
                  if( s != AST__BAD && sj != 0.0 ) {
                     ntn[ j ] = sqrt( s )/sj;
                     nwt[ j ] = sj;
                  }
               }
            }
         }

/* Loop until every grid axes has been assigned to a WCS axis. */
         while( 1 ) {

/* Pass through the array of tan values, finding the smallest. Note the
   pixel and WCS axis for which the smallest tan value occurs. If the tan
   values are equal, favour the one with highest weight. */
            ntn = tn;
            nwt = wt;
            tnmin = AST__BAD;
            wtmax = AST__BAD;
            imin = 0;
            jmin = 0;
            for( i = 0; i < nout; i++ ) {
               for( j = 0; j < nin; j++ ) {
                  if( *ntn != AST__BAD ) {
                     if( tnmin == AST__BAD || *ntn < tnmin ) {
                        tnmin = *ntn;
                        wtmax = *nwt;
                        imin = i;
                        jmin = j;
                     } else if( astEQUAL( *ntn, tnmin ) && *nwt > wtmax ) {
                        wtmax = *nwt;
                        imin = i;
                        jmin = j;
                     }
                  }
                  ntn++;
                  nwt++;
               }
            }

/* Check we found a usable minimum tan value */
            if( tnmin != AST__BAD ) {

/* Assign the pixel axis to the WCS axis. */
               perm[ imin ] = jmin;

/* Set bad all the tan values for this pixel and WCS axis pair. This ensures
   that the pixel axis will not be assigned to another WCS axis, and that
   the WCS will not have another pixel axis assigned to it. */
               ntn = tn;
               for( i = 0; i < nout; i++ ) {
                  for( j = 0; j < nin; j++ ) {
                     if( i == imin || j == jmin ) *ntn = AST__BAD;
                     ntn++;
                  }
               }

/* Leave the loop if no more good tan values were found. */
            } else {
               break;
            }
         }

/* The above process may have left some WCS axes with out any assigned
   pixel axis. We assign the remaining pixel arbitrarily to such axes,
   starting with the first remaining pixel axis. Find the lowest unused
   pixel axis. */
         for( j = 0; j < nin; j++ ) {
            used = 0;
            for( i = 0; i < nout; i++ ) {
               if( perm[ i ] == j ) {
                  used = 1;
                  break;
               }
            }
            if( !used ) break;
         }

/* Now check each WCS axis looking for outputs which were not assigned a
   pixel axis in the above process. */
         for( i = 0; i < nout; i++ ) {
            if( perm[ i ] == -1 ) {

/* Use the next unused axis value. */
               perm[ i ] = j++;

/* Find the next unused axis value. */
               for( ; j < nin; j++ ) {
                  used = 0;
                  for( i2 = 0; i2 < nout; i2++ ) {
                     if( perm[ i2 ] == j ) {
                        used = 1;
                        break;
                     }
                  }
                  if( !used ) break;
               }
            }
         }

/* Indicate success. */
         if( astOK ) ret = 1;
      }

/* Free resources. */
      pset1 = astAnnul( pset1 );
      pset2 = astAnnul( pset2 );
      g0 = astFree( g0 );
      w0 = astFree( w0 );
      tn = astFree( tn );
      wt = astFree( wt );
      dw = astFree( dw );

/* Now, if we can use the TAB algorithm, deal with Mappings that are defined only in the forward direction. */
   } else if( astGetTranForward( map ) && astGetTabOK( this ) > 0 ) {

/* Assume success. */
      ret = 1;

/* Initialise to indicate no outputs have yet been assigned. */
      for( i = 0; i < nout; i++ ) perm[ i ] = -1;

/* Find the output associated with each input. */
      for( j = 0; j < nin; j++ ) {

/* Attempt to split off the current input. */
         outs = astMapSplit( map, 1, &j, &smap );

/* If successfull, store the index of the corresponding input for each
   output. */
         if( outs && smap ) {
            nouts = astGetNout( smap );
            for( i = 0; i < nouts; i++ ) {
               if( perm[ outs[ i ] ] == -1 ) {
                  perm[ outs[ i ] ] = j;
               } else {
                  ret = 0;
               }
            }
         }

/* Free resources. */
         outs = astFree( outs );
         if( smap ) smap = astAnnul( smap );
      }

/* Check all outputs were assigned . */
      for( i = 0; i < nout && ret; i++ ) {
         if( perm[ i ] == -1 ) ret = 0;
      }

/* If succesful, attempt to remove any duplicates from the "perm" array
   (i.e. inputs that supply more than one output). First get a list of
   the inputs that are currently unused (i.e. do not appear in "perm"). */
      if( ret ) {

/* Check each input. */
         for( j = 0; j < nin; j++ ) {

/* See how many outputs are fed by this input. */
            nused = 0;
            for( i = 0; i < nout; i++ ) {
               if( perm[ i ] ==  j ) nused++;
            }

/* If it used more than once, we need to remove all but one of the
   occurrences. */
            if( nused > 1 ) {

/* Choose the occurrence to retain. If the output with the same index as
   the input is one of them, use it. Otherwise, use the first occurrence. */
               if( perm[ j ] == j ) {
                  retain = j;
               } else {
                  for( i = 0; i < nout; i++ ) {
                     if( perm[ i ] ==  j ) {
                        retain = i;
                        break;
                     }
                  }
               }

/* Loop round all occurrences of this input again. */
               for( i = 0; i < nout && ret; i++ ) {
                  if( perm[ i ] ==  j ) {

/* Replace all occurrences, except for the one being retained. */
                     if( i != retain ) {

/* Replace it with the next unused input. */
                        for( j2 = 0; j2 < nin; j2++ ) {
                           used = 0;
                           for( i2 = 0; i2 < nout; i2++ ) {
                              if( perm[ i2 ] == j2 ) {
                                 used = 1;
                                 break;
                              }
                           }
                           if( ! used ) {
                              perm[ i ] = j2;
                              break;
                           }
                        }

/* If there were no unused inputs, we cannot do it. */
                        if( used ) ret = 0;
                     }
                  }
               }
            }
         }
      }
   }

/* Free resources. */
   map = astAnnul( map );

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

static int Write( AstChannel *this_channel, AstObject *object, int *status ) {
/*
*  Name:
*     Write

*  Purpose:
*     Write an Object to a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     int Write( AstChannel *this, AstObject *object, int *status )

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

*  Description:
*     This function writes an Object to a FitsChan.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     object
*        Pointer to the Object which is to be written.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     The number of Objects written to the FitsChan by this invocation of
*     astWrite.

*  Notes:
*     - A value of zero 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: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   AstFitsChan *this;            /* Pointer to the FitsChan structure */
   FitsStore *store;             /* Intermediate storage for WCS information */
   char banner[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN + 1 ]; /* Buffer for begin/end banner */
   const char *class;            /* Pointer to string holding object class */
   const char *method;           /* Pointer to string holding calling method */
   double *dim;                  /* Pointer to array of axis dimensions */
   int card0;                    /* Index of original current card */
   int comm;                     /* Value of Comm attribute */
   int encoding;                 /* FITS encoding scheme to use */
   int i;                        /* Axis index */
   int naxis;                    /* No. of pixel axes */
   int ret;                      /* Number of objects read */

/* Initialise. */
   ret = 0;

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this_channel);

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

/* Ensure the source function has been called */
   ReadFromSource( this, status );

/* Store the calling method, and object class. */
   method = "astWrite";
   class = astGetClass( this );

/* The original current card is re-instated at the end if no object
   is written. Save its index. */
   card0 = astGetCard( this );

/* Indicate that all cards added to the FitsCHan by this call should be
   marked as "new". */
   mark_new = 1;

/* Get the encoding scheme used by the FitsChan. */
   encoding = astGetEncoding( this );

/* First deal with cases where we are writing to a FitsChan in which AST
   objects are encoded using native AST-specific keywords... */
   if( encoding == NATIVE_ENCODING ){

/* Increment the nesting level which keeps track of recursive
   invocations of this function. */
      write_nest++;

/* Initialise the current indentation level for top-level objects. */
      if ( !write_nest ) current_indent = 0;

/* Obtain the value of the Comm attribute. */
      comm = astGetComment( this );

/* If this is the top-level invocation (i.e. we are about to write out
   a new top-level Object), then prefix it with a blank FITS line and
   an appropriate banner of FITS comments, unless comments have been
   suppressed. */
      if ( !write_nest && comm ) {
         astSetFitsCom( this, "        ", "", 0 );
         MakeBanner(
"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++",
                     "", "", banner, status );
         astSetFitsCom( this, "COMMENT", banner, 0 );
         if( astIsAFrameSet( object ) ) {
            MakeBanner( "WCS information in AST format", "", "", banner, status );
            astSetFitsCom( this, "COMMENT", banner, 0 );
            MakeBanner( "See http://www.starlink.ac.uk/ast/", "", "", banner, status );
            astSetFitsCom( this, "COMMENT", banner, 0 );
         }
         MakeBanner( HEADER_TEXT, astGetClass( object ), " object", banner, status );
         astSetFitsCom( this, "COMMENT", banner, 0 );
         MakeBanner(
"................................................................",
                     "", "", banner, status );
         astSetFitsCom( this, "COMMENT", banner, 0 );
      }

/* Invoke the parent astWrite method to write out the Object data. */
      (*parent_write)( this_channel, object, status );

/* Append a banner of FITS comments to the object data, as above, if
   necessary. */
      if ( !write_nest && comm ) {
         MakeBanner(
"................................................................",
                     "", "", banner, status );
         astSetFitsCom( this, "COMMENT", banner, 0 );
         MakeBanner( FOOTER_TEXT, astGetClass( object ), " object", banner, status );
         astSetFitsCom( this, "COMMENT", banner, 0 );
         MakeBanner(
"----------------------------------------------------------------",
                     "", "", banner, status );
         astSetFitsCom( this, "COMMENT", banner, 0 );
      }

/* Return the nesting level to its previous value. */
      write_nest--;

/* Indicate that an object has been written. */
      ret = 1;

/* Now deal with cases where we are writing to a FitsChan in which AST
   objects are encoded using any of the supported foreign encodings... */
   } else {

/* Only proceed if the supplied object is a FrameSet. */
      if( astIsAFrameSet( object ) ){

/* Note the number of pixel (i.e. Base Frame) axes, and allocate memory to
   hold the image dimensions. */
         naxis = astGetNin( (AstFrameSet *) object );
         dim = (double *) astMalloc( sizeof(double)*naxis );
         if( dim ){

/* Note the image dimensions, if known. If not, store AST__BAD values. */
            for( i = 0; i < naxis; i++ ){
               if( !astGetFitsF( this, FormatKey( "NAXIS", i + 1, -1, ' ', status ),
                                 dim + i ) ) dim[ i ] = AST__BAD;
            }

/* Extract the required information from the FrameSet into a standard
   intermediary structure called a FitsStore. The indices of any
   celestial axes are returned. */
            store = FsetToStore( this, (AstFrameSet *) object, naxis, dim,
                                 encoding, method, class, status );

/* If the FrameSet cannot be described in terms of any of the supported
   FITS encodings, a null pointer will have been returned. */
            if( store ){

/* Now put header cards describing the contents of the FitsStore into the
   supplied FitsChan, using the requested encoding. Zero or one is
   returned depending on whether the information could be encoded. */
               ret = FitsFromStore( this, store, encoding, dim,
                                    (AstFrameSet *) object, method, class, status );

/* Release the resources used by the FitsStore. */
               store = FreeStore( store, status );

/* If the Object was written to the FitsChan, set the current card to
   end-of-file. */
               if( ret ) astSetCard( this, INT_MAX );
            }

/* Free workspace holding image dimensions */
            dim = (double *) astFree( (void *) dim );
         }
      }
   }

/* If an error has occurred, return zero and remove any new cards added
   to the FitsCHan by this call. */
   if( !astOK ) ret = 0;

/* Clear the new flag associated with cards which have been added to the
   FitsChan as a result of this function. If the object was not added
   succesfully to the FitsChan, remove any cards which were added before
   the error was discovered. */
   FixNew( this, NEW1, !ret, method, class, status );
   FixNew( this, NEW2, !ret, method, class, status );

/* Indicate that all cards added to the FitsChan from now on should not be
   marked as "new". */
   mark_new = 0;

/* Ensure all output is flushed to any associated output file specified
   by the SinkFile attribute. */
   astWriteFlush( this );

/* If no object was written, re-instate the original current card. */
   if( !ret ) astSetCard( this, card0 );

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

static void WriteBegin( AstChannel *this_channel, const char *class,
                        const char *comment, int *status ) {
/*
*  Name:
*     WriteBegin

*  Purpose:
*     Write a "Begin" data item to a data sink.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void WriteBegin( AstChannel *this, const char *class,
*                      const char *comment )

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

*  Description:
*     This function writes a "Begin" data item to the data sink
*     associated with a FitsChan, so as to begin the output of a new
*     Object definition.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     class
*        Pointer to a constant null-terminated string containing the
*        name of the class to which the Object belongs.
*     comment
*        Pointer to a constant null-terminated string containing a
*        textual comment to be associated with the "Begin"
*        item. Normally, this will describe the purpose of the Object.

*  Notes:
*     - The comment supplied may not actually be used, depending on
*     the nature of the FitsChan supplied.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   AstFitsChan *this;            /* Pointer to the FitsChan structure. */
   char buff[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN + 1 ];
                                 /* Character buffer */
   char keyword[ FITSNAMLEN + 1 ]; /* Buffer for FITS keyword */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this_channel);

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

/* Increment the indentation level for comments. */
   current_indent += INDENT_INC;

/* If we are not beginning a top-level Object definition, and helpful
   information has not been suppressed, generate an indented comment
   to mark the "Begin" item and write it to the FitsChan as a comment
   card with a blank keyword. */
   if ( write_nest && ( astGetFull( this ) >= 0 ) ) {
      MakeIndentedComment( current_indent, '+', "Beginning of ", class, buff, status );
      astSetFitsCom( this, "        ", buff, 0 );
   }

/* Create a unique FITS keyword for this "Begin" item, basing it on
   "BEGAST". */
   CreateKeyword( this, "BEGAST", keyword, status );

/* Generate a pre-quoted version of the class name. */
   PreQuote( class, buff, status );

/* Write the "Begin" item to the FitsChan as a keyword and string
   value. */
   astSetFitsS( this, keyword, buff,
                     astGetComment( this ) ? comment : NULL, 0 );

/* Clear the count of items written. */
   items_written = 0;
}

static void WriteDouble( AstChannel *this_channel, const char *name,
                         int set, int helpful,
                         double value, const char *comment, int *status ) {
/*
*  Name:
*     WriteDouble

*  Purpose:
*     Write a double value to a data sink.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void WriteDouble( AstChannel *this, const char *name,
*                       int set, int helpful,
*                       double value, const char *comment )

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

*  Description:
*     This function writes a named double value, representing the
*     value of a class instance variable, to the data sink associated
*     with a FitsChan. It is intended for use by class "Dump"
*     functions when writing out class information which will
*     subsequently be re-read.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     name
*        Pointer to a constant null-terminated string containing the
*        name to be used to identify the value in the external
*        representation. This will form the key for identifying it
*        again when it is re-read. The name supplied should be unique
*        within its class.
*
*        Mixed case may be used and will be preserved in the external
*        representation (where possible) for cosmetic effect. However,
*        case is not significant when re-reading values.
*
*        It is recommended that a maximum of 6 alphanumeric characters
*        (starting with an alphabetic character) be used. This permits
*        maximum flexibility in adapting to standard external data
*        representations (e.g. FITS).
*     set
*        If this is zero, it indicates that the value being written is
*        a default value (or can be re-generated from other values) so
*        need not necessarily be written out. Such values will
*        typically be included in the external representation with
*        (e.g.) a comment character so that they are available to
*        human readers but will be ignored when re-read. They may also
*        be completely omitted in some circumstances.
*
*        If "set" is non-zero, the value will always be explicitly
*        included in the external representation so that it can be
*        re-read.
*     helpful
*        This flag provides a hint about whether a value whose "set"
*        flag is zero (above) should actually appear at all in the
*        external representaton.
*
*        If the external representation allows values to be "commented
*        out" then, by default, values will be included in this form
*        only if their "helpful" flag is non-zero. Otherwise, they
*        will be omitted entirely. When possible, omitting the more
*        obscure values associated with a class is recommended in
*        order to improve readability.
*
*        This default behaviour may be further modified if the
*        FitsChan's Full attribute is set - either to permit all
*        values to be shown, or to suppress non-essential information
*        entirely.
*     value
*        The value to be written.
*     comment
*        Pointer to a constant null-terminated string containing a
*        textual comment to be associated with the value.
*
*        Note that this comment may not actually be used, depending on
*        the nature of the FitsChan supplied and the setting of its
*        Comm attribute.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   AstFitsChan *this;            /* Pointer to the FitsChan structure. */
   char keyword[ FITSNAMLEN + 1 ]; /* Buffer for FITS keyword */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this_channel);

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

/* Use the "set" and "helpful" flags, along with the FitsChan's
   attributes to decide whether this value should actually be
   written. */
   if ( Use( this, set, helpful, status ) ) {

/* Create a unique FITS keyword from the name supplied. */
      CreateKeyword( this, name, keyword, status );

/* Write the value to the FitsChan as a keyword and value */
      astSetFitsF( this, keyword, value,
                       astGetComment( this ) ? comment : NULL, 0 );

/* If the value is not "set", replace the card just written by a COMMENT
   card containing the text of the card as the comment. */
      if( !set ) MakeIntoComment( this, "astWrite", astGetClass( this ), status );

/* Increment the count of items written. */
      items_written++;
   }
}

static void WriteEnd( AstChannel *this_channel, const char *class, int *status ) {
/*
*  Name:
*     WriteEnd

*  Purpose:
*     Write an "End" data item to a data sink.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void WriteEnd( AstChannel *this, const char *class )

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

*  Description:
*     This function writes an "End" data item to the data sink
*     associated with a FitsChan. This item delimits the end of an
*     Object definition.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     class
*        Pointer to a constant null-terminated string containing the
*        class name of the Object whose definition is being terminated
*        by the "End" item.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   AstFitsChan *this;            /* Pointer to the FitsChan structure. */
   char buff[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN + 1 ];
                                 /* Character buffer */
   char keyword[ FITSNAMLEN + 1 ]; /* Buffer for FITS keyword */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this_channel);

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

/* Create a unique FITS keyword for this "End" item, basing it on
   "ENDAST". */
   CreateKeyword( this, "ENDAST", keyword, status );

/* Generate a pre-quoted version of the class name. */
   PreQuote( class, buff, status );

/* Write the "End" item to the FitsChan as a keyword and string
   value. */
   astSetFitsS( this, keyword, buff,
                     astGetComment( this ) ? "End of object definition" : NULL,
                     0 );

/* If we are not ending a top-level Object definition, and helpful
   information has not been suppressed, generate an indented comment
   to mark the "End" item and write it to the FitsChan as a comment
   card with a blank keyword. */
   if ( write_nest && ( astGetFull( this ) >= 0 ) ) {
      MakeIndentedComment( current_indent, '-', "End of ", class, buff, status );
      astSetFitsCom( this, "        ", buff, 0 );
   }

/* Decrement the indentation level for comments. */
   current_indent -= INDENT_INC;
}

static void WriteFits( AstFitsChan *this, int *status ){

/*
*++
*  Name:
c     astWriteFits
f     AST_WRITEFITS

*  Purpose:
*     Write out all cards in a FitsChan to the sink function.

*  Type:
*     Public virtual function.

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

*  Class Membership:
*     FitsChan method.

*  Description:
c     This function
f     This routine
*     writes out all cards currently in the FitsChan. If the SinkFile
*     attribute is set, they will be written out to the specified sink file.
*     Otherwise, they will be written out using the sink function specified
*     when the FitsChan was created. All cards are then deleted from the
*     FitsChan.

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

*  Notes:
*     - If the SinkFile is unset, and no sink function is available, this
*     method simply empties the FitsChan, and is then equivalent to
c     astEmptyFits.
f     AST_EMPTYFITS.
*     - This method attempt to execute even if an error has occurred
*     previously.
*--
*/

/* Ensure a FitsChan was supplied. */
   if( this ) {

/* Ensure the source function has been called */
      ReadFromSource( this, status );

/* We can usefully use the local destructor function to do the work,
   since it only frees resources used within teh FitsChan, rather than
   freeing the FitsChan itself. */
      Delete( (AstObject *) this, status );
   }
}

static void WriteInt( AstChannel *this_channel, const char *name,
                      int set, int helpful,
                      int value, const char *comment, int *status ) {
/*
*  Name:
*     WriteInt

*  Purpose:
*     Write an int value to a data sink.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void WriteInt( AstChannel *this, const char *name,
*                    int set, int helpful,
*                    int value, const char *comment )

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

*  Description:
*     This function writes a named int value, representing the
*     value of a class instance variable, to the data sink associated
*     with a FitsChan. It is intended for use by class "Dump"
*     functions when writing out class information which will
*     subsequently be re-read.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     name
*        Pointer to a constant null-terminated string containing the
*        name to be used to identify the value in the external
*        representation. This will form the key for identifying it
*        again when it is re-read. The name supplied should be unique
*        within its class.
*
*        Mixed case may be used and will be preserved in the external
*        representation (where possible) for cosmetic effect. However,
*        case is not significant when re-reading values.
*
*        It is recommended that a maximum of 6 alphanumeric characters
*        (starting with an alphabetic character) be used. This permits
*        maximum flexibility in adapting to standard external data
*        representations (e.g. FITS).
*     set
*        If this is zero, it indicates that the value being written is
*        a default value (or can be re-generated from other values) so
*        need not necessarily be written out. Such values will
*        typically be included in the external representation with
*        (e.g.) a comment character so that they are available to
*        human readers but will be ignored when re-read. They may also
*        be completely omitted in some circumstances.
*
*        If "set" is non-zero, the value will always be explicitly
*        included in the external representation so that it can be
*        re-read.
*     helpful
*        This flag provides a hint about whether a value whose "set"
*        flag is zero (above) should actually appear at all in the
*        external representaton.
*
*        If the external representation allows values to be "commented
*        out" then, by default, values will be included in this form
*        only if their "helpful" flag is non-zero. Otherwise, they
*        will be omitted entirely. When possible, omitting the more
*        obscure values associated with a class is recommended in
*        order to improve readability.
*
*        This default behaviour may be further modified if the
*        FitsChan's Full attribute is set - either to permit all
*        values to be shown, or to suppress non-essential information
*        entirely.
*     value
*        The value to be written.
*     comment
*        Pointer to a constant null-terminated string containing a
*        textual comment to be associated with the value.
*
*        Note that this comment may not actually be used, depending on
*        the nature of the FitsChan supplied and the setting of its
*        Comm attribute.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   AstFitsChan *this;            /* Pointer to the FitsChan structure. */
   char keyword[ FITSNAMLEN + 1 ]; /* Buffer for FITS keyword */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this_channel);

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

/* Use the "set" and "helpful" flags, along with the FitsChan's
   attributes to decide whether this value should actually be
   written. */
   if ( Use( this, set, helpful, status ) ) {

/* Create a unique FITS keyword from the name supplied. */
      CreateKeyword( this, name, keyword, status );

/* Write the value to the FitsChan as a keyword and value */
      astSetFitsI( this, keyword, value,
                   astGetComment( this ) ? comment : NULL, 0 );

/* If the value is not "set", replace the card just written by a COMMENT
   card containing the text of the card as the comment. */
      if( !set ) MakeIntoComment( this, "astWrite", astGetClass( this ), status );

/* Increment the count of items written. */
      items_written++;
   }
}

static void WriteIsA( AstChannel *this_channel, const char *class,
                      const char *comment, int *status ) {
/*
*  Name:
*     WriteIsA

*  Purpose:
*     Write an "IsA" data item to a data sink.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void WriteIsA( AstChannel *this, const char *class,
*                    const char *comment )

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

*  Description:
*     This function writes an "IsA" data item to the data sink
*     associated with a FitsChan. This item delimits the end of the
*     data associated with the instance variables of a class, as part
*     of an overall Object definition.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     class
*        Pointer to a constant null-terminated string containing the
*        name of the class whose data are terminated by the "IsA"
*        item.
*     comment
*        Pointer to a constant null-terminated string containing a
*        textual comment to be associated with the "IsA"
*        item. Normally, this will describe the purpose of the class
*        whose data are being terminated.

*  Notes:
*     - The comment supplied may not actually be used, depending on
*     the nature of the FitsChan supplied.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   AstFitsChan *this;            /* Pointer to the FitsChan structure. */
   char buff[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN + 1 ];
                                 /* Character buffer */
   char keyword[ FITSNAMLEN + 1 ]; /* Buffer for FITS keyword */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this_channel);

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

/* Output an "IsA" item only if there has been at least one item
   written since the last "Begin" or "IsA" item, or if the Full
   attribute for the Channel is greater than zero (requesting maximum
   information). */
   if ( items_written || astGetFull( this ) > 0 ) {

/* Create a unique FITS keyword for this "IsA" item, basing it on
   "ISA". */
      CreateKeyword( this, "ISA", keyword, status );

/* Generate a pre-quoted version of the class name. */
      PreQuote( class, buff, status );

/* Write the "IsA" item to the FitsChan as a keyword and string
   value. */
      astSetFitsS( this, keyword, buff,
                        astGetComment( this ) ? comment : NULL, 0 );

/* If helpful information has not been suppressed, generate an
   indented comment to mark the "IsA" item and write it to the
   FitsChan as a comment card with a blank keyword. */
      if ( astGetFull( this ) >= 0 ) {
         MakeIndentedComment( current_indent, '.', "Class boundary", "",
                              buff, status );
         astSetFitsCom( this, "        ", buff, 0 );
      }
   }

/* Clear the count of items written. */
   items_written = 0;
}

static void WriteObject( AstChannel *this_channel, const char *name,
                         int set, int helpful,
                         AstObject *value, const char *comment, int *status ) {
/*
*  Name:
*     WriteObject

*  Purpose:
*     Write an Object value to a data sink.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void WriteObject( AstChannel *this, const char *name,
*                       int set, int helpful,
*                       AstObject *value, const char *comment )

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

*  Description:
*     This function writes a named Object value, representing the
*     value of a class instance variable, to the data sink associated
*     with a FitsChan. It is intended for use by class "Dump"
*     functions when writing out class information which will
*     subsequently be re-read.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     name
*        Pointer to a constant null-terminated string containing the
*        name to be used to identify the value in the external
*        representation. This will form the key for identifying it
*        again when it is re-read. The name supplied should be unique
*        within its class.
*
*        Mixed case may be used and will be preserved in the external
*        representation (where possible) for cosmetic effect. However,
*        case is not significant when re-reading values.
*
*        It is recommended that a maximum of 6 alphanumeric characters
*        (starting with an alphabetic character) be used. This permits
*        maximum flexibility in adapting to standard external data
*        representations (e.g. FITS).
*     set
*        If this is zero, it indicates that the value being written is
*        a default value (or can be re-generated from other values) so
*        need not necessarily be written out. Such values will
*        typically be included in the external representation with
*        (e.g.) a comment character so that they are available to
*        human readers but will be ignored when re-read. They may also
*        be completely omitted in some circumstances.
*
*        If "set" is non-zero, the value will always be explicitly
*        included in the external representation so that it can be
*        re-read.
*     helpful
*        This flag provides a hint about whether a value whose "set"
*        flag is zero (above) should actually appear at all in the
*        external representaton.
*
*        If the external representation allows values to be "commented
*        out" then, by default, values will be included in this form
*        only if their "helpful" flag is non-zero. Otherwise, they
*        will be omitted entirely. When possible, omitting the more
*        obscure values associated with a class is recommended in
*        order to improve readability.
*
*        This default behaviour may be further modified if the
*        FitsChan's Full attribute is set - either to permit all
*        values to be shown, or to suppress non-essential information
*        entirely.
*     value
*        A pointer to the Object to be written.
*     comment
*        Pointer to a constant null-terminated string containing a
*        textual comment to be associated with the value.
*
*        Note that this comment may not actually be used, depending on
*        the nature of the FitsChan supplied and the setting of its
*        Comm attribute.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   AstFitsChan *this;            /* Pointer to the FitsChan structure. */
   char keyword[ FITSNAMLEN + 1 ]; /* Buffer for FITS keyword */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this_channel);

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

/* Use the "set" and "helpful" flags, along with the FitsChan's
   attributes to decide whether this value should actually be
   written. */
   if ( Use( this, set, helpful, status ) ) {

/* Create a unique FITS keyword from the name supplied. */
      CreateKeyword( this, name, keyword, status );

/* Write the value to the FitsChan as a keyword and a blank string value,
   not pre-quoted (this "null" value indicates that an Object description
   follows). */
      astSetFitsS( this, keyword, "",
                        astGetComment( this ) ? comment : NULL, 0 );

/* If the value is "set", write out the Object description. */
      if ( set ) {
         astWrite( this, value );

/* If the value is not set, replace the card just written to the FitsChan
   by COMENT card containing the keyword and blank string value (do not
   write out the Object description). */
      } else {
         MakeIntoComment( this, "astWrite", astGetClass( this ), status );
      }

/* Increment the count of items written. */
      items_written++;
   }
}

static void WriteToSink( AstFitsChan *this, int *status ){
/*
*  Name:
*     WriteToSink

*  Purpose:
*     Write the contents of the FitsChan out to the sink file or function.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void WriteToSink( AstFitsChan *this, int *status )

*  Class Membership:
*     FitsChan member function.

*  Description:
*     If the SinkFile attribute is set, each card in the FitsChan is
*     written out to the sink file. Otherwise, the cards are passed in
*     turn to the sink function specified when the FitsChan was created.
*     If no sink function was provided, the cards are not written out.
*     Cards marked as having been read into an AST object are not written
*     out.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     status
*        Pointer to the inherited status variable.

*  Notes:
*     -  The current card is left unchanged.
*/

/* Local Constants: */
#define ERRBUF_LEN 80

/* Local Variables: */
   FILE *fd;                    /* File descriptor for sink file */
   astDECLARE_GLOBALS           /* Declare the thread specific global data */
   char *errstat;               /* Pointer for system error message */
   char card[ AST__FITSCHAN_FITSCARDLEN + 1]; /* Buffer for header card */
   char errbuf[ ERRBUF_LEN ];   /* Buffer for system error message */
   const char *sink_file;       /* Path to output sink file */
   int icard;                   /* Current card index on entry */
   int old_ignore_used;         /* Original value of external variable ignore_used */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* If the SinkFile attribute is set, open the file. */
   fd = NULL;
   if( astTestSinkFile( this ) ) {
      sink_file = astGetSinkFile( this );
      fd = fopen( sink_file, "w" );
      if( !fd ) {
         if ( errno ) {
#if HAVE_STRERROR_R
            strerror_r( errno, errbuf, ERRBUF_LEN );
            errstat = errbuf;
#else
            errstat = strerror( errno );
#endif
            astError( AST__WRERR, "astDelete(%s): Failed to open output "
                      "SinkFile '%s' - %s.", status, astGetClass( this ),
                      sink_file, errstat );
         } else {
            astError( AST__WRERR, "astDelete(%s): Failed to open output "
                      "SinkFile '%s'.", status, astGetClass( this ),
                      sink_file );
         }
      }
   }

/* Only proceed if a file was opened, or sink function and wrapper were supplied. */
   if( fd || ( this->sink && this->sink_wrap ) ){

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

/* Indicate that cards which have been read into an AST object should skipped
   over by the functions which navigate the linked list of cards. */
      old_ignore_used = ignore_used;
      ignore_used = 1;

/* Ensure that the first card in the FitsChan will be the next one to be
   read. */
      astSetCard( this, 1 );

/* Loop round obtaining and writing out each card, until all cards have been
   processed. */
      while( !astFitsEof( this ) && astOK ){

/* Get the current card, and write it out through the sink function.
   The call to astFindFits increments the current card. */
         if( astFindFits( this, "%f", card, 1 ) ) {

/* If s sink file was opened, write the card out to it. */
            if( fd ) {
               fprintf( fd, "%s\n", card );

/* Otherwise, use the isnk function. The sink function is an externally
   supplied function which may not be thread-safe, so lock a mutex first.
   Also store the channel data pointer in a global variable so that it can
   be accessed in the sink function using macro astChannelData. */
            } else {
               astStoreChannelData( this );
               LOCK_MUTEX3;
               ( *this->sink_wrap )( *this->sink, card, status );
               UNLOCK_MUTEX3;
            }
         }
      }

/* Re-instate the original flag indicating if cards marked as having been
   read should be skipped over. */
      ignore_used = old_ignore_used;

/* Set the current card index back to what it was on entry. */
      astSetCard( this, icard );
   }

/* Close the sink file. */
   if( fd ) fclose( fd );
}

static void WriteString( AstChannel *this_channel, const char *name,
                         int set, int helpful,
                         const char *value, const char *comment, int *status ) {
/*
*  Name:
*     WriteString

*  Purpose:
*     Write a string value to a data sink.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     void WriteString( AstChannel *this, const char *name,
*                       int set, int helpful,
*                       const char *value, const char *comment )

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

*  Description:
*     This function writes a named string value, representing the
*     value of a class instance variable, to the data sink associated
*     with a FitsChan. It is intended for use by class "Dump"
*     functions when writing out class information which will
*     subsequently be re-read.

*  Parameters:
*     this
*        Pointer to the FitsChan.
*     name
*        Pointer to a constant null-terminated string containing the
*        name to be used to identify the value in the external
*        representation. This will form the key for identifying it
*        again when it is re-read. The name supplied should be unique
*        within its class.
*
*        Mixed case may be used and will be preserved in the external
*        representation (where possible) for cosmetic effect. However,
*        case is not significant when re-reading values.
*
*        It is recommended that a maximum of 6 alphanumeric characters
*        (starting with an alphabetic character) be used. This permits
*        maximum flexibility in adapting to standard external data
*        representations (e.g. FITS).
*     set
*        If this is zero, it indicates that the value being written is
*        a default value (or can be re-generated from other values) so
*        need not necessarily be written out. Such values will
*        typically be included in the external representation with
*        (e.g.) a comment character so that they are available to
*        human readers but will be ignored when re-read. They may also
*        be completely omitted in some circumstances.
*
*        If "set" is non-zero, the value will always be explicitly
*        included in the external representation so that it can be
*        re-read.
*     helpful
*        This flag provides a hint about whether a value whose "set"
*        flag is zero (above) should actually appear at all in the
*        external representaton.
*
*        If the external representation allows values to be "commented
*        out" then, by default, values will be included in this form
*        only if their "helpful" flag is non-zero. Otherwise, they
*        will be omitted entirely. When possible, omitting the more
*        obscure values associated with a class is recommended in
*        order to improve readability.
*
*        This default behaviour may be further modified if the
*        FitsChan's Full attribute is set - either to permit all
*        values to be shown, or to suppress non-essential information
*        entirely.
*     value
*        Pointer to a constant null-terminated string containing the
*        value to be written.
*     comment
*        Pointer to a constant null-terminated string containing a
*        textual comment to be associated with the value.
*
*        Note that this comment may not actually be used, depending on
*        the nature of the FitsChan supplied and the setting of its
*        Comm attribute.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   AstFitsChan *this;            /* Pointer to the FitsChan structure. */
   char *c;                      /* Pointer to next buffer character */
   char buff1[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN - 3 ]; /* Buffer for a single substring */
   char buff2[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN - 3 ]; /* Buffer for pre-quoted string */
   char cc;                      /* Next character */
   char keyword[ FITSNAMLEN + 1 ]; /* Buffer for FITS keyword */
   const char *start;            /* Pointer to start of substring */
   int first;                    /* Is this the first sub-string? */
   int nc;                       /* No. of available columns remaining */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this_channel);

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

/* Use the "set" and "helpful" flags, along with the FitsChan's
   attributes to decide whether this value should actually be
   written. */
   if ( Use( this, set, helpful, status ) ) {

/* Create a unique FITS keyword from the name supplied. */
      CreateKeyword( this, name, keyword, status );

/* Store a pointer to the start of the next sub-string (i.e. the
   beggining of the string), and then loop round until the end of the
   string is reached. */
      start = value;
      first = 1;
      while( *start && astOK ){

/* Store the number of characters available in the 80 column header card
   for the next substring, leaving room for the "= " string at the start,
   and the delimiting quotes. Also reserve 2 characters to allow for the
   possibility of double quotes being needed to protect trailing white space
   (see function PreQuote). */
         nc = AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN - 6;

/* If this is the first sub-string reserve room for any comment. */
         if( first ){
            if( comment && comment[0] ) nc -= ChrLen( comment, status ) + 3;

/* If the first card will be turned into a comment card, we need to leave room
   for the keyword name and equals sign, etc, within the 80 columns. */
            if( !set ) nc -= FITSNAMLEN + 5;
         }

/* We need to check the sub-string for single quotes since these will
   take up 2 characters each instead of 1 when encoded since single quotes
   within a string are doubled. Search through from the starting
   character, copying the sub-string into a buffer, and reducing the number
   of available characters remaining in the card for each character. */
         c = buff1;
         while( *start && nc > 0 ){
            cc = *(start++);
            *(c++) = cc;
            if( cc == '\'' ) {
               nc -= 2;
            } else {
               nc -= 1;
            }
         }

/* If the last character in the substring was a single quote, there may
   not have been room for the extra quote which is added when the
   sub-string is encoded. In this case we need to back up a character in
   order to remove the single quote frin this substring and move it into
   the next sub-string. */
         if( nc < 0 ){
            start--;
            c--;
         }

/* If the supplied value has not been exhausted, append an ampersand to
   the string. In this case we need to move the last character in the
   substring into the next substring to make room for the ampersand. */
         if( *start ) {
            start--;
            c--;
            *(c++) = '&';
         }

/* Terminate the buffer. */
         *c = 0;

/* The FITS standard considers trailing white space is be insignificant,
   and so we need to guard against external applications throwing away
   significant trailing white space. This is done by encosing the string,
   including trailing white space, in double quotes. */
         PreQuote( buff1, buff2, status );

/* On the first pass through this loop, write the value to the FitsChan as
   a keyword and value */
         if( first ){
            astSetFitsS( this, keyword, buff2,
                         astGetComment( this ) ? comment : NULL, 0 );

/* If the value is not "set", replace the card just written by a COMMENT
   card containing the text of the card as the comment. */
            if( !set ) MakeIntoComment( this, "astWrite", astGetClass( this ), status );

/* On subsequent passes through the loop, store the string using a CONTINUE
   keyword, with type AST__CONTINUE (this type is like AST__STRING but is
   formatted without an equals sign). */
         } else {
            astSetFitsCN( this, "CONTINUE", buff2, NULL, 0 );
         }
         first = 0;
      }

/* Increment the count of items written. */
      items_written++;
   }
}

static AstMapping *ZPXMapping( AstFitsChan *this, FitsStore *store, char s,
                               int naxes, int zpxaxes[2], const char *method,
                               const char *class, int *status ){
/*
*  Name:
*     ZPXMapping

*  Purpose:
*     Create a Mapping descriping "-ZPX" (IRAF) distortion.

*  Type:
*     Private function.

*  Synopsis:
*     AstMapping *ZPXMapping( AstFitsChan *this, FitsStore *store, char s,
*                             int naxes, int zpxaxes[2], const char *method,
*                             const char *class, int *status )

*  Class Membership:
*     FitsChan

*  Description:
*     This function uses the values in the supplied FitsStore to create a
*     Mapping which implements the "-ZPX" distortion code, produced by
*     the IRAF project. See:
*
*     http://iraf.noao.edu/projects/ccdmosaic/zpx.html
*
*     Note, the Mapping created by this function implements the "lngcor"
*     and "latcor" corrections described in the WAT... keywords. The
*     basic ZPN projection code is handled in the normal way, as any
*     other projection is handled.

*  Parameters:
*     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).
*     zpxaxes
*        The zero-based indices of the two IWC axes that use the ZPX projection.
*     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.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to the Mapping.
*/

/* Local Variables: */
   AstMapping   *ret;
   char *watstr;
   double *cvals[ 2 ];
   int *mvals[ 2 ];
   int ncoeff[ 2 ];
   int i;
   int icoeff;
   int ok;

/* Initialise the pointer to the returned Mapping. */
   ret = NULL;

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

/* Check both axes */
   for( i = 0; i < 2; i++ ){
      mvals[ i ] = NULL;
      cvals[ i ] = NULL;
      ncoeff[ i ] = 0;

/* Concatenate all the IRAF "WAT" keywords together for this axis. These
   keywords are marked as having been used, so that they are not written
   out when the FitsChan is deleted. */
      watstr = ConcatWAT( this, zpxaxes[ i ], method, class, status );

/* Extract the polynomial coefficients from the concatenated WAT string.
   These are returned in the form of a list of PVi_m values for a TPN
   projection. */
      ncoeff[ i ] = WATCoeffs( watstr, i, cvals + i, mvals + i, &ok, status );

/* If the current axis of the ZPX projection uses features not supported
   by AST, do not do any more axes. */
      if( !ok ) break;

/* Free the WAT string. */
      watstr = astFree( watstr );
   }

/* If we can handle the ZPX projection, store the polynomial coefficients in
   a new inverted TPN WcsMap. This WcsMap is used as a correction to the ZPN
   WcsMap to be created later, therefore set its FITSProj value to zero so
   that it is not used as the FITS projection when written out via
   astWrite. Also set TPNTan to zero to indicate that the TAN part of the
   TPN projection should not be used (i.e. just use the polynomial part). */
   if( ok && astOK ) {

      if( ncoeff[ 0 ] || ncoeff[ 1 ] ) {
         ret = (AstMapping *) astWcsMap( naxes, AST__TPN, zpxaxes[ 0 ] + 1,
                                         zpxaxes[ 1 ] + 1, "Invert=1",
                                         status );
         astSetFITSProj( ret, 0 );
         astSetTPNTan( ret, 0 );
         for( i = 0; i < 2; i++ ){
            for( icoeff = 0; icoeff < ncoeff[ i ]; icoeff++ ) {
               astSetPV( ret, zpxaxes[ i ], (mvals[ i ])[ icoeff ],
                         (cvals[ i ])[ icoeff ] );
            }
         }

      } else {
         ret = (AstMapping *) astUnitMap( naxes, " ", status );
      }

/* If the TNX cannot be represented in FITS-WCS (within our restrictions), add
   warning keywords to the FitsChan. */
   } else {
      Warn( this, "zpx", "This FITS header includes, or was "
            "derived from, a ZPX projection which requires "
            "unsupported IRAF-specific corrections. The WCS "
            "information may therefore be incorrect.", method, class,
            status );
   }

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

/* Functions which access class attributes. */
/* ---------------------------------------- */

/* Implement member functions to access the attributes associated with
   this class using the macros defined for this purpose in the
   "object.h" file. For a description of each attribute, see the class
   interface (in the associated .h file). */

/*
*att++
*  Name:
*     FitsTol

*  Purpose:
*     Maximum non-linearity allowed when exporting to FITS-WCS.

*  Type:
*     Public attribute.

*  Synopsis:
*     Floating point.

*  Description:
*     This attribute is used when attempting to write a FrameSet to a
*     FitsChan using a foreign encoding. It specifies the maximum
*     departure from linearity allowed on any axis within the mapping
*     from pixel coordinates to Intermediate World Coordinates. It is
*     expressed in units of pixels. If an axis of the Mapping is found
*     to deviate from linearity by more than this amount, the write
*     operation fails. If the linearity test succeeds, a linear
*     approximation to the mapping is used to determine the FITS keyword
*     values to be placed in the FitsChan.
*
*     The default value is one tenth of a pixel.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/
astMAKE_CLEAR(FitsChan,FitsTol,fitstol,-1.0)
astMAKE_GET(FitsChan,FitsTol,double,1,(this->fitstol==-1.0?0.1:this->fitstol))
astMAKE_SET(FitsChan,FitsTol,double,fitstol,(astMAX(value,0.0)))
astMAKE_TEST(FitsChan,FitsTol,(this->fitstol!=-1.0))

/*
*att++
*  Name:
*     Card

*  Purpose:
*     Index of current FITS card in a FitsChan.

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer.

*  Description:
*     This attribute gives the index of the "current" FITS header card
*     within a FitsChan, the first card having an index of 1. The
c     choice of current card affects the behaviour of functions that
f     choice of current card affects the behaviour of routines that
c     access the contents of the FitsChan, such as astDelFits,
c     astFindFits and astPutFits.
f     access the contents of the FitsChan, such as AST_DELFITS,
f     AST_FINDFITS and AST_PUTFITS.
*
*     A value assigned to Card will position the FitsChan at any
*     desired point, so that a particular card within it can be
*     accessed. Alternatively, the value of Card may be enquired in
*     order to determine the current position of a FitsChan.
*
*     The default value of Card is 1. This means that clearing
c     this attribute (using astClear) effectively "rewinds" the
f     this attribute (using AST_CLEAR) effectively "rewinds" the
*     FitsChan, so that the first card is accessed next.  If Card is
*     set to a value which exceeds the total number of cards in the
*     FitsChan (as given by its Ncard attribute), it is regarded as
*     pointing at the "end-of-file". In this case, the value returned
*     in response to an enquiry is always one more than the number of
*     cards in the FitsChan.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/

/* Encoding. */
/* ========= */

/*
*att++
*  Name:
*     Encoding

*  Purpose:
*     System for encoding Objects as FITS headers.

*  Type:
*     Public attribute.

*  Synopsis:
*     String.

*  Description:
*     This attribute specifies the encoding system to use when AST
*     Objects are stored as FITS header cards in a FitsChan. It
c     affects the behaviour of the astWrite and astRead functions when
f     affects the behaviour of the AST_WRITE and AST_READ routines when
*     they are used to transfer any AST Object to or from an external
*     representation consisting of FITS header cards (i.e. whenever a
*     write or read operation is performed using a FitsChan as the I/O
*     Channel).
*
*     There are several ways (conventions) by which coordinate system
*     information may be represented in the form of FITS headers and
*     the Encoding attribute is used to specify which of these should
*     be used. The encoding options available are outlined in the
*     "Encodings Available" section below, and in more detail in the
*     sections which follow.
*
*     Encoding systems differ in the range of possible Objects
*     (e.g. classes) they can represent, in the restrictions they
*     place on these Objects (e.g. compatibility with some
*     externally-defined coordinate system model) and in the number of
*     Objects that can be stored together in any particular set of
*     FITS header cards (e.g. multiple Objects, or only a single
*     Object). The choice of encoding also affects the range of
*     external applications which can potentially read and interpret
*     the FITS header cards produced.
*
*     The encoding options available are not necessarily mutually
*     exclusive, and it may sometimes be possible to store multiple
*     Objects (or the same Object several times) using different
*     encodings within the same set of FITS header cards. This
*     possibility increases the likelihood of other applications being
*     able to read and interpret the information.
*
*     By default, a FitsChan will attempt to determine which encoding
*     system is already in use, and will set the default Encoding
*     value accordingly (so that subsequent I/O operations adopt the
*     same conventions). It does this by looking for certain critical
*     FITS keywords which only occur in particular encodings. For
*     details of how this works, see the "Choice of Default Encoding"
*     section below. If you wish to ensure that a particular encoding
*     system is used, independently of any FITS cards already present,
*     you should set an explicit Encoding value yourself.

*  Encodings Available:
*     The Encoding attribute can take any of the following (case
*     insensitive) string values to select the corresponding encoding

*     system:
*
*     - "DSS": Encodes coordinate system information in FITS header
*     cards using the convention developed at the Space Telescope
*     Science Institute (STScI) for the Digitised Sky Survey (DSS)
*     astrometric plate calibrations. The main advantages of this
*     encoding are that FITS images which use it are widely available
*     and it is understood by a number of important and
*     well-established astronomy applications. For further details,
*     see the section "The DSS Encoding" below.
*
*     - "FITS-WCS": Encodes coordinate system information in FITS
*     header cards using the conventions described in the FITS
*     world coordinate system (FITS-WCS) papers by E.W. Greisen,
*     M. Calabretta, et al. The main advantages of this encoding are that
*     it should be understood by any FITS-WCS compliant application and
*     is likely to be adopted widely for FITS data in future. For further
*     details, see the section "The FITS-WCS Encoding" below.
*
*     - "FITS-PC": Encodes coordinate system information in FITS
*     header cards using the conventions described in an earlier draft
*     of the FITS world coordinate system papers by E.W. Greisen and
*     M. Calabretta. This encoding uses a combination of CDELTi and
*     PCiiijjj keywords to describe the scale and rotation of the pixel
*     axes. This encoding is included to support existing data and
*     software which uses these now superceded conventions. In general,
*     the "FITS-WCS" encoding (which uses CDi_j or PCi_j keywords to
*     describe the scale and rotation) should be used in preference to
*     "FITS-PC".
*
*     - "FITS-IRAF": Encodes coordinate system information in FITS
*     header cards using the conventions described in the document
*     "World Coordinate Systems Representations Within the FITS
*     Format" by R.J. Hanisch and D.G. Wells, 1988.  This encoding is
*     currently employed by the IRAF data analysis facility, so its
*     use will facilitate data exchange with IRAF. Its main advantages
*     are that it is a stable convention which approximates to a
*     subset of the propsed FITS-WCS encoding (above). This makes it
*     suitable as an interim method for storing coordinate system
*     information in FITS headers until the FITS-WCS encoding becomes
*     stable. Since many datasets currently use the FITS-IRAF
*     encoding, conversion of data from FITS-IRAF to the final form of
*     FITS-WCS is likely to be well supported.
*
*     - "FITS-AIPS": Encodes coordinate system information in FITS
*     header cards using the conventions originally introduced by the
*     AIPS data analysis facility. This is base on the use of CDELTi and
*     CROTAi keuwords to desribe the scale and rotation of each axis.
*     These conventions have been superceded but are still widely used.
*
*     - "FITS-AIPS++": Encodes coordinate system information in FITS
*     header cards using the conventions used by the AIPS++ project.
*     This is an extension of FITS-AIPS which includes some of the
*     features of FITS-IRAF and FITS-PC.
*
*     - "FITS-CLASS": Encodes coordinate system information in FITS
*     header cards using the conventions used by the CLASS project.
*     CLASS is a software package for reducing single-dish radio and
*     sub-mm spectroscopic data. See the section "CLASS FITS format" at
*     http://www.iram.fr/IRAMFR/GILDAS/doc/html/class-html/.
*
*     - "NATIVE": Encodes AST Objects in FITS header cards using a
*     convention which is private to the AST library (but adheres to
*     the general FITS standard) and which uses FITS keywords that
*     will not clash with other encoding systems. The main advantages
*     of this are that any class of AST Object may be encoded, and any
*     (reasonable) number of Objects may be stored sequentially in the
*     same FITS header. This makes FITS headers an almost loss-less
*     communication path for passing AST Objects between applications
*     (although all such applications must, of course, make use of the
*     AST library to interpret the information). For further details,
*     see the section "The NATIVE Encoding" below.

*  Choice of Default Encoding:
*     If the Encoding attribute of a FitsChan is not set, the default
*     value it takes is determined by the presence of certain critical
*     FITS keywords within the FitsChan. The sequence of decisions

*     used to arrive at the default value is as follows:
*
*     - If the FitsChan contains any keywords beginning with the
*     string "BEGAST", then NATIVE encoding is used,
*     - Otherwise, FITS-CLASS is used if the FitsChan contains a DELTAV
*     keyword and a keyword of the form VELO-xxx, where xxx indicates one
*     of the rest frames used by class (e.g. "VELO-LSR"), or "VLSR".
*     - Otherwise, if the FitsChan contains a CTYPE keyword which
*     represents a spectral axis using the conventions of the AIPS and
*     AIPS++ projects (e.g. "FELO-LSR", etc), then one of FITS-AIPS or
*     FITS-AIPS++ encoding is used. FITS-AIPS++ is used if any of the
*     keywords CDi_j, PROJP, LONPOLE or LATPOLE are
*     found in the FitsChan. Otherwise FITS-AIPS is used.
*     - Otherwise, if the FitsChan contains a keyword of the form
*     "PCiiijjj", where "i" and "j" are single digits, then
*     FITS-PC encoding is used,
*     - Otherwise, if the FitsChan contains a keyword of the form
*     "CDiiijjj", where "i" and "j" are single digits, then
*     FITS-IRAF encoding is used,
*     - Otherwise, if the FitsChan contains a keyword of the form
*     "CDi_j", and at least one of RADECSYS, PROJPi, or CjVALi
*     where "i" and "j" are single digits, then FITS-IRAF encoding is
*     used.
*     - Otherwise, if the FitsChan contains any keywords of the form
*     PROJPi, CjVALi or RADECSYS, where "i" and "j" are single digits,
*     then FITS-PC encoding is used.
*     - Otherwise, if the FitsChan contains a keyword of the form
*     CROTAi, where "i" is a single digit, then FITS-AIPS encoding is
*     used.
*     - Otherwise, if the FitsChan contains a keyword of the form
*     CRVALi, where "i" is a single digit, then FITS-WCS encoding is
*     used.
*     - Otherwise, if the FitsChan contains the "PLTRAH" keyword, then
*     DSS encoding is used,
*     - Otherwise, if none of these conditions is met (as would be the
*     case when using an empty FitsChan), then NATIVE encoding is
*     used.
*
*     Except for the NATIVE and DSS encodings, all the above checks
*     also require that the header contains at least one CTYPE, CRPIX and
*     CRVAL keyword (otherwise the checking process continues to the next
*     case).
*
*     Setting an explicit value for the Encoding attribute always
*     over-rides this default behaviour.
*
*     Note that when writing information to a FitsChan, the choice of
*     encoding will depend greatly on the type of application you
*     expect to be reading the information in future. If you do not
*     know this, there may sometimes be an advantage in writing the
*     information several times, using a different encoding on each
*     occasion.

*  The DSS Encoding:
*     The DSS encoding uses FITS header cards to store a multi-term
*     polynomial which relates pixel positions on a digitised
*     photographic plate to celestial coordinates (right ascension and
*     declination). This encoding may only be used to store a single
*     AST Object in any set of FITS header cards, and that Object must
*     be a FrameSet which conforms to the STScI/DSS coordinate system
*     model (this means the Mapping which relates its base and current
*     Frames must include either a DssMap or a WcsMap with type
*     AST__TAN or AST__TPN).
*
c     When reading a DSS encoded Object (using astRead), the FitsChan
f     When reading a DSS encoded Object (using AST_READ), the FitsChan
*     concerned must initially be positioned at the first card (its
*     Card attribute must equal 1) and the result of the read, if
*     successful, will always be a pointer to a FrameSet. The base
*     Frame of this FrameSet represents DSS pixel coordinates, and the
*     current Frame represents DSS celestial coordinates. Such a read
*     is always destructive and causes the FITS header cards required
*     for the construction of the FrameSet to be removed from the
*     FitsChan, which is then left positioned at the "end-of-file". A
*     subsequent read using the same encoding will therefore not
*     return another FrameSet, even if the FitsChan is rewound.
*
c     When astWrite is used to store a FrameSet using DSS encoding,
f     When AST_WRITE is used to store a FrameSet using DSS encoding,
*     an attempt is first made to simplify the FrameSet to see if it
*     conforms to the DSS model.  Specifically, the current Frame must
*     be a FK5 SkyFrame; the projection must be a tangent plane
*     (gnomonic) projection with polynomial corrections conforming to
*     DSS requirements, and north must be parallel to the second base
*     Frame axis.
*
*     If the simplification process succeeds, a description of the
*     FrameSet is written to the FitsChan using appropriate DSS FITS
*     header cards. The base Frame of the FrameSet is used to form the
*     DSS pixel coordinate system and the current Frame gives the DSS
*     celestial coordinate system.  A successful write operation will
*     over-write any existing DSS encoded data in the FitsChan, but
*     will not affect other (non-DSS) header cards. If a destructive
*     read of a DSS encoded Object has previously occurred, then an
*     attempt will be made to store the FITS header cards back in
*     their original locations.
*
*     If an attempt to simplify a FrameSet to conform to the DSS model
*     fails (or if the Object supplied is not a FrameSet), then no
c     data will be written to the FitsChan and astWrite will return
f     data will be written to the FitsChan and AST_WRITE will return
*     zero. No error will result.

*  The FITS-WCS Encoding:
*     The FITS-WCS convention uses FITS header cards to describe the
*     relationship between pixels in an image (not necessarily
*     2-dimensional) and one or more related "world coordinate systems".
*     The FITS-WCS encoding may only be used to store a single AST Object
*     in any set of FITS header cards, and that Object must be a FrameSet
*     which conforms to the FITS-WCS model (the FrameSet may, however,
*     contain multiple Frames which will be result in multiple FITS
*     "alternate axis descriptions"). Details of the use made by this
*     Encoding of the conventions described in the FITS-WCS papers are
*     given in the appendix "FITS-WCS Coverage" of this document. A few
*     main points are  described below.
*
*     The rotation and scaling of the intermediate world coordinate system
*     can be specified using either "CDi_j" keywords, or "PCi_j" together
*     with "CDELTi" keywords. When writing a FrameSet to a FitsChan, the
*     the value of the CDMatrix attribute of the FitsChan determines
*     which system is used.
*
*     In addition, this encoding supports the "TAN with polynomial correction
*     terms" projection which was included in a draft of the FITS-WCS paper,
*     but was not present in the final version. A "TAN with polynomial
*     correction terms" projection is represented using a WcsMap with type
*     AST__TPN (rather than AST__TAN which is used to represent simple
*     TAN projections). When reading a FITS header, a CTYPE keyword value
*     including a "-TAN" code results in an AST__TPN projection if there are
*     any projection parameters (given by the PVi_m keywords) associated with
*     the latitude axis, or if there are projection parameters associated
*     with the longitude axis for m greater than 4. When writing a
*     FrameSet to a FITS header, an AST__TPN projection gives rise to a
*     CTYPE value including the normal "-TAN" code, but the projection
*     parameters are stored in keywords with names "QVi_m", instead of the
*     usual "PVi_m". Since these QV parameters are not part of the
*     FITS-WCS standard they will be ignored by other non-AST software,
*     resulting in the WCS being interpreted as a simple TAN projection
*     without any corrections. This should be seen as an interim solution
*     until such time as an agreed method for describing projection
*     distortions within FITS-WCS has been published.
*
*     AST extends the range of celestial coordinate systems which may be
*     described using this encoding by allowing the inclusion of
*     "AZ--" and "EL--" as the coordinate specification within CTYPE
*     values. These form a longitude/latitude pair of axes which describe
*     azimuth and elevation. The geographic position of the observer
*     should be supplied using the OBSGEO-X/Y/Z keywords described in FITS-WCS
*     paper III. Currently, a simple model is used which includes diurnal
*     aberration, but ignores atmospheric refraction, polar motion, etc.
*     These may be added in a later release.
*
*     If an AST SkyFrame that represents offset rather than absolute
*     coordinates (see attribute SkyRefIs) is written to a FitsChan using
*     FITS-WCS encoding, two alternate axis descriptions will be created.
*     One will describe the offset coordinates, and will use "OFLN" and
*     "OFLT" as the axis codes in the CTYPE keywords. The other will
*     describe absolute coordinates as specified by the System attribute
*     of the SkyFrame, using the usual CTYPE codes ("RA--"/"DEC-", etc).
*     In addition, the absolute coordinates description will contain
*     AST-specific keywords (SREF1/2, SREFP1/2 and SREFIS) that allow the
*     header to be read back into AST in order to reconstruct the original
*     SkyFrame.
*
c     When reading a FITS-WCS encoded Object (using astRead), the FitsChan
f     When reading a FITS-WCS encoded Object (using AST_READ), the FitsChan
*     concerned must initially be positioned at the first card (its
*     Card attribute must equal 1) and the result of the read, if
*     successful, will always be a pointer to a FrameSet. The base
*     Frame of this FrameSet represents FITS-WCS pixel coordinates,
*     and the current Frame represents the physical coordinate system
*     described by the FITS-WCS primary axis descriptions. If
*     secondary axis descriptions are also present, then the FrameSet
*     may contain additional (non-current) Frames which represent
*     these.  Such a read is always destructive and causes the FITS
*     header cards required for the construction of the FrameSet to be
*     removed from the FitsChan, which is then left positioned at the
*     "end-of-file". A subsequent read using the same encoding will
*     therefore not return another FrameSet, even if the FitsChan is
*     rewound.
*
c     When astWrite is used to store a FrameSet using FITS-WCS
f     When AST_WRITE is used to store a FrameSet using FITS-WCS
*     encoding, an attempt is first made to simplify the FrameSet to
*     see if it conforms to the FITS-WCS model. If this simplification
*     process succeeds (as it often should, as the model is reasonably
*     flexible), a description of the FrameSet is written to the
*     FitsChan using appropriate FITS header cards. The base Frame of
*     the FrameSet is used to form the FITS-WCS pixel coordinate
*     system and the current Frame gives the physical coordinate
*     system to be described by the FITS-WCS primary axis
*     descriptions.  Any additional Frames in the FrameSet may be used
*     to construct secondary axis descriptions, where appropriate.
*
*     A successful write operation will over-write any existing
*     FITS-WCS encoded data in the FitsChan, but will not affect other
*     (non-FITS-WCS) header cards. If a destructive read of a FITS-WCS
*     encoded Object has previously occurred, then an attempt will be
*     made to store the FITS header cards back in their original
*     locations. Otherwise, the new cards will be inserted following
*     any other FITS-WCS related header cards present or, failing
*     that, in front of the current card (as given by the Card
*     attribute).
*
*     If an attempt to simplify a FrameSet to conform to the FITS-WCS
*     model fails (or if the Object supplied is not a FrameSet), then
c     no data will be written to the FitsChan and astWrite will
f     no data will be written to the FitsChan and AST_WRITE will
*     return zero. No error will result.

*  The FITS-IRAF Encoding:
*     The FITS-IRAF encoding can, for most purposes, be considered as
*     a subset of the FITS-WCS encoding (above), although it differs
*     in the details of the FITS keywords used. It is used in exactly
*     the same way and has the same restrictions, but with the

*     addition of the following:
*
*     - The only celestial coordinate systems that may be represented
*     are equatorial, galactic and ecliptic,
*     - Sky projections can be represented only if any associated
*     projection parameters are set to their default values.
*     - Secondary axis descriptions are not supported, so when writing
*     a FrameSet to a FitsChan, only information from the base and
*     current Frames will be stored.
*
*     Note that this encoding is provided mainly as an interim measure to
*     provide a more stable alternative to the FITS-WCS encoding until the
*     FITS standard for encoding WCS information is finalised.  The name
*     "FITS-IRAF" indicates the general keyword conventions used and does
*     not imply that this encoding will necessarily support all features of
*     the WCS scheme used by IRAF software. Nevertheless, an attempt has
*     been made to support a few such features where they are known to be
*     used by important sources of data.
*
*     When writing a FrameSet using the FITS-IRAF encoding, axis rotations
*     are specified by a matrix of FITS keywords of the form "CDi_j", where
*     "i" and "j" are single digits. The alternative form "CDiiijjj", which
*     is also in use, is recognised when reading an Object, but is never
*     written.
*
*     In addition, the experimental IRAF "ZPX" and "TNX" sky projections will
*     be accepted when reading, but will never be written (the corresponding
*     FITS "ZPN" or "distorted TAN" projection being used instead). However,
*     there are restrictions on the use of these experimental projections.
*     For "ZPX", longitude and latitude correction surfaces (appearing as
*     "lngcor" or "latcor" terms in the IRAF-specific "WAT" keywords) are
*     not supported. For "TNX" projections, only cubic surfaces encoded as
*     simple polynomials with "half cross-terms" are supported. If an
*     un-usable "TNX" or "ZPX" projection is encountered while reading
*     from a FitsChan, a simpler form of TAN or ZPN projection is used
*     which ignores the unsupported features and may therefore be
*     inaccurate. If this happens, a warning message is added to the
*     contents of the FitsChan as a set of cards using the keyword "ASTWARN".
*
*     You should not normally attempt to mix the foreign FITS encodings within
*     the same FitsChan, since there is a risk that keyword clashes may occur.

*  The FITS-PC Encoding:
*     The FITS-PC encoding can, for most purposes, be considered as
*     equivalent to the FITS-WCS encoding (above), although it differs
*     in the details of the FITS keywords used. It is used in exactly
*     the same way and has the same restrictions.

*  The FITS-AIPS Encoding:
*     The FITS-AIPS encoding can, for most purposes, be considered as
*     equivalent to the FITS-WCS encoding (above), although it differs
*     in the details of the FITS keywords used. It is used in exactly
*     the same way and has the same restrictions, but with the

*     addition of the following:
*
*     - The only celestial coordinate systems that may be represented
*     are equatorial, galactic and ecliptic,
*     - Spectral axes can only be represented if they represent
*     frequency, radio velocity or optical velocity, and are linearly
*     sampled in frequency. In addition, the standard of rest
*     must be LSRK, LSRD, barycentric or geocentric.
*     - Sky projections can be represented only if any associated
*     projection parameters are set to their default values.
*     - The AIT, SFL and MER projections can only be written if the CRVAL
*     keywords are zero for both longitude and latitude axes.
*     - Secondary axis descriptions are not supported, so when writing
*     a FrameSet to a FitsChan, only information from the base and
*     current Frames will be stored.
*     - If there are more than 2 axes in the base and current Frames, any
*     rotation must be restricted to the celestial plane, and must involve
*     no shear.

*  The FITS-AIPS++ Encoding:
*     The FITS-AIPS++ encoding is based on the FITS-AIPS encoding, but
*     includes some features of the FITS-IRAF and FITS-PC encodings.
*     Specifically, any celestial projections supported by FITS-PC may be
*     used, including those which require parameterisation, and the axis
*     rotation and scaling may be specified using CDi_j keywords. When
*     writing a FITS header, rotation will be specified using CROTA/CDELT
*     keywords if possible, otherwise CDi_j keywords will be used instead.

*  The FITS-CLASS Encoding:
*     The FITS-CLASS encoding uses the conventions of the CLASS project.
*     These are described in the section "Developer Manual"/"CLASS FITS

*     Format" contained in the CLASS documentation at:
*
*     http://www.iram.fr/IRAMFR/GILDAS/doc/html/class-html/class.html.
*

*     This encoding is similar to FITS-AIPS with the following restrictions:
*
*     - When a SpecFrame is created by reading a FITS-CLASS header, the
*       attributes describing the observer's position (ObsLat, ObsLon and
*       ObsAlt) are left unset because the CLASS encoding does not specify
*       these values. Conversions to or from the topocentric standard of rest
*       will therefore be inaccurate (typically by up to about 0.5 km/s)
*       unless suitable values are assigned to these attributes after the
*       FrameSet has been created.
*     - When writing a FrameSet to a FITS-CLASS header, the current Frame
*       in the FrameSet must have at least 3 WCS axes, of which one must be
*       a linear spectral axis. The spectral axis in the created header will
*       always describe frequency. If the spectral axis in the supplied
*       FrameSet refers to some other system (e.g. radio velocity, etc),
*       then it will be converted to frequency.
*     - There must be a pair of celestial axes - either (RA,Dec) or
*       (GLON,GLAT). RA and Dec must be either FK4/B1950 or FK5/J2000.
*     - A limited range of projection codes (TAN, ARC, STG, AIT, SFL, SIN)
*       can be used. For AIT and SFL, the reference point must be at the
*       origin of longitude and latitude. For SIN, the associated projection
*       parameters must both be zero.
*     - No rotation of the celestial axes is allowed, unless the spatial
*       axes are degenerate (i.e. cover only a single pixel).
*     - The frequency axis in the created header will always describe
*       frequency in the source rest frame. If the supplied FrameSet uses
*       some other standard of rest then suitable conversion will be applied.
*     - The source velocity must be defined. In other words, the SpecFrame
*       attributes SourceVel and SourceVRF must have been assigned values.
*     - The frequency axis in a FITS-CLASS header does not represent
*       absolute frequency, but instead represents offsets from the rest
*       frequency in the standard of rest of the source.
*
*     When writing a FrameSet out using FITS-CLASS encoding, the current
*     Frame may be temporarily modified if this will allow the header
*     to be produced. If this is done, the associated pixel->WCS Mapping
*     will also be modified to take account of the changes to the Frame.
*     The modifications performed include re-ordering axes (WCS axes, not
*     pixel axes), changing spectral coordinate system and standard of
*     rest, changing the celestial coordinate system and reference equinox,
*     and changing axis units.

*  The NATIVE Encoding:
*     The NATIVE encoding may be used to store a description of any
*     class of AST Object in the form of FITS header cards, and (for
*     most practical purposes) any number of these Object descriptions
*     may be stored within a single set of FITS cards. If multiple
*     Object descriptions are stored, they are written and read
*     sequentially. The NATIVE encoding makes use of unique FITS
*     keywords which are designed not to clash with keywords that have
*     already been used for other purposes (if a potential clash is
*     detected, an alternative keyword is constructed to avoid the
*     clash).
*
*     When reading a NATIVE encoded object from a FitsChan (using
c     astRead), FITS header cards are read, starting at the current
f     AST_READ), FITS header cards are read, starting at the current
*     card (as determined by the Card attribute), until the start of
*     the next Object description is found. This description is then
*     read and converted into an AST Object, for which a pointer is
*     returned. Such a read is always destructive and causes all the
*     FITS header cards involved in the Object description to be
*     removed from the FitsChan, which is left positioned at the
*     following card.
*
*     The Object returned may be of any class, depending on the
*     description that was read, and other AST routines may be used to
*     validate it (for example, by examining its Class or ID attribute
c     using astGetC). If further NATIVE encoded Object descriptions
f     using AST_GETC). If further NATIVE encoded Object descriptions
c     exist in the FitsChan, subsequent calls to astRead will return
f     exist in the FitsChan, subsequent calls to AST_READ will return
*     the Objects they describe in sequence (and destroy their
*     descriptions) until no more remain between the current card and
*     the "end-of-file".
*
c     When astWrite is used to write an Object using NATIVE encoding,
f     When AST_WRITE is used to write an Object using NATIVE encoding,
*     a description of the Object is inserted immediately before the
*     current card (as determined by the Card attribute).  Multiple
*     Object descriptions may be written in this way and are stored
*     separately (and sequentially if the Card attribute is not
*     modified between the writes). A write operation using the NATIVE
*     encoding does not over-write previously written Object
*     descriptions. Note, however, that subsequent behaviour is
*     undefined if an Object description is written inside a
*     previously-written description, so this should be avoided.
*
*     When an Object is written to a FitsChan using NATIVE encoding,
c     astWrite should (barring errors) always transfer data and
f     AST_WRITE should (barring errors) always transfer data and
*     return a value of 1.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/
astMAKE_CLEAR(FitsChan,Encoding,encoding,UNKNOWN_ENCODING)
astMAKE_SET(FitsChan,Encoding,int,encoding,(
   value == NATIVE_ENCODING ||
   value == FITSPC_ENCODING ||
   value == FITSWCS_ENCODING ||
   value == FITSIRAF_ENCODING ||
   value == FITSAIPS_ENCODING ||
   value == FITSAIPSPP_ENCODING ||
   value == FITSCLASS_ENCODING ||
   value == DSS_ENCODING ? value :
   (astError( AST__BADAT, "astSetEncoding: Unknown encoding system %d "
              "supplied.", status, value ), UNKNOWN_ENCODING )))
astMAKE_TEST(FitsChan,Encoding,( this->encoding != UNKNOWN_ENCODING ))

/* DefB1950 */
/* ======== */

/*
*att++
*  Name:
*     DefB1950

*  Purpose:
*     Use FK4 B1950 as defaults?

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer (boolean).

*  Description:
*     This attribute is a boolean value which specifies a default equinox
*     and reference frame to use when reading a FrameSet from a FitsChan
*     with a foreign (i.e. non-native) encoding. It is only used if the FITS
*     header contains RA and DEC axes but contains no information about the
*     reference frame or equinox. If this is the case, then values of FK4 and
*     B1950 are assumed if the DefB1950 attribute has a non-zero value and
*     ICRS is assumed if DefB1950 is zero. The default value for DefB1950
*     depends on the value of the Encoding attribute: for FITS-WCS encoding
*     the default is zero, and for all other encodings it is one.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/
astMAKE_CLEAR(FitsChan,DefB1950,defb1950,-1)
astMAKE_GET(FitsChan,DefB1950,int,1,(this->defb1950 == -1 ? (astGetEncoding(this)== FITSWCS_ENCODING?0:1): this->defb1950))
astMAKE_SET(FitsChan,DefB1950,int,defb1950,( value ? 1 : 0 ))
astMAKE_TEST(FitsChan,DefB1950,( this->defb1950 != -1 ))

/* TabOK */
/* ===== */

/*
*att++
*  Name:
*     TabOK

*  Purpose:
*     Should the FITS-WCS -TAB algorithm be recognised?

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer.

*  Description:
*     This attribute is an integer value which indicates if the "-TAB"
*     algorithm, defined in FITS-WCS paper III, should be supported by
*     the FitsChan. The default value is zero. A zero or negative value
*     results in no support for -TAB axes (i.e. axes that have "-TAB"
*     in their CTYPE keyword value). In this case, the
c     astWrite
f     AST_WRITE
*     method will return zero if the write operation would required the
*     use of the -TAB algorithm, and the
c     astRead
f     AST_READ
*     method will return
c     a NULL pointer
f     AST__NULL
*     if any axis in the supplied header uses the -TAB algorithm.

*     If TabOK is set to a non-zero positive integer, these methods will
*     recognise and convert axes described by the -TAB algorithm, as
*     follows:
*
c     The astWrite
f     The AST_WRITE
*     method will generate headers that use the -TAB algorithm (if
*     possible) if no other known FITS-WCS algorithm can be used to
*     describe the supplied FrameSet (but see the ForceTab attribute). This
*     will result in a table of
*     coordinate values and index vectors being stored in the FitsChan.
*     After the write operation, the calling application should check to
*     see if such a table has been stored in the FitsChan. If so, the
*     table should be retrived from the FitsChan using the
c     astGetTables
f     AST_GETTABLES
*     method, and the data (and headers) within it copied into a new
*     FITS binary table extension. See
c     astGetTables
f     AST_GETTABLES
*     for more information. The FitsChan uses a FitsTable object to store
*     the table data and headers. This FitsTable will contain the required
*     columns and headers as described by FITS-WCS paper III - the
*     coordinates array will be in a column named "COORDS", and the index
*     vector(s) will be in columns named "INDEX<i>" (where <i> is the index
*     of the corresponding FITS WCS  axis). Note, index vectors are only
*     created if required. The EXTNAME value will be set to the value of the
*     AST__TABEXTNAME constant (currently "WCS-TAB"). The EXTVER header
*     will be set to the positive integer value assigned to the TabOK
*     attribute. No value will be stored for the EXTLEVEL header, and should
*     therefore be considered to default to 1.
*
c     The astRead
f     The AST_READ
*     method will generate a FrameSet from headers that use the -TAB
*     algorithm so long as the necessary FITS binary tables are made
*     available. There are two ways to do this: firstly, if the application
*     knows which FITS binary tables will be needed, then it can create a
*     Fitstable describing each such table and store it in the FitsChan
*     (using method
c     astPutTables or astPutTable) before invoking the astRead method.
f     AST_PUTTABLES or AST_PUTTABLE) before invoking the AST_READ method.
*     Secondly, if the application does not know which FITS binary tables
*     will be needed by
c     astRead,
f     AST_READ,
*     then it can register a call-back function with the FitsChan using
*     method
c     astTableSource.
f     AST_TABLESOURCE.
*     This call-back function will be called from within
c     astRead
f     AST_READ
*     if and when a -TAB header is encountered. When called, its arguments
*     will give the name, version and level of the FITS extension containing
*     a required table. The call-back function should read this table from
*     an external FITS file, and create a corresponding FitsTable which
*     it should then return to
c     astRead. Note, currently astRead
f     AST_READ. Note, currently AST_READ
*     can only handle -TAB headers that describe 1-dimensional (i.e.
*     separable) axes.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/
astMAKE_CLEAR(FitsChan,TabOK,tabok,-INT_MAX)
astMAKE_GET(FitsChan,TabOK,int,0,(this->tabok == -INT_MAX ? 0 : this->tabok))
astMAKE_SET(FitsChan,TabOK,int,tabok,value)
astMAKE_TEST(FitsChan,TabOK,( this->tabok != -INT_MAX ))

/* ForceTab */
/* ======== */

/*
*att++
*  Name:
*     ForceTab

*  Purpose:
*     Force the use of the -TAB algorithm when writing FITS-WCS headers?

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer (boolean).

*  Description:
*     This attribute is a  boolean value which indicates if the "-TAB"
*     algorithm, defined in FITS-WCS paper III, should be used if at all
*     possible when writing out a FrameSet using the FITS-WCS encoding.
*
*     Note this attribute has no effect unless the TabOK attribute is set
*     to a positive non-zero value. If, in addition to setting TabOK,
*     ForceTab is set to non-zero value, astWrite will attempt to use the
*     -TAB algorithm even if another algorithm could have been used. If
*     ForceTab is left at its default value of zero, then the -TAB algorithm
*     will be used only if no other algorithm can be used.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/
astMAKE_CLEAR(FitsChan,ForceTab,forcetab,-INT_MAX)
astMAKE_GET(FitsChan,ForceTab,int,0,(this->forcetab == -INT_MAX ? 0 : this->forcetab))
astMAKE_SET(FitsChan,ForceTab,int,forcetab,value)
astMAKE_TEST(FitsChan,ForceTab,( this->forcetab != -INT_MAX ))

/* CarLin */
/* ====== */

/*
*att++
*  Name:
*     CarLin

*  Purpose:
*     Ignore spherical rotations on CAR projections?

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer (boolean).

*  Description:
*     This attribute is a boolean value which specifies how FITS "CAR"
*     (plate carree, or "Cartesian") projections should be treated when
*     reading a FrameSet from a foreign encoded FITS header. If zero (the
*     default), it is assumed that the CAR projection conforms to the
*     conventions described in the FITS world coordinate system (FITS-WCS)
*     paper II "Representation of Celestial Coordinates in FITS" by
*     M. Calabretta & E.W. Greisen. If CarLin is non-zero, then these
*     conventions are ignored, and it is assumed that the mapping from pixel
*     coordinates to celestial coordinates is a simple linear transformation
*     (hence the attribute name "CarLin"). This is appropriate for some older
*     FITS data which claims to have a "CAR" projection, but which in fact do
*     not conform to the conventions of the FITS-WCS paper.
*
*     The FITS-WCS paper specifies that headers which include a CAR projection
*     represent a linear mapping from pixel coordinates to "native spherical
*     coordinates", NOT celestial coordinates. An extra mapping is then
*     required from native spherical to celestial. This mapping is a 3D
*     rotation and so the overall Mapping from pixel to celestial coordinates
*     is NOT linear. See the FITS-WCS papers for further details.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/
astMAKE_CLEAR(FitsChan,CarLin,carlin,-1)
astMAKE_GET(FitsChan,CarLin,int,1,(this->carlin == -1 ? 0 : this->carlin))
astMAKE_SET(FitsChan,CarLin,int,carlin,( value ? 1 : 0 ))
astMAKE_TEST(FitsChan,CarLin,( this->carlin != -1 ))

/* SipReplace */
/* ========== */

/*
*att++
*  Name:
*     SipReplace

*  Purpose:
*     Replace SIP inverse transformation?

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer (boolean).

*  Description:
*     This attribute is a boolean value which specifies how SIP keywords
*     should be handled when reading a FITS-WCS encoded header using the
c     astRead
f     AST_READ
*     function. See
*     http://irsa.ipac.caltech.edu/data/SPITZER/docs/files/spitzer/shupeADASS.pdf
*     for more information about SIP headers. If SipReplace is non-zero,
*     then any SIP keywords describing the inverse transformation (i.e. from
*     WCS to pixel coordinates) are ignored. Instead a new inverse
*     transformation is found by performing a fit to the forward
*     transformation. The SipReplace attribute can be set to zero to prevent
*     this happening. If SipReplace is zero, any SIP keywords describing the
*     inverse transformation are used as supplied, rather than being
*     replaced using a new fit. The default value is 1.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/
astMAKE_CLEAR(FitsChan,SipReplace,sipreplace,-1)
astMAKE_GET(FitsChan,SipReplace,int,1,(this->sipreplace == -1 ? 1 : this->sipreplace))
astMAKE_SET(FitsChan,SipReplace,int,sipreplace,( value ? 1 : 0 ))
astMAKE_TEST(FitsChan,SipReplace,( this->sipreplace != -1 ))

/* PolyTan */
/* ======= */

/*
*att++
*  Name:
*     PolyTan

*  Purpose:
*     Use PVi_m keywords to define distorted TAN projection?

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer.

*  Description:
*     This attribute is a boolean value which specifies how FITS "TAN"
*     projections should be treated when reading a FrameSet from a foreign
*     encoded FITS header. If zero, the projection is assumed to conform
*     to the published FITS-WCS standard. If positive, the convention
*     for a distorted TAN projection included in an early draft version
*     of FITS-WCS paper II are assumed. In this convention the
*     coefficients of a polynomial distortion to be applied to
*     intermediate world coordinates are specified by the PVi_m keywords.
*     This convention was removed from the paper before publication and so
*     does not form part of the standard. Indeed, it is incompatible with
*     the published standard because it re-defines the meaning of the
*     first five PVi_m keywords on the longitude axis, which are reserved
*     by the published standard for other purposes. However, this
*     scheme has now been added to the registry of FITS conventions
*     (http://fits.gsfc.nasa.gov/registry/tpvwcs.html) and headers
*     that use this convention are created by the SCAMP utility
*     (http://www.astromatic.net/software/scamp) and the Dark Energy
*     Camera at NOAO.
*
*     The default value for the PolyTan attribute is -1. A negative
*     values causes the used convention to depend on the contents
*     of the FitsChan. If the FitsChan contains any PVi_m keywords for
*     the latitude axis, or if it contains PVi_m keywords for the
*     longitude axis with "m" greater than 4, then the distorted TAN
*     convention is used. Otherwise, the standard convention is used.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/
astMAKE_CLEAR(FitsChan,PolyTan,polytan,-INT_MAX)
astMAKE_SET(FitsChan,PolyTan,int,polytan,value)
astMAKE_TEST(FitsChan,PolyTan,( this->polytan != -INT_MAX ))
astMAKE_GET(FitsChan,PolyTan,int,-1,(this->polytan == -INT_MAX ? -1 : this->polytan))

/* SipOK */
/* ===== */

/*
*att++
*  Name:
*     SipOK

*  Purpose:
*     Use Spitzer Space Telescope keywords to define distortion?

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer (boolean).

*  Description:
*     This attribute is a boolean value which specifies whether to include
*     support for the "SIP" scheme, which can be used to add distortion to
*     basic FITS-WCS projections. This scheme was first defined by the
*     Spitzer Space Telescope and is described in the following document:
*     http://irsa.ipac.caltech.edu/data/SPITZER/docs/files/spitzer/shupeADASS.pdf
*     The default for SipOK is 1.
*
*     When using
c     astRead
f     AST_READ
*     to read a FITS-WCS encoded header, a suitable PolyMap will always be
*     included in the returned FrameSet if the header contains SIP
*     keywords, regardless of the value of the SipOK attribute. The PolyMap
*     will be immediately before the MatrixMap that corresponds to the FITS-WCS
*     PC or CD matrix.
*
*     When using
c     astWrite
f     AST_WRITE
*     to write a FrameSet to a FITS-WCS encoded header, suitable SIP
*     keywords will be included in the header if the FrameSet contains a
*     PolyMap immediately before the MatrixMap that corresponds to the
*     FITS-WCS PC or CD matrix, but only if the SipOK attribute is non-zero.
*     If the FrameSet contains a PolyMap but SipOK is zero, then an attempt
*     will be made to write out the FrameSet without SIP keywords using a
*     linear approximation to the pixel-to-IWC mapping. If this fails
*     because the Mapping exceeds the linearity requirement specified by
*     attribute FitsTol,
c     astWrite
f     AST_WRITE
*     will return zero, indicating that the FrameSet could not be written
*     out. Note, SIP headers can only be produced for axes that form part
*     of a SkyFrame.
*
*     Note, the SIP distortion scheme is independent of the TPV/TPN
*     distortion schemes (see attribute PolyTan). A FITS-WCS header could
*     in principle, contain keywords for both schemes although this is unlikely.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/
astMAKE_CLEAR(FitsChan,SipOK,sipok,-INT_MAX)
astMAKE_SET(FitsChan,SipOK,int,sipok,value)
astMAKE_TEST(FitsChan,SipOK,( this->sipok != -INT_MAX ))
astMAKE_GET(FitsChan,SipOK,int,1,(this->sipok == -INT_MAX ? 1 : this->sipok))

/* Iwc */
/* === */

/*
*att++
*  Name:
*     Iwc

*  Purpose:
*     Include a Frame representing FITS-WCS intermediate world coordinates?

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer (boolean).

*  Description:
*     This attribute is a boolean value which is used when a FrameSet is
*     read from a FitsChan with a foreign FITS encoding (e.g. FITS-WCS) using
c     astRead.
f     AST_READ.
*     If it has a non-zero value then the returned FrameSet will include
*     Frames representing "intermediate world coordinates" (IWC). These
*     Frames will have Domain name "IWC" for primary axis descriptions, and
*     "IWCa" for secondary axis descriptions, where "a" is replaced by
*     the single alternate axis description character, as used in the
*     FITS-WCS header. The default value for "Iwc" is zero.
*
*     FITS-WCS paper 1 defines IWC as a Cartesian coordinate system with one
*     axis for each WCS axis, and is the coordinate system produced by the
*     rotation matrix (represented by FITS keyword PCi_j, CDi_j, etc).
*     For instance, for a 2-D FITS-WCS header describing projected
*     celestial longitude and latitude, the intermediate world
*     coordinates represent offsets in degrees from the reference point
*     within the plane of projection.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/
astMAKE_CLEAR(FitsChan,Iwc,iwc,-1)
astMAKE_GET(FitsChan,Iwc,int,1,(this->iwc == -1 ? 0 : this->iwc))
astMAKE_SET(FitsChan,Iwc,int,iwc,( value ? 1 : 0 ))
astMAKE_TEST(FitsChan,Iwc,( this->iwc != -1 ))

/*
*att++
*  Name:
*     CDMatrix

*  Purpose:
*     Use CDi_j keywords to represent pixel scaling, rotation, etc?

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer (boolean).

*  Description:
*     This attribute is a boolean value which specifies how the linear
*     transformation from pixel coordinates to intermediate world
*     coordinates should be represented within a FitsChan when using
*     FITS-WCS encoding. This transformation describes the scaling,
*     rotation, shear, etc., of the pixel axes.
*
*     If the attribute has a non-zero value then the transformation is
*     represented by a set of CDi_j keywords representing a square matrix
*     (where "i" is the index of an intermediate world coordinate axis
*     and "j" is the index of a pixel axis). If the attribute has a zero
*     value the transformation is represented by a set of PCi_j keywords
*     (which also represent a square matrix) together with a corresponding
*     set of CDELTi keywords representing the axis scalings. See FITS-WCS
*     paper II "Representation of Celestial Coordinates in FITS" by
*     M. Calabretta & E.W. Greisen, for a complete description of these two
*     schemes.
*
*     The default value of the CDMatrix attribute is determined by the
*     contents of the FitsChan at the time the attribute is accessed. If
*     the FitsChan contains any CDi_j keywords then the default value is
*     non-zero. Otherwise it is zero. Note, reading a FrameSet from a
*     FitsChan will in general consume any CDi_j keywords present in the
*     FitsChan. Thus the default value for CDMatrix following a read will
*     usually be zero, even if the FitsChan originally contained some
*     CDi_j keywords. This behaviour is similar to that of the Encoding
*     attribute, the default value for which is determined by the contents
*     of the FitsChan at the time the attribute is accessed. If you wish
*     to retain the original value of the CDMatrix attribute (that is,
*     the value before reading the FrameSet) then you should enquire the
*     default value before doing the read, and then set that value
*     explicitly.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/
astMAKE_CLEAR(FitsChan,CDMatrix,cdmatrix,-1)
astMAKE_SET(FitsChan,CDMatrix,int,cdmatrix,( value ? 1 : 0 ))
astMAKE_TEST(FitsChan,CDMatrix,( this->cdmatrix != -1 ))

/* Clean */
/* ===== */

/*
*att++
*  Name:
*     Clean

*  Purpose:
*     Remove cards used whilst reading even if an error occurs?

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer (boolean).

*  Description:
*     This attribute indicates whether or not cards should be removed from
*     the FitsChan if an error occurs within
c     astRead.
f     AST_READ.
*     A succesful read on a FitsChan always results in the removal of
*     the cards which were involved in the description of the returned
*     Object. However, in the event of an error during the read (for instance
*     if the cards in the FitsChan have illegal values, or if some required
*     cards are missing) no cards will be removed from the FitsChan if
*     the Clean attribute is zero (the default). If Clean is non-zero then
*     any cards which were used in the aborted attempt to read an object
*     will be removed.
*
*     This provides a means of "cleaning" a FitsChan of WCS related cards
*     which works even in the event of the cards not forming a legal WCS
*     description.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/
astMAKE_CLEAR(FitsChan,Clean,clean,-1)
astMAKE_SET(FitsChan,Clean,int,clean,( value ? 1 : 0 ))
astMAKE_TEST(FitsChan,Clean,( this->clean != -1 ))

/*
*att++
*  Name:
*     FitsAxisOrder

*  Purpose:
*     Frame title.

*  Type:
*     Public attribute.

*  Synopsis:
*     String.

*  Description:
*     This attribute specifies the order for the WCS axes in any new
*     FITS-WCS headers created using the
c     astWrite
f     AST_WRITE
*     method.
*
*     The value of the FitsAxisOrder attribute can be either "<auto>"
*     (the default value), "<copy>" or a space-separated list of axis
*     symbols:
*
*     "<auto>": causes the WCS axis order to be chosen automatically so that
*     the i'th WCS axis in the new FITS header is the WCS axis which is
*     more nearly parallel to the i'th pixel axis.
*
*     "<copy>": causes the WCS axis order to be set so that the i'th WCS
*     axis in the new FITS header is the i'th WCS axis in the current
*     Frame of the FrameSet being written out to the header.
*
*     "Sym1 Sym2...": the space-separated list is seached in turn for
*     the Symbol attribute of each axis in the current Frame of the
*     FrameSet. The order in which these Symbols occur within the
*     space-separated list defines the order of the WCS axes in the
*     new FITS header. An error is reported if Symbol for a current
*     Frame axis is not present in the supplied list. However, no error
*     is reported if the list contains extra words that do not correspond
*     to the Symbol of any current Frame axis.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/
astMAKE_CLEAR(FitsChan,FitsAxisOrder,fitsaxisorder,astFree( this->fitsaxisorder ))
astMAKE_GET(FitsChan,FitsAxisOrder,const char *,NULL,(this->fitsaxisorder ? this->fitsaxisorder : "<auto>" ))
astMAKE_SET(FitsChan,FitsAxisOrder,const char *,fitsaxisorder,astStore( this->fitsaxisorder, value, strlen( value ) + (size_t) 1 ))
astMAKE_TEST(FitsChan,FitsAxisOrder,( this->fitsaxisorder != NULL ))

/* FitsDigits. */
/* =========== */

/*
*att++
*  Name:
*     FitsDigits

*  Purpose:
*     Digits of precision for floating point FITS values.

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer.

*  Description:
*     This attribute gives the number of significant decimal digits to
*     use when formatting floating point values for inclusion in the
*     FITS header cards within a FitsChan.
*
*     By default, a positive value is used which results in no loss of
c     information, assuming that the value's precision is double.
f     information, assuming that the value is double precision.
*     Usually, this causes no problems.
*
*     However, to adhere strictly to the recommendations of the FITS
*     standard, the width of the formatted value (including sign,
*     decimal point and exponent) ought not to be more than 20
*     characters. If you are concerned about this, you should set
*     FitsDigits to a negative value, such as -15. In this case, the
*     absolute value (+15) indicates the maximum number of significant
*     digits to use, but the actual number used may be fewer than this
*     to ensure that the FITS recommendations are satisfied. When
*     using this approach, the resulting number of significant digits
*     may depend on the value being formatted and on the presence of
*     any sign, decimal point or exponent.
*
*     The value of this attribute is effective when FITS header cards
*     are output, either using
c     astFindFits or by the action of the FitsChan's sink function
f     AST_FINDFITS or by the action of the FitsChan's sink routine
*     when it is finally deleted.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/
astMAKE_CLEAR(FitsChan,FitsDigits,fitsdigits,AST__DBL_DIG)
astMAKE_GET(FitsChan,FitsDigits,int,AST__DBL_DIG,this->fitsdigits)
astMAKE_SET(FitsChan,FitsDigits,int,fitsdigits,value)
astMAKE_TEST(FitsChan,FitsDigits,( this->fitsdigits != AST__DBL_DIG ))

/* FitsRounding. */
/* =========== */

/*
*att++
*  Name:
*     FitsRounding

*  Purpose:
*     Controls rounding of floating-point FITS values.

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer.

*  Description:
*     This attribute controls how floating point values are rounded when
*     formatted for inclusion in the FITS header cards within a FitsChan.
*
*     The value is first formatted using the field width specified by
*     attribute FitsDigits. If this formatted value contains a sequence of
*     4 or more adjacent "9"s or 4 or more adjacent "0"s, the formatted
*     value is truncated at the start of the sequence (rounding the
*     final remaining digit up by one if the sequence contains "9"s).
*     However this truncation only occurs if the sequence extends beyond
*     the digit specified by attribute FitsRounding. For instance, if
*     FitsRounding is set to 10, then the rounding will only occur for
*     sequences of 4 or more "9"s or "0"s that extend beyond the tenth
*     significant figure in the formatted value.
*
*     The default value is 10. When setting a new value, negative values
*     are converted to zero.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/
astMAKE_CLEAR(FitsChan,FitsRounding,fitsrounding,-1)
astMAKE_GET(FitsChan,FitsRounding,int,10,((this->fitsrounding!=-1)?this->fitsrounding:10))
astMAKE_SET(FitsChan,FitsRounding,int,fitsrounding,astMAX(0,value))
astMAKE_TEST(FitsChan,FitsRounding,( this->fitsrounding != -1 ))

/* AltAxes. */
/* ======== */

/*
*att++
*  Name:
*     AltAxes

*  Purpose:
*     Controls generation of FITS-WCS alternate axis descriptions

*  Type:
*     Public attribute.

*  Synopsis:
*     String.

*  Description:
*     This attribute controls the generation of FITS-WCS alternate axis
*     keywords by the
c     astWrite
f     AST_WRITE
*     method. It determines which of the Frames in the FrameSet supplied
c     to astWrite
f     to AST_WRITE
*     are used to create a set of alternate axis descriptions. It may be
*     set to one of the following values (case insensitive):
*
*     - "ALL": A set of alternate axes will be created for each Frame in
*     the supplied FrameSet, excluding the Base and Current Frames. This
*     is the default.
*
*     - "NONE": No alternate axes will be created for any of the Frames in
*     the supplied FrameSet.
*
*     - "IDENT": Alternate axes will be created for a Frame only if
*     its Ident attribute is set to a single upper case alphabetical
*     character (A-Z).

*  Notes:
*     - This attribute is used only if the Encoding attribute of the
*     FitsChan is set to (or defaults to) "FITS-WCS" or "FITS-PC".
*     - The Current Frame in the FrameSet is always used to create the
*     primary axis descriptions in the output FITS header.
*     - The Base Frame in the FrameSet is never used to create a set of
*     alternate axis descriptions.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/
astMAKE_CLEAR(FitsChan,AltAxes,altaxes,INT_MAX)
astMAKE_GET(FitsChan,AltAxes,int,INT_MAX,this->altaxes)
astMAKE_SET(FitsChan,AltAxes,int,altaxes,value)
astMAKE_TEST(FitsChan,AltAxes,( this->altaxes != INT_MAX ))

/* CardComm */
/* ======== */

/*
*att++
*  Name:
*     CardComm

*  Purpose:
*     The comment for the current card in a FitsChan.

*  Type:
*     Public attribute.

*  Synopsis:
*     String, read-only.

*  Description:
*     This attribute gives the comment for the current card of the
*     FitsChan. A zero-length string is returned if the card has no comment.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/

/* CardName */
/* ======== */

/*
*att++
*  Name:
*     CardName

*  Purpose:
*     The keyword name of the current card in a FitsChan.

*  Type:
*     Public attribute.

*  Synopsis:
*     String, read-only.

*  Description:
*     This attribute gives the name of the keyword for the
*     current card of the FitsChan.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/

/* CardType */
/* ======== */

/*
*att++
*  Name:
*     CardType

*  Purpose:
*     The data type of the current card in a FitsChan.

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer, read-only.

*  Description:
*     This attribute gives the data type of the keyword value for the
*     current card of the FitsChan. It will be one of the following
*     integer constants: AST__NOTYPE, AST__COMMENT, AST__INT, AST__FLOAT,
*     AST__STRING, AST__COMPLEXF, AST__COMPLEXI, AST__LOGICAL,
*     AST__CONTINUE, AST__UNDEF.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/

/* Ncard */
/* ===== */

/*
*att++
*  Name:
*     Ncard

*  Purpose:
*     Number of FITS header cards in a FitsChan.

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer, read-only.

*  Description:
*     This attribute gives the total number of FITS header cards
*     stored in a FitsChan. It is updated as cards are added or
*     deleted.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/

/* Nkey */
/* ==== */

/*
*att++
*  Name:
*     Nkey

*  Purpose:
*     Number of unique FITS keywords in a FitsChan.

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer, read-only.

*  Description:
*     This attribute gives the total number of unique FITS keywords
*     stored in a FitsChan. It is updated as cards are added or
*     deleted. If no keyword occurrs more than once in the FitsChan, the
*     Ncard and Nkey attributes will be equal. If any keyword occurrs
*     more than once, the Nkey attribute value will be smaller than
*     the Ncard attribute value.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/

/* Warnings. */
/* ======== */

/*
*att++
*  Name:
*     Warnings

*  Purpose:
*     Controls the issuing of warnings about various conditions.

*  Type:
*     Public attribute.

*  Synopsis:
*     String

*  Description:
*     This attribute controls the issuing of warnings about selected
*     conditions when an Object or keyword is read from or written to a
*     FitsChan. The value supplied for the Warnings attribute should
*     consist of a space separated list of condition names (see the
*     AllWarnings attribute for a list of the currently defined names).
*     Each name indicates a condition which should be reported. The default
*     value for Warnings is the string "BadKeyName BadKeyValue Tnx Zpx
*     BadCel BadMat BadPV BadCTYPE".
*
*     The text of any warning will be stored within the FitsChan in the
*     form of one or more new header cards with keyword ASTWARN. If
*     required, applications can check the FitsChan for ASTWARN cards
c     (using astFindFits) after the call to astRead or astWrite has been
f     (using AST_FINDFITS) after the call to AST_READ or AST_WRITE has been
*     performed, and report the text of any such cards to the user. ASTWARN
*     cards will be propagated to any output header unless they are
c     deleted from the FitsChan using astDelFits.
f     deleted from the FitsChan using astDelFits.

*  Notes:
*     This attribute only controls the warnings that are to be stored as
*     a set of header cards in the FitsChan as described above. It has no
*     effect on the storage of warnings in the parent Channel structure.
*     All warnings are stored in the parent Channel structure, from where
*     they can be retrieved using the
c     astWarnings
f     AST_WARNINGS
*     function.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/

/* Clear the Warnings value by freeing the allocated memory and assigning
   a NULL pointer. */
astMAKE_CLEAR(FitsChan,Warnings,warnings,astFree( this->warnings ))

/* If the Warnings value is not set, supply a default in the form of a
   pointer to the constant string "BadKeyName BadKeyValue Tnx Zpx BadCel BadMat BadCTYPE". */
astMAKE_GET(FitsChan,Warnings,const char *,NULL,( this->warnings ? this->warnings :
                                                            "BadKeyName BadKeyValue Tnx Zpx BadPV BadCel BadMat BadCTYPE" ))

/* Set a Warnings value by freeing any previously allocated memory, allocating
   new memory, storing the string and saving the pointer to the copy.
   First check that the list does not contain any unknown conditions. If
   it does, an error is reported by GoodWarns and the current attribute value
   is retained. */
astMAKE_SET(FitsChan,Warnings,const char *,warnings,( GoodWarns( value, status ) ?
                                astStore( this->warnings, value, strlen( value ) + (size_t) 1 ) :
                                this->warnings))

/* The Warnings value is set if the pointer to it is not NULL. */
astMAKE_TEST(FitsChan,Warnings,( this->warnings != NULL ))

/* AllWarnings. */
/* ============ */

/*
*att++
*  Name:
*     AllWarnings

*  Purpose:
*     A list of all currently available condition names.

*  Type:
*     Public attribute.

*  Synopsis:
*     String, read-only

*  Description:
*     This read-only attribute is a space separated list of all the conditions
*     names recognized by the Warnings attribute. The names are listed
*     below.

*  Conditions:
*     The following conditions are currently recognised (all are
*     case-insensitive):
*
*     - "BadCel": This condition arises when reading a FrameSet from a
*     non-Native encoded FitsChan if an unknown celestial co-ordinate
*     system is specified by the CTYPE keywords.
*
*     - "BadCTYPE": This condition arises when reading a FrameSet from a
*     non-Native encoded FitsChan if an illegal algorithm code is specified
*     by a CTYPE keyword, and the illegal code can be converted to an
*     equivalent legal code.
*
*     - "BadKeyName": This condition arises if a FITS keyword name is
*     encountered that contains an illegal character (i.e. one not allowed
*     by the FITS standard).
*
*     - "BadKeyValue": This condition arises if the value of a FITS keyword
*     cannot be determined from the content of the header card.
*
*     - "BadLat": This condition arises when reading a FrameSet from a
*     non-Native encoded FitsChan if the latitude of the reference point
*     has an absolute value greater than 90 degrees. The actual absolute
*     value used is set to exactly 90 degrees in these cases.
*
*     - "BadMat": This condition arises if the matrix describing the
*     transformation from pixel offsets to intermediate world coordinates
*     cannot be inverted. This matrix describes the scaling, rotation, shear,
*     etc., applied to the pixel axes, and is specified by keywords such as
*     PCi_j, CDi_j, CROTA, etc. For example, the matrix will not be invertable
*     if any rows or columns consist entirely of zeros. The FITS-WCS Paper I
*     "Representation of World Coordinates in FITS" by Greisen & Calabretta
*     requires that this matrix be invertable. Many operations (such as
*     grid plotting) will not be possible if the matrix cannot be inverted.
*
*     - "BadPV": This condition arises when reading a FrameSet from a
*     non-Native encoded FitsChan. It is issued if a PVi_m header is found
*     that refers to a projection parameter that is not used by the
*     projection type specified by CTYPE, or the PV values are otherwise
*     inappropriate for the projection type.
*
*     - "BadVal": This condition arises when reading a FrameSet from a
*     non-Native encoded FitsChan if it is not possible to convert the
*     value of a FITS keywords to the expected type. For instance, this
*     can occur if the FITS header contains a string value for a keyword
*     which should have a floating point value, or if the keyword has no
*     value at all (i.e. is a comment card).
*
*     - "Distortion": This condition arises when reading a FrameSet from a
*     non-Native encoded FitsChan if any of the CTYPE keywords specify an
*     unsupported distortion code using the "4-3-3" format specified in
*     FITS-WCS paper IV. Such distortion codes are ignored.
*
*     - "NoCTYPE": This condition arises if a default CTYPE value is used
c     within astRead, due to no value being present in the supplied FitsChan.
f     within AST_READ, due to no value being present in the supplied FitsChan.
*     This condition is only tested for when using non-Native encodings.
*
*     - "NoEquinox": This condition arises if a default equinox value is used
c     within astRead, due to no value being present in the supplied FitsChan.
f     within AST_READ, due to no value being present in the supplied FitsChan.
*     This condition is only tested for when using non-Native encodings.
*
*     - "NoRadesys": This condition arises if a default reference frame is
c     used for an equatorial co-ordinate system within astRead, due to no
f     used for an equatorial co-ordinate system within AST_READ, due to no
*     value being present in the supplied FitsChan. This condition is only
*     tested for when using non-Native encodings.
*
*     - "NoLonpole": This condition arises if a default value is used for
c     the LONPOLE keyword within astRead, due to no value being present
f     the LONPOLE keyword within AST_READ, due to no value being present
*     in the supplied FitsChan. This condition is only tested for when
*     using non-Native encodings.
*
*     - "NoLatpole": This condition arises if a default value is used for
c     the LATPOLE keyword within astRead, due to no value being present
f     the LATPOLE keyword within AST_READ, due to no value being present
*     in the supplied FitsChan. This condition is only tested for when
*     using non-Native encodings.
*
*     - "NoMjd-obs": This condition arises if a default value is used for
c     the date of observation within astRead, due to no value being present
f     the date of observation within AST_READ, due to no value being present
*     in the supplied FitsChan. This condition is only tested for when using
*     non-Native encodings.
*
*     - "Tnx": This condition arises if a FrameSet is read from a FITS
*     header containing an IRAF "TNX" projection which includes terms
*     not supproted by AST. Such terms are ignored and so the resulting
*     FrameSet may be inaccurate.
*
*     - "Zpx": This condition arises if a FrameSet is read from a FITS
*     header containing an IRAF "ZPX" projection which includes "lngcor"
*     or "latcor" correction terms. These terms are not supported by AST
*     and are ignored. The resulting FrameSet may therefore be inaccurate.

*  Applicability:
*     FitsChan
*        All FitsChans have this attribute.
*att--
*/

/* Copy constructor. */
/* ----------------- */

static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
/*
*  Name:
*     Copy

*  Purpose:
*     Copy constructor for FitsChan objects.

*  Type:
*     Private function.

*  Synopsis:
*     void Copy( const AstObject *objin, AstObject *objout, int *status )

*  Description:
*     This function implements the copy constructor for FitsChan objects.

*  Parameters:
*     objin
*        Pointer to the FitsChan to be copied.
*     objout
*        Pointer to the FitsChan being constructed.
*     status
*        Pointer to the inherited status variable.

*  Notes:
*     - The source and sink functions are not propagated (i.e. the
*     pointers are set NULL in the output FitsChan).
*     - This constructor makes a deep copy, including a copy of the
*     keyword values.
*/

/* Local Variables: */
   astDECLARE_GLOBALS        /* Declare the thread specific global data */
   const char *class;        /* Pointer to object class */
   AstFitsChan *in;          /* Pointer to input FitsChan */
   AstFitsChan *out;         /* Pointer to output FitsChan */
   int *flags;
   int icard;
   int old_ignore_used;      /* Original value of external variable ignore_used */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(objin);

/* Obtain pointers to the input and output FitsChans. */
   in = (AstFitsChan *) objin;
   out = (AstFitsChan *) objout;

/* Nullify all pointers in the output FitsChan so that the input
   data will not be deleted in the event of an error occurring. */
   out->card = NULL;
   out->head = NULL;
   out->keyseq = NULL;
   out->keywords = NULL;
   out->source = NULL;
   out->saved_source = NULL;
   out->source_wrap = NULL;
   out->sink = NULL;
   out->sink_wrap = NULL;
   out->warnings = NULL;
   out->tabsource = NULL;
   out->tabsource_wrap = NULL;

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

/* Ensure all cards are copied, including those already read by astRead. */
   old_ignore_used = ignore_used;
   ignore_used = 0;

/* Save the current card index in the input FitsChan. */
   icard = astGetCard( in );

/* Rewind the input FitsChan. */
   astClearCard( in );

/* Copy all the FitsCard structures from input to output. */
   while( !astFitsEof( in ) && astOK ){

/* Get a pointer to the flags mask for this card. */
      flags = CardFlags( in, status );

/* Store a new card in the output, holding the same information as the
   input card. */
      NewCard( out, CardName( in, status ), CardType( in, status ), CardData( in, NULL, status ),
               CardComm( in, status ), (flags?(*flags):0), status );

/* Move on to the next input card. */
      MoveCard( in, 1, "astCopy", class, status );
   }

/* Set the current card in both input and output to the current input
   card on entry. */
   astSetCard( in, icard );
   astSetCard( out, icard );

/* Copy the list of keyword sequence numbers used. */
   if( in->keyseq ) out->keyseq = astCopy( in->keyseq );

/* Copy the Warnings attribute value */
   if( in->warnings ) out->warnings = astStore( NULL, in->warnings,
                                                strlen( in->warnings ) + 1 );

/* Copy any tables currently in the FitsChan structure. */
   if( in->tables ) out->tables = astCopy( in->tables );

/* Reinstate the original setting of the external ignore_used variable. */
   ignore_used = old_ignore_used;

/* If an error occurred, delete the contents of the output Object. */
   if( !astOK ) Delete( objout, status );
}

/* Destructor. */
/* ----------- */

static void Delete( AstObject *obj, int *status ) {
/*
*  Name:
*     Delete

*  Purpose:
*     Destructor for FitsChan objects.

*  Type:
*     Private function.

*  Synopsis:
*     void Delete( AstObject *obj, int *status )

*  Description:
*     This function implements the destructor for FitsChan objects.

*  Parameters:
*     obj
*        Pointer to the FitsChan to be deleted.
*     status
*        Pointer to the inherited status variable.

*  Notes:
*     This function attempts to execute even if the global error status is
*     set.
*/

/* Local Variables: */
   AstFitsChan *this;            /* Pointer to FitsChan */

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

/* Write out the contents of the FitsChan using the sink function
   provided when it was created. */
   WriteToSink( this, status );

/* Remove all cards from the FitsChan. */
   EmptyFits( this, status );
}

/* Dump function. */
/* -------------- */

static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
/*
*  Name:
*     Dump

*  Purpose:
*     Dump function for FitsChan objects.

*  Type:
*     Private function.

*  Synopsis:
*     void Dump( AstObject *this, AstChannel *channel, int *status )

*  Description:
*     This function implements the Dump function which writes out data
*     for the FitsChan class to an output Channel.

*  Parameters:
*     this
*        Pointer to the FitsChan whose data are being written.
*     channel
*        Pointer to the Channel to which the data are being written.
*     status
*        Pointer to the inherited status variable.
*/
#define KEY_LEN 50               /* Maximum length of a keyword */

/* Local Variables: */
   AstFitsChan *this;            /* Pointer to the FitsChan structure */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   char buff[ KEY_LEN + 1 ];     /* Buffer for keyword string */
   const char *class;            /* Object class */
   const char *sval;             /* Pointer to string value */
   double dval;                  /* Double value */
   int cardtype;                 /* Keyword data type */
   int flags;                    /* Keyword flags */
   int icard;                    /* Index of current card */
   int ival;                     /* Integer value */
   int ncard;                    /* No. of cards dumped so far */
   int old_ignore_used;          /* Original value of external variable ignore_used */
   int set;                      /* Attribute value set? */
   void *data;                   /* Pointer to keyword data value */

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

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this_object);

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

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

/* Save the index of ht ecurrent card. */
   icard = astGetCard( this );

/* Write out values representing the instance variables for the
   FitsChan class.  Accompany these with appropriate comment strings,
   possibly depending on the values being written.*/

/* Card. */
/* ----- */
   astWriteInt( channel, "Card", 1, 1, icard, "Index of current card" );

/* Encoding. */
/* --------- */
   set = TestEncoding( this, status );
   ival = set ? GetEncoding( this, status ) : astGetEncoding( this );
   if( ival > UNKNOWN_ENCODING && ival <= MAX_ENCODING ) {
      astWriteString( channel, "Encod", set, 1, xencod[ival], "Encoding system" );
   } else {
      astWriteString( channel, "Encod", set, 1, UNKNOWN_STRING, "Encoding system" );
   }

/* FitsAxisOrder. */
/* -------------- */
   set = TestFitsAxisOrder( this, status );
   sval = set ? GetFitsAxisOrder( this, status ) : astGetFitsAxisOrder( this );
   astWriteString( channel, "FAxOrd", set, 1, sval,
                      "Order of WCS axes in new FITS headers" );

/* FitsDigits. */
/* ----------- */
   set = TestFitsDigits( this, status );
   ival = set ? GetFitsDigits( this, status ) : astGetFitsDigits( this );
   astWriteInt( channel, "FitsDg", set, 1, ival, "No. of digits for floating point values" );

/* FitsRounding. */
/* ------------- */
   set = TestFitsRounding( this, status );
   ival = set ? GetFitsRounding( this, status ) : astGetFitsRounding( this );
   astWriteInt( channel, "FitsRn", set, 1, ival, "No. of digits guarded from rounding" );

/* AltAXes. */
/* -------- */
   set = TestAltAxes( this, status );
   ival = set ? GetAltAxes( this, status ) : astGetAltAxes( this );
   astWriteString( channel, "AltAx", set, 1, xaltax[ival], "Creation of alternate axes" );

/* DefB1950 */
/* -------- */
   set = TestDefB1950( this, status );
   ival = set ? GetDefB1950( this, status ) : astGetDefB1950( this );
   astWriteInt( channel, "DfB1950", set, 1, ival, (ival ? "Default to FK4 B1950": "Default to ICRS") );

/* TabOK */
/* ----- */
   set = TestTabOK( this, status );
   ival = set ? GetTabOK( this, status ) : astGetTabOK( this );
   astWriteInt( channel, "TabOK", set, 1, ival, ( ival > 0 ? "EXTVER value for -TAB headers": "Do not support -TAB CTYPE codes") );

/* ForceTab. */
/* --------- */
   set = TestForceTab( this, status );
   ival = set ? GetForceTab( this, status ) : astGetForceTab( this );
   astWriteInt( channel, "FrcTab", set, 1, ival, ( ival != 0 ? "Force use of -TAB": "Only use -TAB if necessary") );

/* CDMatrix */
/* -------- */
   set = TestCDMatrix( this, status );
   ival = set ? GetCDMatrix( this, status ) : astGetCDMatrix( this );
   astWriteInt( channel, "CdMat", set, 1, ival, (ival ? "Use CD Matrix":"Use PC matrix") );

/* CarLin */
/* ------ */
   set = TestCarLin( this, status );
   ival = set ? GetCarLin( this, status ) : astGetCarLin( this );
   astWriteInt( channel, "CarLin", set, 1, ival, (ival ? "Use simple linear CAR projections": "Use full FITS-WCS CAR projections") );

/* SipReplace */
/* ------ */
   set = TestSipReplace( this, status );
   ival = set ? GetSipReplace( this, status ) : astGetSipReplace( this );
   astWriteInt( channel, "SipReplace", set, 1, ival, "Replace SIP inverse coefficients?" );

/* FitsTol */
/* ------- */
   set = TestFitsTol( this, status );
   dval = set ? GetFitsTol( this, status ) : astGetFitsTol( this );
   astWriteDouble( channel, "FitsTol", set, 1, dval, "[pixel] Max allowed "
                   "departure from linearity");

/* PolyTan */
/* ------- */
   set = TestPolyTan( this, status );
   ival = set ? GetPolyTan( this, status ) : astGetPolyTan( this );
   astWriteInt( channel, "PolyTan", set, 0, ival, (ival ? "Use distorted TAN convention": "Use standard TAN convention") );

/* SipOK */
/* ----- */
   set = TestSipOK( this, status );
   ival = set ? GetSipOK( this, status ) : astGetSipOK( this );
   astWriteInt( channel, "SipOK", set, 0, ival, (ival ? "Use SIP distortion convention": "Ignore SIP keywords") );

/* Iwc */
/* --- */
   set = TestIwc( this, status );
   ival = set ? GetIwc( this, status ) : astGetIwc( this );
   astWriteInt( channel, "Iwc", set, 1, ival, (ival ? "Include an IWC Frame": "Do not include an IWC Frame") );

/* Clean */
/* ----- */
   set = TestClean( this, status );
   ival = set ? GetClean( this, status ) : astGetClean( this );
   astWriteInt( channel, "Clean", set, 0, ival, "Always remove used cards?" );

/* Warnings. */
/* --------- */
   set = TestWarnings( this, status );
   sval = set ? GetWarnings( this, status ) : astGetWarnings( this );
   astWriteString( channel, "Warn", set, 1, sval, "Warnings to be reported" );

/* Now do instance variables which are not attributes. */
/* =================================================== */

/* Ensure all cards are copied, including those already read by astRead. */
   old_ignore_used = ignore_used;
   ignore_used = 0;

/* Rewind the FitsChan. */
   astClearCard( this );

/* Dump each card. */
   ncard = 1;
   while( !astFitsEof( this ) && astOK ){

/* Write out the keyword name. */
      if( CardName( this, status ) ){
         (void) sprintf( buff, "Nm%d", ncard );
         astWriteString( channel, buff, 1, 1, CardName( this, status ),
                         "FITS keyword name" );
      }

/* Write out the keyword type. */
      cardtype = CardType( this, status );
      (void) sprintf( buff, "Ty%d", ncard );
      astWriteString( channel, buff, 1, 1, type_names[ cardtype ],
                      "FITS keyword data type" );

/* Write out the flag values if any are non-zero. */
      flags = *CardFlags( this, status );
      if( flags ){
         (void) sprintf( buff, "Fl%d", ncard );
         astWriteInt( channel, buff, 1, 1, flags, "FITS keyword flags" );
      }

/* Write out the data value, if defined, using the appropriate data type. */
      data = CardData( this, NULL, status );
      if( data && cardtype != AST__UNDEF ){
         if( cardtype == AST__FLOAT ){
            (void) sprintf( buff, "Dt%d", ncard );
            astWriteDouble( channel, buff, 1, 1, *( (double *) data ),
                            "FITS keyword value" );
         } else if( cardtype == AST__STRING || cardtype == AST__CONTINUE ){
            (void) sprintf( buff, "Dt%d", ncard );
            astWriteString( channel, buff, 1, 1, (char *) data,
                            "FITS keyword value" );
         } else if( cardtype == AST__INT ){
            (void) sprintf( buff, "Dt%d", ncard );
            astWriteInt( channel, buff, 1, 1, *( (int *) data ),
                         "FITS keyword value" );
         } else if( cardtype == AST__LOGICAL ){
            (void) sprintf( buff, "Dt%d", ncard );
            astWriteInt( channel, buff, 1, 1, *( (int *) data ),
                         "FITS keyword value" );
         } else if( cardtype == AST__COMPLEXF ){
            (void) sprintf( buff, "Dr%d", ncard );
            astWriteDouble( channel, buff, 1, 1, *( (double *) data ),
                            "FITS keyword real value" );
            (void) sprintf( buff, "Di%d", ncard );
            astWriteDouble( channel, buff, 1, 1, *( ( (double *) data ) + 1 ),
                            "FITS keyword imaginary value" );
         } else if( cardtype == AST__COMPLEXI ){
            (void) sprintf( buff, "Dr%d", ncard );
            astWriteInt( channel, buff, 1, 1, *( (int *) data ),
                         "FITS keyword real value" );
            (void) sprintf( buff, "Di%d", ncard );
            astWriteInt( channel, buff, 1, 1, *( ( (int *) data ) + 1 ),
                         "FITS keyword imaginary value" );
         }
      }

/* Write out the keyword comment. */
      if( CardComm( this, status ) ){
         (void) sprintf( buff, "Cm%d", ncard );
         astWriteString( channel, buff, 1, 1, CardComm( this, status ),
                         "FITS keyword comment" );
      }

/* Move on to the next card. */
      ncard++;
      MoveCard( this, 1, "astDump", class, status );
   }

/* Dump any FitTables. */
   if( this->tables ) {
      astWriteObject( channel, "Tables", 1, 1, this->tables,
                      "A KeyMap holding associated binary tables" );
   }

/* Reinstate the original setting of the external ignore_used variable. */
   ignore_used = old_ignore_used;

/* Reinstate the original current card. */
   astSetCard( this, icard );
#undef KEY_LEN
}

/* Standard class functions. */
/* ========================= */

/* Implement the astIsAFitsChan and astCheckFitsChan functions using the macros
   defined for this purpose in the "object.h" header file. */
astMAKE_ISA(FitsChan,Channel)
astMAKE_CHECK(FitsChan)
AstFitsChan *astFitsChan_( const char *(* source)( void ),
                           void (* sink)( const char * ),
                           const char *options, int *status, ...) {

/*
*++
*  Name:
c     astFitsChan
f     AST_FITSCHAN

*  Purpose:
*     Create a FitsChan.

*  Type:
*     Public function.

*  Synopsis:
c     #include "fitschan.h"
c     AstFitsChan *astFitsChan( const char *(* source)( void ),
c                               void (* sink)( const char * ),
c                               const char *options, ... )
f     RESULT = AST_FITSCHAN( SOURCE, SINK, OPTIONS, STATUS )

*  Class Membership:
*     FitsChan constructor.

*  Description:
*     This function creates a new FitsChan and optionally initialises
*     its attributes.
*
*     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, astSetFits<X>) and
c     existing ones may be deleted (astDelFits) or changed (astSetFits<X>).
f     based on keyword may be performed (using AST_FINDFITS), new
f     cards may be inserted (AST_PUTFITS, AST_PUTCARDS, AST_SETFITS<X>) and
f     existing ones may be deleted (AST_DELFITS) or changed (AST_SETFITS<X>).
*
*     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 accessed for the first time. 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, astWrite
f     data into it (e.g. using AST_PUTFITS, AST_PUTCARDS, AST_WRITE
*     or by using the SourceFile attribute to specifying a text file from
*     which headers should be read). When the FitsChan is deleted, any
*     remaining header cards in the FitsChan can be saved in either of
*     two ways: 1) by specifying a value for the SinkFile attribute (the
*     name of a text file to which header cards should be written), or 2)
*     by providing a sink function (used to to deliver header cards to an
*     external data store). If you do not provide a sink function or a
*     value for SinkFile, 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 (this deletion can be prevented for
*     specific cards by calling the
c     astRetainFits function).
f     AST_RETAINFITS routine).
*
*     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.

*  Parameters:
c     source
f     SOURCE = FUNCTION (Given)
c        Pointer to a source function which takes no arguments and
c        returns a pointer to a null-terminated string. This function
c        will be used by the FitsChan to obtain input FITS header
c        cards. On each invocation, it should read the next input card
c        from some external source (such as a FITS file), and return a
c        pointer to the (null-terminated) contents of the card. It
c        should return a NULL pointer when there are no more cards to
c        be read.
c
c        If "source" is NULL, the FitsChan will remain empty until
c        cards are explicitly stored in it (e.g. using astPutCards,
c        astPutFits or via the SourceFile attribute).
f        A source routine, which is a function taking two arguments: a
f        character argument of length 80 to contain a FITS card, and an
f        integer error status argument. It should return an integer value.
f        This function will be used by the FitsChan to obtain input
f        FITS header cards. On each invocation, it should read the
f        next input card from some external source (such as a FITS
f        file), and return the contents of the card via its character
f        argument. It should return a function result of one unless
f        there are no more cards to be read, in which case it should
f        return zero. If an error occurs, it should set its error
f        status argument to an error value before returning.
f
f        If the null routine AST_NULL is supplied as the SOURCE value,
f        the FitsChan will remain empty until cards are explicitly
f        stored in it (e.g. using AST_PUTCARDS, AST_PUTFITS or via the
f        SourceFile attribute).
c     sink
f     SINK = SUBROUTINE (Given)
c        Pointer to a sink function that takes a pointer to a
c        null-terminated string as an argument and returns void.  If
c        no value has been set for the SinkFile attribute, this
c        function will be used by the FitsChan to deliver any FITS
c        header cards it contains when it is finally deleted. On
c        each invocation, it should deliver the contents of the character
c        string passed to it as a FITS header card to some external
c        data store (such as a FITS file).
f        A sink routine, which is a subroutine which takes two
f        arguments: a character argument of length 80 to contain a
f        FITS card, and an integer error status argument. If no
f        value has been set for the SinkFile attribute, this routine
f        will be used by the FitsChan to deliver any FITS header cards
f        it contains when it is finally deleted. On each invocation,
f        it should deliver the contents of the character string passed
f        to it as a FITS header card to some external data store (such
f        as a FITS file).  If an error occurs, it should set its error
f        status argument to an error value before returning.
*
c        If "sink" is NULL,
f        If the null routine AST_NULL is supplied as the SINK value,
*        and no value has been set for the SinkFile attribute, the
*        contents of the FitsChan will be lost when it is deleted.
c     options
f     OPTIONS = CHARACTER * ( * ) (Given)
c        Pointer to a null-terminated string containing an optional
c        comma-separated list of attribute assignments to be used for
c        initialising the new FitsChan. The syntax used is identical to
c        that for the astSet function and may include "printf" format
c        specifiers identified by "%" symbols in the normal way.
f        A character string containing an optional comma-separated
f        list of attribute assignments to be used for initialising the
f        new FitsChan. The syntax used is identical to that for the
f        AST_SET routine.
c     ...
c        If the "options" string contains "%" format specifiers, then
c        an optional list of additional arguments may follow it in
c        order to supply values to be substituted for these
c        specifiers. The rules for supplying these are identical to
c        those for the astSet function (and for the C "printf"
c        function).
*
*        Note, the FITSCHAN_OPTIONS environment variable may be used
*        to specify default options for all newly created FitsChans.
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Returned Value:
c     astFitsChan()
f     AST_FITSCHAN = INTEGER
*        A pointer to the new FitsChan.

*  Notes:
f     - The names of the routines supplied for the SOURCE and SINK
f     arguments should appear in EXTERNAL statements in the Fortran
f     routine which invokes AST_FITSCHAN. However, this is not generally
f     necessary for the null routine AST_NULL (so long as the AST_PAR
f     include file has been used).
c     - No FITS "END" card will be written via the sink function. You
f     - No FITS "END" card will be written via the sink routine. You
*     should add this card yourself after the FitsChan has been
*     deleted.
*     - A null Object pointer (AST__NULL) will be returned if this
*     function is invoked with the AST error status set, or if it
*     should fail for any reason.
f     - Note that the null routine AST_NULL (one underscore) is
f     different to AST__NULL (two underscores), which is the null Object
f     pointer.

*  Status Handling:
*     The protected interface to this function includes an extra
*     parameter at the end of the parameter list descirbed above. This
*     parameter is a pointer to the integer inherited status
*     variable: "int *status".
*--
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
   AstFitsChan *new;             /* Pointer to new FitsChan */
   va_list args;                 /* Variable argument list */

/* Get a pointer to the thread specific global data structure. */
   astGET_GLOBALS(NULL);

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

/* Initialise the FitsChan, allocating memory and initialising the
   virtual function table as well if necessary. This interface is for
   use by other C functions within AST, and uses the standard "wrapper"
   functions included in this class. */
   new = astInitFitsChan( NULL, sizeof( AstFitsChan ), !class_init,
                          &class_vtab, "FitsChan", source, SourceWrap,
                          sink, SinkWrap );

/* If successful, note that the virtual function table has been
   initialised. */
   if ( astOK ) {
      class_init = 1;

/* Apply any default options specified by "<class>_OPTIONS" environment
   variable. */
      astEnvSet( new );

/* Obtain the variable argument list and pass it along with the
   options string to the astVSet method to initialise the new
   FitsChan's attributes. */
      va_start( args, status );
      astVSet( new, options, NULL, args );
      va_end( args );

/* If an error occurred, clean up by deleting the new object. */
      if ( !astOK ) new = astDelete( new );
   }

/* Return a pointer to the new FitsChan. */
   return new;
}

AstFitsChan *astFitsChanId_( const char *(* source)( void ),
                             void (* sink)( const char * ),
                             const char *options, ... ) {

/*
*  Name:
*     astFitsChanId_

*  Purpose:
*     Create a FitsChan.

*  Type:
*     Private function.

*  Synopsis:
*     #include "fitschan.h"
*     AstFitsChan *astFitsChanId_( const char *(* source)( void ),
*                                  void (* sink)( const char * ),
*                                  const char *options, ... )

*  Class Membership:
*     FitsChan constructor.

*  Description:
*     This function implements the external (public) C interface to the
*     astFitsChan constructor function. Another function (astFitsChanForId)
*     should be called to create a FitsChan for use within other languages.
*     Both functions return an ID value (instead of a true C pointer) to
*     external users, and must be provided because astFitsChan_ has a variable
*     argument list which cannot be encapsulated in a macro (where this conversion would otherwise
*     occur).
*
*     The variable argument list also prevents this function from
*     invoking astFitsChan_ directly, so it must be a re-implementation
*     of it in all respects, except for the final conversion of the
*     result to an ID value.

*  Parameters:
*     As for astFitsChan_.

*  Returned Value:
*     The ID value associated with the new FitsChan.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
   AstFitsChan *new;             /* Pointer to new FitsChan */
   va_list args;                 /* Variable argument list */
   int *status;                  /* Pointer to inherited status value */

/* Get a pointer to the inherited status value. */
   status = astGetStatusPtr;

/* Get a pointer to the thread specific global data structure. */
   astGET_GLOBALS(NULL);

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

/* Initialise the FitsChan, allocating memory and initialising the
   virtual function table as well if necessary. This interface is for
   use by external C functions and uses the standard "wrapper"
   functions included in this class. */
   new = astInitFitsChan( NULL, sizeof( AstFitsChan ), !class_init,
                          &class_vtab, "FitsChan", source, SourceWrap,
                          sink, SinkWrap );

/* If successful, note that the virtual function table has been
   initialised. */
   if ( astOK ) {
      class_init = 1;

/* Apply any default options specified by "<class>_OPTIONS" environment
   variable. */
      astEnvSet( new );

/* Obtain the variable argument list and pass it along with the
   options string to the astVSet method to initialise the new
   FitsChan's attributes. */
      va_start( args, options );
      astVSet( new, options, NULL, args );
      va_end( args );

/* If an error occurred, clean up by deleting the new object. */
      if ( !astOK ) new = astDelete( new );
   }

/* Return an ID value for the new FitsChan. */
   return astMakeId( new );
}

AstFitsChan *astFitsChanForId_( const char *(* source)( void ),
                              char *(* source_wrap)( const char *(*)( void ), int * ),
                              void (* sink)( const char * ),
                              void (* sink_wrap)( void (*)( const char * ),
                                                  const char *, int * ),
                              const char *options, ... ) {

/*
*+
*  Name:
*     astFitsChanFor

*  Purpose:
*     Initialise a FitsChan from a foreign language interface.

*  Type:
*     Public function.

*  Synopsis:
*     #include "fitschan.h"
*     AstFitsChan *astFitsChanFor( const char *(* source)( void ),
*                                char *(* source_wrap)( const char *(*)
*                                                       ( void ), int * ),
*                                void (* sink)( const char * ),
*                                void (* sink_wrap)( void (*)( const char * ),
*                                                    const char *, int * ),
*                                const char *options, ... )

*  Class Membership:
*     FitsChan constructor.

*  Description:
*     This function creates a new FitsChan from a foreign language
*     interface and optionally initialises its attributes.
*
*     A FitsChan implements FITS input/output for the AST library.
*     Writing an Object to a FitsChan (using astWrite) will generate a
*     textual representation of that Object in terms of FITS header cards,
*     and reading from a FitsChan (using astRead) will create a new Object
*     from its FITS representation.
*
*     Normally, when you use a FitsChan, you should provide "source"
*     and "sink" functions which connect it to an external data store
*     by reading and writing the resulting text. This function also
*     requires you to provide "wrapper" functions which will invoke
*     the source and sink functions.

*  Parameters:
*     source
*        Pointer to a "source" function which will be used to obtain
*        FITS header cards. Generally, this will be obtained by
*        casting a pointer to a source function which is compatible
*        with the "source_wrap" wrapper function (below). The pointer
*        should later be cast back to its original type by the
*        "source_wrap" function before the function is invoked.
*
*        If "source" is NULL, the FitsChan will remain empty until
*        cards are added explicitly (e.g. using astPutCards or astPutFits).
*     source_wrap
*        Pointer to a function which can be used to invoke the
*        "source" function supplied (above). This wrapper function is
*        necessary in order to hide variations in the nature of the
*        source function, such as may arise when it is supplied by a
*        foreign (non-C) language interface.
*
*        The single parameter of the "source_wrap" function is a
*        pointer to the "source" function, and it should cast this
*        function pointer (as necessary) and invoke the function with
*        appropriate arguments to obtain the next FITS header card.
*        The "source_wrap" function should then return a pointer
*        to a dynamically allocated, null terminated string containing
*        the text that was read. The string will be freed (using
*        astFree) when no longer required and the "source_wrap"
*        function need not concern itself with this. A NULL pointer
*        should be returned if there is no more input to read.
*
*        If "source" is NULL, the FitsChan will remain empty until
*        cards are added explicitly (e.g. using astPutCards or astPutFits).
*     sink
*        Pointer to a "sink" function which will be used to deliver
*        FITS header cards. Generally, this will be obtained by
*        casting a pointer to a sink function which is compatible with
*        the "sink_wrap" wrapper function (below). The pointer should
*        later be cast back to its original type by the "sink_wrap"
*        function before the function is invoked.
*
*        If "sink" is NULL, the contents of the FitsChan will not be
*        written out before being deleted.
*     sink_wrap
*        Pointer to a function which can be used to invoke the "sink"
*        function supplied (above). This wrapper function is necessary
*        in order to hide variations in the nature of the sink
*        function, such as may arise when it is supplied by a foreign
*        (non-C) language interface.
*
*        The first parameter of the "sink_wrap" function is a pointer
*        to the "sink" function, and the second parameter is a pointer
*        to a const, null-terminated character string containing the
*        text to be written.  The "sink_wrap" function should cast the
*        "sink" function pointer (as necessary) and invoke the
*        function with appropriate arguments to deliver the line of
*        output text. The "sink_wrap" function then returns void.
*
*        If "sink_wrap" is NULL, the contents of the FitsChan will not be
*        written out before being deleted.
*     options
*        Pointer to a null-terminated string containing an optional
*        comma-separated list of attribute assignments to be used for
*        initialising the new FitsChan. The syntax used is identical to
*        that for the astSet function and may include "printf" format
*        specifiers identified by "%" symbols in the normal way.
*     ...
*        If the "options" string contains "%" format specifiers, then
*        an optional list of additional arguments may follow it in
*        order to supply values to be substituted for these
*        specifiers. The rules for supplying these are identical to
*        those for the astSet function (and for the C "printf"
*        function).

*  Returned Value:
*     astFitsChanFor()
*        A pointer to the new FitsChan.

*  Notes:
*     - A null Object pointer (AST__NULL) will be returned if this
*     function is invoked with the global error status set, or if it
*     should fail for any reason.
*     - This function is only available through the public interface
*     to the FitsChan class (not the protected interface) and is
*     intended solely for use in implementing foreign language
*     interfaces to this class.
*-

*  Implememtation Notes:
*     - This function behaves exactly like astFitsChanId_, in that it
*     returns ID values and not true C pointers, but it has two
*     additional arguments. These are pointers to the "wrapper
*     functions" which are needed to accommodate foreign language
*     interfaces.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
   AstFitsChan *new;             /* Pointer to new FitsChan */
   va_list args;                 /* Variable argument list */
   int *status;                  /* Pointer to inherited status value */

/* Get a pointer to the inherited status value. */
   status = astGetStatusPtr;

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

/* Get a pointer to the thread specific global data structure. */
   astGET_GLOBALS(NULL);

/* Initialise the FitsChan, allocating memory and initialising the
   virtual function table as well if necessary. */
   new = astInitFitsChan( NULL, sizeof( AstFitsChan ), !class_init,
                          &class_vtab, "FitsChan", source, source_wrap,
                          sink, sink_wrap );

/* If successful, note that the virtual function table has been
   initialised. */
   if ( astOK ) {
      class_init = 1;

/* Apply any default options specified by "<class>_OPTIONS" environment
   variable. */
      astEnvSet( new );

/* Obtain the variable argument list and pass it along with the
   options string to the astVSet method to initialise the new
   FitsChan's attributes. */
      va_start( args, options );
      astVSet( new, options, NULL, args );
      va_end( args );

/* If an error occurred, clean up by deleting the new object. */
      if ( !astOK ) new = astDelete( new );
   }

/* Return an ID value for the new FitsChan. */
   return astMakeId( new );
}

AstFitsChan *astInitFitsChan_( void *mem, size_t size, int init,
                               AstFitsChanVtab *vtab, const char *name,
                               const char *(* source)( void ),
                               char *(* source_wrap)( const char *(*)( void ), int * ),
                               void (* sink)( const char * ),
                               void (* sink_wrap)( void (*)( const char * ),
                                                   const char *, int * ), int *status ) {

/*
*+
*  Name:
*     astInitFitsChan

*  Purpose:
*     Initialise a FitsChan.

*  Type:
*     Protected function.

*  Synopsis:
*     #include "fitschan.h"
*     AstFitsChan *astInitFitsChan_( void *mem, size_t size, int init,
*                            AstFitsChanVtab *vtab, const char *name,
*                            const char *(* source)( void ),
*                            char *(* source_wrap)( const char *(*)( void ), int * ),
*                            void (* sink)( const char * ),
*                            void (* sink_wrap)( void (*)( const char * ),
*                                                   const char *, int * ) )

*  Class Membership:
*     FitsChan initialiser.

*  Description:
*     This function is provided for use by class implementations to
*     initialise a new FitsChan object. It allocates memory (if
*     necessary) to accommodate the FitsChan plus any additional data
*     associated with the derived class.  It then initialises a
*     FitsChan structure at the start of this memory. If the "init"
*     flag is set, it also initialises the contents of a virtual
*     function table for a FitsChan at the start of the memory passed
*     via the "vtab" parameter.

*  Parameters:
*     mem
*        A pointer to the memory in which the FitsChan is to be
*        initialised.  This must be of sufficient size to accommodate
*        the FitsChan data (sizeof(FitsChan)) plus any data used by the
*        derived class. If a value of NULL is given, this function
*        will allocate the memory itself using the "size" parameter to
*        determine its size.
*     size
*        The amount of memory used by the FitsChan (plus derived class
*        data).  This will be used to allocate memory if a value of
*        NULL is given for the "mem" parameter. This value is also
*        stored in the FitsChan structure, so a valid value must be
*        supplied even if not required for allocating memory.
*     init
*        A boolean flag indicating if the FitsChan's virtual function
*        table is to be initialised. If this value is non-zero, the
*        virtual function table will be initialised by this function.
*     vtab
*        Pointer to the start of the virtual function table to be
*        associated with the new FitsChan.
*     name
*        Pointer to a constant null-terminated character string which
*        contains the name of the class to which the new object
*        belongs (it is this pointer value that will subsequently be
*        returned by the astGetClass method).
*     source
*        Pointer to a "source" function which will be used to obtain
*        FITS header cards. Generally, this will be obtained by
*        casting a pointer to a source function which is compatible
*        with the "source_wrap" wrapper function (below). The pointer
*        should later be cast back to its original type by the
*        "source_wrap" function before the function is invoked.
*
*        If "source" is NULL, the FitsChan will remain empty until
*        cards are added explicitly (e.g. using astPutCards or astPutFits).
*     source_wrap
*        Pointer to a function which can be used to invoke the
*        "source" function supplied (above). This wrapper function is
*        necessary in order to hide variations in the nature of the
*        source function, such as may arise when it is supplied by a
*        foreign (non-C) language interface.
*
*        The single parameter of the "source_wrap" function is a
*        pointer to the "source" function, and it should cast this
*        function pointer (as necessary) and invoke the function with
*        appropriate arguments to obtain the next FITS header card.
*        The "source_wrap" function should then return a pointer
*        to a dynamically allocated, null terminated string containing
*        the text that was read. The string will be freed (using
*        astFree) when no longer required and the "source_wrap"
*        function need not concern itself with this. A NULL pointer
*        should be returned if there is no more input to read.
*
*        If "source" is NULL, the FitsChan will remain empty until
*        cards are added explicitly (e.g. using astPutCards or astPutFits).
*     sink
*        Pointer to a "sink" function which will be used to deliver
*        FITS header cards. Generally, this will be obtained by
*        casting a pointer to a sink function which is compatible with
*        the "sink_wrap" wrapper function (below). The pointer should
*        later be cast back to its original type by the "sink_wrap"
*        function before the function is invoked.
*
*        If "sink" is NULL, the contents of the FitsChan will not be
*        written out before being deleted.
*     sink_wrap
*        Pointer to a function which can be used to invoke the "sink"
*        function supplied (above). This wrapper function is necessary
*        in order to hide variations in the nature of the sink
*        function, such as may arise when it is supplied by a foreign
*        (non-C) language interface.
*
*        The first parameter of the "sink_wrap" function is a pointer
*        to the "sink" function, and the second parameter is a pointer
*        to a const, null-terminated character string containing the
*        text to be written.  The "sink_wrap" function should cast the
*        "sink" function pointer (as necessary) and invoke the
*        function with appropriate arguments to deliver the line of
*        output text. The "sink_wrap" function then returns void.
*
*        If "sink_wrap" is NULL, the contents of the FitsChan will not be
*        written out before being deleted.

*  Returned Value:
*     A pointer to the new FitsChan.

*  Notes:
*     - 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 Variables: */
   AstFitsChan *new;              /* Pointer to new FitsChan */

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

/* If necessary, initialise the virtual function table. */
   if ( init ) astInitFitsChanVtab( vtab, name );

/* Initialise a Channel structure (the parent class) as the first
   component within the FitsChan structure, allocating memory if
   necessary. I am not sure why FitsChan has its own source_wrap and
   sink_wrap items, rather than just using those inherited from Channel.
   It may be possible to do away with the fitschan wrappers and just use
   the channel wrapper, but I have not yet tried this. Old mail from RFWS
   suggests that it may be because the F77 FitsChan source and sink
   interfaces handle fixed length strings (80 characters), whereas
   Channel sournce and sink handle variable length strings. This needs
   investigating. */
   new = (AstFitsChan *) astInitChannel( mem, size, 0,
                                         (AstChannelVtab *) vtab, name,
                                         NULL, NULL, NULL, NULL );
   if ( astOK ) {

/* Initialise the FitsChan data. */
/* ---------------------------- */
      new->head = NULL;
      new->card = NULL;
      new->keyseq = NULL;
      new->keywords = NULL;
      new->defb1950 = -1;
      new->tabok = -INT_MAX;
      new->forcetab = -INT_MAX;
      new->cdmatrix = -1;
      new->carlin = -1;
      new->sipreplace = -1;
      new->fitstol = -1.0;
      new->polytan = -INT_MAX;
      new->sipok = -INT_MAX;
      new->iwc = -1;
      new->clean = -1;
      new->fitsdigits = AST__DBL_DIG;
      new->fitsrounding = -1;
      new->altaxes = INT_MAX;
      new->fitsaxisorder = NULL;
      new->encoding = UNKNOWN_ENCODING;
      new->warnings = NULL;
      new->tables = NULL;

/* Save the pointers to the source and sink functions and the wrapper
   functions that invoke them. */
      new->source = source;
      new->saved_source = NULL;
      new->source_wrap = source_wrap;
      new->sink = sink;
      new->sink_wrap = sink_wrap;
      new->tabsource = NULL;
      new->tabsource_wrap = NULL;

/* Rewind the FitsChan so that the next read operation will return the
   first card. */
      new->card = new->head;

/* If an error occurred, clean up by deleting the new object. */
      if ( !astOK ) new = astDelete( new );
   }

/* Return a pointer to the new object. */
   return new;
}
AstFitsChan *astLoadFitsChan_( void *mem, size_t size,
                               AstFitsChanVtab *vtab, const char *name,
                               AstChannel *channel, int *status ) {

/*
*+
*  Name:
*     astLoadFitsChan

*  Purpose:
*     Load a FitsChan.

*  Type:
*     Protected function.

*  Synopsis:
*     #include "fitschan.h"
*     AstFitsChan *astLoadFitsChan( void *mem, size_t size,
*                                   AstFitsChanVtab *vtab, const char *name,
*                                   AstChannel *channel )

*  Class Membership:
*     FitsChan loader.

*  Description:
*     This function is provided to load a new FitsChan using data read
*     from a Channel. It first loads the data used by the parent class
*     (which allocates memory if necessary) and then initialises a
*     FitsChan structure in this memory, using data read from the input
*     Channel.
*
*     If the "init" flag is set, it also initialises the contents of a
*     virtual function table for a FitsChan at the start of the memory
*     passed via the "vtab" parameter.

*  Parameters:
*     mem
*        A pointer to the memory into which the FitsChan is to be
*        loaded.  This must be of sufficient size to accommodate the
*        FitsChan data (sizeof(FitsChan)) plus any data used by derived
*        classes. If a value of NULL is given, this function will
*        allocate the memory itself using the "size" parameter to
*        determine its size.
*     size
*        The amount of memory used by the FitsChan (plus derived class
*        data).  This will be used to allocate memory if a value of
*        NULL is given for the "mem" parameter. This value is also
*        stored in the FitsChan structure, so a valid value must be
*        supplied even if not required for allocating memory.
*
*        If the "vtab" parameter is NULL, the "size" value is ignored
*        and sizeof(AstFitsChan) is used instead.
*     vtab
*        Pointer to the start of the virtual function table to be
*        associated with the new FitsChan. If this is NULL, a pointer
*        to the (static) virtual function table for the FitsChan class
*        is used instead.
*     name
*        Pointer to a constant null-terminated character string which
*        contains the name of the class to which the new object
*        belongs (it is this pointer value that will subsequently be
*        returned by the astGetClass method).
*
*        If the "vtab" parameter is NULL, the "name" value is ignored
*        and a pointer to the string "FitsChan" is used instead.

*  Returned Value:
*     A pointer to the new FitsChan.

*  Notes:
*     - 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.
*-
*/
#define KEY_LEN 50              /* Maximum length of a keyword */

/* Local Variables: */
   astDECLARE_GLOBALS           /* Pointer to thread-specific global data */
   AstFitsChan *new;            /* Pointer to the new FitsChan */
   char *comment;               /* Pointer to keyword comment */
   char *keynm;                 /* Keyword name */
   char *text;                  /* Textual version of integer value */
   char buff[ KEY_LEN + 1 ];    /* Buffer for keyword string */
   double dval[2];              /* Double precision data values */
   int flags;                   /* Keyword flags */
   int free_data;               /* Should data memory be freed? */
   int ival[2];                 /* Integer data values */
   int ncard;                   /* No. of FitsCards read so far */
   int type;                    /* Keyword type */
   void *data;                  /* Pointer to keyword data value */

/* Initialise. */
   new = NULL;

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

/* Get a pointer to the thread specific global data structure. */
   astGET_GLOBALS(channel);

/* If a NULL virtual function table has been supplied, then this is
   the first loader to be invoked for this FitsChan. In this case the
   FitsChan belongs to this class, so supply appropriate values to be
   passed to the parent class loader (and its parent, etc.). */
   if ( !vtab ) {
      size = sizeof( AstFitsChan );
      vtab = &class_vtab;
      name = "FitsChan";

/* If required, initialise the virtual function table for this class. */
      if ( !class_init ) {
         astInitFitsChanVtab( vtab, name );
         class_init = 1;
      }
   }

/* Invoke the parent class loader to load data for all the ancestral
   classes of the current one, returning a pointer to the resulting
   partly-built FitsChan. */
   new = astLoadChannel( mem, size, (AstChannelVtab *) vtab, name,
                         channel );
   if ( astOK ) {

/* Read input data. */
/* ================ */

/* Request the input Channel to read all the input data appropriate to
   this class into the internal "values list". */
      astReadClassData( channel, "FitsChan" );

/* Initialise the KeyMap holding the keywords in the FitsChan. */
      new->keywords = NULL;

/* Initialise the list of keyword sequence numbers. */
      new->keyseq = NULL;

/* Set the pointers to the source and sink functions, and their
   wrapper functions, to NULL (we cannot restore these since they
   refer to process-specific addresses). */
      new->source = NULL;
      new->saved_source = NULL;
      new->source_wrap = NULL;
      new->sink = NULL;
      new->sink_wrap = NULL;
      new->tabsource = NULL;
      new->tabsource_wrap = NULL;

/* Now read each individual data item from this list and use it to
   initialise the appropriate instance variable(s) for this class. */

/* Encoding. */
/* --------- */
      text = astReadString( channel, "encod", UNKNOWN_STRING );
      if( text && strcmp( text, UNKNOWN_STRING ) ) {
         new->encoding = FindString( MAX_ENCODING + 1, xencod, text,
                                     "the FitsChan component 'Encod'",
                                     "astRead", astGetClass( channel ), status );
      } else {
         new->encoding = UNKNOWN_ENCODING;
      }
      if ( TestEncoding( new, status ) ) SetEncoding( new, new->encoding, status );
      text = astFree( text );

/* FitsAxisOrder. */
/* -------------- */
      new->fitsaxisorder = astReadString( channel, "faxord", NULL );

/* AltAxes. */
/* -------- */
      text = astReadString( channel, "altax", NULL );
      if( text ) {
         new->altaxes = FindString( 3, xaltax, text, "the FitsChan component 'AltAx'",
                                    "astRead", astGetClass( channel ), status );
      } else {
         new->altaxes = INT_MAX;
      }
      if ( TestAltAxes( new, status ) ) SetAltAxes( new, new->altaxes, status );
      text = astFree( text );

/* FitsDigits. */
/* ----------- */
      new->fitsdigits = astReadInt( channel, "fitsdg", AST__DBL_DIG );
      if ( TestFitsDigits( new, status ) ) SetFitsDigits( new, new->fitsdigits, status );

/* FitsRounding. */
/* ------------- */
      new->fitsrounding = astReadInt( channel, "fitsrn", -1 );
      if ( TestFitsRounding( new, status ) ) SetFitsRounding( new, new->fitsrounding, status );

/* DefB1950 */
/* -------- */
      new->defb1950 = astReadInt( channel, "dfb1950", -1 );
      if ( TestDefB1950( new, status ) ) SetDefB1950( new, new->defb1950, status );

/* TabOK */
/* ----- */
      new->tabok = astReadInt( channel, "tabok", -INT_MAX );
      if ( TestTabOK( new, status ) ) SetTabOK( new, new->tabok, status );

/* ForceTab */
/* -------- */
      new->forcetab = astReadInt( channel, "frctab", -INT_MAX );
      if ( TestForceTab( new, status ) ) SetForceTab( new, new->forcetab, status );

/* CDMatrix */
/* -------- */
      new->cdmatrix = astReadInt( channel, "cdmat", -1 );
      if ( TestCDMatrix( new, status ) ) SetCDMatrix( new, new->cdmatrix, status );

/* CarLin */
/* ------ */
      new->carlin = astReadInt( channel, "carlin", -1 );
      if ( TestCarLin( new, status ) ) SetCarLin( new, new->carlin, status );

/* SipReplace */
/* ---------- */
      new->sipreplace = astReadInt( channel, "sipreplace", -1 );
      if ( TestSipReplace( new, status ) ) SetSipReplace( new, new->sipreplace, status );

/* FitsTol */
/* ------- */
      new->fitstol = astReadDouble( channel, "fitstol", -1.0 );
      if ( TestFitsTol( new, status ) ) SetFitsTol( new, new->fitstol, status );

/* PolyTan */
/* ------- */
      new->polytan = astReadInt( channel, "polytan", -1 );
      if ( TestPolyTan( new, status ) ) SetPolyTan( new, new->polytan, status );

/* SipOK */
/* ----- */
      new->sipok = astReadInt( channel, "sipok", -1 );
      if ( TestSipOK( new, status ) ) SetSipOK( new, new->sipok, status );

/* Iwc */
/* --- */
      new->iwc = astReadInt( channel, "iwc", -1 );
      if ( TestIwc( new, status ) ) SetIwc( new, new->iwc, status );

/* Clean */
/* ----- */
      new->clean = astReadInt( channel, "clean", -1 );
      if ( TestClean( new, status ) ) SetClean( new, new->clean, status );

/* Warnings. */
/* --------- */
      new->warnings = astReadString( channel, "warn", NULL );

/* Card. */
/* ----- */

/* Initialise the index of the card to be read next. */
      ncard = 1;
      new->card = NULL;
      new->head = NULL;

/* Load each card. */
      type = AST__NOTYPE + 1;
      while( type != AST__NOTYPE && astOK ){

/* Get the keyword type. */
         (void) sprintf( buff, "ty%d", ncard );
         text = astReadString( channel, buff, " " );
         if( strcmp( text, " " ) ) {
            type = FindString( 9, type_names, text,
                               "a FitsChan keyword data type",
                               "astRead", astGetClass( channel ), status );
         } else {
            type = AST__NOTYPE;
         }
         text = astFree( text );

/* Only proceed if the keyword type was found. */
         if( type != AST__NOTYPE ){

/* Get the keyword name. Use a default blank name. */
            (void) sprintf( buff, "nm%d", ncard );
            keynm = astReadString( channel, buff, "        " );

/* Get the data value, using the appropriate data type, unless the
   keyword is a comment keyword or is undefined. */
            free_data = 0;
            if( type == AST__FLOAT ){
               (void) sprintf( buff, "dt%d", ncard );
               dval[ 0 ] = astReadDouble( channel, buff, AST__BAD );
               data = (void *) dval;
            } else if( type == AST__STRING || type == AST__CONTINUE ){
               (void) sprintf( buff, "dt%d", ncard );
               data = (void *) astReadString( channel, buff, "" );
               free_data = 1;
            } else if( type == AST__INT ){
               (void) sprintf( buff, "dt%d", ncard );
               ival[ 0 ] = astReadInt( channel, buff, 0 );
               data = (void *) ival;
            } else if( type == AST__LOGICAL ){
               (void) sprintf( buff, "dt%d", ncard );
               ival[ 0 ] = astReadInt( channel, buff, 0 );
               data = (void *) ival;
            } else if( type == AST__COMPLEXF ){
               (void) sprintf( buff, "dr%d", ncard );
               dval[ 0 ] = astReadDouble( channel, buff, AST__BAD );
               (void) sprintf( buff, "di%d", ncard );
               dval[ 1 ] = astReadDouble( channel, buff, AST__BAD );
               data = (void *) dval;
            } else if( type == AST__COMPLEXI ){
               (void) sprintf( buff, "dr%d", ncard );
               ival[ 0 ] = astReadInt( channel, buff, 0 );
               (void) sprintf( buff, "di%d", ncard );
               ival[ 1 ] = astReadInt( channel, buff, 0 );
               data = (void *) ival;
            } else {
               data = NULL;
            }

/* Get the keyword flags (only written by versions of AST later than
   V1.4). These are packed into an int. */
            (void) sprintf( buff, "fl%d", ncard );
            flags = astReadInt( channel, buff, 0 );

/* If the flags were not found, use the keyword deletion flag written by
   AST V1.4 and earlier. */
            if( !flags ) {
               (void) sprintf( buff, "dl%d", ncard );
               flags = astReadInt( channel, buff, 0 );
            }

/* Get the keyword comment. */
            (void) sprintf( buff, "cm%d", ncard );
            comment = astReadString( channel, buff, NULL );

/* Append a new card to the output FitsChan. */
            NewCard( new, keynm, type, data, comment, flags, status );

/* Free the character strings, and data (if required). */
            comment = (char *) astFree( (void *) comment );
            keynm = (char *) astFree( (void *) keynm );
            if( free_data ) data = astFree( data );
         }

/* Move on to the next card. */
         ncard++;
      }

/* Set up the current card index. */
      astSetCard( new, astReadInt( channel, "card", 0 ) );

/* Load any FitTables. */
      new->tables =  astReadObject( channel, "tables", NULL );
   }

/* If an error occurred, clean up by deleting the new FitsChan. */
   if ( !astOK ) new = astDelete( new );

/* Return the new FitsChan pointer. */
   return new;
}

/* Virtual function interfaces. */
/* ============================ */

/* These provide the external interface to the virtual functions defined by
   this class. Each simply checks the global error status and then locates and
   executes the appropriate member function, using the function pointer stored
   in the object's virtual function table (this pointer is located using the
   astMEMBER macro defined in "object.h").
   Note that the member function may not be the one defined here, as it may
   have been over-ridden by a derived class. However, it should still have the
   same interface. */

void astWriteFits_( AstFitsChan *this, int *status ){
   if( !this ) return;
   (**astMEMBER(this,FitsChan,WriteFits))(this, status );
}

void astReadFits_( AstFitsChan *this, int *status ){
   if( !astOK ) return;
   (**astMEMBER(this,FitsChan,ReadFits))(this, status );
}

void astEmptyFits_( AstFitsChan *this, int *status ){
   if( !this ) return;
   (**astMEMBER(this,FitsChan,EmptyFits))(this, status );
}

void astShowFits_( AstFitsChan *this, int *status ){
   if( !this ) return;
   (**astMEMBER(this,FitsChan,ShowFits))(this, status );
}

void astPutCards_( AstFitsChan *this, const char *cards, int *status ){
   if( !astOK ) return;
   (**astMEMBER(this,FitsChan,PutCards))(this,cards, status );
}

void astPutFits_( AstFitsChan *this, const char *card, int overwrite, int *status ){
   if( !astOK ) return;
   (**astMEMBER(this,FitsChan,PutFits))(this,card,overwrite, status );
}

void astDelFits_( AstFitsChan *this, int *status ){
   if( !astOK ) return;
   (**astMEMBER(this,FitsChan,DelFits))(this, status );
}

void astPurgeWCS_( AstFitsChan *this, int *status ){
   if( !astOK ) return;
   (**astMEMBER(this,FitsChan,PurgeWCS))(this, status );
}

AstKeyMap *astGetTables_( AstFitsChan *this, int *status ){
   if( !astOK ) return NULL;
   return (**astMEMBER(this,FitsChan,GetTables))(this, status );
}

void astPutTables_( AstFitsChan *this, AstKeyMap *tables, int *status ){
   if( !astOK ) return;
   (**astMEMBER(this,FitsChan,PutTables))(this, tables, status );
}

void astPutTable_( AstFitsChan *this, AstFitsTable *table, const char *extnam,
                   int *status ){
   if( !astOK ) return;
   (**astMEMBER(this,FitsChan,PutTable))(this, table, extnam, status );
}

void astRemoveTables_( AstFitsChan *this, const char *key, int *status ){
   if( !astOK ) return;
   (**astMEMBER(this,FitsChan,RemoveTables))(this, key, status );
}

void astRetainFits_( AstFitsChan *this, int *status ){
   if( !astOK ) return;
   (**astMEMBER(this,FitsChan,RetainFits))(this, status );
}

int astFitsEof_( AstFitsChan *this, int *status ){
   if( !this ) return 1;
   return (**astMEMBER(this,FitsChan,FitsEof))( this, status );
}

void astSetFitsCom_( AstFitsChan *this, const char *name,
                         const char *comment, int overwrite, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,FitsChan,SetFitsCom))( this, name, comment, overwrite, status );
}

void astSetFitsI_( AstFitsChan *this, const char *name, int value,
                     const char *comment, int overwrite, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,FitsChan,SetFitsI))( this, name, value, comment, overwrite, status );
}

void astSetFitsF_( AstFitsChan *this, const char *name, double value,
                       const char *comment, int overwrite, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,FitsChan,SetFitsF))( this, name, value, comment, overwrite, status );
}

void astSetFitsS_( AstFitsChan *this, const char *name, const char *value,
                        const char *comment, int overwrite, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,FitsChan,SetFitsS))( this, name, value, comment, overwrite, status );
}

void astSetFitsCN_( AstFitsChan *this, const char *name, const char *value,
                    const char *comment, int overwrite, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,FitsChan,SetFitsCN))( this, name, value, comment, overwrite, status );
}

void astSetFitsCF_( AstFitsChan *this, const char *name, double *value,
                          const char *comment, int overwrite, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,FitsChan,SetFitsCF))( this, name, value, comment, overwrite, status );
}

void astSetFitsCI_( AstFitsChan *this, const char *name, int *value,
                          const char *comment, int overwrite, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,FitsChan,SetFitsCI))( this, name, value, comment, overwrite, status );
}

void astSetFitsL_( AstFitsChan *this, const char *name, int value,
                         const char *comment, int overwrite, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,FitsChan,SetFitsL))( this, name, value, comment, overwrite, status );
}

void astSetFitsU_( AstFitsChan *this, const char *name, const char *comment,
                   int overwrite, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,FitsChan,SetFitsU))( this, name, comment, overwrite, status );
}

void astSetFitsCM_( AstFitsChan *this, const char *comment, int overwrite, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,FitsChan,SetFitsCM))( this, comment, overwrite, status );
}

void astClearCard_( AstFitsChan *this, int *status ){
   if( !this ) return;
   (**astMEMBER(this,FitsChan,ClearCard))( this, status );
}

void astSetCard_( AstFitsChan *this, int card, int *status ){
   if( !this ) return;
   (**astMEMBER(this,FitsChan,SetCard))( this, card, status );
}

int astTestCard_( AstFitsChan *this, int *status ){
   if( !this ) return 0;
   return (**astMEMBER(this,FitsChan,TestCard))( this, status );
}

int astGetCard_( AstFitsChan *this, int *status ){
   if( !this ) return 0;
   return (**astMEMBER(this,FitsChan,GetCard))( this, status );
}

int astGetNcard_( AstFitsChan *this, int *status ){
   if( !this ) return 0;
   return (**astMEMBER(this,FitsChan,GetNcard))( this, status );
}

int astGetCardType_( AstFitsChan *this, int *status ){
   if( !this ) return AST__NOTYPE;
   return (**astMEMBER(this,FitsChan,GetCardType))( this, status );
}

const char *astGetCardComm_( AstFitsChan *this, int *status ){
   if( !this ) return NULL;
   return (**astMEMBER(this,FitsChan,GetCardComm))( this, status );
}

const char *astGetCardName_( AstFitsChan *this, int *status ){
   if( !this ) return NULL;
   return (**astMEMBER(this,FitsChan,GetCardName))( this, status );
}

int astGetNkey_( AstFitsChan *this, int *status ){
   if( !this ) return 0;
   return (**astMEMBER(this,FitsChan,GetNkey))( this, status );
}

int astGetClean_( AstFitsChan *this, int *status ){
   if( !this ) return 0;
   return (**astMEMBER(this,FitsChan,GetClean))( this, status );
}

const char *astGetAllWarnings_( AstFitsChan *this, int *status ){
   if( !this ) return NULL;
   return (**astMEMBER(this,FitsChan,GetAllWarnings))( this, status );
}

int astGetFitsCF_( AstFitsChan *this, const char *name, double *value, int *status ){
   if( !astOK ) return 0;
   return (**astMEMBER(this,FitsChan,GetFitsCF))( this, name, value, status );
}

int astGetFitsCI_( AstFitsChan *this, const char *name, int *value, int *status ){
   if( !astOK ) return 0;
   return (**astMEMBER(this,FitsChan,GetFitsCI))( this, name, value, status );
}

int astGetFitsF_( AstFitsChan *this, const char *name, double *value, int *status ){
   if( !astOK ) return 0;
   return (**astMEMBER(this,FitsChan,GetFitsF))( this, name, value, status );
}

int astGetFitsI_( AstFitsChan *this, const char *name, int *value, int *status ){
   if( !astOK ) return 0;
   return (**astMEMBER(this,FitsChan,GetFitsI))( this, name, value, status );
}

int astGetFitsL_( AstFitsChan *this, const char *name, int *value, int *status ){
   if( !astOK ) return 0;
   return (**astMEMBER(this,FitsChan,GetFitsL))( this, name, value, status );
}

int astTestFits_( AstFitsChan *this, const char *name, int *there, int *status ){
   if( there ) *there = 0;
   if( !astOK ) return 0;
   return (**astMEMBER(this,FitsChan,TestFits))( this, name, there, status );
}

int astGetFitsS_( AstFitsChan *this, const char *name, char **value, int *status ){
   if( !astOK ) return 0;
   return (**astMEMBER(this,FitsChan,GetFitsS))( this, name, value, status );
}

int astGetFitsCN_( AstFitsChan *this, const char *name, char **value, int *status ){
   if( !astOK ) return 0;
   return (**astMEMBER(this,FitsChan,GetFitsCN))( this, name, value, status );
}

int astFitsGetCom_( AstFitsChan *this, const char *name, char **comment, int *status ){
   if( !astOK ) return 0;
   return (**astMEMBER(this,FitsChan,FitsGetCom))( this, name, comment, status );
}

int astKeyFields_( AstFitsChan *this, const char *filter, int maxfld,
                int *ubnd, int *lbnd, int *status ){
   if( !astOK ) return 0;
   return (**astMEMBER(this,FitsChan,KeyFields))( this, filter, maxfld,
           ubnd, lbnd, status );
}

int astFindFits_( AstFitsChan *this, const char *name, char *card, int inc, int *status ){
   if( !astOK ) return 0;
   return (**astMEMBER(this,FitsChan,FindFits))( this, name, card, inc, status );
}

int astGetEncoding_( AstFitsChan *this, int *status ){
   if( !astOK ) return UNKNOWN_ENCODING;
   return (**astMEMBER(this,FitsChan,GetEncoding))( this, status );
}

int astGetCDMatrix_( AstFitsChan *this, int *status ){
   if( !astOK ) return 0;
   return (**astMEMBER(this,FitsChan,GetCDMatrix))( this, status );
}
void astSetTableSource_( AstFitsChan *this,
                         void (*tabsource)( void ),
                         void (*tabsource_wrap)( void (*)( void ),
                                                 AstFitsChan *, const char *,
                                                 int, int, int * ),
                         int *status ){
   if( !astOK ) return;
   (**astMEMBER(this,FitsChan,SetTableSource))( this, tabsource,
                                                tabsource_wrap, status );
}
void astTableSource_( AstFitsChan *this,
                      void (* tabsource)( AstFitsChan *, const char *,
                                          int, int, int * ),
                      int *status ){
   if( !astOK ) return;
   (**astMEMBER(this,FitsChan,TableSource))( this, tabsource, status );
}

/*
 * A diagnostic function which lists the contents of a FitsChan to
 * standard output.
 */

/*
static void ListFC( AstFitsChan *, const char * );

static void ListFC( AstFitsChan *this, const char *ttl ) {
   FitsCard *cardo;
   char card[ 81 ];
   printf("%s\n----------------------------------------\n", ttl );
   cardo = (FitsCard *) this->card;
   astClearCard( this );
   while( !astFitsEof( this ) && astOK ){
      FormatCard( this, card, "List" );
      if( this->card == cardo ) {
         printf( "%s   <<<<< currrent card <<<<< \n", card );
      } else {
         printf( "%s\n", card );
      }
      MoveCard( this, 1, "List", "FitsChan" );
   }
   this->card = cardo;
}
*/















