Dump.java

  1. /* Copyright 2013-2022 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.rugged.errors;

  18. import java.io.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.frames.FactoryManagedFrame;
  33. import org.orekit.frames.Frame;
  34. import org.orekit.frames.Transform;
  35. import org.orekit.rugged.api.AlgorithmId;
  36. import org.orekit.rugged.linesensor.LineSensor;
  37. import org.orekit.rugged.linesensor.SensorMeanPlaneCrossing;
  38. import org.orekit.rugged.linesensor.SensorPixel;
  39. import org.orekit.rugged.raster.Tile;
  40. import org.orekit.rugged.utils.ExtendedEllipsoid;
  41. import org.orekit.rugged.utils.SpacecraftToObservedBody;
  42. import org.orekit.time.AbsoluteDate;
  43. import org.orekit.time.DateTimeComponents;
  44. import org.orekit.time.TimeScalesFactory;

  45. /**
  46.  * Dump data class.
  47.  * @author Luc Maisonobe
  48.  * @author Guylaine Prat
  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<>();
  69.         this.sensors         = new ArrayList<>();
  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 sensorPosition sensor 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.      */
  138.     public void dumpDirectLocation(final AbsoluteDate date, final Vector3D sensorPosition, final Vector3D los,
  139.                                    final boolean lightTimeCorrection, final boolean aberrationOfLightCorrection,
  140.                                    final boolean refractionCorrection) {
  141.         writer.format(Locale.US,
  142.                       "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",
  143.                       convertDate(date),
  144.                       sensorPosition.getX(), sensorPosition.getY(), sensorPosition.getZ(),
  145.                       los.getX(),      los.getY(),      los.getZ(),
  146.                       lightTimeCorrection, aberrationOfLightCorrection, refractionCorrection);
  147.     }

  148.     /** Dump a direct location result.
  149.      * @param gp resulting geodetic point
  150.      */
  151.     public void dumpDirectLocationResult(final GeodeticPoint gp) {
  152.         if (gp != null) {
  153.             writer.format(Locale.US,
  154.                           "direct location result: latitude %22.15e longitude %22.15e elevation %22.15e%n",
  155.                           gp.getLatitude(), gp.getLongitude(), gp.getAltitude());
  156.         } else {
  157.             writer.format(Locale.US, "direct location result: NULL");
  158.         }
  159.     }

  160.     /** Dump an inverse location computation.
  161.      * @param sensor sensor
  162.      * @param point point to localize
  163.      * @param minLine minimum line number
  164.      * @param maxLine maximum line number
  165.      * @param lightTimeCorrection flag for light time correction
  166.      * @param aberrationOfLightCorrection flag for aberration of light correction
  167.      * @param refractionCorrection flag for refraction correction
  168.      */
  169.     public void dumpInverseLocation(final LineSensor sensor, final GeodeticPoint point,
  170.                                     final int minLine, final int maxLine,
  171.                                     final boolean lightTimeCorrection, final boolean aberrationOfLightCorrection,
  172.                                     final boolean refractionCorrection) {
  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 refraction %b %n",
  176.                       ds.getDumpName(),
  177.                       point.getLatitude(), point.getLongitude(), point.getAltitude(),
  178.                       minLine, maxLine,
  179.                       lightTimeCorrection, aberrationOfLightCorrection, refractionCorrection);
  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.         } else {
  190.             writer.format(Locale.US, "inverse location result: NULL");
  191.         }
  192.     }

  193.     /** Dump an observation transform transform.
  194.      * @param scToBody provider for observation
  195.      * @param index index of the transform
  196.      * @param bodyToInertial transform from body frame to inertial frame
  197.      * @param scToInertial transfrom from spacecraft frame to inertial frame
  198.      */
  199.     public void dumpTransform(final SpacecraftToObservedBody scToBody, final int index,
  200.                               final Transform bodyToInertial, final Transform scToInertial) {
  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.      */
  226.     public void dumpSensorMeanPlane(final SensorMeanPlaneCrossing meanPlane) {
  227.         getSensorData(meanPlane.getSensor()).setMeanPlane(meanPlane);
  228.     }

  229.     /** Dump a sensor LOS.
  230.      * @param sensor sensor
  231.      * @param date date
  232.      * @param i pixel index
  233.      * @param los pixel normalized line-of-sight
  234.      */
  235.     public void dumpSensorLOS(final LineSensor sensor, final AbsoluteDate date, final int i, final Vector3D los) {
  236.         getSensorData(sensor).setLOS(date, i, los);
  237.     }

  238.     /** Dump a sensor datation.
  239.      * @param sensor sensor
  240.      * @param lineNumber line number
  241.      * @param date date
  242.      */
  243.     public void dumpSensorDatation(final LineSensor sensor, final double lineNumber, final AbsoluteDate date) {
  244.         getSensorData(sensor).setDatation(lineNumber, date);
  245.     }

  246.     /** Dump a sensor rate.
  247.      * @param sensor sensor
  248.      * @param lineNumber line number
  249.      * @param rate lines rate
  250.      */
  251.     public void dumpSensorRate(final LineSensor sensor, final double lineNumber, final double rate) {
  252.         getSensorData(sensor).setRate(lineNumber, rate);
  253.     }

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

  267.     /** Get tile data.
  268.      * @param tile tile to which the cell belongs
  269.      * @return index of the tile
  270.      */
  271.     private DumpedTileData getTileData(final Tile tile) {

  272.         for (final DumpedTileData dumpedTileData : tiles) {
  273.             if (tile == dumpedTileData.getTile()) {
  274.                 // the tile is already known
  275.                 return dumpedTileData;
  276.             }
  277.         }

  278.         // it is the first time we encounter this tile, we need to dump its data
  279.         final DumpedTileData dumpedTileData = new DumpedTileData("t" + tiles.size(), tile);
  280.         tiles.add(dumpedTileData);
  281.         dumpedTileData.setElevation(tile.getMinElevationLatitudeIndex(),
  282.                                     tile.getMinElevationLongitudeIndex(),
  283.                                     tile.getMinElevation());
  284.         dumpedTileData.setElevation(tile.getMaxElevationLatitudeIndex(),
  285.                                     tile.getMaxElevationLongitudeIndex(),
  286.                                     tile.getMaxElevation());
  287.         return dumpedTileData;

  288.     }

  289.     /** Get sensor data.
  290.      * @param sensor sensor
  291.      * @return dumped data
  292.      */
  293.     private DumpedSensorData getSensorData(final LineSensor sensor) {

  294.         for (final DumpedSensorData dumpedSensorData : sensors) {
  295.             if (sensor == dumpedSensorData.getSensor()) {
  296.                 // the sensor is already known
  297.                 return dumpedSensorData;
  298.             }
  299.         }

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

  304.     }

  305.     /** Convert a date to string with high accuracy.
  306.      * @param date computation date
  307.      * @return converted date
  308.      */
  309.     private String convertDate(final AbsoluteDate date) {
  310.         final DateTimeComponents dt = date.getComponents(TimeScalesFactory.getUTC());
  311.         return String.format(Locale.US, "%04d-%02d-%02dT%02d:%02d:%017.14fZ",
  312.                 dt.getDate().getYear(), dt.getDate().getMonth(), dt.getDate().getDay(),
  313.                 dt.getTime().getHour(), dt.getTime().getMinute(), dt.getTime().getSecond());
  314.     }

  315.     /** Convert a translation to string.
  316.      * @param translation translation
  317.      * @param velocity linear velocity
  318.      * @param acceleration linear acceleration
  319.      * @return converted rotation
  320.      */
  321.     private String convertTranslation(final Vector3D translation, final Vector3D velocity, final Vector3D acceleration) {
  322.         return String.format(Locale.US,
  323.                              "p %22.15e %22.15e %22.15e v %22.15e %22.15e %22.15e a %22.15e %22.15e %22.15e",
  324.                              translation.getX(),  translation.getY(),  translation.getZ(),
  325.                              velocity.getX(),     velocity.getY(),     velocity.getZ(),
  326.                              acceleration.getX(), acceleration.getY(), acceleration.getZ());
  327.     }

  328.     /** Convert a rotation to string.
  329.      * @param rotation rotation
  330.      * @param rate rate of the rotation
  331.      * @param acceleration angular acceleration
  332.      * @return converted rotation
  333.      */
  334.     private String convertRotation(final Rotation rotation, final Vector3D rate, final Vector3D acceleration) {
  335.         return String.format(Locale.US,
  336.                              "r %22.15e %22.15e %22.15e %22.15e Ω %22.15e %22.15e %22.15e ΩDot %22.15e %22.15e %22.15e",
  337.                              rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
  338.                              rate.getX(), rate.getY(), rate.getZ(),
  339.                              acceleration.getX(), acceleration.getY(), acceleration.getZ());
  340.     }

  341.     /** Deactivate dump.
  342.      */
  343.     public void deactivate() {
  344.         writer.close();
  345.     }

  346.     /** Local class for handling already dumped tile data. */
  347.     private class DumpedTileData {

  348.         /** Name of the tile. */
  349.         private final String name;

  350.         /** Tile associated with this dump. */
  351.         private final Tile tile;

  352.         /** Dumped elevations. */
  353.         private final OpenIntToDoubleHashMap elevations;

  354.         /** Simple constructor.
  355.          * @param name of the tile
  356.          * @param tile tile associated with this dump
  357.          */
  358.         DumpedTileData(final String name, final Tile tile) {
  359.             this.name       = name;
  360.             this.tile       = tile;
  361.             this.elevations = new OpenIntToDoubleHashMap();
  362.             writer.format(Locale.US,
  363.                           "DEM tile: %s latMin %22.15e latStep %22.15e latRows %d lonMin %22.15e lonStep %22.15e lonCols %d%n",
  364.                           name,
  365.                           tile.getMinimumLatitude(), tile.getLatitudeStep(), tile.getLatitudeRows(),
  366.                           tile.getMinimumLongitude(), tile.getLongitudeStep(), tile.getLongitudeColumns());
  367.         }

  368.         /** Get tile associated with this dump.
  369.          * @return tile associated with this dump
  370.          */
  371.         public Tile getTile() {
  372.             return tile;
  373.         }

  374.         /** Set an elevation.
  375.          * @param latitudeIndex latitude index of the cell
  376.          * @param longitudeIndex longitude index of the cell
  377.          * @param elevation elevation of the cell
  378.          */
  379.         public void setElevation(final int latitudeIndex, final int longitudeIndex, final double elevation) {
  380.             final int key = latitudeIndex * tile.getLongitudeColumns() + longitudeIndex;
  381.             if (!elevations.containsKey(key)) {
  382.                 // new cell
  383.                 elevations.put(key, elevation);
  384.                 writer.format(Locale.US,
  385.                               "DEM cell: %s latIndex %d lonIndex %d elevation %22.15e%n",
  386.                               name, latitudeIndex, longitudeIndex, elevation);
  387.             }
  388.         }

  389.     }

  390.     /** Local class for handling already dumped sensor data. */
  391.     private class DumpedSensorData {

  392.         /** Name of the dump. */
  393.         private final String dumpName;

  394.         /** Dumped sensor sensor. */
  395.         private final LineSensor sensor;

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

  398.         /** Datation. */
  399.         private final List<Pair<Double, AbsoluteDate>> datation;

  400.         /** Rate. */
  401.         private final List<Pair<Double, Double>> rates;

  402.         /** Mean plane finder. */
  403.         private SensorMeanPlaneCrossing meanPlane;

  404.         /** Simple constructor.
  405.          * @param dumpName name of the sensor dump (not the name of the sensor itself, for confidentiality reasons)
  406.          * @param sensor dumped sensor
  407.          */
  408.         DumpedSensorData(final String dumpName, final LineSensor sensor) {
  409.             this.dumpName = dumpName;
  410.             this.sensor   = sensor;
  411.             this.losMap   = new HashMap<Integer, List<Pair<AbsoluteDate, Vector3D>>>();
  412.             this.datation = new ArrayList<Pair<Double, AbsoluteDate>>();
  413.             this.rates    = new ArrayList<Pair<Double, Double>>();
  414.             writer.format(Locale.US,
  415.                           "sensor: sensorName %s nbPixels %d position %22.15e %22.15e %22.15e%n",
  416.                           dumpName, sensor.getNbPixels(),
  417.                           sensor.getPosition().getX(), sensor.getPosition().getY(), sensor.getPosition().getZ());
  418.         }

  419.         /** Get the anonymized dump sensor name.
  420.          * @return dump sensorname
  421.          */
  422.         public String getDumpName() {
  423.             return dumpName;
  424.         }

  425.         /** Get dumped sensor.
  426.          * @return dumped sensor
  427.          */
  428.         public LineSensor getSensor() {
  429.             return sensor;
  430.         }

  431.         /** Set the mean plane finder.
  432.          * @param meanPlane mean plane finder
  433.          */
  434.         public void setMeanPlane(final SensorMeanPlaneCrossing meanPlane) {

  435.             if (this.meanPlane == null) {
  436.                 this.meanPlane = meanPlane;
  437.                 final long nbResults = meanPlane.getCachedResults().count();
  438.                 writer.format(Locale.US,
  439.                         "sensor mean plane: sensorName %s minLine %d maxLine %d maxEval %d accuracy %22.15e normal %22.15e %22.15e %22.15e cachedResults %d",
  440.                         dumpName,
  441.                         meanPlane.getMinLine(), meanPlane.getMaxLine(),
  442.                         meanPlane.getMaxEval(), meanPlane.getAccuracy(),
  443.                         meanPlane.getMeanPlaneNormal().getX(), meanPlane.getMeanPlaneNormal().getY(), meanPlane.getMeanPlaneNormal().getZ(),
  444.                         nbResults);
  445.                 meanPlane.getCachedResults().forEach(result -> {
  446.                     try {
  447.                         writer.format(Locale.US,
  448.                                 " 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",
  449.                                 result.getLine(), convertDate(result.getDate()),
  450.                                 result.getTarget().getX(), result.getTarget().getY(), result.getTarget().getZ(),
  451.                                 result.getTargetDirection().getX(),
  452.                                 result.getTargetDirection().getY(),
  453.                                 result.getTargetDirection().getZ(),
  454.                                 result.getTargetDirectionDerivative().getZ(),
  455.                                 result.getTargetDirectionDerivative().getY(),
  456.                                 result.getTargetDirectionDerivative().getZ());
  457.                     } catch (RuggedException re) {
  458.                         throw new RuggedInternalError(re);
  459.                     }
  460.                 });
  461.                 writer.format(Locale.US, "%n");

  462.                 // ensure the transforms for mid date are dumped
  463.                 final AbsoluteDate midDate = meanPlane.getSensor().getDate(0.5 * (meanPlane.getMinLine() + meanPlane.getMaxLine()));
  464.                 meanPlane.getScToBody().getBodyToInertial(midDate);
  465.                 meanPlane.getScToBody().getScToInertial(midDate);
  466.             }
  467.         }

  468.         /** Set a los direction.
  469.          * @param date date
  470.          * @param pixelNumber number of the pixel
  471.          * @param los los direction
  472.          */
  473.         public void setLOS(final AbsoluteDate date, final int pixelNumber, final Vector3D los) {
  474.             List<Pair<AbsoluteDate, Vector3D>> list = losMap.get(pixelNumber);
  475.             if (list == null) {
  476.                 list = new ArrayList<Pair<AbsoluteDate, Vector3D>>();
  477.                 losMap.put(pixelNumber, list);
  478.             }
  479.             for (final Pair<AbsoluteDate, Vector3D> alreadyDumped : list) {
  480.                 if (FastMath.abs(date.durationFrom(alreadyDumped.getFirst())) < 1.0e-12 &&
  481.                     Vector3D.angle(los, alreadyDumped.getSecond()) < 1.0e-12) {
  482.                     return;
  483.                 }
  484.             }
  485.             list.add(new Pair<AbsoluteDate, Vector3D>(date, los));
  486.             writer.format(Locale.US,
  487.                           "sensor LOS: sensorName %s date %s pixelNumber %d los %22.15e %22.15e %22.15e%n",
  488.                           dumpName, convertDate(date), pixelNumber, los.getX(), los.getY(), los.getZ());
  489.         }

  490.         /** Set a datation pair.
  491.          * @param lineNumber line number
  492.          * @param date date
  493.          */
  494.         public void setDatation(final double lineNumber, final AbsoluteDate date) {
  495.             for (final Pair<Double, AbsoluteDate> alreadyDumped : datation) {
  496.                 if (FastMath.abs(date.durationFrom(alreadyDumped.getSecond())) < 1.0e-12 &&
  497.                     FastMath.abs(lineNumber - alreadyDumped.getFirst()) < 1.0e-12) {
  498.                     return;
  499.                 }
  500.             }
  501.             datation.add(new Pair<Double, AbsoluteDate>(lineNumber, date));
  502.             writer.format(Locale.US,
  503.                           "sensor datation: sensorName %s lineNumber %22.15e date %s%n",
  504.                           dumpName, lineNumber, convertDate(date));
  505.         }

  506.         /** Set a rate.
  507.          * @param lineNumber line number
  508.          * @param rate lines rate
  509.          */
  510.         public void setRate(final double lineNumber, final double rate) {
  511.             for (final Pair<Double, Double> alreadyDumped : rates) {
  512.                 if (FastMath.abs(rate - alreadyDumped.getSecond()) < 1.0e-12 &&
  513.                     FastMath.abs(lineNumber - alreadyDumped.getFirst()) < 1.0e-12) {
  514.                     return;
  515.                 }
  516.             }
  517.             rates.add(new Pair<Double, Double>(lineNumber, rate));
  518.             writer.format(Locale.US,
  519.                           "sensor rate: sensorName %s lineNumber %22.15e rate %22.15e%n",
  520.                           dumpName, lineNumber, rate);
  521.         }

  522.     }

  523. }