OceanLoading.java

/* Copyright 2002-2024 CS GROUP
 * Licensed to CS GROUP (CS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * CS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.orekit.models.earth.displacement;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import org.hipparchus.analysis.UnivariateFunction;
import org.hipparchus.analysis.interpolation.SplineInterpolator;
import org.hipparchus.analysis.polynomials.PolynomialSplineFunction;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.SinCos;
import org.orekit.bodies.GeodeticPoint;
import org.orekit.bodies.OneAxisEllipsoid;
import org.orekit.data.BodiesElements;
import org.orekit.frames.Frame;

/**
 * Modeling of displacement of reference points due to ocean loading.
 * <p>
 * This class implements the same model as IERS HARDIP.F program. For a
 * given site, this model uses a set of amplitudes and phases for the 11
 * main tides (M₂, S₂, N₂, K₂, K₁, O₁, P₁, Q₁, Mf, Mm, and Ssa) in BLQ
 * format as provided by the <a
 * href="http://holt.oso.chalmers.se/loading/">Bos-Scherneck web site</a>
 * at Onsala Space Observatory. From these elements, additional admittances
 * are derived using spline interpolation based on tides frequencies for
 * a total of 342 tides, including the 11 main tides.
 * </p>
 * <p>
 * This implementation is a complete rewrite of the original HARDISP.F program
 * developed by Duncan Agnew and copyright 2008 IERS Conventions center. This
 * derived work is not endorsed by the IERS conventions center. What remains
 * from the original program is the model (spline interpolation and coefficients).
 * The code by itself is completely different, using the underlying mathematical
 * library for spline interpolation and the existing Orekit features for nutation
 * arguments, time and time scales handling, tides modeling...
 * </p>
 * <p>
 * Instances of this class are guaranteed to be immutable
 * </p>
 * <p>
 * The original HARDISP.F program is distributed with the following notice:
 * </p>
 * <pre>
 *  Copyright (C) 2008
 *  IERS Conventions Center
 *
 *  ==================================
 *  IERS Conventions Software License
 *  ==================================
 *
 *  NOTICE TO USER:
 *
 *  BY USING THIS SOFTWARE YOU ACCEPT THE FOLLOWING TERMS AND CONDITIONS
 *  WHICH APPLY TO ITS USE.
 *
 *  1. The Software is provided by the IERS Conventions Center ("the
 *     Center").
 *
 *  2. Permission is granted to anyone to use the Software for any
 *     purpose, including commercial applications, free of charge,
 *     subject to the conditions and restrictions listed below.
 *
 *  3. You (the user) may adapt the Software and its algorithms for your
 *     own purposes and you may distribute the resulting "derived work"
 *     to others, provided that the derived work complies with the
 *     following requirements:
 *
 *     a) Your work shall be clearly identified so that it cannot be
 *        mistaken for IERS Conventions software and that it has been
 *        neither distributed by nor endorsed by the Center.
 *
 *     b) Your work (including source code) must contain descriptions of
 *        how the derived work is based upon and/or differs from the
 *        original Software.
 *
 *     c) The name(s) of all modified routine(s) that you distribute
 *        shall be changed.
 *
 *     d) The origin of the IERS Conventions components of your derived
 *        work must not be misrepresented; you must not claim that you
 *        wrote the original Software.
 *
 *     e) The source code must be included for all routine(s) that you
 *        distribute.  This notice must be reproduced intact in any
 *        source distribution.
 *
 *  4. In any published work produced by the user and which includes
 *     results achieved by using the Software, you shall acknowledge
 *     that the Software was used in obtaining those results.
 *
 *  5. The Software is provided to the user "as is" and the Center makes
 *     no warranty as to its use or performance.   The Center does not
 *     and cannot warrant the performance or results which the user may
 *     obtain by using the Software.  The Center makes no warranties,
 *     express or implied, as to non-infringement of third party rights,
 *     merchantability, or fitness for any particular purpose.  In no
 *     event will the Center be liable to the user for any consequential,
 *     incidental, or special damages, including any lost profits or lost
 *     savings, even if a Center representative has been advised of such
 *     damages, or for any claim by any third party.
 *
 *  Correspondence concerning IERS Conventions software should be
 *  addressed as follows:
 *
 *                     Gerard Petit
 *     Internet email: gpetit[at]bipm.org
 *     Postal address: IERS Conventions Center
 *                     Time, frequency and gravimetry section, BIPM
 *                     Pavillon de Breteuil
 *                     92312 Sevres  FRANCE
 *
 *     or
 *
 *                     Brian Luzum
 *     Internet email: brian.luzum[at]usno.navy.mil
 *     Postal address: IERS Conventions Center
 *                     Earth Orientation Department
 *                     3450 Massachusetts Ave, NW
 *                     Washington, DC 20392
 * </pre>
 * @see org.orekit.estimation.measurements.GroundStation
 * @since 9.1
 * @author Luc Maisonobe
 */
