DumpReplayer.java

  1. /* Copyright 2013-2017 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.errors.OrekitException;
  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.utils.DSGenerator;
  63. import org.orekit.rugged.utils.SpacecraftToObservedBody;
  64. import org.orekit.time.AbsoluteDate;
  65. import org.orekit.time.TimeScalesFactory;
  66. import org.orekit.utils.ParameterDriver;

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

  74.     /** Comment start marker. */
  75.     private static final String COMMENT_START = "#";

  76.     /** Keyword for latitude fields. */
  77.     private static final String LATITUDE = "latitude";

  78.     /** Keyword for longitude fields. */
  79.     private static final String LONGITUDE = "longitude";

  80.     /** Keyword for elevation fields. */
  81.     private static final String ELEVATION = "elevation";

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

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

  86.     /** Keyword for frame fields. */
  87.     private static final String FRAME = "frame";

  88.     /** Keyword for date fields. */
  89.     private static final String DATE = "date";

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  156.     /** Keyword for accuracy. */
  157.     private static final String ACCURACY = "accuracy";

  158.     /** Keyword for normal. */
  159.     private static final String NORMAL = "normal";

  160.     /** Keyword for rate. */
  161.     private static final String RATE = "rate";

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

  164.     /** Keyword for target. */
  165.     private static final String TARGET = "target";

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

  168.     /** Constant elevation for constant elevation algorithm. */
  169.     private double constantElevation;

  170.     /** Algorithm identifier. */
  171.     private AlgorithmId algorithmId;

  172.     /** Ellipsoid. */
  173.     private OneAxisEllipsoid ellipsoid;

  174.     /** Tiles list. */
  175.     private final List<ParsedTile> tiles;

  176.     /** Sensors list. */
  177.     private final List<ParsedSensor> sensors;

  178.     /** Interpolator min date. */
  179.     private AbsoluteDate minDate;

  180.     /** Interpolator max date. */
  181.     private AbsoluteDate maxDate;

  182.     /** Interpolator step. */
  183.     private double tStep;

  184.     /** Interpolator overshoot tolerance. */
  185.     private double tolerance;

  186.     /** Inertial frame. */
  187.     private Frame inertialFrame;

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

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

  192.     /** Flag for light time correction. */
  193.     private boolean lightTimeCorrection;

  194.     /** Flag for aberration of light correction. */
  195.     private boolean aberrationOfLightCorrection;

  196.     /** Dumped calls. */
  197.     private final List<DumpedCall> calls;

  198.     /** Simple constructor.
  199.      */
  200.     public DumpReplayer() {
  201.         tiles   = new ArrayList<ParsedTile>();
  202.         sensors = new ArrayList<ParsedSensor>();
  203.         calls   = new ArrayList<DumpedCall>();
  204.     }

  205.     /** Parse a dump file.
  206.      * @param file dump file to parse
  207.      * @exception RuggedException if file cannot be parsed
  208.      */
  209.     public void parse(final File file) throws RuggedException {
  210.         try {
  211.             final BufferedReader reader =
  212.                     new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
  213.             int l = 0;
  214.             for (String line = reader.readLine(); line != null; line = reader.readLine()) {
  215.                 LineParser.parse(++l, file, line, this);
  216.             }
  217.             reader.close();
  218.         } catch (IOException ioe) {
  219.             throw new RuggedException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE, ioe.getLocalizedMessage());
  220.         }
  221.     }

  222.     /** Create a Rugged instance from parsed data.
  223.      * @return rugged instance
  224.      * @exception RuggedException if some data are inconsistent or incomplete
  225.      */
  226.     public Rugged createRugged() throws RuggedException {
  227.         try {
  228.             final RuggedBuilder builder = new RuggedBuilder();

  229.             if (algorithmId == null) {
  230.                 algorithmId = AlgorithmId.IGNORE_DEM_USE_ELLIPSOID;
  231.             }
  232.             builder.setAlgorithm(algorithmId);
  233.             if (algorithmId == AlgorithmId.CONSTANT_ELEVATION_OVER_ELLIPSOID) {
  234.                 builder.setConstantElevation(constantElevation);
  235.             } else if (algorithmId != AlgorithmId.IGNORE_DEM_USE_ELLIPSOID) {
  236.                 builder.setDigitalElevationModel(new TileUpdater() {

  237.                     /** {@inheritDoc} */
  238.                     @Override
  239.                     public void updateTile(final double latitude, final double longitude, final UpdatableTile tile)
  240.                         throws RuggedException {
  241.                         for (final ParsedTile parsedTile : tiles) {
  242.                             if (parsedTile.isInterpolable(latitude, longitude)) {
  243.                                 parsedTile.updateTile(tile);
  244.                                 return;
  245.                             }
  246.                         }
  247.                         throw new RuggedException(RuggedMessages.NO_DEM_DATA,
  248.                                                   FastMath.toDegrees(latitude), FastMath.toDegrees(longitude));
  249.                     }

  250.                 }, 8);
  251.             }

  252.             builder.setLightTimeCorrection(lightTimeCorrection);
  253.             builder.setAberrationOfLightCorrection(aberrationOfLightCorrection);

  254.             builder.setEllipsoid(ellipsoid);

  255.             // build missing transforms by extrapolating the parsed ones
  256.             final int n = (int) FastMath.ceil(maxDate.durationFrom(minDate) / tStep);
  257.             final List<Transform> b2iList = new ArrayList<Transform>(n);
  258.             final List<Transform> s2iList = new ArrayList<Transform>(n);
  259.             for (int i = 0; i < n; ++i) {
  260.                 if (bodyToInertial.containsKey(i)) {
  261.                     // the i-th transform was dumped
  262.                     b2iList.add(bodyToInertial.get(i));
  263.                     s2iList.add(scToInertial.get(i));
  264.                 } else {
  265.                     // the i-th transformed was not dumped, we have to extrapolate it
  266.                     final Map.Entry<Integer, Transform> lower  = bodyToInertial.lowerEntry(i);
  267.                     final Map.Entry<Integer, Transform> higher = bodyToInertial.higherEntry(i);
  268.                     final int closest;
  269.                     if (lower == null) {
  270.                         closest = higher.getKey();
  271.                     } else if (higher == null) {
  272.                         closest = lower.getKey();
  273.                     } else {
  274.                         closest = (i - lower.getKey() <= higher.getKey() - i) ? lower.getKey() : higher.getKey();
  275.                     }
  276.                     b2iList.add(bodyToInertial.get(closest).shiftedBy((i - closest) * tStep));
  277.                     s2iList.add(scToInertial.get(closest).shiftedBy((i - closest) * tStep));
  278.                 }
  279.             }

  280.             // we use Rugged transforms reloading mechanism to ensure the spacecraft
  281.             // to body transforms will be the same as the ones dumped
  282.             final SpacecraftToObservedBody scToBody =
  283.                     new SpacecraftToObservedBody(inertialFrame, ellipsoid.getBodyFrame(),
  284.                                                  minDate, maxDate, tStep, tolerance,
  285.                                                  b2iList, s2iList);
  286.             final ByteArrayOutputStream bos = new ByteArrayOutputStream();
  287.             new ObjectOutputStream(bos).writeObject(scToBody);
  288.             final ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
  289.             builder.setTrajectoryAndTimeSpan(bis);

  290.             final List<SensorMeanPlaneCrossing> planeCrossings = new ArrayList<SensorMeanPlaneCrossing>();
  291.             for (final ParsedSensor parsedSensor : sensors) {
  292.                 final LineSensor sensor = new LineSensor(parsedSensor.name,
  293.                                                          parsedSensor,
  294.                                                          parsedSensor.position,
  295.                                                          parsedSensor);
  296.                 if (parsedSensor.meanPlane != null) {
  297.                     planeCrossings.add(new SensorMeanPlaneCrossing(sensor, scToBody,
  298.                                                                    parsedSensor.meanPlane.minLine,
  299.                                                                    parsedSensor.meanPlane.maxLine,
  300.                                                                    lightTimeCorrection, aberrationOfLightCorrection,
  301.                                                                    parsedSensor.meanPlane.maxEval,
  302.                                                                    parsedSensor.meanPlane.accuracy,
  303.                                                                    parsedSensor.meanPlane.normal,
  304.                                                                    Arrays.stream(parsedSensor.meanPlane.cachedResults)));
  305.                 }
  306.                 builder.addLineSensor(sensor);
  307.             }

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

  309.             final Method setPlaneCrossing = Rugged.class.getDeclaredMethod("setPlaneCrossing",
  310.                                                                            SensorMeanPlaneCrossing.class);
  311.             setPlaneCrossing.setAccessible(true);
  312.             for (final SensorMeanPlaneCrossing planeCrossing : planeCrossings) {
  313.                 setPlaneCrossing.invoke(rugged, planeCrossing);
  314.             }

  315.             return rugged;

  316.         } catch (IOException ioe) {
  317.             throw new RuggedException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE, ioe.getLocalizedMessage());
  318.         } catch (SecurityException e) {
  319.             // this should never happen
  320.             throw RuggedException.createInternalError(e);
  321.         } catch (NoSuchMethodException e) {
  322.             // this should never happen
  323.             throw RuggedException.createInternalError(e);
  324.         } catch (IllegalArgumentException e) {
  325.             // this should never happen
  326.             throw RuggedException.createInternalError(e);
  327.         } catch (IllegalAccessException e) {
  328.             // this should never happen
  329.             throw RuggedException.createInternalError(e);
  330.         } catch (InvocationTargetException e) {
  331.             // this should never happen
  332.             throw RuggedException.createInternalError(e);
  333.         }

  334.     }

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

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

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

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

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

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

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

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

  392.     }

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

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

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

  438.         },

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

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

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

  478.                     });
  479.                 } catch (OrekitException oe) {
  480.                     throw new RuggedException(oe, oe.getSpecifier(), oe.getParts());
  481.                 }
  482.             }

  483.         },

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

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

  500.         },

  501.         /** Parser for search span dump lines. */
  502.         SPAN() {

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

  528.         },

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

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

  613.         },

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

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

  638.         },

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

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

  672.                     /** {@inheritDoc} */
  673.                     @Override
  674.                     public Object execute(final Rugged rugged) throws RuggedException {
  675.                         return rugged.inverseLocation(sensorName, point, minLine, maxLine);
  676.                     }

  677.                 });
  678.             }

  679.         },

  680.         /** Parser for inverse location result dump lines. */
  681.         INVERSE_LOCATION_RESULT() {

  682.             /** {@inheritDoc} */
  683.             @Override
  684.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global)
  685.                 throws RuggedException {
  686.                 if (fields.length < 4 || !fields[0].equals(LINE_NUMBER) || !fields[2].equals(PIXEL_NUMBER)) {
  687.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  688.                 }
  689.                 final SensorPixel sp = new SensorPixel(Double.parseDouble(fields[1]),
  690.                                                        Double.parseDouble(fields[3]));
  691.                 final DumpedCall last = global.calls.get(global.calls.size() - 1);
  692.                 last.expected = sp;
  693.             }

  694.         },

  695.         /** Parser for sensor dump lines. */
  696.         SENSOR() {

  697.             /** {@inheritDoc} */
  698.             @Override
  699.             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global)
  700.                 throws RuggedException {
  701.                 if (fields.length < 8 || !fields[0].equals(SENSOR_NAME) ||
  702.                     !fields[2].equals(NB_PIXELS) || !fields[4].equals(POSITION)) {
  703.                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  704.                 }
  705.                 final ParsedSensor sensor = global.getSensor(fields[1]);
  706.                 sensor.setNbPixels(Integer.parseInt(fields[3]));
  707.                 sensor.setPosition(new Vector3D(Double.parseDouble(fields[5]),
  708.                                                 Double.parseDouble(fields[6]),
  709.                                                 Double.parseDouble(fields[7])));
  710.             }

  711.         },

  712.         /** Parser for sensor mean plane dump lines. */
  713.         SENSOR_MEAN_PLANE() {

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

  757.                 } catch (OrekitException oe) {
  758.                     throw new RuggedException(oe, oe.getSpecifier(), oe.getParts());
  759.                 }
  760.             }

  761.         },

  762.         /** Parser for sensor LOS dump lines. */
  763.         SENSOR_LOS() {

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

  781.                 } catch (OrekitException oe) {
  782.                     throw new RuggedException(oe, oe.getSpecifier(), oe.getParts());
  783.                 }
  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.                 throws RuggedException {
  792.                 try {
  793.                     if (fields.length < 6 || !fields[0].equals(SENSOR_NAME) ||
  794.                         !fields[2].equals(LINE_NUMBER) || !fields[4].equals(DATE)) {
  795.                         throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
  796.                     }
  797.                     final String       sensorName  = fields[1];
  798.                     final double       lineNumber  = Double.parseDouble(fields[3]);
  799.                     final AbsoluteDate date        = new AbsoluteDate(fields[5], TimeScalesFactory.getUTC());
  800.                     global.getSensor(sensorName).setDatation(lineNumber, date);

  801.                 } catch (OrekitException oe) {
  802.                     throw new RuggedException(oe, oe.getSpecifier(), oe.getParts());
  803.                 }

  804.             }

  805.         },

  806.         /** Parser for sensor rate dump lines. */
  807.         SENSOR_RATE() {

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

  820.             }

  821.         };

  822.         /** Parse a line.
  823.          * @param l line number
  824.          * @param file dump file
  825.          * @param line line to parse
  826.          * @param global global parser to store parsed data
  827.          * @exception RuggedException if line is not supported
  828.          */
  829.         public static void parse(final int l, final File file, final String line, final DumpReplayer global)
  830.             throws RuggedException {

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

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

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

  853.         }

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

  864.     }

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

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

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

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

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

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

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

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

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

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

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

  915.         /** Update the tile according to the Digital Elevation Model.
  916.          * @param tile to update
  917.          * @exception RuggedException if tile cannot be updated
  918.          */
  919.         public void updateTile(final UpdatableTile tile)
  920.             throws RuggedException {

  921.             tile.setGeometry(minLatitude, minLongitude,
  922.                              latitudeStep, longitudeStep,
  923.                              latitudeRows, longitudeColumns);

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

  933.         }

  934.     }

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

  937.         /** Name of the sensor. */
  938.         private final String name;

  939.         /** Number of pixels. */
  940.         private int nbPixels;

  941.         /** Position. */
  942.         private Vector3D position;

  943.         /** Mean plane crossing finder. */
  944.         private ParsedMeanPlane meanPlane;

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

  947.         /** Datation. */
  948.         private final List<Pair<Double, AbsoluteDate>> datation;

  949.         /** Rate. */
  950.         private final List<Pair<Double, Double>> rates;

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

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

  966.         /** Set the position.
  967.          * @param position position
  968.          */
  969.         public void setPosition(final Vector3D position) {
  970.             this.position = position;
  971.         }

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

  978.         /** {@inheritDoc} */
  979.         @Override
  980.         public int getNbPixels() {
  981.             return nbPixels;
  982.         }

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

  1004.         /** {@inheritDoc} */
  1005.         @Override
  1006.         public Vector3D getLOS(final int index, final AbsoluteDate date) {
  1007.             final List<Pair<AbsoluteDate, Vector3D>> list = losMap.get(index);
  1008.             if (list == null) {
  1009.                 throw RuggedException.createInternalError(null);
  1010.             }

  1011.             if (list.size() < 2) {
  1012.                 return list.get(0).getSecond();
  1013.             }

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

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

  1029.         }

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

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

  1054.         /** {@inheritDoc} */
  1055.         @Override
  1056.         public AbsoluteDate getDate(final double lineNumber) {

  1057.             if (datation.size() < 2) {
  1058.                 return datation.get(0).getSecond();
  1059.             }

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

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

  1075.         }

  1076.         /** {@inheritDoc} */
  1077.         @Override
  1078.         public double getLine(final AbsoluteDate date) {

  1079.             if (datation.size() < 2) {
  1080.                 return datation.get(0).getFirst();
  1081.             }

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

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

  1097.         }

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

  1113.         /** {@inheritDoc} */
  1114.         @Override
  1115.         public double getRate(final double lineNumber) {

  1116.             if (rates.size() < 2) {
  1117.                 return rates.get(0).getSecond();
  1118.             }

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

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

  1134.         }

  1135.         /** {@inheritDoc} */
  1136.         @Override
  1137.         public Stream<ParameterDriver> getParametersDrivers() {
  1138.             return Stream.<ParameterDriver>empty();
  1139.         }

  1140.     }

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

  1143.         /** Min line. */
  1144.         private final int minLine;

  1145.         /** Max line. */
  1146.         private final int maxLine;

  1147.         /** Maximum number of evaluations. */
  1148.         private final int maxEval;

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

  1151.         /** Mean plane normal. */
  1152.         private final Vector3D normal;

  1153.         /** Cached results. */
  1154.         private final CrossingResult[] cachedResults;

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

  1173.     }

  1174.     /** Local interface for dumped calls. */
  1175.     private abstract static class DumpedCall {

  1176.         /** Expected result. */
  1177.         private Object expected;

  1178.         /** Execute a call.
  1179.          * @param rugged Rugged instance on which called should be performed
  1180.          * @return result of the call
  1181.          * @exception RuggedException if the call fails
  1182.          */
  1183.         public abstract Object execute(Rugged rugged) throws RuggedException;

  1184.     }

  1185. }