Upgrading from Orekit 13.X to Orekit 14.0
Version 14.0 of Orekit introduced some incompatible API changes with respect to versions 13.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.
Custom GNSS Time Systems
overview of the change
In the 13.X series, TimeSystem was an enumerate, and therefore included only
a limited set of values, preventing it to be used for custom GNSS systems that
are not yet officially recognized by IGS. TimeSystem has therefore been
changed to an interface to allow for custom implementations, and a new
PredefinedTimeSystem enumerate has been set up containing the official
implementations.
how to adapt existing source code
If users need to initialize a value from the predefined lists (say GALILEO time
system), then they should use PredefinedTimeSystem.GALILEO enumerate constant.
If they just need to pass around a time system that has already been created (say
parsed from some file or created from scratch by a simulator), then they should
rather use the TimeSystem type, as it is more general and can be used for both
predefined and custom time systems.
Revamping of Rinex Files
overview of the change
In the 13.X series, RinexClock was a flat structure that was
inconsistent with RinexObservation and RinexNavigation and had no
associated writer. The leap seconds setting in all Rinex files was
also inconsistent (in fact, it is inconsistent in the standard
themselves as LEAP SECONDS in clock file does not have the same
meaning in observation and navigation files…).
Support for version 4.02 of Rinex observation and navigation files was added in 13.X series, but only partially. A few messages could not be parsed (NavIC ionosphere messages type L1NV, subtypes KLO and NEQN, GLONASS ionosphere messages LXOC). There was no support for writing Rinex navigation files.
Inconsistencies in clock files and support for missing navigation messages
have been resolved by revamping the classes hierarchy, RinexClockWriter
RinexNavigationWriter and many support container classes have been created.
Note that the GLONASS L1OC and L3OC CDMA navigation messages are still not
supported (they are silently ignored when parsing navigation files).
The change mainly implies several getters that were directly in RinexClock
or in some navigation messages have been moved around. A new RinexClockHeader
has been added, and the clock comments are now regular instances of RinexComment
that can be accessed individually instead of being a single concatenated string.
The getters and setters for leap seconds now explicitly state whereas they
correspond to the separation between UTC and TAI or to the separation between
UTC and GNSS (really GPS). A new IonosphereAij intermediate container has
been added to IonosphereNequickGMessage (and reused in the new
IonosphereNavICNeQuickNMessage). In anticipation of future support for GLONASS
L1OC and L3OC CDMA navigation messages, the GLONASSNavigationMessage has been
renamed GLONASSFdmaNavigationMessage.
The change also implied that the navigation messages in the
org.propagation.analytical.gnss.data packages now require a message type at
construction time (LNAV, CNAV, C2NV…).
how to adapt existing source code
Users should access the data from the header by first retrieving the
header using rinexClock.getHeader(), and then access the relevant
data (using for example rinexClockHeader.getReceivers()). The value
returned by rinexClock.getComments() is now a list of
RinexComment, each instance containing the line number and the
text. The {get|set}LeapSeconds and {get|set}NumberOfLeapSeconds
methods have been replaced by {get|set}LeapSecondsTAI and
{get|set}LeapSecondsGNSS. Access to the aᵢⱼ coefficients from
IonosphereNequickGMessage now uses an intermediate getAij method
to retrieve the intermediate container. References to the
GLONASSNavigationMessage name should be changed to the new name
GLONASSFdmaNavigationMessage.
If navigation messages were built from scratch (instead of being parsed from Rinex navigation files), then the message type should be added to the constructor call.
Fast Number Formatting for Scientific Notation
overview of the change
In the 13.X series, the FastDoubleFormatter was added, alongside
FastLongFormatter, for speeding up writing of large files. The double
formatter could only support decimal format, but not scientific format,
which is used for example in Rinex clock and Rinex navigation files.
This limitation has been lifted in 14.0 by adding a new FastScientificFormatter.
This change implied changing the existing FastDoubleFormatter to an abstract
class which cannot be instanciated anymore, and introducing a FastDecimalFormatter
class. Both FastDecimalFormatter and FastScientificFormatter extend the
FastDoubleFormatter abstract class and can be instanciated.
how to adapt existing source code
Users who built FastDecimalFormatter instances should now build
FastDecimalFormatter instances. Users who only used already built instances
can keep their variables declared as FastDoubleFormatter instances.
Created Clocks Package
overview of the change
In the 13.X series, the most classes related to clock models were in the
org.orekit.time package. Two classes related to quadratic models were
in the org.orekit.estimation.measurements package.
As more and more models are added, these became cumbersome, so a dedicated package
org.orekit.time.clock has been created. The existing classes were moved there
and new models added in 14.0 were created there too.
how to adapt existing source code
Users of clock models and clock offsets just need to update their import statements to get the class from their new package.
Add abstract class for detectors based on BodyShape
overview of the change
There was some code duplication and inconsistencies in naming between the following classes:
(Field)AltitudeDetector, (Field)LongitudeCrossingDetector, (Field)LongitudeRangeCrossingDetector,
(Field)LongitudeExtremumDetector, (Field)LatitudeCrossingDetector, (Field)LatitudeRangeCrossingDetector,
(Field)LatitudeExtremumDetector.
An abstract class has been introduced for the standard detectors and the Field ones.
how to adapt existing source code
The consequence is that the getter is now called getBodyShape and returns a BodyShape.
Please change your calls consequently.
Move (Field)OrbitBlender to propagation package
overview of the change
OrbitBlender and (Field)OrbitBlender depend on classes inside propagation.
They have been moved to this package.
how to adapt existing source code
Change the imports by replacing “orbit” with “propagation”.
Create cr3bp subpackage inside orbit
overview of the change
The package orbit had classes related to the third-body problem with dependencies to the propagation package. For a cleaner architecture, they have been moved to a new subpackage, called cr3bp. This mirrors what is already done inside propagation.
how to adapt existing source code
Change the imports by replacing “orbit” with “orbit.cr3bp” for the following classes:
LibrationOrbit, LibrationOrbitFamily, LibrationOrbitType, LyapunovOrbit, RichardsonExpansion,
CR3BPDifferentialCorrection, HaloOrbit
Use String in API for GNSS Observation Types
overview of the change
Since 13.0, ObservationType was an interface and PredefinedObservationType is an enumerate that provided
official Rinex types. This architecture allowed users to implement their own CustomObservationType for
upcoming missions, using a custom typeBuilder when parsing Rinex files. Such missions would typically reuse
existing names like C1C or L1C but with different supported SatelliteSystem not yet included in Rinex
standards.
There were several places in Orekit (dual frequency smoother, single frequency smoother, Sinex bias,
Rinex observation header,…) where ObservationType was used as a key in a Map or as an element in
a List. This prevented mixing predefined types with custom types, and this prevented searching for
elements in the maps since one cannot override the equals method in enumerate to for example just
compare the name of the observable. A more generic way would be to use only the name (C1C, L1C…)
in these containers, and build the real ObservationType (either predefined ones or custome ones) by applying
the typeBuilder only much later in the data flow.
how to adapt existing source code
In many constructors, methods and containers, types have been changed from ObservationType to String.
Users who called these constructors or methods with an ObservationType type could just use
type.getName() instead to use the name of the observation type.
Users who retrieved ObservationType for containers (like for example RinexObservationHeader.getNbObsPerSat()
or DifferentialSignalBias.getAvailableObservationPairs()) should adjust the type of the container.
Note that the ObservationData that is retrieved when parsing the data part of a Rinex observation file
(i.e. not the header) are still full-blown ObservationType, that were built at parse time by applying
the typeBuilder to the String name (this is typically how custom observation types are parsed in custom
missions).
The SinexBiasParser does not need a typeBuilder anymore to be built, this parameter can just be
removed from users calls.
Create covariance subpackage inside propagation
overview of the change
Covariance-related code has been growing recently, but still seated at the root of propagation. It now has its own subpackage.
how to adapt existing source code
Change the imports by replacing “propagation” with “propagation.covariance” for
LinearKeplerianCovarianceMapper, LinearKeplerianCovarianceHandler, AbstractStateCovarianceInterpolator,
FieldStateCovariance and all the StateCovariance[...] classes.
Remove field parameter of FieldDSSTPropagator and FieldNumericalPropagator
overview of the change
The field parameter of FieldDSSTPropagator and FieldNumericalPropagator has been removed. This parameter
was useless since the field is accessible from the provided integrator.
how to adapt existing source code
Simply remove the first argument that was used with the constructors.
Refactored AbsolutePVCoordinates and deleted ShiftingPVCoordinatesProvider
overview of the change
The classes AbsolutePVCoordinates and ShifingPVCoordinatesProvider (as well as their Field equivalent) were pretty much duplicates.
The former inherited from PVCoordinates (polymorphism) and the latter used it as attribute (class composition).
Only (Field)AbsolutePVCoordinates has been kept, but switching to composition.
This allowed to remove a lot of code duplication with (Field)Orbit.
After all, AbsolutePVCoordinates was originally introduced as an alternative coordinates system not presupposing to be in a two-body problem.
how to adapt existing source code
It is not possible to cast AbsolutePVCoordinates as PVCoordinates anymore, you need to call the getter first.
Most methods have been preserved. For the ones that have not (like toDerivativeStructureVector), call getPVCoordinates first.
Introduced dedicated classes for signal travel time
overview of the change
The methods for signal time travel were all static in AbstractMeasurement. They have been moved to dedicated classes
in its own high level package signal.
how to adapt existing source code
Instead of calling signalTimeOfFlightAdjustableEmitter, instantiate AdjustableEmitterSignalTimer and call compute.
The same applies for the adjustable emitter.
Replace ConstantPVCoordinatesProvider
overview of the change
The class ConstantPVCoordinatesProvider has been removed as it had no Field in it.
Now, GeodeticExtendedPositionProvider covers both needs for Earth-fixed frames.
For inertial ones, ConstantPositionProvider can be used.
how to adapt existing source code
If your frame is Earth-fixed, you can switch to GeodeticExtendedPositionProvider that will also work with Field.
If it is inertial, you can call ConstantPositionProvider.
Refactored counting handlers
overview of the change
The class CountAndContinue is now standalone, with immutable Action. Also, its count cannot be reset.
For more flexible options, CountingHandler is not abstract anymore and can be used readily,
counting all by default and returning a custom Action.
how to adapt existing source code
If you want to reset the count whilst always returning CONTINUE, you can use (Field)CountingHandler.
Otherwise, you can stick to (Field)CountAndContinue.
Updated Lambert solver
overview of the change
LambertSolver has been updated to use a more recent algorithm based on Dario Izzo's solver and Gim Der's work.
The solver can now find all possible solutions for a given problem set-up up the maximum number of revolutions.
Solutions are now returned as objects of class LambertSolution, with the following fields:
nRev: the number of complete revolutions completed for this solution. Accessible viagetNRev()pathType: the type of path followed for this solution (low path, high path or minimum energy path; possible values are defined in enumerateLambertPathType). Accessible viagetPathType()orbitType: the type of orbit geometry for this solution (elliptic, parabolic or hyperbolic; possible values are defined in enumerateLambertOrbitType). Accessible viagetOrbitType()boundaryConditions: an object of classLambertBoundaryConditionsthat contains the boundary conditions for the problem setup used to compute this solution. Accessible viagetBoundaryConditions()boundaryVelocities: an object of classLambertBoundaryVelocitiesthat contains the initial and final velocities corresponding to this solution. Accessible viagetBoundaryVelocities()Note that methodsolvefrom aLambertSolverstill receives as inputposigrade, a flag indicating if the transfer should be posigrade or retrograde. Additionally, the method can be called with or without specifyingnRev, the number of complete revolutions that should be completed during the transfer:- If not specified, the solver will find all possible solutions from 0 complete revolutions
up to the maximum number of revolutions. This results in a total of
2 * (nRevMax + 1)possible solutions (1 for 0 complete revolutions, and 2 for each revolution number; two such sets of solutions, of posigrade and retrograde transfers, can be found) - If specified, the solver will find only the solutions for the given number of complete revolutions, as long as this is below
the maximum possible number of revolutions. This results in either 1 or 2 possible solutions (1 for 0 complete revolutions,
and 2 for each revolution number; one for posigrade and one for retrograde transfers)
For consistency, regardless of how the method is called, the returned solutions will always be a
List<LambertSolution>. It shoud also be noted that nowLambertSolveruses internally a Householder solver, and therefore the maximum number of iterations, atol and rtol can be chosen when instantiating aLambertSolver. Alternatively, a constructor receiving onlymuis available that uses default values for these (2000 iterations, 1.0e-5 for atol and 1.0e-7 for rtol). This also ensures backward compatibility with existing code.
how to adapt existing source code
Existing calls to method solve are still accepted since they use the same signature as the current call providing a specific
value for nRev. However, since now method solve always returns a List<LambertSolution>, you will need to update the code
to take this into account. The previous Lambert solver always returned the low path solution. In the current implementation,
for multi-revolution transfers, the low-path solution will always be returned as the first element of the list. Therefore, to
adapt existing code, you can retrieve the first element of the returned list.
Revamped GNSSPropagatorBuilder
overview of the change
As of 13.X, GNSSPropagatorBuilder was a standalone class, not related at all with PropagatorBuilder
and with a different API. This prevented using such builders in orbit determination, as for
example BatchLSModel requires an array of PropagatorBuilder instances. The class also assumed some
defaults for inertial and body fixed frames that were confusing (for example an ITRF frame without tidal
effect, which is not compatible with geodetic precision needs).
The GNSSPropagatorBuilder has therefore been completely revamped. It is now a specialization of
AbstractAnalyticalPropagatorBuilder and therefore implements the PropagatorBuilder API. All of
its original constructors and methods have been changed to match the interface API and superclass
constructors, the only thing that remains is its name and two private fields. There are no default
frames at all.
The FieldGnssPropagatorBuilder on the other hand was mimicked on GNSSPropagatorBuilder
but there are no equivalent PropagatorBuilder. The class was added with 13.0 for consistency
reasons, but these reasons are not relevant anymore and the class is not really useful. It was
therefore completely removed.
A side effect of the builders changes is that the {Field}XxxAlmanac.getPropagator() methods for
all supported satellites (Beidou, NavIC, GPS, QZSS, Galileo, civilian and legacy messages) now also
requires providing the inertial and body fixed frames.
how to adapt existing source code
Users that used GNSSPropagatorBuilder must change all their uses. The single constructor requires
the inertial and body fixed frames to be set up at construction time, and these frames are now immutable,
so the corresponding setters have been removed. The setters for mass and attitude provider as well as
the propagator building method have all been renamed to match the PropagatorBuilder API.
Users that used FieldGnssPropagatorBuilder should directly build the FieldGnssPropagator by
themselves, there are no builder for that anymore.
Users that called {Field}XxxAlmanac.getPropagator() must now pass the inertial and body fixed frames
when creating the propagator.
Revamped all PropagatorBuilder Hierarchy
overview of the change
As of 13.X, PropagatorBuilder implementations based on AbstractPropagatorBuilder used a template orbit
to set up some metadata that will be used later on each time its build method is called for building the
initial state for a new propagator. This AbstractPropagatorBuilder constructor extracted the date, frame,
mu, orbit type and from the orbit type it extracted the parameter drivers. It also provided getters for these
elements, as required by the PropagatorBuilder interface.
This did not work for GNSSPropagatorBuilder and GNSSPropagator. GNSSPropagator uses specific parameters
which look a lot like KeplerianOrbit but are really not KeplerianOrbit as they are defined with respect
to a rotating Earth frame (the ascending node is a longitude, not a right ascension). There were no OrbitType
for these, and the orbital parameter drivers were specific. Using a first throw-away GNSSPropagator in the
GNSSPropagatorBuilder constructor to get the template orbit from the initial orbit generated by GNSSPropagator
didn't work in practice, because GNSSPropagator did output CartesianOrbit (which are orbits, as required by
AbstractPropagatorBuilder). CartesianOrbit has nothing to do with the not-really-keplerian orbital
parameters input (GNSSPropagator input and output were different types…), and there are non-Keplerian elements
appended to the 6 Keplerian ones. Similar issues happened wit TLE which are also specific, with also different input
and output types, and also include one non-Keplerian element (the B-star parameter).
So a new marker interface OrbitalParameters has been added, on top of the existing Orbit, TLE, and
GNSSOrbitalElements classes. A new OrbitalParameterFactory interface has been added with a complete hierarchy
of implementations (4 implementations for regular orbits, one implementation for TLE and 14 implementations for
the various GNSS almanac and navigation messages). This factory interface references the ParameterDriversList
associated with both the Keplerian orbital parameters and the non-Keplerian ones, and provides a createFromDrivers
method to build a new set of orbital parameters from the current values held by the drivers. The parameter drivers
can be automatically reset from an orbit thanks to a reset(orbit) method. Note that the argument of the reset
method is always an Orbit, even when the orbital parameters are not really orbits, like TLE or GNSS orbital
elements. This allows to feed the factory from regular propagators or from classical ephemeris files. The factory
also provides the additional elements the AbstractPropagatorBuilder initially used, like date, frame, mu and
position angle type. The {Field}GNSSOrbitalElements built by the factories now embed an intermediate
{Field}KeplerianOrbit that uses a dedicated frame built by freezing the Earth frame to the inertial frame at epoch.
This trick allows the right ascension of the ascending node (which corresponds to the {Field}KeplerianOrbit API)
to be numerically equal to the longitude of the ascending node as used in GNSS world. In order to be able to build
this intermediate frame (and change it if time is changed), the factory is built with both the inertial frame and
the Earth frame. These additional frames are also required for building parsers like SEM parsers, YUMA parsers, and
Ntrip clients.
All propagator builders are now built using an OrbitalParameterFactory implementation, which replaces both
the template orbit and the associated frame, mu and position angle type that were used previously. As a convenience, a
new factory method had been added in the Orbit and GNSSOrbitalElements classes, so a factory can easily be built
from a template, hence simplifying transition from the previous API.
The class hierarchy containing {Field}GNSSOrbitalElements has been redesigned and its depth has been considerably
reduced. The GNSSOrbitalElementsDriversProvider class that was formerly the abstract class at the top level of the
hierarchy has been renamed GNSSOrbitalElementsFactory and now implements the OrbitalParameterFactory interface
instead of the ParameterDriversProvider interface. It is not the superclass of {Field}GNSSOrbitalElements anymore.
The GNSSOrbitalElements now contain a KeplerianOrbit, using a special inertial frame which is an Earth frame frozen
at some time. The intermediate CommonGnssData and AbstractAlmanac classes have been removed and their content merged
into GNSSOrbitalElements. Their subclasses are now immutable, as changing the parameters is done by creating a new
set of orbital elements using GNSSOrbitalElementsFactory.
The getOrbitType, getPositionAngleType, getInitialOrbitDate, getFrame, getMu, and
getOrbitalParametersDrivers getters in the PropagatorBuilder interface have been replaced by a single
getOrbitalParameterFactory method, and this factory provides the desired elements. Note that getOrbitType returns
null for TLE as there are no real orbit type there. It returns OrbitType.KEPLERIAN for GNSS as the propagator
uses a hacked KeplerianOrbit internally.
A side effect of using KeplerianOrbit for GNSS is that the state transition and Jacobian matrices generated by
GnssHarvester are now based on Keplerian elements and not Cartesian elements anymore.
The existing TleGenerationAlgorithm has been changed from an interface to an abstract class that implements
OrbitalParameterFactory<TLE> and contains both the template TLE and the driver for the B-star non-Keplerian
model parameter. There are still two implementations, one based on a fixed point method and one based on the least
squares method. The static stateToTLE methods in {Field}TLE nows take TleGenerationAlgorithm instead of
a template TLE.
As the B-star driver has been moved to the factory, the TLE class by itself does not provide
anymore a getParametersDrivers method to retrieve the B-star parameter (B-star is now immutable at the TLE
class level). The field version of the various selectExtrapolator methods also don't take anymore an additional
parameters array to hold the B-star as it is fixed within the TLE itself.
Classical propagator builders that previously required a template Orbit now require an OrbitalParameterFactory
that generates objects belonging to the Orbit hierarchy. The DSSTPropagatorBuilder is special, though,
as it requires explicitly an EquinoctialOrbitFactory. GNSS propagators require an OrbitalParameterFactory that
generates objects belonging to the GNSSOrbitalElements hierarchy. Beware that since GNSS orbital elements refer to
an Earth frame frozen at a specific date to become an inertial frame, the factory date must have been
initialized properly (by calling factory.setWeekAndTime(week, time)). Setting the date allows the frozen frame to be
set properly, otherwise a null pointer exception will be thrown when the propagator builder attempts to use the frame.
As the TleGenerationAlgorithm factory already holds all other data (template TLE, angle type, position scale), the
TLEPropagatorBuilder now only needs one TleGenerationAlgorithm to be built.
As a side effect, RTCM messages constructors (except for GLONASS) now have a factory argument, alongside satellite ID
and accuracy indicator. This should not affect many users as the messages are normally built on the fly by Orekit
itself. Users usually retrieve the already built message by registering message observers to a NtripClient and
wait for notifications when the messages are already built. They should not see the underlying factory that was
used. The message getters that take a TimeScales arguments (in order to avoid DefaultDataContext warnings) has
been removed, as the no argument getter do generate this warning anymore (because the TimeScales is already known
to the factory). The getters for some message data (for example code on L2 channel) are now directly available in the
navigation message returned, so they are not in the RTCM messages anymore.
Another side effect of the change is that AbstractAnalyticalGradientConverter has been moved upward in the
package hierarchy.
how to adapt existing source code
As the builders now require an OrbitalParameterFactory instance and as it is possible to create a factory
from a template orbit by calling its factory, constructors calls of the form:
PropagatorBuilder builder = new SomeBuilder(templateOrbit, positionAngleType, positionScale);
should be replaced by
PropagatorBuilder builder = new SomeBuilder(templateOrbit.factory(positionAngleType, positionScale));
For GNSS propagators the constructor calls of the form:
GNSSPropagatorBuilder builder = new GNSSPropagatorBuilder(orbitalElements, inertial, bodyFixed);
should be replaced by
GNSSPropagatorBuilder<GalileoNavigationMessage> builder = orbitalElements.builder(inertial, bodyFixed);
For TLE propagators the constructor calls of the form:
TLEPropagatorBuilder builder = new TLEPropagatorBuilder(templateTLE,
positionAngleType,
positionScale,
new FixedPointTleGenerationAlgorithm());
should be replaced by
TLEPropagatorBuilder builder = new TLEPropagatorBuilder(new FixedPointTleGenerationAlgorithm(templateTLE,
positionAngleType,
positionScale));
As the getters have been moved from the PropagatorBuilder interface to the OrbitalParameterFactory
interface, calls of the form:
AbsoluteDate date = builder.getInitialOrbitDate();
should be replaced by
AbsoluteDate date = builder.getOrbitalParameterFactory().getDate();
Accessing the initial orbit is done by replacing
Orbit orbit = builder.createInitialOrbit();
should be replaced by (assuming the builder is already known to generate Orbit elements and
not TLE or GNSSOrbitalElements)
Orbit orbit = builder.getOrbitalParameterFactory().createFromDrivers();
As the {Field}GNSSOrbitalElements embed an intermediate {Field}KeplerianOrbit, calls of the form
(take semi major axis as an example):
double a = element.getSma();
should be replaced by
double a = element.getOrbit().getA();
Use of the state transition matrices and Jacobian matrices generated by GNSS propagators should also be adapted as they are now based on Keplerian elements (they were based on Cartesian elements in previous versions).
As explained before, the TLE.B_STAR constant has been moved, and should now be referenced as
TleGenerationAlgorithm.B_STAR. The similar constants for GNSS elements (SEMI_MAJOR_AXIS, ECCENTRICITY,
TIME, INCLINATION_RATE, INCLINATION_COSINE, AF0, AF1…) that were initially defined either in the
GNSSOrbitalElements class or in its former GNSSOrbitalElementsDriversProvider abstract superclass or in the
CommonGnssData class are now defined in either the GNSSOrbitalElementsFactory or the NonKeplerianDriversFactory
concrete classes.
As the getters with a TimeScales argument in the various Rtcm####Data classes have been removed,
calls of the form (taking QZSS legacy message as an example):
QZSSLegacyNavigationMessage message = rtcm1044Data.getQzssNavigationMessage(timeScales);
should be replaced by
QZSSLegacyNavigationMessage message = rtcm1044Data.getQzssNavigationMessage();
As the getters for some parts of navigation messages are now available in the messages itself rather than in the RTCM message, calls of the form:
int code = rtcm19Data.getGpsCodeOnL2();
should be replaced by
int code = rtcm19Data.getGpsNavigationMessage().getL2Codes();
If users extended the AbstractAnalyticalGradientConverter class in custom classes, they should
update the import has the class has been moved upward in the package hierarchy.
Introduced EventFunction
overview of the change
A functional interface called EventFunction has been introduced to define the so-called g function whose roots represent the event occurrences.
This new interface requires implementing the value method both for double and Field,
the latter having a default version based on the former.
The rationale is twofold: first separate when possible the event function evaluation logic from the detection itself,
and second to make the connection between standard detectors and their Field counterpart easier.
The second point in particular opens the door for many practical benefits.
how to adapt existing source code
The only API breaking change is that the method dependsOnTimeOnly has been move from (Field)EventDetector
to EventFunction.
Make IntervalEventTrigger and StartStopEventsTrigger non abstract
overview of the change
The classes IntervalEventTrigger and StartStopEventsTrigger are not abstract anymore.
The reason is that with the introduction of EventFunction, it is much easier to define FieldEventDetector from EventDetector.
Thus implementations of the Fielded versions of the triggers are possible and not abstract anymore.
For specific uses, this default conversion might not be enough, in which case one should still override the method(s).
how to adapt existing source code
The main change is that constructors of IntervalEventTrigger and StartStopEventsTrigger are now public and no longer protected.
The other change is with the signature of convertIntervalDetector, convertStartDetector and convertStopDetector.
They use to return an extension of FieldEventDetector,
but this was a leftover of a previous change to generalize the output to FieldEventDetector instead of FieldAbstractDetector.
They do not need to be typed for a functional reason and thus now simply return FieldEventDetector.
Refactoring of indirect control switches
overview of the change
Following the introduction of EventFunction, the way switches in indirect control are managed has been changed.
Dedicated detectors are not necessary anymore.
how to adapt existing source code
The classes ControlSwitchDetector and FieldControlSwitchDetector have been replaced respectively by
ControlSwitchFunction and FieldSwitchFunction, the former being abstract and the latter an inner class.
Fixed typo in EOPHistory method name
overview of the change
A typo has been fixed in EOPHistory class for the method getNonRotatingOriginNutationCorrection.
how to adapt existing source code
The method EOPHistory#getNonRotatinOriginNutationCorrection have been replaced by
EOPHistory#getNonRotatingOriginNutationCorrection to fix the typo.
Reduce code duplication and renaming for estimators
overview of the change
The abstract classes for least squares and Kalman filters now inherit from the same interface ParameterEstimator.
This allows to decrease code duplication on how the parameter drivers are managed. Naming disparities have also
been fixed.
how to adapt existing source code
The method getPropagatorParametersDrivers in BatchLSEstimator is now called getPropagationParametersDrivers like for Kalman filters.
Abstract away receiver requirements in measurement classes
overview of the change
As of v13.X, all measurement classes that required data from an observing object (a GPS satellite, a ground observation
station, etc.) could only accept either a GroundStation or the PVCoordinatesProvider for a GPS satellite depending on the
type of measurement being used. It was not directly possible for example, to create a OneWayGNSSRange measurement
using a signal sent from a ground site, nor to create a Range measurement using a space-based observer. In order to rectify
this issue, v14.0 has created a space-based observer class ObserverSatellite, and rewritten the inheritance structure such
that both the new class ObserverSatellite and the already-existing class GroundStation both inherit from a new interface
named Observer. All measurement classes in estimation/measurements that formerly accepted a GroundStation value or a
PVCoordinatesProvider value for the GPS satellite trajectory in the OneWayGNSS measurement classes now instead accept an
Observer that can be either a GroundStation or an ObserverSatellite. Additionally, in order to accomodate this change,
the ‘OneWayGNSS’ measurement builder classes which formerly accepted two ObservableSatellite values now accept one
ObservableSatellite and one Observer, which again can be either a GroundStation or an ObserverSatellite. Finally,
in order to accomodate this change, the AbstractIonosphericModel class and the IonosphericModel interface, along with all
the classes that implement these parent objects, have been rewritten to automatically calculate the ionospheric delay between
two objects without needing the user to distinguish whether the link between them is a space-ground link or a space-space link.
how to adapt existing source code
For measurement classes that previously accepted GroundStation observers, no change is required. For the OneWayGNSSRange,
OneWayGNSSPhase, and OneWayGNSSRangeRate measurements, instead of entering the PVCoordinatesProvider and the
QuadraticClockModel directly, it is now necessary to use these values to create an ObserverSatellite and then enter
the ObserverSatellite as the first input value when creating one of these measurements (note: use of the QuadraticClockModel
is optional when creating the ObserverSatellite and really only required if you plan to introduce observer clock bias). The
same is true for the creation of the OneWayGNSS measurement builder classes, except in this case the Observer object
will replace what was the second ObservableSatellite instance in these constructors.
For the creation of any child class of IonosphericModel, it is now necessary to enter a OneAxisEllipsoid earth object as the
first value of the constructor function. (Note: the TroposphericDelay classes will not work for space-space transmissions,
and if a space-based observer is entered into these classes, the code will throw an error.)
Renaming about clocks to avoid ambiguity
overview of the change
Clock handling used the term offset for two different purposes, and this created some ambiguities. In some places,
it referred to a complete time-dependent model, typically with a bias, a rate and an acceleration. In some other
places, it referred only to the constant bias part. In order to avoid these ambiguities, the term offset nor refers
to the full model, and the constant part is now named bias.
The QuadraticClockModel.getClockOffsetDriver() method has been renamed QuadraticClockModel.getClockBiasDriver().
Corresponding methods were also added to the new MeasurementParticipant interface and its implementation classes.
The suffix of the drivers have also been adapted, for example names of the form sat-0-clock-offset are now
sat-0-clock-bias. The {Field}ClockOffset.getOffset() getter has been renamed {Field}ClockOffset.getBias().
how to adapt existing source code
Users calling the methods QuadraticClockModel.getClockOffsetDriver() or {Field}ClockOffset.getOffset() methods,
or the driver names should use the new names.
Make measurement noise a modifier instead of argument for builder
overview of the change
The class AbstractMeasurementBuilder used the noise source as an argument at construction.
However, there is no reason to treat it differently than other measurement modifiers like biases,
so a dedicated EstimationModifier was created to handle it, named MeasurementNoise.
how to adapt existing source code
If you were passing null as a noise source, simply remove the first argument when constructing your builder.
If instead you were passing a noise source, you now need to call addModifier on the instance, wrapped in a MeasurementNoise.
Removed default implementation of setupMatricesComputation
overview of the change
Since version 11.1, the setupMatricesComputation method was declared at th Propagator interface level
with a default implementation that throws an UnsupportedOperationException. The rationale was that only
numerical propagator did implement the method when it was moved to interface level. At that time, it was
already foreseen the default implementation shoul be removed once all propagators do implement the method.
Now, all Orekit-provided propagators do implement the method, so the default implementation from the interface can be safely removed.
how to adapt existing source code
There is no need to adapt anything for Orekit-provided propagators as they already implement everything. The only adaptation needed is for custom propagators that inherited the default implementation. They should now implement the method by themselves. This can be done by adding the following code snippet in the custom propagator code:
public MatricesHarvester setupMatricesComputation(final String stmName, final RealMatrix initialStm,
final DoubleArrayDictionary initialJacobianColumns) {
throw new UnsupportedOperationException();
}
Made GroundStation work with arbitrary body and created EarthBasedStation
overview of the change
The class GroundStation had become overspecialized with Earth-bound sensors.
It has been made more generic by removing the polar and meridian offset and drifts,
as well as the dependence on EOPs. For the latter, EarthBasedStation has been introduced, inheriting from GroundStation.
how to adapt existing source code
If you were estimating polar/merdian offset/drift, simply call EarthBasedStation instead of GroundStation.
The latter can be still used if you are not using these parameter drivers.
Added getBaseInitialState
overview of the change
Up to 13.X, the Propagator and FieldPropagator interfaces both had a getInitialState method to retrieve
the initial state. This state was set up either from implementations constructors or using the resetInitialState
method. This could include some unmanaged data in addition to orbit, attitude and mass.
It was possible to add managed data to the state generated by the propagate method, either using
addAdditionalDataProvider (available to all propagators) or addAdditionalDerivativesProvider for integrated propagators.
These additional states are not returned by getInitialState, hence getInitialState and
propagate(getInitialState().getDate()) returned different values.
In order to improve consistency, the getInitialState now also includes the additional state data. The former behavior
(i.e. returning state without additional data) can be recovered by using the new getBaseInitialState() method that
has been added to the Propagator and FieldPropagator interfaces.
how to adapt existing source code
Use code will most probably not need any modification. If the side effect of having the additional data evaluated
(as the additional data providers are automatically called when getInitialState is called) is costly and not
useful, then users can call getBaseInitialState() instead for a lower overhead.
Use of Optional for optional fields in CCSDS
overview of the change
Getters for not-mandatory fields in CCSDS data classes
return java.util.Optional<T> instead of using null (for object types)
or Double.NaN (for numeric types) as sentinel values.
Mandatory fields continue to return their values directly (non-optional).
Callers should use Optional.isEmpty() to check if a field was present
in the parsed file, or Optional.get() / Optional.orElse(default) to
access the value.
Setters still accept the raw type (including null), maintaining backward
compatibility for programmatic construction of messages.
Creation of ClockModel to generalize QuadraticClockModel
The QuadraticClockModel class was the clock model class used
throughout Orekit to store and model clocks.
This has been replaced with a ClockModel interface and a PolynomialClockModel class.
Classes that used to hold QuadraticClockModels now by default create PolynomialClockModel classes.
However, those clocks can be set to other clock models such as ClockSum, PerfectClockModel, or user-defined clock models.
The methods for getting and setting clock bias, rate, and acceleration values have changed as well.
The values returned from .getClockBiasDriver will depend on the clock instance that is used.
However, getting the value for how far the clock is off of the nominal time is provided through .getOffset, which will take in a given
time and return the ClockOffset class.
how to adapt existing source code
The QuadraticClockModel was replaced with PolynomialClockModel
// The clock is created within the satellite and each value set separately
ObserverSatellite remote = new ObserverSatellite("GNSS-remote", new KeplerianPropagator(o2));
remote.getClockBiasDriver().setReferenceDate(AbsoluteDate.ARBITRARY_EPOCH);
remote.getClockBiasDriver().setValue(1.0e-16);
remote.getClockDriftDriver().setReferenceDate(AbsoluteDate.ARBITRARY_EPOCH);
remote.getClockDriftDriver().setValue(0);
remote.getClockAccelerationDriver().setReferenceDate(AbsoluteDate.ARBITRARY_EPOCH);
remote.getClockAccelerationDriver().setValue(0);
was replaced by:
// The clock is created explicitly and passed into the constructor of the satellite class
PolynomialClockModel remoteClockModel = new PolynomialClockModel(AbsoluteDate.ARBITRARY_EPOCH, 1e-16);
ObserverSatellite remote = new ObserverSatellite("GNSS-remote", new KeplerianPropagator(o2), remoteClockModel);
Previous code checked got clock drift drivers directly from stations:
station.getClockDriftDriver().setValue(groundClockDrift);
was replaced by:
station.getClockModel().getRateDriver().setValue(groundClockDrift);
The .getOffset method to get a FieldClockOffset object was renamed to .getFieldOffset
final FieldClockOffset<T> co = clockModel.getOffset(t0F.shiftedBy(dtF));
was replaced by:
final FieldClockOffset<T> co = clockModel.getFieldOffset(t0F.shiftedBy(dtF));
Added width check to FastLongFormatter
overview of the change
The FastLongFormatter is a much faster implementation of String.format with fixed number of digits for long values.
It is intended to be used when very large files with fixed format are generated, like Rinex or SP3 files.
A few formatters are set up first with a configured width and flag selecting whether padding on the left should use
zero digit or blank characters. These formatters are then reused throughout file generation, with much lower overhead
than String.format.
In order to be compatible with String.format, the width is only a minimum width (hence the zero or blank padding);
the formatter is allowed to exceed this width if needed. If for example a formatter is set up with 3 characters width
and the long value is -12345, the six characters string -12345 is produced without error. Exceeding the prescribed
width may generate errors with some file formats.
In order to prevent these errors the formatter now has an additional flag set up at construction to either allow or forbid exceeding the prescribed width. When this flag is set, this means the width is not a minimum width to be blank or zero padded, but is rather a mandatory width, so the string produced would always have exactly the prescribed width. An exception is thrown when this width is exceeded.
how to adapt existing source code
Users that built FastLongFormatter should now set up the forbidWidthOverstep constructor argument. If they
want to preserve compatibility with previous versions, this parameter should be set to false, which means
width can be exceeded. If they want to ensure width is never exceeded, this parameter should be set to true,
which implies an OrekitException will be thrown if width would be exceeded when attempting to format a too
large value.
Optional participants pos-vel in measurements
overview of the change
Before this change, measurement models computed and stored the full kinematic states of all the participants,
in the same order as signal travel path. However, this can be the bottleneck, especially for massive measurement generation.
A new flag dependsOnParticipantsStates has been added to modifiers, with the default implementation returning false.
In a derivative-free theoretical measurement (class EstimatedMeasurementBase), the method
getParticipants will return an empty array if all the modifiers defined in its originator AbstractMeasurement have a negative flag.
Note that the class EstimatedMeasurement still holds this information.
how to adapt existing source code
For user's defined modifiers, overwrite dependsOnParticipantsStates if the implementation (to return true)
requires a call to getParticipants.
Removed IOException from DataFilter filter
overview of the change
The filtering feature in Orekit allows to stack several DataFilter instances between a raw
Datasource that may for example be a file on disk and a filtered DataSource that will be used
to feed some parser. The library provides several predefined filters that allows to easily load
gzip or Unix-compressed files, but also Hatanaka-compressed Rinex files or fix non-compliant
SP3 files produced by SGF.
The interface was basic as it declared a single one-argument method:
public interface DataFilter {
DataSource filter(DataSource original) throws IOException;
}
When a stack of DataFilter is needed, one uses FiltersManager to apply them in row in the
appropriate order (which may be different from the declaration order), and the
FilterManager.applyRelevantFilters(dataSource) method is called to loop over the
DataFilter.filter(dataSource) methods.
Looking at the implementations of the DataFilter interface, it appeared none of them really
throwed any IOException, and it is in fact not expected custom implementations would throw
such exception either.
The reason why predefined implementations do not throw any exception and user custom implementations
are not expected to throw any exception is due to the delayed opening feature that is used in
DataSource. Input/output exception may arise at a later stage. Once a DataSource has been
filtered, one first has to get an Opener from it by calling DataSource.getOpener(); this doesn't
generate any exception yet. Then the opener openStreamOnce() or openReaderOnce() is called, and
this can trigger an exception, but it happens much later than the filtering stage. Of course, the
exception may also happen at an even later stage, when the InputStream or the Reader is used.
This scheduling is enforced by the API, and this is the reason why the methods were called
openStreamOnce() and openReaderOnce(): they can be called only once, and this lazy call is
delayed until really needed. The rationale behind this design choice was that DataSource could be
used to open network-based streams that cannot be rewound and re-opened.
So the exception has been removed from the interface signature, and also from
FilterManager.applyRelevantFilters(original) method.
how to adapt existing source code
If users called either DataFilter.filter(original) or the FilterManager.applyRelevantFilters(original),
then these methods will not throw IOException anymore, to the calling method signature may need to be
adapted and IOException may need to be removed.
If users implemented the DataFilter interface by themselves and in the unlikely case they needed
throw an IOException from within the filter method, then they should wrap the exception
in an unchecked exception (it could be an OrekitException for example) as follows:
class MyFilter implements DataFilter {
public DataSource filter(final DataSource original) {
try {
// code that could throw an IOException
} catch (IOException ioe) {
throw new MyUncheckedException(ioe, "cannot filter " + original.getName());
}
}
}
Use EOPFittedModel instead of EOPFitter to build PredictedEOPHistory
overview of the change
EOP Prediction has been introduced in Orekit since version 12.0. This introduced classes EOPfitter to perform
the fitting, EOPFittedModel that holds the result of the fitting, and PredictedEOPHistory that can be used
to append fitted EOP data at the end of existing EOP data.
The PredictedEOPHistory constructor required an EOPfitter instance, which was only used in a private method
to build an EOPFittedModel. It is more effective for users if this constructor use an EOPFittedModel, as
some users may already have done the fitting, not necessarily using the same existing rawHistory.
The last argument of PredictedEOPHistory constructor was therefore changed from EOPfitter to EOPFittedModel.
how to adapt existing source code
If users did create PredictedEOPHistory by themselves, they should perform the fitting before calling the
constructor.
Code before the change:
PredictedEOPHistory predicted =
new PredictedEOPHistory(rawHistory, extensionDuration, fitter);
Code after the change:
PredictedEOPHistory predicted =
new PredictedEOPHistory(rawHistory, extensionDuration, fitter.fit(rawHistory));
Added controlled loading of EOP data
overview of the change
EOP data used to be lazily loaded using either the default or a custom data context. A finer control was needed
in some cases, either to validate EOP data before use or to perform some filtering. As users now have the
ability to load data by iterating over different sources, the loaded data may be scattered, unsorted, and
incomplete. This is typically true with Bulletin A, where rapid data pole motion is published weekly and
nutation data is published monthly, several weeks later. Before the change, as Bulletin A loader was only used
as a DataLoader from with a data context, merging and sorting the incomplete EOP data was performed directly
from the Bulletin A loader. Now that users have finer control and can load files individually, this sorting,
deduplicating and merging step has been moved to the EOPHistory container, at construction time. The
EOPHistory container can therefore be fed with scattered, unsorted, and incomplete data while still providing
properly sorted history once built. It is not recommended anymore to use a SortedSet to the EOPHistory
constructors, as it would hinder the internal sorting and merging process.
As part of this change for finer control, the parse method from the EopHistoryLoader.Parser interface
now takes a DataSource argument instead of already opened InputStream and name.
A side effect of allowing scattered, unsorted, and incomplete EOP data to be provided to EOPHistory, the
fillHistory method in the EopHistoryLoader interface changed; it now uses a Collection<EOPEntry>
rather than a SortedSet<EOPEntry> argument.
how to adapt existing source code
If users implemented the EopHistoryLoader interface or its Parser sub-interface in custom classes, they must
adapt their implementations according to the new methods signatures. If they called the fillHistory or
parse methods by themselves, then they should adapt the call sites too.
If users called the EOPHistory container by themselves from custom loaded data, it is advised they don't
use a SortedSet but rather a collection that supports duplicated entries, like List.
The EopHistoryLoader.Parser.newEopC04Parser factory method does not take an ItrfVersionProvider argument
anymore, so callers should remove it. In fact, this argument was always ignored (but it was used in
siblings factory methods for other file formats).