public class OceanLoading implements StationDisplacement {

    // CHECKSTYLE: stop Indentation check
    /** Amplitudes of all tides used. */
    private static final double[] CARTWRIGHT_EDDEN_AMPLITUDE = {
        0.632208,  0.294107,  0.121046,  0.079915,  0.023818, -0.023589,  0.022994,
        0.019333, -0.017871,  0.017192,  0.016018,  0.004671, -0.004662, -0.004519,
        0.004470,  0.004467,  0.002589, -0.002455, -0.002172,  0.001972,  0.001947,
        0.001914, -0.001898,  0.001802,  0.001304,  0.001170,  0.001130,  0.001061,
       -0.001022, -0.001017,  0.001014,  0.000901, -0.000857,  0.000855,  0.000855,
        0.000772,  0.000741,  0.000741, -0.000721,  0.000698,  0.000658,  0.000654,
       -0.000653,  0.000633,  0.000626, -0.000598,  0.000590,  0.000544,  0.000479,
       -0.000464,  0.000413, -0.000390,  0.000373,  0.000366,  0.000366, -0.000360,
       -0.000355,  0.000354,  0.000329,  0.000328,  0.000319,  0.000302,  0.000279,
       -0.000274, -0.000272,  0.000248, -0.000225,  0.000224, -0.000223, -0.000216,
        0.000211,  0.000209,  0.000194,  0.000185, -0.000174, -0.000171,  0.000159,
        0.000131,  0.000127,  0.000120,  0.000118,  0.000117,  0.000108,  0.000107,
        0.000105, -0.000102,  0.000102,  0.000099, -0.000096,  0.000095, -0.000089,
       -0.000085, -0.000084, -0.000081, -0.000077, -0.000072, -0.000067,  0.000066,
        0.000064,  0.000063,  0.000063,  0.000063,  0.000062,  0.000062, -0.000060,
        0.000056,  0.000053,  0.000051,  0.000050,  0.368645, -0.262232, -0.121995,
       -0.050208,  0.050031, -0.049470,  0.020620,  0.020613,  0.011279, -0.009530,
       -0.009469, -0.008012,  0.007414, -0.007300,  0.007227, -0.007131, -0.006644,
        0.005249,  0.004137,  0.004087,  0.003944,  0.003943,  0.003420,  0.003418,
        0.002885,  0.002884,  0.002160, -0.001936,  0.001934, -0.001798,  0.001690,
        0.001689,  0.001516,  0.001514, -0.001511,  0.001383,  0.001372,  0.001371,
       -0.001253, -0.001075,  0.001020,  0.000901,  0.000865, -0.000794,  0.000788,
        0.000782, -0.000747, -0.000745,  0.000670, -0.000603, -0.000597,  0.000542,
        0.000542, -0.000541, -0.000469, -0.000440,  0.000438,  0.000422,  0.000410,
       -0.000374, -0.000365,  0.000345,  0.000335, -0.000321, -0.000319,  0.000307,
        0.000291,  0.000290, -0.000289,  0.000286,  0.000275,  0.000271,  0.000263,
       -0.000245,  0.000225,  0.000225,  0.000221, -0.000202, -0.000200, -0.000199,
        0.000192,  0.000183,  0.000183,  0.000183, -0.000170,  0.000169,  0.000168,
        0.000162,  0.000149, -0.000147, -0.000141,  0.000138,  0.000136,  0.000136,
        0.000127,  0.000127, -0.000126, -0.000121, -0.000121,  0.000117, -0.000116,
       -0.000114, -0.000114, -0.000114,  0.000114,  0.000113,  0.000109,  0.000108,
        0.000106, -0.000106, -0.000106,  0.000105,  0.000104, -0.000103, -0.000100,
       -0.000100, -0.000100,  0.000099, -0.000098,  0.000093,  0.000093,  0.000090,
       -0.000088,  0.000083, -0.000083, -0.000082, -0.000081, -0.000079, -0.000077,
       -0.000075, -0.000075, -0.000075,  0.000071,  0.000071, -0.000071,  0.000068,
        0.000068,  0.000065,  0.000065,  0.000064,  0.000064,  0.000064, -0.000064,
       -0.000060,  0.000056,  0.000056,  0.000053,  0.000053,  0.000053, -0.000053,
        0.000053,  0.000053,  0.000052,  0.000050, -0.066607, -0.035184, -0.030988,
        0.027929, -0.027616, -0.012753, -0.006728, -0.005837, -0.005286, -0.004921,
       -0.002884, -0.002583, -0.002422,  0.002310,  0.002283, -0.002037,  0.001883,
       -0.001811, -0.001687, -0.001004, -0.000925, -0.000844,  0.000766,  0.000766,
       -0.000700, -0.000495, -0.000492,  0.000491,  0.000483,  0.000437, -0.000416,
       -0.000384,  0.000374, -0.000312, -0.000288, -0.000273,  0.000259,  0.000245,
       -0.000232,  0.000229, -0.000216,  0.000206, -0.000204, -0.000202,  0.000200,
        0.000195, -0.000190,  0.000187,  0.000180, -0.000179,  0.000170,  0.000153,
       -0.000137, -0.000119, -0.000119, -0.000112, -0.000110, -0.000110,  0.000107,
       -0.000095, -0.000095, -0.000091, -0.000090, -0.000081, -0.000079, -0.000079,
        0.000077, -0.000073,  0.000069, -0.000067, -0.000066,  0.000065,  0.000064,
       -0.000062,  0.000060,  0.000059, -0.000056,  0.000055, -0.000051
    };
    // CHECKSTYLE: resume Indentation check

