Dump.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.PrintWriter;
  19. import java.util.ArrayList;
  20. import java.util.Calendar;
  21. import java.util.HashMap;
  22. import java.util.List;
  23. import java.util.Locale;
  24. import java.util.Map;
  25. import java.util.TimeZone;

  26. import org.hipparchus.geometry.euclidean.threed.Rotation;
  27. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  28. import org.hipparchus.util.FastMath;
  29. import org.hipparchus.util.OpenIntToDoubleHashMap;
  30. import org.hipparchus.util.Pair;
  31. import org.orekit.bodies.GeodeticPoint;
  32. import org.orekit.errors.OrekitException;
  33. import org.orekit.frames.FactoryManagedFrame;
  34. import org.orekit.frames.Frame;
  35. import org.orekit.frames.Transform;
  36. import org.orekit.rugged.api.AlgorithmId;
  37. import org.orekit.rugged.linesensor.LineSensor;
  38. import org.orekit.rugged.linesensor.SensorMeanPlaneCrossing;
  39. import org.orekit.rugged.linesensor.SensorPixel;
  40. import org.orekit.rugged.raster.Tile;
  41. import org.orekit.rugged.utils.ExtendedEllipsoid;
  42. import org.orekit.rugged.utils.SpacecraftToObservedBody;
  43. import org.orekit.time.AbsoluteDate;
  44. import org.orekit.time.DateTimeComponents;
  45. import org.orekit.time.TimeScalesFactory;

  46. /**
  47.  * Dump data class.
  48.  * @author Luc Maisonobe
  49.  */
  50. class Dump {

  51.     /** Dump file. */
  52.     private final PrintWriter writer;

  53.     /** Tiles list. */
  54.     private final List<DumpedTileData> tiles;

  55.     /** Already dumped sensors. */
  56.     private final List<DumpedSensorData> sensors;

  57.     /** Flag for dumped algorithm. */
  58.     private boolean algorithmDumped;

  59.     /** Flag for dumped ellipsoid. */
  60.     private boolean ellipsoidDumped;

  61.     /** Flags for dumped observation transforms. */
  62.     private boolean[] tranformsDumped;

  63.     /** Simple constructor.
  64.      * @param writer writer to the dump file
  65.      */
  66.     Dump(final PrintWriter writer) {
  67.         this.writer          = writer;
  68.         this.tiles           = new ArrayList<DumpedTileData>();
  69.         this.sensors         = new ArrayList<DumpedSensorData>();
  70.         this.algorithmDumped = false;
  71.         this.ellipsoidDumped = false;
  72.         this.tranformsDumped = null;
  73.         dumpHeader();
  74.     }

  75.     /** Dump header.
  76.      */
  77.     private void dumpHeader() {
  78.         writer.format(Locale.US,
  79.                       "# Rugged library dump file, created on %1$tFT%1$tTZ%n",
  80.                       Calendar.getInstance(TimeZone.getTimeZone("Etc/UTC"), Locale.US));
  81.         writer.format(Locale.US,
  82.                       "# all units are SI units (m, m/s, rad ...)%n");
  83.     }

  84.     /** Dump some context data.
  85.      * @param tile tile to which the cell belongs
  86.      * @param latitudeIndex latitude index of the cell
  87.      * @param longitudeIndex longitude index of the cell
  88.      * @param elevation elevation of the cell
  89.      */
  90.     public void dumpTileCell(final Tile tile,
  91.                              final int latitudeIndex, final int longitudeIndex,
  92.                              final double elevation) {
  93.         getTileData(tile).setElevation(latitudeIndex, longitudeIndex, elevation);
  94.     }

  95.     /** Dump algorithm data.
  96.      * @param algorithmId algorithm ID
  97.      */
  98.     public void dumpAlgorithm(final AlgorithmId algorithmId) {
  99.         if (!algorithmDumped) {
  100.             writer.format(Locale.US,
  101.                           "algorithm: %s%n",
  102.                           algorithmId.name());
  103.             algorithmDumped = true;
  104.         }
  105.     }

  106.     /** Dump algorithm data.
  107.      * @param algorithmId algorithm ID
  108.      * @param specific algorithm specific extra data
  109.      */
  110.     public void dumpAlgorithm(final AlgorithmId algorithmId, final double specific) {
  111.         if (!algorithmDumped) {
  112.             writer.format(Locale.US,
  113.                           "algorithm: %s elevation %22.15e%n",
  114.                           algorithmId.name(), specific);
  115.             algorithmDumped = true;
  116.         }
  117.     }

  118.     /** Dump ellipsoid data.
  119.      * @param ellipsoid ellipsoid to dump
  120.      */
  121.     public void dumpEllipsoid(final ExtendedEllipsoid ellipsoid) {
  122.         if (!ellipsoidDumped) {
  123.             writer.format(Locale.US,
  124.                           "ellipsoid: ae %22.15e f %22.15e frame %s%n",
  125.                           ellipsoid.getA(), ellipsoid.getFlattening(),
  126.                           getKeyOrName(ellipsoid.getBodyFrame()));
  127.             ellipsoidDumped = true;
  128.         }
  129.     }

  130.     /** Dump a direct location computation.
  131.      * @param date date of the location
  132.      * @param position pixel position in spacecraft frame
  133.      * @param los normalized line-of-sight in spacecraft frame
  134.      * @param lightTimeCorrection flag for light time correction
  135.      * @param aberrationOfLightCorrection flag for aberration of light correction
  136.      * @param refractionCorrection flag for refraction correction
  137.      * @exception RuggedException if date cannot be converted to UTC
  138.      */
  139.     public void dumpDirectLocation(final AbsoluteDate date, final Vector3D position, final Vector3D los,
  140.                                    final boolean lightTimeCorrection, final boolean aberrationOfLightCorrection,
  141.                                    final boolean refractionCorrection)
  142.         throws RuggedException {
  143.         writer.format(Locale.US,
  144.                       "direct location: date %s position %22.15e %22.15e %22.15e los %22.15e %22.15e %22.15e lightTime %b aberration %b refraction %b %n",
  145.                       convertDate(date),
  146.                       position.getX(), position.getY(), position.getZ(),
  147.                       los.getX(),      los.getY(),      los.getZ(),
  148.                       lightTimeCorrection, aberrationOfLightCorrection, refractionCorrection);
  149.     }

  150.     /** Dump a direct location result.
  151.      * @param gp resulting geodetic point
  152.      * @exception RuggedException if date cannot be converted to UTC
  153.      */
  154.     public void dumpDirectLocationResult(final GeodeticPoint gp)
  155.         throws RuggedException {
  156.         if (gp != null) {
  157.             writer.format(Locale.US,
  158.                           "direct location result: latitude %22.15e longitude %22.15e elevation %22.15e%n",
  159.                           gp.getLatitude(), gp.getLongitude(), gp.getAltitude());
  160.         }
  161.     }

  162.     /** Dump an inverse location computation.
  163.      * @param sensor sensor
  164.      * @param point point to localize
  165.      * @param minLine minimum line number
  166.      * @param maxLine maximum line number
  167.      * @param lightTimeCorrection flag for light time correction
  168.      * @param aberrationOfLightCorrection flag for aberration of light correction
  169.      */
  170.     public void dumpInverseLocation(final LineSensor sensor, final GeodeticPoint point,
  171.                                     final int minLine, final int maxLine,
  172.                                     final boolean lightTimeCorrection, final boolean aberrationOfLightCorrection) {
  173.         final DumpedSensorData ds = getSensorData(sensor);
  174.         writer.format(Locale.US,
  175.                       "inverse location: sensorName %s latitude %22.15e longitude %22.15e elevation %22.15e minLine %d maxLine %d lightTime %b aberration %b%n",
  176.                       ds.getDumpName(),
  177.                       point.getLatitude(), point.getLongitude(), point.getAltitude(),
  178.                       minLine, maxLine,
  179.                       lightTimeCorrection, aberrationOfLightCorrection);
  180.     }

  181.     /** Dump an inverse location result.
  182.      * @param pixel resulting sensor pixel
  183.      */
  184.     public void dumpInverseLocationResult(final SensorPixel pixel) {
  185.         if (pixel != null) {
  186.             writer.format(Locale.US,
  187.                           "inverse location result: lineNumber %22.15e pixelNumber %22.15e%n",
  188.                           pixel.getLineNumber(), pixel.getPixelNumber());
  189.         }
  190.     }

  191.     /** Dump an observation transform transform.
  192.      * @param scToBody provider for observation
  193.      * @param index index of the transform
  194.      * @param bodyToInertial transform from body frame to inertial frame
  195.      * @param scToInertial transfrom from spacecraft frame to inertial frame
  196.      * @exception RuggedException if reference date cannot be converted to UTC
  197.      */
  198.     public void dumpTransform(final SpacecraftToObservedBody scToBody, final int index,
  199.                               final Transform bodyToInertial, final Transform scToInertial)
  200.         throws RuggedException {
  201.         if (tranformsDumped == null) {
  202.             final AbsoluteDate minDate   = scToBody.getMinDate();
  203.             final AbsoluteDate maxDate   = scToBody.getMaxDate();
  204.             final double       tStep     = scToBody.getTStep();
  205.             final double       tolerance = scToBody.getOvershootTolerance();
  206.             final int          n         = (int) FastMath.ceil(maxDate.durationFrom(minDate) / tStep);
  207.             writer.format(Locale.US,
  208.                           "span: minDate %s maxDate %s tStep %22.15e tolerance %22.15e inertialFrame %s%n",
  209.                           convertDate(minDate), convertDate(maxDate), tStep, tolerance,
  210.                           getKeyOrName(scToBody.getInertialFrame()));
  211.             tranformsDumped = new boolean[n];
  212.         }
  213.         if (!tranformsDumped[index]) {
  214.             writer.format(Locale.US,
  215.                           "transform: index %d body %s spacecraft %s %s%n",
  216.                           index,
  217.                           convertRotation(bodyToInertial.getRotation(), bodyToInertial.getRotationRate(), bodyToInertial.getRotationAcceleration()),
  218.                           convertTranslation(scToInertial.getTranslation(), scToInertial.getVelocity(), scToInertial.getAcceleration()),
  219.                           convertRotation(scToInertial.getRotation(), scToInertial.getRotationRate(), scToInertial.getRotationAcceleration()));
  220.             tranformsDumped[index] = true;
  221.         }
  222.     }

  223.     /** Dump a sensor mean plane.
  224.      * @param meanPlane mean plane associated with sensor
  225.      * @exception RuggedException if some frames cannot be computed at mid date
  226.      */
  227.     public void dumpSensorMeanPlane(final SensorMeanPlaneCrossing meanPlane)
  228.         throws RuggedException {
  229.         getSensorData(meanPlane.getSensor()).setMeanPlane(meanPlane);
  230.     }

  231.     /** Dump a sensor LOS.
  232.      * @param sensor sensor
  233.      * @param date date
  234.      * @param i pixel index
  235.      * @param los pixel normalized line-of-sight
  236.      * @exception RuggedException if date cannot be converted to UTC
  237.      */
  238.     public void dumpSensorLOS(final LineSensor sensor, final AbsoluteDate date, final int i, final Vector3D los)
  239.         throws RuggedException {
  240.         getSensorData(sensor).setLOS(date, i, los);
  241.     }

  242.     /** Dump a sensor datation.
  243.      * @param sensor sensor
  244.      * @param lineNumber line number
  245.      * @param date date
  246.      * @exception RuggedException if date cannot be converted to UTC
  247.      */
  248.     public void dumpSensorDatation(final LineSensor sensor, final double lineNumber, final AbsoluteDate date)
  249.         throws RuggedException {
  250.         getSensorData(sensor).setDatation(lineNumber, date);
  251.     }

  252.     /** Dump a sensor rate.
  253.      * @param sensor sensor
  254.      * @param lineNumber line number
  255.      * @param rate lines rate
  256.      */
  257.     public void dumpSensorRate(final LineSensor sensor, final double lineNumber, final double rate) {
  258.         getSensorData(sensor).setRate(lineNumber, rate);
  259.     }

  260.     /** Get a frame key or name.
  261.      * @param frame frame to convert
  262.      * @return frame key or name
  263.      */
  264.     private String getKeyOrName(final Frame frame) {
  265.         if (frame instanceof FactoryManagedFrame) {
  266.             // if possible, use the predefined frames key, as it is easier to parse
  267.             return ((FactoryManagedFrame) frame).getFactoryKey().toString();
  268.         } else {
  269.             // as a fallback, use the full name of the frame
  270.             return frame.getName();
  271.         }
  272.     }

  273.     /** Get tile data.
  274.      * @param tile tile to which the cell belongs
  275.      * @return index of the tile
  276.      */
  277.     private DumpedTileData getTileData(final Tile tile) {

  278.         for (final DumpedTileData dumpedTileData : tiles) {
  279.             if (tile == dumpedTileData.getTile()) {
  280.                 // the tile is already known
  281.                 return dumpedTileData;
  282.             }
  283.         }

  284.         // it is the first time we encounter this tile, we need to dump its data
  285.         final DumpedTileData dumpedTileData = new DumpedTileData("t" + tiles.size(), tile);
  286.         tiles.add(dumpedTileData);
  287.         dumpedTileData.setElevation(tile.getMinElevationLatitudeIndex(),
  288.                                     tile.getMinElevationLongitudeIndex(),
  289.                                     tile.getMinElevation());
  290.         dumpedTileData.setElevation(tile.getMaxElevationLatitudeIndex(),
  291.                                     tile.getMaxElevationLongitudeIndex(),
  292.                                     tile.getMaxElevation());
  293.         return dumpedTileData;

  294.     }

  295.     /** Get sensor data.
  296.      * @param sensor sensor
  297.      * @return dumped data
  298.      */
  299.     private DumpedSensorData getSensorData(final LineSensor sensor) {

  300.         for (final DumpedSensorData dumpedSensorData : sensors) {
  301.             if (sensor == dumpedSensorData.getSensor()) {
  302.                 // the sensor is already known
  303.                 return dumpedSensorData;
  304.             }
  305.         }

  306.         // it is the first time we encounter this sensor, we need to dump its data
  307.         final DumpedSensorData dumpedSensorData = new DumpedSensorData("s" + sensors.size(), sensor);
  308.         sensors.add(dumpedSensorData);
  309.         return dumpedSensorData;

  310.     }

  311.     /** Convert a date to string with high accuracy.
  312.      * @param date computation date
  313.      * @return converted date
  314.      * @exception RuggedException if date cannot be converted to UTC
  315.      */
  316.     private String convertDate(final AbsoluteDate date)
  317.         throws RuggedException {
  318.         try {
  319.             final DateTimeComponents dt = date.getComponents(TimeScalesFactory.getUTC());
  320.             return String.format(Locale.US, "%04d-%02d-%02dT%02d:%02d:%017.14fZ",
  321.                                  dt.getDate().getYear(), dt.getDate().getMonth(), dt.getDate().getDay(),
  322.                                  dt.getTime().getHour(), dt.getTime().getMinute(), dt.getTime().getSecond());
  323.         } catch (OrekitException oe) {
  324.             throw new RuggedException(oe, oe.getSpecifier(), oe.getParts());
  325.         }
  326.     }

  327.     /** Convert a translation to string.
  328.      * @param translation translation
  329.      * @param velocity linear velocity
  330.      * @param acceleration linear acceleration
  331.      * @return converted rotation
  332.      */
  333.     private String convertTranslation(final Vector3D translation, final Vector3D velocity, final Vector3D acceleration) {
  334.         return String.format(Locale.US,
  335.                              "p %22.15e %22.15e %22.15e v %22.15e %22.15e %22.15e a %22.15e %22.15e %22.15e",
  336.                              translation.getX(),  translation.getY(),  translation.getZ(),
  337.                              velocity.getX(),     velocity.getY(),     velocity.getZ(),
  338.                              acceleration.getX(), acceleration.getY(), acceleration.getZ());
  339.     }

  340.     /** Convert a rotation to string.
  341.      * @param rotation rotation
  342.      * @param rate rate of the rotation
  343.      * @param acceleration angular acceleration
  344.      * @return converted rotation
  345.      */
  346.     private String convertRotation(final Rotation rotation, final Vector3D rate, final Vector3D acceleration) {
  347.         return String.format(Locale.US,
  348.                              "r %22.15e %22.15e %22.15e %22.15e Ω %22.15e %22.15e %22.15e ΩDot %22.15e %22.15e %22.15e",
  349.                              rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
  350.                              rate.getX(), rate.getY(), rate.getZ(),
  351.                              acceleration.getX(), acceleration.getY(), acceleration.getZ());
  352.     }

  353.     /** Deactivate dump.
  354.      */
  355.     public void deactivate() {
  356.         writer.close();
  357.     }

  358.     /** Local class for handling already dumped tile data. */
  359.     private class DumpedTileData {

  360.         /** Name of the tile. */
  361.         private final String name;

  362.         /** Tile associated with this dump. */
  363.         private final Tile tile;

  364.         /** Dumped elevations. */
  365.         private final OpenIntToDoubleHashMap elevations;

  366.         /** Simple constructor.
  367.          * @param name of the tile
  368.          * @param tile tile associated with this dump
  369.          */
  370.         DumpedTileData(final String name, final Tile tile) {
  371.             this.name       = name;
  372.             this.tile       = tile;
  373.             this.elevations = new OpenIntToDoubleHashMap();
  374.             writer.format(Locale.US,
  375.                           "DEM tile: %s latMin %22.15e latStep %22.15e latRows %d lonMin %22.15e lonStep %22.15e lonCols %d%n",
  376.                           name,
  377.                           tile.getMinimumLatitude(), tile.getLatitudeStep(), tile.getLatitudeRows(),
  378.                           tile.getMinimumLongitude(), tile.getLongitudeStep(), tile.getLongitudeColumns());
  379.         }

  380.         /** Get tile associated with this dump.
  381.          * @return tile associated with this dump
  382.          */
  383.         public Tile getTile() {
  384.             return tile;
  385.         }

  386.         /** Set an elevation.
  387.          * @param latitudeIndex latitude index of the cell
  388.          * @param longitudeIndex longitude index of the cell
  389.          * @param elevation elevation of the cell
  390.          */
  391.         public void setElevation(final int latitudeIndex, final int longitudeIndex, final double elevation) {
  392.             final int key = latitudeIndex * tile.getLongitudeColumns() + longitudeIndex;
  393.             if (!elevations.containsKey(key)) {
  394.                 // new cell
  395.                 elevations.put(key, elevation);
  396.                 writer.format(Locale.US,
  397.                               "DEM cell: %s latIndex %d lonIndex %d elevation %22.15e%n",
  398.                               name, latitudeIndex, longitudeIndex, elevation);
  399.             }
  400.         }

  401.     }

  402.     /** Local class for handling already dumped sensor data. */
  403.     private class DumpedSensorData {

  404.         /** Name of the dump. */
  405.         private final String dumpName;

  406.         /** Dumped sensor sensor. */
  407.         private final LineSensor sensor;

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

  410.         /** Datation. */
  411.         private final List<Pair<Double, AbsoluteDate>> datation;

  412.         /** Rate. */
  413.         private final List<Pair<Double, Double>> rates;

  414.         /** Mean plane finder. */
  415.         private SensorMeanPlaneCrossing meanPlane;

  416.         /** Simple constructor.
  417.          * @param dumpName name of the sensor dump (not the name of the sensor itself, for confidentiality reasons)
  418.          * @param sensor dumped sensor
  419.          */
  420.         DumpedSensorData(final String dumpName, final LineSensor sensor) {
  421.             this.dumpName = dumpName;
  422.             this.sensor   = sensor;
  423.             this.losMap   = new HashMap<Integer, List<Pair<AbsoluteDate, Vector3D>>>();
  424.             this.datation = new ArrayList<Pair<Double, AbsoluteDate>>();
  425.             this.rates    = new ArrayList<Pair<Double, Double>>();
  426.             writer.format(Locale.US,
  427.                           "sensor: sensorName %s nbPixels %d position %22.15e %22.15e %22.15e%n",
  428.                           dumpName, sensor.getNbPixels(),
  429.                           sensor.getPosition().getX(), sensor.getPosition().getY(), sensor.getPosition().getZ());
  430.         }

  431.         /** Get the anonymized dump sensor name.
  432.          * @return dump sensorname
  433.          */
  434.         public String getDumpName() {
  435.             return dumpName;
  436.         }

  437.         /** Get dumped sensor.
  438.          * @return dumped sensor
  439.          */
  440.         public LineSensor getSensor() {
  441.             return sensor;
  442.         }

  443.         /** Set the mean plane finder.
  444.          * @param meanPlane mean plane finder
  445.          * @exception RuggedException if frames cannot be computed at mid date
  446.          */
  447.         public void setMeanPlane(final SensorMeanPlaneCrossing meanPlane) throws RuggedException {
  448.             try {
  449.                 if (this.meanPlane == null) {
  450.                     this.meanPlane = meanPlane;
  451.                     final long nbResults = meanPlane.getCachedResults().count();
  452.                     writer.format(Locale.US,
  453.                                   "sensor mean plane: sensorName %s minLine %d maxLine %d maxEval %d accuracy %22.15e normal %22.15e %22.15e %22.15e cachedResults %d",
  454.                                   dumpName,
  455.                                   meanPlane.getMinLine(), meanPlane.getMaxLine(),
  456.                                   meanPlane.getMaxEval(), meanPlane.getAccuracy(),
  457.                                   meanPlane.getMeanPlaneNormal().getX(), meanPlane.getMeanPlaneNormal().getY(), meanPlane.getMeanPlaneNormal().getZ(),
  458.                                   nbResults);
  459.                     meanPlane.getCachedResults().forEach(result -> {
  460.                         try {
  461.                             writer.format(Locale.US,
  462.                                           " lineNumber %22.15e date %s target %22.15e %22.15e %22.15e targetDirection %22.15e %22.15e %22.15e %22.15e %22.15e %22.15e",
  463.                                           result.getLine(), convertDate(result.getDate()),
  464.                                           result.getTarget().getX(), result.getTarget().getY(), result.getTarget().getZ(),
  465.                                           result.getTargetDirection().getX(),
  466.                                           result.getTargetDirection().getY(),
  467.                                           result.getTargetDirection().getZ(),
  468.                                           result.getTargetDirectionDerivative().getZ(),
  469.                                           result.getTargetDirectionDerivative().getY(),
  470.                                           result.getTargetDirectionDerivative().getZ());
  471.                         } catch (RuggedException re) {
  472.                             throw new RuggedExceptionWrapper(re);
  473.                         }
  474.                     });
  475.                     writer.format(Locale.US, "%n");

  476.                     // ensure the transforms for mid date are dumped
  477.                     final AbsoluteDate midDate = meanPlane.getSensor().getDate(0.5 * (meanPlane.getMinLine() + meanPlane.getMaxLine()));
  478.                     meanPlane.getScToBody().getBodyToInertial(midDate);
  479.                     meanPlane.getScToBody().getScToInertial(midDate);

  480.                 }
  481.             } catch (RuggedExceptionWrapper rew) {
  482.                 throw rew.getException();
  483.             }
  484.         }

  485.         /** Set a los direction.
  486.          * @param date date
  487.          * @param pixelNumber number of the pixel
  488.          * @param los los direction
  489.          * @exception RuggedException if date cannot be converted to UTC
  490.          */
  491.         public void setLOS(final AbsoluteDate date, final int pixelNumber, final Vector3D los)
  492.             throws RuggedException {
  493.             List<Pair<AbsoluteDate, Vector3D>> list = losMap.get(pixelNumber);
  494.             if (list == null) {
  495.                 list = new ArrayList<Pair<AbsoluteDate, Vector3D>>();
  496.                 losMap.put(pixelNumber, list);
  497.             }
  498.             for (final Pair<AbsoluteDate, Vector3D> alreadyDumped : list) {
  499.                 if (FastMath.abs(date.durationFrom(alreadyDumped.getFirst())) < 1.0e-12 &&
  500.                     Vector3D.angle(los, alreadyDumped.getSecond()) < 1.0e-12) {
  501.                     return;
  502.                 }
  503.             }
  504.             list.add(new Pair<AbsoluteDate, Vector3D>(date, los));
  505.             writer.format(Locale.US,
  506.                           "sensor LOS: sensorName %s date %s pixelNumber %d los %22.15e %22.15e %22.15e%n",
  507.                           dumpName, convertDate(date), pixelNumber, los.getX(), los.getY(), los.getZ());
  508.         }

  509.         /** Set a datation pair.
  510.          * @param lineNumber line number
  511.          * @param date date
  512.          * @exception RuggedException if date cannot be converted to UTC
  513.          */
  514.         public void setDatation(final double lineNumber, final AbsoluteDate date)
  515.             throws RuggedException {
  516.             for (final Pair<Double, AbsoluteDate> alreadyDumped : datation) {
  517.                 if (FastMath.abs(date.durationFrom(alreadyDumped.getSecond())) < 1.0e-12 &&
  518.                     FastMath.abs(lineNumber - alreadyDumped.getFirst()) < 1.0e-12) {
  519.                     return;
  520.                 }
  521.             }
  522.             datation.add(new Pair<Double, AbsoluteDate>(lineNumber, date));
  523.             writer.format(Locale.US,
  524.                           "sensor datation: sensorName %s lineNumber %22.15e date %s%n",
  525.                           dumpName, lineNumber, convertDate(date));
  526.         }

  527.         /** Set a rate.
  528.          * @param lineNumber line number
  529.          * @param rate lines rate
  530.          */
  531.         public void setRate(final double lineNumber, final double rate) {
  532.             for (final Pair<Double, Double> alreadyDumped : rates) {
  533.                 if (FastMath.abs(rate - alreadyDumped.getSecond()) < 1.0e-12 &&
  534.                     FastMath.abs(lineNumber - alreadyDumped.getFirst()) < 1.0e-12) {
  535.                     return;
  536.                 }
  537.             }
  538.             rates.add(new Pair<Double, Double>(lineNumber, rate));
  539.             writer.format(Locale.US,
  540.                           "sensor rate: sensorName %s lineNumber %22.15e rate %22.15e%n",
  541.                           dumpName, lineNumber, rate);
  542.         }

  543.     }

  544. }