TidalCorrection.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.frames;

  18. import java.util.ArrayList;
  19. import java.util.List;

  20. import org.apache.commons.math3.util.FastMath;
  21. import org.orekit.errors.OrekitException;
  22. import org.orekit.errors.TimeStampedCacheException;
  23. import org.orekit.time.AbsoluteDate;
  24. import org.orekit.time.TimeStamped;
  25. import org.orekit.utils.Constants;
  26. import org.orekit.utils.OrekitConfiguration;
  27. import org.orekit.utils.GenericTimeStampedCache;
  28. import org.orekit.utils.TimeStampedCache;
  29. import org.orekit.utils.TimeStampedGenerator;


  30. /** Compute tidal correction to the pole motion.
  31.  * <p>This class computes the diurnal and semidiurnal variations in the
  32.  * Earth orientation. It is a java translation of the fortran subroutine
  33.  * found at ftp://tai.bipm.org/iers/conv2003/chapter8/ortho_eop.f.</p>
  34.  * @author Pascal Parraud
  35.  * @author Evan Ward
  36.  * @deprecated as of 6.1 replaced by {@link org.orekit.utils.IERSConventions#getEOPTidalCorrection()}
  37.  */
  38. @Deprecated
  39. public class TidalCorrection {

  40.     /** pi;/2. */
  41.     private static final double HALF_PI = FastMath.PI / 2.0;

  42.     /** Angular units conversion factor. */
  43.     private static final double MICRO_ARC_SECONDS_TO_RADIANS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-6;

  44.     /** Time units conversion factor. */
  45.     private static final double MICRO_SECONDS_TO_SECONDS = 1.0e-6;

  46.     /** Number of interpolation points to use. */
  47.     private static final int INTERPOLATION_POINTS = 8;

  48.     /** Step size in days of the interpolation data points. */
  49.     private static final double STEP_SIZE = 3.0 / 32.0;

  50.     /** Difference in days between the modified julian day epoch and the year 1960. */
  51.     private static final double MJD_TO_1960 = 37076.5;

  52.     /** HS parameter. */
  53.     private static final double[] HS = {
  54.         -001.94, -001.25, -006.64, -001.51, -008.02,
  55.         -009.47, -050.20, -001.80, -009.54, +001.52,
  56.         -049.45, -262.21, +001.70, +003.43, +001.94,
  57.         +001.37, +007.41, +020.62, +004.14, +003.94,
  58.         -007.14, +001.37, -122.03, +001.02, +002.89,
  59.         -007.30, +368.78, +050.01, -001.08, +002.93,
  60.         +005.25, +003.95, +020.62, +004.09, +003.42,
  61.         +001.69, +011.29, +007.23, +001.51, +002.16,
  62.         +001.38, +001.80, +004.67, +016.01, +019.32,
  63.         +001.30, -001.02, -004.51, +120.99, +001.13,
  64.         +022.98, +001.06, -001.90, -002.18, -023.58,
  65.         +631.92, +001.92, -004.66, -017.86, +004.47,
  66.         +001.97, +017.20, +294.00, -002.46, -001.02,
  67.         +079.96, +023.83, +002.59, +004.47, +001.95,
  68.         +001.17
  69.     };

  70.     /** PHASE parameter. */
  71.     private static final double[] PHASE = {
  72.         +09.0899831 - HALF_PI, +08.8234208 - HALF_PI, +12.1189598 - HALF_PI,
  73.         +01.4425700 - HALF_PI, +04.7381090 - HALF_PI, +04.4715466 - HALF_PI,
  74.         +07.7670857 - HALF_PI, -02.9093042 - HALF_PI, +00.3862349 - HALF_PI,
  75.         -03.1758666 - HALF_PI, +00.1196725 - HALF_PI, +03.4152116 - HALF_PI,
  76.         +12.8946194 - HALF_PI, +05.5137686 - HALF_PI, +06.4441883 - HALF_PI,
  77.         -04.2322016 - HALF_PI, -00.9366625 - HALF_PI, +08.5427453 - HALF_PI,
  78.         +11.8382843 - HALF_PI, +01.1618945 - HALF_PI, +05.9693878 - HALF_PI,
  79.         -01.2032249 - HALF_PI, +02.0923141 - HALF_PI, -01.7847596 - HALF_PI,
  80.         +08.0679449 - HALF_PI, +00.8953321 - HALF_PI, +04.1908712 - HALF_PI,
  81.         +07.4864102 - HALF_PI, +10.7819493 - HALF_PI, +00.3137975 - HALF_PI,
  82.         +06.2894282 - HALF_PI, +07.2198478 - HALF_PI, -00.1610030 - HALF_PI,
  83.         +03.1345361 - HALF_PI, +02.8679737 - HALF_PI, -04.5128771 - HALF_PI,
  84.         +04.9665307 - HALF_PI, +08.2620698 - HALF_PI, +11.5576089 - HALF_PI,
  85.         +00.6146566 - HALF_PI, +03.9101957 - HALF_PI,
  86.         +20.6617051, +13.2808543, +16.3098310, +08.9289802, +05.0519065,
  87.         +15.8350306, +08.6624178, +11.9579569, +08.0808832, +04.5771061,
  88.         +00.7000324, +14.9869335, +11.4831564, +04.3105437, +07.6060827,
  89.         +03.7290090, +10.6350594, +03.2542086, +12.7336164, +16.0291555,
  90.         +10.1602590, +06.2831853, +02.4061116, +05.0862033, +08.3817423,
  91.         +11.6772814, +14.9728205, +04.0298682, +07.3254073, +09.1574019
  92.     };

  93.     /** FREQUENCY parameter. */
  94.     private static final double[] FREQUENCY = {
  95.         05.18688050, 05.38346657, 05.38439079, 05.41398343, 05.41490765,
  96.         05.61149372, 05.61241794, 05.64201057, 05.64293479, 05.83859664,
  97.         05.83952086, 05.84044508, 05.84433381, 05.87485066, 06.03795537,
  98.         06.06754801, 06.06847223, 06.07236095, 06.07328517, 06.10287781,
  99.         06.24878055, 06.26505830, 06.26598252, 06.28318449, 06.28318613,
  100.         06.29946388, 06.30038810, 06.30131232, 06.30223654, 06.31759007,
  101.         06.33479368, 06.49789839, 06.52841524, 06.52933946, 06.72592553,
  102.         06.75644239, 06.76033111, 06.76125533, 06.76217955, 06.98835826,
  103.         06.98928248, 11.45675174, 11.48726860, 11.68477889, 11.71529575,
  104.         11.73249771, 11.89560406, 11.91188181, 11.91280603, 11.93000800,
  105.         11.94332289, 11.96052486, 12.11031632, 12.12363121, 12.13990896,
  106.         12.14083318, 12.15803515, 12.33834347, 12.36886033, 12.37274905,
  107.         12.37367327, 12.54916865, 12.56637061, 12.58357258, 12.59985198,
  108.         12.60077620, 12.60170041, 12.60262463, 12.82880334, 12.82972756,
  109.         13.06071921
  110.     };

  111.     /** Orthotide weight factors. */
  112.     private static final double[] SP = {
  113.         0.0298,
  114.         0.1408,
  115.         0.0805,
  116.         0.6002,
  117.         0.3025,
  118.         0.1517,
  119.         0.0200,
  120.         0.0905,
  121.         0.0638,
  122.         0.3476,
  123.         0.1645,
  124.         0.0923
  125.     };

  126.     /** Orthoweights for X polar motion. */
  127.     private static final double[] ORTHOWX = {
  128.         -06.77832 * MICRO_ARC_SECONDS_TO_RADIANS,
  129.         -14.86323 * MICRO_ARC_SECONDS_TO_RADIANS,
  130.         +00.47884 * MICRO_ARC_SECONDS_TO_RADIANS,
  131.         -01.45303 * MICRO_ARC_SECONDS_TO_RADIANS,
  132.         +00.16406 * MICRO_ARC_SECONDS_TO_RADIANS,
  133.         +00.42030 * MICRO_ARC_SECONDS_TO_RADIANS,
  134.         +00.09398 * MICRO_ARC_SECONDS_TO_RADIANS,
  135.         +25.73054 * MICRO_ARC_SECONDS_TO_RADIANS,
  136.         -04.77974 * MICRO_ARC_SECONDS_TO_RADIANS,
  137.         +00.28080 * MICRO_ARC_SECONDS_TO_RADIANS,
  138.         +01.94539 * MICRO_ARC_SECONDS_TO_RADIANS,
  139.         -00.73089 * MICRO_ARC_SECONDS_TO_RADIANS
  140.     };

  141.     /** Orthoweights for Y polar motion. */
  142.     private static final double[] ORTHOWY = {
  143.         +14.86283 * MICRO_ARC_SECONDS_TO_RADIANS,
  144.         -06.77846 * MICRO_ARC_SECONDS_TO_RADIANS,
  145.         +01.45234 * MICRO_ARC_SECONDS_TO_RADIANS,
  146.         +00.47888 * MICRO_ARC_SECONDS_TO_RADIANS,
  147.         -00.42056 * MICRO_ARC_SECONDS_TO_RADIANS,
  148.         +00.16469 * MICRO_ARC_SECONDS_TO_RADIANS,
  149.         +15.30276 * MICRO_ARC_SECONDS_TO_RADIANS,
  150.         -04.30615 * MICRO_ARC_SECONDS_TO_RADIANS,
  151.         +00.07564 * MICRO_ARC_SECONDS_TO_RADIANS,
  152.         +02.28321 * MICRO_ARC_SECONDS_TO_RADIANS,
  153.         -00.45717 * MICRO_ARC_SECONDS_TO_RADIANS,
  154.         -01.62010 * MICRO_ARC_SECONDS_TO_RADIANS
  155.     };

  156.     /** Orthoweights for UT1. */
  157.     private static final double[] ORTHOWT = {
  158.         -1.76335 *  MICRO_SECONDS_TO_SECONDS,
  159.         +1.03364 *  MICRO_SECONDS_TO_SECONDS,
  160.         -0.27553 *  MICRO_SECONDS_TO_SECONDS,
  161.         +0.34569 *  MICRO_SECONDS_TO_SECONDS,
  162.         -0.12343 *  MICRO_SECONDS_TO_SECONDS,
  163.         -0.10146 *  MICRO_SECONDS_TO_SECONDS,
  164.         -0.47119 *  MICRO_SECONDS_TO_SECONDS,
  165.         +1.28997 *  MICRO_SECONDS_TO_SECONDS,
  166.         -0.19336 *  MICRO_SECONDS_TO_SECONDS,
  167.         +0.02724 *  MICRO_SECONDS_TO_SECONDS,
  168.         +0.08955 *  MICRO_SECONDS_TO_SECONDS,
  169.         +0.04726 *  MICRO_SECONDS_TO_SECONDS
  170.     };

  171.     /** Cache of computed tidal corrections. */
  172.     private final TimeStampedCache<CorrectionData> cache;

  173.     /** Simple constructor.
  174.      */
  175.     public TidalCorrection() {

  176.         // create cache
  177.         cache = new GenericTimeStampedCache<CorrectionData>(INTERPOLATION_POINTS,
  178.                                                      OrekitConfiguration.getCacheSlotsNumber(),
  179.                                                      Constants.JULIAN_YEAR, 7 * Constants.JULIAN_DAY,
  180.                                                      new Generator(), CorrectionData.class);

  181.     }

  182.     /** Get the dUT1 value.
  183.      * @param date date at which the value is desired
  184.      * @return dUT1 in seconds
  185.      */
  186.     public double getDUT1(final AbsoluteDate date) {

  187.         try {
  188.             final double t       = toDay(date);
  189.             final double tCenter = toDayQuantum(t);

  190.             final int n    = INTERPOLATION_POINTS;
  191.             final int nM12 = (n - 1) / 2;

  192.             final List<CorrectionData> corrections = cache.getNeighbors(date);

  193.             // copy points to a temporary array
  194.             final double[] dtNeville = new double[n];
  195.             for (int i = 0; i < n; i++) {
  196.                 dtNeville[i] = corrections.get(i).dt;
  197.             }

  198.             // interpolate corrections using Neville's algorithm
  199.             final double theta = (t - tCenter) / STEP_SIZE;
  200.             for (int j = 1; j < n; ++j) {
  201.                 for (int i = n - 1; i >= j; --i) {
  202.                     final double c1 = (theta + nM12 - i + j) / j;
  203.                     final double c2 = (theta + nM12 - i) / j;
  204.                     dtNeville[i] = c1 * dtNeville[i] - c2 * dtNeville[i - 1];
  205.                 }
  206.             }

  207.             return dtNeville[n - 1];
  208.         } catch (TimeStampedCacheException tce) {
  209.             // this should never happen as the generator is not bounded
  210.             throw OrekitException.createInternalError(tce);
  211.         }

  212.     }

  213.     /** Get the pole IERS Reference Pole correction.
  214.      * @param date date at which the correction is desired
  215.      * @return pole correction
  216.      */
  217.     public  PoleCorrection getPoleCorrection(final AbsoluteDate date) {

  218.         try {
  219.             final double t       = toDay(date);
  220.             final double tCenter = toDayQuantum(t);

  221.             final int n    = INTERPOLATION_POINTS;
  222.             final int nM12 = (n - 1) / 2;

  223.             final List<CorrectionData> corrections = this.cache.getNeighbors(date);

  224.             // copy points to a temporary array
  225.             final double[] dxNeville = new double[n];
  226.             final double[] dyNeville = new double[n];
  227.             for (int i = 0; i < n; i++) {
  228.                 final CorrectionData correction = corrections.get(i);
  229.                 dxNeville[i] = correction.dx;
  230.                 dyNeville[i] = correction.dy;
  231.             }

  232.             // interpolate corrections using Neville's algorithm
  233.             final double theta = (t - tCenter) / STEP_SIZE;
  234.             for (int j = 1; j < n; ++j) {
  235.                 for (int i = n - 1; i >= j; --i) {
  236.                     final double c1 = (theta + nM12 - i + j) / j;
  237.                     final double c2 = (theta + nM12 - i) / j;
  238.                     dxNeville[i] = c1 * dxNeville[i] - c2 * dxNeville[i - 1];
  239.                     dyNeville[i] = c1 * dyNeville[i] - c2 * dyNeville[i - 1];
  240.                 }
  241.             }

  242.             return new PoleCorrection(dxNeville[n - 1], dyNeville[n - 1]);
  243.         } catch (TimeStampedCacheException tce) {
  244.             // this should never happen as the generator is not bounded
  245.             throw OrekitException.createInternalError(tce);
  246.         }

  247.     }

  248.     /** Convert an {@link AbsoluteDate} to days past the epoch.
  249.      * @param date the date to convert
  250.      * @return days past the epoch, including the fractional part
  251.      */
  252.     private static double toDay(final AbsoluteDate date) {
  253.         return date.durationFrom(AbsoluteDate.MODIFIED_JULIAN_EPOCH) / Constants.JULIAN_DAY - MJD_TO_1960;
  254.     }

  255.     /** Convert days to an {@link AbsoluteDate}.
  256.      * @param t the time in days
  257.      * @return the date corresponding to {@code t}
  258.      */
  259.     private static AbsoluteDate toDate(final double t) {
  260.         return AbsoluteDate.MODIFIED_JULIAN_EPOCH.shiftedBy(Constants.JULIAN_DAY * (t + MJD_TO_1960));
  261.     }

  262.     /** Convert a day to the closest quantum in terms of {@link #STEP_SIZE}.
  263.      * @param t the day to convert
  264.      * @return the closest quantum before t, still in days
  265.      */
  266.     private static double toDayQuantum(final double t) {
  267.         return STEP_SIZE * FastMath.floor(t / STEP_SIZE);
  268.     }

  269.     /** Compute the partials of the tidal variations to the orthoweights.
  270.      * @param t offset from reference epoch in days
  271.      * @return the pole and UT1 correction
  272.      */
  273.     private static CorrectionData computeCorrections(final double t) {

  274.         // compute the time dependent potential matrix
  275.         final double d60A = t + 2;
  276.         final double d60B = t;
  277.         final double d60C = t - 2;

  278.         double anm00 = 0;
  279.         double anm01 = 0;
  280.         double anm02 = 0;
  281.         double bnm00 = 0;
  282.         double bnm01 = 0;
  283.         double bnm02 = 0;
  284.         for (int j = 0; j < 41; j++) {

  285.             final double hsj = HS[j];
  286.             final double pj  = PHASE[j];
  287.             final double fj  = FREQUENCY[j];

  288.             final double alphaA = pj + fj * d60A;
  289.             anm00 += hsj * FastMath.cos(alphaA);
  290.             bnm00 -= hsj * FastMath.sin(alphaA);

  291.             final double alphaB = pj + fj * d60B;
  292.             anm01 += hsj * FastMath.cos(alphaB);
  293.             bnm01 -= hsj * FastMath.sin(alphaB);

  294.             final double alphaC = pj + fj * d60C;
  295.             anm02 += hsj * FastMath.cos(alphaC);
  296.             bnm02 -= hsj * FastMath.sin(alphaC);

  297.         }

  298.         double anm10 = 0;
  299.         double anm11 = 0;
  300.         double anm12 = 0;
  301.         double bnm10 = 0;
  302.         double bnm11 = 0;
  303.         double bnm12 = 0;
  304.         for (int j = 41; j < HS.length; j++) {

  305.             final double hsj = HS[j];
  306.             final double pj  = PHASE[j];
  307.             final double fj  = FREQUENCY[j];

  308.             final double alphaA = pj + fj * d60A;
  309.             anm10 += hsj * FastMath.cos(alphaA);
  310.             bnm10 -= hsj * FastMath.sin(alphaA);

  311.             final double alphaB = pj + fj * d60B;
  312.             anm11 += hsj * FastMath.cos(alphaB);
  313.             bnm11 -= hsj * FastMath.sin(alphaB);

  314.             final double alphaC = pj + fj * d60C;
  315.             anm12 += hsj * FastMath.cos(alphaC);
  316.             bnm12 -= hsj * FastMath.sin(alphaC);

  317.         }

  318.         // orthogonalize the response terms ...
  319.         final double ap0 = anm02 + anm00;
  320.         final double am0 = anm02 - anm00;
  321.         final double bp0 = bnm02 + bnm00;
  322.         final double bm0 = bnm02 - bnm00;
  323.         final double ap1 = anm12 + anm10;
  324.         final double am1 = anm12 - anm10;
  325.         final double bp1 = bnm12 + bnm10;
  326.         final double bm1 = bnm12 - bnm10;

  327.         // ... and fill partials vector
  328.         final double partials0  = SP[0] * anm01;
  329.         final double partials1  = SP[0] * bnm01;
  330.         final double partials2  = SP[1] * anm01 - SP[2] * ap0;
  331.         final double partials3  = SP[1] * bnm01 - SP[2] * bp0;
  332.         final double partials4  = SP[3] * anm01 - SP[4] * ap0 + SP[5] * bm0;
  333.         final double partials5  = SP[3] * bnm01 - SP[4] * bp0 - SP[5] * am0;
  334.         final double partials6  = SP[6] * anm11;
  335.         final double partials7  = SP[6] * bnm11;
  336.         final double partials8  = SP[7] * anm11 - SP[8] * ap1;
  337.         final double partials9  = SP[7] * bnm11 - SP[8] * bp1;
  338.         final double partials10 = SP[9] * anm11 - SP[10] * ap1 + SP[11] * bm1;
  339.         final double partials11 = SP[9] * bnm11 - SP[10] * bp1 - SP[11] * am1;

  340.         // combine partials to set up corrections
  341.         final double dx =
  342.                     partials0 * ORTHOWX[0] + partials1  * ORTHOWX[1]  + partials2  * ORTHOWX[2] +
  343.                     partials3 * ORTHOWX[3] + partials4  * ORTHOWX[4]  + partials5  * ORTHOWX[5] +
  344.                     partials6 * ORTHOWX[6] + partials7  * ORTHOWX[7]  + partials8  * ORTHOWX[8] +
  345.                     partials9 * ORTHOWX[9] + partials10 * ORTHOWX[10] + partials11 * ORTHOWX[11];
  346.         final double dy =
  347.                     partials0 * ORTHOWY[0] + partials1  * ORTHOWY[1]  + partials2  * ORTHOWY[2] +
  348.                     partials3 * ORTHOWY[3] + partials4  * ORTHOWY[4]  + partials5  * ORTHOWY[5] +
  349.                     partials6 * ORTHOWY[6] + partials7  * ORTHOWY[7]  + partials8  * ORTHOWY[8] +
  350.                     partials9 * ORTHOWY[9] + partials10 * ORTHOWY[10] + partials11 * ORTHOWY[11];
  351.         final double dt =
  352.                     partials0 * ORTHOWT[0] + partials1  * ORTHOWT[1]  + partials2  * ORTHOWT[2] +
  353.                     partials3 * ORTHOWT[3] + partials4  * ORTHOWT[4]  + partials5  * ORTHOWT[5] +
  354.                     partials6 * ORTHOWT[6] + partials7  * ORTHOWT[7]  + partials8  * ORTHOWT[8] +
  355.                     partials9 * ORTHOWT[9] + partials10 * ORTHOWT[10] + partials11 * ORTHOWT[11];

  356.         return new CorrectionData(toDate(t), dx, dy, dt);

  357.     }

  358.     /** A data cache container for tidal correction data.
  359.      */
  360.     private static class CorrectionData implements TimeStamped {

  361.         /** date the correction is valid. */
  362.         private final AbsoluteDate date;

  363.         /** x component of the pole correction. */
  364.         private final double dx;

  365.         /** y component of the pole correction. */
  366.         private final double dy;

  367.         /** time component of the correction. */
  368.         private final double dt;

  369.         /** Create a new correction with the given data.
  370.          * @param date date the correction is valid
  371.          * @param dx x component of the pole correction
  372.          * @param dy y component of the pole correction
  373.          * @param dt time component of the correction
  374.          */
  375.         public CorrectionData(final AbsoluteDate date, final double dx, final double dy, final double dt) {
  376.             this.date = date;
  377.             this.dx   = dx;
  378.             this.dy   = dy;
  379.             this.dt   = dt;
  380.         }

  381.         /** {@inheritDoc} */
  382.         public AbsoluteDate getDate() {
  383.             return date;
  384.         }
  385.     }

  386.     /** Generates {@link CorrectionData} for a {@link GenericTimeStampedCache}.
  387.      */
  388.     private static class Generator implements TimeStampedGenerator<CorrectionData> {

  389.         /**
  390.          * {@inheritDoc}
  391.          * <p>
  392.          * <b>Note:</b> this {@link Generator} generates the minimum points necessary to
  393.          * cover the range (existing, date].
  394.          */
  395.         public List<CorrectionData> generate(final CorrectionData existing, final AbsoluteDate date)
  396.             throws TimeStampedCacheException {

  397.             // date in days
  398.             double tStart;
  399.             double tEnd;

  400.             if (existing == null) {
  401.                 // set tStart and tEnd so that n points are generated
  402.                 final int nM12 = (INTERPOLATION_POINTS - 1) / 2;
  403.                 // days to subtract from tStart
  404.                 final double extraBefore = STEP_SIZE * nM12;
  405.                 // days to add to tEnd
  406.                 final double extraAfter = STEP_SIZE * (INTERPOLATION_POINTS - nM12);
  407.                 tStart = toDayQuantum(toDay(date)) - extraBefore;
  408.                 tEnd   = toDayQuantum(toDay(date) + extraAfter);
  409.             } else if (existing.getDate().compareTo(date) > 0) {
  410.                 // existing is after date
  411.                 tStart = toDayQuantum(toDay(date));
  412.                 tEnd   = toDayQuantum(toDay(existing.getDate()));
  413.             } else {
  414.                 // existing is before or same as date
  415.                 tStart = toDayQuantum(toDay(existing.getDate())) + STEP_SIZE;
  416.                 tEnd   = toDayQuantum(toDay(date)) + STEP_SIZE;
  417.             }

  418.             // n is number of points to generate. (tEnd - tStart) / STEP_SIZE should
  419.             // already be *very* close to an integer
  420.             final int n = (int) FastMath.round((tEnd - tStart) / STEP_SIZE);

  421.             // list of generated points
  422.             final List<CorrectionData> generated = new ArrayList<CorrectionData>(n);

  423.             // compute new reference points in [tStart, tEnd)
  424.             for (int i = 0; i < n; ++i) {
  425.                 generated.add(computeCorrections(tStart + i * STEP_SIZE));
  426.             }

  427.             return generated;
  428.         }
  429.     }
  430. }