From 17f9f4aa767d9d1c6653c705975141d656489e98 Mon Sep 17 00:00:00 2001
From: Luc Maisonobe <luc@orekit.org>
Date: Tue, 3 Feb 2015 10:57:27 +0100
Subject: [PATCH] Work In Progress on a debug dump feature.

This is an attempt at solving issue #188.
---
 .../org/orekit/rugged/errors/DumpManager.java |  93 ++++
 .../orekit/rugged/errors/RuggedMessages.java  |   5 +-
 .../org/orekit/rugged/raster/SimpleTile.java  |   8 +
 .../orekit/rugged/raster/SimpleTile.java.orig | 421 ++++++++++++++++++
 .../org/orekit/rugged/RuggedMessages_de.utf8  |   9 +
 .../org/orekit/rugged/RuggedMessages_en.utf8  |   9 +
 .../org/orekit/rugged/RuggedMessages_es.utf8  |   9 +
 .../org/orekit/rugged/RuggedMessages_fr.utf8  |   9 +
 .../org/orekit/rugged/RuggedMessages_gl.utf8  |   9 +
 .../org/orekit/rugged/RuggedMessages_it.utf8  |   9 +
 .../org/orekit/rugged/RuggedMessages_no.utf8  |   9 +
 .../org/orekit/rugged/RuggedMessages_ro.utf8  |   9 +
 .../orekit/rugged/errors/DumpManagerTest.java | 133 ++++++
 .../rugged/errors/RuggedMessagesTest.java     |   2 +-
 14 files changed, 732 insertions(+), 2 deletions(-)
 create mode 100644 src/main/java/org/orekit/rugged/errors/DumpManager.java
 create mode 100644 src/main/java/org/orekit/rugged/raster/SimpleTile.java.orig
 create mode 100644 src/test/java/org/orekit/rugged/errors/DumpManagerTest.java

