From 2ea9c1d2052e76c16ae07b5461a3c330f887db42 Mon Sep 17 00:00:00 2001
From: Luc Maisonobe <luc@orekit.org>
Date: Tue, 6 May 2014 16:11:38 +0200
Subject: [PATCH] First working version of inverse localization.

---
 .../java/org/orekit/rugged/api/Rugged.java    | 137 ++++++++++++++----
 .../org/orekit/rugged/api/RuggedTest.java     |  32 ++--
 2 files changed, 126 insertions(+), 43 deletions(-)

diff --git a/src/main/java/org/orekit/rugged/api/Rugged.java b/src/main/java/org/orekit/rugged/api/Rugged.java
index 3995ee58..7c07c450 100644
--- a/src/main/java/org/orekit/rugged/api/Rugged.java
+++ b/src/main/java/org/orekit/rugged/api/Rugged.java
@@ -30,6 +30,7 @@ import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
 import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
 import org.apache.commons.math3.util.FastMath;
 import org.apache.commons.math3.util.Pair;
+import org.apache.commons.math3.util.Precision;
 import org.orekit.attitudes.Attitude;
 import org.orekit.attitudes.AttitudeProvider;
 import org.orekit.attitudes.TabulatedProvider;
@@ -539,17 +540,17 @@ public class Rugged {
                     final Vector3D  eP2      = ellipsoid.transform(gp1);
                     final double    deltaT2  = eP2.distance(sP) / Constants.SPEED_OF_LIGHT;
                     final Transform shifted2 = inertToBody.shiftedBy(-deltaT2);
-                    gp[i] = alg.refineIntersection(ellipsoid,
-                                                   shifted2.transformPosition(pInert),
-                                                   shifted2.transformVector(lInert),
-                                                   gp1);
+                    gp[i - start] = alg.refineIntersection(ellipsoid,
+                                                           shifted2.transformPosition(pInert),
+                                                           shifted2.transformVector(lInert),
+                                                           gp1);
 
                 } else {
                     // compute DEM intersection without light time correction
                     final Vector3D pBody = inertToBody.transformPosition(pInert);
                     final Vector3D lBody = inertToBody.transformVector(lInert);
-                    gp[i] = alg.refineIntersection(ellipsoid, pBody, lBody,
-                                                   alg.intersection(ellipsoid, pBody, lBody));
+                    gp[i - start] = alg.refineIntersection(ellipsoid, pBody, lBody,
+                                                           alg.intersection(ellipsoid, pBody, lBody));
                 }
 
             }
