PoissonSeriesParser.java

  1. /* Copyright 2002-2018 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.data;

  18. import java.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.util.Arrays;
  23. import java.util.HashMap;
  24. import java.util.Map;
  25. import java.util.regex.Matcher;
  26. import java.util.regex.Pattern;

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

  32. /**
  33.  * Parser for {@link PoissonSeries Poisson series} files.
  34.  * <p>
  35.  * A Poisson series is composed of a time polynomial part and a non-polynomial
  36.  * part which consist in summation series. The {@link SeriesTerm series terms}
  37.  * are harmonic functions (combination of sines and cosines) of polynomial
  38.  * <em>arguments</em>. The polynomial arguments are combinations of luni-solar or
  39.  * planetary {@link BodiesElements elements}.
  40.  * </p>
  41.  * <p>
  42.  * The Poisson series files from IERS have various formats, with or without
  43.  * polynomial part, with or without planetary components, with or without
  44.  * period column, with terms of increasing degrees either in dedicated columns
  45.  * or in successive sections of the file ... This class attempts to read all the
  46.  * commonly found formats, by specifying the columns of interest.
  47.  * </p>
  48.  * <p>
  49.  * The handling of increasing degrees terms (i.e. sin, cos, t sin, t cos, t^2 sin,
  50.  * t^2 cos ...) is done as follows.
  51.  * </p>
  52.  * <ul>
  53.  *   <li>user must specify pairs of columns to be extracted at each line,
  54.  *       in increasing degree order</li>
  55.  *   <li>negative columns indices correspond to inexistent values that will be
  56.  *       replaced by 0.0)</li>
  57.  *   <li>file may provide section headers to specify a degree, which is added
  58.  *       to the current column degree</li>
  59.  * </ul>
  60.  * <p>
  61.  * A file from an old convention, like table 5.1 in IERS conventions 1996, uses
  62.  * separate columns for degree 0 and degree 1, and uses only sine for nutation in
  63.  * longitude and cosine for nutation in obliquity. It reads as follows:
  64.  * </p>
  65.  * <pre>
  66.  * ∆ψ = Σ (Ai+A'it) sin(ARGUMENT), ∆ε = Σ (Bi+B'it) cos(ARGUMENT)
  67.  *
  68.  *      MULTIPLIERS OF      PERIOD           LONGITUDE         OBLIQUITY
  69.  *  l    l'   F    D   Om     days         Ai       A'i       Bi       B'i
  70.  *
  71.  *  0    0    0    0    1   -6798.4    -171996    -174.2    92025      8.9
  72.  *  0    0    2   -2    2     182.6     -13187      -1.6     5736     -3.1
  73.  *  0    0    2    0    2      13.7      -2274      -0.2      977     -0.5
  74.  *  0    0    0    0    2   -3399.2       2062       0.2     -895      0.5
  75.  * </pre>
  76.  * <p>
  77.  * In order to parse the nutation in longitude from the previous table, the
  78.  * following settings should be used:
  79.  * </p>
  80.  * <ul>
  81.  *   <li>totalColumns   = 10 (see {@link #PoissonSeriesParser(int)})</li>
  82.  *   <li>firstDelaunay  =  1 (see {@link #withFirstDelaunay(int)})</li>
  83.  *   <li>no calls to {@link #withFirstPlanetary(int)} as there are no planetary columns in this table</li>
  84.  *   <li>sinCosColumns  =  7, -1 for degree 0 for Ai (see {@link #withSinCos(int, int, double, int, double)})</li>
  85.  *   <li>sinCosColumns  =  8, -1 for degree 1 for A'i (see {@link #withSinCos(int, int, double, int, double)})</li>
  86.  * </ul>
  87.  * <p>
  88.  * In order to parse the nutation in obliquity from the previous table, the
  89.  * following settings should be used:
  90.  * </p>
  91.  * <ul>
  92.  *   <li>totalColumns   = 10 (see {@link #PoissonSeriesParser(int)})</li>
  93.  *   <li>firstDelaunay  =  1 (see {@link #withFirstDelaunay(int)})</li>
  94.  *   <li>no calls to {@link #withFirstPlanetary(int)} as there are no planetary columns in this table</li>
  95.  *   <li>sinCosColumns  =  -1, 9 for degree 0 for Bi (see {@link #withSinCos(int, int, double, int, double)})</li>
  96.  *   <li>sinCosColumns  =  -1, 10 for degree 1 for B'i (see {@link #withSinCos(int, int, double, int, double)})</li>
  97.  * </ul>
  98.  * <p>
  99.  * A file from a recent convention, like table 5.3a in IERS conventions 2010, uses
  100.  * only two columns for sin and cos, and separate degrees in successive sections with
  101.  * dedicated headers. It reads as follows:
  102.  * </p>
  103.  * <pre>
  104.  * ---------------------------------------------------------------------------------------------------
  105.  *
  106.  * (unit microarcsecond; cut-off: 0.1 microarcsecond)
  107.  * (ARG being for various combination of the fundamental arguments of the nutation theory)
  108.  *
  109.  *   Sum_i[A_i * sin(ARG) + A"_i * cos(ARG)]
  110.  *
  111.  * + Sum_i[A'_i * sin(ARG) + A"'_i * cos(ARG)] * t           (see Chapter 5, Eq. (35))
  112.  *
  113.  * The Table below provides the values for A_i and A"_i (j=0) and then A'_i and A"'_i (j=1)
  114.  *
  115.  * The expressions for the fundamental arguments appearing in columns 4 to 8 (luni-solar part)
  116.  * and in columns 9 to 17 (planetary part) are those of the IERS Conventions 2003
  117.  *
  118.  * ----------------------------------------------------------------------------------------------------------
  119.  * j = 0  Number of terms = 1320
  120.  * ----------------------------------------------------------------------------------------------------------
  121.  *     i        A_i             A"_i     l    l'   F    D    Om  L_Me L_Ve  L_E L_Ma  L_J L_Sa  L_U L_Ne  p_A
  122.  * ----------------------------------------------------------------------------------------------------------
  123.  *     1   -17206424.18        3338.60    0    0    0    0    1    0    0    0    0    0    0    0    0    0
  124.  *     2    -1317091.22       -1369.60    0    0    2   -2    2    0    0    0    0    0    0    0    0    0
  125.  *     3     -227641.81         279.60    0    0    2    0    2    0    0    0    0    0    0    0    0    0
  126.  *     4      207455.40         -69.80    0    0    0    0    2    0    0    0    0    0    0    0    0    0
  127.  *     5      147587.70        1181.70    0    1    0    0    0    0    0    0    0    0    0    0    0    0
  128.  *
  129.  * ...
  130.  *
  131.  *  1319          -0.10           0.00    0    0    0    0    0    1    0   -3    0    0    0    0    0   -2
  132.  *  1320          -0.10           0.00    0    0    0    0    0    0    0    1    0    1   -2    0    0    0
  133.  *
  134.  * --------------------------------------------------------------------------------------------------------------
  135.  * j = 1  Number of terms = 38
  136.  * --------------------------------------------------------------------------------------------------------------
  137.  *    i          A'_i            A"'_i    l    l'   F    D   Om L_Me L_Ve  L_E L_Ma  L_J L_Sa  L_U L_Ne  p_A
  138.  * --------------------------------------------------------------------------------------------------------------
  139.  *  1321      -17418.82           2.89    0    0    0    0    1    0    0    0    0    0    0    0    0    0
  140.  *  1322        -363.71          -1.50    0    1    0    0    0    0    0    0    0    0    0    0    0    0
  141.  *  1323        -163.84           1.20    0    0    2   -2    2    0    0    0    0    0    0    0    0    0
  142.  *  1324         122.74           0.20    0    1    2   -2    2    0    0    0    0    0    0    0    0    0
  143.  * </pre>
  144.  * <p>
  145.  * In order to parse the nutation in longitude from the previous table, the
  146.  * following settings should be used:
  147.  * </p>
  148.  * <ul>
  149.  *   <li>totalColumns   = 17 (see {@link #PoissonSeriesParser(int)})</li>
  150.  *   <li>firstDelaunay  =  4 (see {@link #withFirstDelaunay(int)})</li>
  151.  *   <li>firstPlanetary =  9 (see {@link #withFirstPlanetary(int)})</li>
  152.  *   <li>sinCosColumns  =  2,3 (we specify only degree 0, so when we read
  153.  *       section j = 0 we read degree 0, when we read section j = 1 we read
  154.  *       degree 1, see {@link #withSinCos(int, int, double, int, double)} ...)</li>
  155.  * </ul>
  156.  * <p>
  157.  * A file from a recent convention, like table 6.5a in IERS conventions 2010, contains
  158.  * both Doodson arguments (τ, s, h, p, N', ps), Doodson numbers and Delaunay parameters.
  159.  * In this case, the coefficients for the Delaunay parameters must be <em>subtracted</em>
  160.  * from the τ = GMST + π tide parameter, so the signs in the files must be reversed
  161.  * in order to match the Doodson arguments and Doodson numbers. This is done automatically
  162.  * (and consistency is checked) only when the {@link #withDoodson(int, int)} method is
  163.  * called at parser configuration time. Some other files use the γ = GMST + π tide parameter
  164.  * rather than Doodson τ argument and the coefficients for the Delaunay parameters must be
  165.  * <em>added</em> to the γ parameter, so no sign reversal is performed. In order to avoid
  166.  * ambiguity as the two cases are incompatible with each other, trying to add a configuration
  167.  * for τ by calling {@link #withDoodson(int, int)} and to also add a configuration for γ by
  168.  * calling {@link #withGamma(int)} triggers an exception.
  169.  * </p>
  170.  * <p>The table 6.5a file also contains a column for the waves names (the Darwin's symbol)
  171.  * which may be empty, so it must be identified explicitly by calling {@link
  172.  * #withOptionalColumn(int)}. The 6.5a table reads as follows:
  173.  * </p>
  174.  * <pre>
  175.  * The in-phase (ip) amplitudes (A₁ δkfR Hf) and the out-of-phase (op) amplitudes (A₁ δkfI Hf)
  176.  * of the corrections for frequency dependence of k₂₁⁽⁰⁾, taking the nominal value k₂₁ for the
  177.  * diurnal tides as (0.29830 − i 0.00144). Units: 10⁻¹² . The entries for δkfR and δkfI are in
  178.  * units of 10⁻⁵. Multipliers of the Doodson arguments identifying the tidal terms are given,
  179.  * as also those of the Delaunay variables characterizing the nutations produced by these
  180.  * terms.
  181.  *
  182.  * Name   deg/hr    Doodson  τ  s  h  p  N' ps   l  l' F  D  Ω  δkfR  δkfI     Amp.    Amp.
  183.  *                    No.                                       /10−5 /10−5    (ip)    (op)
  184.  *   2Q₁ 12.85429   125,755  1 -3  0  2   0  0   2  0  2  0  2    -29     3    -0.1     0.0
  185.  *    σ₁ 12.92714   127,555  1 -3  2  0   0  0   0  0  2  2  2    -30     3    -0.1     0.0
  186.  *       13.39645   135,645  1 -2  0  1  -1  0   1  0  2  0  1    -45     5    -0.1     0.0
  187.  *    Q₁ 13.39866   135,655  1 -2  0  1   0  0   1  0  2  0  2    -46     5    -0.7     0.1
  188.  *    ρ₁ 13.47151   137,455  1 -2  2 -1   0  0  -1  0  2  2  2    -49     5    -0.1     0.0
  189.  * </pre>
  190.  * <ul>
  191.  *   <li>totalColumns   = 18 (see {@link #PoissonSeriesParser(int)})</li>
  192.  *   <li>optionalColumn =  1 (see {@link #withOptionalColumn(int)})</li>
  193.  *   <li>firstDoodson, Doodson number = 4, 3 (see {@link #withDoodson(int, int)})</li>
  194.  *   <li>firstDelaunay  =  10 (see {@link #withFirstDelaunay(int)})</li>
  195.  *   <li>sinCosColumns  =  17, 18, see {@link #withSinCos(int, int, double, int, double)} ...)</li>
  196.  * </ul>
  197.  * <p>
  198.  * Our parsing algorithm involves adding the section degree from the "j = 0, 1, 2 ..." header
  199.  * to the column degree. A side effect of this algorithm is that it is theoretically possible
  200.  * to mix both formats and have for example degree two term appear as degree 2 column in section
  201.  * j=0 and as degree 1 column in section j=1 and as degree 0 column in section j=2. This case
  202.  * is not expected to be encountered in practice. The real files use either several columns
  203.  * <em>or</em> several sections, but not both at the same time.
  204.  * </p>
  205.  *
  206.  * @author Luc Maisonobe
  207.  * @see SeriesTerm
  208.  * @see PolynomialNutation
  209.  * @since 6.1
  210.  */
  211. public class PoissonSeriesParser {

  212.     /** Default pattern for fields with unknown type (non-space characters). */
  213.     private static final String  UNKNOWN_TYPE_PATTERN = "\\S+";

  214.     /** Pattern for optional fields (either nothing or non-space characters). */
  215.     private static final String  OPTIONAL_FIELD_PATTERN = "\\S*";

  216.     /** Pattern for fields with integer type. */
  217.     private static final String  INTEGER_TYPE_PATTERN = "[-+]?\\p{Digit}+";

  218.     /** Pattern for fields with real type. */
  219.     private static final String  REAL_TYPE_PATTERN = "[-+]?(?:(?:\\p{Digit}+(?:\\.\\p{Digit}*)?)|(?:\\.\\p{Digit}+))(?:[eE][-+]?\\p{Digit}+)?";

  220.     /** Pattern for fields with Doodson number. */
  221.     private static final String  DOODSON_TYPE_PATTERN = "\\p{Digit}{2,3}[.,]\\p{Digit}{3}";

  222.     /** Parser for the polynomial part. */
  223.     private final PolynomialParser polynomialParser;

  224.     /** Fields patterns. */
  225.     private final String[] fieldsPatterns;

  226.     /** Optional column (counting from 1). */
  227.     private final int optional;

  228.     /** Column of the γ = GMST + π tide multiplier (counting from 1). */
  229.     private final int gamma;

  230.     /** Column of the first Doodson multiplier (counting from 1). */
  231.     private final int firstDoodson;

  232.     /** Column of the Doodson number (counting from 1). */
  233.     private final int doodson;

  234.     /** Column of the first Delaunay multiplier (counting from 1). */
  235.     private final int firstDelaunay;

  236.     /** Column of the first planetary multiplier (counting from 1). */
  237.     private final int firstPlanetary;

  238.     /** columns of the sine and cosine coefficients for successive degrees.
  239.      * <p>
  240.      * The ordering is: sin, cos, t sin, t cos, t^2 sin, t^2 cos ...
  241.      * </p>
  242.      */
  243.     private final int[] sinCosColumns;

  244.     /** Multiplicative factors to use for various columns. */
  245.     private final double[] sinCosFactors;

  246.     /** Build a parser for a Poisson series from an IERS table file.
  247.      * @param polynomialParser polynomial parser to use
  248.      * @param fieldsPatterns patterns for fields
  249.      * @param optional optional column
  250.      * @param gamma column of the GMST tide multiplier
  251.      * @param firstDoodson column of the first Doodson multiplier
  252.      * @param doodson column of the Doodson number
  253.      * @param firstDelaunay column of the first Delaunay multiplier
  254.      * @param firstPlanetary column of the first planetary multiplier
  255.      * @param sinCosColumns columns of the sine and cosine coefficients
  256.      * @param factors multiplicative factors to use for various columns
  257.      */
  258.     private PoissonSeriesParser(final PolynomialParser polynomialParser,
  259.                                 final String[] fieldsPatterns, final int optional,
  260.                                 final int gamma, final int firstDoodson,
  261.                                 final int doodson, final int firstDelaunay,
  262.                                 final int firstPlanetary, final int[] sinCosColumns,
  263.                                 final double[] factors) {
  264.         this.polynomialParser = polynomialParser;
  265.         this.fieldsPatterns   = fieldsPatterns;
  266.         this.optional         = optional;
  267.         this.gamma            = gamma;
  268.         this.firstDoodson     = firstDoodson;
  269.         this.doodson          = doodson;
  270.         this.firstDelaunay    = firstDelaunay;
  271.         this.firstPlanetary   = firstPlanetary;
  272.         this.sinCosColumns    = sinCosColumns;
  273.         this.sinCosFactors    = factors;
  274.     }

  275.     /** Build a parser for a Poisson series from an IERS table file.
  276.      * @param totalColumns total number of columns in the non-polynomial sections
  277.      */
  278.     public PoissonSeriesParser(final int totalColumns) {
  279.         this(null, createInitialFieldsPattern(totalColumns), -1,
  280.              -1, -1, -1, -1, -1, new int[0], new double[0]);
  281.     }

  282.     /** Create an array with only non-space fields patterns.
  283.      * @param totalColumns total number of columns
  284.      * @return a new fields pattern array
  285.      */
  286.     private static String[] createInitialFieldsPattern(final int totalColumns) {
  287.         final String[] patterns = new String[totalColumns];
  288.         setPatterns(patterns, 1, totalColumns, UNKNOWN_TYPE_PATTERN);
  289.         return patterns;
  290.     }

  291.     /** Set fields patterns.
  292.      * @param array fields pattern array to modify
  293.      * @param first first column to set (counting from 1), do nothing if non-positive
  294.      * @param count number of columns to set
  295.      * @param pattern pattern to use
  296.      */
  297.     private static void setPatterns(final String[] array, final int first, final int count,
  298.                                     final String pattern) {
  299.         if (first > 0) {
  300.             Arrays.fill(array, first - 1, first - 1 + count, pattern);
  301.         }
  302.     }

  303.     /** Set up polynomial part parsing.
  304.      * @param freeVariable name of the free variable in the polynomial part
  305.      * @param unit default unit for polynomial, if not explicit within the file
  306.      * @return a new parser, with polynomial parser updated
  307.      */
  308.     public PoissonSeriesParser withPolynomialPart(final char freeVariable, final PolynomialParser.Unit unit) {
  309.         return new PoissonSeriesParser(new PolynomialParser(freeVariable, unit), fieldsPatterns, optional,
  310.                                        gamma, firstDoodson, doodson, firstDelaunay,
  311.                                        firstPlanetary, sinCosColumns, sinCosFactors);
  312.     }

  313.     /** Set up optional column.
  314.      * <p>
  315.      * Optional columns typically appears in tides-related files, as some waves have
  316.      * specific names (χ₁, M₂, ...) and other waves don't have names and hence are
  317.      * replaced by spaces in the corresponding file line.
  318.      * </p>
  319.      * <p>
  320.      * At most one column may be optional.
  321.      * </p>
  322.      * @param column optional column (counting from 1)
  323.      * @return a new parser, with updated columns settings
  324.      */
  325.     public PoissonSeriesParser withOptionalColumn(final int column) {

  326.         // update the fields pattern to expect 1 optional field at the right index
  327.         final String[] newFieldsPatterns = fieldsPatterns.clone();
  328.         setPatterns(newFieldsPatterns, optional, 1, UNKNOWN_TYPE_PATTERN);
  329.         setPatterns(newFieldsPatterns, column,   1, OPTIONAL_FIELD_PATTERN);

  330.         return new PoissonSeriesParser(polynomialParser, newFieldsPatterns, column,
  331.                                        gamma, firstDoodson, doodson, firstDelaunay,
  332.                                        firstPlanetary, sinCosColumns, sinCosFactors);

  333.     }

  334.     /** Set up column of GMST tide multiplier.
  335.      * @param column column of the GMST tide multiplier (counting from 1)
  336.      * @return a new parser, with updated columns settings
  337.      * @exception OrekitException if τ has been configured by a previous call
  338.      * to {@link #withDoodson(int, int)}
  339.      * @see #withDoodson(int, int)
  340.      */
  341.     public PoissonSeriesParser withGamma(final int column) throws OrekitException {

  342.         // check we don't try to have both τ and γ configured at the same time
  343.         if (firstDoodson > 0 && column > 0) {
  344.             throw new OrekitException(OrekitMessages.CANNOT_PARSE_BOTH_TAU_AND_GAMMA);
  345.         }

  346.         // update the fields pattern to expect 1 integer at the right index
  347.         final String[] newFieldsPatterns = fieldsPatterns.clone();
  348.         setPatterns(newFieldsPatterns, gamma,  1, UNKNOWN_TYPE_PATTERN);
  349.         setPatterns(newFieldsPatterns, column, 1, INTEGER_TYPE_PATTERN);

  350.         return new PoissonSeriesParser(polynomialParser, newFieldsPatterns, optional,
  351.                                        column, firstDoodson, doodson, firstDelaunay,
  352.                                        firstPlanetary, sinCosColumns, sinCosFactors);

  353.     }

  354.     /** Set up columns for Doodson multipliers and Doodson number.
  355.      * @param firstMultiplierColumn column of the first Doodson multiplier which
  356.      * corresponds to τ (counting from 1)
  357.      * @param numberColumn column of the Doodson number (counting from 1)
  358.      * @return a new parser, with updated columns settings
  359.      * @exception OrekitException if γ has been configured by a previous call
  360.      * to {@link #withGamma(int)}
  361.      * @see #withGamma(int)
  362.      * @see #withFirstDelaunay(int)
  363.      */
  364.     public PoissonSeriesParser withDoodson(final int firstMultiplierColumn, final int numberColumn)
  365.         throws OrekitException {

  366.         // check we don't try to have both τ and γ configured at the same time
  367.         if (gamma > 0 && firstMultiplierColumn > 0) {
  368.             throw new OrekitException(OrekitMessages.CANNOT_PARSE_BOTH_TAU_AND_GAMMA);
  369.         }

  370.         final String[] newFieldsPatterns = fieldsPatterns.clone();

  371.         // update the fields pattern to expect 6 integers at the right indices
  372.         setPatterns(newFieldsPatterns, firstDoodson,          6, UNKNOWN_TYPE_PATTERN);
  373.         setPatterns(newFieldsPatterns, firstMultiplierColumn, 6, INTEGER_TYPE_PATTERN);

  374.         // update the fields pattern to expect 1 Doodson number at the right index
  375.         setPatterns(newFieldsPatterns, doodson,      1, UNKNOWN_TYPE_PATTERN);
  376.         setPatterns(newFieldsPatterns, numberColumn, 1, DOODSON_TYPE_PATTERN);

  377.         return new PoissonSeriesParser(polynomialParser, newFieldsPatterns, optional,
  378.                                        gamma, firstMultiplierColumn, numberColumn, firstDelaunay,
  379.                                        firstPlanetary, sinCosColumns, sinCosFactors);

  380.     }

  381.     /** Set up first column of Delaunay multiplier.
  382.      * @param firstColumn column of the first Delaunay multiplier (counting from 1)
  383.      * @return a new parser, with updated columns settings
  384.      */
  385.     public PoissonSeriesParser withFirstDelaunay(final int firstColumn) {

  386.         // update the fields pattern to expect 5 integers at the right indices
  387.         final String[] newFieldsPatterns = fieldsPatterns.clone();
  388.         setPatterns(newFieldsPatterns, firstDelaunay, 5, UNKNOWN_TYPE_PATTERN);
  389.         setPatterns(newFieldsPatterns, firstColumn,   5, INTEGER_TYPE_PATTERN);

  390.         return new PoissonSeriesParser(polynomialParser, newFieldsPatterns, optional,
  391.                                        gamma, firstDoodson, doodson, firstColumn,
  392.                                        firstPlanetary, sinCosColumns, sinCosFactors);

  393.     }

  394.     /** Set up first column of planetary multiplier.
  395.      * @param firstColumn column of the first planetary multiplier (counting from 1)
  396.      * @return a new parser, with updated columns settings
  397.      */
  398.     public PoissonSeriesParser withFirstPlanetary(final int firstColumn) {

  399.         // update the fields pattern to expect 9 integers at the right indices
  400.         final String[] newFieldsPatterns = fieldsPatterns.clone();
  401.         setPatterns(newFieldsPatterns, firstPlanetary, 9, UNKNOWN_TYPE_PATTERN);
  402.         setPatterns(newFieldsPatterns, firstColumn,    9, INTEGER_TYPE_PATTERN);

  403.         return new PoissonSeriesParser(polynomialParser, newFieldsPatterns, optional,
  404.                                        gamma, firstDoodson, doodson, firstDelaunay,
  405.                                        firstColumn, sinCosColumns, sinCosFactors);

  406.     }

  407.     /** Set up columns of the sine and cosine coefficients.
  408.      * @param degree degree to set up
  409.      * @param sinColumn column of the sine coefficient for t<sup>degree</sup> counting from 1
  410.      * (may be -1 if there are no sine coefficients)
  411.      * @param sinFactor multiplicative factor for the sine coefficient
  412.      * @param cosColumn column of the cosine coefficient for t<sup>degree</sup> counting from 1
  413.      * (may be -1 if there are no cosine coefficients)
  414.      * @param cosFactor multiplicative factor for the cosine coefficient
  415.      * @return a new parser, with updated columns settings
  416.      */
  417.     public PoissonSeriesParser withSinCos(final int degree,
  418.                                           final int sinColumn, final double sinFactor,
  419.                                           final int cosColumn, final double cosFactor) {

  420.         // update the sin/cos columns array
  421.         final int      maxDegree        = FastMath.max(degree, sinCosColumns.length / 2 - 1);
  422.         final int[]    newSinCosColumns = new int[2 * (maxDegree + 1)];
  423.         Arrays.fill(newSinCosColumns, -1);
  424.         System.arraycopy(sinCosColumns, 0, newSinCosColumns, 0, sinCosColumns.length);
  425.         newSinCosColumns[2 * degree]     = sinColumn;
  426.         newSinCosColumns[2 * degree + 1] = cosColumn;

  427.         final double[] newSinCosFactors = new double[2 * (maxDegree + 1)];
  428.         Arrays.fill(newSinCosFactors, Double.NaN);
  429.         System.arraycopy(sinCosFactors, 0, newSinCosFactors, 0, sinCosFactors.length);
  430.         newSinCosFactors[2 * degree]     = sinFactor;
  431.         newSinCosFactors[2 * degree + 1] = cosFactor;

  432.         // update the fields pattern to expect real numbers at the right indices
  433.         final String[] newFieldsPatterns = fieldsPatterns.clone();
  434.         if (2 * degree < sinCosColumns.length) {
  435.             setPatterns(newFieldsPatterns, sinCosColumns[2 * degree], 1, UNKNOWN_TYPE_PATTERN);
  436.         }
  437.         setPatterns(newFieldsPatterns, sinColumn, 1, REAL_TYPE_PATTERN);
  438.         if (2 * degree  + 1 < sinCosColumns.length) {
  439.             setPatterns(newFieldsPatterns, sinCosColumns[2 * degree + 1], 1, UNKNOWN_TYPE_PATTERN);
  440.         }
  441.         setPatterns(newFieldsPatterns, cosColumn, 1, REAL_TYPE_PATTERN);

  442.         return new PoissonSeriesParser(polynomialParser, newFieldsPatterns, optional,
  443.                                        gamma, firstDoodson, doodson, firstDelaunay,
  444.                                        firstPlanetary, newSinCosColumns, newSinCosFactors);

  445.     }

  446.     /** Parse a stream.
  447.      * @param stream stream containing the IERS table
  448.      * @param name name of the resource file (for error messages only)
  449.      * @return parsed Poisson series
  450.      * @exception OrekitException if stream is null or the table cannot be parsed
  451.      */
  452.     public PoissonSeries parse(final InputStream stream, final String name) throws OrekitException {

  453.         if (stream == null) {
  454.             throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, name);
  455.         }

  456.         // the degrees section header should read something like:
  457.         // j = 0  Nb of terms = 1306
  458.         // or something like:
  459.         // j = 0  Number  of terms = 1037
  460.         final Pattern degreeSectionHeaderPattern =
  461.             Pattern.compile("^\\p{Space}*j\\p{Space}*=\\p{Space}*(\\p{Digit}+)" +
  462.                             "[\\p{Alpha}\\p{Space}]+=\\p{Space}*(\\p{Digit}+)\\p{Space}*$");

  463.         // regular lines are simply a space separated list of numbers
  464.         final StringBuilder builder = new StringBuilder("^\\p{Space}*");
  465.         for (int i = 0; i < fieldsPatterns.length; ++i) {
  466.             builder.append("(");
  467.             builder.append(fieldsPatterns[i]);
  468.             builder.append(")");
  469.             builder.append((i < fieldsPatterns.length - 1) ? "\\p{Space}+" : "\\p{Space}*$");
  470.         }
  471.         final Pattern regularLinePattern = Pattern.compile(builder.toString());

  472.         try {

  473.             // setup the reader
  474.             final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
  475.             int lineNumber    =  0;
  476.             int expectedIndex = -1;
  477.             int nTerms        = -1;
  478.             int count         =  0;
  479.             int degree        =  0;

  480.             // prepare the container for the parsed data
  481.             PolynomialNutation polynomial;
  482.             if (polynomialParser == null) {
  483.                 // we don't expect any polynomial, we directly set the zero polynomial
  484.                 polynomial = new PolynomialNutation(new double[0]);
  485.             } else {
  486.                 // the dedicated parser will fill in the polynomial later
  487.                 polynomial = null;
  488.             }
  489.             final Map<Long, SeriesTerm> series = new HashMap<Long, SeriesTerm>();

  490.             for (String line = reader.readLine(); line != null; line = reader.readLine()) {

  491.                 // replace unicode minus sign ('−') by regular hyphen ('-') for parsing
  492.                 // such unicode characters occur in tables that are copy-pasted from PDF files
  493.                 line = line.replace('\u2212', '-');
  494.                 ++lineNumber;

  495.                 final Matcher regularMatcher = regularLinePattern.matcher(line);
  496.                 if (regularMatcher.matches()) {
  497.                     // we have found a regular data line

  498.                     if (expectedIndex > 0) {
  499.                         // we are in a file were terms are numbered, we check the index
  500.                         if (Integer.parseInt(regularMatcher.group(1)) != expectedIndex) {
  501.                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  502.                                                       lineNumber, name, regularMatcher.group());
  503.                         }
  504.                     }

  505.                     // get the Doodson multipliers as well as the Doodson number
  506.                     final int cTau     = (firstDoodson < 0) ? 0 : Integer.parseInt(regularMatcher.group(firstDoodson));
  507.                     final int cS       = (firstDoodson < 0) ? 0 : Integer.parseInt(regularMatcher.group(firstDoodson + 1));
  508.                     final int cH       = (firstDoodson < 0) ? 0 : Integer.parseInt(regularMatcher.group(firstDoodson + 2));
  509.                     final int cP       = (firstDoodson < 0) ? 0 : Integer.parseInt(regularMatcher.group(firstDoodson + 3));
  510.                     final int cNprime  = (firstDoodson < 0) ? 0 : Integer.parseInt(regularMatcher.group(firstDoodson + 4));
  511.                     final int cPs      = (firstDoodson < 0) ? 0 : Integer.parseInt(regularMatcher.group(firstDoodson + 5));
  512.                     final int nDoodson = (doodson      < 0) ? 0 : Integer.parseInt(regularMatcher.group(doodson).replaceAll("[.,]", ""));

  513.                     // get the tide multiplier
  514.                     int cGamma   = (gamma < 0) ? 0 : Integer.parseInt(regularMatcher.group(gamma));

  515.                     // get the Delaunay multipliers
  516.                     int cL       = Integer.parseInt(regularMatcher.group(firstDelaunay));
  517.                     int cLPrime  = Integer.parseInt(regularMatcher.group(firstDelaunay + 1));
  518.                     int cF       = Integer.parseInt(regularMatcher.group(firstDelaunay + 2));
  519.                     int cD       = Integer.parseInt(regularMatcher.group(firstDelaunay + 3));
  520.                     int cOmega   = Integer.parseInt(regularMatcher.group(firstDelaunay + 4));

  521.                     // get the planetary multipliers
  522.                     final int cMe      = (firstPlanetary < 0) ? 0 : Integer.parseInt(regularMatcher.group(firstPlanetary));
  523.                     final int cVe      = (firstPlanetary < 0) ? 0 : Integer.parseInt(regularMatcher.group(firstPlanetary + 1));
  524.                     final int cE       = (firstPlanetary < 0) ? 0 : Integer.parseInt(regularMatcher.group(firstPlanetary + 2));
  525.                     final int cMa      = (firstPlanetary < 0) ? 0 : Integer.parseInt(regularMatcher.group(firstPlanetary + 3));
  526.                     final int cJu      = (firstPlanetary < 0) ? 0 : Integer.parseInt(regularMatcher.group(firstPlanetary + 4));
  527.                     final int cSa      = (firstPlanetary < 0) ? 0 : Integer.parseInt(regularMatcher.group(firstPlanetary + 5));
  528.                     final int cUr      = (firstPlanetary < 0) ? 0 : Integer.parseInt(regularMatcher.group(firstPlanetary + 6));
  529.                     final int cNe      = (firstPlanetary < 0) ? 0 : Integer.parseInt(regularMatcher.group(firstPlanetary + 7));
  530.                     final int cPa      = (firstPlanetary < 0) ? 0 : Integer.parseInt(regularMatcher.group(firstPlanetary + 8));

  531.                     if (nDoodson > 0) {

  532.                         // set up the traditional parameters corresponding to the Doodson arguments
  533.                         cGamma  = cTau;
  534.                         cL      = -cL;
  535.                         cLPrime = -cLPrime;
  536.                         cF      = -cF;
  537.                         cD      = -cD;
  538.                         cOmega  = -cOmega;

  539.                         // check Doodson number, Doodson multipliers and Delaunay multipliers consistency
  540.                         if (nDoodson != doodsonToDoodsonNumber(cTau, cS, cH, cP, cNprime, cPs) ||
  541.                             nDoodson != delaunayToDoodsonNumber(cGamma, cL, cLPrime, cF, cD, cOmega)) {
  542.                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  543.                                                       lineNumber, name, regularMatcher.group());
  544.                         }

  545.                     }

  546.                     final long key = NutationCodec.encode(cGamma, cL, cLPrime, cF, cD, cOmega,
  547.                                                           cMe, cVe, cE, cMa, cJu, cSa, cUr, cNe, cPa);

  548.                     // retrieved the term, or build it if it's the first time it is encountered in the file
  549.                     final SeriesTerm term;
  550.                     if (series.containsKey(key)) {
  551.                         // the term was already known, from another degree
  552.                         term = series.get(key);
  553.                     } else {
  554.                         // the term is a new one
  555.                         term = SeriesTerm.buildTerm(cGamma, cL, cLPrime, cF, cD, cOmega,
  556.                                                     cMe, cVe, cE, cMa, cJu, cSa, cUr, cNe, cPa);
  557.                     }

  558.                     boolean nonZero = false;
  559.                     for (int d = 0; d < sinCosColumns.length / 2; ++d) {
  560.                         final double sinCoeff =
  561.                                 parseCoefficient(regularMatcher, sinCosColumns[2 * d],     sinCosFactors[2 * d]);
  562.                         final double cosCoeff =
  563.                                 parseCoefficient(regularMatcher, sinCosColumns[2 * d + 1], sinCosFactors[2 * d + 1]);
  564.                         if (!Precision.equals(sinCoeff, 0.0, 0) || !Precision.equals(cosCoeff, 0.0, 0)) {
  565.                             nonZero = true;
  566.                             term.add(0, degree + d, sinCoeff, cosCoeff);
  567.                             ++count;
  568.                         }
  569.                     }
  570.                     if (nonZero) {
  571.                         series.put(key, term);
  572.                     }

  573.                     if (expectedIndex > 0) {
  574.                         // we are in a file were terms are numbered
  575.                         // we must update the expected value for next term
  576.                         ++expectedIndex;
  577.                     }

  578.                 } else {

  579.                     final Matcher headerMatcher = degreeSectionHeaderPattern.matcher(line);
  580.                     if (headerMatcher.matches()) {

  581.                         // we have found a degree section header
  582.                         final int nextDegree = Integer.parseInt(headerMatcher.group(1));
  583.                         if ((nextDegree != degree + 1) && (degree != 0 || nextDegree != 0)) {
  584.                             throw new OrekitException(OrekitMessages.MISSING_SERIE_J_IN_FILE,
  585.                                                       degree + 1, name, lineNumber);
  586.                         }

  587.                         if (nextDegree == 0) {
  588.                             // in IERS files split in sections, all terms are numbered
  589.                             // we can check the indices
  590.                             expectedIndex = 1;
  591.                         }

  592.                         if (nextDegree > 0 && count != nTerms) {
  593.                             // the previous degree does not have the expected number of terms
  594.                             throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_IERS_DATA_FILE, name);
  595.                         }

  596.                         // remember the number of terms the upcoming sublist should have
  597.                         nTerms =  Integer.parseInt(headerMatcher.group(2));
  598.                         count  = 0;
  599.                         degree = nextDegree;

  600.                     } else if (polynomial == null) {
  601.                         // look for the polynomial part
  602.                         final double[] coefficients = polynomialParser.parse(line);
  603.                         if (coefficients != null) {
  604.                             polynomial = new PolynomialNutation(coefficients);
  605.                         }
  606.                     }

  607.                 }

  608.             }

  609.             if (polynomial == null || series.isEmpty()) {
  610.                 throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_IERS_DATA_FILE, name);
  611.             }

  612.             if (nTerms > 0 && count != nTerms) {
  613.                 // the last degree does not have the expected number of terms
  614.                 throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_IERS_DATA_FILE, name);
  615.             }

  616.             // build the series
  617.             return new PoissonSeries(polynomial, series);

  618.         } catch (IOException ioe) {
  619.             throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
  620.         }

  621.     }

  622.     /** Parse a scaled coefficient.
  623.      * @param matcher line matcher holding the coefficient
  624.      * @param group group number of the coefficient, or -1 if line does not contain coefficient
  625.      * @param scale scaling factor to apply
  626.      * @return scaled factor, or 0.0 if group is -1
  627.      */
  628.     private double parseCoefficient(final Matcher matcher, final int group, final double scale) {
  629.         if (group < 0) {
  630.             return 0.0;
  631.         } else {
  632.             return scale * Double.parseDouble(matcher.group(group));
  633.         }
  634.     }

  635.     /** Compute Doodson number from Delaunay multipliers.
  636.      * @param cGamma coefficient for γ = GMST + π tide parameter
  637.      * @param cL coefficient for mean anomaly of the Moon
  638.      * @param cLPrime coefficient for mean anomaly of the Sun
  639.      * @param cF coefficient for L - Ω where L is the mean longitude of the Moon
  640.      * @param cD coefficient for mean elongation of the Moon from the Sun
  641.      * @param cOmega coefficient for mean longitude of the ascending node of the Moon
  642.      * @return computed Doodson number
  643.      */
  644.     private int delaunayToDoodsonNumber(final int cGamma,
  645.                                         final int cL, final int cLPrime, final int cF,
  646.                                         final int cD, final int cOmega) {

  647.         // reconstruct Doodson multipliers from gamma and Delaunay multipliers
  648.         final int cTau    = cGamma;
  649.         final int cS      = cGamma + (cL + cF + cD);
  650.         final int cH      = cLPrime - cD;
  651.         final int cP      = -cL;
  652.         final int cNprime = cF - cOmega;
  653.         final int cPs     = -cLPrime;

  654.         return doodsonToDoodsonNumber(cTau, cS, cH, cP, cNprime, cPs);

  655.     }

  656.     /** Compute Doodson number from Doodson multipliers.
  657.      * @param cTau coefficient for mean lunar time
  658.      * @param cS coefficient for mean longitude of the Moon
  659.      * @param cH coefficient for mean longitude of the Sun
  660.      * @param cP coefficient for longitude of Moon mean perigee
  661.      * @param cNprime negative of the longitude of the Moon's mean ascending node on the ecliptic
  662.      * @param cPs coefficient for longitude of Sun mean perigee
  663.      * @return computed Doodson number
  664.      */
  665.     private int doodsonToDoodsonNumber(final int cTau,
  666.                                        final int cS, final int cH, final int cP,
  667.                                        final int cNprime, final int cPs) {

  668.         return ((((cTau * 10 + (cS + 5)) * 10 + (cH + 5)) * 10 + (cP + 5)) * 10 + (cNprime + 5)) * 10 + (cPs + 5);

  669.     }

  670. }