1   /* Copyright 2002-2025 Mark Rutten
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 org.hipparchus.analysis.UnivariateFunction;
20  import org.hipparchus.analysis.solvers.BracketingNthOrderBrentSolver;
21  import org.hipparchus.analysis.solvers.UnivariateSolver;
22  import org.hipparchus.geometry.euclidean.threed.Vector3D;
23  import org.hipparchus.util.FastMath;
24  import org.orekit.estimation.Context;
25  import org.orekit.frames.Frame;
26  import org.orekit.frames.Transform;
27  import org.orekit.propagation.SpacecraftState;
28  import org.orekit.time.AbsoluteDate;
29  import org.orekit.utils.Constants;
30  import org.orekit.utils.PVCoordinates;
31  import org.orekit.utils.ParameterDriver;
32  
33  import java.util.Arrays;
34  
35  /**
36   * Creates a list of {@link FDOA bistatic range-rate measurements}.
37   * @author Mark Rutten
38   */
39  public class FDOAMeasurementCreator extends MeasurementCreator {
40  
41      private final Context context;
42      private final GroundStation secondary;
43      private final GroundStation primary;
44      private final ObservableSatellite satellite;
45      private final double centreFrequency;
46  
47      public FDOAMeasurementCreator(final Context context, final double centreFrequency) {
48          this.context   = context;
49          this.secondary = context.TDOAstations.getKey();
50          this.primary = context.TDOAstations.getValue();
51          this.satellite = new ObservableSatellite(0);
52          this.centreFrequency = centreFrequency;
53      }
54  
55      public ObservableSatellite getSatellite() {
56          return satellite;
57      }
58  
59      public void init(SpacecraftState s0, AbsoluteDate t, double step) {
60          for (final GroundStation station : Arrays.asList(context.TDOAstations.getKey(),
61                                                           context.TDOAstations.getValue())) {
62              for (ParameterDriver driver : Arrays.asList(station.getClockOffsetDriver(),
63                                                          station.getEastOffsetDriver(),
64                                                          station.getNorthOffsetDriver(),
65                                                          station.getZenithOffsetDriver(),
66                                                          station.getPrimeMeridianOffsetDriver(),
67                                                          station.getPrimeMeridianDriftDriver(),
68                                                          station.getPolarOffsetXDriver(),
69                                                          station.getPolarDriftXDriver(),
70                                                          station.getPolarOffsetYDriver(),
71                                                          station.getPolarDriftYDriver())) {
72                  if (driver.getReferenceDate() == null) {
73                      driver.setReferenceDate(s0.getDate());
74                  }
75              }
76          }
77      }
78  
79      public void handleStep(final SpacecraftState currentState) {
80  
81          final AbsoluteDate  date     = currentState.getDate();
82          final Frame         inertial = currentState.getFrame();
83          final Vector3D      position = currentState.getPosition();
84          final Vector3D      velocity = currentState.getPVCoordinates().getVelocity();
85  
86          // Create a BRR measurement only if elevation for both stations is higher than 30°
87          if ((secondary.getBaseFrame().getTrackingCoordinates(position, inertial, date).getElevation()  > FastMath.toRadians(30.0)) &&
88              (primary.getBaseFrame().getTrackingCoordinates(position, inertial, date).getElevation() > FastMath.toRadians(30.0))) {
89  
90              // The solver used
91              final UnivariateSolver solver = new BracketingNthOrderBrentSolver(1.0e-12, 5);
92  
93              final double primaryDelay  = solver.solve(1000, new UnivariateFunction() {
94                  public double value(final double x) {
95                      final Transform t = primary.getOffsetToInertial(inertial, date.shiftedBy(x), false);
96                      final double d = Vector3D.distance(position, t.transformPosition(Vector3D.ZERO));
97                      return d - x * Constants.SPEED_OF_LIGHT;
98                  }
99              }, -1.0, 1.0);
100 
101             final AbsoluteDate primaryReceptionDate = currentState.getDate().shiftedBy(primaryDelay);
102             final PVCoordinates primaryPV   = primary.getOffsetToInertial(inertial, primaryReceptionDate, false)
103                                                        .transformPVCoordinates(PVCoordinates.ZERO);
104 
105             // line of sight at primary
106             final Vector3D primaryLOS = (position.subtract(primaryPV.getPosition())).normalize();
107 
108             // relative velocity, spacecraft-station, at the date of primary reception
109             final Vector3D deltaVp = velocity.subtract(primaryPV.getVelocity());
110 
111             final double secondaryDelay = solver.solve(1000, new UnivariateFunction() {
112                 public double value(final double x) {
113                     final Transform t = secondary.getOffsetToInertial(inertial, date.shiftedBy(x), false);
114                     final double d = Vector3D.distance(position, t.transformPosition(Vector3D.ZERO));
115                     return d - x * Constants.SPEED_OF_LIGHT;
116                 }
117             }, -1.0, 1.0);
118 
119             final AbsoluteDate secondaryReceptionDate = currentState.getDate().shiftedBy(secondaryDelay);
120             final PVCoordinates secondaryPV   = secondary.getOffsetToInertial(inertial, secondaryReceptionDate, false)
121                                                      .transformPVCoordinates(PVCoordinates.ZERO);
122 
123             // line of sight at secondary
124             final Vector3D secondaryLOS = (position.subtract(secondaryPV.getPosition())).normalize();
125 
126             // relative velocity, spacecraft-station, at the date of secondary reception
127             final Vector3D deltaVs = velocity.subtract(secondaryPV.getVelocity());
128 
129             // range rate difference at the date of reception
130             final double rrDifference = deltaVp.dotProduct(primaryLOS) - deltaVs.dotProduct(secondaryLOS);
131             final double fdoa = rrDifference * centreFrequency / Constants.SPEED_OF_LIGHT;
132 
133             addMeasurement(new FDOA(secondary, primary, centreFrequency, primaryReceptionDate,
134                     fdoa, 1.0, 10, satellite));
135 
136         }
137 
138     }
139 }