@@ -592,12 +593,10 @@ public class Rugged {
 
             // compute a quadrilateral that should surround the target
             final double lInf = FastMath.floor(coarseLine);
-            final double lSup = lInf + 1;
             final int    pInf = FastMath.max(0, FastMath.min(sensor.getNbPixels() - 2, (int) FastMath.floor(coarsePixel)));
-            final int    pSup = pInf + 1;
             final IntersectionAlgorithm alg = new FixedAltitudeAlgorithm(groundPoint.getAltitude());
-            final GeodeticPoint[] previous  = directLocalization(sensor, pInf, pSup, alg, lInf);
-            final GeodeticPoint[] next      = directLocalization(sensor, pInf, pSup, alg, lSup);
+            final GeodeticPoint[] previous  = directLocalization(sensor, pInf, pInf + 2, alg, lInf);
+            final GeodeticPoint[] next      = directLocalization(sensor, pInf, pInf + 2, alg, lInf + 1);
 
             final double[] interp = interpolationCoordinates(groundPoint.getLongitude(), groundPoint.getLatitude(),
                                                              previous[0].getLongitude(), previous[0].getLatitude(),
@@ -605,7 +604,7 @@ public class Rugged {
                                                              next[0].getLongitude(),     next[0].getLatitude(),
                                                              next[1].getLongitude(),     next[1].getLatitude());
 
-            return new SensorPixel(lInf + interp[1], pInf + interp[0]);
+            return (interp == null) ? null : new SensorPixel(lInf + interp[1], pInf + interp[0]);
 
         } catch (NoBracketingException nbe) {
             // there are no solutions in the search interval
@@ -621,29 +620,103 @@ public class Rugged {
     /** Compute a point bilinear interpolation coordinates.
      * <p>
      * This method is used to find interpolation coordinates when the
-     * quadrilateral is <em>not</em> an exact rectangle.
+     * quadrilateral is <em>not</em> a perfect rectangle.
      * </p>
-     * @param x point abscissa
-     * @param y point ordinate
-     * @param xA lower left abscissa
-     * @param yA lower left ordinate
-     * @param xB lower right abscissa
-     * @param yB lower right ordinate
-     * @param xC upper left abscissa
-     * @param yC upper left ordinate
-     * @param xD upper right abscissa
-     * @param yD upper right ordinate
-     * @return interpolation coordinates corresponding to point {@code x}, {@code y}
+     * @param xuv desired point abscissa, at interpolation coordinates u, v
+     * @param yuv desired point ordinate, at interpolation coordinates u, v
+     * @param x00 abscissa for base quadrilateral point at u = 0, v = 0
+     * @param y00 ordinate for base quadrilateral point at u = 0, v = 0
+     * @param x10 abscissa for base quadrilateral point at u = 1, v = 0
+     * @param y10 ordinate for base quadrilateral point at u = 1, v = 0
+     * @param x01 abscissa for base quadrilateral point at u = 0, v = 1
+     * @param y01 ordinate for base quadrilateral point at u = 0, v = 1
+     * @param x11 abscissa for base quadrilateral point at u = 1, v = 1
+     * @param y11 ordinate for base quadrilateral point at u = 1, v = 1
+     * @return interpolation coordinates {@code u, v} corresponding to point {@code xuv, yuv},
+     * or null if no such points can be found
      */
-    private double[] interpolationCoordinates(final double x,  final double y,
-                                              final double xA, final double yA,
-                                              final double xB, final double yB,
-                                              final double xC, final double yC,
-                                              final double xD, final double yD) {
-        // TODO: compute coordinates
-        return new double[] {
-            Double.NaN, Double.NaN
-        };
+    private double[] interpolationCoordinates(final double xuv, final double yuv,
+                                              final double x00, final double y00,
+                                              final double x10, final double y10,
+                                              final double x01, final double y01,
+                                              final double x11, final double y11) {
+
+        // bilinear interpolation can be written as follows:
+        // P(0,v) = v P(0,1) + (1 - v) P(0,0)
+        // P(1,v) = v P(1,1) + (1 - v) P(1,0)
+        // P(u,v) = u P(1,v) + (1 - u) P(0,v)
+        // which can be solved for u:
+        // u = [P(u,v) - P(0,v)] / [P(1,v) - P(0,v)]
+        // this equation holds for both x and y coordinates of the various P points, so u can be eliminated:
+        // [x(u,v) - x(0,v)] * [y(1,v) - y(0,v)] - [y(u,v) - y(0,v)] * [x(1,v) - x(0,v)] = 0
+        // this is a quadratic equation in v: a v² + bv + c = 0
+        final double a = y11 * x00 - y10 * x00 + y10 * x01 - y11 * x01 + y01 * x11 - y01 * x10 - y00 * x11 + y00 * x10;
+        final double b = y11 * xuv - y10 * xuv + y00 * xuv - y01 * xuv - y11 * x00 + 2 * y10 * x00 - y10 * x01 + y01 * x10 + y00 * x11 - 2 * y00 * x10 + yuv * x01 + yuv * x10 - yuv * x11 - yuv * x00;
+        final double c = y10 * xuv - y00 * xuv - y10 * x00 + y00 * x10 - yuv * x10 + yuv * x00;
+
+        if (FastMath.abs(a) < Precision.EPSILON * (FastMath.abs(b) + FastMath.abs(c))) {
+            if (FastMath.abs(b) < Precision.EPSILON * FastMath.abs(c)) {
+                return null;
+            } else {
+
+                // solve linear equation in v
+                final double v = -c / b;
+
+                // recover uA from vA
+                final double x0v = v * x01 + (1 - v) * x00;
+                final double x1v = v * x11 + (1 - v) * x10;
+                final double dX  = x1v - x0v;
+                final double y0v = v * y01 + (1 - v) * y00;
+                final double y1v = v * y11 + (1 - v) * y10;
+                final double dY  = y1v - y0v;
+                final double u   = (FastMath.abs(dX) >= FastMath.abs(dX)) ? ((xuv - x0v) / dX) : ((yuv - y0v) / dY);
+
+                return new double[] {
+                    u, v
+                };
+
+            }
+        } else {
+            // solve quadratic equation in v
+            final double bb  = b * b;
+            final double fac = 4 * a * c;
+            if (bb < fac) {
+                return null;
+            }
+            final double s  = FastMath.sqrt(bb - fac);
+            final double vA = (b > 0) ? -(s + b) / (2 * a) : (2 * c) / (s - b);
+            final double vB = c / (a * vA);
+
+            // recover uA from vA
+            final double x0vA = vA * x01 + (1 - vA) * x00;
+            final double x1vA = vA * x11 + (1 - vA) * x10;
+            final double dXA  = x1vA - x0vA;
+            final double y0vA = vA * y01 + (1 - vA) * y00;
+            final double y1vA = vA * y11 + (1 - vA) * y10;
+            final double dYA  = y1vA - y0vA;
+            final double uA   = (FastMath.abs(dXA) >= FastMath.abs(dXA)) ? ((xuv - x0vA) / dXA) : ((yuv - y0vA) / dYA);
+
+            // recover uB from vB
+            final double x0vB = vB * x01 + (1 - vB) * x00;
+            final double x1vB = vB * x11 + (1 - vB) * x10;
+            final double dXB  = x1vB - x0vB;
+            final double y0vB = vB * y01 + (1 - vB) * y00;
+            final double y1vB = vB * y11 + (1 - vB) * y10;
+            final double dYB  = y1vB - y0vB;
+            final double uB   = (FastMath.abs(dXB) >= FastMath.abs(dXB)) ? ((xuv - x0vB) / dXB) : ((yuv - y0vB) / dYB);
+
+            if ((uA * uA + vA * vA) <= (uB * uB + vB * vB)) {
+                return new double[] {
+                    uA, vA
+                };
+            } else {
+                return new double[] {
+                    uA, vA
+                };
+            }
+
+        }
+
     }
 
     /** Local class for finding when ground point crosses mean sensor plane. */
diff --git a/src/test/java/org/orekit/rugged/api/RuggedTest.java b/src/test/java/org/orekit/rugged/api/RuggedTest.java
index 6770f2df..242e9930 100644
--- a/src/test/java/org/orekit/rugged/api/RuggedTest.java
+++ b/src/test/java/org/orekit/rugged/api/RuggedTest.java
@@ -427,8 +427,15 @@ public class RuggedTest {
     @Test
     public void testInverseLocalization()
         throws RuggedException, OrekitException, URISyntaxException {
+        checkInverseLocalization(2000, false, false, 4.0e-5, 4.0e-5);
+        checkInverseLocalization(2000, false, true,  4.0e-5, 4.0e-5);
+        checkInverseLocalization(2000, true,  false, 4.0e-5, 4.0e-5);
+        checkInverseLocalization(2000, true,  true,  4.0e-5, 4.0e-5);
+    }
 
-        int dimension = 3;
+    private void checkInverseLocalization(int dimension, boolean lightTimeCorrection, boolean aberrationOfLightCorrection,
+                                          double maxLineError, double maxPixelError)
+        throws RuggedException, OrekitException, URISyntaxException {
 
         String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
         DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
@@ -459,21 +466,24 @@ public class RuggedTest {
                                    EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
                                    orbitToPV(orbit, earth, lineDatation, firstLine, lastLine, 0.25), 8,
                                    orbitToQ(orbit, earth, lineDatation, firstLine, lastLine, 0.25), 2);
-        rugged.setLightTimeCorrection(false);
-        rugged.setAberrationOfLightCorrection(true);
+        rugged.setLightTimeCorrection(lightTimeCorrection);
+        rugged.setAberrationOfLightCorrection(aberrationOfLightCorrection);
         rugged.setLineSensor(lineSensor);
 
-        double referenceLine = dimension / 2;
+        double referenceLine = 0.56789 * dimension;
         GeodeticPoint[] gp = rugged.directLocalization("line", referenceLine);
 
-        for (int i = 1; i < gp.length - 1; ++i) {
-            SensorPixel sp = rugged.inverseLocalization("line", gp[i], 0, dimension);
-            System.out.println(i + " " + (referenceLine - sp.getLineNumber()) + " " + (i - sp.getPixelNumber()));
-//            Assert.assertEquals(referenceLine, sp.getLineNumber(),  5.0e-6);
-//            Assert.assertEquals(i,             sp.getPixelNumber(), 1.0e-7);
+        for (double p = 1; p < gp.length - 1; p += 0.2) {
+            int    i = (int) FastMath.floor(p);
+            double d = p - i;
+            GeodeticPoint g = new GeodeticPoint((1 - d) * gp[i].getLatitude()  + d * gp[i + 1].getLatitude(),
+                                                (1 - d) * gp[i].getLongitude() + d * gp[i + 1].getLongitude(),
+                                                (1 - d) * gp[i].getAltitude()  + d * gp[i + 1].getAltitude());
+            SensorPixel sp = rugged.inverseLocalization("line", g, 0, dimension);
+            Assert.assertEquals(referenceLine, sp.getLineNumber(),  maxLineError);
+            Assert.assertEquals(p,             sp.getPixelNumber(), maxPixelError);
         }
-
-    }
+     }
 
     private BodyShape createEarth()
        throws OrekitException {
-- 
GitLab