1   /* Copyright 2002-2023 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.ode.events.Action;
20  import org.orekit.bodies.OneAxisEllipsoid;
21  import org.orekit.propagation.SpacecraftState;
22  import org.orekit.propagation.events.handlers.EventHandler;
23  import org.orekit.propagation.events.handlers.StopOnIncreasing;
24  import org.orekit.utils.ExtendedPVCoordinatesProvider;
25  import org.orekit.utils.OccultationEngine;
26  import org.orekit.utils.PVCoordinatesProvider;
27  
28  /** Finder for satellite eclipse related events.
29   * <p>This class finds eclipse events, i.e. satellite within umbra (total
30   * eclipse) or penumbra (partial eclipse).</p>
31   * <p>The occulted body is given through a {@link PVCoordinatesProvider} and its radius in meters. It is modeled as a sphere.
32   * </p>
33   * <p>Since v10.0 the occulting body is a {@link OneAxisEllipsoid}, before it was modeled as a  sphere.
34   * <br>It was changed to precisely model Solar eclipses by the Earth, especially for Low Earth Orbits.
35   * <br>If you want eclipses by a spherical occulting body, set its flattening to 0. when defining its OneAxisEllipsoid model..
36   * </p>
37   * <p>The {@link #withUmbra} or {@link #withPenumbra} methods will tell you if the event is triggered when complete umbra/lighting
38   * is achieved or when entering/living the penumbra zone.
39   * <br>The default behavior is detecting complete umbra/lighting events.
40   * <br>If you want to have both, you'll need to set up two distinct detectors.
41   * </p>
42   * <p>The default implementation behavior is to {@link Action#CONTINUE continue}
43   * propagation when entering the eclipse and to {@link Action#STOP stop} propagation
44   * when exiting the eclipse.
45   * <br>This can be changed by calling {@link #withHandler(EventHandler)} after construction.
46   * </p>
47   * @see org.orekit.propagation.Propagator#addEventDetector(EventDetector)
48   * @author Pascal Parraud
49   * @author Luc Maisonobe
50   */
51  public class EclipseDetector extends AbstractDetector<EclipseDetector> {
52  
53      /** Occultation engine.
54       * @since 12.0
55       */
56      private final OccultationEngine occultationEngine;
57  
58      /** Umbra, if true, or penumbra, if false, detection flag. */
59      private final boolean totalEclipse;
60  
61      /** Margin to apply to eclipse angle. */
62      private final double margin;
63  
64      /** Build a new eclipse detector.
65       * <p>The new instance is a total eclipse (umbra) detector with default
66       * values for maximal checking interval ({@link #DEFAULT_MAXCHECK})
67       * and convergence threshold ({@link #DEFAULT_THRESHOLD}).</p>
68       * @param occulted the body to be occulted
69       * @param occultedRadius the radius of the body to be occulted (m)
70       * @param occulting the occulting body
71       * @since 12.0
72       */
73      public EclipseDetector(final ExtendedPVCoordinatesProvider occulted,  final double occultedRadius,
74                             final OneAxisEllipsoid occulting) {
75          this(new OccultationEngine(occulted, occultedRadius, occulting));
76      }
77  
78      /** Build a new eclipse detector.
79       * <p>The new instance is a total eclipse (umbra) detector with default
80       * values for maximal checking interval ({@link #DEFAULT_MAXCHECK})
81       * and convergence threshold ({@link #DEFAULT_THRESHOLD}).</p>
82       * @param occultationEngine occultation engine
83       * @since 12.0
84       */
85      public EclipseDetector(final OccultationEngine occultationEngine) {
86          this(s -> DEFAULT_MAXCHECK, DEFAULT_THRESHOLD, DEFAULT_MAX_ITER,
87               new StopOnIncreasing(),
88               occultationEngine, 0.0, true);
89      }
90  
91      /** Protected constructor with full parameters.
92       * <p>
93       * This constructor is not public as users are expected to use the builder
94       * API with the various {@code withXxx()} methods to set up the instance
95       * in a readable manner without using a huge amount of parameters.
96       * </p>
97       * @param maxCheck maximum checking interval
98       * @param threshold convergence threshold (s)
99       * @param maxIter maximum number of iterations in the event time search
100      * @param handler event handler to call at event occurrences
101      * @param occultationEngine occultation engine
102      * @param margin to apply to eclipse angle (rad)
103      * @param totalEclipse umbra (true) or penumbra (false) detection flag
104      * @since 12.0
105      */
106     protected EclipseDetector(final AdaptableInterval maxCheck, final double threshold,
107                               final int maxIter, final EventHandler handler,
108                               final OccultationEngine occultationEngine, final double margin, final boolean totalEclipse) {
109         super(maxCheck, threshold, maxIter, handler);
110         this.occultationEngine = occultationEngine;
111         this.margin            = margin;
112         this.totalEclipse      = totalEclipse;
113     }
114 
115     /** {@inheritDoc} */
116     @Override
117     protected EclipseDetector create(final AdaptableInterval newMaxCheck, final double newThreshold,
118                                      final int nawMaxIter, final EventHandler newHandler) {
119         return new EclipseDetector(newMaxCheck, newThreshold, nawMaxIter, newHandler,
120                                    occultationEngine, margin, totalEclipse);
121     }
122 
123     /**
124      * Setup the detector to full umbra detection.
125      * <p>
126      * This will override a penumbra/umbra flag if it has been configured previously.
127      * </p>
128      * @return a new detector with updated configuration (the instance is not changed)
129      * @see #withPenumbra()
130      * @since 6.1
131      */
132     public EclipseDetector withUmbra() {
133         return new EclipseDetector(getMaxCheckInterval(), getThreshold(), getMaxIterationCount(), getHandler(),
134                                    occultationEngine, margin, true);
135     }
136 
137     /**
138      * Setup the detector to penumbra detection.
139      * <p>
140      * This will override a penumbra/umbra flag if it has been configured previously.
141      * </p>
142      * @return a new detector with updated configuration (the instance is not changed)
143      * @see #withUmbra()
144      * @since 6.1
145      */
146     public EclipseDetector withPenumbra() {
147         return new EclipseDetector(getMaxCheckInterval(), getThreshold(), getMaxIterationCount(), getHandler(),
148                                    occultationEngine, margin, false);
149     }
150 
151     /**
152      * Setup a margin to angle detection.
153      * <p>
154      * A positive margin implies eclipses are "larger" hence entry occurs earlier and exit occurs later
155      * than a detector with 0 margin.
156      * </p>
157      * @param newMargin angular margin to apply to eclipse detection (rad)
158      * @return a new detector with updated configuration (the instance is not changed)
159      * @since 12.0
160      */
161     public EclipseDetector withMargin(final double newMargin) {
162         return new EclipseDetector(getMaxCheckInterval(), getThreshold(), getMaxIterationCount(), getHandler(),
163                                    occultationEngine, newMargin, totalEclipse);
164     }
165 
166     /** Get the angular margin used for eclipse detection.
167      * @return angular margin used for eclipse detection (rad)
168      * @since 12.0
169      */
170     public double getMargin() {
171         return margin;
172     }
173 
174     /** Get the occultation engine.
175      * @return occultation engine
176      * @since 12.0
177      */
178     public OccultationEngine getOccultationEngine() {
179         return occultationEngine;
180     }
181 
182     /** Get the total eclipse detection flag.
183      * @return the total eclipse detection flag (true for umbra events detection,
184      * false for penumbra events detection)
185      */
186     public boolean getTotalEclipse() {
187         return totalEclipse;
188     }
189 
190     /** Compute the value of the switching function.
191      * This function becomes negative when entering the region of shadow
192      * and positive when exiting.
193      * @param s the current state information: date, kinematics, attitude
194      * @return value of the switching function
195      */
196     public double g(final SpacecraftState s) {
197         final OccultationEngine.OccultationAngles angles = occultationEngine.angles(s);
198         return totalEclipse ?
199                (angles.getSeparation() - angles.getLimbRadius() + angles.getOccultedApparentRadius() + margin) :
200                (angles.getSeparation() - angles.getLimbRadius() - angles.getOccultedApparentRadius() + margin);
201     }
202 
203 }