From 53f4568271bff3a2ed8bd6a69213979a1e0d83d3 Mon Sep 17 00:00:00 2001
From: Luc Maisonobe <luc@orekit.org>
Date: Wed, 26 Mar 2014 12:21:30 +0100
Subject: [PATCH] Greatly improved accuracy of direct localization.

There were some small errors (at centimeter level) as result points may
be slightly out of line of sight. These errors seemed to be due to the
final linear line-of-sight model that is used at pixel level.

An iterative correction step is performed to ensure the point is really
on the line-of-sight.

The residual error is now at nanometer level.
---
 .../rugged/core/BasicScanAlgorithm.java       |   9 +
 .../core/duvenhage/DuvenhageAlgorithm.java    |  11 +-
 .../rugged/core/AbstractAlgorithmTest.java    | 274 ++++++++++++++++++
 .../rugged/core/BasicScanAlgorithmTest.java   | 201 +------------
 .../duvenhage/DuvenhageAlgorithmTest.java     | 206 +------------
 5 files changed, 302 insertions(+), 399 deletions(-)
 create mode 100644 rugged-core/src/test/java/org/orekit/rugged/core/AbstractAlgorithmTest.java

diff --git a/rugged-core/src/main/java/org/orekit/rugged/core/BasicScanAlgorithm.java b/rugged-core/src/main/java/org/orekit/rugged/core/BasicScanAlgorithm.java
index 8836cbe0..5b9af1b1 100644
--- a/rugged-core/src/main/java/org/orekit/rugged/core/BasicScanAlgorithm.java
+++ b/rugged-core/src/main/java/org/orekit/rugged/core/BasicScanAlgorithm.java
@@ -120,12 +120,21 @@ public class BasicScanAlgorithm implements IntersectionAlgorithm {
                     for (int j = longitudeIndex(tile, minLongitude); j <= longitudeIndex(tile, maxLongitude); ++j) {
                         GeodeticPoint gp = tile.pixelIntersection(entryPoint, ellipsoid.convertLos(entryPoint, los), i, j);
                         if (gp != null) {
+
+                            // improve the point, by projecting it back on the 3D line, fixing the small body curvature at pixel level
+                            final Vector3D      delta     = ellipsoid.transform(gp).subtract(position);
+                            final double        s         = Vector3D.dotProduct(delta, los) / los.getNormSq();
+                            final GeodeticPoint projected = ellipsoid.transform(new Vector3D(1, position, s, los),
+                                                                                ellipsoid.getBodyFrame(), null);
+                            gp = tile.pixelIntersection(projected, ellipsoid.convertLos(projected, los), i, j);
+
                             final Vector3D point = ellipsoid.transform(gp);
                             final double dot = Vector3D.dotProduct(point.subtract(position), los);
                             if (dot < intersectionDot) {
                                 intersectionGP  = gp;
                                 intersectionDot = dot;
                             }
+
                         }
                     }                    
                 }
