PotentialCoefficientsReader.java

  1. /* Copyright 2002-2024 CS GROUP
  2.  * Licensed to CS GROUP (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.forces.gravity.potential;

  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.text.ParseException;
  21. import java.util.Arrays;
  22. import java.util.Locale;

  23. import org.hipparchus.util.FastMath;
  24. import org.hipparchus.util.Precision;
  25. import org.orekit.annotation.DefaultDataContext;
  26. import org.orekit.data.DataContext;
  27. import org.orekit.data.DataLoader;
  28. import org.orekit.errors.OrekitException;
  29. import org.orekit.errors.OrekitMessages;
  30. import org.orekit.time.AbsoluteDate;
  31. import org.orekit.time.DateComponents;
  32. import org.orekit.time.TimeComponents;
  33. import org.orekit.time.TimeScale;

  34. /**This abstract class represents a Gravitational Potential Coefficients file reader.
  35.  *
  36.  * <p> As it exits many different coefficients models and containers this
  37.  *  interface represents all the methods that should be implemented by a reader.
  38.  *  The proper way to use this interface is to call the {@link GravityFieldFactory}
  39.  *  which will determine which reader to use with the selected potential
  40.  *  coefficients file.</p>
  41.  *
  42.  * @see GravityFields
  43.  * @author Fabien Maussion
  44.  */
  45. public abstract class PotentialCoefficientsReader implements DataLoader {

  46.     /** Maximal degree to parse. */
  47.     private int maxParseDegree;

  48.     /** Maximal order to parse. */
  49.     private int maxParseOrder;

  50.     /** Regular expression for supported files names. */
  51.     private final String supportedNames;

  52.     /** Allow missing coefficients in the input data. */
  53.     private final boolean missingCoefficientsAllowed;

  54.     /** Time scale for parsing dates. */
  55.     private final TimeScale timeScale;

  56.     /** Indicator for complete read. */
  57.     private boolean readComplete;

  58.     /** Central body reference radius. */
  59.     private double ae;

  60.     /** Central body attraction coefficient. */
  61.     private double mu;

  62.     /** Converter from triangular to flat form. */
  63.     private Flattener flattener;

  64.     /** Raw tesseral-sectorial coefficients matrix. */
  65.     private double[] rawC;

  66.     /** Raw tesseral-sectorial coefficients matrix. */
  67.     private double[] rawS;

  68.     /** Indicator for normalized raw coefficients. */
  69.     private boolean normalized;

  70.     /** Tide system. */
  71.     private TideSystem tideSystem;

  72.     /** Simple constructor.
  73.      * <p>Build an uninitialized reader.</p>
  74.      *
  75.      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
  76.      *
  77.      * @param supportedNames regular expression for supported files names
  78.      * @param missingCoefficientsAllowed allow missing coefficients in the input data
  79.      * @see #PotentialCoefficientsReader(String, boolean, TimeScale)
  80.      */
  81.     @DefaultDataContext
  82.     protected PotentialCoefficientsReader(final String supportedNames,
  83.                                           final boolean missingCoefficientsAllowed) {
  84.         this(supportedNames, missingCoefficientsAllowed,
  85.                 DataContext.getDefault().getTimeScales().getTT());
  86.     }

  87.     /** Simple constructor.
  88.      * <p>Build an uninitialized reader.</p>
  89.      * @param supportedNames regular expression for supported files names
  90.      * @param missingCoefficientsAllowed allow missing coefficients in the input data
  91.      * @param timeScale to use when parsing dates.
  92.      * @since 10.1
  93.      */
  94.     protected PotentialCoefficientsReader(final String supportedNames,
  95.                                           final boolean missingCoefficientsAllowed,
  96.                                           final TimeScale timeScale) {
  97.         this.supportedNames             = supportedNames;
  98.         this.missingCoefficientsAllowed = missingCoefficientsAllowed;
  99.         this.maxParseDegree             = Integer.MAX_VALUE;
  100.         this.maxParseOrder              = Integer.MAX_VALUE;
  101.         this.readComplete               = false;
  102.         this.ae                         = Double.NaN;
  103.         this.mu                         = Double.NaN;
  104.         this.flattener                  = null;
  105.         this.rawC                       = null;
  106.         this.rawS                       = null;
  107.         this.normalized                 = false;
  108.         this.tideSystem                 = TideSystem.UNKNOWN;
  109.         this.timeScale                  = timeScale;
  110.     }

  111.     /** Get the regular expression for supported files names.
  112.      * @return regular expression for supported files names
  113.      */
  114.     public String getSupportedNames() {
  115.         return supportedNames;
  116.     }

  117.     /** Check if missing coefficients are allowed in the input data.
  118.      * @return true if missing coefficients are allowed in the input data
  119.      */
  120.     public boolean missingCoefficientsAllowed() {
  121.         return missingCoefficientsAllowed;
  122.     }

  123.     /** Set the degree limit for the next file parsing.
  124.      * @param maxParseDegree maximal degree to parse (may be safely
  125.      * set to {@link Integer#MAX_VALUE} to parse all available coefficients)
  126.      * @since 6.0
  127.      */
  128.     public void setMaxParseDegree(final int maxParseDegree) {
  129.         this.maxParseDegree = maxParseDegree;
  130.     }

  131.     /** Get the degree limit for the next file parsing.
  132.      * @return degree limit for the next file parsing
  133.      * @since 6.0
  134.      */
  135.     public int getMaxParseDegree() {
  136.         return maxParseDegree;
  137.     }

  138.     /** Set the order limit for the next file parsing.
  139.      * @param maxParseOrder maximal order to parse (may be safely
  140.      * set to {@link Integer#MAX_VALUE} to parse all available coefficients)
  141.      * @since 6.0
  142.      */
  143.     public void setMaxParseOrder(final int maxParseOrder) {
  144.         this.maxParseOrder = maxParseOrder;
  145.     }

  146.     /** Get the order limit for the next file parsing.
  147.      * @return order limit for the next file parsing
  148.      * @since 6.0
  149.      */
  150.     public int getMaxParseOrder() {
  151.         return maxParseOrder;
  152.     }

  153.     /** {@inheritDoc} */
  154.     public boolean stillAcceptsData() {
  155.         return !(readComplete &&
  156.                  getMaxAvailableDegree() >= getMaxParseDegree() &&
  157.                  getMaxAvailableOrder()  >= getMaxParseOrder());
  158.     }

  159.     /** Set the indicator for completed read.
  160.      * @param readComplete if true, a gravity field has been completely read
  161.      */
  162.     protected void setReadComplete(final boolean readComplete) {
  163.         this.readComplete = readComplete;
  164.     }

  165.     /** Set the central body reference radius.
  166.      * @param ae central body reference radius
  167.      */
  168.     protected void setAe(final double ae) {
  169.         this.ae = ae;
  170.     }

  171.     /** Get the central body reference radius.
  172.      * @return central body reference radius
  173.      */
  174.     protected double getAe() {
  175.         return ae;
  176.     }

  177.     /** Set the central body attraction coefficient.
  178.      * @param mu central body attraction coefficient
  179.      */
  180.     protected void setMu(final double mu) {
  181.         this.mu = mu;
  182.     }

  183.     /** Get the central body attraction coefficient.
  184.      * @return central body attraction coefficient
  185.      */
  186.     protected double getMu() {
  187.         return mu;
  188.     }

  189.     /** Set the {@link TideSystem} used in the gravity field.
  190.      * @param tideSystem tide system used in the gravity field
  191.      */
  192.     protected void setTideSystem(final TideSystem tideSystem) {
  193.         this.tideSystem = tideSystem;
  194.     }

  195.     /** Get the {@link TideSystem} used in the gravity field.
  196.      * @return tide system used in the gravity field
  197.      */
  198.     protected TideSystem getTideSystem() {
  199.         return tideSystem;
  200.     }

  201.     /** Set the tesseral-sectorial coefficients matrix.
  202.      * @param rawNormalized if true, raw coefficients are normalized
  203.      * @param f converter from triangular to flat form
  204.      * @param c raw tesseral-sectorial coefficients matrix
  205.      * @param s raw tesseral-sectorial coefficients matrix
  206.      * @param name name of the file (or zip entry)
  207.      * @since 11.1
  208.      */
  209.     protected void setRawCoefficients(final boolean rawNormalized, final Flattener f,
  210.                                       final double[] c, final double[] s,
  211.                                       final String name) {

  212.         this.flattener = f;

  213.         // normalization indicator
  214.         normalized = rawNormalized;

  215.         // set known constant values, if they were not defined in the file.
  216.         // See Hofmann-Wellenhof and Moritz, "Physical Geodesy",
  217.         // section 2.6 Harmonics of Lower Degree.
  218.         // All S_i,0 are irrelevant because they are multiplied by zero.
  219.         // C0,0 is 1, the central part, since all coefficients are normalized by GM.
  220.         setIfUnset(c, flattener.index(0, 0), 1);
  221.         setIfUnset(s, flattener.index(0, 0), 0);

  222.         if (flattener.getDegree() >= 1) {
  223.             // C1,0, C1,1, and S1,1 are the x,y,z coordinates of the center of mass,
  224.             // which are 0 since all coefficients are given in an Earth centered frame
  225.             setIfUnset(c, flattener.index(1, 0), 0);
  226.             setIfUnset(s, flattener.index(1, 0), 0);
  227.             if (flattener.getOrder() >= 1) {
  228.                 setIfUnset(c, flattener.index(1, 1), 0);
  229.                 setIfUnset(s, flattener.index(1, 1), 0);
  230.             }
  231.         }

  232.         // cosine part
  233.         for (int i = 0; i <= flattener.getDegree(); ++i) {
  234.             for (int j = 0; j <= FastMath.min(i, flattener.getOrder()); ++j) {
  235.                 if (Double.isNaN(c[flattener.index(i, j)])) {
  236.                     throw new OrekitException(OrekitMessages.MISSING_GRAVITY_FIELD_COEFFICIENT_IN_FILE,
  237.                                               'C', i, j, name);
  238.                 }
  239.             }
  240.         }
  241.         rawC = c.clone();

  242.         // sine part
  243.         for (int i = 0; i <= flattener.getDegree(); ++i) {
  244.             for (int j = 0; j <= FastMath.min(i, flattener.getOrder()); ++j) {
  245.                 if (Double.isNaN(s[flattener.index(i, j)])) {
  246.                     throw new OrekitException(OrekitMessages.MISSING_GRAVITY_FIELD_COEFFICIENT_IN_FILE,
  247.                                               'S', i, j, name);
  248.                 }
  249.             }
  250.         }
  251.         rawS = s.clone();

  252.     }

  253.     /**
  254.      * Set a coefficient if it has not been set already.
  255.      * <p>
  256.      * If {@code array[i]} is 0 or NaN this method sets it to {@code value} and returns
  257.      * {@code true}. Otherwise the original value of {@code array[i]} is preserved and
  258.      * this method return {@code false}.
  259.      * <p>
  260.      * If {@code array[i]} does not exist then this method returns {@code false}.
  261.      *
  262.      * @param array the coefficient array.
  263.      * @param i     index in array.
  264.      * @param value the new value to set.
  265.      * @return {@code true} if the coefficient was set to {@code value}, {@code false} if
  266.      * the coefficient was not set to {@code value}. A {@code false} return indicates the
  267.      * coefficient has previously been set to a non-NaN, non-zero value.
  268.      */
  269.     private boolean setIfUnset(final double[] array, final int i, final double value) {
  270.         if (array.length > i && (Double.isNaN(array[i]) || Precision.equals(array[i], 0.0, 0))) {
  271.             // the coefficient was not already initialized
  272.             array[i] = value;
  273.             return true;
  274.         } else {
  275.             return false;
  276.         }
  277.     }

  278.     /** Get the maximal degree available in the last file parsed.
  279.      * @return maximal degree available in the last file parsed
  280.      * @since 6.0
  281.      */
  282.     public int getMaxAvailableDegree() {
  283.         return flattener.getDegree();
  284.     }

  285.     /** Get the maximal order available in the last file parsed.
  286.      * @return maximal order available in the last file parsed
  287.      * @since 6.0
  288.      */
  289.     public int getMaxAvailableOrder() {
  290.         return flattener.getOrder();
  291.     }

  292.     /** {@inheritDoc} */
  293.     public abstract void loadData(InputStream input, String name)
  294.         throws IOException, ParseException, OrekitException;

  295.     /** Get a provider for read spherical harmonics coefficients.
  296.      * @param wantNormalized if true, the provider will provide normalized coefficients,
  297.      * otherwise it will provide un-normalized coefficients
  298.      * @param degree maximal degree
  299.      * @param order maximal order
  300.      * @return a new provider
  301.      * @since 6.0
  302.      */
  303.     public abstract RawSphericalHarmonicsProvider getProvider(boolean wantNormalized, int degree, int order);

  304.     /** Get a time-independent provider containing base harmonics coefficients.
  305.      * <p>
  306.      * Beware that some coeefficients may be missing here, if they are managed as time-dependent
  307.      * piecewise models (as in ICGEM V2.0).
  308.      * </p>
  309.      * @param wantNormalized if true, the raw provider must provide normalized coefficients,
  310.      * otherwise it will provide un-normalized coefficients
  311.      * @param degree maximal degree
  312.      * @param order maximal order
  313.      * @return a new provider, with no time-dependent parts
  314.      * @see #getProvider(boolean, int, int)
  315.      * @since 11.1
  316.      */
  317.     protected ConstantSphericalHarmonics getBaseProvider(final boolean wantNormalized,
  318.                                                          final int degree, final int order) {

  319.         if (!readComplete) {
  320.             throw new OrekitException(OrekitMessages.NO_GRAVITY_FIELD_DATA_LOADED);
  321.         }

  322.         final Flattener truncatedFlattener = new Flattener(degree, order);
  323.         return new ConstantSphericalHarmonics(ae, mu, tideSystem, truncatedFlattener,
  324.                                               rescale(1.0, wantNormalized, truncatedFlattener, flattener, rawC),
  325.                                               rescale(1.0, wantNormalized, truncatedFlattener, flattener, rawS));

  326.     }

  327.     /** Build a coefficients array in flat form.
  328.      * @param flattener converter from triangular to flat form
  329.      * @param value initial value to put in array elements
  330.      * @return built array
  331.      * @since 11.1
  332.      */
  333.     protected static double[] buildFlatArray(final Flattener flattener, final double value) {
  334.         final double[] array = new double[flattener.arraySize()];
  335.         Arrays.fill(array, value);
  336.         return array;
  337.     }

  338.     /**
  339.      * Parse a double from a string. Accept the Fortran convention of using a 'D' or
  340.      * 'd' instead of an 'E' or 'e'.
  341.      *
  342.      * @param string to be parsed.
  343.      * @return the double value of {@code string}.
  344.      */
  345.     protected static double parseDouble(final String string) {
  346.         return Double.parseDouble(string.toUpperCase(Locale.ENGLISH).replace('D', 'E'));
  347.     }

  348.     /** Build a coefficients row.
  349.      * @param degree row degree
  350.      * @param order row order
  351.      * @param value initial value to put in array elements
  352.      * @return built row
  353.      */
  354.     protected static double[] buildRow(final int degree, final int order, final double value) {
  355.         final double[] row = new double[FastMath.min(order, degree) + 1];
  356.         Arrays.fill(row, value);
  357.         return row;
  358.     }

  359.     /** Parse a coefficient.
  360.      * @param field text field to parse
  361.      * @param f converter from triangular to flat form
  362.      * @param array array where to put the coefficient
  363.      * @param i first index in the list
  364.      * @param j second index in the list
  365.      * @param cName name of the coefficient
  366.      * @param name name of the file
  367.      * @since 11.1
  368.      */
  369.     protected void parseCoefficient(final String field, final Flattener f,
  370.                                     final double[] array, final int i, final int j,
  371.                                     final String cName, final String name) {
  372.         final int    index    = f.index(i, j);
  373.         final double value    = parseDouble(field);
  374.         final double oldValue = array[index];
  375.         if (Double.isNaN(oldValue) || Precision.equals(oldValue, 0.0, 0)) {
  376.             // the coefficient was not already initialized
  377.             array[index] = value;
  378.         } else {
  379.             throw new OrekitException(OrekitMessages.DUPLICATED_GRAVITY_FIELD_COEFFICIENT_IN_FILE,
  380.                                       name, i, j, name);
  381.         }
  382.     }

  383.     /** Rescale coefficients arrays.
  384.      * <p>
  385.      * The normalized/unnormalized nature of original coefficients is inherited from previous parsing.
  386.      * </p>
  387.      * @param scale general scaling factor to apply to all elements
  388.      * @param wantNormalized if true, the rescaled coefficients must be normalized,
  389.      * otherwise they must be un-normalized
  390.      * @param rescaledFlattener converter from triangular to flat form
  391.      * @param originalFlattener converter from triangular to flat form
  392.      * @param original original coefficients
  393.      * @return rescaled coefficients
  394.      * @since 11.1
  395.      */
  396.     protected double[] rescale(final double scale, final boolean wantNormalized, final Flattener rescaledFlattener,
  397.                                final Flattener originalFlattener, final double[] original) {

  398.         if (rescaledFlattener.getDegree() > originalFlattener.getDegree()) {
  399.             throw new OrekitException(OrekitMessages.TOO_LARGE_DEGREE_FOR_GRAVITY_FIELD,
  400.                                       rescaledFlattener.getDegree(), flattener.getDegree());
  401.         }

  402.         if (rescaledFlattener.getOrder() > originalFlattener.getOrder()) {
  403.             throw new OrekitException(OrekitMessages.TOO_LARGE_ORDER_FOR_GRAVITY_FIELD,
  404.                                       rescaledFlattener.getOrder(), flattener.getOrder());
  405.         }

  406.         // scaling and normalization factors
  407.         final FactorsGenerator generator;
  408.         if (wantNormalized == normalized) {
  409.             // the parsed coefficients already match the specified normalization
  410.             generator = (n, m) -> scale;
  411.         } else {
  412.             // we need to normalize/unnormalize parsed coefficients
  413.             final double[][] unnormalizationFactors =
  414.                             GravityFieldFactory.getUnnormalizationFactors(rescaledFlattener.getDegree(),
  415.                                                                           rescaledFlattener.getOrder());
  416.             generator = wantNormalized ?
  417.                 (n, m) -> scale / unnormalizationFactors[n][m] :
  418.                 (n, m) -> scale * unnormalizationFactors[n][m];
  419.         }

  420.         // perform rescaling
  421.         final double[] rescaled = buildFlatArray(rescaledFlattener, 0.0);
  422.         for (int n = 0; n <= rescaledFlattener.getDegree(); ++n) {
  423.             for (int m = 0; m <= FastMath.min(n, rescaledFlattener.getOrder()); ++m) {
  424.                 final int    rescaledndex  = rescaledFlattener.index(n, m);
  425.                 final int    originalndex  = originalFlattener.index(n, m);
  426.                 rescaled[rescaledndex] = original[originalndex] * generator.factor(n, m);
  427.             }
  428.         }

  429.         return rescaled;

  430.     }

  431.     /** Rescale coefficients arrays.
  432.      * <p>
  433.      * The normalized/unnormalized nature of original coefficients is inherited from previous parsing.
  434.      * </p>
  435.      * @param wantNormalized if true, the rescaled coefficients must be normalized,
  436.      * otherwise they must be un-normalized
  437.      * @param rescaledFlattener converter from triangular to flat form
  438.      * @param originalFlattener converter from triangular to flat form
  439.      * @param original original coefficients
  440.      * @return rescaled coefficients
  441.      * @since 11.1
  442.      */
  443.     protected TimeDependentHarmonic[] rescale(final boolean wantNormalized, final Flattener rescaledFlattener,
  444.                                               final Flattener originalFlattener, final TimeDependentHarmonic[] original) {

  445.         if (rescaledFlattener.getDegree() > originalFlattener.getDegree()) {
  446.             throw new OrekitException(OrekitMessages.TOO_LARGE_DEGREE_FOR_GRAVITY_FIELD,
  447.                                       rescaledFlattener.getDegree(), flattener.getDegree());
  448.         }

  449.         if (rescaledFlattener.getOrder() > originalFlattener.getOrder()) {
  450.             throw new OrekitException(OrekitMessages.TOO_LARGE_ORDER_FOR_GRAVITY_FIELD,
  451.                                       rescaledFlattener.getOrder(), flattener.getOrder());
  452.         }

  453.         // scaling and normalization factors
  454.         final FactorsGenerator generator;
  455.         if (wantNormalized == normalized) {
  456.             // the parsed coefficients already match the specified normalization
  457.             generator = (n, m) -> 1.0;
  458.         } else {
  459.             // we need to normalize/unnormalize parsed coefficients
  460.             final double[][] unnormalizationFactors =
  461.                             GravityFieldFactory.getUnnormalizationFactors(rescaledFlattener.getDegree(),
  462.                                                                           rescaledFlattener.getOrder());
  463.             generator = wantNormalized ?
  464.                 (n, m) -> 1.0 / unnormalizationFactors[n][m] :
  465.                 (n, m) -> unnormalizationFactors[n][m];
  466.         }

  467.         // perform rescaling
  468.         final TimeDependentHarmonic[] rescaled = new TimeDependentHarmonic[rescaledFlattener.arraySize()];
  469.         for (int n = 0; n <= rescaledFlattener.getDegree(); ++n) {
  470.             for (int m = 0; m <= FastMath.min(n, rescaledFlattener.getOrder()); ++m) {
  471.                 final int originalndex = originalFlattener.index(n, m);
  472.                 if (original[originalndex] != null) {
  473.                     final int    rescaledndex = rescaledFlattener.index(n, m);
  474.                     final double factor       = generator.factor(n, m);
  475.                     rescaled[rescaledndex]    = new TimeDependentHarmonic(factor, original[originalndex]);
  476.                 }
  477.             }
  478.         }

  479.         return rescaled;

  480.     }

  481.     /**
  482.      * Create a date from components. Assumes the time part is noon.
  483.      *
  484.      * @param components year, month, day.
  485.      * @return date.
  486.      */
  487.     protected AbsoluteDate toDate(final DateComponents components) {
  488.         return toDate(components, TimeComponents.H12);
  489.     }

  490.     /**
  491.      * Create a date from components.
  492.      *
  493.      * @param dc dates components.
  494.      * @param tc time components
  495.      * @return date.
  496.      * @since 11.1
  497.      */
  498.     protected AbsoluteDate toDate(final DateComponents dc, final TimeComponents tc) {
  499.         return new AbsoluteDate(dc, tc, timeScale);
  500.     }

  501.     /** Generator for normalization/unnormalization factors.
  502.      * @since 11.1
  503.      */
  504.     private interface FactorsGenerator {
  505.         /** Generator the normalization/unnormalization factors.
  506.          * @param n degree of the gravity field component
  507.          * @param m order of the gravity field component
  508.          * @return factor to apply to term
  509.          */
  510.         double factor(int n, int m);
  511.     }

  512. }