Parsin SLR station data with SinexParser (orekit 13)

Hi I am trying to work with data from the SLR stations, following the tutorial for OD in here. At some point I need to parse the data in a .snx file with the information on the stations position (to build the Groundstation objects). I was having problems in 12.2, the SinexLoader was giving wrong values for the position data, and I am now trying to do it with Orekit 13, using the SinexParser. I am geting an error that I don’t understand here:

sinex_stations_parser = SinexParser(TimeScalesFactory.getTimeScales())
ds = DataSource(stationFile) 
sinex_stations = sinex_stations_parser.parse(ds)

stationFile is the string with the location of the file. In the last line this is the error I get:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
orekit.JavaError: <super: <class 'JavaError'>, <JavaError object>>
    Java stacktrace:
java.lang.StringIndexOutOfBoundsException: String index out of range: 0
	at java.lang.String.charAt(String.java:658)
	at org.orekit.files.sinex.AbstractSinexParser.parse(AbstractSinexParser.java:69)

Maybe more information will be necessary to get to the core problem, but right now I am a bit lost.

Thanks for the help!

I am using Orekit 13.0.1 in python.

It seems your sinex file has some empty lines. The sinex files format enforces all lines to start with one of the five reserved character:

  • “%” for header and trailer lines
  • “*” comment line
  • “+” title at the start of a block
  • “-” title at the end of a block
  • " " data line within a block

The error you see seems to be caused by a line that has no characters at all, so it probably does not obey sinex format.

If this analysis is correct, could you open a ticket on the forge so we produce a more meaningful error message? We could for example say the file format is not respected and point the file name and line number.

Another possibility would be to just ignore empty lines, despite they do not fully respect file format.

The file in question is from this page: CDDIS | | Data and Derived Products | SLR | slrf 2020

Looking at the content indeed there is a complete blank line. But this error did not happen when using SinexLoader in Orekit 12.2.

There are indeed 2 empty lines in the FILE/REFERENCE section, and a third one after the %ENDSNX marker.

The Sinex parsing has been changed a lot between versions 12.x and 13.0, and more checks are performed. We can relax this and allow empty lines. I have already done the work, but not committed it.

Could you open the issue on the forge so I refer to it when fixing this?

Thank you Luc for the fast assistance. I am registering in the forge to open the issue (waiting for administrator approval). Still, I don’t see the third empty line you mention.

I added the issue here. Thanks again :grinning_face:

After fixing the empty line problem I still can’t correctly parse the snx file. I am using the strategy of the SLR tutorial mentioned before, where orekit’s SinexParser is used (actually it uses SinexLoader). It then goes through the station_map and retrieves the data of each active SLR station, and builds the Groundstation objects with that data. The problem I am experiencing is that the parser does not seem to get the correct position/velocity data for each station, resulting in coordinates kilometers within the earh. Here is the code that does this (slightly modified from the tutorial):

