EGMFormatReader.java

  1. /* Copyright 2002-2023 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.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.nio.charset.StandardCharsets;
  23. import java.text.ParseException;
  24. import java.util.Arrays;
  25. import java.util.Locale;
  26. import java.util.regex.Pattern;

  27. import org.hipparchus.util.FastMath;
  28. import org.hipparchus.util.Precision;
  29. import org.orekit.errors.OrekitException;
  30. import org.orekit.errors.OrekitMessages;
  31. import org.orekit.utils.Constants;

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

  118.                         parseCoefficient(tab[2], container.flattener, container.c, i, j, "C", name);
  119.                         parseCoefficient(tab[3], container.flattener, container.s, i, j, "S", name);
  120.                         maxDegree = FastMath.max(maxDegree, i);
  121.                         maxOrder  = FastMath.max(maxOrder,  j);

  122.                     }

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

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

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

  141.         container = container.resize(maxDegree, maxOrder);
  142.         setRawCoefficients(true, container.flattener, container.c, container.s, name);
  143.         setReadComplete(true);

  144.     }

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

  161.     /** Temporary container for reading coefficients.
  162.      * @since 11.1
  163.      */
  164.     private static class Container {

  165.         /** Converter from triangular to flat form. */
  166.         private final Flattener flattener;

  167.         /** Cosine coefficients. */
  168.         private final double[] c;

  169.         /** Sine coefficients. */
  170.         private final double[] s;

  171.         /** Initial value for coefficients. */
  172.         private final double initialValue;

  173.         /** Build a container with given degree and order.
  174.          * @param degree degree of the container
  175.          * @param order order of the container
  176.          * @param initialValue initial value for coefficients
  177.          */
  178.         Container(final int degree, final int order, final double initialValue) {
  179.             this.flattener    = new Flattener(degree, order);
  180.             this.c            = new double[flattener.arraySize()];
  181.             this.s            = new double[flattener.arraySize()];
  182.             this.initialValue = initialValue;
  183.             Arrays.fill(c, initialValue);
  184.             Arrays.fill(s, initialValue);
  185.         }

  186.         /** Build a resized container.
  187.          * @param degree degree of the resized container
  188.          * @param order order of the resized container
  189.          * @return resized container
  190.          */
  191.         Container resize(final int degree, final int order) {
  192.             final Container resized = new Container(degree, order, initialValue);
  193.             for (int n = 0; n <= degree; ++n) {
  194.                 for (int m = 0; m <= FastMath.min(n, order); ++m) {
  195.                     if (flattener.withinRange(n, m)) {
  196.                         final int rIndex = resized.flattener.index(n, m);
  197.                         final int index  = flattener.index(n, m);
  198.                         resized.c[rIndex] = c[index];
  199.                         resized.s[rIndex] = s[index];
  200.                     }
  201.                 }
  202.             }
  203.             return resized;
  204.         }

  205.     }

  206. }