PotentialCoefficientsReader.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.forces.gravity.potential;
- import java.io.IOException;
- import java.io.InputStream;
- import java.text.ParseException;
- import java.util.Arrays;
- import java.util.Locale;
- import org.hipparchus.util.FastMath;
- import org.hipparchus.util.Precision;
- import org.orekit.annotation.DefaultDataContext;
- import org.orekit.data.DataContext;
- import org.orekit.data.DataLoader;
- import org.orekit.errors.OrekitException;
- import org.orekit.errors.OrekitMessages;
- import org.orekit.time.AbsoluteDate;
- import org.orekit.time.DateComponents;
- import org.orekit.time.TimeComponents;
- import org.orekit.time.TimeScale;
- /**This abstract class represents a Gravitational Potential Coefficients file reader.
- *
- * <p> As it exits many different coefficients models and containers this
- * interface represents all the methods that should be implemented by a reader.
- * The proper way to use this interface is to call the {@link GravityFieldFactory}
- * which will determine which reader to use with the selected potential
- * coefficients file.</p>
- *
- * @see GravityFields
- * @author Fabien Maussion
- */
- public abstract class PotentialCoefficientsReader implements DataLoader {
- /** Maximal degree to parse. */
- private int maxParseDegree;
- /** Maximal order to parse. */
- private int maxParseOrder;
- /** Regular expression for supported files names. */
- private final String supportedNames;
- /** Allow missing coefficients in the input data. */
- private final boolean missingCoefficientsAllowed;
- /** Time scale for parsing dates. */
- private final TimeScale timeScale;
- /** Indicator for complete read. */
- private boolean readComplete;
- /** Central body reference radius. */
- private double ae;
- /** Central body attraction coefficient. */
- private double mu;
- /** Converter from triangular to flat form. */
- private Flattener flattener;
- /** Raw tesseral-sectorial coefficients matrix. */
- private double[] rawC;
- /** Raw tesseral-sectorial coefficients matrix. */
- private double[] rawS;
- /** Indicator for normalized raw coefficients. */
- private boolean normalized;
- /** Tide system. */
- private TideSystem tideSystem;
- /** Simple constructor.
- * <p>Build an uninitialized reader.</p>
- *
- * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
- *
- * @param supportedNames regular expression for supported files names
- * @param missingCoefficientsAllowed allow missing coefficients in the input data
- * @see #PotentialCoefficientsReader(String, boolean, TimeScale)
- */
- @DefaultDataContext
- protected PotentialCoefficientsReader(final String supportedNames,
- final boolean missingCoefficientsAllowed) {
- this(supportedNames, missingCoefficientsAllowed,
- DataContext.getDefault().getTimeScales().getTT());
- }
- /** Simple constructor.
- * <p>Build an uninitialized reader.</p>
- * @param supportedNames regular expression for supported files names
- * @param missingCoefficientsAllowed allow missing coefficients in the input data
- * @param timeScale to use when parsing dates.
- * @since 10.1
- */
- protected PotentialCoefficientsReader(final String supportedNames,
- final boolean missingCoefficientsAllowed,
- final TimeScale timeScale) {
- this.supportedNames = supportedNames;
- this.missingCoefficientsAllowed = missingCoefficientsAllowed;
- this.maxParseDegree = Integer.MAX_VALUE;
- this.maxParseOrder = Integer.MAX_VALUE;
- this.readComplete = false;
- this.ae = Double.NaN;
- this.mu = Double.NaN;
- this.flattener = null;
- this.rawC = null;
- this.rawS = null;
- this.normalized = false;
- this.tideSystem = TideSystem.UNKNOWN;
- this.timeScale = timeScale;
- }
- /** Get the regular expression for supported files names.
- * @return regular expression for supported files names
- */
- public String getSupportedNames() {
- return supportedNames;
- }
- /** Check if missing coefficients are allowed in the input data.
- * @return true if missing coefficients are allowed in the input data
- */
- public boolean missingCoefficientsAllowed() {
- return missingCoefficientsAllowed;
- }
- /** Set the degree limit for the next file parsing.
- * @param maxParseDegree maximal degree to parse (may be safely
- * set to {@link Integer#MAX_VALUE} to parse all available coefficients)
- * @since 6.0
- */
- public void setMaxParseDegree(final int maxParseDegree) {
- this.maxParseDegree = maxParseDegree;
- }
- /** Get the degree limit for the next file parsing.
- * @return degree limit for the next file parsing
- * @since 6.0
- */
- public int getMaxParseDegree() {
- return maxParseDegree;
- }
- /** Set the order limit for the next file parsing.
- * @param maxParseOrder maximal order to parse (may be safely
- * set to {@link Integer#MAX_VALUE} to parse all available coefficients)
- * @since 6.0
- */
- public void setMaxParseOrder(final int maxParseOrder) {
- this.maxParseOrder = maxParseOrder;
- }
- /** Get the order limit for the next file parsing.
- * @return order limit for the next file parsing
- * @since 6.0
- */
- public int getMaxParseOrder() {
- return maxParseOrder;
- }
- /** {@inheritDoc} */
- public boolean stillAcceptsData() {
- return !(readComplete &&
- getMaxAvailableDegree() >= getMaxParseDegree() &&
- getMaxAvailableOrder() >= getMaxParseOrder());
- }
- /** Set the indicator for completed read.
- * @param readComplete if true, a gravity field has been completely read
- */
- protected void setReadComplete(final boolean readComplete) {
- this.readComplete = readComplete;
- }
- /** Set the central body reference radius.
- * @param ae central body reference radius
- */
- protected void setAe(final double ae) {
- this.ae = ae;
- }
- /** Get the central body reference radius.
- * @return central body reference radius
- */
- protected double getAe() {
- return ae;
- }
- /** Set the central body attraction coefficient.
- * @param mu central body attraction coefficient
- */
- protected void setMu(final double mu) {
- this.mu = mu;
- }
- /** Get the central body attraction coefficient.
- * @return central body attraction coefficient
- */
- protected double getMu() {
- return mu;
- }
- /** Set the {@link TideSystem} used in the gravity field.
- * @param tideSystem tide system used in the gravity field
- */
- protected void setTideSystem(final TideSystem tideSystem) {
- this.tideSystem = tideSystem;
- }
- /** Get the {@link TideSystem} used in the gravity field.
- * @return tide system used in the gravity field
- */
- protected TideSystem getTideSystem() {
- return tideSystem;
- }
- /** Set the tesseral-sectorial coefficients matrix.
- * @param rawNormalized if true, raw coefficients are normalized
- * @param f converter from triangular to flat form
- * @param c raw tesseral-sectorial coefficients matrix
- * @param s raw tesseral-sectorial coefficients matrix
- * @param name name of the file (or zip entry)
- * @since 11.1
- */
- protected void setRawCoefficients(final boolean rawNormalized, final Flattener f,
- final double[] c, final double[] s,
- final String name) {
- this.flattener = f;
- // normalization indicator
- normalized = rawNormalized;
- // set known constant values, if they were not defined in the file.
- // See Hofmann-Wellenhof and Moritz, "Physical Geodesy",
- // section 2.6 Harmonics of Lower Degree.
- // All S_i,0 are irrelevant because they are multiplied by zero.
- // C0,0 is 1, the central part, since all coefficients are normalized by GM.
- setIfUnset(c, flattener.index(0, 0), 1);
- setIfUnset(s, flattener.index(0, 0), 0);
- if (flattener.getDegree() >= 1) {
- // C1,0, C1,1, and S1,1 are the x,y,z coordinates of the center of mass,
- // which are 0 since all coefficients are given in an Earth centered frame
- setIfUnset(c, flattener.index(1, 0), 0);
- setIfUnset(s, flattener.index(1, 0), 0);
- if (flattener.getOrder() >= 1) {
- setIfUnset(c, flattener.index(1, 1), 0);
- setIfUnset(s, flattener.index(1, 1), 0);
- }
- }
- // cosine part
- for (int i = 0; i <= flattener.getDegree(); ++i) {
- for (int j = 0; j <= FastMath.min(i, flattener.getOrder()); ++j) {
- if (Double.isNaN(c[flattener.index(i, j)])) {
- throw new OrekitException(OrekitMessages.MISSING_GRAVITY_FIELD_COEFFICIENT_IN_FILE,
- 'C', i, j, name);
- }
- }
- }
- rawC = c.clone();
- // sine part
- for (int i = 0; i <= flattener.getDegree(); ++i) {
- for (int j = 0; j <= FastMath.min(i, flattener.getOrder()); ++j) {
- if (Double.isNaN(s[flattener.index(i, j)])) {
- throw new OrekitException(OrekitMessages.MISSING_GRAVITY_FIELD_COEFFICIENT_IN_FILE,
- 'S', i, j, name);
- }
- }
- }
- rawS = s.clone();
- }
- /**
- * Set a coefficient if it has not been set already.
- * <p>
- * If {@code array[i]} is 0 or NaN this method sets it to {@code value} and returns
- * {@code true}. Otherwise the original value of {@code array[i]} is preserved and
- * this method return {@code false}.
- * <p>
- * If {@code array[i]} does not exist then this method returns {@code false}.
- *
- * @param array the coefficient array.
- * @param i index in array.
- * @param value the new value to set.
- * @return {@code true} if the coefficient was set to {@code value}, {@code false} if
- * the coefficient was not set to {@code value}. A {@code false} return indicates the
- * coefficient has previously been set to a non-NaN, non-zero value.
- */
- private boolean setIfUnset(final double[] array, final int i, final double value) {
- if (array.length > i && (Double.isNaN(array[i]) || Precision.equals(array[i], 0.0, 0))) {
- // the coefficient was not already initialized
- array[i] = value;
- return true;
- } else {
- return false;
- }
- }
- /** Get the maximal degree available in the last file parsed.
- * @return maximal degree available in the last file parsed
- * @since 6.0
- */
- public int getMaxAvailableDegree() {
- return flattener.getDegree();
- }
- /** Get the maximal order available in the last file parsed.
- * @return maximal order available in the last file parsed
- * @since 6.0
- */
- public int getMaxAvailableOrder() {
- return flattener.getOrder();
- }
- /** {@inheritDoc} */
- public abstract void loadData(InputStream input, String name)
- throws IOException, ParseException, OrekitException;
- /** Get a provider for read spherical harmonics coefficients.
- * @param wantNormalized if true, the provider will provide normalized coefficients,
- * otherwise it will provide un-normalized coefficients
- * @param degree maximal degree
- * @param order maximal order
- * @return a new provider
- * @since 6.0
- */
- public abstract RawSphericalHarmonicsProvider getProvider(boolean wantNormalized, int degree, int order);
- /** Get a time-independent provider containing base harmonics coefficients.
- * <p>
- * Beware that some coeefficients may be missing here, if they are managed as time-dependent
- * piecewise models (as in ICGEM V2.0).
- * </p>
- * @param wantNormalized if true, the raw provider must provide normalized coefficients,
- * otherwise it will provide un-normalized coefficients
- * @param degree maximal degree
- * @param order maximal order
- * @return a new provider, with no time-dependent parts
- * @see #getProvider(boolean, int, int)
- * @since 11.1
- */
- protected ConstantSphericalHarmonics getBaseProvider(final boolean wantNormalized,
- final int degree, final int order) {
- if (!readComplete) {
- throw new OrekitException(OrekitMessages.NO_GRAVITY_FIELD_DATA_LOADED);
- }
- final Flattener truncatedFlattener = new Flattener(degree, order);
- return new ConstantSphericalHarmonics(ae, mu, tideSystem, truncatedFlattener,
- rescale(1.0, wantNormalized, truncatedFlattener, flattener, rawC),
- rescale(1.0, wantNormalized, truncatedFlattener, flattener, rawS));
- }
- /** Build a coefficients array in flat form.
- * @param flattener converter from triangular to flat form
- * @param value initial value to put in array elements
- * @return built array
- * @since 11.1
- */
- protected static double[] buildFlatArray(final Flattener flattener, final double value) {
- final double[] array = new double[flattener.arraySize()];
- Arrays.fill(array, value);
- return array;
- }
- /**
- * Parse a double from a string. Accept the Fortran convention of using a 'D' or
- * 'd' instead of an 'E' or 'e'.
- *
- * @param string to be parsed.
- * @return the double value of {@code string}.
- */
- protected static double parseDouble(final String string) {
- return Double.parseDouble(string.toUpperCase(Locale.ENGLISH).replace('D', 'E'));
- }
- /** Build a coefficients row.
- * @param degree row degree
- * @param order row order
- * @param value initial value to put in array elements
- * @return built row
- */
- protected static double[] buildRow(final int degree, final int order, final double value) {
- final double[] row = new double[FastMath.min(order, degree) + 1];
- Arrays.fill(row, value);
- return row;
- }
- /** Parse a coefficient.
- * @param field text field to parse
- * @param f converter from triangular to flat form
- * @param array array where to put the coefficient
- * @param i first index in the list
- * @param j second index in the list
- * @param cName name of the coefficient
- * @param name name of the file
- * @since 11.1
- */
- protected void parseCoefficient(final String field, final Flattener f,
- final double[] array, final int i, final int j,
- final String cName, final String name) {
- final int index = f.index(i, j);
- final double value = parseDouble(field);
- final double oldValue = array[index];
- if (Double.isNaN(oldValue) || Precision.equals(oldValue, 0.0, 0)) {
- // the coefficient was not already initialized
- array[index] = value;
- } else {
- throw new OrekitException(OrekitMessages.DUPLICATED_GRAVITY_FIELD_COEFFICIENT_IN_FILE,
- name, i, j, name);
- }
- }
- /** Rescale coefficients arrays.
- * <p>
- * The normalized/unnormalized nature of original coefficients is inherited from previous parsing.
- * </p>
- * @param scale general scaling factor to apply to all elements
- * @param wantNormalized if true, the rescaled coefficients must be normalized,
- * otherwise they must be un-normalized
- * @param rescaledFlattener converter from triangular to flat form
- * @param originalFlattener converter from triangular to flat form
- * @param original original coefficients
- * @return rescaled coefficients
- * @since 11.1
- */
- protected double[] rescale(final double scale, final boolean wantNormalized, final Flattener rescaledFlattener,
- final Flattener originalFlattener, final double[] original) {
- if (rescaledFlattener.getDegree() > originalFlattener.getDegree()) {
- throw new OrekitException(OrekitMessages.TOO_LARGE_DEGREE_FOR_GRAVITY_FIELD,
- rescaledFlattener.getDegree(), flattener.getDegree());
- }
- if (rescaledFlattener.getOrder() > originalFlattener.getOrder()) {
- throw new OrekitException(OrekitMessages.TOO_LARGE_ORDER_FOR_GRAVITY_FIELD,
- rescaledFlattener.getOrder(), flattener.getOrder());
- }
- // scaling and normalization factors
- final FactorsGenerator generator;
- if (wantNormalized == normalized) {
- // the parsed coefficients already match the specified normalization
- generator = (n, m) -> scale;
- } else {
- // we need to normalize/unnormalize parsed coefficients
- final double[][] unnormalizationFactors =
- GravityFieldFactory.getUnnormalizationFactors(rescaledFlattener.getDegree(),
- rescaledFlattener.getOrder());
- generator = wantNormalized ?
- (n, m) -> scale / unnormalizationFactors[n][m] :
- (n, m) -> scale * unnormalizationFactors[n][m];
- }
- // perform rescaling
- final double[] rescaled = buildFlatArray(rescaledFlattener, 0.0);
- for (int n = 0; n <= rescaledFlattener.getDegree(); ++n) {
- for (int m = 0; m <= FastMath.min(n, rescaledFlattener.getOrder()); ++m) {
- final int rescaledndex = rescaledFlattener.index(n, m);
- final int originalndex = originalFlattener.index(n, m);
- rescaled[rescaledndex] = original[originalndex] * generator.factor(n, m);
- }
- }
- return rescaled;
- }
- /** Rescale coefficients arrays.
- * <p>
- * The normalized/unnormalized nature of original coefficients is inherited from previous parsing.
- * </p>
- * @param wantNormalized if true, the rescaled coefficients must be normalized,
- * otherwise they must be un-normalized
- * @param rescaledFlattener converter from triangular to flat form
- * @param originalFlattener converter from triangular to flat form
- * @param original original coefficients
- * @return rescaled coefficients
- * @since 11.1
- */
- protected TimeDependentHarmonic[] rescale(final boolean wantNormalized, final Flattener rescaledFlattener,
- final Flattener originalFlattener, final TimeDependentHarmonic[] original) {
- if (rescaledFlattener.getDegree() > originalFlattener.getDegree()) {
- throw new OrekitException(OrekitMessages.TOO_LARGE_DEGREE_FOR_GRAVITY_FIELD,
- rescaledFlattener.getDegree(), flattener.getDegree());
- }
- if (rescaledFlattener.getOrder() > originalFlattener.getOrder()) {
- throw new OrekitException(OrekitMessages.TOO_LARGE_ORDER_FOR_GRAVITY_FIELD,
- rescaledFlattener.getOrder(), flattener.getOrder());
- }
- // scaling and normalization factors
- final FactorsGenerator generator;
- if (wantNormalized == normalized) {
- // the parsed coefficients already match the specified normalization
- generator = (n, m) -> 1.0;
- } else {
- // we need to normalize/unnormalize parsed coefficients
- final double[][] unnormalizationFactors =
- GravityFieldFactory.getUnnormalizationFactors(rescaledFlattener.getDegree(),
- rescaledFlattener.getOrder());
- generator = wantNormalized ?
- (n, m) -> 1.0 / unnormalizationFactors[n][m] :
- (n, m) -> unnormalizationFactors[n][m];
- }
- // perform rescaling
- final TimeDependentHarmonic[] rescaled = new TimeDependentHarmonic[rescaledFlattener.arraySize()];
- for (int n = 0; n <= rescaledFlattener.getDegree(); ++n) {
- for (int m = 0; m <= FastMath.min(n, rescaledFlattener.getOrder()); ++m) {
- final int originalndex = originalFlattener.index(n, m);
- if (original[originalndex] != null) {
- final int rescaledndex = rescaledFlattener.index(n, m);
- final double factor = generator.factor(n, m);
- rescaled[rescaledndex] = new TimeDependentHarmonic(factor, original[originalndex]);
- }
- }
- }
- return rescaled;
- }
- /**
- * Create a date from components. Assumes the time part is noon.
- *
- * @param components year, month, day.
- * @return date.
- */
- protected AbsoluteDate toDate(final DateComponents components) {
- return toDate(components, TimeComponents.H12);
- }
- /**
- * Create a date from components.
- *
- * @param dc dates components.
- * @param tc time components
- * @return date.
- * @since 11.1
- */
- protected AbsoluteDate toDate(final DateComponents dc, final TimeComponents tc) {
- return new AbsoluteDate(dc, tc, timeScale);
- }
- /** Generator for normalization/unnormalization factors.
- * @since 11.1
- */
- private interface FactorsGenerator {
- /** Generator the normalization/unnormalization factors.
- * @param n degree of the gravity field component
- * @param m order of the gravity field component
- * @return factor to apply to term
- */
- double factor(int n, int m);
- }
- }