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.orekit.bodies.FieldGeodeticPoint;
23  import org.orekit.bodies.OneAxisEllipsoid;
24  import org.orekit.propagation.FieldSpacecraftState;
25  import org.orekit.propagation.events.handlers.FieldEventHandler;
26  import org.orekit.propagation.events.handlers.FieldStopOnIncreasing;
27  import org.orekit.propagation.events.intervals.FieldAdaptableInterval;
28  
29  
30  /** Detector for geographic latitude crossing.
31   * <p>This detector identifies when a spacecraft crosses a fixed
32   * latitude range with respect to a central body.</p>
33   * @author Alberto Ferrero
34   * @since 12.0
35   * @param <T> type of the field elements
36   */
37  public class FieldLatitudeRangeCrossingDetector <T extends CalculusFieldElement<T>>
38          extends FieldAbstractDetector<FieldLatitudeRangeCrossingDetector<T>, T> {
39  
40      /**
41       * Body on which the latitude is defined.
42       */
43      private final OneAxisEllipsoid body;
44  
45      /**
46       * Fixed latitude to be crossed, lower boundary in radians.
47       */
48      private final double fromLatitude;
49  
50      /**
51       * Fixed latitude to be crossed, upper boundary in radians.
52       */
53      private final double toLatitude;
54  
55      /**
56       * Sign, to get reversed inclusion latitude range (lower > upper).
57       */
58      private final double sign;
59  
60      /**
61       * Build a new detector.
62       * <p>The new instance uses default values for maximal checking interval
63       * ({@link #DEFAULT_MAX_CHECK}) and convergence threshold ({@link
64       * #DEFAULT_THRESHOLD}).</p>
65       * @param field        the type of numbers to use.
66       * @param body         body on which the latitude is defined
67       * @param fromLatitude latitude to be crossed, lower range boundary
68       * @param toLatitude   latitude to be crossed, upper range boundary
69       */
70      public FieldLatitudeRangeCrossingDetector(final Field<T> field,
71                                                final OneAxisEllipsoid body,
72                                                final double fromLatitude,
73                                                final double toLatitude) {
74          this(new FieldEventDetectionSettings<>(field, EventDetectionSettings.getDefaultEventDetectionSettings()),
75              new FieldStopOnIncreasing<>(),
76              body,
77              fromLatitude,
78              toLatitude);
79      }
80  
81      /**
82       * Build a detector.
83       *
84       * @param maxCheck     maximal checking interval (s)
85       * @param threshold    convergence threshold (s)
86       * @param body         body on which the latitude is defined
87       * @param fromLatitude latitude to be crossed, lower range boundary
88       * @param toLatitude   latitude to be crossed, upper range boundary
89       */
90      public FieldLatitudeRangeCrossingDetector(final T maxCheck, final T threshold,
91                                                final OneAxisEllipsoid body, final double fromLatitude, final double toLatitude) {
92          this(new FieldEventDetectionSettings<>(FieldAdaptableInterval.of(maxCheck.getReal()), threshold, DEFAULT_MAX_ITER),
93                  new FieldStopOnIncreasing<>(), body, fromLatitude, toLatitude);
94      }
95  
96      /**
97       * Private constructor with full parameters.
98       * <p>
99       * This constructor is private as users are expected to use the builder
100      * API with the various {@code withXxx()} methods to set up the instance
101      * in a readable manner without using a huge amount of parameters.
102      * </p>
103      *
104      * @param detectionSettings event detection settings
105      * @param handler      event handler to call at event occurrences
106      * @param body         body on which the latitude is defined
107      * @param fromLatitude latitude to be crossed, lower range boundary
108      * @param toLatitude   latitude to be crossed, upper range boundary
109      * @since 13.0
110      */
111     protected FieldLatitudeRangeCrossingDetector(final FieldEventDetectionSettings<T> detectionSettings,
112                                                  final FieldEventHandler<T> handler,
113                                                  final OneAxisEllipsoid body,
114                                                  final double fromLatitude,
115                                                  final double toLatitude) {
116         super(detectionSettings, handler);
117         this.body = body;
118         this.fromLatitude = fromLatitude;
119         this.toLatitude = toLatitude;
120         this.sign = FastMath.signum(toLatitude - fromLatitude);
121     }
122 
123     /**
124      * {@inheritDoc}
125      */
126     @Override
127     protected FieldLatitudeRangeCrossingDetector<T> create(final FieldEventDetectionSettings<T> detectionSettings,
128                                                            final FieldEventHandler<T> newHandler) {
129         return new FieldLatitudeRangeCrossingDetector<>(detectionSettings, newHandler,
130             body, fromLatitude, toLatitude);
131     }
132 
133     /**
134      * Get the body on which the geographic zone is defined.
135      *
136      * @return body on which the geographic zone is defined
137      */
138     public OneAxisEllipsoid getBody() {
139         return body;
140     }
141 
142     /**
143      * Get the fixed latitude range to be crossed (radians), lower boundary.
144      *
145      * @return fixed lower boundary latitude range to be crossed (radians)
146      */
147     public double getFromLatitude() {
148         return fromLatitude;
149     }
150 
151     /**
152      * Get the fixed latitude range to be crossed (radians), upper boundary.
153      *
154      * @return fixed lower boundary latitude range to be crossed (radians)
155      */
156     public double getToLatitude() {
157         return toLatitude;
158     }
159 
160     /**
161      * Compute the value of the detection function.
162      * <p>
163      * The value is positive if the spacecraft latitude is inside the latitude range.
164      * It is positive if the spacecraft is northward to lower boundary range and southward to upper boundary range,
165      * with respect to the fixed latitude range.
166      * </p>
167      *
168      * @param s the current state information: date, kinematics, attitude
169      * @return positive if spacecraft inside the range
170      */
171     public T g(final FieldSpacecraftState<T> s) {
172 
173         // convert state to geodetic coordinates
174         final FieldGeodeticPoint<T> gp = body.transform(s.getPVCoordinates().getPosition(),
175             s.getFrame(), s.getDate());
176 
177         // point latitude
178         final T latitude = gp.getLatitude();
179 
180         // inside or outside latitude range
181         return latitude.subtract(fromLatitude).multiply(latitude.negate().add(toLatitude)).multiply(sign);
182 
183     }
184 
185 }