1   /* Copyright 2002-2025 Joseph Reed
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    * Joseph Reed 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.MathUtils;
22  import org.orekit.annotation.DefaultDataContext;
23  import org.orekit.bodies.CelestialBodyFactory;
24  import org.orekit.frames.Frame;
25  import org.orekit.frames.FramesFactory;
26  import org.orekit.propagation.SpacecraftState;
27  import org.orekit.propagation.events.handlers.EventHandler;
28  import org.orekit.propagation.events.handlers.StopOnEvent;
29  import org.orekit.utils.PVCoordinatesProvider;
30  import org.orekit.utils.TimeStampedPVCoordinates;
31  
32  /** Finder for beta angle crossing events.
33   * <p>Locate events when the beta angle (the angle between the orbit plane and the celestial body)
34   * crosses a threshold. The {@link #g(SpacecraftState)} function is negative when the beta angle
35   * is above the threshold and positive when the beta angle is below the threshold.</p>
36   * <p>The inertial frame provided must have it's origin centered at the satellite's orbit plane. The
37   * beta angle is computed as the angle between the celestial body's position in this frame with the
38   * satellite's orbital momentum vector.</p>
39   * <p>The default implementation behavior is to {@link Action#STOP stop}
40   * propagation at the first event date occurrence. This can be changed by calling
41   * {@link #withHandler(EventHandler)} after construction.</p>
42   * @see org.orekit.propagation.Propagator#addEventDetector(EventDetector)
43   * @author Joe Reed
44   * @since 12.1
45   */
46  public class BetaAngleDetector extends AbstractDetector<BetaAngleDetector> {
47  
48      /** Beta angle crossing threshold. */
49      private final double betaAngleThreshold;
50      /** Coordinate provider for the celestial body. */
51      private final PVCoordinatesProvider celestialBodyProvider;
52      /** Inertial frame in which beta angle is calculated. */
53      private final Frame inertialFrame;
54  
55      /**Solar beta angle constructor.
56       * <p>This method uses the default data context, assigns the sun as the celestial
57       * body and uses GCRF as the inertial frame.</p>
58       * @param betaAngleThreshold beta angle threshold (radians)
59       */
60      @DefaultDataContext
61      public BetaAngleDetector(final double betaAngleThreshold) {
62          this(betaAngleThreshold, CelestialBodyFactory.getSun(), FramesFactory.getGCRF());
63      }
64  
65      /** Class constructor.
66       * @param betaAngleThreshold beta angle threshold (radians)
67       * @param celestialBodyProvider coordinate provider for the celestial provider
68       * @param inertialFrame inertial frame in which to compute the beta angle
69       */
70      public BetaAngleDetector(final double betaAngleThreshold, final PVCoordinatesProvider celestialBodyProvider,
71              final Frame inertialFrame) {
72          this(EventDetectionSettings.getDefaultEventDetectionSettings(), new StopOnEvent(),
73                  betaAngleThreshold, celestialBodyProvider, inertialFrame);
74      }
75  
76      /** Protected constructor with full parameters.
77       * <p>This constructor is not public as users are expected to use the builder
78       * API with the various {@code withXxx()} methods to set up the instance
79       * in a readable manner without using a huge amount of parameters.</p>
80       * @param detectionSettings detection settings
81       * @param handler event handler to call at event occurrences
82       * @param betaAngleThreshold beta angle threshold (radians)
83       * @param celestialBodyProvider coordinate provider for the celestial provider
84       * @param inertialFrame inertial frame in which to compute the beta angle
85       */
86      protected BetaAngleDetector(final EventDetectionSettings detectionSettings, final EventHandler handler,
87                               final double betaAngleThreshold, final PVCoordinatesProvider celestialBodyProvider,
88                               final Frame inertialFrame) {
89          super(detectionSettings, handler);
90          this.betaAngleThreshold = betaAngleThreshold;
91          this.celestialBodyProvider = celestialBodyProvider;
92          this.inertialFrame = inertialFrame;
93      }
94  
95      /** Coordinate provider for the celestial body.
96       * @return celestial body's coordinate provider
97       */
98      public PVCoordinatesProvider getCelestialBodyProvider() {
99          return this.celestialBodyProvider;
100     }
101 
102     /** The inertial frame in which beta angle is computed.
103      * @return the inertial frame
104      */
105     public Frame getInertialFrame() {
106         return this.inertialFrame;
107     }
108 
109     /** The beta angle threshold (radians).
110      * @return the beta angle threshold (radians)
111      */
112     public double getBetaAngleThreshold() {
113         return this.betaAngleThreshold;
114     }
115 
116     /** Create a new instance with the provided coordinate provider.
117      * <p>This method does not change the current instance.</p>
118      * @param newProvider the new coordinate provider
119      * @return the new detector instance
120      */
121     public BetaAngleDetector withCelestialProvider(final PVCoordinatesProvider newProvider) {
122         return new BetaAngleDetector(getDetectionSettings(),
123                 getHandler(), getBetaAngleThreshold(), newProvider, getInertialFrame());
124     }
125 
126     /** Create a new instance with the provided beta angle threshold.
127      * <p>This method does not change the current instance.</p>
128      * @param newBetaAngleThreshold the beta angle threshold (radians)
129      * @return the new detector instance
130      */
131     public BetaAngleDetector withBetaThreshold(final double newBetaAngleThreshold) {
132         return new BetaAngleDetector(getDetectionSettings(), getHandler(),
133                 newBetaAngleThreshold, getCelestialBodyProvider(), getInertialFrame());
134     }
135 
136     /** Create a new instance with the provided inertial frame.
137      * <p>This method does not change the current instance.</p>
138      * @param newFrame the inertial frame
139      * @return the new detector instance
140      */
141     public BetaAngleDetector withInertialFrame(final Frame newFrame) {
142         return new BetaAngleDetector(getDetectionSettings(),
143                 getHandler(), getBetaAngleThreshold(), getCelestialBodyProvider(), newFrame);
144     }
145 
146     /** {@inheritDoc} */
147     @Override
148     public double g(final SpacecraftState s) {
149         final double beta = calculateBetaAngle(s, celestialBodyProvider, inertialFrame);
150         return betaAngleThreshold - beta;
151     }
152 
153     /**Calculate the beta angle between the orbit plane and the celestial body.
154      * <p>This method computes the beta angle using the frame from the spacecraft state.</p>
155      * @param state spacecraft state
156      * @param celestialBodyProvider celestial body coordinate provider
157      * @return the beta angle (radians)
158      */
159     public static double calculateBetaAngle(final SpacecraftState state,
160             final PVCoordinatesProvider celestialBodyProvider) {
161         return calculateBetaAngle(state, celestialBodyProvider, state.getFrame());
162     }
163 
164     /**Calculate the beta angle between the orbit plane and the celestial body.
165      * @param state spacecraft state
166      * @param celestialBodyProvider celestial body coordinate provider
167      * @param frame inertial frame in which beta angle will be computed
168      * @return the beta angle (radians)
169      */
170     public static double calculateBetaAngle(final SpacecraftState state,
171             final PVCoordinatesProvider celestialBodyProvider, final Frame frame) {
172         final Vector3D celestialP = celestialBodyProvider.getPosition(state.getDate(), frame);
173         final TimeStampedPVCoordinates pv = state.getPVCoordinates(frame);
174         return MathUtils.SEMI_PI - Vector3D.angle(celestialP, pv.getMomentum());
175     }
176 
177     /** {@inheritDoc} */
178     @Override
179     protected BetaAngleDetector create(final EventDetectionSettings detectionSettings, final EventHandler newHandler) {
180         return new BetaAngleDetector(detectionSettings, newHandler,
181                 getBetaAngleThreshold(), getCelestialBodyProvider(), getInertialFrame());
182     }
183 }