ModifiedHopfieldModel.java

  1. /* Copyright 2022-2025 Thales Alenia Space
  2.  * Licensed to CS Communication & Systèmes (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.troposphere;

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

  20. import org.hipparchus.CalculusFieldElement;
  21. import org.hipparchus.util.FastMath;
  22. import org.hipparchus.util.FieldSinCos;
  23. import org.hipparchus.util.MathUtils;
  24. import org.hipparchus.util.SinCos;
  25. import org.orekit.bodies.FieldGeodeticPoint;
  26. import org.orekit.bodies.GeodeticPoint;
  27. import org.orekit.models.earth.weather.FieldPressureTemperatureHumidity;
  28. import org.orekit.models.earth.weather.PressureTemperatureHumidity;
  29. import org.orekit.models.earth.weather.PressureTemperatureHumidityProvider;
  30. import org.orekit.time.AbsoluteDate;
  31. import org.orekit.time.FieldAbsoluteDate;
  32. import org.orekit.utils.FieldTrackingCoordinates;
  33. import org.orekit.utils.ParameterDriver;
  34. import org.orekit.utils.TrackingCoordinates;

  35. /** The modified Hopfield model.
  36.  * <p>
  37.  * This model from Hopfield 1969, 1970, 1972 is described in equations
  38.  * 5.105, 5.106, 5.107 and 5.108 in Guochang Xu, GPS - Theory, Algorithms
  39.  * and Applications, Springer, 2007.
  40.  * </p>
  41.  * @author Luc Maisonobe
  42.  * @see "Guochang Xu, GPS - Theory, Algorithms and Applications, Springer, 2007"
  43.  * @since 12.1
  44.  */
  45. public class ModifiedHopfieldModel implements TroposphericModel {

  46.     /** Constant for dry altitude effect. */
  47.     private static final double HD0 = 40136.0;

  48.     /** Slope for dry altitude effect. */
  49.     private static final double HD1 = 148.72;

  50.     /** Temperature reference. */
  51.     private static final double T0 = 273.16;

  52.     /** Constant for wet altitude effect. */
  53.     private static final double HW0 = 11000.0;

  54.     /** Dry delay factor. */
  55.     private static final double ND = 77.64e-6;

  56.     /** Wet delay factor, degree 1. */
  57.     private static final double NW1 = -12.96e-6;

  58.     /** Wet delay factor, degree 2. */
  59.     private static final double NW2 = 0.371800;

  60.     /** BAse radius. */
  61.     private static final double RE = 6378137.0;

  62.     /** Provider for pressure, temperature and humidity.
  63.      * @since 13.0
  64.      */
  65.     private final PressureTemperatureHumidityProvider pthProvider;

  66.     /** Create a new Hopfield model.
  67.      * @param pthProvider provider for pressure, temperature and humidity
  68.      * @since 13.0
  69.      */
  70.     public ModifiedHopfieldModel(final PressureTemperatureHumidityProvider pthProvider) {
  71.         this.pthProvider = pthProvider;
  72.     }

  73.     /** {@inheritDoc} */
  74.     @Override
  75.     public TroposphericDelay pathDelay(final TrackingCoordinates trackingCoordinates,
  76.                                        final GeodeticPoint point,
  77.                                        final double[] parameters, final AbsoluteDate date) {

  78.         // zenith angle
  79.         final double zenithAngle = MathUtils.SEMI_PI - trackingCoordinates.getElevation();

  80.         // compute weather parameters
  81.         final PressureTemperatureHumidity weather = pthProvider.getWeatherParameters(point, date);

  82.         // dry component
  83.         final double hd  = HD0 + HD1 * (weather.getTemperature() - T0);
  84.         final double nd  = ND * TroposphericModelUtils.HECTO_PASCAL.fromSI(weather.getPressure()) /
  85.                            weather.getTemperature();

  86.         // wet component
  87.         final double hw  = HW0;
  88.         final double nw  = (NW1 + NW2 / weather.getTemperature()) / weather.getTemperature();

  89.         return  new TroposphericDelay(delay(0.0,         hd, nd),
  90.                                       delay(0.0,         hw, nw),
  91.                                       delay(zenithAngle, hd, nd),
  92.                                       delay(zenithAngle, hw, nw));

  93.     }

  94.     /** {@inheritDoc}
  95.      * <p>
  96.      * The Saastamoinen model is not defined for altitudes below 0.0. for continuity
  97.      * reasons, we use the value for h = 0 when altitude is negative.
  98.      * </p>
  99.      * <p>
  100.      * There are also numerical issues for elevation angles close to zero. For continuity reasons,
  101.      * elevations lower than a threshold will use the value obtained
  102.      * for the threshold itself.
  103.      * </p>
  104.      */
  105.     @Override
  106.     public <T extends CalculusFieldElement<T>> FieldTroposphericDelay<T> pathDelay(final FieldTrackingCoordinates<T> trackingCoordinates,
  107.                                                                                    final FieldGeodeticPoint<T> point,
  108.                                                                                    final T[] parameters, final FieldAbsoluteDate<T> date) {

  109.         // zenith angle
  110.         final T zenithAngle = trackingCoordinates.getElevation().negate().add(MathUtils.SEMI_PI);

  111.         // compute weather parameters
  112.         final FieldPressureTemperatureHumidity<T> weather = pthProvider.getWeatherParameters(point, date);

  113.         // dry component
  114.         final T hd = weather.getTemperature().subtract(T0).multiply(HD1).add(HD0);
  115.         final T nd = TroposphericModelUtils.HECTO_PASCAL.fromSI(weather.getPressure()).
  116.                      multiply(ND).
  117.                      divide(weather.getTemperature());

  118.         // wet component
  119.         final T hw = date.getField().getZero().newInstance(HW0);
  120.         final T nw = weather.getTemperature().reciprocal().multiply(NW2).add(NW1).divide(weather.getTemperature());

  121.         return  new FieldTroposphericDelay<>(delay(date.getField().getZero(), hd, nd),
  122.                                              delay(date.getField().getZero(), hw, nw),
  123.                                              delay(zenithAngle,               hd, nd),
  124.                                              delay(zenithAngle,               hw, nw));

  125.     }

  126.     /** {@inheritDoc} */
  127.     @Override
  128.     public List<ParameterDriver> getParametersDrivers() {
  129.         return Collections.emptyList();
  130.     }

  131.     /** Compute the 9 terms sum delay.
  132.      * @param zenithAngle zenith angle
  133.      * @param hi altitude effect
  134.      * @param ni delay factor
  135.      * @return 9 terms sum delay
  136.      */
  137.     private double delay(final double zenithAngle, final double hi, final double ni) {

  138.         // equation 5.107
  139.         final SinCos scZ   = FastMath.sinCos(zenithAngle);
  140.         final double rePhi = RE + hi;
  141.         final double reS   = RE * scZ.sin();
  142.         final double reC   = RE * scZ.cos();
  143.         final double ri    = FastMath.sqrt(rePhi * rePhi - reS * reS) - reC;

  144.         final double ai    = -scZ.cos() / hi;
  145.         final double bi    = -scZ.sin() * scZ.sin() / (2 * hi * RE);
  146.         final double ai2   = ai * ai;
  147.         final double bi2   = bi * bi;

  148.         final double f1i   = 1;
  149.         final double f2i   = 4 * ai;
  150.         final double f3i   = 6 * ai2 + 4 * bi;
  151.         final double f4i   = 4 * ai * (ai2 + 3 * bi);
  152.         final double f5i   = ai2 * ai2 + 12 * ai2 * bi + 6 * bi2;
  153.         final double f6i   = 4 * ai * bi * (ai2 + 3 * bi);
  154.         final double f7i   = bi2 * (6 * ai2 + 4 * bi);
  155.         final double f8i   = 4 * ai * bi * bi2;
  156.         final double f9i   = bi2 * bi2;

  157.         return ni * (ri * (f1i +
  158.                            ri * (f2i / 2 +
  159.                                  ri * (f3i / 3 +
  160.                                        ri * (f4i / 4 +
  161.                                              ri * (f5i / 5 +
  162.                                                    ri * (f6i / 6 +
  163.                                                           ri * (f7i / 7 +
  164.                                                                 ri * (f8i / 8 +
  165.                                                                       ri * f9i / 9)))))))));

  166.     }

  167.     /** Compute the 9 terms sum delay.
  168.      * @param <T> type of the elements
  169.      * @param zenithAngle zenith angle
  170.      * @param hi altitude effect
  171.      * @param ni delay factor
  172.      * @return 9 terms sum delay
  173.      */
  174.     private <T extends CalculusFieldElement<T>> T delay(final T zenithAngle, final T hi, final T ni) {

  175.         // equation 5.107
  176.         final FieldSinCos<T> scZ   = FastMath.sinCos(zenithAngle);
  177.         final T rePhi = hi.add(RE);
  178.         final T reS   = scZ.sin().multiply(RE);
  179.         final T reC   = scZ.cos().multiply(RE);
  180.         final T ri    = FastMath.sqrt(rePhi.multiply(rePhi).subtract(reS.multiply(reS))).subtract(reC);

  181.         final T ai    = scZ.cos().negate().divide(hi);
  182.         final T bi    = scZ.sin().multiply(scZ.sin()).negate().divide(hi.add(hi).multiply(RE));
  183.         final T ai2   = ai.multiply(ai);
  184.         final T bi2   = bi.multiply(bi);

  185.         final T f1i   = ai.getField().getOne();
  186.         final T f2i   = ai.multiply(4);
  187.         final T f3i   = ai2.multiply(6).add(bi.multiply(4));
  188.         final T f4i   = ai.multiply(4).multiply(ai2.add(bi.multiply(3)));
  189.         final T f5i   = ai2.multiply(ai2).add(ai2.multiply(12).multiply(bi)).add(bi2.multiply(6));
  190.         final T f6i   = ai.multiply(4).multiply(bi).multiply(ai2.add(bi.multiply(3)));
  191.         final T f7i   = bi2.multiply(ai2.multiply(6).add(bi.multiply(4)));
  192.         final T f8i   = ai.multiply(4).multiply(bi).multiply(bi2);
  193.         final T f9i   = bi2.multiply(bi2);

  194.         return ni.
  195.                multiply(ri.
  196.                         multiply(f1i.
  197.                                  add(ri.
  198.                                      multiply(f2i.divide(2).
  199.                                               add(ri.
  200.                                                   multiply(f3i.divide(3).
  201.                                                            add(ri.
  202.                                                                multiply(f4i.divide(4).
  203.                                                                         add(ri.
  204.                                                                             multiply(f5i.divide(5).
  205.                                                                                      add(ri.
  206.                                                                                          multiply(f6i.divide(6).
  207.                                                                                                   add(ri.
  208.                                                                                                       multiply(f7i.divide(7).
  209.                                                                                                                add(ri.
  210.                                                                                                                    multiply(f8i.divide(8).
  211.                                                                                                                             add(ri.multiply(f9i.divide(9)))))))))))))))))));

  212.     }

  213. }