// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* 
// ** Copyright UCAR (c) 1990 - 2016                                         
// ** University Corporation for Atmospheric Research (UCAR)                 
// ** National Center for Atmospheric Research (NCAR)                        
// ** Boulder, Colorado, USA                                                 
// ** BSD licence applies - redistribution and use in source and binary      
// ** forms, with or without modification, are permitted provided that       
// ** the following conditions are met:                                      
// ** 1) If the software is modified to produce derivative works,            
// ** such modified software should be clearly marked, so as not             
// ** to confuse it with the version available from UCAR.                    
// ** 2) Redistributions of source code must retain the above copyright      
// ** notice, this list of conditions and the following disclaimer.          
// ** 3) Redistributions in binary form must reproduce the above copyright   
// ** notice, this list of conditions and the following disclaimer in the    
// ** documentation and/or other materials provided with the distribution.   
// ** 4) Neither the name of UCAR nor the names of its contributors,         
// ** if any, may be used to endorse or promote products derived from        
// ** this software without specific prior written permission.               
// ** DISCLAIMER: THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS  
// ** OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED      
// ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.    
// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* 

/*********************************************************************
 * Cedric.cc
 *
 * Object represting a single UF record
 *
 *********************************************************************/
 
#include <didss/LdataInfo.hh>
#include <rapformats/Cedric.hh>
#include <rapformats/DsRadarMsg.hh>
#include <toolsa/DateTime.hh>
#include <toolsa/TaArray.hh>
#include <toolsa/mem.h>
#include <toolsa/str.h>
#include <toolsa/TaFile.hh>
#include <toolsa/Path.hh>
#include <dataport/bigend.h>
#include <dataport/swap.h>
#include <cerrno>
using namespace std;

///////////////
// constructor

Cedric::Cedric()

{
  MEM_zero(_fieldData);
  _debug = false;
  clear();
}

///////////////
// destructor

Cedric::~Cedric()
  
{
  clear();
}

///////////////////////
// clear 

void Cedric::clear()

{
  
  MEM_zero(_fileHdr);
  MEM_zero(_volHdr);
  _levelHdrs.clear();
  _zArray.clear();
  _zScale = 1000.0; // default is meters - CARTESIAN, is 100.0 for PPI
  _dzScale = 1000.0; // meters, or thousand's of a degree
  _needsSwapOnRead = false;

  setBitsDatum(16);
  setBlockingMode(2);
  setBlockSize(3200);
  setMissingDataVal(-32768);
  setScaleFactor(100);
  setAngleFactor(64);
  setIndexNumberRange(1);
  setIndexNumberAzimuth(2);
  setIndexNumberCoplane(3);
  setId("NONE");
  setProgram("NONE");
  setProject("NONE");
  setScientist("NONE");
  setRadar("NONE");
  setCoordType("CRT ");
  setTape("NONE");
  setHeaderRecordLength(sizeof(_volHdr) / sizeof(si16));

  for (int ii = 0; ii < CED_MAX_FIELDS; ii++) {
    if (_fieldData[ii] != NULL) {
      delete[] _fieldData[ii];
      _fieldData[ii] = NULL;
    }
  }

  // always need 1st landmark at ORIGIN

  addLandmark("ORIGIN", 0.0, 0.0, 0.0);

}

//////////////////////////////////////////////////////////////
// Read a cedric file, load up this object
// Returns 0 on success, -1 on failure

int Cedric::readFromPath(const string &path)
  
{

  _pathInUse = path;

  // clear all data
  
  clear();

  // open file
  
  TaFile inFile; // file closes automatically on destruct
  FILE *in;
  if ((in = inFile.fopenUncompress(path.c_str(), "r")) == NULL) {
    int errNum = errno;
    cerr << "ERROR - Cedric::readFromPath" << endl;
    cerr << "  Cannot read path: " << path << endl;
    cerr << "  " << strerror(errNum) << endl;
    return -1;
  }

  // read file header

  if((fread(&_fileHdr, sizeof(_fileHdr), 1, in) != 1)) {
    int errNum = errno;
    cerr << "ERROR - Cedric::readFromPath" << endl;
    cerr << "  Cannot read file header, path: " << path << endl;
    cerr << "  " << strerror(errNum) << endl;
    return -1;
  }
  _swapWords(_fileHdr.vol_label, 25 * 56);

  // check file header
  
  if (strncmp(_fileHdr.id, "CED1", 4) != 0 &&
      strncmp(_fileHdr.id, "D1CE", 4) != 0) {
    cerr << "ERROR - Cedric::readFromPath" << endl;
    cerr << "  File header does not start with 'CED1'" << endl;
    cerr << "  Not a CEDRIC file" << endl;
    return -1;
  }

  // determine whether we need to swap on read

  _needsSwapOnRead = false;
  if (_fileHdr.byte_order == 0) {
    // file is big-endian
    if (!BE_is_big_endian()) {
      _needsSwapOnRead = true;
    }
  } else {
    // file is little-endian
    if (BE_is_big_endian()) {
      _needsSwapOnRead = true;
    }
  }

  if (_needsSwapOnRead) {
    _swap(_fileHdr);
  }
  
  // read volume header
  
  if((fread(&_volHdr, sizeof(_volHdr), 1, in) != 1)) {
    int errNum = errno;
    cerr << "ERROR - Cedric::readFromPath" << endl;
    cerr << "  Cannot read volume header, path: " << path << endl;
    cerr << "  " << strerror(errNum) << endl;
    return -1;
  }
  _swapWords(&_volHdr, sizeof(_volHdr));

  if (_needsSwapOnRead) {
    _swap(_volHdr);
  }

  // set scaling factor for min and max z

  _setZScale();

  // allocate space for field data
  
  int bytesPerField = _volHdr.ny * _volHdr.nx * _volHdr.nz * sizeof(si16);
  for (int ifield = 0; ifield < _volHdr.num_fields; ifield++) {
    _fieldData[ifield] = new si16[bytesPerField];
  }

  // read in each level, which in turn reads in the fields

  for (int iz = 0; iz < _volHdr.nz; iz++) {
    if (_readLevel(in, iz)) {
      cerr << "ERROR - Cedric::readFromPath" << endl;
      return -1;
    }
  }
  
  return 0;

}

//////////////////////////////////////////////////////////////
// Read header and data for a level
// Returns 0 on success, -1 on failure

int Cedric::_readLevel(FILE *in, int levelNum)

