1 /* Copyright 2002-2026 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 /** {@inheritDoc} */
102 @Override
103 public List<List<GeodeticPoint>> getFootprint(final Transform fovToBody,
104 final OneAxisEllipsoid body,
105 final double angularStep) {
106
107 return getFootprint(fovToBody, body, angularStep, DEFAULT_EXTERNAL_FOOTPRINT, DEFAULT_MAX_DIST);
108
109 }
110
111 /** {@inheritDoc} */
112 @Override
113 public List<List<GeodeticPoint>> getFootprint(final Transform fovToBody,
114 final OneAxisEllipsoid body,
115 final double angularStep,
116 final boolean extendedFootprint,
117 final double maxDistance) {
118
119 final Frame bodyFrame = body.getBodyFrame();
120 final Vector3D position = fovToBody.transformPosition(Vector3D.ZERO);
121 final double r = position.getNorm();
122 if (body.isInside(position)) {
123 throw new OrekitException(OrekitMessages.POINT_INSIDE_ELLIPSOID);
124 }
125
126 // prepare loop around FoV
127 boolean intersectionsFound = false;
128 final int nbPoints = (int) FastMath.ceil(MathUtils.TWO_PI / angularStep);
129 final List<GeodeticPoint> loop = new ArrayList<>(nbPoints);
130
131 // loop in inverse trigonometric order, so footprint is in trigonometric order
132 final double step = MathUtils.TWO_PI / nbPoints;
133 for (int i = 0; i < nbPoints; ++i) {
134 final Vector3D direction = directionAt(-i * step);
135 final Vector3D awaySC = new Vector3D(r, direction);
136 final Vector3D awayBody = fovToBody.transformPosition(awaySC);
137 final Line lineOfSight = new Line(position, awayBody, 1.0e-3);
138 GeodeticPoint gp = body.getIntersectionPoint(lineOfSight, position, bodyFrame, null);
139 if (gp != null &&
140 Vector3D.dotProduct(awayBody.subtract(position), body.transform(gp).subtract(position)) < 0) {
141 // the intersection is in fact on the half-line pointing
142 // towards the back side, it is a spurious intersection
143 gp = null;
144 }
145
146 if (gp != null) {
147 // the line of sight does intersect the body
148 intersectionsFound = true;
149 } else {
150 // the line of sight does not intersect body
151 if (!extendedFootprint) {
152 // we use a point on the limb
153 gp = body.transform(body.pointOnLimb(position, awayBody), bodyFrame, null);
154 } else {
155 // we project in the proper direction (to a point in space)
156 // such that the line's length is the requested value (maxDistance)
157 final double temp_norm = awaySC.getNorm();
158 Vector3D temp_vec = awaySC.scalarMultiply(1 / temp_norm).scalarMultiply(maxDistance);
159 temp_vec = fovToBody.transformPosition(temp_vec);
160 gp = body.transform(temp_vec, bodyFrame, null);
161 }
162 }
163
164 // add the point
165 loop.add(gp);
166
167 }
168
169 final List<List<GeodeticPoint>> footprint = new ArrayList<>();
170 if (!extendedFootprint) { //if we care about non-intersection
171 if (intersectionsFound) {
172 // at least some of the points did intersect the body, there is a footprint
173 footprint.add(loop);
174 } else {
175 // the Field Of View loop does not cross the body
176 // either the body is outside of Field Of View, or it is fully contained
177 // we check the center
178 final Vector3D bodyCenter = fovToBody.getStaticInverse().transformPosition(Vector3D.ZERO);
179 if (offsetFromBoundary(bodyCenter, 0.0, VisibilityTrigger.VISIBLE_ONLY_WHEN_FULLY_IN_FOV) < 0.0) {
180 // the body is fully contained in the Field Of View
181 // the previous loop did compute the full limb as the footprint
182 footprint.add(loop);
183 }
184 }
185 } else { // if we don't care
186 footprint.add(loop);
187 }
188
189 return footprint;
190
191 }
192
193 /** Get boundary direction at angle.
194 * @param angle phase angle of the boundary direction
195 * @return boundary direction at phase angle in spacecraft frame
196 */
197 protected abstract Vector3D directionAt(double angle);
198
199 }