/*****************************************************************************

     FILE:  ROTATE.C
     USAGE: ROTATE [control-file-name]

            This program is used to correct the ADCP water and/or bottom
            track velocities in a CODAS database for any misalignment
            between the transducer and the ship's keel.  The control file
            specifies the following:
    -------------------------------------------------------------------------
    DB_NAME:
    LOG_FILE:
    *_RANGE: (where * = BLOCK or TIME or DAY)
    OPTION_LIST:
    *_RANGE:
    OPTION_LIST:
    .
    .
    .
    -------------------------------------------------------------------------
    The DB_NAME specifies the name of the CODAS database (include a path
    if not in the current directory).

    THE LOG_NAME specified the name to be used for the log file.

    The *_RANGE specifies which range of profiles in the
    database to apply the correction to as indicated in the immediately
    succeeding OPTION_LIST.  The range may be specified by
    block numbers (BLOCK_RANGE), profile times (TIME_RANGE), or
    decimal days (DAY_RANGE).  Examples:

        BLOCK_RANGE:  0 to 5
        TIME_RANGE:   90/01/23 12:20:00 to 90/01/31 01:25:00
        DAY_RANGE:    12.5 to 20.2

    If all the profiles in the database will be corrected in the same way,
    type any one of the following:

        BLOCK_RANGE:  all
        TIME_RANGE:   all
        DAY_RANGE:    all

    The OPTION_LIST allows the user to specify which velocity track(s) will
    be corrected and how.
    -------------------------------------------------------------------------
        OPTION_LIST:

                     water_and_bottom_track:
                     water_track:
                     bottom_track:

    -------------------------------------------------------------------------
    If the correction to be applied is identical for both water and bottom
    track velocities, then select water_and_bottom_track.  Otherwise,
    select water_track and/or bottom_track.  The enumeration should be
    terminated by the keyword 'end'.

    Finally, for each _TRACK selected, one can specify one or more of the
    following:
    -------------------------------------------------------------------------
	 time_angle_file:  
         time_0=
         year_base=
         angle_0=
         angle_1=
         angle_2=
         angle_3=
         angle_sin_H=
         angle_cos_H=
         amplitude=
         store_only!
         rotate_only!
         unrotate!

    -------------------------------------------------------------------------
    The time_angle_file: option can be used to specify the name of an
    external text file containing profile times and the rotation angle
    to use with each profile.  The amplitude value to be used is taken from
    the amplitude= option, if specified; otherwise, it defaults to 1.0.

    Note that for these external file options, there should be a one-to-one
    correspondence between the file entries and the database profiles.
    Otherwise, the program will not perform the rotation.

    The time_0, angle_0=, angle_1=, angle_2=, angle_3=,
    angle_sin_H=, angle_cos_H=, and amplitude= options
    allows the user to specify the correction parameters to be used
    in the following rotation equation:

         corrected u = A * [ u * cos(alpha) - v * sin(alpha) ]
         corrected v = A * [ u * sin(alpha) + v * cos(alpha) ]

    where

         A = (amplitude * amplitude from time_angle_amp_file, if any)
	     * scale_factor (for that _TRACK in ANCILLARY_2 structure)
         alpha = (angle + hd_misalign for that _TRACK in ANCILLARY_2 structure)
                 * PI / 180.0
         angle = angle_0 + angle_sin_H * sin(ship_heading in radians) 
                         + angle_cos_H * cos(ship_heading in radians)
                         + angle_1 * (profile_time - time_0)
                         + angle_2 * (profile_time - time_0)^2
                         + angle_3 * (profile_time - time_0)^3
			 + angle from time_angle_file or time_angle_amp_file
         u = original U velocity
         v = original V velocity

    On an unrotated database loaded with loadping, the scale_factor
    and hd_misalign are initialized to 1 and 0, respectively.

    Unless otherwise specified, the following default values are used:

         year_base= the year for the first profile time in the database
         time_0= 0.0
         angle_0= 0.0
         angle_1= 0.0
         angle_2= 0.0
         angle_3= 0.0
         angle_sin_H= 0.0
         angle_cos_H= 0.0
         amplitude= 1.0

    In general, the correction to the database consists of rotating the
    indicated _track velocities and storing the net values of the angle and
    amplitude used for rotation in the ANCILLARY_2 structure.

    If, for some reason, the user wishes to just store the
    angle/amplitude values without rotating the velocities at the same
    time, then the store_only! option should be specified.  In this
    case, the angle and amplitude should NOT be given relative to the
    current values in the ANCILLARY_2 structure; rather, they should be
    the desired values themselves.  Such a situation would arise in the
    instance of databases loaded with older versions of loadping (before
    89/01/03) which did not initialize hd_misalign and scale_factor to 0
    and 1, respectively.

    If, for some reason, the user specifies the rotate_only! option, this
    has the effect of using the angle/amplitude values already in the
    ANCILLARY_2 structure to perform the rotation on the velocities.

    If unrotate! is specified, then the program rotates the indicated
    track velocities back by the appropriate angle and amplitude
    currently stored in the database and resets the angle and amplitude
    back to 0.0 and 1.0, respectively.

    If neither store_only!, rotate_only!, nor unrotate! is specified,
    the program defaults to the typical case of both storing and rotating.

    Again, this list of options must be terminated by another 'end'.


            J. Ranada - 06/09/88 - University of Hawaii JIMAR
            W. Zhu - 01/03/89
            E. Firing - 89/03/24; modified to eliminate the
                  need for an old version, and to operate regardless
                  of whether bottom track data are present.  Changed
                  location of CONFIGURATION update, so that it would
                  occur only if velocity data actually were changed.
            E. Firing Fri  05-12-1989  fixed a major bug that caused
                  only the top 12 bins to be rotated.  This bug was
                  introduced when the bottom track rotation was added.
            J. Ranada 10/02/90 - dropped "round" part of bt rotation
                  formula -- incorrectly adds/subtracts 0.4999999 to/from
                  new bt velocities.
            J. Ranada 12/07/90 - converted to use control file instead of
                  command-line args
            J. Ranada 08/08/91 - Added unrotate! option which restores the
                  user-selected track velocities back to their unrotated values;
                  resets ancillary2 angle and amplitude back to 0 and 1, resp;
                  and unsets the block data processing mask
                  TRANSDUCER_ORIENTATION bit.
            J. Ranada 09/24/91 - Modified to use ancillary_1.mn_heading for
                  heading-related rotation if available (nonzero), else
                  ancillary_2.last_heading
            J. Ranada 12/04/91 - Added time-trend rotation
            J. Ranada 08/14/92 - Fixed major bug in heading-related rotation:
				 failed to convert ship heading to radians
				 prior to taking sine/cosine.
            J. Ranada 02/08/93 - Added option "time_angle_file" to specify a
				 file of ADCP times and rotation angles to
				 use for each profile within the specified
				 time range; note that pass 1 check this
				 file for a 1-1 match with the database profile
				 times and for valid rotation angles (no NaNs!).
            J. Ranada 07/27/93 - Relaxed test for valid rotation angle to
				 be within +/- 180.0 (used to be just 10.0)
				 so as to allow for gyrocompass corrections
            J. Ranada 08/12/94 - Set COMPASS_ERROR bit in data processing mask
				 if water track rotation uses a non-constant phase
				 (time_angle file or heading or time term)

******************************************************************************/

