1 /* Copyright 2002-2013 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.frames;
18
19 import java.io.Serializable;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collection;
23 import java.util.List;
24
25 import org.apache.commons.math3.RealFieldElement;
26 import org.apache.commons.math3.geometry.euclidean.threed.FieldRotation;
27 import org.apache.commons.math3.geometry.euclidean.threed.FieldVector3D;
28 import org.apache.commons.math3.geometry.euclidean.threed.Line;
29 import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
30 import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
31 import org.apache.commons.math3.util.Pair;
32 import org.orekit.time.AbsoluteDate;
33 import org.orekit.time.TimeInterpolable;
34 import org.orekit.time.TimeShiftable;
35 import org.orekit.time.TimeStamped;
36 import org.orekit.utils.AngularCoordinates;
37 import org.orekit.utils.PVCoordinates;
38 import org.orekit.utils.FieldPVCoordinates;
39
40
41 /** Transformation class in three dimensional space.
42 *
43 * <p>This class represents the transformation engine between {@link Frame frames}.
44 * It is used both to define the relationship between each frame and its
45 * parent frame and to gather all individual transforms into one
46 * operation when converting between frames far away from each other.</p>
47 * <p> The convention used in OREKIT is vectorial transformation. It means
48 * that a transformation is defined as a transform to apply to the
49 * coordinates of a vector expressed in the old frame to obtain the
50 * same vector expressed in the new frame. <p>
51 *
52 * <p>Instances of this class are guaranteed to be immutable.</p>
53 *
54 * <h5> Example </h5>
55 *
56 * <pre>
57 *
58 * 1 ) Example of translation from R<sub>A</sub> to R<sub>B</sub>:
59 * We want to transform the {@link PVCoordinates} PV<sub>A</sub> to PV<sub>B</sub>.
60 *
61 * With : PV<sub>A</sub> = ({1, 0, 0} , {1, 0, 0});
62 * and : PV<sub>B</sub> = ({0, 0, 0} , {0, 0, 0});
63 *
64 * The transform to apply then is defined as follows :
65 *
66 * Vector3D translation = new Vector3D(-1,0,0);
67 * Vector3D velocity = new Vector3D(-1,0,0);
68 *
69 * Transform R1toR2 = new Transform(translation, Velocity);
70 *
71 * PV<sub>B</sub> = R1toR2.transformPVCoordinates(PV<sub>A</sub>);
72 *
73 *
74 * 2 ) Example of rotation from R<sub>A</sub> to R<sub>B</sub>:
75 * We want to transform the {@link PVCoordinates} PV<sub>A</sub> to PV<sub>B</sub>.
76 *
77 * With : PV<sub>A</sub> = ({1, 0, 0}, {1, 0, 0});
78 * and : PV<sub>B</sub> = ({0, 1, 0}, {-2, 1, 0});
79 *
80 * The transform to apply then is defined as follows :
81 *
82 * Rotation rotation = new Rotation(Vector3D.PLUS_K, FastMath.PI / 2);
83 * Vector3D rotationRate = new Vector3D(0, 0, -2);
84 *
85 * Transform R1toR2 = new Transform(rotation, rotationRate);
86 *
87 * PV<sub>B</sub> = R1toR2.transformPVCoordinates(PV<sub>A</sub>);
88 *
89 * </pre>
90 *
91 * @author Luc Maisonobe
92 * @author Fabien Maussion
93 */
94 public class Transform
95 implements TimeStamped, TimeShiftable<Transform>, TimeInterpolable<Transform>, Serializable {
96
97 /** Identity transform. */
98 public static final Transform IDENTITY = new IdentityTransform();
99
100 /** Serializable UID. */
101 private static final long serialVersionUID = -8809893979516295102L;
102
103 /** Date of the transform. */
104 private final AbsoluteDate date;
105
106 /** Cartesian coordinates of the target frame with respect to the original frame. */
107 private final PVCoordinates cartesian;
108
109 /** Angular coordinates of the target frame with respect to the original frame. */
110 private final AngularCoordinates angular;
111
112 /** Build a transform from its primitive operations.
113 * @param date date of the transform
114 * @param cartesian Cartesian coordinates of the target frame with respect to the original frame
115 * @param angular angular coordinates of the target frame with respect to the original frame
116 */
117 private Transform(final AbsoluteDate date,
118 final PVCoordinates cartesian, final AngularCoordinates angular) {
119 this.date = date;
120 this.cartesian = cartesian;
121 this.angular = angular;
122 }
123
124 /** Build a translation transform.
125 * @param date date of the transform
126 * @param translation translation to apply (i.e. coordinates of
127 * the transformed origin, or coordinates of the origin of the
128 * old frame in the new frame)
129 */
130 public Transform(final AbsoluteDate date, final Vector3D translation) {
131 this(date, new PVCoordinates(translation, Vector3D.ZERO), AngularCoordinates.IDENTITY);
132 }
133
134 /** Build a rotation transform.
135 * @param date date of the transform
136 * @param rotation rotation to apply ( i.e. rotation to apply to the
137 * coordinates of a vector expressed in the old frame to obtain the
138 * same vector expressed in the new frame )
139 */
140 public Transform(final AbsoluteDate date, final Rotation rotation) {
141 this(date, PVCoordinates.ZERO, new AngularCoordinates(rotation, Vector3D.ZERO));
142 }
143
144 /** Build a translation transform, with its first time derivative.
145 * @param date date of the transform
146 * @param translation translation to apply (i.e. coordinates of
147 * the transformed origin, or coordinates of the origin of the
148 * old frame in the new frame)
149 * @param velocity the velocity of the translation (i.e. origin
150 * of the old frame velocity in the new frame)
151 */
152 public Transform(final AbsoluteDate date, final Vector3D translation, final Vector3D velocity) {
153 this(date, new PVCoordinates(translation, velocity), AngularCoordinates.IDENTITY);
154 }
155
156 /** Build a translation transform, with its first time derivative.
157 * @param date date of the transform
158 * @param cartesian cartesian part of the transformation to apply (i.e. coordinates of
159 * the transformed origin, or coordinates of the origin of the
160 * old frame in the new frame, with their derivatives)
161 */
162 public Transform(final AbsoluteDate date, final PVCoordinates cartesian) {
163 this(date, cartesian, AngularCoordinates.IDENTITY);
164 }
165
166 /** Build a rotation transform.
167 * @param date date of the transform
168 * @param rotation rotation to apply ( i.e. rotation to apply to the
169 * coordinates of a vector expressed in the old frame to obtain the
170 * same vector expressed in the new frame )
171 * @param rotationRate the axis of the instant rotation
172 * expressed in the new frame. (norm representing angular rate)
173 */
174 public Transform(final AbsoluteDate date, final Rotation rotation, final Vector3D rotationRate) {
175 this(date, PVCoordinates.ZERO, new AngularCoordinates(rotation, rotationRate));
176 }
177
178 /** Build a rotation transform.
179 * @param date date of the transform
180 * @param angular angular part of the transformation to apply (i.e. rotation to
181 * apply to the coordinates of a vector expressed in the old frame to obtain the
182 * same vector expressed in the new frame, with its rotation rate)
183 */
184 public Transform(final AbsoluteDate date, final AngularCoordinates angular) {
185 this(date, PVCoordinates.ZERO, angular);
186 }
187
188 /** Build a transform by combining two existing ones.
189 * <p>
190 * Note that the dates of the two existing transformed are <em>ignored</em>,
191 * and the combined transform date is set to the date supplied in this constructor
192 * without any attempt to shift the raw transforms. This is a design choice allowing
193 * user full control of the combination.
194 * </p>
195 * @param date date of the transform
196 * @param first first transform applied
197 * @param second second transform applied
198 */
199 public Transform(final AbsoluteDate date, final Transform first, final Transform second) {
200 this(date,
201 new PVCoordinates(compositeTranslation(first, second),
202 compositeVelocity(first, second)),
203 new AngularCoordinates(compositeRotation(first, second),
204 compositeRotationRate(first, second)));
205 }
206
207 /** Compute a composite translation.
208 * @param first first applied transform
209 * @param second second applied transform
210 * @return translation part of the composite transform
211 */
212 private static Vector3D compositeTranslation(final Transform first, final Transform second) {
213
214 final Vector3D p1 = first.cartesian.getPosition();
215 final Rotation r1 = first.angular.getRotation();
216 final Vector3D p2 = second.cartesian.getPosition();
217
218 return p1.add(r1.applyInverseTo(p2));
219
220 }
221
222 /** Compute a composite velocity.
223 * @param first first applied transform
224 * @param second second applied transform
225 * @return velocity part of the composite transform
226 */
227 private static Vector3D compositeVelocity(final Transform first, final Transform second) {
228
229 final Vector3D v1 = first.cartesian.getVelocity();
230 final Rotation r1 = first.angular.getRotation();
231 final Vector3D o1 = first.angular.getRotationRate();
232 final Vector3D p2 = second.cartesian.getPosition();
233 final Vector3D v2 = second.cartesian.getVelocity();
234
235 return v1.add(r1.applyInverseTo(v2.add(Vector3D.crossProduct(o1, p2))));
236
237 }
238
239 /** Compute a composite rotation.
240 * @param first first applied transform
241 * @param second second applied transform
242 * @return rotation part of the composite transform
243 */
244 private static Rotation compositeRotation(final Transform first, final Transform second) {
245
246 final Rotation r1 = first.angular.getRotation();
247 final Rotation r2 = second.angular.getRotation();
248
249 return r2.applyTo(r1);
250
251 }
252
253 /** Compute a composite rotation rate.
254 * @param first first applied transform
255 * @param second second applied transform
256 * @return rotation rate part of the composite transform
257 */
258 private static Vector3D compositeRotationRate(final Transform first, final Transform second) {
259
260 final Vector3D o1 = first.angular.getRotationRate();
261 final Rotation r2 = second.angular.getRotation();
262 final Vector3D o2 = second.angular.getRotationRate();
263
264 return o2.add(r2.applyTo(o1));
265
266 }
267
268 /** {@inheritDoc} */
269 public AbsoluteDate getDate() {
270 return date;
271 }
272
273 /** {@inheritDoc} */
274 public Transform shiftedBy(final double dt) {
275 return new Transform(date.shiftedBy(dt), cartesian.shiftedBy(dt), angular.shiftedBy(dt));
276 };
277
278 /** {@inheritDoc}
279 * <p>
280 * Calling this method is equivalent to call {@link #interpolate(AbsoluteDate, boolean,
281 * boolean, Collection)} with both {@code useVelocities} and {@code useRotationRates}
282 * set to true.
283 * </p>
284 */
285 public Transform interpolate(final AbsoluteDate interpolationDate,
286 final Collection<Transform> sample) {
287 return interpolate(interpolationDate, true, true, sample);
288 }
289
290 /** Interpolate a transform from a sample set of existing transforms.
291 * <p>
292 * Note that even if first time derivatives (velocities and rotation rates)
293 * from sample can be ignored, the interpolated instance always includes
294 * interpolated derivatives. This feature can be used explicitly to
295 * compute these derivatives when it would be too complex to compute them
296 * from an analytical formula: just compute a few sample points from the
297 * explicit formula and set the derivatives to zero in these sample points,
298 * then use interpolation to add derivatives consistent with the positions
299 * and rotations.
300 * </p>
301 * <p>
302 * As this implementation of interpolation is polynomial, it should be used only
303 * with small samples (about 10-20 points) in order to avoid <a
304 * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
305 * and numerical problems (including NaN appearing).
306 * </p>
307 * @param date interpolation date
308 * @param useVelocities if true, use sample transforms velocities,
309 * otherwise ignore them and use only positions
310 * @param useRotationRates if true, use sample points rotation rates,
311 * otherwise ignore them and use only rotations
312 * @param sample sample points on which interpolation should be done
313 * @return a new instance, interpolated at specified date
314 */
315 public static Transform interpolate(final AbsoluteDate date,
316 final boolean useVelocities, final boolean useRotationRates,
317 final Collection<Transform> sample) {
318 final List<Pair<AbsoluteDate, PVCoordinates>> datedPV =
319 new ArrayList<Pair<AbsoluteDate, PVCoordinates>>(sample.size());
320 final List<Pair<AbsoluteDate, AngularCoordinates>> datedAC =
321 new ArrayList<Pair<AbsoluteDate, AngularCoordinates>>(sample.size());
322 for (final Transform transform : sample) {
323 datedPV.add(new Pair<AbsoluteDate, PVCoordinates>(transform.getDate(),
324 transform.getCartesian()));
325 datedAC.add(new Pair<AbsoluteDate, AngularCoordinates>(transform.getDate(),
326 transform.getAngular()));
327 }
328 final PVCoordinates interpolatedPV = PVCoordinates.interpolate(date, useVelocities, datedPV);
329 final AngularCoordinates interpolatedAC = AngularCoordinates.interpolate(date, useRotationRates, datedAC);
330 return new Transform(date, interpolatedPV, interpolatedAC);
331 }
332
333 /** Get the inverse transform of the instance.
334 * @return inverse transform of the instance
335 */
336 public Transform getInverse() {
337
338 final Vector3D p = cartesian.getPosition();
339 final Vector3D v = cartesian.getVelocity();
340 final Rotation r = angular.getRotation();
341 final Vector3D o = angular.getRotationRate();
342
343 final Vector3D rT = r.applyTo(p);
344 return new Transform(date,
345 new PVCoordinates(rT.negate(),
346 Vector3D.crossProduct(o, rT).subtract(r.applyTo(v))),
347 angular.revert());
348
349 }
350
351 /** Get a freezed transform.
352 * <p>
353 * This method creates a copy of the instance but frozen in time,
354 * i.e. with velocity and rotation rate forced to zero.
355 * </p>
356 * @return a new transform, without any time-dependent parts
357 */
358 public Transform freeze() {
359 return new Transform(date,
360 new PVCoordinates(cartesian.getPosition(), Vector3D.ZERO),
361 new AngularCoordinates(angular.getRotation(), Vector3D.ZERO));
362 }
363
364 /** Transform a position vector (including translation effects).
365 * @param position vector to transform
366 * @return transformed position
367 */
368 public Vector3D transformPosition(final Vector3D position) {
369 return angular.getRotation().applyTo(cartesian.getPosition().add(position));
370 }
371
372 /** Transform a position vector (including translation effects).
373 * @param position vector to transform
374 * @param <T> the type of the field elements
375 * @return transformed position
376 */
377 public <T extends RealFieldElement<T>> FieldVector3D<T> transformPosition(final FieldVector3D<T> position) {
378 return FieldRotation.applyTo(angular.getRotation(), position.add(cartesian.getPosition()));
379 }
380
381 /** Transform a vector (ignoring translation effects).
382 * @param vector vector to transform
383 * @return transformed vector
384 */
385 public Vector3D transformVector(final Vector3D vector) {
386 return angular.getRotation().applyTo(vector);
387 }
388
389 /** Transform a vector (ignoring translation effects).
390 * @param vector vector to transform
391 * @param <T> the type of the field elements
392 * @return transformed vector
393 */
394 public <T extends RealFieldElement<T>> FieldVector3D<T> transformVector(final FieldVector3D<T> vector) {
395 return FieldRotation.applyTo(angular.getRotation(), vector);
396 }
397
398 /** Transform a line.
399 * @param line to transform
400 * @return transformed line
401 */
402 public Line transformLine(final Line line) {
403 final Vector3D transformedP0 = transformPosition(line.getOrigin());
404 final Vector3D transformedP1 = transformPosition(line.pointAt(1.0e6));
405 return new Line(transformedP0, transformedP1);
406 }
407
408 /** Transform {@link PVCoordinates} including kinematic effects.
409 * @param pv the couple position-velocity to transform.
410 * @return transformed position/velocity
411 */
412 public PVCoordinates transformPVCoordinates(final PVCoordinates pv) {
413 final Vector3D p = pv.getPosition();
414 final Vector3D v = pv.getVelocity();
415 final Vector3D transformedP = angular.getRotation().applyTo(cartesian.getPosition().add(p));
416 final Vector3D cross = Vector3D.crossProduct(angular.getRotationRate(), transformedP);
417 return new PVCoordinates(transformedP,
418 angular.getRotation().applyTo(v.add(cartesian.getVelocity())).subtract(cross));
419 }
420
421 /** Transform {@link FieldPVCoordinates} including kinematic effects.
422 * @param pv the couple position-velocity to transform.
423 * @param <T> the type of the field elements
424 * @return transformed position/velocity
425 */
426 public <T extends RealFieldElement<T>> FieldPVCoordinates<T> transformPVCoordinates(final FieldPVCoordinates<T> pv) {
427 final FieldVector3D<T> p = pv.getPosition();
428 final FieldVector3D<T> v = pv.getVelocity();
429 final FieldVector3D<T> transformedP = FieldRotation.applyTo(angular.getRotation(),
430 p.add(cartesian.getPosition()));
431 final FieldVector3D<T> cross = FieldVector3D.crossProduct(angular.getRotationRate(), transformedP);
432 return new FieldPVCoordinates<T>(transformedP,
433 FieldRotation.applyTo(angular.getRotation(),
434 v.add(cartesian.getVelocity())).subtract(cross));
435 }
436
437 /** Compute the Jacobian of the {@link #transformPVCoordinates(PVCoordinates)}
438 * method of the transform.
439 * <p>
440 * Element {@code jacobian[i][j]} is the derivative of Cartesian coordinate i
441 * of the transformed {@link PVCoordinates} with respect to Cartesian coordinate j
442 * of the input {@link PVCoordinates} in method {@link #transformPVCoordinates(PVCoordinates)}.
443 * </p>
444 * <p>
445 * This definition implies that if we define position-velocity coordinates
446 * <pre>
447 * PV<sub>1</sub> = transform.transformPVCoordinates(PV<sub>0</sub>), then
448 * </pre>
449 * their differentials dPV<sub>1</sub> and dPV<sub>0</sub> will obey the following relation
450 * where J is the matrix computed by this method:<br/>
451 * <pre>
452 * dPV<sub>1</sub> = J × dPV<sub>0</sub>
453 * </pre>
454 * </p>
455 * @param jacobian placeholder 6x6 (or larger) matrix to be filled with the Jacobian, if matrix
456 * is larger than 6x6, only the 6x6 upper left corner will be modified
457 */
458 public void getJacobian(final double[][] jacobian) {
459
460 // elementary matrix for rotation
461 final double[][] mData = angular.getRotation().getMatrix();
462
463 // dP1/dP0
464 System.arraycopy(mData[0], 0, jacobian[0], 0, 3);
465 System.arraycopy(mData[1], 0, jacobian[1], 0, 3);
466 System.arraycopy(mData[2], 0, jacobian[2], 0, 3);
467
468 // dP1/dV0
469 Arrays.fill(jacobian[0], 3, 6, 0.0);
470 Arrays.fill(jacobian[1], 3, 6, 0.0);
471 Arrays.fill(jacobian[2], 3, 6, 0.0);
472
473 // dV1/dP0
474 final Vector3D o = angular.getRotationRate();
475 final double mOx = -o.getX();
476 final double mOy = -o.getY();
477 final double mOz = -o.getZ();
478 for (int i = 0; i < 3; ++i) {
479 jacobian[3][i] = mOy * mData[2][i] - mOz * mData[1][i];
480 jacobian[4][i] = mOz * mData[0][i] - mOx * mData[2][i];
481 jacobian[5][i] = mOx * mData[1][i] - mOy * mData[0][i];
482 }
483
484 // dV1/dV0
485 System.arraycopy(mData[0], 0, jacobian[3], 3, 3);
486 System.arraycopy(mData[1], 0, jacobian[4], 3, 3);
487 System.arraycopy(mData[2], 0, jacobian[5], 3, 3);
488
489 }
490
491 /** Get the underlying elementary cartesian part.
492 * <p>A transform can be uniquely represented as an elementary
493 * translation followed by an elementary rotation. This method
494 * returns this unique elementary translation with its derivative.</p>
495 * @return underlying elementary cartesian part
496 * @see #getTranslation()
497 * @see #getVelocity()
498 */
499 public PVCoordinates getCartesian() {
500 return cartesian;
501 }
502
503 /** Get the underlying elementary translation.
504 * <p>A transform can be uniquely represented as an elementary
505 * translation followed by an elementary rotation. This method
506 * returns this unique elementary translation.</p>
507 * @return underlying elementary translation
508 * @see #getCartesian()
509 * @see #getVelocity()
510 */
511 public Vector3D getTranslation() {
512 return cartesian.getPosition();
513 }
514
515 /** Get the first time derivative of the translation.
516 * @return first time derivative of the translation
517 * @see #getCartesian()
518 * @see #getTranslation()
519 */
520 public Vector3D getVelocity() {
521 return cartesian.getVelocity();
522 }
523
524 /** Get the underlying elementary angular part.
525 * <p>A transform can be uniquely represented as an elementary
526 * translation followed by an elementary rotation. This method
527 * returns this unique elementary rotation with its derivative.</p>
528 * @return underlying elementary angular part
529 * @see #getRotation()
530 * @see #getRotationRate()
531 */
532 public AngularCoordinates getAngular() {
533 return angular;
534 }
535
536 /** Get the underlying elementary rotation.
537 * <p>A transform can be uniquely represented as an elementary
538 * translation followed by an elementary rotation. This method
539 * returns this unique elementary rotation.</p>
540 * @return underlying elementary rotation
541 * @see #getAngular()
542 * @see #getRotationRate()
543 */
544 public Rotation getRotation() {
545 return angular.getRotation();
546 }
547
548 /** Get the first time derivative of the rotation.
549 * <p>The norm represents the angular rate.</p>
550 * @return First time derivative of the rotation
551 * @see #getAngular()
552 * @see #getRotation()
553 */
554 public Vector3D getRotationRate() {
555 return angular.getRotationRate();
556 }
557
558 /** Specialized class for identity transform. */
559 private static class IdentityTransform extends Transform {
560
561 /** Serializable UID. */
562 private static final long serialVersionUID = -9042082036141830517L;
563
564 /** Simple constructor. */
565 public IdentityTransform() {
566 super(AbsoluteDate.J2000_EPOCH, PVCoordinates.ZERO, AngularCoordinates.IDENTITY);
567 }
568
569 /** {@inheritDoc} */
570 @Override
571 public Transform shiftedBy(final double dt) {
572 return this;
573 }
574
575 /** {@inheritDoc} */
576 @Override
577 public Transform getInverse() {
578 return this;
579 };
580
581 /** {@inheritDoc} */
582 @Override
583 public Vector3D transformPosition(final Vector3D position) {
584 return position;
585 }
586
587 /** {@inheritDoc} */
588 @Override
589 public Vector3D transformVector(final Vector3D vector) {
590 return vector;
591 }
592
593 /** {@inheritDoc} */
594 @Override
595 public Line transformLine(final Line line) {
596 return line;
597 }
598
599 /** {@inheritDoc} */
600 @Override
601 public PVCoordinates transformPVCoordinates(final PVCoordinates pv) {
602 return pv;
603 }
604
605 /** {@inheritDoc} */
606 @Override
607 public void getJacobian(final double[][] jacobian) {
608 for (int i = 0; i < 6; ++i) {
609 Arrays.fill(jacobian[i], 0, 6, 0.0);
610 jacobian[i][i] = 1.0;
611 }
612 }
613
614 }
615
616 }