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 /** {@inheritDoc} */
184 @Override
185 public boolean dependsOnTimeOnly() {
186 return true;
187 }
188
189 /** Compute the value of the switching function.
190 * This function measures the difference between the current and the target date.
191 * @param s the current state information: date, kinematics, attitude
192 * @return value of the switching function
193 */
194 public double g(final SpacecraftState s) {
195 gDate = s.getDate();
196 if (currentIndex < 0) {
197 return -1.0;
198 } else {
199 final EventDate event = getClosest(gDate);
200 return event.isgIncrease() ? gDate.durationFrom(event.getDate()) : event.getDate().durationFrom(gDate);
201 }
202 }
203
204 /** Get the current event date according to the propagator.
205 * @return event date
206 */
207 public AbsoluteDate getDate() {
208 return currentIndex < 0 ? null : eventDateList.get(currentIndex).getDate();
209 }
210
211 /** Get the minimum gap between added dates.
212 * @return the minimum gap between added dates (s)
213 */
214 public double getMinGap() {
215 return minGap;
216 }
217
218 /** Add an event date.
219 * <p>The date to add must be:</p>
220 * <ul>
221 * <li>less than the smallest already registered event date minus the maxCheck</li>
222 * <li>or more than the largest already registered event date plus the maxCheck</li>
223 * </ul>
224 * @param target target date
225 * @throws IllegalArgumentException if the date is too close from already defined interval
226 * @see #DateDetector(TimeStamped...)
227 */
228 public void addEventDate(final AbsoluteDate target) throws IllegalArgumentException {
229 final boolean increasing;
230 if (currentIndex < 0) {
231 increasing = (gDate == null) ? true : target.durationFrom(gDate) > 0.0;
232 currentIndex = 0;
233 eventDateList.add(new EventDate(target, increasing));
234 } else {
235 final int lastIndex = eventDateList.size() - 1;
236 final AbsoluteDate firstDate = eventDateList.get(0).getDate();
237 final AbsoluteDate lastDate = eventDateList.get(lastIndex).getDate();
238 if (firstDate.durationFrom(target) > minGap) {
239 increasing = !eventDateList.get(0).isgIncrease();
240 eventDateList.add(0, new EventDate(target, increasing));
241 currentIndex++;
242 } else if (target.durationFrom(lastDate) > minGap) {
243 increasing = !eventDateList.get(lastIndex).isgIncrease();
244 eventDateList.add(new EventDate(target, increasing));
245 } else {
246 throw new OrekitIllegalArgumentException(OrekitMessages.EVENT_DATE_TOO_CLOSE,
247 target,
248 firstDate,
249 lastDate,
250 minGap,
251 firstDate.durationFrom(target),
252 target.durationFrom(lastDate));
253 }
254 }
255 }
256
257 /** Get the closest EventDate to the target date.
258 * @param target target date
259 * @return current EventDate
260 */
261 private EventDate getClosest(final AbsoluteDate target) {
262 final double dt = target.durationFrom(eventDateList.get(currentIndex).getDate());
263 if (dt < 0.0 && currentIndex > 0) {
264 boolean found = false;
265 while (currentIndex > 0 && !found) {
266 if (target.durationFrom(eventDateList.get(currentIndex - 1).getDate()) < eventDateList.get(currentIndex).getDate().durationFrom(target)) {
267 currentIndex--;
268 } else {
269 found = true;
270 }
271 }
272 } else if (dt > 0.0 && currentIndex < eventDateList.size() - 1) {
273 final int maxIndex = eventDateList.size() - 1;
274 boolean found = false;
275 while (currentIndex < maxIndex && !found) {
276 if (target.durationFrom(eventDateList.get(currentIndex + 1).getDate()) > eventDateList.get(currentIndex).getDate().durationFrom(target)) {
277 currentIndex++;
278 } else {
279 found = true;
280 }
281 }
282 }
283 return eventDateList.get(currentIndex);
284 }
285
286 /** Event date specification. */
287 private static class EventDate implements TimeStamped {
288
289 /** Event date. */
290 private final AbsoluteDate eventDate;
291
292 /** Flag for g function way around event date. */
293 private final boolean gIncrease;
294
295 /** Simple constructor.
296 * @param date date
297 * @param increase if true, g function increases around event date
298 */
299 EventDate(final AbsoluteDate date, final boolean increase) {
300 this.eventDate = date;
301 this.gIncrease = increase;
302 }
303
304 /** Getter for event date.
305 * @return event date
306 */
307 public AbsoluteDate getDate() {
308 return eventDate;
309 }
310
311 /** Getter for g function way at event date.
312 * @return g function increasing flag
313 */
314 public boolean isgIncrease() {
315 return gIncrease;
316 }
317
318 }
319
320 }