{

  // read in level header

  CED_level_head_t levelHdr;
  if((fread(&levelHdr, sizeof(levelHdr), 1, in) != 1)) {
    int errNum = errno;
    cerr << "ERROR - Cedric::readFromPath" << endl;
    cerr << "  Cannot read level header, path: " << _pathInUse << endl;
    cerr << "  Level num: " << levelNum << endl;
    cerr << "  " << strerror(errNum) << endl;
    return -1;
  }

  if (_needsSwapOnRead) {
    _swap(levelHdr);
  }
  _swapWords(&levelHdr, sizeof(levelHdr));

  _levelHdrs.push_back(levelHdr);
  _zArray.push_back(levelHdr.coord);

  // read in data for each plane

  int pointsPerPlane = _volHdr.ny * _volHdr.nx;
  int bytesPerPlane = pointsPerPlane * sizeof(si16);

  for (int ifield = 0; ifield < _volHdr.num_fields; ifield++) {
    si16 *plane = _fieldData[ifield] + levelNum * pointsPerPlane;
    int nread = fread(plane, 1, bytesPerPlane, in);
    if (nread != bytesPerPlane) {
      int errNum = errno;
      cerr << "ERROR - Cedric::_readLevel" << endl;
      cerr << "  Cannot read plane data, nbytes: " << bytesPerPlane << endl;
      cerr << "  File path: " << _pathInUse << endl;
      cerr << "  Field num: " << ifield << endl;
      cerr << "  Plane num: " << levelNum << endl;
      cerr << "  " << strerror(errNum) << endl;
      return -1;
    }
    _swapWords(plane, bytesPerPlane);
    if (_needsSwapOnRead) {
      SWAP_array_16(plane, bytesPerPlane);
    }
  } // ifield

  return 0;
  
}

//////////////////////////////////////////////////////////////
// Write file to specified dir
// Returns 0 on success, -1 on failure

int Cedric::writeToDir(const string &dir,
                       const string &appName,
                       const string &volLabel)
  
{

  if (_debug) {
    cerr << "Writing to dir: " << dir << endl;
  }

  DateTime startTime(getVolStartTime());
  DateTime endTime(getVolEndTime());

  DateTime fileTime = startTime;

  char dayStr[BUFSIZ];
  sprintf(dayStr, "%s%.4d%.2d%.2d", PATH_DELIM,
          fileTime.getYear(), fileTime.getMonth(), fileTime.getDay());
  string outDir(dir);
  outDir += dayStr;

  // compute path
  
  string scanType = "SUR";

  char fileName[BUFSIZ];
  sprintf(fileName,
          "%s_%.4d%.2d%.2d_%.2d%.2d%.2d"
          "_to_%.4d%.2d%.2d_%.2d%.2d%.2d"
          "_%s_%s.ced",
          volLabel.c_str(),
          startTime.getYear(), startTime.getMonth(), startTime.getDay(),
          startTime.getHour(), startTime.getMin(), startTime.getSec(),
          endTime.getYear(), endTime.getMonth(), endTime.getDay(),
          endTime.getHour(), endTime.getMin(), endTime.getSec(),
          getRadar().c_str(), getCoordType().c_str());
  
  char outPath[BUFSIZ];
  sprintf(outPath, "%s%s%s",
          outDir.c_str(), PATH_DELIM, fileName);
  
  if (writeToPath(outPath, volLabel)) {
    cerr << "ERROR - Cedric::writeToPath()" << endl;
  }

  // write latest data info file
  
  LdataInfo ldata(dir);
  if (_debug) {
    ldata.setDebug(true);
  }
  string relPath;
  Path::stripDir(dir, outPath, relPath);
  Path rpath(relPath);
  ldata.setDataFileExt(rpath.getExt());
  ldata.setRelDataPath(relPath);
  ldata.setWriter(appName);
  if (ldata.write(getVolStartTime())) {
    cerr << "WARNING - Cedric::writeToDir" << endl;
    cerr << "  Cannot write latest data info file to dir: "
         << dir << endl;
  }

  return 0;

}

//////////////////////////////////////////////////////////////
// Write file to specified path
// Returns 0 on success, -1 on failure

int Cedric::writeToPath(const string &path,
                        const string &volLabel)
  
{

  if (_debug) {
    cerr << "  vol label: " << volLabel << endl;
  }

  _pathInUse = path;
  
  // create tmp dir

  Path outPath(path);
  Path tmpDir(outPath.getDirectory() + PATH_DELIM + "_tmp");
  Path tmpPath(tmpDir.getPath() + PATH_DELIM + outPath.getFile());
  if (ta_makedir_recurse(tmpPath.getDirectory().c_str())) {
    int errNum = errno;
    cerr << "ERROR - Cedric::writeToPath" << endl;
    cerr << "  Cannot make tmp dir: " << tmpPath.getDirectory() << endl;
    cerr << "  Output path: " << _pathInUse << endl;
    cerr << "  " << strerror(errNum) << endl;
    return -1;
  }

  // open the file

  if (_debug) {
    cerr << "  writing to tmp path: " << tmpPath.getPath() << endl;
  }

  TaFile outFile; // file closes automatically on destruct
  FILE *out;
  if ((out = outFile.fopen(tmpPath.getPath().c_str(), "w")) == NULL) {
    int errNum = errno;
    cerr << "ERROR - Cedric::writeToPath" << endl;
    cerr << "  Cannot open file, path: " << tmpPath.getPath() << endl;
    cerr << "  " << strerror(errNum) << endl;
    return -1;
  }

  // set the file header

  int pointsPerPlane = _volHdr.ny * _volHdr.nx;
  int bytesPerPlane = pointsPerPlane * sizeof(si16);
  int bytesPerField = bytesPerPlane * _volHdr.nz;
  int fileSize = sizeof(_fileHdr) + sizeof(_volHdr) + _volHdr.num_fields * bytesPerField;

  MEM_zero(_fileHdr);
  memcpy(_fileHdr.id, "CED1", 4);
  if (!BE_is_big_endian()) {
    _fileHdr.byte_order = 1;
  }
  _fileHdr.file_size = fileSize;
  _fileHdr.vol_index[0] = sizeof(_fileHdr);
  STRncopy(_fileHdr.vol_label[0], volLabel.c_str(), 56);

  // write the file header
  // must make copy and swap words before write

  CED_file_head_t fileHdr = _fileHdr;
  _swapWords(fileHdr.vol_label[0], sizeof(fileHdr.vol_label[0]));

  if (fwrite(&fileHdr, sizeof(fileHdr), 1, out) != 1) {
    int errNum = errno;
    cerr << "ERROR - Cedric::writeToPath" << endl;
    cerr << "  Cannot write file header, path: " << _pathInUse << endl;
    cerr << "  " << strerror(errNum) << endl;
    return -1;
  }

  // write the volume header
  
  CED_vol_head_t volHdr = _volHdr;
  _swapWords(&volHdr, sizeof(volHdr));

  if (fwrite(&volHdr, sizeof(volHdr), 1, out) != 1) {
    int errNum = errno;
    cerr << "ERROR - Cedric::writeToPath" << endl;
    cerr << "  Cannot write vol header, path: " << _pathInUse << endl;
    cerr << "  " << strerror(errNum) << endl;
    return -1;
  }

  // write the planes

  TaArray<si16> planeCopy_;
  si16 *planeCopy = planeCopy_.alloc(pointsPerPlane);
  for (int iz = 0; iz < _volHdr.nz; iz++ /* , z += _volHdr.dz*/) {

    // write the level header
    
    CED_level_head_t levelCopy = _levelHdrs[iz];
    _swapWords(&levelCopy, sizeof(levelCopy));
    
    if (fwrite(&levelCopy, sizeof(levelCopy), 1, out) != 1) {
      int errNum = errno;
      cerr << "ERROR - Cedric::writeToPath" << endl;
      cerr << "  Cannot write level header, path: " << _pathInUse << endl;
      cerr << "  Level num: " << iz << endl;
      cerr << "  " << strerror(errNum) << endl;
      return -1;
    }

    for (int ifield = 0; ifield < _volHdr.num_fields; ifield++) {
      
      const si16 *ptr = _fieldData[ifield] + iz * pointsPerPlane;
      memcpy(planeCopy, ptr, bytesPerPlane);
      _swapWords(planeCopy, bytesPerPlane);
      if ((int) fwrite(planeCopy, 1, bytesPerPlane, out) != bytesPerPlane) {
        int errNum = errno;
        cerr << "ERROR - Cedric::writeToPath" << endl;
        cerr << "  Cannot write level data, path: " << _pathInUse << endl;
        cerr << "  Level num: " << iz << endl;
        cerr << "  Field num: " << ifield << endl;
        cerr << "  " << strerror(errNum) << endl;
        return -1;
      }

    } // ifield

  } // iz

  outFile.fclose();

  if (_debug) {
    cerr << "  Renaming tmp file to path: " << path << endl;
  }

  // rename the tmp to final output file path

  if (rename(tmpPath.getPath().c_str(), path.c_str())) {
    int errNum = errno;
    cerr << "ERROR - Cedric::writeToPath" << endl;
    cerr << "  Cannot rename tmp path to final path" << endl;
    cerr << "  tmp path: " << tmpPath.getPath() << endl;
    cerr << "  final path: " << path << endl;
    cerr << strerror(errNum) << endl;
    return -1;
  }

  return 0;

}

