/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set sw=2 sts=2 et cin: */
/*
 * This file is part of the MUSE Instrument Pipeline
 * Copyright (C) 2010-2014 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <muse.h>

/*----------------------------------------------------------------------------*/
/**
  @brief    Test program to check that all the functions in muse_astro.c work
            correctly.

  This program explicitely tests
    muse_astro_airmass
    muse_astro_compute_airmass
    muse_astro_posangle
    muse_astro_parangle
    muse_astro_angular_distance
    muse_astro_wavelength_vacuum_to_air
    muse_astro_rvcorr_compute
 */
/*----------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
  UNUSED_ARGUMENTS(argc, argv);
  cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_DEBUG);

  /* construct header using a cleaned up VIMOS exposure with all ESO.TEL *
   * keywords and the FITS comments still present                        */
  cpl_propertylist *header = cpl_propertylist_new();
  cpl_propertylist_append_string(header, "ORIGIN", "ESO"); /* European Southern Observatory */
  cpl_propertylist_append_double(header, "MJD-OBS", 54334.01467877); /* Obs start 2007-08-22T00:21:08.246 */
  cpl_propertylist_append_string(header, "DATE-OBS", "2007-08-22T00:21:08.245"); /* Date of observation */
  cpl_propertylist_append_float(header, "RA", 292.242371); /* 19:28:58.1 RA (J2000) pointing */
  cpl_propertylist_append_float(header, "DEC", -41.57659); /* -41:34:35.7  DEC (J2000) pointing */
  cpl_propertylist_append_float(header, "EQUINOX", 2000.); /* Standard FK5 (years) */
  cpl_propertylist_append_string(header, "RADECSYS", "FK5 " ); /* Coordinate reference frame */
  cpl_propertylist_append_float(header, "LST", 63552.4); /* 17:39:12.400 LST at start */
  cpl_propertylist_append_float(header, "UTC", 1264.); /* 00:21:04.000 UT at start */
  cpl_propertylist_append_float(header, "EXPTIME", 900.); /* Integration time (s) */
  cpl_propertylist_append_string(header, "ESO TEL DID", "ESO-VLT-DIC.TCS"); /* Data dictionary for TEL */
  cpl_propertylist_append_string(header, "ESO TEL ID", "v 1.630+.1.5"); /* TCS version number */
  cpl_propertylist_append_string(header, "ESO TEL DATE", "not set"); /* TCS installation date */
  cpl_propertylist_append_float(header, "ESO TEL ALT", 61.567); /* Alt angle at start (deg) */
  cpl_propertylist_append_float(header, "ESO TEL AZ", 313.323); /* Az angle at start (deg) S=0,W=90 */
  cpl_propertylist_append_float(header, "ESO TEL GEOELEV", 2648.); /* Elevation above sea level (m) */
  cpl_propertylist_append_float(header, "ESO TEL GEOLAT", -24.6268); /* Tel geo latitute (+=North) (deg) */
  cpl_propertylist_append_float(header, "ESO TEL GEOLON", -70.4045); /* Tel geo longitude (+=East) (deg) */
  cpl_propertylist_append_string(header, "ESO TEL OPER", "I, Condor"); /* Telescope Operator */
  cpl_propertylist_append_string(header, "ESO TEL FOCU ID", "NB"); /* Telescope focus station ID */
  cpl_propertylist_append_float(header, "ESO TEL FOCU LEN", 120.); /* Focal length (m) */
  cpl_propertylist_append_float(header, "ESO TEL FOCU SCALE", 1.718); /* Focal scale (arcsec/mm) */
  cpl_propertylist_append_float(header, "ESO TEL FOCU VALUE", -30.407); /* M2 setting (mm) */
  cpl_propertylist_append_float(header, "ESO TEL PARANG START", -62.11); /* Parallactic angle at start (deg) */
  cpl_propertylist_append_float(header, "ESO TEL AIRM START", 1.137); /* Airmass at start */
  cpl_propertylist_append_float(header, "ESO TEL AMBI FWHM START", 1.33); /* Observatory Seeing queried from AS */
  cpl_propertylist_append_float(header, "ESO TEL AMBI PRES START", 743.78); /* Observatory ambient air pressure q */
  cpl_propertylist_append_float(header, "ESO TEL AMBI WINDSP", 6.6); /* Observatory ambient wind speed que */
  cpl_propertylist_append_float(header, "ESO TEL AMBI WINDDIR", 10.); /* Observatory ambient wind directio */
  cpl_propertylist_append_float(header, "ESO TEL AMBI RHUM", 8.); /* Observatory ambient relative humi */
  cpl_propertylist_append_float(header, "ESO TEL AMBI TEMP", 9.9); /* Observatory ambient temperature qu */
  cpl_propertylist_append_float(header, "ESO TEL MOON RA", 247.056109); /* 16:28:13.4 RA (J2000) (deg) */
  cpl_propertylist_append_float(header, "ESO TEL MOON DEC", -27.16877); /* -27:10:07.5 DEC (J2000) (deg) */
  cpl_propertylist_append_float(header, "ESO TEL TH M1 TEMP", 8.69); /* M1 superficial temperature */
  cpl_propertylist_append_string(header, "ESO TEL TRAK STATUS", "NORMAL"); /* Tracking status */
  cpl_propertylist_append_string(header, "ESO TEL DOME STATUS", "FULLY-OPEN"); /* Dome status */
  cpl_propertylist_append_bool(header, "ESO TEL CHOP ST", 0); /* True when chopping is active */
  cpl_propertylist_append_float(header, "ESO TEL TARG ALPHA", 192758.2); /* Alpha coordinate for the target */
  cpl_propertylist_append_float(header, "ESO TEL TARG DELTA", -413432.); /* Delta coordinate for the target */
  cpl_propertylist_append_float(header, "ESO TEL TARG EPOCH", 2000.); /* Epoch */
  cpl_propertylist_append_string(header, "ESO TEL TARG EPOCHSYSTEM", "J"); /* Epoch system (default J=Julian) */
  cpl_propertylist_append_float(header, "ESO TEL TARG EQUINOX", 2000.); /* Equinox */
  cpl_propertylist_append_float(header, "ESO TEL TARG PMA", 0.); /* Proper Motion Alpha */
  cpl_propertylist_append_float(header, "ESO TEL TARG PMD", 0.); /* Proper motion Delta */
  cpl_propertylist_append_float(header, "ESO TEL TARG RADVEL", 0.); /* Radial velocity */
  cpl_propertylist_append_float(header, "ESO TEL TARG PARALLAX", 0.); /* Parallax */
  cpl_propertylist_append_string(header, "ESO TEL TARG COORDTYPE", "M"); /* Coordinate type (M=mean A=apparen */
  cpl_propertylist_append_float(header, "ESO TEL PARANG END", -56.763); /* Parallactic angle at end (deg) */
  cpl_propertylist_append_float(header, "ESO TEL AIRM END", 1.112); /* Airmass at end */
  cpl_propertylist_append_float(header, "ESO TEL AMBI FWHM END", 1.04); /* Observatory Seeing queried from AS */
  cpl_propertylist_append_float(header, "ESO TEL AMBI PRES END", 743.88); /* Observatory ambient air pressure q */
  cpl_propertylist_append_float(header, "ESO TEL AMBI TAU0", 0.001525); /* Average coherence time */
  double exptime = muse_pfits_get_exptime(header),
         airm1 = muse_pfits_get_airmass_start(header),
         airm2 = muse_pfits_get_airmass_end(header);
  double airmass = muse_astro_airmass(header);
  cpl_msg_debug(__func__, "exptime=%f, airmass=%f/%f (interpolated between "
                "%f and %f) / %f (calc)", exptime, (airm1+airm2)/2.,
                1./cos((acos(1./airm1) + acos(1./airm2)) / 2.), airm1, airm2,
                airmass);
  cpl_test(airmass < airm1 && airmass > airm2);