#define MAX_NBINS 128    /* maximum number of bins per profile               */

#include <math.h>
#include <time.h>
#include "geninc.h"
#include "data_id.h"     /* CODAS variable name and ID definitions           */
#include "adcp.h"        /* ANCILLARY_2_TYPE structure declaration           */
#include "use_db.h"      /* set_block_dpmask(), db*_cnf(), *_RANGE, GAP, etc.*/
#include "ioserv.h"      /* error_found()                                    */
#include "matfile.h"     /* isnan() */
#include "dpmask.h"      /* TRANSDUCER_ORIENTATION, NOT_TRANSDUCER_ORIENT.   */

#define NEITHER                0
#define STORE                  1  /* only bit 1 on */
#define ROTATE                 2  /* only bit 2 on */
#define STORE_AND_ROTATE       3  /* both bits 1 and 2 on */
#define UNROTATE               7  /* bits 1, 2, and 3 on */
#define WATER_TRACK            1  /* only bit 1 on */
#define BOTM_TRACK             2  /* only bit 2 on */
#define WATER_AND_BOTM_TRACK   7  /* use same params for both tracks */
#define to_radians(deg)        (deg) * 0.01745329251994  /* PI/180.0 */
#define equal(x, y)            (fabs( (x) - (y) ) <= 0.001 ? 1 : 0)  /* some epsilon */
#define badfloat(x)            ( (x) < ADJ_BADFLOAT ? 0 : 1 )