////////////////////////////////////////////
////////////////////////////////////////////

//////////////////////////////
// add a vertical level

void Cedric::addVlevel(int num, double val,
                       int nfields,
                       int nx, int ny,
                       double nyquist)
{

  if (nfields > CED_MAX_FIELDS) {
    nfields = CED_MAX_FIELDS;
  }

  CED_level_head_t hdr;
  memcpy(hdr.id, "LEVEL ", 6);
  hdr.coord = (si16) floor(val * 1000.0 + 0.5);
  hdr.level_number = num + 1;
  hdr.number_fields = nfields;
  hdr.points_per_plane = nx * ny;
  hdr.records_per_field = (nx * ny * sizeof(si16)) / getBlockSize() + 1;
  hdr.records_per_plane = nfields * hdr.records_per_field;
  hdr.nyquist_vel = (int) floor(nyquist * _volHdr.scale_factor + 0.5);

  setPointsPlane(hdr.points_per_plane);
  setRecordsPlane(hdr.records_per_plane);
  setRecordsField(hdr.records_per_field);

  _levelHdrs.push_back(hdr);
  _zArray.push_back(val);

  setRecordsVolume(hdr.records_per_plane * _levelHdrs.size());
  setTotRecords(getRecordsVolume());

  setNumPlanes(_levelHdrs.size());
  setNz(_levelHdrs.size());
  // setTotalPoints(_levelHdrs.size() * nx * ny);

}

//////////////////////////////
// add a field given floats
// returns 0 on success, -1 on failure

int Cedric::addField(const string &name, const fl32 *data, fl32 missingVal)
{

  if (_volHdr.num_fields >= CED_MAX_FIELDS) {
    cerr << "ERROR - Cedric::addField" << endl;
    cerr << "  Cannot add field, name: " << name << endl;
    cerr << "  Too many fields, max: " << CED_MAX_FIELDS << endl;
    return - 1;
  }
  int fieldNum = _volHdr.num_fields;
  _volHdr.num_fields++;
  int npts = _volHdr.nz * _volHdr.ny * _volHdr.nx;

  // compute max absolute value

  double maxAbsVal = 0;
  for (int ii = 0; ii < npts; ii++) {
    if (data[ii] != missingVal) {
      double absVal = fabs(data[ii]);
      if (absVal > maxAbsVal) maxAbsVal = absVal;
    }
  }
  
  // compute scale using offset of 0
  
  int scale = 100;
  if (maxAbsVal > 0) {
    scale = (int) (32760.0 / maxAbsVal) + 1;
    if (scale > 100) {
      scale = 100;
    }
  }

  // compute si16 values

  si16 *si16Data = new si16[npts];
  for (int ii = 0; ii < npts; ii++) {
    if (data[ii] == missingVal) {
      si16Data[ii] = -32768;
    } else {
      int ival = (int) (data[ii] * scale + 0.5);
      if (ival < -32767) {
        ival = -32767;
      } else if (ival > 32767) {
        ival = 32767;
      }
      si16Data[ii] = (si16) ival;
    }
  }

  _setFieldName(fieldNum, name);
  _setFieldData(fieldNum, si16Data, scale);

  delete[] si16Data;

  if (_debug) {
    cerr << "Adding field: " << name << endl;
  }
  
  return 0;

}

// set field details

void Cedric::_setFieldName(int fieldNum, const string &name)
{
  if (fieldNum >= CED_MAX_FIELDS) {
    return;
  }
  _setString(name, _volHdr.field[fieldNum].field_name, 8);
}

//////////////////////////////
// set field data given shorts

void Cedric::_setFieldData(int fieldNum, const si16 *data, int scale_factor)
{
  if (fieldNum >= CED_MAX_FIELDS) {
    return;
  }
  _volHdr.field[fieldNum].field_sf = scale_factor;
  int npts = _volHdr.nz * _volHdr.ny * _volHdr.nx;
  if (_fieldData[fieldNum] != NULL) {
    delete[] _fieldData[fieldNum];
  }
  _fieldData[fieldNum] = new si16[npts];
  memcpy(_fieldData[fieldNum], data, npts * sizeof(si16));
}

//////////////////////////////
// set field data given floats

void Cedric::_setFieldData(int fieldNum, const fl32 *data, fl32 missingVal)
{

  if (fieldNum >= CED_MAX_FIELDS) {
    return;
  }

  int npts = _volHdr.nz * _volHdr.ny * _volHdr.nx;

  // compute max absolute value

  double maxAbsVal = -1.0e99;
  for (int ii = 0; ii < npts; ii++) {
    double absVal = fabs(data[ii]);
    if (absVal > maxAbsVal) maxAbsVal = absVal;
  }

  // compute scale using offset of 0
  
  int scale = (int) (32760.0 / maxAbsVal) + 1;

  // compute si16 values

  si16 *si16Data = new si16[npts];
  for (int ii = 0; ii < npts; ii++) {
    if (data[ii] == missingVal) {
      si16Data[ii] = -32768;
    } else {
      int ival = (int) (data[ii] * scale + 0.5);
      if (ival < -32767) {
        ival = -32767;
      } else if (ival > 32767) {
        ival = 32767;
      }
      si16Data[ii] = (si16) ival;
    }
  }

  _setFieldData(fieldNum, si16Data, scale);

  delete[] si16Data;
}

