1   /* Copyright 2013-2019 CS Systèmes d'Information
2    * Licensed to CS Systèmes d'Information (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.rugged.utils;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  
22  import org.hipparchus.geometry.euclidean.threed.Vector3D;
23  import org.hipparchus.util.FastMath;
24  import org.orekit.bodies.GeodeticPoint;
25  import org.orekit.bodies.OneAxisEllipsoid;
26  import org.orekit.frames.Frame;
27  import org.orekit.frames.Transform;
28  import org.orekit.time.AbsoluteDate;
29  import org.orekit.utils.TimeStampedPVCoordinates;
30  
31  /** Class estimating very roughly when a point may be visible from spacecraft.
32   * <p>
33   * The class only uses spacecraft position to compute a very rough sub-satellite
34   * point. It assumes the position-velocities are regular enough and without holes.
35   * It is intended only only has a quick estimation in order to set up search
36   * boundaries in inverse location.
37   * </p>
38   * @see org.orekit.rugged.api.Rugged#dateLocation(String, org.orekit.bodies.GeodeticPoint, int, int)
39   * @see org.orekit.rugged.api.Rugged#dateLocation(String, double, double, int, int)
40   * @see org.orekit.rugged.api.Rugged#inverseLocation(String, org.orekit.bodies.GeodeticPoint, int, int)
41   * @see org.orekit.rugged.api.Rugged#inverseLocation(String, double, double, int, int)
42   * @author Luc Maisonobe
43   */
44  public class RoughVisibilityEstimator {
45  
46      /** Ground ellipsoid. */
47      private final OneAxisEllipsoid ellipsoid;
48  
49      /** Sub-satellite point. */
50      private final List<TimeStampedPVCoordinates> pvGround;
51  
52      /** Mean angular rate with respect to position-velocity indices. */
53      private final double rateVSIndices;
54  
55      /** Mean angular rate with respect to time. */
56      private final double rateVSTime;
57  
58      /** Last found index. */
59      private int last;
60  
61      /**
62       * Simple constructor.
63       * @param ellipsoid ground ellipsoid
64       * @param frame frame in which position and velocity are defined (may be inertial or body frame)
65       * @param positionsVelocities satellite position and velocity (m and m/s in specified frame)
66       */
67      public RoughVisibilityEstimator(final OneAxisEllipsoid ellipsoid, final Frame frame,
68                                      final List<TimeStampedPVCoordinates> positionsVelocities) {
69  
70          this.ellipsoid = ellipsoid;
71  
72          // project spacecraft position-velocity to ground
73          final Frame bodyFrame = ellipsoid.getBodyFrame();
74          final int n = positionsVelocities.size();
75          this.pvGround = new ArrayList<TimeStampedPVCoordinates>(n);
76          for (final TimeStampedPVCoordinates pv : positionsVelocities) {
77              final Transform t = frame.getTransformTo(bodyFrame, pv.getDate());
78              pvGround.add(ellipsoid.projectToGround(t.transformPVCoordinates(pv), bodyFrame));
79          }
80  
81          // initialize first search at mid point
82          this.last = n / 2;
83  
84          // estimate mean angular rate with respect to indices
85          double alpha = 0;
86          for (int i = 0; i < n - 1; ++i) {
87              // angular motion between points i and i+1
88              alpha += Vector3D.angle(pvGround.get(i).getPosition(),
89                                      pvGround.get(i + 1).getPosition());
90          }
91          this.rateVSIndices = alpha / n;
92  
93          // estimate mean angular rate with respect to time
94          final AbsoluteDate firstDate = pvGround.get(0).getDate();
95          final AbsoluteDate lastDate  = pvGround.get(pvGround.size() - 1).getDate();
96          this.rateVSTime              = alpha / lastDate.durationFrom(firstDate);
97  
98      }
99  
100     /** Estimate <em>very roughly</em> when spacecraft comes close to a ground point.
101      * @param groundPoint ground point to check
102      * @return rough date at which spacecraft comes close to ground point (never null,
103      * but may be really far from reality if ground point is away from trajectory)
104      */
105     public AbsoluteDate estimateVisibility(final GeodeticPoint groundPoint) {
106 
107         final Vector3D point = ellipsoid.transform(groundPoint);
108         int closeIndex = findClose(last, point);
109 
110         // check if there are closer points in previous periods
111         final int repeat = (int) FastMath.rint(2.0 * FastMath.PI / rateVSIndices);
112         for (int index = closeIndex - repeat; index > 0; index -= repeat) {
113             final int otherIndex = findClose(index, point);
114             if (otherIndex != closeIndex &&
115                 Vector3D.distance(pvGround.get(otherIndex).getPosition(), point) <
116                 Vector3D.distance(pvGround.get(closeIndex).getPosition(), point)) {
117                 closeIndex = otherIndex;
118             }
119         }
120 
121         // check if there are closer points in next periods
122         for (int index = closeIndex + repeat; index < pvGround.size(); index += repeat) {
123             final int otherIndex = findClose(index, point);
124             if (otherIndex != closeIndex &&
125                 Vector3D.distance(pvGround.get(otherIndex).getPosition(), point) <
126                 Vector3D.distance(pvGround.get(closeIndex).getPosition(), point)) {
127                 closeIndex = otherIndex;
128             }
129         }
130 
131         // we have found the closest sub-satellite point index
132         last = closeIndex;
133 
134         // final adjustment
135         final TimeStampedPVCoordinates closest = pvGround.get(closeIndex);
136         final double alpha = neededMotion(closest, point);
137         return closest.getDate().shiftedBy(alpha / rateVSTime);
138 
139     }
140 
141     /** Find the index of a close sub-satellite point.
142      * @param start start index for the search
143      * @param point test point
144      * @return index of a sub-satellite point close to the test point
145      */
146     private int findClose(final int start, final Vector3D point) {
147         int current  = start;
148         int previous = Integer.MIN_VALUE;
149         int maxLoop  = 1000;
150         while (maxLoop-- > 0 && FastMath.abs(current - previous) > 1) {
151             previous = current;
152             final double alpha = neededMotion(pvGround.get(current), point);
153             final int    shift = (int) FastMath.rint(alpha / rateVSIndices);
154             current = FastMath.max(0, FastMath.min(pvGround.size() - 1, current + shift));
155         }
156         return current;
157     }
158 
159     /** Estimate angular motion needed to go past test point.
160      * <p>
161      * This estimation is quite crude. The sub-satellite point is properly on the
162      * ellipsoid surface, but we compute the angle assuming a spherical shape.
163      * </p>
164      * @param subSatellite current sub-satellite position-velocity
165      * @param point test point
166      * @return angular motion to go past test point (positive is
167      * test point is ahead, negative if it is behind)
168      */
169     private double neededMotion(final TimeStampedPVCoordinates subSatellite,
170                                 final Vector3D point) {
171 
172         final Vector3D ssP      = subSatellite.getPosition();
173         final Vector3D momentum = subSatellite.getMomentum();
174         final double   y        = Vector3D.dotProduct(point, Vector3D.crossProduct(momentum, ssP).normalize());
175         final double   x        = Vector3D.dotProduct(point, ssP.normalize());
176 
177         return FastMath.atan2(y, x);
178 
179     }
180 
181 }