typedef struct
{
   int option;   /* STORE_AND_ROTATE, ROTATE, or STORE */
   int year_base;
   double time_0;  /* in decimal days */
   double angle_0; /* in degrees */
   double angle_1;
   double angle_2;
   double angle_3;
   double angle_cos_H;
   double angle_sin_H;
   double amp;
   double alfa_deg;
   double alfa_rad;
   FILE  *angle_fp;
} TRACK_TYPE;

#if PROTOTYPE_ALLOWED
int process_range(FILE *fplog, RANGE_TYPE *range, int track_option);
int rotate_water_track(double sinalfa, double cosalfa, double amp);
int set_alfa_and_amp(TRACK_TYPE *track, float *anc2_angle, float *anc2_amp,
   double heading, YMDHMS_TIME_TYPE *prftime);
int rotate_bottom_track(double sinalfa, double cosalfa, double amp);
int read_options(FILE *fp, char *arg, int code);
int is_old(void);
int check_file(FILE *fp, RANGE_TYPE range, int baseyear);
#else
int read_options();
#endif

/*----------------- global variables --------------------*/
TRACK_TYPE water, bottom;
static int year_base;
/*-------------------------------------------------------*/

#if PROTOTYPE_ALLOWED
void do_it(FILE *fp_cnt)
#else
void do_it(fp_cnt)
FILE *fp_cnt;
#endif
{
   int pass;
   unsigned int n = sizeof(YMDHMS_TIME_TYPE);
   YMDHMS_TIME_TYPE prftime;
   FILE_NAME_TYPE dbname, logname;
   char msg[80];
   long range_ofs; /* control file offset to *_RANGE enumeration */
   time_t time_stamp;
   RANGE_TYPE range;
   FILE *fplog;

   static int track_option = 0;

   static OPTION_TYPE track_options[] =
   {
      "end"                    , 0                   , NULL        , (char *) NULL,
      "water_track:"           , WATER_TRACK         , read_options, (char *) &track_option,
      "bottom_track:"          , BOTM_TRACK          , read_options, (char *) &track_option,
      "water_and_bottom_track:", WATER_AND_BOTM_TRACK, read_options, (char *) &track_option,
      NULL                     , 0                   , NULL        , (char *) NULL,
   };

   if (error_found( (fscanf(fp_cnt, " DB_NAME: %s", dbname) != 1),
      "scanning database name\n\n" )) return;
   if (dbopen_cnf(1, dbname, READ_WRITE, DIR_ON_DISK)) return;
   if (dbget_cnf(TIME, (char *) &prftime, &n, "getting time")) return;
   year_base = prftime.year; /* default year_base from 1st db profile time */

   if (error_found( (fscanf(fp_cnt, " LOG_FILE: %s", logname) != 1),
      "scanning log filename\n\n" )) return;
   if (error_found( ( (fplog = fopen(logname, "a+t")) == NULL ),
      "opening log file\n\n" )) return;
   set_msg_file(fplog);
   time(&time_stamp);
   fprintf(fplog, 
      "\n**************************\n %s**************************\n",
      ctime(&time_stamp));
   if (is_old())
   {
      printf("\n ERROR: Database has old configuration/ancillary definitions.");
      printf("\n You must run convadc2 program first to update them OR");
      printf("\n use the old version of the rotate program.");
      goto close;
   }

   for (pass = 1; pass <= 2; pass++)
   {
      sprintf(msg, "\n--------\n Pass %d: ", pass);
      report_msg(msg);
      if (pass == 1) report_msg("Checking control file...\n--------\n");
      else report_msg("Updating database...\n--------\n");
      range_ofs = ftell(fp_cnt); /* will need to rewind for pass 2 */
      while (get_range(fp_cnt, &range) == 0) /* while range found */
      {
         if (pass == 1)
            switch(check_range(&range))
            {
               case GAP:
                  print_range(stdout, &range, "\n WARNING: Gap before range ");
                  print_range(fplog , &range, "\n WARNING: Gap before range ");
                  break;
               case OVERLAP:
                  print_range(stdout, &range, "\n ERROR: Range overlaps preceding => ");
                  print_range(fplog , &range, "\n ERROR: Range overlaps preceding => ");
                  goto close;
               case BAD_RANGE:
                  print_range(stdout, &range, "\n ERROR: Bad range => ");
                  print_range(fplog , &range, "\n ERROR: Bad range => ");
                  goto close;
            }
         fscanf(fp_cnt, " OPTION_LIST: ");
         track_option = water.option = bottom.option = NEITHER; /* initialize */
         if (execute_options(fp_cnt, track_options, ECHO) <= 0) goto close;

         if ( (track_option & WATER_TRACK) && water.angle_fp != NULL )
         {
            if (pass == 1)
            {
               if (check_file(water.angle_fp, range, water.year_base))
                  goto close;
            }
            else
               fseek(water.angle_fp, 0L, SEEK_SET); /* rewind file from previous checking */
         }
         if ( (track_option == BOTM_TRACK) && bottom.angle_fp != NULL )
         {
            if (pass == 1)
            {
               if (check_file(bottom.angle_fp, range, bottom.year_base)) goto close;
            }
            else
               fseek(bottom.angle_fp, 0L, SEEK_SET);
          }

          if (pass == 2)
             if (process_range(fplog, &range, track_option)) goto close;
      } /* end while get_range */
      if (pass == 1)
      {
         if (check_for_gap_at_end(&range) == GAP)
         {
            print_range(stdout, &range, "\n WARNING: Gap after range ");
            print_range(fplog , &range, "\n WARNING: Gap after range ");
         }
         fseek(fp_cnt, range_ofs, SEEK_SET);
      }
   } /* end for pass # */

   close:
      dbclose_cnf();
      puts("\n");
}