    // CHECKSTYLE: stop NoWhitespaceAfter check
    /** Doodson arguments for all tides used. */
    private static final int[][] DOODSON_ARGUMENTS = {
        { 2,  0,  0,  0,  0,  0 }, { 2,  2, -2,  0,  0,  0 }, { 2, -1,  0,  1,  0,  0 },
        { 2,  2,  0,  0,  0,  0 }, { 2,  2,  0,  0,  1,  0 }, { 2,  0,  0,  0, -1,  0 },
        { 2, -1,  2, -1,  0,  0 }, { 2, -2,  2,  0,  0,  0 }, { 2,  1,  0, -1,  0,  0 },
        { 2,  2, -3,  0,  0,  1 }, { 2, -2,  0,  2,  0,  0 }, { 2, -3,  2,  1,  0,  0 },
        { 2,  1, -2,  1,  0,  0 }, { 2, -1,  0,  1, -1,  0 }, { 2,  3,  0, -1,  0,  0 },
        { 2,  1,  0,  1,  0,  0 }, { 2,  2,  0,  0,  2,  0 }, { 2,  2, -1,  0,  0, -1 },
        { 2,  0, -1,  0,  0,  1 }, { 2,  1,  0,  1,  1,  0 }, { 2,  3,  0, -1,  1,  0 },
        { 2,  0,  1,  0,  0, -1 }, { 2,  0, -2,  2,  0,  0 }, { 2, -3,  0,  3,  0,  0 },
        { 2, -2,  3,  0,  0, -1 }, { 2,  4,  0,  0,  0,  0 }, { 2, -1,  1,  1,  0, -1 },
        { 2, -1,  3, -1,  0, -1 }, { 2,  2,  0,  0, -1,  0 }, { 2, -1, -1,  1,  0,  1 },
        { 2,  4,  0,  0,  1,  0 }, { 2, -3,  4, -1,  0,  0 }, { 2, -1,  2, -1, -1,  0 },
        { 2,  3, -2,  1,  0,  0 }, { 2,  1,  2, -1,  0,  0 }, { 2, -4,  2,  2,  0,  0 },
        { 2,  4, -2,  0,  0,  0 }, { 2,  0,  2,  0,  0,  0 }, { 2, -2,  2,  0, -1,  0 },
        { 2,  2, -4,  0,  0,  2 }, { 2,  2, -2,  0, -1,  0 }, { 2,  1,  0, -1, -1,  0 },
        { 2, -1,  1,  0,  0,  0 }, { 2,  2, -1,  0,  0,  1 }, { 2,  2,  1,  0,  0, -1 },
        { 2, -2,  0,  2, -1,  0 }, { 2, -2,  4, -2,  0,  0 }, { 2,  2,  2,  0,  0,  0 },
        { 2, -4,  4,  0,  0,  0 }, { 2, -1,  0, -1, -2,  0 }, { 2,  1,  2, -1,  1,  0 },
        { 2, -1, -2,  3,  0,  0 }, { 2,  3, -2,  1,  1,  0 }, { 2,  4,  0, -2,  0,  0 },
        { 2,  0,  0,  2,  0,  0 }, { 2,  0,  2, -2,  0,  0 }, { 2,  0,  2,  0,  1,  0 },
        { 2, -3,  3,  1,  0, -1 }, { 2,  0,  0,  0, -2,  0 }, { 2,  4,  0,  0,  2,  0 },
        { 2,  4, -2,  0,  1,  0 }, { 2,  0,  0,  0,  0,  2 }, { 2,  1,  0,  1,  2,  0 },
        { 2,  0, -2,  0, -2,  0 }, { 2, -2,  1,  0,  0,  1 }, { 2, -2,  1,  2,  0, -1 },
        { 2, -1,  1, -1,  0,  1 }, { 2,  5,  0, -1,  0,  0 }, { 2,  1, -3,  1,  0,  1 },
        { 2, -2, -1,  2,  0,  1 }, { 2,  3,  0, -1,  2,  0 }, { 2,  1, -2,  1, -1,  0 },
        { 2,  5,  0, -1,  1,  0 }, { 2, -4,  0,  4,  0,  0 }, { 2, -3,  2,  1, -1,  0 },
        { 2, -2,  1,  1,  0,  0 }, { 2,  4,  0, -2,  1,  0 }, { 2,  0,  0,  2,  1,  0 },
        { 2, -5,  4,  1,  0,  0 }, { 2,  0,  2,  0,  2,  0 }, { 2, -1,  2,  1,  0,  0 },
        { 2,  5, -2, -1,  0,  0 }, { 2,  1, -1,  0,  0,  0 }, { 2,  2, -2,  0,  0,  2 },
        { 2, -5,  2,  3,  0,  0 }, { 2, -1, -2,  1, -2,  0 }, { 2, -3,  5, -1,  0, -1 },
        { 2, -1,  0,  0,  0,  1 }, { 2, -2,  0,  0, -2,  0 }, { 2,  0, -1,  1,  0,  0 },
        { 2, -3,  1,  1,  0,  1 }, { 2,  3,  0, -1, -1,  0 }, { 2,  1,  0,  1, -1,  0 },
        { 2, -1,  2,  1,  1,  0 }, { 2,  0, -3,  2,  0,  1 }, { 2,  1, -1, -1,  0,  1 },
        { 2, -3,  0,  3, -1,  0 }, { 2,  0, -2,  2, -1,  0 }, { 2, -4,  3,  2,  0, -1 },
        { 2, -1,  0,  1, -2,  0 }, { 2,  5,  0, -1,  2,  0 }, { 2, -4,  5,  0,  0, -1 },
        { 2, -2,  4,  0,  0, -2 }, { 2, -1,  0,  1,  0,  2 }, { 2, -2, -2,  4,  0,  0 },
        { 2,  3, -2, -1, -1,  0 }, { 2, -2,  5, -2,  0, -1 }, { 2,  0, -1,  0, -1,  1 },
        { 2,  5, -2, -1,  1,  0 }, { 1,  1,  0,  0,  0,  0 }, { 1, -1,  0,  0,  0,  0 },
        { 1,  1, -2,  0,  0,  0 }, { 1, -2,  0,  1,  0,  0 }, { 1,  1,  0,  0,  1,  0 },
        { 1, -1,  0,  0, -1,  0 }, { 1,  2,  0, -1,  0,  0 }, { 1,  0,  0,  1,  0,  0 },
        { 1,  3,  0,  0,  0,  0 }, { 1, -2,  2, -1,  0,  0 }, { 1, -2,  0,  1, -1,  0 },
        { 1, -3,  2,  0,  0,  0 }, { 1,  0,  0, -1,  0,  0 }, { 1,  1,  0,  0, -1,  0 },
        { 1,  3,  0,  0,  1,  0 }, { 1,  1, -3,  0,  0,  1 }, { 1, -3,  0,  2,  0,  0 },
        { 1,  1,  2,  0,  0,  0 }, { 1,  0,  0,  1,  1,  0 }, { 1,  2,  0, -1,  1,  0 },
        { 1,  0,  2, -1,  0,  0 }, { 1,  2, -2,  1,  0,  0 }, { 1,  3, -2,  0,  0,  0 },
        { 1, -1,  2,  0,  0,  0 }, { 1,  1,  1,  0,  0, -1 }, { 1,  1, -1,  0,  0,  1 },
        { 1,  4,  0, -1,  0,  0 }, { 1, -4,  2,  1,  0,  0 }, { 1,  0, -2,  1,  0,  0 },
        { 1, -2,  2, -1, -1,  0 }, { 1,  3,  0, -2,  0,  0 }, { 1, -1,  0,  2,  0,  0 },
        { 1, -1,  0,  0, -2,  0 }, { 1,  3,  0,  0,  2,  0 }, { 1, -3,  2,  0, -1,  0 },
        { 1,  4,  0, -1,  1,  0 }, { 1,  0,  0, -1, -1,  0 }, { 1,  1, -2,  0, -1,  0 },
        { 1, -3,  0,  2, -1,  0 }, { 1,  1,  0,  0,  2,  0 }, { 1,  1, -1,  0,  0, -1 },
        { 1, -1, -1,  0,  0,  1 }, { 1,  0,  2, -1,  1,  0 }, { 1, -1,  1,  0,  0, -1 },
        { 1, -1, -2,  2,  0,  0 }, { 1,  2, -2,  1,  1,  0 }, { 1, -4,  0,  3,  0,  0 },
        { 1, -1,  2,  0,  1,  0 }, { 1,  3, -2,  0,  1,  0 }, { 1,  2,  0, -1, -1,  0 },
        { 1,  0,  0,  1, -1,  0 }, { 1, -2,  2,  1,  0,  0 }, { 1,  4, -2, -1,  0,  0 },
        { 1, -3,  3,  0,  0, -1 }, { 1, -2,  1,  1,  0, -1 }, { 1, -2,  3, -1,  0, -1 },
        { 1,  0, -2,  1, -1,  0 }, { 1, -2, -1,  1,  0,  1 }, { 1,  4, -2,  1,  0,  0 },
        { 1, -4,  4, -1,  0,  0 }, { 1, -4,  2,  1, -1,  0 }, { 1,  5, -2,  0,  0,  0 },
        { 1,  3,  0, -2,  1,  0 }, { 1, -5,  2,  2,  0,  0 }, { 1,  2,  0,  1,  0,  0 },
        { 1,  1,  3,  0,  0, -1 }, { 1, -2,  0,  1, -2,  0 }, { 1,  4,  0, -1,  2,  0 },
        { 1,  1, -4,  0,  0,  2 }, { 1,  5,  0, -2,  0,  0 }, { 1, -1,  0,  2,  1,  0 },
        { 1, -2,  1,  0,  0,  0 }, { 1,  4, -2,  1,  1,  0 }, { 1, -3,  4, -2,  0,  0 },
        { 1, -1,  3,  0,  0, -1 }, { 1,  3, -3,  0,  0,  1 }, { 1,  5, -2,  0,  1,  0 },
        { 1,  1,  2,  0,  1,  0 }, { 1,  2,  0,  1,  1,  0 }, { 1, -5,  4,  0,  0,  0 },
        { 1, -2,  0, -1, -2,  0 }, { 1,  5,  0, -2,  1,  0 }, { 1,  1,  2, -2,  0,  0 },
        { 1,  1, -2,  2,  0,  0 }, { 1, -2,  2,  1,  1,  0 }, { 1,  0,  3, -1,  0, -1 },
        { 1,  2, -3,  1,  0,  1 }, { 1, -2, -2,  3,  0,  0 }, { 1, -1,  2, -2,  0,  0 },
        { 1, -4,  3,  1,  0, -1 }, { 1, -4,  0,  3, -1,  0 }, { 1, -1, -2,  2, -1,  0 },
        { 1, -2,  0,  3,  0,  0 }, { 1,  4,  0, -3,  0,  0 }, { 1,  0,  1,  1,  0, -1 },
        { 1,  2, -1, -1,  0,  1 }, { 1,  2, -2,  1, -1,  0 }, { 1,  0,  0, -1, -2,  0 },
        { 1,  2,  0,  1,  2,  0 }, { 1,  2, -2, -1, -1,  0 }, { 1,  0,  0,  1,  2,  0 },
        { 1,  0,  1,  0,  0,  0 }, { 1,  2, -1,  0,  0,  0 }, { 1,  0,  2, -1, -1,  0 },
        { 1, -1, -2,  0, -2,  0 }, { 1, -3,  1,  0,  0,  1 }, { 1,  3, -2,  0, -1,  0 },
        { 1, -1, -1,  0, -1,  1 }, { 1,  4, -2, -1,  1,  0 }, { 1,  2,  1, -1,  0, -1 },
        { 1,  0, -1,  1,  0,  1 }, { 1, -2,  4, -1,  0,  0 }, { 1,  4, -4,  1,  0,  0 },
        { 1, -3,  1,  2,  0, -1 }, { 1, -3,  3,  0, -1, -1 }, { 1,  1,  2,  0,  2,  0 },
        { 1,  1, -2,  0, -2,  0 }, { 1,  3,  0,  0,  3,  0 }, { 1, -1,  2,  0, -1,  0 },
        { 1, -2,  1, -1,  0,  1 }, { 1,  0, -3,  1,  0,  1 }, { 1, -3, -1,  2,  0,  1 },
        { 1,  2,  0, -1,  2,  0 }, { 1,  6, -2, -1,  0,  0 }, { 1,  2,  2, -1,  0,  0 },
        { 1, -1,  1,  0, -1, -1 }, { 1, -2,  3, -1, -1, -1 }, { 1, -1,  0,  0,  0,  2 },
        { 1, -5,  0,  4,  0,  0 }, { 1,  1,  0,  0,  0, -2 }, { 1, -2,  1,  1, -1, -1 },
        { 1,  1, -1,  0,  1,  1 }, { 1,  1,  2,  0,  0, -2 }, { 1, -3,  1,  1,  0,  0 },
        { 1, -4,  4, -1, -1,  0 }, { 1,  1,  0, -2, -1,  0 }, { 1, -2, -1,  1, -1,  1 },
        { 1, -3,  2,  2,  0,  0 }, { 1,  5, -2, -2,  0,  0 }, { 1,  3, -4,  2,  0,  0 },
        { 1,  1, -2,  0,  0,  2 }, { 1, -1,  4, -2,  0,  0 }, { 1,  2,  2, -1,  1,  0 },
        { 1, -5,  2,  2, -1,  0 }, { 1,  1, -3,  0, -1,  1 }, { 1,  1,  1,  0,  1, -1 },
        { 1,  6, -2, -1,  1,  0 }, { 1, -2,  2, -1, -2,  0 }, { 1,  4, -2,  1,  2,  0 },
        { 1, -6,  4,  1,  0,  0 }, { 1,  5, -4,  0,  0,  0 }, { 1, -3,  4,  0,  0,  0 },
        { 1,  1,  2, -2,  1,  0 }, { 1, -2,  1,  0, -1,  0 }, { 0,  2,  0,  0,  0,  0 },
        { 0,  1,  0, -1,  0,  0 }, { 0,  0,  2,  0,  0,  0 }, { 0,  0,  0,  0,  1,  0 },
        { 0,  2,  0,  0,  1,  0 }, { 0,  3,  0, -1,  0,  0 }, { 0,  1, -2,  1,  0,  0 },
        { 0,  2, -2,  0,  0,  0 }, { 0,  3,  0, -1,  1,  0 }, { 0,  0,  1,  0,  0, -1 },
        { 0,  2,  0, -2,  0,  0 }, { 0,  2,  0,  0,  2,  0 }, { 0,  3, -2,  1,  0,  0 },
        { 0,  1,  0, -1, -1,  0 }, { 0,  1,  0, -1,  1,  0 }, { 0,  4, -2,  0,  0,  0 },
        { 0,  1,  0,  1,  0,  0 }, { 0,  0,  3,  0,  0, -1 }, { 0,  4,  0, -2,  0,  0 },
        { 0,  3, -2,  1,  1,  0 }, { 0,  3, -2, -1,  0,  0 }, { 0,  4, -2,  0,  1,  0 },
        { 0,  0,  2,  0,  1,  0 }, { 0,  1,  0,  1,  1,  0 }, { 0,  4,  0, -2,  1,  0 },
        { 0,  3,  0, -1,  2,  0 }, { 0,  5, -2, -1,  0,  0 }, { 0,  1,  2, -1,  0,  0 },
        { 0,  1, -2,  1, -1,  0 }, { 0,  1, -2,  1,  1,  0 }, { 0,  2, -2,  0, -1,  0 },
        { 0,  2, -3,  0,  0,  1 }, { 0,  2, -2,  0,  1,  0 }, { 0,  0,  2, -2,  0,  0 },
        { 0,  1, -3,  1,  0,  1 }, { 0,  0,  0,  0,  2,  0 }, { 0,  0,  1,  0,  0,  1 },
        { 0,  1,  2, -1,  1,  0 }, { 0,  3,  0, -3,  0,  0 }, { 0,  2,  1,  0,  0, -1 },
        { 0,  1, -1, -1,  0,  1 }, { 0,  1,  0,  1,  2,  0 }, { 0,  5, -2, -1,  1,  0 },
        { 0,  2, -1,  0,  0,  1 }, { 0,  2,  2, -2,  0,  0 }, { 0,  1, -1,  0,  0,  0 },
        { 0,  5,  0, -3,  0,  0 }, { 0,  2,  0, -2,  1,  0 }, { 0,  1,  1, -1,  0, -1 },
        { 0,  3, -4,  1,  0,  0 }, { 0,  0,  2,  0,  2,  0 }, { 0,  2,  0, -2, -1,  0 },
        { 0,  4, -3,  0,  0,  1 }, { 0,  3, -1, -1,  0,  1 }, { 0,  0,  2,  0,  0, -2 },
        { 0,  3, -3,  1,  0,  1 }, { 0,  2, -4,  2,  0,  0 }, { 0,  4, -2, -2,  0,  0 },
        { 0,  3,  1, -1,  0, -1 }, { 0,  5, -4,  1,  0,  0 }, { 0,  3, -2, -1, -1,  0 },
        { 0,  3, -2,  1,  2,  0 }, { 0,  4, -4,  0,  0,  0 }, { 0,  6, -2, -2,  0,  0 },
        { 0,  5,  0, -3,  1,  0 }, { 0,  4, -2,  0,  2,  0 }, { 0,  2,  2, -2,  1,  0 },
        { 0,  0,  4,  0,  0, -2 }, { 0,  3, -1,  0,  0,  0 }, { 0,  3, -3, -1,  0,  1 },
        { 0,  4,  0, -2,  2,  0 }, { 0,  1, -2, -1, -1,  0 }, { 0,  2, -1,  0,  0, -1 },
        { 0,  4, -4,  2,  0,  0 }, { 0,  2,  1,  0,  1, -1 }, { 0,  3, -2, -1,  1,  0 },
        { 0,  4, -3,  0,  1,  1 }, { 0,  2,  0,  0,  3,  0 }, { 0,  6, -4,  0,  0,  0 }
    };
    // CHECKSTYLE: resume NoWhitespaceAfter check

