diff options
Diffstat (limited to 'ast/fitschan.c')
-rw-r--r-- | ast/fitschan.c | 42623 |
1 files changed, 42623 insertions, 0 deletions
diff --git a/ast/fitschan.c b/ast/fitschan.c new file mode 100644 index 0000000..3e85444 --- /dev/null +++ b/ast/fitschan.c @@ -0,0 +1,42623 @@ +/* +*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 +* - TabOK: Should the FITS "-TAB" algorithm be recognised? +* - PolyTan: Use PVi_m keywords to define distorted TAN projection? +* - 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 ot the pixel Frame can be added into the FrameSet. +* - IRAFFromStore and AIPSFromStore modified so that they do not +* create any output keywords if the number of WCS axes is different +* to the number of pixel axes. +* - Handling of OBSGEO-X/Y/Z corrected again. +* - WCSFromStore modified to avouid writing partial axis descriptions. +* 26-APR-2004 (DSB): +* - Corrected text of output SPECSYS keyword values. +* 17-MAY-2004 (DSB): +* - Added IWC attribute. +* 15-JUN-2004 (DSB): +* - Ensure out-of-bounds longitude CRPIX values for CAR +* projections are wrapped back into bounds. +* 21-JUN-2004 (DSB): +* - Ensure primary MJD-OBS value is used when reading foreign FITS +* headers. +* 7-JUL-2004 (DSB): +* - Issue errors if an un-invertable PC/CD matrix is supplied in a +* FITS-WCS Header. +* 11-JUL-2004 (DSB): +* - Re-factor code for checking spectral axis CTYPE values into +* new function IsSpectral. +* - Modify AIPSFromSTore to create spectral axis keywords if +* possible. +* - Modify SpecTrans to recognize AIPS spectral axis keywords, and +* to convert "HZ" to "Hz". +* - Added FITS-AIPS++ encoding. +* 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. +*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 ***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 ClearPolyTan( AstFitsChan *, int * ); +static int GetPolyTan( AstFitsChan *, int * ); +static int TestPolyTan( AstFitsChan *, int * ); +static void SetPolyTan( 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 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 *SIPMapping( 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 *, 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 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 ); + } + astAddFrame( fset, pixel, mapping, frame ); + +/* 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 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 */ + +/* 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 descirbed the pixel->IWC mapping (CRPIX, CD, PC, + CDELT) are not stored by these functions, instead each function + returns a Mapping from WCS to IWC coords (these Mappings + pass on axes of the wrong class without change). These Mappings are + combined in series to get the final WCS->IWC Mapping. First do + celestial axes. */ + iwcmap = CelestialAxes( 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 ); + +/* Now attempt to store values for the keywords describing the pixel->IWC + Mapping (CRPIX, CD, PC, CDELT). This tests that the iwcmap is linear. + Zero is returned if the test fails. */ + ret = MakeIntWorld( pixiwcmap, wcsfrm, wperm, s, store, dim, method, class, + 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 ); + } + +/* 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 *map1; /* Pointer to pre-WcsMap Mapping */ + AstMapping *map3; /* Pointer to post-WcsMap Mapping */ + AstMapping *map; /* Pixel -> WCS mapping */ + AstMapping *ret; /* Returned Mapping */ + AstMapping *tmap0; /* A temporary Mapping */ + AstMapping *tmap1; /* A temporary Mapping */ + AstMapping *tmap2; /* A temporary Mapping */ + AstMapping *tmap3; /* A temporary Mapping */ + AstMapping *tmap4; /* A temporary Mapping */ + AstSkyFrame *skyfrm; /* The SkyFrame defining current WCS axis */ + AstWcsMap *map2; /* Pointer to WcsMap */ + AstWcsMap *map2b; /* Pointer to WcsMap with cleared lat/lonpole */ + char *cval; /* Pointer to keyword value */ + char *temp; /* Pointer to temporary string */ + double *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. */ + map = astGetMapping( fs, AST__BASE, AST__CURRENT ); + +/* 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 ); + +/* PolyTan */ +/* ------- */ + } else if ( !strcmp( attrib, "polytan" ) ) { + astClearPolyTan( 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", 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", DBL_DIG, ( (double *) odata )[ 0 ] ); + CheckZero( cnvtype_text0, ( (double *) odata )[ 0 ], 0, status ); + (void) sprintf( cnvtype_text1, "%.*g", 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( 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->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->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->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 */ + 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; + } + +/* PolyTan */ +/* ------- */ + } else if ( !strcmp( attrib, "polytan" ) ) { + ival = astGetPolyTan( 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", 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->ClearPolyTan = ClearPolyTan; + vtab->TestPolyTan = TestPolyTan; + vtab->SetPolyTan = SetPolyTan; + vtab->GetPolyTan = GetPolyTan; + 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 length of the string is not 8, then it is not an AIPS spectral axis. */ + if( strlen( 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( !strcmp( ctype + 4, "-LSR" ) ){ + *wspecsys = "LSRK"; + } else if( !strcmp( ctype + 4, "LSRK" ) ){ + *wspecsys = "LSRK"; + } else if( !strcmp( ctype + 4, "-LSRK" ) ){ + *wspecsys = "LSRK"; + } else if( !strcmp( ctype + 4, "-LSD" ) ){ + *wspecsys = "LSRD"; + } else if( !strcmp( ctype + 4, "-HEL" ) ){ + *wspecsys = "BARYCENT"; + } else if( !strcmp( ctype + 4, "-EAR" ) || !strcmp( ctype + 4, "-GEO" ) ){ + *wspecsys = "GEOCENTR"; + } else if( !strcmp( ctype + 4, "-OBS" ) || !strcmp( ctype + 4, "-TOP" ) ){ + *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 + DBL_DIG*2 ]; + char invexp[ 12 + DBL_DIG*2 ]; + const char *fexps[ 1 ]; + const char *iexps[ 1 ]; + double crv; + +/* Check the global status. */ + ret = NULL; + if( !astOK ) return ret; + +/* Get the CRVAL value for the specified axis. Use a default of zero. */ + crv = GetItem( &(store->crval), i, 0, s, NULL, method, class, status ); + if( crv == AST__BAD ) crv = 0.0; + +/* Create the MathMap, if possible. */ + if( crv != 0.0 ) { + sprintf( forexp, "s=%.*g*exp(w/%.*g)", DBL_DIG, crv, DBL_DIG, crv ); + sprintf( invexp, "w=%.*g*log(s/%.*g)", DBL_DIG, crv, DBL_DIG, crv ); + fexps[ 0 ] = forexp; + iexps[ 0 ] = invexp; + ret = (AstMapping *) astMathMap( 1, 1, 1, fexps, 1, iexps, "simpfi=1,simpif=1", 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 ); + map = astAnnul( map ); + remap = astAnnul( remap ); + map = tmap; + } + +/* 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", DBL_DIG, + AST__DR2D*astGetRefRA( specfrm ) ); + astPutFits( fc, card, 0 ); + sprintf( card, "CRVAL2 = %.*g", DBL_DIG, + AST__DR2D*astGetRefDec( specfrm ) ); + astPutFits( fc, card, 0 ); + sprintf( card, "MJD-OBS = %.*g", DBL_DIG, + 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, + 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, +* 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 +* suppliedMapping, which must be linear with an optional shift of +* origin (otherwise a value of zero is returned). +* +* Much of the complication in the algorithm arises from the need to +* support cases where the supplied Mapping has more outputs than +* inputs. In these case we add some "degenerate" axes to the grid +* coord system, choosing their unit vectors to be orthogonal to all +* the other grid axes. It is assumed that degenerate axes will never +* be used to find a position other than at the axis value of 1.0. +* +* NOTE, appropriate values for CRVAL keywords should have been stored +* in the FitsStore before calling this function (since this function may +* modify them). + +* Parameters: +* cmap +* A pointer to a Mapping which transforms grid coordinates into +* intermediate world coordinates. The number of outputs must be +* greater than or equal to the number of inputs. +* fr +* Pointer to the final WCS coordinate Frame. +* wperm +* Pointer to an array of integers with one element for each axis of +* the "fr" Frame. Each element holds the zero-based index of the +* FITS-WCS axis (i.e. the value of "i" in the keyword names "CTYPEi", +* "CDi_j", etc) which describes the Frame axis. +* s +* The co-ordinate version character. A space means the primary +* axis descriptions. Otherwise the supplied character should be +* an upper case alphabetical character ('A' to 'Z'). +* store +* A pointer to the FitsStore into which the calculated CRPIX and +* CDi_j values are to be put. +* dim +* An array holding the image dimensions in pixels. AST__BAD can be +* supplied for any unknwon dimensions. +* method +* Pointer to a string holding the name of the calling method. +* This is only for use in constructing error messages. +* class +* Pointer to a string holding the name of the supplied object class. +* This is only for use in constructing error messages. +* 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; + AstPointSet *psetw; + AstPointSet *psetg; + double **fullmat; + double **partmat; + double **ptrg; + double **ptrw; + double *c; + double *cdelt; + double *cdmat; + double *colvec; + double *d; + double *g; + double *g0; + double *m; + double *mat; + double *tol; + double *w0; + double *y; + double cd; + double crp; + double crv; + double cv; + double det; + double err; + double k; + double mxcv; + double skydiag1; + double skydiag0; + double val; + int *iw; + int *lin; + int *pperm; + int *skycol; + int i; + int ii; + int j; + int jax; + int jj; + int nin; + int nout; + int nwcs; + int paxis; + int ret; + int sing; + int skycol0; + int skycol1; + +/* Initialise */ + ret = 0; + +/* Check the inherited status. */ + if( !astOK ) return ret; + +/* Simplify the supplied Mapping to reduce rounding errors when + transforming points. */ + map = astSimplify( cmap ); + +/* Get the number of inputs and outputs for the Mapping. Return if the + number of outputs is smaller than the number of inputs. */ + nin = astGetNin( map ); + nout = astGetNout( map ); + if( nout < nin ) return ret; + +/* Note the number of final World Coordinate axes (not necessarily the + same as "nout", since some intermediate axes may be discarded by a + later PermMap. */ + nwcs = astGetNaxes( fr ); + +/* Allocate work space. */ + g = astMalloc( sizeof(double)*(size_t) nin ); + g0 = astMalloc( sizeof(double)*(size_t) nin ); + w0 = astMalloc( sizeof(double)*(size_t) nout ); + 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 one + tenth 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 ] *= 0.1; + +/* 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 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( j < nin ) { + crp = g0[ j ] - y[ j ]; + +/* If this is a grid axis which has been created to represent a "missing" + input to the mapping, we need to add on 1.0 to the crpix value found + above. This is because the "w0" vector corresponds to a value of zero + on any missing axes, but the FITS grid value for any missing axes is + 1.0. */ + } else { + crp = 1.0 - y[ j ]; + } + +/* Store the CD and CRPIX values for axes which correspond to inputs + of "map". The CD matrix elements are stored in an array and are + converted later to the corresponding PC and CDELT values. */ + if( j < nin || crp == 0.0 ) { + for( i = 0; i < nout; i++ ) { + cdmat[ wperm[ i ]*nout+j ] = colvec[ i ] ; + } + SetItem( &(store->crpix), 0, j, s, crp, 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; + } + } + } + +/* Right justify the returned string in the original field 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 */ + 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 ); + +/* PolyTan */ +/* ------- */ + } else if ( nc = 0, + ( 1 == astSscanf( setting, "polytan= %d %n", &ival, &nc ) ) + && ( nc >= len ) ) { + astSetPolyTan( 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 *SIPMapping( 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( 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: +* 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.*/ + 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 *) ×ys, 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, DBL_DIG, AST__DR2D*ptr2[ ilon ][ 0 ] ); + } else if( i == ilat ) { + sprintf( card, "CRVAL%d = %.*g", i + 1, 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 ); + +/* PolyTan */ +/* ------- */ + } else if ( !strcmp( attrib, "polytan" ) ) { + result = astTestPolyTan( 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. +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 */ + +/* 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. */ + (void) Split( this, name, &lname, &lvalue, &lcom, method, class, status ); + +/* 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. */ + if( SearchCard( this, lname, method, class, status ) ){ + +/* Indicate the card has been found. */ + if( there ) *there = 1; + +/* If the cards data type is no undefined, return 1. */ + if( CardType( this, status ) != AST__UNDEF ) ret = 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 */ + 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 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 ) ){ + 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 parprefix[3]; /* Prefix for projection parameter keywords */ + char combuf[80]; /* Buffer for FITS card comment */ + 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 */ + 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 prj; /* Projection type */ + 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 ); + } + +/* 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 the current +* Frame is the required WCS system. The FrameSet also contains one +* other Frame which defines 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. +*/ + +/* 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 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 ); + +/* The returned Frame is actually a FrameSet in which the current Frame + is the required WCS Frame. The FrameSet contains one other Frame, + which is the Frame representing IWC. Create a FrameSet containing these + two Frames. */ + if( astGetIwc( this ) ) { + fs = astFrameSet( iwcfrm, "", status ); + astInvert( map1 ); + map9 = (AstMapping *) astCmpMap( map1, ret, 1, "", status ); + astInvert( map1 ); + map10 = astSimplify( map9 ); + astAddFrame( fs, AST__BASE, map10, *frm ); + +/* Return this FrameSet instead of the Frame. */ + *frm = astAnnul( *frm ); + *frm = (AstFrame *) fs; + +/* Free resources */ + map9 = astAnnul( map9 ); + map10 = astAnnul( map10 ); + } + +/* 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. ", DBL_DIG, alphap*AST__DR2D, 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). */ + +/* Card. */ +/* ===== */ + +/* +*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 )) + +/* 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, headers that +* use this convention are still to be found, for instance the SCAMP +* utility (http://www.astromatic.net/software/scamp) creates them. +* +* 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)) + +/* 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,DBL_DIG) +astMAKE_GET(FitsChan,FitsDigits,int,DBL_DIG,this->fitsdigits) +astMAKE_SET(FitsChan,FitsDigits,int,fitsdigits,value) +astMAKE_TEST(FitsChan,FitsDigits,( this->fitsdigits != 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 */ + 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") ); + +/* 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") ); + +/* 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->polytan = -INT_MAX; + new->iwc = -1; + new->clean = -1; + new->fitsdigits = 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", 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 ); + +/* PolyTan */ +/* ------- */ + new->polytan = astReadInt( channel, "polytan", -1 ); + if ( TestPolyTan( new, status ) ) SetPolyTan( new, new->polytan, 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; +} +*/ + + + + + + + + + + + |