/*---------------------------------------------------------------------------

   FUNCTION:  process_range(fplog, range)

      Performs the indicated operation(s) on the water and bottom
      track velocities (as specified in the TRACK_TYPE option)
      over the given <range>.

      It sets bit 2 of the data processing mask for each "rotated" block to
      indicate that the rotation has been performed.

   RETURNS: 0 if okay
            1 if DATABASE ERROR occurs

-----------------------------------------------------------------------------*/
#if PROTOTYPE_ALLOWED
int process_range(FILE *fplog, RANGE_TYPE *range, int track_option)
#else
int process_range(fplog, range, track_option)
FILE *fplog;
RANGE_TYPE *range;
int track_option;
#endif
{
   char                 current[sizeof(YMDHMS_TIME_TYPE)]; /* max. required */
   int                  ierr = 0, nsteps = 1, fetch_mean_heading, fetch_time;
   unsigned int         n_anc2, n, n_anc1, n_time = sizeof(YMDHMS_TIME_TYPE);
   int                  iblkprf[2], prev_blk = -99;
   ANCILLARY_1_TYPE     anc1;
   ANCILLARY_2_TYPE     anc2;
   YMDHMS_TIME_TYPE     prftime;
   double               ship_heading, ship_hdg_rad;
   ULONG                dpmask;
   unsigned int         ndpmask = sizeof(ULONG);

   prftime.year = 0;  /* default */
   print_range(stdout, range, "\n Processing range:  ");
   print_range(fplog, range, "\n Processing range:  ");
   if (goto_start_of_range(range)) return(1);

   fetch_mean_heading = (water.angle_cos_H || water.angle_sin_H ||
                        bottom.angle_cos_H || bottom.angle_sin_H);
   fetch_time = (water.angle_1 || water.angle_2 || water.angle_3 ||
                bottom.angle_1 || bottom.angle_2 || bottom.angle_3 ||
		water.angle_fp || bottom.angle_fp);

   while (in_range(current, range) && !ierr)
   {
      n = 2 * sizeof(int);
      if (dbget_cnf(BLOCK_PROFILE_INDEX, (char *) iblkprf, &n,
         "getting block-profile index")) return(1);
      /*---------------------------------------------------
          IF NEW BLOCK, CHECK STRUCT_DEF and MEAN HEADING
        ---------------------------------------------------*/
      if (iblkprf[0] != prev_blk)
      {
         if (is_old()) return(1);

         /* Note: Following is an accommodation for databases loaded with
                  old version of loadping (before 91/09/13) that
                  erroneously set the anc1.mn_heading to zero - 91/09/24 */

         if (fetch_mean_heading)  /* doing heading-related rotation */
         {
            n_anc1 = sizeof(ANCILLARY_1_TYPE);
            if (dbget_cnf(ANCILLARY_1, (char *) &anc1, &n_anc1, "getting ancillary1"))
               return(1);
            fetch_mean_heading = (anc1.mn_heading != 0.0 && !badfloat(anc1.mn_heading)); /* useful value? */
         }
      }

      n_anc2 = sizeof(ANCILLARY_2_TYPE);
      if (dbget_cnf(ANCILLARY_2, (char *) &anc2, &n_anc2, "getting ancillary2"))
         return(1);

      if (fetch_mean_heading)
      {
         n_anc1 = sizeof(ANCILLARY_1_TYPE);
         if (dbget_cnf(ANCILLARY_1, (char *) &anc1, &n_anc1, "getting ancillary1"))
            return(1);
         ship_heading = (double) anc1.mn_heading;
      }
      else
         ship_heading = (double) anc2.last_heading;
      ship_hdg_rad = to_radians(ship_heading);

      if (fetch_time) /* doing time trend rotation or using file with time+angle */
      {
         if (range->type == TIME_RANGE)
            move_byte((char *) current, (char *) &prftime, sizeof(YMDHMS_TIME_TYPE));
         else
            if (dbget_cnf(TIME, (char *) &prftime, &n_time, "getting time"))
               return(1);
      }

      if (track_option & WATER_TRACK)  /* get water track params */
      {
         if (set_alfa_and_amp(&water, &(anc2.watrk_hd_misalign),
            &(anc2.watrk_scale_factor), ship_hdg_rad, &prftime)) return(1);
         if (water.option & ROTATE)
            if (rotate_water_track(sin(water.alfa_rad), cos(water.alfa_rad),
               water.amp)) return(1);
      }
      if (track_option == WATER_AND_BOTM_TRACK)   /* use same params */
      {
         if (water.option == STORE)  /* copy water params to bottom params */
         {
            anc2.botrk_hd_misalign  = water.alfa_deg;
            anc2.botrk_scale_factor = water.amp;
         }
         else 
	    if (water.option & STORE)  /* add water params to bottom -- cumulative */
            {
               anc2.botrk_hd_misalign += water.alfa_deg;
               anc2.botrk_scale_factor *= water.amp;
            }
         if (water.option & ROTATE)
            if (rotate_bottom_track(sin(water.alfa_rad), cos(water.alfa_rad),
               water.amp)) return(1);
      }
      else
      {
         if (track_option & BOTM_TRACK)  /* get bottom track params */
         {
            if (set_alfa_and_amp(&bottom, &(anc2.botrk_hd_misalign),
               &(anc2.botrk_scale_factor), ship_hdg_rad, &prftime)) return(1);
            if (bottom.option & ROTATE)
               if (rotate_bottom_track(sin(bottom.alfa_rad), cos(bottom.alfa_rad),
                  bottom.amp)) return(1);
         }
      }

      if (water.option & STORE || bottom.option & STORE) /* must rewrite anc2 */
         if (dbput_cnf(ANCILLARY_2, (char *) &anc2, &n_anc2,
            "putting ancillary")) return(1);

      /*-----------------------------------
          IF NEW BLOCK, SET/UNSET DP MASK
        -----------------------------------*/
      if (iblkprf[0] != prev_blk)
      {
         prev_blk = iblkprf[0];
         if (water.option == UNROTATE)
	 {
	    if (dbget_cnf(DATA_PROC_MASK, (char *) &dpmask, &ndpmask, 
	       "getting data processing mask")) return(1);
            set_long_bit((FILE *) NULL, (char *) &dpmask, NOT_TRANSDUCER_ORIENTATION);
            set_long_bit((FILE *) NULL, (char *) &dpmask, NOT_COMPASS_ERROR);
	    if (dbput_cnf(DATA_PROC_MASK, (char *) &dpmask, &ndpmask,
	       "storing data processing mask")) return(1);
         }
         else 
	 {
	    if (water.option & ROTATE)
	    {
	       if (dbget_cnf(DATA_PROC_MASK, (char *) &dpmask, &ndpmask, 
		  "getting data processing mask")) return(1);
               set_long_bit((FILE *) NULL, (char *) &dpmask, TRANSDUCER_ORIENTATION);
	       if (fetch_mean_heading || fetch_time) /* non-constant rotation */
		  set_long_bit((FILE *) NULL, (char *) &dpmask, COMPASS_ERROR);
	       if (dbput_cnf(DATA_PROC_MASK, (char *) &dpmask, &ndpmask,
		  "storing data processing mask")) return(1);
            }
         }
      }
      /*------------------------
          MOVE TO NEXT PROFILE
        ------------------------*/
      DBMOVE(&nsteps, &ierr);
      if (ierr)
      {
         if (ierr != SEARCH_BEYOND_END)
         {
            DBERROR(&ierr, " In DBMOVE\n\n");
            return(1);
         }
         else
            report_msg("\n END OF DATABASE\n\n");
      }
   } /* while in_range */;
   return(0);
}

