1   /* Copyright 2013-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.rugged.los;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.List;
22  import java.util.stream.Stream;
23  
24  import org.hipparchus.analysis.differentiation.Derivative;
25  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
26  import org.hipparchus.geometry.euclidean.threed.Vector3D;
27  import org.orekit.rugged.utils.DerivativeGenerator;
28  import org.orekit.time.AbsoluteDate;
29  import org.orekit.utils.ParameterDriver;
30  import org.orekit.utils.ParameterObserver;
31  import org.orekit.utils.TimeSpanMap;
32  
33  /** Builder for lines-of-sight list.
34   * <p>
35   * This class implements the <em>builder pattern</em> to create {@link TimeDependentLOS} instances.
36   * It does so by using a <em>fluent API</em> in order to clarify reading and allow
37   * later extensions with new configuration parameters.
38   * </p>
39   * <p>
40   * This builder aims at creating lines-of-sight directions which are
41   * the result of several transforms applied to an initial list of raw
42   * directions. It therefore allows to take into account the optical
43   * path due to mirrors and the alignments of sensors frames with respect
44   * to a spacecraft.
45   * </p>
46   * @see TimeDependentLOS
47   * @see <a href="https://en.wikipedia.org/wiki/Builder_pattern">Builder pattern (wikipedia)</a>
48   * @see <a href="https://en.wikipedia.org/wiki/Fluent_interface">Fluent interface (wikipedia)</a>
49   * @author Luc Maisonobe
50   */
51  public class LOSBuilder {
52  
53      /** Raw fixed line-of-sights. */
54      private final List<Vector3D> rawLOS;
55  
56      /** Transforms to be applied. */
57      private final List<LOSTransform> transforms;
58  
59      /** Flag for time-independent only transforms. */
60      private boolean timeIndependent;
61  
62      /** Create builder.
63       * @param rawLOS raw fixed lines-of-sight
64       */
65      public LOSBuilder(final List<Vector3D> rawLOS) {
66          this.rawLOS          = rawLOS;
67          this.transforms      = new ArrayList<>();
68          this.timeIndependent = true;
69      }
70  
71      /** Add a transform to be applied after the already registered transforms.
72       * @param transform transform to be applied to the lines-of-sight
73       * @return the builder instance
74       */
75      public LOSBuilder addTransform(final TimeIndependentLOSTransform transform) {
76          transforms.add(new TransformAdapter(transform));
77          return this;
78      }
79  
80      /** Add a transform to be applied after the already registered transforms.
81       * @param transform transform to be applied to the lines-of-sight
82       * @return the builder instance
83       */
84      public LOSBuilder addTransform(final LOSTransform transform) {
85          transforms.add(transform);
86          timeIndependent = false;
87          return this;
88      }
89  
90      /** Build a lines-of-sight provider.
91       * @return lines-of-sight provider
92       */
93      public TimeDependentLOS build() {
94  
95          if (timeIndependent) {
96              // fast implementation for time-independent lines-of-sight
97              return new FixedLOS(rawLOS, transforms);
98          } else {
99              // regular implementation, for time-dependent lines-of-sight
100             return new TransformsSequenceLOS(rawLOS, transforms);
101         }
102 
103     }
104 
105     /** Adapter from time-independent transform to time-dependent transform. */
106     private static class TransformAdapter implements LOSTransform {
107 
108         /** Underlying transform. */
109         private final TimeIndependentLOSTransform transform;
110 
111         /** Simple constructor.
112          * @param transform underlying time-independent transform
113          */
114         TransformAdapter(final TimeIndependentLOSTransform transform) {
115             this.transform = transform;
116         }
117 
118         /** {@inheritDoc} */
119         @Override
120         public Vector3D transformLOS(final int i, final Vector3D los, final AbsoluteDate date) {
121             return transform.transformLOS(i, los);
122         }
123 
124         /** {@inheritDoc} */
125         @Override
126         public <T extends Derivative<T>> FieldVector3D<T> transformLOS(final int i, final FieldVector3D<T> los,
127                                                                        final AbsoluteDate date, final DerivativeGenerator<T> generator) {
128             return transform.transformLOS(i, los, generator);
129         }
130 
131         /** {@inheritDoc} */
132         @Override
133         public Stream<ParameterDriver> getParametersDrivers() {
134             return transform.getParametersDrivers();
135         }
136 
137     }
138 
139     /** Implement time-independent LOS by recomputing directions by applying all transforms each time. */
140     private static class TransformsSequenceLOS implements TimeDependentLOS {
141 
142         /** Raw direction. */
143         private final Vector3D[] raw;
144 
145         /** Transforms to be applied. */
146         private final List<LOSTransform> transforms;
147 
148         /** Simple constructor.
149          * @param raw raw directions
150          * @param transforms transforms to apply
151          */
152         TransformsSequenceLOS(final List<Vector3D> raw, final List<LOSTransform> transforms) {
153 
154             // copy the lists, to ensure immutability of the built object,
155             // in case addTransform is called again after build
156             // or the raw LOS list is changed by caller
157             this.raw = new Vector3D[raw.size()];
158             for (int i = 0; i < raw.size(); ++i) {
159                 this.raw[i] = raw.get(i);
160             }
161 
162             this.transforms = new ArrayList<>(transforms);
163 
164         }
165 
166         /** {@inheritDoc} */
167         public int getNbPixels() {
168             return raw.length;
169         }
170 
171         /** {@inheritDoc} */
172         @Override
173         public Vector3D getLOS(final int index, final AbsoluteDate date) {
174             Vector3D los = raw[index];
175             for (final LOSTransform transform : transforms) {
176                 los = transform.transformLOS(index, los, date);
177             }
178             return los.normalize();
179         }
180 
181         /** {@inheritDoc} */
182         @Override
183         public <T extends Derivative<T>> FieldVector3D<T> getLOSDerivatives(final int index, final AbsoluteDate date,
184                                                                             final DerivativeGenerator<T> generator) {
185 
186             // the raw line of sights are considered to be constant
187             FieldVector3D<T> los = new FieldVector3D<>(generator.constant(raw[index].getX()),
188                                                        generator.constant(raw[index].getY()),
189                                                        generator.constant(raw[index].getZ()));
190 
191             // apply the transforms, which depend on parameters and hence may introduce non-zero derivatives
192             for (final LOSTransform transform : transforms) {
193                 los = transform.transformLOS(index, los, date, generator);
194             }
195 
196             return los.normalize();
197 
198         }
199 
200         @Override
201         public Stream<ParameterDriver> getParametersDrivers() {
202             Stream<ParameterDriver> drivers = Stream.<ParameterDriver>empty();
203             for (final LOSTransform transform : transforms) {
204                 drivers = Stream.concat(drivers, transform.getParametersDrivers());
205             }
206             return drivers;
207         }
208 
209     }
210 
211     /** Implement time-independent LOS by computing directions only when parameters are changed. */
212     private static class FixedLOS extends TransformsSequenceLOS {
213 
214         /** transformed direction for los. */
215         private final Vector3D[] transformed;
216 
217         /** Simple constructor.
218          * @param raw raw directions
219          * @param transforms transforms to apply (must be time-independent!)
220          */
221         FixedLOS(final List<Vector3D> raw, final List<LOSTransform> transforms) {
222 
223             super(raw, transforms);
224             transformed = new Vector3D[raw.size()];
225 
226             // we will reset the transforms to null when parameters are changed
227             final ParameterObserver resettingObserver = new ParameterObserver() {
228                 /** {@inheritDoc} */
229                 @Override
230                 public void valueChanged(final double previousValue, final ParameterDriver driver, final AbsoluteDate date) {
231                     Arrays.fill(transformed, null);
232                 }
233 
234                 /** {@inheritDoc} */
235                 @Override
236                 public void valueSpanMapChanged(final TimeSpanMap<Double> previousValueSpanMap, final ParameterDriver driver) {
237                     Arrays.fill(transformed, null);
238                 }
239             };
240             getParametersDrivers().forEach(driver -> {
241                 driver.addObserver(resettingObserver);
242             });
243         }
244 
245         /** {@inheritDoc} */
246         @Override
247         public Vector3D getLOS(final int index, final AbsoluteDate date) {
248             if (transformed[index] == null) {
249                 // recompute the transformed los direction only if needed
250                 transformed[index] = super.getLOS(index, date);
251             }
252             return transformed[index];
253         }
254 
255     }
256 
257 }