1   /* Copyright 2002-2025 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 org.hipparchus.geometry.euclidean.threed.Vector3D;
20  import org.hipparchus.random.RandomGenerator;
21  import org.hipparchus.random.UnitSphereRandomVectorGenerator;
22  import org.hipparchus.util.FastMath;
23  import org.junit.jupiter.api.AfterEach;
24  import org.junit.jupiter.api.Assertions;
25  import org.junit.jupiter.api.BeforeEach;
26  import org.orekit.Utils;
27  import org.orekit.attitudes.AttitudeProvider;
28  import org.orekit.bodies.GeodeticPoint;
29  import org.orekit.bodies.OneAxisEllipsoid;
30  import org.orekit.errors.OrekitException;
31  import org.orekit.errors.OrekitMessages;
32  import org.orekit.frames.FramesFactory;
33  import org.orekit.frames.TopocentricFrame;
34  import org.orekit.frames.Transform;
35  import org.orekit.orbits.KeplerianOrbit;
36  import org.orekit.orbits.Orbit;
37  import org.orekit.propagation.Propagator;
38  import org.orekit.propagation.SpacecraftState;
39  import org.orekit.propagation.analytical.KeplerianPropagator;
40  import org.orekit.propagation.events.VisibilityTrigger;
41  import org.orekit.time.AbsoluteDate;
42  import org.orekit.utils.Constants;
43  import org.orekit.utils.IERSConventions;
44  import org.orekit.utils.PVCoordinates;
45  
46  import java.util.List;
47  
48  public abstract class AbstractSmoothFieldOfViewTest {
49  
50      protected void doTestFOVAwayFromEarth(final SmoothFieldOfView fov, final AttitudeProvider attitude,
51                                            final Vector3D expectedCenter) {
52          Assertions.assertEquals(0.0, Vector3D.distance(expectedCenter, fov.getCenter()), 1.0e-15);
53          Propagator propagator = new KeplerianPropagator(orbit);
54          propagator.setAttitudeProvider(attitude);
55          SpacecraftState state = propagator.propagate(orbit.getDate().shiftedBy(1000.0));
56          Transform inertToBody = state.getFrame().getTransformTo(earth.getBodyFrame(), state.getDate());
57          Transform fovToBody   = new Transform(state.getDate(),
58                                                state.toTransform().getInverse(),
59                                                inertToBody);
60          List<List<GeodeticPoint>> footprint = fov.getFootprint(fovToBody, earth, FastMath.toRadians(0.1));
61          Assertions.assertTrue(footprint.isEmpty());
62      }
63  
64      protected void doTestNoFootprintInside(final SmoothFieldOfView fov, final Transform fovToBody) {
65          try {
66              fov.getFootprint(fovToBody, earth, FastMath.toRadians(0.1));
67              Assertions.fail("an exception should have been thrown");
68          } catch (OrekitException oe) {
69              Assertions.assertEquals(OrekitMessages.POINT_INSIDE_ELLIPSOID, oe.getSpecifier());
70          }
71      }
72  
73      protected void doTestFootprint(final SmoothFieldOfView fov, final AttitudeProvider attitude,
74                                     final double expectedMinOffset, final double expectedMaxOffset,
75                                     final double expectedMinElevation, final double expectedMaxElevation,
76                                     final double expectedMinDist, final double expectedMaxDist) {
77  
78          Propagator propagator = new KeplerianPropagator(orbit);
79          propagator.setAttitudeProvider(attitude);
80          SpacecraftState state = propagator.propagate(orbit.getDate().shiftedBy(1000.0));
81          Transform inertToBody = state.getFrame().getTransformTo(earth.getBodyFrame(), state.getDate());
82          Transform fovToBody   = new Transform(state.getDate(),
83                                                state.toTransform().getInverse(),
84                                                inertToBody);
85          List<List<GeodeticPoint>> footprint = fov.getFootprint(fovToBody, earth, FastMath.toRadians(0.1));
86          Vector3D subSat = earth.projectToGround(state.getPosition(earth.getBodyFrame()),
87                                                  state.getDate(), earth.getBodyFrame());
88          Assertions.assertEquals(1, footprint.size());
89          List<GeodeticPoint> loop = footprint.get(0);
90          double minOffset = Double.POSITIVE_INFINITY;
91          double maxOffset = 0;
92          double minEl     = Double.POSITIVE_INFINITY;
93          double maxEl     = 0;
94          double minDist   = Double.POSITIVE_INFINITY;
95          double maxDist   = 0;
96          for (int i = 0; i < loop.size(); ++i) {
97  
98              Assertions.assertEquals(0.0, loop.get(i).getAltitude(), 9.0e-9);
99  
100             Vector3D los = fovToBody.getStaticInverse().transformPosition(earth.transform(loop.get(i)));
101             double offset = Vector3D.angle(fov.getCenter(), los);
102             minOffset = FastMath.min(minOffset, offset);
103             maxOffset = FastMath.max(maxOffset, offset);
104 
105             TopocentricFrame topo = new TopocentricFrame(earth, loop.get(i), "onFootprint");
106             final double elevation = topo.getTrackingCoordinates(state.getPosition(),
107                                                                  state.getFrame(),
108                                                                  state.getDate()).
109                                      getElevation();
110             if (elevation > 0.001) {
111                 Assertions.assertEquals(-fov.getMargin(),
112                                     fov.offsetFromBoundary(los, 0.0, VisibilityTrigger.VISIBLE_ONLY_WHEN_FULLY_IN_FOV),
113                                     2.0e-10);
114                 Assertions.assertEquals(0.1 - fov.getMargin(),
115                                     fov.offsetFromBoundary(los, 0.1, VisibilityTrigger.VISIBLE_ONLY_WHEN_FULLY_IN_FOV),
116                                     2.0e-10);
117                 Assertions.assertEquals(-0.1 - fov.getMargin(),
118                                     fov.offsetFromBoundary(los, 0.1, VisibilityTrigger.VISIBLE_AS_SOON_AS_PARTIALLY_IN_FOV),
119                                     2.0e-10);
120             }
121             minEl = FastMath.min(minEl, elevation);
122             maxEl = FastMath.max(maxEl, elevation);
123             final double dist = Vector3D.distance(subSat, earth.transform(loop.get(i)));
124             minDist = FastMath.min(minDist, dist);
125             maxDist = FastMath.max(maxDist, dist);
126 
127         }
128 
129         Assertions.assertEquals(expectedMinOffset,     FastMath.toDegrees(minOffset), 0.001);
130         Assertions.assertEquals(expectedMaxOffset,     FastMath.toDegrees(maxOffset), 0.001);
131         Assertions.assertEquals(expectedMinElevation,  FastMath.toDegrees(minEl),     0.001);
132         Assertions.assertEquals(expectedMaxElevation,  FastMath.toDegrees(maxEl),     0.001);
133         Assertions.assertEquals(expectedMinDist,       minDist,                       1.0);
134         Assertions.assertEquals(expectedMaxDist,       maxDist,                       1.0);
135 
136     }
137 
138     protected void doTestBoundary(final SmoothFieldOfView fov, final RandomGenerator random,
139                                   final double tol) {
140         UnitSphereRandomVectorGenerator spGenerator = new UnitSphereRandomVectorGenerator(3, random);
141         for (int i = 0; i < 1000; ++i) {
142             Vector3D queryLOS = new Vector3D(spGenerator.nextVector());
143             Vector3D closest = fov.projectToBoundary(queryLOS);
144             Assertions.assertEquals(-fov.getMargin(),
145                                 fov.offsetFromBoundary(closest, 0, VisibilityTrigger.VISIBLE_ONLY_WHEN_FULLY_IN_FOV),
146                                 tol);
147         }
148     }
149 
150     @BeforeEach
151     public void setUp() {
152         Utils.setDataRoot("regular-data");
153         earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
154                                      Constants.WGS84_EARTH_FLATTENING,
155                                      FramesFactory.getITRF(IERSConventions.IERS_2010, true));
156         orbit = new KeplerianOrbit(new PVCoordinates(new Vector3D(7.0e6, 1.0e6, 4.0e6),
157                                                      new Vector3D(-500.0, 8000.0, 1000.0)),
158                                    FramesFactory.getEME2000(), AbsoluteDate.J2000_EPOCH,
159                                    Constants.EIGEN5C_EARTH_MU);
160     }
161 
162     @AfterEach
163     public void tearDown() {
164         earth = null;
165         orbit = null;
166     }
167 
168     protected OneAxisEllipsoid earth;
169     protected Orbit            orbit;
170 
171 }