    /** Cartwright-Edden amplitudes for all tides. */
    private static final Map<Tide, Double> CARTWRIGHT_EDDEN_AMPLITUDE_MAP;

    static {
        CARTWRIGHT_EDDEN_AMPLITUDE_MAP = new HashMap<>(CARTWRIGHT_EDDEN_AMPLITUDE.length);
        for (int i = 0; i < CARTWRIGHT_EDDEN_AMPLITUDE.length; ++i) {
            CARTWRIGHT_EDDEN_AMPLITUDE_MAP.put(new Tide(DOODSON_ARGUMENTS[i][0], DOODSON_ARGUMENTS[i][1], DOODSON_ARGUMENTS[i][2],
                                                        DOODSON_ARGUMENTS[i][3], DOODSON_ARGUMENTS[i][4], DOODSON_ARGUMENTS[i][5]),
                                               CARTWRIGHT_EDDEN_AMPLITUDE[i]);
        }
    }

    /** Earth shape. */
    private final OneAxisEllipsoid earth;

    /** Data for main tides, for which we have ocean loading coefficients. */
    private final MainTideData[][] mainTides;

    /** Simple constructor.
     * @param earth Earth shape
     * @param coefficients coefficients for the considered site
     * @see OceanLoadingCoefficientsBLQFactory
     */
    public OceanLoading(final OneAxisEllipsoid earth, final OceanLoadingCoefficients coefficients) {

        this.earth = earth;

        // set up complex admittances, scaled to Cartwright-Edden amplitudes
        // and grouped by species (0: long period, 1: diurnal, 2: semi-diurnal)
        mainTides  = new MainTideData[coefficients.getNbSpecies()][];
        for (int i = 0; i < mainTides.length; ++i) {
            mainTides[i] = new MainTideData[coefficients.getNbTides(i)];
            for (int j = 0; j < mainTides[i].length; ++j) {
                final double amplitude = CARTWRIGHT_EDDEN_AMPLITUDE_MAP.get(coefficients.getTide(i, j));
                mainTides[i][j] = new MainTideData(coefficients, i, j, FastMath.abs(amplitude));
            }
        }

    }

