Field of View Sat Event Detector Example

Hi Orekit community, I am new to Orekit and have been trying to learn it over the last couple days. I am having trouble understanding how the ‘FieldOfViewDetector’ is suppose to work. What I want to accomplish is to see if a Satellite can determine if a gps position on the earth’s surface is within its field of view for some detector/camera. I’m having trouble figuring out how to place the event detector so that it sits with the satellite frame and triggers when contacting the Topocentric frame.

I’m unsure If I am approaching this issue correctly, I’m still very new, any help/guidance/criticism is welcome.

Thank you for any help you can offer. I will gladly create a tutorial of what we build for the community! :slight_smile: Here is my current code/thoughts.

//create orbit
		Vector3D position  = new Vector3D(-6142438.668, 3492467.560, -25767.25680);
		Vector3D velocity  = new Vector3D(505.8479685, 942.7809215, 7435.922231);
		PVCoordinates pvCoordinates = new PVCoordinates(position, velocity);
		AbsoluteDate initialDate = new AbsoluteDate(2004, 01, 01, 23, 30, 00.000, TimeScalesFactory.getUTC());
		Frame inertialFrame = FramesFactory.getEME2000();
		double mu =  3.986004415e+14;
		Orbit initialOrbit = new KeplerianOrbit(pvCoordinates, inertialFrame, initialDate, mu);

//create gps point on earth frame
		double longitude = FastMath.toRadians(45.);
		double latitude  = FastMath.toRadians(25.);
		double altitude  = 0.;
		GeodeticPoint earthPoint = new GeodeticPoint(latitude, longitude, altitude);
		Frame earthFrame = FramesFactory.getITRF(IERSConventions.IERS_2010, true);

		BodyShape earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS, Constants.WGS84_EARTH_FLATTENING, earthFrame);

		TopocentricFrame myHouseFrame = new TopocentricFrame(earth, earthPoint, "MyHouse");
    
//set field of view for camera
		Vector3D cameraDirection = new Vector3D(0,0,1); 
		FieldOfView fov = new CircularFieldOfView(cameraPossition, FastMath.toRadians(180), FastMath.toRadians(30));
		FieldOfViewDetector eventDetector = new FieldOfViewDetector(myHouseFrame, fov);

//what do I do here??
    		eventDetector.withHandler((s, detector, increasing) -> {	
        System.out.println(" Visibility on MyHouse" + 
                (increasing ? " begins at " : " ends at ") +
                s.getDate());
		 return increasing ? Action.CONTINUE : Action.STOP;
		});

//set propagator//attitude 
		NadirPointing nadirLaw = new NadirPointing(inertialFrame, earth);
		Propagator propagator =
			    new EcksteinHechlerPropagator(initialOrbit, nadirLaw,
			                                  Constants.EIGEN5C_EARTH_EQUATORIAL_RADIUS,
			                                  Constants.EIGEN5C_EARTH_MU,
			                                  Constants.EIGEN5C_EARTH_C20,
			                                  Constants.EIGEN5C_EARTH_C30,
			                                  Constants.EIGEN5C_EARTH_C40,
			                                  Constants.EIGEN5C_EARTH_C50,
			                                  Constants.EIGEN5C_EARTH_C60);

//Add event detector to propagator
		propagator.addEventDetector(eventDetector);

//propagate
		SpacecraftState finalState = propagator.propagate(new AbsoluteDate(initialDate, 1500.));

Hi @Evan

You are on the good way :slight_smile:
To improve your code I would recommend you 2 things:

  1. Try to reduce the half aperture angle of your circular field of view. Indeed, 180° is a big value. It means that the aperture angle of the detector is 360°, which is not a realistic value.

  2. Combine the FieldOfViewDetector with an ElevationDetector using a BooleanDetector . In order to improve it, I would recommend you to look at the following answer given by Pascal.

Best regards,
Bryan

Is an ElevationDetector always required with a FieldOfViewDetector in an EO context? As in, does the user have to explicitly state that the central body will be in the way? I get the feeling it does, could some words be added to the documentation to highlight that? To help muppets like me!

Hello again! Ok, I have updated the code and It actually works this time. :slight_smile: Thanks to @bcazabonne. I thought I’d walk through it in case anyone was reading this and having trouble understanding some things. Feel free to correct me if I am inaccurate about anything, although I’ve been programming for many years, I’m pretty new to the orbital mechanics domain and could definitely use correction.