/*

    FUNCTION:  read_options

       It reads the user-specified options in the control file for
       each _TRACK specification.

*/
#if PROTOTYPE_ALLOWED
int read_options(FILE *fp_cnt, char *track_option, int code)
#else
int read_options(fp_cnt, track_option, code)
FILE *fp_cnt;
char *track_option;
int code;
#endif
{
   static double amplitude, angle_0, angle_1, angle_2, angle_3,
                 angle_cos_H, angle_sin_H, time_0;
   static int rotate_option;
   static FILE_NAME_TYPE time_angle_file;

   static OPTION_TYPE rotate_options[] =
   {
      "end"             , 0          , NULL        , (char *) NULL,
      "amplitude="      , TYPE_DOUBLE, op_get_param, (char *) &amplitude,
      "year_base="      , TYPE_INT   , op_get_param, (char *) &year_base,
      "time_0="         , TYPE_DOUBLE, op_get_param, (char *) &time_0,
      "angle_0="        , TYPE_DOUBLE, op_get_param, (char *) &angle_0,
      "angle_1="        , TYPE_DOUBLE, op_get_param, (char *) &angle_1,
      "angle_2="        , TYPE_DOUBLE, op_get_param, (char *) &angle_2,
      "angle_3="        , TYPE_DOUBLE, op_get_param, (char *) &angle_3,
      "angle_cos_H="    , TYPE_DOUBLE, op_get_param, (char *) &angle_cos_H,
      "angle_sin_H="    , TYPE_DOUBLE, op_get_param, (char *) &angle_sin_H,
      "time_angle_file:", TYPE_STRING, op_get_param, (char *) time_angle_file,
      "store_only!"     , STORE      , set_code    , (char *) &rotate_option,
      "rotate_only!"    , ROTATE     , set_code    , (char *) &rotate_option,
      "unrotate!"       , UNROTATE   , set_code    , (char *) &rotate_option,
      NULL              , 0          , NULL        , (char *) NULL
   };
 
   if (* (int *) track_option & code)  /* bit is already set */
   {
      report_msg("\n ERROR: Redundant track option in control file");
      exit(-1);
   }
   /* set defaults: */
   amplitude = 1.0;
   time_0 =
      angle_0 = angle_1 = angle_2 = angle_3 =
      angle_cos_H = angle_sin_H = 0.0;
   strcpy(time_angle_file, "");
   rotate_option = STORE_AND_ROTATE;

   *( (int *) track_option) |= code;
   if (execute_options(fp_cnt, rotate_options, ECHO) <= 0) exit(-1);
   switch (code)
   {
      case WATER_TRACK:
      case WATER_AND_BOTM_TRACK:
         water.option      = rotate_option;
         water.amp         = amplitude;
         water.angle_0     = angle_0;
         water.angle_1     = angle_1;
         water.angle_2     = angle_2;
         water.angle_3     = angle_3;
         water.time_0      = time_0;
         water.year_base   = year_base;
         water.angle_cos_H = angle_cos_H;
         water.angle_sin_H = angle_sin_H;
         water.angle_fp    = (strcmp(time_angle_file, "") == 0 ?
                             NULL :
                             check_fopen(time_angle_file, "rt"));
         break;
      case BOTM_TRACK:
         bottom.option      = rotate_option;
         bottom.amp         = amplitude;
         bottom.angle_0     = angle_0;
         bottom.angle_1     = angle_1;
         bottom.angle_2     = angle_2;
         bottom.angle_3     = angle_3;
         bottom.time_0      = time_0;
         bottom.year_base   = year_base;
         bottom.angle_cos_H = angle_cos_H;
         bottom.angle_sin_H = angle_sin_H;
         bottom.angle_fp    = (strcmp(time_angle_file, "") == 0 ?
                              NULL :
                              check_fopen(time_angle_file, "rt"));
         break;
   }
   return(0);
}

