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