//////////////////////////////
// add a landmark
// returns 0 on success, -1 on failure

int Cedric::addLandmark(const std::string &name,
                        double xpos, double ypos, double zpos)
{
  
  if (_volHdr.num_landmarks >= CED_MAX_LANDMARKS) {
    cerr << "ERROR - Cedric::addLandmark" << endl;
    cerr << "  Cannot add landmark, name: " << name << endl;
    cerr << "  Too many landmarks, max: " << CED_MAX_LANDMARKS << endl;
    return - 1;
  }
  int landmarkNum = _volHdr.num_landmarks;
  _volHdr.num_landmarks++;
  
  _setLandmarkName(landmarkNum, name);
  _setLandmarkXpos(landmarkNum, xpos);
  _setLandmarkYpos(landmarkNum, ypos);
  _setLandmarkZpos(landmarkNum, zpos);

  return 0;

}

///////////////////////
// Set landmark details

void Cedric::_setLandmarkName(int landmarkNum, const string &name)
{
  if (landmarkNum >= CED_MAX_LANDMARKS) {
    return;
  }
  _setString(name, _volHdr.landmark[landmarkNum].name, 6);
}

void Cedric::_setLandmarkXpos(int landmarkNum, double val)
{
  if (landmarkNum >= CED_MAX_LANDMARKS) {
    return;
  }
  _setDouble(val, _volHdr.landmark[landmarkNum].x_position);
}

void Cedric::_setLandmarkYpos(int landmarkNum, double val)
{
  if (landmarkNum >= CED_MAX_LANDMARKS) {
    return;
  }
  _setDouble(val, _volHdr.landmark[landmarkNum].y_position);
}

void Cedric::_setLandmarkZpos(int landmarkNum, double val)
{
  if (landmarkNum >= CED_MAX_LANDMARKS) {
    return;
  }
  _setDouble(val, _volHdr.landmark[landmarkNum].z_position);
}

////////////////////////////////////////////
////////////////////////////////////////////
// get methods

// get field name from field number

string Cedric::getFieldName(int fieldNum) const
{
  if (fieldNum >= CED_MAX_FIELDS) {
    return "InvalidField";
  }
  return _getString(_volHdr.field[fieldNum].field_name, 8);
}

// look up field number from the name
// return -1 if not found

int Cedric::getFieldNum(const string &name) const
{
  for (int ii = 0; ii < _volHdr.num_fields; ii++) {
    string cedName = getFieldName(ii);
    if (name == cedName) {
      return ii;
    }
  }
  return -1;
}

double Cedric::getFieldScaleFactor(int fieldNum) const
{
  if (fieldNum >= CED_MAX_FIELDS) {
    return 1.0;
  }
  return (double) _volHdr.field[fieldNum].field_sf;
}

// get field data in place, as si16
// memory remains owned by this object

const si16 *Cedric::getFieldData(int fieldNum) const
{
  if (fieldNum >= CED_MAX_FIELDS) {
    return NULL;
  }
  return  _fieldData[fieldNum];
}

// get field data as fl32 
// memory is allocated
// caller must delete[] the returned array

fl32 *Cedric::getFieldData(int fieldNum, fl32 missingFl32) const
{
  if (fieldNum >= CED_MAX_FIELDS) {
    return NULL;
  }
  int npts = _volHdr.nz * _volHdr.ny * _volHdr.nx;
  fl32 *fdata = new fl32[npts];
  const si16 *sdata = _fieldData[fieldNum];
  fl32 scale = _volHdr.field[fieldNum].field_sf;
  si16 missingSi16 = _volHdr.missing_val;
  for (int ii = 0; ii < npts; ii++) {
    if (sdata[ii] == missingSi16) {
      fdata[ii] = missingFl32;
    } else {
      fdata[ii] = (fl32) sdata[ii] / scale;
    }
  }
  return fdata;
}

string Cedric::getLandmarkName(int landmarkNum) const
{
  if (landmarkNum >= CED_MAX_LANDMARKS) {
    return "InvalidLandmark";
  }
  return _getString(_volHdr.landmark[landmarkNum].name, 6);
}

double Cedric::getLandmarkXposKm(int landmarkNum) const
{
  if (landmarkNum >= CED_MAX_LANDMARKS) {
    return 0.0;
  }
  return _getDouble(_volHdr.landmark[landmarkNum].x_position);
}

double Cedric::getLandmarkYposKm(int landmarkNum) const
{
  if (landmarkNum >= CED_MAX_LANDMARKS) {
    return 0.0;
  }
  return _getDouble(_volHdr.landmark[landmarkNum].y_position);
}

double Cedric::getLandmarkZposKm(int landmarkNum) const
{
  if (landmarkNum >= CED_MAX_LANDMARKS) {
    return 0.0;
  }
  return _getKmDouble(_volHdr.landmark[landmarkNum].z_position);
}

////////////////////////////////////////////
////////////////////////////////////////////
// private methods

////////////////////////////////////////////
// get string from text that is not guaranteed
// to be null-terminated

string Cedric::_getString(const char *text, int maxLen)
{
  char copy[128];
  memcpy(copy, text, maxLen);
  copy[maxLen] = '\0'; // ensure null termination
  for (int ii = maxLen - 1; ii >= 0; ii--) {
    if (isspace(copy[ii])) {
      copy[ii] = '\0';
    } else {
      break;
    }
  }
  return copy;
}
  
////////////////////////////////////////////
// set string in text in volume header

void Cedric::_setString(const string &str,
                        char *text, size_t maxLen)
{
  // initialize with spaces
  for (size_t ii = 0; ii < maxLen; ii++) {
    text[ii] = ' ';
  }
  // copy in text
  if (str.size() < maxLen) {
    memcpy(text, str.c_str(), str.size());
  } else {
    memcpy(text, str.c_str(), maxLen);
  }
}
  
////////////////////////////////////////////
// get time in unix secs

time_t Cedric::_getTime(si16 year, si16 month, si16 day,
                        si16 hour, si16 min, si16 sec) const
{
  if (year < 50) {
    year += 2000;
  } else if (year < 100) {
    year += 1900;
  }
  DateTime dtime(year, month, day, hour, min, sec);
  return dtime.utime();
}
  
////////////////////////////////////////////
// set time in unix secs

