How do you combine multiple Ephemeris to get one Propagator that has all the PVCoordinates?

Given a satellite ID and date time, I am looking to figure out the position in Lat Long Alt.
There is one sp3 navigation file per day. https://cddis.nasa.gov/Data_and_Derived_Products/GNSS/orbit_products.html

  1. How do I load multiple days(Ephemeris files) into one Ephemeris object or one Propagator that has all the info ? If I load just one day’s navigation data file, the code below works from 12AM to 11:44PM. I get the position in ECEF x y z e.g. {-9,920,197.530017082; 17,398,299.940364283; 17,415,892.930824082}
    However, I am not able to query times between 11:44PM and 11:59PM as it results in this error:
    java -jar orekitsatpos.jar 18 “01-jan-2021 23:55:15”
    Exception in thread “main” org.orekit.errors.TimeStampedCacheException: unable to generate new data after 2021-01-01T23:44:42.000Z, but data is requested for 2021-01-01T23:55:15.000Z which is 6.33E2 s after
    at org.orekit.utils.ImmutableTimeStampedCache.getNeighbors(ImmutableTimeStampedCache.java:125)
    at org.orekit.files.general.EphemerisSegmentPropagator.getPVCoordinates(EphemerisSegmentPropagator.java:98)
    at satposition.SatPosition.main(SatPosition.java:59)
  1. Is there a built in method to get/convert position in Latitude, Longitude and Altitude instead of ECEF format or do we have to use another library?
	public static void main(String[] args) {
		
		//Process input arguments
		int prn = Integer.parseInt(args[0]);
		LocalDateTime dt = LocalDateTime.parse(args[1]);
		
		//Initialize orekit
		File orekitData = new File("resources/orekit-data-master");
		DataProvidersManager manager = DataContext.getDefault().getDataProvidersManager();
		manager.addProvider(new DirectoryCrawler(orekitData));
		
		//File paths
		File sp3FilePreviousDay = new File(getFilePath(SP3_BASEPATH, dt.minusDays(1)));
		File sp3File = new File(getFilePath(SP3_BASEPATH, dt));
		File sp3FileNextDay = new File(getFilePath(SP3_BASEPATH, dt.plusDays(1)));
		
		// Load the SP3 file
		SP3Parser parser = new SP3Parser();
		SP3 sp3= parser.parse(new DataSource(sp3File)); //How to load additional files?
	
		
		//Get Ephemeris
		AbsoluteDate absDate = new AbsoluteDate(dt.getYear(), dt.getMonthValue(), dt.getDayOfMonth(),
				dt.getHour(), dt.getMinute(), dt.getSecond(), TimeScalesFactory.getUTC());
		SP3Ephemeris sp3Ephemeris = sp3.getSatellites().get("G"+prn);
		Propagator propagator = sp3Ephemeris.getPropagator();
		TimeStampedPVCoordinates tspvc = propagator.getPVCoordinates(absDate,FramesFactory.getITRF(IERSConventions.IERS_2010, false));
		
		//Get Position
		System.out.println(tspvc.getPosition());

	}
    private static int calculateGPSWeek(LocalDate date) {
        LocalDate gpsWeekStartDate = LocalDate.parse("1980-01-06");

        long days = date.toEpochDay() - gpsWeekStartDate.toEpochDay();

        long gpsWeek = days / 7;

        return (int) gpsWeek;
    }
    private static String getFilePath(String basePath, LocalDateTime dateTime) {
        int year = dateTime.getYear();
        int dayOfYear = dateTime.getDayOfYear();
        int gpsWeek = calculateGPSWeek(dateTime.toLocalDate());
        int dayOfWeek = dateTime.getDayOfWeek().getValue();

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");

        String yearPath = String.format("%04d", year);
        String dayOfYearPath = String.format("%03d", dayOfYear);
        String gpsWeekPath = String.format("%04d", gpsWeek);
        String dayOfWeekPath = String.format("%d", dayOfWeek);

        return basePath + "/" + year + "/" + dayOfYearPath + "/igs" + gpsWeekPath + dayOfWeekPath+".sp3";
    }