def parseStationData_Orekit(stationFile, stationEccFile, epoch, _ITRF_VERSION: Optional[str] = "ITRF_2014", simpleEOP: Optional[bool] = True):

    itrf = FramesFactory.getITRF(ITRFVersion.valueOf(_ITRF_VERSION), IERSConventions.IERS_2010, simpleEOP)
    ecef = itrf
    wgs84Ellipsoid = ReferenceEllipsoid.getWgs84(ecef)
    
    year = int(_ITRF_VERSION.split('_')[1])

    # Use Orekit parser
    sinex_stations_parser = SinexParser(TimeScalesFactory.getTimeScales())
    ds_stations = DataSource(stationFile) 
    sinex_stations = sinex_stations_parser.parse(ds_stations)
    stations_map = sinex_stations.getStations()
    
    sinex_ecc_parser = SinexParser(TimeScalesFactory.getTimeScales())
    ds_ecc = DataSource(stationEccFile) 
    sinex_ecc = sinex_ecc_parser.parse(ds_ecc)
    ecc_map = sinex_ecc.getStations()

    station_keys = stations_map.keySet()
    
    n_errors = 0
    station_df = pd.DataFrame(columns=['lat_deg', 'lon_deg', 'alt_m', 'GroundStation'])
    for key in station_keys:
        station_data = stations_map.get(key)
        ecc_data = ecc_map.get(key)
        if ecc_data.getEccRefSystem() != Station.ReferenceSystem.XYZ:
            print('Error, eccentricity coordinate system not XYZ')

        epoch_velocity = station_data.getEpoch()
        durationSinceEpoch = datetime_to_absolutedate(epoch).durationFrom(epoch_velocity)  # seconds

        # Computing current station position using velocity data
        station_pos_at_epoch = station_data.getPosition()
        vel = station_data.getVelocity()  # m/s
        station_pos_current = station_pos_at_epoch.add(vel.scalarMultiply(durationSinceEpoch))

        # Adding eccentricity
        try:
            ecc_data = ecc_data.getEccentricities(datetime_to_absolutedate(epoch))
            station_pos_current = station_pos_current.add(ecc_data)
            # Converting to ground station object
            geodeticPoint = wgs84Ellipsoid.transform(station_pos_current, itrf, datetime_to_absolutedate(epoch))
            lon_deg = np.rad2deg(geodeticPoint.getLongitude())
            lat_deg = np.rad2deg(geodeticPoint.getLatitude())
            alt_m = geodeticPoint.getAltitude()
            topocentricFrame = TopocentricFrame(wgs84Ellipsoid, geodeticPoint, key)
            groundStation = GroundStation(topocentricFrame)
            station_df.loc[key] = [lat_deg, lon_deg, alt_m, groundStation]
        except:
            # And exception is thrown when the odDate is not in the date range of the eccentricity entry for this station
            # This is simply for stations which do not exist anymore at odDate
            n_errors += 1
    return station_df, n_errors

An example of the for loop for each station key:

key
'7840'
station_pos_at_epoch
<Vector3D: {4,033,463.47630212; 1,162,694.35703709; 4,647,246.84303809}>
alt_m
-103938.4978470715

The same station information from the .snx file:

1183 STAX   7840  A    1 15:001:00000 m    2 0.403346347630212E+07 0.61390E-03
1184 STAY   7840  A    1 15:001:00000 m    2 0.236627872673329E+05 0.65693E-03
1185 STAZ   7840  A    1 15:001:00000 m    2 0.492430535075408E+07 0.65568E-03
1186 VELX   7840  A    1 15:001:00000 m/y  2 -.131572303249976E-01 0.49144E-04
1187 VELY   7840  A    1 15:001:00000 m/y  2 0.170848681043556E-01 0.48038E-04
1188 VELZ   7840  A    1 15:001:00000 m/y  2 0.981395859644500E-02 0.55278E-04

Notice the STAX posittion data is correct, but the rest are not. In fact the Y and Z position are from the previous station in the file:

1177 STAX   7839  A    1 15:001:00000 m    2 0.419442621299162E+07 0.67699E-03
1178 STAY   7839  A    1 15:001:00000 m    2 0.116269435703709E+07 0.70288E-03
1179 STAZ   7839  A    1 15:001:00000 m    2 0.464724684303809E+07 0.68829E-03
1180 VELX   7839  A    1 15:001:00000 m/y  2 -.169475196163750E-01 0.52863E-04
1181 VELY   7839  A    1 15:001:00000 m/y  2 0.178948924955752E-01 0.50710E-04
1182 VELZ   7839  A    1 15:001:00000 m/y  2 0.104131812643129E-01 0.58086E-04

I hope this is a problem of how I am using the SinexParser and not a bug :sweat_smile:

@luc Could this be related to other formatting problems of the file? I haven’t look into how SinexParser works, but I cant think of another reason for this weird behaviour.

Sorry, I don’t have time to look at this right now.
What I can just tell is that since the coordinates are parsed one line at a time, we have to preserve the previous entries before we build the vector. I think we collect everything when we get the Z coordinate, but as your example seem to properly have the canonical order X, Y, Z it should not be a problem.

I’m sorry I can’t dig in it yet.