void Cedric::_setTime(time_t utime,
                      si16 &year, si16 &month, si16 &day,
                      si16 &hour, si16 &min, si16 &sec)
{
  DateTime dtime(utime);
  year = dtime.getYear();
  if (year > 1999) {
    year -= 2000;
  } else {
    year -= 1900;
  }
  month = dtime.getMonth();
  day = dtime.getDay();
  hour = dtime.getHour();
  min = dtime.getMin();
  sec = dtime.getSec();
}
  
////////////////////////////////////////////
// set coordinate type
// and relevant scaling factors

void Cedric::setCoordType(const std::string &val) {
  _setString(val, _volHdr.scan_mode, 4);
  _setZScale();
}

////////////////////////////////////////////
// set Z scale depending on coordinate type

void Cedric::_setZScale() {
  string coordType = getCoordType();
  if (coordType.find("CRT") != string::npos ||
      coordType.find("LLZ") != string::npos) {
    _zScale = 1000.0; // km to meters
  } else {
    _zScale = 100.0; // degrees uses SF of 100.0
  }
}

////////////////////////////////////////////
// set run time

void Cedric::setDateTimeRun(time_t runTime)
{
  DateTime rtime(runTime);
  char timeStr[32], dateStr[32];
  sprintf(timeStr, "%.2d:%.2d:%.2d",
          rtime.getHour(), rtime.getMin(), rtime.getSec());
  sprintf(dateStr, "%.2d/%.2d/%.2d",
          rtime.getMonth(), rtime.getDay(), rtime.getYear() % 100);
  setDateRun(dateStr);
  setTimeRun(timeStr);
}
  
////////////////////////////////////////////
// convert deg/min/sec into decimal degrees

double Cedric::_getDecDegrees(si16 ideg, si16 imin, si16 isec) const
{
  double ddeg = (double) ideg;
  double dmin = (double) imin;
  double dsec = _getDouble(isec);
  double deg = ddeg + dmin / 60.0 + dsec / 3600.0;
  return deg;
}

////////////////////////////////////////////
// convert decimal degrees into deg/min/sec

void Cedric::_setDegMinSec(double degrees,
                           si16 &ideg, si16 &imin, si16 &isec)
{

  double ddeg = trunc(degrees);
  double dmin = trunc((degrees - ddeg) * 60.0);
  double dsec = (degrees - ddeg - dmin / 60.0) * 3600.0;
  
  ideg = (si16) floor(ddeg + 0.5);
  imin = (si16) floor(dmin + 0.5);
  isec = (si16) floor(dsec * (double) _volHdr.scale_factor + 0.5);

}
  
///////////////
// print object
//

void Cedric::print(ostream &out,
                   bool printData)
  
{

  printMetaData(out);
  
  if (printData) {
    for (int ii = 0; ii < getNumFields(); ii++) {
      printFieldData(ii, out);
    }
  }

}

/////////////////////////
// print headers natively

void Cedric::printNative(ostream &out,
                         bool printData /* = false */)
  
{

  print(_fileHdr, out);
  print(_volHdr, out);
  for (int iz = 0; iz < _volHdr.nz; iz++) {
    out << "============= Level num: " << iz << " ============" << endl;
    print(_levelHdrs[iz], out);
  }

  if (printData) {
    for (int ii = 0; ii < getNumFields(); ii++) {
      printFieldData(ii, out);
    }
  }

}

/////////////////////////////
// print header data

void Cedric::printMetaData(ostream &out)
  