#if PROTOTYPE_ALLOWED
int set_alfa_and_amp(TRACK_TYPE *track, float *anc2_angle, float *anc2_amp,
  double ship_hdg_rad, YMDHMS_TIME_TYPE *prftime)
#else
int set_alfa_and_amp(track, anc2_angle, anc2_amp, ship_hdg_rad, prftime)
TRACK_TYPE *track;
float *anc2_angle, *anc2_amp;
double ship_hdg_rad;
YMDHMS_TIME_TYPE *prftime;
#endif
{
   double t, angtime, ang;
   char angbuf[80];

   switch(track->option)
   {
      case STORE:
      case STORE_AND_ROTATE: /* calculate alfa_deg from user-supplied values */
         if (prftime->year)
            t = year_day(prftime, track->year_base) - track->time_0;
         track->alfa_deg = track->angle_0;
         if (track->angle_1 != 0.0)
            track->alfa_deg += track->angle_1 * t;
         if (track->angle_2 != 0.0)
            track->alfa_deg += track->angle_2 * t * t;
         if (track->angle_3 != 0.0)
            track->alfa_deg += track->angle_3 * t * t * t;
         if (track->angle_cos_H != 0.0)
            track->alfa_deg += track->angle_cos_H * cos(ship_hdg_rad);
         if (track->angle_sin_H != 0.0)
            track->alfa_deg += track->angle_sin_H * sin(ship_hdg_rad);
         if (track->angle_fp)
         {
            while (getline_nc(track->angle_fp, angbuf, 80) != EOF &&
               sscanf(angbuf, " %lf %lf", &angtime, &ang) == 2 &&
               !equal(angtime, t));
            /* pass 1 already checked this file so we assume all is OK */
            track->alfa_deg += ang;
         }
         if (track->option == STORE) /* store_only! is not cumulative */
         {
            *anc2_angle = track->alfa_deg;
            *anc2_amp   = track->amp;
         }
         else
         {
            *anc2_angle += track->alfa_deg;
            *anc2_amp   *= track->amp;
         }
         break;
      case ROTATE:
      case UNROTATE: /* alfa_deg & amp must come from anc2 */
         if (*anc2_angle < 180.0 && *anc2_angle > -180.0 &&
             *anc2_amp   <  10.0 && *anc2_amp   >    0.0)
         {
            if (track->option == ROTATE)
            {
               track->alfa_deg = *anc2_angle;
               track->amp      = *anc2_amp;
            }
            else /* unrotate using negative/reciprocal of anc2 values */
            {
               track->alfa_deg = -(*anc2_angle);
               track->amp      = 1.0 / (*anc2_amp);
               *anc2_angle     = 0.0;
               *anc2_amp       = 1.0;
            }
         }
         else
         {
            report_msg("\n ERROR: Misalignment parameters in ancillary_2");
            report_msg("\n        structure are improperly initialized");
            return(1);
         }
         break;
   }
   track->alfa_rad = to_radians(track->alfa_deg);
   return(0);
}

