RSOs over space port performance (9 hour run)

I have been given a list of 30 ground stations in the form of name/latitude/longitude. I want to know which LEO RSOs (~10k) pass over thier geographic zone, so I used a GeographicZoneDetector and created a 5 mile ‘zone’ around the lat/long position.

SpacePort POJO:

@Slf4j
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class SpacePort {
    private String sp_no;
    private String sp;
    private double lat;
    private double lon;
    private SphericalPolygonsSet zone;
}

Zone creation:

 try {
            // Open the CSV file
            BufferedReader br = new BufferedReader(new FileReader("src/main/resources/spaceports.csv"));
            // Skip the header line
            br.readLine();
            // Read each line in the CSV file
            while ((line = br.readLine()) != null) {
                // Split the line by comma
                String[] spacePortData = line.split(splitBy);
                // Create a new SpacePort object and set its fields
                SpacePort spacePort = new SpacePort();
                spacePort.setSp_no(spacePortData[0]);
                spacePort.setSp(spacePortData[1]);
                spacePort.setLat(Double.parseDouble(spacePortData[2]));
                spacePort.setLon(Double.parseDouble(spacePortData[3]));

                double lat = Math.toRadians(spacePort.getLat());
                double colatitude = Math.PI / 2 - lat;
                double lon = Math.toRadians(spacePort.getLon());
                double radiusMiles = 5.0;  // THIS IS HOW BIG THE CIRCLE WILL BE
                double radiusKm = radiusMiles * 1.60934;
                double angularRadius = radiusKm / 6371.0;

                GeodeticPoint center = new GeodeticPoint(lat, lon, 0.0);
                int numPoints = 100;
                S2Point[] boundary = new S2Point[numPoints];
                for (int k = 0; k < numPoints; ++k) {
                    double theta = 2 * Math.PI * k / numPoints;
                    boundary[k] = new S2Point(
                            center.getLongitude() + angularRadius * Math.cos(theta),
                            colatitude + angularRadius * Math.sin(theta)
                    );
                }
                SphericalPolygonsSet zone = new SphericalPolygonsSet(1e-10, boundary);

                spacePort.setZone(zone);
                // Add the SpacePort object to the list
                spacePorts.add(spacePort);
            }
        }

Main code loop:

I am trying to use Java Streams to take advantage of built in parallelism.

  SpacePortReader spacePortReader = new SpacePortReader();
        TLEReader tleReader = new TLEReader();
        // The circle is defined in these spacePortAreas
        List<SpacePort> spacePortAreas = spacePortReader.readSpacePorts();
        Map<String, TLE> tles =  tleReader.readTLEs();

        spacePortAreas.stream().parallel().forEach(spacePort -> {
            tles.entrySet().parallelStream().forEach(entry -> {
                TLE tle = entry.getValue();
                String rsoName = entry.getKey();
                final Frame earthFrame = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
                final OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
                        Constants.WGS84_EARTH_FLATTENING, earthFrame);
                // Create a propagator for the TLE
                Propagator propagator = TLEPropagator.selectExtrapolator(entry.getValue());
                OneAxisEllipsoid earthShape = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
                        Constants.WGS84_EARTH_FLATTENING, earthFrame);

                GeographicZoneDetector zoneDetector = new GeographicZoneDetector(
                        60.0,
                        1e-3,
                        earthShape,
                        spacePort.getZone(),
                        Math.toRadians(0.1)
                );

                zoneDetector = zoneDetector.withHandler((state, detector, increasing) -> {
                    ZonedDateTime dataInEst = ZonedDateTime.ofInstant(state.getDate().toDate(TimeScalesFactory.getUTC()).toInstant(), ZoneId.of("America/New_York"));
                    if (increasing) {
                        System.out.println(rsoName + ": Entering geographic zone: " + spacePort.getSp() + " at: " + dataInEst);
                    } else {
                        System.out.println(rsoName + ": Exiting geographic zone: " + spacePort.getSp() + " at: " + dataInEst);
                    }
                    return Action.CONTINUE;
                });

                propagator.addEventDetector(zoneDetector);
                propagator.propagate(propagator.getInitialState().getDate().shiftedBy(Constants.JULIAN_DAY));
            });
        });

    }

My question is should this take 9 hours to run? I would love any insights into a better way to code this in order to speed it up. I am very new to satellite propagation and SGP4 so I don’t have anything to compare to.

Any insights are appreciated.
sg

It seems to me you are doing one full propagation for each pair TLE/ground point.
Perhaps you could set one propagation for each TLE but put all 30 zones as different events detectors within this TLE propagation.
Also you could try to use adaptable interval too to avoid checking all zones every 60 seconds and use larger intervals checks when the satellite is known to be far aways. Look at this topic which was raised just today.

Hi,

Are you sure you want to check their geographical zone? So you’re not interested in visibility (from a line of sight)? For the latter you would need an ElevationDetector which might be faster than the one you’re using.

