1   /* Copyright 2022-2026 Romain Serra
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.model;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
21  import org.hipparchus.geometry.euclidean.threed.Vector3D;
22  import org.hipparchus.util.FastMath;
23  import org.hipparchus.util.MathArrays;
24  import org.orekit.annotation.DefaultDataContext;
25  import org.orekit.bodies.BodyShape;
26  import org.orekit.bodies.FieldGeodeticPoint;
27  import org.orekit.bodies.GeodeticPoint;
28  import org.orekit.errors.OrekitException;
29  import org.orekit.errors.OrekitMessages;
30  import org.orekit.frames.FieldStaticTransform;
31  import org.orekit.frames.Frame;
32  import org.orekit.frames.FramesFactory;
33  import org.orekit.frames.StaticTransform;
34  import org.orekit.signal.FieldSignalReceptionCondition;
35  import org.orekit.signal.SignalReceptionCondition;
36  import org.orekit.signal.SignalTravelTimeModel;
37  import org.orekit.time.AbsoluteDate;
38  import org.orekit.time.FieldAbsoluteDate;
39  import org.orekit.utils.FieldPVCoordinatesProvider;
40  import org.orekit.utils.PVCoordinatesProvider;
41  
42  /**
43   * Perfect measurement model for topocentric azimuth and elevation model.
44   * The sensor is the signal receiver and is assumed to be ground-fixed.
45   * @since 14.0
46   * @author Romain Serra
47   */
48  public class TopocentricAzElModel extends AbstractAngularMeasurementModel {
49  
50      /** Body shape where sensor is fixed. */
51      private final BodyShape bodyShape;
52  
53      /** Inertial frame needed for intermediate computations. */
54      private final Frame inertialFrame;
55  
56      /**
57       * Constructor with default inertial frame.
58       * @param bodyShape Earth-fixed frame
59       * @param signalTravelTimeModel time delay computer
60       */
61      @DefaultDataContext
62      public TopocentricAzElModel(final BodyShape bodyShape,
63                                  final SignalTravelTimeModel signalTravelTimeModel) {
64          this(FramesFactory.getGCRF(), bodyShape, signalTravelTimeModel);
65      }
66  
67      /**
68       * Constructor.
69       * @param inertialFrame inertial frame needed for intermediate computations
70       * @param bodyShape Earth-fixed frame
71       * @param signalTravelTimeModel time delay computer
72       */
73      public TopocentricAzElModel(final Frame inertialFrame, final BodyShape bodyShape,
74                                  final SignalTravelTimeModel signalTravelTimeModel) {
75          super(signalTravelTimeModel);
76          if (!inertialFrame.isPseudoInertial()) {
77              throw new OrekitException(OrekitMessages.NON_PSEUDO_INERTIAL_FRAME, inertialFrame.getName());
78          }
79          this.inertialFrame = inertialFrame;
80          this.bodyShape = bodyShape;
81      }
82  
83      /**
84       * Compute theoretical measurement.
85       * @param receiver receiver geodetic coordinates
86       * @param receptionDate signal reception date
87       * @param emitter signal emitter coordinates provider
88       * @return azimuth-elevation (radians)
89       */
90      public double[] value(final GeodeticPoint receiver, final AbsoluteDate receptionDate,
91                            final PVCoordinatesProvider emitter) {
92          return value(receiver, receptionDate, emitter, receptionDate);
93      }
94  
95      /**
96       * Compute theoretical measurement with guess for emission date.
97       * @param receiver receiver geodetic coordinates
98       * @param receptionDate signal reception date
99       * @param emitter signal emitter coordinates provider
100      * @param approxEmissionDate guess for the emission date (shall be adjusted by signal travel time computer)
101      * @return azimuth-elevation (radians)
102      */
103     public double[] value(final GeodeticPoint receiver, final AbsoluteDate receptionDate,
104                           final PVCoordinatesProvider emitter, final AbsoluteDate approxEmissionDate) {
105         // Compute line-of-sight
106         final Frame bodyFixedFrame = bodyShape.getBodyFrame();
107         final Vector3D bodyFixedReceiverPosition = bodyShape.transform(receiver);
108         final StaticTransform toInertialFrameAtReception = bodyFixedFrame.getStaticTransformTo(inertialFrame, receptionDate);
109         final Vector3D receiverPosition = toInertialFrameAtReception.transformPosition(bodyFixedReceiverPosition);
110         final SignalReceptionCondition receptionCondition = new SignalReceptionCondition(receptionDate, receiverPosition,
111                 inertialFrame);
112         final Vector3D apparentLineOfSight = getEmitterToReceiverVector(receptionCondition, emitter, approxEmissionDate)
113                 .normalize();
114 
115         // Compute azimuth and elevation
116         final Vector3D east = toInertialFrameAtReception.transformVector(receiver.getEast());
117         final Vector3D north = toInertialFrameAtReception.transformVector(receiver.getNorth());
118         final Vector3D zenith = toInertialFrameAtReception.transformVector(receiver.getZenith());
119         final double azimuth = FastMath.atan2(apparentLineOfSight.dotProduct(east), apparentLineOfSight.dotProduct(north));
120         final double elevation = FastMath.asin(apparentLineOfSight.dotProduct(zenith) / apparentLineOfSight.getNorm2());
121         return new double[] { azimuth, elevation };
122     }
123 
124     /**
125      * Compute theoretical measurement with FIeld.
126      * @param <T> field type
127      * @param receiver receiver geodetic coordinates
128      * @param receptionDate signal reception date
129      * @param emitter signal emitter coordinates provider
130      * @return azimuth-elevation (radians)
131      */
132     public <T extends CalculusFieldElement<T>> T[] value(final FieldGeodeticPoint<T> receiver,
133                                                          final FieldAbsoluteDate<T> receptionDate,
134                                                          final FieldPVCoordinatesProvider<T> emitter) {
135         return value(receiver, receptionDate, emitter, receptionDate);
136     }
137 
138     /**
139      * Compute theoretical measurement with FIeld with guess for emission date.
140      * @param <T> field type
141      * @param receiver receiver geodetic coordinates
142      * @param receptionDate signal reception date
143      * @param emitter signal emitter coordinates provider
144      * @param approxEmissionDate guess for the emission date (shall be adjusted by signal travel time computer)
145      * @return azimuth-elevation (radians)
146      */
147     public <T extends CalculusFieldElement<T>>  T[] value(final FieldGeodeticPoint<T> receiver,
148                                                           final FieldAbsoluteDate<T> receptionDate,
149                                                           final FieldPVCoordinatesProvider<T> emitter,
150                                                           final FieldAbsoluteDate<T> approxEmissionDate) {
151         // Compute line-of-sight
152         final Frame bodyFixedFrame = bodyShape.getBodyFrame();
153         final FieldVector3D<T> bodyFixedReceiverPosition = bodyShape.transform(receiver);
154         final FieldStaticTransform<T> toInertialFrameAtReception = bodyFixedFrame.getStaticTransformTo(inertialFrame,
155                 receptionDate);
156         final FieldVector3D<T> receiverPosition = toInertialFrameAtReception.transformPosition(bodyFixedReceiverPosition);
157         final FieldSignalReceptionCondition<T> receptionCondition = new FieldSignalReceptionCondition<>(receptionDate,
158                 receiverPosition, inertialFrame);
159         final FieldVector3D<T> apparentLineOfSight = getEmitterToReceiverVector(receptionCondition, emitter, approxEmissionDate).normalize();
160 
161         // Compute azimuth and elevation
162         final FieldVector3D<T> east = toInertialFrameAtReception.transformVector(receiver.getEast());
163         final FieldVector3D<T> north = toInertialFrameAtReception.transformVector(receiver.getNorth());
164         final FieldVector3D<T> zenith = toInertialFrameAtReception.transformVector(receiver.getZenith());
165         final T azimuth = FastMath.atan2(apparentLineOfSight.dotProduct(east),
166                 apparentLineOfSight.dotProduct(north));
167         final T elevation = FastMath.asin(apparentLineOfSight.dotProduct(zenith)
168                 .divide(apparentLineOfSight.getNorm2()));
169         final T[] output = MathArrays.buildArray(receiverPosition.getX().getField(), 2);
170         output[0] = azimuth;
171         output[1] = elevation;
172         return output;
173     }
174 }