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