Dump.java

/* Copyright 2013-2017 CS Systèmes d'Information
 * Licensed to CS Systèmes d'Information (CS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * CS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.orekit.rugged.errors;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

import org.hipparchus.geometry.euclidean.threed.Rotation;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.OpenIntToDoubleHashMap;
import org.hipparchus.util.Pair;
import org.orekit.bodies.GeodeticPoint;
import org.orekit.errors.OrekitException;
import org.orekit.frames.FactoryManagedFrame;
import org.orekit.frames.Frame;
import org.orekit.frames.Transform;
import org.orekit.rugged.api.AlgorithmId;
import org.orekit.rugged.linesensor.LineSensor;
import org.orekit.rugged.linesensor.SensorMeanPlaneCrossing;
import org.orekit.rugged.linesensor.SensorPixel;
import org.orekit.rugged.raster.Tile;
import org.orekit.rugged.utils.ExtendedEllipsoid;
import org.orekit.rugged.utils.SpacecraftToObservedBody;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.DateTimeComponents;
import org.orekit.time.TimeScalesFactory;

/**
 * Dump data class.
 * @author Luc Maisonobe
 */
class Dump {

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

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

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

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

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

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

    /** Simple constructor.
     * @param writer writer to the dump file
     */
    Dump(final PrintWriter writer) {
        this.writer          = writer;
        this.tiles           = new ArrayList<DumpedTileData>();
        this.sensors         = new ArrayList<DumpedSensorData>();
        this.algorithmDumped = false;
        this.ellipsoidDumped = false;
        this.tranformsDumped = null;
        dumpHeader();
    }

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

    /** Dump some context data.
     * @param tile tile to which the cell belongs
     * @param latitudeIndex latitude index of the cell
     * @param longitudeIndex longitude index of the cell
     * @param elevation elevation of the cell
     */
    public void dumpTileCell(final Tile tile,
                             final int latitudeIndex, final int longitudeIndex,
                             final double elevation) {
        getTileData(tile).setElevation(latitudeIndex, longitudeIndex, elevation);
    }

    /** Dump algorithm data.
     * @param algorithmId algorithm ID
     */
    public void dumpAlgorithm(final AlgorithmId algorithmId) {
        if (!algorithmDumped) {
            writer.format(Locale.US,
                          "algorithm: %s%n",
                          algorithmId.name());
            algorithmDumped = true;
        }
    }

    /** Dump algorithm data.
     * @param algorithmId algorithm ID
     * @param specific algorithm specific extra data
     */
    public void dumpAlgorithm(final AlgorithmId algorithmId, final double specific) {
        if (!algorithmDumped) {
            writer.format(Locale.US,
                          "algorithm: %s elevation %22.15e%n",
                          algorithmId.name(), specific);
            algorithmDumped = true;
        }
    }

    /** Dump ellipsoid data.
     * @param ellipsoid ellipsoid to dump
     */
    public void dumpEllipsoid(final ExtendedEllipsoid ellipsoid) {
        if (!ellipsoidDumped) {
            writer.format(Locale.US,
                          "ellipsoid: ae %22.15e f %22.15e frame %s%n",
                          ellipsoid.getA(), ellipsoid.getFlattening(),
                          getKeyOrName(ellipsoid.getBodyFrame()));
            ellipsoidDumped = true;
        }
    }

    /** Dump a direct location computation.
     * @param date date of the location
     * @param position pixel position in spacecraft frame
     * @param los normalized line-of-sight in spacecraft frame
     * @param lightTimeCorrection flag for light time correction
     * @param aberrationOfLightCorrection flag for aberration of light correction
     * @param refractionCorrection flag for refraction correction
     * @exception RuggedException if date cannot be converted to UTC
     */
    public void dumpDirectLocation(final AbsoluteDate date, final Vector3D position, final Vector3D los,
                                   final boolean lightTimeCorrection, final boolean aberrationOfLightCorrection,
                                   final boolean refractionCorrection)
        throws RuggedException {
        writer.format(Locale.US,
                      "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",
                      convertDate(date),
                      position.getX(), position.getY(), position.getZ(),
                      los.getX(),      los.getY(),      los.getZ(),
                      lightTimeCorrection, aberrationOfLightCorrection, refractionCorrection);
    }

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

