1   /* Copyright 2002-2025 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.ionosphere;
18  
19  import java.util.Collections;
20  import java.util.List;
21  
22  import org.hipparchus.CalculusFieldElement;
23  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
24  import org.hipparchus.geometry.euclidean.threed.Vector3D;
25  import org.hipparchus.util.FastMath;
26  import org.orekit.frames.FieldStaticTransform;
27  import org.orekit.frames.StaticTransform;
28  import org.orekit.frames.TopocentricFrame;
29  import org.orekit.propagation.FieldSpacecraftState;
30  import org.orekit.propagation.SpacecraftState;
31  import org.orekit.time.AbsoluteDate;
32  import org.orekit.time.FieldAbsoluteDate;
33  import org.orekit.utils.ParameterDriver;
34  
35  /**
36   * An estimated ionospheric model. The ionospheric delay is computed according to the formula:
37   * <p>
38   *           40.3
39   *    δ =  --------  *  STEC      with, STEC = VTEC * F(elevation)
40   *            f²
41   * </p>
42   * With:
43   * <ul>
44   * <li>f: The frequency of the signal in Hz.</li>
45   * <li>STEC: The Slant Total Electron Content in TECUnits.</li>
46   * <li>VTEC: The Vertical Total Electron Content in TECUnits.</li>
47   * <li>F(elevation): A mapping function which depends on satellite elevation.</li>
48   * </ul>
49   * The VTEC is estimated as a {@link ParameterDriver}
50   *
51   * @author Bryan Cazabonne
52   * @since 10.2
53   */
54  public class EstimatedIonosphericModel implements IonosphericModel, IonosphericDelayModel {
55  
56      /** Name of the parameter of this model: the Vertical Total Electron Content. */
57      public static final String VERTICAL_TOTAL_ELECTRON_CONTENT = "vertical total electron content";
58  
59      /** Ionospheric delay factor. */
60      private static final double FACTOR = 40.3e16;
61  
62      /** Ionospheric mapping Function model. */
63      private final transient IonosphericMappingFunction model;
64  
65      /** Driver for the Vertical Total Electron Content.*/
66      private final transient ParameterDriver vtec;
67  
68  
69      /**
70       * Build a new instance.
71       * @param model ionospheric mapping function
72       * @param vtecValue value of the Vertical Total Electron Content in TECUnits
73       */
74      public EstimatedIonosphericModel(final IonosphericMappingFunction model, final double vtecValue) {
75          this.model = model;
76          this.vtec  = new ParameterDriver(EstimatedIonosphericModel.VERTICAL_TOTAL_ELECTRON_CONTENT,
77                                           vtecValue, FastMath.scalb(1.0, 3), 0.0, 1000.0);
78      }
79  
80      /** {@inheritDoc} */
81      @Override
82      public double pathDelay(final SpacecraftState state, final TopocentricFrame baseFrame,
83                              final double frequency, final double[] parameters) {
84          return pathDelay(state, baseFrame, state.getDate(), frequency, parameters);
85      }
86  
87      /** {@inheritDoc} */
88      @Override
89      public double pathDelay(final SpacecraftState state,
90                              final TopocentricFrame baseFrame, final AbsoluteDate receptionDate,
91                              final double frequency, final double[] parameters) {
92  
93          // we use transform from base frame to inert frame and invert it
94          // because base frame could be peered with inertial frame (hence improving performances with caching)
95          // but the reverse is almost never used
96          final StaticTransform base2Inert = baseFrame.getStaticTransformTo(state.getFrame(), receptionDate);
97          final Vector3D        position   = base2Inert.getInverse().transformPosition(state.getPosition());
98  
99          // Elevation in radians
100         final double          elevation  = position.getDelta();
101 
102         // Only consider measures above the horizon
103         if (elevation > 0.0) {
104             // Delay
105             return pathDelay(elevation, frequency, parameters);
106         }
107 
108         return 0.0;
109     }
110 
111     /**
112      * Calculates the ionospheric path delay for the signal path from a ground
113      * station to a satellite.
114      * <p>
115      * The path delay is computed for any elevation angle.
116      * </p>
117      * @param elevation elevation of the satellite in radians
118      * @param frequency frequency of the signal in Hz
119      * @param parameters ionospheric model parameters
120      * @return the path delay due to the ionosphere in m
121      */
122     public double pathDelay(final double elevation, final double frequency, final double[] parameters) {
123         // Square of the frequency
124         final double freq2 = frequency * frequency;
125         // Mapping factor
126         final double fz = model.mappingFactor(elevation);
127         // "Slant" Total Electron Content
128         final double stec = parameters[0] * fz;
129         // Delay computation
130         final double alpha  = FACTOR / freq2;
131         return alpha * stec;
132     }
133 
134     /** {@inheritDoc} */
135     @Override
136     public <T extends CalculusFieldElement<T>> T pathDelay(final FieldSpacecraftState<T> state, final TopocentricFrame baseFrame,
137                                                        final double frequency, final T[] parameters) {
138         return pathDelay(state, baseFrame, state.getDate(), frequency, parameters);
139     }
140 
141     /** {@inheritDoc} */
142     @Override
143     public <T extends CalculusFieldElement<T>> T pathDelay(final FieldSpacecraftState<T> state,
144                                                            final TopocentricFrame baseFrame, final FieldAbsoluteDate<T> receptionDate,
145                                                            final double frequency, final T[] parameters) {
146 
147         // we use transform from base frame to inert frame and invert it
148         // because base frame could be peered with inertial frame (hence improving performances with caching)
149         // but the reverse is almost never used
150         final FieldStaticTransform<T> base2Inert = baseFrame.getStaticTransformTo(state.getFrame(), receptionDate);
151         final FieldVector3D<T>        position   = base2Inert.getInverse().transformPosition(state.getPosition());
152 
153         // Elevation in radians
154         final T elevation = position.getDelta();
155 
156         if (elevation.getReal() > 0.0) {
157             // Delay
158             return pathDelay(elevation, frequency, parameters);
159         }
160 
161         return elevation.getField().getZero();
162     }
163 
164     /**
165      * Calculates the ionospheric path delay for the signal path from a ground
166      * station to a satellite.
167      * <p>
168      * The path delay is computed for any elevation angle.
169      * </p>
170      * @param <T> type of the elements
171      * @param elevation elevation of the satellite in radians
172      * @param frequency frequency of the signal in Hz
173      * @param parameters ionospheric model parameters at state date
174      * @return the path delay due to the ionosphere in m
175      */
176     public <T extends CalculusFieldElement<T>> T pathDelay(final T elevation, final double frequency, final T[] parameters) {
177         // Square of the frequency
178         final double freq2 = frequency * frequency;
179         // Mapping factor
180         final T fz = model.mappingFactor(elevation);
181         // "Slant" Total Electron Content
182         final T stec = parameters[0].multiply(fz);
183         // Delay computation
184         final double alpha  = FACTOR / freq2;
185         return stec.multiply(alpha);
186     }
187 
188     @Override
189     public List<ParameterDriver> getParametersDrivers() {
190         return Collections.singletonList(vtec);
191     }
192 
193 }