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.troposphere;
18  
19  import org.hipparchus.Field;
20  import org.hipparchus.CalculusFieldElement;
21  import org.hipparchus.analysis.UnivariateFunction;
22  import org.hipparchus.analysis.interpolation.LinearInterpolator;
23  import org.hipparchus.util.FastMath;
24  import org.hipparchus.util.MathArrays;
25  import org.orekit.annotation.DefaultDataContext;
26  import org.orekit.bodies.FieldGeodeticPoint;
27  import org.orekit.bodies.GeodeticPoint;
28  import org.orekit.data.DataContext;
29  import org.orekit.models.earth.weather.FieldPressureTemperatureHumidity;
30  import org.orekit.models.earth.weather.PressureTemperatureHumidity;
31  import org.orekit.time.AbsoluteDate;
32  import org.orekit.time.DateTimeComponents;
33  import org.orekit.time.FieldAbsoluteDate;
34  import org.orekit.time.TimeScale;
35  import org.orekit.utils.FieldTrackingCoordinates;
36  import org.orekit.utils.TrackingCoordinates;
37  
38  /** The Niell Mapping Function  model for radio wavelengths.
39   *  This model is an empirical mapping function. It only needs the
40   *  values of the station latitude, height and the date for the computations.
41   *  <p>
42   *  With this model, the hydrostatic mapping function is time and latitude dependent
43   *  whereas the wet mapping function is only latitude dependent.
44   *  </p>
45   *
46   * @see "A. E. Niell(1996), Global mapping functions for the atmosphere delay of radio wavelengths,
47   *      J. Geophys. Res., 101(B2), pp.  3227–3246, doi:  10.1029/95JB03048."
48   *
49   * @author Bryan Cazabonne
50   *
51   */
52  @SuppressWarnings("deprecation")
53  public class NiellMappingFunctionModel implements MappingFunction, TroposphereMappingFunction {
54  
55      /** Values for the ah average function. */
56      private static final double[] VALUES_FOR_AH_AVERAGE = {
57          1.2769934e-3, 1.2683230e-3, 1.2465397e-3, 1.2196049e-3, 1.2045996e-3
58      };
59  
60      /** Values for the bh average function. */
61      private static final double[] VALUES_FOR_BH_AVERAGE = {
62          2.9153695e-3, 2.9152299e-3, 2.9288445e-3, 2.9022565e-3, 2.9024912e-3
63      };
64  
65      /** Values for the ch average function. */
66      private static final double[] VALUES_FOR_CH_AVERAGE = {
67          62.610505e-3, 62.837393e-3, 63.721774e-3, 63.824265e-3, 64.258455e-3
68      };
69  
70      /** Values for the ah amplitude function. */
71      private static final double[] VALUES_FOR_AH_AMPLITUDE = {
72          0.0, 1.2709626e-5, 2.6523662e-5, 3.4000452e-5, 4.1202191e-5
73      };
74  
75      /** Values for the bh amplitude function. */
76      private static final double[] VALUES_FOR_BH_AMPLITUDE = {
77          0.0, 2.1414979e-5, 3.0160779e-5, 7.2562722e-5, 11.723375e-5
78      };
79  
80      /** X values for the ch amplitude function. */
81      private static final double[] VALUES_FOR_CH_AMPLITUDE = {
82          0.0, 9.0128400e-5, 4.3497037e-5, 84.795348e-5, 170.37206e-5
83      };
84  
85      /** Values for the aw function. */
86      private static final double[] VALUES_FOR_AW = {
87          5.8021897e-4, 5.6794847e-4, 5.8118019e-4, 5.9727542e-4, 6.1641693e-4
88      };
89  
90      /** Values for the bw function. */
91      private static final double[] VALUES_FOR_BW = {
92          1.4275268e-3, 1.5138625e-3, 1.4572752e-3, 1.5007428e-3, 1.7599082e-3
93      };
94  
95      /** Values for the cw function. */
96      private static final double[] VALUES_FOR_CW = {
97          4.3472961e-2, 4.6729510e-2, 4.3908931e-2, 4.4626982e-2, 5.4736038e-2
98      };
99  
100     /** Values for the cw function. */
101     private static final double[] LATITUDE_VALUES = {
102         FastMath.toRadians(15.0), FastMath.toRadians(30.0), FastMath.toRadians(45.0), FastMath.toRadians(60.0), FastMath.toRadians(75.0),
103     };
104 
105     /** Interpolation function for the ah (average) term. */
106     private final UnivariateFunction ahAverageFunction;
107 
108     /** Interpolation function for the bh (average) term. */
109     private final UnivariateFunction bhAverageFunction;
110 
111     /** Interpolation function for the ch (average) term. */
112     private final UnivariateFunction chAverageFunction;
113 
114     /** Interpolation function for the ah (amplitude) term. */
115     private final UnivariateFunction ahAmplitudeFunction;
116 
117     /** Interpolation function for the bh (amplitude) term. */
118     private final UnivariateFunction bhAmplitudeFunction;
119 
120     /** Interpolation function for the ch (amplitude) term. */
121     private final UnivariateFunction chAmplitudeFunction;
122 
123     /** Interpolation function for the aw term. */
124     private final UnivariateFunction awFunction;
125 
126     /** Interpolation function for the bw term. */
127     private final UnivariateFunction bwFunction;
128 
129     /** Interpolation function for the cw term. */
130     private final UnivariateFunction cwFunction;
131 
132     /** UTC time scale. */
133     private final TimeScale utc;
134 
135     /** Builds a new instance.
136      *
137      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
138      *
139      * @see #NiellMappingFunctionModel(TimeScale)
140      */
141     @DefaultDataContext
142     public NiellMappingFunctionModel() {
143         this(DataContext.getDefault().getTimeScales().getUTC());
144     }
145 
146     /** Builds a new instance.
147      * @param utc UTC time scale.
148      * @since 10.1
149      */
150     public NiellMappingFunctionModel(final TimeScale utc) {
151         this.utc = utc;
152         // Interpolation functions for hydrostatic coefficients
153         this.ahAverageFunction    = new LinearInterpolator().interpolate(LATITUDE_VALUES, VALUES_FOR_AH_AVERAGE);
154         this.bhAverageFunction    = new LinearInterpolator().interpolate(LATITUDE_VALUES, VALUES_FOR_BH_AVERAGE);
155         this.chAverageFunction    = new LinearInterpolator().interpolate(LATITUDE_VALUES, VALUES_FOR_CH_AVERAGE);
156         this.ahAmplitudeFunction  = new LinearInterpolator().interpolate(LATITUDE_VALUES, VALUES_FOR_AH_AMPLITUDE);
157         this.bhAmplitudeFunction  = new LinearInterpolator().interpolate(LATITUDE_VALUES, VALUES_FOR_BH_AMPLITUDE);
158         this.chAmplitudeFunction  = new LinearInterpolator().interpolate(LATITUDE_VALUES, VALUES_FOR_CH_AMPLITUDE);
159 
160         // Interpolation functions for wet coefficients
161         this.awFunction  = new LinearInterpolator().interpolate(LATITUDE_VALUES, VALUES_FOR_AW);
162         this.bwFunction  = new LinearInterpolator().interpolate(LATITUDE_VALUES, VALUES_FOR_BW);
163         this.cwFunction  = new LinearInterpolator().interpolate(LATITUDE_VALUES, VALUES_FOR_CW);
164     }
165 
166     /** {@inheritDoc} */
167     @Override
168     @Deprecated
169     public double[] mappingFactors(final double elevation, final GeodeticPoint point,
170                                    final AbsoluteDate date) {
171         return mappingFactors(new TrackingCoordinates(0.0, elevation, 0.0),
172                               point,
173                               TroposphericModelUtils.STANDARD_ATMOSPHERE,
174                               date);
175     }
176 
177     /** {@inheritDoc} */
178     @Override
179     public double[] mappingFactors(final TrackingCoordinates trackingCoordinates, final GeodeticPoint point,
180                                    final PressureTemperatureHumidity weather,
181                                    final AbsoluteDate date) {
182         // Day of year computation
183         final DateTimeComponents dtc = date.getComponents(utc);
184         final int dofyear = dtc.getDate().getDayOfYear();
185 
186         // Temporal factor
187         double t0 = 28;
188         if (point.getLatitude() < 0) {
189             // southern hemisphere: t0 = 28 + an integer half of year
190             t0 += 183;
191         }
192         final double coef    = 2 * FastMath.PI * ((dofyear - t0) / 365.25);
193         final double cosCoef = FastMath.cos(coef);
194 
195         // Compute ah, bh and ch Eq. 5
196         double absLatidude = FastMath.abs(point.getLatitude());
197         // there are no data in the model for latitudes lower than 15°
198         absLatidude = FastMath.max(FastMath.toRadians(15.0), absLatidude);
199         // there are no data in the model for latitudes greater than 75°
200         absLatidude = FastMath.min(FastMath.toRadians(75.0), absLatidude);
201         final double ah = ahAverageFunction.value(absLatidude) - ahAmplitudeFunction.value(absLatidude) * cosCoef;
202         final double bh = bhAverageFunction.value(absLatidude) - bhAmplitudeFunction.value(absLatidude) * cosCoef;
203         final double ch = chAverageFunction.value(absLatidude) - chAmplitudeFunction.value(absLatidude) * cosCoef;
204 
205         final double[] function = new double[2];
206 
207         // Hydrostatic mapping factor
208         function[0] = TroposphericModelUtils.mappingFunction(ah, bh, ch, trackingCoordinates.getElevation());
209 
210         // Wet mapping factor
211         function[1] = TroposphericModelUtils.mappingFunction(awFunction.value(absLatidude),
212                                                              bwFunction.value(absLatidude),
213                                                              cwFunction.value(absLatidude),
214                                                              trackingCoordinates.getElevation());
215 
216         // Apply height correction
217         final double correction = TroposphericModelUtils.computeHeightCorrection(trackingCoordinates.getElevation(),
218                                                                                  point.getAltitude());
219         function[0] = function[0] + correction;
220 
221         return function;
222     }
223 
224     /** {@inheritDoc} */
225     @Override
226     @Deprecated
227     public <T extends CalculusFieldElement<T>> T[] mappingFactors(final T elevation, final FieldGeodeticPoint<T> point,
228                                                                   final FieldAbsoluteDate<T> date) {
229         return mappingFactors(new FieldTrackingCoordinates<>(date.getField().getZero(), elevation, date.getField().getZero()),
230                               point,
231                               new FieldPressureTemperatureHumidity<>(date.getField(),
232                                                                      TroposphericModelUtils.STANDARD_ATMOSPHERE),
233                               date);
234     }
235 
236     /** {@inheritDoc} */
237     @Override
238     public <T extends CalculusFieldElement<T>> T[] mappingFactors(final FieldTrackingCoordinates<T> trackingCoordinates,
239                                                                   final FieldGeodeticPoint<T> point,
240                                                                   final FieldPressureTemperatureHumidity<T> weather,
241                                                                   final FieldAbsoluteDate<T> date) {
242         final Field<T> field = date.getField();
243         final T zero = field.getZero();
244 
245         // Day of year computation
246         final DateTimeComponents dtc = date.getComponents(utc);
247         final int dofyear = dtc.getDate().getDayOfYear();
248 
249         // Temporal factor
250         double t0 = 28;
251         if (point.getLatitude().getReal() < 0) {
252             // southern hemisphere: t0 = 28 + an integer half of year
253             t0 += 183;
254         }
255         final T coef    = zero.getPi().multiply(2.0).multiply((dofyear - t0) / 365.25);
256         final T cosCoef = FastMath.cos(coef);
257 
258         // Compute ah, bh and ch Eq. 5
259         double absLatidude = FastMath.abs(point.getLatitude().getReal());
260         // there are no data in the model for latitudes lower than 15°
261         absLatidude = FastMath.max(FastMath.toRadians(15.0), absLatidude);
262         // there are no data in the model for latitudes greater than 75°
263         absLatidude = FastMath.min(FastMath.toRadians(75.0), absLatidude);
264         final T ah = cosCoef.multiply(ahAmplitudeFunction.value(absLatidude)).negate().add(ahAverageFunction.value(absLatidude));
265         final T bh = cosCoef.multiply(bhAmplitudeFunction.value(absLatidude)).negate().add(bhAverageFunction.value(absLatidude));
266         final T ch = cosCoef.multiply(chAmplitudeFunction.value(absLatidude)).negate().add(chAverageFunction.value(absLatidude));
267 
268         final T[] function = MathArrays.buildArray(field, 2);
269 
270         // Hydrostatic mapping factor
271         function[0] = TroposphericModelUtils.mappingFunction(ah, bh, ch,
272                                                              trackingCoordinates.getElevation());
273 
274         // Wet mapping factor
275         function[1] = TroposphericModelUtils.mappingFunction(zero.newInstance(awFunction.value(absLatidude)), zero.newInstance(bwFunction.value(absLatidude)),
276                                                              zero.newInstance(cwFunction.value(absLatidude)), trackingCoordinates.getElevation());
277 
278         // Apply height correction
279         final T correction = TroposphericModelUtils.computeHeightCorrection(trackingCoordinates.getElevation(),
280                                                                             point.getAltitude(),
281                                                                             field);
282         function[0] = function[0].add(correction);
283 
284         return function;
285     }
286 
287 }