Skip to content
Snippets Groups Projects
Select Git revision
  • 3b44dd964ac7eb5e7a010119dd9d8849c3eff9d0
  • main default protected
  • Kelvinrr-patch-3
  • radius_update
  • revert-616-apollo_pan
  • vims
  • 0.10
  • Kelvinrr-patch-2
  • revert-563-minirf_fix
  • Kelvinrr-patch-1
  • 0.9
  • acpaquette-patch-3
  • acpaquette-patch-2
  • acpaquette-patch-1
  • spiceql
  • ci-coverage
  • 0.10.0
  • 0.9.1
  • 0.9.0
  • 0.8.7
  • 0.8.8
  • 0.8.6
  • 0.8.3
  • 0.8.4
  • 0.8.5
  • 0.8.2
  • 0.8.1
  • 0.8.0
  • 0.7.3
  • 0.7.2
  • 0.7.1
  • 0.7.0
  • 0.6.5
  • 0.6.4
  • 0.6.3
  • 0.6.2
36 results

lro_drivers.py

Blame
  • UsgsAstroLsSensorModel.cpp 98.43 KiB
    //----------------------------------------------------------------------------
    //
    //                                UNCLASSIFIED
    //
    // Copyright © 1989-2017 BAE Systems Information and Electronic Systems Integration Inc.
    //                            ALL RIGHTS RESERVED
    // Use of this software product is governed by the terms of a license
    // agreement. The license agreement is found in the installation directory.
    //
    //             For support, please visit http://www.baesystems.com/gxp
    //
    //  Revision History:
    //  Date        Name         Description
    //  ----------- ------------ -----------------------------------------------
    //  13-Nov-2015 BAE Systems  Initial Implementation
    //  24-Apr-2017 BAE Systems  Update for CSM 3.0.2
    //  24-OCT-2017 BAE Systems  Update for CSM 3.0.3
    //-----------------------------------------------------------------------------
    #define USGS_SENSOR_LIBRARY
    
    #include "UsgsAstroLsSensorModel.h"
    
    #include <algorithm>
    #include <iostream>
    #include <sstream>
    #include <math.h>
    
    #define USGSASTROLINESCANNER_LIBRARY
    
    #include <sstream>
    #include <Error.h>
    #include <json.hpp>
    using json = nlohmann::json;
    
    const std::string UsgsAstroLsSensorModel::_SENSOR_MODEL_NAME
             = "USGS_ASTRO_LINE_SCANNER_SENSOR_MODEL";
    const int     UsgsAstroLsSensorModel::NUM_PARAMETERS = 16;
    const std::string  UsgsAstroLsSensorModel::PARAMETER_NAME[] =
    {
       "IT Pos. Bias   ",   // 0
       "CT Pos. Bias   ",   // 1
       "Rad Pos. Bias  ",   // 2
       "IT Vel. Bias   ",   // 3
       "CT Vel. Bias   ",   // 4
       "Rad Vel. Bias  ",   // 5
       "Omega Bias     ",   // 6
       "Phi Bias       ",   // 7
       "Kappa Bias     ",   // 8
       "Omega Rate     ",   // 9
       "Phi Rate       ",   // 10
       "Kappa Rate     ",   // 11
       "Omega Accl     ",   // 12
       "Phi Accl       ",   // 13
       "Kappa Accl     ",   // 14
       "Focal Bias     "    // 15
    };
    
    
    const std::string  UsgsAstroLsSensorModel::_STATE_KEYWORD[] =
    {
       "m_sensorModelName",
       "m_imageIdentifier",
       "m_sensorType",
       "m_totalLines",
       "m_totalSamples",
       "m_offsetLines",
       "m_offsetSamples",
       "m_platformFlag",
       "m_aberrFlag",
       "m_atmrefFlag",
       "m_intTimeLines",
       "m_intTimeStartTimes",
       "m_intTimes",
       "m_startingEphemerisTime",
       "m_centerEphemerisTime",
       "m_detectorSampleSumming",
       "m_startingSample",
       "m_ikCode",
       "m_focal",
       "m_isisZDirection",
       "m_opticalDistCoef",
       "m_iTransS",
       "m_iTransL",
       "m_detectorSampleOrigin",
       "m_detectorLineOrigin",
       "m_detectorLineOffset",
       "m_semiMajorAxis",
       "m_semiMinorAxis",
       "m_referenceDateAndTime",
       "m_platformIdentifier",
       "m_sensorIdentifier",
       "m_trajectoryIdentifier",
       "m_collectionIdentifier",
       "m_refElevation",
       "m_minElevation",
       "m_maxElevation",
       "m_dtEphem",
       "m_t0Ephem",
       "m_dtQuat",
       "m_t0Quat",
       "m_numEphem",
       "m_numQuaternions",
       "m_ephemPts",
       "m_ephemRates",
       "m_quaternions",
       "m_parameterVals",
       "m_parameterType",
       "m_referencePointXyz",
       "m_gsd",
       "m_flyingHeight",
       "m_halfSwath",
       "m_halfTime",
       "m_covariance",
       "m_imageFlipFlag"
    };
    
    const int UsgsAstroLsSensorModel::NUM_PARAM_TYPES = 4;
    const std::string UsgsAstroLsSensorModel::PARAM_STRING_ALL[] =
    {
       "NONE",
       "FICTITIOUS",
       "REAL",
       "FIXED"
    };
    const csm::param::Type
          UsgsAstroLsSensorModel::PARAM_CHAR_ALL[] =
    {
       csm::param::NONE,
       csm::param::FICTITIOUS,
       csm::param::REAL,
       csm::param::FIXED
    };
    
    
    void UsgsAstroLsSensorModel::replaceModelState(const std::string &stateString )
    {
       reset();
       auto j = json::parse(stateString);
       int num_params    = NUM_PARAMETERS;
       int num_paramsSq = num_params * num_params;
    
       m_imageIdentifier = j["m_imageIdentifier"].get<std::string>();
       m_sensorType = j["m_sensorType"];
       m_totalLines = j["m_totalLines"];
       m_totalSamples = j["m_totalSamples"];
       m_offsetLines = j["m_offsetLines"];
       m_offsetSamples = j["m_offsetSamples"];
       m_platformFlag = j["m_platformFlag"];
       m_aberrFlag = j["m_aberrFlag"];
       m_atmRefFlag = j["m_atmRefFlag"];
       m_intTimeLines = j["m_intTimeLines"].get<std::vector<double>>();
       m_intTimeStartTimes = j["m_intTimeStartTimes"].get<std::vector<double>>();
       m_intTimes = j["m_intTimes"].get<std::vector<double>>();
       m_startingEphemerisTime = j["m_startingEphemerisTime"];
       m_centerEphemerisTime = j["m_centerEphemerisTime"];
       m_detectorSampleSumming = j["m_detectorSampleSumming"];
       m_startingSample = j["m_startingSample"];
       m_ikCode = j["m_ikCode"];
       m_focal = j["m_focal"];
       m_isisZDirection = j["m_isisZDirection"];
       for (int i = 0; i < 3; i++) {
         m_opticalDistCoef[i] = j["m_opticalDistCoef"][i];
         m_iTransS[i] = j["m_iTransS"][i];
         m_iTransL[i] = j["m_iTransL"][i];
       }
       m_detectorSampleOrigin = j["m_detectorSampleOrigin"];
       m_detectorLineOrigin = j["m_detectorLineOrigin"];
       m_detectorLineOffset = j["m_detectorLineOffset"];
       m_semiMajorAxis = j["m_semiMajorAxis"];
       m_semiMinorAxis = j["m_semiMinorAxis"];
       m_referenceDateAndTime = j["m_referenceDateAndTime"];
       m_platformIdentifier = j["m_platformIdentifier"];
       m_sensorIdentifier = j["m_sensorIdentifier"];
       m_trajectoryIdentifier = j["m_trajectoryIdentifier"];
       m_collectionIdentifier = j["m_collectionIdentifier"];
       m_refElevation = j["m_refElevation"];
       m_minElevation = j["m_minElevation"];
       m_maxElevation = j["m_maxElevation"];
       m_dtEphem = j["m_dtEphem"];
       m_t0Ephem = j["m_t0Ephem"];
       m_dtQuat = j["m_dtQuat"];
       m_t0Quat = j["m_t0Quat"];
       m_numEphem = j["m_numEphem"];
       m_numQuaternions = j["m_numQuaternions"];
       m_referencePointXyz.x = j["m_referencePointXyz"][0];
       m_referencePointXyz.y = j["m_referencePointXyz"][1];
       m_referencePointXyz.z = j["m_referencePointXyz"][2];
       m_gsd = j["m_gsd"];
       m_flyingHeight = j["m_flyingHeight"];
       m_halfSwath = j["m_halfSwath"];
       m_halfTime = j["m_halfTime"];
       m_imageFlipFlag = j["m_imageFlipFlag"];
       // Vector = is overloaded so explicit get with type required.
       m_ephemPts = j["m_ephemPts"].get<std::vector<double>>();
       m_ephemRates = j["m_ephemRates"].get<std::vector<double>>();
       m_quaternions = j["m_quaternions"].get<std::vector<double>>();
       m_parameterVals = j["m_parameterVals"].get<std::vector<double>>();
       m_covariance = j["m_covariance"].get<std::vector<double>>();
       for (int i = 0; i < num_params; i++) {
         for (int k = 0; k < NUM_PARAM_TYPES; k++) {
           if (j["m_parameterType"][i] == PARAM_STRING_ALL[k]) {
             m_parameterType[i] = PARAM_CHAR_ALL[k];
             break;
         }
        }
       }
    
       // If computed state values are still default, then compute them
       if (m_gsd == 1.0  && m_flyingHeight == 1000.0)
       {
         updateState();
       }
    
       try
       {
         setLinearApproximation();
       }
       catch (...)
       {
         _linear = false;
       }
    }
    
    std::string UsgsAstroLsSensorModel::getModelNameFromModelState(
       const std::string& model_state)
    {
       // Parse the string to JSON
       auto j = json::parse(model_state);
       // If model name cannot be determined, return a blank string
       std::string model_name;
    
       if (j.find("m_sensorModelName") != j.end()) {
           model_name = j["m_sensorModelName"];
       } else {
           csm::Error::ErrorType aErrorType = csm::Error::INVALID_SENSOR_MODEL_STATE;
           std::string aMessage = "No 'm_sensorModelName' key in the model state object.";
           std::string aFunction = "UsgsAstroLsPlugin::getModelNameFromModelState";
           csm::Error csmErr(aErrorType, aMessage, aFunction);
           throw(csmErr);
       }
       if (model_name != _SENSOR_MODEL_NAME){
           csm::Error::ErrorType aErrorType = csm::Error::SENSOR_MODEL_NOT_SUPPORTED;
           std::string aMessage = "Sensor model not supported.";
           std::string aFunction = "UsgsAstroLsPlugin::getModelNameFromModelState()";
           csm::Error csmErr(aErrorType, aMessage, aFunction);
           throw(csmErr);
       }
       return model_name;
    }
    
    std::string UsgsAstroLsSensorModel::getModelState() const {
          json state;
          state["m_imageIdentifier"] = m_imageIdentifier;
          state["m_sensorType"] = m_sensorType;
          state["m_totalLines"] = m_totalLines;
          state["m_totalSamples"] = m_totalSamples;
          state["m_offsetLines"] = m_offsetLines;
          state["m_offsetSamples"] = m_offsetSamples;
          state["m_platformFlag"] = m_platformFlag;
          state["m_aberrFlag"] = m_aberrFlag;
          state["m_atmRefFlag"] = m_atmRefFlag;
          state["m_intTimeLines"] = m_intTimeLines;
          state["m_intTimeStartTimes"] = m_intTimeStartTimes;
          state["m_intTimes"] = m_intTimes;
          state["m_startingEphemerisTime"] = m_startingEphemerisTime;
          state["m_centerEphemerisTime"] = m_centerEphemerisTime;
          state["m_detectorSampleSumming"] = m_detectorSampleSumming;
          state["m_startingSample"] = m_startingSample;
          state["m_ikCode"] = m_ikCode;
          state["m_focal"] = m_focal;
          state["m_isisZDirection"] = m_isisZDirection;
          state["m_opticalDistCoef"] = std::vector<double>(m_opticalDistCoef, m_opticalDistCoef+3);
          state["m_iTransS"] = std::vector<double>(m_iTransS, m_iTransS+3);
          state["m_iTransL"] = std::vector<double>(m_iTransL, m_iTransL+3);
          state["m_detectorSampleOrigin"] = m_detectorSampleOrigin;
          state["m_detectorLineOrigin"] = m_detectorLineOrigin;
          state["m_detectorLineOffset"] = m_detectorLineOffset;
          state["m_semiMajorAxis"] = m_semiMajorAxis;
          state["m_semiMinorAxis"] = m_semiMinorAxis;
          state["m_referenceDateAndTime"] = m_referenceDateAndTime;
          state["m_platformIdentifier"] = m_platformIdentifier;
          state["m_sensorIdentifier"] = m_sensorIdentifier;
          state["m_trajectoryIdentifier"] = m_trajectoryIdentifier;
          state["m_collectionIdentifier"] = m_collectionIdentifier;
          state["m_refElevation"] = m_refElevation;
          state["m_minElevation"] = m_minElevation;
          state["m_maxElevation"] = m_maxElevation;
          state["m_dtEphem"] = m_dtEphem;
          state["m_t0Ephem"] = m_t0Ephem;
          state["m_dtQuat"] = m_dtQuat;
          state["m_t0Quat"] = m_t0Quat;
          state["m_numEphem"] = m_numEphem;
          state["m_numQuaternions"] = m_numQuaternions;
          state["m_ephemPts"] = m_ephemPts;
          state["m_ephemRates"] = m_ephemRates;
          state["m_quaternions"] = m_quaternions;
          state["m_parameterVals"] = m_parameterVals;
          state["m_parameterType"] = m_parameterType;
          state["m_gsd"] = m_gsd;
          state["m_flyingHeight"] = m_flyingHeight;
          state["m_halfSwath"] = m_halfSwath;
          state["m_halfTime"] = m_halfTime;
          state["m_covariance"] = m_covariance;
          state["m_imageFlipFlag"] = m_imageFlipFlag;
    
          state["m_referencePointXyz"] = json();
          state["m_referencePointXyz"]["x"] = m_referencePointXyz.x;
          state["m_referencePointXyz"]["y"] = m_referencePointXyz.y;
          state["m_referencePointXyz"]["z"] = m_referencePointXyz.z;
    
          return state.dump();
     }
    
    void UsgsAstroLsSensorModel::reset()
    {
      _linear = false; // default until a linear model is made
      _u0    = 0.0;
      _du_dx = 0.0;
      _du_dy = 0.0;
      _du_dz = 0.0;
      _v0    = 0.0;
      _dv_dx = 0.0;
      _dv_dy = 0.0;
      _dv_dz = 0.0;
    
      _no_adjustment.assign(UsgsAstroLsSensorModel::NUM_PARAMETERS, 0.0);
    
      m_imageIdentifier = "";                    // 1
      m_sensorType    = "USGSAstroLineScanner";  // 2
      m_totalLines    = 0;                       // 3
      m_totalSamples  = 0;                       // 4
      m_offsetLines   = 0.0;                     // 7
      m_offsetSamples = 0.0;                     // 8
      m_platformFlag  = 1;                       // 9
      m_aberrFlag     = 0;                       // 10
      m_atmRefFlag    = 0;                       // 11
      m_intTimeLines.clear();
      m_intTimeStartTimes.clear();
      m_intTimes.clear();
      m_startingEphemerisTime = 0.0;             // 13
      m_centerEphemerisTime = 0.0;               // 14
      m_detectorSampleSumming = 1.0;             // 15
      m_startingSample = 1.0;                    // 16
      m_ikCode = -85600;                         // 17
      m_focal = 350.0;                           // 18
      m_isisZDirection = 1.0;                    // 19
      m_opticalDistCoef[0] = 0.0;                // 20
      m_opticalDistCoef[1] = 0.0;                // 20
      m_opticalDistCoef[2] = 0.0;                // 20
      m_iTransS[0] = 0.0;                        // 21
      m_iTransS[1] = 0.0;                        // 21
      m_iTransS[2] = 150.0;                      // 21
      m_iTransL[0] = 0.0;                        // 22
      m_iTransL[1] = 150.0;                      // 22
      m_iTransL[2] = 0.0;                        // 22
      m_detectorSampleOrigin = 2500.0;           // 23
      m_detectorLineOrigin = 0.0;                // 24
      m_detectorLineOffset = 0.0;                // 25
      m_semiMajorAxis = 3400000.0;               // 27
      m_semiMinorAxis = 3350000.0;               // 28
      m_referenceDateAndTime = "";               // 30
      m_platformIdentifier = "";                 // 31
      m_sensorIdentifier = "";                   // 32
      m_trajectoryIdentifier = "";               // 33
      m_collectionIdentifier = "";               // 33
      m_refElevation = 30;                       // 34
      m_minElevation = -8000.0;                  // 34
      m_maxElevation =  8000.0;                  // 35
      m_dtEphem = 2.0;                           // 36
      m_t0Ephem = -70.0;                         // 37
      m_dtQuat =  0.1;                           // 38
      m_t0Quat = -40.0;                          // 39
      m_numEphem = 0;                            // 40
      m_numQuaternions = 0;                      // 41
      m_ephemPts.clear();                        // 42
      m_ephemRates.clear();                      // 43
      m_quaternions.clear();                     // 44
    
      m_parameterVals.assign(NUM_PARAMETERS,0.0);
      m_parameterType.assign(NUM_PARAMETERS,csm::param::REAL);
    
      m_referencePointXyz.x = 0.0;             // 47
      m_referencePointXyz.y = 0.0;             // 47
      m_referencePointXyz.z = 0.0;             // 47
      m_gsd = 1.0;                             // 48
      m_flyingHeight = 1000.0;                 // 49
      m_halfSwath = 1000.0;                    // 50
      m_halfTime = 10.0;                       // 51
    
      m_covariance = std::vector<double>(NUM_PARAMETERS * NUM_PARAMETERS,0.0); // 52
      m_imageFlipFlag = 0;                     // 53
    }
    
    
    //*****************************************************************************
    // UsgsAstroLsSensorModel Constructor
    //*****************************************************************************
    UsgsAstroLsSensorModel::UsgsAstroLsSensorModel()
    {
       _no_adjustment.assign(UsgsAstroLsSensorModel::NUM_PARAMETERS, 0.0);
    }
    
    
    //*****************************************************************************
    // UsgsAstroLsSensorModel Destructor
    //*****************************************************************************
    UsgsAstroLsSensorModel::~UsgsAstroLsSensorModel()
    {
    }
    
    //*****************************************************************************
    // UsgsAstroLsSensorModel updateState
    //*****************************************************************************
    void UsgsAstroLsSensorModel::updateState()
    {
       // If sensor model is being created for the first time
       // This routine will set some parameters not found in the ISD.
    
       // Reference point (image center)
       double lineCtr = m_totalLines / 2.0;
       double sampCtr = m_totalSamples / 2.0;
       csm::ImageCoord ip(lineCtr, sampCtr);
       double refHeight = m_refElevation;
       m_referencePointXyz = imageToGround(ip, refHeight);
       // Compute ground sample distance
       ip.line += 1;
       ip.samp += 1;
       csm::EcefCoord delta = imageToGround(ip, refHeight);
       double dx = delta.x - m_referencePointXyz.x;
       double dy = delta.y - m_referencePointXyz.y;
       double dz = delta.z - m_referencePointXyz.z;
       m_gsd = sqrt((dx*dx + dy*dy + dz*dz) / 2.0);
    
       // Compute flying height
       csm::EcefCoord sensorPos = getSensorPosition(0.0);
       dx = sensorPos.x - m_referencePointXyz.x;
       dy = sensorPos.y - m_referencePointXyz.y;
       dz = sensorPos.z - m_referencePointXyz.z;
       m_flyingHeight = sqrt(dx*dx + dy*dy + dz*dz);
    
       // Compute half swath
       m_halfSwath = m_gsd * m_totalSamples / 2.0;
    
       // Compute half time duration
       double fullImageTime = m_intTimeStartTimes.back() - m_intTimeStartTimes.front()
                              + m_intTimes.back() * (m_totalLines - m_intTimeLines.back());
       m_halfTime = fullImageTime / 2.0;
    
       // Parameter covariance, hardcoded accuracy values
       int num_params = NUM_PARAMETERS;
       int num_paramsSquare = num_params * num_params;
       double variance = m_gsd * m_gsd;
       for (int i = 0; i < num_paramsSquare; i++)
       {
          m_covariance[i] = 0.0;
       }
       for (int i = 0; i < num_params; i++)
       {
          m_covariance[i * num_params + i] = variance;
       }
    }
    
    
    //---------------------------------------------------------------------------
    // Core Photogrammetry
    //---------------------------------------------------------------------------
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::groundToImage
    //***************************************************************************
    csm::ImageCoord UsgsAstroLsSensorModel::groundToImage(
       const csm::EcefCoord& ground_pt,
       double                desired_precision,
       double*               achieved_precision,
       csm::WarningList*     warnings) const
    {
       // The public interface invokes the private interface with no adjustments.
       return groundToImage(
          ground_pt, _no_adjustment,
          desired_precision, achieved_precision, warnings);
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::groundToImage (internal version)
    //***************************************************************************
    csm::ImageCoord UsgsAstroLsSensorModel::groundToImage(
       const csm::EcefCoord& ground_pt,
       const std::vector<double>& adj,
       double                desired_precision,
       double*               achieved_precision,
       csm::WarningList*     warnings) const
    {
       // Search for the line, sample coordinate that viewed a given ground point.
       // This method uses an iterative secant method to search for the image
       // line.
    
       // Convert the ground precision to pixel precision so we can
       // check for convergence without re-intersecting
       csm::ImageCoord approxPoint;
       computeLinearApproximation(ground_pt, approxPoint);
       csm::ImageCoord approxNextPoint = approxPoint;
       if (approxNextPoint.line + 1 < m_totalLines) {
          ++approxNextPoint.line;
       }
       else {
          --approxNextPoint.line;
       }
       csm::EcefCoord approxIntersect = imageToGround(approxPoint, m_refElevation);
       csm::EcefCoord approxNextIntersect = imageToGround(approxNextPoint, m_refElevation);
       double lineDX = approxNextIntersect.x - approxIntersect.x;
       double lineDY = approxNextIntersect.y - approxIntersect.y;
       double lineDZ = approxNextIntersect.z - approxIntersect.z;
       double approxLineRes = sqrt(lineDX * lineDX + lineDY * lineDY + lineDZ * lineDZ);
       // Increase the precision by a small amount to ensure the desired precision is met
       double pixelPrec = desired_precision / approxLineRes * 0.9;
    
       // Start secant method search on the image lines
       double sampCtr = m_totalSamples / 2.0;
       double firstTime = getImageTime(csm::ImageCoord(0.0, sampCtr));
       double lastTime = getImageTime(csm::ImageCoord(m_totalLines, sampCtr));
       double firstOffset = computeViewingPixel(firstTime, ground_pt, adj, pixelPrec/2).line - 0.5;
       double lastOffset = computeViewingPixel(lastTime, ground_pt, adj, pixelPrec/2).line - 0.5;
    
       // Check if both offsets have the same sign.
       // This means there is not guaranteed to be a zero.
       if ((firstOffset > 0) != (lastOffset < 0)) {
            throw csm::Warning(
               csm::Warning::IMAGE_COORD_OUT_OF_BOUNDS,
               "The image coordinate is out of bounds of the image size.",
               "UsgsAstroLsSensorModel::groundToImage");
       }
    
       // Start secant method search
       for (int it = 0; it < 30; it++) {
          double nextTime = ((firstTime * lastOffset) - (lastTime * firstOffset))
                          / (lastOffset - firstOffset);
          // Because time across the image is not continuous, find the exposure closest
          // to the computed nextTime and use that.
    
          // I.E. if the computed nextTime is 0.3, and the middle exposure times for
          // lines are 0.07, 0.17, 0.27, 0.37, and 0.47; then use 0.27 because it is
          // the closest middle exposure time.
          auto referenceTimeIt = std::upper_bound(m_intTimeStartTimes.begin(),
                                                  m_intTimeStartTimes.end(),
                                                  nextTime);
          if (referenceTimeIt != m_intTimeStartTimes.begin()) {
             --referenceTimeIt;
          }
          size_t referenceIndex = std::distance(m_intTimeStartTimes.begin(), referenceTimeIt);
          double computedLine = (nextTime - m_intTimeStartTimes[referenceIndex]) / m_intTimes[referenceIndex]
                              + m_intTimeLines[referenceIndex] - 0.5; // subtract 0.5 for ISIS -> CSM pixel conversion
          double closestLine = floor(computedLine + 0.5);
          nextTime = getImageTime(csm::ImageCoord(closestLine, sampCtr));
    
          double nextOffset = computeViewingPixel(nextTime, ground_pt, adj, pixelPrec/2).line - 0.5;
    
          // remove the farthest away node
          if (fabs(firstTime - nextTime) > fabs(lastTime - nextTime)) {
            firstTime = nextTime;
            firstOffset = nextOffset;
          }
          else {
            lastTime = nextTime;
            lastOffset = nextOffset;
          }
          if (fabs(lastOffset - firstOffset) < pixelPrec) {
             break;
          }
       }
    
       // Avoid division by 0 if the first and last nodes are the same
       double computedTime = firstTime;
       if (fabs(lastOffset - firstOffset) > 10e-15) {
         computedTime = ((firstTime * lastOffset) - (lastTime * firstOffset))
                             / (lastOffset - firstOffset);
       }
    
       auto referenceTimeIt = std::upper_bound(m_intTimeStartTimes.begin(),
                                               m_intTimeStartTimes.end(),
                                               computedTime);
       if (referenceTimeIt != m_intTimeStartTimes.begin()) {
          --referenceTimeIt;
       }
       size_t referenceIndex = std::distance(m_intTimeStartTimes.begin(), referenceTimeIt);
       double computedLine = (computedTime - m_intTimeStartTimes[referenceIndex]) / m_intTimes[referenceIndex]
                           + m_intTimeLines[referenceIndex] - 0.5; // subtract 0.5 for ISIS -> CSM pixel conversion
       double closestLine = floor(computedLine + 0.5); // This assumes pixels are the interval [n, n+1)
       computedTime = getImageTime(csm::ImageCoord(closestLine, sampCtr));
       csm::ImageCoord calculatedPixel = computeViewingPixel(computedTime, ground_pt, adj, pixelPrec/2);
       // The computed pioxel is the detector pixel, so we need to convert that to image lines
       calculatedPixel.line += closestLine;
    
       // Reintersect to ensure the image point actually views the ground point.
       csm::EcefCoord calculatedPoint = imageToGround(calculatedPixel, m_refElevation);
       double dx = ground_pt.x - calculatedPoint.x;
       double dy = ground_pt.y - calculatedPoint.y;
       double dz = ground_pt.z - calculatedPoint.z;
       double len = dx * dx + dy * dy + dz * dz;
    
       // If the final correction is greater than 10 meters,
       // the solution is not valid enough to report even with a warning
       if (len > 100.0) {
          std::ostringstream msg;
          msg << "Did not converge. Ground point: (" << ground_pt.x << ", "
              << ground_pt.y << ", " << ground_pt.z << ") Computed image point: ("
              << calculatedPixel.line << ", " << calculatedPixel.samp
              << ") Computed ground point: (" << calculatedPoint.x << ", "
              << calculatedPoint.y << ", " << calculatedPoint.z << ")";
          throw csm::Error(
             csm::Error::ALGORITHM,
             msg.str(),
             "UsgsAstroLsSensorModel::groundToImage");
       }
    
       if (achieved_precision) {
          *achieved_precision = sqrt(len);
       }
    
       double preSquare = desired_precision * desired_precision;
       if (warnings && (desired_precision > 0.0) && (preSquare < len)) {
          std::stringstream msg;
          msg << "Desired precision not achieved. ";
          msg << len << "  " << preSquare << "\n";
          warnings->push_back(
             csm::Warning(csm::Warning::PRECISION_NOT_MET,
             msg.str().c_str(),
             "UsgsAstroLsSensorModel::groundToImage()"));
       }
    
       return calculatedPixel;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::groundToImage
    //***************************************************************************
    csm::ImageCoordCovar UsgsAstroLsSensorModel::groundToImage(
       const csm::EcefCoordCovar& groundPt,
       double  desired_precision,
       double* achieved_precision,
       csm::WarningList* warnings) const
    {
       // Ground to image with error propagation
       // Compute corresponding image point
       csm::EcefCoord gp;
       gp.x = groundPt.x;
       gp.y = groundPt.y;
       gp.z = groundPt.z;
    
       csm::ImageCoord ip = groundToImage(
          gp, desired_precision, achieved_precision, warnings);
       csm::ImageCoordCovar result(ip.line, ip.samp);
    
       // Compute partials ls wrt XYZ
       std::vector<double> prt(6, 0.0);
       prt = computeGroundPartials(groundPt);
    
       // Error propagation
       double ltx, lty, ltz;
       double stx, sty, stz;
       ltx =
          prt[0] * groundPt.covariance[0] +
          prt[1] * groundPt.covariance[3] +
          prt[2] * groundPt.covariance[6];
       lty =
          prt[0] * groundPt.covariance[1] +
          prt[1] * groundPt.covariance[4] +
          prt[2] * groundPt.covariance[7];
       ltz =
          prt[0] * groundPt.covariance[2] +
          prt[1] * groundPt.covariance[5] +
          prt[2] * groundPt.covariance[8];
       stx =
          prt[3] * groundPt.covariance[0] +
          prt[4] * groundPt.covariance[3] +
          prt[5] * groundPt.covariance[6];
       sty =
          prt[3] * groundPt.covariance[1] +
          prt[4] * groundPt.covariance[4] +
          prt[5] * groundPt.covariance[7];
       stz =
          prt[3] * groundPt.covariance[2] +
          prt[4] * groundPt.covariance[5] +
          prt[5] * groundPt.covariance[8];
    
       double gp_cov[4]; // Input gp cov in image space
       gp_cov[0] = ltx * prt[0] + lty * prt[1] + ltz * prt[2];
       gp_cov[1] = ltx * prt[3] + lty * prt[4] + ltz * prt[5];
       gp_cov[2] = stx * prt[0] + sty * prt[1] + stz * prt[2];
       gp_cov[3] = stx * prt[3] + sty * prt[4] + stz * prt[5];
    
       std::vector<double> unmodeled_cov = getUnmodeledError(ip);
       double sensor_cov[4]; // sensor cov in image space
       determineSensorCovarianceInImageSpace(gp, sensor_cov);
    
       result.covariance[0] = gp_cov[0] + unmodeled_cov[0] + sensor_cov[0];
       result.covariance[1] = gp_cov[1] + unmodeled_cov[1] + sensor_cov[1];
       result.covariance[2] = gp_cov[2] + unmodeled_cov[2] + sensor_cov[2];
       result.covariance[3] = gp_cov[3] + unmodeled_cov[3] + sensor_cov[3];
    
       return result;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::imageToGround
    //***************************************************************************
    csm::EcefCoord UsgsAstroLsSensorModel::imageToGround(
       const csm::ImageCoord& image_pt,
       double height,
       double desired_precision,
       double* achieved_precision,
       csm::WarningList* warnings) const
    {
       double xc, yc, zc;
       double vx, vy, vz;
       double xl, yl, zl;
       double dxl, dyl, dzl;
       losToEcf(
          image_pt.line, image_pt.samp, _no_adjustment,
          xc, yc, zc, vx, vy, vz, xl, yl, zl);
       if (m_aberrFlag == 1)
       {
          lightAberrationCorr(vx, vy, vz, xl, yl, zl, dxl, dyl, dzl);
          xl += dxl;
          yl += dyl;
          zl += dzl;
       }
    
       double aPrec;
       double x, y, z;
       losEllipsoidIntersect(
          height, xc, yc, zc, xl, yl, zl, x, y, z, aPrec, desired_precision);
    
       if (achieved_precision)
          *achieved_precision = aPrec;
    
       if (warnings && (desired_precision > 0.0) && (aPrec > desired_precision))
       {
          warnings->push_back(
             csm::Warning(
                csm::Warning::PRECISION_NOT_MET,
                "Desired precision not achieved.",
                "UsgsAstroLsSensorModel::imageToGround()"));
       }
    
       return csm::EcefCoord(x, y, z);
    }
    
    
    void UsgsAstroLsSensorModel::determineSensorCovarianceInImageSpace(
       csm::EcefCoord &gp,
       double         sensor_cov[4] ) const
    {
       int i, j, totalAdjParams;
       totalAdjParams = getNumParameters();
    
       std::vector<csm::RasterGM::SensorPartials> sensor_partials = computeAllSensorPartials(gp);
    
       sensor_cov[0] = 0.0;
       sensor_cov[1] = 0.0;
       sensor_cov[2] = 0.0;
       sensor_cov[3] = 0.0;
    
       for (i = 0; i < totalAdjParams; i++)
       {
          for (j = 0; j < totalAdjParams; j++)
          {
             sensor_cov[0] += sensor_partials[i].first  * getParameterCovariance(i, j) * sensor_partials[j].first;
             sensor_cov[1] += sensor_partials[i].second * getParameterCovariance(i, j) * sensor_partials[j].first;
             sensor_cov[2] += sensor_partials[i].first  * getParameterCovariance(i, j) * sensor_partials[j].second;
             sensor_cov[3] += sensor_partials[i].second * getParameterCovariance(i, j) * sensor_partials[j].second;
          }
       }
    }
    
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::imageToGround
    //***************************************************************************
    csm::EcefCoordCovar UsgsAstroLsSensorModel::imageToGround(
       const csm::ImageCoordCovar& image_pt,
       double height,
       double heightVariance,
       double desired_precision,
       double* achieved_precision,
       csm::WarningList* warnings) const
    {
       // Image to ground with error propagation
       // Use numerical partials
    
       const double DELTA_IMAGE = 1.0;
       const double DELTA_GROUND = m_gsd;
       csm::ImageCoord ip(image_pt.line, image_pt.samp);
    
       csm::EcefCoord gp = imageToGround(
          ip, height, desired_precision, achieved_precision, warnings);
    
       // Compute numerical partials xyz wrt to lsh
       ip.line = image_pt.line + DELTA_IMAGE;
       ip.samp = image_pt.samp;
       csm::EcefCoord gpl = imageToGround(ip, height, desired_precision);
       double xpl = (gpl.x - gp.x) / DELTA_IMAGE;
       double ypl = (gpl.y - gp.y) / DELTA_IMAGE;
       double zpl = (gpl.z - gp.z) / DELTA_IMAGE;
    
       ip.line = image_pt.line;
       ip.samp = image_pt.samp + DELTA_IMAGE;
       csm::EcefCoord gps = imageToGround(ip, height, desired_precision);
       double xps = (gps.x - gp.x) / DELTA_IMAGE;
       double yps = (gps.y - gp.y) / DELTA_IMAGE;
       double zps = (gps.z - gp.z) / DELTA_IMAGE;
    
       ip.line = image_pt.line;
       ip.samp = image_pt.samp; // +DELTA_IMAGE;
       csm::EcefCoord gph = imageToGround(ip, height + DELTA_GROUND, desired_precision);
       double xph = (gph.x - gp.x) / DELTA_GROUND;
       double yph = (gph.y - gp.y) / DELTA_GROUND;
       double zph = (gph.z - gp.z) / DELTA_GROUND;
    
       // Convert sensor covariance to image space
       double sCov[4];
       determineSensorCovarianceInImageSpace(gp, sCov);
    
       std::vector<double> unmod = getUnmodeledError(image_pt);
    
       double iCov[4];
       iCov[0] = image_pt.covariance[0] + sCov[0] + unmod[0];
       iCov[1] = image_pt.covariance[1] + sCov[1] + unmod[1];
       iCov[2] = image_pt.covariance[2] + sCov[2] + unmod[2];
       iCov[3] = image_pt.covariance[3] + sCov[3] + unmod[3];
    
       // Temporary matrix product
       double t00, t01, t02, t10, t11, t12, t20, t21, t22;
       t00 = xpl * iCov[0] + xps * iCov[2];
       t01 = xpl * iCov[1] + xps * iCov[3];
       t02 = xph * heightVariance;
       t10 = ypl * iCov[0] + yps * iCov[2];
       t11 = ypl * iCov[1] + yps * iCov[3];
       t12 = yph * heightVariance;
       t20 = zpl * iCov[0] + zps * iCov[2];
       t21 = zpl * iCov[1] + zps * iCov[3];
       t22 = zph * heightVariance;
    
       // Ground covariance
       csm::EcefCoordCovar result;
       result.x = gp.x;
       result.y = gp.y;
       result.z = gp.z;
    
       result.covariance[0] = t00 * xpl + t01 * xps + t02 * xph;
       result.covariance[1] = t00 * ypl + t01 * yps + t02 * yph;
       result.covariance[2] = t00 * zpl + t01 * zps + t02 * zph;
       result.covariance[3] = t10 * xpl + t11 * xps + t12 * xph;
       result.covariance[4] = t10 * ypl + t11 * yps + t12 * yph;
       result.covariance[5] = t10 * zpl + t11 * zps + t12 * zph;
       result.covariance[6] = t20 * xpl + t21 * xps + t22 * xph;
       result.covariance[7] = t20 * ypl + t21 * yps + t22 * yph;
       result.covariance[8] = t20 * zpl + t21 * zps + t22 * zph;
    
       return result;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::imageToProximateImagingLocus
    //***************************************************************************
    csm::EcefLocus UsgsAstroLsSensorModel::imageToProximateImagingLocus(
       const csm::ImageCoord& image_pt,
       const csm::EcefCoord& ground_pt,
       double desired_precision,
       double* achieved_precision,
       csm::WarningList* warnings) const
    {
       // Object ray unit direction near given ground location
       const double DELTA_GROUND = m_gsd;
    
       double x = ground_pt.x;
       double y = ground_pt.y;
       double z = ground_pt.z;
    
       // Elevation at input ground point
       double height, aPrec;
       computeElevation(x, y, z, height, aPrec, desired_precision);
    
       // Ground point on object ray with the same elevation
       csm::EcefCoord gp1 = imageToGround(
          image_pt, height, desired_precision, achieved_precision);
    
       // Vector between 2 ground points above
       double dx1 = x - gp1.x;
       double dy1 = y - gp1.y;
       double dz1 = z - gp1.z;
    
       // Unit vector along object ray
       csm::EcefCoord gp2 = imageToGround(
          image_pt, height - DELTA_GROUND, desired_precision, achieved_precision);
       double dx2 = gp2.x - gp1.x;
       double dy2 = gp2.y - gp1.y;
       double dz2 = gp2.z - gp1.z;
       double mag2 = sqrt(dx2 * dx2 + dy2 * dy2 + dz2 * dz2);
       dx2 /= mag2;
       dy2 /= mag2;
       dz2 /= mag2;
    
       // Point on object ray perpendicular to input point
    
       // Locus
       csm::EcefLocus locus;
       double scale = dx1 * dx2 + dy1 * dy2 + dz1 * dz2;
       gp2.x = gp1.x + scale * dx2;
       gp2.y = gp1.y + scale * dy2;
       gp2.z = gp1.z + scale * dz2;
    
       double hLocus;
       computeElevation(gp2.x, gp2.y, gp2.z, hLocus, aPrec, desired_precision);
       locus.point = imageToGround(
          image_pt, hLocus, desired_precision, achieved_precision, warnings);
    
       locus.direction.x = dx2;
       locus.direction.y = dy2;
       locus.direction.z = dz2;
    
       return locus;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::imageToRemoteImagingLocus
    //***************************************************************************
    csm::EcefLocus UsgsAstroLsSensorModel::imageToRemoteImagingLocus(
       const csm::ImageCoord& image_pt,
       double desired_precision,
       double* achieved_precision,
       csm::WarningList* warnings) const
    {
       // Object ray unit direction at exposure station
       // Exposure station arbitrary for orthos, so set elevation to zero
    
       // Set esposure station elevation to zero
       double height = 0.0;
       double GND_DELTA = m_gsd;
       // Point on object ray at zero elevation
       csm::EcefCoord gp1 = imageToGround(
          image_pt, height, desired_precision, achieved_precision, warnings);
    
       // Unit vector along object ray
       csm::EcefCoord gp2 = imageToGround(
          image_pt, height - GND_DELTA, desired_precision, achieved_precision, warnings);
    
       double dx2, dy2, dz2;
       dx2 = gp2.x - gp1.x;
       dy2 = gp2.y - gp1.y;
       dz2 = gp2.z - gp1.z;
       double mag2 = sqrt(dx2 * dx2 + dy2 * dy2 + dz2 * dz2);
       dx2 /= mag2;
       dy2 /= mag2;
       dz2 /= mag2;
    
       // Locus
       csm::EcefLocus locus;
       locus.point = gp1;
       locus.direction.x = dx2;
       locus.direction.y = dy2;
       locus.direction.z = dz2;
    
       return locus;
    }
    
    //---------------------------------------------------------------------------
    // Uncertainty Propagation
    //---------------------------------------------------------------------------
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::computeGroundPartials
    //***************************************************************************
    std::vector<double> UsgsAstroLsSensorModel::computeGroundPartials(
       const csm::EcefCoord& ground_pt) const
    {
       double GND_DELTA = m_gsd;
       // Partial of line, sample wrt X, Y, Z
       double x = ground_pt.x;
       double y = ground_pt.y;
       double z = ground_pt.z;
    
       csm::ImageCoord ipB = groundToImage(ground_pt);
       csm::ImageCoord ipX = groundToImage(csm::EcefCoord(x + GND_DELTA, y, z));
       csm::ImageCoord ipY = groundToImage(csm::EcefCoord(x, y + GND_DELTA, z));
       csm::ImageCoord ipZ = groundToImage(csm::EcefCoord(x, y, z + GND_DELTA));
    
       std::vector<double> partials(6, 0.0);
       partials[0] = (ipX.line - ipB.line) / GND_DELTA;
       partials[3] = (ipX.samp - ipB.samp) / GND_DELTA;
       partials[1] = (ipY.line - ipB.line) / GND_DELTA;
       partials[4] = (ipY.samp - ipB.samp) / GND_DELTA;
       partials[2] = (ipZ.line - ipB.line) / GND_DELTA;
       partials[5] = (ipZ.samp - ipB.samp) / GND_DELTA;
    
       return partials;
    }
    
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::computeSensorPartials
    //***************************************************************************
    csm::RasterGM::SensorPartials UsgsAstroLsSensorModel::computeSensorPartials(
       int index,
       const csm::EcefCoord& ground_pt,
       double  desired_precision,
       double* achieved_precision,
       csm::WarningList* warnings) const
    {
       // Compute image coordinate first
       csm::ImageCoord img_pt = groundToImage(
          ground_pt, desired_precision, achieved_precision);
    
       // Call overloaded function
       return computeSensorPartials(
          index, img_pt, ground_pt, desired_precision, achieved_precision, warnings);
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::computeSensorPartials
    //***************************************************************************
    csm::RasterGM::SensorPartials UsgsAstroLsSensorModel::computeSensorPartials(
       int                    index,
       const csm::ImageCoord& image_pt,
       const csm::EcefCoord&  ground_pt,
       double                 desired_precision,
       double*                achieved_precision,
       csm::WarningList*      warnings) const
    {
       // Compute numerical partials ls wrt specific parameter
    
       const double DELTA = m_gsd;
       std::vector<double> adj(UsgsAstroLsSensorModel::NUM_PARAMETERS, 0.0);
       adj[index] = DELTA;
    
       csm::ImageCoord img1 = groundToImage(
          ground_pt, adj, desired_precision, achieved_precision, warnings);
    
       double line_partial = (img1.line - image_pt.line) / DELTA;
       double sample_partial = (img1.samp - image_pt.samp) / DELTA;
    
       return csm::RasterGM::SensorPartials(line_partial, sample_partial);
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::computeAllSensorPartials
    //***************************************************************************
    std::vector<csm::RasterGM::SensorPartials>
    UsgsAstroLsSensorModel::computeAllSensorPartials(
       const csm::EcefCoord& ground_pt,
       csm::param::Set       pSet,
       double                desired_precision,
       double*               achieved_precision,
       csm::WarningList*     warnings) const
    {
       csm::ImageCoord image_pt = groundToImage(
          ground_pt, desired_precision, achieved_precision, warnings);
    
       return computeAllSensorPartials(
          image_pt, ground_pt, pSet, desired_precision, achieved_precision, warnings);
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::computeAllSensorPartials
    //***************************************************************************
    std::vector<csm::RasterGM::SensorPartials>
    UsgsAstroLsSensorModel::computeAllSensorPartials(
       const csm::ImageCoord& image_pt,
       const csm::EcefCoord&  ground_pt,
       csm::param::Set        pSet,
       double                 desired_precision,
       double*                achieved_precision,
       csm::WarningList*      warnings) const
    {
       std::vector<int> indices = getParameterSetIndices(pSet);
       size_t num = indices.size();
       std::vector<csm::RasterGM::SensorPartials> partials;
       for (int index = 0; index < num; index++)
       {
          partials.push_back(
             computeSensorPartials(
                indices[index], image_pt, ground_pt,
                desired_precision, achieved_precision, warnings));
       }
       return partials;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getParameterCovariance
    //***************************************************************************
    double UsgsAstroLsSensorModel::getParameterCovariance(
       int index1,
       int index2) const
    {
       int index = UsgsAstroLsSensorModel::NUM_PARAMETERS * index1 + index2;
       return m_covariance[index];
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::setParameterCovariance
    //***************************************************************************
    void UsgsAstroLsSensorModel::setParameterCovariance(
       int index1,
       int index2,
       double covariance)
    {
       int index = UsgsAstroLsSensorModel::NUM_PARAMETERS * index1 + index2;
       m_covariance[index] = covariance;
    }
    
    //---------------------------------------------------------------------------
    // Time and Trajectory
    //---------------------------------------------------------------------------
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getTrajectoryIdentifier
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getTrajectoryIdentifier() const
    {
       return m_trajectoryIdentifier;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getReferenceDateAndTime
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getReferenceDateAndTime() const
    {
       if (m_referenceDateAndTime == "UNKNOWN")
       {
          throw csm::Error(
             csm::Error::UNSUPPORTED_FUNCTION,
             "Unsupported function",
             "UsgsAstroLsSensorModel::getReferenceDateAndTime");
       }
       return m_referenceDateAndTime;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getImageTime
    //***************************************************************************
    double UsgsAstroLsSensorModel::getImageTime(
       const csm::ImageCoord& image_pt) const
    {
       // Flip image taken backwards
       double line1 = image_pt.line;
    
       // CSM image convention: UL pixel center == (0.5, 0.5)
       // USGS image convention: UL pixel center == (1.0, 1.0)
    
       double lineCSMFull = line1 + m_offsetLines;
       double lineUSGSFull = floor(lineCSMFull) + 0.5;
    
       // These calculation assumes that the values in the integration time
       // vectors are in terms of ISIS' pixels
       auto referenceLineIt = std::upper_bound(m_intTimeLines.begin(),
                                               m_intTimeLines.end(),
                                               lineUSGSFull);
       if (referenceLineIt != m_intTimeLines.begin()) {
          --referenceLineIt;
       }
       size_t referenceIndex = std::distance(m_intTimeLines.begin(), referenceLineIt);
    
       double time = m_intTimeStartTimes[referenceIndex]
          + m_intTimes[referenceIndex] * (lineUSGSFull - m_intTimeLines[referenceIndex]);
    
       return time;
    
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getSensorPosition
    //***************************************************************************
    csm::EcefCoord UsgsAstroLsSensorModel::getSensorPosition(
       const csm::ImageCoord& imagePt) const
    {
       return getSensorPosition(getImageTime(imagePt));
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getSensorPosition
    //***************************************************************************
    csm::EcefCoord UsgsAstroLsSensorModel::getSensorPosition(double time) const
    
    {
       double x, y, z, vx, vy, vz;
       getAdjSensorPosVel(time, _no_adjustment, x, y, z, vx, vy, vz);
    
       return csm::EcefCoord(x, y, z);
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getSensorVelocity
    //***************************************************************************
    csm::EcefVector UsgsAstroLsSensorModel::getSensorVelocity(
       const csm::ImageCoord& imagePt) const
    {
       return getSensorVelocity(getImageTime(imagePt));
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getSensorVelocity
    //***************************************************************************
    csm::EcefVector UsgsAstroLsSensorModel::getSensorVelocity(double time) const
    {
       double x, y, z, vx, vy, vz;
       getAdjSensorPosVel(time, _no_adjustment, x, y, z, vx, vy, vz);
    
       return csm::EcefVector(vx, vy, vz);
    }
    
    //---------------------------------------------------------------------------
    // Sensor Model Parameters
    //---------------------------------------------------------------------------
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::setParameterValue
    //***************************************************************************
    void UsgsAstroLsSensorModel::setParameterValue(int index, double value)
    {
       m_parameterVals[index] = value;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getParameterValue
    //***************************************************************************
    double UsgsAstroLsSensorModel::getParameterValue(int index) const
    {
       return m_parameterVals[index];
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getParameterName
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getParameterName(int index) const
    {
       return PARAMETER_NAME[index];
    }
    
    std::string UsgsAstroLsSensorModel::getParameterUnits(int index) const
    {
       // All parameters are meters or scaled to meters
       return "m";
    }
    
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getNumParameters
    //***************************************************************************
    int UsgsAstroLsSensorModel::getNumParameters() const
    {
       return UsgsAstroLsSensorModel::NUM_PARAMETERS;
    }
    
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getParameterType
    //***************************************************************************
    csm::param::Type UsgsAstroLsSensorModel::getParameterType(int index) const
    {
       return m_parameterType[index];
    }
    
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::setParameterType
    //***************************************************************************
    void UsgsAstroLsSensorModel::setParameterType(
       int index, csm::param::Type pType)
    {
       m_parameterType[index] = pType;
    }
    
    //---------------------------------------------------------------------------
    // Sensor Model Information
    //---------------------------------------------------------------------------
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getPedigree
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getPedigree() const
    {
       return "USGS_LINE_SCANNER";
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getImageIdentifier
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getImageIdentifier() const
    {
       return m_imageIdentifier;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::setImageIdentifier
    //***************************************************************************
    void UsgsAstroLsSensorModel::setImageIdentifier(
       const std::string& imageId,
       csm::WarningList* warnings)
    {
       // Image id should include the suffix without the path name
       m_imageIdentifier = imageId;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getSensorIdentifier
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getSensorIdentifier() const
    {
       return m_sensorIdentifier;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getPlatformIdentifier
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getPlatformIdentifier() const
    {
       return m_platformIdentifier;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::setReferencePoint
    //***************************************************************************
    void UsgsAstroLsSensorModel::setReferencePoint(const csm::EcefCoord& ground_pt)
    {
       m_referencePointXyz = ground_pt;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getReferencePoint
    //***************************************************************************
    csm::EcefCoord UsgsAstroLsSensorModel::getReferencePoint() const
    {
       // Return ground point at image center
       return m_referencePointXyz;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getSensorModelName
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getModelName() const
    {
       return UsgsAstroLsSensorModel::_SENSOR_MODEL_NAME;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getImageStart
    //***************************************************************************
    csm::ImageCoord UsgsAstroLsSensorModel::getImageStart() const
    {
       return csm::ImageCoord(0.0, 0.0);
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getImageSize
    //***************************************************************************
    csm::ImageVector UsgsAstroLsSensorModel::getImageSize() const
    {
       return csm::ImageVector(m_totalLines, m_totalSamples);
    }
    
    //---------------------------------------------------------------------------
    // Sensor Model State
    //---------------------------------------------------------------------------
    //
    // //***************************************************************************
    // // UsgsAstroLsSensorModel::getSensorModelState
    // //***************************************************************************
    // std::string UsgsAstroLsSensorModel::setModelState(std::string stateString) const
    // {
    //   auto j = json::parse(stateString);
    //   int num_params    = NUM_PARAMETERS;
    //   int num_paramsSq = num_params * num_params;
    //
    //   m_imageIdentifier = j["m_imageIdentifier"];
    //   m_sensorType = j["m_sensorType"];
    //   m_totalLines = j["m_totalLines"];
    //   m_totalSamples = j["m_totalSamples"];
    //   m_offsetLines = j["m_offsetLines"];
    //   m_offsetSamples = j["m_offsetSamples"];
    //   m_platformFlag = j["m_platformFlag"];
    //   m_aberrFlag = j["m_aberrFlag"];
    //   m_atmRefFlag = j["m_atmrefFlag"];
    //   m_intTimeLines = j["m_intTimeLines"].get<std::vector<double>>();
    //   m_intTimeStartTimes = j["m_intTimeStartTimes"].get<std::vector<double>>();
    //   m_intTimes = j["m_intTimes"].get<std::vector<double>>();
    //   m_startingEphemerisTime = j["m_startingEphemerisTime"];
    //   m_centerEphemerisTime = j["m_centerEphemerisTime"];
    //   m_detectorSampleSumming = j["m_detectorSampleSumming"];
    //   m_startingSample = j["m_startingSample"];
    //   m_ikCode = j["m_ikCode"];
    //   m_focal = j["m_focal"];
    //   m_isisZDirection = j["m_isisZDirection"];
    //   for (int i = 0; i < 3; i++) {
    //     m_opticalDistCoef[i] = j["m_opticalDistCoef"][i];
    //     m_iTransS[i] = j["m_iTransS"][i];
    //     m_iTransL[i] = j["m_iTransL"][i];
    //   }
    //   m_detectorSampleOrigin = j["m_detectorSampleOrigin"];
    //   m_detectorLineOrigin = j["m_detectorLineOrigin"];
    //   m_detectorLineOffset = j["m_detectorLineOffset"];
    //   for (int i = 0; i < 9; i++) {
    //       m_mountingMatrix[i] = j["m_mountingMatrix"][i];
    //   }
    //   m_semiMajorAxis = j["m_semiMajorAxis"];
    //   m_semiMinorAxis = j["m_semiMinorAxis"];
    //   m_referenceDateAndTime = j["m_referenceDateAndTime"];
    //   m_platformIdentifier = j["m_platformIdentifier"];
    //   m_sensorIdentifier = j["m_sensorIdentifier"];
    //   m_trajectoryIdentifier = j["m_trajectoryIdentifier"];
    //   m_collectionIdentifier = j["m_collectionIdentifier"];
    //   m_refElevation = j["m_refElevation"];
    //   m_minElevation = j["m_minElevation"];
    //   m_maxElevation = j["m_maxElevation"];
    //   m_dtEphem = j["m_dtEphem"];
    //   m_t0Ephem = j["m_t0Ephem"];
    //   m_dtQuat = j["m_dtQuat"];
    //   m_t0Quat = j["m_t0Quat"];
    //   m_numEphem = j["m_numEphem"];
    //   m_numQuaternions = j["m_numQuaternions"];
    //   m_referencePointXyz.x = j["m_referencePointXyz"][0];
    //   m_referencePointXyz.y = j["m_referencePointXyz"][1];
    //   m_referencePointXyz.z = j["m_referencePointXyz"][2];
    //   m_gsd = j["m_gsd"];
    //   m_flyingHeight = j["m_flyingHeight"];
    //   m_halfSwath = j["m_halfSwath"];
    //   m_halfTime = j["m_halfTime"];
    //   m_imageFlipFlag = j["m_imageFlipFlag"];
    //   // Vector = is overloaded so explicit get with type required.
    //   m_ephemPts = j["m_ephemPts"].get<std::vector<double>>();
    //   m_ephemRates = j["m_ephemRates"].get<std::vector<double>>();
    //   m_quaternions = j["m_quaternions"].get<std::vector<double>>();
    //   m_parameterVals = j["m_parameterVals"].get<std::vector<double>>();
    //   m_covariance = j["m_covariance"].get<std::vector<double>>();
    //
    //   for (int i = 0; i < num_params; i++) {
    //     for (int k = 0; k < NUM_PARAM_TYPES; k++) {
    //       if (j["m_parameterType"][i] == PARAM_STRING_ALL[k]) {
    //         m_parameterType[i] = PARAM_CHAR_ALL[k];
    //         break;
    //     }
    //    }
    //   }
    //
    // }
    
    //---------------------------------------------------------------------------
    //  Monoscopic Mensuration
    //---------------------------------------------------------------------------
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getValidHeightRange
    //***************************************************************************
    std::pair<double, double> UsgsAstroLsSensorModel::getValidHeightRange() const
    {
       return std::pair<double, double>(m_minElevation, m_maxElevation);
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getValidImageRange
    //***************************************************************************
    std::pair<csm::ImageCoord, csm::ImageCoord>
    UsgsAstroLsSensorModel::getValidImageRange() const
    {
       return std::pair<csm::ImageCoord, csm::ImageCoord>(
          csm::ImageCoord(0.0, 0.0),
          csm::ImageCoord(m_totalLines, m_totalSamples));
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getIlluminationDirection
    //***************************************************************************
    csm::EcefVector UsgsAstroLsSensorModel::getIlluminationDirection(
       const csm::EcefCoord& groundPt) const
    {
       throw csm::Error(
          csm::Error::UNSUPPORTED_FUNCTION,
          "Unsupported function",
          "UsgsAstroLsSensorModel::getIlluminationDirection");
    }
    
    //---------------------------------------------------------------------------
    //  Error Correction
    //---------------------------------------------------------------------------
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getNumGeometricCorrectionSwitches
    //***************************************************************************
    int UsgsAstroLsSensorModel::getNumGeometricCorrectionSwitches() const
    {
       return 0;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getGeometricCorrectionName
    //***************************************************************************
    
    std::string UsgsAstroLsSensorModel::getGeometricCorrectionName(int index) const
    {
       // Since there are no geometric corrections, all indices are out of range
       throw csm::Error(
          csm::Error::INDEX_OUT_OF_RANGE,
          "Index is out of range.",
          "UsgsAstroLsSensorModel::getGeometricCorrectionName");
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::setGeometricCorrectionSwitch
    //***************************************************************************
    void UsgsAstroLsSensorModel::setGeometricCorrectionSwitch(
       int  index,
       bool value,
       csm::param::Type pType)
    
    {
       // Since there are no geometric corrections, all indices are out of range
       throw csm::Error(
          csm::Error::INDEX_OUT_OF_RANGE,
          "Index is out of range.",
          "UsgsAstroLsSensorModel::setGeometricCorrectionSwitch");
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getGeometricCorrectionSwitch
    //***************************************************************************
    bool UsgsAstroLsSensorModel::getGeometricCorrectionSwitch(int index) const
    {
       // Since there are no geometric corrections, all indices are out of range
       throw csm::Error(
          csm::Error::INDEX_OUT_OF_RANGE,
          "Index is out of range.",
          "UsgsAstroLsSensorModel::getGeometricCorrectionSwitch");
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getCrossCovarianceMatrix
    //***************************************************************************
    std::vector<double> UsgsAstroLsSensorModel::getCrossCovarianceMatrix(
       const csm::GeometricModel& comparisonModel,
       csm::param::Set            pSet,
       const csm::GeometricModel::GeometricModelList& otherModels) const
    {
       // No correlation between models.
       const std::vector<int>& indices = getParameterSetIndices(pSet);
       size_t num_rows = indices.size();
       const std::vector<int>& indices2 = comparisonModel.getParameterSetIndices(pSet);
       size_t num_cols = indices.size();
    
       return std::vector<double>(num_rows * num_cols, 0.0);
    }
    
    const csm::CorrelationModel&
    UsgsAstroLsSensorModel::getCorrelationModel() const
    {
       // All Line Scanner images are assumed uncorrelated
       return _no_corr_model;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getUnmodeledCrossCovariance
    //***************************************************************************
    std::vector<double> UsgsAstroLsSensorModel::getUnmodeledCrossCovariance(
       const csm::ImageCoord& pt1,
       const csm::ImageCoord& pt2) const
    {
       // No unmodeled error
       return std::vector<double>(4, 0.0);
    }
    
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getCollectionIdentifier
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getCollectionIdentifier() const
    {
       return m_collectionIdentifier;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::hasShareableParameters
    //***************************************************************************
    bool UsgsAstroLsSensorModel::hasShareableParameters() const
    {
       // Parameter sharing is not supported for this sensor
       return false;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::isParameterShareable
    //***************************************************************************
    bool UsgsAstroLsSensorModel::isParameterShareable(int index) const
    {
       // Parameter sharing is not supported for this sensor
       return false;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getParameterSharingCriteria
    //***************************************************************************
    csm::SharingCriteria UsgsAstroLsSensorModel::getParameterSharingCriteria(
       int index) const
    {
       // Parameter sharing is not supported for this sensor,
       // all indices are out of range
       throw csm::Error(
          csm::Error::INDEX_OUT_OF_RANGE,
          "Index out of range.",
          "UsgsAstroLsSensorModel::getParameterSharingCriteria");
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getSensorType
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getSensorType() const
    {
       return CSM_SENSOR_TYPE_EO;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getSensorMode
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getSensorMode() const
    {
       return CSM_SENSOR_MODE_PB;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getVersion
    //***************************************************************************
    csm::Version UsgsAstroLsSensorModel::getVersion() const
    {
       return csm::Version(1, 0, 0);
    }
    
    
    csm::Ellipsoid UsgsAstroLsSensorModel::getEllipsoid() const
    {
       return csm::Ellipsoid(m_semiMajorAxis, m_semiMinorAxis);
    }
    
    void UsgsAstroLsSensorModel::setEllipsoid(
       const csm::Ellipsoid &ellipsoid)
    {
       m_semiMajorAxis = ellipsoid.getSemiMajorRadius();
       m_semiMinorAxis = ellipsoid.getSemiMinorRadius();
    }
    
    
    
    double UsgsAstroLsSensorModel::getValue(
       int   index,
       const std::vector<double> &adjustments) const
    {
       return m_parameterVals[index] + adjustments[index];
    }
    
    
    //***************************************************************************
    // Functions pulled out of losToEcf
    // **************************************************************************
    
    // CSM image image convention: UL pixel center == (0.5, 0.5)
    // USGS image convention: UL pixel center == (1.0, 1.0)
    void UsgsAstroLsSensorModel::convertToUSGSCoordinates(const double& csmLine, const double& csmSample, double &usgsLine, double &usgsSample) const{
      // apply the offset
      double sampleCSMFull = csmSample + m_offsetSamples;
      double sampleUSGSFull = sampleCSMFull + 0.5;
      // why don't we apply a line offset? 
      double fractionalLine = csmLine - floor(csmLine) - 0.5;
    
      usgsSample = sampleUSGSFull;
      usgsLine = fractionalLine; 
    }
    
    
    // Compute distorted focalPlane coordinates in mm
    void UsgsAstroLsSensorModel::computeDistortedFocalPlaneCoordinates(const double& lineUSGS, const double& sampleUSGS, double &distortedLine, double& distortedSample) const{
      double isisDetSample = (sampleUSGS - 1.0)
          * m_detectorSampleSumming + m_startingSample;
      double m11 = m_iTransL[1];
      double m12 = m_iTransL[2];
      double m21 = m_iTransS[1];
      double m22 = m_iTransS[2];
      double t1 = lineUSGS + m_detectorLineOffset
                   - m_detectorLineOrigin - m_iTransL[0];
      double t2 = isisDetSample - m_detectorSampleOrigin - m_iTransS[0];
      double determinant = m11 * m22 - m12 * m21;
      double p11 = m11 / determinant;
      double p12 = -m12 / determinant;
      double p21 = -m21 / determinant;
      double p22 = m22 / determinant;
      double isisNatFocalPlaneX = p11 * t1 + p12 * t2;
      double isisNatFocalPlaneY = p21 * t1 + p22 * t2;
      distortedLine = isisNatFocalPlaneX; 
      distortedSample = isisNatFocalPlaneY; 
    }
    
    // Compute un-distorted image coordinates in mm / apply lens distortion correction
    void UsgsAstroLsSensorModel::computeUndistortedFocalPlaneCoordinates(const double &distortedFocalPlaneX, const double& distortedFocalPlaneY, double& undistortedFocalPlaneX, double& undistortedFocalPlaneY) const{
      undistortedFocalPlaneX = distortedFocalPlaneX;
      undistortedFocalPlaneY = distortedFocalPlaneY;
     if (m_opticalDistCoef[0] != 0.0 ||
        m_opticalDistCoef[1] != 0.0 ||
        m_opticalDistCoef[2] != 0.0)
      {
         double rr = distortedFocalPlaneX * distortedFocalPlaneX
            + distortedFocalPlaneY * distortedFocalPlaneY;
         if (rr > 1.0E-6)
         {
            double dr = m_opticalDistCoef[0] + (rr * (m_opticalDistCoef[1]
               + rr * m_opticalDistCoef[2]));
            undistortedFocalPlaneX = distortedFocalPlaneX * (1.0 - dr);
            undistortedFocalPlaneY = distortedFocalPlaneY * (1.0 - dr);
         }
      }
    };
    
    
    // Define imaging ray in image space (In other words, create a look vector in camera space)
    void UsgsAstroLsSensorModel::createCameraLookVector(double& undistortedFocalPlaneX, double& undistortedFocalPlaneY,  const std::vector<double>& adj, double losIsis[]) const{
       losIsis[0] = -undistortedFocalPlaneX * m_isisZDirection;
       losIsis[1] = -undistortedFocalPlaneY * m_isisZDirection;
       losIsis[2] = -m_focal * (1.0 - getValue(15, adj) / m_halfSwath);
       double isisMag = sqrt(losIsis[0] * losIsis[0]
          + losIsis[1] * losIsis[1]
          + losIsis[2] * losIsis[2]);
       losIsis[0] /= isisMag;
       losIsis[1] /= isisMag;
       losIsis[2] /= isisMag;
    };
    
    
    // Given a time and a flag to indicate whether the a->b or b->a rotation should be calculated
    // uses the quaternions in the m_quaternions member to calclate a rotation matrix. 
    void UsgsAstroLsSensorModel::calculateRotationMatrixFromQuaternions(double time, bool invert, double rotationMatrix[9]) const {
      int nOrder = 8;
      if (m_platformFlag == 0)
         nOrder = 4;
      int nOrderQuat = nOrder;
      if (m_numQuaternions < 6 && nOrder == 8)
         nOrderQuat = 4;
      double q[4];
      lagrangeInterp(
         m_numQuaternions, &m_quaternions[0], m_t0Quat, m_dtQuat,
         time, 4, nOrderQuat, q);
      double norm = sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
    
      if (!invert) {
        q[0] /= norm; 
        q[1] /= norm;
        q[2] /= norm;
        q[3] /= norm;
      }
      else {
        q[0] /= -norm; 
        q[1] /= -norm;
        q[2] /= -norm;
        q[3] /= norm;
      }
    
      rotationMatrix[0] = q[0] * q[0] - q[1] * q[1] - q[2] * q[2] + q[3] * q[3];
      rotationMatrix[1] = 2 * (q[0] * q[1] - q[2] * q[3]);
      rotationMatrix[2] = 2 * (q[0] * q[2] + q[1] * q[3]);
      rotationMatrix[3] = 2 * (q[0] * q[1] + q[2] * q[3]);
      rotationMatrix[4] = -q[0] * q[0] + q[1] * q[1] - q[2] * q[2] + q[3] * q[3];
      rotationMatrix[5] = 2 * (q[1] * q[2] - q[0] * q[3]);
      rotationMatrix[6] = 2 * (q[0] * q[2] - q[1] * q[3]);
      rotationMatrix[7] = 2 * (q[1] * q[2] + q[0] * q[3]);
      rotationMatrix[8] = -q[0] * q[0] - q[1] * q[1] + q[2] * q[2] + q[3] * q[3];
    };
    
    
    void UsgsAstroLsSensorModel::calculateAttitudeCorrection(double time, const std::vector<double>& adj, double attCorr[9]) const {
      double aTime = time - m_t0Quat;
        double euler[3];
        double nTime = aTime / m_halfTime;
        double nTime2 = nTime * nTime;
        euler[0] =
           (getValue(6, adj) + getValue(9, adj)* nTime + getValue(12, adj)* nTime2) / m_flyingHeight;
        euler[1] =
           (getValue(7, adj) + getValue(10, adj)* nTime + getValue(13, adj)* nTime2) / m_flyingHeight;
        euler[2] =
           (getValue(8, adj) + getValue(11, adj)* nTime + getValue(14, adj)* nTime2) / m_halfSwath;
        double cos_a = cos(euler[0]);
        double sin_a = sin(euler[0]);
        double cos_b = cos(euler[1]);
        double sin_b = sin(euler[1]);
        double cos_c = cos(euler[2]);
        double sin_c = sin(euler[2]);
    
        attCorr[0] = cos_b * cos_c;
        attCorr[1] = -cos_a * sin_c + sin_a * sin_b * cos_c;
        attCorr[2] = sin_a * sin_c + cos_a * sin_b * cos_c;
        attCorr[3] = cos_b * sin_c;
        attCorr[4] = cos_a * cos_c + sin_a * sin_b * sin_c;
        attCorr[5] = -sin_a * cos_c + cos_a * sin_b * sin_c;
        attCorr[6] = -sin_b;
        attCorr[7] = sin_a * cos_b;
        attCorr[8] = cos_a * cos_b;
    }
    
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::losToEcf
    //***************************************************************************
    void UsgsAstroLsSensorModel::losToEcf(
       const double& line,       // CSM image convention
       const double& sample,     //    UL pixel center == (0.5, 0.5)
       const std::vector<double>& adj, // Parameter Adjustments for partials
       double&       xc,         // output sensor x coordinate
       double&       yc,         // output sensor y coordinate
       double&       zc,         // output sensor z coordinate
       double&       vx,         // output sensor x velocity
       double&       vy,         // output sensor y velocity
       double&       vz,         // output sensor z velocity
       double&       bodyLookX,         // output line-of-sight x coordinate
       double&       bodyLookY,         // output line-of-sight y coordinate
       double&       bodyLookZ) const  // output line-of-sight z coordinate
    {
       //# private_func_description
       //  Computes image ray (look vector) in ecf coordinate system.
       // Compute adjusted sensor position and velocity
    
       double time = getImageTime(csm::ImageCoord(line, sample));
       getAdjSensorPosVel(time, adj, xc, yc, zc, vx, vy, vz);
       // CSM image image convention: UL pixel center == (0.5, 0.5)
       // USGS image convention: UL pixel center == (1.0, 1.0)
       double sampleCSMFull = sample + m_offsetSamples;
       double sampleUSGSFull = sampleCSMFull;
       double fractionalLine = line - floor(line);
    
       // Compute distorted image coordinates in mm (sample, line on image (pixels) -> focal plane 
       double isisNatFocalPlaneX, isisNatFocalPlaneY; 
       computeDistortedFocalPlaneCoordinates(fractionalLine, sampleUSGSFull, isisNatFocalPlaneX, isisNatFocalPlaneY);
    
       // Remove lens distortion
       double isisFocalPlaneX, isisFocalPlaneY; 
       computeUndistortedFocalPlaneCoordinates(isisNatFocalPlaneX, isisNatFocalPlaneY, isisFocalPlaneX, isisFocalPlaneY);
       
      // Define imaging ray (look vector) in camera space
       double losIsis[3];
       createCameraLookVector(isisFocalPlaneX, isisFocalPlaneY, adj, losIsis);
    
       // Apply attitude correction
       double attCorr[9];
       calculateAttitudeCorrection(time, adj, attCorr); 
    
       double cameraLook[3];
       cameraLook[0] = attCorr[0] * losIsis[0] 
                     + attCorr[1] * losIsis[1]
                     + attCorr[2] * losIsis[2];
       cameraLook[1] = attCorr[3] * losIsis[0] 
                     + attCorr[4] * losIsis[1]
                     + attCorr[5] * losIsis[2];
       cameraLook[2] = attCorr[6] * losIsis[0] 
                     + attCorr[7] * losIsis[1]
                     + attCorr[8] * losIsis[2];
    
    // Rotate the look vector into the body fixed frame from the camera reference frame by applying the rotation matrix from the sensor quaternions
       double cameraToBody[9];
       calculateRotationMatrixFromQuaternions(time, false, cameraToBody);
    
       bodyLookX = cameraToBody[0] * cameraLook[0] 
                 + cameraToBody[1] * cameraLook[1]
                 + cameraToBody[2] * cameraLook[2];
       bodyLookY = cameraToBody[3] * cameraLook[0] 
                 + cameraToBody[4] * cameraLook[1]
                 + cameraToBody[5] * cameraLook[2];
       bodyLookZ = cameraToBody[6] * cameraLook[0] 
                 + cameraToBody[7] * cameraLook[1]
                 + cameraToBody[8] * cameraLook[2];
    }
    
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::lightAberrationCorr
    //**************************************************************************
    void UsgsAstroLsSensorModel::lightAberrationCorr(
       const double& vx,
       const double& vy,
       const double& vz,
       const double& xl,
       const double& yl,
       const double& zl,
       double&       dxl,
       double&       dyl,
       double&       dzl) const
    {
       //# func_description
       //  Computes light aberration correction vector
    
       // Compute angle between the image ray and the velocity vector
    
       double dotP = xl * vx + yl * vy + zl * vz;
       double losMag = sqrt(xl * xl + yl * yl + zl * zl);
       double velocityMag = sqrt(vx *  vx + vy * vy + vz * vz);
       double cosThetap = dotP / (losMag * velocityMag);
       double sinThetap = sqrt(1.0 - cosThetap * cosThetap);
    
       // Image ray is parallel to the velocity vector
    
       if (1.0 == fabs(cosThetap))
       {
          dxl = 0.0;
          dyl = 0.0;
          dzl = 0.0;
       }
    
       // Compute the angle between the corrected image ray and spacecraft
       // velocity.  This key equation is derived using Lorentz transform.
    
       double speedOfLight = 299792458.0;   // meters per second
       double beta = velocityMag / speedOfLight;
       double cosTheta = (beta - cosThetap) / (beta * cosThetap - 1.0);
       double sinTheta = sqrt(1.0 - cosTheta * cosTheta);
    
       // Compute line-of-sight correction
    
       double cfac = ((cosTheta * sinThetap
          - sinTheta * cosThetap) * losMag)
          / (sinTheta * velocityMag);
       dxl = cfac * vx;
       dyl = cfac * vy;
       dzl = cfac * vz;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::lagrangeInterp
    //***************************************************************************
    void UsgsAstroLsSensorModel::lagrangeInterp(
       const int&     numTime,
       const double*  valueArray,
       const double&  startTime,
       const double&  delTime,
       const double&  time,
       const int&     vectorLength,
       const int&     i_order,
       double*        valueVector) const
    {
       // Lagrange interpolation for uniform post interval.
       // Largest order possible is 8th. Points far away from
       // data center are handled gracefully to avoid failure.
    
       // Compute index
    
       double fndex = (time - startTime) / delTime;
       int    index = int(fndex);
    
       //Time outside range
       //printf("%f | %i\n", fndex, index);
       //if (index < 0 || index >= numTime - 1) {
       //    printf("TIME ISSUE\n");
       // double d1 = fndex / (numTime - 1);
       // double d0 = 1.0 - d1;
       // int indx0 = vectorLength * (numTime - 1);
       // for (int i = 0; i < vectorLength; i++)
       // {
       // valueVector[i] = d0 * valueArray[i] + d1 * valueArray[indx0 + i];
       // }
       // return;
       //}
    
       if (index < 0)
       {
          index = 0;
       }
       if (index > numTime - 2)
       {
          index = numTime - 2;
       }
    
       // Define order, max is 8
    
       int order;
       if (index >= 3 && index < numTime - 4) {
          order = 8;
       }
       else if (index == 2 || index == numTime - 4) {
          order = 6;
       }
       else if (index == 1 || index == numTime - 3) {
          order = 4;
       }
       else if (index == 0 || index == numTime - 2) {
          order = 2;
       }
       if (order > i_order) {
          order = i_order;
       }
    
       // Compute interpolation coefficients
       double tp3, tp2, tp1, tm1, tm2, tm3, tm4, d[8];
       double tau = fndex - index;
       if (order == 2) {
          tm1 = tau - 1;
          d[0] = -tm1;
          d[1] = tau;
       }
       else if (order == 4) {
          tp1 = tau + 1;
          tm1 = tau - 1;
          tm2 = tau - 2;
          d[0] = -tau * tm1 * tm2 / 6.0;
          d[1] = tp1 *       tm1 * tm2 / 2.0;
          d[2] = -tp1 * tau *       tm2 / 2.0;
          d[3] = tp1 * tau * tm1 / 6.0;
       }
       else if (order == 6) {
          tp2 = tau + 2;
          tp1 = tau + 1;
          tm1 = tau - 1;
          tm2 = tau - 2;
          tm3 = tau - 3;
          d[0] = -tp1 * tau * tm1 * tm2 * tm3 / 120.0;
          d[1] = tp2 *       tau * tm1 * tm2 * tm3 / 24.0;
          d[2] = -tp2 * tp1 *       tm1 * tm2 * tm3 / 12.0;
          d[3] = tp2 * tp1 * tau *       tm2 * tm3 / 12.0;
          d[4] = -tp2 * tp1 * tau * tm1 *       tm3 / 24.0;
          d[5] = tp2 * tp1 * tau * tm1 * tm2 / 120.0;
       }
       else if (order == 8) {
          tp3 = tau + 3;
          tp2 = tau + 2;
          tp1 = tau + 1;
          tm1 = tau - 1;
          tm2 = tau - 2;
          tm3 = tau - 3;
          tm4 = tau - 4;
          // Why are the denominators hard coded, as it should be x[0] - x[i]
          d[0] = -tp2 * tp1 * tau * tm1 * tm2 * tm3 * tm4 / 5040.0;
          d[1] = tp3 *       tp1 * tau * tm1 * tm2 * tm3 * tm4 / 720.0;
          d[2] = -tp3 * tp2 *       tau * tm1 * tm2 * tm3 * tm4 / 240.0;
          d[3] = tp3 * tp2 * tp1 *       tm1 * tm2 * tm3 * tm4 / 144.0;
          d[4] = -tp3 * tp2 * tp1 * tau *       tm2 * tm3 * tm4 / 144.0;
          d[5] = tp3 * tp2 * tp1 * tau * tm1 *       tm3 * tm4 / 240.0;
          d[6] = -tp3 * tp2 * tp1 * tau * tm1 * tm2 *       tm4 / 720.0;
          d[7] = tp3 * tp2 * tp1 * tau * tm1 * tm2 * tm3 / 5040.0;
       }
    
       // Compute interpolated point
       int    indx0 = index - order / 2 + 1;
       for (int i = 0; i < vectorLength; i++)
       {
          valueVector[i] = 0.0;
       }
    
       for (int i = 0; i < order; i++)
       {
          int jndex = vectorLength * (indx0 + i);
          for (int j = 0; j < vectorLength; j++)
          {
             valueVector[j] += d[i] * valueArray[jndex + j];
          }
       }
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::computeElevation
    //***************************************************************************
    void UsgsAstroLsSensorModel::computeElevation(
       const double& x,
       const double& y,
       const double& z,
       double&       height,
       double&       achieved_precision,
       const double& desired_precision) const
    {
       // Compute elevation given xyz
       // Requires semi-major-axis and eccentricity-square
       const int MKTR = 10;
       double ecc_sqr = 1.0 - m_semiMinorAxis * m_semiMinorAxis / m_semiMajorAxis / m_semiMajorAxis;
       double ep2 = 1.0 - ecc_sqr;
       double d2 = x * x + y * y;
       double d = sqrt(d2);
       double h = 0.0;
       int ktr = 0;
       double hPrev, r;
    
       // Suited for points near equator
       if (d >= z)
       {
          double tt, zz, n;
          double tanPhi = z / d;
          do
          {
             hPrev = h;
             tt = tanPhi * tanPhi;
             r = m_semiMajorAxis / sqrt(1.0 + ep2 * tt);
             zz = z + r * ecc_sqr * tanPhi;
             n = r * sqrt(1.0 + tt);
             h = sqrt(d2 + zz * zz) - n;
             tanPhi = zz / d;
             ktr++;
          } while (MKTR > ktr && fabs(h - hPrev) > desired_precision);
       }
    
       // Suited for points near the poles
       else
       {
          double cc, dd, nn;
          double cotPhi = d / z;
          do
          {
             hPrev = h;
             cc = cotPhi * cotPhi;
             r = m_semiMajorAxis / sqrt(ep2 + cc);
             dd = d - r * ecc_sqr * cotPhi;
             nn = r * sqrt(1.0 + cc) * ep2;
             h = sqrt(dd * dd + z * z) - nn;
             cotPhi = dd / z;
             ktr++;
          } while (MKTR > ktr && fabs(h - hPrev) > desired_precision);
       }
    
       height = h;
       achieved_precision = fabs(h - hPrev);
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::losEllipsoidIntersect
    //**************************************************************************
    void UsgsAstroLsSensorModel::losEllipsoidIntersect(
       const double& height,
       const double& xc,
       const double& yc,
       const double& zc,
       const double& xl,
       const double& yl,
       const double& zl,
       double&       x,
       double&       y,
       double&       z,
       double&       achieved_precision,
       const double& desired_precision) const
    {
       // Helper function which computes the intersection of the image ray
       // with the ellipsoid.  All vectors are in earth-centered-fixed
       // coordinate system with origin at the center of the earth.
    
       const int MKTR = 10;
    
       double ap, bp, k;
       ap = m_semiMajorAxis + height;
       bp = m_semiMinorAxis + height;
       k = ap * ap / (bp * bp);
    
       // Solve quadratic equation for scale factor
       // applied to image ray to compute ground point
    
       double at, bt, ct, quadTerm;
       at = xl * xl + yl * yl + k * zl * zl;
       bt = 2.0 * (xl * xc + yl * yc + k * zl * zc);
       ct = xc * xc + yc * yc + k * zc * zc - ap * ap;
       quadTerm = bt * bt - 4.0 * at * ct;
    
       // If quadTerm is negative, the image ray does not
       // intersect the ellipsoid. Setting the quadTerm to
       // zero means solving for a point on the ray nearest
       // the surface of the ellisoid.
    
       if (0.0 > quadTerm)
       {
          quadTerm = 0.0;
       }
       double scale, scale1, h, slope;
       double sprev, hprev;
       double aPrec, sTerm;
       int ktr = 0;
    
       // Compute ground point vector
    
       sTerm = sqrt(quadTerm);
       scale = (-bt - sTerm);
       scale1 = (-bt + sTerm);
       if (fabs(scale1) < fabs(scale))
          scale = scale1;
       scale /= (2.0 * at);
       x = xc + scale * xl;
       y = yc + scale * yl;
       z = zc + scale * zl;
       computeElevation(x, y, z, h, aPrec, desired_precision);
       slope = -1;
    
       while (MKTR > ktr && fabs(height - h) > desired_precision)
       {
          sprev = scale;
          scale += slope * (height - h);
          x = xc + scale * xl;
          y = yc + scale * yl;
          z = zc + scale * zl;
          hprev = h;
          computeElevation(x, y, z, h, aPrec, desired_precision);
          slope = (sprev - scale) / (hprev - h);
          ktr++;
       }
    
       achieved_precision = fabs(height - h);
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::losPlaneIntersect
    //**************************************************************************
    void UsgsAstroLsSensorModel::losPlaneIntersect(
       const double& xc,          // input: camera x coordinate
       const double& yc,          // input: camera y coordinate
       const double& zc,          // input: camera z coordinate
       const double& xl,          // input: component x image ray
       const double& yl,          // input: component y image ray
       const double& zl,          // input: component z image ray
       double&       x,           // input/output: ground x coordinate
       double&       y,           // input/output: ground y coordinate
       double&       z,           // input/output: ground z coordinate
       int&          mode) const // input: -1 fixed component to be computed
                              //         0(X), 1(Y), or 2(Z) fixed
                              // output: 0(X), 1(Y), or 2(Z) fixed
    {
       //# func_description
       //  Computes 2 of the 3 coordinates of a ground point given the 3rd
       //  coordinate.  The 3rd coordinate that is held fixed corresponds
       //  to the largest absolute component of the image ray.
    
       // Define fixed or largest component
    
       if (-1 == mode)
       {
          if (fabs(xl) > fabs(yl) && fabs(xl) > fabs(zl))
          {
             mode = 0;
          }
          else if (fabs(yl) > fabs(xl) && fabs(yl) > fabs(zl))
          {
             mode = 1;
          }
          else
          {
             mode = 2;
          }
       }
    
       // X is the fixed or largest component
    
       if (0 == mode)
       {
          y = yc + (x - xc) * yl / xl;
          z = zc + (x - xc) * zl / xl;
       }
    
       // Y is the fixed or largest component
    
       else if (1 == mode)
       {
          x = xc + (y - yc) * xl / yl;
          z = zc + (y - yc) * zl / yl;
       }
    
       // Z is the fixed or largest component
    
       else
       {
          x = xc + (z - zc) * xl / zl;
          y = yc + (z - zc) * yl / zl;
       }
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::imageToPlane
    //***************************************************************************
    void UsgsAstroLsSensorModel::imageToPlane(
       const double& line,      // CSM Origin UL corner of UL pixel
       const double& sample,    // CSM Origin UL corner of UL pixel
       const double& height,
       const std::vector<double> &adj,
       double&       x,
       double&       y,
       double&       z,
       int&          mode) const
    {
       //# func_description
       //  Computes ground coordinates by intersecting image ray with
       //  a plane perpendicular to the coordinate axis with the largest
       //  image ray component.  This routine is primarily called by
       //  groundToImage().
    
       // *** Computes camera position and image ray in ecf cs.
    
       double xc, yc, zc;
       double vx, vy, vz;
       double xl, yl, zl;
       double dxl, dyl, dzl;
    
       losToEcf(line, sample, adj, xc, yc, zc, vx, vy, vz, xl, yl, zl);
    
       if (m_aberrFlag == 1)
       {
          lightAberrationCorr(vx, vy, vz, xl, yl, zl, dxl, dyl, dzl);
          xl += dxl;
          yl += dyl;
          zl += dzl;
       }
    
       losPlaneIntersect(xc, yc, zc, xl, yl, zl, x, y, z, mode);
    }
    
    //***************************************************************************
    // UsgsAstroLineScannerSensorModel::getAdjSensorPosVel
    //***************************************************************************
    void UsgsAstroLsSensorModel::getAdjSensorPosVel(
       const double& time,
       const std::vector<double> &adj,
       double&       xc,
       double&       yc,
       double&       zc,
       double&       vx,
       double&       vy,
       double&       vz) const
    {
       // Sensor position and velocity (4th or 8th order Lagrange).
       int nOrder = 8;
       if (m_platformFlag == 0)
          nOrder = 4;
       double sensPosNom[3];
       lagrangeInterp(m_numEphem, &m_ephemPts[0], m_t0Ephem, m_dtEphem,
          time, 3, nOrder, sensPosNom);
       double sensVelNom[3];
       lagrangeInterp(m_numEphem, &m_ephemRates[0], m_t0Ephem, m_dtEphem,
          time, 3, nOrder, sensVelNom);
       // Compute rotation matrix from ICR to ECF
    
       double radialUnitVec[3];
       double radMag = sqrt(sensPosNom[0] * sensPosNom[0] +
          sensPosNom[1] * sensPosNom[1] +
          sensPosNom[2] * sensPosNom[2]);
       for (int i = 0; i < 3; i++)
          radialUnitVec[i] = sensPosNom[i] / radMag;
       double crossTrackUnitVec[3];
       crossTrackUnitVec[0] = sensPosNom[1] * sensVelNom[2]
          - sensPosNom[2] * sensVelNom[1];
       crossTrackUnitVec[1] = sensPosNom[2] * sensVelNom[0]
          - sensPosNom[0] * sensVelNom[2];
       crossTrackUnitVec[2] = sensPosNom[0] * sensVelNom[1]
          - sensPosNom[1] * sensVelNom[0];
       double crossMag = sqrt(crossTrackUnitVec[0] * crossTrackUnitVec[0] +
          crossTrackUnitVec[1] * crossTrackUnitVec[1] +
          crossTrackUnitVec[2] * crossTrackUnitVec[2]);
       for (int i = 0; i < 3; i++)
          crossTrackUnitVec[i] /= crossMag;
       double inTrackUnitVec[3];
       inTrackUnitVec[0] = crossTrackUnitVec[1] * radialUnitVec[2]
          - crossTrackUnitVec[2] * radialUnitVec[1];
       inTrackUnitVec[1] = crossTrackUnitVec[2] * radialUnitVec[0]
          - crossTrackUnitVec[0] * radialUnitVec[2];
       inTrackUnitVec[2] = crossTrackUnitVec[0] * radialUnitVec[1]
          - crossTrackUnitVec[1] * radialUnitVec[0];
       double ecfFromIcr[9];
       ecfFromIcr[0] = inTrackUnitVec[0];
       ecfFromIcr[1] = crossTrackUnitVec[0];
       ecfFromIcr[2] = radialUnitVec[0];
       ecfFromIcr[3] = inTrackUnitVec[1];
       ecfFromIcr[4] = crossTrackUnitVec[1];
       ecfFromIcr[5] = radialUnitVec[1];
       ecfFromIcr[6] = inTrackUnitVec[2];
       ecfFromIcr[7] = crossTrackUnitVec[2];
       ecfFromIcr[8] = radialUnitVec[2];
       // Apply position and velocity corrections
    
       double aTime = time - m_t0Ephem;
       double dvi = getValue(3, adj) / m_halfTime;
       double dvc = getValue(4, adj) / m_halfTime;
       double dvr = getValue(5, adj) / m_halfTime;
       vx = sensVelNom[0]
          + ecfFromIcr[0] * dvi + ecfFromIcr[1] * dvc + ecfFromIcr[2] * dvr;
       vy = sensVelNom[1]
          + ecfFromIcr[3] * dvi + ecfFromIcr[4] * dvc + ecfFromIcr[5] * dvr;
       vz = sensVelNom[2]
          + ecfFromIcr[6] * dvi + ecfFromIcr[7] * dvc + ecfFromIcr[8] * dvr;
       double di = getValue(0, adj) + dvi * aTime;
       double dc = getValue(1, adj) + dvc * aTime;
       double dr = getValue(2, adj) + dvr * aTime;
       xc = sensPosNom[0]
          + ecfFromIcr[0] * di + ecfFromIcr[1] * dc + ecfFromIcr[2] * dr;
       yc = sensPosNom[1]
          + ecfFromIcr[3] * di + ecfFromIcr[4] * dc + ecfFromIcr[5] * dr;
       zc = sensPosNom[2]
          + ecfFromIcr[6] * di + ecfFromIcr[7] * dc + ecfFromIcr[8] * dr;
    }
    
    
    //***************************************************************************
    // UsgsAstroLineScannerSensorModel::computeViewingPixel
    //***************************************************************************
    csm::ImageCoord UsgsAstroLsSensorModel::computeViewingPixel(
       const double& time,
       const csm::EcefCoord& groundPoint,
       const std::vector<double>& adj,
       const double& desiredPrecision) const
    {
      // Helper function to compute the CCD pixel that views a ground point based
      // on the exterior orientation at a given time.
    
       // Get the exterior orientation
       double xc, yc, zc, vx, vy, vz;
       getAdjSensorPosVel(time, adj, xc, yc, zc, vx, vy, vz);
    
       // Compute the look vector
       double bodyLookX = groundPoint.x - xc;
       double bodyLookY = groundPoint.y - yc;
       double bodyLookZ = groundPoint.z - zc;
    
       // Rotate the look vector into the camera reference frame
       double bodyToCamera[9];
       calculateRotationMatrixFromQuaternions(time, true, bodyToCamera);
    
       double cameraLookX = bodyToCamera[0] * bodyLookX
                          + bodyToCamera[1] * bodyLookY
                          + bodyToCamera[2] * bodyLookZ;
       double cameraLookY = bodyToCamera[3] * bodyLookX
                          + bodyToCamera[4] * bodyLookY
                          + bodyToCamera[5] * bodyLookZ;
       double cameraLookZ = bodyToCamera[6] * bodyLookX
                          + bodyToCamera[7] * bodyLookY
                          + bodyToCamera[8] * bodyLookZ;
    
       // Invert the attitude correction
       double attCorr[9];
       calculateAttitudeCorrection(time, adj, attCorr);
    
       double adjustedLookX = attCorr[0] * cameraLookX
                            + attCorr[3] * cameraLookY
                            + attCorr[6] * cameraLookZ;
       double adjustedLookY = attCorr[1] * cameraLookX
                            + attCorr[4] * cameraLookY
                            + attCorr[7] * cameraLookZ;
       double adjustedLookZ = attCorr[2] * cameraLookX
                            + attCorr[5] * cameraLookY
                            + attCorr[8] * cameraLookZ;
    
       // Convert to focal plane coordinate
       double lookScale = m_focal / adjustedLookZ;
       double focalX = adjustedLookX * lookScale;
       double focalY = adjustedLookY * lookScale;
    
       // Invert distortion
       // This method works by iteratively adding distortion until the new distorted
       // point, r, undistorts to within a tolerance of the original point, rp.
       if (m_opticalDistCoef[0] != 0.0 ||
          m_opticalDistCoef[1] != 0.0 ||
          m_opticalDistCoef[2] != 0.0)
        {
          double rp2 = (focalX * focalX) + (focalY * focalY);
          double tolerance = 1.0E-6;
          if (rp2 > tolerance) {
            double rp = sqrt(rp2);
            // Compute first fractional distortion using rp
            double drOverR = m_opticalDistCoef[0]
                           + (rp2 * (m_opticalDistCoef[1] + (rp2 * m_opticalDistCoef[2])));
            // Compute first distorted point estimate, r
            double r = rp + (drOverR * rp);
            double r_prev, r2_prev;
            int iteration = 0;
            do {
              // Don't get in an end-less loop.  This algorithm should
              // converge quickly.  If not then we are probably way outside
              // of the focal plane.  Just set the distorted position to the
              // undistorted position. Also, make sure the focal plane is less
              // than 1km, it is unreasonable for it to grow larger than that.
              if (iteration >= 15 || r > 1E9) {
                drOverR = 0.0;
                break;
              }
    
              r_prev = r;
              r2_prev = r * r;
    
              // Compute new fractional distortion:
              drOverR = m_opticalDistCoef[0]
                      + (r2_prev * (m_opticalDistCoef[1] + (r2_prev * m_opticalDistCoef[2])));
    
              // Compute new estimate of r
              r = rp + (drOverR * r_prev);
              iteration++;
            }
            while (fabs(r * (1 - drOverR) - rp) > desiredPrecision);
    
            focalX = focalX / (1.0 - drOverR);
            focalY = focalY / (1.0 - drOverR);
          }
        }
    
       // Convert to detector line and sample
       double detectorLine = m_iTransL[0]
                           + m_iTransL[1] * focalX
                           + m_iTransL[2] * focalY;
       double detectorSample = m_iTransS[0]
                             + m_iTransS[1] * focalX
                             + m_iTransS[2] * focalY;
    
       // Convert to image sample line
       double line = detectorLine + m_detectorLineOrigin - m_detectorLineOffset
                   - m_offsetLines;
       double sample = (detectorSample + m_detectorSampleOrigin - m_startingSample + 1.0)
                     / m_detectorSampleSumming - m_offsetSamples;
    
       return csm::ImageCoord(line, sample);
    }
    
    
    //***************************************************************************
    // UsgsAstroLineScannerSensorModel::computeLinearApproximation
    //***************************************************************************
    void UsgsAstroLsSensorModel::computeLinearApproximation(
       const csm::EcefCoord &gp,
       csm::ImageCoord      &ip) const
    {
       if (_linear)
       {
          ip.line = _u0 + _du_dx * gp.x + _du_dy * gp.y + _du_dz * gp.z;
          ip.samp = _v0 + _dv_dx * gp.x + _dv_dy * gp.y + _dv_dz * gp.z;
    
          // Since this is valid only over image,
          // don't let result go beyond the image border.
          double numRows = m_totalLines;
          double numCols = m_totalSamples;
          if (ip.line < 0.0)     ip.line = 0.0;
          if (ip.line > numRows) ip.line = numRows;
    
          if (ip.samp < 0.0)     ip.samp = 0.0;
          if (ip.samp > numCols) ip.samp = numCols;
       }
       else
       {
          ip.line = m_totalLines / 2.0;
          ip.samp = m_totalSamples / 2.0;
       }
    }
    
    
    //***************************************************************************
    // UsgsAstroLineScannerSensorModel::setLinearApproximation
    //***************************************************************************
    void UsgsAstroLsSensorModel::setLinearApproximation()
    {
       double u_factors[4] = { 0.0, 0.0, 1.0, 1.0 };
       double v_factors[4] = { 0.0, 1.0, 0.0, 1.0 };
    
       csm::EcefCoord refPt = getReferencePoint();
    
       double height, aPrec;
       double desired_precision = 0.01;
       computeElevation(refPt.x, refPt.y, refPt.z, height, aPrec, desired_precision);
       if (isnan(height))
       {
          _linear = false;
          return;
       }
    
    
       double numRows = m_totalLines;
       double numCols = m_totalSamples;
    
       csm::ImageCoord imagePt;
       csm::EcefCoord  gp[8];
    
       int i;
       for (i = 0; i < 4; i++)
       {
          imagePt.line = u_factors[i] * numRows;
          imagePt.samp = v_factors[i] * numCols;
          gp[i] = imageToGround(imagePt, height);
       }
    
       double delz = 100.0;
       height += delz;
       for (i = 0; i < 4; i++)
       {
          imagePt.line = u_factors[i] * numRows;
          imagePt.samp = v_factors[i] * numCols;
          gp[i + 4] = imageToGround(imagePt, height);
       }
    
       csm::EcefCoord d_du;
       d_du.x = (
          (gp[2].x + gp[3].x + gp[6].x + gp[7].x) -
          (gp[0].x + gp[1].x + gp[4].x + gp[5].x)) / numRows / 4.0;
       d_du.y = (
          (gp[2].y + gp[3].y + gp[6].y + gp[7].y) -
          (gp[0].y + gp[1].y + gp[4].y + gp[5].y)) / numRows / 4.0;
       d_du.z = (
          (gp[2].z + gp[3].z + gp[6].z + gp[7].z) -
          (gp[0].z + gp[1].z + gp[4].z + gp[5].z)) / numRows / 4.0;
    
       csm::EcefCoord d_dv;
       d_dv.x = (
          (gp[1].x + gp[3].x + gp[5].x + gp[7].x) -
          (gp[0].x + gp[2].x + gp[4].x + gp[6].x)) / numCols / 4.0;
       d_dv.y = (
          (gp[1].y + gp[3].y + gp[5].y + gp[7].y) -
          (gp[0].y + gp[2].y + gp[4].y + gp[6].y)) / numCols / 4.0;
       d_dv.z = (
          (gp[1].z + gp[3].z + gp[5].z + gp[7].z) -
          (gp[0].z + gp[2].z + gp[4].z + gp[6].z)) / numCols / 4.0;
    
       double mat3x3[9];
    
       mat3x3[0] = d_du.x;
       mat3x3[1] = d_dv.x;
       mat3x3[2] = 1.0;
       mat3x3[3] = d_du.y;
       mat3x3[4] = d_dv.y;
       mat3x3[5] = 1.0;
       mat3x3[6] = d_du.z;
       mat3x3[7] = d_dv.z;
       mat3x3[8] = 1.0;
    
       double denom = determinant3x3(mat3x3);
    
       if (fabs(denom) < 1.0e-8) // can not get derivatives this way
       {
          _linear = false;
          return;
       }
    
       mat3x3[0] = 1.0;
       mat3x3[3] = 0.0;
       mat3x3[6] = 0.0;
       _du_dx = determinant3x3(mat3x3) / denom;
    
       mat3x3[0] = 0.0;
       mat3x3[3] = 1.0;
       mat3x3[6] = 0.0;
       _du_dy = determinant3x3(mat3x3) / denom;
    
       mat3x3[0] = 0.0;
       mat3x3[3] = 0.0;
       mat3x3[6] = 1.0;
       _du_dz = determinant3x3(mat3x3) / denom;
    
       mat3x3[0] = d_du.x;
       mat3x3[3] = d_du.y;
       mat3x3[6] = d_du.z;
    
       mat3x3[1] = 1.0;
       mat3x3[4] = 0.0;
       mat3x3[7] = 0.0;
       _dv_dx = determinant3x3(mat3x3) / denom;
    
       mat3x3[1] = 0.0;
       mat3x3[4] = 1.0;
       mat3x3[7] = 0.0;
       _dv_dy = determinant3x3(mat3x3) / denom;
    
       mat3x3[1] = 0.0;
       mat3x3[4] = 0.0;
       mat3x3[7] = 1.0;
       _dv_dz = determinant3x3(mat3x3) / denom;
    
       _u0 = -gp[0].x * _du_dx - gp[0].y * _du_dy - gp[0].z * _du_dz;
       _v0 = -gp[0].x * _dv_dx - gp[0].y * _dv_dy - gp[0].z * _dv_dz;
    
       _linear = true;
    }
    
    //***************************************************************************
    // UsgsAstroLineScannerSensorModel::determinant3x3
    //***************************************************************************
    double UsgsAstroLsSensorModel::determinant3x3(double mat[9]) const
    {
       return
          mat[0] * (mat[4] * mat[8] - mat[7] * mat[5]) -
          mat[1] * (mat[3] * mat[8] - mat[6] * mat[5]) +
          mat[2] * (mat[3] * mat[7] - mat[6] * mat[4]);
    }
    
    
    
    
    std::string UsgsAstroLsSensorModel::constructStateFromIsd(const std::string imageSupportData, csm::WarningList *warnings) const
    {
       // Instantiate UsgsAstroLineScanner sensor model
       json isd = json::parse(imageSupportData);
       json state;
    
       int num_params = NUM_PARAMETERS;
    
       state["m_imageIdentifier"] = "UNKNOWN";
       state["m_sensorType"] = "LINE_SCAN";
       state["m_totalLines"] = isd.at("image_lines");
       state["m_totalSamples"] = isd.at("image_samples");
       state["m_offsetLines"] = 0.0;
       state["m_offsetSamples"] = 0.0;
       state["m_platformFlag"] = 1;
       state["m_aberrFlag"] = 0;
       state["m_atmRefFlag"] = 0;
       state["m_centerEphemerisTime"] = isd.at("center_ephemeris_time");
       state["m_startingEphemerisTime"] = isd.at("starting_ephemeris_time");
    
       for (auto& scanRate : isd.at("line_scan_rate")) {
         state["m_intTimeLines"].push_back(scanRate[0]);
         state["m_intTimeStartTimes"].push_back(scanRate[1]);
         state["m_intTimes"].push_back(scanRate[2]);
       }
    
       state["m_detectorSampleSumming"] = isd.at("detector_sample_summing");
       state["m_startingSample"] = isd.at("detector_line_summing");
       state["m_ikCode"] = 0;
       state["m_focal"] = isd.at("focal_length_model").at("focal_length");
       state["m_isisZDirection"] = 1;
       state["m_opticalDistCoef"] = isd.at("optical_distortion").at("radial").at("coefficients");
       state["m_iTransS"] = isd.at("focal2pixel_samples");
       state["m_iTransL"] = isd.at("focal2pixel_lines");
    
       state["m_detectorSampleOrigin"] = isd.at("detector_center").at("sample");
       state["m_detectorLineOrigin"] = isd.at("detector_center").at("line");
       state["m_detectorLineOffset"] = 0;
    
       state["m_dtEphem"] = isd.at("dt_ephemeris");
       state["m_t0Ephem"] = isd.at("t0_ephemeris");
       state["m_dtQuat"] =  isd.at("dt_quaternion");
       state["m_t0Quat"] =  isd.at("t0_quaternion");
    
       state["m_numEphem"] = isd.at("sensor_position").at("positions").size();
       state["m_numQuaternions"] = isd.at("sensor_orientation").at("quaternions").size();
    
       for (auto& location : isd.at("sensor_position").at("positions")) {
         state["m_ephemPts"].push_back(location[0]);
         state["m_ephemPts"].push_back(location[1]);
         state["m_ephemPts"].push_back(location[2]);
       }
    
       for (auto& velocity : isd.at("sensor_position").at("velocities")) {
         state["m_ephemRates"].push_back(velocity[0]);
         state["m_ephemRates"].push_back(velocity[1]);
         state["m_ephemRates"].push_back(velocity[2]);
       }
    
       for (auto& quaternion : isd.at("sensor_orientation").at("quaternions")) {
         state["m_quaternions"].push_back(quaternion[0]);
         state["m_quaternions"].push_back(quaternion[1]);
         state["m_quaternions"].push_back(quaternion[2]);
         state["m_quaternions"].push_back(quaternion[3]);
        }
    
    
       state["m_parameterVals"] = std::vector<double>(NUM_PARAMETERS, 0.0);
       // state["m_parameterVals"][15] = state["m_focal"].get<double>();
    
       // Set the ellipsoid
       state["m_semiMajorAxis"] = isd.at("radii").at("semimajor");
       state["m_semiMinorAxis"] = isd.at("radii").at("semiminor");
    
       // Now finish setting the state data from the ISD read in
    
       // set identifiers
       state["m_referenceDateAndTime"] = "UNKNOWN";
       state["m_platformIdentifier"]   = isd.at("name_platform");
       state["m_sensorIdentifier"]     = isd.at("name_sensor");
       state["m_trajectoryIdentifier"] = "UNKNOWN";
       state["m_collectionIdentifier"] = "UNKNOWN";
    
       // Ground elevations
       state["m_refElevation"] = 0.0;
       state["m_minElevation"] = isd.at("reference_height").at("minheight");
       state["m_maxElevation"] = isd.at("reference_height").at("maxheight");
    
       // Zero ateter values
       for (int i = 0; i < NUM_PARAMETERS; i++) {
          state["m_ateterType"][i] = csm::param::REAL;
       }
    
       // Default to identity covariance
       state["m_covariance"] =
             std::vector<double>(NUM_PARAMETERS * NUM_PARAMETERS, 0.0);
       for (int i = 0; i < NUM_PARAMETERS; i++) {
         state["m_covariance"][i * NUM_PARAMETERS + i] = 1.0;
       }
    
       // Zero computed state values
       state["m_referencePointXyz"] = std::vector<double>(3, 0.0);
       state["m_gsd"] = 1.0;
       state["m_flyingHeight"] = 1000.0;
       state["m_halfSwath"] = 1000.0;
       state["m_halfTime"] = 10.0;
       state["m_imageFlipFlag"] = 0;
       // The state data will still be updated when a sensor model is created since
       // some state data is notin the ISD and requires a SM to compute them.
       return state.dump();
    }