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}AdaptableInterval's method issue 1382
improved robustness of spherical regions computation issue 1388
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, issue 1652
signal time of flight endpoints adjustment issue 1468
improved performance of indirect shooting issue 1523
revamped Sinex and Sinex-bias parsing issue 1538, issue 1649
removed deprecated signature of create method 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
implemented createHarvester in GNSS propagator issue 1572
added getEffectName to EstimationModifier issue 1542
moved adaptable interval issue 1580
renamed EarthITU453AtmosphereRefraction into ITURP834AtmosphericRefraction issue 1590
renamed DEFAULT_MAXCHECK into DEFAULT_MAX_CHECK issue 1595
updated indirect cost functions issue 1597
extract switch handler from AttitudesSequence issue 1607
changed attitude override in Maneuver issue 1609
introduced generic in integrator builder issue 1613
introduced impulse provider for maneuvers issue 1619
added ITU NeQuick2 ionospheric model issue 1625
generalized ProfileThrustPropulsionModel issue 1631
introduce resettable maneuver trigger issue 1633
removed intelligent serialization issue 1627
removed chained getters in {Field}SpacecraftState issue 1650
renamed addGPSLegacyNavigationMessage into addGPSCivilianNavigationMessage issue 1661
changed methods visibility in AbstractPropagators issue 1430
replaced AdditionalStateProvider by AdditionalDataProvider issue 1645
replaced FieldAdditionalStateProvider by FieldAdditionalDataProvider issue 1645
added builder in (Field)SpacecraftState issue 1669
generalized event detectors in AbstractManeuverTriggers's inheritors issue 1677
removed withHandler in EventSlopeFilter, EventShifter and EventEnablingPredicateFilter issue 1678
renamed IRNSS into NavIC issue 1688
generalized use of PressureTemperatureHumidityProvider issue 1693
removed dependency to default data context in RtcmMessageType issue 1700

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 PressureTemperatureHumidityProvider has been introduced, allowing tropospheric models to be applied at any time and any location. This provider generates PressureTemperatureHumidity instances on the fly as needed. The API of GroundStation, TroposphericModel and TroposphereMappingFunction did however not embrace this change thoroughly. Some parts still referred to PressureTemperatureHumidity, some parts mixed use of both PressureTemperatureHumidityProvider and PressureTemperatureHumidity, some parts took an argument of one type and ignored it, using an internal provider. This was confusing and not user-friendly.

The older tropospheric model interface, and the former models implementations 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.

Many tropospheric models constructors now take a PressureTemperatureHumidityProvider instance as an argument. As the provider is internally stored, another change is that there is no need anymore to pass a PressureTemperatureHumidity argument to the pathDelay method of TroposphericModel implementations or to the mappingFactors method of TroposphereMappingFunction implementations, these arguments are simply removed.

The GlobalPressureTemperature2 and GlobalPressureTemperature2w constructors now take an additional TimeScales parameter that allows the class to use custom DataContext and be independent of the default data context.

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.

Improved robustness of spherical regions computation

overview of the change

Computation of spherical regions is based on Binary Space Partitioning trees (BSPTree), which are provided by the underlying Hipparchus library. In some cases, when operating on regions that have almost aligned features (like a sensor swath projected on ground, with sampled boundary points along a very regulat orbit), some numerical errors occur. A lot of efforts have been put in improving robustness. As part of these efforts, the API in the partitioning package from Hipparchus has been changed. Many classes like BSPTree, BSPTreeVisitor, Hyperplane, SubHyperplane, Region, Regionfactory… that were previously parameterized with the space type (Euclidean3D, Euclidean2D, Euclidean1D, Sphere2D, Sphere1D) are now parameterized with both the space type, the associated point type (Vector3D, Vector2D, Vector1D, S2Point, S1Point), the associated hyperplane type (Plane, Line, OrientedPoint, Circle, LimitAngle), and the associated subèhyperplane type (SubPlane, SubLine, SubOrientedPoint, SubCircle, SubLimitAngle). The point types themselves also include their own type as a second parameter in addition to the space type. This improves robustness as it allows stronger typing.

This Hipparchus API change does not affect the Orekit public API at all (the changes are implementation details at Orekit level), but they may affect users who build SphericalPolygonsSet objects using Region and RegionFactory, which are Hipparchus classes.

how to adapt existing source code

