/***************************************************************************
* GFitsTable.cpp - FITS table base class *
* ----------------------------------------------------------------------- *
* copyright (C) 2008-2017 by Juergen Knoedlseder *
* ----------------------------------------------------------------------- *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
* *
***************************************************************************/
/**
* @file GFitsTable.cpp
* @brief FITS table abstract base class implementation
* @author Juergen Knoedlseder
*/
/* __ Includes ___________________________________________________________ */
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include "GException.hpp"
#include "GTools.hpp"
#include "GFitsCfitsio.hpp"
#include "GFits.hpp"
#include "GFitsTable.hpp"
#include "GFitsTableBitCol.hpp"
#include "GFitsTableByteCol.hpp"
#include "GFitsTableBoolCol.hpp"
#include "GFitsTableStringCol.hpp"
#include "GFitsTableUShortCol.hpp"
#include "GFitsTableShortCol.hpp"
#include "GFitsTableULongCol.hpp"
#include "GFitsTableLongCol.hpp"
#include "GFitsTableLongLongCol.hpp"
#include "GFitsTableFloatCol.hpp"
#include "GFitsTableDoubleCol.hpp"
#include "GFitsTableCFloatCol.hpp"
#include "GFitsTableCDoubleCol.hpp"
/* __ Method name definitions ____________________________________________ */
#define G_ACCESS1 "GFitsTable::operator[](int&)"
#define G_ACCESS2 "GFitsTable::operator[](std::string&)"
#define G_SET1 "GFitsTable::set(int&, GFitsTableCol&)"
#define G_SET2 "GFitsTable::set(std::string&, GFitsTableCol&)"
#define G_INSERT1 "GFitsTable::insert(int, GFitsTableCol&)"
#define G_INSERT2 "GFitsTable::insert(std::string&, GFitsTableCol&)"
#define G_INSERT_ROWS "GFitsTable::insert_rows(int&, int&)"
#define G_REMOVE_ROWS "GFitsTable::remove_rows(int&, int&)"
#define G_DATA_OPEN "GFitsTable::data_open(void*)"
#define G_DATA_SAVE "GFitsTable::data_save()"
#define G_GET_TFORM "GFitsTable::get_tform(int&)"
/* __ Macros _____________________________________________________________ */
/* __ Coding definitions _________________________________________________ */
/* __ Debug definitions __________________________________________________ */
//#define G_DEBUG_SAVE //!< Debug data_save() method
/*==========================================================================
= =
= Constructors/destructors =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Void constructor
*
* Construct void instance.
***************************************************************************/
GFitsTable::GFitsTable(void) : GFitsHDU()
{
// Initialise class members for clean destruction
init_members();
// Return
return;
}
/***********************************************************************//**
* @brief Table constructor
*
* @param[in] nrows Number of rows in table.
*
* Construct instance for a specified number of table rows.
***************************************************************************/
GFitsTable::GFitsTable(const int& nrows) : GFitsHDU()
{
// Initialise class members for clean destruction
init_members();
// Store numnber of rows
m_rows = nrows;
// Return
return;
}
/***********************************************************************//**
* @brief Copy constructor
*
* @param[in] table Table.
***************************************************************************/
GFitsTable::GFitsTable(const GFitsTable& table) : GFitsHDU(table)
{
// Initialise class members for clean destruction
init_members();
// Copy members
copy_members(table);
// Return
return;
}
/***********************************************************************//**
* @brief Destructor
***************************************************************************/
GFitsTable::~GFitsTable(void)
{
// Free members
free_members();
// Return
return;
}
/*==========================================================================
= =
= Operators =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Assignment operator
*
* @param[in] table Table.
* @return Table.
***************************************************************************/
GFitsTable& GFitsTable::operator=(const GFitsTable& table)
{
// Execute only if object is not identical
if (this != &table) {
// Copy base class members
this->GFitsHDU::operator=(table);
// Free members
free_members();
// Initialise private members for clean destruction
init_members();
// Copy members
copy_members(table);
} // endif: object was not identical
// Return this object
return *this;
}
/***********************************************************************//**
* @brief Returns pointer to table column
*
* @param[in] colnum Column number [0,...,ncols()-1].
* @return Table column pointer.
*
* @exception GException::fits_no_data
* No data found in table.
* @exception GException::out_of_range
* Column number is out of range.
***************************************************************************/
GFitsTableCol* GFitsTable::operator[](const int& colnum)
{
// If there is no data then throw an exception
if (m_columns == NULL) {
throw GException::fits_no_data(G_ACCESS1, "No columns in table.");
}
// Compile option: raise exception if column number is out of range
#if defined(G_RANGE_CHECK)
if (colnum < 0 || colnum >= m_cols) {
throw GException::out_of_range(G_ACCESS1, colnum, 0, m_cols-1);
}
#endif
// Get column pointer
GFitsTableCol* ptr = m_columns[colnum];
if (ptr == NULL) {
throw GException::fits_no_data(G_ACCESS1, "No data for this column.");
}
// Return pointer
return ptr;
}
/***********************************************************************//**
* @brief Returns pointer to table column (const version)
*
* @param[in] colnum Column number [0,...,ncols()-1].
* @return Table column pointer.
*
* @exception GException::fits_no_data
* No data found in table.
* @exception GException::out_of_range
* Column number is out of range.
***************************************************************************/
const GFitsTableCol* GFitsTable::operator[](const int& colnum) const
{
// If there is no data then throw an exception
if (m_columns == NULL) {
throw GException::fits_no_data(G_ACCESS1, "No columns in table.");
}
// Compile option: raise exception if column number is out of range
#if defined(G_RANGE_CHECK)
if (colnum < 0 || colnum >= m_cols) {
throw GException::out_of_range(G_ACCESS1, colnum, 0, m_cols-1);
}
#endif
// Get column pointer
const GFitsTableCol* ptr = m_columns[colnum];
if (ptr == NULL) {
throw GException::fits_no_data(G_ACCESS1, "No data for this column.");
}
// Return pointer
return ptr;
}
/***********************************************************************//**
* @brief Returns pointer to table column
*
* @param[in] colname Column name.
* @return Table column pointer.
*
* @exception GException::fits_no_data
* No data found in table.
* @exception GException::fits_column_not_found
* Column name not found.
***************************************************************************/
GFitsTableCol* GFitsTable::operator[](const std::string& colname)
{
// If there is no data then throw an exception
if (m_columns == NULL) {
throw GException::fits_no_data(G_ACCESS2, "No columns in table.");
}
// Get column pointer
GFitsTableCol* ptr = ptr_column(colname);
// If column has not been found throw an exception
if (ptr == NULL) {
throw GException::fits_column_not_found(G_ACCESS2, colname);
}
// Return pointer
return ptr;
}
/***********************************************************************//**
* @brief Returns pointer to table column (const version)
*
* @param[in] colname Column name.
* @return Table column pointer.
*
* @exception GException::fits_no_data
* No data found in table.
* @exception GException::fits_column_not_found
* Column name not found.
***************************************************************************/
const GFitsTableCol* GFitsTable::operator[](const std::string& colname) const
{
// If there is no data then throw an exception
if (m_columns == NULL) {
throw GException::fits_no_data(G_ACCESS2, "No columns in table.");
}
// Get column pointer
GFitsTableCol* ptr = ptr_column(colname);
// If column has not been found throw an exception
if (ptr == NULL) {
throw GException::fits_column_not_found(G_ACCESS2, colname);
}
// Return pointer
return ptr;
}
/*==========================================================================
= =
= Public methods =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Set column
*
* @param[in] colnum Column number [0,...,ncols()-1].
* @param[in] column Table column.
* @return Pointer to table column that has been set.
*
* Sets the column of a table by making a deep copy of the @p column
* provided.
***************************************************************************/
GFitsTableCol* GFitsTable::set(const int& colnum, const GFitsTableCol& column)
{
// Check if column number is valid
if (colnum < 0 || colnum >= ncols()) {
throw GException::out_of_range(G_SET1, "Column number", colnum, ncols());
}
// Free existing column
if (m_columns[colnum] != NULL) delete m_columns[colnum];
// Clone column
m_columns[colnum] = column.clone();
// Return pointer to column
return m_columns[colnum];
}
/***********************************************************************//**
* @brief Set column
*
* @param[in] colname Column name.
* @param[in] column Table column.
* @return Pointer to table column that has been set.
*
* Sets the column of a table by making a deep copy of the @p column
* provided.
***************************************************************************/
GFitsTableCol* GFitsTable::set(const std::string& colname,
const GFitsTableCol& column)
{
// Get column number
int colnum = this->colnum(colname);
// If column has not been found throw an exception
if (colnum < 0) {
throw GException::fits_column_not_found(G_SET2, colname);
}
// Set column and return pointer to column
return (set(colnum, column));
}
/***********************************************************************//**
* @brief Insert column into the table
*
* @param[in] colnum Column number [0,...,ncols()].
* @param[in] column Table column.
* @return Pointer to table column that has been appended.
*
* @exception GException::fits_bad_col_length
* The length of the column is incompatible with the number of
* rows in the table.
*
* A column will be inserted at position 'colnum' of the table. If the
* position is beyond the end of the table the column will be appended.
*
* If the table is empty and has 0 rows, the number of rows will be set to
* the length of the column.
*
* The length of the column to be inserted has to be identical to the number
* of rows in the table.
***************************************************************************/
GFitsTableCol* GFitsTable::insert(int colnum, const GFitsTableCol& column)
{
// Make sure that 'colnum' is valid. Since we add one column the total
// number of columns at return will be m_cols+1, hence the column
// index is comprised between [0,m_cols]
//if (colnum < 0) colnum = 0;
//if (colnum > m_cols) colnum = m_cols;
// Check if column number is valid
if (colnum < 0 || colnum > ncols()) {
throw GException::out_of_range(G_INSERT1, "Column number", colnum, ncols()+1);
}
// If the table is empty and has 0 rows then set the number of rows in
// the table to the length of the column
if (m_columns == NULL && m_rows == 0) {
m_rows = column.nrows();
}
// Throw exception if the column length is incompatible with number of
// rows in the table
if (m_rows != column.nrows()) {
throw GException::fits_bad_col_length(G_INSERT1,
column.nrows(), m_rows);
}
// If no column data exist then allocate them now
if (m_columns == NULL) {
m_columns = new GFitsTableCol*[1];
m_cols = 1;
m_columns[0] = NULL;
colnum = 0; // We insert at the first place
}
// ... otherwise make space to insert column
else {
// Allocate fresh memory
GFitsTableCol** tmp = new GFitsTableCol*[m_cols+1];
// Copy over old column pointers. Leave some space at the position
// where we want to insert the column
int src;
int dst;
for (src = 0, dst = 0; dst < m_cols+1; ++dst) {
if (dst == colnum) {
tmp[dst] = NULL;
}
else {
tmp[dst] = m_columns[src];
src++;
}
}
// Free old column pointer array
delete [] m_columns;
// Connect new memory
m_columns = tmp;
// Increment column counter
m_cols++;
} // endelse: we made space to insert a column
// Copy column by cloning it
m_columns[colnum] = column.clone();
// Reset column number since column does not already exist in FITS
// file
m_columns[colnum]->colnum(0);
// Return column pointer
return (m_columns[colnum]);
}
/***********************************************************************//**
* @brief Insert column into the table
*
* @param[in] colname Column name.
* @param[in] column Table column.
* @return Pointer to table column that has been appended
*
* Inserts the column at the position given by the specified column name.
***************************************************************************/
GFitsTableCol* GFitsTable::insert(const std::string& colname,
const GFitsTableCol& column)
{
// Get column number
int colnum = this->colnum(colname);
// If column has not been found throw an exception
if (colnum < 0) {
throw GException::fits_column_not_found(G_INSERT2, colname);
}
// Insert column and return pointer to column
return (insert(colnum, column));
}
/***********************************************************************//**
* @brief Append rows to the table
*
* @param[in] nrows Number of rows to be appended.
*
* Appends @p nrows rows at the end of the FITS table. The method calls the
* insert() method to do the job. Note that a call of this method will lead
* to loading all table data into memory.
***************************************************************************/
void GFitsTable::append_rows(const int& nrows)
{
// Set row number for insertion to end of the file
int rownum = this->nrows();
// Insert rows
insert_rows(rownum, nrows);
// Return
return;
}
/***********************************************************************//**
* @brief Insert rows into the table
*
* @param[in] row Row at which rows are inserted [0,...,nrows()].
* @param[in] nrows Number of rows to be inserted.
*
* @exception GException::fits_invalid_row
* Specified rownum is invalid.
*
* Inserts @p nrows table rows at the specified @p row into the table. If the
* @p row index is set to the number of existing rows, nrows(), @p nrows
* will be appened at the end of the table.
*
* Note that this method will load all table data into memory.
***************************************************************************/
void GFitsTable::insert_rows(const int& row, const int& nrows)
{
// Make sure that row is valid
if (row < 0 || row > m_rows) {
throw GException::fits_invalid_row(G_INSERT_ROWS, row, m_rows);
}
// Continue only if there are rows to be inserted
if (nrows > 0) {
// Insert rows for all columns
for (int icol = 0; icol < m_cols; ++icol) {
m_columns[icol]->insert(row, nrows);
}
// Increment number of rows in table
m_rows += nrows;
} // endfor: there were rows to be inserted
// Return
return;
}
/***********************************************************************//**
* @brief Remove rows from the table
*
* @param[in] row Row from which on rows are removed [0,...,nrows()-1].
* @param[in] nrows Number of rows to be removed.
*
* @exception GException::fits_invalid_row
* Specified rownum is invalid.
* @exception GException::fits_invalid_nrows
* Invalid number of rows specified.
*
* Removes @p nrows table rows from the specified @p row on from the table.
*
* Note that this method will load all column data into memory.
***************************************************************************/
void GFitsTable::remove_rows(const int& row, const int& nrows)
{
// Make sure that row is valid
if (row < 0 || row >= m_rows) {
throw GException::fits_invalid_row(G_REMOVE_ROWS, row, m_rows-1);
}
// Make sure that we don't remove beyond the limit
if (nrows < 0 || nrows > m_rows-row) {
throw GException::fits_invalid_nrows(G_REMOVE_ROWS, nrows, m_rows-row);
}
// Continue only if there are rows to be removed
if (nrows > 0) {
// Remove rows for all columns
for (int icol = 0; icol < m_cols; ++icol) {
m_columns[icol]->remove(row, nrows);
}
// Decrement number of rows in table
m_rows -= nrows;
} // endfor: there were rows to be removed
// Return
return;
}
/***********************************************************************//**
* @brief Checks the presence of a column in table
*
* @param[in] colname Column name.
* @return True of the column exists, false otherwise.
***************************************************************************/
bool GFitsTable::contains(const std::string& colname) const
{
// Get pointer in column
GFitsTableCol* ptr = ptr_column(colname);
// Return state
return (ptr != NULL);
}
/***********************************************************************//**
* @brief Print table information
*
* @param[in] chatter Chattiness (defaults to NORMAL).
* @return String containing table information.
***************************************************************************/
std::string GFitsTable::print(const GChatter& chatter) const
{
// Initialise result string
std::string result;
// Continue only if chatter is not silent
if (chatter != SILENT) {
// Append header
result.append("=== GFitsTable ===");
// Append HDU information
result.append("\n"+print_hdu(chatter));
// Append table type
result.append("\n"+gammalib::parformat("Table type"));
switch (m_type) {
case GFitsHDU::HT_ASCII_TABLE:
result.append("ASCII table");
break;
case GFitsHDU::HT_BIN_TABLE:
result.append("Binary table");
break;
default:
result.append("Unknown");
break;
}
// Append table dimensions
result.append("\n"+gammalib::parformat("Number of rows"));
result.append(gammalib::str(m_rows));
result.append("\n"+gammalib::parformat("Number of columns"));
result.append(gammalib::str(m_cols));
// NORMAL: Append header information
if (chatter >= NORMAL) {
result.append("\n"+m_header.print(gammalib::reduce(chatter)));
}
// NORMAL: Append table columns
if (chatter >= VERBOSE) {
if (m_columns != NULL) {
for (int i = 0; i < m_cols; ++i) {
result.append("\n");
if (m_columns[i] != NULL) {
result.append(m_columns[i]->print(gammalib::reduce(chatter)));
}
else {
result.append(" Column "+gammalib::str(i)+" undefined");
}
}
}
else {
result.append("\n Table columns undefined");
}
}
} // endif: chatter was not silent
// Return result
return result;
}
/*==========================================================================
= =
= Protected methods =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Open Table
*
* @param[in] vptr FITS file pointer.
*
* @exception GException::fits_hdu_not_found
* Specified HDU not found in FITS file.
* @exception GException::fits_error
* A CFITSIO error occured during loading the table.
* @exception GException::fits_unknown_coltype
* FITS column of unsupported type has been found in the FITS file.
* @exception GException::fits_inconsistent_tdim
* The TDIM information provided in the header is inconsistent
* with the size of the column.
*
* This method loads a description of the table into memory. Column data are
* not loaded at this point to save memory. They will be loaded on request
* later.
***************************************************************************/
void GFitsTable::data_open(void* vptr)
{
// Move to HDU
gammalib::fits_move_to_hdu(G_DATA_OPEN, vptr);
// Save FITS file pointer
FPTR_COPY(m_fitsfile, vptr);
// Determine number of rows in table
int status = 0;
long nrows = 0;
status = __ffgnrw(FPTR(m_fitsfile), &nrows, &status);
if (status != 0) {
throw GException::fits_error(G_DATA_OPEN, status);
}
else {
m_rows = (int)nrows;
}
// Determine number of columns in table
status = __ffgncl(FPTR(m_fitsfile), &m_cols, &status);
if (status != 0) {
throw GException::fits_error(G_DATA_OPEN, status);
}
// Allocate and initialise memory for column pointers. Note that this
// initialisation is needed to allow for a clean free_members() call
// in case of any exception.
if (m_columns != NULL) delete [] m_columns;
m_columns = new GFitsTableCol*[m_cols];
for (int i = 0; i < m_cols; ++i) {
m_columns[i] = NULL;
}
// Get table column information
int typecode = 0;
long repeat = 0;
long width = 0;
for (int i = 0; i < m_cols; ++i) {
// Get column name
char keyname[10];
char value[80];
sprintf(keyname, "TTYPE%d", i+1);
status = __ffgkey(FPTR(m_fitsfile), keyname, value, NULL, &status);
if (status != 0) {
throw GException::fits_error(G_DATA_OPEN, status);
}
value[strlen(value)-1] = '\0';
// Get column definition
status = __ffgtcl(FPTR(m_fitsfile), i+1, &typecode, &repeat, &width,
&status);
if (status != 0) {
throw GException::fits_error(G_DATA_OPEN, status);
}
// Check for unsigned columns
unsigned long offset = 0;
sprintf(keyname, "TZERO%d", i+1);
status = __ffgky(FPTR(m_fitsfile), __TULONG, keyname, &offset, NULL, &status);
if (status == 0) {
if (typecode == __TSHORT && offset == 32768u) {
typecode = __TUSHORT;
}
else if (typecode == -__TSHORT && offset == 32768u) {
typecode = -__TUSHORT;
}
else if (typecode == __TLONG && offset == 2147483648u) {
typecode = __TULONG;
}
else if (typecode == -__TLONG && offset == 2147483648u) {
typecode = -__TULONG;
}
else if (typecode == __TINT && offset == 2147483648u) {
typecode = __TUINT;
}
else if (typecode == -__TINT && offset == 2147483648u) {
typecode = -__TUINT;
}
else {
std::string msg = "But column '"+std::string(value)+"' has"
" typecode "+gammalib::str(typecode)+" and"
" unexpected associated TZERO="+
gammalib::str(offset)+".";
throw GException::fits_error(G_DATA_OPEN, 0, msg);
}
}
else {
status = 0;
}
// Get column unit (optional, leave blank if not found)
char unit[80];
sprintf(keyname, "TUNIT%d", i+1);
status = __ffgkey(FPTR(m_fitsfile), keyname, unit, NULL, &status);
if (status != 0) {
status = 0;
unit[0] = '\0';
unit[1] = '\0';
}
else {
unit[strlen(unit)-1] = '\0';
}
// Get column dimension (optional, leave blank if not found)
char dim[80];
sprintf(keyname, "TDIM%d", i+1);
status = __ffgkey(FPTR(m_fitsfile), keyname, dim, NULL, &status);
if (status != 0) {
status = 0;
dim[0] = '\0';
dim[1] = '\0';
}
else {
dim[strlen(dim)-1] = '\0';
}
// If found, extract column dimension into vector array of integers
std::vector vdim;
std::string sdim = gammalib::strip_chars(gammalib::strip_whitespace(&(dim[1])),"()");
if (sdim.length() > 0) {
std::vector elements = gammalib::split(sdim, ",");
for (int k = 0; k < elements.size(); ++k) {
vdim.push_back(gammalib::toint(elements[k]));
}
}
// Allocate column
m_columns[i] = alloc_column(typecode);
if (m_columns[i] == NULL) {
std::ostringstream colname;
colname << value;
throw GException::fits_unknown_coltype(G_DATA_OPEN, colname.str(), typecode);
}
// Store column definition
m_columns[i]->name(gammalib::strip_whitespace(&(value[1])));
m_columns[i]->unit(gammalib::strip_whitespace(&(unit[1])));
m_columns[i]->dim(vdim);
m_columns[i]->colnum(i+1);
m_columns[i]->type(typecode);
m_columns[i]->repeat(repeat);
m_columns[i]->width(width);
m_columns[i]->nrows(m_rows);
m_columns[i]->is_variable(typecode < 0);
m_columns[i]->connect(FPTR(m_fitsfile));
// Extract column vector size
if (m_columns[i]->repeat() == 1) { // ASCII tables
m_columns[i]->number(1);
}
else { // Binary tables
if (typecode == __TSTRING) {
m_columns[i]->number(m_columns[i]->repeat() /
m_columns[i]->width());
}
else {
m_columns[i]->number(m_columns[i]->repeat());
}
}
// If TDIM information was set then check its consistency
if (!vdim.empty()) {
// Compute expectation
int num = vdim[0];
for (int k = 1; k < vdim.size(); ++k) {
num *= vdim[k];
}
// Compare with real size
if (num != m_columns[i]->number()) {
throw GException::fits_inconsistent_tdim(G_DATA_OPEN,
vdim,
m_columns[i]->number());
}
} // endif: Valid TDIM information was found
} // endfor: looped over all columns
// Return
return;
}
/***********************************************************************//**
* @brief Save table into FITS file
*
* @exception GException::fits_error
* A CFITSIO error occured in this method.
* @exception GException::fits_bad_col_length
* Table columns have inconsistent lengths.
*
* Saves the FITS table into the FITS file.
*
* In case that no table HDU exists so far in the FITS file, a new table
* will be appended to the FITS file. In case that a different HDU type
* exists at the requested extension number, the existing HDU will be
* deleted and be replaced by the requested table type.
*
* The method also verifies the consistency of all table columns. Table
* columns need to have identical lengths to be saved into a FITS table.
* All columns with a length of zero will be excluded from saving, and if
* they exist in the FITS file, they will be removed from the file.
*
* @todo This method should also update the header. Even easier, this method
* should save the header into the file using the m_header.save()
* method.
* Only this assures coherence between the files !!!! Once this has
* been implemented (also in the GFitsImage method) we should delete
* the m_header.save() call in GFitsHDU::save.
***************************************************************************/
void GFitsTable::data_save(void)
{
// Debug definition: Dump method entry
#if defined(G_DEBUG_SAVE)
std::cout << "GFitsTable::save: entry" << std::endl;
for (int i = 0; i < m_cols; ++i) {
std::cout << m_columns[i]->print() << std::endl;
}
#endif
// Make sure that column lengths are consistent with table length.
// Columns with zero length will not be considered (why?)
for (int i = 0; i < m_cols; ++i) {
if (m_columns[i] != NULL && m_columns[i]->nrows() > 0) {
if (m_columns[i]->nrows() != m_rows) {
throw GException::fits_bad_col_length(G_DATA_SAVE,
m_columns[i]->nrows(),
m_rows);
}
}
}
// Move to HDU
int status = 0;
int type = 0;
status = __ffmahd(FPTR(m_fitsfile), m_hdunum+1, &type, &status);
// If move was successful but HDU type in file differs from HDU type
// of object then replace the HDU in the file
bool replace = (status == 0 && type != m_type);
// If HDU does not exist in file or should be replaced then create or
// replace it now
if (status == 107 || replace) {
// Reset status
status = 0;
// Initialise number of fields
int tfields = 0;
// Setup cfitsio column definition arrays
char** ttype = NULL;
char** tform = NULL;
char** tunit = NULL;
if (m_cols > 0) {
ttype = new char*[m_cols];
tform = new char*[m_cols];
tunit = new char*[m_cols];
for (int i = 0; i < m_cols; ++i) {
ttype[i] = NULL;
tform[i] = NULL;
tunit[i] = NULL;
}
for (int i = 0; i < m_cols; ++i) {
ttype[tfields] = get_ttype(i);
tform[tfields] = get_tform(i);
tunit[tfields] = get_tunit(i);
if (ttype[tfields] != NULL && tform[tfields] != NULL &&
tunit[tfields] != NULL)
tfields++;
}
}
// Replace FITS HDU by table
if (replace) {
// Delete current FITS HDU
status = __ffdhdu(FPTR(m_fitsfile), NULL, &status);
if (status != 0) {
throw GException::fits_error(G_DATA_SAVE, status);
}
// Insert either ASCII or Binary table at current HDU position
if (exttype() == GFitsHDU::HT_ASCII_TABLE) {
long tbcol = 0;
long rowlen = 0;
status = __ffgabc(tfields, tform, 1, &rowlen, &tbcol, &status);
status = __ffitab(FPTR(m_fitsfile), rowlen, m_rows, tfields, ttype,
&tbcol, tform, tunit, NULL, &status);
}
else {
status = __ffibin(FPTR(m_fitsfile), m_rows, tfields, ttype, tform,
tunit, NULL, 0, &status);
}
if (status != 0) {
throw GException::fits_error(G_DATA_SAVE, status);
}
}
// ... otherwise create FITS table
else {
status = __ffcrtb(FPTR(m_fitsfile), m_type, m_rows, tfields,
ttype, tform, tunit, NULL, &status);
if (status != 0) {
throw GException::fits_error(G_DATA_SAVE, status);
}
}
// De-allocate column definition arrays
if (m_cols > 0) {
for (int i = 0; i < m_cols; ++i) {
if (ttype[i] != NULL) delete [] ttype[i];
if (tform[i] != NULL) delete [] tform[i];
if (tunit[i] != NULL) delete [] tunit[i];
}
if (ttype != NULL) delete [] ttype;
if (ttype != NULL) delete [] tform;
if (ttype != NULL) delete [] tunit;
}
// Connect all existing columns to FITS table
if (m_columns != NULL) {
for (int i = 0; i < m_cols; ++i) {
if (m_columns[i] != NULL) {
FPTR_COPY(m_columns[i]->m_fitsfile, m_fitsfile);
m_columns[i]->colnum(i+1);
}
}
}
// Debug option: Signal table creation
#if defined(G_DEBUG_SAVE)
std::cout << "GFitsTable::save: created new table" << std::endl;
#endif
}
// ... otherwise we signal a FITS error
else if (status != 0) {
throw GException::fits_error(G_DATA_SAVE, status);
}
// Determine number of columns in table
int num_cols = 0;
status = __ffgncl(FPTR(m_fitsfile), &num_cols, &status);
if (status != 0) {
throw GException::fits_error(G_DATA_SAVE, status);
}
// Debug option: Log number of columns in FITS table
#if defined(G_DEBUG_SAVE)
std::cout << "GFitsTable::save: FITS table contains ";
std::cout << num_cols << " columns." << std::endl;
#endif
// If we have no columns in the table then delete all columns from
// FITS table
if (m_columns == NULL && num_cols > 0) {
for (int i = 0; i < num_cols; ++i) {
status = __ffdcol(FPTR(m_fitsfile), i, &status);
if (status != 0) {
throw GException::fits_error(G_DATA_SAVE, status);
}
}
}
// ... otherwise update the FITS table
else {
// Determine number of rows in table
long num_rows = 0;
status = __ffgnrw(FPTR(m_fitsfile), &num_rows, &status);
if (status != 0) {
throw GException::fits_error(G_DATA_SAVE, status);
}
// Debug option: Log number of rows in FITS table
#if defined(G_DEBUG_SAVE)
std::cout << "GFitsTable::save: FITS table contains ";
std::cout << num_rows << " rows." << std::endl;
#endif
// If the table length differs from number of rows in the FITS file
// then readjust FITS table length. We do this by adding or
// deleting rows at the end of the table as we anyways rewrite the
// entire table later
if (m_rows > num_rows) {
// Insert rows at the end of the table
long long firstrow = num_rows;
long long nrows = m_rows - num_rows;
status = __ffirow(FPTR(m_fitsfile), firstrow, nrows, &status);
if (status != 0) {
throw GException::fits_error(G_DATA_SAVE, status);
}
// Debug option: Log row adding
#if defined(G_DEBUG_SAVE)
std::cout << "GFitsTable::save: Added " << nrows;
std::cout << " rows to FITS table." << std::endl;
#endif
}
else if (m_rows < num_rows) {
// Delete rows at the end of the table
long long firstrow = num_rows;
long long nrows = num_rows - m_rows;
status = __ffdrow(FPTR(m_fitsfile), firstrow, nrows, &status);
if (status != 0) {
throw GException::fits_error(G_DATA_SAVE, status);
}
// Debug option: Log row adding
#if defined(G_DEBUG_SAVE)
std::cout << "GFitsTable::save: Deleted " << nrows;
std::cout << " rows from FITS table." << std::endl;
#endif
}
// Debug option: Show where we are
#if defined(G_DEBUG_SAVE)
std::cout << "GFitsTable::save: Now update all columns." << std::endl;
#endif
// Update all columns. The 'm_colnum' field specifies where in the
// FITS file the column resides. If 'm_colnum=0' then we have a new
// column that does not yet exist. In this case we append a new column
// to the FITS file.
for (int i = 0; i < m_cols; ++i) {
// Only consider valid columns
if (m_columns[i] != NULL) {
// If column has no correspondance than add new column in
// FITS table and link column to table.
if (m_columns[i]->colnum() == 0) {
// Increment number of columns in FITS file
num_cols++;
// Append column to FITS file
status = __fficol(FPTR(m_fitsfile), num_cols, get_ttype(i),
get_tform(i), &status);
if (status != 0) {
throw GException::fits_error(G_DATA_SAVE, status);
}
// Connect all column to FITS table by copying over the
// FITS file pointer.
FPTR_COPY(m_columns[i]->m_fitsfile, m_fitsfile);
m_columns[i]->colnum(num_cols);
} // endif: column appended to FITS file
// Now write column into FITS file (only if length is positive)
if (m_columns[i]->nrows() > 0) {
// Debug option: Show which column we're going to write
#if defined(G_DEBUG_SAVE)
std::cout << "GFitsTable::save: Write column " << i;
std::cout << "." << std::endl;
#endif
m_columns[i]->save();
}
} // endif: column was valid
} // endfor: looped over all table columns
// Debug option: Show where we are
#if defined(G_DEBUG_SAVE)
std::cout << "GFitsTable::save: Now delete all obsolete columns.";
std::cout << std::endl;
#endif
// Delete all obsolete columns from FITS file. We do this from last to
// first so that the column numbers remain valid. Also note that
// FITS column counting starts from 1.
for (int colnum = num_cols; colnum > 0; --colnum) {
// Get column name from FITS file
char keyname[10];
char value[80];
sprintf(keyname, "TTYPE%d", colnum);
status = __ffgkey(FPTR(m_fitsfile), keyname, value, NULL, &status);
if (status != 0) {
throw GException::fits_error(G_DATA_SAVE, status);
}
value[strlen(value)-1] = '\0';
std::string colname = gammalib::strip_whitespace(&(value[1]));
// Check if this column is actually in our list of columns
bool used = false;
for (int i = 0; i < m_cols; ++i) {
if (m_columns[i] != NULL &&
m_columns[i]->nrows() > 0 &&
m_columns[i]->name() == colname) {
used = true;
break;
}
}
// If column is not used then delete it now from FITS table
if (!used) {
// Delete column
status = __ffdcol(FPTR(m_fitsfile), colnum, &status);
if (status != 0) {
throw GException::fits_error(G_DATA_SAVE, status);
}
// Debug option: Log column deletion
#if defined(G_DEBUG_SAVE)
std::cout << "GFitsTable::save: Deleted obsolete column ";
std::cout << value << " from FITS table." << std::endl;
#endif
} // endif: deleted column
} // endfor: Looped over all FITS columns
} // endelse: FITS table has been updated
// Debug option: Show where we are
#if defined(G_DEBUG_SAVE)
std::cout << "GFitsTable::save: Now update the FITS header for all columns.";
std::cout << std::endl;
#endif
// Now update the header for all columns (unit and TDIM information)
for (int i = 0; i < m_cols; ++i) {
// Only consider valid columns
if (m_columns[i] != NULL) {
// Get column number
int colnum = m_columns[i]->colnum();
// Update column units if available
if (m_columns[i]->unit().length() > 0) {
// Build keyname
std::string keyname = "TUNIT"+gammalib::str(colnum);
// Update header card
card(keyname, m_columns[i]->unit(), "physical unit of field");
}
// Write TDIM keyword if available
if (m_columns[i]->dim().size() > 0) {
// Build keyname
std::string keyname = "TDIM"+gammalib::str(colnum);
// Build value string
std::string value = "("+gammalib::str(m_columns[i]->dim()[0]);
for (int k = 1; k < m_columns[i]->dim().size(); ++k) {
value += ","+gammalib::str(m_columns[i]->dim()[k]);
}
value += ")";
// Update header card
card(keyname, value, "dimensions of field");
} // endif: wrote TDIM keyword
} // endif: column was valid
} // endfor: looped over all columns
// Debug definition: Dump method exit
#if defined(G_DEBUG_SAVE)
std::cout << "GFitsTable::save: exit" << std::endl;
#endif
// Return
return;
}
/***********************************************************************//**
* @brief Close table
***************************************************************************/
void GFitsTable::data_close(void)
{
// Free members
free_members();
// Initialise members
init_members();
// Return
return;
}
/***********************************************************************//**
* @brief Connect table data to FITS file
*
* @param[in] vptr FITS file pointer.
*
* Connects the table columns to the file specified by the FITS file pointer.
* This method does nothing if the file pointer in not valid.
***************************************************************************/
void GFitsTable::data_connect(void* vptr)
{
// Continue only if file pointer is valid
if (vptr != NULL) {
// Connect all columns
if (m_columns != NULL) {
for (int i = 0; i < m_cols; ++i) {
if (m_columns[i] != NULL) m_columns[i]->connect(vptr);
}
}
} // endif: file pointer was valid
// Return
return;
}
/***********************************************************************//**
* @brief Returns pointer to column type
*
* @param[in] colnum Column number for which type is to be returned
*
* This methods allocates memory for the character string that holds the
* column type. The client has to de-allocate this memory after usage.
* In case that the column does not exist a NULL pointer is returned.
***************************************************************************/
char* GFitsTable::get_ttype(const int& colnum) const
{
// Initialise result with NULL pointer
char* ptr = NULL;
// Get type only if column exists
if (m_columns != NULL && colnum >=0 && colnum < m_cols &&
m_columns[colnum] != NULL) {
int size = m_columns[colnum]->name().length();
ptr = new char[size+1];
std::strncpy(ptr, m_columns[colnum]->name().c_str(), size);
ptr[size] = '\0';
}
// Return result
return ptr;
}
/***********************************************************************//**
* @brief Returns pointer to column format
*
* @param[in] colnum Column number (starting from 0).
*
* @exception GException::fits_unknown_tabtype
* Table is neither ASCII nor Binary.
*
* This methods allocates memory for the character string that holds the
* column format. The client has to de-allocate this memory after usage.
* In case that the column does not exist a NULL pointer is returned.
***************************************************************************/
char* GFitsTable::get_tform(const int& colnum) const
{
// Initialise result with NULL pointer
char* ptr = NULL;
// Get type only if column exists
if (m_columns != NULL && colnum >=0 && colnum < m_cols &&
m_columns[colnum] != NULL) {
// Get table type specific format
int size;
switch (m_type) {
case GFitsHDU::HT_ASCII_TABLE:
size = m_columns[colnum]->ascii_format().length();
ptr = new char[size+1];
std::strncpy(ptr, m_columns[colnum]->ascii_format().c_str(), size);
break;
case GFitsHDU::HT_BIN_TABLE:
size = m_columns[colnum]->tform_binary().length();
ptr = new char[size+1];
std::strncpy(ptr, m_columns[colnum]->tform_binary().c_str(), size);
break;
default:
throw GException::fits_unknown_tabtype(G_GET_TFORM, m_type);
break;
}
ptr[size] = '\0';
}
// Return result
return ptr;
}
/***********************************************************************//**
* @brief Returns pointer to column unit
*
* @param[in] colnum Column number (starting from 0).
*
* This methods allocates memory for the character string that holds the
* column unit. The client has to de-allocate this memory after usage.
* In case that the column does not exist a NULL pointer is returned.
***************************************************************************/
char* GFitsTable::get_tunit(const int& colnum) const
{
// Initialise result with NULL pointer
char* ptr = NULL;
// Get type only if column exists
if (m_columns != NULL && colnum >=0 && colnum < m_cols &&
m_columns[colnum] != NULL) {
int size = m_columns[colnum]->unit().length();
ptr = new char[size+1];
std::strncpy(ptr, m_columns[colnum]->unit().c_str(), size);
ptr[size] = '\0';
}
// Return result
return ptr;
}
/*==========================================================================
= =
= Private methods =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Initialise class members
***************************************************************************/
void GFitsTable::init_members(void)
{
// Initialise members
m_type = -1;
m_rows = 0;
m_cols = 0;
m_columns = NULL;
// Return
return;
}
/***********************************************************************//**
* @brief Copy class members
*
* @param[in] table Table to copy
***************************************************************************/
void GFitsTable::copy_members(const GFitsTable& table)
{
// Copy attributes
m_type = table.m_type;
m_rows = table.m_rows;
m_cols = table.m_cols;
// Copy column definition
if (table.m_columns != NULL && m_cols > 0) {
m_columns = new GFitsTableCol*[m_cols];
for (int i = 0; i < m_cols; ++i) {
if (table.m_columns[i] != NULL) {
m_columns[i] = table.m_columns[i]->clone();
}
else {
m_columns[i] = NULL;
}
}
}
// Return
return;
}
/***********************************************************************//**
* @brief Delete class members
*
* De-allocates all column pointers
***************************************************************************/
void GFitsTable::free_members(void)
{
// Free memory
if (m_columns != NULL) {
for (int i = 0; i < m_cols; ++i) {
if (m_columns[i] != NULL) delete m_columns[i];
}
delete [] m_columns;
}
// Mark memory as freed
m_columns = NULL;
// Return
return;
}
/***********************************************************************//**
* @brief Allocates column
*
* @param[in] typecode cfitsio type code
*
* Allocates a table column depending on the cfitsio type code. If the type
* code is not found then return a NULL pointer.
***************************************************************************/
GFitsTableCol* GFitsTable::alloc_column(int typecode) const
{
// Initialise table column pointer
GFitsTableCol* ptr = NULL;
// Allocate column
switch (std::abs(typecode)) {
case __TBIT:
ptr = new GFitsTableBitCol;
break;
case __TBYTE:
ptr = new GFitsTableByteCol;
break;
case __TLOGICAL:
ptr = new GFitsTableBoolCol;
break;
case __TSTRING:
ptr = new GFitsTableStringCol;
break;
case __TUSHORT:
ptr = new GFitsTableUShortCol;
break;
case __TSHORT:
ptr = new GFitsTableShortCol;
break;
case __TULONG:
ptr = new GFitsTableULongCol;
break;
case __TLONG:
ptr = new GFitsTableLongCol;
break;
case __TFLOAT:
ptr = new GFitsTableFloatCol;
break;
case __TLONGLONG:
ptr = new GFitsTableLongLongCol;
break;
case __TDOUBLE:
ptr = new GFitsTableDoubleCol;
break;
case __TCOMPLEX:
ptr = new GFitsTableCFloatCol;
break;
case __TDBLCOMPLEX:
ptr = new GFitsTableCDoubleCol;
break;
default:
break;
}
// Return
return ptr;
}
/***********************************************************************//**
* @brief Returns pointer of column with given name
*
* @param[in] colname Name of column.
* @return Pointer to table column (NULL if column has not been found).
*
* Returns a pointer to the column with the specified name. If more columns
* with the same name exist, a pointer to the first of these columns is
* returned. If no column with the specified name is found, a NULL pointer
* will be returned.
***************************************************************************/
GFitsTableCol* GFitsTable::ptr_column(const std::string& colname) const
{
// Initialise pointer
GFitsTableCol* ptr = NULL;
// If there are columns then search for the specified name
if (m_columns != NULL) {
for (int i = 0; i < m_cols; ++i) {
if (m_columns[i]->name() == colname) {
ptr = m_columns[i];
break;
}
}
}
// Return pointer
return ptr;
}
/***********************************************************************//**
* @brief Returns column number of a given column name
*
* @param[in] colname Name of column.
* @return Column number (-1 if column has not been found).
*
* Returns the column number of the column with the specified name. If more
* columns with the same name exist, the first of these columns is returned.
***************************************************************************/
int GFitsTable::colnum(const std::string& colname) const
{
// Initialise column number
int colnum = -1;
// If there are columns then search for the specified name
if (m_columns != NULL) {
for (int i = 0; i < m_cols; ++i) {
if (m_columns[i]->name() == colname) {
colnum = i;
break;
}
}
}
// Return column number
return colnum;
}