View Javadoc
1   /*
2    * Copyright (C) 2019 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.navigation.gnss;
17  
18  import com.irurueta.algebra.AlgebraException;
19  import com.irurueta.algebra.Matrix;
20  import com.irurueta.algebra.Utils;
21  import com.irurueta.navigation.frames.CoordinateTransformation;
22  import com.irurueta.navigation.geodesic.Constants;
23  import com.irurueta.units.Time;
24  import com.irurueta.units.TimeConverter;
25  import com.irurueta.units.TimeUnit;
26  
27  import java.util.Collection;
28  
29  
30  /**
31   * Implements one cycle of the GNSS extended Kalman filter.
32   * This implementation is based on the equations defined in "Principles of GNSS, Inertial, and Multi-sensor
33   * Integrated Navigation Systems, Second Edition" and on the companion software available at:
34   * <a href="https://github.com/ymjdz/MATLAB-Codes/blob/master/GNSS_KF_Epoch.m">
35   *     https://github.com/ymjdz/MATLAB-Codes/blob/master/GNSS_KF_Epoch.m
36   * </a>
37   */
38  public class GNSSKalmanEpochEstimator {
39  
40      /**
41       * Speed of light in the vacuum expressed in meters per second (m/s).
42       */
43      public static final double SPEED_OF_LIGHT = Constants.SPEED_OF_LIGHT;
44  
45      /**
46       * Earth rotation rate expressed in radians per second (rad/s).
47       */
48      public static final double EARTH_ROTATION_RATE = Constants.EARTH_ROTATION_RATE;
49  
50      /**
51       * Number of rows and columns of transition and system noise covariance matrices.
52       */
53      private static final int MATRIX_SIZE = 8;
54  
55      /**
56       * Constructor.
57       * Prevents instantiation of utility class.
58       */
59      private GNSSKalmanEpochEstimator() {
60      }
61  
62      /**
63       * Estimates the update of Kalman filter state and covariance matrix for a single
64       * epoch.
65       *
66       * @param measurements        satellite measurements data.
67       * @param propagationInterval propagation interval.
68       * @param previousState       previous GNSS estimates and Kalman filter error
69       *                            covariance matrix.
70       * @param config              system configuration (usually obtained through
71       *                            calibration).
72       * @return new Kalman filter state.
73       * @throws AlgebraException if there are numerical instabilities.
74       */
75      public static GNSSKalmanState estimate(
76              final Collection<GNSSMeasurement> measurements, final Time propagationInterval,
77              final GNSSKalmanState previousState, final GNSSKalmanConfig config) throws AlgebraException {
78          return estimate(measurements, convertTime(propagationInterval), previousState, config);
79      }
80  
81      /**
82       * Estimates the update of Kalman filter state and covariance matrix fo a single
83       * epoch.
84       *
85       * @param measurements        satellite measurements data.
86       * @param propagationInterval propagation interval.
87       * @param previousState       previousGNSS estimates and Kalman filter error
88       *                            covariance matrix.
89       * @param config              system configuration (usually obtained through
90       *                            calibration).
91       * @param result              instance where updated Kalman filter state will be
92       *                            stored.
93       * @throws AlgebraException if there are numerical instabilities.
94       */
95      public static void estimate(final Collection<GNSSMeasurement> measurements,
96                                  final Time propagationInterval,
97                                  final GNSSKalmanState previousState,
98                                  final GNSSKalmanConfig config,
99                                  final GNSSKalmanState result) throws AlgebraException {
100         estimate(measurements, convertTime(propagationInterval), previousState, config, result);
101     }
102 
103     /**
104      * Estimates the update of Kalman filter state and covariance matrix for a single
105      * epoch.
106      *
107      * @param measurements        satellite measurements data.
108      * @param propagationInterval propagation interval.
109      * @param previousEstimation  previousGNSS estimates.
110      * @param previousCovariance  previousKalman filter error covariance matrix.
111      * @param config              system configuration (usually obtained through
112      *                            calibration).
113      * @param updatedEstimation   instance where updated GNSS estimate will be stored
114      *                            after executing this method.
115      * @param updatedCovariance   instance where updated Kalman filter error covariance
116      *                            matrix will be stored.
117      * @throws IllegalArgumentException if provided previous covariance matrix is not
118      *                                  8x8.
119      * @throws AlgebraException         if there are numerical instabilities.
120      */
121     public static void estimate(final Collection<GNSSMeasurement> measurements,
122                                 final Time propagationInterval,
123                                 final GNSSEstimation previousEstimation,
124                                 final Matrix previousCovariance,
125                                 final GNSSKalmanConfig config,
126                                 final GNSSEstimation updatedEstimation,
127                                 final Matrix updatedCovariance) throws AlgebraException {
128         estimate(measurements, convertTime(propagationInterval), previousEstimation, previousCovariance, config,
129                 updatedEstimation, updatedCovariance);
130     }
131 
132     /**
133      * Estimates the update of Kalman filter state and covariance matrix for a single
134      * epoch.
135      *
136      * @param measurements        satellite measurements data.
137      * @param propagationInterval propagation interval expressed in seconds (s).
138      * @param previousState       previous GNSS estimates and Kalman filter error
139      *                            covariance matrix.
140      * @param config              system configuration (usually obtained through
141      *                            calibration).
142      * @return new Kalman filter state.
143      * @throws AlgebraException if there are numerical instabilities.
144      */
145     public static GNSSKalmanState estimate(
146             final Collection<GNSSMeasurement> measurements,
147             final double propagationInterval,
148             final GNSSKalmanState previousState,
149             final GNSSKalmanConfig config) throws AlgebraException {
150         final var result = new GNSSKalmanState();
151         estimate(measurements, propagationInterval, previousState, config, result);
152         return result;
153     }
154 
155     /**
156      * Estimates the update of Kalman filter state and covariance matrix for a single
157      * epoch.
158      *
159      * @param measurements        satellite measurements data.
160      * @param propagationInterval propagation interval expressed in seconds (s).
161      * @param previousState       previous GNSS estimates and Kalman filter error
162      *                            covariance matrix.
163      * @param config              system configuration (usually obtained through
164      *                            calibration).
165      * @param result              instance where updated Kalman filter state will be
166      *                            stored.
167      * @throws AlgebraException if there are numerical instabilities.
168      */
169     public static void estimate(final Collection<GNSSMeasurement> measurements,
170                                 final double propagationInterval,
171                                 final GNSSKalmanState previousState,
172                                 final GNSSKalmanConfig config,
173                                 final GNSSKalmanState result) throws AlgebraException {
174         final var resultEstimation = new GNSSEstimation();
175         final var resultCovariance = new Matrix(GNSSEstimation.NUM_PARAMETERS, GNSSEstimation.NUM_PARAMETERS);
176 
177         estimate(measurements, propagationInterval, previousState.getEstimation(), previousState.getCovariance(),
178                 config, resultEstimation, resultCovariance);
179 
180         result.setEstimation(resultEstimation);
181         result.setCovariance(resultCovariance);
182     }
183 
184     /**
185      * Estimates the update of Kalman filter state and covariance matrix for a single
186      * epoch.
187      *
188      * @param measurements        satellite measurements data.
189      * @param propagationInterval propagation interval expressed in seconds (s).
190      * @param previousEstimation  previous GNSS estimates.
191      * @param previousCovariance  previous Kalman filter error covariance matrix.
192      * @param config              system configuration (usually obtained through
193      *                            calibration).
194      * @param updatedEstimation   instance where updated GNSS estimate will be stored
195      *                            after executing this method.
196      * @param updatedCovariance   instance where updated Kalman filter error covariance
197      *                            matrix will be stored.
198      * @throws IllegalArgumentException if provided previous covariance matrix is not
199      *                                  8x8.
200      * @throws AlgebraException         if there are numerical instabilities.
201      */
202     @SuppressWarnings("DuplicatedCode")
203     public static void estimate(final Collection<GNSSMeasurement> measurements,
204                                 final double propagationInterval,
205                                 final GNSSEstimation previousEstimation,
206                                 final Matrix previousCovariance,
207                                 final GNSSKalmanConfig config,
208                                 final GNSSEstimation updatedEstimation,
209                                 final Matrix updatedCovariance) throws AlgebraException {
210 
211         if (previousCovariance.getRows() != GNSSEstimation.NUM_PARAMETERS
212                 || previousCovariance.getColumns() != GNSSEstimation.NUM_PARAMETERS) {
213             throw new IllegalArgumentException();
214         }
215 
216         // SYSTEM PROPAGATION PHASE
217 
218         // 1. Determine transition matrix using (9.147) and (9.150)
219         final var phiMatrix = Matrix.identity(MATRIX_SIZE, MATRIX_SIZE);
220         phiMatrix.setElementAt(0, 3, propagationInterval);
221         phiMatrix.setElementAt(1, 4, propagationInterval);
222         phiMatrix.setElementAt(2, 5, propagationInterval);
223         phiMatrix.setElementAt(6, 7, propagationInterval);
224 
225         // 2. Determine system noise covariance matrix using (9.152)
226         final var propagationInterval2 = propagationInterval * propagationInterval;
227         final var propagationInterval3 = propagationInterval2 * propagationInterval;
228         final var accelerationPSD = config.getAccelerationPSD();
229         final var clockFrequencyPSD = config.getClockFrequencyPSD();
230         final var clockPhasePSD = config.getClockPhasePSD();
231 
232         final var value1 = accelerationPSD * propagationInterval3 / 3.0;
233         final var value2 = accelerationPSD * propagationInterval2 / 2.0;
234         final var value3 = accelerationPSD * propagationInterval;
235         final var value4 = clockFrequencyPSD * propagationInterval3 / 3.0 + clockPhasePSD * propagationInterval;
236         final var value5 = clockFrequencyPSD * propagationInterval2 / 2.0;
237         final var value6 = clockFrequencyPSD * propagationInterval;
238 
239         final var qMatrix = new Matrix(MATRIX_SIZE, MATRIX_SIZE);
240         qMatrix.setElementAt(0, 0, value1);
241         qMatrix.setElementAt(1, 1, value1);
242         qMatrix.setElementAt(2, 2, value1);
243 
244         qMatrix.setElementAt(0, 3, value2);
245         qMatrix.setElementAt(1, 4, value2);
246         qMatrix.setElementAt(2, 5, value2);
247 
248         qMatrix.setElementAt(3, 0, value2);
249         qMatrix.setElementAt(4, 1, value2);
250         qMatrix.setElementAt(5, 2, value2);
251 
252         qMatrix.setElementAt(3, 3, value3);
253         qMatrix.setElementAt(4, 4, value3);
254         qMatrix.setElementAt(5, 5, value3);
255 
256         qMatrix.setElementAt(6, 6, value4);
257         qMatrix.setElementAt(6, 7, value5);
258         qMatrix.setElementAt(7, 6, value5);
259         qMatrix.setElementAt(7, 7, value6);
260 
261 
262         // 3. Propagate state estimates using (3.14)
263         final var xEstOld = previousEstimation.asMatrix();
264         final var xEstPropagated = phiMatrix.multiplyAndReturnNew(xEstOld);
265         final var propagatedVelocity = xEstPropagated.getSubmatrix(
266                 3, 0, 5, 0);
267         final var propagatedPosition = xEstPropagated.getSubmatrix(
268                 0, 0, 2, 0);
269 
270         // 4. Propagate state estimation error covariance matrix using (3.15)
271         final var pMatrixPropagated = phiMatrix.multiplyAndReturnNew(previousCovariance);
272         phiMatrix.transpose();
273         pMatrixPropagated.multiply(phiMatrix);
274         pMatrixPropagated.add(qMatrix);
275 
276         // MEASUREMENT UPDATE PHASE
277 
278         // Skew symmetric matrix of Earth rate
279         final var omegaIe = Utils.skewMatrix(new double[]{0.0, 0.0, EARTH_ROTATION_RATE});
280 
281         final var numberOfMeasurements = measurements.size();
282         final var uAseT = new Matrix(numberOfMeasurements, 3);
283         final var predMeas = new Matrix(numberOfMeasurements, 2);
284 
285         final var cei = Matrix.identity(CoordinateTransformation.ROWS, CoordinateTransformation.COLS);
286         final var satellitePosition = new Matrix(CoordinateTransformation.ROWS, 1);
287         final var satelliteVelocity = new Matrix(CoordinateTransformation.ROWS, 1);
288         final var deltaR = new Matrix(CoordinateTransformation.ROWS, 1);
289         final var tmp1 = new Matrix(CoordinateTransformation.ROWS, 1);
290         final var tmp2 = new Matrix(CoordinateTransformation.ROWS, 1);
291         final var tmp3 = new Matrix(CoordinateTransformation.ROWS, 1);
292         final var tmp4 = new Matrix(CoordinateTransformation.ROWS, 1);
293         final var tmp5 = new Matrix(CoordinateTransformation.ROWS, 1);
294         final var tmp6 = new Matrix(CoordinateTransformation.ROWS, 1);
295         final var tmp7 = new Matrix(1, CoordinateTransformation.ROWS);
296 
297         // Loop measurements
298         var j = 0;
299         for (final var measurement : measurements) {
300             // Predict approx range
301             final var measX = measurement.getX();
302             final var measY = measurement.getY();
303             final var measZ = measurement.getZ();
304 
305             final var deltaX = measX - xEstPropagated.getElementAtIndex(0);
306             final var deltaY = measY - xEstPropagated.getElementAtIndex(1);
307             final var deltaZ = measZ - xEstPropagated.getElementAtIndex(2);
308             final var approxRange = Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ);
309 
310             // Calculate frame rotation during signal transit time using (8.36)
311             final var ceiValue = EARTH_ROTATION_RATE * approxRange / SPEED_OF_LIGHT;
312             cei.setElementAt(0, 1, ceiValue);
313             cei.setElementAt(1, 0, -ceiValue);
314 
315             // Predict pseudo-range using (9.165)
316             satellitePosition.setElementAtIndex(0, measX);
317             satellitePosition.setElementAtIndex(1, measY);
318             satellitePosition.setElementAtIndex(2, measZ);
319 
320             cei.multiply(satellitePosition, deltaR);
321             for (var i = 0; i < CoordinateTransformation.ROWS; i++) {
322                 deltaR.setElementAtIndex(i, deltaR.getElementAtIndex(i) - xEstPropagated.getElementAtIndex(i));
323             }
324             final var range = Utils.normF(deltaR);
325 
326             predMeas.setElementAt(j, 0, range + xEstPropagated.getElementAtIndex(6));
327 
328             // Predict line of sight
329             for (var i = 0; i < CoordinateTransformation.ROWS; i++) {
330                 uAseT.setElementAt(j, i, deltaR.getElementAtIndex(i) / range);
331             }
332 
333             // Predict pseudo-range rate using (9.165)
334             satelliteVelocity.setElementAtIndex(0, measurement.getVx());
335             satelliteVelocity.setElementAtIndex(1, measurement.getVy());
336             satelliteVelocity.setElementAtIndex(2, measurement.getVz());
337 
338             omegaIe.multiply(satellitePosition, tmp1);
339             satelliteVelocity.add(tmp1, tmp2);
340             cei.multiply(tmp2, tmp3);
341 
342             omegaIe.multiply(propagatedPosition, tmp4);
343             propagatedVelocity.add(tmp4, tmp6);
344 
345             tmp3.subtract(tmp6, tmp5);
346 
347             uAseT.getSubmatrix(j, 0, j, 2, tmp7);
348 
349             final var rangeRate = Utils.dotProduct(tmp7, tmp5);
350 
351             predMeas.setElementAt(j, 1, rangeRate + xEstPropagated.getElementAtIndex(7));
352 
353             j++;
354         }
355 
356         // 5. Set-up measurement matrix using (9.163)
357         final var h = new Matrix(2 * numberOfMeasurements, GNSSEstimation.NUM_PARAMETERS);
358         for (int j1 = 0, j2 = numberOfMeasurements; j1 < numberOfMeasurements; j1++, j2++) {
359             for (int i1 = 0, i2 = 3; i1 < CoordinateTransformation.ROWS; i1++, i2++) {
360                 final var value = -uAseT.getElementAt(j1, i1);
361 
362                 h.setElementAt(j1, i1, value);
363                 h.setElementAt(j2, i2, value);
364             }
365             h.setElementAt(j1, 6, 1.0);
366             h.setElementAt(j2, 7, 1.0);
367         }
368 
369         // 6. Set-up measurement noise covariance matrix assuming all measurements are independent
370         // and have equal variance for a given measurement type
371         final var pseudoRangeSD = config.getPseudoRangeSD();
372         final var pseudoRangeSD2 = pseudoRangeSD * pseudoRangeSD;
373         final var rangeRateSD = config.getRangeRateSD();
374         final var rangeRateSD2 = rangeRateSD * rangeRateSD;
375         final var r = new Matrix(2 * numberOfMeasurements, 2 * numberOfMeasurements);
376         for (int i1 = 0, i2 = numberOfMeasurements; i1 < numberOfMeasurements; i1++, i2++) {
377             r.setElementAt(i1, i1, pseudoRangeSD2);
378             r.setElementAt(i2, i2, rangeRateSD2);
379         }
380 
381         // 7. Calculate Kalman gain using (3.21)
382         final var hTransposed = h.transposeAndReturnNew();
383         final var tmp8 = h.multiplyAndReturnNew(pMatrixPropagated.multiplyAndReturnNew(hTransposed));
384         tmp8.add(r);
385         final var tmp9 = Utils.inverse(tmp8);
386         final var k = pMatrixPropagated.multiplyAndReturnNew(hTransposed);
387         k.multiply(tmp9);
388 
389         // 8. Formulate measurement innovations using (3.88)
390         final var deltaZ = new Matrix(2 * numberOfMeasurements, 1);
391         var i1 = 0;
392         var i2 = numberOfMeasurements;
393         for (final var measurement : measurements) {
394             deltaZ.setElementAtIndex(i1, measurement.getPseudoRange() - predMeas.getElementAt(i1, 0));
395             deltaZ.setElementAtIndex(i2, measurement.getPseudoRate() - predMeas.getElementAt(i1, 1));
396             i1++;
397             i2++;
398         }
399 
400         // 9. Update state estimates using (3.24)
401         xEstPropagated.add(k.multiplyAndReturnNew(deltaZ));
402 
403         // xEstPropagated now contains updated state
404         updatedEstimation.fromMatrix(xEstPropagated);
405 
406         // 10. Update state estimation error covariance matrix using (3.25)
407         if (updatedCovariance.getRows() != GNSSEstimation.NUM_PARAMETERS
408                 || updatedCovariance.getColumns() != GNSSEstimation.NUM_PARAMETERS) {
409             updatedCovariance.resize(GNSSEstimation.NUM_PARAMETERS, GNSSEstimation.NUM_PARAMETERS);
410         }
411         Matrix.identity(updatedCovariance);
412         k.multiply(h);
413         updatedCovariance.subtract(k);
414         updatedCovariance.multiply(pMatrixPropagated);
415     }
416 
417     /**
418      * Converts time instance into a value expressed in seconds.
419      *
420      * @param time time instance to be converted.
421      * @return time value expressed in seconds.
422      */
423     private static double convertTime(final Time time) {
424         return TimeConverter.convert(time.getValue().doubleValue(), time.getUnit(), TimeUnit.SECOND);
425     }
426 }