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 static org.hamcrest.CoreMatchers.is;
20  import static org.hamcrest.CoreMatchers.nullValue;
21  import static org.hamcrest.CoreMatchers.sameInstance;
22  import static org.hamcrest.MatcherAssert.assertThat;
23  import static org.junit.Assert.assertEquals;
24  import static org.orekit.OrekitMatchers.closeTo;
25  import static org.orekit.OrekitMatchers.geodeticPointCloseTo;
26  import static org.orekit.OrekitMatchers.vectorCloseTo;
27  
28  import java.util.Arrays;
29  
30  import org.hipparchus.geometry.euclidean.threed.Line;
31  import org.hipparchus.geometry.euclidean.threed.Rotation;
32  import org.hipparchus.geometry.euclidean.threed.Vector3D;
33  import org.hipparchus.util.FastMath;
34  import org.junit.Assert;
35  import org.junit.Before;
36  import org.junit.BeforeClass;
37  import org.junit.Test;
38  import org.orekit.Utils;
39  import org.orekit.bodies.GeodeticPoint;
40  import org.orekit.forces.gravity.potential.EGMFormatReader;
41  import org.orekit.forces.gravity.potential.GravityFieldFactory;
42  import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
43  import org.orekit.frames.Frame;
44  import org.orekit.frames.FramesFactory;
45  import org.orekit.frames.Transform;
46  import org.orekit.time.AbsoluteDate;
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      @BeforeClass
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.getConstantNormalizedProvider(
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      @Before
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         assertEquals(WGS84, geoid.getEllipsoid());
126         // geoid and reference ellipse are in the same frame
127         assertEquals(WGS84.getBodyFrame(), geoid.getBodyFrame());
128     }
129 
130     /** throws on null */
131     @Test(expected = NullPointerException.class)
132     public void testGeoidNullPotential() {
133         new Geoid(null, WGS84);
134     }
135 
136     /** throws on null */
137     @Test(expected = NullPointerException.class)
138     public void testGeoidNullEllipsoid() {
139         new Geoid(potential, null);
140     }
141 
142     /**
143      * Test several pre-computed points from the Online Geoid Height Evaluation
144      * tool, which takes into account terrain.
145      *
146      * @see <a href="http://geographiclib.sourceforge.net/cgi-bin/GeoidEval">Online
147      * Geoid Height Evaluation</a>
148      * @see <a href="http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm96/egm96.html">Geoid
149      * height for WGS84 and EGM96</a>
150      */
151     @Test
152     public void testGetUndulation() {
153         /*
154          * allow 3 meter of error, which is what the approximations would
155          * suggest, see the comment for Geoid.
156          */
157         final double maxError = 3;
158 
159         // run the test
160         Geoid geoid = getComponent();
161         for (double[] row : reference) {
162             double lat = row[0];
163             double lon = row[1];
164             double undulation = geoid.getUndulation(FastMath.toRadians(lat),
165                     FastMath.toRadians(lon), date);
166             double expected = row[2];
167             // System.out.format("%10g %10g %10g %10g%n", lat, lon, expected,
168             // undulation - expected);
169             Assert.assertEquals(String.format("lat: %5g, lon: %5g", lat, lon),
170                     undulation, expected, maxError);
171         }
172     }
173 
174     /**
175      * check {@link Geoid#getIntersectionPoint(Line, Vector3D, Frame,
176      * AbsoluteDate)} with several points.
177      */
178     @Test
179     public void testGetIntersectionPoint() {
180         // setup
181         Geoid geoid = getComponent();
182         Frame frame = geoid.getBodyFrame();
183 
184         for (double[] point : reference) {
185             GeodeticPoint gp = new GeodeticPoint(FastMath.toRadians(point[0]),
186                     FastMath.toRadians(point[1]), 0);
187             Vector3D expected = geoid.transform(gp);
188             // glancing line: 10% vertical and 90% north (~6 deg elevation)
189             Vector3D slope = gp.getZenith().scalarMultiply(0.1)
190                     .add(gp.getNorth().scalarMultiply(0.9));
191             Vector3D close = expected.add(slope.scalarMultiply(100e3));
192             Vector3D pointOnLine = expected.add(slope);
193             Line line = new Line(close, pointOnLine, 0);
194             // line directed the other way
195             Line otherDirection = new Line(pointOnLine, close, 0);
196 
197             // action
198             GeodeticPoint actual = geoid.getIntersectionPoint(line, close,
199                     frame, date);
200             // other direction
201             GeodeticPoint actualReversed = geoid.getIntersectionPoint(
202                     otherDirection, close, frame, date);
203 
204             // verify
205             String message = String.format("point: %s%n",
206                     Arrays.toString(point));
207             // position accuracy on Earth's surface to 1.3 um.
208             assertThat(message, actualReversed, geodeticPointCloseTo(gp, 1.3e-6));
209             assertThat(message, actual, geodeticPointCloseTo(gp, 1.3e-6));
210         }
211     }
212 
213     /**
214      * check {@link Geoid#getIntersectionPoint(Line, Vector3D, Frame,
215      * AbsoluteDate)} handles frame transformations correctly
216      */
217     @Test
218     public void testGetIntersectionPointFrame() {
219         // setup
220         Geoid geoid = getComponent();
221         Frame frame = new Frame(
222                 geoid.getBodyFrame(),
223                 new Transform(
224                         date,
225                         new Transform(
226                                 date,
227                                 new Vector3D(-1, 2, -3),
228                                 new Vector3D(4, -5, 6)),
229                         new Transform(
230                                 date,
231                                 new Rotation(-7, 8, -9, 10, true),
232                                 new Vector3D(-11, 12, -13))),
233                 "test frame");
234         GeodeticPoint gp = new GeodeticPoint(FastMath.toRadians(46.8743190),
235                 FastMath.toRadians(102.4487290), 0);
236         Vector3D expected = geoid.transform(gp);
237         // glancing line: 10% vertical and 90% north (~6 deg elevation)
238         Vector3D slope = gp.getZenith().scalarMultiply(0.1)
239                 .add(gp.getNorth().scalarMultiply(0.9));
240         Vector3D close = expected.add(slope.scalarMultiply(100));
241         Line line = new Line(expected.add(slope), close, 0);
242         Transform xform = geoid.getBodyFrame().getTransformTo(frame, date);
243         // transform to test frame
244         close = xform.transformPosition(close);
245         line = xform.transformLine(line);
246 
247         // action
248         GeodeticPoint actual = geoid.getIntersectionPoint(line, close, frame,
249                 date);
250 
251         // verify, 1 um position accuracy at Earth's surface
252         assertThat(actual, geodeticPointCloseTo(gp, 1e-6));
253     }
254 
255     /**
256      * check {@link Geoid#getIntersectionPoint(Line, Vector3D, Frame,
257      * AbsoluteDate)} returns null when there is no intersection
258      */
259     @Test
260     public void testGetIntersectionPointNoIntersection() {
261         Geoid geoid = getComponent();
262         Vector3D closeMiss = new Vector3D(geoid.getEllipsoid()
263                 .getEquatorialRadius() + 18, 0, 0);
264         Line line = new Line(closeMiss, closeMiss.add(Vector3D.PLUS_J), 0);
265 
266         // action
267         final GeodeticPoint actual = geoid.getIntersectionPoint(line,
268                 closeMiss, geoid.getBodyFrame(), date);
269 
270         // verify
271         assertThat(actual, nullValue());
272     }
273 
274     /**
275      * check altitude is referenced to the geoid. h<sub>ellipse</sub> =
276      * h<sub>geoid</sub> + N. Where N is the undulation of the geoid.
277      */
278     @Test
279     public void testTransformVector3DFrameAbsoluteDate()
280             {
281         // frame and date are the same
282         Frame frame = FramesFactory.getGCRF();
283         AbsoluteDate date = AbsoluteDate.CCSDS_EPOCH;
284 
285         Geoid geoid = getComponent();
286         // test point at 0,0,0
287         Vector3D point = new Vector3D(WGS84.getEquatorialRadius(), 0, 0);
288         double undulation = geoid.getUndulation(0, 0, date);
289 
290         // check ellipsoidal points and geoidal points differ by undulation
291         GeodeticPoint ellipsoidal = geoid.getEllipsoid().transform(
292                 point, frame, date);
293         GeodeticPoint geoidal = geoid.transform(point, frame, date);
294         assertThat(ellipsoidal.getAltitude() - geoidal.getAltitude(),
295                 is(undulation));
296 
297         // check it is the reverse of transform(GeodeticPoint)
298         point = new Vector3D(0.5, 0.4, 0.31).scalarMultiply(WGS84
299                 .getEquatorialRadius());
300         Vector3D expected = geoid
301                 .transform(geoid.transform(point, frame, date));
302         // allow 2 upls of error
303         assertThat(point, vectorCloseTo(expected, 2));
304 
305     }
306 
307     /**
308      * check that the altitude is referenced to the geoid (includes
309      * undulation).
310      */
311     @Test
312     public void testTransformGeodeticPoint() {
313         // geoid
314         Geoid geoid = getComponent();
315         // ellipsoid
316         ReferenceEllipsoid ellipsoid = geoid.getEllipsoid();
317         // point to test with orthometric height
318         GeodeticPoint orthometric = new GeodeticPoint(0, 75, 5);
319         // undulation at point
320         double undulation = geoid.getUndulation(orthometric.getLatitude(),
321                 orthometric.getLongitude(), date);
322         // same point with height referenced to ellipsoid
323         GeodeticPoint point = new GeodeticPoint(orthometric.getLatitude(),
324                 orthometric.getLongitude(), orthometric.getAltitude()
325                 + undulation);
326 
327         // test they are the same
328         Vector3D expected = ellipsoid.transform(point);
329         Vector3D actual = geoid.transform(orthometric);
330         assertThat(actual, is(expected));
331 
332         // test the point 0,0,0
333         expected = new Vector3D(WGS84.getEquatorialRadius()
334                 + geoid.getUndulation(0, 0, date), 0, 0);
335         actual = geoid.transform(new GeodeticPoint(0, 0, 0));
336         assertThat(actual, vectorCloseTo(expected, 0));
337     }
338 
339     /** check {@link Geoid#getEllipsoid()} */
340     @Test
341     public void testGetEllipsoid() {
342         //setup
343         Geoid geoid = new Geoid(potential, WGS84);
344 
345         //action + verify
346         assertThat(geoid.getEllipsoid(), sameInstance(WGS84));
347     }
348 
349     /**
350      * check {@link Geoid#projectToGround(Vector3D, AbsoluteDate, Frame)}
351      */
352     @Test
353     public void testProjectToGround() {
354         //setup
355         Vector3D p = new Vector3D(7e8, 1e3, 200);
356         Geoid geoid = new Geoid(potential, WGS84);
357 
358         //action
359         Vector3D actual = geoid.projectToGround(p, date, FramesFactory.getGCRF());
360 
361         //verify
362         assertThat(
363                 geoid.transform(actual, geoid.getBodyFrame(), date).getAltitude(),
364                 closeTo(0.0, 1.1e-9));
365     }
366 
367 }