ICGEMFormatReader.java

  1. /* Copyright 2002-2020 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.ArrayList;
  25. import java.util.HashMap;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.regex.Pattern;

  29. import org.hipparchus.util.FastMath;
  30. import org.hipparchus.util.Precision;
  31. import org.orekit.annotation.DefaultDataContext;
  32. import org.orekit.data.DataContext;
  33. import org.orekit.errors.OrekitException;
  34. import org.orekit.errors.OrekitMessages;
  35. import org.orekit.errors.OrekitParseException;
  36. import org.orekit.time.AbsoluteDate;
  37. import org.orekit.time.DateComponents;
  38. import org.orekit.time.TimeScale;
  39. import org.orekit.utils.Constants;

  40. /** Reader for the ICGEM gravity field format.
  41.  *
  42.  * <p>This format is used to describe the gravity field of EIGEN models
  43.  * published by the GFZ Potsdam since 2004. It is described in Franz
  44.  * Barthelmes and Christoph F&ouml;rste paper: "the ICGEM-format".
  45.  * The 2006-02-28 version of this paper can be found <a
  46.  * href="http://op.gfz-potsdam.de/grace/results/grav/g005_ICGEM-Format.pdf">here</a>
  47.  * and the 2011-06-07 version of this paper can be found <a
  48.  * href="http://icgem.gfz-potsdam.de/ICGEM-Format-2011.pdf">here</a>.
  49.  * These versions differ in time-dependent coefficients, which are linear-only prior
  50.  * to 2011 (up to eigen-5 model) and have also harmonic effects after that date
  51.  * (starting with eigen-6 model). Both versions are supported by the class.</p>
  52.  * <p>
  53.  * This reader uses relaxed check on the gravity constant key so any key ending
  54.  * in gravity_constant is accepted and not only earth_gravity_constant as specified
  55.  * in the previous documents. This allows to read also non Earth gravity fields
  56.  * as found in <a href="http://icgem.gfz-potsdam.de/tom_celestial">ICGEM
  57.  * - Gravity Field Models of other Celestial Bodies</a> page to be read.
  58.  * </p>
  59.  * <p>
  60.  * In order to simplify implementation, some design choices have been made: the
  61.  * reference date and the periods of harmonic pulsations are stored globally and
  62.  * not on a per-coefficient basis. This has the following implications:
  63.  * </p>
  64.  * <ul>
  65.  *   <li>
  66.  *     all time-stamped coefficients must share the same reference date, otherwise
  67.  *     an error will be triggered during parsing,
  68.  *   </li>
  69.  *   <li>
  70.  *     in order to avoid too large memory and CPU consumption, only a few different
  71.  *     periods should appear in the file,
  72.  *   </li>
  73.  *   <li>
  74.  *     only one occurrence of each coefficient may appear in the file, otherwise
  75.  *     an error will be triggered during parsing. Multiple occurrences with different
  76.  *     time stamps are forbidden (both because they correspond to a duplicated entry
  77.  *     and because they define two different reference dates as per previous design
  78.  *     choice).
  79.  *   </li>
  80.  * </ul>
  81.  *
  82.  * <p> The proper way to use this class is to call the {@link GravityFieldFactory}
  83.  *  which will determine which reader to use with the selected gravity field file.</p>
  84.  *
  85.  * @see GravityFields
  86.  * @author Luc Maisonobe
  87.  */
  88. public class ICGEMFormatReader extends PotentialCoefficientsReader {

  89.     /** Product type. */
  90.     private static final String PRODUCT_TYPE            = "product_type";

  91.     /** Gravity field product type. */
  92.     private static final String GRAVITY_FIELD           = "gravity_field";

  93.     /** Gravity constant marker. */
  94.     private static final String GRAVITY_CONSTANT        = "gravity_constant";

  95.     /** Reference radius. */
  96.     private static final String REFERENCE_RADIUS        = "radius";

  97.     /** Max degree. */
  98.     private static final String MAX_DEGREE              = "max_degree";

  99.     /** Tide system indicator. */
  100.     private static final String TIDE_SYSTEM_INDICATOR   = "tide_system";

  101.     /** Indicator value for zero-tide system. */
  102.     private static final String ZERO_TIDE               = "zero_tide";

  103.     /** Indicator value for tide-free system. */
  104.     private static final String TIDE_FREE               = "tide_free";

  105.     /** Indicator value for unknown tide system. */
  106.     private static final String TIDE_UNKNOWN            = "unknown";

  107.     /** Normalization indicator. */
  108.     private static final String NORMALIZATION_INDICATOR = "norm";

  109.     /** Indicator value for normalized coefficients. */
  110.     private static final String NORMALIZED              = "fully_normalized";

  111.     /** Indicator value for un-normalized coefficients. */
  112.     private static final String UNNORMALIZED            = "unnormalized";

  113.     /** End of header marker. */
  114.     private static final String END_OF_HEADER           = "end_of_head";

  115.     /** Gravity field coefficient. */
  116.     private static final String GFC                     = "gfc";

  117.     /** Time stamped gravity field coefficient. */
  118.     private static final String GFCT                    = "gfct";

  119.     /** Gravity field coefficient first time derivative. */
  120.     private static final String DOT                     = "dot";

  121.     /** Gravity field coefficient trend. */
  122.     private static final String TRND                    = "trnd";

  123.     /** Gravity field coefficient sine amplitude. */
  124.     private static final String ASIN                    = "asin";

  125.     /** Gravity field coefficient cosine amplitude. */
  126.     private static final String ACOS                    = "acos";

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

  129.     /** Indicator for normalized coefficients. */
  130.     private boolean normalized;

  131.     /** Reference date. */
  132.     private AbsoluteDate referenceDate;

  133.     /** Secular trend of the cosine coefficients. */
  134.     private final List<List<Double>> cTrend;

  135.     /** Secular trend of the sine coefficients. */
  136.     private final List<List<Double>> sTrend;

  137.     /** Cosine part of the cosine coefficients pulsation. */
  138.     private final Map<Double, List<List<Double>>> cCos;

  139.     /** Sine part of the cosine coefficients pulsation. */
  140.     private final Map<Double, List<List<Double>>> cSin;

  141.     /** Cosine part of the sine coefficients pulsation. */
  142.     private final Map<Double, List<List<Double>>> sCos;

  143.     /** Sine part of the sine coefficients pulsation. */
  144.     private final Map<Double, List<List<Double>>> sSin;

  145.     /** Simple constructor.
  146.      *
  147.      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
  148.      *
  149.      * @param supportedNames regular expression for supported files names
  150.      * @param missingCoefficientsAllowed if true, allows missing coefficients in the input data
  151.      * @see #ICGEMFormatReader(String, boolean, TimeScale)
  152.      */
  153.     @DefaultDataContext
  154.     public ICGEMFormatReader(final String supportedNames, final boolean missingCoefficientsAllowed) {
  155.         this(supportedNames, missingCoefficientsAllowed,
  156.                 DataContext.getDefault().getTimeScales().getTT());
  157.     }

  158.     /**
  159.      * Simple constructor.
  160.      *
  161.      * @param supportedNames             regular expression for supported files names
  162.      * @param missingCoefficientsAllowed if true, allows missing coefficients in the input
  163.      *                                   data
  164.      * @param timeScale                  to use when parsing dates.
  165.      * @since 10.1
  166.      */
  167.     public ICGEMFormatReader(final String supportedNames,
  168.                              final boolean missingCoefficientsAllowed,
  169.                              final TimeScale timeScale) {
  170.         super(supportedNames, missingCoefficientsAllowed, timeScale);
  171.         referenceDate = null;
  172.         cTrend = new ArrayList<>();
  173.         sTrend = new ArrayList<>();
  174.         cCos   = new HashMap<>();
  175.         cSin   = new HashMap<>();
  176.         sCos   = new HashMap<>();
  177.         sSin   = new HashMap<>();
  178.     }

  179.     /** {@inheritDoc} */
  180.     public void loadData(final InputStream input, final String name)
  181.         throws IOException, ParseException, OrekitException {

  182.         // reset the indicator before loading any data
  183.         setReadComplete(false);
  184.         referenceDate = null;
  185.         cTrend.clear();
  186.         sTrend.clear();
  187.         cCos.clear();
  188.         cSin.clear();
  189.         sCos.clear();
  190.         sSin.clear();

  191.         // by default, the field is normalized with unknown tide system
  192.         // (will be overridden later if non-default)
  193.         normalized = true;
  194.         TideSystem tideSystem = TideSystem.UNKNOWN;

  195.         boolean inHeader = true;
  196.         double[][] c     = null;
  197.         double[][] s     = null;
  198.         boolean okCoeffs = false;
  199.         int lineNumber   = 0;
  200.         String line      = null;
  201.         try (BufferedReader r = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
  202.             for (line = r.readLine(); line != null; line = r.readLine()) {
  203.                 ++lineNumber;
  204.                 line = line.trim();
  205.                 if (line.length() == 0) {
  206.                     continue;
  207.                 }
  208.                 final String[] tab = SEPARATOR.split(line);
  209.                 if (inHeader) {
  210.                     if ((tab.length == 2) && PRODUCT_TYPE.equals(tab[0])) {
  211.                         if (!GRAVITY_FIELD.equals(tab[1])) {
  212.                             throw new OrekitParseException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  213.                                                            lineNumber, name, line);
  214.                         }
  215.                     } else if ((tab.length == 2) && tab[0].endsWith(GRAVITY_CONSTANT)) {
  216.                         setMu(parseDouble(tab[1]));
  217.                     } else if ((tab.length == 2) && REFERENCE_RADIUS.equals(tab[0])) {
  218.                         setAe(parseDouble(tab[1]));
  219.                     } else if ((tab.length == 2) && MAX_DEGREE.equals(tab[0])) {

  220.                         final int degree = FastMath.min(getMaxParseDegree(), Integer.parseInt(tab[1]));
  221.                         final int order  = FastMath.min(getMaxParseOrder(), degree);
  222.                         c = buildTriangularArray(degree, order, missingCoefficientsAllowed() ? 0.0 : Double.NaN);
  223.                         s = buildTriangularArray(degree, order, missingCoefficientsAllowed() ? 0.0 : Double.NaN);

  224.                     } else if ((tab.length == 2) && TIDE_SYSTEM_INDICATOR.equals(tab[0])) {
  225.                         if (ZERO_TIDE.equals(tab[1])) {
  226.                             tideSystem = TideSystem.ZERO_TIDE;
  227.                         } else if (TIDE_FREE.equals(tab[1])) {
  228.                             tideSystem = TideSystem.TIDE_FREE;
  229.                         } else if (TIDE_UNKNOWN.equals(tab[1])) {
  230.                             tideSystem = TideSystem.UNKNOWN;
  231.                         } else {
  232.                             throw new OrekitParseException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  233.                                                            lineNumber, name, line);
  234.                         }
  235.                     } else if ((tab.length == 2) && NORMALIZATION_INDICATOR.equals(tab[0])) {
  236.                         if (NORMALIZED.equals(tab[1])) {
  237.                             normalized = true;
  238.                         } else if (UNNORMALIZED.equals(tab[1])) {
  239.                             normalized = false;
  240.                         } else {
  241.                             throw new OrekitParseException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  242.                                                            lineNumber, name, line);
  243.                         }
  244.                     } else if ((tab.length == 2) && END_OF_HEADER.equals(tab[0])) {
  245.                         inHeader = false;
  246.                     }
  247.                 } else {
  248.                     if ((tab.length == 7 && GFC.equals(tab[0])) || (tab.length == 8 && GFCT.equals(tab[0]))) {

  249.                         final int i = Integer.parseInt(tab[1]);
  250.                         final int j = Integer.parseInt(tab[2]);
  251.                         if (i < c.length && j < c[i].length) {

  252.                             parseCoefficient(tab[3], c, i, j, "C", name);
  253.                             parseCoefficient(tab[4], s, i, j, "S", name);
  254.                             okCoeffs = true;

  255.                             if (tab.length == 8) {
  256.                                 // check the reference date (format yyyymmdd)
  257.                                 final DateComponents localRef = new DateComponents(Integer.parseInt(tab[7].substring(0, 4)),
  258.                                                                                    Integer.parseInt(tab[7].substring(4, 6)),
  259.                                                                                    Integer.parseInt(tab[7].substring(6, 8)));
  260.                                 if (referenceDate == null) {
  261.                                     // first reference found, store it
  262.                                     referenceDate = toDate(localRef);
  263.                                 } else if (!referenceDate.equals(toDate(localRef))) {
  264.                                     throw new OrekitException(OrekitMessages.SEVERAL_REFERENCE_DATES_IN_GRAVITY_FIELD,
  265.                                                               referenceDate, toDate(localRef), name);
  266.                                 }
  267.                             }

  268.                         }
  269.                     } else if (tab.length == 7 && (DOT.equals(tab[0]) || TRND.equals(tab[0]))) {

  270.                         final int i = Integer.parseInt(tab[1]);
  271.                         final int j = Integer.parseInt(tab[2]);
  272.                         if (i < c.length && j < c[i].length) {

  273.                             // store the secular trend coefficients
  274.                             extendListOfLists(cTrend, i, j, 0.0);
  275.                             extendListOfLists(sTrend, i, j, 0.0);
  276.                             parseCoefficient(tab[3], cTrend, i, j, "Ctrend", name);
  277.                             parseCoefficient(tab[4], sTrend, i, j, "Strend", name);

  278.                         }

  279.                     } else if (tab.length == 8 && (ASIN.equals(tab[0]) || ACOS.equals(tab[0]))) {

  280.                         final int i = Integer.parseInt(tab[1]);
  281.                         final int j = Integer.parseInt(tab[2]);
  282.                         if (i < c.length && j < c[i].length) {

  283.                             // extract arrays corresponding to period
  284.                             final Double period = Double.valueOf(tab[7]);
  285.                             if (!cCos.containsKey(period)) {
  286.                                 cCos.put(period, new ArrayList<>());
  287.                                 cSin.put(period, new ArrayList<>());
  288.                                 sCos.put(period, new ArrayList<>());
  289.                                 sSin.put(period, new ArrayList<>());
  290.                             }
  291.                             final List<List<Double>> cCosPeriod = cCos.get(period);
  292.                             final List<List<Double>> cSinPeriod = cSin.get(period);
  293.                             final List<List<Double>> sCosPeriod = sCos.get(period);
  294.                             final List<List<Double>> sSinPeriod = sSin.get(period);

  295.                             // store the pulsation coefficients
  296.                             extendListOfLists(cCosPeriod, i, j, 0.0);
  297.                             extendListOfLists(cSinPeriod, i, j, 0.0);
  298.                             extendListOfLists(sCosPeriod, i, j, 0.0);
  299.                             extendListOfLists(sSinPeriod, i, j, 0.0);
  300.                             if (ACOS.equals(tab[0])) {
  301.                                 parseCoefficient(tab[3], cCosPeriod, i, j, "Ccos", name);
  302.                                 parseCoefficient(tab[4], sCosPeriod, i, j, "SCos", name);
  303.                             } else {
  304.                                 parseCoefficient(tab[3], cSinPeriod, i, j, "Csin", name);
  305.                                 parseCoefficient(tab[4], sSinPeriod, i, j, "Ssin", name);
  306.                             }

  307.                         }

  308.                     } else {
  309.                         throw new OrekitParseException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  310.                                                        lineNumber, name, line);
  311.                     }
  312.                 }

  313.             }
  314.         } catch (NumberFormatException nfe) {
  315.             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  316.                                       lineNumber, name, line);
  317.         }


  318.         if (missingCoefficientsAllowed() && c.length > 0 && c[0].length > 0) {
  319.             // ensure at least the (0, 0) element is properly set
  320.             if (Precision.equals(c[0][0], 0.0, 0)) {
  321.                 c[0][0] = 1.0;
  322.             }
  323.         }

  324.         if (Double.isNaN(getAe()) || Double.isNaN(getMu()) || !okCoeffs) {
  325.             String loaderName = getClass().getName();
  326.             loaderName = loaderName.substring(loaderName.lastIndexOf('.') + 1);
  327.             throw new OrekitException(OrekitMessages.UNEXPECTED_FILE_FORMAT_ERROR_FOR_LOADER,
  328.                                       name, loaderName);
  329.         }

  330.         setRawCoefficients(normalized, c, s, name);
  331.         setTideSystem(tideSystem);
  332.         setReadComplete(true);

  333.     }

  334.     /** Get a provider for read spherical harmonics coefficients.
  335.      * <p>
  336.      * ICGEM fields do include time-dependent parts which are taken into account
  337.      * in the returned provider.
  338.      * </p>
  339.      * @param wantNormalized if true, the provider will provide normalized coefficients,
  340.      * otherwise it will provide un-normalized coefficients
  341.      * @param degree maximal degree
  342.      * @param order maximal order
  343.      * @return a new provider
  344.      * @since 6.0
  345.      */
  346.     public RawSphericalHarmonicsProvider getProvider(final boolean wantNormalized,
  347.                                                      final int degree, final int order) {

  348.         RawSphericalHarmonicsProvider provider = getConstantProvider(wantNormalized, degree, order);
  349.         if (cTrend.isEmpty() && cCos.isEmpty()) {
  350.             // there are no time-dependent coefficients
  351.             return provider;
  352.         }

  353.         if (!cTrend.isEmpty()) {

  354.             // add the secular trend layer
  355.             final double[][] cArrayTrend = toArray(cTrend);
  356.             final double[][] sArrayTrend = toArray(sTrend);
  357.             rescale(1.0 / Constants.JULIAN_YEAR, normalized, cArrayTrend, sArrayTrend, wantNormalized, cArrayTrend, sArrayTrend);
  358.             provider = new SecularTrendSphericalHarmonics(provider, referenceDate, cArrayTrend, sArrayTrend);

  359.         }

  360.         for (final Map.Entry<Double, List<List<Double>>> entry : cCos.entrySet()) {

  361.             final double period = entry.getKey();

  362.             // add the pulsating layer for the current period
  363.             final double[][] cArrayCos = toArray(cCos.get(period));
  364.             final double[][] sArrayCos = toArray(sCos.get(period));
  365.             final double[][] cArraySin = toArray(cSin.get(period));
  366.             final double[][] sArraySin = toArray(sSin.get(period));
  367.             rescale(1.0, normalized, cArrayCos, sArrayCos, wantNormalized, cArrayCos, sArrayCos);
  368.             rescale(1.0, normalized, cArraySin, sArraySin, wantNormalized, cArraySin, sArraySin);
  369.             provider = new PulsatingSphericalHarmonics(provider, period * Constants.JULIAN_YEAR,
  370.                                                        cArrayCos, cArraySin, sArrayCos, sArraySin);

  371.         }

  372.         return provider;

  373.     }

  374. }