1   /* Copyright 2013-2019 CS Systèmes d'Information
2    * Licensed to CS Systèmes d'Information (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.rugged.errors;
18  
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.frames.FactoryManagedFrame;
35  import org.orekit.frames.Frame;
36  import org.orekit.frames.Transform;
37  import org.orekit.rugged.api.AlgorithmId;
38  import org.orekit.rugged.linesensor.LineSensor;
39  import org.orekit.rugged.linesensor.SensorMeanPlaneCrossing;
40  import org.orekit.rugged.linesensor.SensorPixel;
41  import org.orekit.rugged.raster.Tile;
42  import org.orekit.rugged.utils.ExtendedEllipsoid;
43  import org.orekit.rugged.utils.SpacecraftToObservedBody;
44  import org.orekit.time.AbsoluteDate;
45  import org.orekit.time.DateTimeComponents;
46  import org.orekit.time.TimeScalesFactory;
47  
48  /**
49   * Dump data class.
50   * @author Luc Maisonobe
51   * @author Guylaine Prat
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 sensorPosition sensor 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      */
154     public void dumpDirectLocation(final AbsoluteDate date, final Vector3D sensorPosition, final Vector3D los,
155                                    final boolean lightTimeCorrection, final boolean aberrationOfLightCorrection,
156                                    final boolean refractionCorrection) {
157         writer.format(Locale.US,
158                       "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",
159                       convertDate(date),
160                       sensorPosition.getX(), sensorPosition.getY(), sensorPosition.getZ(),
161                       los.getX(),      los.getY(),      los.getZ(),
162                       lightTimeCorrection, aberrationOfLightCorrection, refractionCorrection);
163     }
164 
165     /** Dump a direct location result.
166      * @param gp resulting geodetic point
167      */
168     public void dumpDirectLocationResult(final GeodeticPoint gp) {
169         if (gp != null) {
170             writer.format(Locale.US,
171                           "direct location result: latitude %22.15e longitude %22.15e elevation %22.15e%n",
172                           gp.getLatitude(), gp.getLongitude(), gp.getAltitude());
173         } else {
174             writer.format(Locale.US, "direct location result: NULL");
175         }
176     }
177 
178     /** Dump an inverse location computation.
179      * @param sensor sensor
180      * @param point point to localize
181      * @param minLine minimum line number
182      * @param maxLine maximum line number
183      * @param lightTimeCorrection flag for light time correction
184      * @param aberrationOfLightCorrection flag for aberration of light correction
185      * @param refractionCorrection flag for refraction correction
186      */
187     public void dumpInverseLocation(final LineSensor sensor, final GeodeticPoint point,
188                                     final int minLine, final int maxLine,
189                                     final boolean lightTimeCorrection, final boolean aberrationOfLightCorrection,
190                                     final boolean refractionCorrection) {
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 refraction %b %n",
194                       ds.getDumpName(),
195                       point.getLatitude(), point.getLongitude(), point.getAltitude(),
196                       minLine, maxLine,
197                       lightTimeCorrection, aberrationOfLightCorrection, refractionCorrection);
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         } else {
209             writer.format(Locale.US, "inverse location result: NULL");
210         }
211     }
212 
213     /** Dump an observation transform transform.
214      * @param scToBody provider for observation
215      * @param index index of the transform
216      * @param bodyToInertial transform from body frame to inertial frame
217      * @param scToInertial transfrom from spacecraft frame to inertial frame
218      */
219     public void dumpTransform(final SpacecraftToObservedBody scToBody, final int index,
220                               final Transform bodyToInertial, final Transform scToInertial) {
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      */
247     public void dumpSensorMeanPlane(final SensorMeanPlaneCrossing meanPlane) {
248         getSensorData(meanPlane.getSensor()).setMeanPlane(meanPlane);
249     }
250 
251     /** Dump a sensor LOS.
252      * @param sensor sensor
253      * @param date date
254      * @param i pixel index
255      * @param los pixel normalized line-of-sight
256      */
257     public void dumpSensorLOS(final LineSensor sensor, final AbsoluteDate date, final int i, final Vector3D los) {
258         getSensorData(sensor).setLOS(date, i, los);
259     }
260 
261     /** Dump a sensor datation.
262      * @param sensor sensor
263      * @param lineNumber line number
264      * @param date date
265      */
266     public void dumpSensorDatation(final LineSensor sensor, final double lineNumber, final AbsoluteDate date) {
267         getSensorData(sensor).setDatation(lineNumber, date);
268     }
269 
270     /** Dump a sensor rate.
271      * @param sensor sensor
272      * @param lineNumber line number
273      * @param rate lines rate
274      */
275     public void dumpSensorRate(final LineSensor sensor, final double lineNumber, final double rate) {
276         getSensorData(sensor).setRate(lineNumber, rate);
277     }
278 
279     /** Get a frame key or name.
280      * @param frame frame to convert
281      * @return frame key or name
282      */
283     private String getKeyOrName(final Frame frame) {
284         if (frame instanceof FactoryManagedFrame) {
285             // if possible, use the predefined frames key, as it is easier to parse
286             return ((FactoryManagedFrame) frame).getFactoryKey().toString();
287         } else {
288             // as a fallback, use the full name of the frame
289             return frame.getName();
290         }
291     }
292 
293     /** Get tile data.
294      * @param tile tile to which the cell belongs
295      * @return index of the tile
296      */
297     private DumpedTileData getTileData(final Tile tile) {
298 
299         for (final DumpedTileData dumpedTileData : tiles) {
300             if (tile == dumpedTileData.getTile()) {
301                 // the tile is already known
302                 return dumpedTileData;
303             }
304         }
305 
306         // it is the first time we encounter this tile, we need to dump its data
307         final DumpedTileData dumpedTileData = new DumpedTileData("t" + tiles.size(), tile);
308         tiles.add(dumpedTileData);
309         dumpedTileData.setElevation(tile.getMinElevationLatitudeIndex(),
310                                     tile.getMinElevationLongitudeIndex(),
311                                     tile.getMinElevation());
312         dumpedTileData.setElevation(tile.getMaxElevationLatitudeIndex(),
313                                     tile.getMaxElevationLongitudeIndex(),
314                                     tile.getMaxElevation());
315         return dumpedTileData;
316 
317     }
318 
319     /** Get sensor data.
320      * @param sensor sensor
321      * @return dumped data
322      */
323     private DumpedSensorData getSensorData(final LineSensor sensor) {
324 
325         for (final DumpedSensorData dumpedSensorData : sensors) {
326             if (sensor == dumpedSensorData.getSensor()) {
327                 // the sensor is already known
328                 return dumpedSensorData;
329             }
330         }
331 
332         // it is the first time we encounter this sensor, we need to dump its data
333         final DumpedSensorData dumpedSensorData = new DumpedSensorData("s" + sensors.size(), sensor);
334         sensors.add(dumpedSensorData);
335         return dumpedSensorData;
336 
337     }
338 
339     /** Convert a date to string with high accuracy.
340      * @param date computation date
341      * @return converted date
342      */
343     private String convertDate(final AbsoluteDate date) {
344         final DateTimeComponents dt = date.getComponents(TimeScalesFactory.getUTC());
345         return String.format(Locale.US, "%04d-%02d-%02dT%02d:%02d:%017.14fZ",
346                 dt.getDate().getYear(), dt.getDate().getMonth(), dt.getDate().getDay(),
347                 dt.getTime().getHour(), dt.getTime().getMinute(), dt.getTime().getSecond());
348     }
349 
350     /** Convert a translation to string.
351      * @param translation translation
352      * @param velocity linear velocity
353      * @param acceleration linear acceleration
354      * @return converted rotation
355      */
356     private String convertTranslation(final Vector3D translation, final Vector3D velocity, final Vector3D acceleration) {
357         return String.format(Locale.US,
358                              "p %22.15e %22.15e %22.15e v %22.15e %22.15e %22.15e a %22.15e %22.15e %22.15e",
359                              translation.getX(),  translation.getY(),  translation.getZ(),
360                              velocity.getX(),     velocity.getY(),     velocity.getZ(),
361                              acceleration.getX(), acceleration.getY(), acceleration.getZ());
362     }
363 
364     /** Convert a rotation to string.
365      * @param rotation rotation
366      * @param rate rate of the rotation
367      * @param acceleration angular acceleration
368      * @return converted rotation
369      */
370     private String convertRotation(final Rotation rotation, final Vector3D rate, final Vector3D acceleration) {
371         return String.format(Locale.US,
372                              "r %22.15e %22.15e %22.15e %22.15e Ω %22.15e %22.15e %22.15e ΩDot %22.15e %22.15e %22.15e",
373                              rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
374                              rate.getX(), rate.getY(), rate.getZ(),
375                              acceleration.getX(), acceleration.getY(), acceleration.getZ());
376     }
377 
378     /** Deactivate dump.
379      */
380     public void deactivate() {
381         writer.close();
382     }
383 
384     /** Local class for handling already dumped tile data. */
385     private class DumpedTileData {
386 
387         /** Name of the tile. */
388         private final String name;
389 
390         /** Tile associated with this dump. */
391         private final Tile tile;
392 
393         /** Dumped elevations. */
394         private final OpenIntToDoubleHashMap elevations;
395 
396         /** Simple constructor.
397          * @param name of the tile
398          * @param tile tile associated with this dump
399          */
400         DumpedTileData(final String name, final Tile tile) {
401             this.name       = name;
402             this.tile       = tile;
403             this.elevations = new OpenIntToDoubleHashMap();
404             writer.format(Locale.US,
405                           "DEM tile: %s latMin %22.15e latStep %22.15e latRows %d lonMin %22.15e lonStep %22.15e lonCols %d%n",
406                           name,
407                           tile.getMinimumLatitude(), tile.getLatitudeStep(), tile.getLatitudeRows(),
408                           tile.getMinimumLongitude(), tile.getLongitudeStep(), tile.getLongitudeColumns());
409         }
410 
411         /** Get tile associated with this dump.
412          * @return tile associated with this dump
413          */
414         public Tile getTile() {
415             return tile;
416         }
417 
418         /** Set an elevation.
419          * @param latitudeIndex latitude index of the cell
420          * @param longitudeIndex longitude index of the cell
421          * @param elevation elevation of the cell
422          */
423         public void setElevation(final int latitudeIndex, final int longitudeIndex, final double elevation) {
424             final int key = latitudeIndex * tile.getLongitudeColumns() + longitudeIndex;
425             if (!elevations.containsKey(key)) {
426                 // new cell
427                 elevations.put(key, elevation);
428                 writer.format(Locale.US,
429                               "DEM cell: %s latIndex %d lonIndex %d elevation %22.15e%n",
430                               name, latitudeIndex, longitudeIndex, elevation);
431             }
432         }
433 
434     }
435 
436     /** Local class for handling already dumped sensor data. */
437     private class DumpedSensorData {
438 
439         /** Name of the dump. */
440         private final String dumpName;
441 
442         /** Dumped sensor sensor. */
443         private final LineSensor sensor;
444 
445         /** LOS map. */
446         private final Map<Integer, List<Pair<AbsoluteDate, Vector3D>>> losMap;
447 
448         /** Datation. */
449         private final List<Pair<Double, AbsoluteDate>> datation;
450 
451         /** Rate. */
452         private final List<Pair<Double, Double>> rates;
453 
454         /** Mean plane finder. */
455         private SensorMeanPlaneCrossing meanPlane;
456 
457         /** Simple constructor.
458          * @param dumpName name of the sensor dump (not the name of the sensor itself, for confidentiality reasons)
459          * @param sensor dumped sensor
460          */
461         DumpedSensorData(final String dumpName, final LineSensor sensor) {
462             this.dumpName = dumpName;
463             this.sensor   = sensor;
464             this.losMap   = new HashMap<Integer, List<Pair<AbsoluteDate, Vector3D>>>();
465             this.datation = new ArrayList<Pair<Double, AbsoluteDate>>();
466             this.rates    = new ArrayList<Pair<Double, Double>>();
467             writer.format(Locale.US,
468                           "sensor: sensorName %s nbPixels %d position %22.15e %22.15e %22.15e%n",
469                           dumpName, sensor.getNbPixels(),
470                           sensor.getPosition().getX(), sensor.getPosition().getY(), sensor.getPosition().getZ());
471         }
472 
473         /** Get the anonymized dump sensor name.
474          * @return dump sensorname
475          */
476         public String getDumpName() {
477             return dumpName;
478         }
479 
480         /** Get dumped sensor.
481          * @return dumped sensor
482          */
483         public LineSensor getSensor() {
484             return sensor;
485         }
486 
487         /** Set the mean plane finder.
488          * @param meanPlane mean plane finder
489          */
490         public void setMeanPlane(final SensorMeanPlaneCrossing meanPlane) {
491 
492             if (this.meanPlane == null) {
493                 this.meanPlane = meanPlane;
494                 final long nbResults = meanPlane.getCachedResults().count();
495                 writer.format(Locale.US,
496                         "sensor mean plane: sensorName %s minLine %d maxLine %d maxEval %d accuracy %22.15e normal %22.15e %22.15e %22.15e cachedResults %d",
497                         dumpName,
498                         meanPlane.getMinLine(), meanPlane.getMaxLine(),
499                         meanPlane.getMaxEval(), meanPlane.getAccuracy(),
500                         meanPlane.getMeanPlaneNormal().getX(), meanPlane.getMeanPlaneNormal().getY(), meanPlane.getMeanPlaneNormal().getZ(),
501                         nbResults);
502                 meanPlane.getCachedResults().forEach(result -> {
503                     try {
504                         writer.format(Locale.US,
505                                 " 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",
506                                 result.getLine(), convertDate(result.getDate()),
507                                 result.getTarget().getX(), result.getTarget().getY(), result.getTarget().getZ(),
508                                 result.getTargetDirection().getX(),
509                                 result.getTargetDirection().getY(),
510                                 result.getTargetDirection().getZ(),
511                                 result.getTargetDirectionDerivative().getZ(),
512                                 result.getTargetDirectionDerivative().getY(),
513                                 result.getTargetDirectionDerivative().getZ());
514                     } catch (RuggedException re) {
515                         throw new RuggedInternalError(re);
516                     }
517                 });
518                 writer.format(Locale.US, "%n");
519 
520                 // ensure the transforms for mid date are dumped
521                 final AbsoluteDate midDate = meanPlane.getSensor().getDate(0.5 * (meanPlane.getMinLine() + meanPlane.getMaxLine()));
522                 meanPlane.getScToBody().getBodyToInertial(midDate);
523                 meanPlane.getScToBody().getScToInertial(midDate);
524             }
525         }
526 
527         /** Set a los direction.
528          * @param date date
529          * @param pixelNumber number of the pixel
530          * @param los los direction
531          */
532         public void setLOS(final AbsoluteDate date, final int pixelNumber, final Vector3D los) {
533             List<Pair<AbsoluteDate, Vector3D>> list = losMap.get(pixelNumber);
534             if (list == null) {
535                 list = new ArrayList<Pair<AbsoluteDate, Vector3D>>();
536                 losMap.put(pixelNumber, list);
537             }
538             for (final Pair<AbsoluteDate, Vector3D> alreadyDumped : list) {
539                 if (FastMath.abs(date.durationFrom(alreadyDumped.getFirst())) < 1.0e-12 &&
540                     Vector3D.angle(los, alreadyDumped.getSecond()) < 1.0e-12) {
541                     return;
542                 }
543             }
544             list.add(new Pair<AbsoluteDate, Vector3D>(date, los));
545             writer.format(Locale.US,
546                           "sensor LOS: sensorName %s date %s pixelNumber %d los %22.15e %22.15e %22.15e%n",
547                           dumpName, convertDate(date), pixelNumber, los.getX(), los.getY(), los.getZ());
548         }
549 
550         /** Set a datation pair.
551          * @param lineNumber line number
552          * @param date date
553          */
554         public void setDatation(final double lineNumber, final AbsoluteDate date) {
555             for (final Pair<Double, AbsoluteDate> alreadyDumped : datation) {
556                 if (FastMath.abs(date.durationFrom(alreadyDumped.getSecond())) < 1.0e-12 &&
557                     FastMath.abs(lineNumber - alreadyDumped.getFirst()) < 1.0e-12) {
558                     return;
559                 }
560             }
561             datation.add(new Pair<Double, AbsoluteDate>(lineNumber, date));
562             writer.format(Locale.US,
563                           "sensor datation: sensorName %s lineNumber %22.15e date %s%n",
564                           dumpName, lineNumber, convertDate(date));
565         }
566 
567         /** Set a rate.
568          * @param lineNumber line number
569          * @param rate lines rate
570          */
571         public void setRate(final double lineNumber, final double rate) {
572             for (final Pair<Double, Double> alreadyDumped : rates) {
573                 if (FastMath.abs(rate - alreadyDumped.getSecond()) < 1.0e-12 &&
574                     FastMath.abs(lineNumber - alreadyDumped.getFirst()) < 1.0e-12) {
575                     return;
576                 }
577             }
578             rates.add(new Pair<Double, Double>(lineNumber, rate));
579             writer.format(Locale.US,
580                           "sensor rate: sensorName %s lineNumber %22.15e rate %22.15e%n",
581                           dumpName, lineNumber, rate);
582         }
583 
584     }
585 
586 }