1   /* Contributed in the public domain.
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.models.earth;
18  
19  import org.hipparchus.geometry.euclidean.threed.Line;
20  import org.hipparchus.geometry.euclidean.threed.Rotation;
21  import org.hipparchus.geometry.euclidean.threed.Vector3D;
22  import org.hipparchus.util.FastMath;
23  import org.junit.jupiter.api.Assertions;
24  import org.junit.jupiter.api.BeforeAll;
25  import org.junit.jupiter.api.BeforeEach;
26  import org.junit.jupiter.api.Test;
27  import org.orekit.Utils;
28  import org.orekit.bodies.GeodeticPoint;
29  import org.orekit.forces.gravity.potential.EGMFormatReader;
30  import org.orekit.forces.gravity.potential.GravityFieldFactory;
31  import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
32  import org.orekit.frames.Frame;
33  import org.orekit.frames.FramesFactory;
34  import org.orekit.frames.StaticTransform;
35  import org.orekit.frames.Transform;
36  import org.orekit.time.AbsoluteDate;
37  
38  import java.util.Arrays;
39  
40  import static org.hamcrest.CoreMatchers.is;
41  import static org.hamcrest.CoreMatchers.nullValue;
42  import static org.hamcrest.CoreMatchers.sameInstance;
43  import static org.hamcrest.MatcherAssert.assertThat;
44  import static org.orekit.OrekitMatchers.closeTo;
45  import static org.orekit.OrekitMatchers.geodeticPointCloseTo;
46  import static org.orekit.OrekitMatchers.vectorCloseTo;
47  
48  /**
49   * Unit tests for {@link Geoid}.
50   *
51   * @author Evan Ward
52   */
53  public class GeoidTest {
54  
55      /** maximum degree used in testing {@link Geoid}. */
56      private static final int maxOrder = 360;
57      /** maximum order used in testing {@link Geoid}. */
58      private static final int maxDegree = 360;
59      /** The WGS84 reference ellipsoid. */
60      private static ReferenceEllipsoid WGS84 = new ReferenceEllipsoid(
61              6378137.00, 1 / 298.257223563, FramesFactory.getGCRF(),
62              3.986004418e14, 7292115e-11);
63      /**
64       * The potential to use in {@link #getComponent()}. Set in {@link
65       * #setUpBefore()}.
66       */
67      private static NormalizedSphericalHarmonicsProvider potential;
68      /** date to use in test cases */
69      private static AbsoluteDate date;
70  
71      /**
72       * load orekit data and gravity field.
73       *
74       * @throws Exception on error.
75       */
76      @BeforeAll
77      public static void setUpBefore() throws Exception {
78          Utils.setDataRoot("geoid:regular-data");
79          GravityFieldFactory.clearPotentialCoefficientsReaders();
80          GravityFieldFactory.addPotentialCoefficientsReader(
81                  new EGMFormatReader("egm96", false));
82          potential = GravityFieldFactory.getNormalizedProvider(
83                  maxDegree, maxOrder);
84          date = null;
85      }
86  
87      /** {lat, lon, expectedValue} points to evaluate the undulation */
88      private double[][] reference;
89  
90      /** create the array of reference points */
91      @BeforeEach
92      public void setUp() {
93          reference = new double[][]{
94                  {0, 75, -100.3168},
95                  {-30, 60, 14.9214},
96                  {0, 130, 76.6053},
97                  {60, -30, 63.7979},
98                  {40, -75, -34.0402},
99                  {28, 92, -30.4056},// the Himalayas
100                 {45, 250, -7.4825},// the rockies
101                 // this section is taken from the NGA's test file
102                 {38.6281550, 269.7791550, -31.628},
103                 {-14.6212170, 305.0211140, -2.969},
104                 {46.8743190, 102.4487290, -43.575},
105                 {-23.6174460, 133.8747120, 15.871},
106                 {38.6254730, 359.9995000, 50.066},
107                 {-.4667440, .0023000, 17.329}};
108     }
109 
110     /**
111      * Gets a new instance of {@link Geoid} to test with. It is given the EGM96
112      * potential and the WGS84 ellipsoid.
113      *
114      * @return a new {@link Geoid}
115      */
116     private Geoid getComponent() {
117         return new Geoid(potential, WGS84);
118     }
119 
120     /** Test constructor and simple getters. */
121     @Test
122     public void testGeoid() {
123         Geoid geoid = getComponent();
124         // reference ellipse is the same
125         Assertions.assertEquals(WGS84, geoid.getEllipsoid());
126         // geoid and reference ellipse are in the same frame
127         Assertions.assertEquals(WGS84.getBodyFrame(), geoid.getBodyFrame());
128     }
129 
130     /** throws on null */
131     @Test
132     public void testGeoidNullPotential() {
133         Assertions.assertThrows(NullPointerException.class, () -> {
134             new Geoid(null, WGS84);
135         });
136     }
137 
138     /** throws on null */
139     @Test
140     public void testGeoidNullEllipsoid() {
141         Assertions.assertThrows(NullPointerException.class, () -> {
142             new Geoid(potential, null);
143         });
144     }
145 
146     /**
147      * Test several pre-computed points from the Online Geoid Height Evaluation
148      * tool, which takes into account terrain.
149      *
150      * @see <a href="http://geographiclib.sourceforge.net/cgi-bin/GeoidEval">Online
151      * Geoid Height Evaluation</a>
152      * @see <a href="http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm96/egm96.html">Geoid
153      * height for WGS84 and EGM96</a>
154      */
155     @Test
156     public void testGetUndulation() {
157         /*
158          * allow 3 meter of error, which is what the approximations would
159          * suggest, see the comment for Geoid.
160          */
161         final double maxError = 3;
162 
163         // run the test
164         Geoid geoid = getComponent();
165         for (double[] row : reference) {
166             double lat = row[0];
167             double lon = row[1];
168             double undulation = geoid.getUndulation(FastMath.toRadians(lat),
169                     FastMath.toRadians(lon), date);
170             double expected = row[2];
171             // System.out.format("%10g %10g %10g %10g%n", lat, lon, expected,
172             // undulation - expected);
173             Assertions.assertEquals(undulation, expected, maxError, String.format("lat: %5g, lon: %5g", lat, lon));
174         }
175     }
176 
177     /**
178      * check {@link Geoid#getIntersectionPoint(Line, Vector3D, Frame,
179      * AbsoluteDate)} with several points.
180      */
181     @Test
182     public void testGetIntersectionPoint() {
183         // setup
184         Geoid geoid = getComponent();
185         Frame frame = geoid.getBodyFrame();
186 
187         for (double[] point : reference) {
188             GeodeticPoint gp = new GeodeticPoint(FastMath.toRadians(point[0]),
189                     FastMath.toRadians(point[1]), 0);
190             Vector3D expected = geoid.transform(gp);
191             // glancing line: 10% vertical and 90% north (~6 deg elevation)
192             Vector3D slope = gp.getZenith().scalarMultiply(0.1)
193                     .add(gp.getNorth().scalarMultiply(0.9));
194             Vector3D close = expected.add(slope.scalarMultiply(100e3));
195             Vector3D pointOnLine = expected.add(slope);
196             Line line = new Line(close, pointOnLine, 0);
197             // line directed the other way
198             Line otherDirection = new Line(pointOnLine, close, 0);
199 
200             // action
201             GeodeticPoint actual = geoid.getIntersectionPoint(line, close,
202                     frame, date);
203             // other direction
204             GeodeticPoint actualReversed = geoid.getIntersectionPoint(
205                     otherDirection, close, frame, date);
206 
207             // verify
208             String message = String.format("point: %s%n",
209                     Arrays.toString(point));
210             // position accuracy on Earth's surface to 1.3 um.
211             assertThat(message, actualReversed, geodeticPointCloseTo(gp, 1.3e-6));
212             assertThat(message, actual, geodeticPointCloseTo(gp, 1.3e-6));
213         }
214     }
215 
216     /**
217      * check {@link Geoid#getIntersectionPoint(Line, Vector3D, Frame,
218      * AbsoluteDate)} handles frame transformations correctly
219      */
220     @Test
221     public void testGetIntersectionPointFrame() {
222         // setup
223         Geoid geoid = getComponent();
224         Frame frame = new Frame(
225                 geoid.getBodyFrame(),
226                 new Transform(
227                         date,
228                         new Transform(
229                                 date,
230                                 new Vector3D(-1, 2, -3),
231                                 new Vector3D(4, -5, 6)),
232                         new Transform(
233                                 date,
234                                 new Rotation(-7, 8, -9, 10, true),
235                                 new Vector3D(-11, 12, -13))),
236                 "test frame");
237         GeodeticPoint gp = new GeodeticPoint(FastMath.toRadians(46.8743190),
238                 FastMath.toRadians(102.4487290), 0);
239         Vector3D expected = geoid.transform(gp);
240         // glancing line: 10% vertical and 90% north (~6 deg elevation)
241         Vector3D slope = gp.getZenith().scalarMultiply(0.1)
242                 .add(gp.getNorth().scalarMultiply(0.9));
243         Vector3D close = expected.add(slope.scalarMultiply(100));
244         Line line = new Line(expected.add(slope), close, 0);
245         StaticTransform xform = geoid.getBodyFrame().getStaticTransformTo(frame, date);
246         // transform to test frame
247         close = xform.transformPosition(close);
248         line = xform.transformLine(line);
249 
250         // action
251         GeodeticPoint actual = geoid.getIntersectionPoint(line, close, frame,
252                 date);
253 
254         // verify, 1 um position accuracy at Earth's surface
255         assertThat(actual, geodeticPointCloseTo(gp, 1e-6));
256     }
257 
258     /**
259      * check {@link Geoid#getIntersectionPoint(Line, Vector3D, Frame,
260      * AbsoluteDate)} returns null when there is no intersection
261      */
262     @Test
263     public void testGetIntersectionPointNoIntersection() {
264         Geoid geoid = getComponent();
265         Vector3D closeMiss = new Vector3D(geoid.getEllipsoid()
266                 .getEquatorialRadius() + 18, 0, 0);
267         Line line = new Line(closeMiss, closeMiss.add(Vector3D.PLUS_J), 0);
268 
269         // action
270         final GeodeticPoint actual = geoid.getIntersectionPoint(line,
271                 closeMiss, geoid.getBodyFrame(), date);
272 
273         // verify
274         assertThat(actual, nullValue());
275     }
276 
277     /**
278      * check altitude is referenced to the geoid. h<sub>ellipse</sub> =
279      * h<sub>geoid</sub> + N. Where N is the undulation of the geoid.
280      */
281     @Test
282     public void testTransformVector3DFrameAbsoluteDate()
283             {
284         // frame and date are the same
285         Frame frame = FramesFactory.getGCRF();
286         AbsoluteDate date = AbsoluteDate.CCSDS_EPOCH;
287 
288         Geoid geoid = getComponent();
289         // test point at 0,0,0
290         Vector3D point = new Vector3D(WGS84.getEquatorialRadius(), 0, 0);
291         double undulation = geoid.getUndulation(0, 0, date);
292 
293         // check ellipsoidal points and geoidal points differ by undulation
294         GeodeticPoint ellipsoidal = geoid.getEllipsoid().transform(
295                 point, frame, date);
296         GeodeticPoint geoidal = geoid.transform(point, frame, date);
297         assertThat(ellipsoidal.getAltitude() - geoidal.getAltitude(),
298                 is(undulation));
299 
300         // check it is the reverse of transform(GeodeticPoint)
301         point = new Vector3D(0.5, 0.4, 0.31).scalarMultiply(WGS84
302                 .getEquatorialRadius());
303         Vector3D expected = geoid
304                 .transform(geoid.transform(point, frame, date));
305         // allow 2 upls of error
306         assertThat(point, vectorCloseTo(expected, 2));
307 
308     }
309 
310     /**
311      * check that the altitude is referenced to the geoid (includes
312      * undulation).
313      */
314     @Test
315     public void testTransformGeodeticPoint() {
316         // geoid
317         Geoid geoid = getComponent();
318         // ellipsoid
319         ReferenceEllipsoid ellipsoid = geoid.getEllipsoid();
320         // point to test with orthometric height
321         GeodeticPoint orthometric = new GeodeticPoint(0, 75, 5);
322         // undulation at point
323         double undulation = geoid.getUndulation(orthometric.getLatitude(),
324                 orthometric.getLongitude(), date);
325         // same point with height referenced to ellipsoid
326         GeodeticPoint point = new GeodeticPoint(orthometric.getLatitude(),
327                 orthometric.getLongitude(), orthometric.getAltitude()
328                 + undulation);
329 
330         // test they are the same
331         Vector3D expected = ellipsoid.transform(point);
332         Vector3D actual = geoid.transform(orthometric);
333         assertThat(actual, is(expected));
334 
335         // test the point 0,0,0
336         expected = new Vector3D(WGS84.getEquatorialRadius()
337                 + geoid.getUndulation(0, 0, date), 0, 0);
338         actual = geoid.transform(new GeodeticPoint(0, 0, 0));
339         assertThat(actual, vectorCloseTo(expected, 0));
340     }
341 
342     /** check {@link Geoid#getEllipsoid()} */
343     @Test
344     public void testGetEllipsoid() {
345         //setup
346         Geoid geoid = new Geoid(potential, WGS84);
347 
348         //action + verify
349         assertThat(geoid.getEllipsoid(), sameInstance(WGS84));
350     }
351 
352     /**
353      * check {@link Geoid#projectToGround(Vector3D, AbsoluteDate, Frame)}
354      */
355     @Test
356     public void testProjectToGround() {
357         //setup
358         Vector3D p = new Vector3D(7e8, 1e3, 200);
359         Geoid geoid = new Geoid(potential, WGS84);
360 
361         //action
362         Vector3D actual = geoid.projectToGround(p, date, FramesFactory.getGCRF());
363 
364         //verify
365         assertThat(
366                 geoid.transform(actual, geoid.getBodyFrame(), date).getAltitude(),
367                 closeTo(0.0, 1.1e-9));
368     }
369 
370 }