PoissonSeriesParser.java

  1. /* Copyright 2002-2013 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.apache.commons.math3.RealFieldElement;
  28. import org.apache.commons.math3.exception.util.DummyLocalizable;
  29. import org.apache.commons.math3.util.FastMath;
  30. import org.apache.commons.math3.util.Precision;
  31. import org.orekit.errors.OrekitException;
  32. import org.orekit.errors.OrekitMessages;

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

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

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

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

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

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

  224.     /** Parser for the polynomial part. */
  225.     private final PolynomialParser polynomialParser;

  226.     /** Fields patterns. */
  227.     private final String[] fieldsPatterns;

  228.     /** Optional column (counting from 1). */
  229.     private final int optional;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  332.         return new PoissonSeriesParser<T>(polynomialParser, newFieldsPatterns, column,
  333.                                           gamma, firstDoodson, doodson, firstDelaunay,
  334.                                           firstPlanetary, sinCosColumns, sinCosFactors);

  335.     }

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

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

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

  352.         return new PoissonSeriesParser<T>(polynomialParser, newFieldsPatterns, optional,
  353.                                           column, firstDoodson, doodson, firstDelaunay,
  354.                                           firstPlanetary, sinCosColumns, sinCosFactors);

  355.     }

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

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

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

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

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

  379.         return new PoissonSeriesParser<T>(polynomialParser, newFieldsPatterns, optional,
  380.                                           gamma, firstMultiplierColumn, numberColumn, firstDelaunay,
  381.                                           firstPlanetary, sinCosColumns, sinCosFactors);

  382.     }

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

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

  392.         return new PoissonSeriesParser<T>(polynomialParser, newFieldsPatterns, optional,
  393.                                           gamma, firstDoodson, doodson, firstColumn,
  394.                                           firstPlanetary, sinCosColumns, sinCosFactors);

  395.     }

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

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

  405.         return new PoissonSeriesParser<T>(polynomialParser, newFieldsPatterns, optional,
  406.                                           gamma, firstDoodson, doodson, firstDelaunay,
  407.                                           firstColumn, sinCosColumns, sinCosFactors);

  408.     }

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

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

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

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

  444.         return new PoissonSeriesParser<T>(polynomialParser, newFieldsPatterns, optional,
  445.                                           gamma, firstDoodson, doodson, firstDelaunay,
  446.                                           firstPlanetary, newSinCosColumns, newSinCosFactors);

  447.     }

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

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

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

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

  474.         try {

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

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

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

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

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

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

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

  515.                     // get the tide multipler
  516.                     int cGamma   = (gamma < 0) ? 0 : Integer.parseInt(regularMatcher.group(gamma));

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

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

  533.                     if (nDoodson > 0) {

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

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

  547.                     }

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

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

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

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

  580.                 } else {

  581.                     final Matcher headerMatcher = degreeSectionHeaderPattern.matcher(line);
  582.                     if (headerMatcher.matches()) {

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

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

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

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

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

  609.                 }

  610.             }

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

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

  618.             // build the series
  619.             return new PoissonSeries<T>(polynomial, series);

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

  623.     }

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

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

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

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

  657.     }

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

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

  671.     }

  672. }