Hi @matrix

Welcome to the Orekit forum :slight_smile:

I think you’re looking for an AggregateBoundedPropagator. It allows combining several BoundedPropagator from SP3 files into a single one.
You can find a topic about this class here: Interpolation over multiple SP3 files
In addition, the API documentation is available here: ORbit Extrapolation KIT 11.3.2 API

The method is:

  1. Create a OneAxisEllipsoid object. Example:
final BodyShape earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
                                                     Constants.WGS84_EARTH_FLATTENING,
                                                     FramesFactory.getITRF(IERSConventions.IERS_2010, false));
  1. Use the earth.transform(Vector3D point, Frame frame, AbsoluteDate date) to get a GeodeticPoint object
  2. Access the Lat/Lon/Alt values with getLatitude(), getLongitude(), and getAltitude() methods of GeodeticPoint

Regards,
Bryan

Blockquote
I think you’re looking for an AggregateBoundedPropagator . It allows combining several BoundedPropagator from SP3 files into a single one.
You can find a topic about this class here: Interpolation over multiple SP3 files

Thank you. It ended up with the same problem as that thread where the last 15 minutes of a day cannot be interpolated with the ephemeris from before and after those 15 minutes with the same TimeStampedCacheException. I wanted to combine multiple days of ephemeris so that there is continuity to get interpolated PVcoordinates between 11:44PM and 11:59PM

Could you provide the .sp3 files?

Thank you,
Bryan

SP3 files for January 4,5,6, 2021
igs21391.sp3 (247.3 KB)
igs21392.sp3 (247.3 KB)
igs21393.sp3 (247.3 KB)
They are also located at:
https://geodesy.noaa.gov/corsdata/rinex/2021/004/igs21391.sp3.gz
https://geodesy.noaa.gov/corsdata/rinex/2021/005/igs21392.sp3.gz
https://geodesy.noaa.gov/corsdata/rinex/2021/006/igs21393.sp3.gz

Updated code with recommended changes:

package satposition;

import java.io.File;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.Arrays;
import java.util.Locale;

import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.orekit.bodies.BodyShape;
import org.orekit.bodies.GeodeticPoint;
import org.orekit.bodies.OneAxisEllipsoid;
import org.orekit.data.DataContext;
import org.orekit.data.DataProvidersManager;
import org.orekit.data.DataSource;
import org.orekit.data.DataSource.ReaderOpener;
import org.orekit.data.DirectoryCrawler;
import org.orekit.files.general.EphemerisFile;
import org.orekit.files.general.OrekitEphemerisFile;
import org.orekit.files.sp3.SP3;
import org.orekit.files.sp3.SP3.SP3Ephemeris;
import org.orekit.files.sp3.SP3Parser;
import org.orekit.frames.FramesFactory;
import org.orekit.propagation.BoundedPropagator;
import org.orekit.propagation.Propagator;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.analytical.AggregateBoundedPropagator;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.DateTimeComponents;
import org.orekit.time.TimeScale;
import org.orekit.time.TimeScales;
import org.orekit.time.TimeScalesFactory;
import org.orekit.utils.Constants;
import org.orekit.utils.IERSConventions;
import org.orekit.utils.TimeStampedPVCoordinates;


public class SatPosition {