#if PROTOTYPE_ALLOWED
int rotate_water_track(double sinalfa, double cosalfa, double amp)
#else
int rotate_water_track(sinalfa, cosalfa, amp)
double sinalfa, cosalfa, amp;
#endif
{
   unsigned int n_vel = MAX_NBINS * sizeof(SHORT), nvalues, i;
   SHORT        u_vel[MAX_NBINS], v_vel[MAX_NBINS], ui, vi;

   if (sinalfa == 0.0 && cosalfa == 1.0 && amp == 1.0) return(0);
   if (dbget_cnf(U, (char *) u_vel, &n_vel, "getting U velocity")) 
      return(1);
   if (dbget_cnf(V, (char *) v_vel, &n_vel, "getting V velocity")) 
      return(1);

   nvalues = n_vel / sizeof(SHORT);
   for (i = 0; i < nvalues; i++)
   {
      ui = u_vel[i];
      vi = v_vel[i];
      if ((ui != BADSHORT) && (vi != BADSHORT))
      {
         u_vel[i] = round(amp*(ui * cosalfa - vi * sinalfa));
         v_vel[i] = round(amp*(ui * sinalfa + vi * cosalfa));
      }
   }

   if (dbput_cnf(U, (char *) u_vel, &n_vel, "storing U velocity"))
      return(1);
   if (dbput_cnf(V, (char *) v_vel, &n_vel, "storing V velocity"))
      return(1);
   return(0);
}

