EphemerisDataPredicate.java

/* Copyright 2022-2026 Luc Maisonobe
 * 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.sinex.orbex;

import org.hipparchus.geometry.euclidean.threed.Rotation;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.sinex.ParseInfo;
import org.orekit.gnss.SatInSystem;
import org.orekit.time.DateTimeComponents;

import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** Predicates for ephemeris data blocks.
 * @author Luc Maisonobe
 * @since 14.0
 */
enum EphemerisDataPredicate implements Predicate<OrbexParseInfo> {

    /** Predicate for time tag line. */
    TIME_TAG(RegexParts.LINE_START + "##" +
             RegexParts.STORED_FOUR_DIGITS + RegexParts.STORED_INTEGER + RegexParts.STORED_INTEGER +
             RegexParts.STORED_INTEGER + RegexParts.STORED_INTEGER + RegexParts.STORED_SINGLE_REAL +
             RegexParts.STORED_INTEGER + RegexParts.LINE_END,
             -1) {

        /** {@inheritDoc} */
        @Override
        protected void store(final Matcher matcher, final double[] columns, final OrbexParseInfo parseInfo) {
            parseInfo.timeTag(new DateTimeComponents(Integer.parseInt(matcher.group(1)),
                                                     Integer.parseInt(matcher.group(2)),
                                                     Integer.parseInt(matcher.group(3)),
                                                     Integer.parseInt(matcher.group(4)),
                                                     Integer.parseInt(matcher.group(5)),
                                                     Double.parseDouble(matcher.group(6))),
                              Integer.parseInt(matcher.group(7)));
        }

    },

    /** Predicate for PCS record. */
    PCS(RegexParts.LINE_START + " PCS" + RegexParts.STORED_SAT_ID +
        RegexParts.IGNORED_FLAGS +
        RegexParts.STORED_ONE_DIGIT + RegexParts.STORED_MULTIPLE_REALS + RegexParts.LINE_END,
        2, 3, 4, 7, 8) {
        @Override
        protected void store(final Matcher matcher, final double[] columns, final OrbexParseInfo parseInfo) {
            final SatInSystem satId = new SatInSystem(matcher.group(1));
            parseInfo.addPosition(satId, new Vector3D(columns[0], columns[1], columns[2]), name());
            if (columns.length > 3) {
                parseInfo.addClockCorrection(satId, columns[3], name());
            }
        }
    },

    /** Predicate for VCS record. */
    VCS(RegexParts.LINE_START + " VCS" + RegexParts.STORED_SAT_ID +
        RegexParts.STORED_ONE_DIGIT + RegexParts.STORED_MULTIPLE_REALS + RegexParts.LINE_END,
        2, 3, 4, 7, 8) {
        @Override
        protected void store(final Matcher matcher, final double[] columns, final OrbexParseInfo parseInfo) {
            final SatInSystem satId = new SatInSystem(matcher.group(1));
            parseInfo.addVelocity(satId, new Vector3D(columns[0], columns[1], columns[2]), name());
            if (columns.length > 3) {
                parseInfo.addClockRate(satId, columns[3], name());
            }
        }
    },

    /** Predicate for POS record. */
    POS(RegexParts.LINE_START + " POS" + RegexParts.STORED_SAT_ID +
        RegexParts.IGNORED_FLAGS +
        RegexParts.STORED_ONE_DIGIT + RegexParts.STORED_MULTIPLE_REALS + RegexParts.LINE_END,
        2, 3) {
        @Override
        protected void store(final Matcher matcher, final double[] columns, final OrbexParseInfo parseInfo) {
            final SatInSystem satId = new SatInSystem(matcher.group(1));
            parseInfo.addPosition(satId, new Vector3D(columns[0], columns[1], columns[2]), name());
        }
    },

    /** Predicate for VEL record. */
    VEL(RegexParts.LINE_START + " VEL" + RegexParts.STORED_SAT_ID +
        RegexParts.STORED_ONE_DIGIT + RegexParts.STORED_MULTIPLE_REALS + RegexParts.LINE_END,
        2, 3) {
        @Override
        protected void store(final Matcher matcher, final double[] columns, final OrbexParseInfo parseInfo) {
            final SatInSystem satId = new SatInSystem(matcher.group(1));
            parseInfo.addVelocity(satId, new Vector3D(columns[0], columns[1], columns[2]), name());
        }
    },

    /** Predicate for CLK record. */
    CLK(RegexParts.LINE_START + " CLK" + RegexParts.STORED_SAT_ID +
        RegexParts.STORED_ONE_DIGIT + RegexParts.STORED_MULTIPLE_REALS + RegexParts.LINE_END,
        2, 1) {
        @Override
        protected void store(final Matcher matcher, final double[] columns, final OrbexParseInfo parseInfo) {
            final SatInSystem satId = new SatInSystem(matcher.group(1));
            parseInfo.addClockCorrection(satId, columns[0], name());
        }
    },

