Upgrading from Orekit 12.X to Orekit 13.0

Version 13.0 of Orekit introduced some incompatible API changes with respect to versions 12.x. These changes are summarized in the following table. The next paragraphs give hints about how users should change application source code to adapt to this new version.

change related issues
building FieldOrbit from Orbit issue 1194
spacecraft state interpolator requires specifying interpolation method issue 1266
renaming_get_events_detectors issue 1270
building an AbsoluteDate from Instant is always in UTC time scale issue 1272
position angles conversions moved issue 1275
fitting of Earth Orientation Parameters issue 1278
tropospheric models revamped issue 1287
empty cache without field argument issue 1329
signal time of flight needs a frame issue 1332
ambiguity handling in phase measurements issue 1333
clock offset applied flag in Rinex files issue 1341
measurement generation returns EstimatedMeasurementBase issue 1350
getPropagators replaced by getPropagatorsMap issue 1370
removal of PropagatorBuilder copy method issue 1378
new_argument_in_(field)_adaptable_interval issue 1382
frame guessing in IGS products issue 1394
Frequency replaced by RadioWave, GnssSignal, or PredefinedGnssSignal where relevant issue 1434, issue 1456
typo in PressureTemperatureHumidityProvider API issue 1447
revamp of dates handling issue 1454
signal time of flight endpoints adjustment issue 1468
improved performance of indirect shooting issue 1523
revamped Sinex and Sinex-bias parsing issue 1538
removed deprecated signature of create in (Field)AbstractDetector issue 1541
use SatInSystem in SatelliteAntenna issue 1542
rates in (Field)Orbit issue 1546
revamped (Field)ImpulseManeuver issue 1575
simplify use of AttitudesSequence issue 1577
deprecate ExtendedPVCoordinatesProvider issue 1576
added getEffectName to EstimationModifier issue 1542
moved_adaptable_interval issue 1580
renamed EarthITU453AtmosphereRefraction into ITURP834AtmosphericRefraction issue 1590
renamed DEFAULT MAXCHECK issue 1595
[updated indirect cost functions](Updated indirect cost functions) issue 1597
[extract switch handler from AttitudesSequence](#Extract switch handler from AttitudesSequence) issue 1607
[changed attitude override in maneuver](#Changed attitude_override in maneuver) issue 1609
[introduced generic in integrator builder](#Introduced generic in integrator builder) issue 1613
[introduced impulse provider for maneuvers](#Introduced impulse provider for maneuvers) issue 1619 [generalize ProfileThrustPropulsionModel](#Generalize ProfileThrustPropulsionModel) issue 1631
[generalize ProfileThrustPropulsionModel](#Generalize ProfileThrustPropulsionModel) issue 1631
[introduce resettable maneuver trigger](#Introduce resettable maneuver trigger) issue 1633

Building FieldOrbit from Orbit

overview of the change

In the 12.X series, two independent ways to convert an Orbit into a FieldOrbit were set up. One was to use a new utility class Fieldifier and the second one was to use a convertToFieldOrbit method from OrbitType. As this was redundant, the utility method in Fieldifier was deprecated and the convertToFieldOrbit method from OrbitType was kept. The deprecated method was removed in 13.0

how to adapt existing source code

If users called the utility method in Fieldifier, they should now call the method from OrbitType. This means replacing (taking a circular orbit as an example):

import org.orekit.orbits.FieldCircularOrbit;
import org.orekit.utils.Fieldifier;

final FieldCircularOrbit<T> fieldCircularOrbit = (FieldCircularOrbit<T>) Fieldifier.fieldify(field, circularOrbit);

by

final FieldCircularOrbit<T> fieldCircularOrbit = (FieldCircularOrbit<T>) circularOrbit.getType().convertToFieldOrbit(field);

Spacecraft state interpolator requires specifying interpolation method

overview of the change

In the 12.X series, it was possible to build a SpacecraftStateInterpolator without specifying the number of interpolation points or the threshold. In this case, it used the default values. This could generate exceptions if the associated interpolators for orbit, mass, attitude… did not use the same default values. The constructor was deprecated in 12.0 and a new constructor added with settings for number of interpolation points and threshold. The deprecated constructor was removed in 13.0

how to adapt existing source code

If user code called the SpacecraftStateInterpolator without specifying the number of interpolation points or the threshold, it should now add these parameters explicitly, using AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS and AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC.

Renaming in (Field)Propagator

overview of the change

Propagators had a method getEventsDetectors, whose naming was inconsistent with similar functions in EventDetectorProvider and Hipparchus.

how to adapt existing source code

Simply replace your calls by getEventDetectors.

Building an AbsoluteDate from Instant is always in UTC time scale

overview of the change

In the 12.X series, it was possible to build an AbsoluteDate from a java.time.Instant, specifying an arbitrary time scale. This was not compliant with the Instant API which requires using UTC. A constructor using only the Instant was added in 12.1 as well as a constructor with at time scale that is enforced to be UTC. The constructor with Instant and TimeScale was deprecated. The deprecated constructor was removed in 13.0.

how to adapt existing source code

If the constructor with a Instant and TimeScale was used and the time scale was already UTC, then there is nothing to do. If the time scale was not UTC, then the semantics must be revised by users, as they did violate Instant API, so they probably need to check how the Instant is produced, to ensure it is really in UTC.

Position angles conversions moved

overview of the change

In the 12.X series, position angles in orbits (anomalies for {Field}KeplerianOrbit, latitude arguments for {Field}CircularOrbit, longitude arguments for {Field}EquinoctialOrbit) could be converted between their MEAN, TRUE and ECCENTRIC flavors using static methods in the orbit classes themselves. These static have been moved to dedicated utility classes {Field}KeplerianAnomalyUtility, {Field}CircularAnomalyUtility, and {Field}EquinoctialAnomalyUtility as of version 12.1. The methods in the orbit classes have been deprecated in 12.1 and removed in 13.0.

how to adapt existing source code

As the methods are static ones, just the name of the class providing the methods should be changed. This means replacing (taking circular orbit and conversion from ECCENTRIC to TRUE as an example):

final double alphaV = CircularOrbit.eccentricToTrue(ex, ey, alphaE);

by

final double alphaV = CircularLatitudeArgumentUtility.eccentricToTrue(ex, ey, alphaE);

Fitting of Earth Orientation Parameters

overview of the change

In the 12.X series, Fitting of Earth Orientation Parameters was introduced. This was initially configured using a fitting duration as well as an exponential weight with a time constant, starting with small weights at the beginning of the fitting duration and increasing weights afterwards. However, this led to numerical errors in some configurations. A new configuration was then used, starting from the last known EOP and decreasing weights when going towards past. The fitting duration was therefore ignored as the exponential decrease made it useless. The corresponding constructor was deprecated. This deprecated constructor was removed in 13.0.

how to adapt existing source code

If users called the SingleParameterFitter with a fitting duration, they should just remove this parameter; anyway it was ignored since 12.0.1, so this should not have any influence.

Tropospheric models revamped

overview of the change

In the 12.X series, tropospheric models implemented the DiscreteTroposphericModel interface. This interface used constant temperature pressure and hygrometry but variable location, which was inconsistent. It did not allow to use models that are azimuth-dependant (slanted atmosphere layers). It also used inconsistent non-SI units. The interface was completely rewritten as TroposphericModel in 12.1, and the existing models were rewritten to implement the new interfaces. A new GroundStation constructor was added that references a PressureTemperatureHumidityProvider that will be used to provide weather parameters to measurements performed by the station. The older tropospheric model interface, the former models implementations and the GroundStation constructor without provider were deprecated. In version 13.0, the deprecated interface, implementations and constructor were all removed.

how to adapt existing source code

All the tropospheric models available in Orekit 12.X are still available, sometimes with different names (for example MariniMurrayModel is now called MariniMurray, SaastamoinenModel is now ModifiedSaastamoinenModel, but there is also a CanonicalSaastamoinenModel) and different constructors. Some models allow passing a PressureTemperatureHumidityProvider to the constructor, which allows to retrieve time and location-dependent weather parameters. Several new tropospheric models have been added in the process. AngularTroposphericDelayModifier was also discovered to be seriously flawed and has been replaced by AngularRadioRefractionModifier.

Users should review the models they use and adapt the class names as well as the new constructor parameters. If they used a simple configuration with fixed weather parameters, they can use either ConstantPressureTemperatureHumidityProvider or TroposphericModelUtils.STANDARD_ATMOSPHERE_PROVIDER (or TroposphericModelUtils.STANDARD_ATMOSPHERE if they need the constant values and not the provider). For better representativity, it is suggested to use either a global model like GlobalPressureTemperature3 or to implement PressureTemperatureHumidityProvider with a custom class downloading real measured data.

Empty cache without field argument

overview of the change

In the 12.X series, the ImmutableFieldTimeStampedCache.emptyCache had a field argument which was in fact ignored. This method has been deprecated in 12.1 and a new method without any argument added. The deprecated method was removed in 13.0

how to adapt existing source code

If users called ImmutableFieldTimeStampedCache.emptyCache with a field argument, they should just remove the argument at the call site.

Signal time of flight needs a frame

overview of the change

In the 12.X series, the AbstractMeasurement method signalTimeOfFlight only used a TimeStampedPVCoordinates without care about the frame it refered too. An additional signature was added, using a PVCoordinatesProvider, which required specifying the frame. For consistency, the frame was also added in the TimeStampedPVCoordinates version and the method without the frame was deprecated. The deprecated method was removed in 13.0.

how to adapt existing source code

If user code called signalTimeOfFlight without a frame, it should now also pass the frame in the arguments list.

Ambiguity handling in phase measurements

overview of the change

In the 12.X series, the phase measurements (both ground measurements and inter-satellite measurements) managed one ambiguity parameter for each measurement, and in addition there were dedicated modifiers for the same purpose. This was cumbersome and wasted a lot of resources since numerous parameter drivers where create and should be managed together as they share the same name. An AmbiguityCache has been set up to drastically reduce the number of parameter drivers (allowing to use only one driver for each emitter/receiver/wavelength triplet) and could be passed to the measurements constructors. This cache holds AmbiguityDriver entries that are specialized ParameterDriver. The constructor without the ambiguity cache were deprecated and a temporary default cache was made available and used by these deprecated constructors. As with this change the phase measurements managed the ambiguity by themselves, the additional modifiers were also deprecated. All deprecated constructors and classes were removed in 13.0, as well as the temporary default cache.

how to adapt existing source code

In order to build phase measurements, users should set up one instance of an ambiguity cache on their own (just calling new AmbiguityCache()) and pass it as an argument to the phase measurements constructors (or the phase measurements builders constructors in the measurement generation use case). The cache will be populated automatically as measurements are created. The ambiguities can be retrieved either from the measurements themselves (using the getAmbiguityDriver method) or from the cache, given the emitter, receiver and wavelength.

Clock offset applied flag in Rinex files

overview of the change

In the 12.X series, the RCV CLOCK OFFS APPL flag in Rinex observation files was parsed as an integer, despite it really has the semantic of a flag. This was confusing as a numerical value could lead people to think it was really the offset itself that was stored there, when in fact the offset is present in the observations. The integer-based getter and setters were deprecated in 12.1 and new boolean-based getters and setters were added. The deprecated getters and setters were removed in 13.0.

how to adapt existing source code

If users called setClkOffset or getClkOffset, they should replace these calls by the boolean versions setClockOffsetApplied and getClockOffsetApplied.

Measurement generation now returns EstimatedMeasurementBase

overview of the change

In the 12.X series, the build method in MeasurementBuilder<T> interface from package org.orekit.estimation.measurements.generation did return an object of type T, where T was the parameterized type in MeasurementBuilder<T> and extended ObservedMeasurement<T>.

Returning only the observed value was limiting as users may sometimes need access to the complete states that were used during the generation. These states were in fact computed internally and discarded after the observed measurement was produced. The rationale of this change was to allow retrieving these complete states.

Starting with version 13.0 the return type of the build method was therefore changed to EstimatedMeasurementBase<T>.

how to adapt existing source code

If the additional information is relevant to the caller application, then users should change the type of the variable they use to store the estimated measurement and use it. This means replacing

final T built = builder.build(date, interpolators);

by

final EstimatedMeasurementBase<T> built = builder.build(date, interpolators);

If the additional information is not relevant to the caller application, then users can just extract the observed measurement from the estimated measurement. This means replacing

final T built = builder.build(date, interpolators);

by

final T built = builder.build(date, interpolators).getObservedMeasurement();

getPropagators replaced by getPropagatorsMap

overview of the change

In the 12.X series, AggregateBoundedPropagator.getPropagators method rebuilt the map each time it was called. It was deplrecated and replaced by a getPropagatorsMap method that retruend the same map each call. The deprecated method was removed in 13.0

how to adapt existing source code

User code should just adapt the method name at call site.

Removal of PropagatorBuilder copy method

overview of the change

In 12.X series, the PropagatorBuilder.copy() method was deprecated and replaced by the .clone() method available natively in Java. The deprecated method was removed in 13.0

how to adapt existing source code

The change is straightforward. Users only have to replace

import org.orekit.propagation.conversion.AbstractPropagatorBuilder;

final AbstractPropagatorBuilder newBuilder = builder.copy();

by

final AbstractPropagatorBuilder newBuilder = builder.clone();

New argument in (Field)AdaptableInterval's method

overview of the change

The method currentInterval in (Field)AdaptableInterval now has a second argument called isForward to indicate the direction of propagation. This allows to tailor the computation knowing this information and follows a similar change in Hipparchus.

how to adapt existing source code

If you only use “constant” intervals, you should use the static method of to create instance and nothing needs changed. Otherwise, just add the second argument in your signature and use if if applicable.

Frame guessing in IGS products

overview of the change

In the 12.X series, frames guessing in IGS products like SP3 or Rinex files was flawed. It missed several cases and returned a default Earth frame that could be wrong. A more general utility method IGSUtils.guessFrame was introduced. This method complies with more classical denominations used by IGS laboratories and also allows as an extension to use a few non-rotating frames as used in some industrial teams. The SP3Parser.guessFrame method and SP3Parser.SP3_FRAME_CENTER_STRING constant were deprecated. The deprecated items were removed in 13.0.

how to adapt existing source code

If user codes did call SP3Parser.guessFrame, they should call instead IGSUtils.guessFrame.

Frequency replaced by RadioWave, GnssSignal, or PredefinedGnssSignal where relevant

overview of the change

In the 12.X series, numerous classes and methods in the library used the enumerates Frequency and ObservationType from package org.orekit.gnss directly. These enumerates listed the predefined frequencies and observable that are used by the existing GNSS systems (GPS, Glonass, Galileo, Beidou, QZSS, IRNSS, SBAS).

Enumerates are fine when dealing with existing systems supported by Orekit, but they are not sufficient when designing new navigation systems using new frequencies or new observables. The rationale of this change was to allow using custom frequencies and observation types. The naming convention for the first enumerate was also awkward as the name Frequency was too generic and therefore confusing and as the API also provided access to other elements (like wavelength, common frequency multiplier, and name). A last problem with the enumerates API was that it used non-SI units (MHz instead of Hz for frequency).

This change was introduced in two steps, one that did not break the API and was introduced in version 12.1 and one that did break the API and was introduced in version 13.0.

Starting with version 12.1, several methods of this enumerate where moved upward in new interface GnssSignal and its superinterface RadioWave. This had no consequence on users source code. Starting with version 13.0, the Frequency enumerate was renamed PredefinedGnssSignal and numerous methods that did reference it were changed to reference either RadioWave, GnssSignal, or PredefinedGnssSignal. The ObservationType was also changed to be an interface and a new enumerate PredefinedObservationType was introduced.

As long as only enumerates were used, relying on the equality operator == to compare a predefined signal with a reference was relevant. It is not relevant anymore when using interfaces implemented by custom user classes. Two predicate methods, closeTo(other) and closeTo(other, tolerance) have therefore been added to the new RadioWave interface to check if frequencies are close enough. The first method uses a default tolerance of 1 mHz, which is good enough for most purposes.

A few method names have been changed according to their return types, for example getFrequencies() in Antenna has been renamed getRadioWaves(), getFrequency() in ObservationType has been renamed getSignal(), and getSignal() in BeidouCivilianNavigationMessage has been renamed getRadioWave().

The Rinex parser and writer API have been extended to allow these new signals and observation types to be used in Rinex files. Note however that as these elements are non-standard, the files produced may be impossible to read with applications that do not rely on Orekit for parsing.

how to adapt existing source code

In most cases, as the interfaces are just higher level abstractions of the same concept that was already implemented by the Frequency enumerate, users can just change the types of the objects they use to either PredefinedGnssSignal, GnssSignal or RadioWave depending on the method. Sometimes the type appears in a parameterized type, so for example the ReceiverAntenna constructor now requires a Map<RadioWave, FrequencyPattern> instead of a Map<Frequency, FrequencyPattern>. Such types changes in methods signatures and field types are sufficient if the only methods used in the enumerate were getName() or getRatio() (which are defined in GnssSignal) or if they were getFrequency() or getWavelength() (which are defined in RadioWave). Note that getMHzFrequency() has been replaced by getFrequency() which returns a predefinedGnssSignal in Hz and not in MHz anymore, for consistency with Orekit convention to use SI units everywhere.

As explained above, if the enumerate was used directly with an equality operator to check against some predefined value, this check should be changed to a proximity check by calling one of the closeTo methods.

The call sites for methods that have changed names must be adapted to use the new names.

Typo in PressureTemperatureHumidityProvider API

overview of the change

The PressureTemperatureHumidityProvider interface introduced in 12.0 defines two methods with name getWeatherParamerers, which was a typo. The error was fixed and the name was changed to getWeatherParameters in 13.0.

how to adapt existing source code

The change is straightforward. Users only have to replace calls to getWeatherParamerers by calls to getWeatherParameters.

Revamp of dates handling

overview of the change

In the 12.X series, dates were implemented using an epoch as a whole number of seconds from a reference date and a double offset corresponding to the fractional parts of the seconds. As the fractional part was between 0.0 and 1.0, its resolution was of the order of magnitude of a femtosecond near 1.0, and when dates resulted from successive computations (typically sequences of calls to shiftedBy), accuracies at picoseconds level could be regularly achieved. There were however rounding problems when dates were output in textual form depending on the number of decimal digits used. When using a number of digits ensuring safe write/read roundtrip operations (like using the Ryū algorithm), this induced writing many digits thar were not human-friendly. When on the other hand the number of digits was chosen to be either human-friendly (say milliseconds or microseconds) or compliant with some standard format, then safe write/read roundtrip operation was not possible anymore and errors crept in, typically in ephemeris data. Another type of problems occurred when standard java Instant, Date or TimeUnit were used as they refer to milliseconds, microseconds or nanoseconds which are decimal-based sub-multiples of the second and not binary-based sub-multiples of primitive double numbers. A similar problem linked to decimal versus binary representation occurred when TT (Terrestrial Time) scale was used. The offset between TT and TAI (International Atomic Time) is by convention exactly 32.184s, and neither 32.184 nor 0.184 can be represented exactly as IEEE754 primitive double numbers. The closest normal numbers that can be represented exactly are respectively $\frac{0x10178D4FDF3B64}{2^{47}}$ which is about 2.5 femtoseconds smaller than 32.184s, and $\frac{0x178D4FDF3B645A}{2^{55}}$, which is about 3.11 attoseconds smaller than 0.184s.

There were also several problems with the linear models between UTC and TAI that were used before year 1972. The offsets were whole numbers of microseconds and slopes were whole numbers of nanoseconds per seconds. Supporting properly the linear models before 1972 may seem moot but is in fact really required because many systems use Unix time, so it is widely used in interfaces or databases. The official Unix time as defined by POSIX explicitly ignores leap seconds, but many users ignore this specificity and would just use new ApsoluteDate(1970, 1, 1, utc), expecting this to be seamlessly interoperable with standard java Instant, Date or TimeUnit. It is not fully possible but at least roundtrip conversions between the two representations, with and without leap seconds, should remain safe. Unfortunately the 1970-01-01 epoch is located within a four years time range (from 1968 to 1972) during which the offset between UTC and TAI exhibited a 30 ns/s slope. This induced a 378691200 ns offset as of 1970-01-01 (to be added to the 4213170 µs offset that was active since 1968-01-01) and the slope continued to be applied for two years later. This complicates safe roundtrip conversions.

In order to alleviate these problems, dates handling has been thoroughly revamped. The whole number of seconds since reference epoch is still stored as a signed primitive long like it was before, so the range of dates that can be represented is still ±292 billions years for AbsoluteDate (but it is still ±5.88 millions years for DateComponents and DateTimeComponents as they use primitive int for the day offset with respect to J2000.0). The fractional part within the second is what was changed: it is now also stored as a non-negative primitive long with fixed precision at a resolution of one attosecond ($10^{-18}s$). The choice of attoseconds allows to represent exactly all important offsets (between TT and TAI, or between UTC and TAI during the linear eras), as well as all times converted from standard java Instant, Date or TimeUnit classes, and as it is a decimal-based sub-multiple, it is both human-friendly, standard formats friendly and often avoids the awkward display of series of 999999 decimals that resulted from binary to decimal conversions. This choice also allows simple computation as adding or subtracting two values in attoseconds that are both smaller than one second never overflows (a primitive long containing a number of attoseconds could hold any values between ±9.22s, so simple additions and subtractions of up to 9 such numbers followed by handling a carry to bring the result back between $0$ and $10^{18}$ and updating the associated seconds number is straightforward). The workhorse of the new implementation is a new TimeOffset class that contains a time offset split into seconds and attoseconds. This new class is therefore much more accurate than the previous one (attoseconds rather than femtoseconds) and more importantly is more robust, much simpler as it does not have to deal with IEEE-754 and finally it is decimal-friendly. Provisions have been made to properly handle NaN, and ±∞ (both in computation, parsing and writing).

Many methods that used primitive double to represent durations or offsets have been rewritten to take TimeOffset instances as arguments or to generate them as return values. This affects the API of classes AbsoluteDate, TimeComponents, DateTimeComponents, GNSSDate, OffsetModel UTCTAIOffset and their field counterparts for the classes that have one, as well as the TimeScale and TimeShiftable interfaces and all their implementations. In most cases, the methods taking a primitive double as an argument have been kept, and they delegate to a new method that creates a TimeOffset instance on the fly from the double. Methods that returned a primitive double have sometimes been kept (for example durationFrom in AbsoluteDate is still there) but a sister method has been created to take advantage of the new implementation with increased accuracy (so there are now accurateDurationFrom and accurateOffsetFrom methods in AbsoluteDate). Some methods that returned a primitive double have been changed and now return TimeOffset instances, this is in particular the case of all the methods in the TimeScale interface.

The field version of AbsoluteDate (FieldAbsoluteDate) has also been rewritten and is now entirely based on AbsoluteDate: each FieldAbsoluteDate embeds an AbsoluteDate that represent the same date but without the Field features. This means that the toAbsoluteDate methods is now a simple getter and returns an already present instance, it is much less costly than before. This improved the consistency between the non-field and the field versions, reduced duplications a lot and greatly simplified the code.

As these changes were made, it appeared the AggregatedPVCoordinatesProvider class threw OrekitIllegalArgumentException and IllegalStateException instead of OrekitException when an out-of-range date was used, which make them more difficult to catch. This has been changed too.

how to adapt existing source code

Despite the change is a revamp of the most widely used class in Orekit (AbsoluteDate), many efforts have been put to preserve the public API as much as possible. Many methods using primitive double or providing primitive double are still there. One big exception to this is the TimeScale interface, which now only uses TimeOffset instances. As most users just use the time scales as opaque objects when reading/writing dates, they should not be affected too much. In any case, they can still continue using primitive double by wrapping them in or out of TimeOffset instances, replacing calls like timeScale.offsetFromTAI(date) by timeScale.offsetFromTAI(date).toDouble(). On the other hand, if they need to call a method that needs a TimeOffset and they only have a primitive double, wrapping is done by replacing calls like object.someMethod(offset) by object.someMethod(new TimeOffset(offset)).

Custom implementations of TimeShiftable could be updated to take advantage of the new shiftedBy(TimeOffset) method, but it is not required as there is a default implementation that delegates to the original shiftedBy(double) method.

If some user code breaks due to API changes, though, it is recommended to avoid using the wrapping between primitive double and TimeOffset. What is recommended is to take the opportunity to remove entirely the primitive double and generalize use of TimeOffset everywhere. This will increase both accuracy of computation and robustness.

Avoiding primitive double also applies to parsing and to literal constants in models or non-regression test input data. When parsing a text like “3.2184e+01” from a String field variable, instead of using new TimeOffset(Double.parseDouble(field)) one should rather use TimeOffset.parse(field). The rationale is that TimeOffset.parse preserves decimals because it will parse the “3” and “2184” parts separately, apply the exponent and split that into an exact 32 seconds part and an exact 184 milliseconds part, whereas the double parsing would be slightly off (about 2.5 femtoseconds in this case) as IEEE754 cannot represent this number exactly. The small parsing error will show up when printing dates in some times scales. TimeOffset.parse(field) also supports parsing special offsets like NaN (regardless of case), -∞ and +∞ (for parsing infinity, the sign is mandatory). When using literal constants in source code (say for example 32.184 as before, which is the offset between TT and TAI) then rather than using new TimeOffset(32.184), users should use the linear combinations constructors as in new TimeOffset(32, TimeOffset.SECOND, 184, TimeOffset.MILLISECOND). There are such linear combinations constructors from 1 to 5 terms and the multiplicative factors can be long integers.

The static method TimeComponents.fromSecond intended to finely tune construction of dates within a leap second occurrence has been replaced by a public constructor taking a single TimeOffset argument instead of its first two arguments.

In the OffsetModel class, the units for slopes in UTC-TAI linear models used prior to 1972 were changed from seconds per day to nanoseconds per UTC second (despite neither is a SI unit), as looking at the values shows these slopes were in fact simple numbers (only three different slopes were used between 1961 and 1972: 15ns/s, 13ns/s and 30ns/s). The offset has also been changed from double to TimeOffset to allow representing exactly the microseconds offsets used in linear models before 1972. As the UTCTAIOffset and OffsetModel classes are mainly intended to implement UTC-TAI loaders and Orekit already provides loaders for the major formats, this should not affect many users. For those users who did implement custom loaders that take old slopes and double offsets into account, they should scale the slopes by changing their parsing code from double slope = Double.parseDouble(field) to int slope = (int) (TimeOffset.parse(field).getAttoSeconds() / SLOPE_FACTOR) were SLOPE_FACTOR is defined as long SLOPE_FACTOR = 86400L * 1000000000L; and then build the offset by using this integer slope in nanoseconds per UTC seconds. Using TimeOffset.parse instead of Double.parseDouble avoids numerical noise as parsing is done in decimal.

If users caught OrekitIllegalArgumentException and IllegalStateException when using AggregatedPVCoordinatesProvider, they must now catch OrekitException to recover from out-of-range dates.

As dates resolution is now always exactly one attosecond, when using shitftedBy to set up a date just before of after another date (for example to set up a transition in a TimeSpanMap), the recommended shift value is either TimeOffset.ATTOSECOND or TimeOffset.ATTOSECOND.negate(). Using Double.MIN_VALUE won't work (anyway, it only worked in previous versions when the date was exactly at a TAI second, i.e. when the offset was exactly 0.0).

Signal time of flight endpoints adjustment

overview of the change

In 12.X series, the signalTimeOfFlight method assumed the signal was received at a known fixed date, but was emitted at an earlier unknown date that is estimated. In some cases (typically in GNSS where signals are generated by atomic clocks), it is the emission date that is perfectly known and it is the later arrival time that should be estimated.

The signalTimeOfFlight method was therefore renamed signalTimeOfFlightAdjustableEmitter and new signalTimeOfFlightAdjustableReceiver methods with various signatures were added in 13.0.

how to adapt existing source code

The change is straightforward. Users only have to replace calls to signalTimeOfFlight by calls to signalTimeOfFlightAdjustableEmitter.

Revamped Sinex and Sinex bias parsing

overview of the change

In 12.X series, Sinex and Sinex-bias files parsing was not in line with other formats parsers implementations. It still refers to DataLoader instead of the new DataSource paradigm, hence relying on a regular expression for supported names and Sinex files being contained in a DataContext. The SinexLoader class was a huge and complicated class and was a nightmare to maintain. It was used both as the parser and as the container for parsed results. It mixed Sinex and Sinex bias, which despite having a common base are different formats. It did not allow proper loading of Observable-Specific Signal Biases, it only handled Differential Signal Biases and probably mixed things up when parsing OSB or IFB files. It also used the older naming convention of Differential Code Bias despite newer biases can refer to phase and not to code only, so they are now named Differential Signal Bias. During parsing, it merged the description within the data despite they are separated in the files. It only used string fields even for parts that had proper associated classes, like satellite in system or observable types. The loaded biases were claimed to be in SI units, but in fact phase biases could not really be parsed because the cycles unit was not aavailable and the code biases despite being in one SI units (converted from nanoseconds to seconds), this was not really consistent with a pseudo-range measurement in meters.

In order to alleviate all these shortcomings, parsing was rewritten from the ground up and split into several specialized block and line parsers as well as several containers. Support for Observable-Specific Signal Biases was added (but support for Ionosphere-Free Signal Bias, IFB, is still lacking, it could however be added easily if required, thanks to the new modular architecture). A new unit, Unit.CYCLE with name “cyc” was added, it is a dimensionless unit (just like the already existing Unit.NONE, Unit.ONE, and Unit.PERCENT).

how to adapt existing source code

Users that relied on SinexLoader class configured from a regular expression for supported names and requiring the Sinex files to be available in a data context should now use either the SinexParser or the SinexBiasParser class and pass to their parse methods the DataSource instances they want to parse. This is simpler and more consistent with other existing parsers in Orekit (CCSDS, Rinex, SP3, CRD, CPF, GPT…). The parse methods return a container holding the parsed data, Sinex in the SinexParser case and SinexBias in the SinexBiasParser case.

When using SinexParser to load Earth Orientation Parameters, users should be aware that since Sinex files contain only one EOP entry block, they should call the parse method with several DataSource instances to have a reasonable time range covered. This was already the case in the previous implementation and was generally worked around using a regular expression that matched several Sinex files. The resulting Sinex container does not implementEopHistoryLoader by itself, instead it has a getEopLoader that returns such an object. The ITRFVersion that should be used to build the EOP history is now specified when calling getEopLoader after parsing and not before parsing as was needed before.

The getters for accessing parsed data have been streamlined. The different levels of maps in SinexBias have been renamed according to the new naming conventions to use Dsc instead of Dcb. The satellites keys have been changed from String to SatInSystem with a specific Pseudo-Random-Number SatInSystem.ANY_PRN (which is set to -1) used to represent system-wide biases. The observations keys have been changed from String to ObservationType. The SinexParser takes a typeBuilder argument allowing users to parse custom ObservationType if needed. This typeBuilder can safely be set to PredefinedObservationType::valueOf when parsing Sinex-bias files containing only predefined observation types.

The code biases are now in meters SI unit, the Constants.SPEED_OF_LIGHT conversion factor is already applied by the parser itself. Users should not apply this factor anymore at their level.

Improved performance of indirect shooting

overview of the change

The bottleneck of indirect shooting was the call to propagate on FieldNumericalPropagator, plagued by event detection as well as adaptive step-size when used. To improve performance, the integration is now done step by step (using the non-Field history), by direct calls to the Hipparchus integrator (requiring to build ODE equations). Also for this reason, the ShootingIntegrationSettings has been revamped, now imposing (Field)ExplicitRungeKuttaIntegratorBuilder.

how to adapt existing source code

The buildFieldPropagator method in AbstractIndirectShooting does not exist anymore, but inheritors must now implement buildFieldODE. The classes ClassicalRungeKuttaIntegrationSettings and DormandPrince54IntegrationSettings have been removed. Instead, you can use the ShootingIntegrationSettingsFactory, which offer more built-in choices.

Use SatInSystem in SatelliteAntenna

overview of the change

In the 12.X series, SatelliteAntenna used separate fields and accessors for satellite system and satellite Pseudo-Random-Number, despite a SatInSystem container already existed for this. This was both awkward and dupplicated PRN offsets logic when parsing satellites antenna from Antex files. PRN numbers in several IGS files (Rinex, Sinex, Antex…) are shifted by a 100 units offset for SBAS system and shifted by 192 units for QZSS systems.

The change was to generalize use of SatInSystem and moving the parsing logic from the various format-specific parsers to SatInSystem.

how to adapt existing source code

The SatelliteAntenna constructor now takes a SatInSystem argument instead of separate system and prn. Users that do not build the instances themselves but simply retrieve them using AntexLoader will not be affected by this change, other users should change the call to the constructor and build a SatInSystem instance beforehand.

The getSatelliteSystem and getPrnNumber have been replaced by a single getSatInSystem method, which in turn gives access to the system and PRN.

Revamped impulsive maneuvers

overview of the change

The classes ImpulseManeuver and (Field)ImpulseManeuver do not inherit anymore from AbstractDetector and FieldAbstractDetector anymore. The rational is that the create method was unsafe, allowing users to replace the handler, which should not happen for the logic to work. Nonetheless, a withDetectionSettings method has been preserved. On another note, the maneuver is now applied no matter the Action returned by the event trigger's handler, whilst before it was only if it was STOP, which was somewhat arbitrary and was preventing from using RecordAndContinue.

how to adapt existing source code

Replace any calls to create by withDetectionSettings when applicable. In case your trigger had a StopOnIncreasing or StopOnDecreasing handler, use an EventPredicateFilter instead.

Removed deprecated signature of create method in (Field)AbstractDetector

overview of the change

The create method of AbstractDetector and FieldAbstractDetector now has only one signature, involving EventDetectionSettings and FieldEventDetectionSettings. This way, future modifications to the detection system will not impact the signature anymore, as it should be buffered by the data container. The old, longer signature which was already deprecated has been removed.

how to adapt existing source code

Implement create with the only possible arguments. Note that if you do not use the with method, you probably do not need to use (Field)AbstractDetector and (Field)EventDetector may well be enough for your need.

Simplify use of AttitudesSequence

overview of the change

The interface AttitudeProvider now implements EventDetectorsProvider, with default methods returning empty Stream. The rational is that internal events such as discontinuities in attitude are thus fed to propagators internally. The class AttitudesSequence leverages this change, so that registerSwitchEvents does not exist anymore.

how to adapt existing source code

Simply delete the calls to registerSwitchEvents. The propagator will automatically use the switches if you used setAttitudeProvider.

Deprecate ExtendedPVCoordinatesProvider

overview of the change

The class ExtendedPVCoordinatesProvider has been deprecated and ExtendedPositionProvider should be used instead. The rationale is that the reference to PVCoordinates is confusing as implementations did not necessarily provide with accelerations. Moreover, ExtendedPositionProvider only requires a Field implementation of getPosition, the other methods are deduced from it using automatic differentiation. This way, all velocity vectors are the first time derivative of the position vectors by construction, something that was not guaranteed before.

how to adapt existing source code

Replace any calls to ExtendedPVCoordinatesProvider and ExtendedPVCoordinatesProviderAdapter by ExtendedPositionProvider and ExtendedPositionProviderAdapter respectively. A method toExtendedPVCoordinatesProvider has been added to ExtendedPositionProvider to facilitate phasing out.

Rates in (Field)Orbit

overview of the change

In the 12.X series, Orbit and FieldOrbit would respectively have Nan and null rates for orbital elements if none was passed explicitly. Now there are default values, which are the Keplerian ones, meaning that most of them are zero (only the position angle varies). In consequence, the method hasDerivatives in orbits has been renamed, as well as removeRates in the interface PositionAngleBased.

how to adapt existing source code

Replace your calls to hasDerivatives by hasNonKeplerianAcceleration, removeRates by withKeplerianRates and hasRates by hasNonKeplerianRates. Moreover, do not expect NaN or null rates anymore.

Added getEffectName to EstimationModifier

overview of the change

A getEffectName has been added in 13.0 to the EstimationModifier interface, so it is easier to analyse the various contributors to an estimated measurement. All modifiers provided by Orekit implement this new method and return a small effect name (troposphere, ionosphere, wind-up…).

how to adapt existing source code

If users implemented their own modifiers, they must implement this method, typically by just returning a literal constant hard-coded in the implementation class or one of its super-classes.

Moved AdaptableInterval

overview of the change

The interfaces AdaptableInterval and FieldAdaptableInterval have been moved down to the subpackage intervals in events, where their native inheritors already were. This makes for a cleaner architecture.

how to adapt existing source code

Modify the import by adding intervals.

Renamed EarthITU453AtmosphereRefraction into ITURP834AtmosphericRefraction

overview of the change

The EarthITU453AtmosphereRefraction used up to 12.0 was misleading as the model implemented was really ITU-R P.834. The class has therefore with renamed with the correct ITU recommendation number: ITURP834AtmosphericRefraction; new ITURP834PathDelay, ITURP834MappingFunction, and ITURP834WeatherParametersProvider models have been added too, with consistent naming.

how to adapt existing source code

Users should just change the class name they use.

Renamed DEFAULT_MAXCHECK into DEFAULT_MAX_CHECK

overview of the change

Different names were in use for the so-called maximum check parameter for event detection. DEFAULT_MAX_CHECK has been set as the unique one, in line with DEFAULT_MAX_ITER.

how to adapt existing source code

Simply replace any calls to DEFAULT_MAXCHECK with DEFAULT_MAX_CHECK.

Updated indirect cost functions

overview of the change

The Field methods in the interface CartesianCost have been extracted to a FieldCartesianCost. The rationale is to give more flexibility for the latter. As a consequence CartesianAdjointDynamicsProvider is now an abstract class. Usual inheritors are available in CartesianAdjointDynamicsProviderFactory.

how to adapt existing source code

For Field methods of cost functions, use the new classes e.g. FieldUnboundedEnergy. Use CartesianAdjointDynamicsProviderFactory if applicable, otherwise get inspiration from it to create your own implementation of CartesianAdjointDynamicsProvider.

Extract switch handler from AttitudesSequence

overview of the change

The interface SwitchHandler has been extracted from AttitudesSequence and renamed AttitudeSwitchHandler for clarity. It is also used by the new class AttitudesSwitcher.

how to adapt existing source code

Simply replace any calls to AttitudesSequences.SwitchHandler with AttitudeSwitchHandler.

Changed attitude override in Maneuver

overview of the change

The (possible null) attribute attitudeOverride in Maneuver is now an instance of a new interface AttitudeRotationModel, aboveAttitudeProvider. It allows users to define ParameterDriver in order to include their derivatives in the State Transition Matrix computation with (Field)NumericalPropagator.

how to adapt existing source code

You need to cast the output of getAttitudeOverride as AttitudeProvider if you intend to use its specific methods after passing it to the constructor of Maneuver.

Introduced generic in integrator builder

overview of the change

Java generics have been introduced in abstract classes inheriting from (Field)ODEIntegratorBuilder. It removes the need for casting on outputs. As part of this change, the two interfaces now only return interfaces, not abstract classes.

how to adapt existing source code

Declare outputs of ODEIntegratorBuilder as ODEIntegrator instead of AbstractIntegrator. Similarly, declare outputs of FieldODEIntegratorBuilder as FieldODEIntegrator instead of AbstractFieldIntegrator.

Introduced impulse provider for maneuvers

overview of the change

The full constructor of (Field)ImpulseManeuver has changed. Instead of providing a given (Field)Vector3D, users have the possibility to build a full logic via the (Field)ImpulseProvider interface. This new API also allows using a single detector for maneuvers of different magnitude. Most of the constructors signatures have been preserved, but the getDeltaVSat does not exist anymore.

how to adapt existing source code

One cannot call getDeltaVSat anymore. If you need to know what velocity was applied, you can use a RecordAndContinue event handler for instance and call the (Field)ImpulseProvider on the collected states.

Generalized ProfileThrustPropulsionModel

overview of the change

The class ProfileThrustPropulsionModel was using polynomial segments for no particular reasons, so they have been generalized as ThrustVectorProvider. The mass is now an argument as well to broaden applications.

how to adapt existing source code

The original constructor of ProfileThrustPropulsionModel has been replaced by an static of method, since now a TimeSpanMap of ThrustVectorProvider is expected as argument.

Introduce resettable maneuver trigger

overview of the change

The interface ManeuverTriggers included resetters which are not used in Maneuver, so a new dedicated, intermediate interface has been introduced, called ResettableManeuverTriggers.

how to adapt existing source code

If you do use resetters, replace ManeuverTriggers by ResettableManeuverTriggers. If you were not implementing these methods with actual logic, remove them.