Step 1) Create a inertial frame. This is the celestial body you want to orbit around. This is the center of mass of the earth, and is fixed. Imagine a coordinate system where the earth doesn’t rotate, but the whole universe rotates around the earth.

//set inertial frame
Frame inertialFrame = FramesFactory.getEME2000();

Step 2) Create an Orbit. I am using a Keplerian orbit here, but orbits can be swapped out. Keplerian orbit is an elliptical orbit that depends on ‘mu’ (the product of the gravitational constant G and the mass M of the body). We start out by defining the initial position, velocity, and the date of the sat:

//create initial orbit
Vector3D position  = new Vector3D(-6142438.668, 3492467.560, -25767.25680);
Vector3D velocity  = new Vector3D(505.8479685, 942.7809215, 7435.922231);
PVCoordinates pvCoordinates = new PVCoordinates(position, velocity);
AbsoluteDate initialDate = new AbsoluteDate(2004, 01, 01, 23, 30, 00.000, TimeScalesFactory.getUTC());

double mu =  3.986004415e+14;
Orbit initialOrbit = new KeplerianOrbit(pvCoordinates, inertialFrame, initialDate, mu);		

Step 3) Create a gps location on the non intertial (ECEF) earth frame. One can image this frame as centred on the earth, but the earth is also spinning with respect to the universe. This frame is useful for defining objects on the earth’s surface.

//create gps point on earth frame
double longitude = FastMath.toRadians(30.);
double latitude  = FastMath.toRadians(25.);
double altitude  = 0.;
		
GeodeticPoint geoPoint = new GeodeticPoint(latitude, longitude, altitude);
Frame earthFrame = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
BodyShape earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS, Constants.WGS84_EARTH_FLATTENING, earthFrame);
		
TopocentricFrame gpsFrame = new TopocentricFrame(earth, geoPoint, "location_one");

Step 4) Create a field of view event detector for your satellite, an elevation detector, and combine them with a booleanDetector. The following code will print out the gps location of the Satelite when the gps location on the earth is detected. I recommend looking through this to try and understand what I did exactly.

//set field of view for camera
		FieldOfView fov = new CircularFieldOfView(Vector3D.PLUS_K, FastMath.toRadians(40), FastMath.toRadians(0));
		final FieldOfViewDetector fd = new FieldOfViewDetector(gpsFrame, fov);
		final ElevationDetector ed = new ElevationDetector(gpsFrame).withConstantElevation(0.);

//combine the detectors, only return true if bot g functions are positive 
		final BooleanDetector detector = BooleanDetector.andCombine(ed, fd).withMaxCheck(10.).withHandler(new EventHandler<BooleanDetector>() {
			
		    private AbsoluteDate start;
		    @Override
		    public Action eventOccurred(SpacecraftState s, BooleanDetector detector, boolean increasing) throws OrekitException {
		        if (increasing) {
		        	
		        	final GeodeticPoint satelliteAsGeodeticPoint = earth.transform(s.getPVCoordinates().getPosition(), s.getFrame(), s.getDate());
		        	 
		            // Entering Area
		            start = s.getDate();
		            System.out.format("ENTERED AREA: date: %s Satellite Coordinates: {latitude: %f3.9, longitude: %f3.9, altitude: %f km}\n",
		            		start,
		            		FastMath.toDegrees(satelliteAsGeodeticPoint.getLatitude()),
		            		FastMath.toDegrees(satelliteAsGeodeticPoint.getLongitude()),
		            		(satelliteAsGeodeticPoint.getAltitude() / 1000));
		            
		            
		            return Action.CONTINUE;
		            
		        } else {
		            // Leaving Area
		            final double duration = s.getDate().durationFrom(start);
		            System.out.format("EXITING AREA: date: %s Pass duration: %f\n", s.getDate(), duration);
		            return Action.CONTINUE;
		        }
		    }
			
		});

Step 5) Make a propagator. We will need to propagate our orbit, there are many propagators out there, I am using EcksteinHeckler which is better for circular orbits, so I think I need to change it out.

		NadirPointing nadirLaw = new NadirPointing(inertialFrame, earth);
		
		Propagator propagator =
			    new EcksteinHechlerPropagator(initialOrbit, nadirLaw,
			                                  Constants.EIGEN5C_EARTH_EQUATORIAL_RADIUS,
			                                  Constants.EIGEN5C_EARTH_MU,
			                                  Constants.EIGEN5C_EARTH_C20,
			                                  Constants.EIGEN5C_EARTH_C30,
			                                  Constants.EIGEN5C_EARTH_C40,
			                                  Constants.EIGEN5C_EARTH_C50,
			                                 Constants.EIGEN5C_EARTH_C60);

