1 /* Copyright 2002-2025 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.gnss;
18
19 import java.util.List;
20
21 import org.hipparchus.geometry.euclidean.threed.Vector3D;
22 import org.hipparchus.linear.MatrixUtils;
23 import org.hipparchus.linear.RealMatrix;
24 import org.hipparchus.util.FastMath;
25 import org.orekit.bodies.GeodeticPoint;
26 import org.orekit.bodies.OneAxisEllipsoid;
27 import org.orekit.errors.OrekitException;
28 import org.orekit.errors.OrekitMessages;
29 import org.orekit.frames.TopocentricFrame;
30 import org.orekit.propagation.Propagator;
31 import org.orekit.time.AbsoluteDate;
32 import org.orekit.utils.ElevationMask;
33 import org.orekit.utils.TrackingCoordinates;
34
35 /**
36 * This class aims at computing the dilution of precision.
37 *
38 * @author Pascal Parraud
39 * @since 8.0
40 * @see <a href="http://en.wikipedia.org/wiki/Dilution_of_precision_%28GPS%29">Dilution of precision</a>
41 */
42 public class DOPComputer {
43
44 // Constants
45 /** Minimum elevation : 0°. */
46 public static final double DOP_MIN_ELEVATION = 0.;
47
48 /** Minimum number of propagators for DOP computation. */
49 private static final int DOP_MIN_PROPAGATORS = 4;
50
51 // Fields
52 /** The location as a topocentric frame. */
53 private final TopocentricFrame frame;
54
55 /** Elevation mask used for computation, if defined. */
56 private final ElevationMask elevationMask;
57
58 /** Minimum elevation value used if no mask is defined. */
59 private final double minElevation;
60
61 /**
62 * Constructor for DOP computation.
63 *
64 * @param frame the topocentric frame linked to the locations where DOP will be computed
65 * @param minElev the minimum elevation to consider (rad)
66 * @param elevMask the elevation mask to consider
67 */
68 private DOPComputer(final TopocentricFrame frame, final double minElev, final ElevationMask elevMask) {
69 // Set the topocentric frame
70 this.frame = frame;
71 // Set the min elevation
72 this.minElevation = minElev;
73 // Set the elevation mask
74 this.elevationMask = elevMask;
75 }
76
77 /**
78 * Creates a DOP computer for one location.
79 *
80 * <p>A minimum elevation of 0° is taken into account to compute
81 * visibility between the location and the GNSS spacecrafts.</p>
82 *
83 * @param shape the body shape on which the location is defined
84 * @param location the point of interest
85 * @return a configured DOP computer
86 */
87 public static DOPComputer create(final OneAxisEllipsoid shape, final GeodeticPoint location) {
88 return new DOPComputer(new TopocentricFrame(shape, location, "Location"), DOP_MIN_ELEVATION, null);
89 }
90
91 /**
92 * Set the minimum elevation.
93 *
94 * <p>This will override an elevation mask if it has been configured as such previously.</p>
95 *
96 * @param newMinElevation minimum elevation for visibility (rad)
97 * @return a new DOP computer with updated configuration (the instance is not changed)
98 *
99 * @see #getMinElevation()
100 */
101 public DOPComputer withMinElevation(final double newMinElevation) {
102 return new DOPComputer(frame, newMinElevation, null);
103 }
104
105 /**
106 * Set the elevation mask.
107 *
108 * <p>This will override the min elevation if it has been configured as such previously.</p>
109 *
110 * @param newElevationMask elevation mask to use for the computation
111 * @return a new detector with updated configuration (the instance is not changed)
112 *
113 * @see #getElevationMask()
114 */
115 public DOPComputer withElevationMask(final ElevationMask newElevationMask) {
116 return new DOPComputer(frame, DOP_MIN_ELEVATION, newElevationMask);
117 }
118
119 /**
120 * Compute the {@link DOP} at a given date for a set of GNSS spacecrafts.
121 * <p>Four GNSS spacecraft at least are needed to compute the DOP.
122 * If less than 4 propagators are provided, an exception will be thrown.
123 * If less than 4 spacecrafts are visible at the date, all DOP values will be
124 * set to {@link java.lang.Double#NaN NaN}.</p>
125 *
126 * @param date the computation date
127 * @param gnss the propagators for GNSS spacecraft involved in the DOP computation
128 * @return the {@link DOP} at the location
129 */
130 public DOP compute(final AbsoluteDate date, final List<Propagator> gnss) {
131
132 // Checks the number of provided propagators
133 if (gnss.size() < DOP_MIN_PROPAGATORS) {
134 throw new OrekitException(OrekitMessages.NOT_ENOUGH_GNSS_FOR_DOP, gnss.size(), DOP_MIN_PROPAGATORS);
135 }
136
137 // Initializes DOP values
138 double gdop = Double.NaN;
139 double pdop = Double.NaN;
140 double hdop = Double.NaN;
141 double vdop = Double.NaN;
142 double tdop = Double.NaN;
143
144 // Loop over the propagators of GNSS orbits
145 final double[][] satDir = new double[gnss.size()][4];
146 int satNb = 0;
147 for (Propagator prop : gnss) {
148 final Vector3D pos = prop.getPosition(date, frame);
149 final TrackingCoordinates tc = frame.getTrackingCoordinates(pos, frame, date);
150 final double elMin = (elevationMask != null) ?
151 elevationMask.getElevation(tc.getAzimuth()) :
152 minElevation;
153 // Only visible satellites are considered
154 if (tc.getElevation() > elMin) {
155 // Create the rows of the H matrix
156 final Vector3D r = pos.normalize();
157 satDir[satNb][0] = r.getX();
158 satDir[satNb][1] = r.getY();
159 satDir[satNb][2] = r.getZ();
160 satDir[satNb][3] = -1.;
161 satNb++;
162 }
163 }
164
165 // DOP values are computed only if at least 4 SV are visible from the location
166 if (satNb > 3) {
167 // Construct matrix H
168 final RealMatrix h = MatrixUtils.createRealMatrix(satNb, 4);
169 for (int k = 0; k < satNb; k++) {
170 h.setRow(k, satDir[k]);
171 }
172
173 // Compute the pseudo-inverse of H
174 final RealMatrix hInv = MatrixUtils.inverse(h.transpose().multiply(h));
175 final double sx2 = hInv.getEntry(0, 0);
176 final double sy2 = hInv.getEntry(1, 1);
177 final double sz2 = hInv.getEntry(2, 2);
178 final double st2 = hInv.getEntry(3, 3);
179
180 // Extract various DOP : GDOP, PDOP, HDOP, VDOP, TDOP
181 gdop = FastMath.sqrt(hInv.getTrace());
182 pdop = FastMath.sqrt(sx2 + sy2 + sz2);
183 hdop = FastMath.sqrt(sx2 + sy2);
184 vdop = FastMath.sqrt(sz2);
185 tdop = FastMath.sqrt(st2);
186 }
187
188 // Return all the DOP values
189 return new DOP(frame.getPoint(), date, satNb, gdop, pdop, hdop, vdop, tdop);
190 }
191
192 /**
193 * Get the minimum elevation.
194 *
195 * @return the minimum elevation (rad)
196 */
197 public double getMinElevation() {
198 return minElevation;
199 }
200
201 /**
202 * Get the elevation mask.
203 *
204 * @return the elevation mask
205 */
206 public ElevationMask getElevationMask() {
207 return elevationMask;
208 }
209 }