1   /* Copyright 2002-2025 Joseph Reed
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    * Joseph Reed 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.Objects;
20  
21  import org.hipparchus.geometry.euclidean.threed.Vector3D;
22  import org.orekit.errors.OrekitException;
23  import org.orekit.errors.OrekitMessages;
24  import org.orekit.frames.Frame;
25  import org.orekit.time.AbsoluteDate;
26  
27  /** Aggregate multiple {@link PVCoordinatesProvider} instances together.
28   * <p>
29   * This can be used to describe an aircraft or surface vehicle.
30   * </p>
31   * @author Joe Reed
32   * @since 11.3
33   */
34  public class AggregatedPVCoordinatesProvider implements PVCoordinatesProvider {
35  
36      /** Map of provider instances by transition time. */
37      private final TimeSpanMap<PVCoordinatesProvider> pvProvMap;
38  
39      /** Earliest date at which {@link #getPVCoordinates(AbsoluteDate, Frame)} will return a valid result. */
40      private final AbsoluteDate minDate;
41  
42      /** Latest date at which {@link #getPVCoordinates(AbsoluteDate, Frame)} will return a valid result. */
43      private final AbsoluteDate maxDate;
44  
45      /** Class constructor.
46       * <p>
47       * Note the provided {@code map} is used directly. Modification of the
48       * map after calling this constructor may result in undefined behavior.
49       * </p>
50       * @param map the map of {@link PVCoordinatesProvider} instances by time.
51       */
52      public AggregatedPVCoordinatesProvider(final TimeSpanMap<PVCoordinatesProvider> map) {
53          this(map, null, null);
54      }
55  
56      /** Class constructor.
57       * <p>
58       * Note the provided {@code map} is used directly. Modification of the
59       * map after calling this constructor may result in undefined behavior.
60       * </p>
61       * @param map the map of {@link PVCoordinatesProvider} instances by time.
62       * @param minDate the earliest valid date, {@code null} if always valid
63       * @param maxDate the latest valid date, {@code null} if always valid
64       */
65      public AggregatedPVCoordinatesProvider(final TimeSpanMap<PVCoordinatesProvider> map,
66                                             final AbsoluteDate minDate, final AbsoluteDate maxDate) {
67          this.pvProvMap = Objects.requireNonNull(map, "PVCoordinatesProvider map must be non-null");
68          this.minDate = minDate == null ? AbsoluteDate.PAST_INFINITY : minDate;
69          this.maxDate = maxDate == null ? AbsoluteDate.FUTURE_INFINITY : maxDate;
70      }
71  
72      /** Get the first date of the range.
73       * @return the first date of the range
74       */
75      public AbsoluteDate getMinDate() {
76          return minDate;
77      }
78  
79      /** Get the last date of the range.
80       * @return the last date of the range
81       */
82      public AbsoluteDate getMaxDate() {
83          return maxDate;
84      }
85  
86      @Override
87      public Vector3D getPosition(final AbsoluteDate date, final Frame frame) {
88          if (date.isBefore(minDate) || date.isAfter(maxDate)) {
89              throw new OrekitException(OrekitMessages.OUT_OF_RANGE_DATE, date, minDate, maxDate);
90          }
91          return pvProvMap.get(date).getPosition(date, frame);
92      }
93  
94      @Override
95      public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame) {
96          if (date.isBefore(minDate) || date.isAfter(maxDate)) {
97              throw new OrekitException(OrekitMessages.OUT_OF_RANGE_DATE, date, minDate, maxDate);
98          }
99          return pvProvMap.get(date).getPVCoordinates(date, frame);
100     }
101 
102     /**
103      * Builder class for {@link AggregatedPVCoordinatesProvider}.
104      */
105     public static class Builder {
106 
107         /** Time span map holding the incremental values. */
108         private final TimeSpanMap<PVCoordinatesProvider> pvProvMap;
109 
110         /**
111          * Create a builder using the {@link InvalidPVProvider} as the initial provider.
112          */
113         public Builder() {
114             this(new InvalidPVProvider());
115         }
116 
117         /**
118          * Create a builder using the provided initial provider.
119          *
120          * @param initialProvider the inital provider
121          */
122         public Builder(final PVCoordinatesProvider initialProvider) {
123             pvProvMap = new TimeSpanMap<>(initialProvider);
124         }
125 
126         /** Add a {@link PVCoordinatesProvider} to the collection.
127          * <p>
128          * The provided date is the transition time, at which this provider will be used.
129          * </p>
130          * @param date the transition date
131          * @param pvProv the provider
132          * @param erasesLater if true, the entry erases all existing transitions that are later than {@code date}
133          * @return this builder instance
134          * @see TimeSpanMap#addValidAfter(Object, AbsoluteDate, boolean)
135          */
136         public Builder addPVProviderAfter(final AbsoluteDate date,
137                                           final PVCoordinatesProvider pvProv,
138                                           final boolean erasesLater) {
139             pvProvMap.addValidAfter(pvProv, date, erasesLater);
140             return this;
141         }
142 
143         /** Add a {@link PVCoordinatesProvider} to the collection.
144          * <p>
145          * The provided date is the final transition time, before which this provider will be used.
146          * </p>
147          * @param date the transition date
148          * @param pvProv the provider
149          * @param erasesEarlier if true, the entry erases all existing transitions that are earlier than {@code date}
150          * @return this builder instance
151          * @see TimeSpanMap#addValidBefore(Object, AbsoluteDate, boolean)
152          */
153         public Builder addPVProviderBefore(final AbsoluteDate date, final PVCoordinatesProvider pvProv, final boolean erasesEarlier) {
154             pvProvMap.addValidBefore(pvProv, date, erasesEarlier);
155             return this;
156         }
157 
158         /** Indicate the date before which the resulting PVCoordinatesProvider is invalid.
159          *
160          * @param firstValidDate first date at which the resuling provider should be valid
161          * @return this instance
162          */
163         public Builder invalidBefore(final AbsoluteDate firstValidDate) {
164             pvProvMap.addValidBefore(new InvalidPVProvider(), firstValidDate, true);
165             return this;
166         }
167 
168         /** Indicate the date after which the resulting PVCoordinatesProvider is invalid.
169          *
170          * @param lastValidDate last date at which the resuling provider should be valid
171          * @return this instance
172          */
173         public Builder invalidAfter(final AbsoluteDate lastValidDate) {
174             pvProvMap.addValidAfter(new InvalidPVProvider(), lastValidDate, true);
175             return this;
176         }
177 
178         /** Build the aggregated PVCoordinatesProvider.
179          *
180          * @return the new provider instance.
181          */
182         public AggregatedPVCoordinatesProvider build() {
183             AbsoluteDate minDate = null;
184             AbsoluteDate maxDate = null;
185             // check the first span
186             if (pvProvMap.getFirstTransition() != null) {
187                 if (pvProvMap.getFirstTransition().getBefore() instanceof InvalidPVProvider) {
188                     minDate = pvProvMap.getFirstTransition().getDate();
189                 }
190             }
191             if (pvProvMap.getLastTransition() != null) {
192                 if (pvProvMap.getLastTransition().getAfter() instanceof InvalidPVProvider) {
193                     maxDate = pvProvMap.getLastTransition().getDate();
194                 }
195             }
196             return new AggregatedPVCoordinatesProvider(pvProvMap, minDate, maxDate);
197         }
198     }
199 
200     /** Implementation of {@link PVCoordinatesProvider} that throws an {@link OrekitException} exception.
201      *
202      */
203     public static class InvalidPVProvider implements PVCoordinatesProvider {
204 
205         /** Empty constructor.
206          * <p>
207          * This constructor is not strictly necessary, but it prevents spurious
208          * javadoc warnings with JDK 18 and later.
209          * </p>
210          * @since 12.0
211          */
212         public InvalidPVProvider() {
213             // nothing to do
214         }
215 
216         @Override
217         public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame) {
218             throw new OrekitException(OrekitMessages.OUT_OF_RANGE_DATE, date);
219         }
220 
221     }
222 
223 }