From daf0c3a7c3c8cc275f5773f13e44aca7a5fda21e Mon Sep 17 00:00:00 2001
From: Luc Maisonobe <luc@orekit.org>
Date: Mon, 15 Dec 2014 17:44:49 +0100
Subject: [PATCH] Use builder pattern and fluent interface to build Rugged
 instance.

---
 checkstyle.xml                                |   5 +
 pom.xml                                       |   7 +
 .../java/org/orekit/rugged/api/Rugged.java    | 605 +------------
 .../org/orekit/rugged/api/RuggedBuilder.java  | 726 ++++++++++++++++
 .../org/orekit/rugged/api/RuggedMessages.java |   2 +-
 .../org/orekit/rugged/RuggedMessages_de.utf8  |   4 +-
 .../org/orekit/rugged/RuggedMessages_en.utf8  |   4 +-
 .../org/orekit/rugged/RuggedMessages_es.utf8  |   4 +-
 .../org/orekit/rugged/RuggedMessages_fr.utf8  |   4 +-
 .../org/orekit/rugged/RuggedMessages_gl.utf8  |   4 +-
 .../org/orekit/rugged/RuggedMessages_it.utf8  |   4 +-
 .../tutorials/direct-location-with-DEM.md     |  17 +-
 .../markdown/tutorials/direct-location.md     | 118 ++-
 src/site/xdoc/changes.xml                     |   3 +
 .../orekit/rugged/api/RuggedBuilderTest.java  | 727 ++++++++++++++++
 .../org/orekit/rugged/api/RuggedTest.java     | 811 +++++-------------
 16 files changed, 1793 insertions(+), 1252 deletions(-)
 create mode 100644 src/main/java/org/orekit/rugged/api/RuggedBuilder.java
 create mode 100644 src/test/java/org/orekit/rugged/api/RuggedBuilderTest.java

diff --git a/checkstyle.xml b/checkstyle.xml
index bcbcb41e..0a2daba9 100644
--- a/checkstyle.xml
+++ b/checkstyle.xml
@@ -97,4 +97,9 @@
     	<property name="onCommentFormat" value="CHECKSTYLE\: resume FallThrough check"/>
     	<property name="checkFormat" value="FallThrough"/>
   	</module>
+    <module name="SuppressionCommentFilter">
+      <property name="offCommentFormat" value="CHECKSTYLE\: stop HiddenField check"/>
+      <property name="onCommentFormat" value="CHECKSTYLE\: resume HiddenField check"/>
+      <property name="checkFormat" value="HiddenField"/>
+    </module>
 </module>
diff --git a/pom.xml b/pom.xml
index 2aa98603..03436278 100644
--- a/pom.xml
+++ b/pom.xml
@@ -358,6 +358,13 @@
           </links>
           <charset>${project.reporting.outputEncoding}</charset>
         </configuration>
+        <reportSets>
+          <reportSet>
+            <reports>
+              <report>javadoc</report>
+            </reports>
+          </reportSet>
+        </reportSets>
       </plugin>
       <plugin>
         <groupId>org.jacoco</groupId>
diff --git a/src/main/java/org/orekit/rugged/api/Rugged.java b/src/main/java/org/orekit/rugged/api/Rugged.java
index 920e522a..949f56ba 100644
--- a/src/main/java/org/orekit/rugged/api/Rugged.java
+++ b/src/main/java/org/orekit/rugged/api/Rugged.java
@@ -16,51 +16,26 @@
  */
 package org.orekit.rugged.api;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
-import org.apache.commons.math3.exception.util.LocalizedFormats;
 import org.apache.commons.math3.geometry.euclidean.threed.FieldVector3D;
-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.orekit.bodies.GeodeticPoint;
-import org.orekit.bodies.OneAxisEllipsoid;
-import org.orekit.errors.OrekitException;
-import org.orekit.errors.PropagationException;
-import org.orekit.frames.Frame;
-import org.orekit.frames.FramesFactory;
 import org.orekit.frames.Transform;
-import org.orekit.propagation.Propagator;
-import org.orekit.propagation.SpacecraftState;
-import org.orekit.propagation.sampling.OrekitFixedStepHandler;
-import org.orekit.rugged.intersection.BasicScanAlgorithm;
-import org.orekit.rugged.intersection.IgnoreDEMAlgorithm;
 import org.orekit.rugged.intersection.IntersectionAlgorithm;
-import org.orekit.rugged.intersection.duvenhage.DuvenhageAlgorithm;
-import org.orekit.rugged.raster.TileUpdater;
 import org.orekit.rugged.utils.ExtendedEllipsoid;
 import org.orekit.rugged.utils.NormalizedGeodeticPoint;
 import org.orekit.rugged.utils.SpacecraftToObservedBody;
 import org.orekit.time.AbsoluteDate;
-import org.orekit.utils.AngularDerivativesFilter;
-import org.orekit.utils.CartesianDerivativesFilter;
 import org.orekit.utils.Constants;
-import org.orekit.utils.IERSConventions;
 import org.orekit.utils.PVCoordinates;
