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.utils;
18
19 import java.util.Collections;
20 import java.util.Comparator;
21 import java.util.SortedSet;
22 import java.util.TreeSet;
23 import java.util.function.Consumer;
24
25 import org.orekit.errors.OrekitException;
26 import org.orekit.errors.OrekitMessages;
27 import org.orekit.time.FieldAbsoluteDate;
28 import org.orekit.time.FieldTimeStamped;
29 import org.hipparchus.Field;
30 import org.hipparchus.CalculusFieldElement;
31 import org.orekit.time.AbsoluteDate;
32
33 /** Container for objects that apply to spans of time.
34
35 * <p>
36 * Time span maps can be seen either as an ordered collection of
37 * {@link Span time spans} or as an ordered collection
38 * of {@link Transition transitions}. Both views are dual one to
39 * each other. A time span extends from one transition to the
40 * next one, and a transition separates one time span from the
41 * next one. Each time span contains one entry that is valid during
42 * the time span; this entry may be null if nothing is valid during
43 * this time span.
44 * </p>
45 * <p>
46 * Typical uses of {@link FieldTimeSpanMap} are to hold piecewise data, like for
47 * example an orbit count that changes at ascending nodes (in which case the
48 * entry would be an {@link Integer}), or a visibility status between several
49 * objects (in which case the entry would be a {@link Boolean}), or a drag
50 * coefficient that is expected to be estimated daily or three-hourly.
51 * </p>
52 * <p>
53 * Time span maps are built progressively. At first, they contain one
54 * {@link Span time span} only whose validity extends from past infinity to
55 * future infinity. Then new entries are added one at a time, associated with
56 * transition dates, in order to build up the complete map. The transition dates
57 * can be either the start of validity (when calling {@link #addValidAfter(Object,
58 * FieldAbsoluteDate, boolean)}), or the end of the validity (when calling {@link
59 * #addValidBefore(Object, FieldAbsoluteDate, boolean)}). Entries are often added at one
60 * end only (and mainly in chronological order), but this is not required. It is
61 * possible for example to first set up a map that covers a large range (say one day),
62 * and then to insert intermediate dates using for example propagation and event
63 * detectors to carve out some parts. This is akin to the way Binary Space Partitioning
64 * Trees work.
65 * </p>
66 * <p>
67 * Since 13.1, this class is thread-safe
68 * </p>
69 * @param <T> Type of the data.
70 * @param <F> type of the date field elements
71
72 * @author Luc Maisonobe
73 * @since 7.1
74 */
75 public class FieldTimeSpanMap<T, F extends CalculusFieldElement<F>> {
76
77 /** Field.*/
78 private final Field<F> field;
79
80 /** Reference to last accessed data.
81 * @since 13.1
82 */
83 private Span<T, F> current;
84
85 /** First span.
86 * @since 13.1
87 */
88 private Span<T, F> firstSpan;
89
90 /** Last span.
91 * @since 13.1
92 */
93 private Span<T, F> lastSpan;
94
95 /** End of early expunged range.
96 * @since 13.1
97 */
98 private FieldAbsoluteDate<F> expungedEarly;
99
100 /** Start of late expunged range.
101 * @since 13.1
102 */
103 private FieldAbsoluteDate<F> expungedLate;
104
105 /** Number of time spans.
106 * @since 13.1
107 */
108 private int nbSpans;
109
110 /** Maximum number of time spans.
111 * @since 13.1
112 */
113 private int maxNbSpans;
114
115 /** Maximum time range between the earliest and the latest transitions.
116 * @since 13.1
117 */
118 private double maxRange;
119
120 /** Expunge policy.
121 * @since 13.1
122 */
123 private ExpungePolicy expungePolicy;
124
125 /** Create a map containing a single object, initially valid throughout the timeline.
126 * <p>
127 * The real validity of this first entry will be truncated as other
128 * entries are either {@link #addValidBefore(Object, FieldAbsoluteDate, boolean)
129 * added before} it or {@link #addValidAfter(Object, FieldAbsoluteDate, boolean)
130 * added after} it.
131 * </p>
132 * <p>
133 * The initial {@link #configureExpunge(int, double, ExpungePolicy) expunge policy}
134 * is to never expunge any entries, it can be changed afterward by calling
135 * {@link #configureExpunge(int, double, ExpungePolicy)}
136 * </p>
137 * @param entry entry (initially valid throughout the timeline)
138 * @param field field used by default.
139 */
140 public FieldTimeSpanMap(final T entry, final Field<F> field) {
141 this.field = field;
142 this.current = new Span<>(field, entry);
143 this.firstSpan = current;
144 this.lastSpan = current;
145 this.nbSpans = 1;
146 configureExpunge(Integer.MAX_VALUE, Double.POSITIVE_INFINITY, ExpungePolicy.EXPUNGE_FARTHEST);
147 }
148
149 /** Configure (or reconfigure) expunge policy for later additions.
150 * <p>
151 * When an entry is added to the map (using either {@link #addValidBefore(Object, FieldAbsoluteDate, boolean)},
152 * {@link #addValidBetween(Object, FieldAbsoluteDate, FieldAbsoluteDate)}, or
153 * {@link #addValidAfter(Object, FieldAbsoluteDate, boolean)} that exceeds the allowed capacity in terms
154 * of number of time spans or maximum time range between the earliest and the latest transitions,
155 * then exceeding data is expunged according to the {@code expungePolicy}.
156 * </p>
157 * <p>
158 * Note that as the policy depends on the date at which new entries are added, the policy will be enforced
159 * only for the <em>next</em> calls to {@link #addValidBefore(Object, FieldAbsoluteDate, boolean)},
160 * {@link #addValidBetween(Object, FieldAbsoluteDate, FieldAbsoluteDate)}, and {@link #addValidAfter(Object,
161 * FieldAbsoluteDate, boolean)}, it is <em>not</em> enforce immediately.
162 * </p>
163 * @param newMaxNbSpans maximum number of time spans
164 * @param newMaxRange maximum time range between the earliest and the latest transitions
165 * @param newExpungePolicy expunge policy to apply when capacity is exceeded
166 * @since 13.1
167 */
168 public synchronized void configureExpunge(final int newMaxNbSpans, final double newMaxRange, final ExpungePolicy newExpungePolicy) {
169 this.maxNbSpans = newMaxNbSpans;
170 this.maxRange = newMaxRange;
171 this.expungePolicy = newExpungePolicy;
172 this.expungedEarly = FieldAbsoluteDate.getPastInfinity(field);
173 this.expungedLate = FieldAbsoluteDate.getFutureInfinity(field);
174 }
175
176 /** Get the number of spans.
177 * <p>
178 * The number of spans is always at least 1. The number of transitions
179 * is always 1 lower than the number of spans.
180 * </p>
181 * @return number of spans
182 * @since 13.1
183 */
184 public synchronized int getSpansNumber() {
185 return nbSpans;
186 }
187
188 /** Add an entry valid before a limit date.
189 * <p>
190 * This method just calls {@link #addValidBefore(Object, FieldAbsoluteDate, boolean)
191 * addValidBefore(entry, latestValidityDate, false)}.
192 * </p>
193 * @param entry entry to add
194 * @param latestValidityDate date before which the entry is valid
195 * @deprecated as of 13.1, replaced by {@link #addValidBefore(Object, FieldAbsoluteDate, boolean)}
196 */
197 @Deprecated
198 public void addValidBefore(final T entry, final FieldAbsoluteDate<F> latestValidityDate) {
199 addValidBefore(entry, latestValidityDate, false);
200 }
201
202 /** Add an entry valid before a limit date.
203 * <p>
204 * As an entry is valid, it truncates the validity of the neighboring
205 * entries already present in the map.
206 * </p>
207 * <p>
208 * If the map already contains transitions that occur earlier than {@code latestValidityDate},
209 * the {@code erasesEarlier} parameter controls what to do with them. Let's consider
210 * the time span [tₖ; tₖ₊₁[ associated with entry eₖ that would have been valid at time
211 * {@code latestValidityDate} prior to the call to the method (i.e. tₖ <
212 * {@code latestValidityDate} < tₖ₊₁).
213 * </p>
214 * <ul>
215 * <li>if {@code erasesEarlier} is {@code true}, then all earlier transitions
216 * up to and including tₖ are erased, and the {@code entry} will be valid from past infinity
217 * to {@code latestValidityDate}</li>
218 * <li>if {@code erasesEarlier} is {@code false}, then all earlier transitions
219 * are preserved, and the {@code entry} will be valid from tₖ
220 * to {@code latestValidityDate}</li>
221 * </ul>
222 * <p>
223 * In both cases, the existing entry eₖ time span will be truncated and will be valid
224 * only from {@code latestValidityDate} to tₖ₊₁.
225 * </p>
226 * @param entry entry to add
227 * @param latestValidityDate date before which the entry is valid
228 * @param erasesEarlier if true, the entry erases all existing transitions
229 * that are earlier than {@code latestValidityDate}
230 * @return span with added entry
231 * @since 13.1
232 */
233 public synchronized Span<T, F> addValidBefore(final T entry, final FieldAbsoluteDate<F> latestValidityDate, final boolean erasesEarlier) {
234
235 // update current reference to transition date
236 locate(latestValidityDate);
237
238 if (erasesEarlier) {
239
240 // drop everything before date
241 current.start = null;
242
243 // update count
244 nbSpans = 0;
245 for (Span<T, F> span = current; span != null; span = span.next()) {
246 ++nbSpans;
247 }
248
249 }
250
251 final Span<T, F> span = new Span<>(field, entry);
252
253 final Transition<T, F> start = current.getStartTransition();
254 if (start != null && start.getDate().equals(latestValidityDate)) {
255 // the transition at the start of the current span is at the exact same date
256 // we update it, without adding a new transition
257 if (start.previous() != null) {
258 start.previous().setAfter(span);
259 }
260 start.setBefore(span);
261 updateFirstIfNeeded(span);
262 } else {
263
264 if (current.getStartTransition() != null) {
265 current.getStartTransition().setAfter(span);
266 }
267
268 // we need to add a new transition somewhere inside the current span
269 insertTransition(latestValidityDate, span, current);
270
271 }
272
273 // we consider the last added transition as the new current one
274 current = span;
275
276 expungeOldData(latestValidityDate);
277
278 return span;
279
280 }
281
282 /** Add an entry valid after a limit date.
283 * <p>
284 * This method just calls {@link #addValidAfter(Object, FieldAbsoluteDate, boolean)
285 * addValidAfter(entry, earliestValidityDate, false)}.
286 * </p>
287 * @param entry entry to add
288 * @param earliestValidityDate date after which the entry is valid
289 * @deprecated as of 13.1, replaced by {@link #addValidAfter(Object, FieldAbsoluteDate, boolean)}
290 */
291 @Deprecated
292 public void addValidAfter(final T entry, final FieldAbsoluteDate<F> earliestValidityDate) {
293 addValidAfter(entry, earliestValidityDate, false);
294 }
295
296 /** Add an entry valid after a limit date.
297 * <p>
298 * As an entry is valid, it truncates or overrides the validity of the neighboring
299 * entries already present in the map.
300 * </p>
301 * <p>
302 * If the map already contains transitions that occur later than {@code earliestValidityDate},
303 * the {@code erasesLater} parameter controls what to do with them. Let's consider
304 * the time span [tₖ; tₖ₊₁[ associated with entry eₖ that would have been valid at time
305 * {@code earliestValidityDate} prior to the call to the method (i.e. tₖ <
306 * {@code earliestValidityDate} < tₖ₊₁).
307 * </p>
308 * <ul>
309 * <li>if {@code erasesLater} is {@code true}, then all later transitions
310 * from and including tₖ₊₁ are erased, and the {@code entry} will be valid from
311 * {@code earliestValidityDate} to future infinity</li>
312 * <li>if {@code erasesLater} is {@code false}, then all later transitions
313 * are preserved, and the {@code entry} will be valid from {@code earliestValidityDate}
314 * to tₖ₊₁</li>
315 * </ul>
316 * <p>
317 * In both cases, the existing entry eₖ time span will be truncated and will be valid
318 * only from tₖ to {@code earliestValidityDate}.
319 * </p>
320 * @param entry entry to add
321 * @param earliestValidityDate date after which the entry is valid
322 * @param erasesLater if true, the entry erases all existing transitions
323 * that are later than {@code earliestValidityDate}
324 * @return span with added entry
325 * @since 13.1
326 */
327 public synchronized Span<T, F> addValidAfter(final T entry, final FieldAbsoluteDate<F> earliestValidityDate, final boolean erasesLater) {
328
329 // update current reference to transition date
330 locate(earliestValidityDate);
331
332 if (erasesLater) {
333
334 // drop everything after date
335 current.end = null;
336
337 // update count
338 nbSpans = 0;
339 for (Span<T, F> span = current; span != null; span = span.previous()) {
340 ++nbSpans;
341 }
342
343 }
344
345 final Span<T, F> span = new Span<>(field, entry);
346 if (current.getEndTransition() != null) {
347 current.getEndTransition().setBefore(span);
348 }
349
350 final Transition<T, F> start = current.getStartTransition();
351 if (start != null && start.getDate().equals(earliestValidityDate)) {
352 // the transition at the start of the current span is at the exact same date
353 // we update it, without adding a new transition
354 start.setAfter(span);
355 updateLastIfNeeded(span);
356 } else {
357 // we need to add a new transition somewhere inside the current span
358 insertTransition(earliestValidityDate, current, span);
359 }
360
361 // we consider the last added transition as the new current one
362 current = span;
363
364 // update metadata
365 expungeOldData(earliestValidityDate);
366
367 return span;
368
369 }
370
371 /** Add an entry valid between two limit dates.
372 * <p>
373 * As an entry is valid, it truncates or overrides the validity of the neighboring
374 * entries already present in the map.
375 * </p>
376 * @param entry entry to add
377 * @param earliestValidityDate date after which the entry is valid
378 * @param latestValidityDate date before which the entry is valid
379 * @return span with added entry
380 * @since 13.1
381 */
382 public synchronized Span<T, F> addValidBetween(final T entry,
383 final FieldAbsoluteDate<F> earliestValidityDate,
384 final FieldAbsoluteDate<F> latestValidityDate) {
385
386 // handle special cases
387 if (AbsoluteDate.PAST_INFINITY.equals(earliestValidityDate.toAbsoluteDate())) {
388 if (AbsoluteDate.FUTURE_INFINITY.equals(latestValidityDate.toAbsoluteDate())) {
389 // we wipe everything in the map
390 current = new Span<>(field, entry);
391 firstSpan = current;
392 lastSpan = current;
393 return current;
394 } else {
395 // we wipe from past infinity
396 return addValidBefore(entry, latestValidityDate, true);
397 }
398 } else if (AbsoluteDate.FUTURE_INFINITY.equals(latestValidityDate.toAbsoluteDate())) {
399 // we wipe up to future infinity
400 return addValidAfter(entry, earliestValidityDate, true);
401 } else {
402
403 // locate spans at earliest and latest dates
404 locate(earliestValidityDate);
405 Span<T, F> latest = current;
406 while (latest.getEndTransition() != null && latest.getEnd().isBeforeOrEqualTo(latestValidityDate)) {
407 latest = latest.next();
408 --nbSpans;
409 }
410 if (latest == current) {
411 // the interval splits one transition in the middle, we need to duplicate the instance
412 latest = new Span<>(field, current.data);
413 if (current.getEndTransition() != null) {
414 current.getEndTransition().setBefore(latest);
415 }
416 }
417
418 final Span<T, F> span = new Span<>(field, entry);
419
420 // manage earliest transition
421 final Transition<T, F> start = current.getStartTransition();
422 if (start != null && start.getDate().equals(earliestValidityDate)) {
423 // the transition at the start of the current span is at the exact same date
424 // we update it, without adding a new transition
425 start.setAfter(span);
426 updateLastIfNeeded(span);
427 } else {
428 // we need to add a new transition somewhere inside the current span
429 insertTransition(earliestValidityDate, current, span);
430 }
431
432 // manage latest transition
433 insertTransition(latestValidityDate, span, latest);
434
435 // we consider the last added transition as the new current one
436 current = span;
437
438 // update metadata
439 final FieldAbsoluteDate<F> midDate = earliestValidityDate.
440 shiftedBy(latestValidityDate.durationFrom(earliestValidityDate).multiply(0.5));
441 expungeOldData(midDate);
442
443 return span;
444
445 }
446
447 }
448
449 /** Get the entry valid at a specified date.
450 * <p>
451 * The expected complexity is O(1) for successive calls with
452 * neighboring dates, which is the more frequent use in propagation
453 * or orbit determination applications, and O(n) for random calls.
454 * </p>
455 * @param date date at which the entry must be valid
456 * @return valid entry at specified date
457 * @see #getSpan(FieldAbsoluteDate)
458 */
459 public synchronized T get(final FieldAbsoluteDate<F> date) {
460 return getSpan(date).getData();
461 }
462
463 /** Get the time span containing a specified date.
464 * <p>
465 * The expected complexity is O(1) for successive calls with
466 * neighboring dates, which is the more frequent use in propagation
467 * or orbit determination applications, and O(n) for random calls.
468 * </p>
469 * @param date date belonging to the desired time span
470 * @return time span containing the specified date
471 * @since 13.1
472 */
473 public synchronized Span<T, F> getSpan(final FieldAbsoluteDate<F> date) {
474
475 // safety check
476 if (date.isBefore(expungedEarly) || date.isAfter(expungedLate)) {
477 throw new OrekitException(OrekitMessages.EXPUNGED_SPAN, date);
478 }
479
480 locate(date);
481 return current;
482 }
483
484 /** Locate the time span containing a specified date.
485 * <p>
486 * The {@code current} field is updated to the located span.
487 * After the method returns, {@code current.getStartTransition()} is either
488 * null or its date is before or equal to date, and {@code
489 * current.getEndTransition()} is either null or its date is after date.
490 * </p>
491 * @param date date belonging to the desired time span
492 * @since 13.1
493 */
494 private synchronized void locate(final FieldAbsoluteDate<F> date) {
495
496 while (current.getStart().isAfter(date)) {
497 // the current span is too late
498 current = current.previous();
499 }
500
501 while (current.getEnd().isBeforeOrEqualTo(date)) {
502
503 final Span<T, F> next = current.next();
504 if (next == null) {
505 // this happens when date is FUTURE_INFINITY
506 return;
507 }
508
509 // the current span is too early
510 current = next;
511
512 }
513
514 }
515
516 /** Insert a transition.
517 * @param date transition date
518 * @param before span before transition
519 * @param after span after transition
520 * @since 13.1
521 */
522 private void insertTransition(final FieldAbsoluteDate<F> date, final Span<T, F> before, final Span<T, F> after) {
523 final Transition<T, F> transition = new Transition<>(this, date);
524 transition.setBefore(before);
525 transition.setAfter(after);
526 updateFirstIfNeeded(before);
527 updateLastIfNeeded(after);
528 ++nbSpans;
529 }
530
531 /** Get the first (earliest) transition.
532 * @return first (earliest) transition, or null if there are no transitions
533 * @since 13.1
534 */
535 public synchronized Transition<T, F> getFirstTransition() {
536 return getFirstSpan().getEndTransition();
537 }
538
539 /** Get the last (latest) transition.
540 * @return last (latest) transition, or null if there are no transitions
541 * @since 13.1
542 */
543 public synchronized Transition<T, F> getLastTransition() {
544 return getLastSpan().getStartTransition();
545 }
546
547 /** Get the first (earliest) span.
548 * @return first (earliest) span
549 * @since 13.1
550 */
551 public synchronized Span<T, F> getFirstSpan() {
552 return firstSpan;
553 }
554
555 /** Get the first (earliest) span with non-null data.
556 * @return first (earliest) span with non-null data
557 * @since 13.1
558 */
559 public synchronized Span<T, F> getFirstNonNullSpan() {
560 Span<T, F> span = getFirstSpan();
561 while (span.getData() == null) {
562 if (span.getEndTransition() == null) {
563 throw new OrekitException(OrekitMessages.NO_CACHED_ENTRIES);
564 }
565 span = span.next();
566 }
567 return span;
568 }
569
570 /** Get the last (latest) span.
571 * @return last (latest) span
572 * @since 13.1
573 */
574 public synchronized Span<T, F> getLastSpan() {
575 return lastSpan;
576 }
577
578 /** Get the last (latest) span with non-null data.
579 * @return last (latest) span with non-null data
580 * @since 13.1
581 */
582 public synchronized Span<T, F> getLastNonNullSpan() {
583 Span<T, F> span = getLastSpan();
584 while (span.getData() == null) {
585 if (span.getStartTransition() == null) {
586 throw new OrekitException(OrekitMessages.NO_CACHED_ENTRIES);
587 }
588 span = span.previous();
589 }
590 return span;
591 }
592
593 /** Extract a range of the map.
594 * <p>
595 * The object returned will be a new independent instance that will contain
596 * only the transitions that lie in the specified range.
597 * </p>
598 * <p>
599 * Consider, for example, a map containing objects O₀ valid before t₁, O₁ valid
600 * between t₁ and t₂, O₂ valid between t₂ and t₃, O₃ valid between t₃ and t₄,
601 * and O₄ valid after t₄. then calling this method with a {@code start}
602 * date between t₁ and t₂ and a {@code end} date between t₃ and t₄
603 * will result in a new map containing objects O₁ valid before t₂, O₂
604 * valid between t₂ and t₃, and O₃ valid after t₃. The validity of O₁
605 * is therefore extended in the past, and the validity of O₃ is extended
606 * in the future.
607 * </p>
608 * @param start earliest date at which a transition is included in the range
609 * (may be set to {@link AbsoluteDate#PAST_INFINITY} to keep all early transitions)
610 * @param end latest date at which a transition is included in the r
611 * (may be set to {@link AbsoluteDate#FUTURE_INFINITY} to keep all late transitions)
612 * @return a new instance with all transitions restricted to the specified range
613 * @since 13.1
614 */
615 public synchronized FieldTimeSpanMap<T, F> extractRange(final FieldAbsoluteDate<F> start,
616 final FieldAbsoluteDate<F> end) {
617
618 Span<T, F> span = getSpan(start);
619 final FieldTimeSpanMap<T, F> range = new FieldTimeSpanMap<>(span.getData(), field);
620 while (span.getEndTransition() != null && span.getEndTransition().getDate().isBeforeOrEqualTo(end)) {
621 span = span.next();
622 range.addValidAfter(span.getData(), span.getStartTransition().getDate(), false);
623 }
624
625 return range;
626
627 }
628
629 /**
630 * Performs an action for each non-null element of the map.
631 * <p>
632 * The action is performed chronologically.
633 * </p>
634 * @param action action to perform on the non-null elements
635 * @since 13.1
636 */
637 public synchronized void forEach(final Consumer<T> action) {
638 for (Span<T, F> span = getFirstSpan(); span != null; span = span.next()) {
639 if (span.getData() != null) {
640 action.accept(span.getData());
641 }
642 }
643 }
644
645 /**
646 * Expunge old data.
647 * @param date date of the latest added data
648 */
649 private synchronized void expungeOldData(final FieldAbsoluteDate<F> date) {
650
651 while (nbSpans > maxNbSpans || lastSpan.getStart().durationFrom(firstSpan.getEnd()).getReal() > maxRange) {
652 // capacity exceeded, we need to purge old data
653 if (expungePolicy.expungeEarliest(date.toAbsoluteDate(),
654 firstSpan.getEnd().toAbsoluteDate(),
655 lastSpan.getStart().toAbsoluteDate())) {
656 // we need to purge the earliest data
657 if (firstSpan.getEnd().isAfter(expungedEarly)) {
658 expungedEarly = firstSpan.getEnd();
659 }
660 firstSpan = firstSpan.next();
661 firstSpan.start = null;
662 if (current.start == null) {
663 // the current span was the one we just expunged
664 // we need to update it
665 current = firstSpan;
666 }
667 } else {
668 // we need to purge the latest data
669 if (lastSpan.getStart().isBefore(expungedLate)) {
670 expungedLate = lastSpan.getStart();
671 }
672 lastSpan = lastSpan.previous();
673 lastSpan.end = null;
674 if (current.end == null) {
675 // the current span was the one we just expunged
676 // we need to update it
677 current = lastSpan;
678 }
679 }
680 --nbSpans;
681 }
682
683 }
684
685 /** Update first span if needed.
686 * @param candidate candidate first span
687 * @since 13.1
688 */
689 private void updateFirstIfNeeded(final Span<T, F> candidate) {
690 if (candidate.getStartTransition() == null) {
691 firstSpan = candidate;
692 }
693 }
694
695 /** Update last span if needed.
696 * @param candidate candidate last span
697 * @since 13.1
698 */
699 private void updateLastIfNeeded(final Span<T, F> candidate) {
700 if (candidate.getEndTransition() == null) {
701 lastSpan = candidate;
702 }
703 }
704
705 /** Get an unmodifiable view of the sorted transitions.
706 * <p>
707 * Note that since 13.1, this method creates a copy of the current data,
708 * it therefore does not update when new spans are added
709 * </p>
710 * @return unmodifiable view of the sorted transitions
711 * @deprecated as of 13.1, this method is replaced by {@link #getFirstTransition()}
712 * and then following intertwined links between {@link Span Span} and {@link Transition Transition}
713 */
714 @Deprecated
715 public SortedSet<Transition<T, F>> getTransitions() {
716 final SortedSet<Transition<T, F>> copy =
717 new TreeSet<>(Comparator.comparing(Transition::getDate));
718 for (Transition<T, F> transition = getFirstTransition(); transition != null; transition = transition.next()) {
719 copy.add(transition);
720 }
721 return Collections.unmodifiableSortedSet(copy);
722 }
723
724 /** Class holding transition times.
725 * <p>
726 * This data type is dual to {@link Span}, it is
727 * focused on one transition date, and gives access to
728 * surrounding valid data whereas {@link Span} is focused
729 * on one valid data, and gives access to surrounding
730 * transition dates.
731 * </p>
732 * @param <S> Type of the data.
733 * @param <F> Type of the field elements
734 * @since 13.1
735 */
736 public static class Transition<S, F extends CalculusFieldElement<F>> implements FieldTimeStamped<F> {
737
738 /** Map this transition belongs to. */
739 private final FieldTimeSpanMap<S, F> map;
740
741 /** Transition date. */
742 private FieldAbsoluteDate<F> date;
743
744 /** Entry valid before the transition. */
745 private Span<S, F> before;
746
747 /** Entry valid after the transition. */
748 private Span<S, F> after;
749
750 /** Simple constructor.
751 * @param map map this transition belongs to
752 * @param date transition date
753 */
754 private Transition(final FieldTimeSpanMap<S, F> map, final FieldAbsoluteDate<F> date) {
755 this.map = map;
756 this.date = date;
757 }
758
759 /** Set the span valid before transition.
760 * @param before span valid before transition (must be non-null)
761 */
762 void setBefore(final Span<S, F> before) {
763 this.before = before;
764 before.end = this;
765 }
766
767 /** Set the span valid after transition.
768 * @param after span valid after transition (must be non-null)
769 */
770 void setAfter(final Span<S, F> after) {
771 this.after = after;
772 after.start = this;
773 }
774
775 /** Get the transition date.
776 * @return transition date
777 */
778 @Override
779 public FieldAbsoluteDate<F> getDate() {
780 return date;
781 }
782
783 /** Move transition.
784 * <p>
785 * When moving a transition to past or future infinity, it will be disconnected
786 * from the time span it initially belonged to as the next or previous time
787 * span validity will be extends to infinity.
788 * </p>
789 * @param newDate new transition date
790 * @param eraseOverridden if true, spans that are entirely between current
791 * and new transition dates will be silently removed, if false and such
792 * spans exist, an exception will be triggered
793 */
794 public void resetDate(final FieldAbsoluteDate<F> newDate, final boolean eraseOverridden) {
795 if (newDate.isAfter(date)) {
796 // we are moving the transition towards future
797
798 // find span after new date
799 Span<S, F> newAfter = after;
800 while (newAfter.getEndTransition() != null &&
801 newAfter.getEndTransition().getDate().isBeforeOrEqualTo(newDate)) {
802 if (eraseOverridden) {
803 map.nbSpans--;
804 } else {
805 // forbidden collision detected
806 throw new OrekitException(OrekitMessages.TRANSITION_DATES_COLLISION,
807 date.toAbsoluteDate(),
808 newDate.toAbsoluteDate(),
809 newAfter.getEndTransition().getDate().toAbsoluteDate());
810 }
811 newAfter = newAfter.next();
812 }
813
814 synchronized (map) {
815 // perform update
816 date = newDate;
817 after = newAfter;
818 after.start = this;
819 map.current = before;
820
821 if (newDate.toAbsoluteDate().isInfinite()) {
822 // we have just moved the transition to future infinity, it should really disappear
823 map.nbSpans--;
824 map.lastSpan = before;
825 before.end = null;
826 }
827 }
828
829 } else {
830 // we are moving transition towards the past
831
832 // find span before new date
833 Span<S, F> newBefore = before;
834 while (newBefore.getStartTransition() != null &&
835 newBefore.getStartTransition().getDate().isAfterOrEqualTo(newDate)) {
836 if (eraseOverridden) {
837 map.nbSpans--;
838 } else {
839 // forbidden collision detected
840 throw new OrekitException(OrekitMessages.TRANSITION_DATES_COLLISION,
841 date.toAbsoluteDate(),
842 newDate.toAbsoluteDate(),
843 newBefore.getStartTransition().getDate().toAbsoluteDate());
844 }
845 newBefore = newBefore.previous();
846 }
847
848 synchronized (map) {
849 // perform update
850 date = newDate;
851 before = newBefore;
852 before.end = this;
853 map.current = after;
854
855 if (newDate.toAbsoluteDate().isInfinite()) {
856 // we have just moved the transition to past infinity, it should really disappear
857 map.nbSpans--;
858 map.firstSpan = after;
859 after.start = null;
860 }
861 }
862
863 }
864 }
865
866 /** Get the previous transition.
867 * @return previous transition, or null if this transition was the first one
868 */
869 public Transition<S, F> previous() {
870 return before.getStartTransition();
871 }
872
873 /** Get the next transition.
874 * @return next transition, or null if this transition was the last one
875 */
876 public Transition<S, F> next() {
877 return after.getEndTransition();
878 }
879
880 /** Get the entry valid before transition.
881 * @return entry valid before transition
882 * @see #getSpanBefore()
883 */
884 public S getBefore() {
885 return before.getData();
886 }
887
888 /** Get the {@link Span} valid before transition.
889 * @return {@link Span} valid before transition
890 */
891 public Span<S, F> getSpanBefore() {
892 return before;
893 }
894
895 /** Get the entry valid after transition.
896 * @return entry valid after transition
897 * @see #getSpanAfter()
898 */
899 public S getAfter() {
900 return after.getData();
901 }
902
903 /** Get the {@link Span} valid after transition.
904 * @return {@link Span} valid after transition
905 */
906 public Span<S, F> getSpanAfter() {
907 return after;
908 }
909
910 }
911
912 /** Holder for one time span.
913 * <p>
914 * This data type is dual to {@link Transition}, it
915 * is focused on one valid data, and gives access to
916 * surrounding transition dates whereas {@link Transition}
917 * is focused on one transition date, and gives access to
918 * surrounding valid data.
919 * </p>
920 * @param <S> Type of the data.
921 * @param <F> Type of the field elements
922 * @since 13.1
923 */
924 public static class Span<S, F extends CalculusFieldElement<F>> {
925
926 /** Field.*/
927 private final Field<F> field;
928
929 /** Valid data. */
930 private final S data;
931
932 /** Start of validity for the data (null if span extends to past infinity). */
933 private Transition<S, F> start;
934
935 /** End of validity for the data (null if span extends to future infinity). */
936 private Transition<S, F> end;
937
938 /** Simple constructor.
939 * @param field field to which dates belong
940 * @param data valid data
941 */
942 private Span(final Field<F> field, final S data) {
943 this.field = field;
944 this.data = data;
945 }
946
947 /** Get the data valid during this time span.
948 * @return data valid during this time span
949 */
950 public S getData() {
951 return data;
952 }
953
954 /** Get the previous time span.
955 * @return previous time span, or null if this time span was the first one
956 */
957 public Span<S, F> previous() {
958 return start == null ? null : start.getSpanBefore();
959 }
960
961 /** Get the next time span.
962 * @return next time span, or null if this time span was the last one
963 */
964 public Span<S, F> next() {
965 return end == null ? null : end.getSpanAfter();
966 }
967
968 /** Get the start of this time span.
969 * @return start of this time span (will be {@link FieldAbsoluteDate#getPastInfinity(Field)}
970 * if {@link #getStartTransition()} returns null)
971 * @see #getStartTransition()
972 */
973 public FieldAbsoluteDate<F> getStart() {
974 return start == null ? FieldAbsoluteDate.getPastInfinity(field) : start.getDate();
975 }
976
977 /** Get the transition at the start of this time span.
978 * @return transition at the start of this time span (null if span extends to past infinity)
979 * @see #getStart()
980 */
981 public Transition<S, F> getStartTransition() {
982 return start;
983 }
984
985 /** Get the end of this time span.
986 * @return end of this time span (will be {@link FieldAbsoluteDate#getFutureInfinity(Field)}
987 * if {@link #getEndTransition()} returns null)
988 * @see #getEndTransition()
989 */
990 public FieldAbsoluteDate<F> getEnd() {
991 return end == null ? FieldAbsoluteDate.getFutureInfinity(field) : end.getDate();
992 }
993
994 /** Get the transition at the end of this time span.
995 * @return transition at the end of this time span (null if span extends to future infinity)
996 * @see #getEnd()
997 */
998 public Transition<S, F> getEndTransition() {
999 return end;
1000 }
1001
1002 }
1003
1004 }