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  
19  import java.io.PrintWriter;
20  import java.util.ArrayList;
21  import java.util.Calendar;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Locale;
25  import java.util.Map;
26  import java.util.TimeZone;
27  
28  import org.hipparchus.geometry.euclidean.threed.Rotation;
29  import org.hipparchus.geometry.euclidean.threed.Vector3D;
30  import org.hipparchus.util.FastMath;
31  import org.hipparchus.util.OpenIntToDoubleHashMap;
32  import org.hipparchus.util.Pair;
33  import org.orekit.bodies.GeodeticPoint;
34  import org.orekit.errors.OrekitException;
35  import org.orekit.frames.FactoryManagedFrame;
36  import org.orekit.frames.Frame;
37  import org.orekit.frames.Transform;
38  import org.orekit.rugged.api.AlgorithmId;
39  import org.orekit.rugged.linesensor.LineSensor;
40  import org.orekit.rugged.linesensor.SensorMeanPlaneCrossing;
41  import org.orekit.rugged.linesensor.SensorPixel;
42  import org.orekit.rugged.raster.Tile;
43  import org.orekit.rugged.utils.ExtendedEllipsoid;
44  import org.orekit.rugged.utils.SpacecraftToObservedBody;
45  import org.orekit.time.AbsoluteDate;
46  import org.orekit.time.DateTimeComponents;
47  import org.orekit.time.TimeScalesFactory;
48  
49  /**
50   * Dump data class.
51   * @author Luc Maisonobe
52   */
53  class Dump {
54  
55      /** Dump file. */
56      private final PrintWriter writer;
57  
58      /** Tiles list. */
59      private final List<DumpedTileData> tiles;
60  
61      /** Already dumped sensors. */
62      private final List<DumpedSensorData> sensors;
63  
64      /** Flag for dumped algorithm. */
65      private boolean algorithmDumped;
66  
67      /** Flag for dumped ellipsoid. */
68      private boolean ellipsoidDumped;
69  
70      /** Flags for dumped observation transforms. */
71      private boolean[] tranformsDumped;
72  
73      /** Simple constructor.
74       * @param writer writer to the dump file
75       */
76      Dump(final PrintWriter writer) {
77          this.writer          = writer;
78          this.tiles           = new ArrayList<DumpedTileData>();
79          this.sensors         = new ArrayList<DumpedSensorData>();
80          this.algorithmDumped = false;
81          this.ellipsoidDumped = false;
82          this.tranformsDumped = null;
83          dumpHeader();
84      }
85  
86      /** Dump header.
87       */
88      private void dumpHeader() {
89          writer.format(Locale.US,
90                        "# Rugged library dump file, created on %1$tFT%1$tTZ%n",
91                        Calendar.getInstance(TimeZone.getTimeZone("Etc/UTC"), Locale.US));
92          writer.format(Locale.US,
93                        "# all units are SI units (m, m/s, rad ...)%n");
94      }
95  
96      /** Dump some context data.
97       * @param tile tile to which the cell belongs
98       * @param latitudeIndex latitude index of the cell
99       * @param longitudeIndex longitude index of the cell
100      * @param elevation elevation of the cell
101      */
102     public void dumpTileCell(final Tile tile,
103                              final int latitudeIndex, final int longitudeIndex,
104                              final double elevation) {
105         getTileData(tile).setElevation(latitudeIndex, longitudeIndex, elevation);
106     }
107 
108     /** Dump algorithm data.
109      * @param algorithmId algorithm ID
110      */
111     public void dumpAlgorithm(final AlgorithmId algorithmId) {
112         if (!algorithmDumped) {
113             writer.format(Locale.US,
114                           "algorithm: %s%n",
115                           algorithmId.name());
116             algorithmDumped = true;
117         }
118     }
119 
120     /** Dump algorithm data.
121      * @param algorithmId algorithm ID
122      * @param specific algorithm specific extra data
123      */
124     public void dumpAlgorithm(final AlgorithmId algorithmId, final double specific) {
125         if (!algorithmDumped) {
126             writer.format(Locale.US,
127                           "algorithm: %s elevation %22.15e%n",
128                           algorithmId.name(), specific);
129             algorithmDumped = true;
130         }
131     }
132 
133     /** Dump ellipsoid data.
134      * @param ellipsoid ellipsoid to dump
135      */
136     public void dumpEllipsoid(final ExtendedEllipsoid ellipsoid) {
137         if (!ellipsoidDumped) {
138             writer.format(Locale.US,
139                           "ellipsoid: ae %22.15e f %22.15e frame %s%n",
140                           ellipsoid.getA(), ellipsoid.getFlattening(),
141                           getKeyOrName(ellipsoid.getBodyFrame()));
142             ellipsoidDumped = true;
143         }
144     }
145 
146     /** Dump a direct location computation.
147      * @param date date of the location
148      * @param position pixel position in spacecraft frame
149      * @param los normalized line-of-sight in spacecraft frame
150      * @param lightTimeCorrection flag for light time correction
151      * @param aberrationOfLightCorrection flag for aberration of light correction
152      * @param refractionCorrection flag for refraction correction
153      * @exception RuggedException if date cannot be converted to UTC
154      */
155     public void dumpDirectLocation(final AbsoluteDate date, final Vector3D position, final Vector3D los,
156                                    final boolean lightTimeCorrection, final boolean aberrationOfLightCorrection,
157                                    final boolean refractionCorrection)
158         throws RuggedException {
159         writer.format(Locale.US,
160                       "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",
161                       convertDate(date),
162                       position.getX(), position.getY(), position.getZ(),
163                       los.getX(),      los.getY(),      los.getZ(),
164                       lightTimeCorrection, aberrationOfLightCorrection, refractionCorrection);
165     }
166 
167     /** Dump a direct location result.
168      * @param gp resulting geodetic point
169      * @exception RuggedException if date cannot be converted to UTC
170      */
171     public void dumpDirectLocationResult(final GeodeticPoint gp)
172         throws RuggedException {
173         if (gp != null) {
174             writer.format(Locale.US,
175                           "direct location result: latitude %22.15e longitude %22.15e elevation %22.15e%n",
176                           gp.getLatitude(), gp.getLongitude(), gp.getAltitude());
177         }
178     }
179 
180     /** Dump an inverse location computation.
181      * @param sensor sensor
182      * @param point point to localize
183      * @param minLine minimum line number
184      * @param maxLine maximum line number
185      * @param lightTimeCorrection flag for light time correction
186      * @param aberrationOfLightCorrection flag for aberration of light correction
187      */
188     public void dumpInverseLocation(final LineSensor sensor, final GeodeticPoint point,
189                                     final int minLine, final int maxLine,
190                                     final boolean lightTimeCorrection, final boolean aberrationOfLightCorrection) {
191         final DumpedSensorData ds = getSensorData(sensor);
192         writer.format(Locale.US,
193                       "inverse location: sensorName %s latitude %22.15e longitude %22.15e elevation %22.15e minLine %d maxLine %d lightTime %b aberration %b%n",
194                       ds.getDumpName(),
195                       point.getLatitude(), point.getLongitude(), point.getAltitude(),
196                       minLine, maxLine,
197                       lightTimeCorrection, aberrationOfLightCorrection);
198     }
199 
200     /** Dump an inverse location result.
201      * @param pixel resulting sensor pixel
202      */
203     public void dumpInverseLocationResult(final SensorPixel pixel) {
204         if (pixel != null) {
205             writer.format(Locale.US,
206                           "inverse location result: lineNumber %22.15e pixelNumber %22.15e%n",
207                           pixel.getLineNumber(), pixel.getPixelNumber());
208         }
209     }
210 
211     /** Dump an observation transform transform.
212      * @param scToBody provider for observation
213      * @param index index of the transform
214      * @param bodyToInertial transform from body frame to inertial frame
215      * @param scToInertial transfrom from spacecraft frame to inertial frame
216      * @exception RuggedException if reference date cannot be converted to UTC
217      */
218     public void dumpTransform(final SpacecraftToObservedBody scToBody, final int index,
219                               final Transform bodyToInertial, final Transform scToInertial)
220         throws RuggedException {
221         if (tranformsDumped == null) {
222             final AbsoluteDate minDate   = scToBody.getMinDate();
223             final AbsoluteDate maxDate   = scToBody.getMaxDate();
224             final double       tStep     = scToBody.getTStep();
225             final double       tolerance = scToBody.getOvershootTolerance();
226             final int          n         = (int) FastMath.ceil(maxDate.durationFrom(minDate) / tStep);
227             writer.format(Locale.US,
228                           "span: minDate %s maxDate %s tStep %22.15e tolerance %22.15e inertialFrame %s%n",
229                           convertDate(minDate), convertDate(maxDate), tStep, tolerance,
230                           getKeyOrName(scToBody.getInertialFrame()));
231             tranformsDumped = new boolean[n];
232         }
233         if (!tranformsDumped[index]) {
234             writer.format(Locale.US,
235                           "transform: index %d body %s spacecraft %s %s%n",
236                           index,
237                           convertRotation(bodyToInertial.getRotation(), bodyToInertial.getRotationRate(), bodyToInertial.getRotationAcceleration()),
238                           convertTranslation(scToInertial.getTranslation(), scToInertial.getVelocity(), scToInertial.getAcceleration()),
239                           convertRotation(scToInertial.getRotation(), scToInertial.getRotationRate(), scToInertial.getRotationAcceleration()));
240             tranformsDumped[index] = true;
241         }
242     }
243 
244     /** Dump a sensor mean plane.
245      * @param meanPlane mean plane associated with sensor
246      * @exception RuggedException if some frames cannot be computed at mid date
247      */
248     public void dumpSensorMeanPlane(final SensorMeanPlaneCrossing meanPlane)
249         throws RuggedException {
250         getSensorData(meanPlane.getSensor()).setMeanPlane(meanPlane);
251     }
252 
253     /** Dump a sensor LOS.
254      * @param sensor sensor
255      * @param date date
256      * @param i pixel index
257      * @param los pixel normalized line-of-sight
258      * @exception RuggedException if date cannot be converted to UTC
259      */
260     public void dumpSensorLOS(final LineSensor sensor, final AbsoluteDate date, final int i, final Vector3D los)
261         throws RuggedException {
262         getSensorData(sensor).setLOS(date, i, los);
263     }
264 
265     /** Dump a sensor datation.
266      * @param sensor sensor
267      * @param lineNumber line number
268      * @param date date
269      * @exception RuggedException if date cannot be converted to UTC
270      */
271     public void dumpSensorDatation(final LineSensor sensor, final double lineNumber, final AbsoluteDate date)
272         throws RuggedException {
273         getSensorData(sensor).setDatation(lineNumber, date);
274     }
275 
276     /** Dump a sensor rate.
277      * @param sensor sensor
278      * @param lineNumber line number
279      * @param rate lines rate
280      */
281     public void dumpSensorRate(final LineSensor sensor, final double lineNumber, final double rate) {
282         getSensorData(sensor).setRate(lineNumber, rate);
283     }
284 
285     /** Get a frame key or name.
286      * @param frame frame to convert
287      * @return frame key or name
288      */
289     private String getKeyOrName(final Frame frame) {
290         if (frame instanceof FactoryManagedFrame) {
291             // if possible, use the predefined frames key, as it is easier to parse
292             return ((FactoryManagedFrame) frame).getFactoryKey().toString();
293         } else {
294             // as a fallback, use the full name of the frame
295             return frame.getName();
296         }
297     }
298 
299     /** Get tile data.
300      * @param tile tile to which the cell belongs
301      * @return index of the tile
302      */
303     private DumpedTileData getTileData(final Tile tile) {
304 
305         for (final DumpedTileData dumpedTileData : tiles) {
306             if (tile == dumpedTileData.getTile()) {
307                 // the tile is already known
308                 return dumpedTileData;
309             }
310         }
311 
312         // it is the first time we encounter this tile, we need to dump its data
313         final DumpedTileData dumpedTileData = new DumpedTileData("t" + tiles.size(), tile);
314         tiles.add(dumpedTileData);
315         dumpedTileData.setElevation(tile.getMinElevationLatitudeIndex(),
316                                     tile.getMinElevationLongitudeIndex(),
317                                     tile.getMinElevation());
318         dumpedTileData.setElevation(tile.getMaxElevationLatitudeIndex(),
319                                     tile.getMaxElevationLongitudeIndex(),
320                                     tile.getMaxElevation());
321         return dumpedTileData;
322 
323     }
324 
325     /** Get sensor data.
326      * @param sensor sensor
327      * @return dumped data
328      */
329     private DumpedSensorData getSensorData(final LineSensor sensor) {
330 
331         for (final DumpedSensorData dumpedSensorData : sensors) {
332             if (sensor == dumpedSensorData.getSensor()) {
333                 // the sensor is already known
334                 return dumpedSensorData;
335             }
336         }
337 
338         // it is the first time we encounter this sensor, we need to dump its data
339         final DumpedSensorData dumpedSensorData = new DumpedSensorData("s" + sensors.size(), sensor);
340         sensors.add(dumpedSensorData);
341         return dumpedSensorData;
342 
343     }
344 
345     /** Convert a date to string with high accuracy.
346      * @param date computation date
347      * @return converted date
348      * @exception RuggedException if date cannot be converted to UTC
349      */
350     private String convertDate(final AbsoluteDate date)
351         throws RuggedException {
352         try {
353             final DateTimeComponents dt = date.getComponents(TimeScalesFactory.getUTC());
354             return String.format(Locale.US, "%04d-%02d-%02dT%02d:%02d:%017.14fZ",
355                                  dt.getDate().getYear(), dt.getDate().getMonth(), dt.getDate().getDay(),
356                                  dt.getTime().getHour(), dt.getTime().getMinute(), dt.getTime().getSecond());
357         } catch (OrekitException oe) {
358             throw new RuggedException(oe, oe.getSpecifier(), oe.getParts());
359         }
360     }
361 
362     /** Convert a translation to string.
363      * @param translation translation
364      * @param velocity linear velocity
365      * @param acceleration linear acceleration
366      * @return converted rotation
367      */
368     private String convertTranslation(final Vector3D translation, final Vector3D velocity, final Vector3D acceleration) {
369         return String.format(Locale.US,
370                              "p %22.15e %22.15e %22.15e v %22.15e %22.15e %22.15e a %22.15e %22.15e %22.15e",
371                              translation.getX(),  translation.getY(),  translation.getZ(),
372                              velocity.getX(),     velocity.getY(),     velocity.getZ(),
373                              acceleration.getX(), acceleration.getY(), acceleration.getZ());
374     }
375 
376     /** Convert a rotation to string.
377      * @param rotation rotation
378      * @param rate rate of the rotation
379      * @param acceleration angular acceleration
380      * @return converted rotation
381      */
382     private String convertRotation(final Rotation rotation, final Vector3D rate, final Vector3D acceleration) {
383         return String.format(Locale.US,
384                              "r %22.15e %22.15e %22.15e %22.15e Ω %22.15e %22.15e %22.15e ΩDot %22.15e %22.15e %22.15e",
385                              rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
386                              rate.getX(), rate.getY(), rate.getZ(),
387                              acceleration.getX(), acceleration.getY(), acceleration.getZ());
388     }
389 
390     /** Deactivate dump.
391      */
392     public void deactivate() {
393         writer.close();
394     }
395 
396     /** Local class for handling already dumped tile data. */
397     private class DumpedTileData {
398 
399         /** Name of the tile. */
400         private final String name;
401 
402         /** Tile associated with this dump. */
403         private final Tile tile;
404 
405         /** Dumped elevations. */
406         private final OpenIntToDoubleHashMap elevations;
407 
408         /** Simple constructor.
409          * @param name of the tile
410          * @param tile tile associated with this dump
411          */
412         DumpedTileData(final String name, final Tile tile) {
413             this.name       = name;
414             this.tile       = tile;
415             this.elevations = new OpenIntToDoubleHashMap();
416             writer.format(Locale.US,
417                           "DEM tile: %s latMin %22.15e latStep %22.15e latRows %d lonMin %22.15e lonStep %22.15e lonCols %d%n",
418                           name,
419                           tile.getMinimumLatitude(), tile.getLatitudeStep(), tile.getLatitudeRows(),
420                           tile.getMinimumLongitude(), tile.getLongitudeStep(), tile.getLongitudeColumns());
421         }
422 
423         /** Get tile associated with this dump.
424          * @return tile associated with this dump
425          */
426         public Tile getTile() {
427             return tile;
428         }
429 
430         /** Set an elevation.
431          * @param latitudeIndex latitude index of the cell
432          * @param longitudeIndex longitude index of the cell
433          * @param elevation elevation of the cell
434          */
435         public void setElevation(final int latitudeIndex, final int longitudeIndex, final double elevation) {
436             final int key = latitudeIndex * tile.getLongitudeColumns() + longitudeIndex;
437             if (!elevations.containsKey(key)) {
438                 // new cell
439                 elevations.put(key, elevation);
440                 writer.format(Locale.US,
441                               "DEM cell: %s latIndex %d lonIndex %d elevation %22.15e%n",
442                               name, latitudeIndex, longitudeIndex, elevation);
443             }
444         }
445 
446     }
447 
448     /** Local class for handling already dumped sensor data. */
449     private class DumpedSensorData {
450 
451         /** Name of the dump. */
452         private final String dumpName;
453 
454         /** Dumped sensor sensor. */
455         private final LineSensor sensor;
456 
457         /** LOS map. */
458         private final Map<Integer, List<Pair<AbsoluteDate, Vector3D>>> losMap;
459 
460         /** Datation. */
461         private final List<Pair<Double, AbsoluteDate>> datation;
462 
463         /** Rate. */
464         private final List<Pair<Double, Double>> rates;
465 
466         /** Mean plane finder. */
467         private SensorMeanPlaneCrossing meanPlane;
468 
469         /** Simple constructor.
470          * @param dumpName name of the sensor dump (not the name of the sensor itself, for confidentiality reasons)
471          * @param sensor dumped sensor
472          */
473         DumpedSensorData(final String dumpName, final LineSensor sensor) {
474             this.dumpName = dumpName;
475             this.sensor   = sensor;
476             this.losMap   = new HashMap<Integer, List<Pair<AbsoluteDate, Vector3D>>>();
477             this.datation = new ArrayList<Pair<Double, AbsoluteDate>>();
478             this.rates    = new ArrayList<Pair<Double, Double>>();
479             writer.format(Locale.US,
480                           "sensor: sensorName %s nbPixels %d position %22.15e %22.15e %22.15e%n",
481                           dumpName, sensor.getNbPixels(),
482                           sensor.getPosition().getX(), sensor.getPosition().getY(), sensor.getPosition().getZ());
483         }
484 
485         /** Get the anonymized dump sensor name.
486          * @return dump sensorname
487          */
488         public String getDumpName() {
489             return dumpName;
490         }
491 
492         /** Get dumped sensor.
493          * @return dumped sensor
494          */
495         public LineSensor getSensor() {
496             return sensor;
497         }
498 
499         /** Set the mean plane finder.
500          * @param meanPlane mean plane finder
501          * @exception RuggedException if frames cannot be computed at mid date
502          */
503         public void setMeanPlane(final SensorMeanPlaneCrossing meanPlane) throws RuggedException {
504             try {
505                 if (this.meanPlane == null) {
506                     this.meanPlane = meanPlane;
507                     final long nbResults = meanPlane.getCachedResults().count();
508                     writer.format(Locale.US,
509                                   "sensor mean plane: sensorName %s minLine %d maxLine %d maxEval %d accuracy %22.15e normal %22.15e %22.15e %22.15e cachedResults %d",
510                                   dumpName,
511                                   meanPlane.getMinLine(), meanPlane.getMaxLine(),
512                                   meanPlane.getMaxEval(), meanPlane.getAccuracy(),
513                                   meanPlane.getMeanPlaneNormal().getX(), meanPlane.getMeanPlaneNormal().getY(), meanPlane.getMeanPlaneNormal().getZ(),
514                                   nbResults);
515                     meanPlane.getCachedResults().forEach(result -> {
516                         try {
517                             writer.format(Locale.US,
518                                           " 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",
519                                           result.getLine(), convertDate(result.getDate()),
520                                           result.getTarget().getX(), result.getTarget().getY(), result.getTarget().getZ(),
521                                           result.getTargetDirection().getX(),
522                                           result.getTargetDirection().getY(),
523                                           result.getTargetDirection().getZ(),
524                                           result.getTargetDirectionDerivative().getZ(),
525                                           result.getTargetDirectionDerivative().getY(),
526                                           result.getTargetDirectionDerivative().getZ());
527                         } catch (RuggedException re) {
528                             throw new RuggedExceptionWrapper(re);
529                         }
530                     });
531                     writer.format(Locale.US, "%n");
532 
533                     // ensure the transforms for mid date are dumped
534                     final AbsoluteDate midDate = meanPlane.getSensor().getDate(0.5 * (meanPlane.getMinLine() + meanPlane.getMaxLine()));
535                     meanPlane.getScToBody().getBodyToInertial(midDate);
536                     meanPlane.getScToBody().getScToInertial(midDate);
537 
538                 }
539             } catch (RuggedExceptionWrapper rew) {
540                 throw rew.getException();
541             }
542         }
543 
544         /** Set a los direction.
545          * @param date date
546          * @param pixelNumber number of the pixel
547          * @param los los direction
548          * @exception RuggedException if date cannot be converted to UTC
549          */
550         public void setLOS(final AbsoluteDate date, final int pixelNumber, final Vector3D los)
551             throws RuggedException {
552             List<Pair<AbsoluteDate, Vector3D>> list = losMap.get(pixelNumber);
553             if (list == null) {
554                 list = new ArrayList<Pair<AbsoluteDate, Vector3D>>();
555                 losMap.put(pixelNumber, list);
556             }
557             for (final Pair<AbsoluteDate, Vector3D> alreadyDumped : list) {
558                 if (FastMath.abs(date.durationFrom(alreadyDumped.getFirst())) < 1.0e-12 &&
559                     Vector3D.angle(los, alreadyDumped.getSecond()) < 1.0e-12) {
560                     return;
561                 }
562             }
563             list.add(new Pair<AbsoluteDate, Vector3D>(date, los));
564             writer.format(Locale.US,
565                           "sensor LOS: sensorName %s date %s pixelNumber %d los %22.15e %22.15e %22.15e%n",
566                           dumpName, convertDate(date), pixelNumber, los.getX(), los.getY(), los.getZ());
567         }
568 
569         /** Set a datation pair.
570          * @param lineNumber line number
571          * @param date date
572          * @exception RuggedException if date cannot be converted to UTC
573          */
574         public void setDatation(final double lineNumber, final AbsoluteDate date)
575             throws RuggedException {
576             for (final Pair<Double, AbsoluteDate> alreadyDumped : datation) {
577                 if (FastMath.abs(date.durationFrom(alreadyDumped.getSecond())) < 1.0e-12 &&
578                     FastMath.abs(lineNumber - alreadyDumped.getFirst()) < 1.0e-12) {
579                     return;
580                 }
581             }
582             datation.add(new Pair<Double, AbsoluteDate>(lineNumber, date));
583             writer.format(Locale.US,
584                           "sensor datation: sensorName %s lineNumber %22.15e date %s%n",
585                           dumpName, lineNumber, convertDate(date));
586         }
587 
588         /** Set a rate.
589          * @param lineNumber line number
590          * @param rate lines rate
591          */
592         public void setRate(final double lineNumber, final double rate) {
593             for (final Pair<Double, Double> alreadyDumped : rates) {
594                 if (FastMath.abs(rate - alreadyDumped.getSecond()) < 1.0e-12 &&
595                     FastMath.abs(lineNumber - alreadyDumped.getFirst()) < 1.0e-12) {
596                     return;
597                 }
598             }
599             rates.add(new Pair<Double, Double>(lineNumber, rate));
600             writer.format(Locale.US,
601                           "sensor rate: sensorName %s lineNumber %22.15e rate %22.15e%n",
602                           dumpName, lineNumber, rate);
603         }
604 
605     }
606 
607 }