	public final static String SP3_BASEPATH= "/sp3/noaa";
	public static void main(String[] args) {
		System.out.println("PRN: "+ args[0]+" @ "+args[1]);
		
		//Process input arguments
		int prn = Integer.parseInt(args[0]);
		LocalDateTime dt = parseDateTime(args[1]);
		
		//Initialize orekit
		File orekitData = new File("resources/orekit-data-master");
		DataProvidersManager manager = DataContext.getDefault().getDataProvidersManager();
		manager.addProvider(new DirectoryCrawler(orekitData));
		
		//File paths
		File sp3FilePreviousDay = new File("resources/igs21391.sp3");
		File sp3File = new File("resources/igs21392.sp3");
		File sp3FileNextDay = new File("resources/igs21393.sp3");
		
//		File sp3FilePreviousDay = new File(getFilePath(SP3_BASEPATH, dt.minusDays(1)));
//		File sp3File = new File(getFilePath(SP3_BASEPATH, dt));
//		File sp3FileNextDay = new File(getFilePath(SP3_BASEPATH, dt.plusDays(1)));
		
		// Load the SP3 files/ephemeris
		SP3 sp3PreviousDay = new SP3Parser().parse(new DataSource(sp3FilePreviousDay));
		SP3Ephemeris sp3EphemerisPreviousDay = sp3PreviousDay.getSatellites().get("G"+prn);
		BoundedPropagator sp3PropagatorPreviousDay = sp3EphemerisPreviousDay.getPropagator();
		
		SP3Parser parser = new SP3Parser();
		SP3 sp3= parser.parse(new DataSource(sp3File));
		SP3Ephemeris sp3Ephemeris = sp3.getSatellites().get("G"+prn);
		BoundedPropagator sp3Propagator = sp3Ephemeris.getPropagator();
		
		SP3 sp3NextDay = new SP3Parser().parse(new DataSource(sp3FileNextDay));
		SP3Ephemeris sp3EphemerisNextDay = sp3NextDay.getSatellites().get("G"+prn);
		BoundedPropagator sp3PropagatorNextDay = sp3EphemerisNextDay.getPropagator();
		
		//Query Ephemeris
		AbsoluteDate absDate = new AbsoluteDate(dt.getYear(), dt.getMonthValue(), dt.getDayOfMonth(),
				dt.getHour(), dt.getMinute(), dt.getSecond(), TimeScalesFactory.getUTC());
		AggregateBoundedPropagator aggregateBoundedPropagator = new AggregateBoundedPropagator(Arrays.asList(sp3PropagatorPreviousDay,sp3Propagator,sp3PropagatorNextDay)) ;
		TimeStampedPVCoordinates tspvc = aggregateBoundedPropagator.getPVCoordinates(absDate,FramesFactory.getITRF(IERSConventions.IERS_2010, false));

		//Get Position
		Vector3D position = tspvc.getPosition();
		System.out.println("ECEF:"+position.getX()+","+position.getY()+","+position.getZ());
		
		
		//Convert ECEF to LLA
		final BodyShape earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
                Constants.WGS84_EARTH_FLATTENING,
                FramesFactory.getITRF(IERSConventions.IERS_2010, false));
		GeodeticPoint gp = earth.transform(position, FramesFactory.getITRF(IERSConventions.IERS_2010,false), absDate);
		System.out.println("Geodetic: "+ gp.getLatitude()*180/Math.PI+", "+ gp.getLongitude()*180/Math.PI+", "+gp.getAltitude());
		double lla[] = convertCartesianToGeodetic(position.getX(), position.getY(), position.getZ());
		System.out.println("Geodetic: "+ Arrays.toString(lla));

		

	}
    private static LocalDateTime parseDateTime(String dateTimeString) {
        DateTimeFormatter formatter = new DateTimeFormatterBuilder().parseCaseInsensitive().appendPattern("dd-MMM-yyyy HH:mm:ss").toFormatter(Locale.ENGLISH);
        try {
            return LocalDateTime.parse(dateTimeString, formatter);
        } catch (Exception e) {
        	System.out.println(dateTimeString);
            e.printStackTrace();
            return null;
        }
    }
    
    private static int calculateGPSWeek(LocalDate date) {
        LocalDate gpsWeekStartDate = LocalDate.parse("1980-01-06"); // GPS week starts from January 6, 1980
        long days = date.toEpochDay() - gpsWeekStartDate.toEpochDay();
        long gpsWeek = days / 7;
        return (int) gpsWeek;
    }
    
    private static String getFilePath(String basePath, LocalDateTime dateTime) {
        int year = dateTime.getYear();
        int dayOfYear = dateTime.getDayOfYear();
        int gpsWeek = calculateGPSWeek(dateTime.toLocalDate());
        int dayOfWeek = dateTime.getDayOfWeek().getValue();
        if (dayOfWeek == 7) {
        	dayOfWeek = 0;  // Adjusting Sunday to 0
        } 
        String dayOfYearPath = String.format("%03d", dayOfYear);
        String gpsWeekPath = String.format("%04d", gpsWeek);

        return basePath + "/" + year + "/" + dayOfYearPath + "/igs" + gpsWeekPath + dayOfWeek+".sp3";
    }
    private static double[] convertCartesianToGeodetic(double x, double y, double z) {

        // Define the constants for the WGS84 ellipsoid
        final double a = 6378137.0;       // Semi-major axis
        final double b = 6356752.314245;  // Semi-minor axis
        final double eSquared = (Math.pow(a, 2) - Math.pow(b, 2)) / Math.pow(a, 2);

        // Calculate the longitude
        double longitude = Math.atan2(y, x);

        // Calculate the distance from the origin to the point in the xy-plane
        double rho = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));

        // Calculate the latitude
        double latitude = Math.atan2(z, rho);

        // Calculate the altitude
        double sinLatitude = Math.sin(latitude);
        double cosLatitude = Math.cos(latitude);
        double numerator = z + eSquared * b * Math.pow(sinLatitude, 3);
        double denominator = rho - eSquared * a * Math.pow(cosLatitude, 3);
        double altitude = rho / Math.cos(latitude) - a * Math.sqrt(1.0 - eSquared * Math.pow(sinLatitude, 2)) +
                numerator / denominator;

        return new double[]{Math.toDegrees(latitude), Math.toDegrees(longitude), altitude};
    }
}

