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.algebra.WrongSizeException;
22  import com.irurueta.navigation.LockedException;
23  import com.irurueta.navigation.NotReadyException;
24  import com.irurueta.navigation.frames.ECEFPosition;
25  import com.irurueta.navigation.frames.ECEFVelocity;
26  import com.irurueta.navigation.frames.NEDPosition;
27  import com.irurueta.navigation.frames.NEDVelocity;
28  import com.irurueta.navigation.frames.converters.ECEFtoNEDPositionVelocityConverter;
29  import com.irurueta.navigation.frames.converters.NEDtoECEFPositionVelocityConverter;
30  import com.irurueta.navigation.geodesic.Constants;
31  
32  import java.util.Collection;
33  
34  /**
35   * Calculates position, velocity, clock offset and clock drift using
36   * unweighted iterated least squares.
37   * Separate calculations are implemented for position and clock offset and
38   * for velocity and clock drift.
39   * This implementation is based on the equations defined in "Principles of GNSS, Inertial, and Multi-sensor
40   * Integrated Navigation Systems, Second Edition" and on the companion software available at:
41   * <a href="https://github.com/ymjdz/MATLAB-Codes/blob/master/GNSS_LS_position_velocity.m">
42   *     https://github.com/ymjdz/MATLAB-Codes/blob/master/GNSS_LS_position_velocity.m
43   * </a>
44   */
45  public class GNSSLeastSquaresPositionAndVelocityEstimator {
46  
47      /**
48       * Minimum number of measurements required to obtain a solution.
49       */
50      public static final int MIN_MEASUREMENTS = 4;
51  
52      /**
53       * Default threshold to determine when convergence has been reached.
54       */
55      public static final double CONVERGENCE_THRESHOLD = 1e-4;
56  
57      /**
58       * Speed of light in the vacuum expressed in meters per second (m/s).
59       */
60      public static final double SPEED_OF_LIGHT = Constants.SPEED_OF_LIGHT;
61  
62      /**
63       * Earth rotation rate expressed in radians per second (rad/s).
64       */
65      public static final double EARTH_ROTATION_RATE = Constants.EARTH_ROTATION_RATE;
66  
67      /**
68       * Number of components of predicted state.
69       */
70      private static final int STATE_COMPONENTS = ECEFPosition.COMPONENTS + 1;
71  
72      /**
73       * Number of elements of position, velocity, etc.
74       */
75      private static final int ELEMS = ECEFPosition.COMPONENTS;
76  
77      /**
78       * Number of elements of position minus one.
79       */
80      private static final int ELEMS_MINUS_ONE = ELEMS - 1;
81  
82      /**
83       * GNSS measurements of a collection of satellites.
84       */
85      private Collection<GNSSMeasurement> measurements;
86  
87      /**
88       * Previously predicted ECEF user position and velocity.
89       */
90      private ECEFPositionAndVelocity priorPositionAndVelocity;
91  
92      /**
93       * Listener to notify events raised by this instance.
94       */
95      private GNSSLeastSquaresPositionAndVelocityEstimatorListener listener;
96  
97      /**
98       * Threshold to determine when convergence has been reached.
99       */
100     private double convergenceThreshold = CONVERGENCE_THRESHOLD;
101 
102     /**
103      * Indicates whether estimation is currently running.
104      */
105     private boolean running;
106 
107     /**
108      * Internal matrix to be reused containing frame rotation during signal transit
109      * time.
110      */
111     private final Matrix cei;
112 
113     /**
114      * Predicted state to be reused.
115      */
116     private final Matrix xPred;
117 
118     /**
119      * Temporal matrix to be reused.
120      */
121     private final Matrix tmp1;
122 
123     /**
124      * Temporal matrix to be reused.
125      */
126     private final Matrix tmp2;
127 
128     /**
129      * Estimated state to be reused.
130      */
131     private final Matrix xEst;
132 
133     /**
134      * Contains square representation of measurement or geometry matrix.
135      */
136     private final Matrix hSqr;
137 
138     /**
139      * Inverse of the square representation of measurement or geometry matrix.
140      */
141     private final Matrix invHSqr;
142 
143     /**
144      * Temporal matrix to be reused.
145      */
146     private final Matrix tmp3;
147 
148     /**
149      * Skew symmetric matrix of Earth rotation rate.
150      */
151     private final Matrix omegaIe;
152 
153     /**
154      * Temporal matrix to be reused.
155      */
156     private final Matrix tmp4;
157 
158     /**
159      * Temporal matrix to be reused.
160      */
161     private final Matrix tmp5;
162 
163     /**
164      * Temporal matrix to be reused.
165      */
166     private final Matrix tmp6;
167 
168     /**
169      * Temporal matrix to be reused.
170      */
171     private final Matrix tmp7;
172 
173     /**
174      * Temporal matrix to be reused.
175      */
176     private final Matrix tmp8;
177 
178     /**
179      * Temporal matrix to be reused.
180      */
181     private final Matrix tmp9;
182 
183     /**
184      * Temporal matrix to be reused.
185      */
186     private final Matrix tmp10;
187 
188     /**
189      * Measurement position to be reused.
190      */
191     private final Matrix measurementPosition;
192 
193     /**
194      * Measurement velocity to be reused.
195      */
196     private final Matrix measurementVelocity;
197 
198     /**
199      * Predicted velocity to be reused.
200      */
201     private final Matrix predVelocity;
202 
203     /**
204      * Result position to be reused.
205      */
206     private final Matrix resultPosition;
207 
208     /**
209      * Constructor.
210      */
211     public GNSSLeastSquaresPositionAndVelocityEstimator() {
212         Matrix cxPred = null;
213         Matrix ctmp1 = null;
214         Matrix ctmp2 = null;
215         Matrix cxEst = null;
216         Matrix chSqr = null;
217         Matrix cinvHSqr = null;
218         Matrix ctmp3 = null;
219         Matrix comegaIe = null;
220         Matrix ccei = null;
221         Matrix ctmp4 = null;
222         Matrix ctmp5 = null;
223         Matrix ctmp6 = null;
224         Matrix ctmp7 = null;
225         Matrix ctmp8 = null;
226         Matrix ctmp9 = null;
227         Matrix ctmp10 = null;
228         Matrix cmeasurementPosition = null;
229         Matrix cmeasurementVelocity = null;
230         Matrix cpredVelocity = null;
231         Matrix cresultPosition = null;
232         try {
233             ccei = Matrix.identity(ELEMS, ELEMS);
234             cxPred = new Matrix(STATE_COMPONENTS, 1);
235             ctmp1 = new Matrix(ELEMS, 1);
236             ctmp2 = new Matrix(ELEMS, 1);
237             cxEst = new Matrix(STATE_COMPONENTS, 1);
238             chSqr = new Matrix(STATE_COMPONENTS, STATE_COMPONENTS);
239             cinvHSqr = new Matrix(STATE_COMPONENTS, STATE_COMPONENTS);
240             ctmp3 = new Matrix(STATE_COMPONENTS, 1);
241             comegaIe = Utils.skewMatrix(new double[]{0.0, 0.0, EARTH_ROTATION_RATE});
242             ctmp4 = new Matrix(STATE_COMPONENTS, 1);
243             ctmp5 = new Matrix(STATE_COMPONENTS, 1);
244             ctmp6 = new Matrix(STATE_COMPONENTS, 1);
245             ctmp7 = new Matrix(STATE_COMPONENTS, 1);
246             ctmp8 = new Matrix(STATE_COMPONENTS, 1);
247             ctmp9 = new Matrix(STATE_COMPONENTS, 1);
248             ctmp10 = new Matrix(STATE_COMPONENTS, 1);
249             cmeasurementPosition = new Matrix(ELEMS, 1);
250             cmeasurementVelocity = new Matrix(ELEMS, 1);
251             cpredVelocity = new Matrix(ELEMS, 1);
252             cresultPosition = new Matrix(ELEMS, 1);
253         } catch (WrongSizeException ignore) {
254             // never happens
255         }
256 
257         this.cei = ccei;
258         this.xPred = cxPred;
259         this.tmp1 = ctmp1;
260         this.tmp2 = ctmp2;
261         this.xEst = cxEst;
262         this.hSqr = chSqr;
263         this.invHSqr = cinvHSqr;
264         this.tmp3 = ctmp3;
265         this.omegaIe = comegaIe;
266         this.tmp4 = ctmp4;
267         this.tmp5 = ctmp5;
268         this.tmp6 = ctmp6;
269         this.tmp7 = ctmp7;
270         this.tmp8 = ctmp8;
271         this.tmp9 = ctmp9;
272         this.tmp10 = ctmp10;
273         this.measurementPosition = cmeasurementPosition;
274         this.measurementVelocity = cmeasurementVelocity;
275         this.predVelocity = cpredVelocity;
276         this.resultPosition = cresultPosition;
277     }
278 
279     /**
280      * Constructor.
281      *
282      * @param measurements GNSS measurements of a collection of satellites.
283      * @throws IllegalArgumentException if less than 4 measurements are provided.
284      */
285     public GNSSLeastSquaresPositionAndVelocityEstimator(final Collection<GNSSMeasurement> measurements) {
286         this();
287         try {
288             setMeasurements(measurements);
289         } catch (final LockedException ignore) {
290             // never happens
291         }
292     }
293 
294     /**
295      * Constructor.
296      *
297      * @param measurements             GNSS measurements of a collection of satellites.
298      * @param priorPositionAndVelocity previously predicted ECEF user position and
299      *                                 velocity.
300      * @throws IllegalArgumentException if less than 4 measurements are provided.
301      */
302     public GNSSLeastSquaresPositionAndVelocityEstimator(
303             final Collection<GNSSMeasurement> measurements,
304             final ECEFPositionAndVelocity priorPositionAndVelocity) {
305         this();
306         try {
307             setMeasurements(measurements);
308             setPriorPositionAndVelocity(priorPositionAndVelocity);
309         } catch (final LockedException ignore) {
310             // never happens
311         }
312     }
313 
314     /**
315      * Constructor.
316      *
317      * @param measurements GNSS measurement of a collection of satellites.
318      * @param priorEstimation  previously predicted GNSS estimation.
319      * @throws IllegalArgumentException if less than 4 measurements are provided.
320      */
321     public GNSSLeastSquaresPositionAndVelocityEstimator(
322             final Collection<GNSSMeasurement> measurements,
323             final GNSSEstimation priorEstimation) {
324         this();
325         try {
326             setMeasurements(measurements);
327             setPriorPositionAndVelocityFromEstimation(priorEstimation);
328         } catch (final LockedException ignore) {
329             // never happens
330         }
331     }
332 
333     /**
334      * Constructor.
335      *
336      * @param listener listener notifying events raised by this instance.
337      */
338     public GNSSLeastSquaresPositionAndVelocityEstimator(
339             final GNSSLeastSquaresPositionAndVelocityEstimatorListener listener) {
340         this();
341         try {
342             setListener(listener);
343         } catch (final LockedException ignore) {
344             // never happens
345         }
346     }
347 
348     /**
349      * Constructor.
350      *
351      * @param measurements GNSS measurements of a collection of satellites.
352      * @param listener     listener notifying events raised by this instance.
353      * @throws IllegalArgumentException if less than 4 measurements are provided.
354      */
355     public GNSSLeastSquaresPositionAndVelocityEstimator(
356             final Collection<GNSSMeasurement> measurements,
357             final GNSSLeastSquaresPositionAndVelocityEstimatorListener listener) {
358         this(measurements);
359         try {
360             setListener(listener);
361         } catch (final LockedException ignore) {
362             // never happens
363         }
364     }
365 
366     /**
367      * Constructor.
368      *
369      * @param measurements             GNSS measurements of a collection of satellites.
370      * @param priorPositionAndVelocity previously predicted ECEF user position and
371      *                                 velocity.
372      * @param listener                 listener notifying events raised by this
373      *                                 instance.
374      * @throws IllegalArgumentException if less than 4 measurements are provided.
375      */
376     public GNSSLeastSquaresPositionAndVelocityEstimator(
377             final Collection<GNSSMeasurement> measurements,
378             final ECEFPositionAndVelocity priorPositionAndVelocity,
379             final GNSSLeastSquaresPositionAndVelocityEstimatorListener listener) {
380         this(measurements, priorPositionAndVelocity);
381         try {
382             setListener(listener);
383         } catch (final LockedException ignore) {
384             // never happens
385         }
386     }
387 
388     /**
389      * Constructor.
390      *
391      * @param measurements GNSS measurement of a collection of satellites.
392      * @param priorEstimation  previously predicted GNSS estimation.
393      * @param listener     listener notifying events raised by this instance.
394      * @throws IllegalArgumentException if less than 4 measurements are provided.
395      */
396     public GNSSLeastSquaresPositionAndVelocityEstimator(
397             final Collection<GNSSMeasurement> measurements,
398             final GNSSEstimation priorEstimation,
399             final GNSSLeastSquaresPositionAndVelocityEstimatorListener listener) {
400         this(measurements, priorEstimation);
401         try {
402             setListener(listener);
403         } catch (final LockedException ignore) {
404             // never happens
405         }
406     }
407 
408     /**
409      * Gets GNSS measurements of a collection of satellites.
410      *
411      * @return GNSS measurements of a collection of satellites.
412      */
413     public Collection<GNSSMeasurement> getMeasurements() {
414         return measurements;
415     }
416 
417     /**
418      * Sets GNSS measurements of a collection of satellites.
419      *
420      * @param measurements GNSS measurements of a collection of satellites.
421      * @throws IllegalArgumentException if less than 4 measurements are provided.
422      * @throws LockedException          if this estimator is already running.
423      */
424     public void setMeasurements(final Collection<GNSSMeasurement> measurements) throws LockedException {
425         if (running) {
426             throw new LockedException();
427         }
428         if (!isValidMeasurements(measurements)) {
429             throw new IllegalArgumentException();
430         }
431 
432         this.measurements = measurements;
433     }
434 
435     /**
436      * Gets previously predicted ECEF user position and velocity.
437      *
438      * @return previously predicted ECEF user position and velocity.
439      */
440     public ECEFPositionAndVelocity getPriorPositionAndVelocity() {
441         return priorPositionAndVelocity;
442     }
443 
444     /**
445      * Sets previously predicted ECEF user position and velocity.
446      *
447      * @param priorPositionAndVelocity previously predicted ECEF user position and
448      *                                 velocity.
449      * @throws LockedException if this estimator is already running.
450      */
451     public void setPriorPositionAndVelocity(
452             final ECEFPositionAndVelocity priorPositionAndVelocity) throws LockedException {
453         if (running) {
454             throw new LockedException();
455         }
456 
457         this.priorPositionAndVelocity = priorPositionAndVelocity;
458     }
459 
460     /**
461      * Sets previously predicted ECEF user position and velocity from a previous
462      * predicted result.
463      *
464      * @param priorEstimation previously predicted GNSS estimation.
465      * @throws LockedException if this estimator is already running.
466      */
467     public void setPriorPositionAndVelocityFromEstimation(
468             final GNSSEstimation priorEstimation) throws LockedException {
469         if (running) {
470             throw new LockedException();
471         }
472 
473         priorPositionAndVelocity = priorEstimation != null ? priorEstimation.getPositionAndVelocity() : null;
474     }
475 
476     /**
477      * Gets listener to notify events raised by this instance.
478      *
479      * @return listener to notify events raised by this instance.
480      */
481     public GNSSLeastSquaresPositionAndVelocityEstimatorListener getListener() {
482         return listener;
483     }
484 
485     /**
486      * Sets listener to notify events raised by this instance.
487      *
488      * @param listener listener to notify events raised by this instance.
489      * @throws LockedException if this estimator is already running.
490      */
491     public void setListener(
492             final GNSSLeastSquaresPositionAndVelocityEstimatorListener listener) throws LockedException {
493         if (running) {
494             throw new LockedException();
495         }
496         this.listener = listener;
497     }
498 
499     /**
500      * Gets threshold to determine when convergence has been reached.
501      *
502      * @return threshold to determine when convergence has been reached.
503      */
504     public double getConvergenceThreshold() {
505         return convergenceThreshold;
506     }
507 
508     /**
509      * Sets threshold to determine when convergence has been reached.
510      *
511      * @param convergenceThreshold threshold to determine when convergence has
512      *                             been reached.
513      * @throws LockedException          if this estimator is already running.
514      * @throws IllegalArgumentException if provided threshold is zero or negative.
515      */
516     public void setConvergenceThreshold(final double convergenceThreshold) throws LockedException,
517             IllegalArgumentException {
518         if (running) {
519             throw new LockedException();
520         }
521         if (convergenceThreshold <= 0.0) {
522             throw new IllegalArgumentException();
523         }
524 
525         this.convergenceThreshold = convergenceThreshold;
526     }
527 
528     /**
529      * Indicates whether this estimator is ready to start the estimation.
530      *
531      * @return true if estimator is ready, false otherwise.
532      */
533     public boolean isReady() {
534         return isValidMeasurements(measurements);
535     }
536 
537     /**
538      * Indicates whether this estimator is currently running or not.
539      *
540      * @return true if estimator is running, false otherwise.
541      */
542     public boolean isRunning() {
543         return running;
544     }
545 
546     /**
547      * Indicates whether provided measurements are valid or not.
548      *
549      * @param gnssMeasurements measurements to be checked.
550      * @return true if at least 4 measurements are provided, false otherwise.
551      */
552     public static boolean isValidMeasurements(final Collection<GNSSMeasurement> gnssMeasurements) {
553         return gnssMeasurements != null && gnssMeasurements.size() >= MIN_MEASUREMENTS;
554     }
555 
556     /**
557      * Estimates new ECEF user position and velocity as well as clock
558      * offset and drift.
559      *
560      * @param result instance where result data will be stored.
561      * @throws NotReadyException if estimator is not ready to start estimation.
562      * @throws LockedException   if estimator is already running.
563      * @throws GNSSException     if estimation fails due to numerical instabilities.
564      */
565     @SuppressWarnings("DuplicatedCode")
566     public void estimate(final GNSSEstimation result) throws NotReadyException, LockedException, GNSSException {
567         if (!isReady()) {
568             throw new NotReadyException();
569         }
570         if (running) {
571             throw new LockedException();
572         }
573 
574         try {
575             running = true;
576 
577             if (listener != null) {
578                 listener.onEstimateStart(this);
579             }
580 
581             // if no prior position and velocity is available, assume that
582             // we are at latitude,longitude equal to the average of satellite
583             // measurements, at Earth's surface (height = 0) and with zero velocity.
584             initializePriorPositionAndVelocityIfNeeded();
585 
586             // POSITION AND CLOCK OFFSET
587 
588             // Setup predicted state
589             final var priorX = priorPositionAndVelocity.getX();
590             final var priorY = priorPositionAndVelocity.getY();
591             final var priorZ = priorPositionAndVelocity.getZ();
592 
593             xPred.setElementAtIndex(0, priorX);
594             xPred.setElementAtIndex(1, priorY);
595             xPred.setElementAtIndex(2, priorZ);
596             xPred.setElementAtIndex(3, 0.0);
597 
598             final var numMeasurements = measurements.size();
599             final var predMeas = new Matrix(numMeasurements, 1);
600             final var h = new Matrix(numMeasurements, STATE_COMPONENTS);
601             for (var i = 0; i < numMeasurements; i++) {
602                 h.setElementAt(i, 3, 1.0);
603             }
604 
605             final var hTrans = new Matrix(STATE_COMPONENTS, numMeasurements);
606             final var hTmp1 = new Matrix(STATE_COMPONENTS, numMeasurements);
607             final var deltaPseudoRange = new Matrix(numMeasurements, 1);
608 
609             // Repeat until convergence
610             var testConvergence = 1.0;
611             while (testConvergence > convergenceThreshold) {
612 
613                 // Loop measurements
614                 var j = 0;
615                 for (final var measurement : measurements) {
616 
617                     // Predict approx range
618                     final var measX = measurement.getX();
619                     final var measY = measurement.getY();
620                     final var measZ = measurement.getZ();
621 
622                     var deltaRx = measX - priorX;
623                     var deltaRy = measY - priorY;
624                     var deltaRz = measZ - priorZ;
625                     final var approxRange = norm(deltaRx, deltaRy, deltaRz);
626 
627                     // Calculate frame rotation during signal transit time using (8.36)
628                     final var ceiValue = EARTH_ROTATION_RATE * approxRange / SPEED_OF_LIGHT;
629                     cei.setElementAt(0, 1, ceiValue);
630                     cei.setElementAt(1, 0, -ceiValue);
631 
632                     // Predict pseudo-range using (9.143)
633                     tmp1.setElementAtIndex(0, measX);
634                     tmp1.setElementAtIndex(1, measY);
635                     tmp1.setElementAtIndex(2, measZ);
636 
637                     cei.multiply(tmp1, tmp2);
638 
639                     deltaRx = tmp2.getElementAtIndex(0) - xPred.getElementAtIndex(0);
640                     deltaRy = tmp2.getElementAtIndex(1) - xPred.getElementAtIndex(1);
641                     deltaRz = tmp2.getElementAtIndex(2) - xPred.getElementAtIndex(2);
642                     final var range = norm(deltaRx, deltaRy, deltaRz);
643 
644                     final var predictedPseudoRange = range + xPred.getElementAtIndex(3);
645                     predMeas.setElementAtIndex(j, predictedPseudoRange);
646 
647                     deltaPseudoRange.setElementAtIndex(j, measurement.getPseudoRange() - predictedPseudoRange);
648 
649                     // Predict line of sight and deploy in measurement matrix, (9.144)
650                     h.setElementAt(j, 0, -deltaRx / range);
651                     h.setElementAt(j, 1, -deltaRy / range);
652                     h.setElementAt(j, 2, -deltaRz / range);
653 
654                     j++;
655                 }
656 
657                 // Unweighted least-squares solution, (9.35)/(9.141)
658                 h.transpose(hTrans);
659                 hTrans.multiply(h, hSqr);
660                 Utils.inverse(hSqr, invHSqr);
661                 invHSqr.multiply(hTrans, hTmp1);
662                 hTmp1.multiply(deltaPseudoRange, tmp3);
663 
664                 xPred.add(tmp3, xEst);
665 
666                 // Test convergence
667                 testConvergence = predictionError();
668 
669                 // Set predictions to estimates for next iteration
670                 xPred.copyFrom(xEst);
671             }
672 
673             // Set outputs to estimates
674             final var resultX = xEst.getElementAtIndex(0);
675             final var resultY = xEst.getElementAtIndex(1);
676             final var resultZ = xEst.getElementAtIndex(2);
677             result.setPositionCoordinates(resultX, resultY, resultZ);
678 
679             final var resultClockOffset = xEst.getElementAtIndex(3);
680             result.setClockOffset(resultClockOffset);
681 
682 
683             // VELOCITY AND CLOCK DRIFT
684 
685             // Setup predicted state
686             final var priorVx = priorPositionAndVelocity.getVx();
687             final var priorVy = priorPositionAndVelocity.getVy();
688             final var priorVz = priorPositionAndVelocity.getVz();
689 
690             xPred.setElementAtIndex(0, priorVx);
691             xPred.setElementAtIndex(1, priorVy);
692             xPred.setElementAtIndex(2, priorVz);
693             xPred.setElementAtIndex(3, 0.0);
694 
695             resultPosition.setElementAtIndex(0, resultX);
696             resultPosition.setElementAtIndex(1, resultY);
697             resultPosition.setElementAtIndex(2, resultZ);
698 
699             final var deltaPseudoRangeRate = new Matrix(numMeasurements, 1);
700 
701             // Repeat until convergence
702             testConvergence = 1.0;
703             while (testConvergence > convergenceThreshold) {
704 
705                 // Loop measurements
706                 var j = 0;
707                 for (final var measurement : measurements) {
708                     // Predict approx range
709                     final var measX = measurement.getX();
710                     final var measY = measurement.getY();
711                     final var measZ = measurement.getZ();
712 
713                     var deltaRx = measX - resultX;
714                     var deltaRy = measY - resultY;
715                     var deltaRz = measZ - resultZ;
716                     final var approxRange = norm(deltaRx, deltaRy, deltaRz);
717 
718                     // Calculate frame rotation during signal transit time using (8.36)
719                     final var ceiValue = EARTH_ROTATION_RATE * approxRange / SPEED_OF_LIGHT;
720                     cei.setElementAt(0, 1, ceiValue);
721                     cei.setElementAt(1, 0, -ceiValue);
722 
723                     // Calculate range using (8.35)
724                     tmp1.setElementAtIndex(0, measX);
725                     tmp1.setElementAtIndex(1, measY);
726                     tmp1.setElementAtIndex(2, measZ);
727 
728                     cei.multiply(tmp1, tmp2);
729 
730                     deltaRx = tmp2.getElementAtIndex(0) - resultX;
731                     deltaRy = tmp2.getElementAtIndex(1) - resultY;
732                     deltaRz = tmp2.getElementAtIndex(2) - resultZ;
733                     final var range = norm(deltaRx, deltaRy, deltaRz);
734 
735                     // Calculate line of sight using (8.41)
736                     final var uaseX = deltaRx / range;
737                     final var uaseY = deltaRy / range;
738                     final var uaseZ = deltaRz / range;
739 
740                     // Predict pseudo-range rate using (9.143)
741                     measurementPosition.setElementAtIndex(0, measX);
742                     measurementPosition.setElementAtIndex(1, measY);
743                     measurementPosition.setElementAtIndex(2, measZ);
744 
745                     final var measVx = measurement.getVx();
746                     final var measVy = measurement.getVy();
747                     final var measVz = measurement.getVz();
748 
749                     measurementVelocity.setElementAtIndex(0, measVx);
750                     measurementVelocity.setElementAtIndex(1, measVy);
751                     measurementVelocity.setElementAtIndex(2, measVz);
752 
753                     omegaIe.multiply(measurementPosition, tmp4);
754 
755                     measurementVelocity.add(tmp4, tmp5);
756 
757                     cei.multiply(tmp5, tmp6);
758 
759                     omegaIe.multiply(resultPosition, tmp7);
760 
761                     xPred.getSubmatrix(0, 0, ELEMS_MINUS_ONE, 0, predVelocity);
762 
763                     predVelocity.add(tmp7, tmp8);
764 
765                     tmp6.subtract(tmp8, tmp9);
766 
767                     final var rangeRate = uaseX * tmp9.getElementAtIndex(0) + uaseY * tmp9.getElementAtIndex(1)
768                             + uaseZ * tmp9.getElementAtIndex(2);
769 
770                     final var predictedPseudoRangeRate = rangeRate + xPred.getElementAtIndex(3);
771                     predMeas.setElementAtIndex(j, predictedPseudoRangeRate);
772 
773                     deltaPseudoRangeRate.setElementAtIndex(j,
774                             measurement.getPseudoRate() - predictedPseudoRangeRate);
775 
776                     // Predict line of sight and deploy in measurement matrix, (9.144)
777                     h.setElementAt(j, 0, -uaseX);
778                     h.setElementAt(j, 1, -uaseY);
779                     h.setElementAt(j, 2, -uaseZ);
780 
781                     j++;
782                 }
783 
784                 // Unweighted least-squares solution, (9.35)/(9.141)
785                 h.transpose(hTrans);
786                 hTrans.multiply(h, hSqr);
787                 Utils.inverse(hSqr, invHSqr);
788                 invHSqr.multiply(hTrans, hTmp1);
789                 hTmp1.multiply(deltaPseudoRangeRate, tmp10);
790 
791                 xPred.add(tmp10, xEst);
792 
793                 // Test convergence
794                 testConvergence = predictionError();
795 
796                 // Set predictions to estimates for next iteration
797                 xPred.copyFrom(xEst);
798             }
799 
800             // Set outputs to estimates
801             final var resultVx = xEst.getElementAtIndex(0);
802             final var resultVy = xEst.getElementAtIndex(1);
803             final var resultVz = xEst.getElementAtIndex(2);
804             result.setVelocityCoordinates(resultVx, resultVy, resultVz);
805 
806             final var resultClockDrift = xEst.getElementAtIndex(3);
807             result.setClockDrift(resultClockDrift);
808 
809         } catch (final AlgebraException e) {
810             throw new GNSSException(e);
811         } finally {
812             if (listener != null) {
813                 listener.onEstimateEnd(this);
814             }
815 
816             running = false;
817         }
818     }
819 
820     /**
821      * Estimates new ECEF user position and velocity as well as clock
822      * offset and drift.
823      *
824      * @return new ECEF user position and velocity, and clock offset and drift.
825      * @throws NotReadyException if estimator is not ready to start estimation.
826      * @throws LockedException   if estimator is already running.
827      * @throws GNSSException     if estimation fails due to numerical instabilities.
828      */
829     public GNSSEstimation estimate() throws NotReadyException, LockedException, GNSSException {
830         final var result = new GNSSEstimation();
831         estimate(result);
832         return result;
833     }
834 
835     /**
836      * Initializes prior position and velocity if not set, assuming that
837      * user is located at the average latitude, longitude of all provided
838      * satellite measurements, at Earth's surface (height = 0) and with zero velocity.
839      */
840     private void initializePriorPositionAndVelocityIfNeeded() {
841         if (priorPositionAndVelocity != null) {
842             return;
843         }
844 
845         var numMeasurements = measurements.size();
846         final var nedPosition = new NEDPosition();
847         final var nedVelocity = new NEDVelocity();
848 
849         final var ecefPosition = new ECEFPosition();
850         final var ecefVelocity = new ECEFVelocity();
851 
852         var userLatitude = 0.0;
853         var userLongitude = 0.0;
854         for (final var measurement : measurements) {
855             measurement.getEcefPosition(ecefPosition);
856             measurement.getEcefVelocity(ecefVelocity);
857             ECEFtoNEDPositionVelocityConverter.convertECEFtoNED(ecefPosition, ecefVelocity, nedPosition, nedVelocity);
858 
859             final var satLatitude = nedPosition.getLatitude();
860             final var satLongitude = nedPosition.getLongitude();
861 
862             userLatitude += satLatitude / numMeasurements;
863             userLongitude += satLongitude / numMeasurements;
864         }
865 
866         nedPosition.setCoordinates(userLatitude, userLongitude, 0.0);
867         nedVelocity.setCoordinates(0.0, 0.0, 0.0);
868 
869         NEDtoECEFPositionVelocityConverter.convertNEDtoECEF(nedPosition, nedVelocity, ecefPosition, ecefVelocity);
870 
871         priorPositionAndVelocity = new ECEFPositionAndVelocity(ecefPosition, ecefVelocity);
872     }
873 
874     /**
875      * Computes norm of provided coordinates.
876      *
877      * @param x x coordinate.
878      * @param y y coordinate.
879      * @param z z coordinate.
880      * @return computed norm.
881      */
882     private static double norm(final double x, final double y, final double z) {
883         return Math.sqrt(x * x + y * y + z * z);
884     }
885 
886     /**
887      * Computes norm of error between estimated state
888      * and predicted state.
889      *
890      * @return norm of error.
891      */
892     private double predictionError() {
893         var sqrPredictionError = 0.0;
894         for (var i = 0; i < STATE_COMPONENTS; i++) {
895             final var diff = xEst.getElementAtIndex(i) - xPred.getElementAtIndex(i);
896             sqrPredictionError += diff * diff;
897         }
898         return Math.sqrt(sqrPredictionError);
899     }
900 }