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.geometry.fov;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  
22  import org.hipparchus.geometry.euclidean.threed.Line;
23  import org.hipparchus.geometry.euclidean.threed.Vector3D;
24  import org.hipparchus.util.FastMath;
25  import org.hipparchus.util.MathUtils;
26  import org.orekit.bodies.GeodeticPoint;
27  import org.orekit.bodies.OneAxisEllipsoid;
28  import org.orekit.errors.OrekitException;
29  import org.orekit.errors.OrekitMessages;
30  import org.orekit.frames.Frame;
31  import org.orekit.frames.Transform;
32  import org.orekit.propagation.events.VisibilityTrigger;
33  
34  /** Class representing a spacecraft sensor Field Of View with shape defined by a smooth single loop.
35   * @author Luc Maisonobe
36   * @since 10.1
37   */
38  public abstract class SmoothFieldOfView extends AbstractFieldOfView {
39  
40      /** Direction of the FOV center. */
41      private final Vector3D center;
42  
43      /** X axis defining FoV boundary. */
44      private final Vector3D xAxis;
45  
46      /** Y axis defining FoV boundary. */
47      private final Vector3D yAxis;
48  
49      /** Z axis defining FoV boundary. */
50      private final Vector3D zAxis;
51  
52      /** Build a new instance.
53       * @param center direction of the FOV center (Z<sub>smooth</sub>), in spacecraft frame
54       * @param primaryMeridian vector defining the (+X<sub>smooth</sub>, Z<sub>smooth</sub>)
55       * half-plane (it is allowed to have {@code primaryMeridian} not orthogonal to
56       * {@code center} as orthogonality will be fixed internally)
57       * @param margin angular margin to apply to the zone (if positive,
58       * the Field Of View will consider points slightly outside of the
59       * zone are still visible)
60       */
61      protected SmoothFieldOfView(final Vector3D center, final Vector3D primaryMeridian,
62                                  final double margin) {
63  
64          super(margin);
65  
66          this.center = center;
67          this.zAxis  = center.normalize();
68          this.yAxis  = Vector3D.crossProduct(center, primaryMeridian).normalize();
69          this.xAxis  = Vector3D.crossProduct(yAxis, center).normalize();
70  
71      }
72  
73      /** Get the direction of the FOV center, in spacecraft frame.
74       * @return direction of the FOV center, in spacecraft frame
75       */
76      public Vector3D getCenter() {
77          return center;
78      }
79  
80      /** Get the X axis defining FoV boundary.
81       * @return X axis defining FoV boundary, in spacecraft frame
82       */
83      public Vector3D getX() {
84          return xAxis;
85      }
86  
87      /** Get the Y axis defining FoV boundary.
88       * @return Y axis defining FoV boundary, in spacecraft frame
89       */
90      public Vector3D getY() {
91          return yAxis;
92      }
93  
94      /** Get the Z axis defining FoV boundary.
95       * @return Z axis defining FoV boundary, in spacecraft frame
96       */
97      public Vector3D getZ() {
98          return zAxis;
99      }
100 
101 
102     /** {@inheritDoc} */
103     @Override
104     public List<List<GeodeticPoint>> getFootprint(final Transform fovToBody,
105                                                   final OneAxisEllipsoid body,
106                                                   final double angularStep) {
107 
108         final Frame     bodyFrame = body.getBodyFrame();
109         final Vector3D  position  = fovToBody.transformPosition(Vector3D.ZERO);
110         final double    r         = position.getNorm();
111         if (body.isInside(position)) {
112             throw new OrekitException(OrekitMessages.POINT_INSIDE_ELLIPSOID);
113         }
114 
115         // prepare loop around FoV
116         boolean                         intersectionsFound = false;
117         final int                       nbPoints           = (int) FastMath.ceil(MathUtils.TWO_PI / angularStep);
118         final List<GeodeticPoint>       loop               = new ArrayList<>(nbPoints);
119 
120         // loop in inverse trigonometric order, so footprint is in trigonometric order
121         final double step = MathUtils.TWO_PI / nbPoints;
122         for (int i = 0; i < nbPoints; ++i) {
123             final Vector3D direction   = directionAt(-i * step);
124             final Vector3D awaySC      = new Vector3D(r, direction);
125             final Vector3D awayBody    = fovToBody.transformPosition(awaySC);
126             final Line     lineOfSight = new Line(position, awayBody, 1.0e-3);
127             GeodeticPoint  gp          = body.getIntersectionPoint(lineOfSight, position, bodyFrame, null);
128             if (gp != null &&
129                 Vector3D.dotProduct(awayBody.subtract(position), body.transform(gp).subtract(position)) < 0) {
130                 // the intersection is in fact on the half-line pointing
131                 // towards the back side, it is a spurious intersection
132                 gp = null;
133             }
134 
135             if (gp != null) {
136                 // the line of sight does intersect the body
137                 intersectionsFound = true;
138             } else {
139                 // the line of sight does not intersect body
140                 // we use a point on the limb
141                 gp = body.transform(body.pointOnLimb(position, awayBody), bodyFrame, null);
142             }
143 
144             // add the point
145             loop.add(gp);
146 
147         }
148 
149         final List<List<GeodeticPoint>> footprint = new ArrayList<>();
150         if (intersectionsFound) {
151             // at least some of the points did intersect the body, there is a footprint
152             footprint.add(loop);
153         } else {
154             // the Field Of View loop does not cross the body
155             // either the body is outside of Field Of View, or it is fully contained
156             // we check the center
157             final Vector3D bodyCenter = fovToBody.getInverse().transformPosition(Vector3D.ZERO);
158             if (offsetFromBoundary(bodyCenter, 0.0, VisibilityTrigger.VISIBLE_ONLY_WHEN_FULLY_IN_FOV) < 0.0) {
159                 // the body is fully contained in the Field Of View
160                 // the previous loop did compute the full limb as the footprint
161                 footprint.add(loop);
162             }
163         }
164 
165         return footprint;
166 
167     }
168 
169     /** Get boundary direction at angle.
170      * @param angle phase angle of the boundary direction
171      * @return boundary direction at phase angle in spacecraft frame
172      */
173     protected abstract Vector3D directionAt(double angle);
174 
175 }