ICGEMFormatReader.java

  1. /* Copyright 2002-2015 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.text.ParseException;
  23. import java.util.ArrayList;
  24. import java.util.HashMap;
  25. import java.util.List;
  26. import java.util.Map;

  27. import org.apache.commons.math3.util.FastMath;
  28. import org.apache.commons.math3.util.Precision;
  29. import org.orekit.errors.OrekitException;
  30. import org.orekit.errors.OrekitMessages;
  31. import org.orekit.time.DateComponents;
  32. import org.orekit.utils.Constants;

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

  75.     /** Product type. */
  76.     private static final String PRODUCT_TYPE            = "product_type";

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

  79.     /** Gravity constant marker. */
  80.     private static final String GRAVITY_CONSTANT        = "earth_gravity_constant";

  81.     /** Reference radius. */
  82.     private static final String REFERENCE_RADIUS        = "radius";

  83.     /** Max degree. */
  84.     private static final String MAX_DEGREE              = "max_degree";

  85.     /** Tide system indicator. */
  86.     private static final String TIDE_SYSTEM_INDICATOR   = "tide_system";

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

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

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

  93.     /** Normalization indicator. */
  94.     private static final String NORMALIZATION_INDICATOR = "norm";

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

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

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

  101.     /** Gravity field coefficient. */
  102.     private static final String GFC                     = "gfc";

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

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

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

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

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

  113.     /** Tide system. */
  114.     private TideSystem tideSystem;

  115.     /** Indicator for normalized coeffcients. */
  116.     private boolean normalized;

  117.     /** Reference date. */
  118.     private DateComponents referenceDate;

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

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

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

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

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

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

  131.     /** Simple constructor.
  132.      * @param supportedNames regular expression for supported files names
  133.      * @param missingCoefficientsAllowed if true, allows missing coefficients in the input data
  134.      */
  135.     public ICGEMFormatReader(final String supportedNames, final boolean missingCoefficientsAllowed) {
  136.         super(supportedNames, missingCoefficientsAllowed);
  137.         referenceDate = null;
  138.         cTrend = new ArrayList<List<Double>>();
  139.         sTrend = new ArrayList<List<Double>>();
  140.         cCos   = new HashMap<Double, List<List<Double>>>();
  141.         cSin   = new HashMap<Double, List<List<Double>>>();
  142.         sCos   = new HashMap<Double, List<List<Double>>>();
  143.         sSin   = new HashMap<Double, List<List<Double>>>();
  144.     }

  145.     /** {@inheritDoc} */
  146.     public void loadData(final InputStream input, final String name)
  147.         throws IOException, ParseException, OrekitException {

  148.         // reset the indicator before loading any data
  149.         setReadComplete(false);
  150.         referenceDate = null;
  151.         cTrend.clear();
  152.         sTrend.clear();
  153.         cCos.clear();
  154.         cSin.clear();
  155.         sCos.clear();
  156.         sSin.clear();

  157.         // by default, the field is normalized with unknown tide system
  158.         // (will be overridden later if non-default)
  159.         normalized = true;
  160.         tideSystem = TideSystem.UNKNOWN;

  161.         final BufferedReader r = new BufferedReader(new InputStreamReader(input, "UTF-8"));
  162.         boolean inHeader = true;
  163.         double[][] c               = null;
  164.         double[][] s               = null;
  165.         boolean okCoeffs           = false;
  166.         int lineNumber   = 0;
  167.         for (String line = r.readLine(); line != null; line = r.readLine()) {
  168.             try {
  169.                 ++lineNumber;
  170.                 if (line.trim().length() == 0) {
  171.                     continue;
  172.                 }
  173.                 final String[] tab = line.split("\\s+");
  174.                 if (inHeader) {
  175.                     if ((tab.length == 2) && PRODUCT_TYPE.equals(tab[0])) {
  176.                         if (!GRAVITY_FIELD.equals(tab[1])) {
  177.                             throw OrekitException.createParseException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  178.                                                                        lineNumber, name, line);
  179.                         }
  180.                     } else if ((tab.length == 2) && GRAVITY_CONSTANT.equals(tab[0])) {
  181.                         setMu(parseDouble(tab[1]));
  182.                     } else if ((tab.length == 2) && REFERENCE_RADIUS.equals(tab[0])) {
  183.                         setAe(parseDouble(tab[1]));
  184.                     } else if ((tab.length == 2) && MAX_DEGREE.equals(tab[0])) {

  185.                         final int degree = FastMath.min(getMaxParseDegree(), Integer.parseInt(tab[1]));
  186.                         final int order  = FastMath.min(getMaxParseOrder(), degree);
  187.                         c = buildTriangularArray(degree, order, missingCoefficientsAllowed() ? 0.0 : Double.NaN);
  188.                         s = buildTriangularArray(degree, order, missingCoefficientsAllowed() ? 0.0 : Double.NaN);

  189.                     } else if ((tab.length == 2) && TIDE_SYSTEM_INDICATOR.equals(tab[0])) {
  190.                         if (ZERO_TIDE.equals(tab[1])) {
  191.                             tideSystem = TideSystem.ZERO_TIDE;
  192.                         } else if (TIDE_FREE.equals(tab[1])) {
  193.                             tideSystem = TideSystem.TIDE_FREE;
  194.                         } else if (TIDE_UNKNOWN.equals(tab[1])) {
  195.                             tideSystem = TideSystem.UNKNOWN;
  196.                         } else {
  197.                             throw OrekitException.createParseException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  198.                                                                        lineNumber, name, line);
  199.                         }
  200.                     } else if ((tab.length == 2) && NORMALIZATION_INDICATOR.equals(tab[0])) {
  201.                         if (NORMALIZED.equals(tab[1])) {
  202.                             normalized = true;
  203.                         } else if (UNNORMALIZED.equals(tab[1])) {
  204.                             normalized = false;
  205.                         } else {
  206.                             throw OrekitException.createParseException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  207.                                                                        lineNumber, name, line);
  208.                         }
  209.                     } else if ((tab.length == 2) && END_OF_HEADER.equals(tab[0])) {
  210.                         inHeader = false;
  211.                     }
  212.                 } else {
  213.                     if ((tab.length == 7 && GFC.equals(tab[0])) || (tab.length == 8 && GFCT.equals(tab[0]))) {

  214.                         final int i = Integer.parseInt(tab[1]);
  215.                         final int j = Integer.parseInt(tab[2]);
  216.                         if (i < c.length && j < c[i].length) {

  217.                             parseCoefficient(tab[3], c, i, j, "C", name);
  218.                             parseCoefficient(tab[4], s, i, j, "S", name);
  219.                             okCoeffs = true;

  220.                             if (tab.length == 8) {
  221.                                 // check the reference date (format yyyymmdd)
  222.                                 final DateComponents localRef = new DateComponents(Integer.parseInt(tab[7].substring(0, 4)),
  223.                                                                                    Integer.parseInt(tab[7].substring(4, 6)),
  224.                                                                                    Integer.parseInt(tab[7].substring(6, 8)));
  225.                                 if (referenceDate == null) {
  226.                                     // first reference found, store it
  227.                                     referenceDate = localRef;
  228.                                 } else if (!referenceDate.equals(localRef)) {
  229.                                     throw new OrekitException(OrekitMessages.SEVERAL_REFERENCE_DATES_IN_GRAVITY_FIELD,
  230.                                                               referenceDate, localRef, name);
  231.                                 }
  232.                             }

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

  235.                         final int i = Integer.parseInt(tab[1]);
  236.                         final int j = Integer.parseInt(tab[2]);
  237.                         if (i < c.length && j < c[i].length) {

  238.                             // store the secular trend coefficients
  239.                             extendListOfLists(cTrend, i, j, 0.0);
  240.                             extendListOfLists(sTrend, i, j, 0.0);
  241.                             parseCoefficient(tab[3], cTrend, i, j, "Ctrend", name);
  242.                             parseCoefficient(tab[4], sTrend, i, j, "Strend", name);

  243.                         }

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

  245.                         final int i = Integer.parseInt(tab[1]);
  246.                         final int j = Integer.parseInt(tab[2]);
  247.                         if (i < c.length && j < c[i].length) {

  248.                             // extract arrays corresponding to period
  249.                             final Double period = Double.valueOf(tab[7]);
  250.                             if (!cCos.containsKey(period)) {
  251.                                 cCos.put(period, new ArrayList<List<Double>>());
  252.                                 cSin.put(period, new ArrayList<List<Double>>());
  253.                                 sCos.put(period, new ArrayList<List<Double>>());
  254.                                 sSin.put(period, new ArrayList<List<Double>>());
  255.                             }
  256.                             final List<List<Double>> cCosPeriod = cCos.get(period);
  257.                             final List<List<Double>> cSinPeriod = cSin.get(period);
  258.                             final List<List<Double>> sCosPeriod = sCos.get(period);
  259.                             final List<List<Double>> sSinPeriod = sSin.get(period);

  260.                             // store the pulsation coefficients
  261.                             extendListOfLists(cCosPeriod, i, j, 0.0);
  262.                             extendListOfLists(cSinPeriod, i, j, 0.0);
  263.                             extendListOfLists(sCosPeriod, i, j, 0.0);
  264.                             extendListOfLists(sSinPeriod, i, j, 0.0);
  265.                             if (ACOS.equals(tab[0])) {
  266.                                 parseCoefficient(tab[3], cCosPeriod, i, j, "Ccos", name);
  267.                                 parseCoefficient(tab[4], sCosPeriod, i, j, "SCos", name);
  268.                             } else {
  269.                                 parseCoefficient(tab[3], cSinPeriod, i, j, "Csin", name);
  270.                                 parseCoefficient(tab[4], sSinPeriod, i, j, "Ssin", name);
  271.                             }

  272.                         }

  273.                     } else {
  274.                         throw OrekitException.createParseException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  275.                                                                    lineNumber, name, line);
  276.                     }
  277.                 }
  278.             } catch (NumberFormatException nfe) {
  279.                 final ParseException pe = OrekitException.createParseException(
  280.                         OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE, lineNumber, name, line);
  281.                 pe.initCause(nfe);
  282.                 throw pe;
  283.             }
  284.         }

  285.         if (missingCoefficientsAllowed() && c.length > 0 && c[0].length > 0) {
  286.             // ensure at least the (0, 0) element is properly set
  287.             if (Precision.equals(c[0][0], 0.0, 1)) {
  288.                 c[0][0] = 1.0;
  289.             }
  290.         }

  291.         if (Double.isNaN(getAe()) || Double.isNaN(getMu()) || !okCoeffs) {
  292.             String loaderName = getClass().getName();
  293.             loaderName = loaderName.substring(loaderName.lastIndexOf('.') + 1);
  294.             throw new OrekitException(OrekitMessages.UNEXPECTED_FILE_FORMAT_ERROR_FOR_LOADER,
  295.                                       name, loaderName);
  296.         }

  297.         setRawCoefficients(normalized, c, s, name);
  298.         setTideSystem(tideSystem);
  299.         setReadComplete(true);

  300.     }

  301.     /** Get a provider for read spherical harmonics coefficients.
  302.      * <p>
  303.      * ICGEM fields do include time-dependent parts which are taken into account
  304.      * in the returned provider.
  305.      * </p>
  306.      * @param wantNormalized if true, the provider will provide normalized coefficients,
  307.      * otherwise it will provide un-normalized coefficients
  308.      * @param degree maximal degree
  309.      * @param order maximal order
  310.      * @return a new provider
  311.      * @exception OrekitException if the requested maximal degree or order exceeds the
  312.      * available degree or order or if no gravity field has read yet
  313.      * @since 6.0
  314.      */
  315.     public RawSphericalHarmonicsProvider getProvider(final boolean wantNormalized,
  316.                                                      final int degree, final int order)
  317.         throws OrekitException {

  318.         RawSphericalHarmonicsProvider provider = getConstantProvider(wantNormalized, degree, order);
  319.         if (cTrend.isEmpty() && cCos.isEmpty()) {
  320.             // there are no time-dependent coefficients
  321.             return provider;
  322.         }

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

  324.             // add the secular trend layer
  325.             final double[][] cArrayTrend = toArray(cTrend);
  326.             final double[][] sArrayTrend = toArray(sTrend);
  327.             rescale(1.0 / Constants.JULIAN_YEAR, normalized, cArrayTrend, sArrayTrend, wantNormalized, cArrayTrend, sArrayTrend);
  328.             provider = new SecularTrendSphericalHarmonics(provider, referenceDate, cArrayTrend, sArrayTrend);

  329.         }

  330.         for (final Double period : cCos.keySet()) {

  331.             // add the pulsating layer for the current period
  332.             final double[][] cArrayCos = toArray(cCos.get(period));
  333.             final double[][] sArrayCos = toArray(sCos.get(period));
  334.             final double[][] cArraySin = toArray(cSin.get(period));
  335.             final double[][] sArraySin = toArray(sSin.get(period));
  336.             rescale(1.0, normalized, cArrayCos, sArrayCos, wantNormalized, cArrayCos, sArrayCos);
  337.             rescale(1.0, normalized, cArraySin, sArraySin, wantNormalized, cArraySin, sArraySin);
  338.             provider = new PulsatingSphericalHarmonics(provider, period * Constants.JULIAN_YEAR,
  339.                                                        cArrayCos, cArraySin, sArrayCos, sArraySin);

  340.         }

  341.         return provider;

  342.     }

  343. }