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