1   /* Copyright 2002-2025 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.propagation.events;
18  
19  import org.hipparchus.geometry.euclidean.threed.Line;
20  import org.hipparchus.geometry.euclidean.threed.Vector3D;
21  import org.hipparchus.ode.events.Action;
22  import org.hipparchus.util.FastMath;
23  import org.junit.jupiter.api.Assertions;
24  import org.junit.jupiter.api.BeforeEach;
25  import org.junit.jupiter.api.Test;
26  import org.orekit.Utils;
27  import org.orekit.bodies.OneAxisEllipsoid;
28  import org.orekit.frames.Frame;
29  import org.orekit.frames.FramesFactory;
30  import org.orekit.frames.TopocentricFrame;
31  import org.orekit.orbits.CircularOrbit;
32  import org.orekit.orbits.PositionAngleType;
33  import org.orekit.propagation.Propagator;
34  import org.orekit.propagation.SpacecraftState;
35  import org.orekit.propagation.analytical.KeplerianPropagator;
36  import org.orekit.propagation.events.EventsLogger.LoggedEvent;
37  import org.orekit.propagation.events.handlers.EventHandler;
38  import org.orekit.time.AbsoluteDate;
39  import org.orekit.time.TimeScale;
40  import org.orekit.time.TimeScalesFactory;
41  import org.orekit.utils.Constants;
42  import org.orekit.utils.IERSConventions;
43  import org.orekit.utils.TrackingCoordinates;
44  
45  public class InterSatDirectViewDetectorTest {
46  
47      @Test
48      public void testFormationFlying() {
49          final OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
50                                                              Constants.WGS84_EARTH_FLATTENING,
51                                                              FramesFactory.getITRF(IERSConventions.IERS_2010, true));
52          final TimeScale utc = TimeScalesFactory.getUTC();
53          final CircularOrbit o1 = new CircularOrbit(7200000.0, 1.0e-3, 2.0e-4,
54                                                     FastMath.toRadians(98.7), FastMath.toRadians(134.0),
55                                                     FastMath.toRadians(21.0), PositionAngleType.MEAN, FramesFactory.getGCRF(),
56                                                     new AbsoluteDate("2003-02-14T01:02:03.000", utc),
57                                                     Constants.EIGEN5C_EARTH_MU);
58          final CircularOrbit o2 = new CircularOrbit(o1.getA(), 2.0e-4, 1.0e-3,
59                                                     o1.getI() + 1.0e-6, o1.getRightAscensionOfAscendingNode() - 3.5e-7,
60                                                     o1.getAlphaM() + 2.2e-6, PositionAngleType.MEAN, o1.getFrame(),
61                                                     o1.getDate(),
62                                                     Constants.EIGEN5C_EARTH_MU);
63          Assertions.assertEquals(o1.getKeplerianPeriod(), o2.getKeplerianPeriod(), 1.0e-10);
64          final Propagator p = new KeplerianPropagator(o1);
65          final EventsLogger logger = new EventsLogger();
66          p.addEventDetector(logger.monitorDetector(new InterSatDirectViewDetector(earth, o2).
67                                                    withMaxCheck(60.0)));
68          p.setStepHandler(10.0, state -> {
69              Vector3D pos1 = state.getPosition();
70              Vector3D pos2 = o2.getPosition(state.getDate(), state.getFrame());
71              Assertions.assertTrue(Vector3D.distance(pos1, pos2) >  8100.0);
72              Assertions.assertTrue(Vector3D.distance(pos1, pos2) < 16400.0);
73          });
74          p.propagate(o1.getDate().shiftedBy(o1.getKeplerianPeriod()));
75          Assertions.assertEquals(0, logger.getLoggedEvents().size());
76      }
77  
78      @Test
79      public void testLeoMeo() {
80          OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
81                                                        Constants.WGS84_EARTH_FLATTENING,
82                                                        FramesFactory.getITRF(IERSConventions.IERS_2010, true));
83          TimeScale utc = TimeScalesFactory.getUTC();
84          CircularOrbit o1 = new CircularOrbit(7200000.0, 1.0e-3, 2.0e-4,
85                                               FastMath.toRadians(50.0), FastMath.toRadians(134.0),
86                                               FastMath.toRadians(21.0), PositionAngleType.MEAN, FramesFactory.getGCRF(),
87                                               new AbsoluteDate("2003-02-14T01:02:03.000", utc),
88                                               Constants.EIGEN5C_EARTH_MU);
89          final CircularOrbit o2 = new CircularOrbit(29600000.0, 2.0e-4, 1.0e-3,
90                                                     FastMath.toRadians(56.0), FastMath.toRadians(111.0),
91                                                     o1.getAlphaM() + 2.2e-6, PositionAngleType.MEAN, o1.getFrame(),
92                                                     o1.getDate(),
93                                                     Constants.EIGEN5C_EARTH_MU);
94  
95          // LEO as primary, MEO as secondary
96          Propagator pA = new KeplerianPropagator(o1);
97          EventsLogger loggerA = new EventsLogger();
98          pA.addEventDetector(loggerA.monitorDetector(new InterSatDirectViewDetector(earth, o2).
99                                                      withMaxCheck(10.0).
100                                                     withHandler(new GrazingHandler())));
101         pA.propagate(o1.getDate().shiftedBy(4 * o1.getKeplerianPeriod()));
102         Assertions.assertEquals(7, loggerA.getLoggedEvents().size());
103 
104         // LEO as secondary, MEO as primary
105         Propagator pB = new KeplerianPropagator(o2);
106         EventsLogger loggerB = new EventsLogger();
107         pB.addEventDetector(loggerB.monitorDetector(new InterSatDirectViewDetector(earth, o1).
108                                                     withMaxCheck(10.0).
109                                                     withHandler(new GrazingHandler())));
110         pB.propagate(o1.getDate().shiftedBy(4 * o1.getKeplerianPeriod()));
111         Assertions.assertEquals(7, loggerB.getLoggedEvents().size());
112 
113     }
114 
115     @Test
116     public void testSkimmingAltitude() {
117         OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
118                                                       Constants.WGS84_EARTH_FLATTENING,
119                                                       FramesFactory.getITRF(IERSConventions.IERS_2010, true));
120         TimeScale utc = TimeScalesFactory.getUTC();
121         CircularOrbit o1 = new CircularOrbit(7200000.0, 1.0e-3, 2.0e-4,
122                                              FastMath.toRadians(50.0), FastMath.toRadians(134.0),
123                                              FastMath.toRadians(21.0), PositionAngleType.MEAN, FramesFactory.getGCRF(),
124                                              new AbsoluteDate("2003-02-14T01:02:03.000", utc),
125                                              Constants.EIGEN5C_EARTH_MU);
126         final CircularOrbit o2 = new CircularOrbit(29600000.0, 2.0e-4, 1.0e-3,
127                                                    FastMath.toRadians(56.0), FastMath.toRadians(111.0),
128                                                    o1.getAlphaM() + 2.2e-6, PositionAngleType.MEAN, o1.getFrame(),
129                                                    o1.getDate(),
130                                                    Constants.EIGEN5C_EARTH_MU);
131 
132         // skimming altitude on ground
133         Propagator pA = new KeplerianPropagator(o1);
134         EventsLogger loggerA = new EventsLogger();
135         pA.addEventDetector(loggerA.monitorDetector(new InterSatDirectViewDetector(earth, o2).
136                                                     withMaxCheck(10.0)));
137         pA.propagate(o1.getDate().shiftedBy(4 * o1.getKeplerianPeriod()));
138         Assertions.assertEquals(7, loggerA.getLoggedEvents().size());
139 
140         // skimming altitude at 500km
141         Propagator pB = new KeplerianPropagator(o2);
142         EventsLogger loggerB = new EventsLogger();
143         pB.addEventDetector(loggerB.monitorDetector(new InterSatDirectViewDetector(earth, o1).
144                                                     withMaxCheck(10.0).
145                                                     withSkimmingAltitude(500000.0)));
146         pB.propagate(o1.getDate().shiftedBy(4 * o1.getKeplerianPeriod()));
147         Assertions.assertEquals(7, loggerB.getLoggedEvents().size());
148 
149         for (int i = 0; i < loggerA.getLoggedEvents().size(); ++i) {
150             final LoggedEvent leA = loggerA.getLoggedEvents().get(i);
151             final LoggedEvent leB = loggerB.getLoggedEvents().get(i);
152             if (leA.isIncreasing()) {
153                 // this is an inter-visibility start
154                 // it should start earlier with skimming altitude at 0km
155                 Assertions.assertTrue(leA.getDate().isBefore(leB));
156             } else {
157                 // this is an inter-visibility end
158                 // it should end earlier with skimming altitude at 500km
159                 Assertions.assertTrue(leB.getDate().isBefore(leA));                
160             }
161         }
162 
163     }
164 
165     private static class GrazingHandler implements EventHandler {
166         public Action eventOccurred(SpacecraftState s, EventDetector detector, boolean increasing) {
167             // just before increasing events and just after decreasing events,
168             // the primary/secondary line intersects Earth limb
169             final InterSatDirectViewDetector isdv = (InterSatDirectViewDetector) detector;
170             final OneAxisEllipsoid earth       = isdv.getCentralBody();
171             final Frame            frame       = earth.getBodyFrame();
172             final double           dt          = increasing ? -1.0e-8 : +1.0e-8;
173             final AbsoluteDate     grazingDate = s.getDate().shiftedBy(dt);
174             final Vector3D pPrimary = s.shiftedBy(dt).getPosition(frame);
175             final Vector3D psecondary  = isdv.getSecondary().getPosition(grazingDate, frame);
176             final Vector3D grazing = earth.getCartesianIntersectionPoint(new Line(pPrimary,  psecondary, 1.0),
177                                                                          pPrimary, frame, grazingDate);
178             final TopocentricFrame topo = new TopocentricFrame(earth, earth.transform(grazing, frame, grazingDate),
179                                                                "grazing");
180             final TrackingCoordinates tcPrimary   = topo.getTrackingCoordinates(pPrimary, frame, grazingDate);
181             final TrackingCoordinates tcSecondary = topo.getTrackingCoordinates(psecondary, frame, grazingDate);
182             Assertions.assertEquals(  0.0, FastMath.toDegrees(tcPrimary.getElevation()), 2.0e-4);
183             Assertions.assertEquals(  0.0, FastMath.toDegrees(tcSecondary.getElevation()), 2.0e-4);
184             Assertions.assertEquals(180.0,
185                                 FastMath.abs(FastMath.toDegrees(tcSecondary.getAzimuth() - tcPrimary.getAzimuth())),
186                                 6.0e-14);
187             return Action.CONTINUE;
188         }
189     }
190 
191     @BeforeEach
192     public void setUp() {
193         Utils.setDataRoot("regular-data");
194     }
195 
196 }
197