1   /* Copyright 2025-2026 Hawkeye 360 (HE360)
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.estimation.measurements;
18  
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  
23  import org.hipparchus.analysis.differentiation.Gradient;
24  import org.hipparchus.analysis.differentiation.GradientField;
25  import org.orekit.frames.FieldTransform;
26  import org.orekit.frames.Frame;
27  import org.orekit.frames.Transform;
28  import org.orekit.propagation.SpacecraftState;
29  import org.orekit.time.AbsoluteDate;
30  import org.orekit.time.FieldAbsoluteDate;
31  import org.orekit.time.clocks.ClockOffset;
32  import org.orekit.time.clocks.FieldClockOffset;
33  import org.orekit.time.clocks.QuadraticFieldClockModel;
34  import org.orekit.utils.FieldPVCoordinatesProvider;
35  import org.orekit.utils.PVCoordinatesProvider;
36  import org.orekit.utils.ParameterDriver;
37  import org.orekit.utils.TimeSpanMap.Span;
38  
39  /** Abstract interface that contains those methods necessary
40   *  for both space and ground-based satellite observers.
41   *
42   * @author Brianna Aubin
43   * @since 14.0
44   */
45  public interface Observer extends MeasurementParticipant {
46  
47      /** Get the type of object being used in measurement observations.
48       * @return boolean
49       */
50      boolean isSpaceBased();
51  
52      /** Return the PVCoordinatesProvider.
53       * @return pos/vel coordinates provider
54       */
55      PVCoordinatesProvider getPVCoordinatesProvider();
56  
57      /** Return the FieldPVCoordinatesProvider.
58       * @param freeParameters number of estimated parameters
59       * @param parameterIndices indices of the estimated parameters in derivatives computations, must be driver
60       * @return pos/vel coordinates provider for values with Gradient field
61       */
62      FieldPVCoordinatesProvider<Gradient> getFieldPVCoordinatesProvider(int freeParameters,
63                                                                         Map<String, Integer> parameterIndices);
64  
65      /** Get the transform between offset frame and inertial frame.
66       * <p>
67       * The offset frame takes the <em>current</em> position offset,
68       * polar motion and the meridian shift into account. The frame
69       * returned is disconnected from later changes in the parameters.
70       * When the {@link ParameterDriver parameters} managing these
71       * offsets are changed, the method must be called again to retrieve
72       * a new offset frame.
73       * </p>
74       * @param inertial inertial frame to transform to
75       * @param date date of the transform
76       * @param clockOffsetAlreadyApplied if true, the specified {@code date} is as read
77       * by the ground station clock (i.e. clock offset <em>not</em> compensated), if false,
78       * the specified {@code date} was already compensated and is a physical absolute date
79       * @return transform between offset frame and inertial frame, at <em>real</em> measurement
80       * date (i.e. with clock, Earth and station offsets applied)
81       */
82      Transform getOffsetToInertial(Frame inertial, AbsoluteDate date, boolean clockOffsetAlreadyApplied);
83  
84      /** Get the transform between offset frame and inertial frame with derivatives.
85       * <p>
86       * As the East and North vectors are not well defined at pole, the derivatives
87       * of these two vectors diverge to infinity as we get closer to the pole.
88       * So this method should not be used for stations less than 0.0001 degree from
89       * either poles.
90       * </p>
91       * @param inertial inertial frame to transform to
92       * @param clockDate date of the transform, clock offset and its derivatives already compensated
93       * @param freeParameters total number of free parameters in the gradient
94       * @param indices indices of the estimated parameters in derivatives computations, must be driver
95       * span name in map, not driver name or will not give right results (see {@link ParameterDriver#getValue(int, Map)})
96       * @return transform between offset frame and inertial frame, at specified date
97       */
98      default FieldTransform<Gradient> getOffsetToInertial(final Frame inertial,
99                                                           final AbsoluteDate clockDate,
100                                                          final int freeParameters,
101                                                          final Map<String, Integer> indices) {
102         // take clock offset into account
103         final Gradient offset = getFieldOffsetValue(freeParameters, clockDate, indices);
104         final FieldAbsoluteDate<Gradient> offsetCompensatedDate = new FieldAbsoluteDate<>(clockDate, offset.negate());
105 
106         return getOffsetToInertial(inertial, offsetCompensatedDate, freeParameters, indices);
107     }
108 
109     /** Get the transform between offset frame and inertial frame with derivatives.
110      * <p>
111      * As the East and North vectors are not well defined at pole, the derivatives
112      * of these two vectors diverge to infinity as we get closer to the pole.
113      * So this method should not be used for stations less than 0.0001 degree from
114      * either poles.
115      * </p>
116      * @param inertial inertial frame to transform to
117      * @param offsetCompensatedDate date of the transform, clock offset and its derivatives already compensated
118      * @param freeParameters total number of free parameters in the gradient
119      * @param indices indices of the estimated parameters in derivatives computations, must be driver
120      * span name in map, not driver name or will not give right results (see {@link ParameterDriver#getValue(int, Map)})
121      * @return transform between offset frame and inertial frame, at specified date
122      */
123     FieldTransform<Gradient> getOffsetToInertial(Frame inertial, FieldAbsoluteDate<Gradient> offsetCompensatedDate,
124                                                  int freeParameters, Map<String, Integer> indices);
125 
126     /** Create a map of the free parameter values.
127      * @param states list of ObservableSatellite measurement states
128      * @param parameterDrivers list of all parameter values for the measurement
129      * @return map of the free parameter values
130      */
131     static Map<String, Integer> getParameterIndices(final SpacecraftState[] states,
132                                                     final List<ParameterDriver> parameterDrivers) {
133 
134         // measurement derivatives are computed with respect to spacecraft state in inertial frame
135         // Parameters:
136         //  - 6k..6k+2 - Position of spacecraft k (counting k from 0 to nbSat-1) in inertial frame
137         //  - 6k+3..6k+5 - Velocity of spacecraft k (counting k from 0 to nbSat-1) in inertial frame
138         //  - 6nbSat..n - measurements parameters (clock offset, etc)
139         int nbParams = 6 * states.length;
140         final Map<String, Integer> paramIndices = new HashMap<>();
141         for (ParameterDriver measurementDriver : parameterDrivers) {
142             if (measurementDriver.isSelected()) {
143                 for (Span<String> span = measurementDriver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
144                     paramIndices.put(span.getData(), nbParams++);
145                 }
146             }
147         }
148         return paramIndices;
149     }
150 
151     /**
152      * Compute actual date taking into account clock offset.
153      * @param date date as registered by observer
154      * @return corrected date
155      */
156     default AbsoluteDate getCorrectedReceptionDate(final AbsoluteDate date) {
157         final ClockOffset localClock = getQuadraticClockModel().getOffset(date);
158         return date.shiftedBy(-localClock.getBias());
159     }
160 
161     /**
162      * Compute actual date taking into account clock offset.
163      * @param date date as registered by observer
164      * @param nbParams number of independent variables for automatic differentiation
165      * @param paramIndices mapping between parameter name and variable index
166      * @return corrected date
167      */
168     default FieldAbsoluteDate<Gradient> getCorrectedReceptionDateField(final AbsoluteDate date,
169                                                                        final int nbParams,
170                                                                        final Map<String, Integer> paramIndices) {
171         final QuadraticFieldClockModel<Gradient> quadraticClockModel = getQuadraticFieldClock(nbParams, date, paramIndices);
172         final GradientField field = GradientField.getField(nbParams);
173         final FieldAbsoluteDate<Gradient> fieldDate = new FieldAbsoluteDate<>(field, date);
174         final FieldClockOffset<Gradient> localClock = quadraticClockModel.getOffset(fieldDate);
175         return fieldDate.shiftedBy(localClock.getBias().negate());
176     }
177 
178 }