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.frames;
17  
18  import com.irurueta.algebra.Matrix;
19  import com.irurueta.algebra.WrongSizeException;
20  import com.irurueta.geometry.InvalidRotationMatrixException;
21  import com.irurueta.geometry.MatrixRotation3D;
22  import com.irurueta.geometry.Rotation3D;
23  import com.irurueta.navigation.geodesic.Constants;
24  import com.irurueta.units.Angle;
25  import com.irurueta.units.AngleConverter;
26  import com.irurueta.units.AngleUnit;
27  import com.irurueta.units.Time;
28  import com.irurueta.units.TimeConverter;
29  import com.irurueta.units.TimeUnit;
30  
31  import java.io.Serializable;
32  import java.util.Objects;
33  
34  /**
35   * Contains a coordinate transformation matrix, or rotation matrix. The coordinate transformation matrix is a 3x3
36   * matrix where a vector may be transformed in one step from one set of resolving axes to another by pre-multiplying it
37   * by the appropriate coordinate transformation matrix. The coordinate transformation matrix is associated to a source
38   * and destination frame.
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/Euler_to_CTM.m">
42   *     https://github.com/ymjdz/MATLAB-Codes/blob/master/Euler_to_CTM.m
43   * </a>
44   * <a href="https://github.com/ymjdz/MATLAB-Codes/blob/master/CTM_to_Euler.m">
45   *     https://github.com/ymjdz/MATLAB-Codes/blob/master/CTM_to_Euler.m
46   * </a>
47   */
48  public class CoordinateTransformation implements Serializable, Cloneable {
49  
50      /**
51       * Number of rows of a coordinate transformation matrix.
52       */
53      public static final int ROWS = MatrixRotation3D.ROTATION3D_INHOM_MATRIX_ROWS;
54  
55      /**
56       * Number of columns of a coordinate transformation matrix.
57       */
58      public static final int COLS = MatrixRotation3D.ROTATION3D_INHOM_MATRIX_ROWS;
59  
60      /**
61       * Default threshold to consider a matrix valid.
62       */
63      public static final double DEFAULT_THRESHOLD = 1e-11;
64  
65      /**
66       * Earth rotation rate expressed in radians per second (rad/s).
67       */
68      public static final double EARTH_ROTATION_RATE = Constants.EARTH_ROTATION_RATE;
69  
70      /**
71       * 3x3 matrix containing a rotation.
72       */
73      Matrix matrix;
74  
75      /**
76       * Source frame type.
77       */
78      private FrameType sourceType;
79  
80      /**
81       * Destination frame type.
82       */
83      private FrameType destinationType;
84  
85      /**
86       * Constructor.
87       * Initializes rotation as the identity (no rotation).
88       *
89       * @param sourceType      source frame type.
90       * @param destinationType destination frame type.
91       * @throws NullPointerException if either source or destination frame types are null.
92       */
93      public CoordinateTransformation(final FrameType sourceType, final FrameType destinationType) {
94          try {
95              matrix = Matrix.identity(ROWS, COLS);
96          } catch (final WrongSizeException ignore) {
97              // never happens
98          }
99  
100         setSourceType(sourceType);
101         setDestinationType(destinationType);
102     }
103 
104     /**
105      * Constructor.
106      *
107      * @param matrix          a 3x3 matrix containing a rotation.
108      * @param sourceType      source frame type.
109      * @param destinationType destination frame type.
110      * @param threshold       threshold to validate rotation matrix.
111      * @throws InvalidRotationMatrixException if provided matrix is not a valid rotation matrix (3x3 and orthonormal).
112      * @throws NullPointerException           if either source or destination frame types are null.
113      * @throws IllegalArgumentException       if provided threshold is negative.
114      */
115     public CoordinateTransformation(final Matrix matrix, final FrameType sourceType, final FrameType destinationType,
116                                     final double threshold) throws InvalidRotationMatrixException {
117         setMatrix(matrix, threshold);
118         setSourceType(sourceType);
119         setDestinationType(destinationType);
120     }
121 
122     /**
123      * Constructor.
124      *
125      * @param matrix          a 3x3 matrix containing a rotation.
126      * @param sourceType      source frame type.
127      * @param destinationType destination frame type.
128      * @throws InvalidRotationMatrixException if provided matrix is not a valid rotation matrix (3x3 and orthonormal).
129      * @throws NullPointerException           if either source or destination frame types are null.
130      */
131     public CoordinateTransformation(final Matrix matrix, final FrameType sourceType, final FrameType destinationType)
132             throws InvalidRotationMatrixException {
133         this(matrix, sourceType, destinationType, DEFAULT_THRESHOLD);
134     }
135 
136     /**
137      * Constructor with Euler angles.
138      * Notice that these angles do not match angles obtained from {@link com.irurueta.geometry.Rotation3D} or
139      * {@link com.irurueta.geometry.Quaternion} because they are referred to different axes.
140      *
141      * @param roll            roll Euler angle (around x-axis) expressed in radians.
142      * @param pitch           pitch Euler angle (around y-axis) expressed in radians.
143      * @param yaw             yaw Euler angle (around z-axis) expressed in radians.
144      * @param sourceType      source frame type.
145      * @param destinationType destination frame type.
146      */
147     public CoordinateTransformation(final double roll, final double pitch, final double yaw,
148                                     final FrameType sourceType, final FrameType destinationType) {
149         this(sourceType, destinationType);
150         setEulerAngles(roll, pitch, yaw);
151     }
152 
153     /**
154      * Constructor with Euler angles.
155      * Notice that these angles do not match angles obtained from {@link com.irurueta.geometry.Rotation3D} or
156      * {@link com.irurueta.geometry.Quaternion} because they are referred to different axes.
157      *
158      * @param roll            roll Euler angle (around x-axis).
159      * @param pitch           pitch Euler angle (around y-axis).
160      * @param yaw             yaw Euler angle (around z-axis).
161      * @param sourceType      source frame type.
162      * @param destinationType destination frame type.
163      */
164     public CoordinateTransformation(final Angle roll, final Angle pitch, final Angle yaw,
165                                     final FrameType sourceType, final FrameType destinationType) {
166         this(sourceType, destinationType);
167         setEulerAngles(roll, pitch, yaw);
168     }
169 
170     /**
171      * Constructor with 3D rotation.
172      *
173      * @param rotation        3D rotation.
174      * @param sourceType      source frame type.
175      * @param destinationType destination frame type.
176      */
177     public CoordinateTransformation(final Rotation3D rotation, final FrameType sourceType,
178                                     final FrameType destinationType) {
179         this(sourceType, destinationType);
180         fromRotation(rotation);
181     }
182 
183     /**
184      * Constructor.
185      *
186      * @param input other coordinate transformation matrix to copy data from.
187      */
188     public CoordinateTransformation(final CoordinateTransformation input) {
189         this(input.sourceType, input.destinationType);
190         copyFrom(input);
191     }
192 
193     /**
194      * Gets matrix containing a rotation.
195      *
196      * @return 3x3 matrix containing a rotation.
197      */
198     public Matrix getMatrix() {
199         Matrix result;
200         try {
201             result = new Matrix(ROWS, COLS);
202             getMatrix(result);
203         } catch (final WrongSizeException ignore) {
204             // never happens
205             result = null;
206         }
207         return result;
208     }
209 
210     /**
211      * Gets matrix containing a rotation.
212      *
213      * @param result instance where internal 3x3 matrix containing a rotation will be copied to.
214      */
215     public void getMatrix(Matrix result) {
216         matrix.copyTo(result);
217     }
218 
219     /**
220      * Sets matrix containing a rotation.
221      *
222      * @param matrix    a 3x3 matrix containing a rotation.
223      * @param threshold threshold to validate rotation matrix.
224      * @throws InvalidRotationMatrixException if provided matrix is not a valid rotation matrix (3x3 and orthonormal).
225      * @throws IllegalArgumentException       if provided threshold is negative.
226      */
227     public void setMatrix(final Matrix matrix, final double threshold) throws InvalidRotationMatrixException {
228 
229         if (!isValidMatrix(matrix, threshold)) {
230             throw new InvalidRotationMatrixException();
231         }
232 
233         this.matrix = matrix;
234     }
235 
236     /**
237      * Sets matrix containing a rotation.
238      *
239      * @param matrix a 3x3 matrix containing a rotation.
240      * @throws InvalidRotationMatrixException if provided matrix is not a valid rotation matrix (3x3 and orthonormal).
241      */
242     public void setMatrix(final Matrix matrix) throws InvalidRotationMatrixException {
243         setMatrix(matrix, DEFAULT_THRESHOLD);
244     }
245 
246     /**
247      * Determines whether provided matrix is a valid rotation matrix (3x3 and orthonormal)
248      * up to provided threshold.
249      *
250      * @param matrix    matrix to be checked.
251      * @param threshold threshold to determine whether matrix is valid.
252      * @return true if matrix is valid, false otherwise.
253      * @throws IllegalArgumentException if provided threshold value is negative.
254      */
255     public static boolean isValidMatrix(final Matrix matrix, final double threshold) {
256         if (threshold < Rotation3D.MIN_THRESHOLD) {
257             throw new IllegalArgumentException();
258         }
259 
260         return Rotation3D.isValidRotationMatrix(matrix, threshold);
261     }
262 
263     /**
264      * Determines whether provided matrix is a valid rotation matrix (3x3 and orthonormal)
265      * up to default threshold {@link #DEFAULT_THRESHOLD}.
266      *
267      * @param matrix matrix to be checked.
268      * @return true if matrix is valid, false otherwise.
269      */
270     public static boolean isValidMatrix(final Matrix matrix) {
271         return isValidMatrix(matrix, DEFAULT_THRESHOLD);
272     }
273 
274     /**
275      * Gets roll Euler angle (around x-axis) expressed in radians.
276      * Notice that this angle does not match angles obtained from {@link com.irurueta.geometry.Rotation3D} or
277      * {@link com.irurueta.geometry.Quaternion} because they are referred to different axes.
278      *
279      * @return roll Euler angle.
280      */
281     public double getRollEulerAngle() {
282         return Math.atan2(matrix.getElementAt(1, 2), matrix.getElementAt(2, 2));
283     }
284 
285     /**
286      * Gets roll Euler angle (around x-axis).
287      * Notice that this angle does not match angles obtained from {@link com.irurueta.geometry.Rotation3D} or
288      * {@link com.irurueta.geometry.Quaternion} because they are referred to different axes.
289      *
290      * @param result instance where roll Euler angle will be stored.
291      */
292     public void getRollEulerAngleMeasurement(final Angle result) {
293         result.setValue(getRollEulerAngle());
294         result.setUnit(AngleUnit.RADIANS);
295     }
296 
297     /**
298      * Gets roll Euler angle (around x-axis).
299      * Notice that this angle does not match angles obtained from {@link com.irurueta.geometry.Rotation3D} or
300      * {@link com.irurueta.geometry.Quaternion} because they are referred to different axes.
301      *
302      * @return roll Euler angle.
303      */
304     public Angle getRollEulerAngleMeasurement() {
305         final var result = new Angle(0.0, AngleUnit.RADIANS);
306         getRollEulerAngleMeasurement(result);
307         return result;
308     }
309 
310     /**
311      * Gets pitch Euler angle (around y-axis) expressed in radians.
312      * Notice that this angle does not match angles obtained from {@link com.irurueta.geometry.Rotation3D} or
313      * {@link com.irurueta.geometry.Quaternion} because they are referred to different axes.
314      *
315      * @return pitch Euler angle.
316      */
317     public double getPitchEulerAngle() {
318         return -Math.asin(matrix.getElementAt(0, 2));
319     }
320 
321     /**
322      * Gets pitch Euler angle (around y-axis).
323      * Notice that this angle does not match angles obtained from {@link com.irurueta.geometry.Rotation3D} or
324      * {@link com.irurueta.geometry.Quaternion} because they are referred to different axes.
325      *
326      * @param result instance where pitch Euler angle will be stored.
327      */
328     public void getPitchEulerAngleMeasurement(final Angle result) {
329         result.setValue(getPitchEulerAngle());
330         result.setUnit(AngleUnit.RADIANS);
331     }
332 
333     /**
334      * Gets pitch Euler angle (around y-axis).
335      * Notice that this angle does not match angles obtained from {@link com.irurueta.geometry.Rotation3D} or
336      * {@link com.irurueta.geometry.Quaternion} because they are referred to different axes.
337      *
338      * @return pitch Euler angle.
339      */
340     public Angle getPitchEulerAngleMeasurement() {
341         final var result = new Angle(0.0, AngleUnit.RADIANS);
342         getPitchEulerAngleMeasurement(result);
343         return result;
344     }
345 
346     /**
347      * Gets yaw Euler angle (around z-axis) expressed in radians.
348      * Notice that this angle does not match angles obtained from {@link com.irurueta.geometry.Rotation3D} or
349      * {@link com.irurueta.geometry.Quaternion} because they are referred to different axes.
350      *
351      * @return yaw Euler angle.
352      */
353     public double getYawEulerAngle() {
354         return Math.atan2(matrix.getElementAt(0, 1), matrix.getElementAt(0, 0));
355     }
356 
357     /**
358      * Gets yaw Euler angle (around z-axis).
359      * Notice that this angle does not match angles obtained from {@link com.irurueta.geometry.Rotation3D} or
360      * {@link com.irurueta.geometry.Quaternion} because they are referred to different axes.
361      *
362      * @param result instance where yaw Euler angle will be stored.
363      */
364     public void getYawEulerAngleMeasurement(final Angle result) {
365         result.setValue(getYawEulerAngle());
366         result.setUnit(AngleUnit.RADIANS);
367     }
368 
369     /**
370      * Gets yaw Euler angle (around z-axis).
371      * Notice that this angle does not match angles obtained from {@link com.irurueta.geometry.Rotation3D} or
372      * {@link com.irurueta.geometry.Quaternion} because they are referred to different axes.
373      *
374      * @return yaw Euler angle.
375      */
376     public Angle getYawEulerAngleMeasurement() {
377         final var result = new Angle(0.0, AngleUnit.RADIANS);
378         getYawEulerAngleMeasurement(result);
379         return result;
380     }
381 
382     /**
383      * Sets euler angles (roll, pitch and yaw) expressed in radians.
384      * Notice that these angles do not match angles obtained from {@link com.irurueta.geometry.Rotation3D} or
385      * {@link com.irurueta.geometry.Quaternion} because they are referred to different axes.
386      *
387      * @param roll  roll Euler angle (around x-axis) expressed in radians.
388      * @param pitch pitch Euler angle (around y-axis) expressed in radians.
389      * @param yaw   yaw Euler angle (around z-axis) expressed in radians.
390      */
391     public void setEulerAngles(final double roll, final double pitch, final double yaw) {
392         final var sinPhi = Math.sin(roll);
393         final var cosPhi = Math.cos(roll);
394         final var sinTheta = Math.sin(pitch);
395         final var cosTheta = Math.cos(pitch);
396         final var sinPsi = Math.sin(yaw);
397         final var cosPsi = Math.cos(yaw);
398 
399         // Calculate coordinate transformation matrix using (2.22)
400         matrix.setElementAt(0, 0, cosTheta * cosPsi);
401         matrix.setElementAt(0, 1, cosTheta * sinPsi);
402         matrix.setElementAt(0, 2, -sinTheta);
403 
404         matrix.setElementAt(1, 0, -cosPhi * sinPsi + sinPhi * sinTheta * cosPsi);
405         matrix.setElementAt(1, 1, cosPhi * cosPsi + sinPhi * sinTheta * sinPsi);
406         matrix.setElementAt(1, 2, sinPhi * cosTheta);
407 
408         matrix.setElementAt(2, 0, sinPhi * sinPsi + cosPhi * sinTheta * cosPsi);
409         matrix.setElementAt(2, 1, -sinPhi * cosPsi + cosPhi * sinTheta * sinPsi);
410         matrix.setElementAt(2, 2, cosPhi * cosTheta);
411     }
412 
413     /**
414      * Sets euler angles (roll, pitch and yaw).
415      * Notice that these angles do not match angles obtained from {@link com.irurueta.geometry.Rotation3D} or
416      * {@link com.irurueta.geometry.Quaternion} because they are referred to different axes.
417      *
418      * @param roll  roll Euler angle (around x-axis).
419      * @param pitch pitch Euler angle (around y-axis).
420      * @param yaw   yaw Euler angle (around z-axis).
421      */
422     public void setEulerAngles(final Angle roll, final Angle pitch, final Angle yaw) {
423         setEulerAngles(AngleConverter.convert(roll.getValue().doubleValue(), roll.getUnit(), AngleUnit.RADIANS),
424                 AngleConverter.convert(pitch.getValue().doubleValue(), pitch.getUnit(), AngleUnit.RADIANS),
425                 AngleConverter.convert(yaw.getValue().doubleValue(), yaw.getUnit(), AngleUnit.RADIANS));
426     }
427 
428     /**
429      * Gets source frame type.
430      *
431      * @return source frame type.
432      */
433     public FrameType getSourceType() {
434         return sourceType;
435     }
436 
437     /**
438      * Sets source frame type.
439      *
440      * @param sourceType source frame type.
441      * @throws NullPointerException if provided value is null.
442      */
443     public void setSourceType(final FrameType sourceType) {
444         if (sourceType == null) {
445             throw new NullPointerException();
446         }
447 
448         this.sourceType = sourceType;
449     }
450 
451     /**
452      * Gets destination frame type.
453      *
454      * @return destination frame type.
455      */
456     public FrameType getDestinationType() {
457         return destinationType;
458     }
459 
460     /**
461      * Sets destination frame type.
462      *
463      * @param destinationType destination frame type.
464      * @throws NullPointerException if provided value is null.
465      */
466     public void setDestinationType(final FrameType destinationType) {
467         if (destinationType == null) {
468             throw new NullPointerException();
469         }
470 
471         this.destinationType = destinationType;
472     }
473 
474     /**
475      * Gets internal matrix as a 3D rotation.
476      *
477      * @return a 3D rotation representing the internal matrix.
478      * @throws InvalidRotationMatrixException if internal matrix cannot be converted to a 3D rotation.
479      */
480     public Rotation3D asRotation() throws InvalidRotationMatrixException {
481         return new MatrixRotation3D(matrix);
482     }
483 
484     /**
485      * Gets internal matrix as a 3D rotation.
486      *
487      * @param result instance where 3D rotation will be stored.
488      * @throws InvalidRotationMatrixException if internal matrix cannot be converted to a 3D rotation.
489      */
490     public void asRotation(final Rotation3D result) throws InvalidRotationMatrixException {
491         result.fromMatrix(matrix);
492     }
493 
494     /**
495      * Sets internal matrix as the inhomogeneous matrix representation of provided 3D rotation.
496      * @param rotation 3D rotation to set matrix from.
497      */
498     public void fromRotation(final Rotation3D rotation) {
499         rotation.asInhomogeneousMatrix(matrix);
500     }
501 
502     /**
503      * Copies this instance data into provided instance.
504      *
505      * @param output destination instance where data will be copied to.
506      */
507     public void copyTo(final CoordinateTransformation output) {
508         output.sourceType = sourceType;
509         output.destinationType = destinationType;
510         matrix.copyTo(output.matrix);
511     }
512 
513     /**
514      * Copies data of provided instance into this instance.
515      *
516      * @param input instance to copy data from.
517      */
518     public void copyFrom(final CoordinateTransformation input) {
519         sourceType = input.sourceType;
520         destinationType = input.destinationType;
521         matrix.copyFrom(input.matrix);
522     }
523 
524     /**
525      * Computes and returns hash code for this instance. Hash codes are almost unique
526      * values that are useful for fast classification and storage of objects in collections.
527      *
528      * @return Hash code.
529      */
530     @Override
531     public int hashCode() {
532         return Objects.hash(sourceType, destinationType, matrix);
533     }
534 
535     /**
536      * Checks if provided object is a CoordinateTransformationMatrix having exactly the same
537      * contents as this instance.
538      *
539      * @param obj Object to be compared.
540      * @return true if both objects are considered to be equal, false otherwise.
541      */
542     @Override
543     public boolean equals(final Object obj) {
544         if (obj == null) {
545             return false;
546         }
547         if (obj == this) {
548             return true;
549         }
550         if (!(obj instanceof CoordinateTransformation other)) {
551             return false;
552         }
553 
554         return equals(other);
555     }
556 
557     /**
558      * Checks if provided instance has exactly the same contents as this instance.
559      *
560      * @param other instance to be compared.
561      * @return true if both instances are considered to be equal, false otherwise.
562      */
563     public boolean equals(final CoordinateTransformation other) {
564         return equals(other, 0.0);
565     }
566 
567     /**
568      * Checks if provided instance has contents similar to this instance up to
569      * provided threshold value.
570      *
571      * @param other     instance to be compared.
572      * @param threshold maximum difference allowed between values on internal matrix.
573      * @return true if both instances are considered to be equal (up to provided threshold),
574      * false otherwise.
575      */
576     public boolean equals(final CoordinateTransformation other, final double threshold) {
577         if (other == null) {
578             return false;
579         }
580         return other.sourceType == sourceType && other.destinationType == destinationType &&
581                 other.matrix.equals(matrix, threshold);
582     }
583 
584     /**
585      * Computes the inverse of this coordinate transformation matrix and stores the result into provided instance.
586      *
587      * @param result instance where inverse will be stored.
588      */
589     public void inverse(final CoordinateTransformation result) {
590         try {
591             final var source = sourceType;
592             final var destination = destinationType;
593             final var m = Matrix.identity(ROWS, COLS);
594             m.copyFrom(this.matrix);
595 
596             result.setSourceType(destination);
597             result.setDestinationType(source);
598 
599             // Because matrix needs to be a rotation (3x3 and orthonormal), its inverse is the transpose
600             m.transpose();
601             result.setMatrix(m);
602         } catch (final WrongSizeException | InvalidRotationMatrixException ignore) {
603             // never happens
604         }
605     }
606 
607     /**
608      * Converts this instance into its inverse coordinate transformation matrix.
609      */
610     public void inverse() {
611         inverse(this);
612     }
613 
614     /**
615      * Computes the inverse of this coordinate transformation matrix and returns it as a new instance.
616      *
617      * @return the inverse of this coordinate transformation matrix.
618      */
619     public CoordinateTransformation inverseAndReturnNew() {
620         final var result = new CoordinateTransformation(destinationType, sourceType);
621         inverse(result);
622         return result;
623     }
624 
625     /**
626      * Computes matrix to convert ECEF to NED coordinates.
627      *
628      * @param latitude  latitude angle.
629      * @param longitude longitude angle.
630      * @param result    instance where computed matrix will be stored.
631      */
632     public static void ecefToNedMatrix(final Angle latitude, final Angle longitude, final Matrix result) {
633         ecefToNedMatrix(
634                 AngleConverter.convert(latitude.getValue().doubleValue(), latitude.getUnit(), AngleUnit.RADIANS),
635                 AngleConverter.convert(longitude.getValue().doubleValue(), longitude.getUnit(), AngleUnit.RADIANS),
636                 result);
637     }
638 
639     /**
640      * Computes matrix to convert ECEF to NED coordinates.
641      *
642      * @param latitude  latitude angle.
643      * @param longitude longitude angle.
644      * @return a new matrix to convert ECEF to NED coordinates.
645      */
646     public static Matrix ecefToNedMatrix(final Angle latitude, final Angle longitude) {
647         return ecefToNedMatrix(
648                 AngleConverter.convert(latitude.getValue().doubleValue(), latitude.getUnit(), AngleUnit.RADIANS),
649                 AngleConverter.convert(longitude.getValue().doubleValue(), longitude.getUnit(), AngleUnit.RADIANS));
650     }
651 
652     /**
653      * Computes matrix to convert ECEF to NED coordinates.
654      *
655      * @param latitude  latitude expressed in radians.
656      * @param longitude longitude expressed in radians.
657      * @param result    instance where computed matrix will be stored.
658      */
659     public static void ecefToNedMatrix(final double latitude, final double longitude, final Matrix result) {
660         if (result.getRows() != ROWS || result.getColumns() != COLS) {
661             try {
662                 result.resize(ROWS, COLS);
663             } catch (final WrongSizeException ignore) {
664                 // never happens
665             }
666         }
667         // Calculate ECEF to NED coordinate transformation matrix using (2.150)
668         final var cosLat = Math.cos(latitude);
669         final var sinLat = Math.sin(latitude);
670         final var cosLong = Math.cos(longitude);
671         final var sinLong = Math.sin(longitude);
672 
673         result.setElementAtIndex(0, -sinLat * cosLong);
674         result.setElementAtIndex(1, -sinLong);
675         result.setElementAtIndex(2, -cosLat * cosLong);
676 
677         result.setElementAtIndex(3, -sinLat * sinLong);
678         result.setElementAtIndex(4, cosLong);
679         result.setElementAtIndex(5, -cosLat * sinLong);
680 
681         result.setElementAtIndex(6, cosLat);
682         result.setElementAtIndex(7, 0.0);
683         result.setElementAtIndex(8, -sinLat);
684     }
685 
686     /**
687      * Computes matrix to convert ECEF to NED coordinates.
688      *
689      * @param latitude  latitude expressed in radians.
690      * @param longitude longitude expressed in radians.
691      * @return a new matrix to convert ECEF to NED coordinates.
692      */
693     public static Matrix ecefToNedMatrix(final double latitude, final double longitude) {
694         Matrix result;
695         try {
696             result = new Matrix(CoordinateTransformation.ROWS, CoordinateTransformation.COLS);
697             ecefToNedMatrix(latitude, longitude, result);
698         } catch (final WrongSizeException ignore) {
699             // never happens
700             result = null;
701         }
702         return result;
703     }
704 
705     /**
706      * Computes ECEF to NED coordinate transformation matrix.
707      *
708      * @param latitude  latitude angle.
709      * @param longitude longitude angle.
710      * @param result    instance where result will be stored.
711      */
712     public static void ecefToNedCoordinateTransformationMatrix(
713             final Angle latitude, final Angle longitude, final CoordinateTransformation result) {
714         ecefToNedCoordinateTransformationMatrix(
715                 AngleConverter.convert(latitude.getValue().doubleValue(), latitude.getUnit(), AngleUnit.RADIANS),
716                 AngleConverter.convert(longitude.getValue().doubleValue(), longitude.getUnit(), AngleUnit.RADIANS),
717                 result);
718     }
719 
720     /**
721      * Computes ECEF to NED coordinate transformation matrix.
722      *
723      * @param latitude  latitude angle.
724      * @param longitude longitude angle.
725      * @return a new ECEF to NED coordinate transformation matrix.
726      */
727     public static CoordinateTransformation ecefToNedCoordinateTransformationMatrix(
728             final Angle latitude, final Angle longitude) {
729         return ecefToNedCoordinateTransformationMatrix(
730                 AngleConverter.convert(latitude.getValue().doubleValue(), latitude.getUnit(), AngleUnit.RADIANS),
731                 AngleConverter.convert(longitude.getValue().doubleValue(), longitude.getUnit(), AngleUnit.RADIANS));
732     }
733 
734     /**
735      * Computes ECEF to NED coordinate transformation matrix.
736      *
737      * @param latitude  latitude expressed in radians.
738      * @param longitude longitude expressed in radians.
739      * @param result    instance where result will be stored.
740      */
741     public static void ecefToNedCoordinateTransformationMatrix(
742             final double latitude, final double longitude, final CoordinateTransformation result) {
743         try {
744             result.setSourceType(FrameType.EARTH_CENTERED_EARTH_FIXED_FRAME);
745             result.setDestinationType(FrameType.LOCAL_NAVIGATION_FRAME);
746             result.setMatrix(ecefToNedMatrix(latitude, longitude));
747         } catch (final InvalidRotationMatrixException ignore) {
748             // never happens
749         }
750     }
751 
752     /**
753      * Computes ECEF to NED coordinate transformation matrix.
754      *
755      * @param latitude  latitude expressed in radians.
756      * @param longitude longitude expressed in radians.
757      * @return a new ECEF to NED coordinate transformation matrix.
758      */
759     public static CoordinateTransformation ecefToNedCoordinateTransformationMatrix(
760             final double latitude, final double longitude) {
761         final var result = new CoordinateTransformation(FrameType.EARTH_CENTERED_EARTH_FIXED_FRAME,
762                 FrameType.LOCAL_NAVIGATION_FRAME);
763         ecefToNedCoordinateTransformationMatrix(latitude, longitude, result);
764         return result;
765     }
766 
767     /**
768      * Computes matrix to convert NED to ECEF coordinates.
769      *
770      * @param latitude  latitude angle.
771      * @param longitude longitude angle.
772      * @param result    instance where computed matrix will be stored.
773      */
774     public static void nedToEcefMatrix(final Angle latitude, final Angle longitude, final Matrix result) {
775         nedToEcefMatrix(
776                 AngleConverter.convert(latitude.getValue().doubleValue(), latitude.getUnit(), AngleUnit.RADIANS),
777                 AngleConverter.convert(longitude.getValue().doubleValue(), longitude.getUnit(), AngleUnit.RADIANS),
778                 result);
779     }
780 
781     /**
782      * Computes matrix to convert NED to ECEF coordinates.
783      *
784      * @param latitude  latitude angle.
785      * @param longitude longitude angle.
786      * @return a new matrix to convert NED to ECEF coordinates.
787      */
788     public static Matrix nedToEcefMatrix(final Angle latitude, final Angle longitude) {
789         return nedToEcefMatrix(
790                 AngleConverter.convert(latitude.getValue().doubleValue(), latitude.getUnit(), AngleUnit.RADIANS),
791                 AngleConverter.convert(longitude.getValue().doubleValue(), longitude.getUnit(), AngleUnit.RADIANS));
792     }
793 
794     /**
795      * Computes matrix to convert NED to ECEF coordinates.
796      *
797      * @param latitude  latitude expressed in radians.
798      * @param longitude longitude expressed in radians.
799      * @param result    instance where computed matrix will be stored.
800      */
801     public static void nedToEcefMatrix(final double latitude, final double longitude, final Matrix result) {
802         // NED to ECEF matrix is the inverse of ECEF to NED matrix.
803         // Since ECEF to NED matrix is a rotation (3x3 and orthonormal), its inverse is the transpose.
804         ecefToNedMatrix(latitude, longitude, result);
805         result.transpose();
806     }
807 
808     /**
809      * Computes matrix to convert NED to ECEF coordinates.
810      *
811      * @param latitude  latitude expressed in radians.
812      * @param longitude longitude expressed in radians.
813      * @return a new matrix to convert NED to ECEF coordinates.
814      */
815     public static Matrix nedToEcefMatrix(final double latitude, final double longitude) {
816         Matrix result;
817         try {
818             result = new Matrix(CoordinateTransformation.ROWS, CoordinateTransformation.COLS);
819             nedToEcefMatrix(latitude, longitude, result);
820         } catch (final WrongSizeException ignore) {
821             // never happens
822             result = null;
823         }
824 
825         return result;
826     }
827 
828     /**
829      * Computes NED to ECEF coordinate transformation matrix.
830      *
831      * @param latitude  latitude angle.
832      * @param longitude longitude angle.
833      * @param result    instance where result will be stored.
834      */
835     public static void nedToEcefCoordinateTransformationMatrix(
836             final Angle latitude, final Angle longitude, final CoordinateTransformation result) {
837         nedToEcefCoordinateTransformationMatrix(
838                 AngleConverter.convert(latitude.getValue().doubleValue(), latitude.getUnit(), AngleUnit.RADIANS),
839                 AngleConverter.convert(longitude.getValue().doubleValue(), longitude.getUnit(), AngleUnit.RADIANS),
840                 result);
841     }
842 
843     /**
844      * Computes NED to ECEF coordinate transformation matrix.
845      *
846      * @param latitude  latitude angle.
847      * @param longitude longitude angle.
848      * @return a new NED to ECEF coordinate transformation matrix.
849      */
850     public static CoordinateTransformation nedToEcefCoordinateTransformationMatrix(
851             final Angle latitude, final Angle longitude) {
852         return nedToEcefCoordinateTransformationMatrix(
853                 AngleConverter.convert(latitude.getValue().doubleValue(), latitude.getUnit(), AngleUnit.RADIANS),
854                 AngleConverter.convert(longitude.getValue().doubleValue(), longitude.getUnit(), AngleUnit.RADIANS));
855     }
856 
857     /**
858      * Computes NED to ECEF coordinate transformation matrix.
859      *
860      * @param latitude  latitude expressed in radians.
861      * @param longitude longitude expressed in radians.
862      * @param result    instance where result will be stored.
863      */
864     public static void nedToEcefCoordinateTransformationMatrix(
865             final double latitude, final double longitude, final CoordinateTransformation result) {
866         try {
867             result.setSourceType(FrameType.LOCAL_NAVIGATION_FRAME);
868             result.setDestinationType(FrameType.EARTH_CENTERED_EARTH_FIXED_FRAME);
869             result.setMatrix(nedToEcefMatrix(latitude, longitude));
870         } catch (final InvalidRotationMatrixException ignore) {
871             // never happens
872         }
873     }
874 
875     /**
876      * Computes NED to ECEF coordinate transformation matrix.
877      *
878      * @param latitude  latitude expressed in radians.
879      * @param longitude longitude expressed in radians.
880      * @return a new NED to ECEF coordinate transformation matrix.
881      */
882     public static CoordinateTransformation nedToEcefCoordinateTransformationMatrix(
883             final double latitude, final double longitude) {
884         final var result = new CoordinateTransformation(FrameType.LOCAL_NAVIGATION_FRAME,
885                 FrameType.EARTH_CENTERED_EARTH_FIXED_FRAME);
886         nedToEcefCoordinateTransformationMatrix(latitude, longitude, result);
887         return result;
888     }
889 
890     /**
891      * Computes ECEF to ECI coordinate transformation matrix taking into account Earth
892      * rotation during provided time interval.
893      *
894      * @param timeInterval a time interval.
895      * @param result       instance where result will be stored.
896      */
897     public static void ecefToEciMatrixFromTimeInterval(final Time timeInterval, final Matrix result) {
898         ecefToEciMatrixFromTimeInterval(TimeConverter.convert(
899                 timeInterval.getValue().doubleValue(), timeInterval.getUnit(), TimeUnit.SECOND), result);
900     }
901 
902     /**
903      * Computes ECEF to ECI coordinate transformation matrix taking into account Earth
904      * rotation during provided time interval.
905      *
906      * @param timeInterval a time interval expressed in seconds (s).
907      * @param result       instance where result will be stored.
908      */
909     public static void ecefToEciMatrixFromTimeInterval(final double timeInterval, final Matrix result) {
910         ecefToEciMatrixFromAngle(EARTH_ROTATION_RATE * timeInterval, result);
911     }
912 
913     /**
914      * Computes ECEF to ECI coordinate transformation matrix for provided Earth
915      * rotation angle.
916      *
917      * @param angle  angle amount the Earth has rotated.
918      * @param result instance where result will be stored.
919      */
920     public static void ecefToEciMatrixFromAngle(final Angle angle, final Matrix result) {
921         ecefToEciMatrixFromAngle(AngleConverter.convert(angle.getValue().doubleValue(), angle.getUnit(),
922                 AngleUnit.RADIANS), result);
923     }
924 
925     /**
926      * Computes ECEF to ECI coordinate transformation matrix for provided Earth
927      * rotation angle.
928      *
929      * @param angle  angle amount the Earth has rotated expressed in radians.
930      * @param result instance where result will be stored.
931      */
932     public static void ecefToEciMatrixFromAngle(final double angle, final Matrix result) {
933         if (result.getRows() != ROWS || result.getColumns() != COLS) {
934             try {
935                 result.resize(ROWS, COLS);
936             } catch (final WrongSizeException ignore) {
937                 // never happens
938             }
939         }
940 
941         final var sinAngle = Math.sin(angle);
942         final var cosAngle = Math.cos(angle);
943 
944         result.setElementAt(0, 0, cosAngle);
945         result.setElementAt(0, 1, -sinAngle);
946         result.setElementAt(0, 2, 0.0);
947 
948         result.setElementAt(1, 0, sinAngle);
949         result.setElementAt(1, 1, cosAngle);
950         result.setElementAt(1, 2, 0.0);
951 
952         result.setElementAt(2, 0, 0.0);
953         result.setElementAt(2, 1, 0.0);
954         result.setElementAt(2, 2, 1.0);
955     }
956 
957     /**
958      * Computes ECEF to ECI coordinate transformation matrix taking into account Earth
959      * rotation during provided time interval.
960      *
961      * @param timeInterval a time interval.
962      * @return a new ECEF to ECI coordinate transformation matrix.
963      */
964     public static Matrix ecefToEciMatrixFromTimeInterval(final Time timeInterval) {
965         return ecefToEciMatrixFromTimeInterval(TimeConverter.convert(
966                 timeInterval.getValue().doubleValue(), timeInterval.getUnit(), TimeUnit.SECOND));
967     }
968 
969     /**
970      * Computes ECEF to ECI coordinate transformation matrix taking into account Earth
971      * rotation during provided time interval.
972      *
973      * @param timeInterval a time interval expressed in seconds (s).
974      * @return a new ECEF to ECI coordinate transformation matrix.
975      */
976     public static Matrix ecefToEciMatrixFromTimeInterval(final double timeInterval) {
977         return ecefToEciMatrixFromAngle(EARTH_ROTATION_RATE * timeInterval);
978     }
979 
980     /**
981      * Computes ECEF to ECI coordinate transformation matrix for provided Earth
982      * rotation angle.
983      *
984      * @param angle angle amount the Earth has rotated.
985      * @return a new ECEF to ECI coordinate transformation matrix.
986      */
987     public static Matrix ecefToEciMatrixFromAngle(final Angle angle) {
988         return ecefToEciMatrixFromAngle(AngleConverter.convert(angle.getValue().doubleValue(), angle.getUnit(),
989                 AngleUnit.RADIANS));
990     }
991 
992     /**
993      * Computes ECEF to ECI coordinate transformation matrix for provided Earth
994      * rotation angle.
995      *
996      * @param angle angle amount the Earth has rotated expressed in radians.
997      * @return a new ECEF to ECI coordinate transformation matrix.
998      */
999     public static Matrix ecefToEciMatrixFromAngle(final double angle) {
1000         Matrix result;
1001         try {
1002             result = new Matrix(CoordinateTransformation.ROWS, CoordinateTransformation.COLS);
1003             ecefToEciMatrixFromAngle(angle, result);
1004         } catch (final WrongSizeException ignore) {
1005             // never happens
1006             result = null;
1007         }
1008         return result;
1009     }
1010 
1011     /**
1012      * Computes ECEF to ECI coordinate transformation matrix taking into account Earth
1013      * rotation during provided time interval.
1014      *
1015      * @param timeInterval a time interval.
1016      * @param result       instance where result will be stored.
1017      */
1018     public static void ecefToEciCoordinateTransformationMatrixFromTimeInterval(
1019             final Time timeInterval, final CoordinateTransformation result) {
1020         ecefToEciCoordinateTransformationMatrixFromTimeInterval(
1021                 TimeConverter.convert(timeInterval.getValue().doubleValue(), timeInterval.getUnit(), TimeUnit.SECOND),
1022                 result);
1023     }
1024 
1025     /**
1026      * Computes ECEF to ECI coordinate transformation matrix taking into account Earth
1027      * rotation during provided time interval.
1028      *
1029      * @param timeInterval a time interval expressed in seconds (s).
1030      * @param result       instance where result will be stored.
1031      */
1032     public static void ecefToEciCoordinateTransformationMatrixFromTimeInterval(
1033             final double timeInterval, final CoordinateTransformation result) {
1034         ecefToEciCoordinateTransformationMatrixFromAngle(EARTH_ROTATION_RATE * timeInterval, result);
1035     }
1036 
1037     /**
1038      * Computes ECEF to ECI coordinate transformation matrix for provided Earth
1039      * rotation angle.
1040      *
1041      * @param angle  angle amount the Earth has rotated.
1042      * @param result instance where result will be stored.
1043      */
1044     public static void ecefToEciCoordinateTransformationMatrixFromAngle(
1045             final Angle angle, final CoordinateTransformation result) {
1046         ecefToEciCoordinateTransformationMatrixFromAngle(
1047                 AngleConverter.convert(angle.getValue().doubleValue(), angle.getUnit(), AngleUnit.RADIANS), result);
1048     }
1049 
1050     /**
1051      * Computes ECEF to ECI coordinate transformation matrix for provided Earth
1052      * rotation angle.
1053      *
1054      * @param angle  angle amount the Earth has rotated expressed in radians.
1055      * @param result instance where result will be stored.
1056      */
1057     public static void ecefToEciCoordinateTransformationMatrixFromAngle(
1058             final double angle, final CoordinateTransformation result) {
1059         try {
1060             result.setSourceType(FrameType.EARTH_CENTERED_EARTH_FIXED_FRAME);
1061             result.setDestinationType(FrameType.EARTH_CENTERED_INERTIAL_FRAME);
1062             result.setMatrix(ecefToEciMatrixFromAngle(angle));
1063         } catch (final InvalidRotationMatrixException ignore) {
1064             // never happens
1065         }
1066     }
1067 
1068     /**
1069      * Computes ECEF to ECI coordinate transformation matrix taking into account Earth
1070      * rotation during provided time interval.
1071      *
1072      * @param timeInterval a time interval.
1073      * @return a new ECEF to ECI coordinate transformation matrix.
1074      */
1075     public static CoordinateTransformation ecefToEciCoordinateTransformationMatrixFromTimeInterval(
1076             final Time timeInterval) {
1077         final var result = new CoordinateTransformation(
1078                 FrameType.EARTH_CENTERED_EARTH_FIXED_FRAME, FrameType.EARTH_CENTERED_INERTIAL_FRAME);
1079         ecefToEciCoordinateTransformationMatrixFromTimeInterval(timeInterval, result);
1080         return result;
1081     }
1082 
1083     /**
1084      * Computes ECEF to ECI coordinate transformation matrix taking into account Earth
1085      * rotation during provided time interval.
1086      *
1087      * @param timeInterval a time interval expressed in seconds (s).
1088      * @return a new ECEF to ECI coordinate transformation matrix.
1089      */
1090     public static CoordinateTransformation ecefToEciCoordinateTransformationMatrixFromTimeInterval(
1091             final double timeInterval) {
1092         final var result = new CoordinateTransformation(
1093                 FrameType.EARTH_CENTERED_EARTH_FIXED_FRAME, FrameType.EARTH_CENTERED_INERTIAL_FRAME);
1094         ecefToEciCoordinateTransformationMatrixFromTimeInterval(timeInterval, result);
1095         return result;
1096     }
1097 
1098     /**
1099      * Computes ECEF to ECI coordinate transformation matrix for provided Earth rotation angle.
1100      *
1101      * @param angle angle amount the Earth has rotated.
1102      * @return a new ECEF to ECI coordinate transformation matrix.
1103      */
1104     public static CoordinateTransformation ecefToEciCoordinateTransformationMatrixFromAngle(final Angle angle) {
1105         final var result = new CoordinateTransformation(
1106                 FrameType.EARTH_CENTERED_EARTH_FIXED_FRAME, FrameType.EARTH_CENTERED_INERTIAL_FRAME);
1107         ecefToEciCoordinateTransformationMatrixFromAngle(angle, result);
1108         return result;
1109     }
1110 
1111     /**
1112      * Computes ECEF to ECI coordinate transformation matrix for provided Earth rotation angle.
1113      *
1114      * @param angle angle amount the Earth has rotated expressed in radians.
1115      * @return a new ECEF to ECI coordinate transformation matrix.
1116      */
1117     public static CoordinateTransformation ecefToEciCoordinateTransformationMatrixFromAngle(final double angle) {
1118         final var result = new CoordinateTransformation(
1119                 FrameType.EARTH_CENTERED_EARTH_FIXED_FRAME, FrameType.EARTH_CENTERED_INERTIAL_FRAME);
1120         ecefToEciCoordinateTransformationMatrixFromAngle(angle, result);
1121         return result;
1122     }
1123 
1124     /**
1125      * Computes ECI to ECEF coordinate transformation matrix taking into account Earth
1126      * rotation during provided time interval.
1127      *
1128      * @param timeInterval a time interval.
1129      * @param result       instance where result will be stored.
1130      */
1131     public static void eciToEcefMatrixFromTimeInterval(final Time timeInterval, final Matrix result) {
1132         eciToEcefMatrixFromTimeInterval(TimeConverter.convert(
1133                 timeInterval.getValue().doubleValue(), timeInterval.getUnit(), TimeUnit.SECOND), result);
1134     }
1135 
1136     /**
1137      * Computes ECI to ECEF coordinate transformation matrix taking into account Earth
1138      * rotation during provided time interval.
1139      *
1140      * @param timeInterval a time interval expressed in seconds (s).
1141      * @param result       instance where result will be stored.
1142      */
1143     public static void eciToEcefMatrixFromTimeInterval(final double timeInterval, final Matrix result) {
1144         eciToEcefMatrixFromAngle(EARTH_ROTATION_RATE * timeInterval, result);
1145     }
1146 
1147     /**
1148      * Computes ECI to ECEF coordinate transformation matrix for provided Earth
1149      * rotation angle.
1150      *
1151      * @param angle  angle amount the Earth has rotated.
1152      * @param result instance where result will be stored.
1153      */
1154     public static void eciToEcefMatrixFromAngle(final Angle angle, final Matrix result) {
1155         eciToEcefMatrixFromAngle(AngleConverter.convert(angle.getValue().doubleValue(), angle.getUnit(),
1156                 AngleUnit.RADIANS), result);
1157     }
1158 
1159     /**
1160      * Computes ECI to ECEF coordinate transformation matrix for provided Earth
1161      * rotation angle.
1162      *
1163      * @param angle  angle amount the Earth has rotated expressed in radians.
1164      * @param result instance where result will be stored.
1165      */
1166     public static void eciToEcefMatrixFromAngle(final double angle, final Matrix result) {
1167         if (result.getRows() != ROWS || result.getColumns() != COLS) {
1168             try {
1169                 result.resize(ROWS, COLS);
1170             } catch (final WrongSizeException ignore) {
1171                 // never happens
1172             }
1173         }
1174 
1175         final var sinAngle = Math.sin(angle);
1176         final var cosAngle = Math.cos(angle);
1177 
1178         result.setElementAt(0, 0, cosAngle);
1179         result.setElementAt(0, 1, sinAngle);
1180         result.setElementAt(0, 2, 0.0);
1181 
1182         result.setElementAt(1, 0, -sinAngle);
1183         result.setElementAt(1, 1, cosAngle);
1184         result.setElementAt(1, 2, 0.0);
1185 
1186         result.setElementAt(2, 0, 0.0);
1187         result.setElementAt(2, 1, 0.0);
1188         result.setElementAt(2, 2, 1.0);
1189     }
1190 
1191     /**
1192      * Computes ECI to ECEF coordinate transformation matrix taking into account Earth
1193      * rotation during provided time interval.
1194      *
1195      * @param timeInterval a time interval.
1196      * @return a new ECI to ECEF coordinate transformation matrix.
1197      */
1198     public static Matrix eciToEcefMatrixFromTimeInterval(final Time timeInterval) {
1199         return eciToEcefMatrixFromTimeInterval(TimeConverter.convert(
1200                 timeInterval.getValue().doubleValue(), timeInterval.getUnit(), TimeUnit.SECOND));
1201     }
1202 
1203     /**
1204      * Computes ECI to ECEF coordinate transformation matrix taking into account Earth
1205      * rotation during provided time interval.
1206      *
1207      * @param timeInterval a time interval expressed in seconds (s).
1208      * @return a new ECI to ECEF coordinate transformation matrix.
1209      */
1210     public static Matrix eciToEcefMatrixFromTimeInterval(final double timeInterval) {
1211         return eciToEcefMatrixFromAngle(EARTH_ROTATION_RATE * timeInterval);
1212     }
1213 
1214     /**
1215      * Computes ECI to ECEF coordinate transformation matrix for provided Earth
1216      * rotation angle.
1217      *
1218      * @param angle angle amount the Earth has rotated.
1219      * @return a new ECI to ECEF coordinate transformation matrix.
1220      */
1221     public static Matrix eciToEcefMatrixFromAngle(final Angle angle) {
1222         return eciToEcefMatrixFromAngle(AngleConverter.convert(
1223                 angle.getValue().doubleValue(), angle.getUnit(), AngleUnit.RADIANS));
1224     }
1225 
1226     /**
1227      * Computes ECI to ECEF coordinate transformation matrix for provided Earth
1228      * rotation angle.
1229      *
1230      * @param angle angle amount the Earth has rotated.
1231      * @return a new ECI to ECEF coordinate transformation matrix.
1232      */
1233     public static Matrix eciToEcefMatrixFromAngle(final double angle) {
1234         Matrix result;
1235         try {
1236             result = new Matrix(CoordinateTransformation.ROWS, CoordinateTransformation.COLS);
1237             eciToEcefMatrixFromAngle(angle, result);
1238         } catch (final WrongSizeException ignore) {
1239             // never happens
1240             result = null;
1241         }
1242         return result;
1243     }
1244 
1245     /**
1246      * Computes ECI to ECEF coordinate transformation matrix taking into account Earth
1247      * rotation during provided time interval.
1248      *
1249      * @param timeInterval a time interval.
1250      * @param result       instance where result will be stored.
1251      */
1252     public static void eciToEcefCoordinateTransformationMatrixFromTimeInterval(
1253             final Time timeInterval, final CoordinateTransformation result) {
1254         eciToEcefCoordinateTransformationMatrixFromTimeInterval(
1255                 TimeConverter.convert(timeInterval.getValue().doubleValue(), timeInterval.getUnit(), TimeUnit.SECOND),
1256                 result);
1257     }
1258 
1259     /**
1260      * Computes ECI to ECEF coordinate transformation matrix taking into account Earth
1261      * rotation during provided time interval.
1262      *
1263      * @param timeInterval a time interval expressed in seconds (s).
1264      * @param result       instance where result will be stored.
1265      */
1266     public static void eciToEcefCoordinateTransformationMatrixFromTimeInterval(
1267             final double timeInterval, final CoordinateTransformation result) {
1268         eciToEcefCoordinateTransformationMatrixFromAngle(EARTH_ROTATION_RATE * timeInterval, result);
1269     }
1270 
1271     /**
1272      * Computes ECI to ECEF coordinate transformation matrix for provided Earth
1273      * rotation angle.
1274      *
1275      * @param angle  angle amount the Earth has rotated.
1276      * @param result instance where result will be stored.
1277      */
1278     public static void eciToEcefCoordinateTransformationMatrixFromAngle(
1279             final Angle angle, final CoordinateTransformation result) {
1280         eciToEcefCoordinateTransformationMatrixFromAngle(
1281                 AngleConverter.convert(angle.getValue().doubleValue(), angle.getUnit(), AngleUnit.RADIANS), result);
1282     }
1283 
1284     /**
1285      * Computes ECI to ECEF coordinate transformation matrix for provided Earth
1286      * rotation angle.
1287      *
1288      * @param angle  angle amount the Earth has rotated expressed in radians.
1289      * @param result instance where result will be stored.
1290      */
1291     public static void eciToEcefCoordinateTransformationMatrixFromAngle(
1292             final double angle, final CoordinateTransformation result) {
1293         try {
1294             result.setSourceType(FrameType.EARTH_CENTERED_INERTIAL_FRAME);
1295             result.setDestinationType(FrameType.EARTH_CENTERED_EARTH_FIXED_FRAME);
1296             result.setMatrix(eciToEcefMatrixFromAngle(angle));
1297         } catch (final InvalidRotationMatrixException ignore) {
1298             // never happens
1299         }
1300     }
1301 
1302     /**
1303      * Computes ECI to ECEF coordinate transformation matrix taking into account Earth
1304      * rotation during provided time interval.
1305      *
1306      * @param timeInterval a time interval.
1307      * @return a new ECI to ECEF coordinate transformation matrix.
1308      */
1309     public static CoordinateTransformation eciToEcefCoordinateTransformationMatrixFromTimeInterval(
1310             final Time timeInterval) {
1311         final var result = new CoordinateTransformation(
1312                 FrameType.EARTH_CENTERED_INERTIAL_FRAME, FrameType.EARTH_CENTERED_EARTH_FIXED_FRAME);
1313         eciToEcefCoordinateTransformationMatrixFromTimeInterval(timeInterval, result);
1314         return result;
1315     }
1316 
1317     /**
1318      * Computes ECI to ECEF coordinate transformation matrix taking into account Earth
1319      * rotation during provided time interval.
1320      *
1321      * @param timeInterval a time interval expressed in seconds (s).
1322      * @return a new ECI to ECEF coordinate transformation matrix.
1323      */
1324     public static CoordinateTransformation eciToEcefCoordinateTransformationMatrixFromInterval(
1325             final double timeInterval) {
1326         final var result = new CoordinateTransformation(
1327                 FrameType.EARTH_CENTERED_INERTIAL_FRAME, FrameType.EARTH_CENTERED_EARTH_FIXED_FRAME);
1328         eciToEcefCoordinateTransformationMatrixFromTimeInterval(timeInterval, result);
1329         return result;
1330     }
1331 
1332     /**
1333      * Computes ECI to ECEF coordinate transformation matrix for provided Earth
1334      * rotation angle.
1335      *
1336      * @param angle angle amount the Earth has rotated.
1337      * @return a new ECI to ECEF coordinate transformation matrix.
1338      */
1339     public static CoordinateTransformation eciToEcefCoordinateTransformationMatrixFromAngle(
1340             final Angle angle) {
1341         final var result = new CoordinateTransformation(
1342                 FrameType.EARTH_CENTERED_INERTIAL_FRAME, FrameType.EARTH_CENTERED_EARTH_FIXED_FRAME);
1343         eciToEcefCoordinateTransformationMatrixFromAngle(angle, result);
1344         return result;
1345     }
1346 
1347     /**
1348      * Computes ECI to ECEF coordinate transformation matrix for provided Earth
1349      * rotation angle.
1350      *
1351      * @param angle angle amount the Earth has rotated expressed in radians.
1352      * @return a new ECI to ECEF coordinate transformation matrix.
1353      */
1354     public static CoordinateTransformation eciToEcefCoordinateTransformationMatrixFromAngle(
1355             final double angle) {
1356         final var result = new CoordinateTransformation(
1357                 FrameType.EARTH_CENTERED_INERTIAL_FRAME, FrameType.EARTH_CENTERED_EARTH_FIXED_FRAME);
1358         eciToEcefCoordinateTransformationMatrixFromAngle(angle, result);
1359         return result;
1360     }
1361 
1362     /**
1363      * Makes a copy of this instance.
1364      *
1365      * @return a copy of this instance.
1366      * @throws CloneNotSupportedException if clone fails for some reason.
1367      */
1368     @Override
1369     protected Object clone() throws CloneNotSupportedException {
1370         final var result = (CoordinateTransformation) super.clone();
1371         copyTo(result);
1372         return result;
1373     }
1374 }