summaryrefslogtreecommitdiffstats
path: root/ast/fitschan.c
diff options
context:
space:
mode:
authorWilliam Joye <wjoye@cfa.harvard.edu>2018-01-09 19:28:07 (GMT)
committerWilliam Joye <wjoye@cfa.harvard.edu>2018-01-09 19:28:07 (GMT)
commit3bfbbb90d4d6ebd44cc5eac38af24d1f78318a58 (patch)
treef278119398ae5d67a6a338705a76db420f6b8f7e /ast/fitschan.c
parent1332d38f2805d986ea130e43218c0d2e870b4dc1 (diff)
downloadblt-3bfbbb90d4d6ebd44cc5eac38af24d1f78318a58.zip
blt-3bfbbb90d4d6ebd44cc5eac38af24d1f78318a58.tar.gz
blt-3bfbbb90d4d6ebd44cc5eac38af24d1f78318a58.tar.bz2
update ast 8.6.2
Diffstat (limited to 'ast/fitschan.c')
-rw-r--r--ast/fitschan.c43747
1 files changed, 43747 insertions, 0 deletions
diff --git a/ast/fitschan.c b/ast/fitschan.c
new file mode 100644
index 0000000..ae7c36e
--- /dev/null
+++ b/ast/fitschan.c
@@ -0,0 +1,43747 @@
+/*
+*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
+* - 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
+* - 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.
+*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 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 int (* 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 };
+/* 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[ AST__FITSCHAN_FITSCARDLEN + 1 ];
+
+/* 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 int 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 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 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 *, 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, 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, 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 * );
+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 * );
+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 * );
+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 new Frame has more axes than the pixel Frame, use a PermMap which
+ assigns constant value 1.0 to the extra axes. If the new Frame has less
+ axes than the pixel Frame, use a PermMap which throws away the extra
+ axes. */
+ if( mapping != NULL ) {
+ nwcs = astGetNin( mapping );
+ if( nwcs != npix ) {
+ inperm = astMalloc( sizeof(int)*(size_t)npix );
+ outperm = astMalloc( sizeof(int)*(size_t)nwcs );
+ if( astOK ) {
+ for( i = 0; i < npix; i++ ) {
+ inperm[ i ] = ( i < nwcs ) ? i : -1;
+ }
+ for( i = 0; i < nwcs; i++ ) {
+ outperm[ i ] = ( i < npix ) ? i : -1;
+ }
+ con = 1.0;
+ pmap = astPermMap( npix, inperm, nwcs, outperm, &con, "", 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( 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 *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 *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.
+* 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, 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[20]; /* 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 );
+
+/* 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 );
+
+/* DefB1950 */
+/* -------- */
+ } else if ( !strcmp( attrib, "defb1950" ) ) {
+ astClearDefB1950( this );
+
+/* TabOK */
+/* ----- */
+ } else if ( !strcmp( attrib, "tabok" ) ) {
+ astClearTabOK( 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, buff,
+ CardName( this, status ), method, astGetClass( this ),
+ status );
+}
+
+static int CnvType( int otype, void *odata, size_t osize, int type, int undef,
+ 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,
+* 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.
+* 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, 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, status );
+ (void) sprintf( cnvtype_text1, "%.*g", AST__DBL_DIG, ( (double *) odata )[ 1 ] );
+ CheckZero( cnvtype_text1, ( (double *) odata )[ 1 ], 0, 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 + 5 ];/* 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,
+ 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,
+* 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.
+* 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, 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, 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, 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, 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[15];/* 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;
+
+/* 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 CRPIX keyword. */
+ ret->naxis = GetMaxJM( &(ret->crpix), ' ', status ) + 1;
+ }
+
+/* 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[ 20 ]; /* Buffer for axis label */
+ char s; /* Co-ordinate version character */
+ int i; /* Pixel axis index */
+ int physical; /* Index of primary physical co-ordinate Frame */
+ int pixel; /* Index of pixel Frame in returned FrameSet */
+ int use; /* Has this co-ordinate version been used? */
+
+/* Initialise */
+ ret = NULL;
+
+/* Check the inherited status. */
+ if( !astOK ) return (AstObject *) ret;
+
+/* Only proceed if there are some axes. */
+ if( store->naxis ) {
+
+/* Create a Frame describing the pixel coordinate system. Give it the Domain
+ GRID. */
+ frame = astFrame( store->naxis, "Title=Pixel Coordinates,Domain=GRID", 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. */
+ 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 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 );
+
+/* Do not add any alternate axis descriptions if the primary axis
+ descriptions could not be produced. */
+ if( primok && astOK ) {
+
+/* Get the number of Frames in the FrameSet. */
+ nfrm = astGetNframe( fset );
+
+/* We now need to allocate a version letter to each Frame. Allocate
+ memory to hold the version letter assigned to each Frame. */
+ sid = (char *) astMalloc( ( nfrm + 1 )*sizeof( char ) );
+
+/* The frms array has an entry for each of the 26 possible version
+ letters (starting at A and ending at Z). Each entry holds the index of
+ the Frame which has been assigned that version character. Initialise
+ this array to indicate that no version letters have yet been assigned. */
+ for( s = 'A'; s <= 'Z'; s++ ) {
+ frms[ (int) s ] = 0;
+ }
+
+/* Loop round all frames (excluding the current and base and IWC Frames which
+ do not need version letters). If the Frame has an Ident attribute consisting
+ of a single upper case letter, use it as its version letter unless that
+ letter has already been given to an earlier frame. IWC Frames are not
+ written out - identify them by giving 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). */
+ s = 'A' - 1;
+ for( ifrm = 1; ifrm <= nfrm; ifrm++ ){
+ if( ifrm != icurr && ifrm != ibase && sid[ ifrm ] != 1 ) {
+ if( sid[ ifrm ] == 0 ){
+ while( frms[ (int) ++s ] != 0 );
+ if( s <= 'Z' ) {
+ sid[ ifrm ] = s;
+ frms[ (int) s ] = ifrm;
+ }
+ }
+ }
+ }
+
+/* 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 int GetObjSize( AstObject *this_object, int *status ) {
+/*
+* Name:
+* GetObjSize
+
+* Purpose:
+* Return the in-memory size of an Object.
+
+* Type:
+* Private function.
+
+* Synopsis:
+* #include "fitschan.h"
+* int 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 */
+ int 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 each keyword CTYPE, CRPIX and CRVAL. If not, then
+* the checking process continues to the next case. This goes some way
+* towards ensuring that the critical keywords used to determine the
+* encoding are part of a genuine WCS description and have not just been
+* left in the header by accident.
+
+* 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 and CRVAL keywords. */
+ haswcs = astKeyFields( this, "CTYPE%d", 0, NULL, NULL ) &&
+ astKeyFields( this, "CRPIX%d", 0, NULL, NULL ) &&
+ astKeyFields( this, "CRVAL%d", 0, NULL, NULL );
+
+/* See if there are any CDi_j keywords. */
+ hascd = astKeyFields( this, "CD%1d_%1d", 0, NULL, NULL );
+
+/* See if there are any PCi_j keywords. */
+ haspc = astKeyFields( this, "PC%1d_%1d", 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", 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;
+
+/* Fitrst 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[ 20 ];
+ 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;
+ }
+
+/* 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;
+ }
+
+/* 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 freedata; /* Should the data pointer be freed? */
+ int i; /* Loop counter for keyword characters */
+ int len; /* Length of current keyword */
+ int nc; /* Number of characters read by "astSscanf" */
+ int nn; /* No. of characters after UnPreQuoting */
+ int type; /* Data type code */
+ void *data; /* Pointer to current data value */
+
+/* Initialise the returned pointer values. */
+ *name = NULL;
+ *val = NULL;
+
+/* Check the global error status. */
+ if ( !astOK ) return;
+
+/* Obtain a pointer to the FitsChan structure. */
+ this = (AstFitsChan *) this_channel;
+
+/* Store the method name and object class. */
+ method = "astRead";
+ class = astGetClass( this );
+
+/* Loop to consider successive cards stored in the FitsChan (starting
+ at the "current" card) until a valid data item is read or "end of
+ file" is reached. Also quit the loop if an error occurs. */
+ done = 0;
+ newdata = NULL;
+ while ( !done && !astFitsEof( this ) && astOK ){
+
+/* Obtain the keyword string, data type code and data value pointer
+ from the current card. */
+ keyword = CardName( this, 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, 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->ClearFitsDigits = ClearFitsDigits;
+ vtab->TestFitsDigits = TestFitsDigits;
+ vtab->SetFitsDigits = SetFitsDigits;
+ vtab->GetFitsDigits = GetFitsDigits;
+ 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->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 *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 *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.
+* 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;
+
+/* 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 output is fed by only one input. */
+ if( ins ) {
+ if( astGetNin( map ) == 1 ) {
+
+/* If so, invert the map so that it goes from pixel to wcs, and then
+ modify the supplied arguments so that they refer to the single required
+ axis. */
+ astInvert( map );
+ lbnd_in += coord_out;
+ ubnd_in += coord_out;
+ 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[ 20 ]; /* 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, 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_DIG*2 ];
+ char invexp[ 12 + AST__DBL_DIG*2 ];
+ const char *fexps[ 1 ];
+ const char *iexps[ 1 ];
+ double crv;
+
+/* Check the global status. */
+ ret = NULL;
+ if( !astOK ) return ret;
+
+/* Get the CRVAL value for the specified axis. Use a default of zero. */
+ crv = GetItem( &(store->crval), i, 0, s, NULL, method, class, 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[ 13 ];/* Name of Equinox attribute for sky axes */
+ char system_attr[ 12 ]; /* Name of System attribute for sky axes */
+ const char *eqn; /* Pointer to original sky Equinox value */
+ const char *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;
+
+/* 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;
+ }
+ }
+ }
+
+/* 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, 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 ) {
+ sprintf( buf, "The spectral CTYPE value %s%s is not legal - "
+ "using %s%.3s%c instead.", astGetC( specfrm, "System" ),
+ algcode, astGetC( specfrm, "System" ), 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 = 1;
+ done = 0;
+ while( !done ) {
+ done = 1;
+ for( i = ii; i < n; 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 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. */
+ 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 );
+
+/* See if the axis is linear. If so, create a ShiftMap which subtracts off
+ the CRVAL value. */
+
+ if( IsMapLinear( map, lbnd_p, ubnd_p, iax, 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( (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[10]; /* 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 *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 )
+
+* 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
+* 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.
+*/
+
+/* Local Constants: */
+#define NSEQ 4 /* No. of adjacent 0's or 9's to produce rounding */
+
+/* Local Variables: */
+ char *a;
+ char *c;
+ char *dot;
+ char *exp;
+ char *last;
+ char *start;
+ char *end;
+ int i;
+ int neg;
+ int nnine;
+ int nonzero;
+ int nzero;
+ int replace;
+ int started;
+ int len;
+ int bu;
+ int nls;
+
+/* Check the inherited status. */
+ if( !astOK ) return;
+
+/* Save the original length of the text. */
+ len = strlen( text );
+
+/* Locate the start of any exponent string. */
+ exp = strpbrk( text, "dDeE" );
+
+/* First check for long strings of adjacent zeros.
+ =============================================== */
+
+/* Indicate that we have not yet found a decimal point in the string. */
+ dot = NULL;
+
+/* The "started" flag controls whether *leading* zeros should be removed
+ if there are more than NSEQ of them. They are only removed if there is an
+ exponent. */
+ started = ( exp != NULL );
+
+/* We are not currently replacing digits with zeros. */
+ replace = 0;
+
+/* We have not yet found any adjacent zeros. */
+ nzero = 0;
+
+/* We have no evidence yet that the number is non-zero. */
+ nonzero = 0;
+
+/* Loop round the supplied text string. */
+ c = text;
+ while( *c && c != exp ){
+
+/* If this is a zero, increment the number of adjacent zeros found, so
+ long as we have previously found a non-zero digit (or there is an
+ exponent). If this is the NSEQ'th adjacent zero, indicate that
+ subsequent digits should be replaced by zeros. */
+ if( *c == '0' ){
+ if( started && ++nzero >= NSEQ ) replace = 1;
+
+/* Note if the number contains a decimal point. */
+ } else if( *c == '.' ){
+ dot = c;
+
+/* If this character is a non-zero digit, indicate that we have found a
+ non-zero digit. If we have previously found a long string of adjacent
+ zeros, replace the digit by '0'. Otherwise, reset the count of
+ adjacent zeros, and indicate the final number is non-zero. */
+ } else if( *c != ' ' && *c != '+' && *c != '-' ){
+ started = 1;
+ if( replace ) {
+ *c = '0';
+ } else {
+ nzero = 0;
+ nonzero = 1;
+ }
+ }
+
+/* Move on to the next character. */
+ c++;
+ }
+
+/* If the final number is zero, just return the most simple decimal zero
+ value. */
+ if( !nonzero ) {
+ strcpy( text, "0.0" );
+
+/* Otherwise, we remove any trailing zeros which occur to the right of a
+ decimal point. */
+ } else if( dot ) {
+
+/* Find the last non-zero digit. */
+ while( c-- > text && *c == '0' );
+
+/* If any trailing zeros were found... */
+ if( c > text ) {
+
+/* Retain one trailing zero after a decimal point. */
+ if( *c == '.' ) c++;
+
+/* We put a terminator following the last non-zero character. The
+ terminator is the exponent, if there was one, or a null character.
+ Remember to update the pointer to the start of the exponent. */
+ c++;
+ if( exp ) {
+ a = exp;
+ exp = c;
+ while( ( *(c++) = *(a++) ) );
+ } else {
+ *c = 0;
+ }
+ }
+ }
+
+/* Next check for long strings of adjacent nines.
+ ============================================= */
+
+/* We have not yet found any adjacent nines. */
+ nnine = 0;
+
+/* We have not yet found a non-nine digit. */
+ a = NULL;
+
+/* We have not yet found a non-blank character */
+ start = NULL;
+ last = NULL;
+
+/* Number is assumed positive. */
+ neg = 0;
+
+/* Indicate that we have not yet found a decimal point in the string. */
+ dot = NULL;
+
+/* Loop round the supplied text string. */
+ c = text;
+ while( *c && c != exp ){
+
+/* Note the address of the first non-blank character. */
+ if( !start && *c != ' ' ) start = c;
+
+/* If this is a nine, increment the number of adjacent nines found. */
+ if( *c == '9' ){
+ ++nnine;
+
+/* Note if the number contains a decimal point. */
+ } else if( *c == '.' ){
+ dot = c;
+
+/* Note if the number is negative. */
+ } else if( *c == '-' ){
+ neg = 1;
+
+/* If this character is a non-nine digit, and we have not had a long
+ sequence of 9's, reset the count of adjacent nines, and update a pointer
+ to "the last non-nine digit prior to a long string of nines". */
+ } else if( *c != ' ' && *c != '+' ){
+ if( nnine < NSEQ ) {
+ nnine = 0;
+ a = c;
+ }
+ }
+
+/* Note the address of the last non-blank character. */
+ if( *c != ' ' ) last = c;
+
+/* Move on to the next character. */
+ c++;
+ }
+
+/* If a long string of adjacent nines was found... */
+ if( nnine >= NSEQ ) {
+ c = NULL;
+
+/* If we found at least one non-nine digit. */
+ if( a ) {
+
+/* "a" points to the last non-nine digit before the first of the group of 9's.
+ Increment this digit by 1. Since we know the digit is not a nine, there
+ is no danger of a carry. */
+ *a = *a + 1;
+
+/* Fill with zeros up to the decimal point, or to the end if there is no
+ decimal point. */
+ c = a + 1;
+ if( dot ) {
+ while( c < dot ) *(c++) = '0';
+ } else {
+ while( *c ) *(c++) = '0';
+ }
+
+/* Now make "c" point to the first character for the terminator. This is
+ usually the character following the last non-nine digit. However, if
+ the last non-nine digit appears immediately before a decimal point, then
+ we append ".0" to the string before appending the terminator. */
+ if( *c == '.' ) {
+ *(++c) = '0';
+ c++;
+ }
+
+/* If all digits were nines, the rounded number will occupy one more
+ character than the supplied number. We can only do the rounding if there
+ is a spare character (i.e.a space) in the supplied string. */
+ } else if( last - start + 1 < len ) {
+
+/* Put the modified text at the left of the available space. */
+ c = text;
+
+/* Start with a minus sing if needed, followed by the leading "1" (caused
+ by the overflow from the long string of 9's). */
+ if( neg ) *(c++) = '-';
+ *(c++) = '1';
+
+/* Now find the number of zeros to place after the leading "1". This is
+ the number of characters in front of the terminator marking the end of
+ the integer part of the number. */
+ if( dot ) {
+ nzero = dot - start;
+ } else if( exp ) {
+ nzero = exp - start;
+ } else {
+ nzero = last - start;
+ }
+
+/* If the number is negative, the above count will include the leading
+ minus sign, which is not a digit. So reduce the count by one. */
+ if( neg ) nzero--;
+
+/* Now put in the correct number of zeros. */
+ for( i = 0; i < nzero; i++ ) *(c++) = '0';
+
+/* If the original string containsed a decimal point, make sure the
+ returned string also contains one. */
+ if( dot ) {
+ *(c++) = '.';
+ if( *c ) *(c++) = '0';
+ }
+ }
+
+/* We put a terminator following the last non-zero character. The
+ terminator is the exponent, if there was one, or a null character. */
+ if( c ) {
+ if( exp ) {
+ while( ( *(c++) = *(exp++) ) );
+ } else {
+ *c = 0;
+ }
+ }
+ }
+
+/* If a minimum field width has been given, right justify the returned
+ string in the original field width. */
+ if( width ) {
+ end = text + len;
+ c = text + strlen( text );
+ 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 = len - 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 );
+ }
+
+/* FitsDigits. */
+/* ----------- */
+ } else if ( nc = 0,
+ ( 1 == astSscanf( setting, "fitsdigits= %d %n", &ival, &nc ) )
+ && ( nc >= len ) ) {
+ astSetFitsDigits( 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 );
+
+/* 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, 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, 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.
+* 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 unknwon dimensions.
+* 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 *scales;
+ double *shifts;
+ 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;
+
+/* 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 ){
+
+/* Check that each Mapping before the PolyMap is a shift of origin, and
+ accumulate them into a single CmpMap. */
+ map_lower = NULL;
+ ok = 1;
+ for( imap = 0; ok && imap < imap_pm; imap++ ) {
+ old_invert = astGetInvert( map_list[ imap ] );
+ astSetInvert( map_list[ imap ], invert_list[ imap ] );
+
+ if( astGetNin( map_list[ imap ] ) != 2 ){
+ ok = 0;
+
+ } else if( astIsAWinMap( map_list[ imap ] ) ) {
+ astWinTerms( map_list[ imap ], &shifts, &scales );
+ if( scales[ 0 ] != 1.0 || scales[ 1 ] != 1.0 ) ok = 0;
+
+ } else if( !astIsAShiftMap( map_list[ imap ] ) &&
+ !astIsAUnitMap( map_list[ imap ] ) ) {
+ ok = 0;
+ }
+
+ 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 );
+ }
+
+/* Check that each Mapping after the PolyMap is a scaling or matrix,
+ and accumulate them into a single CmpMap. */
+ map_upper = NULL;
+ for( imap = imap_pm + 1; ok && imap < nmap; imap++ ) {
+ old_invert = astGetInvert( map_list[ imap ] );
+ astSetInvert( map_list[ imap ], invert_list[ imap ] );
+
+ if( astGetNin( map_list[ imap ] ) != 2 ){
+ ok = 0;
+
+ } else if( astIsAWinMap( map_list[ imap ] ) ) {
+ astWinTerms( map_list[ imap ], &shifts, &scales );
+ if( shifts[ 0 ] != 0.0 || shifts[ 1 ] != 0.0 ) ok = 0;
+
+ } else if( !astIsAMatrixMap( map_list[ imap ] ) &&
+ !astIsAZoomMap( map_list[ imap ] ) &&
+ !astIsAUnitMap( map_list[ imap ] ) ) {
+ ok = 0;
+ }
+
+ 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 );
+ }
+
+/* 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 ) {
+ 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 );
+
+/* Determine the CD matrix. We already know the upper Mapping is linear
+ because we have checked that it contains only linear atomic mappings.
+ So we can use fixed bounds for the fitting area safely. */
+ lbnd[ 0 ] = -1.0;
+ lbnd[ 1 ] = -1.0;
+ ubnd[ 0 ] = 1.0;
+ ubnd[ 1 ] = 1.0;
+ if( !astLinearApprox( map_upper, lbnd, ubnd, 0.01, fit ) ) {
+ astError( AST__INTER, "%s(%s): SipIntWorld: Mapping "
+ "following PolyMap is not linear (internal "
+ "AST programming error).", status, method,
+ class );
+ }
+
+/* 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 );
+ inax1 = astFree( inax1 );
+ smap = astAnnul( smap );
+ }
+ 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[20]; /* Buffer for AST attribute name */
+ char com[80]; /* Buffer for keyword comment */
+ char lattype[MXCTYPELEN];/* Latitude axis CTYPE value */
+ char lontype[MXCTYPELEN];/* 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)", axlon + 1 );
+ sprintf( com, "%s offset from %s",astGetC( skyfrm, attr )+1, skyref );
+ SetItemC( &(store->ctype_com), axlon, 0, s, com, status );
+
+ sprintf( attr, "Symbol(%d)", axlat + 1 );
+ sprintf( com, "%s offset from %s",astGetC( skyfrm, attr )+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)", axlon + 1 );
+ skyref_lon = astGetD( skyfrm, attr );
+ sprintf( attr, "SkyRef(%d)", axlat + 1 );
+ skyref_lat = astGetD( skyfrm, attr );
+
+ sprintf( attr, "SkyRefP(%d)", axlon + 1 );
+ skyrefp_lon = astGetD( skyfrm, attr );
+ sprintf( attr, "SkyRefP(%d)", axlat + 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[ 10 ]; /* Name of System attribute for current WCS axis */
+ char unit_attr[ 10 ]; /* 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 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, 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( dim[ iax ] != 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. */
+ ctype[ 0 ] = 0;
+ if( IsMapLinear( map, lbnd_p, ubnd_p, iax, 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, 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 diretcly 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, 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 + 5 ];/* 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]; /* CTYPE value for spectral axis */
+ char sprj[6]; /* 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 = ' ';
+
+/* Find the number of axes by finding the highest axis number in any
+ CRPIXi keyword name. Pass on if there are no axes for this axis
+ description. */
+ if( s != ' ' ) {
+ sprintf( template, "CRPIX%%d%c", s );
+ } else {
+ strcpy( template, "CRPIX%d" );
+ }
+ if( !astKeyFields( this, template, 1, &naxis, lbnd ) ) {
+ if( s == ' ' ) s = 'A' - 1;
+ continue;
+ }
+
+/* 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;
+ axlon = -1;
+ axlat = -1;
+ 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++;
+ } else {
+ break;
+ }
+ }
+
+/* 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.
+ ----------------- */
+
+/* Check there are some CDELT keywords... */
+ if( s != ' ' ) {
+ sprintf( template, "CDELT%%d%c", s );
+ } else {
+ strcpy( template, "CDELT%d" );
+ }
+ if( astKeyFields( this, template, 0, NULL, NULL ) ){
+
+/* 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, 0, NULL, NULL );
+ if( !gotpcij ){
+
+/* CDjjjiii
+ -------- */
+ if( s == ' ' && astKeyFields( this, "CD%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 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, 0, NULL, NULL ) ){
+
+/* 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, 0, NULL, NULL ) ){
+
+/* 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 ) ){
+ 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 ), AST__FLOAT,
+ 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( !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". */
+ 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
+* the inherited integer 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 a non-zero integer for the final
+* (third) argument.
+*
+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 );
+
+/* FitsDigits. */
+/* ----------- */
+ } else if ( !strcmp( attrib, "fitsdigits" ) ) {
+ result = astTestFitsDigits( this );
+
+/* DefB1950. */
+/* --------- */
+ } else if ( !strcmp( attrib, "defb1950" ) ) {
+ result = astTestDefB1950( this );
+
+/* TabOK. */
+/* ------ */
+ } else if ( !strcmp( attrib, "tabok" ) ) {
+ result = astTestTabOK( 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[ 100 ]; /* 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;
+
+/* 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. 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 ))
+
+/* 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 ))
+
+/* 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" );
+
+/* 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") );
+
+/* 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->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->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 );
+
+/* FitsDigits. */
+/* ----------- */
+ new->fitsdigits = astReadInt( channel, "fitsdg", AST__DBL_DIG );
+ if ( TestFitsDigits( new, status ) ) SetFitsDigits( new, new->fitsdigits, 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 );
+
+/* 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;
+}
+*/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+