RuggedBuilder.java

  1. /* Copyright 2013-2025 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.rugged.api;

  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.io.ObjectInputStream;
  21. import java.io.ObjectOutputStream;
  22. import java.io.OutputStream;
  23. import java.util.ArrayList;
  24. import java.util.Collections;
  25. import java.util.List;

  26. import org.hipparchus.exception.LocalizedCoreFormats;
  27. import org.hipparchus.geometry.euclidean.threed.Rotation;
  28. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  29. import org.orekit.bodies.OneAxisEllipsoid;
  30. import org.orekit.frames.Frame;
  31. import org.orekit.frames.FramesFactory;
  32. import org.orekit.propagation.Propagator;
  33. import org.orekit.rugged.errors.RuggedException;
  34. import org.orekit.rugged.errors.RuggedInternalError;
  35. import org.orekit.rugged.errors.RuggedMessages;
  36. import org.orekit.rugged.intersection.BasicScanAlgorithm;
  37. import org.orekit.rugged.intersection.ConstantElevationAlgorithm;
  38. import org.orekit.rugged.intersection.IgnoreDEMAlgorithm;
  39. import org.orekit.rugged.intersection.IntersectionAlgorithm;
  40. import org.orekit.rugged.intersection.duvenhage.DuvenhageAlgorithm;
  41. import org.orekit.rugged.linesensor.LineSensor;
  42. import org.orekit.rugged.raster.TileUpdater;
  43. import org.orekit.rugged.refraction.AtmosphericRefraction;
  44. import org.orekit.rugged.utils.ExtendedEllipsoid;
  45. import org.orekit.rugged.utils.SpacecraftToObservedBody;
  46. import org.orekit.time.AbsoluteDate;
  47. import org.orekit.utils.AngularDerivativesFilter;
  48. import org.orekit.utils.CartesianDerivativesFilter;
  49. import org.orekit.utils.Constants;
  50. import org.orekit.utils.IERSConventions;
  51. import org.orekit.utils.PVCoordinates;
  52. import org.orekit.utils.TimeStampedAngularCoordinates;
  53. import org.orekit.utils.TimeStampedPVCoordinates;

  54. /** Builder for {@link Rugged} instances.
  55.  * <p>
  56.  * This class implements the <em>builder pattern</em> to create {@link Rugged} instances.
  57.  * It does so by using a <em>fluent API</em> in order to clarify reading and allow
  58.  * later extensions with new configuration parameters.
  59.  * </p>
  60.  * <p>
  61.  * A typical use would be:
  62.  * </p>
  63.  * <pre>
  64.  *   Rugged rugged = new RuggedBuilder().
  65.  *                   setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
  66.  *                   setAlgorithmID(AlgorithmId.Duvenhage).
  67.  *                   setDigitalElevationModel(tileUpdater, maxCachedTiles).
  68.  *                   setTimeSpan(minDate, maxDate, tStep, overshootTolerance).
  69.  *                   setTrajectory(positionsVelocities, pvInterpolationNumber, pvFilter,
  70.  *                                 quaternions, aInterpolationNumber, aFilter).
  71.  *                   addLineSensor(sensor1).
  72.  *                   addLineSensor(sensor2).
  73.  *                   addLineSensor(sensor3).
  74.  *                   build();
  75.  * </pre>
  76.  * <p>
  77.  * If a configuration parameter has not been set prior to the call to {]link #build()}, then
  78.  * an exception will be triggered with an explicit error message.
  79.  * </p>
  80.  * @see <a href="https://en.wikipedia.org/wiki/Builder_pattern">Builder pattern (wikipedia)</a>
  81.  * @see <a href="https://en.wikipedia.org/wiki/Fluent_interface">Fluent interface (wikipedia)</a>
  82.  * @author Luc Maisonobe
  83.  * @author Guylaine Prat
  84.  */
  85. public class RuggedBuilder {

  86.     /** Reference ellipsoid. */
  87.     private ExtendedEllipsoid ellipsoid;

  88.     /** DEM intersection algorithm. */
  89.     private AlgorithmId algorithmID;

  90.     /** Updater used to load Digital Elevation Model tiles. */
  91.     private TileUpdater tileUpdater;

  92.     /** Flag to tell if the Digital Elevation Model tiles are overlapping.
  93.      * @since 4.0 */
  94.     private boolean isOverlappingTiles = true;

  95.     /** Constant elevation over ellipsoid (m).
  96.      * used only with {@link AlgorithmId#CONSTANT_ELEVATION_OVER_ELLIPSOID. */
  97.     private double constantElevation;

  98.     /** Maximum number of tiles stored in the cache. */
  99.     private int maxCachedTiles;

  100.     /** Start of search time span. */
  101.     private AbsoluteDate minDate;

  102.     /** End of search time span. */
  103.     private AbsoluteDate maxDate;

  104.     /** Step to use for inertial frame to body frame transforms cache computations (s). */
  105.     private double tStep;

  106.     /** OvershootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting (s). */
  107.     private double overshootTolerance;

  108.     /** Inertial frame for position/velocity/attitude. */
  109.     private Frame inertial;

  110.     /** Satellite position and velocity (m and m/s in inertial frame). */
  111.     private List<TimeStampedPVCoordinates> pvSample;

  112.     /** Number of points to use for position/velocity interpolation. */
  113.     private int pvNeighborsSize;

  114.     /** Filter for derivatives from the sample to use in position/velocity interpolation. */
  115.     private CartesianDerivativesFilter pvDerivatives;

  116.     /** Satellite quaternions with respect to inertial frame. */
  117.     private List<TimeStampedAngularCoordinates> aSample;

  118.     /** Number of points to use for attitude interpolation. */
  119.     private int aNeighborsSize;

  120.     /** Filter for derivatives from the sample to use in attitude interpolation. */
  121.     private AngularDerivativesFilter aDerivatives;

  122.     /** Propagator for position/velocity/attitude. */
  123.     private Propagator pvaPropagator;

  124.     /** Step to use for inertial/Earth/spacecraft transforms interpolations (s). */
  125.     private double iStep;

  126.     /** Number of points to use for inertial/Earth/spacecraft transforms interpolations. */
  127.     private int iN;

  128.     /** Converter between spacecraft and body. */
  129.     private SpacecraftToObservedBody scToBody;

  130.     /** Flag for light time correction. */
  131.     private boolean lightTimeCorrection;

  132.     /** Flag for aberration of light correction. */
  133.     private boolean aberrationOfLightCorrection;

  134.     /** Atmospheric refraction to use for line of sight correction. */
  135.     private AtmosphericRefraction atmosphericRefraction;

  136.     /** Sensors. */
  137.     private final List<LineSensor> sensors;

  138.     /** Rugged name. */
  139.     private String name;

  140.     /** Create a non-configured builder.
  141.      * <p>
  142.      * The builder <em>must</em> be configured before calling the
  143.      * {@link #build} method, otherwise an exception will be triggered
  144.      * at build time.
  145.      * </p>
  146.      */
  147.     public RuggedBuilder() {
  148.         sensors                     = new ArrayList<>();
  149.         constantElevation           = Double.NaN;
  150.         lightTimeCorrection         = true;
  151.         aberrationOfLightCorrection = true;
  152.         name                        = "Rugged";
  153.     }

  154.     /** Set the reference ellipsoid.
  155.      * @param ellipsoidID reference ellipsoid
  156.      * @param bodyRotatingFrameID body rotating frame identifier
  157.      * from an earlier run and frames mismatch
  158.      * @return the builder instance
  159.      * @see #setEllipsoid(OneAxisEllipsoid)
  160.      * @see #getEllipsoid()
  161.      */
  162.     public RuggedBuilder setEllipsoid(final EllipsoidId ellipsoidID, final BodyRotatingFrameId bodyRotatingFrameID) {
  163.         return setEllipsoid(selectEllipsoid(ellipsoidID, selectBodyRotatingFrame(bodyRotatingFrameID)));
  164.     }

  165.     /** Set the reference ellipsoid.
  166.      * @param newEllipsoid reference ellipsoid
  167.      * @return the builder instance
  168.      * @see #setEllipsoid(EllipsoidId, BodyRotatingFrameId)
  169.      * @see #getEllipsoid()
  170.      */
  171.     public RuggedBuilder setEllipsoid(final OneAxisEllipsoid newEllipsoid) {
  172.         this.ellipsoid = new ExtendedEllipsoid(newEllipsoid.getEquatorialRadius(),
  173.                                                newEllipsoid.getFlattening(),
  174.                                                newEllipsoid.getBodyFrame());
  175.         checkFramesConsistency();
  176.         return this;
  177.     }

  178.     /** Get the ellipsoid.
  179.      * @return the ellipsoid
  180.      * @see #setEllipsoid(EllipsoidId, BodyRotatingFrameId)
  181.      * @see #setEllipsoid(OneAxisEllipsoid)
  182.      */
  183.     public ExtendedEllipsoid getEllipsoid() {
  184.         return ellipsoid;
  185.     }

  186.     /** Get the Rugged name.
  187.      * @return the Rugged name
  188.      * @since 2.0
  189.      */
  190.     public String getName() {
  191.         return name;
  192.     }

  193.     /** Set the Rugged name.
  194.      * @param name the Rugged name
  195.      * @since 2.0
  196.      */
  197.     public void setName(final String name) {
  198.         this.name = name;
  199.     }

  200.     /** Set the algorithm to use for Digital Elevation Model intersection.
  201.      * <p>
  202.      * Note that some algorithms require specific other methods to be called too:
  203.      * <ul>
  204.      *   <li>{@link AlgorithmId#DUVENHAGE DUVENHAGE},
  205.      *   {@link AlgorithmId#DUVENHAGE_FLAT_BODY DUVENHAGE_FLAT_BODY}
  206.      *   and {@link AlgorithmId#BASIC_SLOW_EXHAUSTIVE_SCAN_FOR_TESTS_ONLY
  207.      *   BASIC_SLOW_EXHAUSTIVE_SCAN_FOR_TESTS_ONLY} all
  208.      *   require {@link #setDigitalElevationModel(TileUpdater, int, boolean) setDigitalElevationModel}
  209.      *   or {@link #setDigitalElevationModel(TileUpdater, int) setDigitalElevationModel}
  210.      *   to be called,</li>
  211.      *   <li>{@link AlgorithmId#CONSTANT_ELEVATION_OVER_ELLIPSOID
  212.      *   CONSTANT_ELEVATION_OVER_ELLIPSOID} requires
  213.      *   {@link #setConstantElevation(double) setConstantElevation} to be called,</li>
  214.      *   <li>{@link AlgorithmId#IGNORE_DEM_USE_ELLIPSOID
  215.      *   IGNORE_DEM_USE_ELLIPSOID} does not require
  216.      *   any methods tobe called.</li>
  217.      * </ul>
  218.      *
  219.      * @param newAlgorithmId identifier of algorithm to use for Digital Elevation Model intersection
  220.      * @return the builder instance
  221.      * @see #setDigitalElevationModel(TileUpdater, int, boolean)
  222.      * @see #setDigitalElevationModel(TileUpdater, int)
  223.      * @see #getAlgorithm()
  224.      */
  225.     public RuggedBuilder setAlgorithm(final AlgorithmId newAlgorithmId) {
  226.         this.algorithmID = newAlgorithmId;
  227.         return this;
  228.     }

  229.     /** Get the algorithm to use for Digital Elevation Model intersection.
  230.      * @return algorithm to use for Digital Elevation Model intersection
  231.      * @see #setAlgorithm(AlgorithmId)
  232.      */
  233.     public AlgorithmId getAlgorithm() {
  234.         return algorithmID;
  235.     }

  236.     /** Set the user-provided {@link TileUpdater tile updater}.
  237.      * <p>
  238.      * The DEM tiles must be overlapping, otherwise use {@link #setDigitalElevationModel(TileUpdater, int, boolean)}
  239.      * with flag set to false.
  240.      * </p>
  241.      * <p>
  242.      * Note that when the algorithm specified in {@link #setAlgorithm(AlgorithmId)}
  243.      * is either {@link AlgorithmId#CONSTANT_ELEVATION_OVER_ELLIPSOID
  244.      * CONSTANT_ELEVATION_OVER_ELLIPSOID} or {@link
  245.      * AlgorithmId#IGNORE_DEM_USE_ELLIPSOID IGNORE_DEM_USE_ELLIPSOID},
  246.      * then this method becomes irrelevant and can either be not called at all,
  247.      * or it can be called with an updater set to {@code null}. For all other
  248.      * algorithms, the updater must be properly configured.
  249.      * </p>
  250.      * @param newTileUpdater updater used to load Digital Elevation Model tiles
  251.      * @param newMaxCachedTiles maximum number of tiles stored in the cache
  252.      * @return the builder instance
  253.      * @see #setDigitalElevationModel(TileUpdater, int, boolean)
  254.      * @see #setAlgorithm(AlgorithmId)
  255.      * @see #getTileUpdater()
  256.      * @see #getMaxCachedTiles()
  257.      */
  258.     public RuggedBuilder setDigitalElevationModel(final TileUpdater newTileUpdater, final int newMaxCachedTiles) {
  259.         return setDigitalElevationModel(newTileUpdater, newMaxCachedTiles, true);
  260.     }

  261.     /** Set the user-provided {@link TileUpdater tile updater}.
  262.      * <p>
  263.      * Note that when the algorithm specified in {@link #setAlgorithm(AlgorithmId)}
  264.      * is either {@link AlgorithmId#CONSTANT_ELEVATION_OVER_ELLIPSOID
  265.      * CONSTANT_ELEVATION_OVER_ELLIPSOID} or {@link
  266.      * AlgorithmId#IGNORE_DEM_USE_ELLIPSOID IGNORE_DEM_USE_ELLIPSOID},
  267.      * then this method becomes irrelevant and can either be not called at all,
  268.      * or it can be called with an updater set to {@code null}. For all other
  269.      * algorithms, the updater must be properly configured.
  270.      * </p>
  271.      * @param newTileUpdater updater used to load Digital Elevation Model tiles
  272.      * @param newMaxCachedTiles maximum number of tiles stored in the cache
  273.      * @param newIsOverlappingTiles flag to tell if the DEM tiles are overlapping:
  274.      *                              true if overlapping; false otherwise.
  275.      * @return the builder instance
  276.      * @see #setDigitalElevationModel(TileUpdater, int)
  277.      * @see #setAlgorithm(AlgorithmId)
  278.      * @see #getTileUpdater()
  279.      * @see #getMaxCachedTiles()
  280.      * @see #isOverlappingTiles()
  281.      * @since 4.0
  282.      */
  283.     public RuggedBuilder setDigitalElevationModel(final TileUpdater newTileUpdater, final int newMaxCachedTiles,
  284.                                                   final boolean newIsOverlappingTiles) {
  285.         this.tileUpdater    = newTileUpdater;
  286.         this.maxCachedTiles = newMaxCachedTiles;
  287.         this.isOverlappingTiles = newIsOverlappingTiles;
  288.         return this;
  289.     }

  290.     /** Get the updater used to load Digital Elevation Model tiles.
  291.      * @return updater used to load Digital Elevation Model tiles
  292.      * @see #setDigitalElevationModel(TileUpdater, int, boolean)
  293.      * @see #setDigitalElevationModel(TileUpdater, int)
  294.      * @see #getMaxCachedTiles()
  295.      */
  296.     public TileUpdater getTileUpdater() {
  297.         return tileUpdater;
  298.     }

  299.     /**
  300.      * Get the flag telling if the DEM tiles are overlapping.
  301.      * @return true if the Digital Elevation Model tiles are overlapping;
  302.      *         false otherwise. Default = true.
  303.      * @since 4.0
  304.      */
  305.     public boolean isOverlappingTiles() {
  306.         return isOverlappingTiles;
  307.     }

  308.     /**
  309.      * Set the DEM overlapping tiles flag.
  310.      * @param newIsOverlappingTiles flag to tell if the Digital Elevation Model tiles are overlapping:
  311.      *        true if overlapping; false otherwise
  312.      * @since 4.0
  313.      */
  314.     public void setOverlappingTiles(final boolean newIsOverlappingTiles) {
  315.         this.isOverlappingTiles = newIsOverlappingTiles;
  316.     }

  317.     /** Set the user-provided constant elevation model.
  318.      * <p>
  319.      * Note that this method is relevant <em>only</em> if the algorithm specified
  320.      * in {@link #setAlgorithm(AlgorithmId)} is {@link
  321.      * AlgorithmId#CONSTANT_ELEVATION_OVER_ELLIPSOID CONSTANT_ELEVATION_OVER_ELLIPSOID}.
  322.      * If it is called for another algorithm, the elevation set here will be ignored.
  323.      * </p>
  324.      * @param newConstantElevation constant elevation to use (m)
  325.      * @return the builder instance
  326.      * @see #setAlgorithm(AlgorithmId)
  327.      * @see #getConstantElevation()
  328.      */
  329.     public RuggedBuilder setConstantElevation(final double newConstantElevation) {
  330.         this.constantElevation = newConstantElevation;
  331.         return this;
  332.     }

  333.     /** Get the constant elevation over ellipsoid to use with {@link AlgorithmId#CONSTANT_ELEVATION_OVER_ELLIPSOID}.
  334.      * @return updater used to load Digital Elevation Model tiles
  335.      * @see #setConstantElevation(double)
  336.      */
  337.     public double getConstantElevation() {
  338.         return constantElevation;
  339.     }

  340.     /** Get the maximum number of tiles stored in the cache.
  341.      * @return maximum number of tiles stored in the cache
  342.      * @see #setDigitalElevationModel(TileUpdater, int, boolean)
  343.      * @see #setDigitalElevationModel(TileUpdater, int)
  344.      * @see #getTileUpdater()
  345.      */
  346.     public int getMaxCachedTiles() {
  347.         return maxCachedTiles;
  348.     }

  349.     /** Set the time span to be covered for direct and inverse location calls.
  350.      * <p>
  351.      * This method set only the time span and not the trajectory, therefore it
  352.      * <em>must</em> be used together with either
  353.      * {@link #setTrajectory(InertialFrameId, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)},
  354.      * {@link #setTrajectory(Frame, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)},
  355.      * or {@link #setTrajectory(double, int, CartesianDerivativesFilter, AngularDerivativesFilter, Propagator)}
  356.      * but should <em>not</em> be mixed with {@link #setTrajectoryAndTimeSpan(InputStream)}.
  357.      * </p>
  358.      * @param newMinDate start of search time span
  359.      * @param newMaxDate end of search time span
  360.      * @param newTstep step to use for inertial frame to body frame transforms cache computations (s)
  361.      * @param newOvershootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting (s)
  362.      * @return the builder instance
  363.      * @see #setTrajectoryAndTimeSpan(InputStream)
  364.      * @see #getMinDate()
  365.      * @see #getMaxDate()
  366.      * @see #getTStep()
  367.      * @see #getOvershootTolerance()
  368.      */
  369.     public RuggedBuilder setTimeSpan(final AbsoluteDate newMinDate, final AbsoluteDate newMaxDate,
  370.                                      final double newTstep, final double newOvershootTolerance) {
  371.         this.minDate            = newMinDate;
  372.         this.maxDate            = newMaxDate;
  373.         this.tStep              = newTstep;
  374.         this.overshootTolerance = newOvershootTolerance;
  375.         this.scToBody           = null;
  376.         return this;
  377.     }

  378.     /** Get the start of search time span.
  379.      * @return start of search time span
  380.      * @see #setTimeSpan(AbsoluteDate, AbsoluteDate, double, double)
  381.      */
  382.     public AbsoluteDate getMinDate() {
  383.         return minDate;
  384.     }

  385.     /** Get the end of search time span.
  386.      * @return end of search time span
  387.      * @see #setTimeSpan(AbsoluteDate, AbsoluteDate, double, double)
  388.      */
  389.     public AbsoluteDate getMaxDate() {
  390.         return maxDate;
  391.     }

  392.     /** Get the step to use for inertial frame to body frame transforms cache computations.
  393.      * @return step to use for inertial frame to body frame transforms cache computations
  394.      * @see #setTimeSpan(AbsoluteDate, AbsoluteDate, double, double)
  395.      */
  396.     public double getTStep() {
  397.         return tStep;
  398.     }

  399.     /** Get the tolerance in seconds allowed for {@link #getMinDate()} and {@link #getMaxDate()} overshooting.
  400.      * @return tolerance in seconds allowed for {@link #getMinDate()} and {@link #getMaxDate()} overshooting
  401.      * @see #setTimeSpan(AbsoluteDate, AbsoluteDate, double, double)
  402.      */
  403.     public double getOvershootTolerance() {
  404.         return overshootTolerance;
  405.     }

  406.     /** Set the spacecraft trajectory.
  407.      * <p>
  408.      * This method set only the trajectory and not the time span, therefore it
  409.      * <em>must</em> be used together with the {@link #setTimeSpan(AbsoluteDate, AbsoluteDate, double, double)}
  410.      * but should <em>not</em> be mixed with {@link #setTrajectoryAndTimeSpan(InputStream)}.
  411.      * </p>
  412.      * @param inertialFrameId inertial frame Id used for spacecraft positions/velocities/quaternions
  413.      * @param positionsVelocities satellite position and velocity (m and m/s in inertial frame)
  414.      * @param pvInterpolationNumber number of points to use for position/velocity interpolation
  415.      * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
  416.      * @param quaternions satellite quaternions with respect to inertial frame
  417.      * @param aInterpolationNumber number of points to use for attitude interpolation
  418.      * @param aFilter filter for derivatives from the sample to use in attitude interpolation
  419.      * @return the builder instance
  420.      * @see #setTrajectory(Frame, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)
  421.      * @see #setTrajectory(double, int, CartesianDerivativesFilter, AngularDerivativesFilter, Propagator)
  422.      * @see #setTrajectoryAndTimeSpan(InputStream)
  423.      * @see #getInertialFrame()
  424.      * @see #getPositionsVelocities()
  425.      * @see #getPVInterpolationNumber()
  426.      * @see #getPVInterpolationNumber()
  427.      * @see #getQuaternions()
  428.      * @see #getAInterpolationNumber()
  429.      * @see #getAFilter()
  430.      */
  431.     public RuggedBuilder setTrajectory(final InertialFrameId inertialFrameId,
  432.                                        final List<TimeStampedPVCoordinates> positionsVelocities, final int pvInterpolationNumber,
  433.                                        final CartesianDerivativesFilter pvFilter,
  434.                                        final List<TimeStampedAngularCoordinates> quaternions, final int aInterpolationNumber,
  435.                                        final AngularDerivativesFilter aFilter) {

  436.         return setTrajectory(selectInertialFrame(inertialFrameId),
  437.                              positionsVelocities, pvInterpolationNumber, pvFilter,
  438.                              quaternions, aInterpolationNumber, aFilter);
  439.     }

  440.     /** Set the spacecraft trajectory.
  441.      * <p>
  442.      * This method set only the trajectory and not the time span, therefore it
  443.      * <em>must</em> be used together with the {@link #setTimeSpan(AbsoluteDate, AbsoluteDate, double, double)}
  444.      * but should <em>not</em> be mixed with {@link #setTrajectoryAndTimeSpan(InputStream)}.
  445.      * </p>
  446.      * @param inertialFrame inertial frame used for spacecraft positions/velocities/quaternions
  447.      * @param positionsVelocities satellite position and velocity (m and m/s in inertial frame)
  448.      * @param pvInterpolationNumber number of points to use for position/velocity interpolation
  449.      * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
  450.      * @param quaternions satellite quaternions with respect to inertial frame
  451.      * @param aInterpolationNumber number of points to use for attitude interpolation
  452.      * @param aFilter filter for derivatives from the sample to use in attitude interpolation
  453.      * @return the builder instance
  454.      * @see #setTrajectory(InertialFrameId, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)
  455.      * @see #setTrajectory(double, int, CartesianDerivativesFilter, AngularDerivativesFilter, Propagator)
  456.      * @see #setTrajectoryAndTimeSpan(InputStream)
  457.      * @see #getPositionsVelocities()
  458.      * @see #getPVInterpolationNumber()
  459.      * @see #getPVInterpolationNumber()
  460.      * @see #getQuaternions()
  461.      * @see #getAInterpolationNumber()
  462.      * @see #getAFilter()
  463.      */
  464.     public RuggedBuilder setTrajectory(final Frame inertialFrame,
  465.                                        final List<TimeStampedPVCoordinates> positionsVelocities, final int pvInterpolationNumber,
  466.                                        final CartesianDerivativesFilter pvFilter,
  467.                                        final List<TimeStampedAngularCoordinates> quaternions, final int aInterpolationNumber,
  468.                                        final AngularDerivativesFilter aFilter) {

  469.         this.inertial        = inertialFrame;
  470.         this.pvSample        = positionsVelocities;
  471.         this.pvNeighborsSize = pvInterpolationNumber;
  472.         this.pvDerivatives   = pvFilter;
  473.         this.aSample         = quaternions;
  474.         this.aNeighborsSize  = aInterpolationNumber;
  475.         this.aDerivatives    = aFilter;
  476.         this.pvaPropagator   = null;
  477.         this.iStep           = Double.NaN;
  478.         this.iN              = -1;
  479.         this.scToBody        = null;
  480.         return this;
  481.     }

  482.     /** Set the spacecraft trajectory.
  483.      * <p>
  484.      * This method set only the trajectory and not the time span, therefore it
  485.      * <em>must</em> be used together with the {@link #setTimeSpan(AbsoluteDate, AbsoluteDate, double, double)}
  486.      * but should <em>not</em> be mixed with {@link #setTrajectoryAndTimeSpan(InputStream)}.
  487.      * </p>
  488.      * @param interpolationStep step to use for inertial/Earth/spacecraft transforms interpolations (s)
  489.      * @param interpolationNumber number of points to use for inertial/Earth/spacecraft transforms interpolations
  490.      * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
  491.      * @param aFilter filter for derivatives from the sample to use in attitude interpolation
  492.      * @param propagator global propagator
  493.      * @return the builder instance
  494.      * @see #setTrajectory(InertialFrameId, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)
  495.      * @see #setTrajectory(Frame, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)
  496.      * @see #setTrajectoryAndTimeSpan(InputStream)
  497.      */
  498.     public RuggedBuilder setTrajectory(final double interpolationStep, final int interpolationNumber,
  499.                                        final CartesianDerivativesFilter pvFilter, final AngularDerivativesFilter aFilter,
  500.                                        final Propagator propagator) {
  501.         this.inertial        = propagator.getFrame();
  502.         this.pvSample        = null;
  503.         this.pvNeighborsSize = -1;
  504.         this.pvDerivatives   = pvFilter;
  505.         this.aSample         = null;
  506.         this.aNeighborsSize  = -1;
  507.         this.aDerivatives    = aFilter;
  508.         this.pvaPropagator   = propagator;
  509.         this.iStep           = interpolationStep;
  510.         this.iN              = interpolationNumber;
  511.         this.scToBody        = null;
  512.         return this;
  513.     }

  514.     /** Get the inertial frame.
  515.      * @return inertial frame
  516.      */
  517.     public Frame getInertialFrame() {
  518.         return inertial;
  519.     }

  520.     /** Get the satellite position and velocity (m and m/s in inertial frame).
  521.      * @return satellite position and velocity (m and m/s in inertial frame)
  522.      * @see #setTrajectory(double, int, CartesianDerivativesFilter, AngularDerivativesFilter, Propagator)
  523.      */
  524.     public List<TimeStampedPVCoordinates> getPositionsVelocities() {
  525.         return pvSample;
  526.     }

  527.     /** Get the number of points to use for position/velocity interpolation.
  528.      * @return number of points to use for position/velocity interpolation
  529.      * @see #setTrajectory(double, int, CartesianDerivativesFilter, AngularDerivativesFilter, Propagator)
  530.      */
  531.     public int getPVInterpolationNumber() {
  532.         return pvNeighborsSize;
  533.     }

  534.     /** Get the filter for derivatives from the sample to use in position/velocity interpolation.
  535.      * @return filter for derivatives from the sample to use in position/velocity interpolation
  536.      * @see #setTrajectory(double, int, CartesianDerivativesFilter, AngularDerivativesFilter, Propagator)
  537.      */
  538.     public CartesianDerivativesFilter getPVFilter() {
  539.         return pvDerivatives;
  540.     }

  541.     /** Get the satellite quaternions with respect to inertial frame.
  542.      * @return satellite quaternions with respect to inertial frame
  543.      * @see #setTrajectory(double, int, CartesianDerivativesFilter, AngularDerivativesFilter, Propagator)
  544.      */
  545.     public List<TimeStampedAngularCoordinates> getQuaternions() {
  546.         return aSample;
  547.     }

  548.     /** Get the number of points to use for attitude interpolation.
  549.      * @return number of points to use for attitude interpolation
  550.      * @see #setTrajectory(double, int, CartesianDerivativesFilter, AngularDerivativesFilter, Propagator)
  551.      */
  552.     public int getAInterpolationNumber() {
  553.         return aNeighborsSize;
  554.     }

  555.     /** Get the filter for derivatives from the sample to use in attitude interpolation.
  556.      * @return filter for derivatives from the sample to use in attitude interpolation
  557.      * @see #setTrajectory(double, int, CartesianDerivativesFilter, AngularDerivativesFilter, Propagator)
  558.      */
  559.     public AngularDerivativesFilter getAFilter() {
  560.         return aDerivatives;
  561.     }

  562.     /** Set both the spacecraft trajectory and the time span.
  563.      * <p>
  564.      * This method set both the trajectory and the time span in a tightly coupled
  565.      * way, therefore it should <em>not</em> be mixed with the individual methods
  566.      * {@link #setTrajectory(double, int, CartesianDerivativesFilter, AngularDerivativesFilter, Propagator)},
  567.      * {@link #setTrajectory(Frame, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)},
  568.      * {@link #setTrajectory(InertialFrameId, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)},
  569.      * or {@link #setTimeSpan(AbsoluteDate, AbsoluteDate, double, double)}.
  570.      * </p>
  571.      * @param storageStream stream from where to read previous instance {@link #storeInterpolator(OutputStream)
  572.      * stored interpolator} (caller opened it and remains responsible for closing it)
  573.      * @return the builder instance
  574.      * @see #setTrajectory(InertialFrameId, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)
  575.      * @see #setTrajectory(Frame, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)
  576.      * @see #setTrajectory(double, int, CartesianDerivativesFilter, AngularDerivativesFilter, Propagator)
  577.      * @see #storeInterpolator(OutputStream)
  578.      */
  579.     public RuggedBuilder setTrajectoryAndTimeSpan(final InputStream storageStream) {

  580.         try {
  581.             this.inertial           = null;
  582.             this.pvSample           = null;
  583.             this.pvNeighborsSize    = -1;
  584.             this.pvDerivatives      = null;
  585.             this.aSample            = null;
  586.             this.aNeighborsSize     = -1;
  587.             this.aDerivatives       = null;
  588.             this.pvaPropagator      = null;
  589.             this.iStep              = Double.NaN;
  590.             this.iN                 = -1;
  591.             this.scToBody           = (SpacecraftToObservedBody) new ObjectInputStream(storageStream).readObject();
  592.             this.minDate            = scToBody.getMinDate();
  593.             this.maxDate            = scToBody.getMaxDate();
  594.             this.tStep              = scToBody.getTStep();
  595.             this.overshootTolerance = scToBody.getOvershootTolerance();
  596.             checkFramesConsistency();
  597.             return this;

  598.         } catch (ClassNotFoundException cnfe) {
  599.             throw new RuggedException(cnfe, RuggedMessages.NOT_INTERPOLATOR_DUMP_DATA);
  600.         } catch (ClassCastException cce) {
  601.             throw new RuggedException(cce, RuggedMessages.NOT_INTERPOLATOR_DUMP_DATA);
  602.         } catch (IOException ioe) {
  603.             throw new RuggedException(ioe, RuggedMessages.NOT_INTERPOLATOR_DUMP_DATA);
  604.         }
  605.     }

  606.     /** Store frames transform interpolator.
  607.      * <p>
  608.      * This method allows to reuse the interpolator built in one instance, to build
  609.      * another instance by calling {@link #setTrajectoryAndTimeSpan(InputStream)}.
  610.      * This reduces the builder initialization time as setting up the interpolator can be long, it is
  611.      * mainly intended to be used when several runs are done (for example in an image processing chain)
  612.      * with the same configuration.
  613.      * </p>
  614.      * <p>
  615.      * This method must be called <em>after</em> both the ellipsoid and trajectory have been set.
  616.      * </p>
  617.      * @param storageStream stream where to store the interpolator
  618.      * (caller opened it and remains responsible for closing it)
  619.      * @see #setEllipsoid(EllipsoidId, BodyRotatingFrameId)
  620.      * @see #setEllipsoid(OneAxisEllipsoid)
  621.      * @see #setTrajectory(InertialFrameId, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)
  622.      * @see #setTrajectory(Frame, List, int, CartesianDerivativesFilter, List, int, AngularDerivativesFilter)
  623.      * @see #setTrajectory(double, int, CartesianDerivativesFilter, AngularDerivativesFilter, Propagator)
  624.      * @see #setTrajectoryAndTimeSpan(InputStream)
  625.      */
  626.     public void storeInterpolator(final OutputStream storageStream) {
  627.         try {
  628.             createInterpolatorIfNeeded();
  629.             new ObjectOutputStream(storageStream).writeObject(scToBody);
  630.         } catch (IOException ioe) {
  631.             throw new RuggedException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE, ioe.getMessage());
  632.         }
  633.     }

  634.     /** Check frames consistency.
  635.      */
  636.     private void checkFramesConsistency() {
  637.         if (ellipsoid != null && scToBody != null &&
  638.             !ellipsoid.getBodyFrame().getName().equals(scToBody.getBodyFrame().getName())) {
  639.             // if frames have been set both by direct calls and by deserializing an interpolator dump and a mismatch occurs
  640.             throw new RuggedException(RuggedMessages.FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP,
  641.                                       ellipsoid.getBodyFrame().getName(), scToBody.getBodyFrame().getName());
  642.         }
  643.     }

  644.     /** Create a transform interpolator if needed.
  645.      */
  646.     private void createInterpolatorIfNeeded() {

  647.         if (ellipsoid == null) {
  648.             throw new RuggedException(RuggedMessages.UNINITIALIZED_CONTEXT, "RuggedBuilder.setEllipsoid()");
  649.         }

  650.         if (scToBody == null) {
  651.             if (pvSample != null) {
  652.                 scToBody = createInterpolator(inertial, ellipsoid.getBodyFrame(),
  653.                                               minDate, maxDate, tStep, overshootTolerance,
  654.                                               pvSample, pvNeighborsSize, pvDerivatives,
  655.                                               aSample, aNeighborsSize, aDerivatives);
  656.             } else if (pvaPropagator != null) {
  657.                 scToBody = createInterpolator(inertial, ellipsoid.getBodyFrame(),
  658.                                               minDate, maxDate, tStep, overshootTolerance,
  659.                                               iStep, iN, pvDerivatives, aDerivatives, pvaPropagator);
  660.             } else {
  661.                 throw new RuggedException(RuggedMessages.UNINITIALIZED_CONTEXT, "RuggedBuilder.setTrajectory()");
  662.             }
  663.         }
  664.     }

  665.     /** Create a transform interpolator from positions and quaternions lists.
  666.      * @param inertialFrame inertial frame
  667.      * @param bodyFrame observed body frame
  668.      * @param minDate start of search time span
  669.      * @param maxDate end of search time span
  670.      * @param tStep step to use for inertial frame to body frame transforms cache computations
  671.      * @param overshootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting
  672.      * @param positionsVelocities satellite position and velocity
  673.      * @param pvInterpolationNumber number of points to use for position/velocity interpolation
  674.      * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
  675.      * @param quaternions satellite quaternions
  676.      * @param aInterpolationNumber number of points to use for attitude interpolation
  677.      * @param aFilter filter for derivatives from the sample to use in attitude interpolation
  678.      * @return transforms interpolator
  679.      */
  680.     private static SpacecraftToObservedBody createInterpolator(final Frame inertialFrame, final Frame bodyFrame,
  681.                                                                final AbsoluteDate minDate, final AbsoluteDate maxDate,
  682.                                                                final double tStep, final double overshootTolerance,
  683.                                                                final List<TimeStampedPVCoordinates> positionsVelocities,
  684.                                                                final int pvInterpolationNumber,
  685.                                                                final CartesianDerivativesFilter pvFilter,
  686.                                                                final List<TimeStampedAngularCoordinates> quaternions,
  687.                                                                final int aInterpolationNumber,
  688.                                                                final AngularDerivativesFilter aFilter) {

  689.         return new SpacecraftToObservedBody(inertialFrame, bodyFrame,
  690.                                             minDate, maxDate, tStep, overshootTolerance,
  691.                                             positionsVelocities, pvInterpolationNumber,
  692.                                             pvFilter, quaternions, aInterpolationNumber,
  693.                                             aFilter);
  694.     }

  695.     /** Create a transform interpolator from a propagator.
  696.      * @param inertialFrame inertial frame
  697.      * @param bodyFrame observed body frame
  698.      * @param minDate start of search time span
  699.      * @param maxDate end of search time span
  700.      * @param tStep step to use for inertial frame to body frame transforms cache computations
  701.      * @param overshootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting
  702.      * @param interpolationStep step to use for inertial/Earth/spacecraft transforms interpolations
  703.      * @param interpolationNumber number of points of to use for inertial/Earth/spacecraft transforms interpolations
  704.      * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
  705.      * @param aFilter filter for derivatives from the sample to use in attitude interpolation
  706.      * @param propagator global propagator
  707.      * @return transforms interpolator
  708.      */
  709.     private static SpacecraftToObservedBody createInterpolator(final Frame inertialFrame, final Frame bodyFrame,
  710.                                                                final AbsoluteDate minDate, final AbsoluteDate maxDate,
  711.                                                                final double tStep, final double overshootTolerance,
  712.                                                                final double interpolationStep, final int interpolationNumber,
  713.                                                                final CartesianDerivativesFilter pvFilter,
  714.                                                                final AngularDerivativesFilter aFilter,
  715.                                                                final Propagator propagator) {

  716.         // extract position/attitude samples from propagator
  717.         final List<TimeStampedPVCoordinates> positionsVelocities =
  718.                 new ArrayList<>();
  719.         final List<TimeStampedAngularCoordinates> quaternions =
  720.                 new ArrayList<>();
  721.         propagator.getMultiplexer().add(interpolationStep,
  722.             currentState -> {
  723.                 final AbsoluteDate  date = currentState.getDate();
  724.                 final PVCoordinates pv   = currentState.getPVCoordinates(inertialFrame);
  725.                 final Rotation      q    = currentState.getAttitude().getRotation();
  726.                 positionsVelocities.add(new TimeStampedPVCoordinates(date, pv.getPosition(), pv.getVelocity(), Vector3D.ZERO));
  727.                 quaternions.add(new TimeStampedAngularCoordinates(date, q, Vector3D.ZERO, Vector3D.ZERO));
  728.             });
  729.         propagator.propagate(minDate.shiftedBy(-interpolationStep), maxDate.shiftedBy(interpolationStep));

  730.         // orbit/attitude to body converter
  731.         return createInterpolator(inertialFrame, bodyFrame,
  732.                 minDate, maxDate, tStep, overshootTolerance,
  733.                 positionsVelocities, interpolationNumber,
  734.                 pvFilter, quaternions, interpolationNumber,
  735.                 aFilter);
  736.     }

  737.     /** Set flag for light time correction.
  738.      * <p>
  739.      * This methods set the flag for compensating or not light time between
  740.      * ground and spacecraft. Compensating this delay improves location
  741.      * accuracy and is <em>enabled</em> by default (i.e. not calling this
  742.      * method before building is therefore equivalent to calling it with
  743.      * a parameter set to {@code true}). Not compensating it is mainly useful
  744.      * for validation purposes against system that do not compensate it.
  745.      * </p>
  746.      * @param newLightTimeCorrection if true, the light travel time between ground
  747.      * and spacecraft is compensated for more accurate location
  748.      * @return the builder instance
  749.      * @see #setAberrationOfLightCorrection(boolean)
  750.      * @see #getLightTimeCorrection()
  751.      */
  752.     public RuggedBuilder setLightTimeCorrection(final boolean newLightTimeCorrection) {
  753.         this.lightTimeCorrection = newLightTimeCorrection;
  754.         return this;
  755.     }

  756.     /** Get the light time correction flag.
  757.      * @return light time correction flag
  758.      * @see #setLightTimeCorrection(boolean)
  759.      */
  760.     public boolean getLightTimeCorrection() {
  761.         return lightTimeCorrection;
  762.     }

  763.     /** Set flag for aberration of light correction.
  764.      * <p>
  765.      * This methods set the flag for compensating or not aberration of light,
  766.      * which is velocity composition between light and spacecraft when the
  767.      * light from ground points reaches the sensor.
  768.      * Compensating this velocity composition improves location
  769.      * accuracy and is <em>enabled</em> by default (i.e. not calling this
  770.      * method before building is therefore equivalent to calling it with
  771.      * a parameter set to {@code true}). Not compensating it is useful
  772.      * in two cases: for validation purposes against system that do not
  773.      * compensate it or when the pixels line of sight already include the
  774.      * correction.
  775.      * </p>
  776.      * @param newAberrationOfLightCorrection if true, the aberration of light
  777.      * is corrected for more accurate location
  778.      * @return the builder instance
  779.      * @see #setLightTimeCorrection(boolean)
  780.      * @see #getAberrationOfLightCorrection()
  781.      */
  782.     public RuggedBuilder setAberrationOfLightCorrection(final boolean newAberrationOfLightCorrection) {
  783.         this.aberrationOfLightCorrection = newAberrationOfLightCorrection;
  784.         return this;
  785.     }

  786.     /** Get the aberration of light correction flag.
  787.      * @return aberration of light correction flag
  788.      * @see #setAberrationOfLightCorrection(boolean)
  789.      */
  790.     public boolean getAberrationOfLightCorrection() {
  791.         return aberrationOfLightCorrection;
  792.     }

  793.     /** Set atmospheric refraction for line of sight correction.
  794.      * <p>
  795.      * This method sets an atmospheric refraction model to be used between
  796.      * spacecraft and ground for the correction of intersected points on ground.
  797.      * Compensating for the effect of atmospheric refraction improves location
  798.      * accuracy.
  799.      * </p>
  800.      * @param newAtmosphericRefraction the atmospheric refraction model to be used for more accurate location
  801.      * @return the builder instance
  802.      * @see #getRefractionCorrection()
  803.      */
  804.     public RuggedBuilder setRefractionCorrection(final AtmosphericRefraction newAtmosphericRefraction) {
  805.         this.atmosphericRefraction = newAtmosphericRefraction;
  806.         return this;
  807.     }

  808.     /** Get the atmospheric refraction model.
  809.      * @return atmospheric refraction model
  810.      * @see #setRefractionCorrection(AtmosphericRefraction)
  811.      */
  812.     public AtmosphericRefraction getRefractionCorrection() {
  813.         return atmosphericRefraction;
  814.     }

  815.     /** Set up line sensor model.
  816.      * @param lineSensor line sensor model
  817.      * @return the builder instance
  818.      */
  819.     public RuggedBuilder addLineSensor(final LineSensor lineSensor) {
  820.         sensors.add(lineSensor);
  821.         return this;
  822.     }

  823.     /** Remove all line sensors.
  824.      * @return the builder instance
  825.      */
  826.     public RuggedBuilder clearLineSensors() {
  827.         sensors.clear();
  828.         return this;
  829.     }

  830.     /** Get all line sensors.
  831.      * @return all line sensors (in an unmodifiable list)
  832.      */
  833.     public List<LineSensor> getLineSensors() {
  834.         return Collections.unmodifiableList(sensors);
  835.     }

  836.     /** Select inertial frame.
  837.      * @param inertialFrameId inertial frame identifier
  838.      * @return inertial frame
  839.      */
  840.     private static Frame selectInertialFrame(final InertialFrameId inertialFrameId) {

  841.         // set up the inertial frame
  842.         switch (inertialFrameId) {
  843.             case GCRF :
  844.                 return FramesFactory.getGCRF();
  845.             case EME2000 :
  846.                 return FramesFactory.getEME2000();
  847.             case MOD :
  848.                 return FramesFactory.getMOD(IERSConventions.IERS_1996);
  849.             case TOD :
  850.                 return FramesFactory.getTOD(IERSConventions.IERS_1996, true);
  851.             case VEIS1950 :
  852.                 return FramesFactory.getVeis1950();
  853.             default :
  854.                 // this should never happen
  855.                 throw new RuggedInternalError(null);
  856.         }
  857.     }

  858.     /** Select body rotating frame.
  859.      * @param bodyRotatingFrame body rotating frame identifier
  860.      * @return body rotating frame
  861.      */
  862.     private static Frame selectBodyRotatingFrame(final BodyRotatingFrameId bodyRotatingFrame) {

  863.         // set up the rotating frame
  864.         switch (bodyRotatingFrame) {
  865.             case ITRF :
  866.                 return FramesFactory.getITRF(IERSConventions.IERS_2010, true);
  867.             case ITRF_EQUINOX :
  868.                 return FramesFactory.getITRFEquinox(IERSConventions.IERS_1996, true);
  869.             case GTOD :
  870.                 return FramesFactory.getGTOD(IERSConventions.IERS_1996, true);
  871.             default :
  872.                 // this should never happen
  873.                 throw new RuggedInternalError(null);
  874.         }
  875.     }

  876.     /** Select ellipsoid.
  877.      * @param ellipsoidID reference ellipsoid identifier
  878.      * @param bodyFrame body rotating frame
  879.      * @return selected ellipsoid
  880.      */
  881.     private static OneAxisEllipsoid selectEllipsoid(final EllipsoidId ellipsoidID, final Frame bodyFrame) {

  882.         // set up the ellipsoid
  883.         switch (ellipsoidID) {
  884.             case GRS80 :
  885.                 return new OneAxisEllipsoid(Constants.GRS80_EARTH_EQUATORIAL_RADIUS,
  886.                                             Constants.GRS80_EARTH_FLATTENING,
  887.                                             bodyFrame);
  888.             case WGS84 :
  889.                 return new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
  890.                                             Constants.WGS84_EARTH_FLATTENING,
  891.                                             bodyFrame);
  892.             case IERS96 :
  893.                 return new OneAxisEllipsoid(Constants.IERS96_EARTH_EQUATORIAL_RADIUS,
  894.                                             Constants.IERS96_EARTH_FLATTENING,
  895.                                             bodyFrame);
  896.             case IERS2003 :
  897.                 return new OneAxisEllipsoid(Constants.IERS2003_EARTH_EQUATORIAL_RADIUS,
  898.                                             Constants.IERS2003_EARTH_FLATTENING,
  899.                                             bodyFrame);
  900.             default :
  901.                 // this should never happen
  902.                 throw new RuggedInternalError(null);
  903.         }

  904.     }

  905.     /** Create DEM intersection algorithm.
  906.      * @param algorithmID intersection algorithm identifier
  907.      * @param updater updater used to load Digital Elevation Model tiles
  908.      * @param maxCachedTiles maximum number of tiles stored in the cache
  909.      * @param constantElevation constant elevation over ellipsoid
  910.      * @param isOverlappingTiles flag to tell if the DEM tiles are overlapping:
  911.      *                           true if overlapping; false otherwise.
  912.      * @return selected algorithm
  913.      */
  914.     private static IntersectionAlgorithm createAlgorithm(final AlgorithmId algorithmID,
  915.                                                          final TileUpdater updater, final int maxCachedTiles,
  916.                                                          final double constantElevation, final boolean isOverlappingTiles) {
  917.         // set up the algorithm
  918.         switch (algorithmID) {
  919.             case DUVENHAGE :
  920.                 return new DuvenhageAlgorithm(updater, maxCachedTiles, false, isOverlappingTiles);
  921.             case DUVENHAGE_FLAT_BODY :
  922.                 return new DuvenhageAlgorithm(updater, maxCachedTiles, true, isOverlappingTiles);
  923.             case BASIC_SLOW_EXHAUSTIVE_SCAN_FOR_TESTS_ONLY :
  924.                 return new BasicScanAlgorithm(updater, maxCachedTiles, isOverlappingTiles);
  925.             case CONSTANT_ELEVATION_OVER_ELLIPSOID :
  926.                 return new ConstantElevationAlgorithm(constantElevation);
  927.             case IGNORE_DEM_USE_ELLIPSOID :
  928.                 return new IgnoreDEMAlgorithm();
  929.             default :
  930.                 // this should never happen
  931.                 throw new RuggedInternalError(null);
  932.         }
  933.     }

  934.     /** Build a {@link Rugged} instance.
  935.      * @return built instance
  936.      */
  937.     public Rugged build() {

  938.         if (algorithmID == null) {
  939.             throw new RuggedException(RuggedMessages.UNINITIALIZED_CONTEXT, "RuggedBuilder.setAlgorithmID()");
  940.         }
  941.         if (algorithmID == AlgorithmId.CONSTANT_ELEVATION_OVER_ELLIPSOID) {
  942.             if (Double.isNaN(constantElevation)) {
  943.                 throw new RuggedException(RuggedMessages.UNINITIALIZED_CONTEXT, "RuggedBuilder.setConstantElevation()");
  944.             }
  945.         } else if (algorithmID != AlgorithmId.IGNORE_DEM_USE_ELLIPSOID) {
  946.             if (tileUpdater == null) {
  947.                 throw new RuggedException(RuggedMessages.UNINITIALIZED_CONTEXT, "RuggedBuilder.setDigitalElevationModel()");
  948.             }
  949.         }
  950.         createInterpolatorIfNeeded();
  951.         return new Rugged(createAlgorithm(algorithmID, tileUpdater, maxCachedTiles, constantElevation, isOverlappingTiles), ellipsoid,
  952.                           lightTimeCorrection, aberrationOfLightCorrection, atmosphericRefraction, scToBody, sensors, name);
  953.     }
  954. }