1 /* Copyright 2002-2021 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.NavigableSet;
21 import java.util.TreeSet;
22 import java.util.function.Consumer;
23
24 import org.orekit.time.AbsoluteDate;
25 import org.orekit.time.ChronologicalComparator;
26 import org.orekit.time.TimeStamped;
27
28 /** Container for objects that apply to spans of time.
29
30 * @param <T> Type of the data.
31
32 * @author Luc Maisonobe
33 * @since 7.1
34 */
35 public class TimeSpanMap<T> {
36
37 /** Container for the data. */
38 private final NavigableSet<Transition<T>> data;
39
40 /** Create a map containing a single object, initially valid throughout the timeline.
41 * <p>
42 * The real validity of this first entry will be truncated as other
43 * entries are either {@link #addValidBefore(Object, AbsoluteDate)
44 * added before} it or {@link #addValidAfter(Object, AbsoluteDate)
45 * added after} it.
46 * </p>
47 * @param entry entry (initially valid throughout the timeline)
48 */
49 public TimeSpanMap(final T entry) {
50 data = new TreeSet<Transition<T>>(new ChronologicalComparator());
51 data.add(new Transition<T>(AbsoluteDate.ARBITRARY_EPOCH, entry, entry));
52 }
53
54 /** Add an entry valid before a limit date.
55 * <p>
56 * As an entry is valid, it truncates the validity of the neighboring
57 * entries already present in the map.
58 * </p>
59 * <p>
60 * The transition dates should be entered only once, either
61 * by a call to this method or by a call to {@link #addValidAfter(Object,
62 * AbsoluteDate)}. Repeating a transition date will lead to unexpected
63 * result and is not supported.
64 * </p>
65 * <p>
66 * Using addValidBefore(entry, t) will make 'entry' valid in ]-∞, t[ (note the open bracket).
67 * </p>
68 * @param entry entry to add
69 * @param latestValidityDate date before which the entry is valid
70 * (must be different from <em>all</em> dates already used for transitions)
71 */
72 public void addValidBefore(final T entry, final AbsoluteDate latestValidityDate) {
73
74 if (data.size() == 1) {
75 final Transition<T> single = data.first();
76 if (single.getBefore() == single.getAfter()) {
77 // the single entry was a dummy one, without a real transition
78 // we replace it entirely
79 data.clear();
80 data.add(new Transition<T>(latestValidityDate, entry, single.getAfter()));
81 return;
82 }
83 }
84
85 final Transition<T> previous =
86 data.floor(new Transition<T>(latestValidityDate, entry, null));
87 if (previous == null) {
88 // the new transition will be the first one
89 data.add(new Transition<T>(latestValidityDate, entry, data.first().getBefore()));
90 } else {
91 // the new transition will be after the previous one
92 data.remove(previous);
93 data.add(new Transition<T>(previous.date, previous.getBefore(), entry));
94 data.add(new Transition<T>(latestValidityDate, entry, previous.getAfter()));
95 }
96
97 }
98
99 /** Add an entry valid after a limit date.
100 * <p>
101 * As an entry is valid, it truncates the validity of the neighboring
102 * entries already present in the map.
103 * </p>
104 * <p>
105 * The transition dates should be entered only once, either
106 * by a call to this method or by a call to {@link #addValidBefore(Object,
107 * AbsoluteDate)}. Repeating a transition date will lead to unexpected
108 * result and is not supported.
109 * </p>
110 * <p>
111 * Using addValidAfter(entry, t) will make 'entry' valid [t, +∞[ (note the closed bracket).
112 * </p>
113 * @param entry entry to add
114 * @param earliestValidityDate date after which the entry is valid
115 * (must be different from <em>all</em> dates already used for transitions)
116 */
117 public void addValidAfter(final T entry, final AbsoluteDate earliestValidityDate) {
118
119 if (data.size() == 1) {
120 final Transition<T> single = data.first();
121 if (single.getBefore() == single.getAfter()) {
122 // the single entry was a dummy one, without a real transition
123 // we replace it entirely
124 data.clear();
125 data.add(new Transition<T>(earliestValidityDate, single.getBefore(), entry));
126 return;
127 }
128 }
129
130 final Transition<T> next =
131 data.ceiling(new Transition<T>(earliestValidityDate, entry, null));
132 if (next == null) {
133 // the new transition will be the last one
134 data.add(new Transition<T>(earliestValidityDate, data.last().getAfter(), entry));
135 } else {
136 // the new transition will be before the next one
137 data.remove(next);
138 data.add(new Transition<T>(earliestValidityDate, next.getBefore(), entry));
139 data.add(new Transition<T>(next.date, entry, next.getAfter()));
140 }
141
142 }
143
144 /** Get the entry valid at a specified date.
145 * @param date date at which the entry must be valid
146 * @return valid entry at specified date
147 */
148 public T get(final AbsoluteDate date) {
149 final Transition<T> previous = data.floor(new Transition<T>(date, null, null));
150 if (previous == null) {
151 // there are no transition before the specified date
152 // return the first valid entry
153 return data.first().getBefore();
154 } else {
155 return previous.getAfter();
156 }
157 }
158
159 /** Get the time span containing a specified date.
160 * @param date date belonging to the desired time span
161 * @return time span containing the specified date
162 * @since 9.3
163 */
164 public Span<T> getSpan(final AbsoluteDate date) {
165 final Transition<T> previous = data.floor(new Transition<T>(date, null, null));
166 if (previous == null) {
167 // there are no transition before the specified date
168 // return the first valid entry
169 return new Span<>(data.first().getBefore(),
170 AbsoluteDate.PAST_INFINITY,
171 data.first().getDate());
172 } else {
173 final Transition<T> next = data.higher(previous);
174 return new Span<>(previous.getAfter(),
175 previous.getDate(),
176 next == null ? AbsoluteDate.FUTURE_INFINITY : next.getDate());
177 }
178 }
179
180 /** Extract a range of the map.
181 * <p>
182 * The object returned will be a new independent instance that will contain
183 * only the transitions that lie in the specified range.
184 * </p>
185 * <p>
186 * Consider for example a map containing objects O₀ valid before t₁, O₁ valid
187 * between t₁ and t₂, O₂ valid between t₂ and t₃, O₃ valid between t₃ and t₄,
188 * and O₄ valid after t₄. then calling this method with a {@code start}
189 * date between t₁ and t₂ and a {@code end} date between t₃ and t₄
190 * will result in a new map containing objects O₁ valid before t₂, O₂
191 * valid between t₂ and t₃, and O₃ valid after t₃. The validity of O₁
192 * is therefore extended in the past, and the validity of O₃ is extended
193 * in the future.
194 * </p>
195 * @param start earliest date at which a transition is included in the range
196 * (may be set to {@link AbsoluteDate#PAST_INFINITY} to keep all early transitions)
197 * @param end latest date at which a transition is included in the r
198 * (may be set to {@link AbsoluteDate#FUTURE_INFINITY} to keep all late transitions)
199 * @return a new instance with all transitions restricted to the specified range
200 * @since 9.2
201 */
202 public TimeSpanMap<T> extractRange(final AbsoluteDate start, final AbsoluteDate end) {
203
204 final NavigableSet<Transition<T>> inRange = data.subSet(new Transition<T>(start, null, null), true,
205 new Transition<T>(end, null, null), true);
206 if (inRange.isEmpty()) {
207 // there are no transitions at all in the range
208 // we need to pick up the only valid object
209 return new TimeSpanMap<>(get(start));
210 }
211
212 final TimeSpanMap<T> range = new TimeSpanMap<>(inRange.first().before);
213 for (final Transition<T> transition : inRange) {
214 range.addValidAfter(transition.after, transition.getDate());
215 }
216
217 return range;
218
219 }
220
221 /** Get an unmodifiable view of the sorted transitions.
222 * @return unmodifiable view of the sorted transitions
223 */
224 public NavigableSet<Transition<T>> getTransitions() {
225 return Collections.unmodifiableNavigableSet(data);
226 }
227
228 /**
229 * Performs an action for each element of map.
230 * <p>
231 * The action is performed chronologically.
232 * </p>
233 * @param action action to perform on the elements
234 * @since 10.3
235 */
236 public void forEach(final Consumer<T> action) {
237 boolean first = true;
238 for (Transition<T> transition : data) {
239 if (first) {
240 if (transition.getBefore() != null) {
241 action.accept(transition.getBefore());
242 }
243 first = false;
244 }
245 if (transition.getAfter() != null) {
246 action.accept(transition.getAfter());
247 }
248 }
249 }
250
251 /** Class holding transition times.
252 * <p>
253 * This data type is dual to {@link Span}, it is
254 * focused one transition date, and gives access to
255 * surrounding valid data whereas {@link Span} is focused
256 * on one valid data, and gives access to surrounding
257 * transition dates.
258 * </p>
259 * @param <S> Type of the data.
260 */
261 public static class Transition<S> implements TimeStamped {
262
263 /** Transition date. */
264 private final AbsoluteDate date;
265
266 /** Entry valid before the transition. */
267 private final S before;
268
269 /** Entry valid after the transition. */
270 private final S after;
271
272 /** Simple constructor.
273 * @param date transition date
274 * @param before entry valid before the transition
275 * @param after entry valid after the transition
276 */
277 private Transition(final AbsoluteDate date, final S before, final S after) {
278 this.date = date;
279 this.before = before;
280 this.after = after;
281 }
282
283 /** Get the transition date.
284 * @return transition date
285 */
286 @Override
287 public AbsoluteDate getDate() {
288 return date;
289 }
290
291 /** Get the entry valid before transition.
292 * @return entry valid before transition
293 */
294 public S getBefore() {
295 return before;
296 }
297
298 /** Get the entry valid after transition.
299 * @return entry valid after transition
300 */
301 public S getAfter() {
302 return after;
303 }
304
305 }
306
307 /** Holder for one time span.
308 * <p>
309 * This data type is dual to {@link Transition}, it
310 * is focused on one valid data, and gives access to
311 * surrounding transition dates whereas {@link Transition}
312 * is focused on one transition date, and gives access to
313 * surrounding valid data.
314 * </p>
315 * @param <S> Type of the data.
316 * @since 9.3
317 */
318 public static class Span<S> {
319
320 /** Valid data. */
321 private final S data;
322
323 /** Start of validity for the data. */
324 private final AbsoluteDate start;
325
326 /** End of validity for the data. */
327 private final AbsoluteDate end;
328
329 /** Simple constructor.
330 * @param data valid data
331 * @param start start of validity for the data
332 * @param end end of validity for the data
333 */
334 private Span(final S data, final AbsoluteDate start, final AbsoluteDate end) {
335 this.data = data;
336 this.start = start;
337 this.end = end;
338 }
339
340 /** Get the data valid during this time span.
341 * @return data valid during this time span
342 */
343 public S getData() {
344 return data;
345 }
346
347 /** Get the start of this time span.
348 * @return start of this time span
349 */
350 public AbsoluteDate getStart() {
351 return start;
352 }
353
354 /** Get the end of this time span.
355 * @return end of this time span
356 */
357 public AbsoluteDate getEnd() {
358 return end;
359 }
360
361 }
362
363 }