Step 6) Add the detector to the propagator

		propagator.addEventDetector(detector);

Step 7) Propagate by 10000 seconds

SpacecraftState finalState = propagator.propagate(new AbsoluteDate(initialDate, 10000.));

This seems fine to me, good work.

It is not required, it is strongly recommended. The “problem” of the FieldOfViewDetector is that it can detect visibility points on the Earth from the other side of the Earth. That’s why it is strongly recommended to combine it with an ElevationDetector for station visibility applications. However, calculating station visibility opportunities is not the only application of the FieldOfViewDetector. That’s why the user have to be carreful.

Off course, could you open an issue on our Gitlab repository?

The code you wrote looks good! I could not have done better :slightly_smiling_face:

The important part here is “in an EO context”. Orekit is not limited to Earth Observation context, and when you are in this context, the code does not know it if you don’t configure it. If for example you are working on interplanetary planning mission and want to use a FieldOfViewDetector for optical navigation, then you either are far enough from any celestial body to just use the detector without bothering about masking bodies, or you want to write a robust software that works throughout the mission and would use the detector with several potential masking bodies, and none of them would be the central body of your propagation (you may even not have any central body at all).

I’ll put something in Github. I agree, this way round is preferable. I’ve pulled my hair out trying to work around built-in assumptions before, I’d much rather have to add a few lines to explicitly state how it should operate.

And that’s an excellent example!

if one wanted to determine if a TopocentricFrame is in the day. What’s the best way to do that? I want to believe that it should be as easy as calculating the elevation of the sun in the correct reference frame? I’m considering this:

PVCoordinatesProvider sun = CelestialBodyFactory.getSun();
Frame earthITRFFrame = FramesFactory.getITRF(IERSConventions.IERS_2010, true);

Vector3D sunPosition = sun.getPVCoordinates(s.getDate(), earthITRFFrame).getPosition();
double sunAngle = gpsFrame.getElevation(sunPosition, earthITRFFrame, s.getDate());
double sunAngleInDegrees = FastMath.toDegrees(sunAngle);
		        	
if (sunAngleInDegrees > 20 && sunAngleInDegrees < 160) {
	Boolean daytime = true;
}

Is this right? Thanks again for your support guys!

There is a dedicated event detector for this: GroundAtNightDetector.
It takes into account artmospheric refraction and there are three predefined elevation thresholds available:

  • CIVIL_DAWN_DUSK_ELEVATION which is 6° below horizon
  • NAUTICAL_DAWN_DUSK_ELEVATION which is 12° below horizon
  • ASTRONOMICAL_DAWN_DUSK_ELEVATION which is 18° below horizon

Also note that TopocentricFrame.getElevation always returns an angle between -90° (nadir) and +90° (zenith), so checking against a limit at 160 degrees is useless.

@luc
I can’t thank you enough man! Really.

I went ahead and created a GroundAtDayDetector:

	private class GroundAtDayDetector extends GroundAtNightDetector {

		public GroundAtDayDetector(TopocentricFrame groundLocation, PVCoordinatesProvider sun, double dawnDuskElevation,
				AtmosphericRefractionModel refractionModel) {
			super(groundLocation, sun, dawnDuskElevation, refractionModel);
		}
		
	    @Override
	    public double g(final SpacecraftState state) {
	    	return -1 * super.g(state);
	   
	    }
	}
	

I feel a bit cheeky, but I think to determine the ‘day’ using the refraction models, we can just take the inverse of the g function? It helps me satisfy my BooleanDetector ‘is it day’ requirements.

Thanks again for all your help! I hope you guys have a good day!

Evan

See also BooleanDetector.notCombine(...)

I could very well be wrong, I often am, but from what I read about BooleanDetector.notCombine, it ‘negates’ or cancels out one detector. I don’t want to cancel out the detector, I want the inverse of the g function, or to turn a ‘false’ into a ‘true’.

Thank you

Evan

From the BooleanDetector javadoc:

 * This class provides AND and OR operations for event detectors. This class treats
 * positive values of the g function as true and negative values as false.

and from the notCombine javadoc:

     * @return an new event detector whose g function is the same magnitude but opposite

So it does exactly what you need: it changes false to true (and true to false).