{

  out << "======== CEDRIC VOLUME METADATA =========" << endl;

  out << "  VolNum: " << getVolumeNum() << endl;
  out << "  VolLabel: " << getVolumeLabel() << endl;
  
  out << "  Id: " << getId() << endl;
  out << "  Program: " << getProgram() << endl;
  out << "  Project: " << getProject() << endl;
  out << "  Scientist: " << getScientist() << endl;
  out << "  Radar: " << getRadar() << endl;
  out << "  CoordType: " << getCoordType() << endl;
  out << "  Tape: " << getTape() << endl;

  out << "  TapeStartTime: " << DateTime::strm(getTapeStartTime()) << endl;
  out << "  TapeEndTime: " << DateTime::strm(getTapeEndTime()) << endl;

  out << "  LatitudeDeg: " << getLatitudeDeg() << endl;
  out << "  LongitudeDeg: " << getLongitudeDeg() << endl;
  out << "  OriginHtM: " << getOriginHtM() << endl;
  
  out << "  XaxisAngleFromNDeg: " << getXaxisAngleFromNDeg() << endl;

  out << "  OriginX: " << getOriginX() << endl;
  out << "  OriginY: " << getOriginY() << endl;
  
  out << "  TimeZone: " << getTimeZone() << endl;
  out << "  Sequence: " << getSequence() << endl;
  out << "  Submitter: " << getSubmitter() << endl;
  out << "  DateRun: " << getDateRun() << endl;
  out << "  TimeRun: " << getTimeRun() << endl;

  out << "  TapeEdNumber: " << getTapeEdNumber() << endl;
  out << "  HeaderRecordLength: " << getHeaderRecordLength() << endl;
  
  out << "  Computer: " << getComputer() << endl;

  out << "  BitsDatum: " << getBitsDatum() << endl;
  out << "  BlockingMode: " << getBlockingMode() << endl;
  out << "  BlockSize: " << getBlockSize() << endl;
  out << "  MissingDataVal: " << getMissingDataVal() << endl;
  
  out << "  ScaleFactor: " << getScaleFactor() << endl;
  out << "  AngleFactor: " << getAngleFactor() << endl;
  
  out << "  Source: " << getSource() << endl;

  out << "  TapeLabel2: " << getTapeLabel2() << endl;
  out << "  TapeLabel3: " << getTapeLabel3() << endl;
  out << "  TapeLabel4: " << getTapeLabel4() << endl;
  out << "  TapeLabel5: " << getTapeLabel5() << endl;
  out << "  TapeLabel6: " << getTapeLabel6() << endl;

  out << "  RecordsPlane: " << getRecordsPlane() << endl;
  out << "  RecordsField: " << getRecordsField() << endl;
  out << "  RecordsVolume: " << getRecordsVolume() << endl;
  out << "  TotalRecords: " << getTotalRecords() << endl;
  out << "  TotRecords: " << getTotRecords() << endl;

  out << "  VolName: " << getVolName() << endl;

  out << "  NumPlanes: " << getNumPlanes() << endl;

  out << "  CubicKm: " << getCubicKm() << endl;
  out << "  TotalPoints: " << getTotalPoints() << endl;
  out << "  SamplingDensity: " << getSamplingDensity() << endl;

  out << "  NumPulses: " << getNumPulses() << endl;
  out << "  VolumeNumber: " << getVolumeNumber() << endl;
  
  out << "  VolStartTime: " << DateTime::strm(getVolStartTime()) << endl;
  out << "  VolEndTime: " << DateTime::strm(getVolEndTime()) << endl;
  
  out << "  VolumeTimeSec: " << getVolumeTimeSec() << endl;
  out << "  IndexNumberTime: " << getIndexNumberTime() << endl;

  out << "  MinRangeKm: " << getMinRangeKm() << endl;
  out << "  MaxRangeKm: " << getMaxRangeKm() << endl;

  out << "  NumGatesBeam: " << getNumGatesBeam() << endl;
  out << "  GateSpacingKm: " << getGateSpacingKm() << endl;
  out << "  GateSpacingM: " << getGateSpacingM() << endl;
  out << "  MinGates: " << getMinGates() << endl;
  out << "  MaxGates: " << getMaxGates() << endl;
  out << "  IndexNumberRange: " << getIndexNumberRange() << endl;
  out << "  MinAzimuthDeg: " << getMinAzimuthDeg() << endl;
  out << "  MaxAzimuthDeg: " << getMaxAzimuthDeg() << endl;
  out << "  NumBeamsPlane: " << getNumBeamsPlane() << endl;
  out << "  AveAngleDeg: " << getAveAngleDeg() << endl;
  out << "  MinBeamsPlane: " << getMinBeamsPlane() << endl;
  out << "  MaxBeamsPlane: " << getMaxBeamsPlane() << endl;
  out << "  NumStepsBeam: " << getNumStepsBeam() << endl;
  out << "  IndexNumberAzimuth: " << getIndexNumberAzimuth() << endl;

  out << "  PlaneType: " << getPlaneType() << endl;

  out << "  MinElevDeg: " << getMinElevDeg() << endl;
  out << "  MaxElevDeg: " << getMaxElevDeg() << endl;
  out << "  NumElevs: " << getNumElevs() << endl;
  out << "  AveDeltaElevDeg: " << getAveDeltaElevDeg() << endl;
  out << "  AveElevDeg: " << getAveElevDeg() << endl;
  out << "  Direction: " << getDirection() << endl;
  out << "  BaselineAngleDeg: " << getBaselineAngleDeg() << endl;
  out << "  IndexNumberCoplane: " << getIndexNumberCoplane() << endl;
  
  out << "  MinX: " << getMinX() << endl;
  out << "  MaxX: " << getMaxX() << endl;
  out << "  Nx: " << getNx() << endl;
  out << "  Dx: " << getDx() << endl;
  out << "  FastAxis: " << getFastAxis() << endl;
  
  out << "  MinY: " << getMinY() << endl;
  out << "  MaxY: " << getMaxY() << endl;
  out << "  Ny: " << getNy() << endl;
  out << "  Dy: " << getDy() << endl;
  out << "  MidAxis: " << getMidAxis() << endl;
  
  out << "  MinZ: " << getMinZ() << endl;
  out << "  MaxZ: " << getMaxZ() << endl;
  out << "  Nz: " << getNz() << endl;
  out << "  Dz: " << getDz() << endl;
  out << "  SlowAxis: " << getSlowAxis() << endl;
  
  out << "  NumFields: " << getNumFields() << endl;
  for (int ii = 0; ii < getNumFields(); ii++) {
    out << "    FIELD:" << endl;
    out << "      num: " << ii << endl;
    out << "      name: " << getFieldName(ii) << endl;
    out << "      scale_factor: " << getFieldScaleFactor(ii) << endl;
  } // ii
  // const si16 *getFieldData(int fieldNum) const;

  out << "  PointsPlane: " << getPointsPlane() << endl;
  out << "  NumRadars: " << getNumRadars() << endl;

  out << "  NyquistVel: " << getNyquistVel() << endl;
  out << "  RadarConst: " << getRadarConst() << endl;
  
  out << "  NumLandmarks: " << getNumLandmarks() << endl;
  for (int ii = 0; ii < getNumLandmarks(); ii++) {
    out << "    LANDMARK:" << endl;
    out << "      num: " << ii << endl;
    out << "      name: " << getLandmarkName(ii) << endl;
    out << "      x pos km: " << getLandmarkXposKm(ii) << endl;
    out << "      y pos km: " << getLandmarkYposKm(ii) << endl;
    out << "      z pos km: " << getLandmarkZposKm(ii) << endl;
  } // ii

  out << "=========================================" << endl;

}

/////////////////////////////////////////////////////////
// print with data

void Cedric::printFieldData(int fieldNum, ostream &out) const
  
{

  fl32 missingFl32 = -9999.0;
  fl32 *fdata = getFieldData(fieldNum, missingFl32);
  if (fdata == NULL) {
    out << "WARNING - invalid field num: " << fieldNum << endl;
    return;
  }

  // compute min and max

  int nptsPlane = _volHdr.ny * _volHdr.nx;
  double minVal = 1.0e99;
  double maxVal = -1.0e99;
  for (int iz = 0; iz < _volHdr.nz; iz++) {
    const fl32 *planeData = fdata + iz * nptsPlane;
    for (int ii = 1; ii < nptsPlane; ii++) {
      fl32 val = planeData[ii];
      if (val != missingFl32) {
        if (val > maxVal) {
          maxVal = val;
        }
        if (val < minVal) {
          minVal = val;
        }
      }
    } // ii
  }

  out << "================== Data ===================" << endl;
  out << "==>> Field name: " << getFieldName(fieldNum) << endl;
  out << "     minVal: " << minVal << endl;
  out << "     maxVal: " << maxVal << endl;
  int printed = 0;
  int count = 1;
  for (int iz = 0; iz < _volHdr.nz; iz++) {
    out << "====>> Plane num, z: " << iz << ", " << getZ(iz) << endl;
    const fl32 *planeData = fdata + iz * nptsPlane;
    fl32 prevVal = planeData[0];
    for (int ii = 1; ii < nptsPlane; ii++) {
      fl32 val = planeData[ii];
      if (val != prevVal) {
        _printPacked(out, count, prevVal, missingFl32);
        printed++;
        if (printed > 6) {
          out << endl;
          printed = 0;
        }
        prevVal = val;
        count = 1;
      } else {
        count++;
      }
    } // ii
    _printPacked(out, count, prevVal, missingFl32);
    out << endl;
  }
  out << "===========================================" << endl;

  delete[] fdata;

}

/////////////////////////////////////////////////////////////////
// print in packed format, using count for identical data values

void Cedric::_printPacked(ostream &out, int count, fl32 val, fl32 missingFl32)

{
  
  char outstr[1024];
  if (count > 1) {
    out << count << "*";
  }
  if (val == missingFl32) {
    out << "MISS ";
  } else {
    if (fabs(val) > 0.01) {
      sprintf(outstr, "%.3f ", val);
      out << outstr;
    } else if (val == 0.0) {
      out << "0.0 ";
    } else {
      sprintf(outstr, "%.3e ", val);
      out << outstr;
    }
  }
}

void Cedric::print(CED_file_head_t &hdr,
                   ostream &out)
  
