1 /* Copyright 2002-2021 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.Field;
23 import org.hipparchus.CalculusFieldElement;
24 import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
25 import org.hipparchus.geometry.euclidean.threed.Vector3D;
26 import org.hipparchus.util.FastMath;
27 import org.hipparchus.util.FieldSinCos;
28 import org.hipparchus.util.MathUtils;
29 import org.hipparchus.util.SinCos;
30 import org.orekit.annotation.DefaultDataContext;
31 import org.orekit.bodies.FieldGeodeticPoint;
32 import org.orekit.bodies.GeodeticPoint;
33 import org.orekit.data.DataContext;
34 import org.orekit.frames.TopocentricFrame;
35 import org.orekit.propagation.FieldSpacecraftState;
36 import org.orekit.propagation.SpacecraftState;
37 import org.orekit.time.AbsoluteDate;
38 import org.orekit.time.DateTimeComponents;
39 import org.orekit.time.FieldAbsoluteDate;
40 import org.orekit.time.TimeScale;
41 import org.orekit.utils.Constants;
42 import org.orekit.utils.ParameterDriver;
43
44 /**
45 * Klobuchar ionospheric delay model.
46 * Klobuchar ionospheric delay model is designed as a GNSS correction model.
47 * The parameters for the model are provided by the GPS satellites in their broadcast
48 * messsage.
49 * This model is based on the assumption the electron content is concentrated
50 * in 350 km layer.
51 *
52 * The delay refers to L1 (1575.42 MHz).
53 * If the delay is sought for L2 (1227.60 MHz), multiply the result by 1.65 (Klobuchar, 1996).
54 * More generally, since ionospheric delay is inversely proportional to the square of the signal
55 * frequency f, to adapt this model to other GNSS frequencies f, multiply by (L1 / f)^2.
56 *
57 * References:
58 * ICD-GPS-200, Rev. C, (1997), pp. 125-128
59 * Klobuchar, J.A., Ionospheric time-delay algorithm for single-frequency GPS users,
60 * IEEE Transactions on Aerospace and Electronic Systems, Vol. 23, No. 3, May 1987
61 * Klobuchar, J.A., "Ionospheric Effects on GPS", Global Positioning System: Theory and
62 * Applications, 1996, pp.513-514, Parkinson, Spilker.
63 *
64 * @author Joris Olympio
65 * @since 7.1
66 *
67 */
68 public class KlobucharIonoModel implements IonosphericModel {
69
70 /** Serializable UID. */
71 private static final long serialVersionUID = 7277525837842061107L;
72
73 /** The 4 coefficients of a cubic equation representing the amplitude of the vertical delay. Units are sec/semi-circle^(i-1) for the i-th coefficient, i=1, 2, 3, 4. */
74 private final double[] alpha;
75
76 /** The 4 coefficients of a cubic equation representing the period of the model. Units are sec/semi-circle^(i-1) for the i-th coefficient, i=1, 2, 3, 4. */
77 private final double[] beta;
78
79 /** GPS time scale. */
80 private final TimeScale gps;
81
82 /** Create a new Klobuchar ionospheric delay model, when a single frequency system is used.
83 * This model accounts for at least 50 percent of RMS error due to ionospheric propagation effect (ICD-GPS-200)
84 *
85 * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
86 *
87 * @param alpha coefficients of a cubic equation representing the amplitude of the vertical delay.
88 * @param beta coefficients of a cubic equation representing the period of the model.
89 * @see #KlobucharIonoModel(double[], double[], TimeScale)
90 */
91 @DefaultDataContext
92 public KlobucharIonoModel(final double[] alpha, final double[] beta) {
93 this(alpha, beta, DataContext.getDefault().getTimeScales().getGPS());
94 }
95
96 /**
97 * Create a new Klobuchar ionospheric delay model, when a single frequency system is
98 * used. This model accounts for at least 50 percent of RMS error due to ionospheric
99 * propagation effect (ICD-GPS-200)
100 *
101 * @param alpha coefficients of a cubic equation representing the amplitude of the
102 * vertical delay.
103 * @param beta coefficients of a cubic equation representing the period of the
104 * model.
105 * @param gps GPS time scale.
106 * @since 10.1
107 */
108 public KlobucharIonoModel(final double[] alpha,
109 final double[] beta,
110 final TimeScale gps) {
111 this.alpha = alpha.clone();
112 this.beta = beta.clone();
113 this.gps = gps;
114 }
115
116 /**
117 * Calculates the ionospheric path delay for the signal path from a ground
118 * station to a satellite.
119 * <p>
120 * The path delay is computed for any elevation angle.
121 * </p>
122 * @param date current date
123 * @param geo geodetic point of receiver/station
124 * @param elevation elevation of the satellite in radians
125 * @param azimuth azimuth of the satellite in radians
126 * @param frequency frequency of the signal in Hz
127 * @param parameters ionospheric model parameters
128 * @return the path delay due to the ionosphere in m
129 */
130 public double pathDelay(final AbsoluteDate date, final GeodeticPoint geo,
131 final double elevation, final double azimuth, final double frequency,
132 final double[] parameters) {
133
134 // Sine and cosine of the azimuth
135 final SinCos sc = FastMath.sinCos(azimuth);
136
137 // degrees to semicircles
138 final double rad2semi = 1. / FastMath.PI;
139 final double semi2rad = FastMath.PI;
140
141 // Earth Centered angle
142 final double psi = 0.0137 / (elevation / FastMath.PI + 0.11) - 0.022;
143
144 // Subionospheric latitude: the latitude of the IPP (Ionospheric Pierce Point)
145 // in [-0.416, 0.416], semicircle
146 final double latIono = FastMath.min(
147 FastMath.max(geo.getLatitude() * rad2semi + psi * sc.cos(), -0.416),
148 0.416);
149
150 // Subionospheric longitude: the longitude of the IPP
151 // in semicircle
152 final double lonIono = geo.getLongitude() * rad2semi + (psi * sc.sin() / FastMath.cos(latIono * semi2rad));
153
154 // Geomagnetic latitude, semicircle
155 final double latGeom = latIono + 0.064 * FastMath.cos((lonIono - 1.617) * semi2rad);
156
157 // day of week and tow (sec)
158 // Note: Sunday=0, Monday=1, Tuesday=2, Wednesday=3, Thursday=4, Friday=5, Saturday=6
159 final DateTimeComponents dtc = date.getComponents(gps);
160 final int dofweek = dtc.getDate().getDayOfWeek();
161 final double secday = dtc.getTime().getSecondsInLocalDay();
162 final double tow = dofweek * 86400. + secday;
163
164 final double t = 43200. * lonIono + tow;
165 final double tsec = t - FastMath.floor(t / 86400.) * 86400; // Seconds of day
166
167 // Slant factor, semicircle
168 final double slantFactor = 1.0 + 16.0 * FastMath.pow(0.53 - elevation / FastMath.PI, 3);
169
170 // Period of model, seconds
171 final double period = FastMath.max(72000., beta[0] + (beta[1] + (beta[2] + beta[3] * latGeom) * latGeom) * latGeom);
172
173 // Phase of the model, radians
174 // (Max at 14.00 = 50400 sec local time)
175 final double x = 2.0 * FastMath.PI * (tsec - 50400.0) / period;
176
177 // Amplitude of the model, seconds
178 final double amplitude = FastMath.max(0, alpha[0] + (alpha[1] + (alpha[2] + alpha[3] * latGeom) * latGeom) * latGeom);
179
180 // Ionospheric correction (L1)
181 double ionoTimeDelayL1 = slantFactor * (5. * 1e-9);
182 if (FastMath.abs(x) < 1.570) {
183 ionoTimeDelayL1 += slantFactor * (amplitude * (1.0 - FastMath.pow(x, 2) / 2.0 + FastMath.pow(x, 4) / 24.0));
184 }
185
186 // Ionospheric delay for the L1 frequency, in meters, with slant correction.
187 final double ratio = FastMath.pow(1575.42e6 / frequency, 2);
188 return ratio * Constants.SPEED_OF_LIGHT * ionoTimeDelayL1;
189 }
190
191 /** {@inheritDoc} */
192 @Override
193 public double pathDelay(final SpacecraftState state, final TopocentricFrame baseFrame,
194 final double frequency, final double[] parameters) {
195
196 // Elevation in radians
197 final Vector3D position = state.getPVCoordinates(baseFrame).getPosition();
198 final double elevation = position.getDelta();
199
200 // Only consider measures above the horizon
201 if (elevation > 0.0) {
202 // Date
203 final AbsoluteDate date = state.getDate();
204 // Geodetic point
205 final GeodeticPoint geo = baseFrame.getPoint();
206 // Azimuth angle in radians
207 double azimuth = FastMath.atan2(position.getX(), position.getY());
208 if (azimuth < 0.) {
209 azimuth += MathUtils.TWO_PI;
210 }
211 // Delay
212 return pathDelay(date, geo, elevation, azimuth, frequency, parameters);
213 }
214
215 return 0.0;
216 }
217
218 /**
219 * Calculates the ionospheric path delay for the signal path from a ground
220 * station to a satellite.
221 * <p>
222 * The path delay is computed for any elevation angle.
223 * </p>
224 * @param <T> type of the elements
225 * @param date current date
226 * @param geo geodetic point of receiver/station
227 * @param elevation elevation of the satellite in radians
228 * @param azimuth azimuth of the satellite in radians
229 * @param frequency frequency of the signal in Hz
230 * @param parameters ionospheric model parameters
231 * @return the path delay due to the ionosphere in m
232 */
233 public <T extends CalculusFieldElement<T>> T pathDelay(final FieldAbsoluteDate<T> date, final FieldGeodeticPoint<T> geo,
234 final T elevation, final T azimuth, final double frequency,
235 final T[] parameters) {
236
237 // Sine and cosine of the azimuth
238 final FieldSinCos<T> sc = FastMath.sinCos(azimuth);
239
240 // Field
241 final Field<T> field = date.getField();
242 final T zero = field.getZero();
243 final T one = field.getOne();
244
245 // degrees to semicircles
246 final T pi = one.getPi();
247 final T rad2semi = pi.reciprocal();
248
249 // Earth Centered angle
250 final T psi = elevation.divide(pi).add(0.11).divide(0.0137).reciprocal().subtract(0.022);
251
252 // Subionospheric latitude: the latitude of the IPP (Ionospheric Pierce Point)
253 // in [-0.416, 0.416], semicircle
254 final T latIono = FastMath.min(
255 FastMath.max(geo.getLatitude().multiply(rad2semi).add(psi.multiply(sc.cos())), zero.subtract(0.416)),
256 zero.add(0.416));
257
258 // Subionospheric longitude: the longitude of the IPP
259 // in semicircle
260 final T lonIono = geo.getLongitude().multiply(rad2semi).add(psi.multiply(sc.sin()).divide(FastMath.cos(latIono.multiply(pi))));
261
262 // Geomagnetic latitude, semicircle
263 final T latGeom = latIono.add(FastMath.cos(lonIono.subtract(1.617).multiply(pi)).multiply(0.064));
264
265 // day of week and tow (sec)
266 // Note: Sunday=0, Monday=1, Tuesday=2, Wednesday=3, Thursday=4, Friday=5, Saturday=6
267 final DateTimeComponents dtc = date.getComponents(gps);
268 final int dofweek = dtc.getDate().getDayOfWeek();
269 final double secday = dtc.getTime().getSecondsInLocalDay();
270 final double tow = dofweek * 86400. + secday;
271
272 final T t = lonIono.multiply(43200.).add(tow);
273 final T tsec = t.subtract(FastMath.floor(t.divide(86400.)).multiply(86400.)); // Seconds of day
274
275 // Slant factor, semicircle
276 final T slantFactor = FastMath.pow(elevation.divide(pi).negate().add(0.53), 3).multiply(16.0).add(one);
277
278 // Period of model, seconds
279 final T period = FastMath.max(zero.add(72000.), latGeom.multiply(latGeom.multiply(latGeom.multiply(beta[3]).add(beta[2])).add(beta[1])).add(beta[0]));
280
281 // Phase of the model, radians
282 // (Max at 14.00 = 50400 sec local time)
283 final T x = tsec.subtract(50400.0).multiply(pi.multiply(2.0)).divide(period);
284
285 // Amplitude of the model, seconds
286 final T amplitude = FastMath.max(zero, latGeom.multiply(latGeom.multiply(latGeom.multiply(alpha[3]).add(alpha[2])).add(alpha[1])).add(alpha[0]));
287
288 // Ionospheric correction (L1)
289 T ionoTimeDelayL1 = slantFactor.multiply(5. * 1e-9);
290 if (FastMath.abs(x.getReal()) < 1.570) {
291 ionoTimeDelayL1 = ionoTimeDelayL1.add(slantFactor.multiply(amplitude.multiply(one.subtract(FastMath.pow(x, 2).multiply(0.5)).add(FastMath.pow(x, 4).divide(24.0)))));
292 }
293
294 // Ionospheric delay for the L1 frequency, in meters, with slant correction.
295 final double ratio = FastMath.pow(1575.42e6 / frequency, 2);
296 return ionoTimeDelayL1.multiply(Constants.SPEED_OF_LIGHT).multiply(ratio);
297 }
298
299 /** {@inheritDoc} */
300 @Override
301 public <T extends CalculusFieldElement<T>> T pathDelay(final FieldSpacecraftState<T> state, final TopocentricFrame baseFrame,
302 final double frequency, final T[] parameters) {
303
304 // Elevation and azimuth in radians
305 final FieldVector3D<T> position = state.getPVCoordinates(baseFrame).getPosition();
306 final T elevation = position.getDelta();
307
308 if (elevation.getReal() > 0.0) {
309 // Date
310 final FieldAbsoluteDate<T> date = state.getDate();
311 // Geodetic point
312 final FieldGeodeticPoint<T> geo = baseFrame.getPoint(date.getField());
313 // Azimuth angle in radians
314 T azimuth = FastMath.atan2(position.getX(), position.getY());
315 if (azimuth.getReal() < 0.) {
316 azimuth = azimuth.add(MathUtils.TWO_PI);
317 }
318 // Delay
319 return pathDelay(date, geo, elevation, azimuth, frequency, parameters);
320 }
321
322 return elevation.getField().getZero();
323 }
324
325 @Override
326 public List<ParameterDriver> getParametersDrivers() {
327 return Collections.emptyList();
328 }
329 }