Skip to content
Snippets Groups Projects
Select Git revision
  • ca0a98b3eabc676be9a8506f92f6028b2991fc6e
  • main default
  • develop
  • feature/arch_support
  • feature/global_refactoring
  • v1.0.0
6 results

xstartup

Blame
  • UsgsAstroSarSensorModel.cpp 43.72 KiB
    #include "UsgsAstroSarSensorModel.h"
    #include "Utilities.h"
    
    #include <string.h>
    #include <cmath>
    #include <functional>
    #include <iomanip>
    
    #include <nlohmann/json.hpp>
    
    using json = nlohmann::json;
    using namespace std;
    
    #define MESSAGE_LOG(...)         \
      if (m_logger) {                \
        m_logger->info(__VA_ARGS__); \
      }
    
    const string UsgsAstroSarSensorModel::_SENSOR_MODEL_NAME =
        "USGS_ASTRO_SAR_SENSOR_MODEL";
    const int UsgsAstroSarSensorModel::NUM_PARAMETERS = 6;
    const string UsgsAstroSarSensorModel::PARAMETER_NAME[] = {
        "X Pos. Bias   ",  // 0
        "Y Pos. Bias   ",  // 1
        "Z Pos. Bias   ",  // 2
        "X Vel. Bias   ",  // 3
        "Y Vel. Bias   ",  // 4
        "Z Vel. Bias   "   // 5
    };
    
    const int UsgsAstroSarSensorModel::NUM_PARAM_TYPES = 4;
    const string UsgsAstroSarSensorModel::PARAM_STRING_ALL[] = {
        "NONE", "FICTITIOUS", "REAL", "FIXED"};
    const csm::param::Type UsgsAstroSarSensorModel::PARAM_CHAR_ALL[] = {
        csm::param::NONE, csm::param::FICTITIOUS, csm::param::REAL,
        csm::param::FIXED};
    
    string UsgsAstroSarSensorModel::constructStateFromIsd(
        const string imageSupportData, csm::WarningList* warnings) {
      MESSAGE_LOG("UsgsAstroSarSensorModel constructing state from ISD, with {}",
                  imageSupportData);
      json isd = json::parse(imageSupportData);
      json state = {};
    
      csm::WarningList* parsingWarnings = new csm::WarningList;
    
      state["m_modelName"] = getSensorModelName(isd, parsingWarnings);
      state["m_imageIdentifier"] = getImageId(isd, parsingWarnings);
      state["m_sensorName"] = getSensorName(isd, parsingWarnings);
      state["m_platformName"] = getPlatformName(isd, parsingWarnings);
    
      state["m_nLines"] = getTotalLines(isd, parsingWarnings);
      state["m_nSamples"] = getTotalSamples(isd, parsingWarnings);
    
      // Zero computed state values
      state["m_referencePointXyz"] = vector<double>(3, 0.0);
    
      // sun_position and velocity are required for getIlluminationDirection
      state["m_sunPosition"] = getSunPositions(isd, parsingWarnings);
      state["m_sunVelocity"] = getSunVelocities(isd, parsingWarnings);
    
      state["m_centerEphemerisTime"] = getCenterTime(isd, parsingWarnings);
      state["m_startingEphemerisTime"] = getStartingTime(isd, parsingWarnings);
      state["m_endingEphemerisTime"] = getEndingTime(isd, parsingWarnings);
    
      state["m_exposureDuration"] = getExposureDuration(isd, parsingWarnings);
    
      try {
        state["m_dtEphem"] = isd.at("dt_ephemeris");
      } catch (...) {
        std::string msg = "dt_ephemeris not in ISD";
        MESSAGE_LOG(msg);
        parsingWarnings->push_back(
            csm::Warning(csm::Warning::DATA_NOT_AVAILABLE, msg,
                         "UsgsAstroSarSensorModel::constructStateFromIsd()"));
      }
    
      try {
        state["m_t0Ephem"] = isd.at("t0_ephemeris");
      } catch (...) {
        std::string msg = "t0_ephemeris not in ISD";
        MESSAGE_LOG(msg);
        parsingWarnings->push_back(
            csm::Warning(csm::Warning::DATA_NOT_AVAILABLE, msg,
                         "UsgsAstroSarSensorModel::constructStateFromIsd()"));
      }
    
      state["m_positions"] = getSensorPositions(isd, parsingWarnings);
      state["m_velocities"] = getSensorVelocities(isd, parsingWarnings);
    
      state["m_currentParameterValue"] = vector<double>(NUM_PARAMETERS, 0.0);
    
      // get radii
      state["m_minorAxis"] = getSemiMinorRadius(isd, parsingWarnings);
      state["m_majorAxis"] = getSemiMajorRadius(isd, parsingWarnings);
    
      // set identifiers
      state["m_platformIdentifier"] = getPlatformName(isd, parsingWarnings);
      state["m_sensorIdentifier"] = getSensorName(isd, parsingWarnings);
    
      // get reference_height
      state["m_minElevation"] = -1000;
      state["m_maxElevation"] = 1000;
    
      // SAR specific values
      state["m_scaledPixelWidth"] = getScaledPixelWidth(isd, parsingWarnings);
      state["m_scaleConversionCoefficients"] =
          getScaleConversionCoefficients(isd, parsingWarnings);
      state["m_scaleConversionTimes"] =
          getScaleConversionTimes(isd, parsingWarnings);
      state["m_wavelength"] = getWavelength(isd, parsingWarnings);
      state["m_lookDirection"] = getLookDirection(isd, parsingWarnings);
    
      // Default to identity covariance
      state["m_covariance"] = 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;
      }
    
      if (!parsingWarnings->empty()) {
        if (warnings) {
          warnings->insert(warnings->end(), parsingWarnings->begin(),
                           parsingWarnings->end());
        }
        std::string message =
            "ISD is invalid for creating the sensor model with error [";
        csm::Warning warn = parsingWarnings->front();
        message += warn.getMessage();
        message += "]";
        parsingWarnings = nullptr;
        delete parsingWarnings;
        MESSAGE_LOG(message);
        throw csm::Error(csm::Error::SENSOR_MODEL_NOT_CONSTRUCTIBLE, message,
                         "UsgsAstroSarSensorModel::constructStateFromIsd");
      }
    
      delete parsingWarnings;
      parsingWarnings = nullptr;
    
      // The state data will still be updated when a sensor model is created since
      // some state data is not in the ISD and requires a SM to compute them.
      return state.dump();
    }
    
    string UsgsAstroSarSensorModel::getModelNameFromModelState(
        const string& model_state) {
      MESSAGE_LOG("Getting model name from model state: {}", model_state);
      // Parse the string to JSON
      auto j = json::parse(model_state);
      // If model name cannot be determined, return a blank string
      string model_name;
    
      if (j.find("m_modelName") != j.end()) {
        model_name = j["m_modelName"];
      } else {
        csm::Error::ErrorType aErrorType = csm::Error::INVALID_SENSOR_MODEL_STATE;
        string aMessage = "No 'm_modelName' key in the model state object.";
        string aFunction = "UsgsAstroSarSensorModel::getModelNameFromModelState";
        MESSAGE_LOG(aMessage);
        csm::Error csmErr(aErrorType, aMessage, aFunction);
        throw(csmErr);
      }
      if (model_name != _SENSOR_MODEL_NAME) {
        csm::Error::ErrorType aErrorType = csm::Error::SENSOR_MODEL_NOT_SUPPORTED;
        string aMessage = "Sensor model not supported.";
        string aFunction = "UsgsAstroSarSensorModel::getModelNameFromModelState()";
        MESSAGE_LOG(aMessage);
        csm::Error csmErr(aErrorType, aMessage, aFunction);
        throw(csmErr);
      }
      return model_name;
    }
    
    UsgsAstroSarSensorModel::UsgsAstroSarSensorModel() {
      MESSAGE_LOG("Constructing UsgsAstroSarSensorModel");
      reset();
    }
    
    void UsgsAstroSarSensorModel::reset() {
      MESSAGE_LOG("Resetting UsgsAstroSarSensorModel");
      m_imageIdentifier = "Unknown";
      m_sensorName = "Unknown";
      m_platformIdentifier = "Unknown";
      m_nLines = 0;
      m_nSamples = 0;
      m_exposureDuration = 0;
      m_scaledPixelWidth = 0;
      m_lookDirection = LookDirection::LEFT;
      m_startingEphemerisTime = 0;
      m_centerEphemerisTime = 0;
      m_endingEphemerisTime = 0;
      m_majorAxis = 0;
      m_minorAxis = 0;
      m_platformIdentifier = "Unknown";
      m_sensorIdentifier = "Unknown";
      m_trajectoryIdentifier = "Unknown";
      m_collectionIdentifier = "Unknown";
      m_minElevation = -1000;
      m_maxElevation = 1000;
      m_dtEphem = 0;
      m_t0Ephem = 0;
      m_scaleConversionCoefficients.clear();
      m_scaleConversionTimes.clear();
      m_positions.clear();
      m_velocities.clear();
      m_currentParameterValue = vector<double>(NUM_PARAMETERS, 0.0);
      m_parameterType = vector<csm::param::Type>(NUM_PARAMETERS, csm::param::REAL);
      m_referencePointXyz.x = 0.0;
      m_referencePointXyz.y = 0.0;
      m_referencePointXyz.z = 0.0;
      m_covariance = vector<double>(NUM_PARAMETERS * NUM_PARAMETERS, 0.0);
      m_sunPosition.clear();
      m_sunVelocity.clear();
      m_wavelength = 0;
      m_noAdjustments = std::vector<double>(NUM_PARAMETERS, 0.0);
    }
    
    void UsgsAstroSarSensorModel::replaceModelState(const string& argState) {
      reset();
    
      MESSAGE_LOG("Replacing model state with: {}", argState);
      auto stateJson = json::parse(argState);
    
      m_imageIdentifier = stateJson["m_imageIdentifier"].get<string>();
      m_platformIdentifier = stateJson["m_platformIdentifier"].get<string>();
      m_sensorName = stateJson["m_sensorName"].get<string>();
      m_nLines = stateJson["m_nLines"];
      m_nSamples = stateJson["m_nSamples"];
      m_exposureDuration = stateJson["m_exposureDuration"];
      m_scaledPixelWidth = stateJson["m_scaledPixelWidth"];
      std::string lookStr = stateJson["m_lookDirection"];
      if (lookStr.compare("right") == 0) {
        m_lookDirection = UsgsAstroSarSensorModel::RIGHT;
      } else if (lookStr.compare("left") == 0) {
        m_lookDirection = UsgsAstroSarSensorModel::LEFT;
      } else {
        std::string message = "Could not determine look direction from state";
        MESSAGE_LOG(message);
        throw csm::Error(csm::Error::INVALID_SENSOR_MODEL_STATE, message,
                         "UsgsAstroSarSensorModel::replaceModelState");
      }
      m_wavelength = stateJson["m_wavelength"];
      m_startingEphemerisTime = stateJson["m_startingEphemerisTime"];
      m_centerEphemerisTime = stateJson["m_centerEphemerisTime"];
      m_endingEphemerisTime = stateJson["m_endingEphemerisTime"];
      m_majorAxis = stateJson["m_majorAxis"];
      m_minorAxis = stateJson["m_minorAxis"];
      m_platformIdentifier = stateJson["m_platformIdentifier"].get<string>();
      m_sensorIdentifier = stateJson["m_sensorIdentifier"].get<string>();
      m_minElevation = stateJson["m_minElevation"];
      m_maxElevation = stateJson["m_maxElevation"];
      m_dtEphem = stateJson["m_dtEphem"];
      m_t0Ephem = stateJson["m_t0Ephem"];
      m_scaleConversionCoefficients =
          stateJson["m_scaleConversionCoefficients"].get<vector<double>>();
      m_scaleConversionTimes =
          stateJson["m_scaleConversionTimes"].get<vector<double>>();
      m_positions = stateJson["m_positions"].get<vector<double>>();
      m_velocities = stateJson["m_velocities"].get<vector<double>>();
      m_currentParameterValue =
          stateJson["m_currentParameterValue"].get<vector<double>>();
      m_referencePointXyz.x = stateJson["m_referencePointXyz"][0];
      m_referencePointXyz.y = stateJson["m_referencePointXyz"][1];
      m_referencePointXyz.z = stateJson["m_referencePointXyz"][2];
      m_covariance = stateJson["m_covariance"].get<vector<double>>();
      m_sunPosition = stateJson["m_sunPosition"].get<vector<double>>();
      m_sunVelocity = stateJson["m_sunVelocity"].get<vector<double>>();
    
      // If sensor model is being created for the first time, this routine will set
      // the reference point
      if (m_referencePointXyz.x == 0 && m_referencePointXyz.y == 0 &&
          m_referencePointXyz.z == 0) {
        MESSAGE_LOG("Updating State")
    
        double lineCtr = m_nLines / 2.0;
        double sampCtr = m_nSamples / 2.0;
        csm::ImageCoord ip(lineCtr, sampCtr);
        MESSAGE_LOG("updateState: center image coordinate set to {} {}", lineCtr,
                    sampCtr)
    
        double refHeight = 0;
        m_referencePointXyz = imageToGround(ip, refHeight);
        MESSAGE_LOG("updateState: reference point (x, y, z) {} {} {}",
                    m_referencePointXyz.x, m_referencePointXyz.y,
                    m_referencePointXyz.z)
      }
    }
    
    string UsgsAstroSarSensorModel::getModelState() const {
      json state = {};
    
      state["m_modelName"] = _SENSOR_MODEL_NAME;
      state["m_imageIdentifier"] = m_imageIdentifier;
      state["m_sensorName"] = m_sensorName;
      state["m_platformName"] = m_platformName;
      state["m_nLines"] = m_nLines;
      state["m_nSamples"] = m_nSamples;
      state["m_referencePointXyz"] = {m_referencePointXyz.x, m_referencePointXyz.y,
                                      m_referencePointXyz.z};
      state["m_sunPosition"] = m_sunPosition;
      state["m_sunVelocity"] = m_sunVelocity;
      state["m_centerEphemerisTime"] = m_centerEphemerisTime;
      state["m_startingEphemerisTime"] = m_startingEphemerisTime;
      state["m_endingEphemerisTime"] = m_endingEphemerisTime;
      state["m_exposureDuration"] = m_exposureDuration;
      state["m_dtEphem"] = m_dtEphem;
      state["m_t0Ephem"] = m_t0Ephem;
      state["m_positions"] = m_positions;
      state["m_velocities"] = m_velocities;
      state["m_currentParameterValue"] = m_currentParameterValue;
      state["m_minorAxis"] = m_minorAxis;
      state["m_majorAxis"] = m_majorAxis;
      state["m_platformIdentifier"] = m_platformIdentifier;
      state["m_sensorIdentifier"] = m_sensorIdentifier;
      state["m_minElevation"] = m_minElevation;
      state["m_maxElevation"] = m_maxElevation;
      state["m_scaledPixelWidth"] = m_scaledPixelWidth;
      if (m_lookDirection == 0) {
        state["m_lookDirection"] = "left";
      } else if (m_lookDirection == 1) {
        state["m_lookDirection"] = "right";
      } else {
        std::string message = "Could not parse look direction from json state.";
        MESSAGE_LOG(message);
        throw csm::Error(csm::Error::INVALID_SENSOR_MODEL_STATE, message,
                         "UsgsAstroSarSensorModel::getModelState");
      }
      state["m_wavelength"] = m_wavelength;
      state["m_scaleConversionCoefficients"] = m_scaleConversionCoefficients;
      state["m_scaleConversionTimes"] = m_scaleConversionTimes;
      state["m_covariance"] = m_covariance;
    
      return state.dump();
    }
    
    csm::ImageCoord UsgsAstroSarSensorModel::groundToImage(
        const csm::EcefCoord& groundPt, double desiredPrecision,
        double* achievedPrecision, csm::WarningList* warnings) const {
      MESSAGE_LOG(
          "Computing groundToImage(ImageCoord) for {}, {}, {}, with desired "
          "precision {}"
          "No adjustments.",
          groundPt.x, groundPt.y, groundPt.z, desiredPrecision);
    
      csm::ImageCoord imagePt = groundToImage(
          groundPt, m_noAdjustments, desiredPrecision, achievedPrecision, warnings);
      return imagePt;
    }
    
    csm::ImageCoord UsgsAstroSarSensorModel::groundToImage(
        const csm::EcefCoord& groundPt, const std::vector<double> adj,
        double desiredPrecision, double* achievedPrecision,
        csm::WarningList* warnings) const {
      MESSAGE_LOG(
          "Computing groundToImage(ImageCoord) for {}, {}, {}, with desired "
          "precision {}, and "
          "adjustments.",
          groundPt.x, groundPt.y, groundPt.z, desiredPrecision);
    
      // Find time of closest approach to groundPt and the corresponding slant range
      // by finding the root of the doppler shift frequency
      try {
        double timeTolerance = m_exposureDuration * desiredPrecision / 2.0;
    
        double time = dopplerShift(groundPt, timeTolerance, adj);
        double slantRangeValue = slantRange(groundPt, time, adj);
    
        // Find the ground range, based on the ground-range-to-slant-range
        // polynomial defined by the range coefficient set, with a time closest to
        // the calculated time of closest approach
        double groundTolerance = m_scaledPixelWidth * desiredPrecision / 2.0;
        double groundRange = slantRangeToGroundRange(
            groundPt, time, slantRangeValue, groundTolerance);
    
        double line = (time - m_startingEphemerisTime) / m_exposureDuration + 0.5;
        double sample = groundRange / m_scaledPixelWidth;
        return csm::ImageCoord(line, sample);
      } catch (std::exception& error) {
        std::string message = "Could not calculate groundToImage, with error [";
        message += error.what();
        message += "]";
        MESSAGE_LOG(message);
        throw csm::Error(csm::Error::UNKNOWN_ERROR, message, "groundToImage");
      }
    }
    
    // Calculate the root
    double UsgsAstroSarSensorModel::dopplerShift(
        csm::EcefCoord groundPt, double tolerance,
        const std::vector<double> adj) const {
      MESSAGE_LOG("Calculating doppler shift with: {}, {}, {}, and tolerance {}.",
                  groundPt.x, groundPt.y, groundPt.z, tolerance);
      csm::EcefVector groundVec(groundPt.x, groundPt.y, groundPt.z);
      std::function<double(double)> dopplerShiftFunction = [this, groundVec,
                                                            adj](double time) {
        csm::EcefVector spacecraftPosition =
            getAdjustedSpacecraftPosition(time, adj);
        csm::EcefVector spacecraftVelocity = getAdjustedSensorVelocity(time, adj);
        csm::EcefVector lookVector = spacecraftPosition - groundVec;
    
        double slantRange = norm(lookVector);
    
        double dopplerShift = -2.0 * dot(lookVector, spacecraftVelocity) /
                              (slantRange * m_wavelength);
    
        return dopplerShift;
      };
    
      // Do root-finding for "dopplerShift"
      double timePadding = m_exposureDuration * m_nLines * 0.25;
      return brentRoot(m_startingEphemerisTime - timePadding,
                       m_endingEphemerisTime + timePadding, dopplerShiftFunction,
                       tolerance);
    }
    
    double UsgsAstroSarSensorModel::slantRange(csm::EcefCoord surfPt, double time,
                                               std::vector<double> adj) const {
      MESSAGE_LOG("Calculating slant range with: {}, {}, {}, and time {}.",
                  surfPt.x, surfPt.y, surfPt.z, time);
      csm::EcefVector surfVec(surfPt.x, surfPt.y, surfPt.z);
      csm::EcefVector spacecraftPosition = getAdjustedSpacecraftPosition(time, adj);
      return norm(spacecraftPosition - surfVec);
    }
    
    double UsgsAstroSarSensorModel::slantRangeToGroundRange(
        const csm::EcefCoord& groundPt, double time, double slantRange,
        double tolerance) const {
      MESSAGE_LOG(
          "Calculating slant range to ground range with: {}, {}, {}, {}, {}, {}",
          groundPt.x, groundPt.y, groundPt.z, time, slantRange, tolerance);
    
      std::vector<double> coeffs = getRangeCoefficients(time);
    
      // Need to come up with an initial guess when solving for ground
      // range given slant range. Naively use the middle of the image.
      double guess = 0.5 * m_nSamples * m_scaledPixelWidth;
    
      // Tolerance to 1/20th of a pixel for now.
      coeffs[0] -= slantRange;
      return polynomialRoot(coeffs, guess, tolerance);
    }
    
    double UsgsAstroSarSensorModel::groundRangeToSlantRange(
        double groundRange, const std::vector<double>& coeffs) const {
      return evaluatePolynomial(coeffs, groundRange);
    }
    
    csm::ImageCoordCovar UsgsAstroSarSensorModel::groundToImage(
        const csm::EcefCoordCovar& groundPt, double desiredPrecision,
        double* achievedPrecision, csm::WarningList* warnings) const {
      MESSAGE_LOG("Calculating groundToImage with: {}, {}, {}, {}", groundPt.x,
                  groundPt.y, groundPt.z, desiredPrecision);
      // Ground to image with error propagation
      // Compute corresponding image point
      csm::EcefCoord gp(groundPt);
    
      csm::ImageCoord ip =
          groundToImage(gp, desiredPrecision, achievedPrecision, 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;
    }
    
    csm::EcefCoord UsgsAstroSarSensorModel::imageToGround(
        const csm::ImageCoord& imagePt, double height, double desiredPrecision,
        double* achievedPrecision, csm::WarningList* warnings) const {
      MESSAGE_LOG("Calculating imageToGround with: {}, {}, {}, {}", imagePt.samp,
                  imagePt.line, height, desiredPrecision);
      double time =
          m_startingEphemerisTime + (imagePt.line - 0.5) * m_exposureDuration;
      double groundRange = imagePt.samp * m_scaledPixelWidth;
      std::vector<double> coeffs = getRangeCoefficients(time);
      double slantRange = groundRangeToSlantRange(groundRange, coeffs);
    
      // Compute the in-track, cross-track, nadir, coordinate system to solve in
      csm::EcefVector spacecraftPosition = getSpacecraftPosition(time);
      double positionMag = norm(spacecraftPosition);
      csm::EcefVector spacecraftVelocity = getSensorVelocity(time);
      // In-track unit vector
      csm::EcefVector vHat = normalized(spacecraftVelocity);
      // Nadir unit vector
      csm::EcefVector tHat = normalized(rejection(spacecraftPosition, vHat));
      // Cross-track unit vector
      csm::EcefVector uHat = cross(vHat, tHat);
    
      // Compute the spacecraft position in the new coordinate system
      //   The cross-track unit vector is orthogonal to the position so we ignore it
      double nadirComp = dot(spacecraftPosition, tHat);
    
      // Iterate to find proper radius value
      double pointRadius = m_majorAxis + height;
      double radiusSqr;
      double pointHeight;
      csm::EcefVector groundVec;
      do {
        radiusSqr = pointRadius * pointRadius;
        double alpha =
            (radiusSqr - slantRange * slantRange - positionMag * positionMag) /
            (2 * nadirComp);
        double beta = sqrt(slantRange * slantRange - alpha * alpha);
        if (m_lookDirection == LEFT) {
          beta *= -1;
        }
        groundVec = alpha * tHat + beta * uHat + spacecraftPosition;
        pointHeight = computeEllipsoidElevation(
            groundVec.x, groundVec.y, groundVec.z, m_majorAxis, m_minorAxis);
        pointRadius -= (pointHeight - height);
      } while (fabs(pointHeight - height) > desiredPrecision);
    
      csm::EcefCoord groundPt(groundVec.x, groundVec.y, groundVec.z);
    
      return groundPt;
    }
    
    csm::EcefCoordCovar UsgsAstroSarSensorModel::imageToGround(
        const csm::ImageCoordCovar& imagePt, double height, double heightVariance,
        double desiredPrecision, double* achievedPrecision,
        csm::WarningList* warnings) const {
      MESSAGE_LOG("Calculating imageToGroundWith: {}, {}, {}, {}, {}", imagePt.samp,
                  imagePt.line, height, heightVariance, desiredPrecision);
      // Image to ground with error propagation
      // Use numerical partials
      const double DELTA_IMAGE = 1.0;
      const double DELTA_GROUND = m_scaledPixelWidth;
      csm::ImageCoord ip(imagePt.line, imagePt.samp);
    
      csm::EcefCoord gp =
          imageToGround(ip, height, desiredPrecision, achievedPrecision, warnings);
    
      // Compute numerical partials xyz wrt to lsh
      ip.line = imagePt.line + DELTA_IMAGE;
      ip.samp = imagePt.samp;
      csm::EcefCoord gpl = imageToGround(ip, height, desiredPrecision);
      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 = imagePt.line;
      ip.samp = imagePt.samp + DELTA_IMAGE;
      csm::EcefCoord gps = imageToGround(ip, height, desiredPrecision);
      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 = imagePt.line;
      ip.samp = imagePt.samp;  // +DELTA_IMAGE;
      csm::EcefCoord gph =
          imageToGround(ip, height + DELTA_GROUND, desiredPrecision);
      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(imagePt);
    
      double iCov[4];
      iCov[0] = imagePt.covariance[0] + sCov[0] + unmod[0];
      iCov[1] = imagePt.covariance[1] + sCov[1] + unmod[1];
      iCov[2] = imagePt.covariance[2] + sCov[2] + unmod[2];
      iCov[3] = imagePt.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;
    }
    
    csm::EcefLocus UsgsAstroSarSensorModel::imageToProximateImagingLocus(
        const csm::ImageCoord& imagePt, const csm::EcefCoord& groundPt,
        double desiredPrecision, double* achievedPrecision,
        csm::WarningList* warnings) const {
      // Compute the slant range
      double time =
          m_startingEphemerisTime + (imagePt.line - 0.5) * m_exposureDuration;
      double groundRange = imagePt.samp * m_scaledPixelWidth;
      std::vector<double> coeffs = getRangeCoefficients(time);
      double slantRange = groundRangeToSlantRange(groundRange, coeffs);
    
      // Project the sensor to ground point vector onto the 0 doppler plane
      // then compute the closest point at the slant range to that
      csm::EcefVector spacecraftPosition = getSpacecraftPosition(time);
      csm::EcefVector spacecraftVelocity = getSensorVelocity(time);
      csm::EcefVector groundVec(groundPt.x, groundPt.y, groundPt.z);
      csm::EcefVector lookVec =
          normalized(rejection(groundVec - spacecraftPosition, spacecraftVelocity));
      csm::EcefVector closestVec = spacecraftPosition + slantRange * lookVec;
    
      // Compute the tangent at the closest point
      csm::EcefVector tangent;
      if (m_lookDirection == LEFT) {
        tangent = cross(spacecraftVelocity, lookVec);
      } else {
        tangent = cross(lookVec, spacecraftVelocity);
      }
      tangent = normalized(tangent);
    
      return csm::EcefLocus(closestVec.x, closestVec.y, closestVec.z, tangent.x,
                            tangent.y, tangent.z);
    }
    
    csm::EcefLocus UsgsAstroSarSensorModel::imageToRemoteImagingLocus(
        const csm::ImageCoord& imagePt, double desiredPrecision,
        double* achievedPrecision, csm::WarningList* warnings) const {
      // Compute the slant range
      double time =
          m_startingEphemerisTime + (imagePt.line - 0.5) * m_exposureDuration;
      double groundRange = imagePt.samp * m_scaledPixelWidth;
      std::vector<double> coeffs = getRangeCoefficients(time);
      double slantRange = groundRangeToSlantRange(groundRange, coeffs);
    
      // Project the negative sensor position vector onto the 0 doppler plane
      // then compute the closest point at the slant range to that
      csm::EcefVector spacecraftPosition = getSpacecraftPosition(time);
      csm::EcefVector spacecraftVelocity = getSensorVelocity(time);
      csm::EcefVector lookVec =
          normalized(rejection(-1 * spacecraftPosition, spacecraftVelocity));
      csm::EcefVector closestVec = spacecraftPosition + slantRange * lookVec;
    
      // Compute the tangent at the closest point
      csm::EcefVector tangent;
      if (m_lookDirection == LEFT) {
        tangent = cross(spacecraftVelocity, lookVec);
      } else {
        tangent = cross(lookVec, spacecraftVelocity);
      }
      tangent = normalized(tangent);
    
      return csm::EcefLocus(closestVec.x, closestVec.y, closestVec.z, tangent.x,
                            tangent.y, tangent.z);
    }
    
    csm::ImageCoord UsgsAstroSarSensorModel::getImageStart() const {
      return csm::ImageCoord(0.0, 0.0);
    }
    
    csm::ImageVector UsgsAstroSarSensorModel::getImageSize() const {
      return csm::ImageVector(m_nLines, m_nSamples);
    }
    
    pair<csm::ImageCoord, csm::ImageCoord>
    UsgsAstroSarSensorModel::getValidImageRange() const {
      csm::ImageCoord start = getImageStart();
      csm::ImageVector size = getImageSize();
      return make_pair(
          start, csm::ImageCoord(start.line + size.line, start.samp + size.samp));
    }
    
    pair<double, double> UsgsAstroSarSensorModel::getValidHeightRange() const {
      return make_pair(m_minElevation, m_maxElevation);
    }
    
    csm::EcefVector UsgsAstroSarSensorModel::getIlluminationDirection(
        const csm::EcefCoord& groundPt) const {
      csm::EcefVector groundVec(groundPt.x, groundPt.y, groundPt.z);
      csm::EcefVector sunPosition =
          getSunPosition(getImageTime(groundToImage(groundPt)));
      csm::EcefVector illuminationDirection = normalized(groundVec - sunPosition);
      return illuminationDirection;
    }
    
    double UsgsAstroSarSensorModel::getImageTime(
        const csm::ImageCoord& imagePt) const {
      return m_startingEphemerisTime + (imagePt.line - 0.5) * m_exposureDuration;
    }
    
    csm::EcefVector UsgsAstroSarSensorModel::getSpacecraftPosition(
        double time) const {
      MESSAGE_LOG("getSpacecraftPosition at {} without adjustments", time)
      csm::EcefCoord spacecraftPosition = getSensorPosition(time);
      return csm::EcefVector(spacecraftPosition.x, spacecraftPosition.y,
                             spacecraftPosition.z);
    }
    
    csm::EcefVector UsgsAstroSarSensorModel::getAdjustedSpacecraftPosition(
        double time, std::vector<double> adj) const {
      MESSAGE_LOG("getSpacecraftPosition at {} with adjustments", time)
      csm::EcefCoord spacecraftPosition = getAdjustedSensorPosition(time, adj);
      return csm::EcefVector(spacecraftPosition.x, spacecraftPosition.y,
                             spacecraftPosition.z);
    }
    
    csm::EcefCoord UsgsAstroSarSensorModel::getSensorPosition(double time) const {
      MESSAGE_LOG("getSensorPosition at {}.", time)
      csm::EcefCoord sensorPosition =
          getAdjustedSensorPosition(time, m_noAdjustments);
      return sensorPosition;
    }
    
    csm::EcefCoord UsgsAstroSarSensorModel::getSensorPosition(
        const csm::ImageCoord& imagePt) const {
      MESSAGE_LOG("getSensorPosition at {}, {}.", imagePt.samp, imagePt.line);
      double time = getImageTime(imagePt);
      return getSensorPosition(time);
    }
    
    csm::EcefCoord UsgsAstroSarSensorModel::getAdjustedSensorPosition(
        double time, std::vector<double> adj) const {
      MESSAGE_LOG("getSensorPosition at {}. With adjustments: {}.", time);
      int numPositions = m_positions.size();
      csm::EcefVector spacecraftPosition = csm::EcefVector();
    
      // If there are multiple positions, use Lagrange interpolation
      if ((numPositions / 3) > 1) {
        double position[3];
        lagrangeInterp(numPositions / 3, &m_positions[0], m_t0Ephem, m_dtEphem,
                       time, 3, 8, position);
        spacecraftPosition.x = position[0] + getValue(0, adj);
        spacecraftPosition.y = position[1] + getValue(1, adj);
        spacecraftPosition.z = position[2] + getValue(2, adj);
      } else {
        spacecraftPosition.x = m_positions[0] + getValue(0, adj);
        spacecraftPosition.y = m_positions[1] + getValue(1, adj);
        spacecraftPosition.z = m_positions[2] + getValue(2, adj);
      }
      return csm::EcefCoord(spacecraftPosition.x, spacecraftPosition.y,
                            spacecraftPosition.z);
    }
    
    csm::EcefVector UsgsAstroSarSensorModel::getSensorVelocity(
        const csm::ImageCoord& imagePt) const {
      MESSAGE_LOG("getSensorVelocity at {}, {}. No adjustments.", imagePt.samp,
                  imagePt.line);
      double time = getImageTime(imagePt);
      return getSensorVelocity(time);
    }
    
    csm::EcefVector UsgsAstroSarSensorModel::getSensorVelocity(double time) const {
      MESSAGE_LOG("getSensorVelocity at {}. Without adjustments.", time);
      csm::EcefVector spacecraftVelocity =
          getAdjustedSensorVelocity(time, m_noAdjustments);
      return spacecraftVelocity;
    }
    
    csm::EcefVector UsgsAstroSarSensorModel::getAdjustedSensorVelocity(
        double time, std::vector<double> adj) const {
      MESSAGE_LOG("getSensorVelocity at {}. With adjustments.", time);
      int numVelocities = m_velocities.size();
      csm::EcefVector spacecraftVelocity = csm::EcefVector();
    
      // If there are multiple positions, use Lagrange interpolation
      if ((numVelocities / 3) > 1) {
        double velocity[3];
        lagrangeInterp(numVelocities / 3, &m_velocities[0], m_t0Ephem, m_dtEphem,
                       time, 3, 8, velocity);
        spacecraftVelocity.x = velocity[0] + getValue(3, adj);
        spacecraftVelocity.y = velocity[1] + getValue(4, adj);
        spacecraftVelocity.z = velocity[2] + getValue(5, adj);
      } else {
        spacecraftVelocity.x = m_velocities[0] + getValue(3, adj);
        spacecraftVelocity.y = m_velocities[1] + getValue(4, adj);
        spacecraftVelocity.z = m_velocities[2] + getValue(5, adj);
      }
      return spacecraftVelocity;
    }
    
    csm::RasterGM::SensorPartials UsgsAstroSarSensorModel::computeSensorPartials(
        int index, const csm::EcefCoord& groundPt, double desiredPrecision,
        double* achievedPrecision, csm::WarningList* warnings) const {
      MESSAGE_LOG(
          "Calculating computeSensorPartials for ground point {}, {}, {} with "
          "desired precision {}",
          groundPt.x, groundPt.y, groundPt.z, desiredPrecision)
    
      // Compute image coordinate first
      csm::ImageCoord imgPt =
          groundToImage(groundPt, desiredPrecision, achievedPrecision);
    
      // Call overloaded function
      return computeSensorPartials(index, imgPt, groundPt, desiredPrecision,
                                   achievedPrecision, warnings);
    }
    
    csm::RasterGM::SensorPartials UsgsAstroSarSensorModel::computeSensorPartials(
        int index, const csm::ImageCoord& imagePt, const csm::EcefCoord& groundPt,
        double desiredPrecision, double* achievedPrecision,
        csm::WarningList* warnings) const {
      MESSAGE_LOG(
          "Calculating computeSensorPartials (with image points {}, {}) for ground "
          "point {}, {}, "
          "{} with desired precision {}",
          imagePt.line, imagePt.samp, groundPt.x, groundPt.y, groundPt.z,
          desiredPrecision)
    
      // Compute numerical partials wrt specific parameter
    
      const double DELTA = m_scaledPixelWidth;
      std::vector<double> adj(UsgsAstroSarSensorModel::NUM_PARAMETERS, 0.0);
      adj[index] = DELTA;
    
      csm::ImageCoord adjImgPt = groundToImage(groundPt, adj, desiredPrecision,
                                               achievedPrecision, warnings);
    
      double linePartial = (adjImgPt.line - imagePt.line) / DELTA;
      double samplePartial = (adjImgPt.samp - imagePt.samp) / DELTA;
    
      return csm::RasterGM::SensorPartials(linePartial, samplePartial);
    }
    
    vector<double> UsgsAstroSarSensorModel::computeGroundPartials(
        const csm::EcefCoord& groundPt) const {
      double GND_DELTA = m_scaledPixelWidth;
      // Partial of line, sample wrt X, Y, Z
      double x = groundPt.x;
      double y = groundPt.y;
      double z = groundPt.z;
    
      csm::ImageCoord ipB = groundToImage(groundPt);
      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;
    }
    
    const csm::CorrelationModel& UsgsAstroSarSensorModel::getCorrelationModel()
        const {
      return _NO_CORR_MODEL;
    }
    
    vector<double> UsgsAstroSarSensorModel::getUnmodeledCrossCovariance(
        const csm::ImageCoord& pt1, const csm::ImageCoord& pt2) const {
      return vector<double>(4, 0.0);
    }
    
    csm::EcefCoord UsgsAstroSarSensorModel::getReferencePoint() const {
      return m_referencePointXyz;
    }
    
    void UsgsAstroSarSensorModel::setReferencePoint(
        const csm::EcefCoord& groundPt) {
      m_referencePointXyz = groundPt;
    }
    
    int UsgsAstroSarSensorModel::getNumParameters() const { return NUM_PARAMETERS; }
    
    string UsgsAstroSarSensorModel::getParameterName(int index) const {
      return PARAMETER_NAME[index];
    }
    
    string UsgsAstroSarSensorModel::getParameterUnits(int index) const {
      return "m";
    }
    
    bool UsgsAstroSarSensorModel::hasShareableParameters() const { return false; }
    
    bool UsgsAstroSarSensorModel::isParameterShareable(int index) const {
      return false;
    }
    
    csm::SharingCriteria UsgsAstroSarSensorModel::getParameterSharingCriteria(
        int index) const {
      return csm::SharingCriteria();
    }
    
    double UsgsAstroSarSensorModel::getParameterValue(int index) const {
      return m_currentParameterValue[index];
    }
    
    void UsgsAstroSarSensorModel::setParameterValue(int index, double value) {
      m_currentParameterValue[index] = value;
    }
    
    csm::param::Type UsgsAstroSarSensorModel::getParameterType(int index) const {
      return m_parameterType[index];
    }
    
    void UsgsAstroSarSensorModel::setParameterType(int index,
                                                   csm::param::Type pType) {
      m_parameterType[index] = pType;
    }
    
    double UsgsAstroSarSensorModel::getParameterCovariance(int index1,
                                                           int index2) const {
      return m_covariance[index1 * NUM_PARAMETERS + index2];
    }
    
    void UsgsAstroSarSensorModel::setParameterCovariance(int index1, int index2,
                                                         double covariance)
    
    {
      m_covariance[index1 * NUM_PARAMETERS + index2] = covariance;
    }
    
    int UsgsAstroSarSensorModel::getNumGeometricCorrectionSwitches() const {
      return 0;
    }
    
    string UsgsAstroSarSensorModel::getGeometricCorrectionName(int index) const {
      throw csm::Error(csm::Error::INDEX_OUT_OF_RANGE, "Index is out of range.",
                       "UsgsAstroSarSensorModel::getGeometricCorrectionName");
    }
    
    void UsgsAstroSarSensorModel::setGeometricCorrectionSwitch(
        int index, bool value, csm::param::Type pType) {
      throw csm::Error(csm::Error::INDEX_OUT_OF_RANGE, "Index is out of range.",
                       "UsgsAstroSarSensorModel::setGeometricCorrectionSwitch");
    }
    
    bool UsgsAstroSarSensorModel::getGeometricCorrectionSwitch(int index) const {
      throw csm::Error(csm::Error::INDEX_OUT_OF_RANGE, "Index is out of range.",
                       "UsgsAstroSarSensorModel::getGeometricCorrectionSwitch");
    }
    
    vector<double> UsgsAstroSarSensorModel::getCrossCovarianceMatrix(
        const csm::GeometricModel& comparisonModel, csm::param::Set pSet,
        const csm::GeometricModel::GeometricModelList& otherModels) const {
      // Return covariance matrix
      if (&comparisonModel == this) {
        vector<int> paramIndices = getParameterSetIndices(pSet);
        int numParams = paramIndices.size();
        vector<double> covariances(numParams * numParams, 0.0);
        for (int i = 0; i < numParams; i++) {
          for (int j = 0; j < numParams; j++) {
            covariances[i * numParams + j] =
                getParameterCovariance(paramIndices[i], paramIndices[j]);
          }
        }
        return covariances;
      }
      // No correlation between models.
      const vector<int>& indices = getParameterSetIndices(pSet);
      size_t num_rows = indices.size();
      const vector<int>& indices2 = comparisonModel.getParameterSetIndices(pSet);
      size_t num_cols = indices.size();
    
      return vector<double>(num_rows * num_cols, 0.0);
    }
    
    csm::Version UsgsAstroSarSensorModel::getVersion() const {
      return csm::Version(1, 0, 0);
    }
    
    string UsgsAstroSarSensorModel::getModelName() const {
      return _SENSOR_MODEL_NAME;
    }
    
    string UsgsAstroSarSensorModel::getPedigree() const { return "USGS_SAR"; }
    
    string UsgsAstroSarSensorModel::getImageIdentifier() const {
      return m_imageIdentifier;
    }
    
    void UsgsAstroSarSensorModel::setImageIdentifier(const string& imageId,
                                                     csm::WarningList* warnings) {
      m_imageIdentifier = imageId;
    }
    
    string UsgsAstroSarSensorModel::getSensorIdentifier() const {
      return m_sensorIdentifier;
    }
    
    string UsgsAstroSarSensorModel::getPlatformIdentifier() const {
      return m_platformIdentifier;
    }
    
    string UsgsAstroSarSensorModel::getCollectionIdentifier() const {
      return m_collectionIdentifier;
    }
    
    string UsgsAstroSarSensorModel::getTrajectoryIdentifier() const {
      return m_trajectoryIdentifier;
    }
    
    string UsgsAstroSarSensorModel::getSensorType() const { return "SAR"; }
    
    string UsgsAstroSarSensorModel::getSensorMode() const { return "STRIP"; }
    
    string UsgsAstroSarSensorModel::getReferenceDateAndTime() const {
      csm::ImageCoord referencePointImage = groundToImage(m_referencePointXyz);
      double relativeTime = getImageTime(referencePointImage);
      time_t ephemTime = m_centerEphemerisTime + relativeTime;
      struct tm t = {0};  // Initalize to all 0's
      t.tm_year = 100;    // This is year-1900, so 100 = 2000
      t.tm_mday = 1;
      time_t timeSinceEpoch = mktime(&t);
      time_t finalTime = ephemTime + timeSinceEpoch;
      char buffer[16];
      strftime(buffer, 16, "%Y%m%dT%H%M%S", localtime(&finalTime));
      buffer[15] = '\0';
    
      return buffer;
    }
    
    csm::Ellipsoid UsgsAstroSarSensorModel::getEllipsoid() const {
      return csm::Ellipsoid(m_majorAxis, m_minorAxis);
    }
    
    void UsgsAstroSarSensorModel::setEllipsoid(const csm::Ellipsoid& ellipsoid) {
      m_majorAxis = ellipsoid.getSemiMajorRadius();
      m_minorAxis = ellipsoid.getSemiMinorRadius();
    }
    
    void UsgsAstroSarSensorModel::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;
        }
      }
    }
    
    std::vector<double> UsgsAstroSarSensorModel::getRangeCoefficients(
        double time) const {
      int numCoeffs = m_scaleConversionCoefficients.size();
      std::vector<double> interpCoeffs;
    
      double endTime = m_scaleConversionTimes.back();
      if ((numCoeffs / 4) > 1) {
        double coefficients[4];
        double dtEphem = (endTime - m_scaleConversionTimes[0]) /
                         (m_scaleConversionCoefficients.size() / 4);
        lagrangeInterp(m_scaleConversionCoefficients.size() / 4,
                       &m_scaleConversionCoefficients[0], m_scaleConversionTimes[0],
                       dtEphem, time, 4, 8, coefficients);
        interpCoeffs.push_back(coefficients[0]);
        interpCoeffs.push_back(coefficients[1]);
        interpCoeffs.push_back(coefficients[2]);
        interpCoeffs.push_back(coefficients[3]);
      } else {
        interpCoeffs.push_back(m_scaleConversionCoefficients[0]);
        interpCoeffs.push_back(m_scaleConversionCoefficients[1]);
        interpCoeffs.push_back(m_scaleConversionCoefficients[2]);
        interpCoeffs.push_back(m_scaleConversionCoefficients[3]);
      }
      return interpCoeffs;
    }
    
    csm::EcefVector UsgsAstroSarSensorModel::getSunPosition(
        const double imageTime) const {
      int numSunPositions = m_sunPosition.size();
      int numSunVelocities = m_sunVelocity.size();
      csm::EcefVector sunPosition = csm::EcefVector();
    
      // If there are multiple positions, use Lagrange interpolation
      if ((numSunPositions / 3) > 1) {
        double sunPos[3];
        double endTime = m_t0Ephem + m_nLines * m_exposureDuration;
        double sun_dtEphem = (endTime - m_t0Ephem) / (numSunPositions / 3);
        lagrangeInterp(numSunPositions / 3, &m_sunPosition[0], m_t0Ephem,
                       sun_dtEphem, imageTime, 3, 8, sunPos);
        sunPosition.x = sunPos[0];
        sunPosition.y = sunPos[1];
        sunPosition.z = sunPos[2];
      } else if ((numSunVelocities / 3) >= 1) {
        // If there is one position triple with at least one velocity triple
        //  then the illumination direction is calculated via linear extrapolation.
        sunPosition.x = (imageTime * m_sunVelocity[0] + m_sunPosition[0]);
        sunPosition.y = (imageTime * m_sunVelocity[1] + m_sunPosition[1]);
        sunPosition.z = (imageTime * m_sunVelocity[2] + m_sunPosition[2]);
      } else {
        // If there is one position triple with no velocity triple, then the
        //  illumination direction is the difference of the original vectors.
        sunPosition.x = m_sunPosition[0];
        sunPosition.y = m_sunPosition[1];
        sunPosition.z = m_sunPosition[2];
      }
      return sunPosition;
    }
    
    double UsgsAstroSarSensorModel::getValue(
        int index, const std::vector<double>& adjustments) const {
      return m_currentParameterValue[index] + adjustments[index];
    }