    /** Dump an inverse location computation.
     * @param sensor sensor
     * @param point point to localize
     * @param minLine minimum line number
     * @param maxLine maximum line number
     * @param lightTimeCorrection flag for light time correction
     * @param aberrationOfLightCorrection flag for aberration of light correction
     */
    public void dumpInverseLocation(final LineSensor sensor, final GeodeticPoint point,
                                    final int minLine, final int maxLine,
                                    final boolean lightTimeCorrection, final boolean aberrationOfLightCorrection) {
        final DumpedSensorData ds = getSensorData(sensor);
        writer.format(Locale.US,
                      "inverse location: sensorName %s latitude %22.15e longitude %22.15e elevation %22.15e minLine %d maxLine %d lightTime %b aberration %b%n",
                      ds.getDumpName(),
                      point.getLatitude(), point.getLongitude(), point.getAltitude(),
                      minLine, maxLine,
                      lightTimeCorrection, aberrationOfLightCorrection);
    }

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

    /** Dump an observation transform transform.
     * @param scToBody provider for observation
     * @param index index of the transform
     * @param bodyToInertial transform from body frame to inertial frame
     * @param scToInertial transfrom from spacecraft frame to inertial frame
     * @exception RuggedException if reference date cannot be converted to UTC
     */
    public void dumpTransform(final SpacecraftToObservedBody scToBody, final int index,
                              final Transform bodyToInertial, final Transform scToInertial)
        throws RuggedException {
        if (tranformsDumped == null) {
            final AbsoluteDate minDate   = scToBody.getMinDate();
            final AbsoluteDate maxDate   = scToBody.getMaxDate();
            final double       tStep     = scToBody.getTStep();
            final double       tolerance = scToBody.getOvershootTolerance();
            final int          n         = (int) FastMath.ceil(maxDate.durationFrom(minDate) / tStep);
            writer.format(Locale.US,
                          "span: minDate %s maxDate %s tStep %22.15e tolerance %22.15e inertialFrame %s%n",
                          convertDate(minDate), convertDate(maxDate), tStep, tolerance,
                          getKeyOrName(scToBody.getInertialFrame()));
            tranformsDumped = new boolean[n];
        }
        if (!tranformsDumped[index]) {
            writer.format(Locale.US,
                          "transform: index %d body %s spacecraft %s %s%n",
                          index,
                          convertRotation(bodyToInertial.getRotation(), bodyToInertial.getRotationRate(), bodyToInertial.getRotationAcceleration()),
                          convertTranslation(scToInertial.getTranslation(), scToInertial.getVelocity(), scToInertial.getAcceleration()),
                          convertRotation(scToInertial.getRotation(), scToInertial.getRotationRate(), scToInertial.getRotationAcceleration()));
            tranformsDumped[index] = true;
        }
    }

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

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

    /** Dump a sensor datation.
     * @param sensor sensor
     * @param lineNumber line number
     * @param date date
     * @exception RuggedException if date cannot be converted to UTC
     */
    public void dumpSensorDatation(final LineSensor sensor, final double lineNumber, final AbsoluteDate date)
        throws RuggedException {
        getSensorData(sensor).setDatation(lineNumber, date);
    }

    /** Dump a sensor rate.
     * @param sensor sensor
     * @param lineNumber line number
     * @param rate lines rate
     */
    public void dumpSensorRate(final LineSensor sensor, final double lineNumber, final double rate) {
        getSensorData(sensor).setRate(lineNumber, rate);
    }

    /** Get a frame key or name.
     * @param frame frame to convert
     * @return frame key or name
     */
    private String getKeyOrName(final Frame frame) {
        if (frame instanceof FactoryManagedFrame) {
            // if possible, use the predefined frames key, as it is easier to parse
            return ((FactoryManagedFrame) frame).getFactoryKey().toString();
        } else {
            // as a fallback, use the full name of the frame
            return frame.getName();
        }
    }

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

        for (final DumpedTileData dumpedTileData : tiles) {
            if (tile == dumpedTileData.getTile()) {
                // the tile is already known
                return dumpedTileData;
            }
        }

