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.Field;
20 import org.hipparchus.CalculusFieldElement;
21 import org.hipparchus.analysis.CalculusFieldUnivariateFunction;
22 import org.hipparchus.analysis.UnivariateFunction;
23 import org.hipparchus.analysis.solvers.AllowedSolution;
24 import org.hipparchus.analysis.solvers.BracketingNthOrderBrentSolver;
25 import org.hipparchus.analysis.solvers.FieldBracketingNthOrderBrentSolver;
26 import org.hipparchus.analysis.solvers.UnivariateSolver;
27 import org.hipparchus.exception.MathRuntimeException;
28 import org.hipparchus.geometry.euclidean.threed.FieldLine;
29 import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
30 import org.hipparchus.geometry.euclidean.threed.Line;
31 import org.hipparchus.geometry.euclidean.threed.Vector3D;
32 import org.hipparchus.util.FastMath;
33 import org.orekit.annotation.DefaultDataContext;
34 import org.orekit.bodies.FieldGeodeticPoint;
35 import org.orekit.bodies.GeodeticPoint;
36 import org.orekit.errors.OrekitException;
37 import org.orekit.forces.gravity.HolmesFeatherstoneAttractionModel;
38 import org.orekit.forces.gravity.potential.GravityFields;
39 import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
40 import org.orekit.forces.gravity.potential.TideSystem;
41 import org.orekit.frames.FieldTransform;
42 import org.orekit.frames.Frame;
43 import org.orekit.frames.Transform;
44 import org.orekit.time.AbsoluteDate;
45 import org.orekit.time.FieldAbsoluteDate;
46 import org.orekit.utils.TimeStampedPVCoordinates;
47
48 /**
49 * A geoid is a level surface of the gravity potential of a body. The gravity
50 * potential, W, is split so W = U + T, where U is the normal potential (defined
51 * by the ellipsoid) and T is the anomalous potential.[3](eq. 2-137)
52 *
53 * <p> The {@link #getIntersectionPoint(Line, Vector3D, Frame, AbsoluteDate)}
54 * method is tailored specifically for Earth's geoid. All of the other methods
55 * in this class are general and will work for an arbitrary body.
56 *
57 * <p> There are several components that are needed to define a geoid[1]:
58 *
59 * <ul> <li>Geopotential field. These are the coefficients of the spherical
60 * harmonics: S<sub>n,m</sub> and C<sub>n,m</sub></li>
61 *
62 * <li>Reference Ellipsoid. The ellipsoid is used to define the undulation of
63 * the geoid (distance between ellipsoid and geoid) and U<sub>0</sub> the value
64 * of the normal gravity potential at the surface of the ellipsoid.</li>
65 *
66 * <li>W<sub>0</sub>, the potential at the geoid. The value of the potential on
67 * the level surface. This is taken to be U<sub>0</sub>, the normal gravity
68 * potential at the surface of the {@link ReferenceEllipsoid}.</li>
69 *
70 * <li>Permanent Tide System. This implementation assumes that the geopotential
71 * field and the reference ellipsoid use the same permanent tide system. If the
72 * assumption is false it will produce errors of about 0.5 m. Conversion between
73 * tide systems is a possible improvement.[1,2]</li>
74 *
75 * <li>Topographic Masses. That is mass outside of the geoid, e.g. mountains.
76 * This implementation ignores topographic masses, which causes up to 3m error
77 * in the Himalayas, and ~ 1.5m error in the Rockies. This could be improved
78 * through the use of DTED and calculating height anomalies or using the
79 * correction coefficients.[1]</li> </ul>
80 *
81 * <p> This implementation also assumes that the normal to the reference
82 * ellipsoid is the same as the normal to the geoid. This assumption enables the
83 * equation: (height above geoid) = (height above ellipsoid) - (undulation),
84 * which is used in {@link #transform(GeodeticPoint)} and {@link
85 * #transform(Vector3D, Frame, AbsoluteDate)}.
86 *
87 * <p> In testing, the error in the undulations calculated by this class were
88 * off by less than 3 meters, which matches the assumptions outlined above.
89 *
90 * <p> References:
91 *
92 * <ol> <li>Dru A. Smith. There is no such thing as "The" EGM96 geoid: Subtle
93 * points on the use of a global geopotential model. IGeS Bulletin No. 8:17-28,
94 * 1998. <a href= "http://www.ngs.noaa.gov/PUBS_LIB/EGM96_GEOID_PAPER/egm96_geoid_paper.html"
95 * >http://www.ngs.noaa.gov/PUBS_LIB/EGM96_GEOID_PAPER/egm96_geoid_paper.html</a></li>
96 *
97 * <li> Martin Losch, Verena Seufer. How to Compute Geoid Undulations (Geoid
98 * Height Relative to a Given Reference Ellipsoid) from Spherical Harmonic
99 * Coefficients for Satellite Altimetry Applications. , 2003. <a
100 * href="http://mitgcm.org/~mlosch/geoidcookbook.pdf">mitgcm.org/~mlosch/geoidcookbook.pdf</a>
101 * </li>
102 *
103 * <li>Weikko A. Heiskanen, Helmut Moritz. Physical Geodesy. W. H. Freeman and
104 * Company, 1967. (especially sections 2.13 and equation 2-144 Bruns
105 * Formula)</li>
106 *
107 * <li>S. A. Holmes, W. E. Featherstone. A unified approach to the Clenshaw
108 * summation and the recursive computation of very high degree and order
109 * normalised associated Legendre functions. Journal of Geodesy, 76(5):279,
110 * 2002.</li>
111 *
112 * <li>DMA TR 8350.2. 1984.</li>
113 *
114 * <li>Department of Defense World Geodetic System 1984. 2000. NIMA TR 8350.2
115 * Third Edition, Amendment 1.</li> </ol>
116 *
117 * @author Evan Ward
118 */
119 public class Geoid implements EarthShape {
120
121 /**
122 * uid is date of last modification.
123 */
124 private static final long serialVersionUID = 20150312L;
125
126 /**
127 * A number larger than the largest undulation. Wikipedia says the geoid
128 * height is in [-106, 85]. I chose 100 to be safe.
129 */
130 private static final double MAX_UNDULATION = 100;
131 /**
132 * A number smaller than the smallest undulation. Wikipedia says the geoid
133 * height is in [-106, 85]. I chose -150 to be safe.
134 */
135 private static final double MIN_UNDULATION = -150;
136 /**
137 * the maximum number of evaluations for the line search in {@link
138 * #getIntersectionPoint(Line, Vector3D, Frame, AbsoluteDate)}.
139 */
140 private static final int MAX_EVALUATIONS = 100;
141
142 /**
143 * the default date to use when evaluating the {@link #harmonics}. Used when
144 * no other dates are available. Should be removed in a future release.
145 */
146 private final AbsoluteDate defaultDate;
147 /**
148 * the reference ellipsoid.
149 */
150 private final ReferenceEllipsoid referenceEllipsoid;
151 /**
152 * the geo-potential combined with an algorithm for evaluating the spherical
153 * harmonics. The Holmes and Featherstone method is very robust.
154 */
155 private final transient HolmesFeatherstoneAttractionModel harmonics;
156
157 /**
158 * Creates a geoid from the given geopotential, reference ellipsoid and the
159 * assumptions in the comment for {@link Geoid}.
160 *
161 * @param geopotential the gravity potential. Only the anomalous
162 * potential will be used. It is assumed that the
163 * {@code geopotential} and the {@code
164 * referenceEllipsoid} are defined in the same
165 * frame. Usually a {@link GravityFields#getConstantNormalizedProvider(int,
166 * int) constant geopotential} is used to define a
167 * time-invariant Geoid.
168 * @param referenceEllipsoid the normal gravity potential.
169 * @throws NullPointerException if {@code geopotential == null ||
170 * referenceEllipsoid == null}
171 */
172 @DefaultDataContext
173 public Geoid(final NormalizedSphericalHarmonicsProvider geopotential,
174 final ReferenceEllipsoid referenceEllipsoid) {
175 // parameter check
176 if (geopotential == null || referenceEllipsoid == null) {
177 throw new NullPointerException();
178 }
179
180 // subtract the ellipsoid from the geopotential
181 final SubtractEllipsoid potential = new SubtractEllipsoid(geopotential,
182 referenceEllipsoid);
183
184 // set instance parameters
185 this.referenceEllipsoid = referenceEllipsoid;
186 this.harmonics = new HolmesFeatherstoneAttractionModel(
187 referenceEllipsoid.getBodyFrame(), potential);
188 this.defaultDate = AbsoluteDate.J2000_EPOCH;
189 }
190
191 @Override
192 public Frame getBodyFrame() {
193 // same as for reference ellipsoid.
194 return this.getEllipsoid().getBodyFrame();
195 }
196
197 /**
198 * Gets the Undulation of the Geoid, N at the given position. N is the
199 * distance between the {@link #getEllipsoid() reference ellipsoid} and the
200 * geoid. The latitude and longitude parameters are both defined with
201 * respect to the reference ellipsoid. For EGM96 and the WGS84 ellipsoid the
202 * undulation is between -107m and +86m.
203 *
204 * <p> NOTE: Restrictions are not put on the range of the arguments {@code
205 * geodeticLatitude} and {@code longitude}.
206 *
207 * @param geodeticLatitude geodetic latitude (angle between the local normal
208 * and the equatorial plane on the reference
209 * ellipsoid), in radians.
210 * @param longitude on the reference ellipsoid, in radians.
211 * @param date of evaluation. Used for time varying geopotential
212 * fields.
213 * @return the undulation in m, positive means the geoid is higher than the
214 * ellipsoid.
215 * @see Geoid
216 * @see <a href="http://en.wikipedia.org/wiki/Geoid">Geoid on Wikipedia</a>
217 */
218 public double getUndulation(final double geodeticLatitude,
219 final double longitude,
220 final AbsoluteDate date) {
221 /*
222 * equations references are to the algorithm printed in the geoid
223 * cookbook[2]. See comment for Geoid.
224 */
225 // reference ellipsoid
226 final ReferenceEllipsoid ellipsoid = this.getEllipsoid();
227
228 // position in geodetic coordinates
229 final GeodeticPoint gp = new GeodeticPoint(geodeticLatitude, longitude, 0);
230 // position in Cartesian coordinates, is converted to geocentric lat and
231 // lon in the Holmes and Featherstone class
232 final Vector3D position = ellipsoid.transform(gp);
233
234 // get normal gravity from ellipsoid, eq 15
235 final double normalGravity = ellipsoid
236 .getNormalGravity(geodeticLatitude);
237
238 // calculate disturbing potential, T, eq 30.
239 final double mu = this.harmonics.getMu();
240 final double T = this.harmonics.nonCentralPart(date, position, mu);
241 // calculate undulation, eq 30
242 return T / normalGravity;
243 }
244
245 @Override
246 public ReferenceEllipsoid getEllipsoid() {
247 return this.referenceEllipsoid;
248 }
249
250 /**
251 * This class implements equations 20-24 in the geoid cook book.(Losch and
252 * Seufer) It modifies C<sub>2n,0</sub> where n = 1,2,...,5.
253 *
254 * @see "DMA TR 8350.2. 1984."
255 */
256 private static final class SubtractEllipsoid implements
257 NormalizedSphericalHarmonicsProvider {
258 /**
259 * provider of the fully normalized coefficients, includes the reference
260 * ellipsoid.
261 */
262 private final NormalizedSphericalHarmonicsProvider provider;
263 /**
264 * the reference ellipsoid to subtract from {@link #provider}.
265 */
266 private final ReferenceEllipsoid ellipsoid;
267
268 /**
269 * @param provider potential used for GM<sub>g</sub> and a<sub>g</sub>,
270 * and of course the coefficients Cnm, and Snm.
271 * @param ellipsoid Used to calculate the fully normalized
272 * J<sub>2n</sub>
273 */
274 private SubtractEllipsoid(
275 final NormalizedSphericalHarmonicsProvider provider,
276 final ReferenceEllipsoid ellipsoid) {
277 super();
278 this.provider = provider;
279 this.ellipsoid = ellipsoid;
280 }
281
282 @Override
283 public int getMaxDegree() {
284 return this.provider.getMaxDegree();
285 }
286
287 @Override
288 public int getMaxOrder() {
289 return this.provider.getMaxOrder();
290 }
291
292 @Override
293 public double getMu() {
294 return this.provider.getMu();
295 }
296
297 @Override
298 public double getAe() {
299 return this.provider.getAe();
300 }
301
302 @Override
303 public AbsoluteDate getReferenceDate() {
304 return this.provider.getReferenceDate();
305 }
306
307 @Deprecated
308 @Override
309 public double getOffset(final AbsoluteDate date) {
310 return this.provider.getOffset(date);
311 }
312
313 @Override
314 public NormalizedSphericalHarmonics onDate(final AbsoluteDate date) {
315 return new NormalizedSphericalHarmonics() {
316
317 /** the original harmonics */
318 private final NormalizedSphericalHarmonics delegate = provider.onDate(date);
319
320 @Override
321 public double getNormalizedCnm(final int n, final int m) {
322 return getCorrectedCnm(n, m, this.delegate.getNormalizedCnm(n, m));
323 }
324
325 @Override
326 public double getNormalizedSnm(final int n, final int m) {
327 return this.delegate.getNormalizedSnm(n, m);
328 }
329
330 @Override
331 public AbsoluteDate getDate() {
332 return date;
333 }
334 };
335 }
336
337 /**
338 * Get the corrected Cnm for different GM or a values.
339 *
340 * @param n degree
341 * @param m order
342 * @param uncorrectedCnm uncorrected Cnm coefficient
343 * @return the corrected Cnm coefficient.
344 */
345 private double getCorrectedCnm(final int n,
346 final int m,
347 final double uncorrectedCnm) {
348 double Cnm = uncorrectedCnm;
349 // n = 2,4,6,8, or 10 and m = 0
350 if (m == 0 && n <= 10 && n % 2 == 0 && n > 0) {
351 // correction factor for different GM and a, 1 if no difference
352 final double gmRatio = this.ellipsoid.getGM() / this.getMu();
353 final double aRatio = this.ellipsoid.getEquatorialRadius() /
354 this.getAe();
355 /*
356 * eq 20 in the geoid cook book[2], with eq 3-61 in chapter 3 of
357 * DMA TR 8350.2
358 */
359 // halfN = 1,2,3,4,5 for n = 2,4,6,8,10, respectively
360 final int halfN = n / 2;
361 Cnm = Cnm - gmRatio * FastMath.pow(aRatio, halfN) *
362 this.ellipsoid.getC2n0(halfN);
363 }
364 // return is a modified Cnm
365 return Cnm;
366 }
367
368 @Override
369 public TideSystem getTideSystem() {
370 return this.provider.getTideSystem();
371 }
372
373 }
374
375 /**
376 * {@inheritDoc}
377 *
378 * <p> The intersection point is computed using a line search along the
379 * specified line. This is accurate when the geoid is slowly varying.
380 */
381 @Override
382 public GeodeticPoint getIntersectionPoint(final Line lineInFrame,
383 final Vector3D closeInFrame,
384 final Frame frame,
385 final AbsoluteDate date) {
386 /*
387 * It is assumed that the geoid is slowly varying over it's entire
388 * surface. Therefore there will one local intersection.
389 */
390 // transform to body frame
391 final Frame bodyFrame = this.getBodyFrame();
392 final Transform frameToBody = frame.getTransformTo(bodyFrame, date);
393 final Vector3D close = frameToBody.transformPosition(closeInFrame);
394 final Line lineInBodyFrame = frameToBody.transformLine(lineInFrame);
395
396 // set the line's direction so the solved for value is always positive
397 final Line line;
398 if (lineInBodyFrame.getAbscissa(close) < 0) {
399 line = lineInBodyFrame.revert();
400 } else {
401 line = lineInBodyFrame;
402 }
403
404 final ReferenceEllipsoid ellipsoid = this.getEllipsoid();
405 // calculate end points
406 // distance from line to center of earth, squared
407 final double d2 = line.pointAt(0.0).getNormSq();
408 // the minimum abscissa, squared
409 final double n = ellipsoid.getPolarRadius() + MIN_UNDULATION;
410 final double minAbscissa2 = n * n - d2;
411 // smaller end point of the interval = 0.0 or intersection with
412 // min_undulation sphere
413 final double lowPoint = FastMath.sqrt(FastMath.max(minAbscissa2, 0.0));
414 // the maximum abscissa, squared
415 final double x = ellipsoid.getEquatorialRadius() + MAX_UNDULATION;
416 final double maxAbscissa2 = x * x - d2;
417 // larger end point of the interval
418 final double highPoint = FastMath.sqrt(maxAbscissa2);
419
420 // line search function
421 final UnivariateFunction heightFunction = new UnivariateFunction() {
422 @Override
423 public double value(final double x) {
424 try {
425 final GeodeticPoint geodetic =
426 transform(line.pointAt(x), bodyFrame, date);
427 return geodetic.getAltitude();
428 } catch (OrekitException e) {
429 // due to frame transform -> re-throw
430 throw new RuntimeException(e);
431 }
432 }
433 };
434
435 // compute answer
436 if (maxAbscissa2 < 0) {
437 // ray does not pierce bounding sphere -> no possible intersection
438 return null;
439 }
440 // solve line search problem to find the intersection
441 final UnivariateSolver solver = new BracketingNthOrderBrentSolver();
442 try {
443 final double abscissa = solver.solve(MAX_EVALUATIONS, heightFunction, lowPoint, highPoint);
444 // return intersection point
445 return this.transform(line.pointAt(abscissa), bodyFrame, date);
446 } catch (MathRuntimeException e) {
447 // no intersection
448 return null;
449 }
450 }
451
452 @Override
453 public Vector3D projectToGround(final Vector3D point,
454 final AbsoluteDate date,
455 final Frame frame) {
456 final GeodeticPoint gp = this.transform(point, frame, date);
457 final GeodeticPoint gpZero =
458 new GeodeticPoint(gp.getLatitude(), gp.getLongitude(), 0);
459 final Transform bodyToFrame = this.getBodyFrame().getTransformTo(frame, date);
460 return bodyToFrame.transformPosition(this.transform(gpZero));
461 }
462
463 /**
464 * {@inheritDoc}
465 *
466 * <p> The intersection point is computed using a line search along the
467 * specified line. This is accurate when the geoid is slowly varying.
468 */
469 @Override
470 public <T extends CalculusFieldElement<T>> FieldGeodeticPoint<T> getIntersectionPoint(final FieldLine<T> lineInFrame,
471 final FieldVector3D<T> closeInFrame,
472 final Frame frame,
473 final FieldAbsoluteDate<T> date) {
474
475 final Field<T> field = date.getField();
476 /*
477 * It is assumed that the geoid is slowly varying over it's entire
478 * surface. Therefore there will one local intersection.
479 */
480 // transform to body frame
481 final Frame bodyFrame = this.getBodyFrame();
482 final FieldTransform<T> frameToBody = frame.getTransformTo(bodyFrame, date);
483 final FieldVector3D<T> close = frameToBody.transformPosition(closeInFrame);
484 final FieldLine<T> lineInBodyFrame = frameToBody.transformLine(lineInFrame);
485
486 // set the line's direction so the solved for value is always positive
487 final FieldLine<T> line;
488 if (lineInBodyFrame.getAbscissa(close).getReal() < 0) {
489 line = lineInBodyFrame.revert();
490 } else {
491 line = lineInBodyFrame;
492 }
493
494 final ReferenceEllipsoid ellipsoid = this.getEllipsoid();
495 // calculate end points
496 // distance from line to center of earth, squared
497 final T d2 = line.pointAt(0.0).getNormSq();
498 // the minimum abscissa, squared
499 final double n = ellipsoid.getPolarRadius() + MIN_UNDULATION;
500 final T minAbscissa2 = d2.negate().add(n * n);
501 // smaller end point of the interval = 0.0 or intersection with
502 // min_undulation sphere
503 final T lowPoint = minAbscissa2.getReal() < 0 ? field.getZero() : minAbscissa2.sqrt();
504 // the maximum abscissa, squared
505 final double x = ellipsoid.getEquatorialRadius() + MAX_UNDULATION;
506 final T maxAbscissa2 = d2.negate().add(x * x);
507 // larger end point of the interval
508 final T highPoint = maxAbscissa2.sqrt();
509
510 // line search function
511 final CalculusFieldUnivariateFunction<T> heightFunction = z -> {
512 try {
513 final FieldGeodeticPoint<T> geodetic =
514 transform(line.pointAt(z), bodyFrame, date);
515 return geodetic.getAltitude();
516 } catch (OrekitException e) {
517 // due to frame transform -> re-throw
518 throw new RuntimeException(e);
519 }
520 };
521
522 // compute answer
523 if (maxAbscissa2.getReal() < 0) {
524 // ray does not pierce bounding sphere -> no possible intersection
525 return null;
526 }
527 // solve line search problem to find the intersection
528 final FieldBracketingNthOrderBrentSolver<T> solver =
529 new FieldBracketingNthOrderBrentSolver<>(field.getZero().add(1.0e-14),
530 field.getZero().add(1.0e-6),
531 field.getZero().add(1.0e-15),
532 5);
533 try {
534 final T abscissa = solver.solve(MAX_EVALUATIONS, heightFunction, lowPoint, highPoint,
535 AllowedSolution.ANY_SIDE);
536 // return intersection point
537 return this.transform(line.pointAt(abscissa), bodyFrame, date);
538 } catch (MathRuntimeException e) {
539 // no intersection
540 return null;
541 }
542 }
543
544 @Override
545 public TimeStampedPVCoordinates projectToGround(
546 final TimeStampedPVCoordinates pv,
547 final Frame frame) {
548 throw new UnsupportedOperationException();
549 }
550
551 /**
552 * {@inheritDoc}
553 *
554 * @param date date of the conversion. Used for computing frame
555 * transformations and for time dependent geopotential.
556 * @return The surface relative point at the same location. Altitude is
557 * orthometric height, that is height above the {@link Geoid}. Latitude and
558 * longitude are both geodetic and defined with respect to the {@link
559 * #getEllipsoid() reference ellipsoid}.
560 * @see #transform(GeodeticPoint)
561 * @see <a href="http://en.wikipedia.org/wiki/Orthometric_height">Orthometric_height</a>
562 */
563 @Override
564 public GeodeticPoint transform(final Vector3D point, final Frame frame,
565 final AbsoluteDate date) {
566 // convert using reference ellipsoid, altitude referenced to ellipsoid
567 final GeodeticPoint ellipsoidal = this.getEllipsoid().transform(
568 point, frame, date);
569 // convert altitude to orthometric using the undulation.
570 final double undulation = this.getUndulation(ellipsoidal.getLatitude(),
571 ellipsoidal.getLongitude(), date);
572 // add undulation to the altitude
573 return new GeodeticPoint(
574 ellipsoidal.getLatitude(),
575 ellipsoidal.getLongitude(),
576 ellipsoidal.getAltitude() - undulation
577 );
578 }
579
580 /**
581 * {@inheritDoc}
582 *
583 * @param date date of the conversion. Used for computing frame
584 * transformations and for time dependent geopotential.
585 * @return The surface relative point at the same location. Altitude is
586 * orthometric height, that is height above the {@link Geoid}. Latitude and
587 * longitude are both geodetic and defined with respect to the {@link
588 * #getEllipsoid() reference ellipsoid}.
589 * @see #transform(GeodeticPoint)
590 * @see <a href="http://en.wikipedia.org/wiki/Orthometric_height">Orthometric_height</a>
591 */
592 @Override
593 public <T extends CalculusFieldElement<T>> FieldGeodeticPoint<T> transform(final FieldVector3D<T> point, final Frame frame,
594 final FieldAbsoluteDate<T> date) {
595 // convert using reference ellipsoid, altitude referenced to ellipsoid
596 final FieldGeodeticPoint<T> ellipsoidal = this.getEllipsoid().transform(
597 point, frame, date);
598 // convert altitude to orthometric using the undulation.
599 final double undulation = this.getUndulation(ellipsoidal.getLatitude().getReal(),
600 ellipsoidal.getLongitude().getReal(),
601 date.toAbsoluteDate());
602 // add undulation to the altitude
603 return new FieldGeodeticPoint<>(
604 ellipsoidal.getLatitude(),
605 ellipsoidal.getLongitude(),
606 ellipsoidal.getAltitude().subtract(undulation)
607 );
608 }
609
610 /**
611 * {@inheritDoc}
612 *
613 * @param point The surface relative point to transform. Altitude is
614 * orthometric height, that is height above the {@link Geoid}.
615 * Latitude and longitude are both geodetic and defined with
616 * respect to the {@link #getEllipsoid() reference ellipsoid}.
617 * @return point at the same location but as a Cartesian point in the {@link
618 * #getBodyFrame() body frame}.
619 * @see #transform(Vector3D, Frame, AbsoluteDate)
620 */
621 @Override
622 public Vector3D transform(final GeodeticPoint point) {
623 try {
624 // convert orthometric height to height above ellipsoid using undulation
625 // TODO pass in date to allow user to specify
626 final double undulation = this.getUndulation(
627 point.getLatitude(),
628 point.getLongitude(),
629 this.defaultDate
630 );
631 final GeodeticPoint ellipsoidal = new GeodeticPoint(
632 point.getLatitude(),
633 point.getLongitude(),
634 point.getAltitude() + undulation
635 );
636 // transform using reference ellipsoid
637 return this.getEllipsoid().transform(ellipsoidal);
638 } catch (OrekitException e) {
639 //this method, as defined in BodyShape, is not permitted to throw
640 //an OrekitException, so wrap in an exception we can throw.
641 throw new RuntimeException(e);
642 }
643 }
644
645 /**
646 * {@inheritDoc}
647 *
648 * @param point The surface relative point to transform. Altitude is
649 * orthometric height, that is height above the {@link Geoid}.
650 * Latitude and longitude are both geodetic and defined with
651 * respect to the {@link #getEllipsoid() reference ellipsoid}.
652 * @param <T> type of the field elements
653 * @return point at the same location but as a Cartesian point in the {@link
654 * #getBodyFrame() body frame}.
655 * @see #transform(Vector3D, Frame, AbsoluteDate)
656 * @since 9.0
657 */
658 @Override
659 public <T extends CalculusFieldElement<T>> FieldVector3D<T> transform(final FieldGeodeticPoint<T> point) {
660 try {
661 // convert orthometric height to height above ellipsoid using undulation
662 // TODO pass in date to allow user to specify
663 final double undulation = this.getUndulation(
664 point.getLatitude().getReal(),
665 point.getLongitude().getReal(),
666 this.defaultDate
667 );
668 final FieldGeodeticPoint<T> ellipsoidal = new FieldGeodeticPoint<>(
669 point.getLatitude(),
670 point.getLongitude(),
671 point.getAltitude().add(undulation)
672 );
673 // transform using reference ellipsoid
674 return this.getEllipsoid().transform(ellipsoidal);
675 } catch (OrekitException e) {
676 //this method, as defined in BodyShape, is not permitted to throw
677 //an OrekitException, so wrap in an exception we can throw.
678 throw new RuntimeException(e);
679 }
680 }
681
682 }