1   /* Copyright 2002-2016 CS Systèmes d'Information
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.bodies;
18  
19  import java.io.Serializable;
20  
21  import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
22  import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
23  import org.apache.commons.math3.geometry.euclidean.threed.FieldVector3D;
24  import org.apache.commons.math3.geometry.euclidean.threed.Line;
25  import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
26  import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
27  import org.apache.commons.math3.util.FastMath;
28  import org.apache.commons.math3.util.MathArrays;
29  import org.orekit.errors.OrekitException;
30  import org.orekit.frames.Frame;
31  import org.orekit.frames.Transform;
32  import org.orekit.time.AbsoluteDate;
33  import org.orekit.utils.PVCoordinates;
34  import org.orekit.utils.TimeStampedPVCoordinates;
35  
36  
37  /** Modeling of a one-axis ellipsoid.
38  
39   * <p>One-axis ellipsoids is a good approximate model for most planet-size
40   * and larger natural bodies. It is the equilibrium shape reached by
41   * a fluid body under its own gravity field when it rotates. The symmetry
42   * axis is the rotation or polar axis.</p>
43  
44   * @author Luc Maisonobe
45   */
46  public class OneAxisEllipsoid extends Ellipsoid implements BodyShape {
47  
48      /** Serializable UID. */
49      private static final long serialVersionUID = 20130518L;
50  
51      /** Body frame related to body shape. */
52      private final Frame bodyFrame;
53  
54      /** Equatorial radius power 2. */
55      private final double ae2;
56  
57      /** Flattening. */
58      private final double f;
59  
60      /** Eccentricity power 2. */
61      private final double e2;
62  
63      /** 1 minus flatness. */
64      private final double g;
65  
66      /** g * g. */
67      private final double g2;
68  
69      /** Convergence limit. */
70      private double angularThreshold;
71  
72      /** Simple constructor.
73       * <p>Standard values for Earth models can be found in the {@link org.orekit.utils.Constants Constants} class:</p>
74       * <table border="1" cellpadding="5">
75       * <tr bgcolor="#ccccff"><th>model</th><th>a<sub>e</sub> (m)</th> <th>f</th></tr>
76       * <tr><td bgcolor="#eeeeff">GRS 80</td>
77       *     <td>{@link org.orekit.utils.Constants#GRS80_EARTH_EQUATORIAL_RADIUS Constants.GRS80_EARTH_EQUATORIAL_RADIUS}</td>
78       *     <td>{@link org.orekit.utils.Constants#GRS80_EARTH_FLATTENING Constants.GRS80_EARTH_FLATTENING}</td></tr>
79       * <tr><td bgcolor="#eeeeff">WGS84</td>
80       *     <td>{@link org.orekit.utils.Constants#WGS84_EARTH_EQUATORIAL_RADIUS Constants.WGS84_EARTH_EQUATORIAL_RADIUS}</td>
81       *     <td>{@link org.orekit.utils.Constants#WGS84_EARTH_FLATTENING Constants.WGS84_EARTH_FLATTENING}</td></tr>
82       * </table>
83       * @param ae equatorial radius
84       * @param f the flattening (f = (a-b)/a)
85       * @param bodyFrame body frame related to body shape
86       * @see org.orekit.frames.FramesFactory#getITRF(org.orekit.utils.IERSConventions, boolean)
87       */
88      public OneAxisEllipsoid(final double ae, final double f,
89                              final Frame bodyFrame) {
90          super(bodyFrame, ae, ae, ae * (1.0 - f));
91          this.f = f;
92          this.ae2 = ae * ae;
93          this.e2 = f * (2.0 - f);
94          this.g = 1.0 - f;
95          this.g2 = g * g;
96          setAngularThreshold(1.0e-12);
97          this.bodyFrame = bodyFrame;
98      }
99  
100     /** Set the angular convergence threshold.
101      * <p>The angular threshold is used both to identify points close to
102      * the ellipse axes and as the convergence threshold used to
103      * stop the iterations in the {@link #transform(Vector3D, Frame,
104      * AbsoluteDate)} method.</p>
105      * <p>If this method is not called, the default value is set to
106      * 10<sup>-12</sup>.</p>
107      * @param angularThreshold angular convergence threshold (rad)
108      */
109     public void setAngularThreshold(final double angularThreshold) {
110         this.angularThreshold = angularThreshold;
111     }
112 
113     /** Get the equatorial radius of the body.
114      * @return equatorial radius of the body (m)
115      */
116     public double getEquatorialRadius() {
117         return getA();
118     }
119 
120     /** Get the flattening of the body: f = (a-b)/a.
121      * @return the flattening
122      */
123     public double getFlattening() {
124         return f;
125     }
126 
127     /** {@inheritDoc} */
128     public Frame getBodyFrame() {
129         return bodyFrame;
130     }
131 
132     /** {@inheritDoc} */
133     public GeodeticPoint getIntersectionPoint(final Line line, final Vector3D close,
134                                               final Frame frame, final AbsoluteDate date)
135         throws OrekitException {
136 
137         // transform line and close to body frame
138         final Transform frameToBodyFrame = frame.getTransformTo(bodyFrame, date);
139         final Line lineInBodyFrame = frameToBodyFrame.transformLine(line);
140         final Vector3D closeInBodyFrame = frameToBodyFrame.transformPosition(close);
141         final double closeAbscissa = lineInBodyFrame.toSubSpace(closeInBodyFrame).getX();
142 
143         // compute some miscellaneous variables outside of the loop
144         final Vector3D point    = lineInBodyFrame.getOrigin();
145         final double x          = point.getX();
146         final double y          = point.getY();
147         final double z          = point.getZ();
148         final double z2         = z * z;
149         final double r2         = x * x + y * y;
150 
151         final Vector3D direction = lineInBodyFrame.getDirection();
152         final double dx         = direction.getX();
153         final double dy         = direction.getY();
154         final double dz         = direction.getZ();
155         final double cz2        = dx * dx + dy * dy;
156 
157         // abscissa of the intersection as a root of a 2nd degree polynomial :
158         // a k^2 - 2 b k + c = 0
159         final double a  = 1.0 - e2 * cz2;
160         final double b  = -(g2 * (x * dx + y * dy) + z * dz);
161         final double c  = g2 * (r2 - ae2) + z2;
162         final double b2 = b * b;
163         final double ac = a * c;
164         if (b2 < ac) {
165             return null;
166         }
167         final double s  = FastMath.sqrt(b2 - ac);
168         final double k1 = (b < 0) ? (b - s) / a : c / (b + s);
169         final double k2 = c / (a * k1);
170 
171         // select the right point
172         final double k =
173             (FastMath.abs(k1 - closeAbscissa) < FastMath.abs(k2 - closeAbscissa)) ? k1 : k2;
174         final Vector3D intersection = lineInBodyFrame.toSpace(new Vector1D(k));
175         final double ix = intersection.getX();
176         final double iy = intersection.getY();
177         final double iz = intersection.getZ();
178 
179         final double lambda = FastMath.atan2(iy, ix);
180         final double phi    = FastMath.atan2(iz, g2 * FastMath.sqrt(ix * ix + iy * iy));
181         return new GeodeticPoint(phi, lambda, 0.0);
182 
183     }
184 
185     /** {@inheritDoc} */
186     public Vector3D transform(final GeodeticPoint point) {
187         final double longitude = point.getLongitude();
188         final double cLambda   = FastMath.cos(longitude);
189         final double sLambda   = FastMath.sin(longitude);
190         final double latitude  = point.getLatitude();
191         final double cPhi      = FastMath.cos(latitude);
192         final double sPhi      = FastMath.sin(latitude);
193         final double h         = point.getAltitude();
194         final double n         = getA() / FastMath.sqrt(1.0 - e2 * sPhi * sPhi);
195         final double r         = (n + h) * cPhi;
196         return new Vector3D(r * cLambda, r * sLambda, (g2 * n + h) * sPhi);
197     }
198 
199     /** Transform a surface-relative point to a Cartesian point.
200      * @param point surface-relative point, using time as the single derivation parameter
201      * @return point at the same location but as a Cartesian point including derivatives
202      */
203     public PVCoordinates transform(final FieldGeodeticPoint<DerivativeStructure> point) {
204 
205         final DerivativeStructure latitude  = point.getLatitude();
206         final DerivativeStructure longitude = point.getLongitude();
207         final DerivativeStructure altitude  = point.getAltitude();
208 
209         final DerivativeStructure cLambda = longitude.cos();
210         final DerivativeStructure sLambda = longitude.sin();
211         final DerivativeStructure cPhi    = latitude.cos();
212         final DerivativeStructure sPhi    = latitude.sin();
213         final DerivativeStructure n       = sPhi.multiply(sPhi).multiply(e2).subtract(1.0).negate().sqrt().reciprocal().multiply(getA());
214         final DerivativeStructure r       = n.add(altitude).multiply(cPhi);
215 
216         return new PVCoordinates(new FieldVector3D<DerivativeStructure>(r.multiply(cLambda),
217                                                                         r.multiply(sLambda),
218                                                                         sPhi.multiply(altitude.add(n.multiply(g2)))));
219     }
220 
221     /** {@inheritDoc} */
222     public Vector3D projectToGround(final Vector3D point, final AbsoluteDate date, final Frame frame)
223         throws OrekitException {
224 
225         // transform point to body frame
226         final Transform  toBody    = frame.getTransformTo(bodyFrame, date);
227         final Vector3D   p         = toBody.transformPosition(point);
228         final double     z         = p.getZ();
229         final double     r         = FastMath.hypot(p.getX(), p.getY());
230 
231         // set up the 2D meridian ellipse
232         final Ellipse meridian = new Ellipse(Vector3D.ZERO,
233                                              new Vector3D(p.getX() / r, p.getY() / r, 0),
234                                              Vector3D.PLUS_K,
235                                              getA(), getC(), bodyFrame);
236 
237         // find the closest point in the meridian plane
238         final Vector3D groundPoint = meridian.toSpace(meridian.projectToEllipse(new Vector2D(r, z)));
239 
240         // transform point back to initial frame
241         return toBody.getInverse().transformPosition(groundPoint);
242 
243     }
244 
245     /** {@inheritDoc} */
246     public TimeStampedPVCoordinates projectToGround(final TimeStampedPVCoordinates pv, final Frame frame)
247         throws OrekitException {
248 
249         // transform point to body frame
250         final Transform                toBody        = frame.getTransformTo(bodyFrame, pv.getDate());
251         final TimeStampedPVCoordinates pvInBodyFrame = toBody.transformPVCoordinates(pv);
252         final Vector3D                 p             = pvInBodyFrame.getPosition();
253         final double                   r             = FastMath.hypot(p.getX(), p.getY());
254 
255         // set up the 2D ellipse corresponding to first principal curvature along meridian
256         final Vector3D meridian = new Vector3D(p.getX() / r, p.getY() / r, 0);
257         final Ellipse firstPrincipalCurvature =
258                 new Ellipse(Vector3D.ZERO, meridian, Vector3D.PLUS_K, getA(), getC(), bodyFrame);
259 
260         // project coordinates in the meridian plane
261         final TimeStampedPVCoordinates gpFirst = firstPrincipalCurvature.projectToEllipse(pvInBodyFrame);
262         final Vector3D                 gpP     = gpFirst.getPosition();
263         final double                   gr      = MathArrays.linearCombination(gpP.getX(), meridian.getX(),
264                                                                               gpP.getY(), meridian.getY());
265         final double                   gz      = gpP.getZ();
266 
267         // topocentric frame
268         final Vector3D east   = new Vector3D(-meridian.getY(), meridian.getX(), 0);
269         final Vector3D zenith = new Vector3D(gr * getC() / getA(), meridian, gz * getA() / getC(), Vector3D.PLUS_K).normalize();
270         final Vector3D north  = Vector3D.crossProduct(zenith, east);
271 
272         // set up the ellipse corresponding to second principal curvature in the zenith/east plane
273         final Ellipse secondPrincipalCurvature  = getPlaneSection(gpP, north);
274         final TimeStampedPVCoordinates gpSecond = secondPrincipalCurvature.projectToEllipse(pvInBodyFrame);
275 
276         final Vector3D gpV = gpFirst.getVelocity().add(gpSecond.getVelocity());
277         final Vector3D gpA = gpFirst.getAcceleration().add(gpSecond.getAcceleration());
278 
279         // moving projected point
280         final TimeStampedPVCoordinates groundPV =
281                 new TimeStampedPVCoordinates(pv.getDate(), gpP, gpV, gpA);
282 
283         // transform moving projected point back to initial frame
284         return toBody.getInverse().transformPVCoordinates(groundPV);
285 
286     }
287 
288     /** {@inheritDoc} */
289     public GeodeticPoint transform(final Vector3D point, final Frame frame, final AbsoluteDate date)
290         throws OrekitException {
291 
292         // transform point to body frame
293         final Vector3D pointInBodyFrame = frame.getTransformTo(bodyFrame, date).transformPosition(point);
294         final double   r2               = pointInBodyFrame.getX() * pointInBodyFrame.getX() +
295                                           pointInBodyFrame.getY() * pointInBodyFrame.getY();
296         final double   r                = FastMath.sqrt(r2);
297         final double   z                = pointInBodyFrame.getZ();
298 
299         // set up the 2D meridian ellipse
300         final Ellipse meridian = new Ellipse(Vector3D.ZERO,
301                                              new Vector3D(pointInBodyFrame.getX() / r, pointInBodyFrame.getY() / r, 0),
302                                              Vector3D.PLUS_K,
303                                              getA(), getC(), bodyFrame);
304 
305         // project point on the 2D meridian ellipse
306         final Vector2D ellipsePoint = meridian.projectToEllipse(new Vector2D(r, z));
307 
308         // relative position of test point with respect to its ellipse sub-point
309         final double dr = r - ellipsePoint.getX();
310         final double dz = z - ellipsePoint.getY();
311         final double insideIfNegative = g2 * (r2 - ae2) + z * z;
312 
313         return new GeodeticPoint(FastMath.atan2(ellipsePoint.getY(), g2 * ellipsePoint.getX()),
314                                  FastMath.atan2(pointInBodyFrame.getY(), pointInBodyFrame.getX()),
315                                  FastMath.copySign(FastMath.hypot(dr, dz), insideIfNegative));
316 
317     }
318 
319     /** Transform a Cartesian point to a surface-relative point.
320      * @param point Cartesian point
321      * @param frame frame in which Cartesian point is expressed
322      * @param date date of the computation (used for frames conversions)
323      * @return point at the same location but as a surface-relative point,
324      * using time as the single derivation parameter
325      * @exception OrekitException if point cannot be converted to body frame
326      */
327     public FieldGeodeticPoint<DerivativeStructure> transform(final PVCoordinates point,
328                                                              final Frame frame, final AbsoluteDate date)
329         throws OrekitException {
330 
331         // transform point to body frame
332         final Transform toBody = frame.getTransformTo(bodyFrame, date);
333         final PVCoordinates pointInBodyFrame = toBody.transformPVCoordinates(point);
334         final FieldVector3D<DerivativeStructure> p = pointInBodyFrame.toDerivativeStructureVector(2);
335         final DerivativeStructure   pr2 = p.getX().multiply(p.getX()).add(p.getY().multiply(p.getY()));
336         final DerivativeStructure   pr  = pr2.sqrt();
337         final DerivativeStructure   pz  = p.getZ();
338 
339         // project point on the ellipsoid surface
340         final TimeStampedPVCoordinates groundPoint = projectToGround(new TimeStampedPVCoordinates(date, pointInBodyFrame),
341                                                                      bodyFrame);
342         final FieldVector3D<DerivativeStructure> gp = groundPoint.toDerivativeStructureVector(2);
343         final DerivativeStructure   gpr2 = gp.getX().multiply(gp.getX()).add(gp.getY().multiply(gp.getY()));
344         final DerivativeStructure   gpr  = gpr2.sqrt();
345         final DerivativeStructure   gpz  = gp.getZ();
346 
347         // relative position of test point with respect to its ellipse sub-point
348         final DerivativeStructure dr  = pr.subtract(gpr);
349         final DerivativeStructure dz  = pz.subtract(gpz);
350         final double insideIfNegative = g2 * (pr2.getReal() - ae2) + pz.getReal() * pz.getReal();
351 
352         return new FieldGeodeticPoint<DerivativeStructure>(DerivativeStructure.atan2(gpz, gpr.multiply(g2)),
353                                                            DerivativeStructure.atan2(p.getY(), p.getX()),
354                                                            DerivativeStructure.hypot(dr, dz).copySign(insideIfNegative));
355     }
356 
357     /** Replace the instance with a data transfer object for serialization.
358      * <p>
359      * This intermediate class serializes the files supported names, the
360      * ephemeris type and the body name.
361      * </p>
362      * @return data transfer object that will be serialized
363      */
364     private Object writeReplace() {
365         return new DataTransferObject(getA(), f, bodyFrame, angularThreshold);
366     }
367 
368     /** Internal class used only for serialization. */
369     private static class DataTransferObject implements Serializable {
370 
371         /** Serializable UID. */
372         private static final long serialVersionUID = 20130518L;
373 
374         /** Equatorial radius. */
375         private final double ae;
376 
377         /** Flattening. */
378         private final double f;
379 
380         /** Body frame related to body shape. */
381         private final Frame bodyFrame;
382 
383         /** Convergence limit. */
384         private final double angularThreshold;
385 
386         /** Simple constructor.
387          * @param ae equatorial radius
388          * @param f the flattening (f = (a-b)/a)
389          * @param bodyFrame body frame related to body shape
390          * @param angularThreshold convergence limit
391          */
392         DataTransferObject(final double ae, final double f,
393                                   final Frame bodyFrame, final double angularThreshold) {
394             this.ae               = ae;
395             this.f                = f;
396             this.bodyFrame        = bodyFrame;
397             this.angularThreshold = angularThreshold;
398         }
399 
400         /** Replace the deserialized data transfer object with a
401          * {@link JPLCelestialBody}.
402          * @return replacement {@link JPLCelestialBody}
403          */
404         private Object readResolve() {
405             final OneAxisEllipsoid ellipsoid = new OneAxisEllipsoid(ae, f, bodyFrame);
406             ellipsoid.setAngularThreshold(angularThreshold);
407             return ellipsoid;
408         }
409 
410     }
411 
412 }