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                                                                                             final boolean fillParticipants) {
105         // Compute emission date
106         final AbsoluteDate receptionDate = station.getCorrectedReceptionDate(getDate());
107         final PVCoordinatesProvider receiverPVProvider = station.getPVCoordinatesProvider();
108         final SpacecraftState state = states[0];
109         final Frame frame = state.getFrame();
110         final PVCoordinatesProvider emitter = AbstractParticipant.extractPVCoordinatesProvider(state, state.getPVCoordinates());
111         final Vector3D receiverPosition = receiverPVProvider.getPosition(receptionDate, frame);
112         final SignalReceptionCondition receptionCondition = new SignalReceptionCondition(receptionDate,
113                 receiverPosition, frame);
114         final AbsoluteDate emissionDate = computeEmissionDate(receptionCondition, emitter);
115 
116         // Compute azimuth and elevation
117         final BodyShape bodyShape = station.getBaseFrame().getParentShape();
118         final GeodeticPoint geodeticPoint = bodyShape.transform(receiverPosition, frame, receptionDate);
119         final TopocentricAzElModel measurementModel = new TopocentricAzElModel(frame, bodyShape,
120                 getSignalTravelTimeModel().getWarmedUpModel());
121         final double[] azEl = measurementModel.value(geodeticPoint, receptionDate, emitter, emissionDate);
122 
123         // Prepare the estimation
124         final double shift = emissionDate.durationFrom(state);
125         final SpacecraftState shiftedState = state.shiftedBy(shift);
126         final EstimatedMeasurementBase<AngularAzEl> estimated = new EstimatedMeasurementBase<>(this, iteration, evaluation,
127                 new SpacecraftState[] { shiftedState }, fillParticipants ? new TimeStampedPVCoordinates[] {
128                         shiftedState.getPVCoordinates(), receiverPVProvider.getPVCoordinates(receptionDate, frame) } :
129                 new TimeStampedPVCoordinates[0]);
130         estimated.setEstimatedValue(wrapFirstAngle(azEl[0]), azEl[1]);
131         return estimated;
132     }
133 
134     /** {@inheritDoc} */
135     @Override
136     protected EstimatedMeasurement<AngularAzEl> theoreticalEvaluation(final int iteration, final int evaluation,
137                                                                       final SpacecraftState[] states) {
138         // Azimuth/elevation derivatives are computed with respect to spacecraft state in inertial frame
139         // and station parameters
140         // ----------------------
141         //
142         // Parameters:
143         //  - 0..2 - Position of the spacecraft in inertial frame
144         //  - 3..5 - Velocity of the spacecraft in inertial frame
145         //  - 6..n - station parameters (clock offset, station offsets, pole, prime meridian...)
146 
147         // Create the parameter indices map
148         final Map<String, Integer> paramIndices = getParameterIndices(states);
149         final int nbParams = 6 * states.length + paramIndices.size();
150         final SpacecraftState state = states[0];
151         final TimeStampedFieldPVCoordinates<Gradient> pva = AbstractMeasurement.getCoordinates(state, 0, nbParams);
152 
153         // Compute emission date
154         final FieldPVCoordinatesProvider<Gradient> receiverPVProvider = station.getFieldPVCoordinatesProvider(nbParams,
155                 paramIndices);
156         final Frame frame = state.getFrame();
157         final FieldAbsoluteDate<Gradient> receptionDate = station.getCorrectedReceptionDateField(getDate(), nbParams, paramIndices);
158         final FieldVector3D<Gradient> receiverPosition = receiverPVProvider.getPosition(receptionDate, frame);
159         final FieldPVCoordinatesProvider<Gradient> emitter = AbstractParticipant.extractFieldPVCoordinatesProvider(state, pva);
160         final FieldAbsoluteDate<Gradient> emissionDate = computeEmissionDateField(frame, receiverPosition, receptionDate, emitter);
161 
162         // Compute azimuth and elevation
163         final BodyShape bodyShape = station.getBaseFrame().getParentShape();
164         final FieldGeodeticPoint<Gradient> geodeticPoint = bodyShape.transform(receiverPosition, frame, receptionDate);
165         final TopocentricAzElModel measurementModel = new TopocentricAzElModel(frame, bodyShape,
166                 getSignalTravelTimeModel().getWarmedUpModel());
167         final Gradient[] azEl = measurementModel.value(geodeticPoint, receptionDate, emitter, emissionDate);
168 
169         // Prepare the estimation
170         final double shift = emissionDate.toAbsoluteDate().durationFrom(state);
171         final SpacecraftState shiftedState = state.shiftedBy(shift);
172         final EstimatedMeasurement<AngularAzEl> estimated = new EstimatedMeasurement<>(this, iteration, evaluation,
173                 new SpacecraftState[] { shiftedState },
174                 new TimeStampedPVCoordinates[] {shiftedState.getPVCoordinates(),
175                         getStation().getPVCoordinatesProvider().getPVCoordinates(receptionDate.toAbsoluteDate(), frame) });
176         fillEstimatedMeasurement(azEl[0], azEl[1], paramIndices, estimated);
177         return estimated;
178     }
179 
180     /** Calculate the Line Of Sight of the given measurement.
181      * @param outputFrame output frame of the line of sight vector
182      * @return Vector3D the line of Sight of the measurement
183      */
184     public Vector3D getObservedLineOfSight(final Frame outputFrame) {
185         return station.getBaseFrame().getStaticTransformTo(outputFrame, getDate())
186             .transformVector(new Vector3D(MathUtils.SEMI_PI - getObservedValue()[0], getObservedValue()[1]));
187     }
188 
189 }