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