-import org.orekit.utils.TimeStampedAngularCoordinates;
-import org.orekit.utils.TimeStampedPVCoordinates;
 
 /** Main class of Rugged library API.
+ * @see RuggedBuilder
  * @author Luc Maisonobe
  */
 public class Rugged {
@@ -106,481 +81,55 @@ public class Rugged {
      * and {@link #setAberrationOfLightCorrection(boolean) setAberrationOfLightCorrection}
      * can be made after construction if these phenomena should not be corrected.
      * </p>
-     * @param updater updater used to load Digital Elevation Model tiles
-     * @param maxCachedTiles maximum number of tiles stored in the cache
-     * @param algorithmID identifier of algorithm to use for Digital Elevation Model intersection
-     * @param ellipsoidID identifier of reference ellipsoid
-     * @param inertialFrameID identifier of inertial frame used for spacecraft positions/velocities/quaternions
-     * @param bodyRotatingFrameID identifier of body rotating frame for observed ground points
-     * @param minDate start of search time span
-     * @param maxDate end of search time span
-     * @param tStep step to use for inertial frame to body frame transforms cache computations
-     * @param overshootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting
-     * @param positionsVelocities satellite position and velocity (m and m/s in inertial frame)
-     * @param pvInterpolationNumber number of points to use for position/velocity interpolation
-     * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
-     * @param quaternions satellite quaternions with respect to inertial frame
-     * @param aInterpolationNumber number of points to use for attitude interpolation
-     * @param aFilter filter for derivatives from the sample to use in attitude interpolation
-     * @exception RuggedException if data needed for some frame cannot be loaded or if position
-     * or attitude samples do not fully cover the [{@code minDate}, {@code maxDate}] search time span
-     */
-    public Rugged(final TileUpdater updater, final int maxCachedTiles,
-                  final AlgorithmId algorithmID, final EllipsoidId ellipsoidID,
-                  final InertialFrameId inertialFrameID, final BodyRotatingFrameId bodyRotatingFrameID,
-                  final AbsoluteDate minDate, final AbsoluteDate maxDate, final double tStep,
-                  final double overshootTolerance,
-                  final List<TimeStampedPVCoordinates> positionsVelocities, final int pvInterpolationNumber,
-                  final CartesianDerivativesFilter pvFilter,
-                  final List<TimeStampedAngularCoordinates> quaternions, final int aInterpolationNumber,
-                  final AngularDerivativesFilter aFilter)
-        throws RuggedException {
-        this(updater, maxCachedTiles, algorithmID,
-             selectEllipsoid(ellipsoidID, selectBodyRotatingFrame(bodyRotatingFrameID)),
-             selectInertialFrame(inertialFrameID),
-             minDate, maxDate, tStep, overshootTolerance,
-             positionsVelocities, pvInterpolationNumber, pvFilter,
-             quaternions, aInterpolationNumber, aFilter);
-    }
-
-    /** Build a configured instance.
-     * <p>
-     * By default, the instance performs both light time correction (which refers
-     * to ground point motion with respect to inertial frame) and aberration of
-     * light correction (which refers to spacecraft proper velocity). Explicit calls
-     * to {@link #setLightTimeCorrection(boolean) setLightTimeCorrection}
-     * and {@link #setAberrationOfLightCorrection(boolean) setAberrationOfLightCorrection}
-     * can be made after construction if these phenomena should not be corrected.
-     * </p>
-     * @param updater updater used to load Digital Elevation Model tiles
-     * @param maxCachedTiles maximum number of tiles stored in the cache
-     * @param algorithmID identifier of algorithm to use for Digital Elevation Model intersection
-     * @param ellipsoid reference ellipsoid
-     * @param inertialFrame inertial frame used for spacecraft positions/velocities/quaternions
-     * @param minDate start of search time span
-     * @param maxDate end of search time span
-     * @param tStep step to use for inertial frame to body frame transforms cache computations
-     * @param overshootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting
-     * @param positionsVelocities satellite position and velocity (m and m/s in inertial frame)
-     * @param pvInterpolationNumber number of points to use for position/velocity interpolation
-     * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
-     * @param quaternions satellite quaternions with respect to inertial frame
-     * @param aInterpolationNumber number of points to use for attitude interpolation
-     * @param aFilter filter for derivatives from the sample to use in attitude interpolation
-     * @exception RuggedException if data needed for some frame cannot be loaded or if position
-     * or attitude samples do not fully cover the [{@code minDate}, {@code maxDate}] search time span
-     */
-    public Rugged(final TileUpdater updater, final int maxCachedTiles,
-                  final AlgorithmId algorithmID, final OneAxisEllipsoid ellipsoid, final Frame inertialFrame,
-                  final AbsoluteDate minDate, final AbsoluteDate maxDate, final double tStep,
-                  final double overshootTolerance,
-                  final List<TimeStampedPVCoordinates> positionsVelocities, final int pvInterpolationNumber,
-                  final CartesianDerivativesFilter pvFilter,
-                  final List<TimeStampedAngularCoordinates> quaternions, final int aInterpolationNumber,
-                  final AngularDerivativesFilter aFilter)
-        throws RuggedException {
-        this(updater, maxCachedTiles, algorithmID, ellipsoid,
-             createInterpolator(inertialFrame, ellipsoid.getBodyFrame(),
-                                minDate, maxDate, tStep, overshootTolerance,
-                                positionsVelocities, pvInterpolationNumber, pvFilter,
-                                quaternions, aInterpolationNumber, aFilter));
-    }
-
-    /** Build a configured instance.
-     * <p>
-     * By default, the instance performs both light time correction (which refers
-     * to ground point motion with respect to inertial frame) and aberration of
-     * light correction (which refers to spacecraft proper velocity). Explicit calls
-     * to {@link #setLightTimeCorrection(boolean) setLightTimeCorrection}
-     * and {@link #setAberrationOfLightCorrection(boolean) setAberrationOfLightCorrection}
-     * can be made after construction if these phenomena should not be corrected.
-     * </p>
-     * @param updater updater used to load Digital Elevation Model tiles
-     * @param maxCachedTiles maximum number of tiles stored in the cache
-     * @param algorithmID identifier of algorithm to use for Digital Elevation Model intersection
-     * @param ellipsoidID identifier of reference ellipsoid
-     * @param inertialFrameID identifier of inertial frame for spacecraft positions/velocities/quaternions
-     * @param bodyRotatingFrameID identifier of body rotating frame for observed ground points
-     * @param minDate start of search time span
-     * @param maxDate end of search time span
-     * @param tStep step to use for inertial frame to body frame transforms cache computations
-     * @param overshootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting
-     * @param interpolationStep step to use for inertial/Earth/spacraft transforms interpolations
-     * @param interpolationNumber number of points to use for inertial/Earth/spacraft transforms interpolations
-     * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
-     * @param aFilter filter for derivatives from the sample to use in attitude interpolation
-     * @param propagator global propagator
-     * @exception RuggedException if data needed for some frame cannot be loaded
-     */
-    public Rugged(final TileUpdater updater, final int maxCachedTiles,
-                  final AlgorithmId algorithmID, final EllipsoidId ellipsoidID,
-                  final InertialFrameId inertialFrameID, final BodyRotatingFrameId bodyRotatingFrameID,
-                  final AbsoluteDate minDate, final AbsoluteDate maxDate, final double tStep,
-                  final double overshootTolerance,
-                  final double interpolationStep, final int interpolationNumber,
-                  final CartesianDerivativesFilter pvFilter, final AngularDerivativesFilter aFilter,
-                  final Propagator propagator)
-        throws RuggedException {
-        this(updater, maxCachedTiles, algorithmID,
-             selectEllipsoid(ellipsoidID, selectBodyRotatingFrame(bodyRotatingFrameID)),
-             selectInertialFrame(inertialFrameID),
-             minDate, maxDate, tStep, overshootTolerance,
-             interpolationStep, interpolationNumber, pvFilter, aFilter, propagator);
-    }
-
-    /** Build a configured instance.
-     * <p>
-     * By default, the instance performs both light time correction (which refers
-     * to ground point motion with respect to inertial frame) and aberration of
-     * light correction (which refers to spacecraft proper velocity). Explicit calls
-     * to {@link #setLightTimeCorrection(boolean) setLightTimeCorrection}
-     * and {@link #setAberrationOfLightCorrection(boolean) setAberrationOfLightCorrection}
-     * can be made after construction if these phenomena should not be corrected.
-     * </p>
-     * @param updater updater used to load Digital Elevation Model tiles
-     * @param maxCachedTiles maximum number of tiles stored in the cache
-     * @param algorithmID identifier of algorithm to use for Digital Elevation Model intersection
-     * @param ellipsoid f reference ellipsoid
-     * @param inertialFrame inertial frame for spacecraft positions/velocities/quaternions
-     * @param minDate start of search time span
-     * @param maxDate end of search time span
-     * @param tStep step to use for inertial frame to body frame transforms cache computations
-     * @param overshootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting
-     * @param interpolationStep step to use for inertial/Earth/spacraft transforms interpolations
-     * @param interpolationNumber number of points of to use for inertial/Earth/spacraft transforms interpolations
-     * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
-     * @param aFilter filter for derivatives from the sample to use in attitude interpolation
-     * @param propagator global propagator
-     * @exception RuggedException if data needed for some frame cannot be loaded
-     */
-    public Rugged(final TileUpdater updater, final int maxCachedTiles,
-                  final AlgorithmId algorithmID, final OneAxisEllipsoid ellipsoid, final Frame inertialFrame,
-                  final AbsoluteDate minDate, final AbsoluteDate maxDate, final double tStep,
-                  final double overshootTolerance,
-                  final double interpolationStep, final int interpolationNumber,
-                  final CartesianDerivativesFilter pvFilter, final AngularDerivativesFilter aFilter,
-                  final Propagator propagator)
-        throws RuggedException {
-        this(updater, maxCachedTiles, algorithmID, ellipsoid,
-             createInterpolator(inertialFrame, ellipsoid.getBodyFrame(),
-                                minDate, maxDate, tStep, overshootTolerance,
-                                interpolationStep, interpolationNumber, pvFilter, aFilter, propagator));
-    }
-
-    /** Build a configured instance, reusing the interpolator dumped from a previous instance.
-     * <p>
-     * By default, the instance performs both light time correction (which refers
-     * to ground point motion with respect to inertial frame) and aberration of
-     * light correction (which refers to spacecraft proper velocity). Explicit calls
-     * to {@link #setLightTimeCorrection(boolean) setLightTimeCorrection}
-     * and {@link #setAberrationOfLightCorrection(boolean) setAberrationOfLightCorrection}
-     * can be made after construction if these phenomena should not be corrected.
-     * </p>
-     * @param updater updater used to load Digital Elevation Model tiles
-     * @param maxCachedTiles maximum number of tiles stored in the cache
-     * @param algorithmID identifier of algorithm to use for Digital Elevation Model intersection
-     * @param ellipsoidID identifier of reference ellipsoid
-     * @param inertialFrameID identifier of inertial frame for spacecraft positions/velocities/quaternions
-     * @param bodyRotatingFrameID identifier of body rotating frame for observed ground points
-     * @param dumpStream stream from where to read previous instance dumped interpolator
-     * (caller opened it and remains responsible for closing it)
-     * @exception RuggedException if dump file cannot be loaded
-     * or if frames do not match the ones referenced in the dump file
-     * @see #dumpInterpolator(OutputStream)
-     */
-    public Rugged(final TileUpdater updater, final int maxCachedTiles,
-                  final AlgorithmId algorithmID, final EllipsoidId ellipsoidID,
-                  final InertialFrameId inertialFrameID, final BodyRotatingFrameId bodyRotatingFrameID,
-                  final InputStream dumpStream)
-        throws RuggedException {
-        this(updater, maxCachedTiles, algorithmID,
-             selectEllipsoid(ellipsoidID, selectBodyRotatingFrame(bodyRotatingFrameID)),
-             selectInertialFrame(inertialFrameID), dumpStream);
-    }
-
-    /** Build a configured instance, reusing the interpolator dumped from a previous instance.
-     * <p>
-     * By default, the instance performs both light time correction (which refers
-     * to ground point motion with respect to inertial frame) and aberration of
-     * light correction (which refers to spacecraft proper velocity). Explicit calls
-     * to {@link #setLightTimeCorrection(boolean) setLightTimeCorrection}
-     * and {@link #setAberrationOfLightCorrection(boolean) setAberrationOfLightCorrection}
-     * can be made after construction if these phenomena should not be corrected.
-     * </p>
-     * @param updater updater used to load Digital Elevation Model tiles
-     * @param maxCachedTiles maximum number of tiles stored in the cache
-     * @param algorithmID identifier of algorithm to use for Digital Elevation Model intersection
-     * @param ellipsoid reference ellipsoid
-     * @param inertialFrame inertial frame for spacecraft positions/velocities/quaternions
-     * @param dumpStream stream from where to read previous instance dumped interpolator
-     * (caller opened it and remains responsible for closing it)
-     * @exception RuggedException if dump file cannot be loaded
-     * or if frames do not match the ones referenced in the dump file
-     * @see #dumpInterpolator(OutputStream)
-     */
-    public Rugged(final TileUpdater updater, final int maxCachedTiles,
-                  final AlgorithmId algorithmID, final OneAxisEllipsoid ellipsoid, final Frame inertialFrame,
-                  final InputStream dumpStream)
-        throws RuggedException {
-        this(updater, maxCachedTiles, algorithmID, ellipsoid,
-             createInterpolator(inertialFrame, ellipsoid.getBodyFrame(), dumpStream));
-    }
-
-    /** Build a configured instance.
-     * <p>
-     * By default, the instance performs both light time correction (which refers
-     * to ground point motion with respect to inertial frame) and aberration of
-     * light correction (which refers to spacecraft proper velocity). Explicit calls
-     * to {@link #setLightTimeCorrection(boolean) setLightTimeCorrection}
-     * and {@link #setAberrationOfLightCorrection(boolean) setAberrationOfLightCorrection}
-     * can be made after construction if these phenomena should not be corrected.
-     * </p>
-     * @param updater updater used to load Digital Elevation Model tiles
-     * @param maxCachedTiles maximum number of tiles stored in the cache
-     * @param algorithmID identifier of algorithm to use for Digital Elevation Model intersection
+     * @param algorithm algorithm to use for Digital Elevation Model intersection
      * @param ellipsoid f reference ellipsoid
+     * @param lightTimeCorrection if true, the light travel time between ground
+     * @param aberrationOfLightCorrection if true, the aberration of light
+     * is corrected for more accurate location
+     * and spacecraft is compensated for more accurate location
      * @param scToBody transforms interpolator
+     * @param sensors sensors
      */
-    private Rugged(final TileUpdater updater, final int maxCachedTiles,
-                  final AlgorithmId algorithmID, final OneAxisEllipsoid ellipsoid,
-                  final SpacecraftToObservedBody scToBody) {
+    Rugged(final IntersectionAlgorithm algorithm, final ExtendedEllipsoid ellipsoid,
+           final boolean lightTimeCorrection, final boolean aberrationOfLightCorrection,
+           final SpacecraftToObservedBody scToBody, final Collection<LineSensor> sensors) {
 
         // space reference
-        this.ellipsoid = extend(ellipsoid);
+        this.ellipsoid = ellipsoid;
 
         // orbit/attitude to body converter
         this.scToBody = scToBody;
 
         // intersection algorithm
-        this.algorithm = selectAlgorithm(algorithmID, updater, maxCachedTiles);
+        this.algorithm = algorithm;
 
         this.sensors = new HashMap<String, LineSensor>();
-        this.finders = new HashMap<String, SensorMeanPlaneCrossing>();
-
-        setLightTimeCorrection(true);
-        setAberrationOfLightCorrection(true);
-
-    }
-
-    /** Create a transform interpolator from positions and quaternions lists.
-     * @param inertialFrame inertial frame
-     * @param bodyFrame observed body frame
-     * @param minDate start of search time span
-     * @param maxDate end of search time span
-     * @param tStep step to use for inertial frame to body frame transforms cache computations
-     * @param overshootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting
-     * @param positionsVelocities satellite position and velocity
-     * @param pvInterpolationNumber number of points to use for position/velocity interpolation
-     * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
-     * @param quaternions satellite quaternions
-     * @param aInterpolationNumber number of points to use for attitude interpolation
-     * @param aFilter filter for derivatives from the sample to use in attitude interpolation
-     * @return transforms interpolator
-     * @exception RuggedException if data needed for some frame cannot be loaded or if position
-     * or attitude samples do not fully cover the [{@code minDate}, {@code maxDate}] search time span
-     */
-    private static SpacecraftToObservedBody createInterpolator(final Frame inertialFrame, final Frame bodyFrame,
-                                                               final AbsoluteDate minDate, final AbsoluteDate maxDate,
-                                                               final double tStep, final double overshootTolerance,
-                                                               final List<TimeStampedPVCoordinates> positionsVelocities,
-                                                               final int pvInterpolationNumber,
-                                                               final CartesianDerivativesFilter pvFilter,
-                                                               final List<TimeStampedAngularCoordinates> quaternions,
-                                                               final int aInterpolationNumber,
-                                                               final AngularDerivativesFilter aFilter)
-        throws RuggedException {
-        return new SpacecraftToObservedBody(inertialFrame, bodyFrame,
-                                            minDate, maxDate, tStep,
-                                            overshootTolerance, positionsVelocities, pvInterpolationNumber,
-                                            pvFilter, quaternions, aInterpolationNumber,
-                                            aFilter);
-    }
-
-    /** Create a transform interpolator from a propagator.
-     * @param inertialFrame inertial frame
-     * @param bodyFrame observed body frame
-     * @param minDate start of search time span
-     * @param maxDate end of search time span
-     * @param tStep step to use for inertial frame to body frame transforms cache computations
-     * @param overshootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting
-     * @param interpolationStep step to use for inertial/Earth/spacraft transforms interpolations
-     * @param interpolationNumber number of points of to use for inertial/Earth/spacraft transforms interpolations
-     * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
-     * @param aFilter filter for derivatives from the sample to use in attitude interpolation
-     * @param propagator global propagator
-     * @return transforms interpolator
-     * @exception RuggedException if data needed for some frame cannot be loaded
-     */
-    private static SpacecraftToObservedBody createInterpolator(final Frame inertialFrame, final Frame bodyFrame,
-                                                               final AbsoluteDate minDate, final AbsoluteDate maxDate,
-                                                               final double tStep, final double overshootTolerance,
-                                                               final double interpolationStep, final int interpolationNumber,
-                                                               final CartesianDerivativesFilter pvFilter,
-                                                               final AngularDerivativesFilter aFilter,
-                                                               final Propagator propagator)
-        throws RuggedException {
-        try {
-
-            // extract position/attitude samples from propagator
-            final List<TimeStampedPVCoordinates> positionsVelocities =
-                    new ArrayList<TimeStampedPVCoordinates>();
-            final List<TimeStampedAngularCoordinates> quaternions =
-                    new ArrayList<TimeStampedAngularCoordinates>();
-            propagator.setMasterMode(interpolationStep, new OrekitFixedStepHandler() {
-
-                /** {@inheritDoc} */
-                @Override
-                public void init(final SpacecraftState s0, final AbsoluteDate t) {
-                }
-
-                /** {@inheritDoc} */
-                @Override
-                public void handleStep(final SpacecraftState currentState, final boolean isLast)
-                    throws PropagationException {
-                    try {
-                        final AbsoluteDate  date = currentState.getDate();
-                        final PVCoordinates pv   = currentState.getPVCoordinates(inertialFrame);
-                        final Rotation      q    = currentState.getAttitude().getRotation();
-                        positionsVelocities.add(new TimeStampedPVCoordinates(date, pv.getPosition(), pv.getVelocity(), Vector3D.ZERO));
-                        quaternions.add(new TimeStampedAngularCoordinates(date, q, Vector3D.ZERO, Vector3D.ZERO));
-                    } catch (OrekitException oe) {
-                        throw new PropagationException(oe);
-                    }
-                }
-
-            });
-            propagator.propagate(minDate.shiftedBy(-interpolationStep), maxDate.shiftedBy(interpolationStep));
-
-            // orbit/attitude to body converter
-            return createInterpolator(inertialFrame, bodyFrame,
-                                      minDate, maxDate, tStep,
-                                      overshootTolerance, positionsVelocities, interpolationNumber,
-                                      pvFilter, quaternions, interpolationNumber,
-                                      aFilter);
-
-        } catch (PropagationException pe) {
-            throw new RuggedException(pe, pe.getSpecifier(), pe.getParts());
-        }
-    }
-
-    /** Create a transform interpolator from positions and quaternions lists.
-     * @param inertialFrame inertial frame
-     * @param bodyFrame observed body frame
-     * @param dumpStream stream from where to read previous instance dumped interpolator
-     * (caller opened it and remains responsible for closing it)
-     * @return recovered interpolator from dump
-     * @exception RuggedException if dump file cannot be loaded or if
-     * frames do not match the ones referenced in the dump file
-     */
-    private static SpacecraftToObservedBody createInterpolator(final Frame inertialFrame, final Frame bodyFrame,
-                                                               final InputStream dumpStream)
-        throws RuggedException {
-        try {
-            final ObjectInputStream ois = new ObjectInputStream(dumpStream);
-            final SpacecraftToObservedBody scToBody = (SpacecraftToObservedBody) ois.readObject();
-            if (!inertialFrame.getName().equals(scToBody.getInertialFrameName())) {
-                throw new RuggedException(RuggedMessages.FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP,
-                                          inertialFrame.getName(), scToBody.getInertialFrameName());
-            }
-            if (!bodyFrame.getName().equals(scToBody.getBodyFrameName())) {
-                throw new RuggedException(RuggedMessages.FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP,
-                                          bodyFrame.getName(), scToBody.getBodyFrameName());
-            }
-            return scToBody;
-        } catch (ClassNotFoundException cnfe) {
-            throw new RuggedException(cnfe, RuggedMessages.NOT_INTERPOLATOR_DUMP_DATA);
-        } catch (ClassCastException cce) {
-            throw new RuggedException(cce, RuggedMessages.NOT_INTERPOLATOR_DUMP_DATA);
-        } catch (IOException ioe) {
-            throw new RuggedException(ioe, RuggedMessages.NOT_INTERPOLATOR_DUMP_DATA);
+        for (final LineSensor s : sensors) {
+            this.sensors.put(s.getName(), s);
         }
-    }
+        this.finders = new HashMap<String, SensorMeanPlaneCrossing>();
 
-    /** Dump frames transform interpolator.
-     * <p>
-     * This method allows to reuse the interpolator built in one instance, to build
-     * another instance by calling {@link #Rugged(TileUpdater, int, AlgorithmId, OneAxisEllipsoid, Frame, InputStream)}
-     * or {@link #Rugged(TileUpdater, int, AlgorithmId, EllipsoidId, InertialFrameId, BodyRotatingFrameId, InputStream)}.
-     * This reduces Rugged initialization time as setting up the interpolator can be long, it is
-     * mainly intended to be used when several runs are done (for example in an image processing chain)
-     * with the same configuration.
-     * </p>
-     * @param dumpStream stream where to dump the interpolator
-     * (caller opened it and remains responsible for closing it)
-     * @exception RuggedException if interpolator cannot be written to file
-     * @see #Rugged(TileUpdater, int, AlgorithmId, OneAxisEllipsoid, Frame, InputStream)
-     * @see #Rugged(TileUpdater, int, AlgorithmId, EllipsoidId, InertialFrameId, BodyRotatingFrameId, InputStream)
-     */
-    public void dumpInterpolator(final OutputStream dumpStream) throws RuggedException {
-        try {
-            final ObjectOutputStream oos = new ObjectOutputStream(dumpStream);
-            oos.writeObject(scToBody);
-        } catch (IOException ioe) {
-            throw new RuggedException(ioe, LocalizedFormats.SIMPLE_MESSAGE, ioe.getMessage());
-        }
-    }
+        this.lightTimeCorrection         = lightTimeCorrection;
+        this.aberrationOfLightCorrection = aberrationOfLightCorrection;
 
-    /** Set flag for light time correction.
-     * <p>
-     * This methods set the flag for compensating or not light time between
-     * ground and spacecraft. Compensating this delay improves location
-     * accuracy and is enabled by default. Not compensating it is mainly useful
-     * for validation purposes against system that do not compensate it.
-     * </p>
-     * @param lightTimeCorrection if true, the light travel time between ground
-     * and spacecraft is compensated for more accurate location
-     * @see #isLightTimeCorrected()
-     * @see #setAberrationOfLightCorrection(boolean)
-     */
-    public void setLightTimeCorrection(final boolean lightTimeCorrection) {
-        this.lightTimeCorrection = lightTimeCorrection;
     }
 
     /** Get flag for light time correction.
      * @return true if the light time between ground and spacecraft is
      * compensated for more accurate location
-     * @see #setLightTimeCorrection(boolean)
      */
     public boolean isLightTimeCorrected() {
         return lightTimeCorrection;
     }
 
-    /** Set flag for aberration of light correction.
-     * <p>
-     * This methods set the flag for compensating or not aberration of light,
-     * which is velocity composition between light and spacecraft when the
-     * light from ground points reaches the sensor.
-     * Compensating this velocity composition improves location
-     * accuracy and is enabled by default. Not compensating it is useful
-     * in two cases: for validation purposes against system that do not
-     * compensate it or when the pixels line of sight already include the
-     * correction.
-     * </p>
-     * @param aberrationOfLightCorrection if true, the aberration of light
-     * is corrected for more accurate location
-     * @see #isAberrationOfLightCorrected()
-     * @see #setLightTimeCorrection(boolean)
-     */
-    public void setAberrationOfLightCorrection(final boolean aberrationOfLightCorrection) {
-        this.aberrationOfLightCorrection = aberrationOfLightCorrection;
-    }
-
     /** Get flag for aberration of light correction.
      * @return true if the aberration of light time is corrected
      * for more accurate location
-     * @see #setAberrationOfLightCorrection(boolean)
      */
     public boolean isAberrationOfLightCorrected() {
         return aberrationOfLightCorrection;
     }
 
-    /** Set up line sensor model.
-     * @param lineSensor line sensor model
-     */
-    public void addLineSensor(final LineSensor lineSensor) {
-        sensors.put(lineSensor.getName(), lineSensor);
-    }
-
     /** Get the line sensors.
      * @return line sensors
      */
@@ -602,126 +151,6 @@ public class Rugged {
         return scToBody.getMaxDate();
     }
 
-    /** Select inertial frame.
-     * @param inertialFrameId inertial frame identifier
-     * @return inertial frame
-     * @exception RuggedException if data needed for some frame cannot be loaded
-     */
-    private static Frame selectInertialFrame(final InertialFrameId inertialFrameId)
-        throws RuggedException {
-
-        try {
-            // set up the inertial frame
-            switch (inertialFrameId) {
-            case GCRF :
-                return FramesFactory.getGCRF();
-            case EME2000 :
-                return FramesFactory.getEME2000();
-            case MOD :
-                return FramesFactory.getMOD(IERSConventions.IERS_1996);
-            case TOD :
-                return FramesFactory.getTOD(IERSConventions.IERS_1996, true);
-            case VEIS1950 :
-                return FramesFactory.getVeis1950();
-            default :
-                // this should never happen
-                throw RuggedException.createInternalError(null);
-            }
-        } catch (OrekitException oe) {
-            throw new RuggedException(oe, oe.getSpecifier(), oe.getParts().clone());
-        }
-
-    }
-
-    /** Select body rotating frame.
-     * @param bodyRotatingFrame body rotating frame identifier
-     * @return body rotating frame
-     * @exception RuggedException if data needed for some frame cannot be loaded
-     */
-    private static Frame selectBodyRotatingFrame(final BodyRotatingFrameId bodyRotatingFrame)
-        throws RuggedException {
-
-        try {
-            // set up the rotating frame
-            switch (bodyRotatingFrame) {
-            case ITRF :
-                return FramesFactory.getITRF(IERSConventions.IERS_2010, true);
-            case ITRF_EQUINOX :
-                return FramesFactory.getITRFEquinox(IERSConventions.IERS_1996, true);
-            case GTOD :
-                return FramesFactory.getGTOD(IERSConventions.IERS_1996, true);
-            default :
-                // this should never happen
-                throw RuggedException.createInternalError(null);
-            }
-        } catch (OrekitException oe) {
-            throw new RuggedException(oe, oe.getSpecifier(), oe.getParts().clone());
-        }
-
-    }
-
-    /** Select ellipsoid.
-     * @param ellipsoidID reference ellipsoid identifier
-     * @param bodyFrame body rotating frame
-     * @return selected ellipsoid
-     */
-    private static OneAxisEllipsoid selectEllipsoid(final EllipsoidId ellipsoidID, final Frame bodyFrame) {
-
-        // set up the ellipsoid
-        switch (ellipsoidID) {
-        case GRS80 :
-            return new OneAxisEllipsoid(6378137.0, 1.0 / 298.257222101, bodyFrame);
-        case WGS84 :
-            return new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
-                                        Constants.WGS84_EARTH_FLATTENING,
-                                        bodyFrame);
-        case IERS96 :
-            return new OneAxisEllipsoid(6378136.49, 1.0 / 298.25645, bodyFrame);
-        case IERS2003 :
-            return new OneAxisEllipsoid(6378136.6, 1.0 / 298.25642, bodyFrame);
-        default :
-            // this should never happen
-            throw RuggedException.createInternalError(null);
-        }
-
-    }
-
-    /** Convert a {@link OneAxisEllipsoid} into a {@link ExtendedEllipsoid}.
-     * @param ellipsoid ellpsoid to extend
-     * @return extended ellipsoid
-     */
-    private static ExtendedEllipsoid extend(final OneAxisEllipsoid ellipsoid) {
-        return new ExtendedEllipsoid(ellipsoid.getEquatorialRadius(),
-                                     ellipsoid.getFlattening(),
-                                     ellipsoid.getBodyFrame());
-    }
-
-    /** Select DEM intersection algorithm.
-     * @param algorithmID intersection algorithm identifier
-     * @param updater updater used to load Digital Elevation Model tiles
-     * @param maxCachedTiles maximum number of tiles stored in the cache
-     * @return selected algorithm
-     */
-    private IntersectionAlgorithm selectAlgorithm(final AlgorithmId algorithmID,
-                                                  final TileUpdater updater, final int maxCachedTiles) {
-
-        // set up the algorithm
-        switch (algorithmID) {
-        case DUVENHAGE :
-            return new DuvenhageAlgorithm(updater, maxCachedTiles, false);
-        case DUVENHAGE_FLAT_BODY :
-            return new DuvenhageAlgorithm(updater, maxCachedTiles, true);
-        case BASIC_SLOW_EXHAUSTIVE_SCAN_FOR_TESTS_ONLY :
-            return new BasicScanAlgorithm(updater, maxCachedTiles);
-        case IGNORE_DEM_USE_ELLIPSOID :
-            return new IgnoreDEMAlgorithm();
-        default :
-            // this should never happen
-            throw RuggedException.createInternalError(null);
-        }
-
-    }
-
     /** Check if a date is in the supported range.
      * <p>
      * The supporte range is given by the {@code minDate} and
diff --git a/src/main/java/org/orekit/rugged/api/RuggedBuilder.java b/src/main/java/org/orekit/rugged/api/RuggedBuilder.java
new file mode 100644
index 00000000..e5423c84
--- /dev/null
+++ b/src/main/java/org/orekit/rugged/api/RuggedBuilder.java
@@ -0,0 +1,726 @@
+/* Copyright 2013-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.api;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
+import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
+import org.orekit.bodies.OneAxisEllipsoid;
+import org.orekit.errors.OrekitException;
+import org.orekit.errors.PropagationException;
+import org.orekit.frames.Frame;
+import org.orekit.frames.FramesFactory;
+import org.orekit.propagation.Propagator;
+import org.orekit.propagation.SpacecraftState;
+import org.orekit.propagation.sampling.OrekitFixedStepHandler;
+import org.orekit.rugged.intersection.BasicScanAlgorithm;
+import org.orekit.rugged.intersection.IgnoreDEMAlgorithm;
+import org.orekit.rugged.intersection.IntersectionAlgorithm;
+import org.orekit.rugged.intersection.duvenhage.DuvenhageAlgorithm;
+import org.orekit.rugged.raster.TileUpdater;
+import org.orekit.rugged.utils.ExtendedEllipsoid;
+import org.orekit.rugged.utils.SpacecraftToObservedBody;
+import org.orekit.time.AbsoluteDate;
+import org.orekit.utils.AngularDerivativesFilter;
+import org.orekit.utils.CartesianDerivativesFilter;
+import org.orekit.utils.Constants;
+import org.orekit.utils.IERSConventions;
+import org.orekit.utils.PVCoordinates;
+import org.orekit.utils.TimeStampedAngularCoordinates;
+import org.orekit.utils.TimeStampedPVCoordinates;
+
+/** Builder for {@link Rugged} instances.
+ * <p>
+ * This class implements the <em>builder pattern</em> to create{@link Rugged} instances.
+ * It does so by using a <em>fluent API</em> in order to clarify reading and allow
+ * later extensions with new configuration parameters.
+ * </p>
+ * <p>
+ * A typical use would be:
+ * </p>
+ * <pre>
+ *   Rugged rugged = new RuggedBuilder().
+ *                   setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+ *                   setAlgorithmID(AlgorithmId.Duvenhage).
+ *                   setDigitalElevationModel(tileUpdater, maxCachedTiles).
+ *                   setTimeSpan(minDate, maxDate, tStep, overshootTolerance).
+ *                   setTrajectory(positionsVelocities, pvInterpolationNumber, pvFilter,
+ *                                 quaternions, aInterpolationNumber, aFilter).
+ *                   addLineSensor(sensor1).
+ *                   addLineSensor(sensor2).
+ *                   addLineSensor(sensor3).
+ *                   build();
+ * </pre>
+ * <p>
+ * If a configuration parameter has not been set prior to the call to {]link #build()}, then
+ * an exception will be triggered with an explicit error message.
+ * </p>
+ * @see <a href="https://en.wikipedia.org/wiki/Builder_pattern">Builder pattern (wikipedia)</a>
+ * @see <a href="https://en.wikipedia.org/wiki/Fluent_interface">Fluent interface (wikipedia)</a>
+ * @author Luc Maisonobe
+ */
+public class RuggedBuilder {
+
+    /** Reference ellipsoid. */
+    private ExtendedEllipsoid ellipsoid;
+
+    /** DEM intersection algorithm. */
+    private AlgorithmId algorithmID;
+
+    /** Updater used to load Digital Elevation Model tiles. */
+    private TileUpdater tileUpdater;
+
+    /** Maximum number of tiles stored in the cache. */
+    private int maxCachedTiles;
+
+    /** Start of search time span. */
+    private AbsoluteDate startDate;
+
+    /** End of search time span. */
+    private AbsoluteDate endDate;
+
+    /** Step to use for inertial frame to body frame transforms cache computations. */
+    private double step;
+
+    /** OvershootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting. */
+    private double tolerance;
+
+    /** Inertial frame for position/velocity/attitude. */
+    private Frame inertial;
+
+    /** Satellite position and velocity (m and m/s in inertial frame). */
+    private List<TimeStampedPVCoordinates> pvSample;
+
+    /** Number of points to use for position/velocity interpolation. */
+    private int pvNeighborsSize;
+
+    /** Filter for derivatives from the sample to use in position/velocity interpolation. */
+    private CartesianDerivativesFilter pvDerivatives;
+
+    /** Satellite quaternions with respect to inertial frame. */
+    private List<TimeStampedAngularCoordinates> aSample;
+
+    /** Number of points to use for attitude interpolation. */
+    private int aNeighborsSize;
+
+    /** Filter for derivatives from the sample to use in attitude interpolation. */
+    private AngularDerivativesFilter aDerivatives;
+
+    /** Propagator for position/velocity/attitude. */
+    private Propagator pvaPropagator;
+
+    /** Step to use for inertial/Earth/spacraft transforms interpolations. */
+    private double iStep;
+
+    /** Number of points to use for inertial/Earth/spacraft transforms interpolations. */
+    private int iN;
+
+    /** Converter between spacecraft and body. */
+    private SpacecraftToObservedBody scToBody;
+
+    /** Flag for light time correction. */
+    private boolean lightTimeCorrection;
+
+    /** Flag for aberration of light correction. */
+    private boolean aberrationOfLightCorrection;
+
+    /** Sensors. */
+    private final List<LineSensor> sensors;
+
+    /** Create a non-configured builder.
+     * <p>
+     * The builder <em>must</em> be configured before calling the
+     * {@link #build} method, otherwise an exception will be triggered
+     * at build time.
+     * </p>
+     */
+    public RuggedBuilder() {
+        sensors                     = new ArrayList<LineSensor>();
+        lightTimeCorrection         = true;
+        aberrationOfLightCorrection = true;
+    }
+
+    /** Set the reference ellipsoid.
+     * @param ellipsoidID reference ellipsoid
+     * @param bodyRotatingFrameID body rotating frame identifier
+     * @exception RuggedException if data needed for some frame cannot be loaded
+     * or if trajectory has been {@link #setTrajectory(InputStream) recovered}
+     * from an earlier run and frames mismatch
+     * @return the builder instance
+     * @see #setEllipsoid(OneAxisEllipsoid)
+     */
+    public RuggedBuilder setEllipsoid(final EllipsoidId ellipsoidID, final BodyRotatingFrameId bodyRotatingFrameID)
+        throws RuggedException {
+        return setEllipsoid(selectEllipsoid(ellipsoidID, selectBodyRotatingFrame(bodyRotatingFrameID)));
+    }
+
+    /** Set the reference ellipsoid.
+     * @param ellipsoid reference ellipsoid
+     * @return the builder instance
+     * @see #setEllipsoid(EllipsoidId, BodyRotatingFrameId)
+     * @exception RuggedException if trajectory has been
+     * {@link #setTrajectory(InputStream) recovered} from an earlier run and frames
+     * mismatch
+     */
+    // CHECKSTYLE: stop HiddenField check
+    public RuggedBuilder setEllipsoid(final OneAxisEllipsoid ellipsoid)
+        throws RuggedException {
+        // CHECKSTYLE: resume HiddenField check
+        this.ellipsoid = new ExtendedEllipsoid(ellipsoid.getEquatorialRadius(),
+                                               ellipsoid.getFlattening(),
+                                               ellipsoid.getBodyFrame());
+        checkFramesConsistency();
+        return this;
+    }
+
+    /** Set the algorithm to use for Digital Elevation Model intersection.
+     * <p>
+     * Note that when the specified algorithm is {@link AlgorithmId#IGNORE_DEM_USE_ELLIPSOID},
+     * then calling {@link #setDigitalElevationModel(TileUpdater, int)} is irrelevant and can either
+     * be completely avoided, or the method can be called with an updater set to {@code null}.
+     * For all other algorithms, the updater must be properly configured.
+     * </p>
+     * @param algorithmID identifier of algorithm to use for Digital Elevation Model intersection
+     * @return the builder instance
+     * @see #setDigitalElevationModel(TileUpdater, int)
+     */
+    // CHECKSTYLE: stop HiddenField check
+    public RuggedBuilder setAlgorithm(final AlgorithmId algorithmID) {
+        // CHECKSTYLE: resume HiddenField check
+        this.algorithmID = algorithmID;
+        return this;
+    }
+
+    /** Set the user-provided {@link TileUpdater tile updater}.
+     * <p>
+     * Note that when the algorithm specified in {@link #setAlgorithm(AlgorithmId)}
+     * is {@link AlgorithmId#IGNORE_DEM_USE_ELLIPSOID},then this method becomes irrelevant
+     * and can either be not called at all, or it can be called with an updater set to
+     * {@code null}. For all other algorithms, the updater must be properly configured.
+     * </p>
+     * @param tileUpdater updater used to load Digital Elevation Model tiles
+     * @param maxCachedTiles maximum number of tiles stored in the cache
+     * @return the builder instance
+     * @see #setAlgorithm(AlgorithmId)
+     */
+    // CHECKSTYLE: stop HiddenField check
+    public RuggedBuilder setDigitalElevationModel(final TileUpdater tileUpdater, final int maxCachedTiles) {
+        // CHECKSTYLE: resume HiddenField check
+        this.tileUpdater    = tileUpdater;
+        this.maxCachedTiles = maxCachedTiles;
+        return this;
+    }
+
+    /** Set the time span to be covered for direct and inverse location calls.
+     * @param minDate start of search time span
+     * @param maxDate end of search time span
+     * @param tStep step to use for inertial frame to body frame transforms cache computations
+     * @param overshootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting
+     * @return the builder instance
+     */
+    public RuggedBuilder setTimeSpan(final AbsoluteDate minDate, final AbsoluteDate maxDate,
+                                     final double tStep, final double overshootTolerance) {
+        this.startDate = minDate;
+        this.endDate   = maxDate;
+        this.step      = tStep;
+        this.tolerance = overshootTolerance;
+        return this;
+    }
+
+    /** Set the spacecraft trajectory.
+     * @param inertialFrame inertial frame used for spacecraft positions/velocities/quaternions
+     * @param positionsVelocities satellite position and velocity (m and m/s in inertial frame)
+     * @param pvInterpolationNumber number of points to use for position/velocity interpolation
+     * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
+     * @param quaternions satellite quaternions with respect to inertial frame
+     * @param aInterpolationNumber number of points to use for attitude interpolation
+     * @param aFilter filter for derivatives from the sample to use in attitude interpolation
+     * @return the builder instance
+     * @exception RuggedException if data needed for inertial frame cannot be loaded
+     * @see #setTrajectory(Frame, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)
+     * @see #setTrajectory(double, int, CartesianDerivativesFilter, AngularDerivativesFilter, Propagator)
+     * @see #setTrajectory(InputStream)
+     */
+    public RuggedBuilder setTrajectory(final InertialFrameId inertialFrame,
+                                       final List<TimeStampedPVCoordinates> positionsVelocities, final int pvInterpolationNumber,
+                                       final CartesianDerivativesFilter pvFilter,
+                                       final List<TimeStampedAngularCoordinates> quaternions, final int aInterpolationNumber,
+                                       final AngularDerivativesFilter aFilter)
+        throws RuggedException {
+        return setTrajectory(selectInertialFrame(inertialFrame),
+                             positionsVelocities, pvInterpolationNumber, pvFilter,
+                             quaternions, aInterpolationNumber, aFilter);
+    }
+
+    /** Set the spacecraft trajectory.
+     * @param inertialFrame inertial frame used for spacecraft positions/velocities/quaternions
+     * @param positionsVelocities satellite position and velocity (m and m/s in inertial frame)
+     * @param pvInterpolationNumber number of points to use for position/velocity interpolation
+     * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
+     * @param quaternions satellite quaternions with respect to inertial frame
+     * @param aInterpolationNumber number of points to use for attitude interpolation
+     * @param aFilter filter for derivatives from the sample to use in attitude interpolation
+     * @return the builder instance
+     * @see #setTrajectory(InertialFrameId, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)
+     * @see #setTrajectory(double, int, CartesianDerivativesFilter, AngularDerivativesFilter, Propagator)
+     * @see #setTrajectory(InputStream)
+     */
+    public RuggedBuilder setTrajectory(final Frame inertialFrame,
+                                       final List<TimeStampedPVCoordinates> positionsVelocities, final int pvInterpolationNumber,
+                                       final CartesianDerivativesFilter pvFilter,
+                                       final List<TimeStampedAngularCoordinates> quaternions, final int aInterpolationNumber,
+                                       final AngularDerivativesFilter aFilter) {
+        this.inertial        = inertialFrame;
+        this.pvSample        = positionsVelocities;
+        this.pvNeighborsSize = pvInterpolationNumber;
+        this.pvDerivatives   = pvFilter;
+        this.aSample         = quaternions;
+        this.aNeighborsSize  = aInterpolationNumber;
+        this.aDerivatives    = aFilter;
+        this.pvaPropagator   = null;
+        this.iStep           = Double.NaN;
+        this.iN              = -1;
+        this.scToBody        = null;
+        return this;
+    }
+
+    /** Set the spacecraft trajectory.
+     * @param interpolationStep step to use for inertial/Earth/spacraft transforms interpolations
+     * @param interpolationNumber number of points to use for inertial/Earth/spacraft transforms interpolations
+     * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
+     * @param aFilter filter for derivatives from the sample to use in attitude interpolation
+     * @param propagator global propagator
+     * @return the builder instance
+     * @see #setTrajectory(InertialFrameId, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)
+     * @see #setTrajectory(Frame, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)
+     * @see #setTrajectory(InputStream)
+     */
+    public RuggedBuilder setTrajectory(final double interpolationStep, final int interpolationNumber,
+                                       final CartesianDerivativesFilter pvFilter, final AngularDerivativesFilter aFilter,
+                                       final Propagator propagator) {
+        this.inertial        = propagator.getFrame();
+        this.pvSample        = null;
+        this.pvNeighborsSize = -1;
+        this.pvDerivatives   = pvFilter;
+        this.aSample         = null;
+        this.aNeighborsSize  = -1;
+        this.aDerivatives    = aFilter;
+        this.pvaPropagator   = propagator;
+        this.iStep           = interpolationStep;
+        this.iN              = interpolationNumber;
+        this.scToBody        = null;
+        return this;
+    }
+
+    /** Set the spacecraft trajectory.
+     * @param dumpStream stream from where to read previous instance dumped interpolator
+     * (caller opened it and remains responsible for closing it)
+     * @return the builder instance
+     * @exception RuggedException if dump file cannot be loaded
+     * or if frames do not match the ones referenced in the dump file
+     * @see #setTrajectory(InertialFrameId, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)
+     * @see #setTrajectory(Frame, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)
+     * @see #setTrajectory(double, int, CartesianDerivativesFilter, AngularDerivativesFilter, Propagator)
+     * @see #dumpInterpolator(OutputStream)
+     */
+    public RuggedBuilder setTrajectory(final InputStream dumpStream)
+        throws RuggedException {
+        try {
+            this.inertial        = null;
+            this.pvSample        = null;
+            this.pvNeighborsSize = -1;
+            this.pvDerivatives   = null;
+            this.aSample         = null;
+            this.aNeighborsSize  = -1;
+            this.aDerivatives    = null;
+            this.pvaPropagator   = null;
+            this.iStep           = Double.NaN;
+            this.iN              = -1;
+            this.scToBody        = (SpacecraftToObservedBody) new ObjectInputStream(dumpStream).readObject();
+            checkFramesConsistency();
+            return this;
+        } catch (ClassNotFoundException cnfe) {
+            throw new RuggedException(cnfe, RuggedMessages.NOT_INTERPOLATOR_DUMP_DATA);
+        } catch (ClassCastException cce) {
+            throw new RuggedException(cce, RuggedMessages.NOT_INTERPOLATOR_DUMP_DATA);
+        } catch (IOException ioe) {
+            throw new RuggedException(ioe, RuggedMessages.NOT_INTERPOLATOR_DUMP_DATA);
+        }
+    }
+
+    /** Dump frames transform interpolator.
+     * <p>
+     * This method allows to reuse the interpolator built in one instance, to build
+     * another instance by calling {@link #setTrajectory(InputStream)}.
+     * This reduces the builder initialization time as setting up the interpolator can be long, it is
+     * mainly intended to be used when several runs are done (for example in an image processing chain)
+     * with the same configuration.
+     * </p>
+     * <p>
+     * This method must be called <em>after</em> both the ellipsoid and trajectory have been set.
+     * </p>
+     * @param dumpStream stream where to dump the interpolator
+     * (caller opened it and remains responsible for closing it)
+     * @exception RuggedException if interpolator cannot be written to file
+     * @see #setEllipsoid(EllipsoidId, BodyRotatingFrameId)
+     * @see #setEllipsoid(OneAxisEllipsoid)
+     * @see #setTrajectory(InertialFrameId, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)
+     * @see #setTrajectory(Frame, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)
+     * @see #setTrajectory(double, int, CartesianDerivativesFilter, AngularDerivativesFilter, Propagator)
+     * @see #setTrajectory(InputStream)
+     */
+    public void dumpInterpolator(final OutputStream dumpStream) throws RuggedException {
+        try {
+            createInterpolatorIfNeeded();
+            new ObjectOutputStream(dumpStream).writeObject(scToBody);
+        } catch (IOException ioe) {
+            throw new RuggedException(ioe, LocalizedFormats.SIMPLE_MESSAGE, ioe.getMessage());
+        }
+    }
+
+    /** Check frames consistency.
+     * @exception RuggedException if frames have been set both by direct calls and by
+     * deserializing an interpolator dump and a mismatch occurs
+     */
+    private void checkFramesConsistency() throws RuggedException {
+        if (ellipsoid != null && scToBody != null &&
+            !ellipsoid.getBodyFrame().getName().equals(scToBody.getBodyFrameName())) {
+            throw new RuggedException(RuggedMessages.FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP,
+                                      ellipsoid.getBodyFrame().getName(), scToBody.getBodyFrameName());
+        }
+    }
+
+    /** Create a transform interpolator if needed.
+     * @exception RuggedException if data needed for some frame cannot be loaded or if position
+     * or attitude samples do not fully cover the [{@code minDate}, {@code maxDate}] search time span,
+     * or propagator fails.
+     */
+    private void createInterpolatorIfNeeded() throws RuggedException {
+
+        if (ellipsoid == null) {
+            throw new RuggedException(RuggedMessages.UNINITIALIZED_CONTEXT, "RuggedBuilder.setEllipsoid()");
+        }
+
+        if (scToBody == null) {
+            if (pvSample != null) {
+                scToBody = createInterpolator(inertial, ellipsoid.getBodyFrame(),
+                                              startDate, endDate, step, tolerance,
+                                              pvSample, pvNeighborsSize, pvDerivatives,
+                                              aSample, aNeighborsSize, aDerivatives);
+            } else if (pvaPropagator != null) {
+                scToBody = createInterpolator(inertial, ellipsoid.getBodyFrame(),
+                                              startDate, endDate, step, tolerance,
+                                              iStep, iN, pvDerivatives, aDerivatives, pvaPropagator);
+            } else {
+                throw new RuggedException(RuggedMessages.UNINITIALIZED_CONTEXT, "RuggedBuilder.setTrajectory()");
+            }
+        }
+
+    }
+
+    /** Create a transform interpolator from positions and quaternions lists.
+     * @param inertialFrame inertial frame
+     * @param bodyFrame observed body frame
+     * @param minDate start of search time span
+     * @param maxDate end of search time span
+     * @param tStep step to use for inertial frame to body frame transforms cache computations
+     * @param overshootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting
+     * @param positionsVelocities satellite position and velocity
+     * @param pvInterpolationNumber number of points to use for position/velocity interpolation
+     * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
+     * @param quaternions satellite quaternions
+     * @param aInterpolationNumber number of points to use for attitude interpolation
+     * @param aFilter filter for derivatives from the sample to use in attitude interpolation
+     * @return transforms interpolator
+     * @exception RuggedException if data needed for some frame cannot be loaded or if position
+     * or attitude samples do not fully cover the [{@code minDate}, {@code maxDate}] search time span
+     */
+    private static SpacecraftToObservedBody createInterpolator(final Frame inertialFrame, final Frame bodyFrame,
+                                                               final AbsoluteDate minDate, final AbsoluteDate maxDate,
+                                                               final double tStep, final double overshootTolerance,
+                                                               final List<TimeStampedPVCoordinates> positionsVelocities,
+                                                               final int pvInterpolationNumber,
+                                                               final CartesianDerivativesFilter pvFilter,
+                                                               final List<TimeStampedAngularCoordinates> quaternions,
+                                                               final int aInterpolationNumber,
+                                                               final AngularDerivativesFilter aFilter)
+        throws RuggedException {
+        return new SpacecraftToObservedBody(inertialFrame, bodyFrame,
+                                            minDate, maxDate, tStep,
+                                            overshootTolerance, positionsVelocities, pvInterpolationNumber,
+                                            pvFilter, quaternions, aInterpolationNumber,
+                                            aFilter);
+    }
+
+    /** Create a transform interpolator from a propagator.
+     * @param inertialFrame inertial frame
+     * @param bodyFrame observed body frame
+     * @param minDate start of search time span
+     * @param maxDate end of search time span
+     * @param tStep step to use for inertial frame to body frame transforms cache computations
+     * @param overshootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting
+     * @param interpolationStep step to use for inertial/Earth/spacraft transforms interpolations
+     * @param interpolationNumber number of points of to use for inertial/Earth/spacraft transforms interpolations
+     * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
+     * @param aFilter filter for derivatives from the sample to use in attitude interpolation
+     * @param propagator global propagator
+     * @return transforms interpolator
+     * @exception RuggedException if data needed for some frame cannot be loaded
+     */
+    private static SpacecraftToObservedBody createInterpolator(final Frame inertialFrame, final Frame bodyFrame,
+                                                               final AbsoluteDate minDate, final AbsoluteDate maxDate,
+                                                               final double tStep, final double overshootTolerance,
+                                                               final double interpolationStep, final int interpolationNumber,
+                                                               final CartesianDerivativesFilter pvFilter,
+                                                               final AngularDerivativesFilter aFilter,
+                                                               final Propagator propagator)
+        throws RuggedException {
+        try {
+
+            // extract position/attitude samples from propagator
+            final List<TimeStampedPVCoordinates> positionsVelocities =
+                    new ArrayList<TimeStampedPVCoordinates>();
+            final List<TimeStampedAngularCoordinates> quaternions =
+                    new ArrayList<TimeStampedAngularCoordinates>();
+            propagator.setMasterMode(interpolationStep, new OrekitFixedStepHandler() {
+
+                /** {@inheritDoc} */
+                @Override
+                public void init(final SpacecraftState s0, final AbsoluteDate t) {
+                }
+
+                /** {@inheritDoc} */
+                @Override
+                public void handleStep(final SpacecraftState currentState, final boolean isLast)
+                    throws PropagationException {
+                    try {
+                        final AbsoluteDate  date = currentState.getDate();
+                        final PVCoordinates pv   = currentState.getPVCoordinates(inertialFrame);
+                        final Rotation      q    = currentState.getAttitude().getRotation();
+                        positionsVelocities.add(new TimeStampedPVCoordinates(date, pv.getPosition(), pv.getVelocity(), Vector3D.ZERO));
+                        quaternions.add(new TimeStampedAngularCoordinates(date, q, Vector3D.ZERO, Vector3D.ZERO));
+                    } catch (OrekitException oe) {
+                        throw new PropagationException(oe);
+                    }
+                }
+
+            });
+            propagator.propagate(minDate.shiftedBy(-interpolationStep), maxDate.shiftedBy(interpolationStep));
+
+            // orbit/attitude to body converter
+            return createInterpolator(inertialFrame, bodyFrame,
+                                      minDate, maxDate, tStep,
+                                      overshootTolerance, positionsVelocities, interpolationNumber,
+                                      pvFilter, quaternions, interpolationNumber,
+                                      aFilter);
+
+        } catch (PropagationException pe) {
+            throw new RuggedException(pe, pe.getSpecifier(), pe.getParts());
+        }
+    }
+
+    /** Set flag for light time correction.
+     * <p>
+     * This methods set the flag for compensating or not light time between
+     * ground and spacecraft. Compensating this delay improves location
+     * accuracy and is <em>enabled</em> by default (i.e. not calling this
+     * method before building is therefore equivalent to calling it with
+     * a parameter set to {@code true}). Not compensating it is mainly useful
+     * for validation purposes against system that do not compensate it.
+     * </p>
+     * @param lightTimeCorrection if true, the light travel time between ground
+     * and spacecraft is compensated for more accurate location
+     * @return the builder instance
+     * @see #setAberrationOfLightCorrection(boolean)
+     */
+    // CHECKSTYLE: stop HiddenField check
+    public RuggedBuilder setLightTimeCorrection(final boolean lightTimeCorrection) {
+        // CHECKSTYLE: resume HiddenField check
+        this.lightTimeCorrection = lightTimeCorrection;
+        return this;
+    }
+
+    /** Set flag for aberration of light correction.
+     * <p>
+     * This methods set the flag for compensating or not aberration of light,
+     * which is velocity composition between light and spacecraft when the
+     * light from ground points reaches the sensor.
+     * Compensating this velocity composition improves location
+     * accuracy and is <em>enabled</em> by default (i.e. not calling this
+     * method before building is therefore equivalent to calling it with
+     * a parameter set to {@code true}). Not compensating it is useful
+     * in two cases: for validation purposes against system that do not
+     * compensate it or when the pixels line of sight already include the
+     * correction.
+     * </p>
+     * @param aberrationOfLightCorrection if true, the aberration of light
+     * is corrected for more accurate location
+     * @return the builder instance
+     * @see #setLightTimeCorrection(boolean)
+     */
+    // CHECKSTYLE: stop HiddenField check
+    public RuggedBuilder setAberrationOfLightCorrection(final boolean aberrationOfLightCorrection) {
+        // CHECKSTYLE: resume HiddenField check
+        this.aberrationOfLightCorrection = aberrationOfLightCorrection;
+        return this;
+    }
+
+    /** Set up line sensor model.
+     * @param lineSensor line sensor model
+     * @return the builder instance
+     */
+    public RuggedBuilder addLineSensor(final LineSensor lineSensor) {
+        sensors.add(lineSensor);
+        return this;
+    }
+
+    /** Select inertial frame.
+     * @param inertialFrameId inertial frame identifier
+     * @return inertial frame
+     * @exception RuggedException if data needed for some frame cannot be loaded
+     */
+    private static Frame selectInertialFrame(final InertialFrameId inertialFrameId)
+        throws RuggedException {
+
+        try {
+            // set up the inertial frame
+            switch (inertialFrameId) {
+            case GCRF :
+                return FramesFactory.getGCRF();
+            case EME2000 :
+                return FramesFactory.getEME2000();
+            case MOD :
+                return FramesFactory.getMOD(IERSConventions.IERS_1996);
+            case TOD :
+                return FramesFactory.getTOD(IERSConventions.IERS_1996, true);
+            case VEIS1950 :
+                return FramesFactory.getVeis1950();
+            default :
+                // this should never happen
+                throw RuggedException.createInternalError(null);
+            }
+        } catch (OrekitException oe) {
+            throw new RuggedException(oe, oe.getSpecifier(), oe.getParts().clone());
+        }
+
+    }
+
+    /** Select body rotating frame.
+     * @param bodyRotatingFrame body rotating frame identifier
+     * @return body rotating frame
+     * @exception RuggedException if data needed for some frame cannot be loaded
+     */
+    private static Frame selectBodyRotatingFrame(final BodyRotatingFrameId bodyRotatingFrame)
+        throws RuggedException {
+
+        try {
+            // set up the rotating frame
+            switch (bodyRotatingFrame) {
+            case ITRF :
+                return FramesFactory.getITRF(IERSConventions.IERS_2010, true);
+            case ITRF_EQUINOX :
+                return FramesFactory.getITRFEquinox(IERSConventions.IERS_1996, true);
+            case GTOD :
+                return FramesFactory.getGTOD(IERSConventions.IERS_1996, true);
+            default :
+                // this should never happen
+                throw RuggedException.createInternalError(null);
+            }
+        } catch (OrekitException oe) {
+            throw new RuggedException(oe, oe.getSpecifier(), oe.getParts().clone());
+        }
+
+    }
+
+    /** Select ellipsoid.
+     * @param ellipsoidID reference ellipsoid identifier
+     * @param bodyFrame body rotating frame
+     * @return selected ellipsoid
+     */
+    private static OneAxisEllipsoid selectEllipsoid(final EllipsoidId ellipsoidID, final Frame bodyFrame) {
+
+        // set up the ellipsoid
+        switch (ellipsoidID) {
+        case GRS80 :
+            return new OneAxisEllipsoid(6378137.0, 1.0 / 298.257222101, bodyFrame);
+        case WGS84 :
+            return new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
+                                        Constants.WGS84_EARTH_FLATTENING,
+                                        bodyFrame);
+        case IERS96 :
+            return new OneAxisEllipsoid(6378136.49, 1.0 / 298.25645, bodyFrame);
+        case IERS2003 :
+            return new OneAxisEllipsoid(6378136.6, 1.0 / 298.25642, bodyFrame);
+        default :
+            // this should never happen
+            throw RuggedException.createInternalError(null);
+        }
+
+    }
+
+    /** Create DEM intersection algorithm.
+     * @param algorithmID intersection algorithm identifier
+     * @param updater updater used to load Digital Elevation Model tiles
+     * @param maxCachedTiles maximum number of tiles stored in the cache
+     * @return selected algorithm
+     */
+    private static IntersectionAlgorithm createAlgorithm(final AlgorithmId algorithmID,
+                                                         final TileUpdater updater, final int maxCachedTiles) {
+
+        // set up the algorithm
+        switch (algorithmID) {
+        case DUVENHAGE :
+            return new DuvenhageAlgorithm(updater, maxCachedTiles, false);
+        case DUVENHAGE_FLAT_BODY :
+            return new DuvenhageAlgorithm(updater, maxCachedTiles, true);
+        case BASIC_SLOW_EXHAUSTIVE_SCAN_FOR_TESTS_ONLY :
+            return new BasicScanAlgorithm(updater, maxCachedTiles);
+        case IGNORE_DEM_USE_ELLIPSOID :
+            return new IgnoreDEMAlgorithm();
+        default :
+            // this should never happen
+            throw RuggedException.createInternalError(null);
+        }
+
+    }
+
+    /** Build a {@link Rugged} instance.
+     * @return built instance
+     * @exception RuggedException if the builder is not properly configured
+     * or if some internal elements cannot be built (frames, ephemerides, ...)
+     */
+    public Rugged build() throws RuggedException {
+        if (algorithmID == null) {
+            throw new RuggedException(RuggedMessages.UNINITIALIZED_CONTEXT, "RuggedBuilder.setAlgorithmID()");
+        }
+        if (algorithmID != AlgorithmId.IGNORE_DEM_USE_ELLIPSOID && (tileUpdater == null)) {
+            throw new RuggedException(RuggedMessages.UNINITIALIZED_CONTEXT, "RuggedBuilder.setDigitalElevationModel()");
+        }
+        createInterpolatorIfNeeded();
+        return new Rugged(createAlgorithm(algorithmID, tileUpdater, maxCachedTiles), ellipsoid,
+                          lightTimeCorrection, aberrationOfLightCorrection, scToBody, sensors);
+    }
+
+}
diff --git a/src/main/java/org/orekit/rugged/api/RuggedMessages.java b/src/main/java/org/orekit/rugged/api/RuggedMessages.java
index 47223e49..1e8f5dc2 100644
--- a/src/main/java/org/orekit/rugged/api/RuggedMessages.java
+++ b/src/main/java/org/orekit/rugged/api/RuggedMessages.java
@@ -55,7 +55,7 @@ public enum RuggedMessages implements Localizable {
     OUT_OF_TILE_ANGLES("no data at latitude {0} and longitude {1}, tile covers only latitudes {2} to {3} and longitudes {4} to {5}"),
     TILE_WITHOUT_REQUIRED_NEIGHBORS_SELECTED("the tile selected for latitude {0} and longitude {1} does not contain required point neighborhood"),
     OUT_OF_TIME_RANGE("date {0} is out of time span [{1}, {2}]"),
-    UNINITIALIZED_CONTEXT("general context has not been initialized"),
+    UNINITIALIZED_CONTEXT("general context has not been initialized (missing call to {0})"),
     EMPTY_TILE("tile is empty: {0} ⨉ {1}"),
     UNKNOWN_SENSOR("unknown sensor {0}"),
     LINE_OF_SIGHT_DOES_NOT_REACH_GROUND("line-of-sight does not reach ground"),
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 5b6aade0..62efdb1d 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_de.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_de.utf8
@@ -13,8 +13,8 @@ TILE_WITHOUT_REQUIRED_NEIGHBORS_SELECTED = <MISSING TRANSLATION>
 # date {0} is out of time span [{1}, {2}]
 OUT_OF_TIME_RANGE = Angabe {0} ist außerhalb der Zeitspanne [{1}, {2}]
 
-# general context has not been initialized
-UNINITIALIZED_CONTEXT = der allgemeine Zusammenhang kann nicht initialisiert (gestartet/geladen) werden
+# general context has not been initialized (missing call to {0})
+UNINITIALIZED_CONTEXT = <MISSING TRANSLATION>
 
 # tile is empty: {0} * {1}
 EMPTY_TILE = die Ziegel ist leer: {0} * {1}
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 9cfe08af..1c6fb33b 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8
@@ -13,8 +13,8 @@ TILE_WITHOUT_REQUIRED_NEIGHBORS_SELECTED = the tile selected for latitude {0} an
 # date {0} is out of time span [{1}, {2}]
 OUT_OF_TIME_RANGE = date {0} is out of time span [{1}, {2}]
 
-# general context has not been initialized
-UNINITIALIZED_CONTEXT = general context has not been initialized
+# general context has not been initialized (missing call to {0})
+UNINITIALIZED_CONTEXT = general context has not been initialized (missing call to {0})
 
 # tile is empty: {0} ⨉ {1}
 EMPTY_TILE = tile is empty: {0} ⨉ {1}
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 93f48949..e1a7beca 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_es.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_es.utf8
@@ -13,8 +13,8 @@ TILE_WITHOUT_REQUIRED_NEIGHBORS_SELECTED = <MISSING TRANSLATION>
 # date {0} is out of time span [{1}, {2}]
 OUT_OF_TIME_RANGE = la fecha {0} está fuera del intervalo temporal [{1}, {2}]
 
-# general context has not been initialized
-UNINITIALIZED_CONTEXT = el contexto general no ha sido inicializado
+# general context has not been initialized (missing call to {0})
+UNINITIALIZED_CONTEXT = <MISSING TRANSLATION>
 
 # tile is empty: {0} ⨉ {1}
 EMPTY_TILE = la faceta está vacía: {0} ⨉ {1}
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 5e13858c..96e63b5a 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8
@@ -13,8 +13,8 @@ TILE_WITHOUT_REQUIRED_NEIGHBORS_SELECTED = la tuile sélectionnée pour la latit
 # date {0} is out of time span [{1}, {2}]
 OUT_OF_TIME_RANGE = la date {0} est hors de la plage temporelle [{1}, {2}]
 
-# general context has not been initialized
-UNINITIALIZED_CONTEXT = le contexte général n''a pas été initialisé
+# general context has not been initialized (missing call to {0})
+UNINITIALIZED_CONTEXT = le contexte général n''a pas été initialisé (appel manquant à {0})
 
 # tile is empty: {0} ⨉ {1}
 EMPTY_TILE = la tuile est vide : {0} ⨉ {1}
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 2568ab8d..15d699a1 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_gl.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_gl.utf8
@@ -13,8 +13,8 @@ TILE_WITHOUT_REQUIRED_NEIGHBORS_SELECTED = <MISSING TRANSLATION>
 # date {0} is out of time span [{1}, {2}]
 OUT_OF_TIME_RANGE = a data {0} está fora do intervalo temporal [{1}, {2}]
 
-# general context has not been initialized
-UNINITIALIZED_CONTEXT = o contexto xeral non foi inicializado
+# general context has not been initialized (missing call to {0})
+UNINITIALIZED_CONTEXT = <MISSING TRANSLATION>
 
 # tile is empty: {0} ⨉ {1}
 EMPTY_TILE = a faceta está baleira: {0} ⨉ {1}
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 76513623..265dfcce 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_it.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_it.utf8
@@ -13,8 +13,8 @@ TILE_WITHOUT_REQUIRED_NEIGHBORS_SELECTED = <MISSING TRANSLATION>
 # date {0} is out of time span [{1}, {2}]
 OUT_OF_TIME_RANGE = la data {0} è fuori dell''intervallo temporale [{1}, {2}]
 
-# general context has not been initialized
-UNINITIALIZED_CONTEXT = il contesto generale non è stato inizializzato
+# general context has not been initialized (missing call to {0})
+UNINITIALIZED_CONTEXT = <MISSING TRANSLATION>
 
 # tile is empty: {0} ⨉ {1}
 EMPTY_TILE = il quadrante è vuoto : {0} ⨉ {1}
diff --git a/src/site/markdown/tutorials/direct-location-with-DEM.md b/src/site/markdown/tutorials/direct-location-with-DEM.md
index 7cc29105..9a6809ec 100644
--- a/src/site/markdown/tutorials/direct-location-with-DEM.md
+++ b/src/site/markdown/tutorials/direct-location-with-DEM.md
@@ -112,15 +112,20 @@ Instantiate an object derived from TileUpdater :
  
 Initialize Rugged with these parameters :
 
-    Rugged rugged = new Rugged(updater, nbTiles, algoId, 
-                               EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF, 
-                               startDate, stopDate, 0.1, 10.0, 
-                               satellitePVList, 6, CartesianDerivativesFilter.USE_P, 
-                               satelliteQList, 8, AngularDerivativesFilter.USE_R);
+    Rugged rugged = new RuggedBuilder().
+                    setDigitalElevationModel(updater, nbTiles).
+                    setAlgorithm(algoId). 
+                    setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+                    setTimeSpan(startDate, stopDate, 0.1, 10.0). 
+                    setTrajectory(InertialFrameId.EME2000,
+                                  satellitePVList, 6, CartesianDerivativesFilter.USE_P, 
+                                  satelliteQList, 8, AngularDerivativesFilter.USE_R).
+                    addLineSensor(lineSensor).
+                    build();
 
 ## Computing a direct location grid
 
-In a similar way as in the first tutorial [[DirectLocation|Direct location]], we call Rugged direct location method. This time it is called in a loop so as to generate a full grid on disk. 
+In a similar way as in the first tutorial [DirectLocation](./direct-location.html), we call Rugged direct location method. This time it is called in a loop so as to generate a full grid on disk. 
 
     DataOutputStream dos = new DataOutputStream(new FileOutputStream("demDirectLoc.c1"));
     int lineStep = (maxLine - minLine) / nbLineStep;
diff --git a/src/site/markdown/tutorials/direct-location.md b/src/site/markdown/tutorials/direct-location.md
index ca33ff40..6f0005c6 100644
--- a/src/site/markdown/tutorials/direct-location.md
+++ b/src/site/markdown/tutorials/direct-location.md
@@ -21,8 +21,9 @@ list of positions, velocities and attitude quaternions recorded during the acqui
 passing all this information to Rugged, we will be able to precisely locate each point of
 the image on the Earth. Well, not exactly precise, as this first tutorial does not use a
 Digital Elevation Model, but considers the Earth as an ellipsoid. The DEM will be added in
-a second tutorial [Direct location with DEM](direct-location-with-DEM.html). The objective here is limited to explain how to initialize everything
-Rugged needs to know about the sensor and the acquisition.   
+a second tutorial: [Direct location with a DEM](direct-location-with-DEM.html). The objective
+here is limited to explain how to initialize everything Rugged needs to know about the sensor
+and the acquisition.   
 
 
 ## Sensor's definition
@@ -190,49 +191,100 @@ Finally we can initialize Rugged. It looks like this:
     import org.orekit.utils.AngularDerivativesFilter;
     import org.orekit.utils.CartesianDerivativesFilter;
     import org.orekit.utils.IERSConventions;
-    Rugged rugged = new Rugged (demTileUpdater, nbTiles,  demAlgoId, 
-                                EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                acquisitionStartDate, acquisitionStopDate, tStep, timeTolerance,
-                                satellitePVList, nbPVPoints, CartesianDerivativesFilter.USE_P,
-                                satelliteQList, nbQPoints, AngularDerivativesFilter.USE_R);
+    Rugged rugged = new RuggedBuilder().
+                    setAlgorithm(demAlgoId). 
+                    setDigitalElevationModel(demTileUpdater, nbTiles).
+                    setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+                    setTimeSpan(acquisitionStartDate, acquisitionStopDate, tStep, timeTolerance). 
+                    setTrajectory(InertialFrameId.EME2000,
+                                  satellitePVList, nbPVPoints, CartesianDerivativesFilter.USE_P,
+                                  satelliteQList,  nbQPoints,  AngularDerivativesFilter.USE_R).
+                    addLineSensor(lineSensor).
+                    build();
 
 Argh, that sounds complicated. It is not so difficult since we have already defined most of what is
-needed. Let's describe the arguments line by line:
- 
-The first 3 are related to the DEM. Since we are ignoring the DEM, they can be set respectively to
-`null`, `0` and `AlgorithmId.IGNORE_DEM_USE_ELLIPSOID`.
-
-The next 3 arguments define the ellipsoid and the reference frames: `EllipsoidId.WGS84`,
-`InertialFrameId.EME2000`, `BodyRotatingFrameId.ITRF`
-
-On the third line, we find the time interval of the acquisition: acquisitionStartDate, acquisitionStopDate,
+needed. Let's describe the Rugged instance building process line by line:
+
+As the Rugged top level object that will be used for all user interaction is quite involved and can be
+built in several different ways, a [builder pattern](https://en.wikipedia.org/wiki/Builder_pattern)
+approach has been adopted. The builder itself is configured by calling dedicated setters for each
+concept (Digital Elevation Model, intersection algorithm, trajectory, ...).
+As all these concepts can be chained together, the setters themselves implement the
+[fluent interface](https://en.wikipedia.org/wiki/Fluent_interface) pattern, which implies each setter
+returns the builder instance, and therefore another setter can be called directly.
+
+The first setter specifies the intersection algorithm to use. As this tutorial is intended to be very
+simple for a beginning, we choose to use directly the ellipsoid and not a real Digital Elevation Model,
+so we can use `AlgorithmId.IGNORE_DEM_USE_ELLIPSOID` as the single parameter of this setter.
+
+The second setter specifies the Digital Elevation Model. In fact, as we decided to ignore the Digital
+Elevation Model in this tutorial, we could have omitted this call and it would have worked correctly.
+We preferred to let it in so users do not forget to set the Digital Elevation Model for intersection
+algorithms that really use them. As the model will be ignored, we can put the parameters for this
+setter to `null` and `0`. Of course if another algorithm had been chosen,  null parameters would clearly
+not work, this is explained in another tutorial: [Direct location with a DEM](direct-location-with-DEM.html).
+
+The third setter defines the shape and orientation of the ellipsoid. We use simple predefined enumerates:
+`EllipsoidId.WGS84`, `InertialFrameId.EME2000`, but could also use a custom ellipsoid if needed.
+
+The fourth setter is used to define the global time span of the search algorithms (direct and inverse
+location). Four parameters are used for this: acquisitionStartDate, acquisitionStopDate,
 tStep (step at which the pre-computed frames transforms cache will be filled), timeTolerance (margin
-allowed for extrapolation during inverse location, in seconds. A few lines must be taken into account like: timeTolerance = 5/lineSensor.getRate(0)). This is an important information as Rugged
-will pre-compute a lot of stuff at initialization in order to optimize further calls to the direct and
-inverse location functions. 
-
-On the fourth line, the arguments are the list of time-stamped positions and velocities as well as options
-for interpolation: number of points to use and type of filter for derivatives. The interpolation polynomials for nbPVPoints without any derivatives (case of CartesianDerivativesFilter.USE_P: only positions are used, without velocities) have a degree nbPVPoints - 1. In case of computation with velocities included (case of CartesianDerivativesFilter.USE_PV), the interpolation polynomials have a degree 2*nbPVPoints - 1. If the positions/velocities data are of good quality and spaced by a few seconds, one may choose only a few points but interpolate with both positions and velocities; in other cases, one may choose more points but interpolate only with positions.
-We find the same arguments on the last line for the attitude quaternions. 
+allowed for extrapolation during inverse location, in seconds. The tStep parameter is a key parameter
+to achieve good performances as frames transforms will be precomputed throughout the time span using
+this time step. These computation are costly because they involve Earth precession/nutation models to
+be evaluated. So the transformed are precomputed and cached instead of being recomputed for each pixel.
+However, if direct and inverse location are expected to be performed over a reduced time span (say a few
+tens of seconds), precomputing the transforms over half an orbit at one millisecond rate would be a
+waste of computing power. Typical values are therefore to restrict the time span as much as possible
+to properly cover the expected direct and inverse location calls, and to use a step between one millisecond
+and one second, depending on the required accuracy. The exact value to use is mission-dependent. The
+final timeTolerance parameter is simply a margin used before and after the final precomputed transforms to
+allow a slight extrapolation if during a search the interval is slightly overshoot. A typical value is
+to allow a few images lines so for example a 5 lines tolerance would imply computing the tolerance as:
+timeTolerance = 5 / lineSensor.getRate(0)).
+`BodyRotatingFrameId.ITRF`
+
+The fifth setter defines the spacecraft evolution. The arguments are the list of time-stamped positions and
+velocities as well as the inertial frame with respect to which they are defined and options for interpolation:
+number of points to use and type of filter for derivatives. The interpolation polynomials for nbPVPoints
+without any derivatives (case of CartesianDerivativesFilter.USE_P: only positions are used, without velocities)
+have a degree nbPVPoints - 1. In case of computation with velocities included (case of
+CartesianDerivativesFilter.USE_PV), the interpolation polynomials have a degree 2*nbPVPoints - 1. If the
+positions/velocities data are of good quality and separated by a few seconds, one may choose only a few points
+but interpolate with both positions and velocities; in other cases, one may choose more points but interpolate
+only with positions. We find similar arguments for the attitude quaternions. 
+
+The sixth setter used registers a line sensor. As can be deduced from its prefix (`add` instead of `set`), it
+can be called several time to register several sensors that will all be available in the built Rugged instance.
+We have called the method only once here, so we will use only one sensor.
+
+After the last setter has been called, we call the `build()` method which really build the Rugged instance
+(and not a RuggedBuilder instance has the setter did).
 
 Rugged takes into account by default some corrections for more accurate locations: 
 
 * light time correction (compensates or not light time between ground and spacecraft). 
 * aberration of light correction (compensates or not aberration of light, which is velocity composition between light and spacecraft when the light from ground points reaches the sensor).
 
-Not compensating the delay or the velocity composition are mainly useful for validation purposes against system that do not compensate it. When the pixels line of sight already includes the aberration of light correction, one must obviously deactivate the correction.
-In order not to take into account those corrections, one must finalize Rugged initialization by setting the related flags to false:
-
-    rugged.setLightTimeCorrection(false);
-
-or
-
-    rugged.setAberrationOfLightCorrection(false);
+Not compensating the delay or the velocity composition are mainly useful for validation purposes against system
+that do not compensate it. When the pixels line of sight already includes the aberration of light correction,
+one must obviously deactivate the correction.
 
+If those corrections should be ignored, some other setters must be inserted before the call to `build()`:
 
-The sensor models are added after initialization. We can add as many as we want. 
+    setXxxx().
+    setLightTimeCorrection(false).
+    setAberrationOfLightCorrection(false).
+    build();
 
-    rugged.addLineSensor(lineSensor);
+The various setters can be called in any order. The only important thing is that once a Rugged instance
+has been created by calling the `build()` method, it is independent from the builder so later calls
+to setters will not change the build instance. In fact, it is possible to create a builder, then
+call its `build()` method to create a first Rugged instance, and then to modify the builder configuration
+by calling again some of the setters and building a second Rugged instance from the new configuration.
+This allows to perform comparisons between two different configurations in the same program and without
+having to recreate everything.
 
 ## Direct location
 
diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml
index bed361b5..8a828c65 100644
--- a/src/site/xdoc/changes.xml
+++ b/src/site/xdoc/changes.xml
@@ -22,6 +22,9 @@
   <body>
     <release version="1.0" date="TBD"
              description="TBD">
+      <action dev="luc" type="update" >
+        Use builder pattern and fluent interface to create the top level Rugged instance.
+      </action>
       <action dev="luc" type="fix" >
         Force geodetic points to remain in the same longitude range as the tile of the
         Digital Elevation Model they belong to.
diff --git a/src/test/java/org/orekit/rugged/api/RuggedBuilderTest.java b/src/test/java/org/orekit/rugged/api/RuggedBuilderTest.java
new file mode 100644
index 00000000..dc6d83db
--- /dev/null
+++ b/src/test/java/org/orekit/rugged/api/RuggedBuilderTest.java
@@ -0,0 +1,727 @@
+/* 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.api;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
+import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.math3.ode.nonstiff.DormandPrince853Integrator;
+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.attitudes.AttitudeProvider;
+import org.orekit.attitudes.NadirPointing;
+import org.orekit.attitudes.YawCompensation;
+import org.orekit.bodies.BodyShape;
+import org.orekit.bodies.CelestialBodyFactory;
+import org.orekit.bodies.GeodeticPoint;
+import org.orekit.bodies.OneAxisEllipsoid;
+import org.orekit.data.DataProvidersManager;
+import org.orekit.data.DirectoryCrawler;
+import org.orekit.errors.OrekitException;
+import org.orekit.errors.PropagationException;
+import org.orekit.forces.gravity.HolmesFeatherstoneAttractionModel;
+import org.orekit.forces.gravity.ThirdBodyAttraction;
+import org.orekit.forces.gravity.potential.GravityFieldFactory;
+import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
+import org.orekit.frames.Frame;
+import org.orekit.frames.FramesFactory;
+import org.orekit.frames.Transform;
+import org.orekit.orbits.CircularOrbit;
+import org.orekit.orbits.Orbit;
+import org.orekit.orbits.OrbitType;
+import org.orekit.orbits.PositionAngle;
+import org.orekit.propagation.Propagator;
+import org.orekit.propagation.SpacecraftState;
+import org.orekit.propagation.analytical.KeplerianPropagator;
+import org.orekit.propagation.numerical.NumericalPropagator;
+import org.orekit.propagation.sampling.OrekitFixedStepHandler;
+import org.orekit.rugged.raster.RandomLandscapeUpdater;
+import org.orekit.rugged.raster.TileUpdater;
+import org.orekit.rugged.raster.VolcanicConeElevationUpdater;
+import org.orekit.time.AbsoluteDate;
+import org.orekit.time.TimeScale;
+import org.orekit.time.TimeScalesFactory;
+import org.orekit.utils.AngularDerivativesFilter;
+import org.orekit.utils.CartesianDerivativesFilter;
+import org.orekit.utils.Constants;
+import org.orekit.utils.IERSConventions;
+import org.orekit.utils.PVCoordinates;
+import org.orekit.utils.TimeStampedAngularCoordinates;
+import org.orekit.utils.TimeStampedPVCoordinates;
+
+public class RuggedBuilderTest {
+
+    @Rule
+    public TemporaryFolder tempFolder = new TemporaryFolder();
+
+    @Test
+    public void testSetContextWithEphemerides()
+        throws RuggedException, OrekitException, URISyntaxException {
+
+        String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
+        DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
+        AbsoluteDate t0 = new AbsoluteDate("2012-01-01T00:00:00", TimeScalesFactory.getUTC());
+
+        List<TimeStampedPVCoordinates> pv = Arrays.asList(
+            createPV(t0, 0.000, -1545168.478, -7001985.361,       0.000, -1095.152224, 231.344922, -7372.851944),
+            createPV(t0, 1.000, -1546262.794, -7001750.226,   -7372.851, -1093.478904, 238.925123, -7372.847995),
+            createPV(t0, 2.000, -1547355.435, -7001507.511,  -14745.693, -1091.804408, 246.505033, -7372.836044),
+            createPV(t0, 3.000, -1548446.402, -7001257.216,  -22118.520, -1090.128736, 254.084644, -7372.816090),
+            createPV(t0, 4.000, -1549535.693, -7000999.342,  -29491.323, -1088.451892, 261.663949, -7372.788133),
+            createPV(t0, 5.000, -1550623.306, -7000733.888,  -36864.094, -1086.773876, 269.242938, -7372.752175),
+            createPV(t0, 6.000, -1551709.240, -7000460.856,  -44236.825, -1085.094690, 276.821604, -7372.708214),
+            createPV(t0, 7.000, -1552793.495, -7000180.245,  -51609.507, -1083.414336, 284.399938, -7372.656251),
+            createPV(t0, 8.000, -1553876.068, -6999892.056,  -58982.134, -1081.732817, 291.977932, -7372.596287),
+            createPV(t0, 9.000, -1554956.960, -6999596.289,  -66354.697, -1080.050134, 299.555578, -7372.528320),
+            createPV(t0,10.000, -1556036.168, -6999292.945,  -73727.188, -1078.366288, 307.132868, -7372.452352),
+            createPV(t0,11.000, -1557113.692, -6998982.024,  -81099.599, -1076.681282, 314.709792, -7372.368382),
+            createPV(t0,12.000, -1558189.530, -6998663.526,  -88471.922, -1074.995118, 322.286344, -7372.276411),
+            createPV(t0,13.000, -1559263.682, -6998337.451,  -95844.150, -1073.307797, 329.862513, -7372.176439),
+            createPV(t0,14.000, -1560336.145, -6998003.801, -103216.273, -1071.619321, 337.438294, -7372.068466),
+            createPV(t0,15.000, -1561406.920, -6997662.575, -110588.284, -1069.929692, 345.013676, -7371.952492),
+            createPV(t0,16.000, -1562476.004, -6997313.774, -117960.175, -1068.238912, 352.588652, -7371.828517),
+            createPV(t0,17.000, -1563543.398, -6996957.398, -125331.938, -1066.546983, 360.163213, -7371.696542),
+            createPV(t0,18.000, -1564609.098, -6996593.447, -132703.565, -1064.853906, 367.737352, -7371.556566),
+            createPV(t0,19.000, -1565673.105, -6996221.923, -140075.049, -1063.159684, 375.311060, -7371.408591),
+            createPV(t0,20.000, -1566735.417, -6995842.825, -147446.380, -1061.464319, 382.884328, -7371.252616));
+
+        List<TimeStampedAngularCoordinates> q = Arrays.asList(
+            createQ(t0, 0.000, 0.516354347549, -0.400120145429,  0.583012133139,  0.483093065155),
+            createQ(t0, 1.000, 0.516659035405, -0.399867643627,  0.582741754688,  0.483302551263),
+            createQ(t0, 2.000, 0.516963581177, -0.399615033309,  0.582471217473,  0.483511904409),
+            createQ(t0, 3.000, 0.517267984776, -0.399362314553,  0.582200521577,  0.483721124530),
+            createQ(t0, 4.000, 0.517572246112, -0.399109487434,  0.581929667081,  0.483930211565),
+            createQ(t0, 5.000, 0.517876365096, -0.398856552030,  0.581658654071,  0.484139165451),
+            createQ(t0, 6.000, 0.518180341637, -0.398603508416,  0.581387482627,  0.484347986126),
+            createQ(t0, 7.000, 0.518484175647, -0.398350356669,  0.581116152834,  0.484556673529),
+            createQ(t0, 8.000, 0.518787867035, -0.398097096866,  0.580844664773,  0.484765227599),
+            createQ(t0, 9.000, 0.519091415713, -0.397843729083,  0.580573018530,  0.484973648272),
+            createQ(t0,10.000, 0.519394821590, -0.397590253397,  0.580301214186,  0.485181935488),
+            createQ(t0,11.000, 0.519698084578, -0.397336669885,  0.580029251825,  0.485390089185),
+            createQ(t0,12.000, 0.520001204587, -0.397082978623,  0.579757131530,  0.485598109301),
+            createQ(t0,13.000, 0.520304181527, -0.396829179688,  0.579484853385,  0.485805995775),
+            createQ(t0,14.000, 0.520607015311, -0.396575273158,  0.579212417473,  0.486013748545),
+            createQ(t0,15.000, 0.520909705847, -0.396321259108,  0.578939823877,  0.486221367550),
+            createQ(t0,16.000, 0.521212253049, -0.396067137616,  0.578667072681,  0.486428852729),
+            createQ(t0,17.000, 0.521514656825, -0.395812908759,  0.578394163969,  0.486636204020),
+            createQ(t0,18.000, 0.521816917089, -0.395558572613,  0.578121097824,  0.486843421362),
+            createQ(t0,19.000, 0.522119033749, -0.395304129256,  0.577847874330,  0.487050504694),
+            createQ(t0,20.000, 0.522421006719, -0.395049578765,  0.577574493570,  0.487257453954));
+
+        TileUpdater updater =
+                new VolcanicConeElevationUpdater(new GeodeticPoint(FastMath.toRadians(13.25667),
+                                                                   FastMath.toRadians(123.685),
+                                                                   2463.0),
+                                                 FastMath.toRadians(30.0), 16.0,
+                                                 FastMath.toRadians(1.0), 1201);
+
+        RuggedBuilder builder = new RuggedBuilder().
+                                setDigitalElevationModel(updater, 8).
+                                setAlgorithm(AlgorithmId.DUVENHAGE).
+                                setEllipsoid(EllipsoidId.GRS80, BodyRotatingFrameId.ITRF).
+                                setTimeSpan(pv.get(0).getDate(), pv.get(pv.size() - 1).getDate(), 0.001, 5.0).
+                                setTrajectory(InertialFrameId.EME2000,
+                                              pv, 8, CartesianDerivativesFilter.USE_PV,
+                                              q, 8, AngularDerivativesFilter.USE_R);
+
+        // light time correction and aberration of light correction are enabled by default
+        Rugged rugged = builder.build();
+        Assert.assertTrue(rugged.isLightTimeCorrected());
+        Assert.assertTrue(rugged.isAberrationOfLightCorrected());
+
+        builder.setLightTimeCorrection(false);
+        rugged = builder.build();
+        Assert.assertFalse(rugged.isLightTimeCorrected());
+        Assert.assertTrue(rugged.isAberrationOfLightCorrected());
+
+        builder.setAberrationOfLightCorrection(false);
+        rugged = builder.build();
+        Assert.assertFalse(rugged.isLightTimeCorrected());
+        Assert.assertFalse(rugged.isAberrationOfLightCorrected());
+
+    }
+
+    @Test
+    public void testSetContextWithPropagator()
+        throws RuggedException, OrekitException, URISyntaxException {
+
+        String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
+        DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
+        BodyShape  earth                                  = createEarth();
+        NormalizedSphericalHarmonicsProvider gravityField = createGravityField();
+        Orbit      orbit                                  = createOrbit(gravityField.getMu());
+        Propagator propagator                             = createPropagator(earth, gravityField, orbit);
+
+        TileUpdater updater =
+                new VolcanicConeElevationUpdater(new GeodeticPoint(FastMath.toRadians(13.25667),
+                                                                   FastMath.toRadians(123.685),
+                                                                   2463.0),
+                                                 FastMath.toRadians(30.0), 16.0,
+                                                 FastMath.toRadians(1.0), 1201);
+
+        RuggedBuilder builder = new RuggedBuilder().
+                setDigitalElevationModel(updater, 8).
+                setAlgorithm(AlgorithmId.DUVENHAGE).
+                setEllipsoid(EllipsoidId.IERS96, BodyRotatingFrameId.ITRF).
+                setTimeSpan(orbit.getDate().shiftedBy(-10.0), orbit.getDate().shiftedBy(+10.0), 0.001, 5.0).
+                setTrajectory(1.0, 8, CartesianDerivativesFilter.USE_PV, AngularDerivativesFilter.USE_R, propagator);
+
+        // light time correction and aberration of light correction are enabled by default
+        Rugged rugged = builder.build();
+        Assert.assertTrue(rugged.isLightTimeCorrected());
+        Assert.assertTrue(rugged.isAberrationOfLightCorrected());
+
+        builder.setLightTimeCorrection(false);
+        rugged = builder.build();
+        Assert.assertFalse(rugged.isLightTimeCorrected());
+        Assert.assertTrue(rugged.isAberrationOfLightCorrected());
+
+        builder.setAberrationOfLightCorrection(false);
+        rugged = builder.build();
+        Assert.assertFalse(rugged.isLightTimeCorrected());
+        Assert.assertFalse(rugged.isAberrationOfLightCorrected());
+        Assert.assertEquals(orbit.getDate().shiftedBy(-10.0), rugged.getMinDate());
+        Assert.assertEquals(orbit.getDate().shiftedBy(+10.0), rugged.getMaxDate());
+        Assert.assertEquals(0, rugged.getLineSensors().size());
+
+    }
+
+    @Test
+    public void testOutOfTimeRange()
+        throws RuggedException, OrekitException, URISyntaxException {
+
+        String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
+        DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
+        AbsoluteDate t0 = new AbsoluteDate("2012-01-01T00:00:00", TimeScalesFactory.getUTC());
+
+        List<TimeStampedPVCoordinates> pv = Arrays.asList(
+            createPV(t0, 0.000, -1545168.478, -7001985.361,       0.000, -1095.152224, 231.344922, -7372.851944),
+            createPV(t0, 1.000, -1546262.794, -7001750.226,   -7372.851, -1093.478904, 238.925123, -7372.847995),
+            createPV(t0, 2.000, -1547355.435, -7001507.511,  -14745.693, -1091.804408, 246.505033, -7372.836044),
+            createPV(t0, 3.000, -1548446.402, -7001257.216,  -22118.520, -1090.128736, 254.084644, -7372.816090),
+            createPV(t0, 4.000, -1549535.693, -7000999.342,  -29491.323, -1088.451892, 261.663949, -7372.788133),
+            createPV(t0, 5.000, -1550623.306, -7000733.888,  -36864.094, -1086.773876, 269.242938, -7372.752175),
+            createPV(t0, 6.000, -1551709.240, -7000460.856,  -44236.825, -1085.094690, 276.821604, -7372.708214),
+            createPV(t0, 7.000, -1552793.495, -7000180.245,  -51609.507, -1083.414336, 284.399938, -7372.656251),
+            createPV(t0, 8.000, -1553876.068, -6999892.056,  -58982.134, -1081.732817, 291.977932, -7372.596287),
+            createPV(t0, 9.000, -1554956.960, -6999596.289,  -66354.697, -1080.050134, 299.555578, -7372.528320),
+            createPV(t0,10.000, -1556036.168, -6999292.945,  -73727.188, -1078.366288, 307.132868, -7372.452352));
+
+        List<TimeStampedAngularCoordinates> q = Arrays.asList(
+            createQ(t0, 4.000, 0.517572246112, -0.399109487434,  0.581929667081,  0.483930211565),
+            createQ(t0, 5.000, 0.517876365096, -0.398856552030,  0.581658654071,  0.484139165451),
+            createQ(t0, 6.000, 0.518180341637, -0.398603508416,  0.581387482627,  0.484347986126),
+            createQ(t0, 7.000, 0.518484175647, -0.398350356669,  0.581116152834,  0.484556673529),
+            createQ(t0, 8.000, 0.518787867035, -0.398097096866,  0.580844664773,  0.484765227599));
+
+        TileUpdater updater =
+                new VolcanicConeElevationUpdater(new GeodeticPoint(FastMath.toRadians(13.25667),
+                                                                   FastMath.toRadians(123.685),
+                                                                   2463.0),
+                                                 FastMath.toRadians(30.0), 16.0,
+                                                 FastMath.toRadians(1.0), 1201);
+
+        Assert.assertNotNull(new RuggedBuilder().
+                             setDigitalElevationModel(updater, 8).
+                             setAlgorithm(AlgorithmId.DUVENHAGE).
+                             setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+                             setTimeSpan(t0.shiftedBy(4), t0.shiftedBy(6), 0.001, 5.0).
+                             setTrajectory(InertialFrameId.EME2000,
+                                           pv,2, CartesianDerivativesFilter.USE_PV,
+                                           q, 2, AngularDerivativesFilter.USE_R).
+                             build());
+        try {
+            new RuggedBuilder().
+            setDigitalElevationModel(updater, 8).
+            setAlgorithm(AlgorithmId.DUVENHAGE).
+            setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+            setTimeSpan(t0.shiftedBy(-1), t0.shiftedBy(6), 0.001, 0.0001).
+            setTrajectory(InertialFrameId.EME2000,
+                          pv, 2, CartesianDerivativesFilter.USE_PV,
+                          q, 2, AngularDerivativesFilter.USE_R);
+        } catch (RuggedException re) {
+            Assert.assertEquals(RuggedMessages.OUT_OF_TIME_RANGE, re.getSpecifier());
+            Assert.assertEquals(t0.shiftedBy(-1), re.getParts()[0]);
+        }
+
+        try {
+            new RuggedBuilder().
+            setDigitalElevationModel(updater, 8).
+            setAlgorithm(AlgorithmId.DUVENHAGE).
+            setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+            setTimeSpan(t0.shiftedBy(2), t0.shiftedBy(6), 0.001, 0.0001).
+            setTrajectory(InertialFrameId.EME2000,
+                          pv, 2, CartesianDerivativesFilter.USE_PV,
+                          q, 2, AngularDerivativesFilter.USE_R);
+        } catch (RuggedException re) {
+            Assert.assertEquals(RuggedMessages.OUT_OF_TIME_RANGE, re.getSpecifier());
+            Assert.assertEquals(t0.shiftedBy(2), re.getParts()[0]);
+        }
+
+        try {
+            new RuggedBuilder().
+            setDigitalElevationModel(updater, 8).
+            setAlgorithm(AlgorithmId.DUVENHAGE).
+            setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+            setTimeSpan(t0.shiftedBy(4), t0.shiftedBy(9), 0.001, 0.0001).
+            setTrajectory(InertialFrameId.EME2000,
+                          pv, 2, CartesianDerivativesFilter.USE_PV,
+                          q, 2, AngularDerivativesFilter.USE_R);
+        } catch (RuggedException re) {
+            Assert.assertEquals(RuggedMessages.OUT_OF_TIME_RANGE, re.getSpecifier());
+            Assert.assertEquals(t0.shiftedBy(9), re.getParts()[0]);
+        }
+
+        try {
+            new RuggedBuilder().
+            setDigitalElevationModel(updater, 8).
+            setAlgorithm(AlgorithmId.DUVENHAGE).
+            setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+            setTimeSpan(t0.shiftedBy(4), t0.shiftedBy(12), 0.001, 0.0001).
+            setTrajectory(InertialFrameId.EME2000,
+                          pv, 2, CartesianDerivativesFilter.USE_PV,
+                          q, 2, AngularDerivativesFilter.USE_R);
+        } catch (RuggedException re) {
+            Assert.assertEquals(RuggedMessages.OUT_OF_TIME_RANGE, re.getSpecifier());
+            Assert.assertEquals(t0.shiftedBy(12), re.getParts()[0]);
+        }
+
+    }
+
+    @Test
+    public void testInterpolatorDump()
+        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 = createEarth();
+        final Orbit      orbit = 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);
+        List<TimeDependentLOS> los = 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, 0x84186d1344722b8fl,
+                                           FastMath.toRadians(1.0), 257);
+
+        RuggedBuilder original = new RuggedBuilder().
+                                setDigitalElevationModel(updater, 8).
+                                setAlgorithm(AlgorithmId.DUVENHAGE).
+                                setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+                                setTimeSpan(minDate, maxDate, 0.001, 5.0).
+                                setTrajectory(InertialFrameId.EME2000,
+                                              orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                                              8, CartesianDerivativesFilter.USE_PV,
+                                              orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                                              2, AngularDerivativesFilter.USE_R).
+                                addLineSensor(lineSensor);
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        original.dumpInterpolator(bos);
+        Assert.assertTrue(bos.size() > 100000);
+        Assert.assertTrue(bos.size() < 200000);
+
+        GeodeticPoint[] gpOriginal = original.build().directLocation("line", 100);
+
+        RuggedBuilder recovered = new RuggedBuilder().
+                setDigitalElevationModel(updater, 8).
+                setAlgorithm(AlgorithmId.DUVENHAGE).
+                setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+                setTrajectory(new ByteArrayInputStream(bos.toByteArray())).
+                addLineSensor(lineSensor);
+        GeodeticPoint[] gpRecovered = recovered.build().directLocation("line", 100);
+
+        for (int i = 0; i < gpOriginal.length; ++i) {
+            Vector3D pOriginal  = earth.transform(gpOriginal[i]);
+            Vector3D pRecovered = earth.transform(gpRecovered[i]);
+            Assert.assertEquals(0.0, Vector3D.distance(pOriginal, pRecovered), 1.0e-15);
+        }
+
+    }
+
+    @Test
+    public void testInterpolatorCannotDump()
+        throws RuggedException, OrekitException, URISyntaxException, IOException {
+
+        int dimension = 200;
+
+        String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
+        DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
+        final BodyShape  earth = createEarth();
+        final Orbit      orbit = 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);
+        List<TimeDependentLOS> los = 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);
+
+        RuggedBuilder original = new RuggedBuilder().
+                setAlgorithm(AlgorithmId.IGNORE_DEM_USE_ELLIPSOID).
+                setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+                setTimeSpan(minDate, maxDate, 0.001, 5.0).
+                setTrajectory(InertialFrameId.EME2000,
+                              orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              8, CartesianDerivativesFilter.USE_PV,
+                              orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              2, AngularDerivativesFilter.USE_R);
+
+        FileOutputStream fos = new FileOutputStream(tempFolder.newFile());
+        fos.close();
+        try {
+            original.dumpInterpolator(fos);
+            Assert.fail("an exception should have been thrown");
+        } catch (RuggedException re) {
+            Assert.assertEquals(IOException.class, re.getCause().getClass());
+        }
+    }
+    
+    @Test
+    public void testInterpolatorDumpWrongFrame()
+        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 = createEarth();
+        final Orbit      orbit = 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);
+        List<TimeDependentLOS> los = 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);
+
+        RuggedBuilder original = new RuggedBuilder().
+                setDigitalElevationModel(null, -1).
+                setAlgorithm(AlgorithmId.IGNORE_DEM_USE_ELLIPSOID).
+                setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+                setTimeSpan(minDate, maxDate, 0.001, 5.0).
+                setTrajectory(InertialFrameId.EME2000,
+                              orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              8, CartesianDerivativesFilter.USE_PV,
+                              orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              2, AngularDerivativesFilter.USE_R);
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        original.dumpInterpolator(bos);
+        Assert.assertTrue(bos.size() > 100000);
+        Assert.assertTrue(bos.size() < 200000);
+
+        for (BodyRotatingFrameId bId : Arrays.asList(BodyRotatingFrameId.GTOD,
+                                                     BodyRotatingFrameId.ITRF_EQUINOX)) {
+            try {
+                new RuggedBuilder().
+                setAlgorithm(AlgorithmId.IGNORE_DEM_USE_ELLIPSOID).
+                setEllipsoid(EllipsoidId.WGS84, bId).
+                setTrajectory(new ByteArrayInputStream(bos.toByteArray())).build();
+                Assert.fail("an exception should have been thrown");
+            } catch (RuggedException re) {
+                Assert.assertEquals(RuggedMessages.FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP,
+                                    re.getSpecifier());
+            }
+        }
+    }
+
+    @Test
+    public void testInterpolatorNotADump()
+        throws RuggedException, OrekitException, URISyntaxException {
+
+        String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
+        DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
+
+        // the following array is a real serialization file corresponding to the following
+        // made-up empty class that does not exist in Rugged:
+        //        public class NonExistentClass implements java.io.Serializable {
+        //            private static final long serialVersionUID = -1;
+        //        }
+        byte[] nonExistentClass = new byte[] {
+            -84, -19,   0,   5, 115, 114,
+              0,  16,  78, 111, 110,  69,
+            120, 105, 115, 116, 101, 110,
+            116,  67, 108,  97, 115, 115,
+             -1,  -1,  -1,  -1,  -1,  -1,
+             -1,  -1,   2,   0,   0, 120,
+            112
+        };
+
+        // the following array is a real serialization file of object Integer.valueOf(1)
+        byte[] integerOne = new byte[] {
+          -84, -19,   0,   5, 115, 114,
+            0,  17, 106,  97, 118,  97,
+           46, 108,  97, 110, 103,  46,
+           73, 110, 116, 101, 103, 101,
+          114,  18, -30, -96, -92,  -9,
+         -127,-121,  56,   2,   0,   1,
+           73,   0,   5, 118,  97, 108,
+          117, 101, 120, 114,   0,  16,
+          106,  97, 118,  97,  46, 108,
+           97, 110, 103,  46,  78, 117,
+          109,  98, 101, 114,-122, -84,
+         -107,  29,  11,-108, -32,-117,
+            2,   0,   0, 120, 112,   0,
+            0,   0,   1
+        };
+
+        // the following array is a truncation of the previous one
+        byte[] truncatedDump = new byte[] {
+          -84, -19,   0,   5, 115, 114,
+            0,  17, 106,  97, 118,  97
+        };
+
+        byte[] notSerialization = new byte[] {
+            1, 2, 3, 4, 5, 6
+        };
+
+        for (byte[] array : Arrays.asList(nonExistentClass, integerOne, truncatedDump, notSerialization)) {
+            try {
+                new RuggedBuilder().setTrajectory(new ByteArrayInputStream(array));
+                Assert.fail("an exception should have been thrown");
+            } catch (RuggedException re) {
+                Assert.assertEquals(RuggedMessages.NOT_INTERPOLATOR_DUMP_DATA,
+                                    re.getSpecifier());
+                if (array == nonExistentClass) {
+                    Assert.assertEquals(ClassNotFoundException.class, re.getCause().getClass());
+                } else if (array == integerOne) {
+                    Assert.assertEquals(ClassCastException.class, re.getCause().getClass());
+                } else if (array == truncatedDump) {
+                    Assert.assertEquals(EOFException.class, re.getCause().getClass());
+                } else if (array == notSerialization) {
+                    Assert.assertEquals(StreamCorruptedException.class, re.getCause().getClass());
+                }
+            }
+        }
+
+    }
+
+    protected void addSatellitePV(TimeScale gps, Frame eme2000, Frame itrf,
+                                  ArrayList<TimeStampedPVCoordinates> satellitePVList,
+                                  String absDate,
+                                  double px, double py, double pz, double vx, double vy, double vz)
+        throws OrekitException {
+        AbsoluteDate ephemerisDate = new AbsoluteDate(absDate, gps);
+        Vector3D position = new Vector3D(px, py, pz);
+        Vector3D velocity = new Vector3D(vx, vy, vz);
+        PVCoordinates pvITRF = new PVCoordinates(position, velocity);
+        Transform transform = itrf.getTransformTo(eme2000, ephemerisDate);
+        Vector3D pEME2000 = transform.transformPosition(pvITRF.getPosition());
+        Vector3D vEME2000 = transform.transformVector(pvITRF.getVelocity());
+        satellitePVList.add(new TimeStampedPVCoordinates(ephemerisDate, pEME2000, vEME2000, Vector3D.ZERO));
+    }
+
+    protected void addSatelliteQ(TimeScale gps, ArrayList<TimeStampedAngularCoordinates> satelliteQList, String absDate, double q0, double q1, double q2,
+            double q3) {
+        AbsoluteDate attitudeDate = new AbsoluteDate(absDate, gps);
+        Rotation rotation = new Rotation(q0, q1, q2, q3, true);
+        TimeStampedAngularCoordinates pair =
+                new TimeStampedAngularCoordinates(attitudeDate, rotation, Vector3D.ZERO, Vector3D.ZERO);
+        satelliteQList.add(pair);
+    }
+
+    private BodyShape createEarth()
+       throws OrekitException {
+        return new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
+                                    Constants.WGS84_EARTH_FLATTENING,
+                                    FramesFactory.getITRF(IERSConventions.IERS_2010, true));
+    }
+
+    private NormalizedSphericalHarmonicsProvider createGravityField()
+        throws OrekitException {
+        return GravityFieldFactory.getNormalizedProvider(12, 12);
+    }
+
+    private Orbit createOrbit(double mu)
+        throws OrekitException {
+        // the following 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
+        AbsoluteDate date = new AbsoluteDate("2012-01-01T00:00:00.000", TimeScalesFactory.getUTC());
+        Frame eme2000 = FramesFactory.getEME2000();
+        return new CircularOrbit(7173352.811913891,
+                                 -4.029194321683225E-4, 0.0013530362644647786,
+                                 FastMath.toRadians(98.63218182243709),
+                                 FastMath.toRadians(77.55565567747836),
+                                 FastMath.PI, PositionAngle.TRUE,
+                                 eme2000, date, mu);
+    }
+
+    private Propagator createPropagator(BodyShape earth,
+                                        NormalizedSphericalHarmonicsProvider gravityField,
+                                        Orbit orbit)
+        throws OrekitException {
+
+        AttitudeProvider yawCompensation = new YawCompensation(new NadirPointing(earth));
+        SpacecraftState state = new SpacecraftState(orbit,
+                                                    yawCompensation.getAttitude(orbit,
+                                                                                orbit.getDate(),
+                                                                                orbit.getFrame()),
+                                                    1180.0);
+
+        // numerical model for improving orbit
+        OrbitType type = OrbitType.CIRCULAR;
+        double[][] tolerances = NumericalPropagator.tolerances(0.1, orbit, type);
+        DormandPrince853Integrator integrator =
+                new DormandPrince853Integrator(1.0e-4 * orbit.getKeplerianPeriod(),
+                                               1.0e-1 * orbit.getKeplerianPeriod(),
+                                               tolerances[0], tolerances[1]);
+        integrator.setInitialStepSize(1.0e-2 * orbit.getKeplerianPeriod());
+        NumericalPropagator numericalPropagator = new NumericalPropagator(integrator);
+        numericalPropagator.addForceModel(new HolmesFeatherstoneAttractionModel(earth.getBodyFrame(), gravityField));
+        numericalPropagator.addForceModel(new ThirdBodyAttraction(CelestialBodyFactory.getSun()));
+        numericalPropagator.addForceModel(new ThirdBodyAttraction(CelestialBodyFactory.getMoon()));
+        numericalPropagator.setOrbitType(type);
+        numericalPropagator.setInitialState(state);
+        numericalPropagator.setAttitudeProvider(yawCompensation);
+        return numericalPropagator;
+
+    }
+
+    private List<TimeDependentLOS> createLOSPerfectLine(Vector3D center, Vector3D normal, double halfAperture, int n) {
+        List<TimeDependentLOS> list = new ArrayList<TimeDependentLOS>(n);
+        for (int i = 0; i < n; ++i) {
+            double alpha = (halfAperture * (2 * i + 1 - n)) / (n - 1);
+            list.add(new FixedLOS(new Rotation(normal, alpha).applyTo(center)));
+        }
+        return list;
+    }
+
+    private TimeStampedPVCoordinates createPV(AbsoluteDate t0, double dt,
+                                              double px, double py, double pz,
+                                              double vx, double vy, double vz) {
+        return new TimeStampedPVCoordinates(t0.shiftedBy(dt),
+                                            new Vector3D(px, py, pz),
+                                            new Vector3D(vx, vy, vz),
+                                            Vector3D.ZERO);
+    }
+
+    private TimeStampedAngularCoordinates createQ(AbsoluteDate t0, double dt,
+                                                       double q0, double q1, double q2, double q3) {
+        return new TimeStampedAngularCoordinates(t0.shiftedBy(dt),
+                                                 new Rotation(q0, q1, q2, q3, true),
+                                                 Vector3D.ZERO, Vector3D.ZERO);
+    }
+
+    private List<TimeStampedPVCoordinates> orbitToPV(Orbit orbit, BodyShape earth,
+                                                     AbsoluteDate minDate, AbsoluteDate maxDate,
+                                                     double step)
+        throws PropagationException {
+        Propagator propagator = new KeplerianPropagator(orbit);
+        propagator.setAttitudeProvider(new YawCompensation(new NadirPointing(earth)));
+        propagator.propagate(minDate);
+        final List<TimeStampedPVCoordinates> list = new ArrayList<TimeStampedPVCoordinates>();
+        propagator.setMasterMode(step, new OrekitFixedStepHandler() {
+            public void init(SpacecraftState s0, AbsoluteDate t) {
+            }   
+            public void handleStep(SpacecraftState currentState, boolean isLast) {
+                list.add(new TimeStampedPVCoordinates(currentState.getDate(),
+                                                      currentState.getPVCoordinates().getPosition(),
+                                                      currentState.getPVCoordinates().getVelocity(),
+                                                      Vector3D.ZERO));
+            }
+        });
+        propagator.propagate(maxDate);
+        return list;
+    }
+
+    private List<TimeStampedAngularCoordinates> orbitToQ(Orbit orbit, BodyShape earth,
+                                                         AbsoluteDate minDate, AbsoluteDate maxDate,
+                                                         double step)
+        throws PropagationException {
+        Propagator propagator = new KeplerianPropagator(orbit);
+        propagator.setAttitudeProvider(new YawCompensation(new NadirPointing(earth)));
+        propagator.propagate(minDate);
+        final List<TimeStampedAngularCoordinates> list = new ArrayList<TimeStampedAngularCoordinates>();
+        propagator.setMasterMode(step, new OrekitFixedStepHandler() {
+            public void init(SpacecraftState s0, AbsoluteDate t) {
+            }   
+            public void handleStep(SpacecraftState currentState, boolean isLast) {
+                list.add(new TimeStampedAngularCoordinates(currentState.getDate(),
+                                                           currentState.getAttitude().getRotation(),
+                                                           Vector3D.ZERO, Vector3D.ZERO));
+            }
+        });
+        propagator.propagate(maxDate);
+        return list;
+    }
+
+}
+
diff --git a/src/test/java/org/orekit/rugged/api/RuggedTest.java b/src/test/java/org/orekit/rugged/api/RuggedTest.java
index 5a61f439..8a9bbc14 100644
--- a/src/test/java/org/orekit/rugged/api/RuggedTest.java
+++ b/src/test/java/org/orekit/rugged/api/RuggedTest.java
@@ -17,19 +17,13 @@
 package org.orekit.rugged.api;
 
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.EOFException;
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.RandomAccessFile;
-import java.io.StreamCorruptedException;
 import java.net.URISyntaxException;
 import java.nio.MappedByteBuffer;
 import java.nio.channels.FileChannel;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
 
@@ -90,191 +84,6 @@ public class RuggedTest {
     @Rule
     public TemporaryFolder tempFolder = new TemporaryFolder();
 
-    @Test
-    public void testSetContextWithEphemerides()
-        throws RuggedException, OrekitException, URISyntaxException {
-
-        String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
-        DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
-        AbsoluteDate t0 = new AbsoluteDate("2012-01-01T00:00:00", TimeScalesFactory.getUTC());
-
-        List<TimeStampedPVCoordinates> pv = Arrays.asList(
-            createPV(t0, 0.000, -1545168.478, -7001985.361,       0.000, -1095.152224, 231.344922, -7372.851944),
-            createPV(t0, 1.000, -1546262.794, -7001750.226,   -7372.851, -1093.478904, 238.925123, -7372.847995),
-            createPV(t0, 2.000, -1547355.435, -7001507.511,  -14745.693, -1091.804408, 246.505033, -7372.836044),
-            createPV(t0, 3.000, -1548446.402, -7001257.216,  -22118.520, -1090.128736, 254.084644, -7372.816090),
-            createPV(t0, 4.000, -1549535.693, -7000999.342,  -29491.323, -1088.451892, 261.663949, -7372.788133),
-            createPV(t0, 5.000, -1550623.306, -7000733.888,  -36864.094, -1086.773876, 269.242938, -7372.752175),
-            createPV(t0, 6.000, -1551709.240, -7000460.856,  -44236.825, -1085.094690, 276.821604, -7372.708214),
-            createPV(t0, 7.000, -1552793.495, -7000180.245,  -51609.507, -1083.414336, 284.399938, -7372.656251),
-            createPV(t0, 8.000, -1553876.068, -6999892.056,  -58982.134, -1081.732817, 291.977932, -7372.596287),
-            createPV(t0, 9.000, -1554956.960, -6999596.289,  -66354.697, -1080.050134, 299.555578, -7372.528320),
-            createPV(t0,10.000, -1556036.168, -6999292.945,  -73727.188, -1078.366288, 307.132868, -7372.452352),
-            createPV(t0,11.000, -1557113.692, -6998982.024,  -81099.599, -1076.681282, 314.709792, -7372.368382),
-            createPV(t0,12.000, -1558189.530, -6998663.526,  -88471.922, -1074.995118, 322.286344, -7372.276411),
-            createPV(t0,13.000, -1559263.682, -6998337.451,  -95844.150, -1073.307797, 329.862513, -7372.176439),
-            createPV(t0,14.000, -1560336.145, -6998003.801, -103216.273, -1071.619321, 337.438294, -7372.068466),
-            createPV(t0,15.000, -1561406.920, -6997662.575, -110588.284, -1069.929692, 345.013676, -7371.952492),
-            createPV(t0,16.000, -1562476.004, -6997313.774, -117960.175, -1068.238912, 352.588652, -7371.828517),
-            createPV(t0,17.000, -1563543.398, -6996957.398, -125331.938, -1066.546983, 360.163213, -7371.696542),
-            createPV(t0,18.000, -1564609.098, -6996593.447, -132703.565, -1064.853906, 367.737352, -7371.556566),
-            createPV(t0,19.000, -1565673.105, -6996221.923, -140075.049, -1063.159684, 375.311060, -7371.408591),
-            createPV(t0,20.000, -1566735.417, -6995842.825, -147446.380, -1061.464319, 382.884328, -7371.252616));
-
-        List<TimeStampedAngularCoordinates> q = Arrays.asList(
-            createQ(t0, 0.000, 0.516354347549, -0.400120145429,  0.583012133139,  0.483093065155),
-            createQ(t0, 1.000, 0.516659035405, -0.399867643627,  0.582741754688,  0.483302551263),
-            createQ(t0, 2.000, 0.516963581177, -0.399615033309,  0.582471217473,  0.483511904409),
-            createQ(t0, 3.000, 0.517267984776, -0.399362314553,  0.582200521577,  0.483721124530),
-            createQ(t0, 4.000, 0.517572246112, -0.399109487434,  0.581929667081,  0.483930211565),
-            createQ(t0, 5.000, 0.517876365096, -0.398856552030,  0.581658654071,  0.484139165451),
-            createQ(t0, 6.000, 0.518180341637, -0.398603508416,  0.581387482627,  0.484347986126),
-            createQ(t0, 7.000, 0.518484175647, -0.398350356669,  0.581116152834,  0.484556673529),
-            createQ(t0, 8.000, 0.518787867035, -0.398097096866,  0.580844664773,  0.484765227599),
-            createQ(t0, 9.000, 0.519091415713, -0.397843729083,  0.580573018530,  0.484973648272),
-            createQ(t0,10.000, 0.519394821590, -0.397590253397,  0.580301214186,  0.485181935488),
-            createQ(t0,11.000, 0.519698084578, -0.397336669885,  0.580029251825,  0.485390089185),
-            createQ(t0,12.000, 0.520001204587, -0.397082978623,  0.579757131530,  0.485598109301),
-            createQ(t0,13.000, 0.520304181527, -0.396829179688,  0.579484853385,  0.485805995775),
-            createQ(t0,14.000, 0.520607015311, -0.396575273158,  0.579212417473,  0.486013748545),
-            createQ(t0,15.000, 0.520909705847, -0.396321259108,  0.578939823877,  0.486221367550),
-            createQ(t0,16.000, 0.521212253049, -0.396067137616,  0.578667072681,  0.486428852729),
-            createQ(t0,17.000, 0.521514656825, -0.395812908759,  0.578394163969,  0.486636204020),
-            createQ(t0,18.000, 0.521816917089, -0.395558572613,  0.578121097824,  0.486843421362),
-            createQ(t0,19.000, 0.522119033749, -0.395304129256,  0.577847874330,  0.487050504694),
-            createQ(t0,20.000, 0.522421006719, -0.395049578765,  0.577574493570,  0.487257453954));
-
-        TileUpdater updater =
-                new VolcanicConeElevationUpdater(new GeodeticPoint(FastMath.toRadians(13.25667), FastMath.toRadians(123.685), 2463.0),
-                                                 FastMath.toRadians(30.0), 16.0,
-                                                 FastMath.toRadians(1.0), 1201);
-
-        Rugged rugged = new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                   EllipsoidId.GRS80, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                   pv.get(0).getDate(), pv.get(pv.size() - 1).getDate(), 0.001,
-                                   5.0, pv, 8, CartesianDerivativesFilter.USE_PV, q, 8, AngularDerivativesFilter.USE_R);
-
-        Assert.assertTrue(rugged.isLightTimeCorrected());
-        rugged.setLightTimeCorrection(false);
-        Assert.assertFalse(rugged.isLightTimeCorrected());
-        Assert.assertTrue(rugged.isAberrationOfLightCorrected());
-        rugged.setAberrationOfLightCorrection(false);
-        Assert.assertFalse(rugged.isAberrationOfLightCorrected());
-
-    }
-
-    @Test
-    public void testSetContextWithPropagator()
-        throws RuggedException, OrekitException, URISyntaxException {
-
-        String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
-        DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
-        BodyShape  earth                                  = createEarth();
-        NormalizedSphericalHarmonicsProvider gravityField = createGravityField();
-        Orbit      orbit                                  = createOrbit(gravityField.getMu());
-        Propagator propagator                             = createPropagator(earth, gravityField, orbit);
-
-        TileUpdater updater =
-                new VolcanicConeElevationUpdater(new GeodeticPoint(FastMath.toRadians(13.25667), FastMath.toRadians(123.685), 2463.0),
-                                                 FastMath.toRadians(30.0), 16.0,
-                                                 FastMath.toRadians(1.0), 1201);
-
-        Rugged rugged = new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                   EllipsoidId.IERS96, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                   orbit.getDate().shiftedBy(-10.0), orbit.getDate().shiftedBy(+10.0), 0.001,
-                                   5.0, 1.0, 8, CartesianDerivativesFilter.USE_PV, AngularDerivativesFilter.USE_R, propagator);
-
-        Assert.assertTrue(rugged.isLightTimeCorrected());
-        rugged.setLightTimeCorrection(false);
-        Assert.assertFalse(rugged.isLightTimeCorrected());
-        Assert.assertTrue(rugged.isAberrationOfLightCorrected());
-        rugged.setAberrationOfLightCorrection(false);
-        Assert.assertFalse(rugged.isAberrationOfLightCorrected());
-        Assert.assertEquals(orbit.getDate().shiftedBy(-10.0), rugged.getMinDate());
-        Assert.assertEquals(orbit.getDate().shiftedBy(+10.0), rugged.getMaxDate());
-        Assert.assertEquals(0, rugged.getLineSensors().size());
-
-    }
-
-    @Test
-    public void testOutOfTimeRange()
-        throws RuggedException, OrekitException, URISyntaxException {
-
-        String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
-        DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
-        AbsoluteDate t0 = new AbsoluteDate("2012-01-01T00:00:00", TimeScalesFactory.getUTC());
-
-        List<TimeStampedPVCoordinates> pv = Arrays.asList(
-            createPV(t0, 0.000, -1545168.478, -7001985.361,       0.000, -1095.152224, 231.344922, -7372.851944),
-            createPV(t0, 1.000, -1546262.794, -7001750.226,   -7372.851, -1093.478904, 238.925123, -7372.847995),
-            createPV(t0, 2.000, -1547355.435, -7001507.511,  -14745.693, -1091.804408, 246.505033, -7372.836044),
-            createPV(t0, 3.000, -1548446.402, -7001257.216,  -22118.520, -1090.128736, 254.084644, -7372.816090),
-            createPV(t0, 4.000, -1549535.693, -7000999.342,  -29491.323, -1088.451892, 261.663949, -7372.788133),
-            createPV(t0, 5.000, -1550623.306, -7000733.888,  -36864.094, -1086.773876, 269.242938, -7372.752175),
-            createPV(t0, 6.000, -1551709.240, -7000460.856,  -44236.825, -1085.094690, 276.821604, -7372.708214),
-            createPV(t0, 7.000, -1552793.495, -7000180.245,  -51609.507, -1083.414336, 284.399938, -7372.656251),
-            createPV(t0, 8.000, -1553876.068, -6999892.056,  -58982.134, -1081.732817, 291.977932, -7372.596287),
-            createPV(t0, 9.000, -1554956.960, -6999596.289,  -66354.697, -1080.050134, 299.555578, -7372.528320),
-            createPV(t0,10.000, -1556036.168, -6999292.945,  -73727.188, -1078.366288, 307.132868, -7372.452352));
-
-        List<TimeStampedAngularCoordinates> q = Arrays.asList(
-            createQ(t0, 4.000, 0.517572246112, -0.399109487434,  0.581929667081,  0.483930211565),
-            createQ(t0, 5.000, 0.517876365096, -0.398856552030,  0.581658654071,  0.484139165451),
-            createQ(t0, 6.000, 0.518180341637, -0.398603508416,  0.581387482627,  0.484347986126),
-            createQ(t0, 7.000, 0.518484175647, -0.398350356669,  0.581116152834,  0.484556673529),
-            createQ(t0, 8.000, 0.518787867035, -0.398097096866,  0.580844664773,  0.484765227599));
-
-        TileUpdater updater =
-                new VolcanicConeElevationUpdater(new GeodeticPoint(FastMath.toRadians(13.25667), FastMath.toRadians(123.685), 2463.0),
-                                                 FastMath.toRadians(30.0), 16.0,
-                                                 FastMath.toRadians(1.0), 1201);
-
-        Assert.assertNotNull(new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                        EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                        t0.shiftedBy(4), t0.shiftedBy(6), 0.001,
-                                        5.0, pv,2, CartesianDerivativesFilter.USE_PV, q, 2, AngularDerivativesFilter.USE_R));
-        try {
-            new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                   EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                   t0.shiftedBy(-1), t0.shiftedBy(6), 0.001,
-                                   0.0001, pv, 2, CartesianDerivativesFilter.USE_PV, q, 2, AngularDerivativesFilter.USE_R);
-        } catch (RuggedException re) {
-            Assert.assertEquals(RuggedMessages.OUT_OF_TIME_RANGE, re.getSpecifier());
-            Assert.assertEquals(t0.shiftedBy(-1), re.getParts()[0]);
-        }
-
-        try {
-            new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                   EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                   t0.shiftedBy(2), t0.shiftedBy(6), 0.001,
-                                   0.0001, pv, 2, CartesianDerivativesFilter.USE_PV, q, 2, AngularDerivativesFilter.USE_R);
-        } catch (RuggedException re) {
-            Assert.assertEquals(RuggedMessages.OUT_OF_TIME_RANGE, re.getSpecifier());
-            Assert.assertEquals(t0.shiftedBy(2), re.getParts()[0]);
-        }
-
-        try {
-            new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                   EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                   t0.shiftedBy(4), t0.shiftedBy(9), 0.001,
-                                   0.0001, pv, 2, CartesianDerivativesFilter.USE_PV, q, 2, AngularDerivativesFilter.USE_R);
-        } catch (RuggedException re) {
-            Assert.assertEquals(RuggedMessages.OUT_OF_TIME_RANGE, re.getSpecifier());
-            Assert.assertEquals(t0.shiftedBy(9), re.getParts()[0]);
-        }
-
-        try {
-            new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                   EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                   t0.shiftedBy(4), t0.shiftedBy(12), 0.001,
-                                   0.0001, pv, 2, CartesianDerivativesFilter.USE_PV, q, 2, AngularDerivativesFilter.USE_R);
-        } catch (RuggedException re) {
-            Assert.assertEquals(RuggedMessages.OUT_OF_TIME_RANGE, re.getSpecifier());
-            Assert.assertEquals(t0.shiftedBy(12), re.getParts()[0]);
-        }
-
-    }
-
     // the following test is disabled by default
     // it is only used to check timings, and also creates a large (66M) temporary file
     @Ignore
@@ -319,16 +128,16 @@ public class RuggedTest {
         propagator.propagate(lineDatation.getDate(lastLine).shiftedBy(+1.0));
         Propagator ephemeris = propagator.getGeneratedEphemeris();
 
-        Rugged rugged = new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                   EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                   lineDatation.getDate(firstLine), lineDatation.getDate(lastLine), 0.001,
-                                   5.0, 1.0 / lineDatation.getRate(0.0),
-                                   8, CartesianDerivativesFilter.USE_PV,
-                                   AngularDerivativesFilter.USE_R, ephemeris);
-        rugged.setLightTimeCorrection(true);
-        rugged.setAberrationOfLightCorrection(true);
-
-        rugged.addLineSensor(lineSensor);
+        Rugged rugged = new RuggedBuilder().
+                setDigitalElevationModel(updater, 8).
+                setAlgorithm(AlgorithmId.DUVENHAGE).
+                setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+                setTimeSpan(lineDatation.getDate(firstLine), lineDatation.getDate(lastLine), 0.001, 5.0).
+                setTrajectory(1.0 / lineDatation.getRate(0.0),
+                              8, CartesianDerivativesFilter.USE_PV,AngularDerivativesFilter.USE_R,
+                              ephemeris).
+                addLineSensor(lineSensor).
+                build();
 
         try {
 
@@ -396,15 +205,18 @@ public class RuggedTest {
         AbsoluteDate minDate = lineSensor.getDate(firstLine);
         AbsoluteDate maxDate = lineSensor.getDate(lastLine);
 
-        Rugged rugged = new Rugged(null, -1, AlgorithmId.IGNORE_DEM_USE_ELLIPSOID,
-                                   EllipsoidId.IERS2003, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                   minDate, maxDate, 0.001,
-                                   5.0, orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                   8,
-                                   CartesianDerivativesFilter.USE_PV, orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                   2, AngularDerivativesFilter.USE_R);
-
-        rugged.addLineSensor(lineSensor);
+        RuggedBuilder builder = new RuggedBuilder().
+                setAlgorithm(AlgorithmId.IGNORE_DEM_USE_ELLIPSOID).
+                setEllipsoid(EllipsoidId.IERS2003, BodyRotatingFrameId.ITRF).
+                setTimeSpan(minDate, maxDate, 0.001, 5.0).
+                setTrajectory(InertialFrameId.EME2000,
+                              orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              8, CartesianDerivativesFilter.USE_PV,
+                              orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              2, AngularDerivativesFilter.USE_R).
+                addLineSensor(lineSensor);
+
+        Rugged rugged = builder.build();
         Assert.assertEquals(1, rugged.getLineSensors().size());
         Assert.assertTrue(lineSensor == rugged.getLineSensor("line"));
         try {
@@ -414,16 +226,24 @@ public class RuggedTest {
             Assert.assertEquals(RuggedMessages.UNKNOWN_SENSOR, re.getSpecifier());
             Assert.assertEquals("dummy", re.getParts()[0]);
         }
-        Assert.assertEquals(7176419.526, rugged.getScToInertial(lineSensor.getDate(dimension / 2)).getTranslation().getNorm(), 1.0e-3);
-        Assert.assertEquals(0.0, rugged.getBodyToInertial(lineSensor.getDate(dimension / 2)).getTranslation().getNorm(), 1.0e-3);
-        Assert.assertEquals(0.0, rugged.getInertialToBody(lineSensor.getDate(dimension / 2)).getTranslation().getNorm(), 1.0e-3);
-
-        rugged.setLightTimeCorrection(true);
-        rugged.setAberrationOfLightCorrection(false);
+        Assert.assertEquals(7176419.526,
+                            rugged.getScToInertial(lineSensor.getDate(dimension / 2)).getTranslation().getNorm(),
+                            1.0e-3);
+        Assert.assertEquals(0.0,
+                            rugged.getBodyToInertial(lineSensor.getDate(dimension / 2)).getTranslation().getNorm(),
+                            1.0e-3);
+        Assert.assertEquals(0.0,
+                            rugged.getInertialToBody(lineSensor.getDate(dimension / 2)).getTranslation().getNorm(),
+                            1.0e-3);
+
+        builder.setLightTimeCorrection(true);
+        builder.setAberrationOfLightCorrection(false);
+        rugged = builder.build();
         GeodeticPoint[] gpWithLightTimeCorrection = rugged.directLocation("line", 200);
 
-        rugged.setLightTimeCorrection(false);
-        rugged.setAberrationOfLightCorrection(false);
+        builder.setLightTimeCorrection(false);
+        builder.setAberrationOfLightCorrection(false);
+        rugged = builder.build();
         GeodeticPoint[] gpWithoutLightTimeCorrection = rugged.directLocation("line", 200);
 
         for (int i = 0; i < gpWithLightTimeCorrection.length; ++i) {
@@ -463,22 +283,25 @@ public class RuggedTest {
         AbsoluteDate minDate = lineSensor.getDate(firstLine);
         AbsoluteDate maxDate = lineSensor.getDate(lastLine);
 
-        Rugged rugged = new Rugged(null, -1, AlgorithmId.IGNORE_DEM_USE_ELLIPSOID,
-                                   EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                   minDate, maxDate, 0.001,
-                                   5.0, orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                   8,
-                                   CartesianDerivativesFilter.USE_PV, orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                   2, AngularDerivativesFilter.USE_R);
-
-        rugged.addLineSensor(lineSensor);
-
-        rugged.setLightTimeCorrection(false);
-        rugged.setAberrationOfLightCorrection(true);
+        RuggedBuilder builder = new RuggedBuilder().
+                
+                setAlgorithm(AlgorithmId.IGNORE_DEM_USE_ELLIPSOID).
+                setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+                setTimeSpan(minDate, maxDate, 0.001, 5.0).
+                setTrajectory(InertialFrameId.EME2000,
+                              orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              8, CartesianDerivativesFilter.USE_PV,
+                              orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              2, AngularDerivativesFilter.USE_R).
+                addLineSensor(lineSensor).
+                setLightTimeCorrection(false).
+                setAberrationOfLightCorrection(true);
+        Rugged rugged = builder.build();
         GeodeticPoint[] gpWithAberrationOfLightCorrection = rugged.directLocation("line", 200);
 
-        rugged.setLightTimeCorrection(false);
-        rugged.setAberrationOfLightCorrection(false);
+        builder.setLightTimeCorrection(false);
+        builder.setAberrationOfLightCorrection(false);
+        rugged = builder.build();
         GeodeticPoint[] gpWithoutAberrationOfLightCorrection = rugged.directLocation("line", 200);
 
         for (int i = 0; i < gpWithAberrationOfLightCorrection.length; ++i) {
@@ -507,7 +330,8 @@ public class RuggedTest {
         // 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);
-        List<TimeDependentLOS> los = createLOSPerfectLine(new Rotation(Vector3D.PLUS_I, FastMath.toRadians(50.0)).applyTo(Vector3D.PLUS_K),
+        List<TimeDependentLOS> los = 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
@@ -522,25 +346,21 @@ public class RuggedTest {
                 new RandomLandscapeUpdater(0.0, 9000.0, 0.5, 0xf0a401650191f9f6l,
                                            FastMath.toRadians(1.0), 257);
 
-        Rugged ruggedFull = new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                       EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                       minDate, maxDate, 0.001,
-                                       5.0, orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                       8,
-                                       CartesianDerivativesFilter.USE_PV, orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                       2, AngularDerivativesFilter.USE_R);
-        ruggedFull.addLineSensor(lineSensor);
-        GeodeticPoint[] gpWithFlatBodyCorrection = ruggedFull.directLocation("line", 100);
-
-        Rugged ruggedFlat = new Rugged(updater, 8, AlgorithmId.DUVENHAGE_FLAT_BODY,
-                                       EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                       minDate, maxDate, 0.001,
-                                       5.0, orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                       8,
-                                       CartesianDerivativesFilter.USE_PV, orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                       2, AngularDerivativesFilter.USE_R);
-        ruggedFlat.addLineSensor(lineSensor);
-        GeodeticPoint[] gpWithoutFlatBodyCorrection = ruggedFlat.directLocation("line", 100);
+        RuggedBuilder builder = new RuggedBuilder().
+                setDigitalElevationModel(updater, 8).
+                setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+                setTimeSpan(minDate, maxDate, 0.001, 5.0).
+                setTrajectory(InertialFrameId.EME2000,
+                              orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              8, CartesianDerivativesFilter.USE_PV,
+                              orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              2, AngularDerivativesFilter.USE_R).
+                addLineSensor(lineSensor);
+        GeodeticPoint[] gpWithFlatBodyCorrection =
+                builder.setAlgorithm(AlgorithmId.DUVENHAGE).build().directLocation("line", 100);
+
+        GeodeticPoint[] gpWithoutFlatBodyCorrection =
+                builder.setAlgorithm(AlgorithmId.DUVENHAGE_FLAT_BODY).build().directLocation("line", 100);
 
         SummaryStatistics stats = new SummaryStatistics();
         for (int i = 0; i < gpWithFlatBodyCorrection.length; ++i) {
@@ -571,7 +391,8 @@ public class RuggedTest {
         // 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);
-        List<TimeDependentLOS> los = createLOSPerfectLine(new Rotation(Vector3D.PLUS_I, FastMath.toRadians(50.0)).applyTo(Vector3D.PLUS_K),
+        List<TimeDependentLOS> los = 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
@@ -586,14 +407,17 @@ public class RuggedTest {
                 new RandomLandscapeUpdater(0.0, 9000.0, 0.5, 0xf0a401650191f9f6l,
                                            FastMath.toRadians(1.0), 257);
 
-        Rugged rugged = new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                   EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                   minDate, maxDate, 0.001,
-                                   5.0, orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                   8,
-                                   CartesianDerivativesFilter.USE_PV, orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                   2, AngularDerivativesFilter.USE_R);
-        rugged.addLineSensor(lineSensor);
+        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,
+                              orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              8, CartesianDerivativesFilter.USE_PV,
+                              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) {
@@ -624,7 +448,8 @@ public class RuggedTest {
         // 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);
-        List<TimeDependentLOS> los = createLOSPerfectLine(new Rotation(Vector3D.PLUS_I, FastMath.toRadians(50.0)).applyTo(Vector3D.PLUS_K),
+        List<TimeDependentLOS> los = 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
@@ -639,16 +464,20 @@ public class RuggedTest {
                 new RandomLandscapeUpdater(0.0, 9000.0, 0.5, 0xf0a401650191f9f6l,
                                            FastMath.toRadians(1.0), 257);
 
-        Rugged rugged = new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                   EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                   minDate, maxDate, 0.001,
-                                   5.0, orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                   8,
-                                   CartesianDerivativesFilter.USE_PV, orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                   2, AngularDerivativesFilter.USE_R);
-        rugged.setAberrationOfLightCorrection(false);
-        rugged.setLightTimeCorrection(false);
-        rugged.addLineSensor(lineSensor);
+        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,
+                              orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              8, CartesianDerivativesFilter.USE_PV,
+                              orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              2, AngularDerivativesFilter.USE_R).
+                setAberrationOfLightCorrection(false).
+                setLightTimeCorrection(false).
+                addLineSensor(lineSensor).
+                build();
         GeodeticPoint[] gpLine = rugged.directLocation("line", 100);
 
         for (int i = 0; i < gpLine.length; ++i) {
@@ -679,7 +508,8 @@ public class RuggedTest {
         // 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);
-        List<TimeDependentLOS> los = createLOSPerfectLine(new Rotation(Vector3D.PLUS_I, FastMath.toRadians(50.0)).applyTo(Vector3D.PLUS_K),
+        List<TimeDependentLOS> los = 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
@@ -694,25 +524,21 @@ public class RuggedTest {
                 new RandomLandscapeUpdater(0.0, 9000.0, 0.5, 0xe12ef744f224cf43l,
                                            FastMath.toRadians(1.0), 257);
 
-        Rugged ruggedDuvenhage = new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                            EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                            minDate, maxDate, 0.001,
-                                            5.0, orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                            8,
-                                            CartesianDerivativesFilter.USE_PV, orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                            2, AngularDerivativesFilter.USE_R);
-        ruggedDuvenhage.addLineSensor(lineSensor);
-        GeodeticPoint[] gpDuvenhage = ruggedDuvenhage.directLocation("line", 100);
-
-        Rugged ruggedBasicScan = new Rugged(updater, 8, AlgorithmId.BASIC_SLOW_EXHAUSTIVE_SCAN_FOR_TESTS_ONLY,
-                                            EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                            minDate, maxDate, 0.001,
-                                            5.0, orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                            8,
-                                            CartesianDerivativesFilter.USE_PV, orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                            2, AngularDerivativesFilter.USE_R);
-        ruggedBasicScan.addLineSensor(lineSensor);
-        GeodeticPoint[] gpBasicScan = ruggedBasicScan.directLocation("line", 100);
+        RuggedBuilder builder = new RuggedBuilder().
+                setDigitalElevationModel(updater, 8).
+                setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+                setTimeSpan(minDate, maxDate, 0.001, 5.0).
+                setTrajectory(InertialFrameId.EME2000,
+                              orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              8, CartesianDerivativesFilter.USE_PV,
+                              orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              2, AngularDerivativesFilter.USE_R).
+                addLineSensor(lineSensor);
+        GeodeticPoint[] gpDuvenhage =
+                builder.setAlgorithm(AlgorithmId.DUVENHAGE).build().directLocation("line", 100);
+
+        GeodeticPoint[] gpBasicScan =
+                builder.setAlgorithm(AlgorithmId.BASIC_SLOW_EXHAUSTIVE_SCAN_FOR_TESTS_ONLY).build().directLocation("line", 100);
 
         double[] data = new double[gpDuvenhage.length]; 
         for (int i = 0; i < gpDuvenhage.length; ++i) {
@@ -724,256 +550,6 @@ public class RuggedTest {
 
     }
 
-    @Test
-    public void testInterpolatorDump()
-        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 = createEarth();
-        final Orbit      orbit = 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);
-        List<TimeDependentLOS> los = 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, 0x84186d1344722b8fl,
-                                           FastMath.toRadians(1.0), 257);
-
-        Rugged ruggedOriginal = new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                       EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                       minDate, maxDate, 0.001,
-                                       5.0, orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                       8,
-                                       CartesianDerivativesFilter.USE_PV, orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                       2, AngularDerivativesFilter.USE_R);
-        ruggedOriginal.addLineSensor(lineSensor);
-        GeodeticPoint[] gpOriginal = ruggedOriginal.directLocation("line", 100);
-
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        ruggedOriginal.dumpInterpolator(bos);
-        Assert.assertTrue(bos.size() > 100000);
-        Assert.assertTrue(bos.size() < 200000);
-
-        Rugged ruggedRecovered = new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                            EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                            new ByteArrayInputStream(bos.toByteArray()));
-        ruggedRecovered.addLineSensor(lineSensor);
-        GeodeticPoint[] gpRecovered = ruggedRecovered.directLocation("line", 100);
-
-        for (int i = 0; i < gpOriginal.length; ++i) {
-            Vector3D pOriginal  = earth.transform(gpOriginal[i]);
-            Vector3D pRecovered = earth.transform(gpRecovered[i]);
-            Assert.assertEquals(0.0, Vector3D.distance(pOriginal, pRecovered), 1.0e-15);
-        }
-
-    }
-
-    @Test
-    public void testInterpolatorCannotDump()
-        throws RuggedException, OrekitException, URISyntaxException, IOException {
-
-        int dimension = 200;
-
-        String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
-        DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
-        final BodyShape  earth = createEarth();
-        final Orbit      orbit = 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);
-        List<TimeDependentLOS> los = 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);
-
-        Rugged ruggedOriginal = new Rugged(null, -1, AlgorithmId.IGNORE_DEM_USE_ELLIPSOID,
-                                       EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                       minDate, maxDate, 0.001,
-                                       5.0, orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                       8,
-                                       CartesianDerivativesFilter.USE_PV, orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                       2, AngularDerivativesFilter.USE_R);
-
-        FileOutputStream fos = new FileOutputStream(tempFolder.newFile());
-        fos.close();
-        try {
-            ruggedOriginal.dumpInterpolator(fos);
-            Assert.fail("an exception should have been thrown");
-        } catch (RuggedException re) {
-            Assert.assertEquals(IOException.class, re.getCause().getClass());
-        }
-    }
-    
-    @Test
-    public void testInterpolatorDumpWrongFrame()
-        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 = createEarth();
-        final Orbit      orbit = 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);
-        List<TimeDependentLOS> los = 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);
-
-        Rugged ruggedOriginal = new Rugged(null, -1, AlgorithmId.IGNORE_DEM_USE_ELLIPSOID,
-                                       EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                       minDate, maxDate, 0.001,
-                                       5.0, orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                       8,
-                                       CartesianDerivativesFilter.USE_PV, orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                       2, AngularDerivativesFilter.USE_R);
-
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        ruggedOriginal.dumpInterpolator(bos);
-        Assert.assertTrue(bos.size() > 100000);
-        Assert.assertTrue(bos.size() < 200000);
-
-        for (InertialFrameId iId : Arrays.asList(InertialFrameId.GCRF,
-                                                 InertialFrameId.MOD,
-                                                 InertialFrameId.TOD,
-                                                 InertialFrameId.VEIS1950)) {
-            try {
-                new Rugged(null, -1, AlgorithmId.IGNORE_DEM_USE_ELLIPSOID,
-                           EllipsoidId.WGS84, iId, BodyRotatingFrameId.ITRF,
-                           new ByteArrayInputStream(bos.toByteArray()));
-                Assert.fail("an exception should have been thrown");
-            } catch (RuggedException re) {
-                Assert.assertEquals(RuggedMessages.FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP,
-                                    re.getSpecifier());
-            }
-        }
-
-        for (BodyRotatingFrameId bId : Arrays.asList(BodyRotatingFrameId.GTOD,
-                                                     BodyRotatingFrameId.ITRF_EQUINOX)) {
-            try {
-                new Rugged(null, -1, AlgorithmId.IGNORE_DEM_USE_ELLIPSOID,
-                           EllipsoidId.WGS84, InertialFrameId.EME2000, bId,
-                           new ByteArrayInputStream(bos.toByteArray()));
-                Assert.fail("an exception should have been thrown");
-            } catch (RuggedException re) {
-                Assert.assertEquals(RuggedMessages.FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP,
-                                    re.getSpecifier());
-            }
-        }
-    }
-
-    @Test
-    public void testInterpolatorNotADump()
-        throws RuggedException, OrekitException, URISyntaxException {
-
-        String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
-        DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
-
-        // the following array is a real serialization file corresponding to the following
-        // made-up empty class that does not exist in Rugged:
-        //        public class NonExistentClass implements java.io.Serializable {
-        //            private static final long serialVersionUID = -1;
-        //        }
-        byte[] nonExistentClass = new byte[] {
-            -84, -19,   0,   5, 115, 114,
-              0,  16,  78, 111, 110,  69,
-            120, 105, 115, 116, 101, 110,
-            116,  67, 108,  97, 115, 115,
-             -1,  -1,  -1,  -1,  -1,  -1,
-             -1,  -1,   2,   0,   0, 120,
-            112
-        };
-
-        // the following array is a real serialization file of object Integer.valueOf(1)
-        byte[] integerOne = new byte[] {
-          -84, -19,   0,   5, 115, 114,
-            0,  17, 106,  97, 118,  97,
-           46, 108,  97, 110, 103,  46,
-           73, 110, 116, 101, 103, 101,
-          114,  18, -30, -96, -92,  -9,
-         -127,-121,  56,   2,   0,   1,
-           73,   0,   5, 118,  97, 108,
-          117, 101, 120, 114,   0,  16,
-          106,  97, 118,  97,  46, 108,
-           97, 110, 103,  46,  78, 117,
-          109,  98, 101, 114,-122, -84,
-         -107,  29,  11,-108, -32,-117,
-            2,   0,   0, 120, 112,   0,
-            0,   0,   1
-        };
-
-        // the following array is a truncation of the previous one
-        byte[] truncatedDump = new byte[] {
-          -84, -19,   0,   5, 115, 114,
-            0,  17, 106,  97, 118,  97
-        };
-
-        byte[] notSerialization = new byte[] {
-            1, 2, 3, 4, 5, 6
-        };
-
-        for (byte[] array : Arrays.asList(nonExistentClass, integerOne, truncatedDump, notSerialization)) {
-            try {
-                new Rugged(null, -1, AlgorithmId.IGNORE_DEM_USE_ELLIPSOID,
-                           EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                           new ByteArrayInputStream(array));
-                Assert.fail("an exception should have been thrown");
-            } catch (RuggedException re) {
-                Assert.assertEquals(RuggedMessages.NOT_INTERPOLATOR_DUMP_DATA,
-                                    re.getSpecifier());
-                if (array == nonExistentClass) {
-                    Assert.assertEquals(ClassNotFoundException.class, re.getCause().getClass());
-                } else if (array == integerOne) {
-                    Assert.assertEquals(ClassCastException.class, re.getCause().getClass());
-                } else if (array == truncatedDump) {
-                    Assert.assertEquals(EOFException.class, re.getCause().getClass());
-                } else if (array == notSerialization) {
-                    Assert.assertEquals(StreamCorruptedException.class, re.getCause().getClass());
-                }
-            }
-        }
-
-    }
-
     // the following test is disabled by default
     // it is only used to check timings, and also creates a large (38M) temporary file
     @Ignore
@@ -1016,18 +592,22 @@ public class RuggedTest {
                 new RandomLandscapeUpdater(0.0, 9000.0, 0.5, 0xf0a401650191f9f6l,
                                            FastMath.toRadians(1.0), 257);
 
-        Rugged rugged = new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                   EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                   minDate, maxDate, 0.001,
-                                   5.0, orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                   8,
-                                   CartesianDerivativesFilter.USE_PV, orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                   2, AngularDerivativesFilter.USE_R);
-        rugged.setLightTimeCorrection(true);
-        rugged.setAberrationOfLightCorrection(true);
+        RuggedBuilder builder = new RuggedBuilder().
+                setDigitalElevationModel(updater, 8).
+                setAlgorithm(AlgorithmId.DUVENHAGE).
+                setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+                setTimeSpan(minDate, maxDate, 0.001, 5.0).
+                setTrajectory(InertialFrameId.EME2000,
+                              orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              8, CartesianDerivativesFilter.USE_PV,
+                              orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              2, AngularDerivativesFilter.USE_R);
+        builder.setLightTimeCorrection(true);
+        builder.setAberrationOfLightCorrection(true);
         for (LineSensor lineSensor : sensors) {
-            rugged.addLineSensor(lineSensor);
+            builder.addLineSensor(lineSensor);
         }
+        Rugged rugged = builder.build();
 
         double lat0  = FastMath.toRadians(-22.9);
         double lon0  = FastMath.toRadians(142.4);
@@ -1136,11 +716,14 @@ public class RuggedTest {
         addSatellitePV(gps, eme2000, itrf, satellitePVList, "2009-12-11T17:02:18.592937", -1249311.381d, -6220723.191d, 3326367.397d, -1896.952d, -3101.26d, -6513.056d);
 
         TileUpdater updater = new RandomLandscapeUpdater(0.0, 9000.0, 0.5, 0xf0a401650191f9f6l, FastMath.toRadians(1.0), 257);
-        Rugged rugged = new Rugged(updater, 8,
-                                   AlgorithmId.DUVENHAGE, EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                   satellitePVList.get(0).getDate(), satellitePVList.get(satellitePVList.size() - 1).getDate(), 0.001,
-                                   5.0, satellitePVList, 8,
-                                   CartesianDerivativesFilter.USE_PV, satelliteQList, 8, AngularDerivativesFilter.USE_R);
+        RuggedBuilder builder = new RuggedBuilder().
+                setDigitalElevationModel(updater, 8).
+                setAlgorithm(AlgorithmId.DUVENHAGE).
+                setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+                setTimeSpan(satellitePVList.get(0).getDate(), satellitePVList.get(satellitePVList.size() - 1).getDate(), 0.001, 5.0).
+                setTrajectory(InertialFrameId.EME2000,
+                              satellitePVList, 8, CartesianDerivativesFilter.USE_PV,
+                              satelliteQList, 8, AngularDerivativesFilter.USE_R);
 
         List<TimeDependentLOS> lineOfSight = new ArrayList<TimeDependentLOS>();
         lineOfSight.add(new FixedLOS(new Vector3D(-0.011204, 0.181530, 1d).normalize()));
@@ -1175,8 +758,9 @@ public class RuggedTest {
         AbsoluteDate absDate = new AbsoluteDate("2009-12-11T16:58:51.593", gps);
         LinearLineDatation lineDatation = new LinearLineDatation(absDate, 1d, 638.5696040868454);
         LineSensor lineSensor = new LineSensor("perfect-line", lineDatation, offset, lineOfSight);
-        rugged.addLineSensor(lineSensor);
+        builder.addLineSensor(lineSensor);
 
+        Rugged rugged = builder.build();
         GeodeticPoint point1 = new GeodeticPoint(0.7053784581520293, -1.7354535645320581, 691.856741468848);
         SensorPixel sensorPixel1 = rugged.inverseLocation(lineSensor.getName(), point1, 1, 131328);
         Assert.assertEquals(   2.01472, sensorPixel1.getLineNumber(), 1.0e-5);
@@ -1289,11 +873,6 @@ public class RuggedTest {
         addSatellitePV(gps, eme2000, itrf, satellitePVList, "2013-07-07T17:20:39.857531", -931933.933d, -6354591.778d, 3173886.968d, -1693.833d, -3045.116d,
                 -6595.024d);
 
-        TileUpdater updater = null;
-        Rugged rugged = new Rugged(updater, 8, AlgorithmId.IGNORE_DEM_USE_ELLIPSOID, EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                satellitePVList.get(0).getDate(), satellitePVList.get(satellitePVList.size() - 1).getDate(), 0.1, 10.0, satellitePVList,
-                6, CartesianDerivativesFilter.USE_P, satelliteQList, 8, AngularDerivativesFilter.USE_R);
-
         List<TimeDependentLOS> lineOfSight = new ArrayList<TimeDependentLOS>();
         lineOfSight.add(new FixedLOS(new Vector3D(0.0046536264d, -0.1851800945d, 1d)));
         lineOfSight.add(new FixedLOS(new Vector3D(0.0000001251d, -0.0002815246d, 1d)));
@@ -1302,7 +881,16 @@ public class RuggedTest {
         AbsoluteDate absDate = new AbsoluteDate("2013-07-07T17:16:36.857", gps);
         LinearLineDatation lineDatation = new LinearLineDatation(absDate, 0.03125d, 19.95565693384045);
         LineSensor lineSensor = new LineSensor("QUICK_LOOK", lineDatation, offset, lineOfSight);
-        rugged.addLineSensor(lineSensor);
+        Rugged rugged = new RuggedBuilder().
+                setAlgorithm(AlgorithmId.IGNORE_DEM_USE_ELLIPSOID).
+                setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+                setTimeSpan(satellitePVList.get(0).getDate(),
+                            satellitePVList.get(satellitePVList.size() - 1).getDate(), 0.1, 10.0).
+                setTrajectory(InertialFrameId.EME2000,
+                              satellitePVList, 6, CartesianDerivativesFilter.USE_P,
+                              satelliteQList, 8, AngularDerivativesFilter.USE_R).
+                addLineSensor(lineSensor).
+                build();
 
         GeodeticPoint[] temp = rugged.directLocation("QUICK_LOOK", -250);
         GeodeticPoint first = temp[0];
@@ -1353,14 +941,18 @@ public class RuggedTest {
                 new RandomLandscapeUpdater(0.0, 9000.0, 0.3, 0xf0a401650191f9f6l,
                                            FastMath.toRadians(1.0), 257);
 
-        Rugged rugged = new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                   EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                   minDate, maxDate, 0.001,
-                                   5.0, orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                   8, 
-                                   CartesianDerivativesFilter.USE_PV, orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                   2, AngularDerivativesFilter.USE_R);
-        rugged.addLineSensor(lineSensor);
+        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,
+                              orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              8, CartesianDerivativesFilter.USE_PV,
+                              orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              2, AngularDerivativesFilter.USE_R).
+                addLineSensor(lineSensor).
+                build();
 
         int lineNumber = 97;
         GeodeticPoint[] gp = rugged.directLocation("curved", lineNumber);
@@ -1391,12 +983,13 @@ public class RuggedTest {
             double q3) {
         AbsoluteDate attitudeDate = new AbsoluteDate(absDate, gps);
         Rotation rotation = new Rotation(q0, q1, q2, q3, true);
-        TimeStampedAngularCoordinates pair = new TimeStampedAngularCoordinates(attitudeDate, rotation, Vector3D.ZERO, Vector3D.ZERO);
+        TimeStampedAngularCoordinates pair =
+                new TimeStampedAngularCoordinates(attitudeDate, rotation, Vector3D.ZERO, Vector3D.ZERO);
         satelliteQList.add(pair);
     }
 
     private void checkInverseLocation(int dimension, boolean lightTimeCorrection, boolean aberrationOfLightCorrection,
-                                          double maxLineError, double maxPixelError)
+                                      double maxLineError, double maxPixelError)
         throws RuggedException, OrekitException, URISyntaxException {
 
         String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
@@ -1410,7 +1003,8 @@ public class RuggedTest {
         // 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, 2.6" per pixel
         Vector3D position = new Vector3D(1.5, 0, -0.2);
-        List<TimeDependentLOS> los = createLOSPerfectLine(new Rotation(Vector3D.PLUS_I, FastMath.toRadians(50.0)).applyTo(Vector3D.PLUS_K),
+        List<TimeDependentLOS> los = createLOSPerfectLine(new Rotation(Vector3D.PLUS_I,
+                                                                       FastMath.toRadians(50.0)).applyTo(Vector3D.PLUS_K),
                                                           Vector3D.PLUS_I,
                                                           FastMath.toRadians(dimension * 2.6 / 3600.0), dimension);
 
@@ -1426,16 +1020,20 @@ public class RuggedTest {
                 new RandomLandscapeUpdater(0.0, 9000.0, 0.3, 0xf0a401650191f9f6l,
                                            FastMath.toRadians(1.0), 257);
 
-        Rugged rugged = new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                   EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                   minDate, maxDate, 0.001,
-                                   5.0, orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                   8, 
-                                   CartesianDerivativesFilter.USE_PV, orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                   2, AngularDerivativesFilter.USE_R);
-        rugged.setLightTimeCorrection(lightTimeCorrection);
-        rugged.setAberrationOfLightCorrection(aberrationOfLightCorrection);
-        rugged.addLineSensor(lineSensor);
+        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,
+                              orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              8, CartesianDerivativesFilter.USE_PV,
+                              orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              2, AngularDerivativesFilter.USE_R).
+                setLightTimeCorrection(lightTimeCorrection).
+                setAberrationOfLightCorrection(aberrationOfLightCorrection).
+                addLineSensor(lineSensor).
+                build();
 
         double referenceLine = 0.87654 * dimension;
         GeodeticPoint[] gp = rugged.directLocation("line", referenceLine);
@@ -1496,7 +1094,8 @@ public class RuggedTest {
         // 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, 2.6" per pixel
         Vector3D position = new Vector3D(1.5, 0, -0.2);
-        List<TimeDependentLOS> los = createLOSPerfectLine(new Rotation(Vector3D.PLUS_I, FastMath.toRadians(50.0)).applyTo(Vector3D.PLUS_K),
+        List<TimeDependentLOS> los = createLOSPerfectLine(new Rotation(Vector3D.PLUS_I,
+                                                                       FastMath.toRadians(50.0)).applyTo(Vector3D.PLUS_K),
                                                           Vector3D.PLUS_I,
                                                           FastMath.toRadians(dimension * 2.6 / 3600.0), dimension);
 
@@ -1512,16 +1111,20 @@ public class RuggedTest {
                 new RandomLandscapeUpdater(0.0, 9000.0, 0.5, 0xf0a401650191f9f6l,
                                            FastMath.toRadians(1.0), 257);
 
-        Rugged rugged = new Rugged(updater, 8, AlgorithmId.DUVENHAGE,
-                                   EllipsoidId.WGS84, InertialFrameId.EME2000, BodyRotatingFrameId.ITRF,
-                                   minDate, maxDate, 0.001,
-                                   5.0, orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                   8, 
-                                   CartesianDerivativesFilter.USE_PV, orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
-                                   2, AngularDerivativesFilter.USE_R);
-        rugged.setLightTimeCorrection(lightTimeCorrection);
-        rugged.setAberrationOfLightCorrection(aberrationOfLightCorrection);
-        rugged.addLineSensor(lineSensor);
+        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,
+                              orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              8, CartesianDerivativesFilter.USE_PV,
+                              orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
+                              2, AngularDerivativesFilter.USE_R).
+                setLightTimeCorrection(lightTimeCorrection).
+                setAberrationOfLightCorrection(aberrationOfLightCorrection).
+                addLineSensor(lineSensor).
+                build();
 
         double referenceLine = 0.87654 * dimension;
         GeodeticPoint[] gp = rugged.directLocation("line", referenceLine);
@@ -1642,22 +1245,6 @@ public class RuggedTest {
         return list;
     }
 
-    private TimeStampedPVCoordinates createPV(AbsoluteDate t0, double dt,
-                                              double px, double py, double pz,
-                                              double vx, double vy, double vz) {
-        return new TimeStampedPVCoordinates(t0.shiftedBy(dt),
-                                            new Vector3D(px, py, pz),
-                                            new Vector3D(vx, vy, vz),
-                                            Vector3D.ZERO);
-    }
-
-    private TimeStampedAngularCoordinates createQ(AbsoluteDate t0, double dt,
-                                                       double q0, double q1, double q2, double q3) {
-        return new TimeStampedAngularCoordinates(t0.shiftedBy(dt),
-                                                 new Rotation(q0, q1, q2, q3, true),
-                                                 Vector3D.ZERO, Vector3D.ZERO);
-    }
-
     private List<TimeStampedPVCoordinates> orbitToPV(Orbit orbit, BodyShape earth,
                                                      AbsoluteDate minDate, AbsoluteDate maxDate,
                                                      double step)
-- 
GitLab