    /** {@inheritDoc} */
    @Override
    public Vector3D displacement(final BodiesElements elements, final Frame earthFrame,
                                 final Vector3D referencePoint) {

        // allocate arrays for each species splines
        final UnivariateFunction[] realZSpline      = new UnivariateFunction[mainTides.length];
        final UnivariateFunction[] imaginaryZSpline = new UnivariateFunction[mainTides.length];
        final UnivariateFunction[] realWSpline      = new UnivariateFunction[mainTides.length];
        final UnivariateFunction[] imaginaryWSpline = new UnivariateFunction[mainTides.length];
        final UnivariateFunction[] realSSpline      = new UnivariateFunction[mainTides.length];
        final UnivariateFunction[] imaginarySSpline = new UnivariateFunction[mainTides.length];

        // prepare splines for each species
        for (int i = 0; i < mainTides.length; ++i) {

            // compute current rates
            final double[] rates = new double[mainTides[i].length];
            for (int j = 0; j < rates.length; ++j) {
                rates[j] = mainTides[i][j].tide.getRate(elements);
            }

            // set up splines for the current rates
            realZSpline[i]      = spline(rates, mainTides[i], d -> d.realZ);
            imaginaryZSpline[i] = spline(rates, mainTides[i], d -> d.imaginaryZ);
            realWSpline[i]      = spline(rates, mainTides[i], d -> d.realW);
            imaginaryWSpline[i] = spline(rates, mainTides[i], d -> d.imaginaryW);
            realSSpline[i]      = spline(rates, mainTides[i], d -> d.realS);
            imaginarySSpline[i] = spline(rates, mainTides[i], d -> d.imaginaryS);

        }

        // evaluate all harmonics using interpolated admittances
        double dz = 0;
        double dw = 0;
        double ds = 0;
        for (final Map.Entry<Tide, Double> entry : CARTWRIGHT_EDDEN_AMPLITUDE_MAP.entrySet()) {

            final Tide   tide      = entry.getKey();
            final double amplitude = entry.getValue();
            final int    i         = tide.getTauMultiplier();
            final double rate      = tide.getRate(elements);

            // apply splines for the current rate of this tide
            final double rZ = realZSpline[i].value(rate);
            final double iZ = imaginaryZSpline[i].value(rate);
            final double rW = realWSpline[i].value(rate);
            final double iW = imaginaryWSpline[i].value(rate);
            final double rS = realSSpline[i].value(rate);
            final double iS = imaginarySSpline[i].value(rate);

            // phase for the current tide, including corrections
            final double correction;
            if (tide.getTauMultiplier() == 0) {
                correction = FastMath.PI;
            } else if (tide.getTauMultiplier() == 1) {
                correction = 0.5 * FastMath.PI;
            } else {
                correction = 0.0;
            }
            final double phase = tide.getPhase(elements) + correction;

            dz += amplitude * FastMath.hypot(rZ, iZ) * FastMath.cos(phase + FastMath.atan2(iZ, rZ));
            dw += amplitude * FastMath.hypot(rW, iW) * FastMath.cos(phase + FastMath.atan2(iW, rW));
            ds += amplitude * FastMath.hypot(rS, iS) * FastMath.cos(phase + FastMath.atan2(iS, rS));

        }

        // convert to proper frame
        final GeodeticPoint gp = earth.transform(referencePoint, earthFrame, elements.getDate());
        return new Vector3D(dz, gp.getZenith(),
                            dw, gp.getWest(),
                            ds, gp.getSouth());

    }