#if PROTOTYPE_ALLOWED
int rotate_bottom_track(double sinalfa, double cosalfa, double amp)
#else
int rotate_bottom_track(sinalfa, cosalfa, amp)
double sinalfa, cosalfa, amp;
#endif
{
   unsigned int         n_bt = sizeof(BOTTOM_TRACK_TYPE);
   int                  bt_present;
   BOTTOM_TRACK_TYPE    bt;
   float                bt_u, bt_v;

   if (sinalfa == 0.0 && cosalfa == 1.0 && amp == 1.0) return(0);
   bt_present = (!dbget_cnf(BOTTOM_TRACK, (char *) &bt, &n_bt,
      "getting BOTTOM_TRACK") && (n_bt != 0));
   if (bt_present)
   {
      bt_u = bt.u;
      bt_v = bt.v;
      if ((bt_u < ADJ_BADFLOAT) && (bt_v < ADJ_BADFLOAT))
      {  /* 90/10/02 dropped "round" part of formula */
         bt.u = amp*(bt_u * cosalfa - bt_v * sinalfa);
         bt.v = amp*(bt_u * sinalfa + bt_v * cosalfa);
      }
      if (dbput_cnf(BOTTOM_TRACK, (char *) &bt, &n_bt, "storing bottom track"))
         return(1);
   }
   return(0);
}

#if PROTOTYPE_ALLOWED
int is_old(void)
#else
int is_old()
#endif
{
   struct
   {
      STRUCT_DEF_HDR_TYPE  header;
      STRUCT_DEF_ELEM_TYPE elem[23];
   } conf1_def;
   unsigned int n = sizeof(conf1_def);

   strcpy(conf1_def.header.name, "CONFIGURATION_1");
   if (dbget_cnf(STRUCTURE_DEF, (char *) &conf1_def, &n,
      "getting structure definition")) return(1);
   return(strncmp(conf1_def.elem[21].name, "scale_factor", 12) == 0);
}

#if PROTOTYPE_ALLOWED
int check_file(FILE *fpangle, RANGE_TYPE range, int baseyear)
#else
int check_file(fpangle, range, baseyear)
FILE *fpangle;
RANGE_TYPE range;
int baseyear;
#endif
{
   YMDHMS_TIME_TYPE prftime;
   unsigned int n;
   char   angbuf[80];
   double dbtime, angtime, ang;
   char   current[sizeof(YMDHMS_TIME_TYPE)]; /* max. required */

   if (goto_start_of_range(&range)) return(1);
   while (in_range(current, &range))
   {
      n = sizeof(YMDHMS_TIME_TYPE);
      if (dbget_cnf(TIME, (char *) &prftime, &n, "getting time")) return(-1);
      dbtime = year_day(&prftime, baseyear);

      while (getline_nc(fpangle, angbuf, 80) != EOF &&
         sscanf(angbuf, " %lf %lf", &angtime, &ang) == 2 &&
         !equal(angtime, dbtime)); /* match times */
      if (feof(fpangle))
      {
         fprintf(stderr, "\n ERROR: No angle file time to match database time %f\n\n",
            dbtime);
         return(-1);
      }
      if (fabs(ang) > 180.0 || isnan(ang))
      {
         fprintf(stderr, "\n ERROR: Unreasonable angle in file for time %f\n\n",
            angtime);
         return(-1);
      }
      switch(dbmove_cnf(1))
      {
         case 0:  break;
         case SEARCH_BEYOND_END: return(0); break;
         default:  return(-1); break;
      }
   }
   return(0);
}
