DumpReplayer.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.errors;

  18. import java.io.BufferedReader;
  19. import java.io.ByteArrayInputStream;
  20. import java.io.ByteArrayOutputStream;
  21. import java.io.File;
  22. import java.io.FileInputStream;
  23. import java.io.IOException;
  24. import java.io.InputStreamReader;
  25. import java.io.ObjectOutputStream;
  26. import java.lang.reflect.InvocationTargetException;
  27. import java.lang.reflect.Method;
  28. import java.util.ArrayList;
  29. import java.util.Arrays;
  30. import java.util.HashMap;
  31. import java.util.List;
  32. import java.util.Map;
  33. import java.util.NavigableMap;
  34. import java.util.TreeMap;
  35. import java.util.regex.Pattern;
  36. import java.util.stream.Stream;

  37. import org.hipparchus.analysis.differentiation.Derivative;
  38. import org.hipparchus.exception.LocalizedCoreFormats;
  39. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  40. import org.hipparchus.geometry.euclidean.threed.Rotation;
  41. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  42. import org.hipparchus.util.FastMath;
  43. import org.hipparchus.util.OpenIntToDoubleHashMap;
  44. import org.hipparchus.util.Pair;
  45. import org.orekit.bodies.GeodeticPoint;
  46. import org.orekit.bodies.OneAxisEllipsoid;
  47. import org.orekit.frames.Frame;
  48. import org.orekit.frames.FramesFactory;
  49. import org.orekit.frames.Predefined;
  50. import org.orekit.frames.Transform;
  51. import org.orekit.rugged.api.AlgorithmId;
  52. import org.orekit.rugged.api.Rugged;
  53. import org.orekit.rugged.api.RuggedBuilder;
  54. import org.orekit.rugged.linesensor.LineDatation;
  55. import org.orekit.rugged.linesensor.LineSensor;
  56. import org.orekit.rugged.linesensor.SensorMeanPlaneCrossing;
  57. import org.orekit.rugged.linesensor.SensorMeanPlaneCrossing.CrossingResult;
  58. import org.orekit.rugged.linesensor.SensorPixel;
  59. import org.orekit.rugged.los.TimeDependentLOS;
  60. import org.orekit.rugged.raster.TileUpdater;
  61. import org.orekit.rugged.raster.UpdatableTile;
  62. import org.orekit.rugged.refraction.AtmosphericRefraction;
  63. import org.orekit.rugged.refraction.MultiLayerModel;
  64. import org.orekit.rugged.utils.DerivativeGenerator;
  65. import org.orekit.rugged.utils.ExtendedEllipsoid;
  66. import org.orekit.rugged.utils.SpacecraftToObservedBody;
  67. import org.orekit.time.AbsoluteDate;
  68. import org.orekit.time.TimeScalesFactory;
  69. import org.orekit.utils.ParameterDriver;

  70. /** Replayer for Rugged debug dumps.
  71.  * @author Luc Maisonobe
  72.  * @author Guylaine Prat
  73.  * @see DumpManager
  74.  * @see Dump
  75.  */
  76. public class DumpReplayer {

  77.     /** Comment start marker. */
  78.     private static final String COMMENT_START = "#";

  79.     /** Keyword for latitude fields. */
  80.     private static final String LATITUDE = "latitude";

  81.     /** Keyword for longitude fields. */
  82.     private static final String LONGITUDE = "longitude";

  83.     /** Keyword for elevation fields. */
  84.     private static final String ELEVATION = "elevation";

  85.     /** Keyword for ellipsoid equatorial radius fields. */
  86.     private static final String AE = "ae";

  87.     /** Keyword for ellipsoid flattening fields. */
  88.     private static final String F = "f";

  89.     /** Keyword for frame fields. */
  90.     private static final String FRAME = "frame";

  91.     /** Keyword for date fields. */
  92.     private static final String DATE = "date";

  93.     /** Keyword for sensor position fields. */
  94.     private static final String POSITION = "position";

  95.     /** Keyword for sensor line-of-sight fields. */
  96.     private static final String LOS = "los";

  97.     /** Keyword for light-time correction fields. */
  98.     private static final String LIGHT_TIME = "lightTime";

  99.     /** Keyword for aberration of light correction fields. */
  100.     private static final String ABERRATION = "aberration";

  101.     /** Keyword for atmospheric refraction correction fields. */
  102.     private static final String REFRACTION = "refraction";

  103.     /** Keyword for min date fields. */
  104.     private static final String MIN_DATE = "minDate";

  105.     /** Keyword for max date fields. */
  106.     private static final String MAX_DATE = "maxDate";

  107.     /** Keyword for time step fields. */
  108.     private static final String T_STEP = "tStep";

  109.     /** Keyword for overshoot tolerance fields. */
  110.     private static final String TOLERANCE = "tolerance";

  111.     /** Keyword for inertial frames fields. */
  112.     private static final String INERTIAL_FRAME = "inertialFrame";

  113.     /** Keyword for observation transform index fields. */
  114.     private static final String INDEX = "index";

  115.     /** Keyword for body meta-fields. */
  116.     private static final String BODY = "body";

  117.     /** Keyword for rotation fields. */
  118.     private static final String R = "r";

  119.     /** Keyword for rotation rate fields. */
  120.     private static final String OMEGA = "Ω";

  121.     /** Keyword for rotation acceleration fields. */
  122.     private static final String OMEGA_DOT = "ΩDot";

  123.     /** Keyword for spacecraft meta-fields. */
  124.     private static final String SPACECRAFT = "spacecraft";

  125.     /** Keyword for position fields. */
  126.     private static final String P = "p";

  127.     /** Keyword for velocity fields. */
  128.     private static final String V = "v";

  129.     /** Keyword for acceleration fields. */
  130.     private static final String A = "a";

  131.     /** Keyword for minimum latitude fields. */
  132.     private static final String LAT_MIN = "latMin";

  133.     /** Keyword for latitude step fields. */
  134.     private static final String LAT_STEP = "latStep";

  135.     /** Keyword for latitude rows fields. */
  136.     private static final String LAT_ROWS = "latRows";

  137.     /** Keyword for minimum longitude fields. */
  138.     private static final String LON_MIN = "lonMin";

  139.     /** Keyword for longitude step fields. */
  140.     private static final String LON_STEP = "lonStep";

  141.     /** Keyword for longitude columns fields. */
  142.     private static final String LON_COLS = "lonCols";

  143.     /** Keyword for latitude index fields. */
  144.     private static final String LAT_INDEX = "latIndex";

  145.     /** Keyword for longitude index fields. */
  146.     private static final String LON_INDEX = "lonIndex";

  147.     /** Keyword for sensor name. */
  148.     private static final String SENSOR_NAME = "sensorName";

  149.     /** Keyword for min line. */
  150.     private static final String MIN_LINE = "minLine";

  151.     /** Keyword for max line. */
  152.     private static final String MAX_LINE = "maxLine";

  153.     /** Keyword for line number. */
  154.     private static final String LINE_NUMBER = "lineNumber";

  155.     /** Keyword for number of pixels. */
  156.     private static final String NB_PIXELS = "nbPixels";

  157.     /** Keyword for pixel number. */
  158.     private static final String PIXEL_NUMBER = "pixelNumber";

  159.     /** Keyword for max number of evaluations. */
  160.     private static final String MAX_EVAL = "maxEval";

  161.     /** Keyword for accuracy. */
  162.     private static final String ACCURACY = "accuracy";

  163.     /** Keyword for normal. */
  164.     private static final String NORMAL = "normal";

  165.     /** Keyword for rate. */
  166.     private static final String RATE = "rate";

  167.     /** Keyword for cached results. */
  168.     private static final String CACHED_RESULTS = "cachedResults";

  169.     /** Keyword for target. */
  170.     private static final String TARGET = "target";

  171.     /** Keyword for target direction. */
  172.     private static final String TARGET_DIRECTION = "targetDirection";

  173.     /** Keyword for null result. */
  174.     private static final String NULL_RESULT = "NULL";

  175.     /** Pattern for delimiting regular expressions. */
  176.     private static final Pattern SEPARATOR = Pattern.compile("\\s+");

  177.     /** Empty pattern. */
  178.     private static final Pattern PATTERN = Pattern.compile(" ");

  179.     /** Constant elevation for constant elevation algorithm. */
  180.     private double constantElevation;

  181.     /** Algorithm identifier. */
  182.     private AlgorithmId algorithmId;

  183.     /** Ellipsoid. */
  184.     private OneAxisEllipsoid ellipsoid;

  185.     /** Tiles list. */
  186.     private final List<ParsedTile> tiles;

  187.     /** Sensors list. */
  188.     private final List<ParsedSensor> sensors;

  189.     /** Interpolator min date. */
  190.     private AbsoluteDate minDate;

  191.     /** Interpolator max date. */
  192.     private AbsoluteDate maxDate;

  193.     /** Interpolator step. */
  194.     private double tStep;

  195.     /** Interpolator overshoot tolerance. */
  196.     private double tolerance;

  197.     /** Inertial frame. */
  198.     private Frame inertialFrame;

  199.     /** Transforms sample from observed body frame to inertial frame. */
  200.     private NavigableMap<Integer, Transform> bodyToInertial;

  201.     /** Transforms sample from spacecraft frame to inertial frame. */
  202.     private NavigableMap<Integer, Transform> scToInertial;

  203.     /** Flag for light time correction. */
  204.     private boolean lightTimeCorrection;

  205.     /** Flag for aberration of light correction. */
  206.     private boolean aberrationOfLightCorrection;

  207.     /** Flag for atmospheric refraction. */
  208.     private boolean atmosphericRefraction;

  209.     /** Dumped calls. */
  210.     private final List<DumpedCall> calls;


  211.     /** Simple constructor.
  212.      */
  213.     public DumpReplayer() {
  214.         tiles   = new ArrayList<>();
  215.         sensors = new ArrayList<>();
  216.         calls   = new ArrayList<>();
  217.     }

  218.     /** Parse a dump file.
  219.      * @param file dump file to parse
  220.      */
  221.     public void parse(final File file) {
  222.         try {
  223.             final BufferedReader reader =
  224.                     new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
  225.             int l = 0;
  226.             for (String line = reader.readLine(); line != null; line = reader.readLine()) {
  227.                 LineParser.parse(++l, file, line, this);
  228.             }
  229.             reader.close();
  230.         } catch (IOException ioe) {
  231.             throw new RuggedException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE, ioe.getLocalizedMessage());
  232.         }
  233.     }

  234.     /** Create a Rugged instance from parsed data.
  235.      * @return rugged instance
  236.      */
  237.     public Rugged createRugged() {
  238.         try {
  239.             final RuggedBuilder builder = new RuggedBuilder();

  240.             if (algorithmId == null) {
  241.                 algorithmId = AlgorithmId.IGNORE_DEM_USE_ELLIPSOID;
  242.             }
  243.             builder.setAlgorithm(algorithmId);
  244.             if (algorithmId == AlgorithmId.CONSTANT_ELEVATION_OVER_ELLIPSOID) {
  245.                 builder.setConstantElevation(constantElevation);
  246.             } else if (algorithmId != AlgorithmId.IGNORE_DEM_USE_ELLIPSOID) {
  247.                 // In the case of user used a non overlapping DEM: no need here to take it into account
  248.                 // as Rugged during the run created if necessary zipper tiles.
  249.                 // At this stage, the read DEM in the dump behave like an overlapping DEM.
  250.                 builder.setDigitalElevationModel(new TileUpdater() {

  251.                     /** {@inheritDoc} */
  252.                     @Override
  253.                     public void updateTile(final double latitude, final double longitude, final UpdatableTile tile) {
  254.                         for (final ParsedTile parsedTile : tiles) {
  255.                             if (parsedTile.isInterpolable(latitude, longitude)) {
  256.                                 parsedTile.updateTile(tile);
  257.                                 return;
  258.                             }
  259.                         }
  260.                         throw new RuggedException(RuggedMessages.NO_DEM_DATA,
  261.                                                   FastMath.toDegrees(latitude), FastMath.toDegrees(longitude));
  262.                     }
  263.                 }, 8);
  264.             }

  265.             builder.setEllipsoid(ellipsoid);

  266.             builder.setLightTimeCorrection(lightTimeCorrection);
  267.             builder.setAberrationOfLightCorrection(aberrationOfLightCorrection);
  268.             if (atmosphericRefraction) { // Use the default model with the default configuration values
  269.                 final ExtendedEllipsoid extendedEllipsoid = builder.getEllipsoid();
  270.                 final AtmosphericRefraction atmosphericModel = new MultiLayerModel(extendedEllipsoid);
  271.                 // Build Rugged with atmospheric refraction model
  272.                 builder.setRefractionCorrection(atmosphericModel);
  273.             }


  274.             // build missing transforms by extrapolating the parsed ones
  275.             final int n = (int) FastMath.ceil(maxDate.durationFrom(minDate) / tStep);
  276.             final List<Transform> b2iList = new ArrayList<>(n);
  277.             final List<Transform> s2iList = new ArrayList<>(n);
  278.             for (int i = 0; i < n; ++i) {
  279.                 if (bodyToInertial.containsKey(i)) {
  280.                     // the i-th transform was dumped
  281.                     b2iList.add(bodyToInertial.get(i));
  282.                     s2iList.add(scToInertial.get(i));
  283.                 } else {
  284.                     // the i-th transformed was not dumped, we have to extrapolate it
  285.                     final Map.Entry<Integer, Transform> lower  = bodyToInertial.lowerEntry(i);
  286.                     final Map.Entry<Integer, Transform> higher = bodyToInertial.higherEntry(i);
  287.                     final int closest;
  288.                     if (lower == null) {
  289.                         closest = higher.getKey();
  290.                     } else if (higher == null) {
  291.                         closest = lower.getKey();
  292.                     } else {
  293.                         closest = (i - lower.getKey() <= higher.getKey() - i) ? lower.getKey() : higher.getKey();
  294.                     }
  295.                     b2iList.add(bodyToInertial.get(closest).shiftedBy((i - closest) * tStep));
  296.                     s2iList.add(scToInertial.get(closest).shiftedBy((i - closest) * tStep));
  297.                 }
  298.             }

  299.             // we use Rugged transforms reloading mechanism to ensure the spacecraft
  300.             // to body transforms will be the same as the ones dumped
  301.             final SpacecraftToObservedBody scToBody =
  302.                     new SpacecraftToObservedBody(inertialFrame, ellipsoid.getBodyFrame(),
  303.                                                  minDate, maxDate, tStep, tolerance,
  304.                                                  b2iList, s2iList);
  305.             final ByteArrayOutputStream bos = new ByteArrayOutputStream();
  306.             new ObjectOutputStream(bos).writeObject(scToBody);
  307.             final ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
  308.             builder.setTrajectoryAndTimeSpan(bis);

  309.             final List<SensorMeanPlaneCrossing> planeCrossings = new ArrayList<>();
  310.             for (final ParsedSensor parsedSensor : sensors) {
  311.                 final LineSensor sensor = new LineSensor(parsedSensor.name,
  312.                                                          parsedSensor,
  313.                                                          parsedSensor.position,
  314.                                                          parsedSensor);
  315.                 if (parsedSensor.meanPlane != null) {
  316.                     planeCrossings.add(new SensorMeanPlaneCrossing(sensor, scToBody,
  317.                                                                    parsedSensor.meanPlane.minLine,
  318.                                                                    parsedSensor.meanPlane.maxLine,
  319.                                                                    lightTimeCorrection, aberrationOfLightCorrection,
  320.                                                                    parsedSensor.meanPlane.maxEval,
  321.                                                                    parsedSensor.meanPlane.accuracy,
  322.                                                                    parsedSensor.meanPlane.normal,
  323.                                                                    Arrays.stream(parsedSensor.meanPlane.cachedResults)));
  324.                 }
  325.                 builder.addLineSensor(sensor);
  326.             }

  327.             final Rugged rugged = builder.build();

  328.             final Method setPlaneCrossing = Rugged.class.getDeclaredMethod("setPlaneCrossing",
  329.                                                                            SensorMeanPlaneCrossing.class);
  330.             setPlaneCrossing.setAccessible(true);
  331.             for (final SensorMeanPlaneCrossing planeCrossing : planeCrossings) {
  332.                 setPlaneCrossing.invoke(rugged, planeCrossing);
  333.             }

  334.             return rugged;

  335.         } catch (IOException | NoSuchMethodException  | IllegalAccessException  | InvocationTargetException e) {
  336.             // this should never happen
  337.             throw new RuggedInternalError(e);
  338.         }
  339.     }

  340.     /** Get a sensor by name.
  341.      * @param name sensor name
  342.      * @return parsed sensor
  343.      */
  344.     private ParsedSensor getSensor(final String name) {
  345.         for (final ParsedSensor sensor : sensors) {
  346.             if (sensor.name.equals(name)) {
  347.                 return sensor;
  348.             }
  349.         }
  350.         final ParsedSensor sensor = new ParsedSensor(name);
  351.         sensors.add(sensor);
  352.         return sensor;
  353.     }

  354.     /** Execute all dumped calls.
  355.      * <p>
  356.      * The dumped calls correspond to computation methods like direct or inverse
  357.      * location.
  358.      * </p>
  359.      * @param rugged Rugged instance on which calls will be performed
  360.      * @return results of all dumped calls
  361.      */
  362.     public Result[] execute(final Rugged rugged) {
  363.         final Result[] results = new Result[calls.size()];
  364.         for (int i = 0; i < calls.size(); ++i) {
  365.             results[i] = new Result(calls.get(i).expected,
  366.                                     calls.get(i).execute(rugged));
  367.         }
  368.         return results;
  369.     }

  370.     /** Container for replay results. */
  371.     public static class Result {

  372.         /** Expected result. */
  373.         private final Object expected;

  374.         /** Replayed result. */
  375.         private final Object replayed;

  376.         /** Simple constructor.
  377.          * @param expected expected result
  378.          * @param replayed replayed result
  379.          */
  380.         private Result(final Object expected, final Object replayed) {
  381.             this.expected = expected;
  382.             this.replayed = replayed;
  383.         }

  384.         /** Get the expected result.
  385.          * @return expected result
  386.          */
  387.         public Object getExpected() {
  388.             return expected;
  389.         }

  390.         /** Get the replayed result.
  391.          * @return replayed result
  392.          */
  393.         public Object getReplayed() {
  394.             return replayed;
  395.         }

  396.     }

  397.     /** Line parsers. */
  398.     private enum LineParser {

  399.         /** Parser for algorithm dump lines. */
  400.         ALGORITHM() {

  401.             /** {@inheritDoc} */
  402.             @Override
  403.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
  404.                 try {
  405.                     if (fields.length < 1) {
  406.                         throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  407.                     }
  408.                     global.algorithmId = AlgorithmId.valueOf(fields[0]);
  409.                     if (global.algorithmId == AlgorithmId.CONSTANT_ELEVATION_OVER_ELLIPSOID) {
  410.                         if (fields.length < 3 || !fields[1].equals(ELEVATION)) {
  411.                             throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  412.                         }
  413.                         global.constantElevation = Double.parseDouble(fields[2]);
  414.                     }
  415.                 } catch (IllegalArgumentException iae) {
  416.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  417.                 }
  418.             }

  419.         },

  420.         /** Parser for ellipsoid dump lines. */
  421.         ELLIPSOID() {

  422.             /** {@inheritDoc} */
  423.             @Override
  424.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
  425.                 if (fields.length < 6 || !fields[0].equals(AE) || !fields[2].equals(F) || !fields[4].equals(FRAME)) {
  426.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  427.                 }
  428.                 final double ae   = Double.parseDouble(fields[1]);
  429.                 final double f    = Double.parseDouble(fields[3]);
  430.                 final Frame  bodyFrame;
  431.                 try {
  432.                     bodyFrame = FramesFactory.getFrame(Predefined.valueOf(fields[5]));
  433.                 } catch (IllegalArgumentException iae) {
  434.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  435.                 }
  436.                 global.ellipsoid = new OneAxisEllipsoid(ae, f, bodyFrame);
  437.             }

  438.         },

  439.         /** Parser for direct location calls dump lines. */
  440.         DIRECT_LOCATION() {

  441.             /** {@inheritDoc} */
  442.             @Override
  443.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
  444.                 if (fields.length < 16 ||
  445.                         !fields[0].equals(DATE) ||
  446.                         !fields[2].equals(POSITION) || !fields[6].equals(LOS) ||
  447.                         !fields[10].equals(LIGHT_TIME) || !fields[12].equals(ABERRATION) ||
  448.                         !fields[14].equals(REFRACTION)) {
  449.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  450.                 }
  451.                 final AbsoluteDate date = new AbsoluteDate(fields[1], TimeScalesFactory.getUTC());
  452.                 final Vector3D position = new Vector3D(Double.parseDouble(fields[3]),
  453.                         Double.parseDouble(fields[4]),
  454.                         Double.parseDouble(fields[5]));
  455.                 final Vector3D los      = new Vector3D(Double.parseDouble(fields[7]),
  456.                         Double.parseDouble(fields[8]),
  457.                         Double.parseDouble(fields[9]));
  458.                 if (global.calls.isEmpty()) {
  459.                     global.lightTimeCorrection         = Boolean.parseBoolean(fields[11]);
  460.                     global.aberrationOfLightCorrection = Boolean.parseBoolean(fields[13]);
  461.                     global.atmosphericRefraction       = Boolean.parseBoolean(fields[15]);
  462.                 } else {
  463.                     if (global.lightTimeCorrection != Boolean.parseBoolean(fields[11])) {
  464.                         throw new RuggedException(RuggedMessages.LIGHT_TIME_CORRECTION_REDEFINED,
  465.                                 l, file.getAbsolutePath(), line);
  466.                     }
  467.                     if (global.aberrationOfLightCorrection != Boolean.parseBoolean(fields[13])) {
  468.                         throw new RuggedException(RuggedMessages.ABERRATION_OF_LIGHT_CORRECTION_REDEFINED,
  469.                                 l, file.getAbsolutePath(), line);
  470.                     }
  471.                     if (global.atmosphericRefraction != Boolean.parseBoolean(fields[15])) {
  472.                         throw new RuggedException(RuggedMessages.ATMOSPHERIC_REFRACTION_REDEFINED,
  473.                                 l, file.getAbsolutePath(), line);
  474.                     }
  475.                 }
  476.                 global.calls.add(new DumpedCall() {

  477.                     /** {@inheritDoc} */
  478.                     @Override
  479.                     public Object execute(final Rugged rugged) {
  480.                         return rugged.directLocation(date, position, los);
  481.                     }

  482.                 });
  483.             }
  484.         },

  485.         /** Parser for direct location result dump lines. */
  486.         DIRECT_LOCATION_RESULT() {

  487.             /** {@inheritDoc} */
  488.             @Override
  489.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
  490.                 if (fields.length == 1) {
  491.                     if (fields[0].equals(NULL_RESULT)) {
  492.                         final GeodeticPoint gp = null;
  493.                         final DumpedCall last = global.calls.get(global.calls.size() - 1);
  494.                         last.expected = gp;
  495.                     } else {
  496.                         throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  497.                     }
  498.                 } else if (fields.length < 6 || !fields[0].equals(LATITUDE) ||
  499.                            !fields[2].equals(LONGITUDE) || !fields[4].equals(ELEVATION)) {
  500.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  501.                 } else {
  502.                     final GeodeticPoint gp = new GeodeticPoint(Double.parseDouble(fields[1]),
  503.                                                                Double.parseDouble(fields[3]),
  504.                                                                Double.parseDouble(fields[5]));
  505.                     final DumpedCall last = global.calls.get(global.calls.size() - 1);
  506.                     last.expected = gp;
  507.                 }
  508.             }

  509.         },

  510.         /** Parser for search span dump lines. */
  511.         SPAN() {

  512.             /** {@inheritDoc} */
  513.             @Override
  514.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
  515.                 if (fields.length < 10 ||
  516.                         !fields[0].equals(MIN_DATE)  || !fields[2].equals(MAX_DATE) || !fields[4].equals(T_STEP)   ||
  517.                         !fields[6].equals(TOLERANCE) || !fields[8].equals(INERTIAL_FRAME)) {
  518.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  519.                 }
  520.                 global.minDate        = new AbsoluteDate(fields[1], TimeScalesFactory.getUTC());
  521.                 global.maxDate        = new AbsoluteDate(fields[3], TimeScalesFactory.getUTC());
  522.                 global.tStep          = Double.parseDouble(fields[5]);
  523.                 global.tolerance      = Double.parseDouble(fields[7]);
  524.                 global.bodyToInertial = new TreeMap<Integer, Transform>();
  525.                 global.scToInertial   = new TreeMap<Integer, Transform>();
  526.                 try {
  527.                     global.inertialFrame = FramesFactory.getFrame(Predefined.valueOf(fields[9]));
  528.                 } catch (IllegalArgumentException iae) {
  529.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  530.                 }
  531.             }
  532.         },

  533.         /** Parser for observation transforms dump lines. */
  534.         TRANSFORM() {

  535.             /** {@inheritDoc} */
  536.             @Override
  537.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
  538.                 if (fields.length < 42 ||
  539.                     !fields[0].equals(INDEX) ||
  540.                     !fields[2].equals(BODY)  ||
  541.                     !fields[3].equals(R)     || !fields[8].equals(OMEGA)    || !fields[12].equals(OMEGA_DOT) ||
  542.                     !fields[16].equals(SPACECRAFT) ||
  543.                     !fields[17].equals(P)    || !fields[21].equals(V)   || !fields[25].equals(A) ||
  544.                     !fields[29].equals(R)    || !fields[34].equals(OMEGA)   || !fields[38].equals(OMEGA_DOT)) {
  545.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  546.                 }
  547.                 final int i   = Integer.parseInt(fields[1]);
  548.                 final AbsoluteDate date = global.minDate.shiftedBy(i * global.tStep);
  549.                 global.bodyToInertial.put(i,
  550.                                           new Transform(date,
  551.                                                         new Rotation(Double.parseDouble(fields[4]),
  552.                                                                      Double.parseDouble(fields[5]),
  553.                                                                      Double.parseDouble(fields[6]),
  554.                                                                      Double.parseDouble(fields[7]),
  555.                                                                      false),
  556.                                                         new Vector3D(Double.parseDouble(fields[9]),
  557.                                                                      Double.parseDouble(fields[10]),
  558.                                                                      Double.parseDouble(fields[11])),
  559.                                                         new Vector3D(Double.parseDouble(fields[13]),
  560.                                                                      Double.parseDouble(fields[14]),
  561.                                                                      Double.parseDouble(fields[15]))));
  562.                 global.scToInertial.put(i,
  563.                                         new Transform(date,
  564.                                                       new Transform(date,
  565.                                                                     new Vector3D(Double.parseDouble(fields[18]),
  566.                                                                                  Double.parseDouble(fields[19]),
  567.                                                                                  Double.parseDouble(fields[20])),
  568.                                                                     new Vector3D(Double.parseDouble(fields[22]),
  569.                                                                                  Double.parseDouble(fields[23]),
  570.                                                                                  Double.parseDouble(fields[24])),
  571.                                                                     new Vector3D(Double.parseDouble(fields[26]),
  572.                                                                                  Double.parseDouble(fields[27]),
  573.                                                                                  Double.parseDouble(fields[28]))),
  574.                                                       new Transform(date,
  575.                                                                     new Rotation(Double.parseDouble(fields[30]),
  576.                                                                                  Double.parseDouble(fields[31]),
  577.                                                                                  Double.parseDouble(fields[32]),
  578.                                                                                  Double.parseDouble(fields[33]),
  579.                                                                                  false),
  580.                                                                     new Vector3D(Double.parseDouble(fields[35]),
  581.                                                                                  Double.parseDouble(fields[36]),
  582.                                                                                  Double.parseDouble(fields[37])),
  583.                                                                     new Vector3D(Double.parseDouble(fields[39]),
  584.                                                                                  Double.parseDouble(fields[40]),
  585.                                                                                  Double.parseDouble(fields[41])))));
  586.             }

  587.         },

  588.         /** Parser for DEM tile global geometry dump lines. */
  589.         DEM_TILE() {

  590.             /** {@inheritDoc} */
  591.             @Override
  592.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
  593.                 if (fields.length < 13 ||
  594.                         !fields[1].equals(LAT_MIN) || !fields[3].equals(LAT_STEP) || !fields[5].equals(LAT_ROWS) ||
  595.                         !fields[7].equals(LON_MIN) || !fields[9].equals(LON_STEP) || !fields[11].equals(LON_COLS)) {
  596.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  597.                 }
  598.                 final String name             = fields[0];
  599.                 final double minLatitude      = Double.parseDouble(fields[2]);
  600.                 final double latitudeStep     = Double.parseDouble(fields[4]);
  601.                 final int    latitudeRows     = Integer.parseInt(fields[6]);
  602.                 final double minLongitude     = Double.parseDouble(fields[8]);
  603.                 final double longitudeStep    = Double.parseDouble(fields[10]);
  604.                 final int    longitudeColumns = Integer.parseInt(fields[12]);
  605.                 for (final ParsedTile tile : global.tiles) {
  606.                     if (tile.name.equals(name)) {
  607.                         throw new RuggedException(RuggedMessages.TILE_ALREADY_DEFINED,
  608.                                                   name, l, file.getAbsolutePath(), line);
  609.                     }
  610.                 }
  611.                 global.tiles.add(new ParsedTile(name,
  612.                                                 minLatitude, latitudeStep, latitudeRows,
  613.                                                 minLongitude, longitudeStep, longitudeColumns));
  614.             }

  615.         },

  616.         /** Parser for DEM cells dump lines. */
  617.         DEM_CELL() {

  618.             /** {@inheritDoc} */
  619.             @Override
  620.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
  621.                 if (fields.length < 7 ||
  622.                     !fields[1].equals(LAT_INDEX) || !fields[3].equals(LON_INDEX) || !fields[5].equals(ELEVATION)) {
  623.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  624.                 }
  625.                 final String name      = fields[0];
  626.                 final int    latIndex  = Integer.parseInt(fields[2]);
  627.                 final int    lonIndex  = Integer.parseInt(fields[4]);
  628.                 final double elevation = Double.parseDouble(fields[6]);
  629.                 for (final ParsedTile tile : global.tiles) {
  630.                     if (tile.name.equals(name)) {
  631.                         final int index = latIndex * tile.longitudeColumns + lonIndex;
  632.                         tile.elevations.put(index, elevation);
  633.                         return;
  634.                     }
  635.                 }
  636.                 throw new RuggedException(RuggedMessages.UNKNOWN_TILE,
  637.                                           name, l, file.getAbsolutePath(), line);
  638.             }

  639.         },

  640.         /** Parser for inverse location calls dump lines. */
  641.         INVERSE_LOCATION() {

  642.             /** {@inheritDoc} */
  643.             @Override
  644.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
  645.                 if (fields.length < 18 ||
  646.                         !fields[0].equals(SENSOR_NAME) ||
  647.                         !fields[2].equals(LATITUDE) || !fields[4].equals(LONGITUDE) || !fields[6].equals(ELEVATION) ||
  648.                         !fields[8].equals(MIN_LINE) || !fields[10].equals(MAX_LINE) ||
  649.                         !fields[12].equals(LIGHT_TIME) || !fields[14].equals(ABERRATION) ||
  650.                         !fields[16].equals(REFRACTION)) {
  651.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  652.                 }
  653.                 final String sensorName = fields[1];
  654.                 final GeodeticPoint point = new GeodeticPoint(Double.parseDouble(fields[3]),
  655.                                                               Double.parseDouble(fields[5]),
  656.                                                               Double.parseDouble(fields[7]));
  657.                 final int minLine = Integer.parseInt(fields[9]);
  658.                 final int maxLine = Integer.parseInt(fields[11]);
  659.                 if (global.calls.isEmpty()) {
  660.                     global.lightTimeCorrection         = Boolean.parseBoolean(fields[13]);
  661.                     global.aberrationOfLightCorrection = Boolean.parseBoolean(fields[15]);
  662.                     global.atmosphericRefraction       = Boolean.parseBoolean(fields[17]);
  663.                 } else {
  664.                     if (global.lightTimeCorrection != Boolean.parseBoolean(fields[13])) {
  665.                         throw new RuggedException(RuggedMessages.LIGHT_TIME_CORRECTION_REDEFINED,
  666.                                                   l, file.getAbsolutePath(), line);
  667.                     }
  668.                     if (global.aberrationOfLightCorrection != Boolean.parseBoolean(fields[15])) {
  669.                         throw new RuggedException(RuggedMessages.ABERRATION_OF_LIGHT_CORRECTION_REDEFINED,
  670.                                                   l, file.getAbsolutePath(), line);
  671.                     }
  672.                     if (global.atmosphericRefraction != Boolean.parseBoolean(fields[17])) {
  673.                         throw new RuggedException(RuggedMessages.ATMOSPHERIC_REFRACTION_REDEFINED,
  674.                                                   l, file.getAbsolutePath(), line);
  675.                     }
  676.                 }
  677.                 global.calls.add(new DumpedCall() {

  678.                     /** {@inheritDoc} */
  679.                     @Override
  680.                     public Object execute(final Rugged rugged) {
  681.                         return rugged.inverseLocation(sensorName, point, minLine, maxLine);
  682.                     }

  683.                 });
  684.             }

  685.         },

  686.         /** Parser for inverse location result dump lines. */
  687.         INVERSE_LOCATION_RESULT() {

  688.             /** {@inheritDoc} */
  689.             @Override
  690.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
  691.                 if (fields.length == 1) {
  692.                     if (fields[0].equals(NULL_RESULT)) {
  693.                         final SensorPixel sp = null;
  694.                         final DumpedCall last = global.calls.get(global.calls.size() - 1);
  695.                         last.expected = sp;
  696.                     } else {
  697.                         throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  698.                     }
  699.                 } else if (fields.length < 4 || !fields[0].equals(LINE_NUMBER) || !fields[2].equals(PIXEL_NUMBER)) {
  700.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  701.                 } else {
  702.                     final SensorPixel sp = new SensorPixel(Double.parseDouble(fields[1]),
  703.                                                            Double.parseDouble(fields[3]));
  704.                     final DumpedCall last = global.calls.get(global.calls.size() - 1);
  705.                     last.expected = sp;
  706.                 }
  707.             }

  708.         },

  709.         /** Parser for sensor dump lines. */
  710.         SENSOR() {

  711.             /** {@inheritDoc} */
  712.             @Override
  713.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
  714.                 if (fields.length < 8 || !fields[0].equals(SENSOR_NAME) ||
  715.                     !fields[2].equals(NB_PIXELS) || !fields[4].equals(POSITION)) {
  716.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  717.                 }
  718.                 final ParsedSensor sensor = global.getSensor(fields[1]);
  719.                 sensor.setNbPixels(Integer.parseInt(fields[3]));
  720.                 sensor.setPosition(new Vector3D(Double.parseDouble(fields[5]),
  721.                                                 Double.parseDouble(fields[6]),
  722.                                                 Double.parseDouble(fields[7])));
  723.             }

  724.         },

  725.         /** Parser for sensor mean plane dump lines. */
  726.         SENSOR_MEAN_PLANE() {

  727.             /** {@inheritDoc} */
  728.             @Override
  729.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
  730.                 if (fields.length < 16 || !fields[0].equals(SENSOR_NAME) ||
  731.                         !fields[2].equals(MIN_LINE) || !fields[4].equals(MAX_LINE) ||
  732.                         !fields[6].equals(MAX_EVAL) || !fields[8].equals(ACCURACY) ||
  733.                         !fields[10].equals(NORMAL)  || !fields[14].equals(CACHED_RESULTS)) {
  734.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  735.                 }
  736.                 final String   sensorName = fields[1];
  737.                 final int      minLine    = Integer.parseInt(fields[3]);
  738.                 final int      maxLine    = Integer.parseInt(fields[5]);
  739.                 final int      maxEval    = Integer.parseInt(fields[7]);
  740.                 final double   accuracy   = Double.parseDouble(fields[9]);
  741.                 final Vector3D normal     = new Vector3D(Double.parseDouble(fields[11]),
  742.                         Double.parseDouble(fields[12]),
  743.                         Double.parseDouble(fields[13]));
  744.                 final int      n          = Integer.parseInt(fields[15]);
  745.                 final CrossingResult[] cachedResults = new CrossingResult[n];
  746.                 int base = 16;
  747.                 for (int i = 0; i < n; ++i) {
  748.                     if (fields.length < base + 15 || !fields[base].equals(LINE_NUMBER) ||
  749.                             !fields[base + 2].equals(DATE) || !fields[base + 4].equals(TARGET) ||
  750.                             !fields[base + 8].equals(TARGET_DIRECTION)) {
  751.                         throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  752.                     }
  753.                     final double       ln                    = Double.parseDouble(fields[base + 1]);
  754.                     final AbsoluteDate date                  = new AbsoluteDate(fields[base + 3], TimeScalesFactory.getUTC());
  755.                     final Vector3D     target                = new Vector3D(Double.parseDouble(fields[base +  5]),
  756.                             Double.parseDouble(fields[base +  6]),
  757.                             Double.parseDouble(fields[base +  7]));
  758.                     final Vector3D targetDirection           = new Vector3D(Double.parseDouble(fields[base +  9]),
  759.                             Double.parseDouble(fields[base + 10]),
  760.                             Double.parseDouble(fields[base + 11]));
  761.                     final Vector3D targetDirectionDerivative = new Vector3D(Double.parseDouble(fields[base + 12]),
  762.                             Double.parseDouble(fields[base + 13]),
  763.                             Double.parseDouble(fields[base + 14]));
  764.                     cachedResults[i] = new CrossingResult(date, ln, target, targetDirection, targetDirectionDerivative);
  765.                     base += 15;
  766.                 }
  767.                 global.getSensor(sensorName).setMeanPlane(new ParsedMeanPlane(minLine, maxLine, maxEval, accuracy, normal, cachedResults));
  768.             }
  769.         },

  770.         /** Parser for sensor LOS dump lines. */
  771.         SENSOR_LOS() {

  772.             /** {@inheritDoc} */
  773.             @Override
  774.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
  775.                 if (fields.length < 10 || !fields[0].equals(SENSOR_NAME) ||
  776.                         !fields[2].equals(DATE) || !fields[4].equals(PIXEL_NUMBER) ||
  777.                         !fields[6].equals(LOS)) {
  778.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  779.                 }
  780.                 final String       sensorName  = fields[1];
  781.                 final AbsoluteDate date        = new AbsoluteDate(fields[3], TimeScalesFactory.getUTC());
  782.                 final int          pixelNumber = Integer.parseInt(fields[5]);
  783.                 final Vector3D     los         = new Vector3D(Double.parseDouble(fields[7]),
  784.                         Double.parseDouble(fields[8]),
  785.                         Double.parseDouble(fields[9]));
  786.                 global.getSensor(sensorName).setLOS(date, pixelNumber, los);
  787.             }
  788.         },

  789.         /** Parser for sensor datation dump lines. */
  790.         SENSOR_DATATION() {

  791.             /** {@inheritDoc} */
  792.             @Override
  793.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
  794.                 if (fields.length < 6 || !fields[0].equals(SENSOR_NAME) ||
  795.                         !fields[2].equals(LINE_NUMBER) || !fields[4].equals(DATE)) {
  796.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  797.                 }
  798.                 final String       sensorName  = fields[1];
  799.                 final double       lineNumber  = Double.parseDouble(fields[3]);
  800.                 final AbsoluteDate date        = new AbsoluteDate(fields[5], TimeScalesFactory.getUTC());
  801.                 global.getSensor(sensorName).setDatation(lineNumber, date);
  802.             }
  803.         },

  804.         /** Parser for sensor rate dump lines. */
  805.         SENSOR_RATE() {

  806.             /** {@inheritDoc} */
  807.             @Override
  808.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
  809.                 if (fields.length < 6 || !fields[0].equals(SENSOR_NAME) ||
  810.                     !fields[2].equals(LINE_NUMBER) || !fields[4].equals(RATE)) {
  811.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  812.                 }
  813.                 final String       sensorName  = fields[1];
  814.                 final double       lineNumber  = Double.parseDouble(fields[3]);
  815.                 final double       rate  = Double.parseDouble(fields[5]);
  816.                 global.getSensor(sensorName).setRate(lineNumber, rate);

  817.             }

  818.         };

  819.         /** Parse a line.
  820.          * @param l line number
  821.          * @param file dump file
  822.          * @param line line to parse
  823.          * @param global global parser to store parsed data
  824.          */
  825.         public static void parse(final int l, final File file, final String line, final DumpReplayer global) {

  826.             final String trimmed = line.trim();
  827.             if (trimmed.length() == 0 || trimmed.startsWith(COMMENT_START)) {
  828.                 return;
  829.             }

  830.             final int colon = line.indexOf(':');
  831.             if (colon > 0) {
  832.                 final String parsedKey = PATTERN.matcher(line.substring(0, colon).trim()).replaceAll("_").toUpperCase();
  833.                 try {
  834.                     final LineParser parser = LineParser.valueOf(parsedKey);
  835.                     final String[] fields;
  836.                     if (colon + 1 >= line.length()) {
  837.                         fields = new String[0];
  838.                     } else {
  839.                         fields = SEPARATOR.split(line.substring(colon + 1).trim());
  840.                     }
  841.                     parser.parse(l, file, line, fields, global);
  842.                 } catch (IllegalArgumentException iae) {
  843.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  844.                 }

  845.             } else {
  846.                 throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  847.             }

  848.         }

  849.         /** Parse a line.
  850.          * @param l line number
  851.          * @param file dump file
  852.          * @param line complete line
  853.          * @param fields data fields from the line
  854.          * @param global global parser to store parsed data
  855.          */
  856.         public abstract void parse(int l, File file, String line, String[] fields, DumpReplayer global);

  857.     }

  858.     /** Local class for handling already parsed tile data. */
  859.     private static class ParsedTile {

  860.         /** Name of the tile. */
  861.         private final String name;

  862.         /** Minimum latitude. */
  863.         private final double minLatitude;

  864.         /** Step in latitude (size of one raster element). */
  865.         private final double latitudeStep;

  866.         /** Number of latitude rows. */
  867.         private int latitudeRows;

  868.         /** Minimum longitude. */
  869.         private final double minLongitude;

  870.         /** Step in longitude (size of one raster element). */
  871.         private final double longitudeStep;

  872.         /** Number of longitude columns. */
  873.         private int longitudeColumns;

  874.         /** Raster elevation data. */
  875.         private final OpenIntToDoubleHashMap elevations;

  876.         /** Simple constructor.
  877.          * @param name of the tile
  878.          * @param minLatitude minimum latitude
  879.          * @param latitudeStep step in latitude (size of one raster element)
  880.          * @param latitudeRows number of latitude rows
  881.          * @param minLongitude minimum longitude
  882.          * @param longitudeStep step in longitude (size of one raster element)
  883.          * @param longitudeColumns number of longitude columns
  884.          */
  885.         ParsedTile(final String name,
  886.                    final double minLatitude, final double latitudeStep, final int latitudeRows,
  887.                    final double minLongitude, final double longitudeStep, final int longitudeColumns) {
  888.             this.name             = name;
  889.             this.minLatitude      = minLatitude;
  890.             this.latitudeStep     = latitudeStep;
  891.             this.minLongitude     = minLongitude;
  892.             this.longitudeStep    = longitudeStep;
  893.             this.latitudeRows     = latitudeRows;
  894.             this.longitudeColumns = longitudeColumns;
  895.             this.elevations       = new OpenIntToDoubleHashMap();
  896.         }

  897.         /** Check if a point is in the interpolable region of the tile.
  898.          * @param latitude point latitude
  899.          * @param longitude point longitude
  900.          * @return true if the point is in the interpolable region of the tile
  901.          */
  902.         public boolean isInterpolable(final double latitude, final double longitude) {
  903.             final int latitudeIndex  = (int) FastMath.floor((latitude  - minLatitude)  / latitudeStep);
  904.             final int longitudeIndex = (int) FastMath.floor((longitude - minLongitude) / longitudeStep);
  905.             return latitudeIndex  >= 0 && latitudeIndex  <= latitudeRows     - 2 &&
  906.                    longitudeIndex >= 0 && longitudeIndex <= longitudeColumns - 2;
  907.         }

  908.         /** Update the tile according to the Digital Elevation Model.
  909.          * @param tile to update
  910.          */
  911.         public void updateTile(final UpdatableTile tile) {

  912.             tile.setGeometry(minLatitude, minLongitude,
  913.                              latitudeStep, longitudeStep,
  914.                              latitudeRows, longitudeColumns);

  915.             final OpenIntToDoubleHashMap.Iterator iterator = elevations.iterator();
  916.             while (iterator.hasNext()) {
  917.                 iterator.advance();
  918.                 final int    index          = iterator.key();
  919.                 final int    latitudeIndex  = index / longitudeColumns;
  920.                 final int    longitudeIndex = index % longitudeColumns;
  921.                 final double elevation      = iterator.value();
  922.                 tile.setElevation(latitudeIndex, longitudeIndex, elevation);
  923.             }

  924.         }

  925.     }

  926.     /** Local class for handling already parsed sensor data. */
  927.     private static class ParsedSensor implements LineDatation, TimeDependentLOS {

  928.         /** Name of the sensor. */
  929.         private final String name;

  930.         /** Number of pixels. */
  931.         private int nbPixels;

  932.         /** Position. */
  933.         private Vector3D position;

  934.         /** Mean plane crossing finder. */
  935.         private ParsedMeanPlane meanPlane;

  936.         /** LOS map. */
  937.         private final Map<Integer, List<Pair<AbsoluteDate, Vector3D>>> losMap;

  938.         /** Datation. */
  939.         private final List<Pair<Double, AbsoluteDate>> datation;

  940.         /** Rate. */
  941.         private final List<Pair<Double, Double>> rates;

  942.         /** simple constructor.
  943.          * @param name name of the sensor
  944.          */
  945.         ParsedSensor(final String name) {
  946.             this.name     = name;
  947.             this.losMap   = new HashMap<Integer, List<Pair<AbsoluteDate, Vector3D>>>();
  948.             this.datation = new ArrayList<Pair<Double, AbsoluteDate>>();
  949.             this.rates    = new ArrayList<Pair<Double, Double>>();
  950.         }

  951.         /** Set the mean place finder.
  952.          * @param meanPlane mean plane finder
  953.          */
  954.         public void setMeanPlane(final ParsedMeanPlane meanPlane) {
  955.             this.meanPlane = meanPlane;
  956.         }

  957.         /** Set the position.
  958.          * @param position position
  959.          */
  960.         public void setPosition(final Vector3D position) {
  961.             this.position = position;
  962.         }

  963.         /** Set the number of pixels.
  964.          * @param nbPixels number of pixels
  965.          */
  966.         public void setNbPixels(final int nbPixels) {
  967.             this.nbPixels = nbPixels;
  968.         }

  969.         /** {@inheritDoc} */
  970.         @Override
  971.         public int getNbPixels() {
  972.             return nbPixels;
  973.         }

  974.         /** Set a los direction.
  975.          * @param date date
  976.          * @param pixelNumber number of the pixel
  977.          * @param los los direction
  978.          */
  979.         public void setLOS(final AbsoluteDate date, final int pixelNumber, final Vector3D los) {
  980.             List<Pair<AbsoluteDate, Vector3D>> list = losMap.get(pixelNumber);
  981.             if (list == null) {
  982.                 list = new ArrayList<Pair<AbsoluteDate, Vector3D>>();
  983.                 losMap.put(pixelNumber, list);
  984.             }
  985.             // find insertion index to have LOS sorted chronologically
  986.             int index = 0;
  987.             while (index < list.size()) {
  988.                 if (list.get(index).getFirst().compareTo(date) > 0) {
  989.                     break;
  990.                 }
  991.                 ++index;
  992.             }
  993.             list.add(index, new Pair<AbsoluteDate, Vector3D>(date, los));
  994.         }

  995.         /** {@inheritDoc} */
  996.         @Override
  997.         public Vector3D getLOS(final int index, final AbsoluteDate date) {
  998.             final List<Pair<AbsoluteDate, Vector3D>> list = losMap.get(index);
  999.             if (list == null) {
  1000.                 throw new RuggedInternalError(null);
  1001.             }

  1002.             if (list.size() < 2) {
  1003.                 return list.get(0).getSecond();
  1004.             }

  1005.             // find entries bracketing the the date
  1006.             int sup = 0;
  1007.             while (sup < list.size() - 1) {
  1008.                 if (list.get(sup).getFirst().compareTo(date) >= 0) {
  1009.                     break;
  1010.                 }
  1011.                 ++sup;
  1012.             }
  1013.             final int inf = (sup == 0) ? sup++ : (sup - 1);

  1014.             final AbsoluteDate dInf  = list.get(inf).getFirst();
  1015.             final Vector3D     lInf  = list.get(inf).getSecond();
  1016.             final AbsoluteDate dSup  = list.get(sup).getFirst();
  1017.             final Vector3D     lSup  = list.get(sup).getSecond();
  1018.             final double       alpha = date.durationFrom(dInf) / dSup.durationFrom(dInf);
  1019.             return new Vector3D(alpha, lSup, 1 - alpha, lInf);

  1020.         }

  1021.         /** {@inheritDoc} */
  1022.         @Override
  1023.         public <T extends Derivative<T>> FieldVector3D<T> getLOSDerivatives(final int index, final AbsoluteDate date,
  1024.                                                                             final DerivativeGenerator<T> generator) {
  1025.             final Vector3D los = getLOS(index, date);
  1026.             return new FieldVector3D<>(generator.constant(los.getX()),
  1027.                                        generator.constant(los.getY()),
  1028.                                        generator.constant(los.getZ()));
  1029.         }

  1030.         /** Set a datation pair.
  1031.          * @param lineNumber line number
  1032.          * @param date date
  1033.          */
  1034.         public void setDatation(final double lineNumber, final AbsoluteDate date) {
  1035.             // find insertion index to have datations sorted chronologically
  1036.             int index = 0;
  1037.             while (index < datation.size()) {
  1038.                 if (datation.get(index).getSecond().compareTo(date) > 0) {
  1039.                     break;
  1040.                 }
  1041.                 ++index;
  1042.             }
  1043.             datation.add(index, new Pair<Double, AbsoluteDate>(lineNumber, date));
  1044.         }

  1045.         /** {@inheritDoc} */
  1046.         @Override
  1047.         public AbsoluteDate getDate(final double lineNumber) {

  1048.             if (datation.size() < 2) {
  1049.                 return datation.get(0).getSecond();
  1050.             }

  1051.             // find entries bracketing the line number
  1052.             int sup = 0;
  1053.             while (sup < datation.size() - 1) {
  1054.                 if (datation.get(sup).getFirst() >= lineNumber) {
  1055.                     break;
  1056.                 }
  1057.                 ++sup;
  1058.             }
  1059.             final int inf = (sup == 0) ? sup++ : (sup - 1);

  1060.             final double       lInf  = datation.get(inf).getFirst();
  1061.             final AbsoluteDate dInf  = datation.get(inf).getSecond();
  1062.             final double       lSup  = datation.get(sup).getFirst();
  1063.             final AbsoluteDate dSup  = datation.get(sup).getSecond();
  1064.             final double       alpha = (lineNumber - lInf) / (lSup - lInf);
  1065.             return dInf.shiftedBy(alpha * dSup.durationFrom(dInf));

  1066.         }

  1067.         /** {@inheritDoc} */
  1068.         @Override
  1069.         public double getLine(final AbsoluteDate date) {

  1070.             if (datation.size() < 2) {
  1071.                 return datation.get(0).getFirst();
  1072.             }

  1073.             // find entries bracketing the date
  1074.             int sup = 0;
  1075.             while (sup < datation.size() - 1) {
  1076.                 if (datation.get(sup).getSecond().compareTo(date) >= 0) {
  1077.                     break;
  1078.                 }
  1079.                 ++sup;
  1080.             }
  1081.             final int inf = (sup == 0) ? sup++ : (sup - 1);

  1082.             final double       lInf  = datation.get(inf).getFirst();
  1083.             final AbsoluteDate dInf  = datation.get(inf).getSecond();
  1084.             final double       lSup  = datation.get(sup).getFirst();
  1085.             final AbsoluteDate dSup  = datation.get(sup).getSecond();
  1086.             final double       alpha = date.durationFrom(dInf) / dSup.durationFrom(dInf);
  1087.             return alpha * lSup + (1 - alpha) * lInf;

  1088.         }

  1089.         /** Set a rate.
  1090.          * @param lineNumber line number
  1091.          * @param rate lines rate
  1092.          */
  1093.         public void setRate(final double lineNumber, final double rate) {
  1094.             // find insertion index to have rates sorted by line numbers
  1095.             int index = 0;
  1096.             while (index < rates.size()) {
  1097.                 if (rates.get(index).getFirst() > lineNumber) {
  1098.                     break;
  1099.                 }
  1100.                 ++index;
  1101.             }
  1102.             rates.add(index, new Pair<Double, Double>(lineNumber, rate));
  1103.         }

  1104.         /** {@inheritDoc} */
  1105.         @Override
  1106.         public double getRate(final double lineNumber) {

  1107.             if (rates.size() < 2) {
  1108.                 return rates.get(0).getSecond();
  1109.             }

  1110.             // find entries bracketing the line number
  1111.             int sup = 0;
  1112.             while (sup < rates.size() - 1) {
  1113.                 if (rates.get(sup).getFirst() >= lineNumber) {
  1114.                     break;
  1115.                 }
  1116.                 ++sup;
  1117.             }
  1118.             final int inf = (sup == 0) ? sup++ : (sup - 1);

  1119.             final double lInf  = rates.get(inf).getFirst();
  1120.             final double rInf  = rates.get(inf).getSecond();
  1121.             final double lSup  = rates.get(sup).getFirst();
  1122.             final double rSup  = rates.get(sup).getSecond();
  1123.             final double alpha = (lineNumber - lInf) / (lSup - lInf);
  1124.             return alpha * rSup + (1 - alpha) * rInf;

  1125.         }

  1126.         /** {@inheritDoc} */
  1127.         @Override
  1128.         public Stream<ParameterDriver> getParametersDrivers() {
  1129.             return Stream.<ParameterDriver>empty();
  1130.         }

  1131.     }

  1132.     /** Local class for handling already parsed mean plane data. */
  1133.     private static class ParsedMeanPlane {

  1134.         /** Min line. */
  1135.         private final int minLine;

  1136.         /** Max line. */
  1137.         private final int maxLine;

  1138.         /** Maximum number of evaluations. */
  1139.         private final int maxEval;

  1140.         /** Accuracy to use for finding crossing line number. */
  1141.         private final double accuracy;

  1142.         /** Mean plane normal. */
  1143.         private final Vector3D normal;

  1144.         /** Cached results. */
  1145.         private final CrossingResult[] cachedResults;

  1146.         /** simple constructor.
  1147.          * @param minLine min line
  1148.          * @param maxLine max line
  1149.          * @param maxEval maximum number of evaluations
  1150.          * @param accuracy accuracy to use for finding crossing line number
  1151.          * @param normal mean plane normal
  1152.          * @param cachedResults cached results
  1153.          */
  1154.         ParsedMeanPlane(final int minLine, final int maxLine,
  1155.                         final int maxEval, final double accuracy, final Vector3D normal,
  1156.                         final CrossingResult[] cachedResults) {
  1157.             this.minLine       = minLine;
  1158.             this.maxLine       = maxLine;
  1159.             this.maxEval       = maxEval;
  1160.             this.accuracy      = accuracy;
  1161.             this.normal        = normal;
  1162.             this.cachedResults = cachedResults.clone();
  1163.         }

  1164.     }

  1165.     /** Local interface for dumped calls. */
  1166.     private abstract static class DumpedCall {

  1167.         /** Expected result. */
  1168.         private Object expected;

  1169.         /** Execute a call.
  1170.          * @param rugged Rugged instance on which called should be performed
  1171.          * @return result of the call
  1172.          */
  1173.         public abstract Object execute(Rugged rugged);

  1174.     }

  1175. }