#if 0 /* produce example header from VIMOS IFU input */
  cpl_propertylist_erase_regexp(header,
      "HISTORY|^DATA|^C|PI-COI|OBSERVER|INSTRUME|TELESCOP|PIPEFILE|OBJECT"
      "|ESO QC|ESO DET|ESO PRO|ESO ADA|ESO OCS|ESO DET|ESO TPL|ESO OBS|ESO INS",
      0);
  cpl_propertylist_save(header, "airmass_header.fits", CPL_IO_CREATE);
#endif

  /* should work without airmass headers present */
  double airmass2 = muse_astro_airmass(header);
  cpl_propertylist_erase_regexp(header, "ESO TEL AIRM", 0);
  cpl_msg_debug(__func__, "airmass=%f (computed again)", airmass2);
  cpl_test_abs(airmass, airmass2, DBL_EPSILON);

  /* should work with weird airmass headers present, but should *
   * output warning (check this visually!) and propagate error  */
  cpl_errorstate ps = cpl_errorstate_get();
  cpl_propertylist_append_float(header, "ESO TEL AIRM START", airmass + 0.1);
  cpl_propertylist_append_float(header, "ESO TEL AIRM END", airmass + 0.2);
  cpl_test(cpl_errorstate_is_equal(ps));
  airmass2 = muse_astro_airmass(header);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_abs(airmass, airmass2, DBL_EPSILON);

  /* should still work without relevant headers present, if the airmass header stay */
  cpl_propertylist_erase_regexp(header, "^LST$|^DEC$", 0);
  ps = cpl_errorstate_get();
  airmass2 = muse_astro_airmass(header);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  cpl_msg_debug(__func__, "airmass=%f (start/end average: %f)",
                airmass2, (2. * airmass + 0.3) / 2.);
  /* only check against float accuracy, as the headers were added as floats */
  cpl_test_abs(airmass2, (2. * airmass + 0.3) / 2., FLT_EPSILON);

  /* should fail without all relevant headers present */
  cpl_propertylist_erase_regexp(header, "ESO TEL AIRM", 0);
  ps = cpl_errorstate_get();
  airmass2 = muse_astro_airmass(header);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  cpl_msg_debug(__func__, "airmass=%f (+1: %e)", airmass2, airmass2 + 1.);
  cpl_test(airmass2 == -1.);

  /* check NULL input, too */
  ps = cpl_errorstate_get();
  cpl_test(muse_astro_airmass(NULL) == -1.);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);

  cpl_propertylist_delete(header);
  cpl_msg_debug(__func__, "==================================================");

  /* compare some examples values with those computed by IRAF/astcalc */
  /* First example:
       observatory = "lapalma"
       ra = 0:0:0
       dec = 0:0:0
       date = "2010-09-28"
       ut = 23:40:00
       lst = mst (date, ut, obsdb(observatory, "longitude"))
       airmeff = eairmass(ra, dec, lst, 3600., obsdb(observatory, "latitude"))
       print("    eff1", airmeff)
       airmeff = eairmass(ra, dec, lst, 5400., obsdb(observatory, "latitude"))
       print("    eff2", airmeff)
   */
  double iraf1[] = { 1.154341748999268, 1.150767549592499 };
  airmass = muse_astro_compute_airmass(0., 0., 82741.05, 3600., 28.7583333);
  cpl_test_abs(airmass, iraf1[0], 1e-4);
  cpl_msg_debug(__func__, "exptime=3600., airmass=%f (delta=%e)", airmass,
                airmass - iraf1[0]);

  airmass = muse_astro_compute_airmass(0., 0., 82741.05, 5400., 28.7583333);
  cpl_test_abs(airmass, iraf1[1], 1e-4);
  cpl_msg_debug(__func__, "exptime=5400., airmass=%f (delta=%e)", airmass,
                airmass - iraf1[1]);

  cpl_msg_debug(__func__, "==================================================");

  /* Second example:
       observatory = "esovlt"
       ra = 12:12:12
       dec = 0:0:0
       date = "2012-12-12"
       ut = 12:12:12
       lst = mst(date, ut, obsdb(observatory, "longitude"))
       airmeff = eairmass(ra, dec, lst, 1800., obsdb(observatory, "latitude"))
       print("    eff1", airmeff)
       airmeff = eairmass(ra, dec, lst, 3600., obsdb(observatory, "latitude"))
       print("    eff2", airmeff)
       airmeff = eairmass(ra, dec, lst, 5400., obsdb(observatory, "latitude"))
       print("    eff3", airmeff)
       ra = 18:12:12
       airmeff = eairmass(ra, dec, lst, 7200., obsdb(observatory, "latitude"))
       print("    eff4", airmeff)
       ra = 18:30:00
       airmeff = eairmass(ra, dec, lst, 7200., obsdb(observatory, "latitude"))
       print("    eff5", airmeff)
   */
  double iraf2[] = { 1.139314072854456, 1.165309818587244, 1.200687424281816,
                     2.856629208947552, 3.751736985169669 };

  airmass = muse_astro_compute_airmass(183.05 /* 12h12m12s */, 0.,
                                       46618.36 /* 12h56m58s36 */, 1800.,
                                       -24.6253);
  cpl_test_abs(airmass, iraf2[0], 5e-4);
  cpl_msg_debug(__func__, "exptime=1800., airmass=%f (delta=%e)", airmass,
                airmass - iraf2[0]);

  airmass = muse_astro_compute_airmass(183.05 /* 12h12m12s */, 0.,
                                       46618.36 /* 12h56m58s36 */, 3600.,
                                       -24.6253);
  cpl_test_abs(airmass, iraf2[1], 5e-4);
  cpl_msg_debug(__func__, "exptime=3600., airmass=%f (delta=%e)", airmass,
                airmass - iraf2[1]);

  airmass = muse_astro_compute_airmass(183.05 /* 12h12m12s */, 0.,
                                       46618.36 /* 12h56m58s36 */, 5400.,
                                       -24.6253);
  cpl_test_abs(airmass, iraf2[2], 5e-4);
  cpl_msg_debug(__func__, "exptime=5400., airmass=%f (delta=%e)", airmass,
                airmass - iraf2[2]);

  airmass = muse_astro_compute_airmass(273.05 /* 18h12m12s */, 0.,
                                       46618.36 /* 12h56m58s36 */, 7200.,
                                       -24.6253);
  cpl_test_abs(airmass, iraf2[3], 7e-3);
  cpl_msg_debug(__func__, "exptime=7200., airmass=%f (delta=%e)", airmass,
                airmass - iraf2[3]);

  airmass = muse_astro_compute_airmass(277.50 /* 18h30m00s */, 0.,
                                       46618.36 /* 12h56m58s36 */, 7200.,
                                       -24.6253);
  cpl_test_abs(airmass, iraf2[4], 4e-2);
  cpl_msg_debug(__func__, "exptime=7200., airmass=%f (delta=%e)", airmass,
                airmass - iraf2[4]);

  cpl_msg_debug(__func__, "==================================================");

  /* check some special and failure cases */
  ps = cpl_errorstate_get();
  /* bad RA */
  cpl_test(muse_astro_compute_airmass(-0.1, 0., 46618.36, 7200., -24.6253) == -1.);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test(muse_astro_compute_airmass(360., 0., 46618.36, 7200., -24.6253) == -1.);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  /* bad DEC */
  cpl_test(muse_astro_compute_airmass(277.50, -90.1, 46618.36, 7200., -24.6253) == -1.);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test(muse_astro_compute_airmass(277.50, 90.1, 46618.36, 7200., -24.6253) == -1.);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  /* bad LST */
  cpl_test(muse_astro_compute_airmass(277.50, 0., -0.1, 7200., -24.6253) == -1.);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test(muse_astro_compute_airmass(277.50, 0., 86400.1, 7200., -24.6253) == -1.);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  /* bad latitude */
  cpl_test(muse_astro_compute_airmass(277.50, 0., 46618.36, 7200., -90.1) == -1.);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test(muse_astro_compute_airmass(277.50, 0., 46618.36, 7200., 90.1) == -1.);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  /* bad exptime */
  cpl_test(muse_astro_compute_airmass(277.50, 0., 46618.36, -0.1, -24.6253) == -1.);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  /* bad hour angle to put object below horizon (RA = 10h, LST = 0h and 20h) */
  cpl_test(muse_astro_compute_airmass(150., -24., 0., 1., -24.) == -1.);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_OUTPUT);
  cpl_errorstate_set(ps);
  cpl_test(muse_astro_compute_airmass(150., -24., 72000., 1., -24.) == -1.);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_OUTPUT);
  cpl_errorstate_set(ps);
  /* object below horizon, but only at end of exposure (RA = 10h, LST = 16.0h), *
   * visually check the correct error message!                                  */
  cpl_test(muse_astro_compute_airmass(150., -24., 57600, 3600., -24.) == -1.);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_OUTPUT);
  cpl_errorstate_set(ps);
  /* object below horizon, but only at mid of exposure (RA = 10h, LST = 16.5h); *
   * visually check the correct error message!                                  */
  cpl_test(muse_astro_compute_airmass(150., -24., 59400, 3600., -24.) == -1.);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_OUTPUT);
  cpl_errorstate_set(ps);
  /* special case of strong negative hour angle HA = LST / 240 - RA = -290., *
   * RA = 290. = 19:20:00; this should pass, just for test coverage          */
  airmass = muse_astro_compute_airmass(290., 0., 0., 1., -24.6253);
  cpl_msg_debug(__func__, "airmass with HA = -290: %f", airmass);
  cpl_test_abs(airmass, 3.196871455072828, 8e-3);
  cpl_test(cpl_errorstate_is_equal(ps));

  cpl_msg_debug(__func__, "==================================================");

  /* test muse_astro_posangle();                                *
   * use the header of the INM data of PreDryRun2 of 2012-09-27 */
  header = cpl_propertylist_new();
  cpl_propertylist_append_float(header, "ESO INS DROT POSANG", 30.); /* Position angle (deg) */
  cpl_propertylist_append_string(header, "ESO INS DROT MODE", "OFF"); /*Instrument derotator mode. */
  ps = cpl_errorstate_get();
  double posang = muse_astro_posangle(header);
  cpl_test_abs(posang, 30., DBL_EPSILON);
  cpl_test(cpl_errorstate_is_equal(ps));
  /* now it should fail without the actual POSANG entry, *
   * because INS.DROT.START is missing in that INM data  */
  cpl_propertylist_erase_regexp(header, "ESO INS DROT POSANG", 0);
  ps = cpl_errorstate_get();
  posang = muse_astro_posangle(header);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(ps);
  cpl_test(posang == 0.);
  cpl_propertylist_delete(header);

  /* try again using the header of the INM data of the secret DryRun of 2013-04-16 */
  header = cpl_propertylist_new();
  cpl_propertylist_append_float(header, "ESO INS DROT POSANG", 0.); /* Position angle (deg) */
  cpl_propertylist_append_string(header, "ESO INS DROT MODE", "OFF"); /* Instrument derotator mode. */
  /* try with full headers */
  ps = cpl_errorstate_get();
  posang = muse_astro_posangle(header);
  cpl_test_abs(posang, 0., DBL_EPSILON);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_propertylist_delete(header);

  /* try again, with an example from a real dataset with PA=0 */
  header = cpl_propertylist_new();
  cpl_propertylist_append_string(header, "ESO INS DROT MODE", "SKY");
  cpl_propertylist_append_float(header, "ESO INS DROT POSANG", 0.);
  ps = cpl_errorstate_get();
  cpl_msg_info("", "_____ realposang0sky _______________________________________________");
  posang = muse_astro_posangle(header); /* verify visually, that no warning appears */
  cpl_msg_info("", "^^^^^ realposang0sky ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_abs(posang, 0., DBL_EPSILON);
  /* again with weird INS.DROT.MODE */
  cpl_propertylist_update_string(header, "ESO INS DROT MODE", "UNKNOWN");
  ps = cpl_errorstate_get();
  cpl_msg_info("", "_____ realposang0UNKNOWN _______________________________________________");
  posang = muse_astro_posangle(header); /* verify visually, that the warning now appears */
  cpl_msg_info("", "^^^^^ realposang0UNKNOWN ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_abs(posang, 0., DBL_EPSILON);
  /* again without INS.DROT.MODE */
  cpl_propertylist_erase(header, "ESO INS DROT MODE");
  ps = cpl_errorstate_get();
  cpl_msg_info("", "_____ realposang0NONE _______________________________________________");
  posang = muse_astro_posangle(header); /* verify visually, that the warning still appears */
  cpl_msg_info("", "^^^^^ realposang0NONE ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(ps);
  cpl_test_abs(posang, 0., DBL_EPSILON);
  cpl_propertylist_delete(header);

  /* try again, with an example from a real dataset with PA=150. */
  header = cpl_propertylist_new();
  cpl_propertylist_append_string(header, "DATE-OBS", "2014-02-22T04:16:40.837");
  cpl_propertylist_append_string(header, "OBJECT", "NGC3201-off1");
  cpl_propertylist_append_string(header, "ESO INS DROT MODE", "SKY");
  cpl_propertylist_append_float(header, "ESO INS DROT POSANG", 150.);
  ps = cpl_errorstate_get();
  cpl_msg_info("", "_____ realposang150SKY _______________________________________________");
  posang = muse_astro_posangle(header);
  cpl_msg_info("", "^^^^^ realposang150SKY ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
  cpl_errorstate_set(ps);
  cpl_test_abs(posang, -150., DBL_EPSILON); /* we need it with reverse sign internally */
  /* again with other possible INS.DROT.MODE */
  cpl_propertylist_update_string(header, "ESO INS DROT MODE", "STAT");
  ps = cpl_errorstate_get();
  cpl_msg_info("", "_____ realposang150STAT _______________________________________________");
  posang = muse_astro_posangle(header); /* no warning for STAT mode, either */
  cpl_msg_info("", "^^^^^ realposang150STAT ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
  cpl_test_abs(posang, 150., DBL_EPSILON); /* now with original sign */
  /* again with weird INS.DROT.MODE */
  cpl_propertylist_update_string(header, "ESO INS DROT MODE", "UNKNOWN");
  ps = cpl_errorstate_get();
  cpl_msg_info("", "_____ realposang150UNKNOWN _______________________________________________");
  posang = muse_astro_posangle(header); /* verify visually, that the warning now appears */
  cpl_msg_info("", "^^^^^ realposang150UNKNOWN ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
  cpl_test_abs(posang, 150., DBL_EPSILON); /* now with original sign */
  /* now try the failure */
  cpl_propertylist_erase(header, "ESO INS DROT MODE");
  ps = cpl_errorstate_get();
  cpl_msg_info("", "_____ realposang150MISSING _______________________________________________");
  posang = muse_astro_posangle(header);
  cpl_msg_info("", "^^^^^ realposang150MISSING ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
  cpl_test(!cpl_errorstate_is_equal(ps) && /* missing MODE */
           cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(ps);
  cpl_test_abs(posang, 150., DBL_EPSILON);
  cpl_propertylist_delete(header);
  /* with NULL input */
  ps = cpl_errorstate_get();
  posang = muse_astro_posangle(NULL);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_test(posang == 0.);
  cpl_errorstate_set(ps);

  /* test muse_astro_parangle() */
  cpl_propertylist *htest = cpl_propertylist_new();
  /* start with the real case observed in Comm2a (2014-04-28T01:25:11.388) */
  cpl_propertylist_append_double(htest, "ESO TEL PARANG START", -174.915);
  cpl_propertylist_append_double(htest, "ESO TEL PARANG END", 179.183);
  double parang = muse_astro_parangle(htest);
  cpl_msg_debug(__func__, "parang = %.30f", parang);
  cpl_test_rel(parang, -177.866, DBL_EPSILON);
  cpl_propertylist_update_double(htest, "ESO TEL PARANG START", -179.);
  cpl_propertylist_update_double(htest, "ESO TEL PARANG END", 178.);
  parang = muse_astro_parangle(htest);
  cpl_msg_debug(__func__, "parang = %.2f", parang);
  cpl_test_rel(parang, 179.5, DBL_EPSILON);
  cpl_propertylist_update_double(htest, "ESO TEL PARANG START", -178.);
  cpl_propertylist_update_double(htest, "ESO TEL PARANG END", 179.);
  parang = muse_astro_parangle(htest);
  cpl_msg_debug(__func__, "parang = %.2f", parang);
  cpl_test_rel(parang, -179.5, DBL_EPSILON);
  cpl_propertylist_update_double(htest, "ESO TEL PARANG START", -1.);
  cpl_propertylist_update_double(htest, "ESO TEL PARANG END", 2.);
  parang = muse_astro_parangle(htest);
  cpl_msg_debug(__func__, "parang = %f", parang);
  cpl_test_rel(parang, 0.5, DBL_EPSILON);
  cpl_propertylist_update_double(htest, "ESO TEL PARANG START", -2.);
  cpl_propertylist_update_double(htest, "ESO TEL PARANG END", 1.);
  parang = muse_astro_parangle(htest);
  cpl_msg_debug(__func__, "parang = %f", parang);
  cpl_test_rel(parang, -0.5, DBL_EPSILON);
  cpl_propertylist_update_double(htest, "ESO TEL PARANG START", 10.);
  cpl_propertylist_update_double(htest, "ESO TEL PARANG END", 20.);
  parang = muse_astro_parangle(htest);
  cpl_msg_debug(__func__, "parang = %f", parang);
  cpl_test_rel(parang, 15., DBL_EPSILON);
  cpl_propertylist_update_double(htest, "ESO TEL PARANG START", -44.);
  cpl_propertylist_update_double(htest, "ESO TEL PARANG END", -33.);
  parang = muse_astro_parangle(htest);
  cpl_msg_debug(__func__, "parang = %f", parang);
  cpl_test_rel(parang, -38.5, DBL_EPSILON);
  /* failure cases */
  cpl_errorstate es = cpl_errorstate_get();
  parang = muse_astro_parangle(NULL);
  cpl_test(!cpl_errorstate_is_equal(es) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(es);
  cpl_test(parang == 0.);
  cpl_propertylist_update_double(htest, "ESO TEL PARANG START", 10.);
  cpl_propertylist_erase(htest, "ESO TEL PARANG END");
  es = cpl_errorstate_get();
  parang = muse_astro_parangle(htest);
  cpl_test(!cpl_errorstate_is_equal(es) &&
           cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(es);
  cpl_propertylist_erase(htest, "ESO TEL PARANG START");
  cpl_propertylist_append_double(htest, "ESO TEL PARANG END", 20.);
  es = cpl_errorstate_get();
  parang = muse_astro_parangle(htest);
  cpl_test(!cpl_errorstate_is_equal(es) &&
           cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(es);
  cpl_propertylist_delete(htest);

#if 0 /* testing of PARANG behavior through meridian */
  es = cpl_errorstate_get();
  double ha, lat = muse_pfits_get_geolat(NULL) * CPL_MATH_RAD_DEG,
         dec = 13.59557 * CPL_MATH_RAD_DEG,
         dec2 = -33. * CPL_MATH_RAD_DEG,
         dec3 = -85. * CPL_MATH_RAD_DEG;
  cpl_errorstate_set(es);
  for (ha = -5.; ha <= 5.; ha += 0.5) {
    double t = ha * CPL_MATH_RAD_DEG, /* hour angle in rad */
           denom1 = tan(lat) * cos(dec) - sin(dec) * cos(t),
           parang1 = atan2(sin(t), denom1) * CPL_MATH_DEG_RAD,
           denom2 = tan(lat) * cos(dec2) - sin(dec2) * cos(t),
           parang2 = atan2(sin(t), denom2) * CPL_MATH_DEG_RAD,
           denom3 = tan(lat) * cos(dec3) - sin(dec3) * cos(t),
           parang3 = atan2(sin(t), denom3) * CPL_MATH_DEG_RAD;
    cpl_msg_info(__func__, "ha = %4.1f: parang1 = %.3f, parang2 = %.3f, parang3 = %.3f",
                 ha, parang1, parang2, parang3);
  }
  for (ha = 175.; ha <= 185.; ha += 0.5) {
    double t = ha * CPL_MATH_RAD_DEG, /* hour angle in rad */
           denom3 = tan(lat) * cos(dec3) - sin(dec3) * cos(t),
           parang3 = atan2(sin(t), denom3) * CPL_MATH_DEG_RAD;
    cpl_msg_info(__func__, "ha = %.1f: parang3 = %f", ha, parang3);
  }
#endif

  /* test muse_astro_angular_distance();                                       *
   * some trivial cases plus a few cross-checked with skycoor (wcstools-3.8.4) */
  ps = cpl_errorstate_get();
  cpl_test_abs(muse_astro_angular_distance(20., 0., 0., 0.), 20., DBL_EPSILON);
  cpl_test_abs(muse_astro_angular_distance(0., 0., 90., 0.), 90., DBL_EPSILON);
  cpl_test_abs(muse_astro_angular_distance(0., -0.2/3600., 0., 0.2/3600.),
               0.4/3600., DBL_EPSILON);
  cpl_test_abs(muse_astro_angular_distance(0., -45., 0., 45.), 90.,
               100. * DBL_EPSILON);
  cpl_test_abs(muse_astro_angular_distance(175.5, -45., 175.5, 45.), 90.,
               100. * DBL_EPSILON);
  cpl_test_abs(muse_astro_angular_distance(340., -25., 70., -25.),
               79.7114149, FLT_EPSILON);
  cpl_test_abs(muse_astro_angular_distance(10., -80., 130., -80.),
               17.2983302, FLT_EPSILON);
  cpl_test_abs(muse_astro_angular_distance(154.46815, -46.315784, 185.39429, -55.753656)
               * 3600., 76955.588, 4e-4);
  cpl_test_abs(muse_astro_angular_distance(40.173793, -24.221244, 34.662801, -28.530101)
               * 3600., 23584.671, 5e-5);
  cpl_test_abs(muse_astro_angular_distance(154.40629, -46.40866, 154.39712, -46.413931)
               * 3600., 29.633, 4e-4);
  /* nonsense inputs */
  cpl_test_abs(muse_astro_angular_distance(-360., 0., 360., 0.), 0.,
               130. * DBL_EPSILON);
  cpl_test_abs(muse_astro_angular_distance(-180., -90., 180., 90.), 180.,
               DBL_EPSILON);
  cpl_test_abs(muse_astro_angular_distance(-180., -180., 180., 180.), 0.,
               100. * DBL_EPSILON);
  cpl_test(cpl_errorstate_is_equal(ps));

  /* test muse_astro_wavelength_vacuum_to_air():                          *
   * use reference values from NIST for both air and vacuum of the same   *
   * lines in the MUSE wavelength range. Compare them to 10x FLT_EPSILON, *
   * given the listed accuracy of 8 digits this should be a good limit.   */
  ps = cpl_errorstate_get();
  double awav = muse_astro_wavelength_vacuum_to_air(4801.2540);
  cpl_msg_debug(__func__, "AWAV(4801.2540) = %.5f", awav);
  cpl_test_rel(awav, 4799.9123, 10. * FLT_EPSILON);
  awav = muse_astro_wavelength_vacuum_to_air(7347.6941);
  cpl_test_rel(awav, 7345.6704, 10. * FLT_EPSILON);
  awav = muse_astro_wavelength_vacuum_to_air(9165.1667);
  cpl_test_rel(awav, 9162.6520, 10. * FLT_EPSILON);
  cpl_test(cpl_errorstate_is_equal(ps));

  /* test muse_astro_rvcorr_compute() */
  /* start with failure cases: NULL header */
  ps = cpl_errorstate_get();
  muse_astro_rvcorr rvcorr = muse_astro_rvcorr_compute(NULL);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test(rvcorr.bary == 0.);
  cpl_test(rvcorr.helio == 0.);
  cpl_test(rvcorr.geo == 0.);
  /* no data in the header */
  header = cpl_propertylist_new();
  ps = cpl_errorstate_get();
  rvcorr = muse_astro_rvcorr_compute(header);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(ps);
  cpl_test(rvcorr.bary == 0.);
  cpl_test(rvcorr.helio == 0.);
  cpl_test(rvcorr.geo == 0.);
  /* success case: minimally filled header, derived from           *
   * first observation of Tol 1924-416 during MUSE SV in June 2014 */
  cpl_propertylist_append_double(header, "RA", 291.992500); // [deg] 19:27:58.1 RA (J2000) pointing
  cpl_propertylist_append_double(header, "DEC", -41.57556); // [deg] -41:34:31.9 DEC (J2000) pointing
  cpl_propertylist_append_double(header, "EQUINOX", 2000.0); // Standard FK5
  cpl_propertylist_append_double(header, "EXPTIME", 750.0000000); // Integration time
  cpl_propertylist_append_double(header, "MJD-OBS", 56833.16803954); // Obs start
  cpl_propertylist_append_double(header, "LST", 63185.545); // [s] 17:33:05.545 LST
  ps = cpl_errorstate_get();
  rvcorr = muse_astro_rvcorr_compute(header);
  cpl_test(cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_NONE);
  cpl_msg_debug(__func__, "Velocity corrections: %f %f %f km/s",
                rvcorr.bary, rvcorr.helio, rvcorr.geo);
#if 0
  cpl_propertylist_save(header, "header1.fits", CPL_IO_CREATE);
#endif
  /* rvsao/bcvcorr 2.8.2 says
   * vocl> bcvcorr header1.fits year=2014 month=6 day=25 ut=04:01:58.616 obslat=-24.625278 obslon=70.402222 obsalt=2648.
   * gbcvel = 6.7336  ghcvel = 6.7371  geovel = 0.1532
   * bcv = 6.8869  hcv = 6.8903 computed
   */
  cpl_test_abs(rvcorr.bary, 6.8869, 0.01);
  cpl_test_abs(rvcorr.helio, 6.8903, 0.01);
  cpl_test_abs(rvcorr.geo, 0.1532, 0.01);
  /* fully filled header of same observation, with TEL.GEO data */
  cpl_propertylist_insert_string(header, "RA", "TELESCOP", "ESO-VLT-U4"); // ESO <TEL>
  cpl_propertylist_insert_string(header, "TELESCOP", "INSTRUME", "MUSE"); // Instrument used.
  cpl_propertylist_insert_string(header, "INSTRUME", "OBJECT", "ESO338-IG0"); // Original target.
  cpl_propertylist_insert_string(header, "EXPTIME", "RADECSYS", "FK5"); // Coordinate system
  cpl_propertylist_insert_string(header, "LST", "DATE-OBS", "2014-06-25T04:01:58.616"); // Observing date
  cpl_propertylist_insert_double(header, "LST", "UTC", 14509.000); // [s] 04:01:48.000 UTC
  cpl_propertylist_append_double(header, "ESO TEL GEOELEV", 2648.0); // [m] Elevation above sea level
  cpl_propertylist_append_double(header, "ESO TEL GEOLAT", -24.6270); // [deg] Tel geo latitute (+=North)
  cpl_propertylist_append_double(header, "ESO TEL GEOLON", -70.4040); // [deg] Tel geo longitude (+=East)
  ps = cpl_errorstate_get();
  rvcorr = muse_astro_rvcorr_compute(header);
  cpl_test(cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_NONE);
  cpl_msg_debug(__func__, "Velocity corrections: %f %f %f km/s",
                rvcorr.bary, rvcorr.helio, rvcorr.geo);
#if 0
  cpl_propertylist_save(header, "header2.fits", CPL_IO_CREATE);
#endif
  cpl_propertylist_delete(header);
  /* IRAF rvcorrect says:                                                           *
   * ##   HJD          VOBS   VHELIO     VLSR   VDIURNAL   VLUNAR  VANNUAL   VSOLAR *
   * 2456833.67340     0.00     6.79    12.26      0.152    0.007    6.634    5.467 */
  /* rvsao/bcvcorr says
   * gbcvel = 6.7317  ghcvel = 6.7351  geovel = -0.2906
   * bcv = 6.4411  hcv = 6.4445 computed
   */
  cpl_test_abs(rvcorr.bary, 6.441, 1e-4);
  cpl_test_abs(rvcorr.helio, 6.4445, 1e-4);
  cpl_test_abs(rvcorr.geo, -0.2906, 1e-4);
  /* fully filled header of exposure at end of sequence, *
   * still of the same object in June 2014               */
  header = cpl_propertylist_new();
  cpl_propertylist_append_string(header, "TELESCOP", "ESO-VLT-U4"); // ESO <TEL>
  cpl_propertylist_append_string(header, "INSTRUME", "MUSE"); // Instrument used.
  cpl_propertylist_append_string(header, "OBJECT", "ESO338-IG04"); // Original target.
  cpl_propertylist_append_double(header, "RA", 291.993241); // [deg] 19:27:58.3 RA (J2000) pointing
  cpl_propertylist_append_double(header, "DEC", -41.57611); // [deg] -41:34:34.0 DEC (J2000) pointing
  cpl_propertylist_append_double(header, "EQUINOX", 2000.0); // Standard FK5
  cpl_propertylist_append_string(header, "RADECSYS", "FK5"); // Coordinate system
  cpl_propertylist_append_double(header, "EXPTIME", 750.0000000); // Integration time
  cpl_propertylist_append_double(header, "MJD-OBS", 56833.23799131); // Obs start
  cpl_propertylist_append_string(header, "DATE-OBS", "2014-06-25T05:42:42.449"); // Observing date
  cpl_propertylist_append_double(header, "UTC", 20553.000); // [s] 05:42:33.000 UTC
  cpl_propertylist_append_double(header, "LST", 69246.094); // [s] 19:14:06.094 LST
  cpl_propertylist_append_double(header, "ESO TEL GEOELEV", 2648.0); // [m] Elevation above sea level
  cpl_propertylist_append_double(header, "ESO TEL GEOLAT", -24.6270); // [deg] Tel geo latitute (+=North)
  cpl_propertylist_append_double(header, "ESO TEL GEOLON", -70.4040); // [deg] Tel geo longitude (+=East)
  ps = cpl_errorstate_get();
  rvcorr = muse_astro_rvcorr_compute(header);
  cpl_test(cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_NONE);
  cpl_msg_debug(__func__, "Velocity corrections: %f %f %f km/s",
                rvcorr.bary, rvcorr.helio, rvcorr.geo);
#if 0
  cpl_propertylist_save(header, "header3.fits", CPL_IO_CREATE);
#endif
  cpl_propertylist_delete(header);
  /* IRAF rvcorrect says:                                                           *
   * ##   HJD          VOBS   VHELIO     VLSR   VDIURNAL   VLUNAR  VANNUAL   VSOLAR *
   * 2456833.74336     0.00     6.63    12.09      0.019    0.007    6.602    5.467 */
  /* rvsao/bcvcorr says
   * gbcvel = 6.7000  ghcvel = 6.7034  geovel = -0.2091
   * bcv = 6.4909  hcv = 6.4944 computed
   */
  cpl_test_abs(rvcorr.bary, 6.4909, 1e-4);
  cpl_test_abs(rvcorr.helio, 6.4944, 1e-4);
  cpl_test_abs(rvcorr.geo, -0.2091, 1e-4);
  /* fully filled header, this time derived from the observation of the same *
   * object (Tol 1924-416), but from MUSE Comm2B at end of July 2014         */
  header = cpl_propertylist_new();
  cpl_propertylist_append_string(header, "OBJECT", "Tol 1924-416"); // Original target.
  cpl_propertylist_append_double(header, "RA", 291.992792); // [deg] 19:27:58.2 RA (J2000) pointing
  cpl_propertylist_append_double(header, "DEC", -41.57449); // [deg] -41:34:28.1 DEC (J2000) pointing
  cpl_propertylist_append_double(header, "EQUINOX", 2000.0); // Standard FK5
  cpl_propertylist_append_string(header, "RADECSYS", "FK5"); // Coordinate system
  cpl_propertylist_append_double(header, "EXPTIME", 900.0000000); // Integration time
  cpl_propertylist_append_double(header, "MJD-OBS", 56868.14878751); // Obs start
  cpl_propertylist_append_string(header, "DATE-OBS", "2014-07-30T03:34:15.240"); // Observing date
  cpl_propertylist_append_double(header, "UTC", 12846.000); // [s] 03:34:06.000 UTC
  cpl_propertylist_append_double(header, "LST", 69797.461); // [s] 19:23:17.461 LST
  cpl_propertylist_append_double(header, "ESO TEL GEOELEV", 2648.0); // [m] Elevation above sea level
  cpl_propertylist_append_double(header, "ESO TEL GEOLAT", -24.6270); // [deg] Tel geo latitute (+=North)
  cpl_propertylist_append_double(header, "ESO TEL GEOLON", -70.4040); // [deg] Tel geo longitude (+=East)
  ps = cpl_errorstate_get();
  rvcorr = muse_astro_rvcorr_compute(header);
  cpl_test(cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_NONE);
  cpl_msg_debug(__func__, "Velocity corrections: %f %f %f km/s",
                rvcorr.bary, rvcorr.helio, rvcorr.geo);
#if 0
  cpl_propertylist_save(header, "header4.fits", CPL_IO_CREATE);
#endif
  cpl_propertylist_delete(header);
  /* IRAF rvcorrect says:                                                           *
   * ##   HJD          VOBS   VHELIO     VLSR   VDIURNAL   VLUNAR  VANNUAL   VSOLAR *
   * 2456868.65399     0.00    -9.48    -4.01      0.006   -0.010   -9.476    5.468 */
  /* rvsao/bcvcorr says
   * gbcvel = -9.3980  ghcvel = -9.3940  geovel = -0.1980
   * bcv = -9.5960  hcv = -9.5921 computed
   */
  cpl_test_abs(rvcorr.bary, -9.5960, 1e-4);
  cpl_test_abs(rvcorr.helio, -9.5921, 1e-4);
  cpl_test_abs(rvcorr.geo, -0.1980, 1e-4);
  /* fully filled header, again derived from the observation of Tol 1924-416, *
   * but from then end of MUSE Comm2B beginning of August 2014                */
  header = cpl_propertylist_new();
  cpl_propertylist_append_string(header, "OBJECT", "Tol 1924-416"); // Original target.
  cpl_propertylist_append_double(header, "RA", 291.992791); // [deg] 19:27:58.2 RA (J2000) pointing
  cpl_propertylist_append_double(header, "DEC", -41.57483); // [deg] -41:34:29.3 DEC (J2000) pointing
  cpl_propertylist_append_double(header, "EQUINOX", 2000.0); // Standard FK5
  cpl_propertylist_append_string(header, "RADECSYS", "FK5"); // Coordinate system
  cpl_propertylist_append_double(header, "EXPTIME", 900.0000000); // Integration time
  cpl_propertylist_append_double(header, "MJD-OBS", 56872.09891026); // Obs start
  cpl_propertylist_append_string(header, "DATE-OBS", "2014-08-03T02:22:25.846"); // Observing date
  cpl_propertylist_append_double(header, "UTC", 8536.000); // [s] 02:22:16.000 UTC
  cpl_propertylist_append_double(header, "LST", 66421.853); // [s] 18:27:01.853 LST
  cpl_propertylist_append_double(header, "ESO TEL GEOELEV", 2648.0); // [m] Elevation above sea level
  cpl_propertylist_append_double(header, "ESO TEL GEOLAT", -24.6270); // [deg] Tel geo latitute (+=North)
  cpl_propertylist_append_double(header, "ESO TEL GEOLON", -70.4040); // [deg] Tel geo longitude (+=East)
  ps = cpl_errorstate_get();
  rvcorr = muse_astro_rvcorr_compute(header);
  cpl_test(cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_NONE);
  cpl_msg_debug(__func__, "Velocity corrections: %f %f %f km/s",
                rvcorr.bary, rvcorr.helio, rvcorr.geo);
#if 0
  cpl_propertylist_save(header, "header5.fits", CPL_IO_CREATE);
#endif
  cpl_propertylist_delete(header);
  /* IRAF rvcorrect says:                                                           *
   * ##   HJD          VOBS   VHELIO     VLSR   VDIURNAL   VLUNAR  VANNUAL   VSOLAR *
   * 2456872.60398     0.00   -11.13    -5.66      0.083   -0.011  -11.199    5.467 */
  /* rvsao/bcvcorr says
   * gbcvel = -11.1251  ghcvel = -11.1211  geovel = -0.2522
   * bcv = -11.3773  hcv = -11.3733 computed
   */
  cpl_test_abs(rvcorr.bary, -11.3773, 1e-4);
  cpl_test_abs(rvcorr.helio, -11.3733, 1e-4);
  cpl_test_abs(rvcorr.geo, -0.2522, 1e-4);

  return cpl_test_end(0);
}
