/* $Id: casu_wcsutils.c,v 1.3 2015/11/18 20:06:47 jim Exp $
 *
 * This file is part of the CASU Pipeline utilities
 * Copyright (C) 2015 European Southern Observatory
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * $Author: jim $
 * $Date: 2015/11/18 20:06:47 $
 * $Revision: 1.3 $
 * $Name:  $
 */

/* Includes */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <cpl.h>
#include <math.h>

#include "casu_wcsutils.h"
#include "casu_utils.h"

#define WCS_FATAL_ERR(_p) {cpl_msg_error(fctid,"Unable to find keyword %s",_p); cpl_error_reset(); return(wcs);}

/* WCS keywords that should be removed from FITS tables*/

#define SZKEY 9
#define NNOTABKEYS 6
static const char *notabkeys[NNOTABKEYS] = {"^CRVAL[1-2]*","^CRPIX[1-2]*",
                                            "^CD[1-2]*_[1-2]*","^CDELT[1-2]*",
                                            "^CTYPE[1-2]*","^PV[1-9]*_[1-9]*"};

/**
    \ingroup casu_routines
    \defgroup casu_wcsutils casu_wcsutils

    \brief 
    These are support routines used for defining and manipulating image
    and table world coordinate system information.

    \author
    Jim Lewis, CASU

*/

/**@{*/

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_xytoradec
    \par Purpose:
        Convert x,y -> ra,dec
    \par Description:
        A WCS structure is used to convert input x,y coordinates
        to equatorial coordinates.
    \par Language:
        C
    \param wcs
        Input WCS structure
    \param x
        Input X
    \param y
        Input Y
    \param ra
        Output RA
    \param dec
        Output Dec
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

