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