    /** Predicate for CRT record. */
    CRT(RegexParts.LINE_START + " CRT" + RegexParts.STORED_SAT_ID +
        RegexParts.STORED_ONE_DIGIT + RegexParts.STORED_MULTIPLE_REALS + RegexParts.LINE_END,
        2, 1) {
        @Override
        protected void store(final Matcher matcher, final double[] columns, final OrbexParseInfo parseInfo) {
            final SatInSystem satId = new SatInSystem(matcher.group(1));
            parseInfo.addClockRate(satId, columns[0], name());
        }
    },

    /** Predicate for ATT record. */
    ATT(RegexParts.LINE_START + " ATT" + RegexParts.STORED_SAT_ID +
        RegexParts.STORED_ONE_DIGIT + RegexParts.STORED_MULTIPLE_REALS + RegexParts.LINE_END,
        2, 4) {
        @Override
        protected void store(final Matcher matcher, final double[] columns, final OrbexParseInfo parseInfo) {
            final SatInSystem satId = new SatInSystem(matcher.group(1));
            final Rotation bodyToCoordinateSystem = new Rotation(columns[0], columns[1], columns[2], columns[3],
                                                                 true);
            parseInfo.addAttitude(satId, bodyToCoordinateSystem);
        }
    };

    /** Pattern for the record. */
    private final Pattern pattern;

    /** Group index of the column number. */
    private final int columnGroup;

    /** Allowed numbers of columns. */
    private final int[] allowedColumns;

    /** Simple constructor.
     * @param regex regex for the record
     * @param columnGroup group index of the column number
     * @param allowedColumns allowed numbers of columns
     */
    EphemerisDataPredicate(final String regex, final int columnGroup, final int... allowedColumns) {
        this.pattern        = Pattern.compile(regex);
        this.columnGroup    = columnGroup;
        this.allowedColumns = allowedColumns.clone();
    }

    /** {@inheritDoc} */
    @Override
    public boolean test(final OrbexParseInfo parseInfo) {
        final Matcher matcher = pattern.matcher(parseInfo.getLine());
        if (matcher.matches()) {
            // the line corresponds to this predicate

            // check the number of columns
            final double[] columns;
            if (columnGroup < 0) {
                columns = null;
            } else {

                // check the internal consistency of the line itself
                // beware that the fields array as a spurious empty first element
                final int nbAnnounced = Integer.parseInt(matcher.group(columnGroup));
                final String[] fields = ParseInfo.SPLIT_AT_BLANKS.split(matcher.group(columnGroup + 1));
                if (nbAnnounced != fields.length - 1) {
                    throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
                                              parseInfo.getLineNumber(), parseInfo.getName(),
                                              parseInfo.getLine());
                }

                // check the number of fields with respect to the type of record
                boolean found = false;
                for (final int allowed : allowedColumns) {
                    // check if the announced number of columns matches one of the allowed numbers
                    found |= nbAnnounced == allowed;
                }
                if (!found) {
                    throw new OrekitException(OrekitMessages.ORBEX_WRONG_COLUMNS,
                                              parseInfo.getLineNumber(), parseInfo.getName(),
                                              nbAnnounced, name());
                }

                // get the columns as real numbers
                columns = new double[nbAnnounced];
                for (int i = 0; i < nbAnnounced; i++) {
                    columns[i] = Double.parseDouble(fields[i + 1]);
                }

            }

            store(matcher, columns, parseInfo);
            return true;

        } else {
            // the line corresponds to another predicate
            return false;
        }
    }

    /**
     * Store parsed fields.
     *
     * @param matcher   matcher for the record regex
     * @param columns   real columns
     * @param parseInfo container for parse info
     */
    protected abstract void store(Matcher matcher, double[] columns, OrbexParseInfo parseInfo);

    /** Utility class for defining regex parts. */
    private static class RegexParts {

        /** Regex for line start. */
        private static final String LINE_START = "^";

        /** Regex for satellite ID. */
        private static final String STORED_SAT_ID = "\\p{Blank}(\\p{Alpha}\\p{Digit}{2})";

        /** Regex for one digit integer. */
        private static final String STORED_ONE_DIGIT = "\\p{Blank}+(\\p{Digit})";

        /** Regex for four digits integer. */
        private static final String STORED_FOUR_DIGITS = "\\p{Blank}+(\\p{Digit}{4})";

        /** Regex for integer. */
        private static final String STORED_INTEGER = "\\p{Blank}+(\\p{Digit}+)";

        /** Regex for real number. */
        private static final String STORED_SINGLE_REAL = "\\p{Blank}+([-0-9.]+)";

        /** Regex for several real numbers. */
        private static final String STORED_MULTIPLE_REALS = "((?:\\p{Blank}+[-0-9.]+)+)";

        /** Regex for ignored flagS. */
        private static final String IGNORED_FLAGS = " {4}[ E][ P] {2}[ M][ P] {3}";

        /** Regex for line end. */
        private static final String LINE_END = "\\p{Blank}*$";

        /** Private constructor for a utility class. */
        private RegexParts() {
            // nothing to do
        }

    }
}