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.converters;
17  
18  import com.irurueta.algebra.Matrix;
19  import com.irurueta.algebra.WrongSizeException;
20  import com.irurueta.geometry.InvalidRotationMatrixException;
21  import com.irurueta.navigation.frames.CoordinateTransformation;
22  import com.irurueta.navigation.frames.ECEFFrame;
23  import com.irurueta.navigation.frames.ECIorECEFFrame;
24  import com.irurueta.navigation.frames.FrameType;
25  import com.irurueta.navigation.frames.InvalidSourceAndDestinationFrameTypeException;
26  import com.irurueta.navigation.frames.NEDFrame;
27  import com.irurueta.navigation.geodesic.Constants;
28  
29  /**
30   * Converts from ECEF frame to NED frame.
31   * This implementation is based on the equations defined in "Principles of GNSS, Inertial, and Multi-sensor
32   * Integrated Navigation Systems, Second Edition" and on the companion software available at:
33   * <a href="https://github.com/ymjdz/MATLAB-Codes/blob/master/ECEF_to_NED.m">
34   *     https://github.com/ymjdz/MATLAB-Codes/blob/master/ECEF_to_NED.m
35   * </a>
36   */
37  public class ECEFtoNEDFrameConverter implements FrameConverter<ECEFFrame, NEDFrame> {
38  
39      /**
40       * The equatorial radius of WGS84 ellipsoid (6378137 m) defining Earth's shape.
41       */
42      public static final double EARTH_EQUATORIAL_RADIUS_WGS84 = Constants.EARTH_EQUATORIAL_RADIUS_WGS84;
43  
44      /**
45       * Earth eccentricity as defined on the WGS84 ellipsoid.
46       */
47      public static final double EARTH_ECCENTRICITY = Constants.EARTH_ECCENTRICITY;
48  
49      /**
50       * Converts source ECEF frame to a new NED frame instance.
51       *
52       * @param source source frame to convert from.
53       * @return a new destination frame instance.
54       */
55      @Override
56      public NEDFrame convertAndReturnNew(final ECEFFrame source) {
57          final var result = new NEDFrame();
58          convert(source, result);
59          return result;
60      }
61  
62      /**
63       * Converts source ECEF frame to destination NED frame.
64       *
65       * @param source      source frame to convert from.
66       * @param destination destination frame instance to convert to.
67       */
68      @Override
69      public void convert(final ECEFFrame source, final NEDFrame destination) {
70          convertECEFtoNED(source, destination);
71      }
72  
73      /**
74       * Gets source frame type.
75       *
76       * @return source frame type.
77       */
78      @Override
79      public FrameType getSourceType() {
80          return FrameType.EARTH_CENTERED_EARTH_FIXED_FRAME;
81      }
82  
83      /**
84       * Gets destination frame type.
85       *
86       * @return destination frame type.
87       */
88      @Override
89      public FrameType getDestinationType() {
90          return FrameType.LOCAL_NAVIGATION_FRAME;
91      }
92  
93      /**
94       * Converts source ECEF frame to a new NED frame instance.
95       *
96       * @param source source frame to convert from.
97       * @return a new destination frame instance.
98       */
99      public static NEDFrame convertECEFtoNEDAndReturnNew(final ECEFFrame source) {
100         final var result = new NEDFrame();
101         convertECEFtoNED(source, result);
102         return result;
103     }
104 
105     /**
106      * Converts source ECEF frame to destination NED frame.
107      *
108      * @param source      source frame to convert from.
109      * @param destination destination frame instance to convert to.
110      */
111     @SuppressWarnings("DuplicatedCode")
112     public static void convertECEFtoNED(final ECEFFrame source, final NEDFrame destination) {
113         final var x = source.getX();
114         final var y = source.getY();
115         final var z = source.getZ();
116 
117         // Convert position using Borkowski closed-form exact solution from (2.113).
118         final var longitude = Math.atan2(y, x);
119 
120         // From (C.29) and (C.30)
121         final var ecc2 = EARTH_ECCENTRICITY * EARTH_ECCENTRICITY;
122         final var k1 = Math.sqrt(1.0 - ecc2) * Math.abs(z);
123         final var k2 = ecc2 * EARTH_EQUATORIAL_RADIUS_WGS84;
124 
125         final var x2 = x * x;
126         final var y2 = y * y;
127         final var beta = Math.sqrt(x2 + y2);
128 
129         final var e = (k1 - k2) / beta;
130         final var f = (k1 + k2) / beta;
131 
132         // From (C.31)
133         final var p = 4.0 / 3.0 * (e * f + 1.0);
134 
135         // From (C.32)
136         final var e2 = e * e;
137         final var f2 = f * f;
138         final var q = 2.0 * (e2 - f2);
139 
140         // From (C.33)
141         final var p3 = p * p * p;
142         final var q2 = q * q;
143         final var d = p3 + q2;
144 
145         // From (C.34)
146         final var sqrtD = Math.sqrt(d);
147         final var exp = 1.0 / 3.0;
148         final var v = Math.pow(sqrtD - q, exp) - Math.pow(sqrtD + q, exp);
149 
150         // From (C.35)
151         final var g = 0.5 * (Math.sqrt(e2 + v) + e);
152 
153         // From (C.36)
154         final var g2 = g * g;
155         final var t = Math.sqrt(g2 + (f - v * g) / (2.0 * g - e)) - g;
156 
157         // From (C.37)
158         final var t2 = t * t;
159         final var latitude = Math.signum(z) * Math.atan((1.0 - t2) / (2.0 * t * Math.sqrt(1.0 - ecc2)));
160 
161         // From (C.38)
162         final var height = (beta - EARTH_EQUATORIAL_RADIUS_WGS84 * t) * Math.cos(latitude)
163                 + (z - Math.signum(z) * EARTH_EQUATORIAL_RADIUS_WGS84 * Math.sqrt(1.0 - ecc2))
164                 * Math.sin(latitude);
165 
166         try {
167             // Calculate ECEF to NED coordinate transformation matrix
168             final var cen = CoordinateTransformation.ecefToNedMatrix(latitude, longitude);
169 
170             // Transform velocity using (2.73)
171             final var vx = source.getVx();
172             final var vy = source.getVy();
173             final var vz = source.getVz();
174             final var vEbe = new Matrix(ECIorECEFFrame.NUM_VELOCITY_COORDINATES, 1);
175             vEbe.setElementAtIndex(0, vx);
176             vEbe.setElementAtIndex(1, vy);
177             vEbe.setElementAtIndex(2, vz);
178 
179             final var vEbn = cen.multiplyAndReturnNew(vEbe);
180             final var vn = vEbn.getElementAtIndex(0);
181             final var ve = vEbn.getElementAtIndex(1);
182             final var vd = vEbn.getElementAtIndex(2);
183 
184             // Transform attitude using (2.15)
185             final var cbe = source.getCoordinateTransformation().getMatrix();
186             cen.multiply(cbe); // cen is now cbn
187 
188             final var c = new CoordinateTransformation(cen, FrameType.BODY_FRAME, FrameType.LOCAL_NAVIGATION_FRAME);
189 
190             // Set result
191             destination.setLatitude(latitude);
192             destination.setLongitude(longitude);
193             destination.setHeight(height);
194 
195             destination.setVn(vn);
196             destination.setVe(ve);
197             destination.setVd(vd);
198 
199             destination.setCoordinateTransformation(c);
200 
201         } catch (final WrongSizeException | InvalidRotationMatrixException
202                 | InvalidSourceAndDestinationFrameTypeException ignore) {
203             // never happens
204         }
205     }
206 }