1 /* Copyright 2002-2021 CS GROUP
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.bodies;
18
19 import java.io.Serializable;
20
21 import org.hipparchus.CalculusFieldElement;
22 import org.hipparchus.geometry.euclidean.threed.FieldRotation;
23 import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
24 import org.hipparchus.geometry.euclidean.threed.Rotation;
25 import org.hipparchus.geometry.euclidean.threed.RotationConvention;
26 import org.hipparchus.geometry.euclidean.threed.Vector3D;
27 import org.hipparchus.util.Precision;
28 import org.orekit.annotation.DefaultDataContext;
29 import org.orekit.bodies.JPLEphemeridesLoader.EphemerisType;
30 import org.orekit.data.DataContext;
31 import org.orekit.errors.OrekitException;
32 import org.orekit.errors.OrekitInternalError;
33 import org.orekit.frames.FieldTransform;
34 import org.orekit.frames.Frame;
35 import org.orekit.frames.Transform;
36 import org.orekit.frames.TransformProvider;
37 import org.orekit.time.AbsoluteDate;
38 import org.orekit.time.FieldAbsoluteDate;
39 import org.orekit.utils.FieldPVCoordinates;
40 import org.orekit.utils.PVCoordinates;
41 import org.orekit.utils.TimeStampedFieldPVCoordinates;
42 import org.orekit.utils.TimeStampedPVCoordinates;
43
44 /** Implementation of the {@link CelestialBody} interface using JPL or INPOP ephemerides.
45 * @author Luc Maisonobe
46 */
47 class JPLCelestialBody implements CelestialBody {
48
49 /** Serializable UID. */
50 private static final long serialVersionUID = 3809787672779740923L;
51
52 /** Name of the body. */
53 private final String name;
54
55 /** Regular expression for supported files names. */
56 private final String supportedNames;
57
58 /** Ephemeris type to generate. */
59 private final JPLEphemeridesLoader.EphemerisType generateType;
60
61 /** Raw position-velocity provider. */
62 private final transient JPLEphemeridesLoader.RawPVProvider rawPVProvider;
63
64 /** Attraction coefficient of the body (m³/s²). */
65 private final double gm;
66
67 /** Scaling factor for position-velocity. */
68 private final double scale;
69
70 /** IAU pole. */
71 private final IAUPole iauPole;
72
73 /** Inertially oriented, body-centered frame. */
74 private final Frame inertialFrame;
75
76 /** Body oriented, body-centered frame. */
77 private final Frame bodyFrame;
78
79 /** Build an instance and the underlying frame.
80 * @param name name of the body
81 * @param supportedNames regular expression for supported files names
82 * @param generateType ephemeris type to generate
83 * @param rawPVProvider raw position-velocity provider
84 * @param gm attraction coefficient (in m³/s²)
85 * @param scale scaling factor for position-velocity
86 * @param iauPole IAU pole implementation
87 * @param definingFrameAlignedWithICRF frame in which celestial body coordinates are defined,
88 * this frame <strong>must</strong> be aligned with ICRF
89 * @param inertialFrameName name to use for inertial frame (if null a default name will be built)
90 * @param bodyOrientedFrameName name to use for body-oriented frame (if null a default name will be built)
91 */
92 JPLCelestialBody(final String name, final String supportedNames,
93 final JPLEphemeridesLoader.EphemerisType generateType,
94 final JPLEphemeridesLoader.RawPVProvider rawPVProvider,
95 final double gm, final double scale,
96 final IAUPole iauPole, final Frame definingFrameAlignedWithICRF,
97 final String inertialFrameName, final String bodyOrientedFrameName) {
98 this.name = name;
99 this.gm = gm;
100 this.scale = scale;
101 this.supportedNames = supportedNames;
102 this.generateType = generateType;
103 this.rawPVProvider = rawPVProvider;
104 this.iauPole = iauPole;
105 this.inertialFrame = new InertiallyOriented(definingFrameAlignedWithICRF, inertialFrameName);
106 this.bodyFrame = new BodyOriented(bodyOrientedFrameName);
107 }
108
109 /** {@inheritDoc} */
110 public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame) {
111
112 // apply the scale factor to raw position-velocity
113 final PVCoordinates rawPV = rawPVProvider.getRawPV(date);
114 final TimeStampedPVCoordinates scaledPV = new TimeStampedPVCoordinates(date, scale, rawPV);
115
116 // the raw PV are relative to the parent of the body centered inertially oriented frame
117 final Transform transform = getInertiallyOrientedFrame().getParent().getTransformTo(frame, date);
118
119 // convert to requested frame
120 return transform.transformPVCoordinates(scaledPV);
121
122 }
123
124 /** Get the {@link FieldPVCoordinates} of the body in the selected frame.
125 * @param date current date
126 * @param frame the frame where to define the position
127 * @param <T> type fo the field elements
128 * @return time-stamped position/velocity of the body (m and m/s)
129 */
130 public <T extends CalculusFieldElement<T>> TimeStampedFieldPVCoordinates<T> getPVCoordinates(final FieldAbsoluteDate<T> date,
131 final Frame frame) {
132
133 // apply the scale factor to raw position-velocity
134 final FieldPVCoordinates<T> rawPV = rawPVProvider.getRawPV(date);
135 final TimeStampedFieldPVCoordinates<T> scaledPV = new TimeStampedFieldPVCoordinates<>(date, scale, rawPV);
136
137 // the raw PV are relative to the parent of the body centered inertially oriented frame
138 final FieldTransform<T> transform = getInertiallyOrientedFrame().getParent().getTransformTo(frame, date);
139
140 // convert to requested frame
141 return transform.transformPVCoordinates(scaledPV);
142
143 }
144
145 /** Replace the instance with a data transfer object for serialization.
146 * <p>
147 * This intermediate class serializes the files supported names, the ephemeris type
148 * and the body name.
149 * </p>
150 * @return data transfer object that will be serialized
151 */
152 @DefaultDataContext
153 private Object writeReplace() {
154 return new DTOCelestialBody(supportedNames, generateType, name);
155 }
156
157 /** {@inheritDoc} */
158 public String getName() {
159 return name;
160 }
161
162 /** {@inheritDoc} */
163 public double getGM() {
164 return gm;
165 }
166
167 /** {@inheritDoc} */
168 public Frame getInertiallyOrientedFrame() {
169 return inertialFrame;
170 }
171
172 /** {@inheritDoc} */
173 public Frame getBodyOrientedFrame() {
174 return bodyFrame;
175 }
176
177 /** Inertially oriented body centered frame. */
178 private class InertiallyOriented extends Frame {
179
180 /** Serializable UID. */
181 private static final long serialVersionUID = -8849993808761896559L;
182
183 /** Suffix for inertial frame name. */
184 private static final String INERTIAL_FRAME_SUFFIX = "/inertial";
185
186 /** Simple constructor.
187 * @param definingFrame frame in which celestial body coordinates are defined
188 * @param frameName name to use (if null a default name will be built)
189 */
190 InertiallyOriented(final Frame definingFrame, final String frameName) {
191 super(definingFrame, new TransformProvider() {
192
193 /** Serializable UID. */
194 private static final long serialVersionUID = -8610328386110652400L;
195
196 /** {@inheritDoc} */
197 public Transform getTransform(final AbsoluteDate date) {
198
199 // compute translation from parent frame to self
200 final PVCoordinates pv = getPVCoordinates(date, definingFrame);
201 final Transform translation = new Transform(date, pv.negate());
202
203 // compute rotation from ICRF frame to self,
204 // as per the "Report of the IAU/IAG Working Group on Cartographic
205 // Coordinates and Rotational Elements of the Planets and Satellites"
206 // These definitions are common for all recent versions of this report
207 // published every three years, the precise values of pole direction
208 // and W angle coefficients may vary from publication year as models are
209 // adjusted. These coefficients are not in this class, they are in the
210 // specialized classes that do implement the getPole and getPrimeMeridianAngle
211 // methods
212 final Vector3D pole = iauPole.getPole(date);
213 final Vector3D qNode = iauPole.getNode(date);
214 final Transform rotation =
215 new Transform(date, new Rotation(pole, qNode, Vector3D.PLUS_K, Vector3D.PLUS_I));
216
217 // update transform from parent to self
218 return new Transform(date, translation, rotation);
219
220 }
221
222 /** {@inheritDoc} */
223 public <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date) {
224
225 // compute translation from parent frame to self
226 final FieldPVCoordinates<T> pv = getPVCoordinates(date, definingFrame);
227 final FieldTransform<T> translation = new FieldTransform<>(date, pv.negate());
228
229 // compute rotation from ICRF frame to self,
230 // as per the "Report of the IAU/IAG Working Group on Cartographic
231 // Coordinates and Rotational Elements of the Planets and Satellites"
232 // These definitions are common for all recent versions of this report
233 // published every three years, the precise values of pole direction
234 // and W angle coefficients may vary from publication year as models are
235 // adjusted. These coefficients are not in this class, they are in the
236 // specialized classes that do implement the getPole and getPrimeMeridianAngle
237 // methods
238 final FieldVector3D<T> pole = iauPole.getPole(date);
239 FieldVector3D<T> qNode = FieldVector3D.crossProduct(Vector3D.PLUS_K, pole);
240 if (qNode.getNormSq().getReal() < Precision.SAFE_MIN) {
241 qNode = FieldVector3D.getPlusI(date.getField());
242 }
243 final FieldTransform<T> rotation =
244 new FieldTransform<>(date,
245 new FieldRotation<>(pole,
246 qNode,
247 FieldVector3D.getPlusK(date.getField()),
248 FieldVector3D.getPlusI(date.getField())));
249
250 // update transform from parent to self
251 return new FieldTransform<>(date, translation, rotation);
252
253 }
254
255 }, frameName == null ? name + INERTIAL_FRAME_SUFFIX : frameName, true);
256 }
257
258 /** Replace the instance with a data transfer object for serialization.
259 * <p>
260 * This intermediate class serializes the files supported names, the ephemeris type
261 * and the body name.
262 * </p>
263 * @return data transfer object that will be serialized
264 */
265 @DefaultDataContext
266 private Object writeReplace() {
267 return new DTOInertialFrame(supportedNames, generateType, name);
268 }
269
270 }
271
272 /** Body oriented body centered frame. */
273 private class BodyOriented extends Frame {
274
275 /** Serializable UID. */
276 private static final long serialVersionUID = 20170109L;
277
278 /** Suffix for body frame name. */
279 private static final String BODY_FRAME_SUFFIX = "/rotating";
280
281 /** Simple constructor.
282 * @param frameName name to use (if null a default name will be built)
283 */
284 BodyOriented(final String frameName) {
285 super(inertialFrame, new TransformProvider() {
286
287 /** Serializable UID. */
288 private static final long serialVersionUID = 20170109L;
289
290 /** {@inheritDoc} */
291 public Transform getTransform(final AbsoluteDate date) {
292 final double dt = 10.0;
293 final double w0 = iauPole.getPrimeMeridianAngle(date);
294 final double w1 = iauPole.getPrimeMeridianAngle(date.shiftedBy(dt));
295 return new Transform(date,
296 new Rotation(Vector3D.PLUS_K, w0, RotationConvention.FRAME_TRANSFORM),
297 new Vector3D((w1 - w0) / dt, Vector3D.PLUS_K));
298 }
299
300 /** {@inheritDoc} */
301 public <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date) {
302 final double dt = 10.0;
303 final T w0 = iauPole.getPrimeMeridianAngle(date);
304 final T w1 = iauPole.getPrimeMeridianAngle(date.shiftedBy(dt));
305 return new FieldTransform<>(date,
306 new FieldRotation<>(FieldVector3D.getPlusK(date.getField()), w0,
307 RotationConvention.FRAME_TRANSFORM),
308 new FieldVector3D<>(w1.subtract(w0).divide(dt), Vector3D.PLUS_K));
309 }
310
311 }, frameName == null ? name + BODY_FRAME_SUFFIX : frameName, false);
312 }
313
314 /** Replace the instance with a data transfer object for serialization.
315 * <p>
316 * This intermediate class serializes the files supported names, the ephemeris type
317 * and the body name.
318 * </p>
319 * @return data transfer object that will be serialized
320 */
321 @DefaultDataContext
322 private Object writeReplace() {
323 return new DTOBodyFrame(supportedNames, generateType, name);
324 }
325
326 }
327
328 /** Internal class used only for serialization. */
329 @DefaultDataContext
330 private abstract static class DataTransferObject implements Serializable {
331
332 /** Serializable UID. */
333 private static final long serialVersionUID = 674742836536072422L;
334
335 /** Regular expression for supported files names. */
336 private final String supportedNames;
337
338 /** Ephemeris type to generate. */
339 private final EphemerisType generateType;
340
341 /** Name of the body. */
342 private final String name;
343
344 /** Simple constructor.
345 * @param supportedNames regular expression for supported files names
346 * @param generateType ephemeris type to generate
347 * @param name name of the body
348 */
349 DataTransferObject(final String supportedNames, final EphemerisType generateType, final String name) {
350 this.supportedNames = supportedNames;
351 this.generateType = generateType;
352 this.name = name;
353 }
354
355 /** Get the body associated with the serialized data.
356 * @return body associated with the serialized data
357 */
358 protected JPLCelestialBody getBody() {
359
360 try {
361 // first try to use the factory, in order to avoid building a new instance
362 // each time we deserialize and have the object properly cached
363 final CelestialBody factoryProvided =
364 DataContext.getDefault().getCelestialBodies().getBody(name);
365 if (factoryProvided instanceof JPLCelestialBody) {
366 final JPLCelestialBody jplBody = (JPLCelestialBody) factoryProvided;
367 if (supportedNames.equals(jplBody.supportedNames) && generateType == jplBody.generateType) {
368 // the factory created exactly the object we needed, just return it
369 return jplBody;
370 }
371 }
372
373 // the factory does not return the object we want
374 // we create a new one from scratch and don't cache it
375 return (JPLCelestialBody) new JPLEphemeridesLoader(supportedNames, generateType).loadCelestialBody(name);
376
377 } catch (OrekitException oe) {
378 throw new OrekitInternalError(oe);
379 }
380
381 }
382
383 }
384
385 /** Specialization of the data transfer object for complete celestial body serialization. */
386 @DefaultDataContext
387 private static class DTOCelestialBody extends DataTransferObject {
388
389 /** Serializable UID. */
390 private static final long serialVersionUID = -8287341529741045958L;
391
392 /** Simple constructor.
393 * @param supportedNames regular expression for supported files names
394 * @param generateType ephemeris type to generate
395 * @param name name of the body
396 */
397 DTOCelestialBody(final String supportedNames, final EphemerisType generateType, final String name) {
398 super(supportedNames, generateType, name);
399 }
400
401 /** Replace the deserialized data transfer object with a {@link JPLCelestialBody}.
402 * @return replacement {@link JPLCelestialBody}
403 */
404 private Object readResolve() {
405 return getBody();
406 }
407
408 }
409
410 /** Specialization of the data transfer object for inertially oriented frame serialization. */
411 @DefaultDataContext
412 private static class DTOInertialFrame extends DataTransferObject {
413
414 /** Serializable UID. */
415 private static final long serialVersionUID = 7915071664444154948L;
416
417 /** Simple constructor.
418 * @param supportedNames regular expression for supported files names
419 * @param generateType ephemeris type to generate
420 * @param name name of the body
421 */
422 DTOInertialFrame(final String supportedNames, final EphemerisType generateType, final String name) {
423 super(supportedNames, generateType, name);
424 }
425
426 /** Replace the deserialized data transfer object with a {@link Frame}.
427 * @return replacement {@link Frame}
428 */
429 private Object readResolve() {
430 return getBody().inertialFrame;
431 }
432
433 }
434
435 /** Specialization of the data transfer object for body oriented frame serialization. */
436 @DefaultDataContext
437 private static class DTOBodyFrame extends DataTransferObject {
438
439 /** Serializable UID. */
440 private static final long serialVersionUID = -3194195019557081000L;
441
442 /** Simple constructor.
443 * @param supportedNames regular expression for supported files names
444 * @param generateType ephemeris type to generate
445 * @param name name of the body
446 */
447 DTOBodyFrame(final String supportedNames, final EphemerisType generateType, final String name) {
448 super(supportedNames, generateType, name);
449 }
450
451 /** Replace the deserialized data transfer object with a {@link Frame}.
452 * @return replacement {@link Frame}
453 */
454 private Object readResolve() {
455 return getBody().bodyFrame;
456 }
457
458 }
459
460 }