Skip to content
Snippets Groups Projects
Select Git revision
  • da137da7fe71b7f1d90e6e751593c6b63a65963b
  • main default protected
  • oleg-alexandrov-patch-1
  • radtan
  • 2.0
  • Kelvinrr-patch-1
  • acpaquette-patch-1
  • gxp_testing
  • 2.0.2
  • 2.0.1
  • 2.0.0
  • 1.7.0
  • 1.6.0
  • 1.5.2
  • 1.5.1
  • 1.5.0
  • 1.4.1
  • 1.4.0
  • 1.3.1
  • 1.3.0
  • 1.2.0
  • 1.1.1
  • 1.1.0
  • 1.0.0
24 results

UsgsAstroLsSensorModel.cpp

Blame
  • UsgsAstroLsSensorModel.cpp 82.14 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 USGSASTROLINESCANNER_LIBRARY
    
    #include "UsgsAstroLsSensorModel.h"
    
    #include <algorithm>
    #include <iostream>
    #include <sstream>
    #include <math.h>
    
    //*****************************************************************************
    // UsgsAstroLsSensorModel Constructor
    //*****************************************************************************
    UsgsAstroLsSensorModel::UsgsAstroLsSensorModel()
    {
       _no_adjustment.assign(UsgsAstroLsStateData::NUM_PARAMETERS, 0.0);
    }
    
    //*****************************************************************************
    // UsgsAstroLsSensorModel Destructor
    //*****************************************************************************
    UsgsAstroLsSensorModel::~UsgsAstroLsSensorModel()
    {
    }
    
    //*****************************************************************************
    // UsgsAstroLsSensorModel set
    //*****************************************************************************
    void UsgsAstroLsSensorModel::set(const UsgsAstroLsStateData &state_data)
    {
       _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(UsgsAstroLsStateData::NUM_PARAMETERS, 0.0);
       _data = state_data;
    
       // If needed set state data elements that need a sensor model to compute
       // Update if still using default settings
       if (_data.m_Gsd == 1.0  && _data.m_FlyingHeight == 1000.0)
       {
          updateState();
       }
    
       try
       {
          setLinearApproximation();
       }
       catch (...)
       {
          _linear = false;
       }
    }
    
    //*****************************************************************************
    // 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 = _data.m_TotalLines / 2.0;
       double sampCtr = _data.m_TotalSamples / 2.0;
       csm::ImageCoord ip(lineCtr, sampCtr);
       double refHeight = _data.m_RefElevation;
       _data.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 - _data.m_ReferencePointXyz.x;
       double dy = delta.y - _data.m_ReferencePointXyz.y;
       double dz = delta.z - _data.m_ReferencePointXyz.z;
       _data.m_Gsd = sqrt((dx*dx + dy*dy + dz*dz) / 2.0);
    
       // Compute flying height
       csm::EcefCoord sensorPos = getSensorPosition(0.0);
       dx = sensorPos.x - _data.m_ReferencePointXyz.x;
       dy = sensorPos.y - _data.m_ReferencePointXyz.y;
       dz = sensorPos.z - _data.m_ReferencePointXyz.z;
       _data.m_FlyingHeight = sqrt(dx*dx + dy*dy + dz*dz);
    
       // Compute half swath
       _data.m_HalfSwath = _data.m_Gsd * _data.m_TotalSamples / 2.0;
    
       // Compute half time duration
       double fullImageTime = _data.m_IntTimeStartTimes.back() - _data.m_IntTimeStartTimes.front()
                              + _data.m_IntTimes.back() * (_data.m_TotalLines - _data.m_IntTimeLines.back());
       _data.m_HalfTime = fullImageTime / 2.0;
    
       // Parameter covariance, hardcoded accuracy values
       int num_params = _data.NUM_PARAMETERS;
       int num_params_square = num_params * num_params;
       double variance = _data.m_Gsd * _data.m_Gsd;
       for (int i = 0; i < num_params_square; i++)
       {
          _data.m_Covariance[i] = 0.0;
       }
       for (int i = 0; i < num_params; i++)
       {
          _data.m_Covariance[i * num_params + i] = variance;
       }
    
       // Set flag for flipping image along horizontal axis
       int index = int((_data.m_NumEphem - 1) / 2.0) * 3;
       double xs, ys, zs;    // mid sensor position
       xs = _data.m_EphemPts[index];
       ys = _data.m_EphemPts[index + 1];
       zs = _data.m_EphemPts[index + 2];
       double xv, yv, zv;    // mid sensor velocity
       xv = _data.m_EphemRates[index];
       yv = _data.m_EphemRates[index + 1];
       zv = _data.m_EphemRates[index + 2];
       double xm, ym, zm;    // mid line position (mid sample)
       xm = _data.m_ReferencePointXyz.x;
       ym = _data.m_ReferencePointXyz.y;
       zm = _data.m_ReferencePointXyz.z;
       // mid line position (first sample)
       csm::EcefCoord posf = imageToGround(csm::ImageCoord(lineCtr, 0.0), refHeight);
       // mid line position (last sample)
       csm::EcefCoord posl = imageToGround(csm::ImageCoord(lineCtr, _data.m_TotalSamples - 1), refHeight);
       double xd, yd, zd;    // unit vector normal to image footprint
       xd = posl.x - posf.x;
       yd = posl.y - posf.y;
       zd = posl.z - posf.z;
       double xn, yn, zn;
       xn = yv*zd - zv*yd;
       yn = zv*xd - xv*zd;
       zn = xv*yd - yv*xd;
       double nmag = sqrt(xn*xn + yn*yn + zn*zn);
       xn /= nmag;
       yn /= nmag;
       zn /= nmag;
       double xo, yo, zo;   // unit vector ground-to_satellite
       xo = xs - xm;
       yo = ys - ym;
       zo = zs - zm;
       double omag = sqrt(xo*xo + yo*yo + zo*zo);
       xo /= omag;
       yo /= omag;
       zo /= omag;
       double triple_product = xn*xo + yn*yo + zn*zo;
       _data.m_ImageFlipFlag = 0;
       if (triple_product > 0.0)
          _data.m_ImageFlipFlag = 1;
    }
    
    
    //---------------------------------------------------------------------------
    // 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 bisection method to search for the image
       // line.
       //
       // For a given search window, this routine involves projecting the
       // ground point onto the focal plane based on the instrument orientation
       // at the start and end of the search window. Then, it computes the focal
       // plane intersection at a mid-point of the search window. Then, it reduces
       // the search window based on the signs of the intersected line offsets from
       // the center of the ccd. For example, if the line offset is -145 at the
       // start of the window, 10 at the mid point, and 35 at the end of the search
       // window, the window will be reduced to the start of the old window to the
       // middle of the old window.
       //
       // In order to achieve faster convergence, the mid point is calculated
       // using the False Position Method instead of simple bisection. This method
       // uses the zero of the line between the two ends of the search window for
       // the mid point instead of a simple bisection. In most cases, this will
       // converge significantly faster, but it can be slower than simple bisection
       // in some situations.
    
       // Start bisection search on the image lines
       double sampCtr = _data.m_TotalSamples / 2.0;
       double firstTime = getImageTime(csm::ImageCoord(0.0, sampCtr));
       double lastTime = getImageTime(csm::ImageCoord(_data.m_TotalLines, sampCtr));
       double firstOffset = computeViewingPixel(firstTime, ground_pt, adj).line - 0.5;
       double lastOffset = computeViewingPixel(lastTime, ground_pt, adj).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");
       }
    
       // 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 < _data.m_TotalLines) {
          ++approxNextPoint.line;
       }
       else {
          --approxNextPoint.line;
       }
       csm::EcefCoord approxIntersect = imageToGround(approxPoint, _data.m_RefElevation);
       csm::EcefCoord approxNextIntersect = imageToGround(approxNextPoint, _data.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 bisection search for zero
       for (int it = 0; it < 30; it++) {
          double nextTime = ((firstTime * lastOffset) - (lastTime * firstOffset))
                          / (lastOffset - firstOffset);
          double nextOffset = computeViewingPixel(nextTime, ground_pt, adj).line - 0.5;
          // We're looking for a zero, so check that either firstLine and middleLine have
          // opposite signs, or middleLine and lastLine have opposite signs.
          if ((firstOffset > 0) == (nextOffset < 0)) {
             lastTime = nextTime;
             lastOffset = nextOffset;
          }
          else {
             firstTime = nextTime;
             firstOffset = nextOffset;
          }
          if (fabs(lastOffset - firstOffset) < pixelPrec) {
             break;
          }
       }
    
       // Check that the desired precision was met
    
       double computedTime = ((firstTime * lastOffset) - (lastTime * firstOffset))
                           / (lastOffset - firstOffset);
       csm::ImageCoord calculatedPixel = computeViewingPixel(computedTime, ground_pt, adj);
       // The computed viewing line is the detector line, so we need to convert that to image lines
       auto referenceTimeIt = std::upper_bound(_data.m_IntTimeStartTimes.begin(),
                                               _data.m_IntTimeStartTimes.end(),
                                               computedTime);
       if (referenceTimeIt != _data.m_IntTimeStartTimes.begin()) {
          --referenceTimeIt;
       }
       size_t referenceIndex = std::distance(_data.m_IntTimeStartTimes.begin(), referenceTimeIt);
       calculatedPixel.line += _data.m_IntTimeLines[referenceIndex] - 1
                             + (computedTime - _data.m_IntTimeStartTimes[referenceIndex])
                             / _data.m_IntTimes[referenceIndex];
       csm::EcefCoord calculatedPoint = imageToGround(calculatedPixel, _data.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;
    
       // Check that the pixel is actually in the image
       if ((calculatedPixel.samp < 0) ||
           (calculatedPixel.samp > _data.m_TotalSamples)) {
          throw csm::Warning(
             csm::Warning::IMAGE_COORD_OUT_OF_BOUNDS,
             "The image coordinate is out of bounds of the image size.",
             "UsgsAstroLsSensorModel::groundToImage");
       }
    
       // 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) {
          throw csm::Error(
             csm::Error::ALGORITHM,
             "Did not converge.",
             "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 (_data.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 = _data.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 = _data.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 = _data.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 = _data.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 = _data.m_Gsd;
       std::vector<double> adj(UsgsAstroLsStateData::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 = UsgsAstroLsStateData::NUM_PARAMETERS * index1 + index2;
       return _data.m_Covariance[index];
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::setParameterCovariance
    //***************************************************************************
    void UsgsAstroLsSensorModel::setParameterCovariance(
       int index1,
       int index2,
       double covariance)
    {
       int index = UsgsAstroLsStateData::NUM_PARAMETERS * index1 + index2;
       _data.m_Covariance[index] = covariance;
    }
    
    //---------------------------------------------------------------------------
    // Time and Trajectory
    //---------------------------------------------------------------------------
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getTrajectoryIdentifier
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getTrajectoryIdentifier() const
    {
       return _data.m_TrajectoryIdentifier;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getReferenceDateAndTime
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getReferenceDateAndTime() const
    {
       if (_data.m_ReferenceDateAndTime == "UNKNOWN")
       {
          throw csm::Error(
             csm::Error::UNSUPPORTED_FUNCTION,
             "Unsupported function",
             "UsgsAstroLsSensorModel::getReferenceDateAndTime");
       }
       return _data.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 + _data.m_OffsetLines;
       double lineUSGSFull = 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(_data.m_IntTimeLines.begin(),
                                               _data.m_IntTimeLines.end(),
                                               lineUSGSFull);
       if (referenceLineIt != _data.m_IntTimeLines.begin()) {
          --referenceLineIt;
       }
       size_t referenceIndex = std::distance(_data.m_IntTimeLines.begin(), referenceLineIt);
    
       double time = _data.m_IntTimeStartTimes[referenceIndex]
          + _data.m_IntTimes[referenceIndex] * (lineUSGSFull - _data.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)
    {
       _data.m_ParameterVals[index] = value;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getParameterValue
    //***************************************************************************
    double UsgsAstroLsSensorModel::getParameterValue(int index) const
    {
       return _data.m_ParameterVals[index];
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getParameterName
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getParameterName(int index) const
    {
       return _data.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 UsgsAstroLsStateData::NUM_PARAMETERS;
    }
    
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getParameterType
    //***************************************************************************
    csm::param::Type UsgsAstroLsSensorModel::getParameterType(int index) const
    {
       return _data.m_ParameterType[index];
    }
    
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::setParameterType
    //***************************************************************************
    void UsgsAstroLsSensorModel::setParameterType(
       int index, csm::param::Type pType)
    {
       _data.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 _data.m_ImageIdentifier;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::setImageIdentifier
    //***************************************************************************
    void UsgsAstroLsSensorModel::setImageIdentifier(
       const std::string& imageId,
       csm::WarningList* warnings)
    {
       // Image id should include the suffix without the path name
       _data.m_ImageIdentifier = imageId;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getSensorIdentifier
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getSensorIdentifier() const
    {
       return _data.m_SensorIdentifier;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getPlatformIdentifier
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getPlatformIdentifier() const
    {
       return _data.m_PlatformIdentifier;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::setReferencePoint
    //***************************************************************************
    void UsgsAstroLsSensorModel::setReferencePoint(const csm::EcefCoord& ground_pt)
    {
       _data.m_ReferencePointXyz = ground_pt;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getReferencePoint
    //***************************************************************************
    csm::EcefCoord UsgsAstroLsSensorModel::getReferencePoint() const
    {
       // Return ground point at image center
       return _data.m_ReferencePointXyz;
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getSensorModelName
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getModelName() const
    {
       return UsgsAstroLsStateData::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(_data.m_TotalLines, _data.m_TotalSamples);
    }
    
    //---------------------------------------------------------------------------
    // Sensor Model State
    //---------------------------------------------------------------------------
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getSensorModelState
    //***************************************************************************
    std::string UsgsAstroLsSensorModel::getModelState() const
    {
       return _data.toJson();
    }
    
    //---------------------------------------------------------------------------
    //  Monoscopic Mensuration
    //---------------------------------------------------------------------------
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::getValidHeightRange
    //***************************************************************************
    std::pair<double, double> UsgsAstroLsSensorModel::getValidHeightRange() const
    {
       return std::pair<double, double>(_data.m_MinElevation, _data.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(_data.m_TotalLines, _data.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 _data.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);
    }
    
    //***************************************************************************
    // UsgsAstroLsSensorModel::replaceModelState
    //***************************************************************************
    void UsgsAstroLsSensorModel::replaceModelState(const std::string& argState)
    {
       set(argState);
    }
    
    
    csm::Ellipsoid UsgsAstroLsSensorModel::getEllipsoid() const
    {
       return csm::Ellipsoid(_data.m_SemiMajorAxis, _data.m_SemiMinorAxis);
    }
    
    void UsgsAstroLsSensorModel::setEllipsoid(
       const csm::Ellipsoid &ellipsoid)
    {
       _data.m_SemiMajorAxis = ellipsoid.getSemiMajorRadius();
       _data.m_SemiMinorAxis = ellipsoid.getSemiMinorRadius();
    }
    
    
    
    double UsgsAstroLsSensorModel::getValue(
       int   index,
       const std::vector<double> &adjustments) const
    {
       return _data.m_ParameterVals[index] + adjustments[index];
    }
    
    //***************************************************************************
    // 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&       xl,         // output line-of-sight x coordinate
       double&       yl,         // output line-of-sight y coordinate
       double&       zl) const  // output line-of-sight z coordinate
    {
       //# private_func_description
       //  Computes image ray 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 + _data.m_OffsetSamples;
       double sampleUSGSFull = sampleCSMFull + 0.5;
       double fractionalLine = line - floor(line) - 0.5;
    
       // Compute distorted image coordinates in mm
    
       double isisDetSample = (sampleUSGSFull - 1.0)
          * _data.m_DetectorSampleSumming + _data.m_StartingSample;
       double m11 = _data.m_ITransL[1];
       double m12 = _data.m_ITransL[2];
       double m21 = _data.m_ITransS[1];
       double m22 = _data.m_ITransS[2];
       double t1 = fractionalLine + _data.m_DetectorLineOffset
                   - _data.m_DetectorLineOrigin - _data.m_ITransL[0];
       double t2 = isisDetSample - _data.m_DetectorSampleOrigin - _data.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;
    
       // Remove lens distortion
       double isisFocalPlaneX = isisNatFocalPlaneX;
       double isisFocalPlaneY = isisNatFocalPlaneY;
       switch (_data.m_IkCode)
       {
       case -85610:
       case -85600:
          isisFocalPlaneY = isisNatFocalPlaneY / (1.0 + _data.m_OpticalDistCoef[0]
             * isisNatFocalPlaneY * isisNatFocalPlaneY);
          break;
    
       default:
          if (_data.m_OpticalDistCoef[0] != 0.0 ||
             _data.m_OpticalDistCoef[1] != 0.0 ||
             _data.m_OpticalDistCoef[2] != 0.0)
          {
             double rr = isisNatFocalPlaneX * isisNatFocalPlaneX
                + isisNatFocalPlaneY * isisNatFocalPlaneY;
             if (rr > 1.0E-6)
             {
                double dr = _data.m_OpticalDistCoef[0] + (rr * (_data.m_OpticalDistCoef[1]
                   + rr * _data.m_OpticalDistCoef[2]));
                isisFocalPlaneX = isisNatFocalPlaneX * (1.0 - dr);
                isisFocalPlaneY = isisNatFocalPlaneY * (1.0 - dr);
             }
          }
          break;
       }
    
       // Define imaging ray in image space
    
       double losIsis[3];
       losIsis[0] = -isisFocalPlaneX * _data.m_IsisZDirection;
       losIsis[1] = -isisFocalPlaneY * _data.m_IsisZDirection;
       losIsis[2] = -_data.m_Focal * (1.0 - getValue(15, adj) / _data.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;
    
       // Apply boresight correction
    
       double losApl[3];
       losApl[0] =
          _data.m_MountingMatrix[0] * losIsis[0]
          + _data.m_MountingMatrix[1] * losIsis[1]
          + _data.m_MountingMatrix[2] * losIsis[2];
       losApl[1] =
          _data.m_MountingMatrix[3] * losIsis[0]
          + _data.m_MountingMatrix[4] * losIsis[1]
          + _data.m_MountingMatrix[5] * losIsis[2];
       losApl[2] =
          _data.m_MountingMatrix[6] * losIsis[0]
          + _data.m_MountingMatrix[7] * losIsis[1]
          + _data.m_MountingMatrix[8] * losIsis[2];
    
       // Apply attitude correction
    
       double aTime = time - _data.m_T0Quat;
       double euler[3];
       double nTime = aTime / _data.m_HalfTime;
       double nTime2 = nTime * nTime;
       euler[0] =
          (getValue(6, adj) + getValue(9, adj)* nTime + getValue(12, adj)* nTime2) / _data.m_FlyingHeight;
       euler[1] =
          (getValue(7, adj) + getValue(10, adj)* nTime + getValue(13, adj)* nTime2) / _data.m_FlyingHeight;
       euler[2] =
          (getValue(8, adj) + getValue(11, adj)* nTime + getValue(14, adj)* nTime2) / _data.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]);
       double plFromApl[9];
       plFromApl[0] = cos_b * cos_c;
       plFromApl[1] = -cos_a * sin_c + sin_a * sin_b * cos_c;
       plFromApl[2] = sin_a * sin_c + cos_a * sin_b * cos_c;
       plFromApl[3] = cos_b * sin_c;
       plFromApl[4] = cos_a * cos_c + sin_a * sin_b * sin_c;
       plFromApl[5] = -sin_a * cos_c + cos_a * sin_b * sin_c;
       plFromApl[6] = -sin_b;
       plFromApl[7] = sin_a * cos_b;
       plFromApl[8] = cos_a * cos_b;
       double losPl[3];
       losPl[0] = plFromApl[0] * losApl[0] + plFromApl[1] * losApl[1]
          + plFromApl[2] * losApl[2];
       losPl[1] = plFromApl[3] * losApl[0] + plFromApl[4] * losApl[1]
          + plFromApl[5] * losApl[2];
       losPl[2] = plFromApl[6] * losApl[0] + plFromApl[7] * losApl[1]
          + plFromApl[8] * losApl[2];
    
       // Apply rotation matrix from sensor quaternions
    
       int nOrder = 8;
       if (_data.m_PlatformFlag == 0)
          nOrder = 4;
       int nOrderQuat = nOrder;
       if (_data.m_NumQuaternions < 6 && nOrder == 8)
          nOrderQuat = 4;
       double q[4];
       lagrangeInterp(
          _data.m_NumQuaternions, &_data.m_Quaternions[0], _data.m_T0Quat, _data.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]);
       q[0] /= norm;
       q[1] /= norm;
       q[2] /= norm;
       q[3] /= norm;
       double ecfFromPl[9];
       ecfFromPl[0] = q[0] * q[0] - q[1] * q[1] - q[2] * q[2] + q[3] * q[3];
       ecfFromPl[1] = 2 * (q[0] * q[1] - q[2] * q[3]);
       ecfFromPl[2] = 2 * (q[0] * q[2] + q[1] * q[3]);
       ecfFromPl[3] = 2 * (q[0] * q[1] + q[2] * q[3]);
       ecfFromPl[4] = -q[0] * q[0] + q[1] * q[1] - q[2] * q[2] + q[3] * q[3];
       ecfFromPl[5] = 2 * (q[1] * q[2] - q[0] * q[3]);
       ecfFromPl[6] = 2 * (q[0] * q[2] - q[1] * q[3]);
       ecfFromPl[7] = 2 * (q[1] * q[2] + q[0] * q[3]);
       ecfFromPl[8] = -q[0] * q[0] - q[1] * q[1] + q[2] * q[2] + q[3] * q[3];
       xl = ecfFromPl[0] * losPl[0] + ecfFromPl[1] * losPl[1]
          + ecfFromPl[2] * losPl[2];
       yl = ecfFromPl[3] * losPl[0] + ecfFromPl[4] * losPl[1]
          + ecfFromPl[5] * losPl[2];
       zl = ecfFromPl[6] * losPl[0] + ecfFromPl[7] * losPl[1]
          + ecfFromPl[8] * losPl[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 - _data.m_SemiMinorAxis * _data.m_SemiMinorAxis / _data.m_SemiMajorAxis / _data.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 = _data.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 = _data.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 = _data.m_SemiMajorAxis + height;
       bp = _data.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 (_data.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 (_data.m_PlatformFlag == 0)
          nOrder = 4;
       double sensPosNom[3];
       lagrangeInterp(_data.m_NumEphem, &_data.m_EphemPts[0], _data.m_T0Ephem, _data.m_DtEphem,
          time, 3, nOrder, sensPosNom);
       double sensVelNom[3];
       lagrangeInterp(_data.m_NumEphem, &_data.m_EphemRates[0], _data.m_T0Ephem, _data.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 - _data.m_T0Ephem;
       double dvi = getValue(3, adj) / _data.m_HalfTime;
       double dvc = getValue(4, adj) / _data.m_HalfTime;
       double dvr = getValue(5, adj) / _data.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
    {
       // 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
       int nOrder = 8;
       if (_data.m_PlatformFlag == 0)
          nOrder = 4;
       int nOrderQuat = nOrder;
       if (_data.m_NumQuaternions < 6 && nOrder == 8)
          nOrderQuat = 4;
       double q[4];
       lagrangeInterp(
          _data.m_NumQuaternions, &_data.m_Quaternions[0], _data.m_T0Quat, _data.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]);
       // Divide by the negative norm for 0 through 2 to invert the quaternion
       q[0] /= -norm;
       q[1] /= -norm;
       q[2] /= -norm;
       q[3] /= norm;
       double bodyToCamera[9];
       bodyToCamera[0] = q[0] * q[0] - q[1] * q[1] - q[2] * q[2] + q[3] * q[3];
       bodyToCamera[1] = 2 * (q[0] * q[1] - q[2] * q[3]);
       bodyToCamera[2] = 2 * (q[0] * q[2] + q[1] * q[3]);
       bodyToCamera[3] = 2 * (q[0] * q[1] + q[2] * q[3]);
       bodyToCamera[4] = -q[0] * q[0] + q[1] * q[1] - q[2] * q[2] + q[3] * q[3];
       bodyToCamera[5] = 2 * (q[1] * q[2] - q[0] * q[3]);
       bodyToCamera[6] = 2 * (q[0] * q[2] - q[1] * q[3]);
       bodyToCamera[7] = 2 * (q[1] * q[2] + q[0] * q[3]);
       bodyToCamera[8] = -q[0] * q[0] - q[1] * q[1] + q[2] * q[2] + q[3] * q[3];
       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 aTime = time - _data.m_T0Quat;
       double euler[3];
       double nTime = aTime / _data.m_HalfTime;
       double nTime2 = nTime * nTime;
       euler[0] =
          (getValue(6, adj) + getValue(9, adj)* nTime + getValue(12, adj)* nTime2) / _data.m_FlyingHeight;
       euler[1] =
          (getValue(7, adj) + getValue(10, adj)* nTime + getValue(13, adj)* nTime2) / _data.m_FlyingHeight;
       euler[2] =
          (getValue(8, adj) + getValue(11, adj)* nTime + getValue(14, adj)* nTime2) / _data.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]);
       double attCorr[9];
       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;
       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;
    
       // Invert the boresight correction
       double correctedLookX = _data.m_MountingMatrix[0] * adjustedLookX
                             + _data.m_MountingMatrix[3] * adjustedLookY
                             + _data.m_MountingMatrix[6] * adjustedLookZ;
       double correctedLookY = _data.m_MountingMatrix[1] * adjustedLookX
                             + _data.m_MountingMatrix[4] * adjustedLookY
                             + _data.m_MountingMatrix[7] * adjustedLookZ;
       double correctedLookZ = _data.m_MountingMatrix[2] * adjustedLookX
                             + _data.m_MountingMatrix[5] * adjustedLookY
                             + _data.m_MountingMatrix[8] * adjustedLookZ;
    
       // Convert to focal plane coordinate
       double lookScale = _data.m_Focal / correctedLookZ;
       double focalX = correctedLookX * lookScale;
       double focalY = correctedLookY * lookScale;
    
       // TODO invert distortion here
       // We probably only want to try and invert the distortion if we are
       // reasonably close to the actual CCD because the distortion equations are
       // sometimes only well behaved close to the CCD.
    
       // Convert to detector line and sample
       double detectorLine = _data.m_ITransL[0]
                           + _data.m_ITransL[1] * focalX
                           + _data.m_ITransL[2] * focalY;
       double detectorSample = _data.m_ITransS[0]
                             + _data.m_ITransS[1] * focalX
                             + _data.m_ITransS[2] * focalY;
    
       // Convert to image sample line
       double line = detectorLine + _data.m_DetectorLineOrigin - _data.m_DetectorLineOffset
                   - _data.m_OffsetLines + 0.5;
       double sample = (detectorSample + _data.m_DetectorSampleOrigin - _data.m_StartingSample)
                     / _data.m_DetectorSampleSumming - _data.m_OffsetSamples + 0.5;
       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 = _data.m_TotalLines;
          double numCols = _data.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 = _data.m_TotalLines / 2.0;
          ip.samp = _data.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 = _data.m_TotalLines;
       double numCols = _data.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]);
    }