Cheers,
Romain.

I’m not expert, but it looks like you are setting the event detection threshold to be 1e-3 seconds?
Each time a zero crossing is found (following a max step 60s), according to JavaDoc the system performs a root finding check to the 1e-3 second precision to specify. I don’t know how efficient this is, but i’d imagine looking for about 4-5 orders of precision increase in the zero crossing time would add up to a notable overhead.
If you don’t need to know the Zone entry/exit times to a millisecond, try changing this and see how it affects performance.
Of course as @serrof suggested, elevation detection might make more sense for line of sight, if that is what you are trying to achieve, and I would anticipate that even less time precision would suffice unless you demanded very high elevations.

2 Likes

Thank you both.

@Serrof
I would happily use the ElevationDetector if it will do the job. Knowing that my goal is to know when a satellite is directly over a ground point, would I just set elevation to 90 degrees? Like this:

// Elevation detector
double maxcheck  = 60.0;
double threshold =  0.001;
double elevation = Math.toRadians(90.0); // 90 degrees
EventDetector staVisi =
        new ElevationDetector(maxcheck, threshold, staFrame).
                withConstantElevation(elevation).
                withHandler(new OverheadPassHandler());

propagator.addEventDetector(staVisi);

For context, I am interested in knowing when any given LEO RSO is directly above the ground station. I thought it would be improbably to detect that a satellite was directly above a single lat/long point, so in order to increase my ground area, presumably making it easier to detect something larger that a single lat/long point, I surrounded my point in a SphericalPolygonsSet.

So much faster, but can it be right?

I have modified my code to have a single propagator for every individual TLE (~10k), where each propagator has 30 Elevation detectors, one for each ground station. The code now runs in 19 seconds!

However, with the elevation set to 90 degrees, I get no detections. If I lower the elevation to 85 degrees I get many? I am not sure how to interpret my results.

Refactored Code:

public static void main(String[] args) {
        manager.addProvider(new DirectoryCrawler(orekitData));
        SpacePortReader spacePortReader = new SpacePortReader();
        TLEReader tleReader = new TLEReader();
        List<SpacePort> spacePorts = spacePortReader.readSpacePorts();
        Map<String, TLE> tles =  tleReader.readTLEs();

        // Creates 10,436 propagators
        List<Propagator> propagators = tles.entrySet().stream()
                .map(entry -> {
                    TLE tle = entry.getValue();
                    String rsoName = entry.getKey();
                    // Create a propagator for the TLE
                    Propagator propagator = TLEPropagator.selectExtrapolator(tle);
                    double maxcheck  = 60.0;
                    double threshold =  0.001;
                    double elevation = Math.toRadians(90.0); // 90 degrees
                    final Frame earthFrame = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
                    final OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS, Constants.WGS84_EARTH_FLATTENING, earthFrame);

                    spacePorts.forEach(spacePort -> {
                        TopocentricFrame staFrame = new TopocentricFrame(earth, spacePort.getGeodeticPoint(), spacePort.getName());
                        propagator.addEventDetector(new ElevationDetector(maxcheck, threshold, staFrame)
                            .withConstantElevation(elevation)
                            .withHandler(new RSOOverheadHandler(spacePort.getId(), rsoName)));
                        }
                    );
                    // Each propagator has 30 EventDetectors
                    return propagator;
                })
                .toList();
        System.out.println("Propagators Initialized");

        propagators
                .parallelStream()
                .forEach(propagator ->
                        propagator.propagate(propagator.getInitialState().getDate().shiftedBy(5400)) // 90 minutes
                );

    }

EventHandler

// Custom event handler
public class RSOOverheadHandler implements EventHandler {
    String groundStationName;
    String rsoName;

    public RSOOverheadHandler(String groundStationName, String rsoName) {
        this.groundStationName = groundStationName;
        this.rsoName = rsoName;
    }

    @Override
    public Action eventOccurred(SpacecraftState s, EventDetector detector, boolean increasing) {

        if (increasing) {
            System.out.printf("RSO %s is directly overhead of %s at: %s%n", rsoName,groundStationName, s.getDate());

        }
        return Action.CONTINUE;
    }

    @Override
    public SpacecraftState resetState(EventDetector detector, SpacecraftState oldState) {
        return oldState;
    }
}

Lastly, I have tried to corroborate my results using Satellite Observing Opportunities - In-The-Sky.org. As you can see below, they predict STARLINK-1337 passes over me today. If I search the output of my code, I get no predicted passes.


Hello @gormanst
I think the reason you get no detections for 90 degrees is because that’s the maximum value the elevation can reach, so there’s not really a zero crossing. Both immediately before and immediately after the max elevation, you have a lower elevation value, meaning that the g function is both times negative, so there is no detection of a zero crossing happening. You would only be able to detect it if the step you’re using randomly happens to end up on the 90 degrees elevation timestamp.

This is my guess as to why you don’t detect anything for 90 degrees, but you get many for a lower value.