    /** Get a spline function for interpolating between main tide data.
     * @param rates rates for the tides species
     * @param data data for the tides species
     * @param selector data selector
     * @return spline function for interpolating the selected data
     */
    private UnivariateFunction spline(final double[] rates, final MainTideData[] data,
                                      final Function<MainTideData, Double> selector) {
        final double[] y = new double[data.length];
        for (int i = 0; i < y.length; ++i) {
            y[i] = selector.apply(data[i]);
        }
        final PolynomialSplineFunction psf = new SplineInterpolator().interpolate(rates, y);

        // as per HARDISP program EVAL subroutine, if spline evaluation is outside of range,
        // the closest value is used. This occurs for example for long period tides.
        // The main tides have rates 0.0821°/h, 0.5444°/h and 1.0980°/h. However,
        // tide 55565 has rate 0.0022°/h, which is below the min rate and tide 75565 has
        // rate 1.1002°/h, which is above max rate
        final double[] knots          = psf.getKnots();
        final double   minRate        = knots[0];
        final double   valueAtMinRate = psf.value(minRate);
        final double   maxRate        = knots[knots.length - 1];
        final double   valueAtMaxRate = psf.value(maxRate);
        return t -> (t < minRate) ? valueAtMinRate : (t > maxRate) ? valueAtMaxRate : psf.value(t);

    }

