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.attitudes;
18  
19  
20  import org.hipparchus.Field;
21  import org.hipparchus.CalculusFieldElement;
22  import org.hipparchus.geometry.euclidean.threed.Line;
23  import org.hipparchus.geometry.euclidean.threed.Rotation;
24  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
25  import org.hipparchus.geometry.euclidean.threed.Vector3D;
26  import org.hipparchus.util.Decimal64Field;
27  import org.hipparchus.util.FastMath;
28  import org.junit.After;
29  import org.junit.Assert;
30  import org.junit.Before;
31  import org.junit.Test;
32  import org.orekit.Utils;
33  import org.orekit.bodies.GeodeticPoint;
34  import org.orekit.bodies.OneAxisEllipsoid;
35  import org.orekit.errors.OrekitException;
36  import org.orekit.errors.OrekitMessages;
37  import org.orekit.frames.Frame;
38  import org.orekit.frames.FramesFactory;
39  import org.orekit.frames.Transform;
40  import org.orekit.orbits.CircularOrbit;
41  import org.orekit.orbits.FieldOrbit;
42  import org.orekit.orbits.KeplerianOrbit;
43  import org.orekit.orbits.Orbit;
44  import org.orekit.orbits.PositionAngle;
45  import org.orekit.propagation.FieldSpacecraftState;
46  import org.orekit.propagation.Propagator;
47  import org.orekit.propagation.SpacecraftState;
48  import org.orekit.propagation.analytical.KeplerianPropagator;
49  import org.orekit.time.AbsoluteDate;
50  import org.orekit.time.DateComponents;
51  import org.orekit.time.FieldAbsoluteDate;
52  import org.orekit.time.TimeComponents;
53  import org.orekit.time.TimeScalesFactory;
54  import org.orekit.utils.AngularCoordinates;
55  import org.orekit.utils.Constants;
56  import org.orekit.utils.IERSConventions;
57  import org.orekit.utils.PVCoordinates;
58  import org.orekit.utils.TimeStampedFieldPVCoordinates;
59  import org.orekit.utils.TimeStampedPVCoordinates;
60  
61  
62  public class TargetPointingTest {
63  
64      // Computation date
65      private AbsoluteDate date;
66  
67      // Body mu
68      private double mu;
69  
70      // Reference frame = ITRF
71      private Frame itrf;
72  
73      // Transform from EME2000 to ITRF
74      private Transform eme2000ToItrf;
75  
76      /** Test if both constructors are equivalent
77       */
78      @Test
79      public void testConstructors() {
80  
81          //  Satellite position
82          // ********************
83          CircularOrbit circ =
84              new CircularOrbit(7178000.0, 0.5e-4, -0.5e-4, FastMath.toRadians(50.), FastMath.toRadians(270.),
85                                     FastMath.toRadians(5.300), PositionAngle.MEAN,
86                                     FramesFactory.getEME2000(), date, mu);
87  
88          //  Attitude laws
89          // ***************
90          // Elliptic earth shape
91          OneAxisEllipsoid earthShape = new OneAxisEllipsoid(6378136.460, 1 / 298.257222101, itrf);
92  
93          // Target definition as a geodetic point AND as a position/velocity vector
94          GeodeticPoint geoTargetITRF = new GeodeticPoint(FastMath.toRadians(43.36), FastMath.toRadians(1.26), 600.);
95          Vector3D pTargetITRF = earthShape.transform(geoTargetITRF);
96  
97          // Attitude law definition from geodetic point target
98          TargetPointing geoTargetAttitudeLaw = new TargetPointing(circ.getFrame(), geoTargetITRF, earthShape);
99  
100         //  Attitude law definition from position/velocity target
101         TargetPointing pvTargetAttitudeLaw = new TargetPointing(circ.getFrame(), itrf, pTargetITRF);
102 
103         // Check that both attitude are the same
104         // Get satellite rotation for target pointing law
105         Rotation rotPv = pvTargetAttitudeLaw.getAttitude(circ, date, circ.getFrame()).getRotation();
106 
107         // Get satellite rotation for nadir pointing law
108         Rotation rotGeo = geoTargetAttitudeLaw.getAttitude(circ, date, circ.getFrame()).getRotation();
109 
110         // Rotations composition
111         Rotation rotCompo = rotGeo.composeInverse(rotPv, RotationConvention.VECTOR_OPERATOR);
112         double angle = rotCompo.getAngle();
113         Assert.assertEquals(angle, 0.0, Utils.epsilonAngle);
114 
115     }
116 
117     /** Test if geodetic constructor works
118      */
119     @Test
120     public void testGeodeticConstructor() {
121 
122         //  Satellite position
123         // ********************
124         CircularOrbit circ =
125             new CircularOrbit(7178000.0, 0.5e-4, -0.5e-4, FastMath.toRadians(50.), FastMath.toRadians(270.),
126                                    FastMath.toRadians(5.300), PositionAngle.MEAN,
127                                    FramesFactory.getEME2000(), date, mu);
128 
129         //  Attitude law
130         // **************
131 
132         // Elliptic earth shape
133         OneAxisEllipsoid earthShape = new OneAxisEllipsoid(6378136.460, 1 / 298.257222101, itrf);
134 
135         // Target definition as a geodetic point
136         GeodeticPoint geoTargetITRF = new GeodeticPoint(FastMath.toRadians(43.36), FastMath.toRadians(1.26), 600.);
137 
138         //  Attitude law definition
139         TargetPointing geoTargetAttitudeLaw = new TargetPointing(circ.getFrame(), geoTargetITRF, earthShape);
140 
141         // Check that observed ground point is the same as defined target
142         Vector3D pObservedEME2000 = geoTargetAttitudeLaw.getTargetPV(circ, date, FramesFactory.getEME2000()).getPosition();
143         GeodeticPoint geoObserved = earthShape.transform(pObservedEME2000, FramesFactory.getEME2000(), date);
144 
145         Assert.assertEquals(geoObserved.getLongitude(), geoTargetITRF.getLongitude(), Utils.epsilonAngle);
146         Assert.assertEquals(geoObserved.getLatitude(), geoTargetITRF.getLatitude(), Utils.epsilonAngle);
147         Assert.assertEquals(geoObserved.getAltitude(), geoTargetITRF.getAltitude(), 1.1e-8);
148 
149     }
150 
151     @Test
152     public void testIssue115() {
153 
154         //  Satellite position
155         // ********************
156         CircularOrbit circ =
157             new CircularOrbit(7178000.0, 0.5e-4, -0.5e-4, FastMath.toRadians(50.), FastMath.toRadians(270.),
158                                    FastMath.toRadians(5.300), PositionAngle.MEAN,
159                                    FramesFactory.getEME2000(), date, mu);
160 
161         //  Attitude law
162         // **************
163 
164         // Elliptic earth shape
165         OneAxisEllipsoid earthShape = new OneAxisEllipsoid(6378136.460, 1 / 298.257222101, itrf);
166 
167         // Target definition as a geodetic point
168         GeodeticPoint geoTargetITRF = new GeodeticPoint(FastMath.toRadians(43.36), FastMath.toRadians(1.26), 600.);
169 
170         //  Attitude law definition
171         TargetPointing geoTargetAttitudeLaw = new TargetPointing(circ.getFrame(), geoTargetITRF, earthShape);
172 
173         // Check that observed ground point is the same as defined target
174         Frame cirf = FramesFactory.getCIRF(IERSConventions.IERS_2010, true);
175         Attitude att1 = geoTargetAttitudeLaw.getAttitude(circ, date, cirf);
176         Attitude att2 = geoTargetAttitudeLaw.getAttitude(circ, date, itrf);
177         Attitude att3 = att2.withReferenceFrame(cirf);
178         Assert.assertEquals(0.0, Rotation.distance(att3.getRotation(), att1.getRotation()), 1.0e-15);
179 
180     }
181 
182     @Test
183     public void testWrongFrame() {
184         try {
185             // in the following line, the frames have been intentionnally reversed
186             new TargetPointing(itrf, FramesFactory.getEME2000(),
187                                new Vector3D(Constants.WGS84_EARTH_EQUATORIAL_RADIUS, 0, 0));
188             Assert.fail("an exception should have been thrown");
189         } catch (OrekitException oe) {
190             Assert.assertEquals(OrekitMessages.NON_PSEUDO_INERTIAL_FRAME, oe.getSpecifier());
191             Assert.assertEquals(itrf.getName(), oe.getParts()[0]);
192         }
193     }
194 
195     /** Test with nadir target : Check that when the target is the same as nadir target at date,
196      * satellite attitude is the same as nadir attitude at the same date, but different at a different date.
197      */
198     @Test
199     public void testNadirTarget() {
200 
201         // Elliptic earth shape
202         OneAxisEllipsoid earthShape = new OneAxisEllipsoid(6378136.460, 1 / 298.257222101, itrf);
203 
204         // Satellite on any position
205         CircularOrbit circOrbit =
206             new CircularOrbit(7178000.0, 1.e-5, 0., FastMath.toRadians(50.), 0.,
207                                    FastMath.toRadians(90.), PositionAngle.TRUE,
208                                    FramesFactory.getEME2000(), date, mu);
209 
210         //  Target attitude provider with target under satellite nadir
211         // *******************************************************
212         // Definition of nadir target
213         // Create nadir pointing attitude provider
214         NadirPointing nadirAttitudeLaw = new NadirPointing(circOrbit.getFrame(), earthShape);
215 
216         // Check nadir target
217         Vector3D pNadirTarget  = nadirAttitudeLaw.getTargetPV(circOrbit, date, itrf).getPosition();
218         GeodeticPoint geoNadirTarget = earthShape.transform(pNadirTarget, itrf, date);
219 
220         // Create target attitude provider
221         TargetPointing targetAttitudeLaw = new TargetPointing(circOrbit.getFrame(), geoNadirTarget, earthShape);
222 
223         //  1/ Test that attitudes are the same at date
224         // *********************************************
225         // i.e the composition of inverse earth pointing rotation
226         // with nadir pointing rotation shall be identity.
227 
228         // Get satellite rotation from target pointing law at date
229         Rotation rotTarget = targetAttitudeLaw.getAttitude(circOrbit, date, circOrbit.getFrame()).getRotation();
230 
231         // Get satellite rotation from nadir pointing law at date
232         Rotation rotNadir = nadirAttitudeLaw.getAttitude(circOrbit, date, circOrbit.getFrame()).getRotation();
233 
234         // Compose attitude rotations
235         Rotation rotCompo = rotTarget.composeInverse(rotNadir, RotationConvention.VECTOR_OPERATOR);
236         double angle = rotCompo.getAngle();
237         Assert.assertEquals(angle, 0.0, Utils.epsilonAngle);
238 
239 
240         //  2/ Test that attitudes are different at a different date
241         // **********************************************************
242 
243         // Extrapolation one minute later
244         KeplerianPropagator extrapolator = new KeplerianPropagator(circOrbit);
245         double delta_t = 60.0; // extrapolation duration in seconds
246         AbsoluteDate extrapDate = date.shiftedBy(delta_t);
247         Orbit extrapOrbit = extrapolator.propagate(extrapDate).getOrbit();
248 
249 
250         // Get satellite rotation from target pointing law at date + 1min
251         Rotation extrapRotTarget = targetAttitudeLaw.getAttitude(extrapOrbit, extrapDate, extrapOrbit.getFrame()).getRotation();
252 
253         // Get satellite rotation from nadir pointing law at date
254         Rotation extrapRotNadir = nadirAttitudeLaw.getAttitude(extrapOrbit, extrapDate, extrapOrbit.getFrame()).getRotation();
255 
256         // Compose attitude rotations
257         Rotation extrapRotCompo = extrapRotTarget.composeInverse(extrapRotNadir, RotationConvention.VECTOR_OPERATOR);
258         double extrapAngle = extrapRotCompo.getAngle();
259         Assert.assertEquals(extrapAngle, FastMath.toRadians(24.684793905118823), Utils.epsilonAngle);
260 
261     }
262 
263     /** Test if defined target belongs to the direction pointed by the satellite
264      */
265     @Test
266     public void testTargetInPointingDirection() {
267 
268         // Create computation date
269         AbsoluteDate date = new AbsoluteDate(new DateComponents(2008, 04, 07),
270                                              TimeComponents.H00,
271                                              TimeScalesFactory.getUTC());
272 
273         // Reference frame = ITRF
274         Frame itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
275 
276         // Elliptic earth shape
277         OneAxisEllipsoid earthShape = new OneAxisEllipsoid(6378136.460, 1 / 298.257222101, itrf);
278 
279         // Create target pointing attitude provider
280         GeodeticPoint geoTarget = new GeodeticPoint(FastMath.toRadians(43.36), FastMath.toRadians(1.26), 600.);
281         TargetPointing targetAttitudeLaw = new TargetPointing(FramesFactory.getEME2000(), geoTarget, earthShape);
282 
283         //  Satellite position
284         // ********************
285         // Create satellite position as circular parameters
286         CircularOrbit circ =
287             new CircularOrbit(7178000.0, 0.5e-4, -0.5e-4, FastMath.toRadians(50.), FastMath.toRadians(270.),
288                                    FastMath.toRadians(5.300), PositionAngle.MEAN,
289                                    FramesFactory.getEME2000(), date, mu);
290 
291         // Transform satellite position to position/velocity parameters in EME2000 frame
292         PVCoordinates pvSatEME2000 = circ.getPVCoordinates();
293 
294         //  Pointing direction
295         // ********************
296         // Get satellite attitude rotation, i.e rotation from EME2000 frame to satellite frame
297         Rotation rotSatEME2000 = targetAttitudeLaw.getAttitude(circ, date, circ.getFrame()).getRotation();
298 
299         // Transform Z axis from satellite frame to EME2000
300         Vector3D zSatEME2000 = rotSatEME2000.applyInverseTo(Vector3D.PLUS_K);
301 
302         // Line containing satellite point and following pointing direction
303         Vector3D p = eme2000ToItrf.transformPosition(pvSatEME2000.getPosition());
304         Line pointingLine = new Line(p,
305                                      p.add(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
306                                            eme2000ToItrf.transformVector(zSatEME2000)),
307                                      1.0e-10);
308 
309         // Check that the line contains earth center
310         double distance = pointingLine.distance(earthShape.transform(geoTarget));
311 
312         Assert.assertEquals(0, distance, 1.e-7);
313     }
314 
315     /** Test the difference between pointing over two longitudes separated by 5°
316      */
317     @Test
318     public void testSlewedTarget() {
319 
320         // Spheric earth shape
321         OneAxisEllipsoid earthShape = new OneAxisEllipsoid(6378136.460, 0., itrf);
322 
323         //  Satellite position
324         // ********************
325         // Create satellite position as circular parameters
326         CircularOrbit circ =
327             new CircularOrbit(42164000.0, 0.5e-8, -0.5e-8, 0., 0.,
328                                    FastMath.toRadians(5.300), PositionAngle.MEAN,
329                                    FramesFactory.getEME2000(), date, mu);
330 
331         // Create nadir pointing attitude provider
332         // **********************************
333         NadirPointing nadirAttitudeLaw = new NadirPointing(circ.getFrame(), earthShape);
334 
335         // Get observed ground point from nadir pointing law
336         Vector3D pNadirObservedEME2000 = nadirAttitudeLaw.getTargetPV(circ, date, FramesFactory.getEME2000()).getPosition();
337         Vector3D pNadirObservedITRF = eme2000ToItrf.transformPosition(pNadirObservedEME2000);
338 
339         GeodeticPoint geoNadirObserved = earthShape.transform(pNadirObservedITRF, itrf, date);
340 
341         // Create target pointing attitude provider with target equal to nadir target
342         // *********************************************************************
343         TargetPointing targetLawRef = new TargetPointing(circ.getFrame(), itrf, pNadirObservedITRF);
344 
345         // Get attitude rotation in EME2000
346         Rotation rotSatRefEME2000 = targetLawRef.getAttitude(circ, date, circ.getFrame()).getRotation();
347 
348         // Create target pointing attitude provider with target 5° from nadir target
349         // ********************************************************************
350         GeodeticPoint geoTarget = new GeodeticPoint(geoNadirObserved.getLatitude(),
351                                                     geoNadirObserved.getLongitude() - FastMath.toRadians(5), geoNadirObserved.getAltitude());
352         Vector3D pTargetITRF = earthShape.transform(geoTarget);
353         TargetPointing targetLaw = new TargetPointing(circ.getFrame(), itrf, pTargetITRF);
354 
355         // Get attitude rotation
356         Rotation rotSatEME2000 = targetLaw.getAttitude(circ, date, circ.getFrame()).getRotation();
357 
358         checkField(Decimal64Field.getInstance(), targetLaw, circ, circ.getDate(), circ.getFrame());
359 
360         // Compute difference between both attitude providers
361         // *********************************************
362         // Difference between attitudes
363         //  expected
364         double tanDeltaExpected = (6378136.460/(42164000.0-6378136.460))*FastMath.tan(FastMath.toRadians(5));
365         double deltaExpected = FastMath.atan(tanDeltaExpected);
366 
367         //  real
368         double deltaReal = Rotation.distance(rotSatEME2000, rotSatRefEME2000);
369 
370         Assert.assertEquals(deltaReal, deltaExpected, 1.e-4);
371 
372     }
373 
374     @Test
375     public void testSpin() {
376 
377         Frame itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
378 
379         // Elliptic earth shape
380         OneAxisEllipsoid earthShape = new OneAxisEllipsoid(6378136.460, 1 / 298.257222101, itrf);
381 
382         // Create target pointing attitude provider
383         GeodeticPoint geoTarget = new GeodeticPoint(FastMath.toRadians(43.36), FastMath.toRadians(1.26), 600.);
384         AttitudeProvider law = new TargetPointing(FramesFactory.getEME2000(), geoTarget, earthShape);
385 
386         KeplerianOrbit orbit =
387             new KeplerianOrbit(7178000.0, 1.e-4, FastMath.toRadians(50.),
388                               FastMath.toRadians(10.), FastMath.toRadians(20.),
389                               FastMath.toRadians(30.), PositionAngle.MEAN,
390                               FramesFactory.getEME2000(), date, 3.986004415e14);
391 
392         Propagator propagator = new KeplerianPropagator(orbit, law);
393 
394         double h = 0.01;
395         SpacecraftState sMinus = propagator.propagate(date.shiftedBy(-h));
396         SpacecraftState s0     = propagator.propagate(date);
397         SpacecraftState sPlus  = propagator.propagate(date.shiftedBy(h));
398 
399         // check spin is consistent with attitude evolution
400         double errorAngleMinus     = Rotation.distance(sMinus.shiftedBy(h).getAttitude().getRotation(),
401                                                        s0.getAttitude().getRotation());
402         double evolutionAngleMinus = Rotation.distance(sMinus.getAttitude().getRotation(),
403                                                        s0.getAttitude().getRotation());
404         Assert.assertEquals(0.0, errorAngleMinus, 1.0e-5 * evolutionAngleMinus);
405         double errorAnglePlus      = Rotation.distance(s0.getAttitude().getRotation(),
406                                                        sPlus.shiftedBy(-h).getAttitude().getRotation());
407         double evolutionAnglePlus  = Rotation.distance(s0.getAttitude().getRotation(),
408                                                        sPlus.getAttitude().getRotation());
409         Assert.assertEquals(0.0, errorAnglePlus, 1.0e-5 * evolutionAnglePlus);
410 
411         Vector3D spin0 = s0.getAttitude().getSpin();
412         Vector3D reference = AngularCoordinates.estimateRate(sMinus.getAttitude().getRotation(),
413                                                              sPlus.getAttitude().getRotation(),
414                                                              2 * h);
415         Assert.assertEquals(0.0, spin0.subtract(reference).getNorm(), 1.1e-10);
416 
417     }
418 
419     private <T extends CalculusFieldElement<T>> void checkField(final Field<T> field, final GroundPointing provider,
420                                                             final Orbit orbit, final AbsoluteDate date,
421                                                             final Frame frame)
422         {
423 
424         final Attitude attitudeD = provider.getAttitude(orbit, date, frame);
425         final FieldOrbit<T> orbitF = new FieldSpacecraftState<>(field, new SpacecraftState(orbit)).getOrbit();
426         final FieldAbsoluteDate<T> dateF = new FieldAbsoluteDate<>(field, date);
427         final FieldAttitude<T> attitudeF = provider.getAttitude(orbitF, dateF, frame);
428         Assert.assertEquals(0.0, Rotation.distance(attitudeD.getRotation(), attitudeF.getRotation().toRotation()), 1.0e-15);
429         Assert.assertEquals(0.0, Vector3D.distance(attitudeD.getSpin(), attitudeF.getSpin().toVector3D()), 1.0e-15);
430         Assert.assertEquals(0.0, Vector3D.distance(attitudeD.getRotationAcceleration(), attitudeF.getRotationAcceleration().toVector3D()), 1.0e-15);
431 
432         final TimeStampedPVCoordinates         pvD = provider.getTargetPV(orbit, date, frame);
433         final TimeStampedFieldPVCoordinates<T> pvF = provider.getTargetPV(orbitF, dateF, frame);
434         Assert.assertEquals(0.0, Vector3D.distance(pvD.getPosition(),     pvF.getPosition().toVector3D()),     1.0e-15);
435         Assert.assertEquals(0.0, Vector3D.distance(pvD.getVelocity(),     pvF.getVelocity().toVector3D()),     1.0e-15);
436         Assert.assertEquals(0.0, Vector3D.distance(pvD.getAcceleration(), pvF.getAcceleration().toVector3D()), 1.0e-15);
437 
438     }
439 
440     @Before
441     public void setUp() {
442         try {
443 
444             Utils.setDataRoot("regular-data");
445 
446             // Computation date
447             date = new AbsoluteDate(new DateComponents(2008, 04, 07),
448                                     TimeComponents.H00,
449                                     TimeScalesFactory.getUTC());
450 
451             // Body mu
452             mu = 3.9860047e14;
453 
454             // Reference frame = ITRF
455             itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
456 
457             // Transform from EME2000 to ITRF
458             eme2000ToItrf = FramesFactory.getEME2000().getTransformTo(itrf, date);
459 
460         } catch (OrekitException oe) {
461             Assert.fail(oe.getMessage());
462         }
463 
464     }
465 
466     @After
467     public void tearDown() {
468         date = null;
469         itrf = null;
470         eme2000ToItrf = null;
471     }
472 
473 }
474