1 /* Copyright 2002-2025 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 java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.List;
22
23 import org.hipparchus.ode.events.Action;
24 import org.orekit.errors.OrekitIllegalArgumentException;
25 import org.orekit.errors.OrekitMessages;
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.propagation.events.intervals.DateDetectionAdaptableIntervalFactory;
30 import org.orekit.time.AbsoluteDate;
31 import org.orekit.time.TimeStamped;
32
33 /** Finder for date events.
34 * <p>This class finds date events (i.e. occurrence of some predefined dates).</p>
35 * <p>As of version 5.1, it is an enhanced date detector:</p>
36 * <ul>
37 * <li>it can be defined without prior date ({@link #DateDetector(TimeStamped...)})</li>
38 * <li>several dates can be added ({@link #addEventDate(AbsoluteDate)})</li>
39 * </ul>
40 * <p>The gap between the added dates must be more than the minGap.</p>
41 * <p>The default implementation behavior is to {@link Action#STOP stop}
42 * propagation at the first event date occurrence. This can be changed by calling
43 * {@link #withHandler(EventHandler)} after construction.</p>
44 * @see org.orekit.propagation.Propagator#addEventDetector(EventDetector)
45 * @author Luc Maisonobe
46 * @author Pascal Parraud
47 */
48 public class DateDetector extends AbstractDetector<DateDetector> implements TimeStamped {
49
50 /** Default value for max check.
51 * @since 12.0
52 */
53 public static final double DEFAULT_MAX_CHECK = DateDetectionAdaptableIntervalFactory.DEFAULT_MAX_CHECK;
54
55 /** Default value for minimum gap between added dates.
56 * @since 12.0
57 */
58 public static final double DEFAULT_MIN_GAP = 1.0;
59
60 /** Default value for convergence threshold.
61 * @since 12.0
62 */
63 public static final double DEFAULT_THRESHOLD = 1.0e-10;
64
65 /** Minimum gap between added dates.
66 * @since 12.0
67 */
68 private final double minGap;
69
70 /** Last date for g computation. */
71 private AbsoluteDate gDate;
72
73 /** List of event dates. */
74 private final ArrayList<EventDate> eventDateList;
75
76 /** Current event date. */
77 private int currentIndex;
78
79 /** Build a new instance.
80 * <p>First event dates are set here, but others can be
81 * added later with {@link #addEventDate(AbsoluteDate)}, although the max. check should probably be changed then.</p>
82 * @param dates list of event dates
83 * @see #addEventDate(AbsoluteDate)
84 * @since 12.0
85 */
86 public DateDetector(final TimeStamped... dates) {
87 this(new EventDetectionSettings(DateDetectionAdaptableIntervalFactory.getDatesDetectionConstantInterval(dates),
88 DEFAULT_THRESHOLD, EventDetectionSettings.DEFAULT_MAX_ITER),
89 new StopOnEvent(), DEFAULT_MIN_GAP, dates);
90 }
91
92 /** Build a new instance from a single time.
93 * <p>First event dates are set here, but others can be
94 * added later with {@link #addEventDate(AbsoluteDate)}, although the max. check should probably be changed then.</p>
95 * @param date event date
96 * @see #addEventDate(AbsoluteDate)
97 * @since 13.0
98 */
99 public DateDetector(final AbsoluteDate date) {
100 this((TimeStamped) date);
101 }
102
103 /** Build a new instance.
104 * <p>First event dates are set here, but others can be
105 * added later with {@link #addEventDate(AbsoluteDate)}, although the max. check should probably be changed then.</p>
106 * @param minGap minimum gap between added dates (s)
107 * @param dates list of event dates
108 * @see #addEventDate(AbsoluteDate)
109 * @since 13.0
110 */
111 public DateDetector(final double minGap, final TimeStamped... dates) {
112 this(new EventDetectionSettings(DateDetectionAdaptableIntervalFactory.getDatesDetectionConstantInterval(dates),
113 DEFAULT_THRESHOLD, EventDetectionSettings.DEFAULT_MAX_ITER),
114 new StopOnEvent(), minGap, dates);
115 }
116
117 /** Build a new instance from a single time.
118 * <p>First event dates are set here, but others can be
119 * added later with {@link #addEventDate(AbsoluteDate)}, although the max. check should probably be changed then.</p>
120 * @param minGap minimum gap between added dates (s)
121 * @param date event date
122 * @see #addEventDate(AbsoluteDate)
123 * @since 13.0
124 */
125 public DateDetector(final double minGap, final AbsoluteDate date) {
126 this(minGap, (TimeStamped) date);
127 }
128
129 /** Protected constructor with full parameters.
130 * <p>
131 * This constructor is not public as users are expected to use the builder
132 * API with the various {@code withXxx()} methods to set up the instance
133 * in a readable manner without using a huge amount of parameters.
134 * </p>
135 * @param detectionSettings detection settings
136 * @param handler event handler to call at event occurrences
137 * @param minGap minimum gap between added dates (s)
138 * @param dates list of event dates
139 * @since 12.2
140 */
141 protected DateDetector(final EventDetectionSettings detectionSettings,
142 final EventHandler handler, final double minGap, final TimeStamped... dates) {
143 super(detectionSettings, handler);
144 this.currentIndex = -1;
145 this.gDate = null;
146 this.eventDateList = new ArrayList<>();
147 this.minGap = minGap;
148 for (final TimeStamped ts : dates) {
149 final AbsoluteDate date = ts.getDate();
150 final boolean notPresentYet = eventDateList.stream().noneMatch(d -> d.getDate().isEqualTo(date));
151 if (notPresentYet) {
152 addEventDate(date);
153 }
154 }
155 }
156
157 /**
158 * Setup minimum gap between added dates.
159 * @param newMinGap new minimum gap between added dates
160 * @return a new detector with updated configuration (the instance is not changed)
161 * @since 12.0
162 */
163 public DateDetector withMinGap(final double newMinGap) {
164 return new DateDetector(getDetectionSettings(), getHandler(), newMinGap,
165 eventDateList.toArray(new EventDate[0]));
166 }
167
168 /** {@inheritDoc} */
169 @Override
170 protected DateDetector create(final EventDetectionSettings detectionSettings, final EventHandler newHandler) {
171 return new DateDetector(detectionSettings, newHandler, minGap,
172 eventDateList.toArray(new EventDate[0]));
173 }
174
175 /** Get all event dates currently managed, in chronological order.
176 * @return all event dates currently managed, in chronological order
177 * @since 11.1
178 */
179 public List<TimeStamped> getDates() {
180 return Collections.unmodifiableList(eventDateList);
181 }
182
183 /** Compute the value of the switching function.
184 * This function measures the difference between the current and the target date.
185 * @param s the current state information: date, kinematics, attitude
186 * @return value of the switching function
187 */
188 public double g(final SpacecraftState s) {
189 gDate = s.getDate();
190 if (currentIndex < 0) {
191 return -1.0;
192 } else {
193 final EventDate event = getClosest(gDate);
194 return event.isgIncrease() ? gDate.durationFrom(event.getDate()) : event.getDate().durationFrom(gDate);
195 }
196 }
197
198 /** Get the current event date according to the propagator.
199 * @return event date
200 */
201 public AbsoluteDate getDate() {
202 return currentIndex < 0 ? null : eventDateList.get(currentIndex).getDate();
203 }
204
205 /** Get the minimum gap between added dates.
206 * @return the minimum gap between added dates (s)
207 */
208 public double getMinGap() {
209 return minGap;
210 }
211
212 /** Add an event date.
213 * <p>The date to add must be:</p>
214 * <ul>
215 * <li>less than the smallest already registered event date minus the maxCheck</li>
216 * <li>or more than the largest already registered event date plus the maxCheck</li>
217 * </ul>
218 * @param target target date
219 * @throws IllegalArgumentException if the date is too close from already defined interval
220 * @see #DateDetector(TimeStamped...)
221 */
222 public void addEventDate(final AbsoluteDate target) throws IllegalArgumentException {
223 final boolean increasing;
224 if (currentIndex < 0) {
225 increasing = (gDate == null) ? true : target.durationFrom(gDate) > 0.0;
226 currentIndex = 0;
227 eventDateList.add(new EventDate(target, increasing));
228 } else {
229 final int lastIndex = eventDateList.size() - 1;
230 final AbsoluteDate firstDate = eventDateList.get(0).getDate();
231 final AbsoluteDate lastDate = eventDateList.get(lastIndex).getDate();
232 if (firstDate.durationFrom(target) > minGap) {
233 increasing = !eventDateList.get(0).isgIncrease();
234 eventDateList.add(0, new EventDate(target, increasing));
235 currentIndex++;
236 } else if (target.durationFrom(lastDate) > minGap) {
237 increasing = !eventDateList.get(lastIndex).isgIncrease();
238 eventDateList.add(new EventDate(target, increasing));
239 } else {
240 throw new OrekitIllegalArgumentException(OrekitMessages.EVENT_DATE_TOO_CLOSE,
241 target,
242 firstDate,
243 lastDate,
244 minGap,
245 firstDate.durationFrom(target),
246 target.durationFrom(lastDate));
247 }
248 }
249 }
250
251 /** Get the closest EventDate to the target date.
252 * @param target target date
253 * @return current EventDate
254 */
255 private EventDate getClosest(final AbsoluteDate target) {
256 final double dt = target.durationFrom(eventDateList.get(currentIndex).getDate());
257 if (dt < 0.0 && currentIndex > 0) {
258 boolean found = false;
259 while (currentIndex > 0 && !found) {
260 if (target.durationFrom(eventDateList.get(currentIndex - 1).getDate()) < eventDateList.get(currentIndex).getDate().durationFrom(target)) {
261 currentIndex--;
262 } else {
263 found = true;
264 }
265 }
266 } else if (dt > 0.0 && currentIndex < eventDateList.size() - 1) {
267 final int maxIndex = eventDateList.size() - 1;
268 boolean found = false;
269 while (currentIndex < maxIndex && !found) {
270 if (target.durationFrom(eventDateList.get(currentIndex + 1).getDate()) > eventDateList.get(currentIndex).getDate().durationFrom(target)) {
271 currentIndex++;
272 } else {
273 found = true;
274 }
275 }
276 }
277 return eventDateList.get(currentIndex);
278 }
279
280 /** Event date specification. */
281 private static class EventDate implements TimeStamped {
282
283 /** Event date. */
284 private final AbsoluteDate eventDate;
285
286 /** Flag for g function way around event date. */
287 private final boolean gIncrease;
288
289 /** Simple constructor.
290 * @param date date
291 * @param increase if true, g function increases around event date
292 */
293 EventDate(final AbsoluteDate date, final boolean increase) {
294 this.eventDate = date;
295 this.gIncrease = increase;
296 }
297
298 /** Getter for event date.
299 * @return event date
300 */
301 public AbsoluteDate getDate() {
302 return eventDate;
303 }
304
305 /** Getter for g function way at event date.
306 * @return g function increasing flag
307 */
308 public boolean isgIncrease() {
309 return gIncrease;
310 }
311
312 }
313
314 }