PolygonalFieldOfView.java

  1. /* Copyright 2002-2020 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. import java.util.ArrayList;
  19. import java.util.List;

  20. import org.hipparchus.geometry.enclosing.EnclosingBall;
  21. import org.hipparchus.geometry.euclidean.threed.Line;
  22. import org.hipparchus.geometry.euclidean.threed.Rotation;
  23. import org.hipparchus.geometry.euclidean.threed.RotationConvention;
  24. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  25. import org.hipparchus.geometry.partitioning.Region;
  26. import org.hipparchus.geometry.spherical.twod.Edge;
  27. import org.hipparchus.geometry.spherical.twod.S2Point;
  28. import org.hipparchus.geometry.spherical.twod.Sphere2D;
  29. import org.hipparchus.geometry.spherical.twod.SphericalPolygonsSet;
  30. import org.hipparchus.geometry.spherical.twod.Vertex;
  31. import org.hipparchus.util.FastMath;
  32. import org.hipparchus.util.MathUtils;
  33. import org.hipparchus.util.SinCos;
  34. import org.orekit.bodies.GeodeticPoint;
  35. import org.orekit.bodies.OneAxisEllipsoid;
  36. import org.orekit.errors.OrekitException;
  37. import org.orekit.errors.OrekitMessages;
  38. import org.orekit.frames.Frame;
  39. import org.orekit.frames.Transform;
  40. import org.orekit.propagation.events.VisibilityTrigger;

  41. /** Class representing a spacecraft sensor Field Of View with polygonal shape.
  42.  * <p>Fields Of View are zones defined on the unit sphere centered on the
  43.  * spacecraft. They can have any shape, they can be split in several
  44.  * non-connected patches and can have holes.</p>
  45.  * @author Luc Maisonobe
  46.  * @since 10.1
  47.  */
  48. public class PolygonalFieldOfView extends AbstractFieldOfView {

  49.     /** Spherical zone. */
  50.     private final SphericalPolygonsSet zone;

  51.     /** Spherical cap surrounding the zone. */
  52.     private final EnclosingBall<Sphere2D, S2Point> cap;

  53.     /** Build a new instance.
  54.      * @param zone interior of the Field Of View, in spacecraft frame
  55.      * @param margin angular margin to apply to the zone (if positive,
  56.      * points outside of the raw FoV but close enough to the boundary are
  57.      * considered visible; if negative, points inside of the raw FoV
  58.      * but close enough to the boundary are considered not visible)
  59.      */
  60.     public PolygonalFieldOfView(final SphericalPolygonsSet zone, final double margin) {
  61.         super(margin);
  62.         this.zone = zone;
  63.         this.cap  = zone.getEnclosingCap();
  64.     }

  65.     /** Build Field Of View with a regular polygon shape.
  66.      * @param center center of the polygon (the center is in the inside part)
  67.      * @param meridian point defining the reference meridian for middle of first edge
  68.      * @param insideRadius distance of the edges middle points to the center
  69.      * (the polygon vertices will therefore be farther away from the center)
  70.      * @param n number of sides of the polygon
  71.      * @param margin angular margin to apply to the zone (if positive,
  72.      * points outside of the raw FoV but close enough to the boundary are
  73.      * considered visible; if negative, points inside of the raw FoV
  74.      * but close enough to the boundary are considered not visible)
  75.      * @deprecated as of 10.1, replaced by {@link #PolygonalFieldOfView(Vector3D,
  76.      * DefiningConeType, Vector3D, double, int, double)}
  77.      */
  78.     @Deprecated
  79.     public PolygonalFieldOfView(final Vector3D center, final Vector3D meridian,
  80.                                 final double insideRadius, final int n,
  81.                                 final double margin) {
  82.         this(center, DefiningConeType.INSIDE_CONE_TOUCHING_POLYGON_AT_EDGES_MIDDLE,
  83.              meridian, insideRadius, n, margin);
  84.     }

  85.     /** Build Field Of View with a regular polygon shape.
  86.      * @param center center of the polygon (the center is in the inside part)
  87.      * @param coneType type of defining cone
  88.      * @param meridian point defining the reference meridian for one contact
  89.      * point between defining cone and polygon (i.e. either a polygon edge
  90.      * middle point or a polygon vertex)
  91.      * @param radius defining cone angular radius
  92.      * @param n number of sides of the polygon
  93.      * @param margin angular margin to apply to the zone (if positive,
  94.      * points outside of the raw FoV but close enough to the boundary are
  95.      * considered visible; if negative, points inside of the raw FoV
  96.      * but close enough to the boundary are considered not visible)
  97.      * @since 10.1
  98.      */
  99.     public PolygonalFieldOfView(final Vector3D center, final DefiningConeType coneType,
  100.                                 final Vector3D meridian, final double radius,
  101.                                 final int n, final double margin) {

  102.         super(margin);

  103.         final double   verticesRadius = coneType.verticesRadius(radius, n);
  104.         final Vector3D vertex         = coneType.createVertex(center, meridian, verticesRadius, n);
  105.         this.zone                     = new SphericalPolygonsSet(center, vertex, verticesRadius,
  106.                                                                  n, 1.0e-12 * verticesRadius);

  107.         final Rotation r = new Rotation(center, MathUtils.TWO_PI / n, RotationConvention.VECTOR_OPERATOR);
  108.         final S2Point[] support = new S2Point[n];
  109.         support[0] = new S2Point(vertex);
  110.         for (int i = 1; i < n; ++i) {
  111.             support[i] = new S2Point(r.applyTo(support[i - 1].getVector()));
  112.         }
  113.         this.cap = new EnclosingBall<>(new S2Point(center), Vector3D.angle(center, vertex), support);

  114.     }

  115.     /** Get the interior zone.
  116.      * @return the interior zone
  117.      */
  118.     public SphericalPolygonsSet getZone() {
  119.         return zone;
  120.     }

  121.     /** {@inheritDoc} */
  122.     @Override
  123.     public double offsetFromBoundary(final Vector3D lineOfSight, final double angularRadius,
  124.                                      final VisibilityTrigger trigger) {

  125.         final S2Point los             = new S2Point(lineOfSight);
  126.         final double  margin          = getMargin();
  127.         final double  correctedRadius = trigger.radiusCorrection(angularRadius);
  128.         final double  deadBand        = margin + angularRadius;

  129.         // for faster computation, we start using only the surrounding cap, to filter out
  130.         // far away points (which correspond to most of the points if the Field Of View is small)
  131.         final double crudeDistance = cap.getCenter().distance(los) - cap.getRadius();
  132.         if (crudeDistance > deadBand + 0.01) {
  133.             // we know we are strictly outside of the zone,
  134.             // use the crude distance to compute the (positive) return value
  135.             return crudeDistance + correctedRadius - margin;
  136.         }

  137.         // we are close, we need to compute carefully the exact offset;
  138.         // we project the point to the closest zone boundary
  139.         return zone.projectToBoundary(los).getOffset() + correctedRadius - margin;

  140.     }

  141.     /** {@inheritDoc} */
  142.     @Override
  143.     public Vector3D projectToBoundary(final Vector3D lineOfSight) {
  144.         return ((S2Point) zone.projectToBoundary(new S2Point(lineOfSight)).getProjected()).getVector();
  145.     }

  146.     /** {@inheritDoc} */
  147.     @Override
  148.     public List<List<GeodeticPoint>> getFootprint(final Transform fovToBody,
  149.                                                   final OneAxisEllipsoid body,
  150.                                                   final double angularStep) {

  151.         final Frame     bodyFrame = body.getBodyFrame();
  152.         final Vector3D  position  = fovToBody.transformPosition(Vector3D.ZERO);
  153.         final double    r         = position.getNorm();
  154.         if (body.isInside(position)) {
  155.             throw new OrekitException(OrekitMessages.POINT_INSIDE_ELLIPSOID);
  156.         }

  157.         final List<List<GeodeticPoint>> footprint = new ArrayList<>();

  158.         final List<Vertex> boundary = zone.getBoundaryLoops();
  159.         for (final Vertex loopStart : boundary) {
  160.             int count = 0;
  161.             final List<GeodeticPoint> loop  = new ArrayList<>();
  162.             boolean intersectionsFound      = false;
  163.             for (Edge edge = loopStart.getOutgoing();
  164.                  count == 0 || edge.getStart() != loopStart;
  165.                  edge = edge.getEnd().getOutgoing()) {
  166.                 ++count;
  167.                 final int    n     = (int) FastMath.ceil(edge.getLength() / angularStep);
  168.                 final double delta =  edge.getLength() / n;
  169.                 for (int i = 0; i < n; ++i) {
  170.                     final Vector3D awaySC      = new Vector3D(r, edge.getPointAt(i * delta));
  171.                     final Vector3D awayBody    = fovToBody.transformPosition(awaySC);
  172.                     final Line     lineOfSight = new Line(position, awayBody, 1.0e-3);
  173.                     GeodeticPoint  gp          = body.getIntersectionPoint(lineOfSight, position,
  174.                                                                            bodyFrame, null);
  175.                     if (gp != null &&
  176.                         Vector3D.dotProduct(awayBody.subtract(position),
  177.                                             body.transform(gp).subtract(position)) < 0) {
  178.                         // the intersection is in fact on the half-line pointing
  179.                         // towards the back side, it is a spurious intersection
  180.                         gp = null;
  181.                     }

  182.                     if (gp != null) {
  183.                         // the line of sight does intersect the body
  184.                         intersectionsFound = true;
  185.                     } else {
  186.                         // the line of sight does not intersect body
  187.                         // we use a point on the limb
  188.                         gp = body.transform(body.pointOnLimb(position, awayBody), bodyFrame, null);
  189.                     }

  190.                     // add the point in front of the list
  191.                     // (to ensure the loop will be in trigonometric orientation)
  192.                     loop.add(0, gp);

  193.                 }
  194.             }

  195.             if (intersectionsFound) {
  196.                 // at least some of the points did intersect the body,
  197.                 // this loop contributes to the footprint
  198.                 footprint.add(loop);
  199.             }

  200.         }

  201.         if (footprint.isEmpty()) {
  202.             // none of the Field Of View loops cross the body
  203.             // either the body is outside of Field Of View, or it is fully contained
  204.             // we check the center
  205.             final Vector3D bodyCenter = fovToBody.getInverse().transformPosition(Vector3D.ZERO);
  206.             if (zone.checkPoint(new S2Point(bodyCenter)) != Region.Location.OUTSIDE) {
  207.                 // the body is fully contained in the Field Of View
  208.                 // we use the full limb as the footprint
  209.                 final Vector3D x        = bodyCenter.orthogonal();
  210.                 final Vector3D y        = Vector3D.crossProduct(bodyCenter, x).normalize();
  211.                 final double   sinEta   = body.getEquatorialRadius() / r;
  212.                 final double   sinEta2  = sinEta * sinEta;
  213.                 final double   cosAlpha = (FastMath.cos(angularStep) + sinEta2 - 1) / sinEta2;
  214.                 final int      n        = (int) FastMath.ceil(MathUtils.TWO_PI / FastMath.acos(cosAlpha));
  215.                 final double   delta    = MathUtils.TWO_PI / n;
  216.                 final List<GeodeticPoint> loop = new ArrayList<>(n);
  217.                 for (int i = 0; i < n; ++i) {
  218.                     final SinCos sc = FastMath.sinCos(i * delta);
  219.                     final Vector3D outside = new Vector3D(r * sc.cos(), x,
  220.                                                           r * sc.sin(), y);
  221.                     loop.add(body.transform(body.pointOnLimb(position, outside), bodyFrame, null));
  222.                 }
  223.                 footprint.add(loop);
  224.             }
  225.         }

  226.         return footprint;

  227.     }

  228.     /** Enumerate for cone/polygon relative position.
  229.      * @since 10.1
  230.      */
  231.     public enum DefiningConeType {

  232.         /** Constant for cones inside polygons and touching it at polygon edges middle points. */
  233.         INSIDE_CONE_TOUCHING_POLYGON_AT_EDGES_MIDDLE() {

  234.             /** {@inheritDoc}*/
  235.             @Override
  236.             protected double verticesRadius(final double radius, final int n) {
  237.                 // convert the inside (edges middle points) radius to outside (vertices) radius
  238.                 return FastMath.atan(FastMath.tan(radius) / FastMath.cos(FastMath.PI / n));
  239.             }

  240.             /** {@inheritDoc}*/
  241.             @Override
  242.             protected Vector3D createVertex(final Vector3D center, final Vector3D meridian,
  243.                                             final double verticesRadius, final int n) {
  244.                 // convert the edge middle meridian to a vertex
  245.                 final SinCos scA = FastMath.sinCos(FastMath.PI / n);
  246.                 final SinCos scR = FastMath.sinCos(verticesRadius);
  247.                 final Vector3D z = center.normalize();
  248.                 final Vector3D y = Vector3D.crossProduct(center, meridian).normalize();
  249.                 final Vector3D x = Vector3D.crossProduct(y, z);
  250.                 return new Vector3D(scR.sin() * scA.cos(), x, scR.sin() * scA.sin(), y, scR.cos(), z);
  251.             }

  252.         },

  253.         /** Constant for cones outside polygons and touching it at polygon vertices. */
  254.         OUTSIDE_CONE_TOUCHING_POLYGON_AT_VERTICES() {

  255.             /** {@inheritDoc}*/
  256.             @Override
  257.             protected double verticesRadius(final double radius, final int n) {
  258.                 return radius;
  259.             }

  260.             /** {@inheritDoc}*/
  261.             @Override
  262.             protected Vector3D createVertex(final Vector3D center, final Vector3D meridian,
  263.                                             final double verticesRadius, final int n) {
  264.                 // convert the vertex meridian to a vertex
  265.                 final SinCos scR = FastMath.sinCos(verticesRadius);
  266.                 final Vector3D z = center.normalize();
  267.                 final Vector3D y = Vector3D.crossProduct(center, meridian).normalize();
  268.                 final Vector3D x = Vector3D.crossProduct(y, z);
  269.                 return new Vector3D(scR.sin(), x, scR.cos(), z);
  270.             }

  271.         };

  272.         /** Compute radius of cone going through vertices.
  273.          * @param radius defining cone angular radius
  274.          * @param n number of sides of the polygon
  275.          * @return radius of cone going through vertices
  276.          */
  277.         protected abstract double verticesRadius(double radius, int n);

  278.         /** Create a vertex.
  279.          * @param center center of the polygon (the center is in the inside part)
  280.          * @param meridian point defining the reference meridian for one contact
  281.          * point between defining cone and polygon (i.e. either a polygon edge
  282.          * middle point or a polygon vertex)
  283.          * @param verticesRadius defining radius of cone passing through vertices
  284.          * @param n number of sides of the polygon
  285.          * @return created vertex
  286.          */
  287.         protected abstract Vector3D createVertex(Vector3D center, Vector3D meridian,
  288.                                                  double verticesRadius, int n);

  289.     }

  290. }