ManeuverFieldType.java

/* Copyright 2002-2024 CS GROUP
 * Licensed to CS GROUP (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.files.ccsds.ndm.odm.ocm;

import org.hipparchus.util.Precision;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.ccsds.definitions.OnOff;
import org.orekit.files.ccsds.definitions.TimeConverter;
import org.orekit.files.ccsds.utils.ContextBinding;
import org.orekit.time.DateTimeComponents;
import org.orekit.utils.AccurateFormatter;
import org.orekit.utils.units.Unit;

/** Maneuver field type used in CCSDS {@link Ocm Orbit Comprehensive Messages}.
 * @author Luc Maisonobe
 * @since 11.0
 */
public enum ManeuverFieldType {

    // CHECKSTYLE: stop MultipleStringLiterals check

    /** Absolute epoch time. */
    TIME_ABSOLUTE("n/a",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDate(context.getTimeSystem().getConverter(context).parse(field)),
        (u, converter, maneuver) -> {
            final DateTimeComponents dt = converter.components(maneuver.getDate());
            return AccurateFormatter.format(dt.getDate().getYear(), dt.getDate().getMonth(), dt.getDate().getDay(),
                                            dt.getTime().getHour(), dt.getTime().getMinute(), dt.getTime().getSecond());
        }),

    /** Relative epoch time. */
    TIME_RELATIVE("s",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDate(context.getReferenceDate().shiftedBy(toSI(field, u))),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(converter.offset(maneuver.getDate())))),

    /** Maneuver duration. */
    MAN_DURA("s",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDuration(toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getDuration()))),

    /** Mass change. */
    DELTA_MASS("kg",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDeltaMass(toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getDeltaMass()))),

    /** Acceleration along X axis. */
    ACC_X("km/s²",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setAcceleration(0, toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getAcceleration().getX()))),

    /** Acceleration along Y axis. */
    ACC_Y("km/s²",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setAcceleration(1, toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getAcceleration().getY()))),

    /** Acceleration along Z axis. */
    ACC_Z("km/s²",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setAcceleration(2, toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getAcceleration().getZ()))),

    /** Interpolation mode between current and next acceleration line. */
    ACC_INTERP("n/a",
        (field, u, context, maneuver, lineNumber, fileName) -> {
            try {
                maneuver.setAccelerationInterpolation(OnOff.valueOf(field));
            } catch (IllegalArgumentException iae) {
                throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD,
                                          lineNumber, fileName, field);
            }
        },
        (u, converter, maneuver) -> maneuver.getAccelerationInterpolation().name()),

    /** One σ percent error on acceleration magnitude. */
    ACC_MAG_SIGMA("%",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setAccelerationMagnitudeSigma(toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getAccelerationMagnitudeSigma()))),

    /** One σ off-nominal acceleration direction. */
    ACC_DIR_SIGMA("°",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setAccelerationDirectionSigma(toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getAccelerationDirectionSigma()))),

    /** Velocity increment along X axis. */
    DV_X("km/s",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDv(0, toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getDv().getX()))),

    /** Velocity increment along Y axis. */
    DV_Y("km/s",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDv(1, toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getDv().getY()))),

    /** Velocity increment along Z axis. */
    DV_Z("km/s",
        (field, u, context, maneuver, lineNumber, fileName) ->  maneuver.setDv(2, toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getDv().getZ()))),

    /** One σ percent error on ΔV magnitude. */
    DV_MAG_SIGMA("%",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDvMagSigma(toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getDvMagSigma()))),

    /** One σ angular off-nominal ΔV direction. */
    DV_DIR_SIGMA("°",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDvDirSigma(toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getDvDirSigma()))),

    /** Thrust component along X axis. */
    THR_X("N",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setThrust(0, toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getThrust().getX()))),

    /** Thrust component along Y axis. */
    THR_Y("N",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setThrust(1, toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getThrust().getY()))),

    /** Thrust component along Z axis. */
    THR_Z("N",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setThrust(2, toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getThrust().getZ()))),

    /** Thrust efficiency η typically between 0.0 and 1.0. */
    THR_EFFIC("n/a",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setThrustEfficiency(toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getThrustEfficiency()))),

    /** Interpolation mode between current and next acceleration line. */
    THR_INTERP("n/a",
        (field, u, context, maneuver, lineNumber, fileName) -> {
            try {
                maneuver.setThrustInterpolation(OnOff.valueOf(field));
            } catch (IllegalArgumentException iae) {
                throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD,
                                          lineNumber, fileName, field);
            }
        },
        (u, converter, maneuver) -> maneuver.getThrustInterpolation().name()),

    /** Thrust specific impulse. */
    THR_ISP("s",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setThrustIsp(toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getThrustIsp()))),

    /** One σ percent error on thrust magnitude. */
    THR_MAG_SIGMA("%",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setThrustMagnitudeSigma(toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getThrustMagnitudeSigma()))),

    /** One σ angular off-nominal thrust direction. */
    THR_DIR_SIGMA("°",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setThrustDirectionSigma(toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getThrustDirectionSigma()))),

    /** Identifier of resulting "child" object deployed from this host. */
    DEPLOY_ID("n/a",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDeployId(field),
        (u, converter, maneuver) -> maneuver.getDeployId()),

    /** Velocity increment of deployed "child" object along X axis. */
    DEPLOY_DV_X("km/s",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDeployDv(0, toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getDeployDv().getX()))),

    /** Velocity increment of deployed "child" object along Y axis. */
    DEPLOY_DV_Y("km/s",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDeployDv(1, toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getDeployDv().getY()))),

    /** Velocity increment of deployed "child" object along Z axis. */
    DEPLOY_DV_Z("km/s",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDeployDv(2, toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getDeployDv().getZ()))),

    /** Decrement in host mass as a result of deployment (shall be ≤ 0). */
    DEPLOY_MASS("kg",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDeployMass(toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getDeployMass()))),

    /** One σ percent error on deployment ΔV magnitude. */
    DEPLOY_DV_SIGMA("%",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDeployDvSigma(toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getDeployDvSigma()))),

    /** One σ angular off-nominal deployment vector direction. */
    DEPLOY_DIR_SIGMA("°",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDeployDirSigma(toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getDeployDirSigma()))),

    /** Ratio of child-to-host ΔV vectors. */
    DEPLOY_DV_RATIO("n/a",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDeployDvRatio(toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getDeployDvRatio()))),

    /** Typical (50th percentile) product of drag coefficient times cross-sectional area of deployed "child" object. */
    DEPLOY_DV_CDA("m²",
        (field, u, context, maneuver, lineNumber, fileName) -> maneuver.setDeployDvCda(toSI(field, u)),
        (u, converter, maneuver) -> AccurateFormatter.format(u.fromSI(maneuver.getDeployDvCda())));

    // CHECKSTYLE: resume MultipleStringLiterals check

    /** Elements units. */
    private final Unit unit;

    /** Processing method. */
    private final transient FieldProcessor processor;

    /** Writing method. */
    private final transient FieldWriter writer;

    /** Simple constructor.
     * @param unitSpecifications field unit specifications
     * @param processor field processing method
     * @param writer field writing method
     */
    ManeuverFieldType(final String unitSpecifications, final FieldProcessor processor, final FieldWriter writer) {
        this.unit      = Unit.parse(unitSpecifications);
        this.processor = processor;
        this.writer    = writer;
    }

    /** Get the field unit.
     * @return field unit
     */
    public Unit getUnit() {
        return unit;
    }

    /** Check if parsed unit is compatible with field type.
     * @param parsedUnit unit to check
     */
    public void checkUnit(final Unit parsedUnit) {
        if (unit == Unit.NONE ^ parsedUnit == Unit.NONE ||
            !(unit.sameDimension(parsedUnit) && Precision.equals(unit.getScale(), parsedUnit.getScale(), 1))) {
            throw new OrekitException(OrekitMessages.INCOMPATIBLE_UNITS,
                                      unit.getName(), parsedUnit.getName());
        }
    }

    /** Check if a field is a time field.
     * @return true if field is a time field
     */
    public boolean isTime() {
        return this == TIME_ABSOLUTE || this == TIME_RELATIVE;
    }

    /** Get the value in SI units corresponding to a field.
     * @param field text field
     * @param unit unit to use
     * @return double value in SI units
     */
    private static double toSI(final String field, final Unit unit) {
        return unit.toSI(Double.parseDouble(field));
    }

    /** Process one field.
     * @param field field to process
     * @param context context binding
     * @param maneuver maneuver to fill
     * @param lineNumber line number at which the field occurs
     * @param fileName name of the file in which the field occurs
     */
    public void process(final String field, final ContextBinding context, final OrbitManeuver maneuver,
                        final int lineNumber, final String fileName) {
        processor.process(field, unit, context, maneuver, lineNumber, fileName);
    }

    /** Output one maneuver field.
         * @param converter converter for dates
     * @param maneuver maneuver containing the field to output
     * @return output field
     */
    public String outputField(final TimeConverter converter, final OrbitManeuver maneuver) {
        return writer.output(unit, converter, maneuver);
    }

    /** Interface for processing one field. */
    interface FieldProcessor {
        /** Process one field.
         * @param field field to process
         * @param unit unit to use
         * @param context context binding
         * @param maneuver maneuver to fill
         * @param lineNumber line number at which the field occurs
         * @param fileName name of the file in which the field occurs
         */
        void process(String field, Unit unit, ContextBinding context, OrbitManeuver maneuver,
                     int lineNumber, String fileName);
    }

    /** Interface for writing one field. */
    interface FieldWriter {
        /** Process one field.
         * @param unit unit to use
         * @param converter converter for dates
         * @param maneuver maneuver containing the field to output
         * @return output field
         */
        String output(Unit unit, TimeConverter converter, OrbitManeuver maneuver);
    }

}