SmoothFieldOfView.java
/* Copyright 2002-2024 CS GROUP
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.geometry.fov;
import java.util.ArrayList;
import java.util.List;
import org.hipparchus.geometry.euclidean.threed.Line;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.MathUtils;
import org.orekit.bodies.GeodeticPoint;
import org.orekit.bodies.OneAxisEllipsoid;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.frames.Frame;
import org.orekit.frames.Transform;
import org.orekit.propagation.events.VisibilityTrigger;
/** Class representing a spacecraft sensor Field Of View with shape defined by a smooth single loop.
* @author Luc Maisonobe
* @since 10.1
*/
public abstract class SmoothFieldOfView extends AbstractFieldOfView {
/** Direction of the FOV center. */
private final Vector3D center;
/** X axis defining FoV boundary. */
private final Vector3D xAxis;
/** Y axis defining FoV boundary. */
private final Vector3D yAxis;
/** Z axis defining FoV boundary. */
private final Vector3D zAxis;
/** Build a new instance.
* @param center direction of the FOV center (Z<sub>smooth</sub>), in spacecraft frame
* @param primaryMeridian vector defining the (+X<sub>smooth</sub>, Z<sub>smooth</sub>)
* half-plane (it is allowed to have {@code primaryMeridian} not orthogonal to
* {@code center} as orthogonality will be fixed internally)
* @param margin angular margin to apply to the zone (if positive,
* the Field Of View will consider points slightly outside of the
* zone are still visible)
*/
protected SmoothFieldOfView(final Vector3D center, final Vector3D primaryMeridian,
final double margin) {
super(margin);
this.center = center;
this.zAxis = center.normalize();
this.yAxis = Vector3D.crossProduct(center, primaryMeridian).normalize();
this.xAxis = Vector3D.crossProduct(yAxis, center).normalize();
}
/** Get the direction of the FOV center, in spacecraft frame.
* @return direction of the FOV center, in spacecraft frame
*/
public Vector3D getCenter() {
return center;
}
/** Get the X axis defining FoV boundary.
* @return X axis defining FoV boundary, in spacecraft frame
*/
public Vector3D getX() {
return xAxis;
}
/** Get the Y axis defining FoV boundary.
* @return Y axis defining FoV boundary, in spacecraft frame
*/
public Vector3D getY() {
return yAxis;
}
/** Get the Z axis defining FoV boundary.
* @return Z axis defining FoV boundary, in spacecraft frame
*/
public Vector3D getZ() {
return zAxis;
}
/** {@inheritDoc} */
@Override
public List<List<GeodeticPoint>> getFootprint(final Transform fovToBody,
final OneAxisEllipsoid body,
final double angularStep) {
final Frame bodyFrame = body.getBodyFrame();
final Vector3D position = fovToBody.transformPosition(Vector3D.ZERO);
final double r = position.getNorm();
if (body.isInside(position)) {
throw new OrekitException(OrekitMessages.POINT_INSIDE_ELLIPSOID);
}
// prepare loop around FoV
boolean intersectionsFound = false;
final int nbPoints = (int) FastMath.ceil(MathUtils.TWO_PI / angularStep);
final List<GeodeticPoint> loop = new ArrayList<>(nbPoints);
// loop in inverse trigonometric order, so footprint is in trigonometric order
final double step = MathUtils.TWO_PI / nbPoints;
for (int i = 0; i < nbPoints; ++i) {
final Vector3D direction = directionAt(-i * step);
final Vector3D awaySC = new Vector3D(r, direction);
final Vector3D awayBody = fovToBody.transformPosition(awaySC);
final Line lineOfSight = new Line(position, awayBody, 1.0e-3);
GeodeticPoint gp = body.getIntersectionPoint(lineOfSight, position, bodyFrame, null);
if (gp != null &&
Vector3D.dotProduct(awayBody.subtract(position), body.transform(gp).subtract(position)) < 0) {
// the intersection is in fact on the half-line pointing
// towards the back side, it is a spurious intersection
gp = null;
}
if (gp != null) {
// the line of sight does intersect the body
intersectionsFound = true;
} else {
// the line of sight does not intersect body
// we use a point on the limb
gp = body.transform(body.pointOnLimb(position, awayBody), bodyFrame, null);
}
// add the point
loop.add(gp);
}
final List<List<GeodeticPoint>> footprint = new ArrayList<>();
if (intersectionsFound) {
// at least some of the points did intersect the body, there is a footprint
footprint.add(loop);
} else {
// the Field Of View loop does not cross the body
// either the body is outside of Field Of View, or it is fully contained
// we check the center
final Vector3D bodyCenter = fovToBody.getStaticInverse().transformPosition(Vector3D.ZERO);
if (offsetFromBoundary(bodyCenter, 0.0, VisibilityTrigger.VISIBLE_ONLY_WHEN_FULLY_IN_FOV) < 0.0) {
// the body is fully contained in the Field Of View
// the previous loop did compute the full limb as the footprint
footprint.add(loop);
}
}
return footprint;
}
/** Get boundary direction at angle.
* @param angle phase angle of the boundary direction
* @return boundary direction at phase angle in spacecraft frame
*/
protected abstract Vector3D directionAt(double angle);
}