diff --git a/rugged-core/src/main/java/org/orekit/rugged/core/duvenhage/DuvenhageAlgorithm.java b/rugged-core/src/main/java/org/orekit/rugged/core/duvenhage/DuvenhageAlgorithm.java
index 2f8aea9a..12e3d106 100644
--- a/rugged-core/src/main/java/org/orekit/rugged/core/duvenhage/DuvenhageAlgorithm.java
+++ b/rugged-core/src/main/java/org/orekit/rugged/core/duvenhage/DuvenhageAlgorithm.java
@@ -159,7 +159,16 @@ public class DuvenhageAlgorithm implements IntersectionAlgorithm {
 
         if (entryLat == exitLat && entryLon == exitLon) {
             // we have narrowed the search down to a single Digital Elevation Model pixel
-            return tile.pixelIntersection(entry, ellipsoid.convertLos(entry, los), exitLat, exitLon);
+            GeodeticPoint intersection = tile.pixelIntersection(entry, ellipsoid.convertLos(entry, los), exitLat, exitLon);
+            if (intersection != null) {
+                // improve the point, by projecting it back on the 3D line, fixing the small body curvature at pixel level
+                final Vector3D      delta     = ellipsoid.transform(intersection).subtract(position);
+                final double        s         = Vector3D.dotProduct(delta, los) / los.getNormSq();
+                final GeodeticPoint projected = ellipsoid.transform(new Vector3D(1, position, s, los),
+                                                                    ellipsoid.getBodyFrame(), null);
+                intersection = tile.pixelIntersection(projected, ellipsoid.convertLos(projected, los), exitLat, exitLon);
+            }
+            return intersection;
         }
 
         // find the deepest level in the min/max kd-tree at which entry and exit share a sub-tile
diff --git a/rugged-core/src/test/java/org/orekit/rugged/core/AbstractAlgorithmTest.java b/rugged-core/src/test/java/org/orekit/rugged/core/AbstractAlgorithmTest.java
new file mode 100644
index 00000000..204260fa
--- /dev/null
+++ b/rugged-core/src/test/java/org/orekit/rugged/core/AbstractAlgorithmTest.java
@@ -0,0 +1,274 @@
+/* Copyright 2002-2014 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.rugged.core;
+
+
+import java.io.File;
+import java.net.URISyntaxException;
+
+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.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.orekit.attitudes.Attitude;
+import org.orekit.bodies.GeodeticPoint;
+import org.orekit.data.DataProvidersManager;
+import org.orekit.data.DirectoryCrawler;
+import org.orekit.errors.OrekitException;
+import org.orekit.frames.FramesFactory;
+import org.orekit.frames.Transform;
+import org.orekit.orbits.CartesianOrbit;
+import org.orekit.propagation.SpacecraftState;
+import org.orekit.rugged.api.RuggedException;
+import org.orekit.rugged.api.TileUpdater;
+import org.orekit.rugged.core.duvenhage.MinMaxTreeTile;
+import org.orekit.rugged.core.duvenhage.MinMaxTreeTileFactory;
+import org.orekit.rugged.core.raster.CliffsElevationUpdater;
+import org.orekit.rugged.core.raster.IntersectionAlgorithm;
+import org.orekit.rugged.core.raster.VolcanicConeElevationUpdater;
+import org.orekit.time.AbsoluteDate;
+import org.orekit.time.TimeScalesFactory;
+import org.orekit.utils.Constants;
+import org.orekit.utils.IERSConventions;
+import org.orekit.utils.PVCoordinates;
+
+public abstract class AbstractAlgorithmTest {
+
+    protected abstract IntersectionAlgorithm createAlgorithm();
+
+    @Test
+    public void testMayonVolcanoOnSubTileCorner()
+        throws RuggedException, OrekitException {
+
+        setUpMayonVolcanoContext();
+
+        // test point approximately 1.6km North-North-West and 800 meters below volcano summit
+        // note that this test point is EXACTLY at a pixel corner, and even at corners of
+        // middle level (12 and above) sub-tiles
+        final double latitude  = FastMath.toRadians(13.27);
+        final double longitude = FastMath.toRadians(123.68);
+        MinMaxTreeTile tile = new MinMaxTreeTileFactory().createTile();
+        updater.updateTile(latitude, longitude, tile);
+        double altitude = tile.interpolateElevation(latitude, longitude);
+        final GeodeticPoint groundGP = new GeodeticPoint(latitude, longitude, altitude);
+        Vector3D groundP = earth.transform(groundGP);
+
+        final IntersectionAlgorithm algorithm = createAlgorithm();
+        algorithm.setUpTilesManagement(updater, 8);
+
+        // preliminary check: the point has been chosen in the spacecraft (YZ) plane
+        Transform earthToSpacecraft = new Transform(state.getDate(),
+                                                    earth.getBodyFrame().getTransformTo(state.getFrame(), state.getDate()),
+                                                    state.toTransform());
+        Vector3D pointInSpacecraftFrame = earthToSpacecraft.transformPosition(groundP);
+        Assert.assertEquals(     0.000, pointInSpacecraftFrame.getX(), 1.0e-3);
+        Assert.assertEquals(-87754.914, pointInSpacecraftFrame.getY(), 1.0e-3);
+        Assert.assertEquals(790330.254, pointInSpacecraftFrame.getZ(), 1.0e-3);
+
+        // test direct localization
+        Vector3D      position = state.getPVCoordinates(earth.getBodyFrame()).getPosition();
+        Vector3D      los      = groundP.subtract(position);
+        GeodeticPoint result   = algorithm.intersection(earth, position, los);
+        Assert.assertEquals(0.0, groundP.distance(earth.transform(result)), 2.0e-9);
+
+    }
+
+    @Test
+    public void testMayonVolcanoWithinPixel()
+        throws RuggedException, OrekitException {
+
+        setUpMayonVolcanoContext();
+
+        final double latitude  = FastMath.toRadians(13.2696);
+        final double longitude = FastMath.toRadians(123.6803);
+        MinMaxTreeTile tile = new MinMaxTreeTileFactory().createTile();
+        updater.updateTile(latitude, longitude, tile);
+        double altitude = tile.interpolateElevation(latitude, longitude);
+        final GeodeticPoint groundGP = new GeodeticPoint(latitude, longitude, altitude);
+        Vector3D groundP = earth.transform(groundGP);
+
+        final IntersectionAlgorithm algorithm = createAlgorithm();
+        algorithm.setUpTilesManagement(updater, 8);
+
+        // test direct localization
+        Vector3D      position = state.getPVCoordinates(earth.getBodyFrame()).getPosition();
+        Vector3D      los      = groundP.subtract(position);
+        GeodeticPoint result   = algorithm.intersection(earth, position, los);
+        Assert.assertEquals(0.0, groundP.distance(earth.transform(result)), 2.0e-9);
+
+    }
+
+    @Test
+    public void testCliffsOfMoher()
+        throws RuggedException, OrekitException {
+
+        setUpCliffsOfMoherContext();
+
+        // test point on top the cliffs, roughly 15m East of edge (inland)
+        final double latitude  = FastMath.toRadians(52.98045);
+        final double longitude = FastMath.toRadians(-9.421826199814143);
+        MinMaxTreeTile tile = new MinMaxTreeTileFactory().createTile();
+        updater.updateTile(latitude, longitude, tile);
+        double altitude = tile.interpolateElevation(latitude, longitude);
+        final GeodeticPoint groundGP = new GeodeticPoint(latitude, longitude, altitude);
+        Vector3D groundP = earth.transform(groundGP);
+
+        final IntersectionAlgorithm algorithm = createAlgorithm();
+        algorithm.setUpTilesManagement(updater, 8);
+
+        // preliminary check: the point has been chosen in the spacecraft (YZ) plane
+        Transform earthToSpacecraft = new Transform(state.getDate(),
+                                                    earth.getBodyFrame().getTransformTo(state.getFrame(), state.getDate()),
+                                                    state.toTransform());
+        Vector3D pointInSpacecraftFrame = earthToSpacecraft.transformPosition(groundP);
+        Assert.assertEquals(     0.000, pointInSpacecraftFrame.getX(), 1.0e-3);
+        Assert.assertEquals( 66702.419, pointInSpacecraftFrame.getY(), 1.0e-3);
+        Assert.assertEquals(796873.178, pointInSpacecraftFrame.getZ(), 1.0e-3);
+
+        Vector3D      position = state.getPVCoordinates(earth.getBodyFrame()).getPosition();
+        Vector3D      los      = groundP.subtract(position);
+        GeodeticPoint result   = algorithm.intersection(earth, position, los);
+        Assert.assertEquals(0.0, groundP.distance(earth.transform(result)), 2.0e-9);
+
+    }
+
+    private void setUpMayonVolcanoContext()
+        throws RuggedException, OrekitException {
+
+        // Mayon Volcano location according to Wikipedia: 13°15′24″N 123°41′6″E
+        GeodeticPoint summit =
+                new GeodeticPoint(FastMath.toRadians(13.25667), FastMath.toRadians(123.685), 2463.0);
+        updater = new VolcanicConeElevationUpdater(summit,
+                                                   FastMath.toRadians(30.0), 16.0,
+                                                   FastMath.toRadians(1.0), 1201);
+
+        // some orbital parameters have been computed using Orekit
+        // tutorial about phasing, using the following configuration:
+        //
+        //  orbit.date                          = 2012-01-01T00:00:00.000
+        //  phasing.orbits.number               = 143
+        //  phasing.days.number                 =  10
+        //  sun.synchronous.reference.latitude  = 0
+        //  sun.synchronous.reference.ascending = false
+        //  sun.synchronous.mean.solar.time     = 10:30:00
+        //  gravity.field.degree                = 12
+        //  gravity.field.order                 = 12
+        //
+        // the resulting phased orbit has then been propagated to a date corresponding
+        // to test point lying in the spacecraft (YZ) plane (with nadir pointing and yaw compensation)
+        AbsoluteDate crossing = new AbsoluteDate("2012-01-06T02:27:15.942757185", TimeScalesFactory.getUTC());
+        state = new SpacecraftState(new CartesianOrbit(new PVCoordinates(new Vector3D( -649500.423763743,
+                                                                                       -6943715.537565755,
+                                                                                       1657929.137063380),
+                                                                         new Vector3D(-1305.453711368668,
+                                                                                      -1600.627551928136,
+                                                                                      -7167.286855869801)),
+                                                       FramesFactory.getEME2000(),
+                                                       crossing,
+                                                       Constants.EIGEN5C_EARTH_MU),
+                                                       new Attitude(crossing,
+                                                                    FramesFactory.getEME2000(),
+                                                                    new Rotation(-0.40904880353552850,
+                                                                                  0.46125295378582530,
+                                                                                 -0.63525007056319790,
+                                                                                 -0.46516893361386025,
+                                                                                 true),
+                                                                    new Vector3D(-7.048568391860185e-05,
+                                                                                 -1.043582650222194e-03,
+                                                                                  1.700400341147713e-05)));
+
+    }
+
+    private void setUpCliffsOfMoherContext()
+        throws RuggedException, OrekitException {
+
+        // cliffs of Moher location according to Wikipedia: 52°56′10″N 9°28′15″ W
+        GeodeticPoint north = new GeodeticPoint(FastMath.toRadians(52.9984),
+                                                FastMath.toRadians(-9.4072),
+                                                120.0);
+        GeodeticPoint south = new GeodeticPoint(FastMath.toRadians(52.9625),
+                                                FastMath.toRadians(-9.4369),
+                                                120.0);
+
+        // pixels are about 10m x 10m here and a tile covers 1km x 1km
+        updater = new CliffsElevationUpdater(north, south,
+                                             120.0, 0.0,
+                                             FastMath.toRadians(0.015), 101);
+
+        // some orbital parameters have been computed using Orekit
+        // tutorial about phasing, using the following configuration:
+        //
+        //  orbit.date                          = 2012-01-01T00:00:00.000
+        //  phasing.orbits.number               = 143
+        //  phasing.days.number                 =  10
+        //  sun.synchronous.reference.latitude  = 0
+        //  sun.synchronous.reference.ascending = false
+        //  sun.synchronous.mean.solar.time     = 10:30:00
+        //  gravity.field.degree                = 12
+        //  gravity.field.order                 = 12
+        //
+        // the resulting phased orbit has then been propagated to a date corresponding
+        // to test point lying in the spacecraft (YZ) plane (with nadir pointing and yaw compensation)
+        AbsoluteDate crossing = new AbsoluteDate("2012-01-07T11:50:04.935272115", TimeScalesFactory.getUTC());
+        state = new SpacecraftState(new CartesianOrbit(new PVCoordinates(new Vector3D(  412324.544397459,
+                                                                                      -4325872.329311633,
+                                                                                       5692124.593989491),
+                                                                         new Vector3D(-1293.174701214779,
+                                                                                      -5900.764863603793,
+                                                                                      -4378.671036383179)),
+                                                       FramesFactory.getEME2000(),
+                                                       crossing,
+                                                       Constants.EIGEN5C_EARTH_MU),
+                                                       new Attitude(crossing,
+                                                                    FramesFactory.getEME2000(),
+                                                                    new Rotation(-0.17806699079182878,
+                                                                                  0.60143347387211290,
+                                                                                 -0.73251248177468900,
+                                                                                 -0.26456641385623986,
+                                                                                 true),
+                                                                    new Vector3D(-4.289600857433520e-05,
+                                                                                 -1.039151496480297e-03,
+                                                                                  5.811423736843181e-05)));
+
+    }
+
+    @Before
+    public void setUp()
+            throws OrekitException, URISyntaxException {
+        String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
+        DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
+        earth = new ExtendedEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
+                                      Constants.WGS84_EARTH_FLATTENING,
+                                      FramesFactory.getITRF(IERSConventions.IERS_2010, true));
+
+    }
+
+    @After
+    public void tearDown() {
+        earth     = null;
+        updater   = null;
+        state     = null;
+    }
+
+    private ExtendedEllipsoid earth;
+    private TileUpdater       updater;
+    private SpacecraftState   state;
+
+}
diff --git a/rugged-core/src/test/java/org/orekit/rugged/core/BasicScanAlgorithmTest.java b/rugged-core/src/test/java/org/orekit/rugged/core/BasicScanAlgorithmTest.java
index dbf7e3f3..4181b840 100644
--- a/rugged-core/src/test/java/org/orekit/rugged/core/BasicScanAlgorithmTest.java
+++ b/rugged-core/src/test/java/org/orekit/rugged/core/BasicScanAlgorithmTest.java
@@ -17,205 +17,12 @@
 package org.orekit.rugged.core;
 
 
-import java.io.File;
-import java.net.URISyntaxException;
+import org.orekit.rugged.core.raster.IntersectionAlgorithm;
 
-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.junit.Assert;
-import org.junit.Test;
-import org.orekit.attitudes.Attitude;
-import org.orekit.bodies.GeodeticPoint;
-import org.orekit.data.DataProvidersManager;
-import org.orekit.data.DirectoryCrawler;
-import org.orekit.errors.OrekitException;
-import org.orekit.frames.FramesFactory;
-import org.orekit.frames.Transform;
-import org.orekit.orbits.CartesianOrbit;
-import org.orekit.propagation.SpacecraftState;
-import org.orekit.rugged.api.RuggedException;
-import org.orekit.rugged.api.TileUpdater;
-import org.orekit.rugged.core.ExtendedEllipsoid;
-import org.orekit.rugged.core.raster.CliffsElevationUpdater;
-import org.orekit.rugged.core.raster.SimpleTile;
-import org.orekit.rugged.core.raster.SimpleTileFactory;
-import org.orekit.rugged.core.raster.VolcanicConeElevationUpdater;
-import org.orekit.time.AbsoluteDate;
-import org.orekit.time.TimeScalesFactory;
-import org.orekit.utils.Constants;
-import org.orekit.utils.IERSConventions;
-import org.orekit.utils.PVCoordinates;
-
-public class BasicScanAlgorithmTest {
-
-    @Test
-    public void testMayonVolcano()
-        throws RuggedException, OrekitException, URISyntaxException {
-
-        String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
-        DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
-        ExtendedEllipsoid earth = new ExtendedEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
-                                                        Constants.WGS84_EARTH_FLATTENING,
-                                                        FramesFactory.getITRF(IERSConventions.IERS_2010, true));
-
-        // Mayon Volcano location according to Wikipedia: 13°15′24″N 123°41′6″E
-        GeodeticPoint summit =
-                new GeodeticPoint(FastMath.toRadians(13.25667), FastMath.toRadians(123.685), 2463.0);
-        TileUpdater updater = new VolcanicConeElevationUpdater(summit,
-                                                               FastMath.toRadians(30.0), 16.0,
-                                                               FastMath.toRadians(1.0), 1201);
-
-        // test point approximately 1.6km North-North-West and 800 meters below volcano summit
-        double latitude  = FastMath.toRadians(13.27);
-        double longitude = FastMath.toRadians(123.68);
-        SimpleTile tile = new SimpleTileFactory().createTile();
-        updater.updateTile(latitude, longitude, tile);
-        double altitude = tile.interpolateElevation(latitude, longitude);
-        GeodeticPoint groundGP = new GeodeticPoint(latitude, longitude, altitude);
-        Vector3D groundP       = earth.transform(groundGP);
-
-        BasicScanAlgorithm basicScan = new BasicScanAlgorithm();
-        basicScan.setUpTilesManagement(updater, 8);
-
-        // some orbital parameters have been computed using Orekit
-        // tutorial about phasing, using the following configuration:
-        //
-        //  orbit.date                          = 2012-01-01T00:00:00.000
-        //  phasing.orbits.number               = 143
-        //  phasing.days.number                 =  10
-        //  sun.synchronous.reference.latitude  = 0
-        //  sun.synchronous.reference.ascending = false
-        //  sun.synchronous.mean.solar.time     = 10:30:00
-        //  gravity.field.degree                = 12
-        //  gravity.field.order                 = 12
-        //
-        // the resulting phased orbit has then been propagated to a date corresponding
-        // to test point lying in the spacecraft (YZ) plane (with nadir pointing and yaw compensation)
-        AbsoluteDate crossing = new AbsoluteDate("2012-01-06T02:27:15.942757185", TimeScalesFactory.getUTC());
-        SpacecraftState state =
-                new SpacecraftState(new CartesianOrbit(new PVCoordinates(new Vector3D( -649500.423763743,
-                                                                                       -6943715.537565755,
-                                                                                       1657929.137063380),
-                                                                         new Vector3D(-1305.453711368668,
-                                                                                      -1600.627551928136,
-                                                                                      -7167.286855869801)),
-                                                       FramesFactory.getEME2000(),
-                                                       crossing,
-                                                       Constants.EIGEN5C_EARTH_MU),
-                                                       new Attitude(crossing,
-                                                                    FramesFactory.getEME2000(),
-                                                                    new Rotation(-0.40904880353552850,
-                                                                                  0.46125295378582530,
-                                                                                 -0.63525007056319790,
-                                                                                 -0.46516893361386025,
-                                                                                 true),
-                                                                    new Vector3D(-7.048568391860185e-05,
-                                                                                 -1.043582650222194e-03,
-                                                                                  1.700400341147713e-05)));
-
-        // preliminary check: the point has been chosen in the spacecraft (YZ) plane
-        Transform earthToSpacecraft = new Transform(state.getDate(),
-                                                    earth.getBodyFrame().getTransformTo(state.getFrame(), state.getDate()),
-                                                    state.toTransform());
-        Vector3D pointInSpacecraftFrame = earthToSpacecraft.transformPosition(groundP);
-        Assert.assertEquals(     0.000, pointInSpacecraftFrame.getX(), 1.0e-3);
-        Assert.assertEquals(-87754.914, pointInSpacecraftFrame.getY(), 1.0e-3);
-        Assert.assertEquals(790330.254, pointInSpacecraftFrame.getZ(), 1.0e-3);
-
-        Vector3D      position = state.getPVCoordinates(earth.getBodyFrame()).getPosition();
-        Vector3D      los      = groundP.subtract(position);
-        GeodeticPoint result   = basicScan.intersection(earth, position, los);
-        Assert.assertEquals(0.0, groundP.distance(earth.transform(result)), 0.03);
-
-    }
-
-    @Test
-    public void testCliffsOfMoher()
-        throws RuggedException, OrekitException, URISyntaxException {
-
-        String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
-        DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
-        ExtendedEllipsoid earth = new ExtendedEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
-                                                        Constants.WGS84_EARTH_FLATTENING,
-                                                        FramesFactory.getITRF(IERSConventions.IERS_2010, true));
-
-        // cliffs of Moher location according to Wikipedia: 52°56′10″N 9°28′15″ W
-        GeodeticPoint north = new GeodeticPoint(FastMath.toRadians(52.9984),
-                                                FastMath.toRadians(-9.4072),
-                                                120.0);
-        GeodeticPoint south = new GeodeticPoint(FastMath.toRadians(52.9625),
-                                                FastMath.toRadians(-9.4369),
-                                                120.0);
-
-        // pixels are about 10m x 10m here and a tile covers 1km x 1km
-        TileUpdater updater = new CliffsElevationUpdater(north, south,
-                                                         120.0, 0.0,
-                                                         FastMath.toRadians(0.015), 101);
-
-        // test point on top the cliffs, roughly 15m East of edge (inland)
-        double latitude  = 0.5 * (north.getLatitude()  + south.getLatitude());
-        double longitude = 0.5 * (north.getLongitude() + south.getLongitude()) +
-                          15.0 / (Constants.WGS84_EARTH_EQUATORIAL_RADIUS * FastMath.cos(latitude));
-        SimpleTile tile = new SimpleTileFactory().createTile();
-        updater.updateTile(latitude, longitude, tile);
-        double altitude = tile.interpolateElevation(latitude, longitude);
-        GeodeticPoint groundGP = new GeodeticPoint(latitude, longitude, altitude);
-        Vector3D groundP       = earth.transform(groundGP);
-
-        BasicScanAlgorithm basicScan = new BasicScanAlgorithm();
-        basicScan.setUpTilesManagement(updater, 8);
-
-        // some orbital parameters have been computed using Orekit
-        // tutorial about phasing, using the following configuration:
-        //
-        //  orbit.date                          = 2012-01-01T00:00:00.000
-        //  phasing.orbits.number               = 143
-        //  phasing.days.number                 =  10
-        //  sun.synchronous.reference.latitude  = 0
-        //  sun.synchronous.reference.ascending = false
-        //  sun.synchronous.mean.solar.time     = 10:30:00
-        //  gravity.field.degree                = 12
-        //  gravity.field.order                 = 12
-        //
-        // the resulting phased orbit has then been propagated to a date corresponding
-        // to test point lying in the spacecraft (YZ) plane (with nadir pointing and yaw compensation)
-        AbsoluteDate crossing = new AbsoluteDate("2012-01-07T11:50:04.935272115", TimeScalesFactory.getUTC());
-        SpacecraftState state =
-                new SpacecraftState(new CartesianOrbit(new PVCoordinates(new Vector3D(  412324.544397459,
-                                                                                      -4325872.329311633,
-                                                                                       5692124.593989491),
-                                                                         new Vector3D(-1293.174701214779,
-                                                                                      -5900.764863603793,
-                                                                                      -4378.671036383179)),
-                                                       FramesFactory.getEME2000(),
-                                                       crossing,
-                                                       Constants.EIGEN5C_EARTH_MU),
-                                                       new Attitude(crossing,
-                                                                    FramesFactory.getEME2000(),
-                                                                    new Rotation(-0.17806699079182878,
-                                                                                  0.60143347387211290,
-                                                                                 -0.73251248177468900,
-                                                                                 -0.26456641385623986,
-                                                                                 true),
-                                                                    new Vector3D(-4.289600857433520e-05,
-                                                                                 -1.039151496480297e-03,
-                                                                                  5.811423736843181e-05)));
-
-        // preliminary check: the point has been chosen in the spacecraft (YZ) plane
-        Transform earthToSpacecraft = new Transform(state.getDate(),
-                                                    earth.getBodyFrame().getTransformTo(state.getFrame(), state.getDate()),
-                                                    state.toTransform());
-        Vector3D pointInSpacecraftFrame = earthToSpacecraft.transformPosition(groundP);
-        Assert.assertEquals(     0.000, pointInSpacecraftFrame.getX(), 1.0e-3);
-        Assert.assertEquals( 66702.419, pointInSpacecraftFrame.getY(), 1.0e-3);
-        Assert.assertEquals(796873.178, pointInSpacecraftFrame.getZ(), 1.0e-3);
-
-        Vector3D      position = state.getPVCoordinates(earth.getBodyFrame()).getPosition();
-        Vector3D      los      = groundP.subtract(position);
-        GeodeticPoint result   = basicScan.intersection(earth, position, los);
-        Assert.assertEquals(0.0, groundP.distance(earth.transform(result)), 1.0e-10);
+public class BasicScanAlgorithmTest extends AbstractAlgorithmTest {
 
+    public IntersectionAlgorithm createAlgorithm() {
+        return new BasicScanAlgorithm();
     }
 
 }
diff --git a/rugged-core/src/test/java/org/orekit/rugged/core/duvenhage/DuvenhageAlgorithmTest.java b/rugged-core/src/test/java/org/orekit/rugged/core/duvenhage/DuvenhageAlgorithmTest.java
index eb9b75cd..535bc08a 100644
--- a/rugged-core/src/test/java/org/orekit/rugged/core/duvenhage/DuvenhageAlgorithmTest.java
+++ b/rugged-core/src/test/java/org/orekit/rugged/core/duvenhage/DuvenhageAlgorithmTest.java
@@ -17,209 +17,13 @@
 package org.orekit.rugged.core.duvenhage;
 
 
-import java.io.File;
-import java.net.URISyntaxException;
+import org.orekit.rugged.core.AbstractAlgorithmTest;
+import org.orekit.rugged.core.raster.IntersectionAlgorithm;
 
-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.junit.Assert;
-import org.junit.Test;
-import org.orekit.attitudes.Attitude;
-import org.orekit.bodies.GeodeticPoint;
-import org.orekit.data.DataProvidersManager;
-import org.orekit.data.DirectoryCrawler;
-import org.orekit.errors.OrekitException;
-import org.orekit.frames.FramesFactory;
-import org.orekit.frames.Transform;
-import org.orekit.orbits.CartesianOrbit;
-import org.orekit.propagation.SpacecraftState;
-import org.orekit.rugged.api.RuggedException;
-import org.orekit.rugged.api.TileUpdater;
-import org.orekit.rugged.core.ExtendedEllipsoid;
-import org.orekit.rugged.core.raster.CliffsElevationUpdater;
-import org.orekit.rugged.core.raster.VolcanicConeElevationUpdater;
-import org.orekit.time.AbsoluteDate;
-import org.orekit.time.TimeScalesFactory;
-import org.orekit.utils.Constants;
-import org.orekit.utils.IERSConventions;
-import org.orekit.utils.PVCoordinates;
-
-public class DuvenhageAlgorithmTest {
-
-    @Test
-    public void testMayonVolcano()
-        throws RuggedException, OrekitException, URISyntaxException {
-
-        String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
-        DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
-        ExtendedEllipsoid earth = new ExtendedEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
-                                                        Constants.WGS84_EARTH_FLATTENING,
-                                                        FramesFactory.getITRF(IERSConventions.IERS_2010, true));
-
-        // Mayon Volcano location according to Wikipedia: 13°15′24″N 123°41′6″E
-        GeodeticPoint summit =
-                new GeodeticPoint(FastMath.toRadians(13.25667), FastMath.toRadians(123.685), 2463.0);
-        TileUpdater updater = new VolcanicConeElevationUpdater(summit,
-                                                               FastMath.toRadians(30.0), 16.0,
-                                                               FastMath.toRadians(1.0), 1201);
-
-        // test point approximately 1.6km North-North-West and 800 meters below volcano summit
-        // note that this test point is EXACTLY at a pixel corner, and even at corners of
-        // middle level (12 and above) sub-tiles
-        double latitude  = FastMath.toRadians(13.27);
-        double longitude = FastMath.toRadians(123.68);
-        MinMaxTreeTile tile = new MinMaxTreeTileFactory().createTile();
-        updater.updateTile(latitude, longitude, tile);
-        double altitude = tile.interpolateElevation(latitude, longitude);
-        GeodeticPoint groundGP = new GeodeticPoint(latitude, longitude, altitude);
-        Vector3D groundP       = earth.transform(groundGP);
-
-        DuvenhageAlgorithm duvenhage = new DuvenhageAlgorithm();
-        duvenhage.setUpTilesManagement(updater, 8);
-
-        // some orbital parameters have been computed using Orekit
-        // tutorial about phasing, using the following configuration:
-        //
-        //  orbit.date                          = 2012-01-01T00:00:00.000
-        //  phasing.orbits.number               = 143
-        //  phasing.days.number                 =  10
-        //  sun.synchronous.reference.latitude  = 0
-        //  sun.synchronous.reference.ascending = false
-        //  sun.synchronous.mean.solar.time     = 10:30:00
-        //  gravity.field.degree                = 12
-        //  gravity.field.order                 = 12
-        //
-        // the resulting phased orbit has then been propagated to a date corresponding
-        // to test point lying in the spacecraft (YZ) plane (with nadir pointing and yaw compensation)
-        AbsoluteDate crossing = new AbsoluteDate("2012-01-06T02:27:15.942757185", TimeScalesFactory.getUTC());
-        SpacecraftState state =
-                new SpacecraftState(new CartesianOrbit(new PVCoordinates(new Vector3D( -649500.423763743,
-                                                                                       -6943715.537565755,
-                                                                                       1657929.137063380),
-                                                                         new Vector3D(-1305.453711368668,
-                                                                                      -1600.627551928136,
-                                                                                      -7167.286855869801)),
-                                                       FramesFactory.getEME2000(),
-                                                       crossing,
-                                                       Constants.EIGEN5C_EARTH_MU),
-                                                       new Attitude(crossing,
-                                                                    FramesFactory.getEME2000(),
-                                                                    new Rotation(-0.40904880353552850,
-                                                                                  0.46125295378582530,
-                                                                                 -0.63525007056319790,
-                                                                                 -0.46516893361386025,
-                                                                                 true),
-                                                                    new Vector3D(-7.048568391860185e-05,
-                                                                                 -1.043582650222194e-03,
-                                                                                  1.700400341147713e-05)));
-
-        // preliminary check: the point has been chosen in the spacecraft (YZ) plane
-        Transform earthToSpacecraft = new Transform(state.getDate(),
-                                                    earth.getBodyFrame().getTransformTo(state.getFrame(), state.getDate()),
-                                                    state.toTransform());
-        Vector3D pointInSpacecraftFrame = earthToSpacecraft.transformPosition(groundP);
-        Assert.assertEquals(     0.000, pointInSpacecraftFrame.getX(), 1.0e-3);
-        Assert.assertEquals(-87754.914, pointInSpacecraftFrame.getY(), 1.0e-3);
-        Assert.assertEquals(790330.254, pointInSpacecraftFrame.getZ(), 1.0e-3);
-
-        Vector3D      position = state.getPVCoordinates(earth.getBodyFrame()).getPosition();
-        Vector3D      los      = groundP.subtract(position);
-        GeodeticPoint result   = duvenhage.intersection(earth, position, los);
-        Assert.assertEquals(groundGP.getLatitude(),  result.getLatitude(),  1.0e-10);
-        Assert.assertEquals(groundGP.getLongitude(), result.getLongitude(), 1.0e-10);
-        Assert.assertEquals(groundGP.getAltitude(),  result.getAltitude(),  1.0e-9);
-
-    }
-
-    @Test
-    public void testCliffsOfMoher()
-        throws RuggedException, OrekitException, URISyntaxException {
-
-        String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
-        DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
-        ExtendedEllipsoid earth = new ExtendedEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
-                                                        Constants.WGS84_EARTH_FLATTENING,
-                                                        FramesFactory.getITRF(IERSConventions.IERS_2010, true));
-
-        // cliffs of Moher location according to Wikipedia: 52°56′10″N 9°28′15″ W
-        GeodeticPoint north = new GeodeticPoint(FastMath.toRadians(52.9984),
-                                                FastMath.toRadians(-9.4072),
-                                                120.0);
-        GeodeticPoint south = new GeodeticPoint(FastMath.toRadians(52.9625),
-                                                FastMath.toRadians(-9.4369),
-                                                120.0);
-
-        // pixels are about 10m x 10m here and a tile covers 1km x 1km
-        TileUpdater updater = new CliffsElevationUpdater(north, south,
-                                                         120.0, 0.0,
-                                                         FastMath.toRadians(0.015), 101);
-
-        // test point on top the cliffs, roughly 15m East of edge (inland)
-        double latitude  = 0.5 * (north.getLatitude()  + south.getLatitude());
-        double longitude = 0.5 * (north.getLongitude() + south.getLongitude()) +
-                          15.0 / (Constants.WGS84_EARTH_EQUATORIAL_RADIUS * FastMath.cos(latitude));
-        MinMaxTreeTile tile = new MinMaxTreeTileFactory().createTile();
-        updater.updateTile(latitude, longitude, tile);
-        double altitude = tile.interpolateElevation(latitude, longitude);
-        GeodeticPoint groundGP = new GeodeticPoint(latitude, longitude, altitude);
-        Vector3D groundP       = earth.transform(groundGP);
-
-        DuvenhageAlgorithm duvenhage = new DuvenhageAlgorithm();
-        duvenhage.setUpTilesManagement(updater, 8);
-
-        // some orbital parameters have been computed using Orekit
-        // tutorial about phasing, using the following configuration:
-        //
-        //  orbit.date                          = 2012-01-01T00:00:00.000
-        //  phasing.orbits.number               = 143
-        //  phasing.days.number                 =  10
-        //  sun.synchronous.reference.latitude  = 0
-        //  sun.synchronous.reference.ascending = false
-        //  sun.synchronous.mean.solar.time     = 10:30:00
-        //  gravity.field.degree                = 12
-        //  gravity.field.order                 = 12
-        //
-        // the resulting phased orbit has then been propagated to a date corresponding
-        // to test point lying in the spacecraft (YZ) plane (with nadir pointing and yaw compensation)
-        AbsoluteDate crossing = new AbsoluteDate("2012-01-07T11:50:04.935272115", TimeScalesFactory.getUTC());
-        SpacecraftState state =
-                new SpacecraftState(new CartesianOrbit(new PVCoordinates(new Vector3D(  412324.544397459,
-                                                                                      -4325872.329311633,
-                                                                                       5692124.593989491),
-                                                                         new Vector3D(-1293.174701214779,
-                                                                                      -5900.764863603793,
-                                                                                      -4378.671036383179)),
-                                                       FramesFactory.getEME2000(),
-                                                       crossing,
-                                                       Constants.EIGEN5C_EARTH_MU),
-                                                       new Attitude(crossing,
-                                                                    FramesFactory.getEME2000(),
-                                                                    new Rotation(-0.17806699079182878,
-                                                                                  0.60143347387211290,
-                                                                                 -0.73251248177468900,
-                                                                                 -0.26456641385623986,
-                                                                                 true),
-                                                                    new Vector3D(-4.289600857433520e-05,
-                                                                                 -1.039151496480297e-03,
-                                                                                  5.811423736843181e-05)));
-
-        // preliminary check: the point has been chosen in the spacecraft (YZ) plane
-        Transform earthToSpacecraft = new Transform(state.getDate(),
-                                                    earth.getBodyFrame().getTransformTo(state.getFrame(), state.getDate()),
-                                                    state.toTransform());
-        Vector3D pointInSpacecraftFrame = earthToSpacecraft.transformPosition(groundP);
-        Assert.assertEquals(     0.000, pointInSpacecraftFrame.getX(), 1.0e-3);
-        Assert.assertEquals( 66702.419, pointInSpacecraftFrame.getY(), 1.0e-3);
-        Assert.assertEquals(796873.178, pointInSpacecraftFrame.getZ(), 1.0e-3);
-
-        Vector3D      position = state.getPVCoordinates(earth.getBodyFrame()).getPosition();
-        Vector3D      los      = groundP.subtract(position);
-        GeodeticPoint result   = duvenhage.intersection(earth, position, los);
-        Assert.assertEquals(groundGP.getLatitude(),  result.getLatitude(),  1.0e-10);
-        Assert.assertEquals(groundGP.getLongitude(), result.getLongitude(), 1.0e-10);
-        Assert.assertEquals(groundGP.getAltitude(),  result.getAltitude(),  1.0e-9);
+public class DuvenhageAlgorithmTest extends AbstractAlgorithmTest {
 
+    protected IntersectionAlgorithm createAlgorithm() {
+        return new DuvenhageAlgorithm();
     }
 
 }
-- 
GitLab