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