1   /* Copyright 2023-2025 Alberto Ferrero
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    * Alberto Ferrero 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.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.util.FastMath;
22  import org.hipparchus.util.MathUtils;
23  import org.orekit.bodies.FieldGeodeticPoint;
24  import org.orekit.bodies.OneAxisEllipsoid;
25  import org.orekit.propagation.FieldSpacecraftState;
26  import org.orekit.propagation.events.handlers.FieldContinueOnEvent;
27  import org.orekit.propagation.events.handlers.FieldEventHandler;
28  import org.orekit.propagation.events.handlers.FieldStopOnIncreasing;
29  import org.orekit.propagation.events.intervals.FieldAdaptableInterval;
30  import org.orekit.time.FieldAbsoluteDate;
31  
32  /** Detector for geographic longitude crossing.
33   * <p>This detector identifies when a spacecraft crosses a fixed
34   * longitude with respect to a central body.</p>
35   * @author Alberto Ferrero
36   * @since 12.0
37   * @param <T> type of the field elements
38   */
39  public class FieldLongitudeCrossingDetector <T extends CalculusFieldElement<T>>
40      extends FieldAbstractDetector<FieldLongitudeCrossingDetector<T>, T> {
41  
42      /**
43      * Body on which the longitude is defined.
44      */
45      private final OneAxisEllipsoid body;
46  
47      /**
48      * Fixed longitude to be crossed.
49      */
50      private final double longitude;
51  
52      /**
53      * Filtering detector.
54      */
55      private final FieldEventEnablingPredicateFilter<T> filtering;
56  
57      /**
58      * Build a new detector.
59      * <p>The new instance uses default values for maximal checking interval
60      * ({@link #DEFAULT_MAX_CHECK}) and convergence threshold ({@link
61      * #DEFAULT_THRESHOLD}).</p>
62      *
63      * @param field     the type of numbers to use.
64      * @param body      body on which the longitude is defined
65      * @param longitude longitude to be crossed
66      */
67      public FieldLongitudeCrossingDetector(final Field<T> field, final OneAxisEllipsoid body, final double longitude) {
68          this(new FieldEventDetectionSettings<>(field, EventDetectionSettings.getDefaultEventDetectionSettings()),
69                  new FieldStopOnIncreasing<>(), body, longitude);
70      }
71  
72      /**
73      * Build a detector.
74      *
75      * @param maxCheck  maximal checking interval (s)
76      * @param threshold convergence threshold (s)
77      * @param body      body on which the longitude is defined
78      * @param longitude longitude to be crossed
79      */
80      public FieldLongitudeCrossingDetector(final T maxCheck,
81                                            final T threshold,
82                                            final OneAxisEllipsoid body,
83                                            final double longitude) {
84          this(new FieldEventDetectionSettings<>(FieldAdaptableInterval.of(maxCheck.getReal()), threshold, DEFAULT_MAX_ITER),
85                  new FieldStopOnIncreasing<>(), body, longitude);
86      }
87  
88      /**
89      * Protected constructor with full parameters.
90      * <p>
91      * This constructor is not public as users are expected to use the builder
92      * API with the various {@code withXxx()} methods to set up the instance
93      * in a readable manner without using a huge amount of parameters.
94      * </p>
95      *
96      * @param detectionSettings event detection settings
97      * @param handler   event handler to call at event occurrences
98      * @param body      body on which the longitude is defined
99      * @param longitude longitude to be crossed
100     */
101     protected FieldLongitudeCrossingDetector(
102         final FieldEventDetectionSettings<T> detectionSettings,
103         final FieldEventHandler<T> handler,
104         final OneAxisEllipsoid body,
105         final double longitude) {
106 
107         super(detectionSettings, handler);
108 
109         this.body = body;
110         this.longitude = longitude;
111 
112         // we filter out spurious longitude crossings occurring at the antimeridian
113         final FieldRawLongitudeCrossingDetector<T> raw = new FieldRawLongitudeCrossingDetector<>(detectionSettings,
114             new FieldContinueOnEvent<>());
115         final FieldEnablingPredicate<T> predicate =
116             (state, detector, g) -> FastMath.abs(g).getReal() < 0.5 * FastMath.PI;
117         this.filtering = new FieldEventEnablingPredicateFilter<>(raw, predicate);
118 
119     }
120 
121     /**
122     * {@inheritDoc}
123     */
124     @Override
125     protected FieldLongitudeCrossingDetector<T> create(
126         final FieldEventDetectionSettings<T> detectionSettings, final FieldEventHandler<T> newHandler) {
127         return new FieldLongitudeCrossingDetector<>(detectionSettings, newHandler, body, longitude);
128     }
129 
130     /**
131     * Get the body on which the geographic zone is defined.
132     *
133     * @return body on which the geographic zone is defined
134     */
135     public OneAxisEllipsoid getBody() {
136         return body;
137     }
138 
139     /**
140     * Get the fixed longitude to be crossed (radians).
141     *
142     * @return fixed longitude to be crossed (radians)
143     */
144     public double getLongitude() {
145         return longitude;
146     }
147 
148     /**
149     * {@inheritDoc}
150     */
151     @Override
152     public void init(final FieldSpacecraftState<T> s0, final FieldAbsoluteDate<T> t) {
153         super.init(s0, t);
154         filtering.init(s0, t);
155     }
156 
157     /**
158     * Compute the value of the detection function.
159     * <p>
160     * The value is the longitude difference between the spacecraft and the fixed
161     * longitude to be crossed, with some sign tweaks to ensure continuity.
162     * These tweaks imply the {@code increasing} flag in events detection becomes
163     * irrelevant here! As an example, the longitude of a prograde spacecraft
164     * will always increase, but this g function will increase and decrease so it
165     * will cross the zero value once per orbit, in increasing and decreasing
166     * directions on alternate orbits. If eastwards and westwards crossing have to
167     * be distinguished, the velocity direction has to be checked instead of looking
168     * at the {@code increasing} flag.
169     * </p>
170     *
171     * @param s the current state information: date, kinematics, attitude
172     * @return longitude difference between the spacecraft and the fixed
173     * longitude, with some sign tweaks to ensure continuity
174     */
175     public T g(final FieldSpacecraftState<T> s) {
176         return filtering.g(s);
177     }
178 
179     private class FieldRawLongitudeCrossingDetector <TT extends CalculusFieldElement<TT>>
180         extends FieldAbstractDetector<FieldRawLongitudeCrossingDetector<TT>, TT> {
181 
182         /**
183         * Protected constructor with full parameters.
184         * <p>
185         * This constructor is not public as users are expected to use the builder
186         * API with the various {@code withXxx()} methods to set up the instance
187         * in a readable manner without using a huge amount of parameters.
188         * </p>
189         *
190         * @param detectionSettings event detection settings
191         * @param handler   event handler to call at event occurrences
192         */
193         protected FieldRawLongitudeCrossingDetector(
194             final FieldEventDetectionSettings<TT> detectionSettings,
195             final FieldEventHandler<TT> handler) {
196             super(detectionSettings, handler);
197         }
198 
199         /**
200         * {@inheritDoc}
201         */
202         @Override
203         protected FieldRawLongitudeCrossingDetector<TT> create(
204             final FieldEventDetectionSettings<TT> detectionSettings,
205             final FieldEventHandler<TT> newHandler) {
206             return new FieldRawLongitudeCrossingDetector<>(detectionSettings, newHandler);
207         }
208 
209         /**
210         * Compute the value of the detection function.
211         * <p>
212         * The value is the longitude difference between the spacecraft and the fixed
213         * longitude to be crossed, and it <em>does</em> change sign twice around
214         * the central body: once at expected longitude and once at antimeridian.
215         * The second sign change is a spurious one and is filtered out by the
216         * outer class.
217         * </p>
218         *
219         * @param s the current state information: date, kinematics, attitude
220         * @return longitude difference between the spacecraft and the fixed
221         * longitude
222         */
223         public TT g(final FieldSpacecraftState<TT> s) {
224 
225             // convert state to geodetic coordinates
226             final FieldGeodeticPoint<TT> gp = body.transform(s.getPosition(),
227                 s.getFrame(), s.getDate());
228 
229             // longitude difference
230             final TT zero = gp.getLongitude().getField().getZero();
231             return MathUtils.normalizeAngle(gp.getLongitude().subtract(longitude), zero);
232 
233         }
234 
235     }
236 
237 }