DumpReplayer.java

  1. /* Copyright 2013-2019 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.stream.Stream;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  174.     /** Constant elevation for constant elevation algorithm. */
  175.     private double constantElevation;

  176.     /** Algorithm identifier. */
  177.     private AlgorithmId algorithmId;

  178.     /** Ellipsoid. */
  179.     private OneAxisEllipsoid ellipsoid;

  180.     /** Tiles list. */
  181.     private final List<ParsedTile> tiles;

  182.     /** Sensors list. */
  183.     private final List<ParsedSensor> sensors;

  184.     /** Interpolator min date. */
  185.     private AbsoluteDate minDate;

  186.     /** Interpolator max date. */
  187.     private AbsoluteDate maxDate;

  188.     /** Interpolator step. */
  189.     private double tStep;

  190.     /** Interpolator overshoot tolerance. */
  191.     private double tolerance;

  192.     /** Inertial frame. */
  193.     private Frame inertialFrame;

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

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

  198.     /** Flag for light time correction. */
  199.     private boolean lightTimeCorrection;

  200.     /** Flag for aberration of light correction. */
  201.     private boolean aberrationOfLightCorrection;

  202.     /** Flag for atmospheric refraction. */
  203.     private boolean atmosphericRefraction;

  204.     /** Dumped calls. */
  205.     private final List<DumpedCall> calls;

  206.     /** Simple constructor.
  207.      */
  208.     public DumpReplayer() {
  209.         tiles   = new ArrayList<ParsedTile>();
  210.         sensors = new ArrayList<ParsedSensor>();
  211.         calls   = new ArrayList<DumpedCall>();
  212.     }

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

  229.     /** Create a Rugged instance from parsed data.
  230.      * @return rugged instance
  231.      */
  232.     public Rugged createRugged() {
  233.         try {
  234.             final RuggedBuilder builder = new RuggedBuilder();

  235.             if (algorithmId == null) {
  236.                 algorithmId = AlgorithmId.IGNORE_DEM_USE_ELLIPSOID;
  237.             }
  238.             builder.setAlgorithm(algorithmId);
  239.             if (algorithmId == AlgorithmId.CONSTANT_ELEVATION_OVER_ELLIPSOID) {
  240.                 builder.setConstantElevation(constantElevation);
  241.             } else if (algorithmId != AlgorithmId.IGNORE_DEM_USE_ELLIPSOID) {
  242.                 builder.setDigitalElevationModel(new TileUpdater() {

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

  257.             builder.setEllipsoid(ellipsoid);

  258.             builder.setLightTimeCorrection(lightTimeCorrection);
  259.             builder.setAberrationOfLightCorrection(aberrationOfLightCorrection);
  260.             if (atmosphericRefraction) { // Use the default model with the default configuration values
  261.                 final ExtendedEllipsoid extendedEllipsoid = builder.getEllipsoid();
  262.                 final AtmosphericRefraction atmosphericModel = new MultiLayerModel(extendedEllipsoid);
  263.                 // Build Rugged with atmospheric refraction model
  264.                 builder.setRefractionCorrection(atmosphericModel);
  265.             }


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

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

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

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

  320.             final Method setPlaneCrossing = Rugged.class.getDeclaredMethod("setPlaneCrossing",
  321.                                                                            SensorMeanPlaneCrossing.class);
  322.             setPlaneCrossing.setAccessible(true);
  323.             for (final SensorMeanPlaneCrossing planeCrossing : planeCrossings) {
  324.                 setPlaneCrossing.invoke(rugged, planeCrossing);
  325.             }

  326.             return rugged;

  327.         } catch (IOException ioe) {
  328.             throw new RuggedException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE, ioe.getLocalizedMessage());
  329.         } catch (SecurityException e) {
  330.             // this should never happen
  331.             throw new RuggedInternalError(e);
  332.         } catch (NoSuchMethodException e) {
  333.             // this should never happen
  334.             throw new RuggedInternalError(e);
  335.         } catch (IllegalArgumentException e) {
  336.             // this should never happen
  337.             throw new RuggedInternalError(e);
  338.         } catch (IllegalAccessException e) {
  339.             // this should never happen
  340.             throw new RuggedInternalError(e);
  341.         } catch (InvocationTargetException e) {
  342.             // this should never happen
  343.             throw new RuggedInternalError(e);
  344.         }
  345.     }

  346.     /** Get a sensor by name.
  347.      * @param name sensor name
  348.      * @return parsed sensor
  349.      */
  350.     private ParsedSensor getSensor(final String name) {
  351.         for (final ParsedSensor sensor : sensors) {
  352.             if (sensor.name.equals(name)) {
  353.                 return sensor;
  354.             }
  355.         }
  356.         final ParsedSensor sensor = new ParsedSensor(name);
  357.         sensors.add(sensor);
  358.         return sensor;
  359.     }

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

  376.     /** Container for replay results. */
  377.     public static class Result {

  378.         /** Expected result. */
  379.         private final Object expected;

  380.         /** Replayed result. */
  381.         private final Object replayed;

  382.         /** Simple constructor.
  383.          * @param expected expected result
  384.          * @param replayed replayed result
  385.          */
  386.         private Result(final Object expected, final Object replayed) {
  387.             this.expected = expected;
  388.             this.replayed = replayed;
  389.         }

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

  396.         /** Get the replayed result.
  397.          * @return replayed result
  398.          */
  399.         public Object getReplayed() {
  400.             return replayed;
  401.         }

  402.     }

  403.     /** Line parsers. */
  404.     private enum LineParser {

  405.         /** Parser for algorithm dump lines. */
  406.         ALGORITHM() {

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

  425.         },

  426.         /** Parser for ellipsoid dump lines. */
  427.         ELLIPSOID() {

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

  444.         },

  445.         /** Parser for direct location calls dump lines. */
  446.         DIRECT_LOCATION() {

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

  483.                     /** {@inheritDoc} */
  484.                     @Override
  485.                     public Object execute(final Rugged rugged) {
  486.                         return rugged.directLocation(date, position, los);
  487.                     }

  488.                 });
  489.             }
  490.         },

  491.         /** Parser for direct location result dump lines. */
  492.         DIRECT_LOCATION_RESULT() {

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

  515.         },

  516.         /** Parser for search span dump lines. */
  517.         SPAN() {

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

  539.         /** Parser for observation transforms dump lines. */
  540.         TRANSFORM() {

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

  593.         },

  594.         /** Parser for DEM tile global geometry dump lines. */
  595.         DEM_TILE() {

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

  621.         },

  622.         /** Parser for DEM cells dump lines. */
  623.         DEM_CELL() {

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

  645.         },

  646.         /** Parser for inverse location calls dump lines. */
  647.         INVERSE_LOCATION() {

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

  684.                     /** {@inheritDoc} */
  685.                     @Override
  686.                     public Object execute(final Rugged rugged) {
  687.                         return rugged.inverseLocation(sensorName, point, minLine, maxLine);
  688.                     }

  689.                 });
  690.             }

  691.         },

  692.         /** Parser for inverse location result dump lines. */
  693.         INVERSE_LOCATION_RESULT() {

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

  714.         },

  715.         /** Parser for sensor dump lines. */
  716.         SENSOR() {

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

  730.         },

  731.         /** Parser for sensor mean plane dump lines. */
  732.         SENSOR_MEAN_PLANE() {

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

  776.         /** Parser for sensor LOS dump lines. */
  777.         SENSOR_LOS() {

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

  795.         /** Parser for sensor datation dump lines. */
  796.         SENSOR_DATATION() {

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

  810.         /** Parser for sensor rate dump lines. */
  811.         SENSOR_RATE() {

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

  823.             }

  824.         };

  825.         /** Parse a line.
  826.          * @param l line number
  827.          * @param file dump file
  828.          * @param line line to parse
  829.          * @param global global parser to store parsed data
  830.          */
  831.         public static void parse(final int l, final File file, final String line, final DumpReplayer global) {

  832.             final String trimmed = line.trim();
  833.             if (trimmed.length() == 0 || trimmed.startsWith(COMMENT_START)) {
  834.                 return;
  835.             }

  836.             final int colon = line.indexOf(':');
  837.             if (colon > 0) {
  838.                 final String parsedKey = line.substring(0, colon).trim().replaceAll(" ", "_").toUpperCase();
  839.                 try {
  840.                     final LineParser parser = LineParser.valueOf(parsedKey);
  841.                     final String[] fields;
  842.                     if (colon + 1 >= line.length()) {
  843.                         fields = new String[0];
  844.                     } else {
  845.                         fields = line.substring(colon + 1).trim().split("\\s+");
  846.                     }
  847.                     parser.parse(l, file, line, fields, global);
  848.                 } catch (IllegalArgumentException iae) {
  849.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  850.                 }

  851.             } else {
  852.                 throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  853.             }

  854.         }

  855.         /** Parse a line.
  856.          * @param l line number
  857.          * @param file dump file
  858.          * @param line complete line
  859.          * @param fields data fields from the line
  860.          * @param global global parser to store parsed data
  861.          */
  862.         public abstract void parse(int l, File file, String line, String[] fields, DumpReplayer global);

  863.     }

  864.     /** Local class for handling already parsed tile data. */
  865.     private static class ParsedTile {

  866.         /** Name of the tile. */
  867.         private final String name;

  868.         /** Minimum latitude. */
  869.         private final double minLatitude;

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

  872.         /** Number of latitude rows. */
  873.         private int latitudeRows;

  874.         /** Minimum longitude. */
  875.         private final double minLongitude;

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

  878.         /** Number of longitude columns. */
  879.         private int longitudeColumns;

  880.         /** Raster elevation data. */
  881.         private final OpenIntToDoubleHashMap elevations;

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

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

  914.         /** Update the tile according to the Digital Elevation Model.
  915.          * @param tile to update
  916.          */
  917.         public void updateTile(final UpdatableTile tile) {

  918.             tile.setGeometry(minLatitude, minLongitude,
  919.                              latitudeStep, longitudeStep,
  920.                              latitudeRows, longitudeColumns);

  921.             final OpenIntToDoubleHashMap.Iterator iterator = elevations.iterator();
  922.             while (iterator.hasNext()) {
  923.                 iterator.advance();
  924.                 final int    index          = iterator.key();
  925.                 final int    latitudeIndex  = index / longitudeColumns;
  926.                 final int    longitudeIndex = index % longitudeColumns;
  927.                 final double elevation      = iterator.value();
  928.                 tile.setElevation(latitudeIndex, longitudeIndex, elevation);
  929.             }

  930.         }

  931.     }

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

  934.         /** Name of the sensor. */
  935.         private final String name;

  936.         /** Number of pixels. */
  937.         private int nbPixels;

  938.         /** Position. */
  939.         private Vector3D position;

  940.         /** Mean plane crossing finder. */
  941.         private ParsedMeanPlane meanPlane;

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

  944.         /** Datation. */
  945.         private final List<Pair<Double, AbsoluteDate>> datation;

  946.         /** Rate. */
  947.         private final List<Pair<Double, Double>> rates;

  948.         /** simple constructor.
  949.          * @param name name of the sensor
  950.          */
  951.         ParsedSensor(final String name) {
  952.             this.name     = name;
  953.             this.losMap   = new HashMap<Integer, List<Pair<AbsoluteDate, Vector3D>>>();
  954.             this.datation = new ArrayList<Pair<Double, AbsoluteDate>>();
  955.             this.rates    = new ArrayList<Pair<Double, Double>>();
  956.         }

  957.         /** Set the mean place finder.
  958.          * @param meanPlane mean plane finder
  959.          */
  960.         public void setMeanPlane(final ParsedMeanPlane meanPlane) {
  961.             this.meanPlane = meanPlane;
  962.         }

  963.         /** Set the position.
  964.          * @param position position
  965.          */
  966.         public void setPosition(final Vector3D position) {
  967.             this.position = position;
  968.         }

  969.         /** Set the number of pixels.
  970.          * @param nbPixels number of pixels
  971.          */
  972.         public void setNbPixels(final int nbPixels) {
  973.             this.nbPixels = nbPixels;
  974.         }

  975.         /** {@inheritDoc} */
  976.         @Override
  977.         public int getNbPixels() {
  978.             return nbPixels;
  979.         }

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

  1001.         /** {@inheritDoc} */
  1002.         @Override
  1003.         public Vector3D getLOS(final int index, final AbsoluteDate date) {
  1004.             final List<Pair<AbsoluteDate, Vector3D>> list = losMap.get(index);
  1005.             if (list == null) {
  1006.                 throw new RuggedInternalError(null);
  1007.             }

  1008.             if (list.size() < 2) {
  1009.                 return list.get(0).getSecond();
  1010.             }

  1011.             // find entries bracketing the the date
  1012.             int sup = 0;
  1013.             while (sup < list.size() - 1) {
  1014.                 if (list.get(sup).getFirst().compareTo(date) >= 0) {
  1015.                     break;
  1016.                 }
  1017.                 ++sup;
  1018.             }
  1019.             final int inf = (sup == 0) ? sup++ : (sup - 1);

  1020.             final AbsoluteDate dInf  = list.get(inf).getFirst();
  1021.             final Vector3D     lInf  = list.get(inf).getSecond();
  1022.             final AbsoluteDate dSup  = list.get(sup).getFirst();
  1023.             final Vector3D     lSup  = list.get(sup).getSecond();
  1024.             final double       alpha = date.durationFrom(dInf) / dSup.durationFrom(dInf);
  1025.             return new Vector3D(alpha, lSup, 1 - alpha, lInf);

  1026.         }

  1027.         /** {@inheritDoc} */
  1028.         @Override
  1029.         public FieldVector3D<DerivativeStructure> getLOSDerivatives(final int index, final AbsoluteDate date,
  1030.                                                                     final DSGenerator generator) {
  1031.             final Vector3D los = getLOS(index, date);
  1032.             return new FieldVector3D<DerivativeStructure>(generator.constant(los.getX()),
  1033.                                                           generator.constant(los.getY()),
  1034.                                                           generator.constant(los.getZ()));
  1035.         }

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

  1051.         /** {@inheritDoc} */
  1052.         @Override
  1053.         public AbsoluteDate getDate(final double lineNumber) {

  1054.             if (datation.size() < 2) {
  1055.                 return datation.get(0).getSecond();
  1056.             }

  1057.             // find entries bracketing the line number
  1058.             int sup = 0;
  1059.             while (sup < datation.size() - 1) {
  1060.                 if (datation.get(sup).getFirst() >= lineNumber) {
  1061.                     break;
  1062.                 }
  1063.                 ++sup;
  1064.             }
  1065.             final int inf = (sup == 0) ? sup++ : (sup - 1);

  1066.             final double       lInf  = datation.get(inf).getFirst();
  1067.             final AbsoluteDate dInf  = datation.get(inf).getSecond();
  1068.             final double       lSup  = datation.get(sup).getFirst();
  1069.             final AbsoluteDate dSup  = datation.get(sup).getSecond();
  1070.             final double       alpha = (lineNumber - lInf) / (lSup - lInf);
  1071.             return dInf.shiftedBy(alpha * dSup.durationFrom(dInf));

  1072.         }

  1073.         /** {@inheritDoc} */
  1074.         @Override
  1075.         public double getLine(final AbsoluteDate date) {

  1076.             if (datation.size() < 2) {
  1077.                 return datation.get(0).getFirst();
  1078.             }

  1079.             // find entries bracketing the date
  1080.             int sup = 0;
  1081.             while (sup < datation.size() - 1) {
  1082.                 if (datation.get(sup).getSecond().compareTo(date) >= 0) {
  1083.                     break;
  1084.                 }
  1085.                 ++sup;
  1086.             }
  1087.             final int inf = (sup == 0) ? sup++ : (sup - 1);

  1088.             final double       lInf  = datation.get(inf).getFirst();
  1089.             final AbsoluteDate dInf  = datation.get(inf).getSecond();
  1090.             final double       lSup  = datation.get(sup).getFirst();
  1091.             final AbsoluteDate dSup  = datation.get(sup).getSecond();
  1092.             final double       alpha = date.durationFrom(dInf) / dSup.durationFrom(dInf);
  1093.             return alpha * lSup + (1 - alpha) * lInf;

  1094.         }

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

  1110.         /** {@inheritDoc} */
  1111.         @Override
  1112.         public double getRate(final double lineNumber) {

  1113.             if (rates.size() < 2) {
  1114.                 return rates.get(0).getSecond();
  1115.             }

  1116.             // find entries bracketing the line number
  1117.             int sup = 0;
  1118.             while (sup < rates.size() - 1) {
  1119.                 if (rates.get(sup).getFirst() >= lineNumber) {
  1120.                     break;
  1121.                 }
  1122.                 ++sup;
  1123.             }
  1124.             final int inf = (sup == 0) ? sup++ : (sup - 1);

  1125.             final double lInf  = rates.get(inf).getFirst();
  1126.             final double rInf  = rates.get(inf).getSecond();
  1127.             final double lSup  = rates.get(sup).getFirst();
  1128.             final double rSup  = rates.get(sup).getSecond();
  1129.             final double alpha = (lineNumber - lInf) / (lSup - lInf);
  1130.             return alpha * rSup + (1 - alpha) * rInf;

  1131.         }

  1132.         /** {@inheritDoc} */
  1133.         @Override
  1134.         public Stream<ParameterDriver> getParametersDrivers() {
  1135.             return Stream.<ParameterDriver>empty();
  1136.         }

  1137.     }

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

  1140.         /** Min line. */
  1141.         private final int minLine;

  1142.         /** Max line. */
  1143.         private final int maxLine;

  1144.         /** Maximum number of evaluations. */
  1145.         private final int maxEval;

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

  1148.         /** Mean plane normal. */
  1149.         private final Vector3D normal;

  1150.         /** Cached results. */
  1151.         private final CrossingResult[] cachedResults;

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

  1170.     }

  1171.     /** Local interface for dumped calls. */
  1172.     private abstract static class DumpedCall {

  1173.         /** Expected result. */
  1174.         private Object expected;

  1175.         /** Execute a call.
  1176.          * @param rugged Rugged instance on which called should be performed
  1177.          * @return result of the call
  1178.          */
  1179.         public abstract Object execute(Rugged rugged);

  1180.     }

  1181. }