{

  out << "========= CEDRIC FILE HEADER ==========" << endl;
  out << "  id: " << _getString(hdr.id, 4) << endl;
  out << "  byte_order: " << hdr.byte_order << endl;
  out << "  file_size: " << hdr.file_size << endl;
  out << "  reserved1: " << hdr.reserved1 << endl;
  out << "  vol_index[0]: " << hdr.vol_index[0] << endl;
  out << "  vol_label[0]: " << _getString(hdr.vol_label[0], 56) << endl;
  out << "  reserved2: " << hdr.reserved2 << endl;
  out << "  reserved3: " << hdr.reserved3 << endl;
  out << "  reserved4: " << hdr.reserved4 << endl;
  out << "  reserved5: " << hdr.reserved5 << endl;
  out << "  reserved6: " << hdr.reserved6 << endl;
  out << "  reserved7: " << hdr.reserved7 << endl;
  out << "=======================================" << endl;

}

void Cedric::print(CED_vol_head_t &hdr,
                   ostream &out)
  
{

  out << "======== CEDRIC VOLUME HEADER =========" << endl;
  out << "  id: " << _getString(hdr.id, 8) << endl;
  out << "  program: " << _getString(hdr.program, 6) << endl;
  out << "  project: " << _getString(hdr.project, 4) << endl;
  out << "  scientist: " << _getString(hdr.scientist, 6) << endl;
  out << "  radar: " << _getString(hdr.radar, 6) << endl;
  out << "  scan_mode: " << _getString(hdr.scan_mode, 4) << endl;
  out << "  tape: " << _getString(hdr.tape, 6) << endl;
  
  out << "  tape_start_year: " << hdr.tape_start_year << endl;
  out << "  tape_start_month: " << hdr.tape_start_month << endl;
  out << "  tape_start_day: " << hdr.tape_start_day << endl;
  out << "  tape_start_hour: " << hdr.tape_start_hour << endl;
  out << "  tape_start_min: " << hdr.tape_start_min << endl;
  out << "  tape_start_sec: " << hdr.tape_start_sec << endl;
  out << "  tape_end_year: " << hdr.tape_end_year << endl;
  out << "  tape_end_month: " << hdr.tape_end_month << endl;
  out << "  tape_end_day: " << hdr.tape_end_day << endl;
  out << "  tape_end_hour: " << hdr.tape_end_hour << endl;
  out << "  tape_end_min: " << hdr.tape_end_min << endl;
  out << "  tape_end_sec: " << hdr.tape_end_sec << endl;
  
  out << "  lat_deg: " << hdr.lat_deg << endl;
  out << "  lat_min: " << hdr.lat_min << endl;
  out << "  lat_sec: " << hdr.lat_sec << endl;
  out << "  lon_deg: " << hdr.lon_deg << endl;
  out << "  lon_min: " << hdr.lon_min << endl;
  out << "  lon_sec: " << hdr.lon_sec << endl;
  
  out << "  origin_height: " << hdr.origin_height << endl;
  
  out << "  angle1: " << hdr.angle1 << endl;
  out << "  origin_x: " << hdr.origin_x << endl;
  out << "  origin_y: " << hdr.origin_y << endl;
  
  out << "  time_zone: " << _getString(hdr.time_zone, 4) << endl;
  
  out << "  sequence: " << _getString(hdr.sequence, 6) << endl;
  out << "  submitter: " << _getString(hdr.submitter, 6) << endl;
  out << "  dateRun: " << _getString(hdr.date_run, 8) << endl;
  out << "  time_run: " << _getString(hdr.time_run, 8) << endl;
  
  out << "  sh59: " << hdr.sh59 << endl;
  out << "  tape_ed_number: " << hdr.tape_ed_number << endl;
  out << "  header_record_length: " << hdr.header_record_length << endl;
  
  out << "  computer: " << _getString(hdr.computer, 2) << endl;
  out << "  bits_datum: " << hdr.bits_datum << endl;
  out << "  blocking_mode: " << hdr.blocking_mode << endl;
  out << "  block_size: " << hdr.block_size << endl;
  out << "  sh66: " << hdr.sh66 << endl;
  out << "  missing_val: " << hdr.missing_val << endl;

  out << "  scale_factor: " << hdr.scale_factor << endl;
  out << "  angle_factor: " << hdr.angle_factor << endl;

  out << "  sh70: " << hdr.sh70 << endl;
  out << "  source: " << _getString(hdr.source, 8) << endl;
  out << "  tape_label2: " << _getString(hdr.tape_label2, 8) << endl;
  out << "  tape_label3: " << _getString(hdr.tape_label3, 8) << endl;
  out << "  tape_label4: " << _getString(hdr.tape_label4, 8) << endl;
  out << "  tape_label5: " << _getString(hdr.tape_label5, 8) << endl;
  out << "  tape_label6: " << _getString(hdr.tape_label6, 8) << endl;
  out << "  sh95: " << hdr.sh95 << endl;
  out << "  records_plane: " << hdr.records_plane << endl;
  out << "  records_field: " << hdr.records_field << endl;
  out << "  records_volume: " << hdr.records_volume << endl;
  out << "  total_records: " << hdr.total_records << endl;
  out << "  tot_records: " << hdr.tot_records << endl;
  out << "  vol_name: " << _getString(hdr.vol_name, 8) << endl;
  out << "  sh105: " << hdr.sh105 << endl;
  out << "  num_planes: " << hdr.num_planes << endl;
  out << "  cubic_km: " << hdr.cubic_km << endl;
  out << "  total_points: " << hdr.total_points << endl;
  out << "  sampling_density: " << hdr.sampling_density << endl;
  out << "  num_pulses: " << hdr.num_pulses << endl;
  out << "  volume_number: " << hdr.volume_number << endl;
  out << "  sh112: " << hdr.sh112 << endl;
  out << "  sh113: " << hdr.sh113 << endl;
  out << "  sh114: " << hdr.sh114 << endl;
  out << "  sh115: " << hdr.sh115 << endl;

  out << "  vol_start_year: " << hdr.vol_start_year << endl;
  out << "  vol_start_month: " << hdr.vol_start_month << endl;
  out << "  vol_start_day: " << hdr.vol_start_day << endl;
  out << "  vol_start_hour: " << hdr.vol_start_hour << endl;
  out << "  vol_start_min: " << hdr.vol_start_min << endl;
  out << "  vol_start_second: " << hdr.vol_start_second << endl;
  out << "  vol_end_year: " << hdr.vol_end_year << endl;
  out << "  vol_end_month: " << hdr.vol_end_month << endl;
  out << "  vol_end_day: " << hdr.vol_end_day << endl;
  out << "  vol_end_hour: " << hdr.vol_end_hour << endl;
  out << "  vol_end_min: " << hdr.vol_end_min << endl;
  out << "  vol_end_second: " << hdr.vol_end_second << endl;
  
  out << "  volume_time: " << hdr.volume_time << endl;
  out << "  index_number_time: " << hdr.index_number_time << endl;
  out << "  sh130: " << hdr.sh130 << endl;
  out << "  sh131: " << hdr.sh131 << endl;
  out << "  min_range: " << hdr.min_range << endl;
  out << "  max_range: " << hdr.max_range << endl;
  out << "  num_gates_beam: " << hdr.num_gates_beam << endl;
  out << "  gate_spacing: " << hdr.gate_spacing << endl;
  out << "  min_gates: " << hdr.min_gates << endl;
  out << "  max_gates: " << hdr.max_gates << endl;
  out << "  sh138: " << hdr.sh138 << endl;
  out << "  index_number_range: " << hdr.index_number_range << endl;
  out << "  sh140: " << hdr.sh140 << endl;
  out << "  sh141: " << hdr.sh141 << endl;
  out << "  min_azimuth: " << hdr.min_azimuth << endl;
  out << "  max_azimuth: " << hdr.max_azimuth << endl;
  out << "  num_beams_plane: " << hdr.num_beams_plane << endl;
  out << "  ave_angle: " << hdr.ave_angle << endl;
  out << "  min_beams_plane: " << hdr.min_beams_plane << endl;
  out << "  max_beams_plane: " << hdr.max_beams_plane << endl;
  out << "  num_steps_beam: " << hdr.num_steps_beam << endl;
  out << "  index_number_azimuth: " << hdr.index_number_azimuth << endl;
  out << "  sh150: " << hdr.sh150 << endl;
  out << "  plane_type: " << _getString(hdr.plane_type, 2) << endl;
  out << "  min_elev: " << hdr.min_elev << endl;
  out << "  max_elev: " << hdr.max_elev << endl;
  out << "  num_elevs: " << hdr.num_elevs << endl;
  out << "  ave_delta_elev: " << hdr.ave_delta_elev << endl;
  out << "  ave_elev: " << hdr.ave_elev << endl;
  out << "  direction: " << hdr.direction << endl;
  out << "  baseline_angle: " << hdr.baseline_angle << endl;
  out << "  index_number_coplane: " << hdr.index_number_coplane << endl;

  out << "  min_x: " << hdr.min_x << endl;
  out << "  max_x: " << hdr.max_x << endl;
  out << "  nx: " << hdr.nx << endl;
  out << "  dx: " << hdr.dx << endl;
  out << "  fast_axis: " << hdr.fast_axis << endl;
  
  out << "  min_y: " << hdr.min_y << endl;
  out << "  max_y: " << hdr.max_y << endl;
  out << "  ny: " << hdr.ny << endl;
  out << "  dy: " << hdr.dy << endl;
  out << "  mid_axis: " << hdr.mid_axis << endl;

  out << "  min_z: " << hdr.min_z << endl;
  out << "  max_z: " << hdr.max_z << endl;
  out << "  nz: " << hdr.nz << endl;
  out << "  dz: " << hdr.dz << endl;
  out << "  slow_axis: " << hdr.slow_axis << endl;

  out << "  num_fields: " << hdr.num_fields << endl;

  for (int ii = 0; ii < hdr.num_fields; ii++) {
    out << "    FIELD:" << endl;
    out << "      num: " << ii << endl;
    out << "      name: " << _getString(hdr.field[ii].field_name, 8) << endl;
    out << "      scale_factor: " << hdr.field[ii].field_sf << endl;
  } // ii

  out << "  points_plane: " << hdr.points_plane << endl;

  out << "  num_landmarks: " << hdr.num_landmarks << endl;
  out << "  num_radars: " << hdr.num_radars << endl;
  out << "  nyquist_vel: " << hdr.nyquist_vel << endl;
  out << "  radar_const: " << hdr.radar_const << endl;
  
  for (int ii = 0; ii < hdr.num_landmarks; ii++) {
    out << "    LANDMARK:" << endl;
    out << "      num: " << ii << endl;
    out << "      name: " << _getString(hdr.landmark[ii].name, 8) << endl;
    out << "      x_position: " << hdr.landmark[ii].x_position << endl;
    out << "      y_position: " << hdr.landmark[ii].y_position << endl;
    out << "      z_position: " << hdr.landmark[ii].z_position << endl;
  } // ii

  out << "=======================================" << endl;

}

