HarrisPriester.java

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

  18. import org.hipparchus.RealFieldElement;
  19. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  20. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  21. import org.hipparchus.util.FastMath;
  22. import org.hipparchus.util.Precision;
  23. import org.orekit.bodies.OneAxisEllipsoid;
  24. import org.orekit.errors.OrekitException;
  25. import org.orekit.errors.OrekitMessages;
  26. import org.orekit.frames.Frame;
  27. import org.orekit.time.AbsoluteDate;
  28. import org.orekit.time.FieldAbsoluteDate;
  29. import org.orekit.utils.PVCoordinatesProvider;


  30. /** This atmosphere model is the realization of the Modified Harris-Priester model.
  31.  * <p>
  32.  * This model is a static one that takes into account the diurnal density bulge.
  33.  * It doesn't need any space weather data but a density vs. altitude table, which
  34.  * depends on solar activity.
  35.  * </p>
  36.  * <p>
  37.  * The implementation relies on the book:<br>
  38.  * <b>Satellite Orbits</b><br>
  39.  * <i>Oliver Montenbruck, Eberhard Gill</i><br>
  40.  * Springer 2005
  41.  * </p>
  42.  * @author Pascal Parraud
  43.  */
  44. public class HarrisPriester implements Atmosphere {

  45.     /** Serializable UID.*/
  46.     private static final long serialVersionUID = 2772347498196369601L;

  47.     // Constants :

  48.     /** Default cosine exponent value. */
  49.     private static final int N_DEFAULT = 4;

  50.     /** Minimal value for calculating poxer of cosine. */
  51.     private static final double MIN_COS = 1.e-12;

  52.     /** Lag angle for diurnal bulge. */
  53.     private static final double LAG = FastMath.toRadians(30.0);
  54.     /** Lag angle cosine. */
  55.     private static final double COSLAG = FastMath.cos(LAG);
  56.     /** Lag angle sine. */
  57.     private static final double SINLAG = FastMath.sin(LAG);

  58.     // CHECKSTYLE: stop NoWhitespaceAfter check
  59.     /** Harris-Priester min-max density (kg/m3) vs. altitude (m) table.
  60.      *  These data are valid for a mean solar activity. */
  61.     private static final double[][] ALT_RHO = {
  62.         {  100000.0, 4.974e-07, 4.974e-07 },
  63.         {  120000.0, 2.490e-08, 2.490e-08 },
  64.         {  130000.0, 8.377e-09, 8.710e-09 },
  65.         {  140000.0, 3.899e-09, 4.059e-09 },
  66.         {  150000.0, 2.122e-09, 2.215e-09 },
  67.         {  160000.0, 1.263e-09, 1.344e-09 },
  68.         {  170000.0, 8.008e-10, 8.758e-10 },
  69.         {  180000.0, 5.283e-10, 6.010e-10 },
  70.         {  190000.0, 3.617e-10, 4.297e-10 },
  71.         {  200000.0, 2.557e-10, 3.162e-10 },
  72.         {  210000.0, 1.839e-10, 2.396e-10 },
  73.         {  220000.0, 1.341e-10, 1.853e-10 },
  74.         {  230000.0, 9.949e-11, 1.455e-10 },
  75.         {  240000.0, 7.488e-11, 1.157e-10 },
  76.         {  250000.0, 5.709e-11, 9.308e-11 },
  77.         {  260000.0, 4.403e-11, 7.555e-11 },
  78.         {  270000.0, 3.430e-11, 6.182e-11 },
  79.         {  280000.0, 2.697e-11, 5.095e-11 },
  80.         {  290000.0, 2.139e-11, 4.226e-11 },
  81.         {  300000.0, 1.708e-11, 3.526e-11 },
  82.         {  320000.0, 1.099e-11, 2.511e-11 },
  83.         {  340000.0, 7.214e-12, 1.819e-11 },
  84.         {  360000.0, 4.824e-12, 1.337e-11 },
  85.         {  380000.0, 3.274e-12, 9.955e-12 },
  86.         {  400000.0, 2.249e-12, 7.492e-12 },
  87.         {  420000.0, 1.558e-12, 5.684e-12 },
  88.         {  440000.0, 1.091e-12, 4.355e-12 },
  89.         {  460000.0, 7.701e-13, 3.362e-12 },
  90.         {  480000.0, 5.474e-13, 2.612e-12 },
  91.         {  500000.0, 3.916e-13, 2.042e-12 },
  92.         {  520000.0, 2.819e-13, 1.605e-12 },
  93.         {  540000.0, 2.042e-13, 1.267e-12 },
  94.         {  560000.0, 1.488e-13, 1.005e-12 },
  95.         {  580000.0, 1.092e-13, 7.997e-13 },
  96.         {  600000.0, 8.070e-14, 6.390e-13 },
  97.         {  620000.0, 6.012e-14, 5.123e-13 },
  98.         {  640000.0, 4.519e-14, 4.121e-13 },
  99.         {  660000.0, 3.430e-14, 3.325e-13 },
  100.         {  680000.0, 2.632e-14, 2.691e-13 },
  101.         {  700000.0, 2.043e-14, 2.185e-13 },
  102.         {  720000.0, 1.607e-14, 1.779e-13 },
  103.         {  740000.0, 1.281e-14, 1.452e-13 },
  104.         {  760000.0, 1.036e-14, 1.190e-13 },
  105.         {  780000.0, 8.496e-15, 9.776e-14 },
  106.         {  800000.0, 7.069e-15, 8.059e-14 },
  107.         {  840000.0, 4.680e-15, 5.741e-14 },
  108.         {  880000.0, 3.200e-15, 4.210e-14 },
  109.         {  920000.0, 2.210e-15, 3.130e-14 },
  110.         {  960000.0, 1.560e-15, 2.360e-14 },
  111.         { 1000000.0, 1.150e-15, 1.810e-14 }
  112.     };
  113.     // CHECKSTYLE: resume NoWhitespaceAfter check

  114.     /** Cosine exponent from 2 to 6 according to inclination. */
  115.     private double n;

  116.     /** Sun position. */
  117.     private PVCoordinatesProvider sun;

  118.     /** Earth body shape. */
  119.     private OneAxisEllipsoid earth;

  120.     /** Density table. */
  121.     private double[][] tabAltRho;

  122.     /** Simple constructor for Modified Harris-Priester atmosphere model.
  123.      *  <p>The cosine exponent value is set to 4 by default.</p>
  124.      *  <p>The default embedded density table is the one given in the referenced
  125.      *  book from Montenbruck &amp; Gill. It is given for mean solar activity and
  126.      *  spreads over 100 to 1000 km.</p>
  127.      * @param sun the sun position
  128.      * @param earth the earth body shape
  129.      */
  130.     public HarrisPriester(final PVCoordinatesProvider sun,
  131.                           final OneAxisEllipsoid earth) {
  132.         this(sun, earth, ALT_RHO, N_DEFAULT);
  133.     }

  134.     /** Constructor for Modified Harris-Priester atmosphere model.
  135.      *  <p>Recommanded values for the cosine exponent spread over the range
  136.      *  2, for low inclination orbits, to 6, for polar orbits.</p>
  137.      *  <p> The default embedded density table is the one given in the referenced
  138.      *  book from Montenbruck &amp; Gill. It is given for mean solar activity and
  139.      *  spreads over 100 to 1000 km. </p>
  140.      *  @param sun the sun position
  141.      * @param earth the earth body shape
  142.      * @param n the cosine exponent
  143.      */
  144.     public HarrisPriester(final PVCoordinatesProvider sun,
  145.                           final OneAxisEllipsoid earth,
  146.                           final double n) {
  147.         this(sun, earth, ALT_RHO, n);
  148.     }

  149.     /** Constructor for Modified Harris-Priester atmosphere model.
  150.      *  <p>The provided density table must be an array such as:
  151.      *  <ul>
  152.      *   <li>tabAltRho[][0] = altitude (m)</li>
  153.      *   <li>tabAltRho[][1] = min density (kg/m³)</li>
  154.      *   <li>tabAltRho[][2] = max density (kg/m³)</li>
  155.      *  </ul>
  156.      *  <p> The altitude must be increasing without limitation in range. The
  157.      *  internal density table is a copy of the provided one.
  158.      *
  159.      *  <p>The cosine exponent value is set to 4 by default.</p>
  160.      * @param sun the sun position
  161.      * @param earth the earth body shape
  162.      * @param tabAltRho the density table
  163.      */
  164.     public HarrisPriester(final PVCoordinatesProvider sun,
  165.                           final OneAxisEllipsoid earth,
  166.                           final double[][] tabAltRho) {
  167.         this(sun, earth, tabAltRho, N_DEFAULT);
  168.     }

  169.     /** Constructor for Modified Harris-Priester atmosphere model.
  170.      *  <p>Recommanded values for the cosine exponent spread over the range
  171.      *  2, for low inclination orbits, to 6, for polar orbits.</p>
  172.      *  <p>The provided density table must be an array such as:
  173.      *  <ul>
  174.      *   <li>tabAltRho[][0] = altitude (m)</li>
  175.      *   <li>tabAltRho[][1] = min density (kg/m³)</li>
  176.      *   <li>tabAltRho[][2] = max density (kg/m³)</li>
  177.      *  </ul>
  178.      *  <p> The altitude must be increasing without limitation in range. The
  179.      *  internal density table is a copy of the provided one.
  180.      *
  181.      *  @param sun the sun position
  182.      * @param earth the earth body shape
  183.      * @param tabAltRho the density table
  184.      * @param n the cosine exponent
  185.      */
  186.     public HarrisPriester(final PVCoordinatesProvider sun,
  187.                           final OneAxisEllipsoid earth,
  188.                           final double[][] tabAltRho,
  189.                           final double n) {
  190.         this.sun   = sun;
  191.         this.earth = earth;
  192.         setTabDensity(tabAltRho);
  193.         setN(n);
  194.     }

  195.     /** {@inheritDoc} */
  196.     public Frame getFrame() {
  197.         return earth.getBodyFrame();
  198.     }

  199.     /** Set parameter N, the cosine exponent.
  200.      *  @param n the cosine exponent
  201.      */
  202.     private void setN(final double n) {
  203.         this.n = n;
  204.     }

  205.     /** Set a user define density table to deal with different solar activities.
  206.      *  @param tab density vs. altitude table
  207.      */
  208.     private void setTabDensity(final double[][] tab) {
  209.         this.tabAltRho = new double[tab.length][];
  210.         for (int i = 0; i < tab.length; i++) {
  211.             this.tabAltRho[i] = tab[i].clone();
  212.         }
  213.     }

  214.     /** Get the current density table.
  215.      *  <p>The density table is an array such as:
  216.      *  <ul>
  217.      *   <li>tabAltRho[][0] = altitude (m)</li>
  218.      *   <li>tabAltRho[][1] = min density (kg/m³)</li>
  219.      *   <li>tabAltRho[][2] = max density (kg/m³)</li>
  220.      *  </ul>
  221.      *  <p> The altitude must be increasing without limitation in range.
  222.      *
  223.      *  <p>
  224.      *  The returned density table is a copy of the current one.
  225.      *  </p>
  226.      *  @return density vs. altitude table
  227.      */
  228.     public double[][] getTabDensity() {
  229.         final double[][] copy = new double[tabAltRho.length][];
  230.         for (int i = 0; i < tabAltRho.length; i++) {
  231.             copy[i] = tabAltRho[i].clone();
  232.         }
  233.         return copy;
  234.     }

  235.     /** Get the minimal altitude for the model.
  236.      * <p>No computation is possible below this altitude.</p>
  237.      *  @return the minimal altitude (m)
  238.      */
  239.     public double getMinAlt() {
  240.         return tabAltRho[0][0];
  241.     }

  242.     /** Get the maximal altitude for the model.
  243.      * <p>Above this altitude, density is assumed to be zero.</p>
  244.      *  @return the maximal altitude (m)
  245.      */
  246.     public double getMaxAlt() {
  247.         return tabAltRho[tabAltRho.length - 1][0];
  248.     }

  249.     /** Get the local density.
  250.      * @param sunInEarth position of the Sun in Earth frame (m)
  251.      * @param posInEarth target position in Earth frame (m)
  252.      * @return the local density (kg/m³)
  253.      * @exception OrekitException if altitude is below the model minimal altitude
  254.      */
  255.     public double getDensity(final Vector3D sunInEarth, final Vector3D posInEarth)
  256.         throws OrekitException {

  257.         final double posAlt = getHeight(posInEarth);
  258.         // Check for height boundaries
  259.         if (posAlt < getMinAlt()) {
  260.             throw new OrekitException(OrekitMessages.ALTITUDE_BELOW_ALLOWED_THRESHOLD, posAlt, getMinAlt());
  261.         }
  262.         if (posAlt > getMaxAlt()) {
  263.             return 0.;
  264.         }

  265.         // Diurnal bulge apex direction
  266.         final Vector3D sunDir = sunInEarth.normalize();
  267.         final Vector3D bulDir = new Vector3D(sunDir.getX() * COSLAG - sunDir.getY() * SINLAG,
  268.                                              sunDir.getX() * SINLAG + sunDir.getY() * COSLAG,
  269.                                              sunDir.getZ());

  270.         // Cosine of angle Psi between the diurnal bulge apex and the satellite
  271.         final double cosPsi = bulDir.normalize().dotProduct(posInEarth.normalize());
  272.         // (1 + cos(Psi))/2 = cos²(Psi/2)
  273.         final double c2Psi2 = (1. + cosPsi) / 2.;
  274.         final double cPsi2  = FastMath.sqrt(c2Psi2);
  275.         final double cosPow = (cPsi2 > MIN_COS) ? c2Psi2 * FastMath.pow(cPsi2, n - 2) : 0.;

  276.         // Search altitude index in density table
  277.         int ia = 0;
  278.         while (ia < tabAltRho.length - 2 && posAlt > tabAltRho[ia + 1][0]) {
  279.             ia++;
  280.         }

  281.         // Fractional satellite height
  282.         final double dH = (tabAltRho[ia][0] - posAlt) / (tabAltRho[ia][0] - tabAltRho[ia + 1][0]);

  283.         // Min exponential density interpolation
  284.         final double rhoMin = tabAltRho[ia][1] * FastMath.pow(tabAltRho[ia + 1][1] / tabAltRho[ia][1], dH);

  285.         if (Precision.equals(cosPow, 0.)) {
  286.             return rhoMin;
  287.         } else {
  288.             // Max exponential density interpolation
  289.             final double rhoMax = tabAltRho[ia][2] * FastMath.pow(tabAltRho[ia + 1][2] / tabAltRho[ia][2], dH);
  290.             return rhoMin + (rhoMax - rhoMin) * cosPow;
  291.         }

  292.     }

  293.     /** Get the local density.
  294.      * @param sunInEarth position of the Sun in Earth frame (m)
  295.      * @param posInEarth target position in Earth frame (m)
  296.      * @return the local density (kg/m³)
  297.      * @param <T> instance of RealFieldElement<T>
  298.      * @exception OrekitException if altitude is below the model minimal altitude
  299.      */
  300.     public <T extends RealFieldElement<T>> T getDensity(final Vector3D sunInEarth, final FieldVector3D<T> posInEarth)
  301.         throws OrekitException {
  302.         final T zero = posInEarth.getX().getField().getZero();
  303.         final T posAlt = getHeight(posInEarth);
  304.         // Check for height boundaries
  305.         if (posAlt.getReal() < getMinAlt()) {
  306.             throw new OrekitException(OrekitMessages.ALTITUDE_BELOW_ALLOWED_THRESHOLD, posAlt, getMinAlt());
  307.         }
  308.         if (posAlt.getReal() > getMaxAlt()) {
  309.             return zero;
  310.         }

  311.         // Diurnal bulge apex direction
  312.         final Vector3D sunDir = sunInEarth.normalize();
  313.         final Vector3D bulDir = new Vector3D(sunDir.getX() * COSLAG - sunDir.getY() * SINLAG,
  314.                                              sunDir.getX() * SINLAG + sunDir.getY() * COSLAG,
  315.                                              sunDir.getZ());

  316.         // Cosine of angle Psi between the diurnal bulge apex and the satellite
  317.         final T cosPsi = posInEarth.normalize().dotProduct(bulDir.normalize());
  318.         // (1 + cos(Psi))/2 = cos²(Psi/2)
  319.         final T c2Psi2 = cosPsi.add(1.).divide(2);
  320.         final T cPsi2  = c2Psi2.sqrt();
  321.         final T cosPow = (cPsi2.getReal() > MIN_COS) ? c2Psi2.multiply(cPsi2.pow(n - 2)) : zero;

  322.         // Search altitude index in density table
  323.         int ia = 0;
  324.         while (ia < tabAltRho.length - 2 && posAlt.getReal() > tabAltRho[ia + 1][0]) {
  325.             ia++;
  326.         }

  327.         // Fractional satellite height
  328.         final T dH = posAlt.negate().add(tabAltRho[ia][0]).divide(tabAltRho[ia][0] - tabAltRho[ia + 1][0]);

  329.         // Min exponential density interpolation
  330.         final T rhoMin = zero.add(tabAltRho[ia + 1][1] / tabAltRho[ia][1]).pow(dH).multiply(tabAltRho[ia][1]);

  331.         if (Precision.equals(cosPow.getReal(), 0.)) {
  332.             return zero.add(rhoMin);
  333.         } else {
  334.             // Max exponential density interpolation
  335.             final T rhoMax = zero.add(tabAltRho[ia + 1][2] / tabAltRho[ia][2]).pow(dH).multiply(tabAltRho[ia][2]);
  336.             return rhoMin.add(rhoMax.subtract(rhoMin).multiply(cosPow));
  337.         }

  338.     }

  339.     /** Get the local density at some position.
  340.      * @param date current date
  341.      * @param position current position
  342.      * @param frame the frame in which is defined the position
  343.      * @return local density (kg/m³)
  344.      * @exception OrekitException if some frame conversion cannot be performed
  345.      *            or if altitude is below the model minimal altitude
  346.      */
  347.     public double getDensity(final AbsoluteDate date, final Vector3D position, final Frame frame)
  348.         throws OrekitException {

  349.         // Sun position in earth frame
  350.         final Vector3D sunInEarth = sun.getPVCoordinates(date, earth.getBodyFrame()).getPosition();

  351.         // Target position in earth frame
  352.         final Vector3D posInEarth = frame.getTransformTo(earth.getBodyFrame(), date).transformPosition(position);

  353.         return getDensity(sunInEarth, posInEarth);
  354.     }

  355.     /** Get the local density at some position.
  356.      * @param date current date
  357.      * @param position current position
  358.      * @param <T> implements a RealFieldElement
  359.      * @param frame the frame in which is defined the position
  360.      * @return local density (kg/m³)
  361.      * @exception OrekitException if some frame conversion cannot be performed
  362.      *            or if altitude is below the model minimal altitude
  363.      */
  364.     public <T extends RealFieldElement<T>> T getDensity(final FieldAbsoluteDate<T> date,
  365.                                                         final FieldVector3D<T> position,
  366.                                                         final Frame frame)
  367.             throws OrekitException {
  368.         // Sun position in earth frame
  369.         final Vector3D sunInEarth = sun.getPVCoordinates(date.toAbsoluteDate(), earth.getBodyFrame()).getPosition();

  370.         // Target position in earth frame
  371.         final FieldVector3D<T> posInEarth = frame.getTransformTo(earth.getBodyFrame(), date.toAbsoluteDate()).transformPosition(position);

  372.         return getDensity(sunInEarth, posInEarth);
  373.     }

  374.     /** Get the height above the Earth for the given position.
  375.      *  <p>
  376.      *  The height computation is an approximation valid for the considered atmosphere.
  377.      *  </p>
  378.      *  @param position current position in Earth frame
  379.      *  @return height (m)
  380.      */
  381.     private double getHeight(final Vector3D position) {
  382.         final double a    = earth.getEquatorialRadius();
  383.         final double f    = earth.getFlattening();
  384.         final double e2   = f * (2. - f);
  385.         final double r    = position.getNorm();
  386.         final double sl   = position.getZ() / r;
  387.         final double cl2  = 1. - sl * sl;
  388.         final double coef = FastMath.sqrt((1. - e2) / (1. - e2 * cl2));

  389.         return r - a * coef;
  390.     }

  391.     /** Get the height above the Earth for the given position.
  392.      *  <p>
  393.      *  The height computation is an approximation valid for the considered atmosphere.
  394.      *  </p>
  395.      *  @param position current position in Earth frame
  396.      *  @param <T> instance of RealFieldElement<T>
  397.      *  @return height (m)
  398.      */
  399.     private <T extends RealFieldElement<T>> T getHeight(final FieldVector3D<T> position) {
  400.         final double a    = earth.getEquatorialRadius();
  401.         final double f    = earth.getFlattening();
  402.         final double e2   = f * (2. - f);
  403.         final T r    = position.getNorm();
  404.         final T sl   = position.getZ().divide(r);
  405.         final T cl2  = sl.multiply(sl).negate().add(1.);
  406.         final T coef = cl2.multiply(-e2).add(1.).reciprocal().multiply(1. - e2).sqrt();

  407.         return r.subtract(coef.multiply(a));
  408.     }

  409. }