extern void casu_xytoradec(cpl_wcs *wcs, double x, double y, double *ra,
                           double *dec) {
    double *xy,*radec;
    cpl_matrix *from,*to;
    cpl_array *status;

    /* Load up the information */
    
    from = cpl_matrix_new(1,2);
    xy = cpl_matrix_get_data(from);
    xy[0] = x;
    xy[1] = y;

    /* Call the conversion routine */

    cpl_wcs_convert(wcs,from,&to,&status,CPL_WCS_PHYS2WORLD);

    /* Pass it back now */

    radec = cpl_matrix_get_data(to);
    *ra = radec[0];
    *dec = radec[1];

    /* Tidy and exit */

    cpl_matrix_delete(from);
    cpl_matrix_delete(to);
    cpl_array_delete(status);
    return;
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_radectoxy
    \par Purpose:
        Convert ra,dec --> x,y
    \par Description:
        A WCS structure is used to convert input equatorial 
        coordinates to x,y.
    \par Language:
        C
    \param wcs
        Input WCS structure
    \param ra
        Input RA
    \param dec
        Input Dec
    \param x
        Output X
    \param y
        Output Y
    \return   Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

extern void casu_radectoxy(cpl_wcs *wcs, double ra, double dec, double *x,
                           double *y) {
    double *xy,*radec;
    cpl_matrix *from,*to;
    cpl_array *status;

    /* Load up the information */
    
    from = cpl_matrix_new(1,2);
    radec = cpl_matrix_get_data(from);
    radec[0] = ra;
    radec[1] = dec;

    /* Call the conversion routine */

    cpl_wcs_convert(wcs,from,&to,&status,CPL_WCS_WORLD2PHYS);

    /* Pass it back now */

    xy = cpl_matrix_get_data(to);
    *x = xy[0];
    *y = xy[1];

    /* Tidy and exit */

    cpl_matrix_delete(from);
    cpl_matrix_delete(to);
    cpl_array_delete(status);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_xytoxy_list
    \par Purpose:
        Convert x,y on one frame to x,y on another
    \par Description:
        The WCS structures of two images is used to convert a list of
        x,y positions on one image to the frame of reference of the other
    \par Language:
        C
    \param wcs1
        Input WCS structure for the first image
    \param wcs2
        Input WCS structure for the second image
    \param nc
        The number of input coordinates
    \param x_1
        Input X coordinate list
    \param y_1
        Input Y coordinate list
    \param x_2
        The output X coordinate list
    \param y_2
        The output Y coordinate list
    \return   Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

extern void casu_xytoxy_list(cpl_wcs *wcs1, cpl_wcs *wcs2, int nc, double *x_1,
                             double *y_1, double *x_2, double *y_2) {
    double *xy1;
    cpl_matrix *from,*radec;
    cpl_array *status;
    int i;

    /* Load up the information */

    from = cpl_matrix_new(nc,2);
    xy1 = cpl_matrix_get_data(from);
    for (i = 0; i < nc; i++) {
        xy1[2*i] = x_1[i];
        xy1[2*i+1] = y_1[i];
    }

    /* Convert to RA and Dec */

    cpl_wcs_convert(wcs1,from,&radec,&status,CPL_WCS_PHYS2WORLD);
    cpl_array_delete(status);
    cpl_matrix_delete(from);

    /* Now convert to x,y in the reference frame of the second image */

    cpl_wcs_convert(wcs2,radec,&from,&status,CPL_WCS_WORLD2PHYS);
    cpl_array_delete(status);
    cpl_matrix_delete(radec);

    /* No pass it back now */

    xy1 = cpl_matrix_get_data(from);
    for (i = 0; i < nc; i++) {
        x_2[i] = xy1[2*i];
        y_2[i] = xy1[2*i+1];
    }

    /* Tidy and exit */

    cpl_matrix_delete(from);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_radectoxieta
    \par Purpose:
        Convert ra,dec --> xi,eta
    \par Description:
        A WCS structure is used to convert input equatorial 
        coordinates to standard coordinates
    \par Language:
        C
    \param wcs
        Input WCS structure
    \param ra
        Input RA
    \param dec
        Input Dec
    \param xi
        Output xi in radians
    \param eta
        Output eta in radians
    \return
        Nothing
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

extern void casu_radectoxieta(cpl_wcs *wcs, double ra, double dec, 
                              double *xi, double *eta) {

    double *xy,*radec;
    cpl_matrix *from,*to;
    cpl_array *status;

    /* Load up the information */
    
    from = cpl_matrix_new(1,2);
    radec = cpl_matrix_get_data(from);
    radec[0] = ra;
    radec[1] = dec;

    /* Call the conversion routine */

    cpl_wcs_convert(wcs,from,&to,&status,CPL_WCS_WORLD2STD);

    /* Pass it back now */

    xy = cpl_matrix_get_data(to);
    *xi = xy[0]/DEGRAD;
    *eta = xy[1]/DEGRAD;

    /* Tidy and exit */

    cpl_matrix_delete(from);
    cpl_matrix_delete(to);
    cpl_array_delete(status);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_coverage
    \par Purpose:
        Get coverage in ra,dec of a frame 
    \par Description:
        Given a property list (presumably from an input FITS image) this
        routine works out the min and max equatorial coordinates covered by
        the image.
    \par Language:
        C
    \param plist
        Input property list
    \param fudge
        Percentage fudge factor
    \param ra1
         Lower RA
    \param ra2
         Upper RA
    \param dec1
         Lower Dec
    \param dec2
         Upper Dec
    \param status
        Standard input and output casu status variable
    \return 
        Standard casu status variable
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

extern int casu_coverage(cpl_propertylist *plist, int fudge, double *ra1, 
                         double *ra2, double *dec1, double *dec2, 
                         int *status) {

    cpl_wcs *wcs;
    double ra,dec,x,y,dra,ddec,boxfudge,min_4q,max_1q;
    int first_quad,fourth_quad,ni,nj,istep;
    const int *naxes;
    long i,j;
    const cpl_array *a;

    /* Initialise these in case of failure later*/

    *ra1 = 0.0;
    *ra2 = 0.0;
    *dec1 = 0.0;
    *dec2 = 0.0;
    if (*status != CASU_OK)
        return(*status);

    /* Grab the WCS info from the property list */

    wcs = cpl_wcs_new_from_propertylist(plist);
    if (wcs == NULL) 
        FATAL_ERROR

    /* Get the size of the data array */

    a = cpl_wcs_get_image_dims(wcs);
    naxes = cpl_array_get_data_int_const(a);

    /* Find the RA and Dec limits of the image */

    *ra1 = 370.0;
    *ra2 = -370.0;
    *dec1 = 95.0;
    *dec2 = -95.0;
    first_quad = 0;
    fourth_quad = 0;
    min_4q = 370.0;
    max_1q = 0.0;
    istep = 10;
    nj = naxes[1]/istep + 2;
    ni = naxes[0]/istep + 2;
    for (j = 1; j <= nj; j++) {
        y = (double)min(naxes[1],(istep*(j-1)+1));
        for (i = 1; i <= ni; i++) {
            x = (double)min(naxes[0],(istep*(i-1)+1));
            casu_xytoradec(wcs,x,y,&ra,&dec);
            if (ra >= 0.0 && ra <= 90.0) {
                first_quad = 1;
                max_1q = max(ra,max_1q);
            } else if (ra >= 270.0 && ra <= 360.0) {
                fourth_quad = 1;
                min_4q = min((ra-360.0),min_4q);
            }
            *ra1 = min(*ra1,ra);
            *ra2 = max(*ra2,ra);
            *dec1 = min(*dec1,dec);
            *dec2 = max(*dec2,dec);
        }       
    }
    cpl_wcs_delete(wcs);

    /* Now have a look to see if you had RA values in both the first and
       fourth quadrants.  If you have, then make the minimum RA a negative
       value.  This will be the signal to the caller that you have the
       wraparound... */

    if (first_quad && fourth_quad) {
        *ra1 = min_4q;
        *ra2 = max_1q;
    }

    /* Pad out search a bit */

    if (fudge) {
        boxfudge = 0.01*(float)fudge;
        dra = 0.5*boxfudge*(*ra2 - *ra1);
        *ra1 -= dra;
        *ra2 += dra;
        ddec = 0.5*boxfudge*(*dec2 - *dec1);
        *dec1 -= ddec;
        *dec2 += ddec;
    }

    /* Exit */

    GOOD_STATUS
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_crpixshift
    \par Purpose:
        Shift and scale the values of CRPIXn in a header
    \par Description:
        The values of CRPIXn are rescaled by a given factor and offset
        by a given value for each axis. This will fail for anything other
        than 2d images. This routine is useful for output images that have
        been resampled and shifted. The axes will be transformed in the 
        following way: crpixnew[i] = (crpixold[i] - sh[i])/scalefac - 1.0;
    \par Language:
        C
    \param p
        The property list defining the header for the file. This must have
        a valid FITS WCS.
    \param scalefac
        The scaling factor for both axes.
    \param sh
        The shift for both axes
    \return 
        Standard casu status variable
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

extern int casu_crpixshift(cpl_propertylist *p, double scalefac, double sh[]) {
    int i;
    double val;
    char key[SZKEY];
    cpl_type type;
    const char *fctid = "casu_crpixshift";

    /* Make sure that the scale factor isn't zero */

    if (scalefac == 0.0) {
        cpl_msg_error(fctid,"Scale factor is zero!");
        return(CASU_FATAL);
    }

    /* Loop through both axes. Shift and scale the values of crpix */

    for (i = 1; i <= 2; i++) {
        snprintf(key,SZKEY,"CRPIX%d",i);

        /* First make sure the property exists. It damn well better exist! */

        if (cpl_propertylist_has(p,key) == 0) {
            cpl_msg_error(fctid,"Header is missing WCS key %s",key);
            return(CASU_FATAL);
        }

        /* Now find the type... */

        type = cpl_propertylist_get_type(p,key);

        /* Switch for the type. If it's neither float nor double, then 
           signal an error as this is clearly nonsense. */

        switch (type) {
        case CPL_TYPE_FLOAT:
            val = (double)cpl_propertylist_get_float(p,key);
            break;
        case CPL_TYPE_DOUBLE:
            val = cpl_propertylist_get_double(p,key);
            break;
        default:
            cpl_msg_error(fctid,"Header has WCS key %s as non-floating point!",
                          key);
            return(CASU_FATAL);
        }

        /* Right, now do the actual work */

        val = (val - sh[i-1])/scalefac - 1.0;

        /* And update the property now */

        switch (type) {
        case CPL_TYPE_FLOAT:
            cpl_propertylist_update_float(p,key,(float)val);
            break;
        case CPL_TYPE_DOUBLE:
            cpl_propertylist_update_double(p,key,val);
            break;
        default:
            break;
        }
    }
    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_rescalecd
    \par Purpose:
        Scale the values of the CD matrix
    \par Description:
        The values of CDi_j are rescaled by a given amount each. The elements
        will each be multiplied by the same given scale factor. This routine
        is useful for output images that have been resampled.
    \par Language:
        C
    \param p
        The property list defining the header for the file. This must have
        a valid FITS WCS.
    \param scalefac
        The scaling factor for both axes.
    \return 
        Standard casu status variable
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

extern int casu_rescalecd(cpl_propertylist *p, double scalefac) {
    int i,j;
    cpl_type type;
    char key[SZKEY];
    const char *fctid = "casu_rescalecd";
    double val;

    /* Make sure that the scale factor isn't zero */

    if (scalefac == 0.0) {
        cpl_msg_error(fctid,"Scale factor is zero!");
        return(CASU_FATAL);
    }

    /* Loop through both axes. Shift and scale the values of cd */

    for (i = 1; i <= 2; i++) {
        for (j = 1; j <= 2; j++) {
            snprintf(key,SZKEY,"CD%d_%d",i,j);

            /* First make sure the property exists. It damn well better exist! */

            if (cpl_propertylist_has(p,key) == 0) {
                cpl_msg_error(fctid,"Header is missing WCS key %s",key);
                return(CASU_FATAL);
            }

            /* Now find the type... */

            type = cpl_propertylist_get_type(p,key);

            /* Switch for the type. If it's neither float nor double, then 
               signal an error as this is clearly nonsense. */

            switch (type) {
            case CPL_TYPE_FLOAT:
                val = (double)cpl_propertylist_get_float(p,key);
                break;
            case CPL_TYPE_DOUBLE:
                val = cpl_propertylist_get_double(p,key);
                break;
            default:
                cpl_msg_error(fctid,"Header has WCS key %s as non-floating point!",
                              key);
                return(CASU_FATAL);
            }

            /* Right, now do the actual work */

            val *= scalefac;

            /* And update the property now */

            switch (type) {
            case CPL_TYPE_FLOAT:
                cpl_propertylist_update_float(p,key,(float)val);
                break;
            case CPL_TYPE_DOUBLE:
                cpl_propertylist_update_double(p,key,val);
                break;
            default:
                break;
            }
        }
    }
    return(CASU_OK);
}
    
/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_diffxywcs
    \par Purpose:
        Work out the cartesian offsets between two images using their WCSs
    \par Description:
        The wcs of two images is used to define the cartesian offsets between
        them. The sense of the offset is that xoff = xprog - xref.
    \par Language:
        C
    \param wcs
        The wcs structure of the programme image
    \param wcsref
        The wcs structure of the reference image
    \param xoff
        The X offset
    \param yoff
        The Y offset
    \param status
        Standard input and output casu status variable
    \return 
        Standard casu status variable
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

extern int casu_diffxywcs(cpl_wcs *wcs, cpl_wcs *wcsref, float *xoff,
                          float *yoff, int *status) {
    double xc,yc,ra,dec,xnew,ynew;
    const cpl_array *a;
    const int *dims;
    const char *fctid = "casu_diffxywcs";

    /* Inherited status */

    *xoff = 0.0;
    *yoff = 0.0;
    if (*status != CASU_OK) 
        return(*status);

    /* Check for rubbish input */

    if (wcs == NULL || wcsref == NULL) {
        cpl_msg_error(fctid,"NULL wcs information");
        FATAL_ERROR
    }

    /* Work out the ra and dec of the central pixel in the reference image */

    a = cpl_wcs_get_image_dims(wcsref);
    dims = cpl_array_get_data_int_const(a);
    xc = 0.5*(double)dims[0];
    yc = 0.5*(double)dims[1];
    casu_xytoradec(wcsref,xc,yc,&ra,&dec);

    /* Ok, calculate where in x,y space these equatorial coordinates fall on
       the programme image */

    casu_radectoxy(wcs,ra,dec,&xnew,&ynew);

    /* Right, now return the offsets */

    *xoff = (float)(xc - xnew);
    *yoff = (float)(yc - ynew);
    GOOD_STATUS
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_removewcs
    \par Purpose:
        Remove FITS image WCS keywords from a propertylist
    \par Description:
        Remove FITS WCS keywords from a propertylist. This is sometimes
        necessary if a FITS table header has been based on an image header.
    \par Language:
        C
    \param p
        The input propertylist
    \param status
        Standard input and output casu status variable
    \return 
        Standard casu status variable
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

extern int casu_removewcs(cpl_propertylist *p, int *status) {
    int i;
    const char *fctid = "casu_removewcs";

    /* Inherited status */

    if (*status != CASU_OK)
        return(*status);
    if (p == NULL) {
        cpl_msg_error(fctid,"Propertylist passed is NULL\nProgramming error");
        FATAL_ERROR
    }

    /* Loop through all the template keywords and remove them */

    for (i = 0; i < NNOTABKEYS; i++) 
        cpl_propertylist_erase_regexp(p,notabkeys[i],0);

    GOOD_STATUS
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_tabwcs
    \par Purpose:
        Remove FITS image WCS keywords from a propertylist and replace with
        tabular keywords
    \par Description:
        Replace the FITS image WCS keywords in a propertylist with the
        relevant table FITS keyword. This is not very general
    \par Language:
        C
    \param p
        The input propertylist
    \param xcol
        The column number for the X position
    \param ycol
        The column number for the Y position
    \param status
        Standard input and output casu status variable
    \return 
        Standard casu status variable
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

extern int casu_tabwcs(cpl_propertylist *p, int xcol, int ycol, int *status) {
    int i;
    char key[9],key2[9];
    const char *fctid="casu_tabwcs";

    /* Inherited status */

    if (*status != CASU_OK)
        return(*status);
    if (p == NULL) {
        cpl_msg_error(fctid,"Propertylist passed is NULL\nProgramming error");
        FATAL_ERROR
    }

    /* If either of the columns are -1, then just get rid of the image WCS 
       and get out of here */

    if (xcol == -1 || ycol == -1) {
        casu_removewcs(p,status);
        GOOD_STATUS
    }

    /* Go through the standard WCS header keywords one by one
       and translate them.  Start with CTYPE */

    (void)snprintf(key,8,"TCTYP%d",xcol);
    casu_rename_property(p,"CTYPE1",key);
    (void)snprintf(key,8,"TCTYP%d",ycol);
    casu_rename_property(p,"CTYPE2",key);


    /* Now CRVAL */

    (void)snprintf(key,8,"TCRVL%d",xcol);
    casu_rename_property(p,"CRVAL1",key);
    (void)snprintf(key,8,"TCRVL%d",ycol);
    casu_rename_property(p,"CRVAL2",key);

    /* Now CRPIX */

    (void)snprintf(key,8,"TCRPX%d",xcol);
    casu_rename_property(p,"CRPIX1",key);
    (void)snprintf(key,8,"TCRPX%d",ycol);
    casu_rename_property(p,"CRPIX2",key);

    /* Now PV matrix */

    for (i = 1; i <= 5; i++) {
        (void)snprintf(key2,8,"PV2_%d",i);
        (void)snprintf(key,8,"TV%d_%d",ycol,i);
        if (cpl_propertylist_has(p,key2))
            casu_rename_property(p,key2,key);
    }

    /* Now the CD matrix */

    (void)snprintf(key,8,"TC%d_%d",xcol,xcol);
    casu_rename_property(p,"CD1_1",key);
    (void)snprintf(key,8,"TC%d_%d",xcol,ycol);
    casu_rename_property(p,"CD1_2",key);
    (void)snprintf(key,8,"TC%d_%d",ycol,xcol);
    casu_rename_property(p,"CD2_1",key);
    (void)snprintf(key,8,"TC%d_%d",ycol,ycol);
    casu_rename_property(p,"CD2_2",key);

    /* Now get out of here */

    GOOD_STATUS
    
}

/**@}*/

/*

$Log: casu_wcsutils.c,v $
Revision 1.3  2015/11/18 20:06:47  jim
superficial mod

Revision 1.2  2015/08/07 13:06:54  jim
Fixed copyright to ESO

Revision 1.1.1.1  2015/06/12 10:44:32  jim
Initial import

Revision 1.4  2015/05/13 11:47:57  jim
Fixed regular expressions

Revision 1.3  2015/01/29 11:56:27  jim
modified comments

Revision 1.2  2013/11/21 09:38:14  jim
detabbed

Revision 1.1.1.1  2013-08-27 12:07:48  jim
Imported


*/
