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.Vector3D;
20  import org.hipparchus.ode.events.Action;
21  import org.junit.jupiter.api.AfterEach;
22  import org.junit.jupiter.api.Assertions;
23  import org.junit.jupiter.api.BeforeEach;
24  import org.junit.jupiter.api.Test;
25  import org.junit.jupiter.params.ParameterizedTest;
26  import org.junit.jupiter.params.provider.EnumSource;
27  import org.mockito.Mockito;
28  import org.orekit.Utils;
29  import org.orekit.bodies.CelestialBodyFactory;
30  import org.orekit.bodies.OneAxisEllipsoid;
31  import org.orekit.errors.OrekitException;
32  import org.orekit.frames.FramesFactory;
33  import org.orekit.orbits.EquinoctialOrbit;
34  import org.orekit.orbits.Orbit;
35  import org.orekit.propagation.Propagator;
36  import org.orekit.propagation.SpacecraftState;
37  import org.orekit.propagation.analytical.KeplerianPropagator;
38  import org.orekit.propagation.events.handlers.EventHandler;
39  import org.orekit.time.AbsoluteDate;
40  import org.orekit.time.TimeScalesFactory;
41  import org.orekit.utils.Constants;
42  import org.orekit.utils.IERSConventions;
43  import org.orekit.utils.PVCoordinates;
44  
45  class EventSlopeFilterTest {
46  
47      private AbsoluteDate     iniDate;
48      private Propagator       propagator;
49      private OneAxisEllipsoid earth;
50  
51      private double sunRadius = 696000000.;
52      private double earthRadius = 6400000.;
53  
54      @ParameterizedTest
55      @EnumSource(FilterType.class)
56      void testWithDetectionSettings(final FilterType filterType) {
57          // GIVEN
58          final DateDetector detector = new DateDetector();
59          final EventSlopeFilter<DateDetector> template = new EventSlopeFilter<>(detector, filterType);
60          final EventDetectionSettings detectionSettings = Mockito.mock();
61          // WHEN
62          final EventSlopeFilter<DateDetector> eventSlopeFilter = template.withDetectionSettings(detectionSettings);
63          // THEN
64          Assertions.assertEquals(detector, eventSlopeFilter.getDetector());
65          Assertions.assertEquals(detectionSettings, eventSlopeFilter.getDetectionSettings());
66      }
67  
68      @Test
69      void testInit() {
70          // GIVEN
71          final TestHandler handler = new TestHandler();
72          final EventSlopeFilter<DateDetector> slopeFilter = new EventSlopeFilter<>(new DateDetector().withHandler(handler),
73                  FilterType.TRIGGER_ONLY_INCREASING_EVENTS);
74          final SpacecraftState mockedState = Mockito.mock();
75          Mockito.when(mockedState.getDate()).thenReturn(AbsoluteDate.ARBITRARY_EPOCH);
76          // WHEN
77          slopeFilter.init(mockedState, AbsoluteDate.ARBITRARY_EPOCH);
78          // THEN
79          Assertions.assertTrue(handler.initialized);
80      }
81  
82      @Test
83      void testFinish() {
84          // GIVEN
85          final TestHandler handler = new TestHandler();
86          final EventSlopeFilter<DateDetector> slopeFilter = new EventSlopeFilter<>(new DateDetector().withHandler(handler),
87                  FilterType.TRIGGER_ONLY_INCREASING_EVENTS);
88          final SpacecraftState mockedState = Mockito.mock();
89          // WHEN
90          slopeFilter.finish(mockedState);
91          // THEN
92          Assertions.assertTrue(handler.finished);
93      }
94  
95      private static class TestHandler implements EventHandler {
96          boolean initialized = false;
97          boolean finished = false;
98  
99          @Override
100         public void init(SpacecraftState initialState, AbsoluteDate target, EventDetector detector) {
101             EventHandler.super.init(initialState, target, detector);
102             initialized = true;
103         }
104 
105         @Override
106         public void finish(SpacecraftState finalState, EventDetector detector) {
107             EventHandler.super.finish(finalState, detector);
108             finished = true;
109         }
110 
111         @Override
112         public Action eventOccurred(SpacecraftState s, EventDetector detector, boolean increasing) {
113             return null;
114         }
115     }
116 
117     @Test
118     void testEnums() {
119         // this test is here only for test coverage ...
120 
121         Assertions.assertEquals(5, Transformer.values().length);
122         Assertions.assertSame(Transformer.UNINITIALIZED, Transformer.valueOf("UNINITIALIZED"));
123         Assertions.assertSame(Transformer.PLUS,          Transformer.valueOf("PLUS"));
124         Assertions.assertSame(Transformer.MINUS,         Transformer.valueOf("MINUS"));
125         Assertions.assertSame(Transformer.MIN,           Transformer.valueOf("MIN"));
126         Assertions.assertSame(Transformer.MAX,           Transformer.valueOf("MAX"));
127 
128         Assertions.assertEquals(2, FilterType.values().length);
129         Assertions.assertSame(FilterType.TRIGGER_ONLY_DECREASING_EVENTS,
130                           FilterType.valueOf("TRIGGER_ONLY_DECREASING_EVENTS"));
131         Assertions.assertSame(FilterType.TRIGGER_ONLY_INCREASING_EVENTS,
132                           FilterType.valueOf("TRIGGER_ONLY_INCREASING_EVENTS"));
133 
134     }
135 
136     @Test
137     void testReplayForward() {
138         EclipseDetector detector =
139                 new EclipseDetector(CelestialBodyFactory.getSun(), sunRadius,
140                                      new OneAxisEllipsoid(earthRadius,
141                                                           0.0,
142                                                           FramesFactory.getITRF(IERSConventions.IERS_2010, true))).
143                 withMaxCheck(60.0).
144                 withThreshold(1.0e-3).
145                 withPenumbra().withHandler(new Counter());
146         final EventDetectionSettings settings = EventDetectionSettings.getDefaultEventDetectionSettings().withMaxIter(200);
147         final EventSlopeFilter<EclipseDetector> filter =
148                 new EventSlopeFilter<>(detector, FilterType.TRIGGER_ONLY_INCREASING_EVENTS).
149                 withDetectionSettings(settings);
150         Assertions.assertSame(detector, filter.getDetector());
151         Assertions.assertEquals(200, filter.getMaxIterationCount());
152 
153         propagator.clearEventsDetectors();
154         propagator.addEventDetector(filter);
155         propagator.propagate(iniDate, iniDate.shiftedBy(7 * Constants.JULIAN_DAY));
156         Assertions.assertEquals(102, ((Counter) detector.getHandler()).getIncreasingCounter());
157         Assertions.assertEquals( 0, ((Counter) detector.getHandler()).getDecreasingCounter());
158         ((Counter) detector.getHandler()).reset();
159 
160         propagator.clearEventsDetectors();
161         propagator.setStepHandler(10.0, currentState -> {
162             // we exceed the events history in the past,
163             // and in this example get stuck with Transformer.MAX
164             // transformer, hence the g function is always positive
165             // in the test range
166             Assertions.assertTrue(filter.g(currentState) > 0);
167         });
168         propagator.propagate(iniDate.shiftedBy(-3600), iniDate.shiftedBy(Constants.JULIAN_DAY + 3600));
169     }
170 
171     @Test
172     void testReplayBackward() {
173         EclipseDetector detector =
174                         new EclipseDetector(CelestialBodyFactory.getSun(), sunRadius,
175                                             new OneAxisEllipsoid(earthRadius,
176                                                                  0.0,
177                                                                  FramesFactory.getITRF(IERSConventions.IERS_2010, true))).
178                        withMaxCheck(60.0).
179                        withThreshold(1.0e-3).
180                        withPenumbra().
181                        withHandler(new Counter());
182         final EventDetectionSettings settings = EventDetectionSettings.getDefaultEventDetectionSettings().withMaxIter(200);
183         final EventSlopeFilter<EclipseDetector> filter =
184                 new EventSlopeFilter<>(detector, FilterType.TRIGGER_ONLY_DECREASING_EVENTS).
185                 withDetectionSettings(settings);
186         Assertions.assertEquals(200, filter.getMaxIterationCount());
187 
188         propagator.clearEventsDetectors();
189         propagator.addEventDetector(filter);
190         propagator.propagate(iniDate.shiftedBy(7 * Constants.JULIAN_DAY), iniDate);
191         Assertions.assertEquals(  0, ((Counter) detector.getHandler()).getIncreasingCounter());
192         Assertions.assertEquals(102, ((Counter) detector.getHandler()).getDecreasingCounter());
193         ((Counter) detector.getHandler()).reset();
194 
195         propagator.clearEventsDetectors();
196         propagator.setStepHandler(10.0, currentState -> {
197                 // we exceed the events history in the past,
198                 // and in this example get stuck with Transformer.MIN
199                 // transformer, hence the g function is always negative
200                 // in the test range
201                 Assertions.assertTrue(filter.g(currentState) < 0);
202             });
203         propagator.propagate(iniDate.shiftedBy(7 * Constants.JULIAN_DAY + 3600),
204                              iniDate.shiftedBy(6 * Constants.JULIAN_DAY + 3600));
205     }
206 
207     @Test
208     void testUmbra() {
209         EclipseDetector detector =
210                         new EclipseDetector(CelestialBodyFactory.getSun(), sunRadius,
211                                             new OneAxisEllipsoid(earthRadius,
212                                                                  0.0,
213                                                                  FramesFactory.getITRF(IERSConventions.IERS_2010, true))).
214                        withMaxCheck(60.0).
215                        withThreshold(1.0e-3).
216                        withPenumbra().
217                        withHandler(new Counter());
218 
219         propagator.clearEventsDetectors();
220         propagator.addEventDetector(detector);
221         propagator.propagate(iniDate, iniDate.shiftedBy(Constants.JULIAN_DAY));
222         Assertions.assertEquals(14, ((Counter) detector.getHandler()).getIncreasingCounter());
223         Assertions.assertEquals(15, ((Counter) detector.getHandler()).getDecreasingCounter());
224         ((Counter) detector.getHandler()).reset();
225 
226         propagator.clearEventsDetectors();
227         propagator.addEventDetector(new EventSlopeFilter<>(detector, FilterType.TRIGGER_ONLY_INCREASING_EVENTS));
228         propagator.propagate(iniDate, iniDate.shiftedBy(Constants.JULIAN_DAY));
229         Assertions.assertEquals(14, ((Counter) detector.getHandler()).getIncreasingCounter());
230         Assertions.assertEquals( 0, ((Counter) detector.getHandler()).getDecreasingCounter());
231         ((Counter) detector.getHandler()).reset();
232 
233         propagator.clearEventsDetectors();
234         propagator.addEventDetector(new EventSlopeFilter<>(detector, FilterType.TRIGGER_ONLY_DECREASING_EVENTS));
235         propagator.propagate(iniDate, iniDate.shiftedBy(Constants.JULIAN_DAY));
236         Assertions.assertEquals( 0, ((Counter) detector.getHandler()).getIncreasingCounter());
237         Assertions.assertEquals(15, ((Counter) detector.getHandler()).getDecreasingCounter());
238 
239     }
240 
241     @Test
242     void testPenumbra() {
243         EclipseDetector detector =
244                         new EclipseDetector(CelestialBodyFactory.getSun(), sunRadius,
245                                             new OneAxisEllipsoid(earthRadius,
246                                                                  0.0,
247                                                                  FramesFactory.getITRF(IERSConventions.IERS_2010, true))).
248                        withMaxCheck(60.0).
249                        withThreshold(1.0e-3).
250                        withPenumbra().
251                        withHandler(new Counter());
252 
253         propagator.clearEventsDetectors();
254         propagator.addEventDetector(detector);
255         propagator.propagate(iniDate, iniDate.shiftedBy(Constants.JULIAN_DAY));
256         Assertions.assertEquals(14, ((Counter) detector.getHandler()).getIncreasingCounter());
257         Assertions.assertEquals(15, ((Counter) detector.getHandler()).getDecreasingCounter());
258         ((Counter) detector.getHandler()).reset();
259 
260         propagator.clearEventsDetectors();
261         final EventSlopeFilter<EclipseDetector> outOfEclipseDetector =
262               new EventSlopeFilter<>(detector, FilterType.TRIGGER_ONLY_INCREASING_EVENTS);
263         propagator.addEventDetector(outOfEclipseDetector);
264         propagator.propagate(iniDate, iniDate.shiftedBy(Constants.JULIAN_DAY));
265         Assertions.assertEquals(14, ((Counter) detector.getHandler()).getIncreasingCounter());
266         Assertions.assertEquals(0, ((Counter) detector.getHandler()).getDecreasingCounter());
267         Assertions.assertEquals(FilterType.TRIGGER_ONLY_INCREASING_EVENTS, outOfEclipseDetector.getFilterType());
268         ((Counter) detector.getHandler()).reset();
269 
270         propagator.clearEventsDetectors();
271         final EventSlopeFilter<EclipseDetector> enteringEclipseDetector =
272               new EventSlopeFilter<>(detector, FilterType.TRIGGER_ONLY_DECREASING_EVENTS);
273         propagator.addEventDetector(enteringEclipseDetector);
274         propagator.propagate(iniDate, iniDate.shiftedBy(Constants.JULIAN_DAY));
275         Assertions.assertEquals(0, ((Counter) detector.getHandler()).getIncreasingCounter());
276         Assertions.assertEquals(15, ((Counter) detector.getHandler()).getDecreasingCounter());
277         Assertions.assertEquals(FilterType.TRIGGER_ONLY_DECREASING_EVENTS, enteringEclipseDetector.getFilterType());
278 
279     }
280 
281     @Test
282     void testForwardIncreasingStartPos() {
283 
284         SpacecraftState s = propagator.getInitialState();
285         double startLatitude = earth.transform(s.getPosition(),
286                                               s.getFrame(), s.getDate()).getLatitude();
287 
288         // at start time, the g function is positive
289         doTestLatitude(75500.0, startLatitude - 0.1, 12, FilterType.TRIGGER_ONLY_INCREASING_EVENTS);
290 
291     }
292 
293     @Test
294     void testForwardIncreasingStartZero() {
295 
296         SpacecraftState s = propagator.getInitialState();
297         double startLatitude = earth.transform(s.getPosition(),
298                                               s.getFrame(), s.getDate()).getLatitude();
299 
300         // at start time, the g function is exactly 0
301         doTestLatitude(75500.0, startLatitude, 12, FilterType.TRIGGER_ONLY_INCREASING_EVENTS);
302 
303     }
304 
305     @Test
306     void testForwardIncreasingStartNeg() {
307 
308         SpacecraftState s = propagator.getInitialState();
309         double startLatitude = earth.transform(s.getPosition(),
310                                               s.getFrame(), s.getDate()).getLatitude();
311 
312         // at start time, the g function is negative
313         doTestLatitude(75500.0, startLatitude + 0.1, 13, FilterType.TRIGGER_ONLY_INCREASING_EVENTS);
314 
315     }
316 
317     @Test
318     void testForwardDecreasingStartPos() {
319 
320         SpacecraftState s = propagator.getInitialState();
321         double startLatitude = earth.transform(s.getPosition(),
322                                               s.getFrame(), s.getDate()).getLatitude();
323 
324         // at start time, the g function is positive
325         doTestLatitude(75500.0, startLatitude - 0.1, 13, FilterType.TRIGGER_ONLY_DECREASING_EVENTS);
326     }
327 
328     @Test
329     void testForwardDecreasingStartZero() {
330 
331         SpacecraftState s = propagator.getInitialState();
332         double startLatitude = earth.transform(s.getPosition(),
333                                               s.getFrame(), s.getDate()).getLatitude();
334 
335         // at start time, the g function is exactly 0
336         doTestLatitude(75500.0, startLatitude, 13, FilterType.TRIGGER_ONLY_DECREASING_EVENTS);
337     }
338 
339     @Test
340     void testForwardDecreasingStartNeg() {
341 
342         SpacecraftState s = propagator.getInitialState();
343         double startLatitude = earth.transform(s.getPosition(),
344                                               s.getFrame(), s.getDate()).getLatitude();
345 
346         // at start time, the g function is negative
347         doTestLatitude(75500.0, startLatitude + 0.1, 13, FilterType.TRIGGER_ONLY_DECREASING_EVENTS);
348     }
349 
350     @Test
351     void testBackwardIncreasingStartPos() {
352 
353         SpacecraftState s = propagator.getInitialState();
354         double startLatitude = earth.transform(s.getPosition(),
355                                               s.getFrame(), s.getDate()).getLatitude();
356 
357         // at start time, the g function is positive
358         doTestLatitude(-75500.0, startLatitude - 0.1, 13, FilterType.TRIGGER_ONLY_INCREASING_EVENTS);
359 
360     }
361 
362     @Test
363     void testBackwardIncreasingStartZero() {
364 
365         SpacecraftState s = propagator.getInitialState();
366         double startLatitude = earth.transform(s.getPosition(),
367                                               s.getFrame(), s.getDate()).getLatitude();
368 
369         // at start time, the g function is exactly 0
370         doTestLatitude(-75500.0, startLatitude, 12, FilterType.TRIGGER_ONLY_INCREASING_EVENTS);
371 
372     }
373 
374     @Test
375     void testBackwardIncreasingStartNeg() {
376 
377         SpacecraftState s = propagator.getInitialState();
378         double startLatitude = earth.transform(s.getPosition(),
379                                               s.getFrame(), s.getDate()).getLatitude();
380 
381         // at start time, the g function is negative
382         doTestLatitude(-75500.0, startLatitude + 0.1, 12, FilterType.TRIGGER_ONLY_INCREASING_EVENTS);
383 
384     }
385 
386     @Test
387     void testBackwardDecreasingStartPos() {
388 
389         SpacecraftState s = propagator.getInitialState();
390         double startLatitude = earth.transform(s.getPosition(),
391                                               s.getFrame(), s.getDate()).getLatitude();
392 
393         // at start time, the g function is positive
394         doTestLatitude(-75500.0, startLatitude - 0.1, 13, FilterType.TRIGGER_ONLY_DECREASING_EVENTS);
395     }
396 
397     @Test
398     void testBackwardDecreasingStartZero() {
399 
400         SpacecraftState s = propagator.getInitialState();
401         double startLatitude = earth.transform(s.getPosition(),
402                                               s.getFrame(), s.getDate()).getLatitude();
403 
404         // at start time, the g function is exactly 0
405         doTestLatitude(-75500.0, startLatitude, 13, FilterType.TRIGGER_ONLY_DECREASING_EVENTS);
406     }
407 
408     @Test
409     void testBackwardDecreasingStartNeg() {
410 
411         SpacecraftState s = propagator.getInitialState();
412         double startLatitude = earth.transform(s.getPosition(),
413                                               s.getFrame(), s.getDate()).getLatitude();
414 
415         // at start time, the g function is negative
416         doTestLatitude(-75500.0, startLatitude + 0.1, 13, FilterType.TRIGGER_ONLY_DECREASING_EVENTS);
417     }
418 
419     private void doTestLatitude(final double dt, final double latitude, final int expected, final FilterType filter)
420         {
421         final int[] count = new int[2];
422         LatitudeCrossingDetector detector =
423                 new LatitudeCrossingDetector(earth, latitude).
424                 withMaxCheck(300.0).
425                 withMaxIter(100).
426                 withThreshold(1.0e-3).
427                 withHandler(new EventHandler() {
428 
429                     @Override
430                     public Action eventOccurred(SpacecraftState s, EventDetector detector, boolean increasing) {
431                         Assertions.assertEquals(filter.getTriggeredIncreasing(), increasing);
432                         count[0]++;
433                         return Action.RESET_STATE;
434                     }
435 
436                     @Override
437                     public SpacecraftState resetState(EventDetector detector, SpacecraftState oldState) {
438                         count[1]++;
439                         return oldState;
440                     }
441 
442                 });
443         Assertions.assertSame(earth, detector.getBody());
444         propagator.addEventDetector(new EventSlopeFilter<EventDetector>(detector, filter));
445         AbsoluteDate target = propagator.getInitialState().getDate().shiftedBy(dt);
446         SpacecraftState finalState = propagator.propagate(target);
447         Assertions.assertEquals(0.0, finalState.getDate().durationFrom(target), 1.0e-10);
448         Assertions.assertEquals(expected, count[0]);
449         Assertions.assertEquals(expected, count[1]);
450     }
451 
452     private static class Counter implements EventHandler {
453 
454         private int increasingCounter;
455         private int decreasingCounter;
456 
457         public Counter() {
458             reset();
459         }
460 
461         public void reset() {
462             increasingCounter = 0;
463             decreasingCounter = 0;
464         }
465 
466         @Override
467         public Action eventOccurred(SpacecraftState s, EventDetector ed, boolean increasing) {
468             if (increasing) {
469                 increasingCounter++;
470             } else {
471                 decreasingCounter++;
472             }
473             return Action.CONTINUE;
474         }
475 
476         public int getIncreasingCounter() {
477             return increasingCounter;
478         }
479 
480         public int getDecreasingCounter() {
481             return decreasingCounter;
482         }
483 
484     }
485 
486     @BeforeEach
487     public void setUp() {
488         try {
489             Utils.setDataRoot("regular-data");
490             double mu  = 3.9860047e14;
491             final Vector3D position  = new Vector3D(-6142438.668, 3492467.560, -25767.25680);
492             final Vector3D velocity  = new Vector3D(505.8479685, 942.7809215, 7435.922231);
493             iniDate = new AbsoluteDate(1969, 7, 28, 4, 0, 0.0, TimeScalesFactory.getTT());
494             final Orbit orbit = new EquinoctialOrbit(new PVCoordinates(position,  velocity),
495                                                      FramesFactory.getGCRF(), iniDate, mu);
496             propagator = new KeplerianPropagator(orbit, Utils.defaultLaw(), mu);
497             earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
498                                          Constants.WGS84_EARTH_FLATTENING,
499                                          FramesFactory.getITRF(IERSConventions.IERS_2010, true));
500 
501         } catch (OrekitException oe) {
502             Assertions.fail(oe.getLocalizedMessage());
503         }
504     }
505 
506     @AfterEach
507     public void tearDown() {
508         iniDate    = null;
509         propagator = null;
510         earth      = null;
511     }
512 
513 }
514