From 23477623004bc144ee3b90d87200a7acb3b7223b Mon Sep 17 00:00:00 2001
From: kledmundson <6842706+kledmundson@users.noreply.github.com>
Date: Mon, 10 Feb 2025 08:58:49 -0700
Subject: [PATCH] Added adjusted XYZ point coordinate sigmas to the points.csv
 jigsaw output file. Modified (#5711)

ctest FunctionalTestJigsawApollo to validate this output. Addresses #5710.
---
 CHANGELOG.md                                  |   2 +
 .../BundleSolutionInfo/BundleSolutionInfo.cpp | 140 ++++++++++--------
 .../BundleSolutionInfo/BundleSolutionInfo.h   |   2 +
 isis/tests/FunctionalTestsJigsaw.cpp          |  20 +--
 4 files changed, 95 insertions(+), 69 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 040cfc0deb..813750f4ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -36,6 +36,8 @@ release.
 ## [Unreleased]
 
 ### Added
+- Added adjusted XYZ point coordinate sigmas to the points.csv jigsaw output file. Modified
+ctest FunctionalTestJigsawApollo to validate this output. [#5710](https://github.com/DOI-USGS/ISIS3/issues/5710)
 - Added OFFBODY and OFFBODYTRIM parameters to cam2cam. Added tests and updated documentation. [#3602] (https://github.com/DOI-USGS/ISIS3/issues/3602)
 
 ### Changed
diff --git a/isis/src/control/objs/BundleSolutionInfo/BundleSolutionInfo.cpp b/isis/src/control/objs/BundleSolutionInfo/BundleSolutionInfo.cpp
index 512dc2de7b..fddd8a8df0 100755
--- a/isis/src/control/objs/BundleSolutionInfo/BundleSolutionInfo.cpp
+++ b/isis/src/control/objs/BundleSolutionInfo/BundleSolutionInfo.cpp
@@ -1611,32 +1611,47 @@ namespace Isis {
 
     double dLat, dLon, dRadius;
     double dX, dY, dZ;
-    double dSigmaLat, dSigmaLong, dSigmaRadius;
     QString strStatus;
     double cor_lat_dd = 0.0;                      // lat correction, decimal degrees
     double cor_lon_dd = 0.0;                      // lon correction, decimal degrees
     double cor_rad_km = 0.0;                      // radius correction, kilometers
+
     double cor_lat_m = 0.0;                       // lat correction, meters
     double cor_lon_m = 0.0;                       // lon correction, meters
     double cor_rad_m = 0.0;                       // radius correction, meters
+
+    double cor_x_km = 0.0;                        // x correction, kilometers
+    double cor_y_km = 0.0;                        // y correction, kilometers
+    double cor_z_km = 0.0;                        // z correction, kilometers
+
     double latInit = Isis::Null;
     double lonInit = Isis::Null;
     double radInit = Isis::Null;
+
     int numMeasures, numRejectedMeasures;
     double dResidualRms;
 
+    // bundle coordinate type (Lat, Lon, Radius or XYZ)
+    bool bundleLatLonRadius = true;
+    if ( m_settings->controlPointCoordTypeBundle() == SurfacePoint::Rectangular ) {
+      bundleLatLonRadius = false;
+    }
+
     // print column headers
     if (m_settings->errorPropagation()) {
-      snprintf(buf, sizeof(buf), ",,,,,3-d,3-d,3-d,Sigma,Sigma,Sigma,Correction,Correction,Correction,Coordinate,"
-              "Coordinate,Coordinate\nPoint,Point,Accepted,Rejected,Residual,Latitude,Longitude,"
-              "Radius,Latitude,Longitude,Radius,Latitude,Longitude,Radius,X,Y,Z\nLabel,Status,"
-              "Measures,Measures,RMS,(dd),(dd),(km),(m),(m),(m),(m),(m),(m),(km),(km),(km)\n");
+      snprintf(buf, sizeof(buf), ",,,,,3-d,3-d,3-d,Sigma,Sigma,Sigma,Correction,Correction,Correction,"
+              "Coordinate,Coordinate,Coordinate,Sigma,Sigma,Sigma\n"
+              "Point,Point,Accepted,Rejected,Residual,Latitude,Longitude,Radius,Latitude,Longitude,"
+              "Radius,Latitude,Longitude,Radius,X,Y,Z,X,Y,Z\n"
+              "Label,Status,Measures,Measures,RMS,(dd),(dd),(km),(m),(m),(m),(m),(m),(m),(km),(km),(km),"
+              "(m),(m),(m)\n");
     }
     else {
-      snprintf(buf, sizeof(buf), ",,,,,3-d,3-d,3-d,Correction,Correction,Correction,Coordinate,Coordinate,"
-              "Coordinate\nPoint,Point,Accepted,Rejected,Residual,Latitude,Longitude,Radius,"
-              "Latitude,Longitude,Radius,X,Y,Z\nLabel,Status,Measures,Measures,RMS,(dd),(dd),(km),"
-              "(m),(m),(m),(km),(km),(km)\n");
+      snprintf(buf, sizeof(buf), ",,,,,3-d,3-d,3-d,Correction,Correction,Correction,Coordinate,"
+              "Coordinate,Coordinate\n"
+              "Point,Point,Accepted,Rejected,Residual,Latitude,Longitude,Radius,Latitude,Longitude,"
+              "Radius,X,Y,Z\n"
+              "Label,Status,Measures,Measures,RMS,(dd),(dd),(km),(m),(m),(m),(km),(km),(km)\n");
     }
     fpOut << buf;
 
@@ -1651,53 +1666,53 @@ namespace Isis {
         continue;
       }
 
-      dLat              = bundlecontrolpoint->adjustedSurfacePoint().GetLatitude().degrees();
-      dLon              = bundlecontrolpoint->adjustedSurfacePoint().GetLongitude().degrees();
-      dRadius           = bundlecontrolpoint->adjustedSurfacePoint().GetLocalRadius().kilometers();
-      dX                = bundlecontrolpoint->adjustedSurfacePoint().GetX().kilometers();
-      dY                = bundlecontrolpoint->adjustedSurfacePoint().GetY().kilometers();
-      dZ                = bundlecontrolpoint->adjustedSurfacePoint().GetZ().kilometers();
+      dLat                = bundlecontrolpoint->adjustedSurfacePoint().GetLatitude().degrees();
+      dLon                = bundlecontrolpoint->adjustedSurfacePoint().GetLongitude().degrees();
+      dRadius             = bundlecontrolpoint->adjustedSurfacePoint().GetLocalRadius().kilometers();
+      dX                  = bundlecontrolpoint->adjustedSurfacePoint().GetX().kilometers();
+      dY                  = bundlecontrolpoint->adjustedSurfacePoint().GetY().kilometers();
+      dZ                  = bundlecontrolpoint->adjustedSurfacePoint().GetZ().kilometers();
       numMeasures         = bundlecontrolpoint->numberOfMeasures();
       numRejectedMeasures = bundlecontrolpoint->numberOfRejectedMeasures();
-      dResidualRms      = bundlecontrolpoint->residualRms();
+      dResidualRms        = bundlecontrolpoint->residualRms();
 
       // Use the local radius in meters, rad*1000., to convert radians to meters now instead of the
       // target body equatorial radius (DAC 09/17/2018; from BundleControlPoint.cpp)
       double rtm = dRadius * 1000.;
 
-      // point corrections and initial sigmas
+      // point corrections
+      //   Latitude, Longitude corrections are decimal degrees;
+      //   Radius correction is kilometers
       boost::numeric::ublas::bounded_vector< double, 3 > corrections = bundlecontrolpoint->
                                                                             corrections();
 
-      if (m_settings->controlPointCoordTypeBundle() == SurfacePoint::Rectangular) {
-        double xCor = corrections(0);  // km
-        double yCor = corrections(1);  // km
-        double zCor = corrections(2);  // km
+      // solution is rectangular (point coordinates are X,Y,Z)
+      // must compute latitudinal initial coordinates and corrections
+      if (bundleLatLonRadius == false) {
+        cor_x_km = corrections(0);
+        cor_y_km = corrections(1);
+        cor_z_km = corrections(2);
 
         if (!IsSpecial(dX) && !IsSpecial(dY) && !IsSpecial(dZ)) {
-          SurfacePoint rectPoint(Displacement(dX - xCor, Displacement::Kilometers),
-                                Displacement(dY - yCor, Displacement::Kilometers),
-                                Displacement(dZ - zCor, Displacement::Kilometers));
+          SurfacePoint rectInitPoint(Displacement(dX - cor_x_km, Displacement::Kilometers),
+                                     Displacement(dY - cor_y_km, Displacement::Kilometers),
+                                     Displacement(dZ - cor_z_km, Displacement::Kilometers));
           
-          latInit = rectPoint.GetLatitude().degrees();
-          lonInit = rectPoint.GetLongitude().degrees();
-          radInit = rectPoint.GetLocalRadius().kilometers();
+          latInit = rectInitPoint.GetLatitude().degrees();
+          lonInit = rectInitPoint.GetLongitude().degrees();
+          radInit = rectInitPoint.GetLocalRadius().kilometers();
           
-          if (!IsSpecial(dLat)) {
-            cor_lat_dd = (dLat - latInit); // degrees
-            cor_lat_m  =  cor_lat_dd * DEG2RAD * rtm;
-          }
-          if (!IsSpecial(dLon)) {
-            cor_lon_dd = (dLon - lonInit); // degrees
-            cor_lon_m  =  cor_lon_dd * DEG2RAD * rtm * cos(dLat*DEG2RAD);  // lon corrections meters
-          }
-          if (!IsSpecial(dRadius)) {
-            cor_rad_km  =  dRadius - radInit;
-            cor_rad_m  =  cor_rad_km * 1000.;
-          }
+          cor_lat_dd = (dLat - latInit); // degrees
+          cor_lon_dd = (dLon - lonInit); // degrees
+          cor_rad_km  =  dRadius - radInit;
+
+          cor_lat_m  =  cor_lat_dd * DEG2RAD * rtm;
+          cor_lon_m  =  cor_lon_dd * DEG2RAD * rtm * cos(dLat*DEG2RAD);  // lon corrections meters
+          cor_rad_m  =  cor_rad_km * 1000.;
         }
       }
-      else if (m_settings->controlPointCoordTypeBundle() == SurfacePoint::Latitudinal) {
+      // solution is latitudinal (point coordinates are Latitude, Longitude, Radius)
+      else if (bundleLatLonRadius == true) {
         cor_lat_dd = corrections(0) * RAD2DEG;   // lat correction, decimal degs
         cor_lon_dd = corrections(1) * RAD2DEG;   // lon correction, decimal degs
         cor_rad_m  = corrections(2) * 1000.0;    // radius correction, meters
@@ -1706,16 +1721,14 @@ namespace Isis {
         cor_lon_m = bundlecontrolpoint->adjustedSurfacePoint().LongitudeToMeters(corrections(1));
         cor_rad_km = corrections(2);
 
-        if (!IsSpecial(dLat)) {
-          latInit = dLat - cor_lat_dd;
-        }
-
-        if (!IsSpecial(dLon)) {
-          lonInit = dLon - cor_lon_dd;
-        }
+        if (!IsSpecial(dLat) && !IsSpecial(dLon) && !IsSpecial(dRadius)) {
+          SurfacePoint latInitPoint(Latitude(dLat - cor_lat_dd, Angle::Degrees),
+                                    Longitude(dLon - cor_lon_dd, Angle::Degrees),
+                                    Distance(dRadius - corrections(2), Distance::Kilometers));
 
-        if (!IsSpecial(dRadius)) {
-          radInit = dRadius - corrections(2); // km
+          latInit = latInitPoint.GetLatitude().degrees();
+          lonInit = latInitPoint.GetLongitude().degrees();
+          radInit = latInitPoint.GetLocalRadius().kilometers();
         }
       }
 
@@ -1733,22 +1746,31 @@ namespace Isis {
       }
 
       if (m_settings->errorPropagation()) {
+        double dSigmaLat, dSigmaLong, dSigmaRadius;
+        double dSigmaX, dSigmaY, dSigmaZ;
+        
         dSigmaLat = bundlecontrolpoint->adjustedSurfacePoint().GetLatSigmaDistance().meters();
         dSigmaLong = bundlecontrolpoint->adjustedSurfacePoint().GetLonSigmaDistance().meters();
         dSigmaRadius = bundlecontrolpoint->adjustedSurfacePoint().GetLocalRadiusSigma().meters();
 
-        snprintf(buf, sizeof(buf), "%s,%s,%d,%d,%6.2lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,"
-                     "%16.8lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf\n",
-                bundlecontrolpoint->id().toLatin1().data(), strStatus.toLatin1().data(),
-                numMeasures, numRejectedMeasures, dResidualRms, dLat, dLon, dRadius, dSigmaLat,
-                dSigmaLong, dSigmaRadius, cor_lat_m, cor_lon_m, cor_rad_m, dX, dY, dZ);
+        dSigmaX = bundlecontrolpoint->adjustedSurfacePoint().GetXSigma().meters();
+        dSigmaY = bundlecontrolpoint->adjustedSurfacePoint().GetYSigma().meters();
+        dSigmaZ = bundlecontrolpoint->adjustedSurfacePoint().GetZSigma().meters();
+
+        snprintf(buf, sizeof(buf), "%s,%s,%d,%d,%6.2lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,"
+                                   "%16.8lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,"
+                                   "%16.8lf\n",
+                 bundlecontrolpoint->id().toLatin1().data(), strStatus.toLatin1().data(),
+                 numMeasures, numRejectedMeasures, dResidualRms, dLat, dLon, dRadius, dSigmaLat,
+                 dSigmaLong, dSigmaRadius, cor_lat_m, cor_lon_m, cor_rad_m, dX, dY, dZ,
+                 dSigmaX,dSigmaY,dSigmaZ);
       }
       else
-        snprintf(buf, sizeof(buf), "%s,%s,%d,%d,%6.2lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,"
-                     "%16.8lf,%16.8lf\n",
-                bundlecontrolpoint->id().toLatin1().data(), strStatus.toLatin1().data(),
-                numMeasures, numRejectedMeasures, dResidualRms, dLat, dLon, dRadius, cor_lat_m,
-                cor_lon_m, cor_rad_m, dX, dY, dZ);
+        snprintf(buf, sizeof(buf), "%s,%s,%d,%d,%6.2lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,%16.8lf,"
+                                   "%16.8lf,%16.8lf,%16.8lf\n",
+                 bundlecontrolpoint->id().toLatin1().data(), strStatus.toLatin1().data(),
+                 numMeasures, numRejectedMeasures, dResidualRms, dLat, dLon, dRadius, cor_lat_m,
+                 cor_lon_m, cor_rad_m, dX, dY, dZ);
 
       fpOut << buf;
     }
diff --git a/isis/src/control/objs/BundleSolutionInfo/BundleSolutionInfo.h b/isis/src/control/objs/BundleSolutionInfo/BundleSolutionInfo.h
index 170efe0311..c1e6be6c1c 100755
--- a/isis/src/control/objs/BundleSolutionInfo/BundleSolutionInfo.h
+++ b/isis/src/control/objs/BundleSolutionInfo/BundleSolutionInfo.h
@@ -168,6 +168,8 @@ namespace Isis {
    *                          bundle adjustment, the Lat/Lon/Radius point corrections written
    *                          to the points.csv file are incorrect and do not match those written
    *                          to the bundleout.txt file.
+   *   @history 2025-01-19 Ken Edmundson - Added three columns to the end of each line in the
+   *                          points.csv file for the adjusted XYZ point sigmas.
    */
   class BundleSolutionInfo : public QObject {
     Q_OBJECT
diff --git a/isis/tests/FunctionalTestsJigsaw.cpp b/isis/tests/FunctionalTestsJigsaw.cpp
index 9ec8ba322c..50008ebe62 100644
--- a/isis/tests/FunctionalTestsJigsaw.cpp
+++ b/isis/tests/FunctionalTestsJigsaw.cpp
@@ -69,18 +69,18 @@ TEST_F(ApolloNetwork, FunctionalTestJigsawApollo) {
   int numColumns = line.columns();
   int numRows = line.rows();
 
-  ASSERT_EQ(numColumns, 12);
+  ASSERT_EQ(numColumns, 15);
   ASSERT_EQ(numRows, 398);
 
   // Validate the line information is correct
   csvLine = line.getRow(0);
-  compareCsvLine(csvLine, "3-d,3-d,3-d,Sigma,Sigma,Sigma,Correction,Correction,Correction,Coordinate,Coordinate,Coordinate");
+  compareCsvLine(csvLine, "3-d,3-d,3-d,Sigma,Sigma,Sigma,Correction,Correction,Correction,Coordinate,Coordinate,Coordinate,Sigma,Sigma,Sigma");
 
   csvLine = line.getRow(1);
-  compareCsvLine(csvLine, "Point,Point,Accepted,Rejected,Residual,Latitude,Longitude,Radius,Latitude,Longitude,Radius,Latitude,Longitude,Radius,X,Y,Z");
+  compareCsvLine(csvLine, "Point,Point,Accepted,Rejected,Residual,Latitude,Longitude,Radius,Latitude,Longitude,Radius,Latitude,Longitude,Radius,X,Y,Z,X,Y,Z");
 
   csvLine = line.getRow(2);
-  compareCsvLine(csvLine, "Label,Status,Measures,Measures,RMS,(dd),(dd),(km),(m),(m),(m),(m),(m),(m),(km),(km),(km)");
+  compareCsvLine(csvLine, "Label,Status,Measures,Measures,RMS,(dd),(dd),(km),(m),(m),(m),(m),(m),(m),(km),(km),(km),(m),(m),(m)");
 
   // Compare all of the values from the network against the values in the CSV
   QList<ControlPoint*> points = outputNet.GetPoints();
@@ -115,14 +115,14 @@ TEST_F(ApolloNetwork, FunctionalTestJigsawApollo) {
 
   // Spot check a few points for hard-coded values
 // A few "Free" points:
-  compareCsvLine(line.getRow(30), "AS15_000031957,FREE,3,0,0.33,24.25013429,6.15097050,1735.93990543,270.68671676,265.71819251,500.96944842,860.25781493,-1823.63228489,-677.74533463,1573.65050943,169.59077243,712.98695596");
-  compareCsvLine(line.getRow(185), "AS15_000055107,FREE,2,0,2.22,24.26598395,6.75841991,1735.27498710,303.08885516,295.63588060,562.91712019,876.14367347,-1869.62276530,-708.50440630,1570.96622187,186.17020493,713.15150243");
-  compareCsvLine(line.getRow(396), "AS15_Tie14,FREE,4,0,0.76,23.34007344,4.52764905,1737.15233677,245.96412227,251.30260955,443.11518635,1022.08062058,-1897.32816475,-372.27330000,1590.02287614,125.90958874,688.23852695");
+  compareCsvLine(line.getRow(30), "AS15_000031957,FREE,3,0,0.33,24.25013429,6.15097050,1735.93990543,270.68671676,265.71819251,500.96944842,860.25781493,-1823.63228489,-677.74533463,1573.65050943,169.59077243,712.98695596, 465.66755434, 267.66330908, 326.12562996");
+  compareCsvLine(line.getRow(185), "AS15_000055107,FREE,2,0,2.22,24.26598395,6.75841991,1735.27498710,303.08885516,295.63588060,562.91712019,876.14367347,-1869.62276530,-708.50440630,1570.96622187,186.17020493,713.15150243, 522.79515846, 298.42172842, 365.74418181");
+  compareCsvLine(line.getRow(396), "AS15_Tie14,FREE,4,0,0.76,23.34007344,4.52764905,1737.15233677,245.96412227,251.30260955,443.11518635,1022.08062058,-1897.32816475,-372.27330000,1590.02287614,125.90958874,688.23852695, 414.98905206, 251.82613853, 290.46531976");
 
   // A few "Constrained" points:
-  compareCsvLine(line.getRow(352), "AS15_SocetPAN_01,CONSTRAINED,3,0,0.27,27.61487917,2.18951566,1735.78407271,160.95596643,162.33483092,285.90375398,103.62041173,223.18286146,306.44770820,1536.92627520,58.76110229,804.58132250", 2);
-  compareCsvLine(line.getRow(360), "AS15_SocetPAN_10,CONSTRAINED,4,0,1.14,25.96587004,3.54262524,1735.72172102,113.85794037,113.34020561,189.03901900,-54.11385318,174.35206231,4.97102217,1557.52735013,96.42556502,759.96089165", 2);
-  compareCsvLine(line.getRow(380), "AS15_SocetPAN_40,CONSTRAINED,2,0,0.42,25.77498986,1.88090884,1735.56132012,133.81392881,132.83513465,230.53187100,23.85721304,82.06385794,171.57011514,1562.04594097,51.29735448,754.68811810", 2);
+  compareCsvLine(line.getRow(352), "AS15_SocetPAN_01,CONSTRAINED,3,0,0.27,27.61487917,2.18951566,1735.78407271,160.95596643,162.33483092,285.90375398,103.62041173,223.18286146,306.44770820,1536.92627520,58.76110229,804.58132250, 265.9686138, 162.53199952, 191.94901028", 2);
+  compareCsvLine(line.getRow(360), "AS15_SocetPAN_10,CONSTRAINED,4,0,1.14,25.96587004,3.54262524,1735.72172102,113.85794037,113.34020561,189.03901900,-54.11385318,174.35206231,4.97102217,1557.52735013,96.42556502,759.96089165, 176.89344204, 113.71419, 131.61753927", 2);
+  compareCsvLine(line.getRow(380), "AS15_SocetPAN_40,CONSTRAINED,2,0,0.42,25.77498986,1.88090884,1735.56132012,133.81392881,132.83513465,230.53187100,23.85721304,82.06385794,171.57011514,1562.04594097,51.29735448,754.68811810, 214.5688998, 133.00415652, 158.00748601", 2);
 
   // Check for the correct line output format and csv file structure for the images.csv file
   line = CSVReader(imagesOutput,
-- 
GitLab