diff --git a/src/main/java/org/orekit/rugged/errors/DumpManager.java b/src/main/java/org/orekit/rugged/errors/DumpManager.java
new file mode 100644
index 00000000..6ce1aa71
--- /dev/null
+++ b/src/main/java/org/orekit/rugged/errors/DumpManager.java
@@ -0,0 +1,93 @@
+/* Copyright 2013-2015 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.errors;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Locale;
+
+/**
+ * Class managing debug dumps.
+ * <p>
+ * <em>WARNING</em>: this class is public only for technical reasons,
+ * it is not considered to belong to the public API of the library and should
+ * not be called by user code. It is only intended to be called internally by
+ * the Rugged library itself. This class may be changed or even removed at any
+ * time, so user code should not rely on it.
+ * </p>
+ * @author Luc Maisonobe
+ */
+public class DumpManager {
+
+    /** Dump file (default initial value is null, i.e. nothing is dumped). */
+    private static final ThreadLocal<PrintWriter> DUMP = new ThreadLocal<PrintWriter>();
+
+    /** Private constructor for utility class.
+     */
+    private DumpManager() {
+        // by default, nothing is dumped
+    }
+
+    /** Activate debug dump.
+     * @param file dump file
+     * @exception RuggedException if debug dump is already active for this thread
+     * or if debug file cannot be opened
+     */
+    public static void activate(final File file) throws RuggedException {
+        if (isActive()) {
+            throw new RuggedException(RuggedMessages.DEBUG_DUMP_ALREADY_ACTIVE);
+        } else {
+            try {
+                DUMP.set(new PrintWriter(file));
+            } catch (IOException ioe) {
+                throw new RuggedException(ioe, RuggedMessages.DEBUG_DUMP_ACTIVATION_ERROR,
+                                          file.getAbsolutePath(), ioe.getLocalizedMessage());
+            }
+        }
+    }
+
+    /** Deactivate debug dump.
+     * @exception RuggedException if debug dump is already active for this thread
+     */
+    public static void deactivate() throws RuggedException {
+        if (isActive()) {
+            DUMP.get().close();
+            DUMP.set(null);
+        } else {
+            throw new RuggedException(RuggedMessages.DEBUG_DUMP_ALREADY_ACTIVE);
+        }
+    }
+
+    /** Check if dump is active for this thread.
+     * @return true if dump is active for this thread
+     */
+    public static boolean isActive() {
+        return DUMP.get() != null;
+    }
+
+    /** Dump some context data.
+     * @param msgPattern message pattern
+     * @param args message arguments
+     */
+    public static void dump(final String msgPattern, final Object... args) {
+        if (isActive()) {
+            DUMP.get().format(Locale.US, msgPattern, args);
+        }
+    }
+
+}
diff --git a/src/main/java/org/orekit/rugged/errors/RuggedMessages.java b/src/main/java/org/orekit/rugged/errors/RuggedMessages.java
index dd42ee25..d4fed0a1 100644
--- a/src/main/java/org/orekit/rugged/errors/RuggedMessages.java
+++ b/src/main/java/org/orekit/rugged/errors/RuggedMessages.java
@@ -65,7 +65,10 @@ public enum RuggedMessages implements Localizable {
     DEM_ENTRY_POINT_IS_BEHIND_SPACECRAFT("line-of-sight enters the Digital Elevation Model behind spacecraft!"),
     FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP("frame {0} does not match frame {1} from interpolator dump"),
     NOT_INTERPOLATOR_DUMP_DATA("data is not an interpolator dump"),
-    ESTIMATED_PARAMETERS_NUMBER_MISMATCH("number of estimated parameters mismatch, expected {0} got {1}");
+    ESTIMATED_PARAMETERS_NUMBER_MISMATCH("number of estimated parameters mismatch, expected {0} got {1}"),
+    DEBUG_DUMP_ALREADY_ACTIVE("debug dump is already active for this thread"),
+    DEBUG_DUMP_ACTIVATION_ERROR("unable to active debug dump with file {0}: {1}"),
+    DEBUG_DUMP_NOT_ACTIVE("debug dump is not active for this thread");
 
     // CHECKSTYLE: resume JavadocVariable check
 
diff --git a/src/main/java/org/orekit/rugged/raster/SimpleTile.java b/src/main/java/org/orekit/rugged/raster/SimpleTile.java
index d438ee2e..a6643041 100644
--- a/src/main/java/org/orekit/rugged/raster/SimpleTile.java
+++ b/src/main/java/org/orekit/rugged/raster/SimpleTile.java
@@ -20,6 +20,7 @@ import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
 import org.apache.commons.math3.util.FastMath;
 import org.apache.commons.math3.util.Precision;
 import org.orekit.bodies.GeodeticPoint;
+import org.orekit.rugged.errors.DumpManager;
 import org.orekit.rugged.errors.RuggedException;
 import org.orekit.rugged.errors.RuggedMessages;
 import org.orekit.rugged.utils.NormalizedGeodeticPoint;
@@ -239,6 +240,13 @@ public class SimpleTile implements Tile {
         final double e10  = getElevationAtIndices(latitudeIndex,     longitudeIndex + 1);
         final double e01  = getElevationAtIndices(latitudeIndex + 1, longitudeIndex);
         final double e11  = getElevationAtIndices(latitudeIndex + 1, longitudeIndex + 1);
+
+        DumpManager.dump("DEM cell: latMin = %22.15e latStep = %22.15e lonMin = %22.15e lonStep = %22.15e latIndex = %d lonIndex = %d e00 = %22.15e e10 = %22.15e e01 = %22.15e e11 = %22.15e%n",
+                         getMinimumLatitude(), getLatitudeStep(),
+                         getMinimumLongitude(), getLongitudeStep(),
+                         latitudeIndex, longitudeIndex,
+                         e00, e10, e01, e11);
+
         return (e00 * (1.0 - dLon) + dLon * e10) * (1.0 - dLat) +
                (e01 * (1.0 - dLon) + dLon * e11) * dLat;
 
diff --git a/src/main/java/org/orekit/rugged/raster/SimpleTile.java.orig b/src/main/java/org/orekit/rugged/raster/SimpleTile.java.orig
new file mode 100644
index 00000000..d438ee2e
--- /dev/null
+++ b/src/main/java/org/orekit/rugged/raster/SimpleTile.java.orig
@@ -0,0 +1,421 @@
+/* Copyright 2013-2015 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.raster;
+
+import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+import org.orekit.bodies.GeodeticPoint;
+import org.orekit.rugged.errors.RuggedException;
+import org.orekit.rugged.errors.RuggedMessages;
+import org.orekit.rugged.utils.NormalizedGeodeticPoint;
+
+
+/** Simple implementation of a {@link Tile}.
+ * @see SimpleTileFactory
+ * @author Luc Maisonobe
+ */
+public class SimpleTile implements Tile {
+
+    /** Tolerance used to interpolate points slightly out of tile (in cells). */
+    private static final double TOLERANCE = 1.0 / 8.0;
+
+    /** Minimum latitude. */
+    private double minLatitude;
+
+    /** Minimum longitude. */
+    private double minLongitude;
+
+    /** Step in latitude (size of one raster element). */
+    private double latitudeStep;
+
+    /** Step in longitude (size of one raster element). */
+    private double longitudeStep;
+
+    /** Number of latitude rows. */
+    private int latitudeRows;
+
+    /** Number of longitude columns. */
+    private int longitudeColumns;
+
+    /** Minimum elevation. */
+    private double minElevation;
+
+    /** Maximum elevation. */
+    private double maxElevation;
+
+    /** Elevation array. */
+    private double[] elevations;
+
+    /** Simple constructor.
+     * <p>
+     * Creates an empty tile.
+     * </p>
+     */
+    protected SimpleTile() {
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setGeometry(final double newMinLatitude, final double newMinLongitude,
+                            final double newLatitudeStep, final double newLongitudeStep,
+                            final int newLatitudeRows, final int newLongitudeColumns)
+        throws RuggedException {
+        this.minLatitude      = newMinLatitude;
+        this.minLongitude     = newMinLongitude;
+        this.latitudeStep     = newLatitudeStep;
+        this.longitudeStep    = newLongitudeStep;
+        this.latitudeRows     = newLatitudeRows;
+        this.longitudeColumns = newLongitudeColumns;
+        this.minElevation     = Double.POSITIVE_INFINITY;
+        this.maxElevation     = Double.NEGATIVE_INFINITY;
+
+        if (newLatitudeRows < 1 || newLongitudeColumns < 1) {
+            throw new RuggedException(RuggedMessages.EMPTY_TILE, newLatitudeRows, newLongitudeColumns);
+        }
+        this.elevations = new double[newLatitudeRows * newLongitudeColumns];
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void tileUpdateCompleted() throws RuggedException {
+        processUpdatedElevation(elevations);
+    }
+
+    /** Process elevation array at completion.
+     * <p>
+     * This method is called at tile update completion, it is
+     * expected to be overridden by subclasses. The default
+     * implementation does nothing.
+     * </p>
+     * @param elevationsArray elevations array
+     */
+    protected void processUpdatedElevation(final double[] elevationsArray) {
+        // do nothing by default
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getMinimumLatitude() {
+        return getLatitudeAtIndex(0);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getLatitudeAtIndex(final int latitudeIndex) {
+        return minLatitude + latitudeStep * latitudeIndex;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getMaximumLatitude() {
+        return getLatitudeAtIndex(latitudeRows - 1);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getMinimumLongitude() {
+        return getLongitudeAtIndex(0);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getLongitudeAtIndex(final int longitudeIndex) {
+        return minLongitude + longitudeStep * longitudeIndex;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getMaximumLongitude() {
+        return getLongitudeAtIndex(longitudeColumns - 1);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getLatitudeStep() {
+        return latitudeStep;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getLongitudeStep() {
+        return longitudeStep;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getLatitudeRows() {
+        return latitudeRows;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getLongitudeColumns() {
+        return longitudeColumns;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getMinElevation() {
+        return minElevation;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getMaxElevation() {
+        return maxElevation;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setElevation(final int latitudeIndex, final int longitudeIndex,
+                             final double elevation) throws RuggedException {
+        if (latitudeIndex  < 0 || latitudeIndex  > (latitudeRows - 1) ||
+            longitudeIndex < 0 || longitudeIndex > (longitudeColumns - 1)) {
+            throw new RuggedException(RuggedMessages.OUT_OF_TILE_INDICES,
+                                      latitudeIndex, longitudeIndex,
+                                      latitudeRows - 1, longitudeColumns - 1);
+        }
+        minElevation = FastMath.min(minElevation, elevation);
+        maxElevation = FastMath.max(maxElevation, elevation);
+        elevations[latitudeIndex * getLongitudeColumns() + longitudeIndex] = elevation;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getElevationAtIndices(final int latitudeIndex, final int longitudeIndex) {
+        return elevations[latitudeIndex * getLongitudeColumns() + longitudeIndex];
+    }
+
+    /** {@inheritDoc}
+     * <p>
+     * This classes uses an arbitrary 1/8 cell tolerance for interpolating
+     * slightly out of tile points.
+     * </p>
+     */
+    @Override
+    public double interpolateElevation(final double latitude, final double longitude)
+        throws RuggedException {
+
+        final double doubleLatitudeIndex  = getDoubleLatitudeIndex(latitude);
+        final double doubleLongitudeIndex = getDoubleLontitudeIndex(longitude);
+        if (doubleLatitudeIndex  < -TOLERANCE || doubleLatitudeIndex  >= (latitudeRows - 1 + TOLERANCE) ||
+            doubleLongitudeIndex < -TOLERANCE || doubleLongitudeIndex >= (longitudeColumns - 1 + TOLERANCE)) {
+            throw new RuggedException(RuggedMessages.OUT_OF_TILE_ANGLES,
+                                      FastMath.toDegrees(latitude),
+                                      FastMath.toDegrees(longitude),
+                                      FastMath.toDegrees(getMinimumLatitude()),
+                                      FastMath.toDegrees(getMaximumLatitude()),
+                                      FastMath.toDegrees(getMinimumLongitude()),
+                                      FastMath.toDegrees(getMaximumLongitude()));
+        }
+
+        final int latitudeIndex  = FastMath.max(0,
+                                                FastMath.min(latitudeRows - 2,
+                                                             (int) FastMath.floor(doubleLatitudeIndex)));
+        final int longitudeIndex = FastMath.max(0,
+                                                FastMath.min(longitudeColumns - 2,
+                                                             (int) FastMath.floor(doubleLongitudeIndex)));
+
+        // bi-linear interpolation
+        final double dLat = doubleLatitudeIndex  - latitudeIndex;
+        final double dLon = doubleLongitudeIndex - longitudeIndex;
+        final double e00  = getElevationAtIndices(latitudeIndex,     longitudeIndex);
+        final double e10  = getElevationAtIndices(latitudeIndex,     longitudeIndex + 1);
+        final double e01  = getElevationAtIndices(latitudeIndex + 1, longitudeIndex);
+        final double e11  = getElevationAtIndices(latitudeIndex + 1, longitudeIndex + 1);
+        return (e00 * (1.0 - dLon) + dLon * e10) * (1.0 - dLat) +
+               (e01 * (1.0 - dLon) + dLon * e11) * dLat;
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public NormalizedGeodeticPoint cellIntersection(final GeodeticPoint p, final Vector3D los,
+                                                    final int latitudeIndex, final int longitudeIndex)
+        throws RuggedException {
+
+        // ensure neighboring cells to not fall out of tile
+        final int iLat  = FastMath.max(0, FastMath.min(latitudeRows     - 2, latitudeIndex));
+        final int jLong = FastMath.max(0, FastMath.min(longitudeColumns - 2, longitudeIndex));
+
+        // Digital Elevation Mode coordinates at cell vertices
+        final double x00 = getLongitudeAtIndex(jLong);
+        final double y00 = getLatitudeAtIndex(iLat);
+        final double z00 = getElevationAtIndices(iLat,     jLong);
+        final double z01 = getElevationAtIndices(iLat + 1, jLong);
+        final double z10 = getElevationAtIndices(iLat,     jLong + 1);
+        final double z11 = getElevationAtIndices(iLat + 1, jLong + 1);
+
+        // line-of-sight coordinates at close points
+        final double dxA = (p.getLongitude() - x00) / longitudeStep;
+        final double dyA = (p.getLatitude()  - y00) / latitudeStep;
+        final double dzA = p.getAltitude();
+        final double dxB = dxA + los.getX() / longitudeStep;
+        final double dyB = dyA + los.getY() / latitudeStep;
+        final double dzB = dzA + los.getZ();
+
+        // points along line-of-sight can be defined as a linear progression
+        // along the line depending on free variable t: p(t) = p + t * los.
+        // As the point latitude and longitude are linear with respect to t,
+        // and as Digital Elevation Model is quadratic with respect to latitude
+        // and longitude, the altitude of DEM at same horizontal position as
+        // point is quadratic in t:
+        // z_DEM(t) = u t² + v t + w
+        final double u = (dxA - dxB) * (dyA - dyB) * (z00 - z10 - z01 + z11);
+        final double v = ((dxA - dxB) * (1 - dyA) + (dyA - dyB) * (1 - dxA)) * z00 +
+                         (dxA * (dyA - dyB) - (dxA - dxB) * (1 - dyA)) * z10 +
+                         (dyA * (dxA - dxB) - (dyA - dyB) * (1 - dxA)) * z01 +
+                         ((dxB - dxA) * dyA + (dyB - dyA) * dxA) * z11;
+        final double w = (1 - dxA) * ((1 - dyA) * z00 + dyA * z01) +
+                         dxA       * ((1 - dyA) * z10 + dyA * z11);
+
+        // subtract linear z from line-of-sight
+        // z_DEM(t) - z_LOS(t) = a t² + b t + c
+        final double a = u;
+        final double b = v + dzA - dzB;
+        final double c = w - dzA;
+
+        // solve the equation
+        final double t1;
+        final double t2;
+        if (FastMath.abs(a) <= Precision.EPSILON * FastMath.abs(c)) {
+            // the equation degenerates to a linear (or constant) equation
+            final double t = -c / b;
+            t1 = Double.isNaN(t) ? 0.0 : t;
+            t2 = Double.POSITIVE_INFINITY;
+        } else {
+            // the equation is quadratic
+            final double b2  = b * b;
+            final double fac = 4 * a * c;
+            if (b2 < fac) {
+                // no intersection at all
+                return null;
+            }
+            final double s = FastMath.sqrt(b2 - fac);
+            t1 = (b < 0) ? (s - b) / (2 * a) : -2 * c / (b + s);
+            t2 = c / (a * t1);
+
+        }
+
+        final NormalizedGeodeticPoint p1 = interpolate(t1, p, dxA, dyA, los, x00);
+        final NormalizedGeodeticPoint p2 = interpolate(t2, p, dxA, dyA, los, x00);
+
+        // select the first point along line-of-sight
+        if (p1 == null) {
+            return p2;
+        } else if (p2 == null) {
+            return p1;
+        } else {
+            return t1 <= t2 ? p1 : p2;
+        }
+
+    }
+
+    /** Interpolate point along a line.
+     * @param t abscissa along the line
+     * @param p start point
+     * @param dxP relative coordinate of the start point with respect to current cell
+     * @param dyP relative coordinate of the start point with respect to current cell
+     * @param los direction of the line-of-sight, in geodetic space
+     * @param centralLongitude reference longitude lc such that the point longitude will
+     * be normalized between lc-Ï€ and lc+Ï€
+     * @return interpolated point along the line
+     */
+    private NormalizedGeodeticPoint interpolate(final double t, final GeodeticPoint p,
+                                                final double dxP, final double dyP,
+                                                final Vector3D los, final double centralLongitude) {
+
+        if (Double.isInfinite(t)) {
+            return null;
+        }
+
+        final double dx = dxP + t * los.getX() / longitudeStep;
+        final double dy = dyP + t * los.getY() / latitudeStep;
+        if (dx >= -TOLERANCE && dx <= 1 + TOLERANCE && dy >= -TOLERANCE && dy <= 1 + TOLERANCE) {
+            return new NormalizedGeodeticPoint(p.getLatitude()  + t * los.getY(),
+                                               p.getLongitude() + t * los.getX(),
+                                               p.getAltitude()  + t * los.getZ(),
+                                               centralLongitude);
+        } else {
+            return null;
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getFloorLatitudeIndex(final double latitude) {
+        return (int) FastMath.floor(getDoubleLatitudeIndex(latitude));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getFloorLongitudeIndex(final double longitude) {
+        return (int) FastMath.floor(getDoubleLontitudeIndex(longitude));
+    }
+
+    /** Get the latitude index of a point.
+     * @param latitude geodetic latitude
+     * @return latitute index (it may lie outside of the tile!)
+     */
+    private double getDoubleLatitudeIndex(final double latitude) {
+        return (latitude  - minLatitude)  / latitudeStep;
+    }
+
+    /** Get the longitude index of a point.
+     * @param longitude geodetic latitude
+     * @return longitude index (it may lie outside of the tile!)
+     */
+    private double getDoubleLontitudeIndex(final double longitude) {
+        return (longitude - minLongitude) / longitudeStep;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Location getLocation(final double latitude, final double longitude) {
+        final int latitudeIndex  = getFloorLatitudeIndex(latitude);
+        final int longitudeIndex = getFloorLongitudeIndex(longitude);
+        if (longitudeIndex < 0) {
+            if (latitudeIndex < 0) {
+                return Location.SOUTH_WEST;
+            } else if (latitudeIndex <= (latitudeRows - 2)) {
+                return Location.WEST;
+            } else {
+                return Location.NORTH_WEST;
+            }
+        } else if (longitudeIndex <= (longitudeColumns - 2)) {
+            if (latitudeIndex < 0) {
+                return Location.SOUTH;
+            } else if (latitudeIndex <= (latitudeRows - 2)) {
+                return Location.HAS_INTERPOLATION_NEIGHBORS;
+            } else {
+                return Location.NORTH;
+            }
+        } else {
+            if (latitudeIndex < 0) {
+                return Location.SOUTH_EAST;
+            } else if (latitudeIndex <= (latitudeRows - 2)) {
+                return Location.EAST;
+            } else {
+                return Location.NORTH_EAST;
+            }
+        }
+    }
+
+}
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_de.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_de.utf8
index 999dbcf4..1b36f2d4 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_de.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_de.utf8
@@ -45,3 +45,12 @@ NOT_INTERPOLATOR_DUMP_DATA = die Angabe ist nicht das Backup der Interpolation
 
 # number of estimated parameters mismatch, expected {0} got {1}
 ESTIMATED_PARAMETERS_NUMBER_MISMATCH = <MISSING TRANSLATION>
+
+# debug dump is already active for this thread
+DEBUG_DUMP_ALREADY_ACTIVE = <MISSING TRANSLATION>
+
+# unable to active debug dump with file {0}: {1}
+DEBUG_DUMP_ACTIVATION_ERROR = <MISSING TRANSLATION>
+
+# debug dump is not active for this thread
+DEBUG_DUMP_NOT_ACTIVE = <MISSING TRANSLATION>
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8
index bcf1729e..6489c7d3 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8
@@ -45,3 +45,12 @@ NOT_INTERPOLATOR_DUMP_DATA = data is not an interpolator dump
 
 # number of estimated parameters mismatch, expected {0} got {1}
 ESTIMATED_PARAMETERS_NUMBER_MISMATCH = number of estimated parameters mismatch, expected {0} got {1}
+
+# debug dump is already active for this thread
+DEBUG_DUMP_ALREADY_ACTIVE = debug dump is already active for this thread
+
+# unable to active debug dump with file {0}: {1}
+DEBUG_DUMP_ACTIVATION_ERROR = unable to active debug dump with file {0}: {1}
+
+# debug dump is not active for this thread
+DEBUG_DUMP_NOT_ACTIVE = debug dump is not active for this thread
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_es.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_es.utf8
index 3c1ff3eb..e2b1c469 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_es.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_es.utf8
@@ -45,3 +45,12 @@ NOT_INTERPOLATOR_DUMP_DATA = los datos no están en el volcado de datos del inte
 
 # number of estimated parameters mismatch, expected {0} got {1}
 ESTIMATED_PARAMETERS_NUMBER_MISMATCH = <MISSING TRANSLATION>
+
+# debug dump is already active for this thread
+DEBUG_DUMP_ALREADY_ACTIVE = <MISSING TRANSLATION>
+
+# unable to active debug dump with file {0}: {1}
+DEBUG_DUMP_ACTIVATION_ERROR = <MISSING TRANSLATION>
+
+# debug dump is not active for this thread
+DEBUG_DUMP_NOT_ACTIVE = <MISSING TRANSLATION>
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8
index d8842ce1..04d17033 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8
@@ -45,3 +45,12 @@ NOT_INTERPOLATOR_DUMP_DATA = les données ne correspondent pas à une sauvegarde
 
 # number of estimated parameters mismatch, expected {0} got {1}
 ESTIMATED_PARAMETERS_NUMBER_MISMATCH = incohérence du nombre de paramètres estimés, {0} attendus, {1} renseignés
+
+# debug dump is already active for this thread
+DEBUG_DUMP_ALREADY_ACTIVE = capture de débogage déjà active pour ce fil d''exécution
+
+# unable to active debug dump with file {0}: {1}
+DEBUG_DUMP_ACTIVATION_ERROR = impossible d''activer la capture de débogage avec le fichier {0} : {1}
+
+# debug dump is not active for this thread
+DEBUG_DUMP_NOT_ACTIVE = capture de débogage inactive pour ce fil d''exécution
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_gl.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_gl.utf8
index a841bad3..eeafc69d 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_gl.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_gl.utf8
@@ -45,3 +45,12 @@ NOT_INTERPOLATOR_DUMP_DATA = os datos non están no baleirado de datos do interp
 
 # number of estimated parameters mismatch, expected {0} got {1}
 ESTIMATED_PARAMETERS_NUMBER_MISMATCH = <MISSING TRANSLATION>
+
+# debug dump is already active for this thread
+DEBUG_DUMP_ALREADY_ACTIVE = <MISSING TRANSLATION>
+
+# unable to active debug dump with file {0}: {1}
+DEBUG_DUMP_ACTIVATION_ERROR = <MISSING TRANSLATION>
+
+# debug dump is not active for this thread
+DEBUG_DUMP_NOT_ACTIVE = <MISSING TRANSLATION>
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_it.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_it.utf8
index 69a983c8..f7aae438 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_it.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_it.utf8
@@ -45,3 +45,12 @@ NOT_INTERPOLATOR_DUMP_DATA = i dati non corrispondono a un salvatagggio d''inter
 
 # number of estimated parameters mismatch, expected {0} got {1}
 ESTIMATED_PARAMETERS_NUMBER_MISMATCH = <MISSING TRANSLATION>
+
+# debug dump is already active for this thread
+DEBUG_DUMP_ALREADY_ACTIVE = <MISSING TRANSLATION>
+
+# unable to active debug dump with file {0}: {1}
+DEBUG_DUMP_ACTIVATION_ERROR = <MISSING TRANSLATION>
+
+# debug dump is not active for this thread
+DEBUG_DUMP_NOT_ACTIVE = <MISSING TRANSLATION>
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_no.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_no.utf8
index 081166d6..1950b611 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_no.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_no.utf8
@@ -45,3 +45,12 @@ NOT_INTERPOLATOR_DUMP_DATA = dataen er ikke en interpolar-dump
 
 # number of estimated parameters mismatch, expected {0} got {1}
 ESTIMATED_PARAMETERS_NUMBER_MISMATCH = <MISSING TRANSLATION>
+
+# debug dump is already active for this thread
+DEBUG_DUMP_ALREADY_ACTIVE = <MISSING TRANSLATION>
+
+# unable to active debug dump with file {0}: {1}
+DEBUG_DUMP_ACTIVATION_ERROR = <MISSING TRANSLATION>
+
+# debug dump is not active for this thread
+DEBUG_DUMP_NOT_ACTIVE = <MISSING TRANSLATION>
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_ro.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_ro.utf8
index 33d24adc..b1159b05 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_ro.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_ro.utf8
@@ -45,3 +45,12 @@ NOT_INTERPOLATOR_DUMP_DATA = datele nu reprezintă o copie de siguranță a inte
 
 # number of estimated parameters mismatch, expected {0} got {1}
 ESTIMATED_PARAMETERS_NUMBER_MISMATCH = <MISSING TRANSLATION>
+
+# debug dump is already active for this thread
+DEBUG_DUMP_ALREADY_ACTIVE = <MISSING TRANSLATION>
+
+# unable to active debug dump with file {0}: {1}
+DEBUG_DUMP_ACTIVATION_ERROR = <MISSING TRANSLATION>
+
+# debug dump is not active for this thread
+DEBUG_DUMP_NOT_ACTIVE = <MISSING TRANSLATION>
diff --git a/src/test/java/org/orekit/rugged/errors/DumpManagerTest.java b/src/test/java/org/orekit/rugged/errors/DumpManagerTest.java
new file mode 100644
index 00000000..5cd1e90f
--- /dev/null
+++ b/src/test/java/org/orekit/rugged/errors/DumpManagerTest.java
@@ -0,0 +1,133 @@
+/* Copyright 2013-2015 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.errors;
+
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+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.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.orekit.bodies.BodyShape;
+import org.orekit.bodies.GeodeticPoint;
+import org.orekit.data.DataProvidersManager;
+import org.orekit.data.DirectoryCrawler;
+import org.orekit.errors.OrekitException;
+import org.orekit.orbits.Orbit;
+import org.orekit.rugged.TestUtils;
+import org.orekit.rugged.api.AlgorithmId;
+import org.orekit.rugged.api.BodyRotatingFrameId;
+import org.orekit.rugged.api.EllipsoidId;
+import org.orekit.rugged.api.InertialFrameId;
+import org.orekit.rugged.api.Rugged;
+import org.orekit.rugged.api.RuggedBuilder;
+import org.orekit.rugged.linesensor.LineDatation;
+import org.orekit.rugged.linesensor.LineSensor;
+import org.orekit.rugged.linesensor.LinearLineDatation;
+import org.orekit.rugged.los.TimeDependentLOS;
+import org.orekit.rugged.raster.RandomLandscapeUpdater;
+import org.orekit.rugged.raster.TileUpdater;
+import org.orekit.time.AbsoluteDate;
+import org.orekit.time.TimeScalesFactory;
+import org.orekit.utils.AngularDerivativesFilter;
+import org.orekit.utils.CartesianDerivativesFilter;
+import org.orekit.utils.Constants;
+
+public class DumpManagerTest {
+
+    @Rule
+    public TemporaryFolder tempFolder = new TemporaryFolder();
+
+    @Test
+    public void testDump() throws URISyntaxException, IOException, OrekitException, RuggedException {
+
+        File dump = tempFolder.newFile();
+        DumpManager.activate(dump);
+        locationsinglePoint();
+        DumpManager.deactivate();
+        BufferedReader br = new BufferedReader(new FileReader(dump));
+        for (String line = br.readLine(); line != null; line = br.readLine()) {
+            System.out.println(line);
+        }
+        br.close();
+    }
+
+   public void locationsinglePoint()
+        throws RuggedException, OrekitException, URISyntaxException {
+
+       int dimension = 200;
+
+       String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
+       DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
+       final BodyShape  earth = TestUtils.createEarth();
+       final Orbit      orbit = TestUtils.createOrbit(Constants.EIGEN5C_EARTH_MU);
+
+       AbsoluteDate crossing = new AbsoluteDate("2012-01-01T12:30:00.000", TimeScalesFactory.getUTC());
+
+       // one line sensor
+       // position: 1.5m in front (+X) and 20 cm above (-Z) of the S/C center of mass
+       // los: swath in the (YZ) plane, looking at 50° roll, ±1° aperture
+       Vector3D position = new Vector3D(1.5, 0, -0.2);
+       TimeDependentLOS los = TestUtils.createLOSPerfectLine(new Rotation(Vector3D.PLUS_I,
+                                                                          FastMath.toRadians(50.0)).applyTo(Vector3D.PLUS_K),
+                                                             Vector3D.PLUS_I, FastMath.toRadians(1.0), dimension);
+
+       // linear datation model: at reference time we get line 100, and the rate is one line every 1.5ms
+       LineDatation lineDatation = new LinearLineDatation(crossing, dimension / 2, 1.0 / 1.5e-3);
+       int firstLine = 0;
+       int lastLine  = dimension;
+       LineSensor lineSensor = new LineSensor("line", lineDatation, position, los);
+       AbsoluteDate minDate = lineSensor.getDate(firstLine);
+       AbsoluteDate maxDate = lineSensor.getDate(lastLine);
+
+       TileUpdater updater =
+               new RandomLandscapeUpdater(0.0, 9000.0, 0.5, 0xf0a401650191f9f6l,
+                                          FastMath.toRadians(1.0), 257);
+
+       Rugged rugged = new RuggedBuilder().
+               setDigitalElevationModel(updater, 8).
+               setAlgorithm(AlgorithmId.DUVENHAGE).
+               setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+               setTimeSpan(minDate, maxDate, 0.001, 5.0).
+               setTrajectory(InertialFrameId.EME2000,
+                             TestUtils.orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                             8, CartesianDerivativesFilter.USE_PV,
+                             TestUtils.orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                             2, AngularDerivativesFilter.USE_R).
+               addLineSensor(lineSensor).build();
+       GeodeticPoint[] gpLine = rugged.directLocation("line", 100);
+
+       for (int i = 0; i < gpLine.length; ++i) {
+           GeodeticPoint gpPixel =
+                   rugged.directLocation(lineSensor.getDate(100), lineSensor.getPosition(),
+                                             lineSensor.getLos(lineSensor.getDate(100), i));
+           Assert.assertEquals(gpLine[i].getLatitude(),  gpPixel.getLatitude(),  1.0e-10);
+           Assert.assertEquals(gpLine[i].getLongitude(), gpPixel.getLongitude(), 1.0e-10);
+           Assert.assertEquals(gpLine[i].getAltitude(),  gpPixel.getAltitude(),  1.0e-10);
+       }
+
+    }
+
+}
diff --git a/src/test/java/org/orekit/rugged/errors/RuggedMessagesTest.java b/src/test/java/org/orekit/rugged/errors/RuggedMessagesTest.java
index 962bf717..353e5793 100644
--- a/src/test/java/org/orekit/rugged/errors/RuggedMessagesTest.java
+++ b/src/test/java/org/orekit/rugged/errors/RuggedMessagesTest.java
@@ -30,7 +30,7 @@ public class RuggedMessagesTest {
 
     @Test
     public void testMessageNumber() {
-        Assert.assertEquals(16, RuggedMessages.values().length);
+        Assert.assertEquals(19, RuggedMessages.values().length);
     }
 
     @Test
-- 
GitLab