        // it is the first time we encounter this tile, we need to dump its data
        final DumpedTileData dumpedTileData = new DumpedTileData("t" + tiles.size(), tile);
        tiles.add(dumpedTileData);
        dumpedTileData.setElevation(tile.getMinElevationLatitudeIndex(),
                                    tile.getMinElevationLongitudeIndex(),
                                    tile.getMinElevation());
        dumpedTileData.setElevation(tile.getMaxElevationLatitudeIndex(),
                                    tile.getMaxElevationLongitudeIndex(),
                                    tile.getMaxElevation());
        return dumpedTileData;

    }

    /** Get sensor data.
     * @param sensor sensor
     * @return dumped data
     */
    private DumpedSensorData getSensorData(final LineSensor sensor) {

        for (final DumpedSensorData dumpedSensorData : sensors) {
            if (sensor == dumpedSensorData.getSensor()) {
                // the sensor is already known
                return dumpedSensorData;
            }
        }

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

    }

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

    /** Convert a translation to string.
     * @param translation translation
     * @param velocity linear velocity
     * @param acceleration linear acceleration
     * @return converted rotation
     */
    private String convertTranslation(final Vector3D translation, final Vector3D velocity, final Vector3D acceleration) {
        return String.format(Locale.US,
                             "p %22.15e %22.15e %22.15e v %22.15e %22.15e %22.15e a %22.15e %22.15e %22.15e",
                             translation.getX(),  translation.getY(),  translation.getZ(),
                             velocity.getX(),     velocity.getY(),     velocity.getZ(),
                             acceleration.getX(), acceleration.getY(), acceleration.getZ());
    }

    /** Convert a rotation to string.
     * @param rotation rotation
     * @param rate rate of the rotation
     * @param acceleration angular acceleration
     * @return converted rotation
     */
    private String convertRotation(final Rotation rotation, final Vector3D rate, final Vector3D acceleration) {
        return String.format(Locale.US,
                             "r %22.15e %22.15e %22.15e %22.15e Ω %22.15e %22.15e %22.15e ΩDot %22.15e %22.15e %22.15e",
                             rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
                             rate.getX(), rate.getY(), rate.getZ(),
                             acceleration.getX(), acceleration.getY(), acceleration.getZ());
    }

    /** Deactivate dump.
     */
    public void deactivate() {
        writer.close();
    }

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

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

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

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

        /** Simple constructor.
         * @param name of the tile
         * @param tile tile associated with this dump
         */
        DumpedTileData(final String name, final Tile tile) {
            this.name       = name;
            this.tile       = tile;
            this.elevations = new OpenIntToDoubleHashMap();
            writer.format(Locale.US,
                          "DEM tile: %s latMin %22.15e latStep %22.15e latRows %d lonMin %22.15e lonStep %22.15e lonCols %d%n",
                          name,
                          tile.getMinimumLatitude(), tile.getLatitudeStep(), tile.getLatitudeRows(),
                          tile.getMinimumLongitude(), tile.getLongitudeStep(), tile.getLongitudeColumns());
        }

        /** Get tile associated with this dump.
         * @return tile associated with this dump
         */
        public Tile getTile() {
            return tile;
        }

        /** Set an elevation.
         * @param latitudeIndex latitude index of the cell
         * @param longitudeIndex longitude index of the cell
         * @param elevation elevation of the cell
         */
        public void setElevation(final int latitudeIndex, final int longitudeIndex, final double elevation) {
            final int key = latitudeIndex * tile.getLongitudeColumns() + longitudeIndex;
            if (!elevations.containsKey(key)) {
                // new cell
                elevations.put(key, elevation);
                writer.format(Locale.US,
                              "DEM cell: %s latIndex %d lonIndex %d elevation %22.15e%n",
                              name, latitudeIndex, longitudeIndex, elevation);
            }
        }

    }

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

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

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

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

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

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

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

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

        /** Get the anonymized dump sensor name.
         * @return dump sensorname
         */
        public String getDumpName() {
            return dumpName;
        }

        /** Get dumped sensor.
         * @return dumped sensor
         */
        public LineSensor getSensor() {
            return sensor;
        }

        /** Set the mean plane finder.
         * @param meanPlane mean plane finder
         * @exception RuggedException if frames cannot be computed at mid date
         */
        public void setMeanPlane(final SensorMeanPlaneCrossing meanPlane) throws RuggedException {
            try {
                if (this.meanPlane == null) {
                    this.meanPlane = meanPlane;
                    final long nbResults = meanPlane.getCachedResults().count();
                    writer.format(Locale.US,
                                  "sensor mean plane: sensorName %s minLine %d maxLine %d maxEval %d accuracy %22.15e normal %22.15e %22.15e %22.15e cachedResults %d",
                                  dumpName,
                                  meanPlane.getMinLine(), meanPlane.getMaxLine(),
                                  meanPlane.getMaxEval(), meanPlane.getAccuracy(),
                                  meanPlane.getMeanPlaneNormal().getX(), meanPlane.getMeanPlaneNormal().getY(), meanPlane.getMeanPlaneNormal().getZ(),
                                  nbResults);
                    meanPlane.getCachedResults().forEach(result -> {
                        try {
                            writer.format(Locale.US,
                                          " 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",
                                          result.getLine(), convertDate(result.getDate()),
                                          result.getTarget().getX(), result.getTarget().getY(), result.getTarget().getZ(),
                                          result.getTargetDirection().getX(),
                                          result.getTargetDirection().getY(),
                                          result.getTargetDirection().getZ(),
                                          result.getTargetDirectionDerivative().getZ(),
                                          result.getTargetDirectionDerivative().getY(),
                                          result.getTargetDirectionDerivative().getZ());
                        } catch (RuggedException re) {
                            throw new RuggedExceptionWrapper(re);
                        }
                    });
                    writer.format(Locale.US, "%n");

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

                }
            } catch (RuggedExceptionWrapper rew) {
                throw rew.getException();
            }
        }

        /** Set a los direction.
         * @param date date
         * @param pixelNumber number of the pixel
         * @param los los direction
         * @exception RuggedException if date cannot be converted to UTC
         */
        public void setLOS(final AbsoluteDate date, final int pixelNumber, final Vector3D los)
            throws RuggedException {
            List<Pair<AbsoluteDate, Vector3D>> list = losMap.get(pixelNumber);
            if (list == null) {
                list = new ArrayList<Pair<AbsoluteDate, Vector3D>>();
                losMap.put(pixelNumber, list);
            }
            for (final Pair<AbsoluteDate, Vector3D> alreadyDumped : list) {
                if (FastMath.abs(date.durationFrom(alreadyDumped.getFirst())) < 1.0e-12 &&
                    Vector3D.angle(los, alreadyDumped.getSecond()) < 1.0e-12) {
                    return;
                }
            }
            list.add(new Pair<AbsoluteDate, Vector3D>(date, los));
            writer.format(Locale.US,
                          "sensor LOS: sensorName %s date %s pixelNumber %d los %22.15e %22.15e %22.15e%n",
                          dumpName, convertDate(date), pixelNumber, los.getX(), los.getY(), los.getZ());
        }

        /** Set a datation pair.
         * @param lineNumber line number
         * @param date date
         * @exception RuggedException if date cannot be converted to UTC
         */
        public void setDatation(final double lineNumber, final AbsoluteDate date)
            throws RuggedException {
            for (final Pair<Double, AbsoluteDate> alreadyDumped : datation) {
                if (FastMath.abs(date.durationFrom(alreadyDumped.getSecond())) < 1.0e-12 &&
                    FastMath.abs(lineNumber - alreadyDumped.getFirst()) < 1.0e-12) {
                    return;
                }
            }
            datation.add(new Pair<Double, AbsoluteDate>(lineNumber, date));
            writer.format(Locale.US,
                          "sensor datation: sensorName %s lineNumber %22.15e date %s%n",
                          dumpName, lineNumber, convertDate(date));
        }

        /** Set a rate.
         * @param lineNumber line number
         * @param rate lines rate
         */
        public void setRate(final double lineNumber, final double rate) {
            for (final Pair<Double, Double> alreadyDumped : rates) {
                if (FastMath.abs(rate - alreadyDumped.getSecond()) < 1.0e-12 &&
                    FastMath.abs(lineNumber - alreadyDumped.getFirst()) < 1.0e-12) {
                    return;
                }
            }
            rates.add(new Pair<Double, Double>(lineNumber, rate));
            writer.format(Locale.US,
                          "sensor rate: sensorName %s lineNumber %22.15e rate %22.15e%n",
                          dumpName, lineNumber, rate);
        }

    }

}