The required changes at users level are limited to variables declarations. They should replace Region<Sphere2D> by Region<Sphere2D, S2Point, Circle, SubCircle> and RegionFactory<Sphere2D> by RegionFactory<Sphere2D, S2Point, Circle, SubCircle>.

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).

A new createMedian factory method has also been added to {Field}AbsoluteDate

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.

The Sinex parse also did not load complete phase center positions, it parsed SITE/ECCENTRICITY but this represents only a global Antenna Reference Point (ARP). The real phase centers are offset with respect to the ARP and are frequency dependent. For ground receivers, the phase centers are provided in the SITE/GPS_PHASE_CENTER and SITE/GAL_PHASE_CENTER blocks. For satellites, there is a SATELLITE/PHASE_CENTER block. All these blocks were ignored.

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.

The getAntennaType getter in the Station objects provided by Sinex.getStations has been replaced by a getAntennaKey method that returns a container with both the antenna type and the serial number. A new getPhaseCenters method that returns a map from GNSS signals to phase centers has also been added. This method uses the antenna keys and handles cases where for example the SITE/GPS_PHASE_CENTER block in the Sinex file uses a catch-all “—–” entry that matches all serial numbers whereas the SITE/ANTENNA block uses specific serial numbers for the antennas.

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 {Field}ImpulseManeuver

overview of the change

The classes ImpulseManeuver and FieldImpulseManeuver 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.

Implemented createHarvester in GNSS propagator

overview of the change

The GNSS propagators are specialized analytical propagators that fit the orbit of navigation satellites for short duration (up to a few hours, far less than one orbit). They correspond to the models that are broadcast in navigation signals so end users can compute their own position, knowing the emitting satellites position. In the 12.X series, these propagators could only be built by loading external data (Rinex navigation files, RTCM streams…) but could not be fit by Orekit itself using orbit determination or fitting of precise ephemerides.

The change was to replace the fixed parameters of the analytical models (initial orbital parameters, drifts and harmonic correction coefficients) by ParameterDriver, introduce a trio of new classes GnssHarvester, GnssGradientConverter and FieldGnssPropagator and link them to the GNSSPropagator class, so it now returns an instance of GnssHarvester when its setupMatricesComputation method is called instead of throwing an UnsupportedOperationException as it did previously.

In order to make this change, the GNSSOrbitalElements interface that is at the root of a deep classes hierarchy containing all GNSS almanacs and navigation messages has been changed from an interface to a class that holds the various ParameterDriver instances. This implies that directly at this root level, the parameters can be changed (by calling ParameterDriver.setValue()). Some cleanup have been made so the setDate method that was available deeper in the hierarchy has been removed to ensure the date remains always consistent with the getTimeDriver() parameter when it is updated, either by calling setValue at parameter driver level or by calling setTime directly. The constructors then require additional parameters (TimeScales and SatelliteSystem) to properly recompute the date on the fly. The methods related to mean motion have been renamed for consistency with GPS interface specification, adding a suffix 0 to {set|get}MeanMotion and {set|get}DeltaN, moving the rates that should be added to these 0 values up in the class hierarchy, and letting the callers add the rates. The rates additions are always taken into account in the specialized propagator, they are non-zero only in navigation messages for some rates in civilian navigation mesages for other rates.

how to adapt existing source code

The main change for user code is that now it is possible to call setupMatricesComputation for GNSS propagators (this was the goal of the change).

Side effects are that users building directly classes from the hierarchy rooted at GNSSOrbitalElements now need to provide TimeScales and SatelliteSystem arguments to base class constructors for the dates handling, and they need to remove existing calls to setDate (which are obsolete as date is always kept in sync with the GNSS time parameter). If they developed their own classes, these classes should now extend GNSSOrbitalElements instead of implementing it as it is now a class.

If users called {set|get}MeanMotion or {set|get}DeltaN methods, they should add the 0 suffix and call {set|get}MeanMotion0 or {set|get}DeltaN0. Users must be aware that these getters are now restricted to the fixed value and that the rates (getADot, getDeltaN0, getDeltaN0Dot) must be added explicitly.

These effects should not affect many users as classes in the GNSSOrbitalElements hierarchy are mainly built internally (for example by the Rinex parsers) and users generally only get the already built instances to pass them for example to GNSS propagators. One should be aware that in some strange cases, the SatelliteSystem to use at construction is not always the one corresponding to the real constellation. This happens for example internally when we parse Rinex navigation files, as in these files the week number for Galileo, IRNSS and QZSS must be aligned with GPS week and not the original system week (but for an unknown reason, Rinex files specification requires to not change alignment for Beidou satellites and to keep the Beidou constellation reference).

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, for instance with oldState:

