OceanLoading.java

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

  18. import java.util.HashMap;
  19. import java.util.Map;
  20. import java.util.function.Function;

  21. import org.hipparchus.analysis.UnivariateFunction;
  22. import org.hipparchus.analysis.interpolation.SplineInterpolator;
  23. import org.hipparchus.analysis.polynomials.PolynomialSplineFunction;
  24. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  25. import org.hipparchus.util.FastMath;
  26. import org.hipparchus.util.SinCos;
  27. import org.orekit.bodies.GeodeticPoint;
  28. import org.orekit.bodies.OneAxisEllipsoid;
  29. import org.orekit.data.BodiesElements;
  30. import org.orekit.frames.Frame;

  31. /**
  32.  * Modeling of displacement of reference points due to ocean loading.
  33.  * <p>
  34.  * This class implements the same model as IERS HARDIP.F program. For a
  35.  * given site, this model uses a set of amplitudes and phases for the 11
  36.  * main tides (M₂, S₂, N₂, K₂, K₁, O₁, P₁, Q₁, Mf, Mm, and Ssa) in BLQ
  37.  * format as provided by the <a
  38.  * href="http://holt.oso.chalmers.se/loading/">Bos-Scherneck web site</a>
  39.  * at Onsala Space Observatory. From these elements, additional admittances
  40.  * are derived using spline interpolation based on tides frequencies for
  41.  * a total of 342 tides, including the 11 main tides.
  42.  * </p>
  43.  * <p>
  44.  * This implementation is a complete rewrite of the original HARDISP.F program
  45.  * developed by Duncan Agnew and copyright 2008 IERS Conventions center. This
  46.  * derived work is not endorsed by the IERS conventions center. What remains
  47.  * from the original program is the model (spline interpolation and coefficients).
  48.  * The code by itself is completely different, using the underlying mathematical
  49.  * library for spline interpolation and the existing Orekit features for nutation
  50.  * arguments, time and time scales handling, tides modeling...
  51.  * </p>
  52.  * <p>
  53.  * Instances of this class are guaranteed to be immutable
  54.  * </p>
  55.  * <p>
  56.  * The original HARDISP.F program is distributed with the following notice:
  57.  * </p>
  58.  * <pre>
  59.  *  Copyright (C) 2008
  60.  *  IERS Conventions Center
  61.  *
  62.  *  ==================================
  63.  *  IERS Conventions Software License
  64.  *  ==================================
  65.  *
  66.  *  NOTICE TO USER:
  67.  *
  68.  *  BY USING THIS SOFTWARE YOU ACCEPT THE FOLLOWING TERMS AND CONDITIONS
  69.  *  WHICH APPLY TO ITS USE.
  70.  *
  71.  *  1. The Software is provided by the IERS Conventions Center ("the
  72.  *     Center").
  73.  *
  74.  *  2. Permission is granted to anyone to use the Software for any
  75.  *     purpose, including commercial applications, free of charge,
  76.  *     subject to the conditions and restrictions listed below.
  77.  *
  78.  *  3. You (the user) may adapt the Software and its algorithms for your
  79.  *     own purposes and you may distribute the resulting "derived work"
  80.  *     to others, provided that the derived work complies with the
  81.  *     following requirements:
  82.  *
  83.  *     a) Your work shall be clearly identified so that it cannot be
  84.  *        mistaken for IERS Conventions software and that it has been
  85.  *        neither distributed by nor endorsed by the Center.
  86.  *
  87.  *     b) Your work (including source code) must contain descriptions of
  88.  *        how the derived work is based upon and/or differs from the
  89.  *        original Software.
  90.  *
  91.  *     c) The name(s) of all modified routine(s) that you distribute
  92.  *        shall be changed.
  93.  *
  94.  *     d) The origin of the IERS Conventions components of your derived
  95.  *        work must not be misrepresented; you must not claim that you
  96.  *        wrote the original Software.
  97.  *
  98.  *     e) The source code must be included for all routine(s) that you
  99.  *        distribute.  This notice must be reproduced intact in any
  100.  *        source distribution.
  101.  *
  102.  *  4. In any published work produced by the user and which includes
  103.  *     results achieved by using the Software, you shall acknowledge
  104.  *     that the Software was used in obtaining those results.
  105.  *
  106.  *  5. The Software is provided to the user "as is" and the Center makes
  107.  *     no warranty as to its use or performance.   The Center does not
  108.  *     and cannot warrant the performance or results which the user may
  109.  *     obtain by using the Software.  The Center makes no warranties,
  110.  *     express or implied, as to non-infringement of third party rights,
  111.  *     merchantability, or fitness for any particular purpose.  In no
  112.  *     event will the Center be liable to the user for any consequential,
  113.  *     incidental, or special damages, including any lost profits or lost
  114.  *     savings, even if a Center representative has been advised of such
  115.  *     damages, or for any claim by any third party.
  116.  *
  117.  *  Correspondence concerning IERS Conventions software should be
  118.  *  addressed as follows:
  119.  *
  120.  *                     Gerard Petit
  121.  *     Internet email: gpetit[at]bipm.org
  122.  *     Postal address: IERS Conventions Center
  123.  *                     Time, frequency and gravimetry section, BIPM
  124.  *                     Pavillon de Breteuil
  125.  *                     92312 Sevres  FRANCE
  126.  *
  127.  *     or
  128.  *
  129.  *                     Brian Luzum
  130.  *     Internet email: brian.luzum[at]usno.navy.mil
  131.  *     Postal address: IERS Conventions Center
  132.  *                     Earth Orientation Department
  133.  *                     3450 Massachusetts Ave, NW
  134.  *                     Washington, DC 20392
  135.  * </pre>
  136.  * @see org.orekit.estimation.measurements.GroundStation
  137.  * @since 9.1
  138.  * @author Luc Maisonobe
  139.  */
  140. public class OceanLoading implements StationDisplacement {

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

  195.     // CHECKSTYLE: stop NoWhitespaceAfter check
  196.     /** Doodson arguments for all tides used. */
  197.     private static final int[][] DOODSON_ARGUMENTS = {
  198.         { 2,  0,  0,  0,  0,  0 }, { 2,  2, -2,  0,  0,  0 }, { 2, -1,  0,  1,  0,  0 },
  199.         { 2,  2,  0,  0,  0,  0 }, { 2,  2,  0,  0,  1,  0 }, { 2,  0,  0,  0, -1,  0 },
  200.         { 2, -1,  2, -1,  0,  0 }, { 2, -2,  2,  0,  0,  0 }, { 2,  1,  0, -1,  0,  0 },
  201.         { 2,  2, -3,  0,  0,  1 }, { 2, -2,  0,  2,  0,  0 }, { 2, -3,  2,  1,  0,  0 },
  202.         { 2,  1, -2,  1,  0,  0 }, { 2, -1,  0,  1, -1,  0 }, { 2,  3,  0, -1,  0,  0 },
  203.         { 2,  1,  0,  1,  0,  0 }, { 2,  2,  0,  0,  2,  0 }, { 2,  2, -1,  0,  0, -1 },
  204.         { 2,  0, -1,  0,  0,  1 }, { 2,  1,  0,  1,  1,  0 }, { 2,  3,  0, -1,  1,  0 },
  205.         { 2,  0,  1,  0,  0, -1 }, { 2,  0, -2,  2,  0,  0 }, { 2, -3,  0,  3,  0,  0 },
  206.         { 2, -2,  3,  0,  0, -1 }, { 2,  4,  0,  0,  0,  0 }, { 2, -1,  1,  1,  0, -1 },
  207.         { 2, -1,  3, -1,  0, -1 }, { 2,  2,  0,  0, -1,  0 }, { 2, -1, -1,  1,  0,  1 },
  208.         { 2,  4,  0,  0,  1,  0 }, { 2, -3,  4, -1,  0,  0 }, { 2, -1,  2, -1, -1,  0 },
  209.         { 2,  3, -2,  1,  0,  0 }, { 2,  1,  2, -1,  0,  0 }, { 2, -4,  2,  2,  0,  0 },
  210.         { 2,  4, -2,  0,  0,  0 }, { 2,  0,  2,  0,  0,  0 }, { 2, -2,  2,  0, -1,  0 },
  211.         { 2,  2, -4,  0,  0,  2 }, { 2,  2, -2,  0, -1,  0 }, { 2,  1,  0, -1, -1,  0 },
  212.         { 2, -1,  1,  0,  0,  0 }, { 2,  2, -1,  0,  0,  1 }, { 2,  2,  1,  0,  0, -1 },
  213.         { 2, -2,  0,  2, -1,  0 }, { 2, -2,  4, -2,  0,  0 }, { 2,  2,  2,  0,  0,  0 },
  214.         { 2, -4,  4,  0,  0,  0 }, { 2, -1,  0, -1, -2,  0 }, { 2,  1,  2, -1,  1,  0 },
  215.         { 2, -1, -2,  3,  0,  0 }, { 2,  3, -2,  1,  1,  0 }, { 2,  4,  0, -2,  0,  0 },
  216.         { 2,  0,  0,  2,  0,  0 }, { 2,  0,  2, -2,  0,  0 }, { 2,  0,  2,  0,  1,  0 },
  217.         { 2, -3,  3,  1,  0, -1 }, { 2,  0,  0,  0, -2,  0 }, { 2,  4,  0,  0,  2,  0 },
  218.         { 2,  4, -2,  0,  1,  0 }, { 2,  0,  0,  0,  0,  2 }, { 2,  1,  0,  1,  2,  0 },
  219.         { 2,  0, -2,  0, -2,  0 }, { 2, -2,  1,  0,  0,  1 }, { 2, -2,  1,  2,  0, -1 },
  220.         { 2, -1,  1, -1,  0,  1 }, { 2,  5,  0, -1,  0,  0 }, { 2,  1, -3,  1,  0,  1 },
  221.         { 2, -2, -1,  2,  0,  1 }, { 2,  3,  0, -1,  2,  0 }, { 2,  1, -2,  1, -1,  0 },
  222.         { 2,  5,  0, -1,  1,  0 }, { 2, -4,  0,  4,  0,  0 }, { 2, -3,  2,  1, -1,  0 },
  223.         { 2, -2,  1,  1,  0,  0 }, { 2,  4,  0, -2,  1,  0 }, { 2,  0,  0,  2,  1,  0 },
  224.         { 2, -5,  4,  1,  0,  0 }, { 2,  0,  2,  0,  2,  0 }, { 2, -1,  2,  1,  0,  0 },
  225.         { 2,  5, -2, -1,  0,  0 }, { 2,  1, -1,  0,  0,  0 }, { 2,  2, -2,  0,  0,  2 },
  226.         { 2, -5,  2,  3,  0,  0 }, { 2, -1, -2,  1, -2,  0 }, { 2, -3,  5, -1,  0, -1 },
  227.         { 2, -1,  0,  0,  0,  1 }, { 2, -2,  0,  0, -2,  0 }, { 2,  0, -1,  1,  0,  0 },
  228.         { 2, -3,  1,  1,  0,  1 }, { 2,  3,  0, -1, -1,  0 }, { 2,  1,  0,  1, -1,  0 },
  229.         { 2, -1,  2,  1,  1,  0 }, { 2,  0, -3,  2,  0,  1 }, { 2,  1, -1, -1,  0,  1 },
  230.         { 2, -3,  0,  3, -1,  0 }, { 2,  0, -2,  2, -1,  0 }, { 2, -4,  3,  2,  0, -1 },
  231.         { 2, -1,  0,  1, -2,  0 }, { 2,  5,  0, -1,  2,  0 }, { 2, -4,  5,  0,  0, -1 },
  232.         { 2, -2,  4,  0,  0, -2 }, { 2, -1,  0,  1,  0,  2 }, { 2, -2, -2,  4,  0,  0 },
  233.         { 2,  3, -2, -1, -1,  0 }, { 2, -2,  5, -2,  0, -1 }, { 2,  0, -1,  0, -1,  1 },
  234.         { 2,  5, -2, -1,  1,  0 }, { 1,  1,  0,  0,  0,  0 }, { 1, -1,  0,  0,  0,  0 },
  235.         { 1,  1, -2,  0,  0,  0 }, { 1, -2,  0,  1,  0,  0 }, { 1,  1,  0,  0,  1,  0 },
  236.         { 1, -1,  0,  0, -1,  0 }, { 1,  2,  0, -1,  0,  0 }, { 1,  0,  0,  1,  0,  0 },
  237.         { 1,  3,  0,  0,  0,  0 }, { 1, -2,  2, -1,  0,  0 }, { 1, -2,  0,  1, -1,  0 },
  238.         { 1, -3,  2,  0,  0,  0 }, { 1,  0,  0, -1,  0,  0 }, { 1,  1,  0,  0, -1,  0 },
  239.         { 1,  3,  0,  0,  1,  0 }, { 1,  1, -3,  0,  0,  1 }, { 1, -3,  0,  2,  0,  0 },
  240.         { 1,  1,  2,  0,  0,  0 }, { 1,  0,  0,  1,  1,  0 }, { 1,  2,  0, -1,  1,  0 },
  241.         { 1,  0,  2, -1,  0,  0 }, { 1,  2, -2,  1,  0,  0 }, { 1,  3, -2,  0,  0,  0 },
  242.         { 1, -1,  2,  0,  0,  0 }, { 1,  1,  1,  0,  0, -1 }, { 1,  1, -1,  0,  0,  1 },
  243.         { 1,  4,  0, -1,  0,  0 }, { 1, -4,  2,  1,  0,  0 }, { 1,  0, -2,  1,  0,  0 },
  244.         { 1, -2,  2, -1, -1,  0 }, { 1,  3,  0, -2,  0,  0 }, { 1, -1,  0,  2,  0,  0 },
  245.         { 1, -1,  0,  0, -2,  0 }, { 1,  3,  0,  0,  2,  0 }, { 1, -3,  2,  0, -1,  0 },
  246.         { 1,  4,  0, -1,  1,  0 }, { 1,  0,  0, -1, -1,  0 }, { 1,  1, -2,  0, -1,  0 },
  247.         { 1, -3,  0,  2, -1,  0 }, { 1,  1,  0,  0,  2,  0 }, { 1,  1, -1,  0,  0, -1 },
  248.         { 1, -1, -1,  0,  0,  1 }, { 1,  0,  2, -1,  1,  0 }, { 1, -1,  1,  0,  0, -1 },
  249.         { 1, -1, -2,  2,  0,  0 }, { 1,  2, -2,  1,  1,  0 }, { 1, -4,  0,  3,  0,  0 },
  250.         { 1, -1,  2,  0,  1,  0 }, { 1,  3, -2,  0,  1,  0 }, { 1,  2,  0, -1, -1,  0 },
  251.         { 1,  0,  0,  1, -1,  0 }, { 1, -2,  2,  1,  0,  0 }, { 1,  4, -2, -1,  0,  0 },
  252.         { 1, -3,  3,  0,  0, -1 }, { 1, -2,  1,  1,  0, -1 }, { 1, -2,  3, -1,  0, -1 },
  253.         { 1,  0, -2,  1, -1,  0 }, { 1, -2, -1,  1,  0,  1 }, { 1,  4, -2,  1,  0,  0 },
  254.         { 1, -4,  4, -1,  0,  0 }, { 1, -4,  2,  1, -1,  0 }, { 1,  5, -2,  0,  0,  0 },
  255.         { 1,  3,  0, -2,  1,  0 }, { 1, -5,  2,  2,  0,  0 }, { 1,  2,  0,  1,  0,  0 },
  256.         { 1,  1,  3,  0,  0, -1 }, { 1, -2,  0,  1, -2,  0 }, { 1,  4,  0, -1,  2,  0 },
  257.         { 1,  1, -4,  0,  0,  2 }, { 1,  5,  0, -2,  0,  0 }, { 1, -1,  0,  2,  1,  0 },
  258.         { 1, -2,  1,  0,  0,  0 }, { 1,  4, -2,  1,  1,  0 }, { 1, -3,  4, -2,  0,  0 },
  259.         { 1, -1,  3,  0,  0, -1 }, { 1,  3, -3,  0,  0,  1 }, { 1,  5, -2,  0,  1,  0 },
  260.         { 1,  1,  2,  0,  1,  0 }, { 1,  2,  0,  1,  1,  0 }, { 1, -5,  4,  0,  0,  0 },
  261.         { 1, -2,  0, -1, -2,  0 }, { 1,  5,  0, -2,  1,  0 }, { 1,  1,  2, -2,  0,  0 },
  262.         { 1,  1, -2,  2,  0,  0 }, { 1, -2,  2,  1,  1,  0 }, { 1,  0,  3, -1,  0, -1 },
  263.         { 1,  2, -3,  1,  0,  1 }, { 1, -2, -2,  3,  0,  0 }, { 1, -1,  2, -2,  0,  0 },
  264.         { 1, -4,  3,  1,  0, -1 }, { 1, -4,  0,  3, -1,  0 }, { 1, -1, -2,  2, -1,  0 },
  265.         { 1, -2,  0,  3,  0,  0 }, { 1,  4,  0, -3,  0,  0 }, { 1,  0,  1,  1,  0, -1 },
  266.         { 1,  2, -1, -1,  0,  1 }, { 1,  2, -2,  1, -1,  0 }, { 1,  0,  0, -1, -2,  0 },
  267.         { 1,  2,  0,  1,  2,  0 }, { 1,  2, -2, -1, -1,  0 }, { 1,  0,  0,  1,  2,  0 },
  268.         { 1,  0,  1,  0,  0,  0 }, { 1,  2, -1,  0,  0,  0 }, { 1,  0,  2, -1, -1,  0 },
  269.         { 1, -1, -2,  0, -2,  0 }, { 1, -3,  1,  0,  0,  1 }, { 1,  3, -2,  0, -1,  0 },
  270.         { 1, -1, -1,  0, -1,  1 }, { 1,  4, -2, -1,  1,  0 }, { 1,  2,  1, -1,  0, -1 },
  271.         { 1,  0, -1,  1,  0,  1 }, { 1, -2,  4, -1,  0,  0 }, { 1,  4, -4,  1,  0,  0 },
  272.         { 1, -3,  1,  2,  0, -1 }, { 1, -3,  3,  0, -1, -1 }, { 1,  1,  2,  0,  2,  0 },
  273.         { 1,  1, -2,  0, -2,  0 }, { 1,  3,  0,  0,  3,  0 }, { 1, -1,  2,  0, -1,  0 },
  274.         { 1, -2,  1, -1,  0,  1 }, { 1,  0, -3,  1,  0,  1 }, { 1, -3, -1,  2,  0,  1 },
  275.         { 1,  2,  0, -1,  2,  0 }, { 1,  6, -2, -1,  0,  0 }, { 1,  2,  2, -1,  0,  0 },
  276.         { 1, -1,  1,  0, -1, -1 }, { 1, -2,  3, -1, -1, -1 }, { 1, -1,  0,  0,  0,  2 },
  277.         { 1, -5,  0,  4,  0,  0 }, { 1,  1,  0,  0,  0, -2 }, { 1, -2,  1,  1, -1, -1 },
  278.         { 1,  1, -1,  0,  1,  1 }, { 1,  1,  2,  0,  0, -2 }, { 1, -3,  1,  1,  0,  0 },
  279.         { 1, -4,  4, -1, -1,  0 }, { 1,  1,  0, -2, -1,  0 }, { 1, -2, -1,  1, -1,  1 },
  280.         { 1, -3,  2,  2,  0,  0 }, { 1,  5, -2, -2,  0,  0 }, { 1,  3, -4,  2,  0,  0 },
  281.         { 1,  1, -2,  0,  0,  2 }, { 1, -1,  4, -2,  0,  0 }, { 1,  2,  2, -1,  1,  0 },
  282.         { 1, -5,  2,  2, -1,  0 }, { 1,  1, -3,  0, -1,  1 }, { 1,  1,  1,  0,  1, -1 },
  283.         { 1,  6, -2, -1,  1,  0 }, { 1, -2,  2, -1, -2,  0 }, { 1,  4, -2,  1,  2,  0 },
  284.         { 1, -6,  4,  1,  0,  0 }, { 1,  5, -4,  0,  0,  0 }, { 1, -3,  4,  0,  0,  0 },
  285.         { 1,  1,  2, -2,  1,  0 }, { 1, -2,  1,  0, -1,  0 }, { 0,  2,  0,  0,  0,  0 },
  286.         { 0,  1,  0, -1,  0,  0 }, { 0,  0,  2,  0,  0,  0 }, { 0,  0,  0,  0,  1,  0 },
  287.         { 0,  2,  0,  0,  1,  0 }, { 0,  3,  0, -1,  0,  0 }, { 0,  1, -2,  1,  0,  0 },
  288.         { 0,  2, -2,  0,  0,  0 }, { 0,  3,  0, -1,  1,  0 }, { 0,  0,  1,  0,  0, -1 },
  289.         { 0,  2,  0, -2,  0,  0 }, { 0,  2,  0,  0,  2,  0 }, { 0,  3, -2,  1,  0,  0 },
  290.         { 0,  1,  0, -1, -1,  0 }, { 0,  1,  0, -1,  1,  0 }, { 0,  4, -2,  0,  0,  0 },
  291.         { 0,  1,  0,  1,  0,  0 }, { 0,  0,  3,  0,  0, -1 }, { 0,  4,  0, -2,  0,  0 },
  292.         { 0,  3, -2,  1,  1,  0 }, { 0,  3, -2, -1,  0,  0 }, { 0,  4, -2,  0,  1,  0 },
  293.         { 0,  0,  2,  0,  1,  0 }, { 0,  1,  0,  1,  1,  0 }, { 0,  4,  0, -2,  1,  0 },
  294.         { 0,  3,  0, -1,  2,  0 }, { 0,  5, -2, -1,  0,  0 }, { 0,  1,  2, -1,  0,  0 },
  295.         { 0,  1, -2,  1, -1,  0 }, { 0,  1, -2,  1,  1,  0 }, { 0,  2, -2,  0, -1,  0 },
  296.         { 0,  2, -3,  0,  0,  1 }, { 0,  2, -2,  0,  1,  0 }, { 0,  0,  2, -2,  0,  0 },
  297.         { 0,  1, -3,  1,  0,  1 }, { 0,  0,  0,  0,  2,  0 }, { 0,  0,  1,  0,  0,  1 },
  298.         { 0,  1,  2, -1,  1,  0 }, { 0,  3,  0, -3,  0,  0 }, { 0,  2,  1,  0,  0, -1 },
  299.         { 0,  1, -1, -1,  0,  1 }, { 0,  1,  0,  1,  2,  0 }, { 0,  5, -2, -1,  1,  0 },
  300.         { 0,  2, -1,  0,  0,  1 }, { 0,  2,  2, -2,  0,  0 }, { 0,  1, -1,  0,  0,  0 },
  301.         { 0,  5,  0, -3,  0,  0 }, { 0,  2,  0, -2,  1,  0 }, { 0,  1,  1, -1,  0, -1 },
  302.         { 0,  3, -4,  1,  0,  0 }, { 0,  0,  2,  0,  2,  0 }, { 0,  2,  0, -2, -1,  0 },
  303.         { 0,  4, -3,  0,  0,  1 }, { 0,  3, -1, -1,  0,  1 }, { 0,  0,  2,  0,  0, -2 },
  304.         { 0,  3, -3,  1,  0,  1 }, { 0,  2, -4,  2,  0,  0 }, { 0,  4, -2, -2,  0,  0 },
  305.         { 0,  3,  1, -1,  0, -1 }, { 0,  5, -4,  1,  0,  0 }, { 0,  3, -2, -1, -1,  0 },
  306.         { 0,  3, -2,  1,  2,  0 }, { 0,  4, -4,  0,  0,  0 }, { 0,  6, -2, -2,  0,  0 },
  307.         { 0,  5,  0, -3,  1,  0 }, { 0,  4, -2,  0,  2,  0 }, { 0,  2,  2, -2,  1,  0 },
  308.         { 0,  0,  4,  0,  0, -2 }, { 0,  3, -1,  0,  0,  0 }, { 0,  3, -3, -1,  0,  1 },
  309.         { 0,  4,  0, -2,  2,  0 }, { 0,  1, -2, -1, -1,  0 }, { 0,  2, -1,  0,  0, -1 },
  310.         { 0,  4, -4,  2,  0,  0 }, { 0,  2,  1,  0,  1, -1 }, { 0,  3, -2, -1,  1,  0 },
  311.         { 0,  4, -3,  0,  1,  1 }, { 0,  2,  0,  0,  3,  0 }, { 0,  6, -4,  0,  0,  0 }
  312.     };
  313.     // CHECKSTYLE: resume NoWhitespaceAfter check

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

  316.     static {
  317.         CARTWRIGHT_EDDEN_AMPLITUDE_MAP = new HashMap<>(CARTWRIGHT_EDDEN_AMPLITUDE.length);
  318.         for (int i = 0; i < CARTWRIGHT_EDDEN_AMPLITUDE.length; ++i) {
  319.             CARTWRIGHT_EDDEN_AMPLITUDE_MAP.put(new Tide(DOODSON_ARGUMENTS[i][0], DOODSON_ARGUMENTS[i][1], DOODSON_ARGUMENTS[i][2],
  320.                                                         DOODSON_ARGUMENTS[i][3], DOODSON_ARGUMENTS[i][4], DOODSON_ARGUMENTS[i][5]),
  321.                                                CARTWRIGHT_EDDEN_AMPLITUDE[i]);
  322.         }
  323.     }

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

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

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

  334.         this.earth = earth;

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

  345.     }

  346.     /** {@inheritDoc} */
  347.     @Override
  348.     public Vector3D displacement(final BodiesElements elements, final Frame earthFrame,
  349.                                  final Vector3D referencePoint) {

  350.         // allocate arrays for each species splines
  351.         final UnivariateFunction[] realZSpline      = new UnivariateFunction[mainTides.length];
  352.         final UnivariateFunction[] imaginaryZSpline = new UnivariateFunction[mainTides.length];
  353.         final UnivariateFunction[] realWSpline      = new UnivariateFunction[mainTides.length];
  354.         final UnivariateFunction[] imaginaryWSpline = new UnivariateFunction[mainTides.length];
  355.         final UnivariateFunction[] realSSpline      = new UnivariateFunction[mainTides.length];
  356.         final UnivariateFunction[] imaginarySSpline = new UnivariateFunction[mainTides.length];

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

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

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

  371.         }

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

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

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

  388.             // phase for the current tide, including corrections
  389.             final double correction;
  390.             if (tide.getTauMultiplier() == 0) {
  391.                 correction = FastMath.PI;
  392.             } else if (tide.getTauMultiplier() == 1) {
  393.                 correction = 0.5 * FastMath.PI;
  394.             } else {
  395.                 correction = 0.0;
  396.             }
  397.             final double phase = tide.getPhase(elements) + correction;

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

  401.         }

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

  407.     }

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

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

  432.     }

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

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

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

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

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

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

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

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

  449.         /** Simple constructor.
  450.          * @param coefficients coefficients for the considered site
  451.          * @param i tide species
  452.          * @param j tide index in the species
  453.          * @param absAmplitude absolute value of the Cartwright-Edden amplitude of the tide
  454.          */
  455.         MainTideData(final OceanLoadingCoefficients coefficients, final int i, final int j, final double absAmplitude) {
  456.             // Sine and Cosine of difference angles
  457.             final SinCos scZenith = FastMath.sinCos(coefficients.getZenithPhase(i, j));
  458.             final SinCos scWest   = FastMath.sinCos(coefficients.getWestPhase(i, j));
  459.             final SinCos scSouth  = FastMath.sinCos(coefficients.getSouthPhase(i, j));
  460.             // Initialize attributes
  461.             tide       = coefficients.getTide(i, j);
  462.             realZ      = coefficients.getZenithAmplitude(i, j) * scZenith.cos() / absAmplitude;
  463.             imaginaryZ = coefficients.getZenithAmplitude(i, j) * scZenith.sin() / absAmplitude;
  464.             realW      = coefficients.getWestAmplitude(i, j)   * scWest.cos()   / absAmplitude;
  465.             imaginaryW = coefficients.getWestAmplitude(i, j)   * scWest.sin()   / absAmplitude;
  466.             realS      = coefficients.getSouthAmplitude(i, j)  * scSouth.cos()  / absAmplitude;
  467.             imaginaryS = coefficients.getSouthAmplitude(i, j)  * scSouth.sin()  / absAmplitude;
  468.         }

  469.     }

  470. }