EGMFormatReader.java

  1. /* Copyright 2002-2025 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 org.hipparchus.util.FastMath;
  19. import org.hipparchus.util.Precision;
  20. import org.orekit.errors.OrekitException;
  21. import org.orekit.errors.OrekitMessages;
  22. import org.orekit.utils.Constants;

  23. import java.io.BufferedReader;
  24. import java.io.IOException;
  25. import java.io.InputStream;
  26. import java.io.InputStreamReader;
  27. import java.nio.charset.StandardCharsets;
  28. import java.text.ParseException;
  29. import java.util.Locale;
  30. import java.util.regex.Pattern;

  31. /**This reader is adapted to the EGM Format.
  32.  *
  33.  * <p> The proper way to use this class is to call the {@link GravityFieldFactory}
  34.  *  which will determine which reader to use with the selected gravity field file.</p>
  35.  *
  36.  * @see GravityFields
  37.  * @author Fabien Maussion
  38.  */
  39. public class EGMFormatReader extends PotentialCoefficientsReader {

  40.     /** Pattern for delimiting regular expressions. */
  41.     private static final Pattern SEPARATOR = Pattern.compile("\\s+");

  42.     /** Start degree and order for coefficients container. */
  43.     private static final int START_DEGREE_ORDER = 120;

  44.     /** Flag for using WGS84 values for equatorial radius and central attraction coefficient. */
  45.     private final boolean useWgs84Coefficients;

  46.     /** Simple constructor.
  47.      * @param supportedNames regular expression for supported files names
  48.      * @param missingCoefficientsAllowed if true, allows missing coefficients in the input data
  49.      */
  50.     public EGMFormatReader(final String supportedNames, final boolean missingCoefficientsAllowed) {
  51.         this(supportedNames, missingCoefficientsAllowed, false);
  52.     }

  53.     /**
  54.      * Simple constructor that allows overriding 'standard' EGM96 ae and mu with
  55.      * WGS84 variants.
  56.      *
  57.      * @param supportedNames regular expression for supported files names
  58.      * @param missingCoefficientsAllowed if true, allows missing coefficients in the input data
  59.      * @param useWgs84Coefficients if true, the WGS84 values will be used for equatorial radius
  60.      * and central attraction coefficient
  61.      */
  62.     public EGMFormatReader(final String supportedNames, final boolean missingCoefficientsAllowed,
  63.                            final boolean useWgs84Coefficients) {
  64.         super(supportedNames, missingCoefficientsAllowed, null);
  65.         this.useWgs84Coefficients = useWgs84Coefficients;
  66.     }


  67.     /** {@inheritDoc} */
  68.     public void loadData(final InputStream input, final String name)
  69.         throws IOException, ParseException, OrekitException {

  70.         // reset the indicator before loading any data
  71.         setReadComplete(false);

  72.         // both EGM96 and EGM2008 use the same values for ae and mu
  73.         // if a new EGM model changes them, we should have some selection logic
  74.         // based on file name (a better way would be to have the data in the
  75.         // file...)
  76.         if (this.useWgs84Coefficients) {
  77.             setAe(Constants.WGS84_EARTH_EQUATORIAL_RADIUS);
  78.             setMu(Constants.WGS84_EARTH_MU);
  79.         } else {
  80.             setAe(Constants.EGM96_EARTH_EQUATORIAL_RADIUS);
  81.             setMu(Constants.EGM96_EARTH_MU);
  82.         }

  83.         final String lowerCaseName = name.toLowerCase(Locale.US);
  84.         if (lowerCaseName.contains("2008") || lowerCaseName.contains("zerotide")) {
  85.             setTideSystem(TideSystem.ZERO_TIDE);
  86.         } else {
  87.             setTideSystem(TideSystem.TIDE_FREE);
  88.         }

  89.         TemporaryCoefficientsContainer container = new TemporaryCoefficientsContainer(START_DEGREE_ORDER, START_DEGREE_ORDER,
  90.                                                                                       missingCoefficientsAllowed() ? 0.0 : Double.NaN);
  91.         boolean okFields = true;
  92.         int       maxDegree  = -1;
  93.         int       maxOrder   = -1;
  94.         int lineNumber = 0;
  95.         String line = null;
  96.         try (BufferedReader r = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
  97.             for (line = r.readLine(); okFields && line != null; line = r.readLine()) {
  98.                 lineNumber++;
  99.                 if (line.length() >= 15) {

  100.                     // get the fields defining the current potential terms
  101.                     final String[] tab = SEPARATOR.split(line.trim());
  102.                     if (tab.length != 6) {
  103.                         okFields = false;
  104.                     }

  105.                     final int i = Integer.parseInt(tab[0]);
  106.                     final int j = Integer.parseInt(tab[1]);
  107.                     if (i < 0 || j < 0) {
  108.                         throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  109.                                                   lineNumber, name, line);
  110.                     }

  111.                     if (i <= getMaxParseDegree() && j <= getMaxParseOrder()) {

  112.                         while (!container.getFlattener().withinRange(i, j)) {
  113.                             // we need to resize the container
  114.                             container = container.resize(container.getFlattener().getDegree() * 2,
  115.                                                          container.getFlattener().getOrder() * 2);
  116.                         }

  117.                         parseCoefficient(tab[2], container.getFlattener(), container.getC(), i, j, "C", name);
  118.                         parseCoefficient(tab[3], container.getFlattener(), container.getS(), i, j, "S", name);
  119.                         maxDegree = FastMath.max(maxDegree, i);
  120.                         maxOrder  = FastMath.max(maxOrder,  j);

  121.                     }

  122.                 }
  123.             }
  124.         } catch (NumberFormatException nfe) {
  125.             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  126.                                       lineNumber, name, line);
  127.         }

  128.         if (missingCoefficientsAllowed() && getMaxParseDegree() > 0 && getMaxParseOrder() > 0) {
  129.             // ensure at least the (0, 0) element is properly set
  130.             if (Precision.equals(container.getC()[container.getFlattener().index(0, 0)], 0.0, 0)) {
  131.                 container.getC()[container.getFlattener().index(0, 0)] = 1.0;
  132.             }
  133.         }

  134.         if (!(okFields && maxDegree >= 0)) {
  135.             String loaderName = getClass().getName();
  136.             loaderName = loaderName.substring(loaderName.lastIndexOf('.') + 1);
  137.             throw new OrekitException(OrekitMessages.UNEXPECTED_FILE_FORMAT_ERROR_FOR_LOADER,
  138.                                       name, loaderName);
  139.         }

  140.         container = container.resize(maxDegree, maxOrder);
  141.         setRawCoefficients(true, container.getFlattener(), container.getC(), container.getS(), name);
  142.         setReadComplete(true);

  143.     }

  144.     /** Get a provider for read spherical harmonics coefficients.
  145.      * <p>
  146.      * EGM fields don't include time-dependent parts, so this method returns
  147.      * directly a constant provider.
  148.      * </p>
  149.      * @param wantNormalized if true, the provider will provide normalized coefficients,
  150.      * otherwise it will provide un-normalized coefficients
  151.      * @param degree maximal degree
  152.      * @param order maximal order
  153.      * @return a new provider
  154.      * @since 6.0
  155.      */
  156.     public RawSphericalHarmonicsProvider getProvider(final boolean wantNormalized,
  157.                                                      final int degree, final int order) {
  158.         return getBaseProvider(wantNormalized, degree, order);
  159.     }
  160. }