1   /* Copyright 2002-2021 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.hipparchus.util.FastMath;
22  import org.orekit.bodies.OneAxisEllipsoid;
23  import org.orekit.propagation.SpacecraftState;
24  import org.orekit.propagation.events.handlers.EventHandler;
25  import org.orekit.propagation.events.handlers.StopOnIncreasing;
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      /** Occulting body. */
54      private final OneAxisEllipsoid occulting;
55  
56      /** Occulted body. */
57      private final PVCoordinatesProvider occulted;
58  
59      /** Occulted body radius (m). */
60      private final double occultedRadius;
61  
62      /** Umbra, if true, or penumbra, if false, detection flag. */
63      private final boolean totalEclipse;
64  
65      /** Build a new eclipse detector.
66       * <p>The new instance is a total eclipse (umbra) detector with default
67       * values for maximal checking interval ({@link #DEFAULT_MAXCHECK})
68       * and convergence threshold ({@link #DEFAULT_THRESHOLD}).</p>
69       * @param occulted the body to be occulted
70       * @param occultedRadius the radius of the body to be occulted (m)
71       * @param occulting the occulting body
72       * @since 10.0
73       */
74      public EclipseDetector(final PVCoordinatesProvider occulted,  final double occultedRadius,
75                             final OneAxisEllipsoid occulting) {
76          this(DEFAULT_MAXCHECK, DEFAULT_THRESHOLD, DEFAULT_MAX_ITER,
77               new StopOnIncreasing<EclipseDetector>(),
78               occulted, occultedRadius, occulting, true);
79      }
80  
81      /** Private constructor with full parameters.
82       * <p>
83       * This constructor is private as users are expected to use the builder
84       * API with the various {@code withXxx()} methods to set up the instance
85       * in a readable manner without using a huge amount of parameters.
86       * </p>
87       * @param maxCheck maximum checking interval (s)
88       * @param threshold convergence threshold (s)
89       * @param maxIter maximum number of iterations in the event time search
90       * @param handler event handler to call at event occurrences
91       * @param occulted the body to be occulted
92       * @param occultedRadius the radius of the body to be occulted in meters
93       * @param occulting the occulting body
94       * @param totalEclipse umbra (true) or penumbra (false) detection flag
95       * @since 10.0
96       */
97      private EclipseDetector(final double maxCheck, final double threshold,
98                              final int maxIter, final EventHandler<? super EclipseDetector> handler,
99                              final PVCoordinatesProvider occulted,  final double occultedRadius,
100                             final OneAxisEllipsoid occulting, final boolean totalEclipse) {
101         super(maxCheck, threshold, maxIter, handler);
102         this.occulted       = occulted;
103         this.occultedRadius = FastMath.abs(occultedRadius);
104         this.occulting      = occulting;
105         this.totalEclipse   = totalEclipse;
106     }
107 
108     /** {@inheritDoc} */
109     @Override
110     protected EclipseDetector create(final double newMaxCheck, final double newThreshold,
111                                      final int nawMaxIter, final EventHandler<? super EclipseDetector> newHandler) {
112         return new EclipseDetector(newMaxCheck, newThreshold, nawMaxIter, newHandler,
113                                    occulted, occultedRadius, occulting, totalEclipse);
114     }
115 
116     /**
117      * Setup the detector to full umbra detection.
118      * <p>
119      * This will override a penumbra/umbra flag if it has been configured previously.
120      * </p>
121      * @return a new detector with updated configuration (the instance is not changed)
122      * @see #withPenumbra()
123      * @since 6.1
124      */
125     public EclipseDetector withUmbra() {
126         return new EclipseDetector(getMaxCheckInterval(), getThreshold(), getMaxIterationCount(), getHandler(),
127                                    occulted, occultedRadius, occulting, true);
128     }
129 
130     /**
131      * Setup the detector to penumbra detection.
132      * <p>
133      * This will override a penumbra/umbra flag if it has been configured previously.
134      * </p>
135      * @return a new detector with updated configuration (the instance is not changed)
136      * @see #withUmbra()
137      * @since 6.1
138      */
139     public EclipseDetector withPenumbra() {
140         return new EclipseDetector(getMaxCheckInterval(), getThreshold(), getMaxIterationCount(), getHandler(),
141                                    occulted, occultedRadius, occulting, false);
142     }
143 
144     /** Getter for the occulting body.
145      * @return the occulting body
146      */
147     public OneAxisEllipsoid getOcculting() {
148         return occulting;
149     }
150 
151     /** Getter for the occulted body.
152      * @return the occulted body
153      */
154     public PVCoordinatesProvider getOcculted() {
155         return occulted;
156     }
157 
158     /** Getter for the occultedRadius.
159      * @return the occultedRadius
160      */
161     public double getOccultedRadius() {
162         return occultedRadius;
163     }
164 
165     /** Get the total eclipse detection flag.
166      * @return the total eclipse detection flag (true for umbra events detection,
167      * false for penumbra events detection)
168      */
169     public boolean getTotalEclipse() {
170         return totalEclipse;
171     }
172 
173     /** Compute the value of the switching function.
174      * This function becomes negative when entering the region of shadow
175      * and positive when exiting.
176      * @param s the current state information: date, kinematics, attitude
177      * @return value of the switching function
178      */
179     public double g(final SpacecraftState s) {
180         final Vector3D pted  = occulted.getPVCoordinates(s.getDate(), occulting.getBodyFrame()).getPosition();
181         final Vector3D psat  = s.getPVCoordinates(occulting.getBodyFrame()).getPosition();
182         final Vector3D plimb = occulting.pointOnLimb(psat, pted);
183         final Vector3D ps    = psat.subtract(pted);
184         final Vector3D pi    = psat.subtract(plimb);
185         final double angle   = Vector3D.angle(ps, psat);
186         final double rs      = FastMath.asin(occultedRadius / ps.getNorm());
187         if (Double.isNaN(rs)) {
188             return FastMath.PI;
189         }
190         final double ro = Vector3D.angle(pi, psat);
191         return totalEclipse ? (angle - ro + rs) : (angle - ro - rs);
192     }
193 }