Vector3D deltaVSat = im.getImpulseProvider().getImpulse(oldState, im.forward);

Added ITU NeQuick2 ionospheric model

overview of the change

The NeQuick model from Aeronomy and Radiopropagation Laboratory of the Abdus Salam International Centre for Theoretical Physics Trieste, Italy has been available in Orekit since version 10.1 but only as the Galileo-specific version. This version differs from the original model recommended by ITU in a number of ways. It uses a different modified dip latitude grid, it uses a different way to integrate electron density, it has different clipping constraints at low altitudes… The ITU recommended implementation has therefore been added as a new NeQuickITU class alongside the Galileo-pecific one which has been renamed from NeQuickModel to NeQuickGalileo, and moved in a dedicated nequick package. The low-level electronDensity method does not take a modip parameter anymore, this parameter is computed internally using different interpolation tables for NeQuickITU and NeQuickGalileo.

how to adapt existing source code

Users that relied on the Galileo-specific model and that still want to use it have to use the new name of the class, NeQuickGalileo and import it from the org.orekit.models.earth.ionosphere.nequick package. If they called the low level electronDensity method, they should remove the modip parameter.

Users that want to use the ITU-recommended version should use NeQuickITU from the org.orekit.models.earth.ionosphere.nequick package.

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.

Removed intelligent serialization

overview of the change

All objects that implemented Serializable and that needed a dedicated so-called data transfer object to do so have had that code removed, as well as their serializationUID. The motivations were numerous. First of all, often that process was based on a default DataContext and the Field equivalent classes were usually not Serializable. Moreover, it made code maintenance more tedious, especially since Orekit is still in Java 1.8 and does not have the Record type which is very convenient for serialization.

how to adapt existing source code

Stop using native serialization of Orbit, PVCoordinates, etc. If needed by users, they are encouraged to implement their own mechanism.

Removed chained getters in SpacecraftState

overview of the change

{Field}SpacecraftState wraps getters from the - possibly present - {Field}Orbit attribute. This is not safe as it can be NaN for Orbit and null for FieldOrbit. These methods e.g. getA have been removed.

how to adapt existing source code

Use getOrbit before getA, getOrbit before getMu, getOrbit before getKeplerianPeriod, etc. One can check if they are present with isOrbitDefined.

Renamed addGPSLegacyNavigationMessage into addGPSCivilianNavigationMessage

overview of the change

There was a copy-paste error in the RinexNavigation class; the addGPSLegacyNavigationMessage appeared twice, once taking a GPSCivilianNavigationMessage argument and once taking a GPSLegacyNavigationMessage. One of these methods should have been named addGPSCivilianNavigationMessage. This was fixed in 13.0

how to adapt existing source code

If users called addGPSLegacyNavigationMessage with a GPSCivilianNavigationMessage argument, they should change the method name to addGPSCivilianNavigationMessage.

Changed methods visibility in AbstractPropagators

overview of the change

Visibility of methods (Field)AbstractPropagator#updateAdditionalStates and (Field)AbstractAnalyticalPropagator#basicPropagate and propagateOrbit were changed from protected to public.

how to adapt existing source code

Simply change visibility of corresponding methods in classes subclassing (Field)AbstractPropagator or (Field)AbstractAnalyticalPropagator.

Replaced AdditionalStateProvider by AdditionalDataProvider

overview of the change

A new interface AdditionalDataProvider has been introduced to generalize the mechanism of AdditionalStateProvider to any object.

how to adapt existing source code (AdditionalStateProvider)

Class implementing AdditionalStateProvider have to replace

public class MyAdditional implements AdditionalStateProvider {
    
    // Some code
    
    public double[] getAdditionalState(SpacecraftState state) {
        // Compute additional state
    }
    
    // Some code
}

by

public class MyAdditional implements AdditionalDataProvider<double[]> {

    // Some code

    public double[] getAdditionalData(SpacecraftState state) {
        // Compute additional state
    }

    // Some code
}

how to adapt existing source code (Propagator)

In Propagator, the following methods

