Unverified Commit fdb313be authored by paarongiroux's avatar paarongiroux Committed by GitHub
Browse files

Campt Error App tests using TestCube fixture (#3679)



* Abstracted campt app.

* adds campt error app tests using cube fixture, adds new campt method to take in a pointer to a cube

* updated variable names

* Added default test case.

* finished assterts

* Changed expect_double_eq to expect_near.

* added coordlist and allowoutside tests

* removed extra tests, updated APP_XML variable

Co-authored-by: default avatarKaitlyn Lee <kdl222@nau.edu>
Co-authored-by: default avatarKelvin Rodriguez <kr788@nau.edu>
parent e74144bb
Loading
Loading
Loading
Loading
+268 −0
Original line number Diff line number Diff line
#include "campt.h"

#include <string>
#include <iomanip>

#include "Brick.h"
#include "Camera.h"
#include "CameraPointInfo.h"
#include "CSVReader.h"
#include "Distance.h"
#include "IException.h"
#include "iTime.h"
#include "Longitude.h"
#include "Progress.h"
#include "PvlGroup.h"
#include "SpecialPixel.h"
#include "TProjection.h"

using namespace std;
using namespace Isis;

namespace Isis{

  QList< QPair<double, double> > getPoints(const UserInterface &ui, bool usePointList);
  QList<PvlGroup *> getCameraPointInfo(const UserInterface &ui,
                                      QList< QPair<double, double> > points,
                                      CameraPointInfo &campt);
  void writePoints(const UserInterface &ui, QList<PvlGroup*> camPoints, Pvl *log);

  void campt(UserInterface &ui, Pvl *log) {
    Cube *cube = new Cube(ui.GetFileName("FROM"), "r");
    campt(cube, ui, log);
  }

  void campt(Cube *cube, UserInterface &ui, Pvl *log) {
    // Setup our input cube
    CameraPointInfo campt;

    QString fileFormat = ui.GetString("FORMAT");
    if(fileFormat=="PVL")
        campt.SetCSVOutput(false);
    else
        campt.SetCSVOutput(true);

    campt.SetCube(cube->fileName());

    // Grab the provided points (coordinates)
    QList< QPair<double, double> > points = getPoints(ui, ui.WasEntered("COORDLIST"));

    // Get the camera point info for coordiante
    QList<PvlGroup*> camPoints = getCameraPointInfo(ui, points, campt);

    writePoints(ui, camPoints, log);
  }


  // We can grab our coordinates, either from the ui position parameters or the coordlist parameter
  // This method returns a list of double pairs (i.e. a list of coordinates)
  QList< QPair<double, double> > getPoints(const UserInterface &ui, bool usePointList) {
    double point1 = 0.0;
    double point2 = 0.0;
    QList< QPair<double, double> > points;
    QString pointType = ui.GetString("TYPE");

    // Check if the provided coordinate list is valid, i.e. a Samp/Line or Lat/Long coordinate per row
    if (usePointList) {

      CSVReader reader;
      reader.read(FileName(ui.GetFileName("COORDLIST")).expanded());

      if (!reader.isTableValid(reader.getTable()) || reader.columns() != 2) {
        QString msg = "Coordinate file formatted incorrectly.\n"
                      "Each row must have two columns: a sample,line or a latitude,longitude pair.";
        throw IException(IException::User, msg, _FILEINFO_);
      }

      for (int row = 0; row < reader.rows(); row++) {
        point1 = toDouble(reader.getRow(row)[0]);
        point2 = toDouble(reader.getRow(row)[1]);
        points.append(QPair<double, double>(point1, point2));
      }

    }
    // Grab the coordinate from the ui position parameters if no coordinate list is provided
    else {
      if (pointType == "IMAGE") {
        if (ui.WasEntered("SAMPLE"))
          point1 = ui.GetDouble("SAMPLE");
        if (ui.WasEntered("LINE"))
          point2 = ui.GetDouble("LINE");
      }
      else {
        point1 = ui.GetDouble("LATITUDE");
        point2 = ui.GetDouble("LONGITUDE");
      }
      points.append(QPair<double, double>(point1, point2));
    }

    return points;
  }


  // Gets the camera information for each point (coordinate).
  // Passed in a list of coordinates, passed in by reference a CameraPointInfo object.
  // Returns a list of PvlGroup pointers - these groups contain the camera info for each coordinate.
  QList<PvlGroup*> getCameraPointInfo(const UserInterface &ui,
                                      QList< QPair<double, double> > points,
                                      CameraPointInfo &campt) {
    // Setup our parameters from ui and variables
    QList<PvlGroup*> cameraPoints;
    bool usePointList = ui.WasEntered("COORDLIST");
    bool allowOutside = ui.GetBoolean("ALLOWOUTSIDE");
    QString type;
    if (ui.WasEntered("COORDLIST")) {
      type = ui.GetString("COORDTYPE");
    }
    else {
      type = ui.GetString("TYPE");
    }
    PvlGroup *camPoint = NULL;

    // Depending on what type is selected, set values accordingly
    for (int i = 0; i < points.size(); i++) {

      QPair<double, double> pt = points[i];
      if (type == "GROUND") {
        camPoint = campt.SetGround(pt.first, pt.second, allowOutside, usePointList);
      }
      else {
        if (usePointList) {
          camPoint = campt.SetImage(pt.first, pt.second, allowOutside, usePointList);
        }
        else {
          if (ui.WasEntered("SAMPLE") && ui.WasEntered("LINE")) {
            camPoint = campt.SetImage(pt.first, pt.second, allowOutside);
          }
          else {
            if (ui.WasEntered("SAMPLE")) {
              camPoint = campt.SetSample(pt.first, allowOutside);
            }
            else if (ui.WasEntered("LINE")) {
              camPoint = campt.SetLine(pt.second, allowOutside);
            }
            else {
              camPoint = campt.SetCenter(allowOutside);
            }
          }
        }
      }
      cameraPoints.append(camPoint);
    }
    camPoint = NULL;
    return cameraPoints;
  }


  // Write our point coordinates to std out in pvl format, or to a pvl or some type of flat file
  void writePoints(const UserInterface &ui, QList<PvlGroup*> camPoints, Pvl *log) {
    // Progress should increment for each point we process
    Progress prog;
    prog.SetMaximumSteps(camPoints.size());
    QString outFile;
    // Get user params from ui
    if (ui.WasEntered("TO")) {
      outFile = FileName(ui.GetFileName("TO")).expanded();
    }
    bool append = ui.GetBoolean("APPEND");
    QString fileFormat = ui.GetString("FORMAT");
    PvlGroup *point = NULL;

    for (int p = 0; p < camPoints.size(); p++) {
        bool fileExists = FileName(outFile).fileExists();

      prog.CheckStatus();
      point = camPoints[p];

      // Remove units on look direction vectors
      point -> findKeyword("LookDirectionBodyFixed").setUnits("");
      point -> findKeyword("LookDirectionJ2000").setUnits("");
      point -> findKeyword("LookDirectionCamera").setUnits("");

      // write to output file
      if (ui.WasEntered("TO")) {
        // Write the pvl group out to the file
        if (fileFormat == "PVL") {
          Pvl temp;
          temp.setTerminator("");
          temp.addGroup((*point));

         // we don't want to overwrite successive points in outfile
          if (append || p > 0) {
            temp.append(outFile);
          }
          else {
            temp.write(outFile);
          }
       }
        // Create a flatfile from PVL data
        // The flatfile is comma delimited and can be imported into Excel
        else {
          ofstream os;
          bool writeHeader = false;
          if (append || p > 0) {
          os.open(outFile.toLatin1().data(), ios::app);
            if (!fileExists) {
              writeHeader = true;
            }
          }
          else {
            os.open(outFile.toLatin1().data(), ios::out);
            writeHeader = true;
          }

          if (writeHeader) {
            for (int i = 0; i < (*point).keywords(); i++) {
              if ((*point)[i].size() == 3) {
                os << (*point)[i].name() << "X,"
                << (*point)[i].name() << "Y,"
                << (*point)[i].name() << "Z";
              }
              else {
                os << (*point)[i].name();
              }

              if (i < point->keywords() - 1) {
                os << ",";
              }
            }
            os << endl;
          }



          for (int i = 0; i < (*point).keywords(); i++) {
            if ((*point)[i].size() == 3) {
              os << (QString)(*point)[i][0] << ","
              << (QString)(*point)[i][1] << ","
              << (QString)(*point)[i][2];
            }
            else {
              os << (QString)(*point)[i];
            }

            if (i < (*point).keywords() - 1) {
              os << ",";
            }
          }
          os << endl;
        }
      }

      // No output file specified
      else {
        // don't log data -
        if (ui.GetString("FORMAT") == "FLAT") {
          string msg = "Flat file must have a name.";
          throw IException(IException::User, msg, _FILEINFO_);
        }
      }

      // we still want to output the results
      log->addGroup((*point));
      delete point;
      point = NULL;
    }
    prog.CheckStatus();
  }
}
+12 −0
Original line number Diff line number Diff line
#ifndef campt_h
#define campt_h

#include "Pvl.h"
#include "UserInterface.h"

namespace Isis{
  extern void campt(Cube *cube, UserInterface &ui, Pvl *log);
  extern void campt(UserInterface &ui, Pvl *log);
}

#endif
+14 −253
Original line number Diff line number Diff line
#include "Isis.h"

#include <string>
#include <iomanip>
#include "campt.h"

#include "Brick.h"
#include "Camera.h"
#include "CameraPointInfo.h"
#include "CSVReader.h"
#include "Distance.h"
#include "IException.h"
#include "iTime.h"
#include "Longitude.h"
#include "Progress.h"
#include "PvlGroup.h"
#include "SpecialPixel.h"
#include "TProjection.h"
#include "Application.h"
#include "Pvl.h"

using namespace std;
using namespace Isis;

QList< QPair<double, double> > getPoints(const UserInterface &ui, bool usePointList);
QList<PvlGroup *> getCameraPointInfo(const UserInterface &ui,
                                    QList< QPair<double, double> > points,
                                    CameraPointInfo &campt);
void writePoints(const UserInterface &ui, QList<PvlGroup*> camPoints);




void IsisMain() {
  UserInterface &ui = Application::GetUserInterface();

  // Setup our input cube
  CameraPointInfo campt;

  QString fileFormat = ui.GetString("FORMAT");
  if(fileFormat=="PVL")
      campt.SetCSVOutput(false);
  else
      campt.SetCSVOutput(true);

  campt.SetCube( ui.GetFileName("FROM") + "+" + ui.GetInputAttribute("FROM").toString() );

  // Grab the provided points (coordinates)
  QList< QPair<double, double> > points = getPoints(ui, ui.WasEntered("COORDLIST"));

  // Get the camera point info for coordiante

  QList<PvlGroup*> camPoints = getCameraPointInfo(ui, points, campt);

  writePoints(ui, camPoints);
}


// We can grab our coordinates, either from the ui position parameters or the coordlist parameter
// This method returns a list of double pairs (i.e. a list of coordinates)
QList< QPair<double, double> > getPoints(const UserInterface &ui, bool usePointList) {
  double point1 = 0.0;
  double point2 = 0.0;
  QList< QPair<double, double> > points;
  QString pointType = ui.GetString("TYPE");

  // Check if the provided coordinate list is valid, i.e. a Samp/Line or Lat/Long coordinate per row
  if (usePointList) {

    CSVReader reader;
    reader.read(FileName(ui.GetFileName("COORDLIST")).expanded());

    if (!reader.isTableValid(reader.getTable()) || reader.columns() != 2) {
      QString msg = "Coordinate file formatted incorrectly.\n"
                    "Each row must have two columns: a sample,line or a latitude,longitude pair.";
      throw IException(IException::User, msg, _FILEINFO_);
    }

    for (int row = 0; row < reader.rows(); row++) {
      point1 = toDouble(reader.getRow(row)[0]);
      point2 = toDouble(reader.getRow(row)[1]);
      points.append(QPair<double, double>(point1, point2));
    }

  }
  // Grab the coordinate from the ui position parameters if no coordinate list is provided
  else {
    if (pointType == "IMAGE") {
      if (ui.WasEntered("SAMPLE"))
        point1 = ui.GetDouble("SAMPLE");
      if (ui.WasEntered("LINE"))
        point2 = ui.GetDouble("LINE");
    }
    else {
      point1 = ui.GetDouble("LATITUDE");
      point2 = ui.GetDouble("LONGITUDE");
    }
    points.append(QPair<double, double>(point1, point2));
  }

  return points;
}


// Gets the camera information for each point (coordinate).
// Passed in a list of coordinates, passed in by reference a CameraPointInfo object.
// Returns a list of PvlGroup pointers - these groups contain the camera info for each coordinate.
QList<PvlGroup*> getCameraPointInfo(const UserInterface &ui,
                                    QList< QPair<double, double> > points,
                                    CameraPointInfo &campt) {
  // Setup our parameters from ui and variables
  QList<PvlGroup*> cameraPoints;
  bool usePointList = ui.WasEntered("COORDLIST");
  bool allowOutside = ui.GetBoolean("ALLOWOUTSIDE");
  QString type;
  if (ui.WasEntered("COORDLIST")) {
    type = ui.GetString("COORDTYPE");
  }
  else {
    type = ui.GetString("TYPE");
  }
  PvlGroup *camPoint = NULL;

  // Depending on what type is selected, set values accordingly
  for (int i = 0; i < points.size(); i++) {

    QPair<double, double> pt = points[i];
    if (type == "GROUND") {
      camPoint = campt.SetGround(pt.first, pt.second, allowOutside, usePointList);
    }
    else {
      if (usePointList) {
        camPoint = campt.SetImage(pt.first, pt.second, allowOutside, usePointList);
      }
      else {
        if (ui.WasEntered("SAMPLE") && ui.WasEntered("LINE")) {
          camPoint = campt.SetImage(pt.first, pt.second, allowOutside);
        }
        else {
          if (ui.WasEntered("SAMPLE")) {
            camPoint = campt.SetSample(pt.first, allowOutside);
          }
          else if (ui.WasEntered("LINE")) {
            camPoint = campt.SetLine(pt.second, allowOutside);
          }
          else {
            camPoint = campt.SetCenter(allowOutside);
  Pvl appLog;
  try {
    campt(ui, &appLog);
  }
  catch (...) {
    for (auto grpIt = appLog.beginGroup(); grpIt!= appLog.endGroup(); grpIt++) {
      Application::Log(*grpIt);
    }
      }
    }
    cameraPoints.append(camPoint);
  }
  camPoint = NULL;
  return cameraPoints;
}


// Write our point coordinates to std out in pvl format, or to a pvl or some type of flat file
void writePoints(const UserInterface &ui, QList<PvlGroup*> camPoints) {
  // Progress should increment for each point we process
  Progress prog;
  prog.SetMaximumSteps(camPoints.size());
  QString outFile;
  // Get user params from ui
  if (ui.WasEntered("TO")) {
    outFile = FileName(ui.GetFileName("TO")).expanded();
  }
  bool append = ui.GetBoolean("APPEND");
  QString fileFormat = ui.GetString("FORMAT");
  PvlGroup *point = NULL;

  for (int p = 0; p < camPoints.size(); p++) {
      bool fileExists = FileName(outFile).fileExists();

    prog.CheckStatus();
    point = camPoints[p];

    // Remove units on look direction vectors
    point -> findKeyword("LookDirectionBodyFixed").setUnits("");
    point -> findKeyword("LookDirectionJ2000").setUnits("");
    point -> findKeyword("LookDirectionCamera").setUnits("");

    // write to output file
    if (ui.WasEntered("TO")) {
      // Write the pvl group out to the file
      if (fileFormat == "PVL") {
        Pvl temp;
        temp.setTerminator("");
        temp.addGroup((*point));

       // we don't want to overwrite successive points in outfile
        if (append || p > 0) {
          temp.append(outFile);
        }
        else {
          temp.write(outFile);
        }
     }
      // Create a flatfile from PVL data
      // The flatfile is comma delimited and can be imported into Excel
      else {
        ofstream os;
        bool writeHeader = false;
        if (append || p > 0) {
        os.open(outFile.toLatin1().data(), ios::app);
          if (!fileExists) {
            writeHeader = true;
          }
        }
        else {
          os.open(outFile.toLatin1().data(), ios::out);
          writeHeader = true;
        }

        if (writeHeader) {
          for (int i = 0; i < (*point).keywords(); i++) {
            if ((*point)[i].size() == 3) {
              os << (*point)[i].name() << "X,"
              << (*point)[i].name() << "Y,"
              << (*point)[i].name() << "Z";
            }
            else {
              os << (*point)[i].name();
            }

            if (i < point->keywords() - 1) {
              os << ",";
            }
          }
          os << endl;
        }



        for (int i = 0; i < (*point).keywords(); i++) {
          if ((*point)[i].size() == 3) {
            os << (QString)(*point)[i][0] << ","
            << (QString)(*point)[i][1] << ","
            << (QString)(*point)[i][2];
          }
          else {
            os << (QString)(*point)[i];
          }

          if (i < (*point).keywords() - 1) {
            os << ",";
          }
        }
        os << endl;
      }
    }

    // No output file specified
    else {
      // don't log data -
      if (ui.GetString("FORMAT") == "FLAT") {
        string msg = "Flat file must have a name.";
        throw IException(IException::User, msg, _FILEINFO_);
      }
    throw;
  }
  
    // we still want to output the results
    Application::Log((*point));
    delete point;
    point = NULL;
  for (auto grpIt = appLog.beginGroup(); grpIt!= appLog.endGroup(); grpIt++) {
    Application::Log(*grpIt);
  }
  prog.CheckStatus();
}
 No newline at end of file
+1 −1
Original line number Diff line number Diff line
@@ -1376,7 +1376,7 @@ namespace Isis {
      return;
    }

    if (m_usingAle) {
    if (m_usingAle || !m_usingNaif) {
      double og_time = m_bodyRotation->EphemerisTime();  
      m_bodyRotation->SetEphemerisTime(et.Et());
      m_sunPosition->SetEphemerisTime(et.Et());
+139 −0
Original line number Diff line number Diff line
#include <QTemporaryFile>

#include "campt.h"
#include "Fixtures.h"
#include "Pvl.h"
#include "PvlGroup.h"
#include "TestUtilities.h"

#include "gmock/gmock.h"

using namespace Isis;

static QString APP_XML = FileName("$ISISROOT/bin/xml/campt.xml").expanded();

TEST_F(DefaultCube, FunctionalTestCamptBadColumnError) {
  // set up bad coordinates file
  std::ofstream of;
  QTemporaryFile badList;
  ASSERT_TRUE(badList.open());
  of.open(badList.fileName().toStdString());
  of << "1, 10,\n10,100,500\n100";
  of.close();

  // configure UserInterface arguments
  QVector<QString> args = {"to=output.pvl", "coordlist=" + badList.fileName(),
                           "coordtype=image"};
  UserInterface options(APP_XML, args);
  Pvl appLog;

  try {
    campt(testCube, options, &appLog);
    FAIL() << "Expected an exception to be thrown";
  }
  catch(Isis::IException &e) {
    EXPECT_TRUE(e.toString().toLatin1().contains("Coordinate file formatted incorrectly."))
      << e.toString().toStdString();
  }
  catch(...) {
    FAIL() << "Expected an IException with message: \"Coordinate file formatted incorrectly.\n"
              "Each row must have two columns: a sample,line or a latitude,longitude pair.\"";
  }
}


TEST_F(DefaultCube, FunctionalTestCamptFlatFileError) {
  // configure UserInterface arguments for flat file error
  QVector<QString> args = {"format=flat"};
  UserInterface options(APP_XML, args);
  Pvl appLog;

  try {
    campt(testCube, options, &appLog);
    FAIL() << "Expected an exception to be thrown";
  }
  catch(Isis::IException &e) {
    EXPECT_TRUE(e.toString().toLatin1().contains("Flat file must have a name."))
      << e.toString().toStdString();
  }
  catch(...) {
    FAIL() << "Expected an IException with message: \"Flat file must have a name.\"";
  }
}

TEST_F(DefaultCube, FunctionalTestCamptDefaultParameters) {
  QVector<QString> args = {};
  UserInterface options(APP_XML, args);
  Pvl appLog;

  campt(testCube, options, &appLog);
  PvlGroup groundPoint = appLog.findGroup("GroundPoint");

  EXPECT_DOUBLE_EQ( (double) groundPoint.findKeyword("Sample"), 602.0);
  EXPECT_DOUBLE_EQ( (double) groundPoint.findKeyword("Line"), 528.0);
  EXPECT_PRED_FORMAT2(AssertQStringsEqual, groundPoint.findKeyword("PixelValue"), "Null");
  EXPECT_NEAR( (double) groundPoint.findKeyword("RightAscension"), 310.2070335306, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("Declination"), -46.327246785573, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("PlanetocentricLatitude"), 10.181441241544, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("PlanetographicLatitude"), 10.299790241741, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("PositiveEast360Longitude"), 255.89292858176, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("PositiveEast180Longitude"), -104.10707141824, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("PositiveWest360Longitude"), 104.10707141824, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("PositiveWest180Longitude"), 104.10707141824, 1e-8);

  EXPECT_NEAR( toDouble(groundPoint.findKeyword("BodyFixedCoordinate")[0]), -818.59644749774, 1e-8);
  EXPECT_NEAR( toDouble(groundPoint.findKeyword("BodyFixedCoordinate")[1]), -3257.2675597135, 1e-8);
  EXPECT_NEAR( toDouble(groundPoint.findKeyword("BodyFixedCoordinate")[2]),  603.17640797124, 1e-8);

  EXPECT_NEAR( (double) groundPoint.findKeyword("LocalRadius"), 3412288.6569795, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("SampleResolution"), 18.904248467739, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("LineResolution"), 18.904248467739, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("ObliqueDetectorResolution"), 19.336214219327, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("ObliquePixelResolution"), 19.336214219327, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("ObliqueLineResolution"), 19.336214219327, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("ObliqueSampleResolution"), 19.336214219327, 1e-8);

  EXPECT_NEAR( toDouble(groundPoint.findKeyword("SpacecraftPosition")[0]), -1152.8979327717, 1e-8);
  EXPECT_NEAR( toDouble(groundPoint.findKeyword("SpacecraftPosition")[1]), -3930.9421518203, 1e-8);
  EXPECT_NEAR( toDouble(groundPoint.findKeyword("SpacecraftPosition")[2]),  728.14118380775, 1e-8);

  EXPECT_NEAR( (double) groundPoint.findKeyword("SpacecraftAzimuth"), 240.08514246657, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("SlantDistance"), 762.37204454685, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("TargetCenterDistance"), 4160.7294345949, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("SubSpacecraftLatitude"), 10.078847382918, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("SubSpacecraftLongitude"), 253.65422317887, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("SpacecraftAltitude"), 753.22374841704, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("OffNadirAngle"), 9.9273765143684, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("SubSpacecraftGroundAzimuth"), 267.5318718687, 1e-8);

  EXPECT_NEAR( toDouble(groundPoint.findKeyword("SunPosition")[0]),  147591102.63158, 1e-8);
  EXPECT_NEAR( toDouble(groundPoint.findKeyword("SunPosition")[1]), -127854342.1274, 1e-8);
  EXPECT_NEAR( toDouble(groundPoint.findKeyword("SunPosition")[2]), -81844199.02275, 1e-8);

  EXPECT_NEAR( (double) groundPoint.findKeyword("SubSolarAzimuth"), 92.033828156965, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("SolarDistance"), 1.4153000672557, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("SubSolarLatitude"), -22.740326163641, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("SubSolarLongitude"), 319.09846558533, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("SubSolarGroundAzimuth"), 118.87356333938, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("Phase"), 80.528381932125, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("Incidence"), 70.127983116628, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("Emission"), 12.133564327344, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("NorthAzimuth"), 332.65918493997, 1e-8);

  EXPECT_NEAR( (double) groundPoint.findKeyword("EphemerisTime"), -709401200.26114, 1e-8);
  EXPECT_PRED_FORMAT2(AssertQStringsEqual, groundPoint.findKeyword("UTC"), "1977-07-09T20:05:51.5549999");
  EXPECT_NEAR( (double) groundPoint.findKeyword("LocalSolarTime"), 7.7862975330952, 1e-8);
  EXPECT_NEAR( (double) groundPoint.findKeyword("SolarLongitude"), 294.73518830595, 1e-8);

  EXPECT_NEAR( toDouble(groundPoint.findKeyword("LookDirectionBodyFixed")[0]),  0.43850176257802, 1e-8);
  EXPECT_NEAR( toDouble(groundPoint.findKeyword("LookDirectionBodyFixed")[1]),  0.88365594846443, 1e-8);
  EXPECT_NEAR( toDouble(groundPoint.findKeyword("LookDirectionBodyFixed")[2]), -0.16391573737569, 1e-8);

  EXPECT_NEAR( toDouble(groundPoint.findKeyword("LookDirectionJ2000")[0]),  0.44577814515745, 1e-8);
  EXPECT_NEAR( toDouble(groundPoint.findKeyword("LookDirectionJ2000")[1]), -0.52737586689974, 1e-8);
  EXPECT_NEAR( toDouble(groundPoint.findKeyword("LookDirectionJ2000")[2]), -0.72329561059897, 1e-8);

  EXPECT_NEAR( toDouble(groundPoint.findKeyword("LookDirectionCamera")[0]), -1.27447324380581e-04, 1e-8);
  EXPECT_NEAR( toDouble(groundPoint.findKeyword("LookDirectionCamera")[1]),  2.5816511718707e-05, 1e-8);
  EXPECT_NEAR( toDouble(groundPoint.findKeyword("LookDirectionCamera")[2]),  0.99999999154535, 1e-8);
}
Loading