    /** Container for main tide data. */
    private static class MainTideData {

        /** Tide for which we have ocean loading coefficients. */
        private final Tide tide;

        /** Scaled real part of admittance along zenith axis. */
        private final double realZ;

        /** Scaled imaginary part of admittance along zenith axis. */
        private final double imaginaryZ;

        /** Scaled real part of admittance along west axis. */
        private final double realW;

        /** Scaled imaginary part of admittance along west axis. */
        private final double imaginaryW;

        /** Scaled real part of admittance along south axis. */
        private final double realS;

        /** Scaled imaginary part of admittance south axis. */
        private final double imaginaryS;

        /** Simple constructor.
         * @param coefficients coefficients for the considered site
         * @param i tide species
         * @param j tide index in the species
         * @param absAmplitude absolute value of the Cartwright-Edden amplitude of the tide
         */
        MainTideData(final OceanLoadingCoefficients coefficients, final int i, final int j, final double absAmplitude) {
            // Sine and Cosine of difference angles
            final SinCos scZenith = FastMath.sinCos(coefficients.getZenithPhase(i, j));
            final SinCos scWest   = FastMath.sinCos(coefficients.getWestPhase(i, j));
            final SinCos scSouth  = FastMath.sinCos(coefficients.getSouthPhase(i, j));
            // Initialize attributes
            tide       = coefficients.getTide(i, j);
            realZ      = coefficients.getZenithAmplitude(i, j) * scZenith.cos() / absAmplitude;
            imaginaryZ = coefficients.getZenithAmplitude(i, j) * scZenith.sin() / absAmplitude;
            realW      = coefficients.getWestAmplitude(i, j)   * scWest.cos()   / absAmplitude;
            imaginaryW = coefficients.getWestAmplitude(i, j)   * scWest.sin()   / absAmplitude;
            realS      = coefficients.getSouthAmplitude(i, j)  * scSouth.cos()  / absAmplitude;
            imaginaryS = coefficients.getSouthAmplitude(i, j)  * scSouth.sin()  / absAmplitude;
        }

    }

}