1   /* Copyright 2002-2026 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.estimation.measurements;
18  
19  import java.util.Map;
20  
21  import org.hipparchus.analysis.differentiation.Gradient;
22  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
23  import org.hipparchus.geometry.euclidean.threed.Vector3D;
24  import org.hipparchus.util.MathUtils;
25  import org.orekit.bodies.BodyShape;
26  import org.orekit.bodies.FieldGeodeticPoint;
27  import org.orekit.bodies.GeodeticPoint;
28  import org.orekit.estimation.measurements.model.TopocentricAzElModel;
29  import org.orekit.frames.Frame;
30  import org.orekit.propagation.SpacecraftState;
31  import org.orekit.signal.SignalReceptionCondition;
32  import org.orekit.signal.SignalTravelTimeModel;
33  import org.orekit.time.AbsoluteDate;
34  import org.orekit.time.FieldAbsoluteDate;
35  import org.orekit.utils.FieldPVCoordinatesProvider;
36  import org.orekit.utils.PVCoordinatesProvider;
37  import org.orekit.utils.TimeStampedFieldPVCoordinates;
38  import org.orekit.utils.TimeStampedPVCoordinates;
39  
40  /** Class modeling an Azimuth-Elevation measurement from a ground station.
41   * The motion of the spacecraft during the signal flight time is taken into
42   * account. The date of the measurement corresponds to the reception on
43   * ground of the reflected signal.
44   *
45   * @author Thierry Ceolin
46   * @since 8.0
47   */
48  public class AngularAzEl extends AngularMeasurement<AngularAzEl> {
49  
50      /** Type of the measurement. */
51      public static final String MEASUREMENT_TYPE = "AngularAzEl";
52  
53      /**
54       * Ground-based signal receiver.
55       */
56      private final GroundStation station;
57  
58      /** Simple constructor.
59       * @param station ground station from which measurement is performed
60       * @param date date of the measurement
61       * @param angular observed value
62       * @param measurementQuality measurement quality as used in estimation (in Orekit, the crossed-terms
63       *                           of the covariance matrix are only used by Kalman filters, not least squares)
64       * @param signalTravelTimeModel signal travel time model
65       * @param satellite satellite related to this measurement
66       * @since 14.0
67       */
68      public AngularAzEl(final GroundStation station, final AbsoluteDate date,
69                         final double[] angular, final MeasurementQuality measurementQuality,
70                         final SignalTravelTimeModel signalTravelTimeModel, final ObservableSatellite satellite) {
71          super(date, angular, measurementQuality, signalTravelTimeModel, satellite);
72          this.station = station;
73          station.getParametersDrivers().forEach(this::addParameterDriver);
74      }
75  
76      /** Simple constructor.
77       * @param station ground station from which measurement is performed
78       * @param date date of the measurement
79       * @param angular observed value
80       * @param sigma theoretical standard deviation
81       * @param baseWeight base weight
82       * @param satellite satellite related to this measurement
83       * @since 9.3
84       */
85      public AngularAzEl(final GroundStation station, final AbsoluteDate date,
86                         final double[] angular, final double[] sigma, final double[] baseWeight,
87                         final ObservableSatellite satellite) {
88          this(station, date, angular, new MeasurementQuality(sigma, baseWeight), new SignalTravelTimeModel(), satellite);
89      }
90  
91      /**
92       * Getter for the ground receiver.
93       * @return station
94       */
95      public GroundStation getStation() {
96          return station;
97      }
98  
99      /** {@inheritDoc} */
100     @Override
101     protected EstimatedMeasurementBase<AngularAzEl> theoreticalEvaluationWithoutDerivatives(final int iteration,
102                                                                                             final int evaluation,
103                                                                                             final SpacecraftState[] states) {
104         // Compute emission date
105         final AbsoluteDate receptionDate = station.getCorrectedReceptionDate(getDate());
106         final PVCoordinatesProvider receiverPVProvider = station.getPVCoordinatesProvider();
107         final SpacecraftState state = states[0];
108         final Frame frame = state.getFrame();
109         final PVCoordinatesProvider emitter = AbstractParticipant.extractPVCoordinatesProvider(state, state.getPVCoordinates());
110         final TimeStampedPVCoordinates receiverPV = receiverPVProvider.getPVCoordinates(receptionDate, frame);
111         final SignalReceptionCondition receptionCondition = new SignalReceptionCondition(receptionDate,
112                 receiverPV.getPosition(), frame);
113         final AbsoluteDate emissionDate = computeEmissionDate(receptionCondition, emitter);
114 
115         // Compute azimuth and elevation
116         final BodyShape bodyShape = station.getBaseFrame().getParentShape();
117         final GeodeticPoint geodeticPoint = bodyShape.transform(receiverPV.getPosition(), frame, receptionDate);
118         final TopocentricAzElModel measurementModel = new TopocentricAzElModel(frame, bodyShape,
119                 getSignalTravelTimeModel().getWarmedUpModel());
120         final double[] azEl = measurementModel.value(geodeticPoint, receptionDate, emitter, emissionDate);
121 
122         // Prepare the estimation
123         final double shift = emissionDate.durationFrom(state);
124         final SpacecraftState shiftedState = state.shiftedBy(shift);
125         final EstimatedMeasurementBase<AngularAzEl> estimated = new EstimatedMeasurementBase<>(this, iteration, evaluation,
126                 new SpacecraftState[] { shiftedState },
127                 new TimeStampedPVCoordinates[] { shiftedState.getPVCoordinates(), receiverPV });
128         estimated.setEstimatedValue(wrapFirstAngle(azEl[0]), azEl[1]);
129         return estimated;
130     }
131 
132     /** {@inheritDoc} */
133     @Override
134     protected EstimatedMeasurement<AngularAzEl> theoreticalEvaluation(final int iteration, final int evaluation,
135                                                                       final SpacecraftState[] states) {
136         // Azimuth/elevation derivatives are computed with respect to spacecraft state in inertial frame
137         // and station parameters
138         // ----------------------
139         //
140         // Parameters:
141         //  - 0..2 - Position of the spacecraft in inertial frame
142         //  - 3..5 - Velocity of the spacecraft in inertial frame
143         //  - 6..n - station parameters (clock offset, station offsets, pole, prime meridian...)
144 
145         // Create the parameter indices map
146         final Map<String, Integer> paramIndices = getParameterIndices(states);
147         final int nbParams = 6 * states.length + paramIndices.size();
148         final SpacecraftState state = states[0];
149         final TimeStampedFieldPVCoordinates<Gradient> pva = AbstractMeasurement.getCoordinates(state, 0, nbParams);
150 
151         // Compute emission date
152         final FieldPVCoordinatesProvider<Gradient> receiverPVProvider = station.getFieldPVCoordinatesProvider(nbParams,
153                 paramIndices);
154         final Frame frame = state.getFrame();
155         final FieldAbsoluteDate<Gradient> receptionDate = station.getCorrectedReceptionDateField(getDate(), nbParams, paramIndices);
156         final FieldVector3D<Gradient> receiverPosition = receiverPVProvider.getPosition(receptionDate, frame);
157         final FieldPVCoordinatesProvider<Gradient> emitter = AbstractParticipant.extractFieldPVCoordinatesProvider(state, pva);
158         final FieldAbsoluteDate<Gradient> emissionDate = computeEmissionDateField(frame, receiverPosition, receptionDate, emitter);
159 
160         // Compute azimuth and elevation
161         final BodyShape bodyShape = station.getBaseFrame().getParentShape();
162         final FieldGeodeticPoint<Gradient> geodeticPoint = bodyShape.transform(receiverPosition, frame, receptionDate);
163         final TopocentricAzElModel measurementModel = new TopocentricAzElModel(frame, bodyShape,
164                 getSignalTravelTimeModel().getWarmedUpModel());
165         final Gradient[] azEl = measurementModel.value(geodeticPoint, receptionDate, emitter, emissionDate);
166 
167         // Prepare the estimation
168         final double shift = emissionDate.toAbsoluteDate().durationFrom(state);
169         final SpacecraftState shiftedState = state.shiftedBy(shift);
170         final EstimatedMeasurement<AngularAzEl> estimated = new EstimatedMeasurement<>(this, iteration, evaluation,
171                 new SpacecraftState[] { shiftedState },
172                 new TimeStampedPVCoordinates[] {shiftedState.getPVCoordinates(),
173                         getStation().getPVCoordinatesProvider().getPVCoordinates(receptionDate.toAbsoluteDate(), frame) });
174         fillEstimatedMeasurement(azEl[0], azEl[1], paramIndices, estimated);
175         return estimated;
176     }
177 
178     /** Calculate the Line Of Sight of the given measurement.
179      * @param outputFrame output frame of the line of sight vector
180      * @return Vector3D the line of Sight of the measurement
181      */
182     public Vector3D getObservedLineOfSight(final Frame outputFrame) {
183         return station.getBaseFrame().getStaticTransformTo(outputFrame, getDate())
184             .transformVector(new Vector3D(MathUtils.SEMI_PI - getObservedValue()[0], getObservedValue()[1]));
185     }
186 
187 }