SatPosition.java (7.0 KB)
Test input arguments:
Working: 18 “05-jan-2021 13:48:00”
Not working: 18 “05-jan-2021 23:48:00”

Hi @matrix and @bcazabonne ,

For this specific case, I think that it is better to construct a merged sp3.

Here is the preliminary solution. Of course, there are many things to do to ensure the timestamps are continuous, or no gap.

    public static SP3 appendSP3(final SP3 mergedSp3, final String sp3_filename) {
        final SP3 sp3_tmp = new SP3Parser().parse(new DataSource(sp3_filename));
        if (mergedSp3 == null) {
            return sp3_tmp;
        }
        for (final Map.Entry<String, SP3Ephemeris> entry : sp3_tmp.getSatellites().entrySet()) {
            final String satId = entry.getKey();
            mergedSp3.addSatellite(satId);
            for (final SP3Coordinate coordinate : entry.getValue().getCoordinates()) {
                mergedSp3.addSatelliteCoordinate(satId, coordinate);
            }
        }
        return mergedSp3;
    }

    public static void main(String[] args) {
        ......
        final SP3 mergedSp3 = appendSP3(null, "/home/lirw/Downloads/msp3/igs21391.sp3");
        appendSP3(mergedSp3, "/home/lirw/Downloads/msp3/igs21392.sp3");
        appendSP3(mergedSp3, "/home/lirw/Downloads/msp3/igs21393.sp3");
        final BoundedPropagator propagator = mergedSp3.getSatellites().get(satId).getPropagator();
        ......
    }

Thank you @lirw1984 ! This solved the issue. I am able to query for positions between 11:44PM and 11:59PM now.

Digging out an old thread, because new developments simplify things a lot.

Since issue 1155 has been solved, it is now possible to splice together several SP3 files to create a single one covering the whole range. This allows interpolating between the last point of one file and the first point of next file seamlessly.

2 Likes