DumpReplayer.java

  1. /* Copyright 2013-2022 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.                 builder.setDigitalElevationModel(new TileUpdater() {

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

  262.             builder.setEllipsoid(ellipsoid);

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


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

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

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

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

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

  331.             return rugged;

  332.         } catch (IOException | NoSuchMethodException  | IllegalAccessException  | InvocationTargetException e) {
  333.             // this should never happen
  334.             throw new RuggedInternalError(e);
  335.         }
  336.     }

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

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

  367.     /** Container for replay results. */
  368.     public static class Result {

  369.         /** Expected result. */
  370.         private final Object expected;

  371.         /** Replayed result. */
  372.         private final Object replayed;

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

  381.         /** Get the expected result.
  382.          * @return expected result
  383.          */
  384.         public Object getExpected() {
  385.             return expected;
  386.         }

  387.         /** Get the replayed result.
  388.          * @return replayed result
  389.          */
  390.         public Object getReplayed() {
  391.             return replayed;
  392.         }

  393.     }

  394.     /** Line parsers. */
  395.     private enum LineParser {

  396.         /** Parser for algorithm dump lines. */
  397.         ALGORITHM() {

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

  416.         },

  417.         /** Parser for ellipsoid dump lines. */
  418.         ELLIPSOID() {

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

  435.         },

  436.         /** Parser for direct location calls dump lines. */
  437.         DIRECT_LOCATION() {

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

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

  479.                 });
  480.             }
  481.         },

  482.         /** Parser for direct location result dump lines. */
  483.         DIRECT_LOCATION_RESULT() {

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

  506.         },

  507.         /** Parser for search span dump lines. */
  508.         SPAN() {

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

  530.         /** Parser for observation transforms dump lines. */
  531.         TRANSFORM() {

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

  584.         },

  585.         /** Parser for DEM tile global geometry dump lines. */
  586.         DEM_TILE() {

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

  612.         },

  613.         /** Parser for DEM cells dump lines. */
  614.         DEM_CELL() {

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

  636.         },

  637.         /** Parser for inverse location calls dump lines. */
  638.         INVERSE_LOCATION() {

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

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

  680.                 });
  681.             }

  682.         },

  683.         /** Parser for inverse location result dump lines. */
  684.         INVERSE_LOCATION_RESULT() {

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

  705.         },

  706.         /** Parser for sensor dump lines. */
  707.         SENSOR() {

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

  721.         },

  722.         /** Parser for sensor mean plane dump lines. */
  723.         SENSOR_MEAN_PLANE() {

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

  767.         /** Parser for sensor LOS dump lines. */
  768.         SENSOR_LOS() {

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

  786.         /** Parser for sensor datation dump lines. */
  787.         SENSOR_DATATION() {

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

  801.         /** Parser for sensor rate dump lines. */
  802.         SENSOR_RATE() {

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

  814.             }

  815.         };

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

  823.             final String trimmed = line.trim();
  824.             if (trimmed.length() == 0 || trimmed.startsWith(COMMENT_START)) {
  825.                 return;
  826.             }

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

  842.             } else {
  843.                 throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  844.             }

  845.         }

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

  854.     }

  855.     /** Local class for handling already parsed tile data. */
  856.     private static class ParsedTile {

  857.         /** Name of the tile. */
  858.         private final String name;

  859.         /** Minimum latitude. */
  860.         private final double minLatitude;

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

  863.         /** Number of latitude rows. */
  864.         private int latitudeRows;

  865.         /** Minimum longitude. */
  866.         private final double minLongitude;

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

  869.         /** Number of longitude columns. */
  870.         private int longitudeColumns;

  871.         /** Raster elevation data. */
  872.         private final OpenIntToDoubleHashMap elevations;

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

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

  905.         /** Update the tile according to the Digital Elevation Model.
  906.          * @param tile to update
  907.          */
  908.         public void updateTile(final UpdatableTile tile) {

  909.             tile.setGeometry(minLatitude, minLongitude,
  910.                              latitudeStep, longitudeStep,
  911.                              latitudeRows, longitudeColumns);

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

  921.         }

  922.     }

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

  925.         /** Name of the sensor. */
  926.         private final String name;

  927.         /** Number of pixels. */
  928.         private int nbPixels;

  929.         /** Position. */
  930.         private Vector3D position;

  931.         /** Mean plane crossing finder. */
  932.         private ParsedMeanPlane meanPlane;

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

  935.         /** Datation. */
  936.         private final List<Pair<Double, AbsoluteDate>> datation;

  937.         /** Rate. */
  938.         private final List<Pair<Double, Double>> rates;

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

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

  954.         /** Set the position.
  955.          * @param position position
  956.          */
  957.         public void setPosition(final Vector3D position) {
  958.             this.position = position;
  959.         }

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

  966.         /** {@inheritDoc} */
  967.         @Override
  968.         public int getNbPixels() {
  969.             return nbPixels;
  970.         }

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

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

  999.             if (list.size() < 2) {
  1000.                 return list.get(0).getSecond();
  1001.             }

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

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

  1017.         }

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

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

  1042.         /** {@inheritDoc} */
  1043.         @Override
  1044.         public AbsoluteDate getDate(final double lineNumber) {

  1045.             if (datation.size() < 2) {
  1046.                 return datation.get(0).getSecond();
  1047.             }

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

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

  1063.         }

  1064.         /** {@inheritDoc} */
  1065.         @Override
  1066.         public double getLine(final AbsoluteDate date) {

  1067.             if (datation.size() < 2) {
  1068.                 return datation.get(0).getFirst();
  1069.             }

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

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

  1085.         }

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

  1101.         /** {@inheritDoc} */
  1102.         @Override
  1103.         public double getRate(final double lineNumber) {

  1104.             if (rates.size() < 2) {
  1105.                 return rates.get(0).getSecond();
  1106.             }

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

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

  1122.         }

  1123.         /** {@inheritDoc} */
  1124.         @Override
  1125.         public Stream<ParameterDriver> getParametersDrivers() {
  1126.             return Stream.<ParameterDriver>empty();
  1127.         }

  1128.     }

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

  1131.         /** Min line. */
  1132.         private final int minLine;

  1133.         /** Max line. */
  1134.         private final int maxLine;

  1135.         /** Maximum number of evaluations. */
  1136.         private final int maxEval;

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

  1139.         /** Mean plane normal. */
  1140.         private final Vector3D normal;

  1141.         /** Cached results. */
  1142.         private final CrossingResult[] cachedResults;

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

  1161.     }

  1162.     /** Local interface for dumped calls. */
  1163.     private abstract static class DumpedCall {

  1164.         /** Expected result. */
  1165.         private Object expected;

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

  1171.     }

  1172. }