1   /* Copyright 2002-2021 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.Arrays;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.Map;
23  
24  import org.hipparchus.Field;
25  import org.hipparchus.analysis.differentiation.Gradient;
26  import org.hipparchus.analysis.differentiation.GradientField;
27  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
28  import org.hipparchus.util.MathUtils;
29  import org.orekit.frames.FieldTransform;
30  import org.orekit.propagation.SpacecraftState;
31  import org.orekit.time.AbsoluteDate;
32  import org.orekit.time.FieldAbsoluteDate;
33  import org.orekit.utils.ParameterDriver;
34  import org.orekit.utils.TimeStampedFieldPVCoordinates;
35  import org.orekit.utils.TimeStampedPVCoordinates;
36  
37  /** Class modeling an Azimuth-Elevation measurement from a ground station.
38   * The motion of the spacecraft during the signal flight time is taken into
39   * account. The date of the measurement corresponds to the reception on
40   * ground of the reflected signal.
41   *
42   * @author Thierry Ceolin
43   * @since 8.0
44   */
45  public class AngularAzEl extends AbstractMeasurement<AngularAzEl> {
46  
47      /** Ground station from which measurement is performed. */
48      private final GroundStation station;
49  
50      /** Simple constructor.
51       * @param station ground station from which measurement is performed
52       * @param date date of the measurement
53       * @param angular observed value
54       * @param sigma theoretical standard deviation
55       * @param baseWeight base weight
56       * @param satellite satellite related to this measurement
57       * @since 9.3
58       */
59      public AngularAzEl(final GroundStation station, final AbsoluteDate date,
60                         final double[] angular, final double[] sigma, final double[] baseWeight,
61                         final ObservableSatellite satellite) {
62          super(date, angular, sigma, baseWeight, Collections.singletonList(satellite));
63          addParameterDriver(station.getClockOffsetDriver());
64          addParameterDriver(station.getEastOffsetDriver());
65          addParameterDriver(station.getNorthOffsetDriver());
66          addParameterDriver(station.getZenithOffsetDriver());
67          addParameterDriver(station.getPrimeMeridianOffsetDriver());
68          addParameterDriver(station.getPrimeMeridianDriftDriver());
69          addParameterDriver(station.getPolarOffsetXDriver());
70          addParameterDriver(station.getPolarDriftXDriver());
71          addParameterDriver(station.getPolarOffsetYDriver());
72          addParameterDriver(station.getPolarDriftYDriver());
73          this.station = station;
74      }
75  
76      /** Get the ground station from which measurement is performed.
77       * @return ground station from which measurement is performed
78       */
79      public GroundStation getStation() {
80          return station;
81      }
82  
83      /** {@inheritDoc} */
84      @Override
85      protected EstimatedMeasurement<AngularAzEl> theoreticalEvaluation(final int iteration, final int evaluation,
86                                                                        final SpacecraftState[] states) {
87  
88          final SpacecraftState state = states[0];
89  
90          // Azimuth/elevation derivatives are computed with respect to spacecraft state in inertial frame
91          // and station parameters
92          // ----------------------
93          //
94          // Parameters:
95          //  - 0..2 - Position of the spacecraft in inertial frame
96          //  - 3..5 - Velocity of the spacecraft in inertial frame
97          //  - 6..n - station parameters (clock offset, station offsets, pole, prime meridian...)
98  
99          // Get the number of parameters used for derivation
100         // Place the selected drivers into a map
101         int nbParams = 6;
102         final Map<String, Integer> indices = new HashMap<>();
103         for (ParameterDriver driver : getParametersDrivers()) {
104             if (driver.isSelected()) {
105                 indices.put(driver.getName(), nbParams++);
106             }
107         }
108         final Field<Gradient>         field   = GradientField.getField(nbParams);
109         final FieldVector3D<Gradient> zero    = FieldVector3D.getZero(field);
110 
111         // Coordinates of the spacecraft expressed as a gradient
112         final TimeStampedFieldPVCoordinates<Gradient> pvaDS = getCoordinates(state, 0, nbParams);
113 
114         // Transform between station and inertial frame, expressed as a gradient
115         // The components of station's position in offset frame are the 3 last derivative parameters
116         final FieldTransform<Gradient> offsetToInertialDownlink =
117                         station.getOffsetToInertial(state.getFrame(), getDate(), nbParams, indices);
118         final FieldAbsoluteDate<Gradient> downlinkDateDS =
119                         offsetToInertialDownlink.getFieldDate();
120 
121         // Station position/velocity in inertial frame at end of the downlink leg
122         final TimeStampedFieldPVCoordinates<Gradient> stationDownlink =
123                         offsetToInertialDownlink.transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(downlinkDateDS,
124                                                                                                             zero, zero, zero));
125         // Station topocentric frame (east-north-zenith) in inertial frame expressed as Gradient
126         final FieldVector3D<Gradient> east   = offsetToInertialDownlink.transformVector(FieldVector3D.getPlusI(field));
127         final FieldVector3D<Gradient> north  = offsetToInertialDownlink.transformVector(FieldVector3D.getPlusJ(field));
128         final FieldVector3D<Gradient> zenith = offsetToInertialDownlink.transformVector(FieldVector3D.getPlusK(field));
129 
130         // Compute propagation times
131         // (if state has already been set up to pre-compensate propagation delay,
132         //  we will have delta == tauD and transitState will be the same as state)
133 
134         // Downlink delay
135         final Gradient tauD = signalTimeOfFlight(pvaDS, stationDownlink.getPosition(), downlinkDateDS);
136 
137         // Transit state
138         final Gradient        delta        = downlinkDateDS.durationFrom(state.getDate());
139         final Gradient        deltaMTauD   = tauD.negate().add(delta);
140         final SpacecraftState transitState = state.shiftedBy(deltaMTauD.getValue());
141 
142         // Transit state (re)computed with derivative structures
143         final TimeStampedFieldPVCoordinates<Gradient> transitStateDS = pvaDS.shiftedBy(deltaMTauD);
144 
145         // Station-satellite vector expressed in inertial frame
146         final FieldVector3D<Gradient> staSat = transitStateDS.getPosition().subtract(stationDownlink.getPosition());
147 
148         // Compute azimuth/elevation
149         final Gradient baseAzimuth = staSat.dotProduct(east).atan2(staSat.dotProduct(north));
150         final double   twoPiWrap   = MathUtils.normalizeAngle(baseAzimuth.getReal(), getObservedValue()[0]) -
151                                                 baseAzimuth.getReal();
152         final Gradient azimuth     = baseAzimuth.add(twoPiWrap);
153         final Gradient elevation   = staSat.dotProduct(zenith).divide(staSat.getNorm()).asin();
154 
155         // Prepare the estimation
156         final EstimatedMeasurement<AngularAzEl> estimated =
157                         new EstimatedMeasurement<>(this, iteration, evaluation,
158                                                    new SpacecraftState[] {
159                                                        transitState
160                                                    }, new TimeStampedPVCoordinates[] {
161                                                        transitStateDS.toTimeStampedPVCoordinates(),
162                                                        stationDownlink.toTimeStampedPVCoordinates()
163                                                    });
164 
165         // azimuth - elevation values
166         estimated.setEstimatedValue(azimuth.getValue(), elevation.getValue());
167 
168         // Partial derivatives of azimuth/elevation with respect to state
169         // (beware element at index 0 is the value, not a derivative)
170         final double[] azDerivatives = azimuth.getGradient();
171         final double[] elDerivatives = elevation.getGradient();
172         estimated.setStateDerivatives(0,
173                                       Arrays.copyOfRange(azDerivatives, 0, 6), Arrays.copyOfRange(elDerivatives, 0, 6));
174 
175         // Set partial derivatives with respect to parameters
176         // (beware element at index 0 is the value, not a derivative)
177         for (final ParameterDriver driver : getParametersDrivers()) {
178             final Integer index = indices.get(driver.getName());
179             if (index != null) {
180                 estimated.setParameterDerivatives(driver, azDerivatives[index], elDerivatives[index]);
181             }
182         }
183 
184         return estimated;
185     }
186 }