void Cedric::print(CED_level_head_t &hdr, ostream &out)
  
{

  out << "======== CEDRIC LEVEL HEADER =========" << endl;
  out << "  id: " << _getString(hdr.id, 6) << endl;
  out << "  coord: " << hdr.coord << endl;
  out << "  level_number: " << hdr.level_number << endl;
  out << "  number_fields: " << hdr.number_fields << endl;
  out << "  points_per_plane: " << hdr.points_per_plane << endl;
  out << "  records_per_field: " << hdr.records_per_field << endl;
  out << "  records_per_plane: " << hdr.records_per_plane << endl;
  out << "  nyquist_vel: " << hdr.nyquist_vel << endl;
  out << "======================================" << endl;

}

void Cedric::_swapWords(void *array, int nBytes)
{
  int nWords = nBytes / 2;
  si16 *si16s = (si16 *) array;
  for (int ii = 0; ii < nWords; ii += 2) {
    si16 tmp = si16s[ii];
    si16s[ii] = si16s[ii+1];
    si16s[ii+1] = tmp;
  }
}

/******************************************************************************
 * SWAP_CEDRIC_FILE_HEADER: Swap the bytes if necessary.
 *
 */

void Cedric::_swap(CED_file_head_t &hdr)
{
  SWAP_array_32(&hdr.byte_order, 28 * 4);
}


/******************************************************************************
 * SWAP_CEDRIC_VOL_HEADER: Swap the bytes if necessary
 *
 */

void Cedric::_swap(CED_vol_head_t &hdr)
{
  
  SWAP_array_16(&hdr.tape_start_year, 4); /* 21-42 */
  SWAP_array_16(&hdr.tape_ed_number, 4); /* 60-61 */
  SWAP_array_16(&hdr.bits_datum, 14); /* 63-69 */
  SWAP_array_16(&hdr.records_plane, 10); /* 96-100 */
  SWAP_array_16(&hdr.num_planes, 88); /* 106-149 */
  SWAP_array_16(&hdr.min_elev, 48); /* 152-175 */
  SWAP_array_16(&hdr.points_plane, 10); /* 301-305 */

  for(int i = 0; i < hdr.num_fields && i < CED_MAX_FIELDS; i++) {
    SWAP_array_16(&hdr.field[i].field_sf, 2);
  }
 
  for(int i = 0; i < hdr.num_landmarks && i < CED_MAX_LANDMARKS; i++) {
    SWAP_array_16(&hdr.landmark[i].x_position, 6);
  }

}

/******************************************************************************
 * SWAP_CEDRIC_LEVEKL_HEADER: Swap the bytes if necessary
 *
 */

void Cedric::_swap(CED_level_head_t &hdr)
{
  SWAP_array_16(&hdr.coord, sizeof(CED_level_head_t) - 6);
}