void addAdditionalStateProvider(AdditionalStateProvider additionalStateProvider);
List<AdditionalStateProvider> getAdditionalStateProviders();
boolean isAdditionalStateManaged(String name);
String[] getManagedAdditionalStates();

are replaced by

void addAdditionalDataProvider(AdditionalDataProvider additionalStateProvider);
List<AdditionalDataProvider<?>> getAdditionalDataProviders();
boolean isAdditionalDataManaged(String name);
String[] getManagedAdditionalData();

how to adapt existing source code (AbstractPropagator)

In AbstractPropagator, the following method

SpacecraftState updateAdditionalStates(SpacecraftState original);

are replaced by

SpacecraftState updateAdditionalData(SpacecraftState original);

how to adapt existing source code (SpacecraftState)

In SpacecraftState, the following methods

SpacecraftState addAdditionalState(final String name, final double... value);
boolean hasAdditionalState(final String name);
public DoubleArrayDictionary getAdditionalStatesValues();

are replaced by

SpacecraftState addAdditionalData(final String name, final Object value);
boolean hasAdditionalData(final String name);
public DataDictionary getAdditionalDataValues();

The double[] getAdditionalState(final String name) method has been preserved. It uses the generalized mechanism but safe cast are performed to provide a double[]. The new method Object getAdditionalData(final String name) is also available.

Replaced FieldAdditionalStateProvider by FieldAdditionalDataProvider

overview of the change

A new interface FieldAdditionalDataProvider has been introduced to ensure consistency with AdditionalDataProvider. It replaces the FieldAdditionalStateProvider interface.

how to adapt existing source code (FieldAdditionalStateProvider)

Class implementing FieldAdditionalStateProvider have to replace

public class MyFieldAdditional<T extends CalculusFieldElement<T>> implements FieldAdditionalStateProvider<T> {

    // Some code

    public T[] getAdditionalState(FieldSpacecraftState<T> state) {
        // Compute additional state
    }

    // Some code
}

by

public class MyFieldAdditional<T extends CalculusFieldElement<T>> implements FieldAdditionalDataProvider<T> {

    // Some code

    public T[] getAdditionalData(FieldSpacecraftState<T> state) {
        // Compute additional state
    }

    // Some code
}

how to adapt existing source code (FieldPropagator)

In FieldPropagator, the following methods

void addAdditionalStateProvider(FieldAdditionalStateProvider<T> additionalStateProvider);
List<FieldAdditionalStateProvider<T>> getAdditionalStateProviders();
boolean isAdditionalStateManaged(String name);
String[] getManagedAdditionalStates();

are replaced by

void addAdditionalStateProvider(FieldAdditionalDataProvider<T> additionalDataProvider);
List<FieldAdditionalDataProvider<T>> getAdditionalDataProviders();
boolean isAdditionalDataManaged(String name);
String[] getManagedAdditionalData();

how to adapt existing source code (FieldAbstractPropagator)

In FieldAbstractPropagator, the following method

FieldSpacecraftState<T> updateAdditionalStates(FieldSpacecraftState<T> original);

are replaced by

FieldSpacecraftState<T> updateAdditionalData(FieldSpacecraftState<T> original);

how to adapt existing source code (FieldSpacecraftState)

In FieldSpacecraftState, the following methods

FieldSpacecraftState<T> addAdditionalState(final String name, final T... value);
boolean hasAdditionalState(final String name);
public FieldArrayDictionary getAdditionalStatesValues();

are replaced by

FieldSpacecraftState<T> addAdditionalData(final String name, final T... value);
boolean hasAdditionalData(final String name);
public FieldArrayDictionary getAdditionalDataValues();

Added builder in (Field)SpacecraftState

overview of the change

(Field)SpacecraftState now has withXXX methods to create new instances. Some constructors have been removed and some only deprecated for convenience.

how to adapt existing source code

Use withMass, withAttitude, withAdditionalData and withAdditionalStatesDerivatives instead of constructors with partial attribute selection.

Generalized event detectors in AbstractManeuverTriggers's inheritors

overview of the change

StartStopFiringEventsTrigger and IntervalEventTrigger no longer required (Field)AbstractDetector, only (Field)EventDetector. This is more generic and the only counterpart for custom implementations is to define the FieldEventDetectionSettings properly in the Field conversion methods (convertAndSetUpStartHandler, convertAndSetUpStopHandler and convertAndSetUpHandler), if used.

how to adapt existing source code

You may need to change the declaration type of the trigger detectors if you retrieved them (from (Field)AbstractDetector to (Field)EventDetector). If you had a custom Field implementation, update the detector conversion routines. Most built-in FieldEventDetector in Orekit have a withDetectionSettings method so changes should be minimal.

Removed withHandler in EventSlopeFilter, EventShifter and EventEnablingPredicateFilter

overview of the change

EventSlopeFilter, EventShifter and EventEnablingPredicateFilter all have a privately-defined, specific EventHandler that should not be discarded for them to properly work. Therefore, withHandler has been removed, by removing the inheritance from AbstractDetector altogether because of the create method.

how to adapt existing source code

If you need to use a specific EventHandler, you can pass it to the underlying detector, as the specific handler in the filter/shifter will call it. Note also that any detector can be wrapped via the interface DetectorModifier, where one can apply custom overloads. On the other hand, a consequence of not inheriting from AbstractDetector is that there is no longer any withThreshold, withMaxIter, withMaxCheck methods. However, EventSlopeFilter, EventShifter and EventEnablingPredicateFilter all still have withDetectionSettings whilst (Field)EventDetectionSettings now has its own builder mechanism. The latter can also be passed directly in the detector's constructor.

Renamed IRNSS into NavIC

overview of the change

The system formerly known as IRNSS (Indian Regional Navigation Satellite System) has been officially renamed NavIC (Navigation with Indian Constellation). This denomination has been adopted in latest Rinex standards. Numerous enumerates, constants and methods referred to this name. Most of them have been renamed; for example the getIrnssEpoch() in TimesScales has been renamed into getNavicEpoch(), the IRNSS_MU constant in GNSSConstants has been renamed into NAVIC_MU and the IRNSSNavigationMessage class has been renamed into NavICLegacyNavigationMessage. The literal constants that are parsed from files still using the former name have been kept as is, as these names are mandatory. As an example, in Antex files, the known satellite names are still parsed from the IRNSS-1IGSO string literal despite the corresponding entry in the SatelliteType enumerate has been changed to NAVIC_1IGSO. Another example occurs in Rinex files, where the identifier for NavIC time is still IRN.

There have been no structural changes, only renaming has been performed.

how to adapt existing source code

Users calling methods that follow camelcase convention should replace IRNSS by NavIC in method names. Users referring constants or enumerates that follow uppercase convention should replace IRNSS by NAVIC.

Generalized use of PressureTemperatureHumidityProvider

overview of the change

In 12.X series, a PressureTemperatureHumidityProvider has been introduced, allowing tropospheric models to be applied at any time and any location. This provider generates PressureTemperatureHumidity instances on the fly as needed. The older API of GroundStation, TroposphericModel and TroposphereMappingFunction did not embrace this change thoroughly. Some parts still referred to PressureTemperatureHumidity, some parts mixed use of both PressureTemperatureHumidityProvider and PressureTemperatureHumidity, some parts took an argument of one type and ignored it, using an internal provider. This was confusing and not user-friendly.

The new design uses PressureTemperatureHumidityProvider thoroughly and the provider is now only under the responsibility of the tropospheric models. The GroundStation class does not reference it anymore.

how to adapt existing source code

The main changes for users are the constructors of tropospheric models. Many of them now take a PressureTemperatureHumidityProvider instance as an argument.

As the provider is internally stored, another change is that there is no need anymore to pass a PressureTemperatureHumidity argument to the pathDelay method of TroposphericModel implementations or to the mappingFactors method of TroposphereMappingFunction implementations, these arguments are simply removed.

Removed dependency to default data context in RtcmMessageType

overview of the change

In 12.X series, the RtcmMessageType enumerate used the default data context to build GNSS dates. This prevented users to have full control of their data context, which is a problem on stream-based always-on services.

This dependency was removed by simply passing a reference to a user-provided TimeScales factory, which could be the default one or could be a custom one. This new parameter is passed by the parser, so the constructors of RtcmMessagesParser, IgsSsrMessagesParser, MessagesParser and NtripClient were changed to add this TimeScales factory. The getParser method in the ntrip Type enumerate also takes this TimeScales factory as an argument because it build the parser corresponding to the type.

how to adapt existing source code

The main changes for users are to add the TimesScales factory to either the constructors if they call them directly or to the getParser method in the ntrip Type enumerate. The factory can be the one returned by DataContext.getDefault().getTimeScales() if the default data context is suitable for users needs.