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.
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
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);
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
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
.
Propagators had a method getEventsDetectors
, whose naming was inconsistent with similar functions in EventDetectorProvider
and Hipparchus.
Simply replace your calls by getEventDetectors
.
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.
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.
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.
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);
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.
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.
In the 12.X series, tropospheric models implemented the DiscreteTroposphericModel
interface. This interface used constant temperature pressure and hygrometry but
variable location, which was inconsistent. It did not allow to use models that
are azimuth-dependant (slanted atmosphere layers). It also used inconsistent non-SI
units. The interface was completely rewritten as TroposphericModel
in 12.1, and
the existing models were rewritten to implement the new interfaces. A new GroundStation
constructor was added that references a PressureTemperatureHumidityProvider
that
will be used to provide weather parameters to measurements performed by the station.
The older tropospheric model interface, the former models implementations and the
GroundStation
constructor without provider were deprecated. In version 13.0,
the deprecated interface, implementations and constructor were all removed.
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.
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
If users called ImmutableFieldTimeStampedCache.emptyCache
with a field
argument, they should just remove the argument at the call site.
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.
If user code called signalTimeOfFlight
without a frame, it should now also
pass the frame in the arguments list.
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.
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.
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.
If users called setClkOffset
or getClkOffset
, they should replace these calls
by the boolean versions setClockOffsetApplied
and getClockOffsetApplied
.
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>
.
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();
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
User code should just adapt the method name at call site.
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
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();
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.
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.
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.
If user codes did call SP3Parser.guessFrame
, they should call instead IGSUtils.guessFrame
.
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.
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.
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.
The change is straightforward. Users only have to replace calls to getWeatherParamerers
by calls to getWeatherParameters
.
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.
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).
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.
The change is straightforward. Users only have to replace calls to signalTimeOfFlight
by calls to signalTimeOfFlightAdjustableEmitter
.
In 12.X series, Sinex and Sinex-bias files parsing was not in line with other formats parsers
implementations. It still refers to DataLoader
instead of the new DataSource
paradigm,
hence relying on a regular expression for supported names and Sinex files being contained
in a DataContext
. The SinexLoader
class was a huge and complicated class and was a nightmare
to maintain. It was used both as the parser and as the container for parsed results. It mixed
Sinex and Sinex bias, which despite having a common base are different formats. It did not allow
proper loading of Observable-Specific Signal Biases, it only handled Differential Signal Biases
and probably mixed things up when parsing OSB or IFB files. It also used the older naming convention
of Differential Code Bias despite newer biases can refer to phase and not to code only, so they
are now named Differential Signal Bias. During parsing, it merged the description within the data
despite they are separated in the files. It only used string fields even for parts that had proper
associated classes, like satellite in system or observable types. The loaded biases were claimed
to be in SI units, but in fact phase biases could not really be parsed because the cycles unit
was not aavailable and the code biases despite being in one SI units (converted from nanoseconds
to seconds), this was not really consistent with a pseudo-range measurement in meters.
In order to alleviate all these shortcomings, parsing was rewritten from the ground up and split into
several specialized block and line parsers as well as several containers. Support for Observable-Specific
Signal Biases was added (but support for Ionosphere-Free Signal Bias, IFB, is still lacking, it could
however be added easily if required, thanks to the new modular architecture). A new unit, Unit.CYCLE
with name “cyc” was added, it is a dimensionless unit (just like the already existing Unit.NONE
,
Unit.ONE
, and Unit.PERCENT
).
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 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
.
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.
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
.
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.
The classes ImpulseManeuver
and (Field)ImpulseManeuver
do not inherit anymore from AbstractDetector
and
FieldAbstractDetector
anymore. The rational is that the create
method was unsafe, allowing users to replace the
handler, which should not happen for the logic to work. Nonetheless, a withDetectionSettings
method has been preserved.
On another note, the maneuver is now applied no matter the Action
returned by the event trigger's handler,
whilst before it was only if it was STOP
, which was somewhat arbitrary and was preventing from using RecordAndContinue
.
Replace any calls to create
by withDetectionSettings
when applicable. In case your trigger had a
StopOnIncreasing
or StopOnDecreasing
handler, use an EventPredicateFilter
instead.
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.
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.
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.
Simply delete the calls to registerSwitchEvents
.
The propagator will automatically use the switches if you used setAttitudeProvider
.
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.
Replace any calls to ExtendedPVCoordinatesProvider
and ExtendedPVCoordinatesProviderAdapter
by ExtendedPositionProvider
and ExtendedPositionProviderAdapter
respectively.
A method toExtendedPVCoordinatesProvider
has been added to ExtendedPositionProvider
to facilitate phasing out.
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
.
Replace your calls to hasDerivatives
by hasNonKeplerianAcceleration
, removeRates
by withKeplerianRates
and hasRates
by hasNonKeplerianRates
.
Moreover, do not expect NaN or null rates anymore.
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…).
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.
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.
Modify the import by adding intervals
.
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.
Users should just change the class name they use.
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
.
Simply replace any calls to DEFAULT_MAXCHECK
with DEFAULT_MAX_CHECK
.
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
.
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
.
The interface SwitchHandler
has been extracted from AttitudesSequence
and renamed AttitudeSwitchHandler
for clarity.
It is also used by the new class AttitudesSwitcher
.
Simply replace any calls to AttitudesSequences.SwitchHandler
with AttitudeSwitchHandler
.
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
.
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
.
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.
Declare outputs of ODEIntegratorBuilder
as ODEIntegrator
instead of AbstractIntegrator
.
Similarly, declare outputs of FieldODEIntegratorBuilder
as FieldODEIntegrator
instead of AbstractFieldIntegrator
.
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.
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.
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.
The original constructor of ProfileThrustPropulsionModel
has been replaced by an static of
method,
since now a TimeSpanMap
of ThrustVectorProvider
is expected as argument.
The interface ManeuverTriggers
included resetters which are not used in Maneuver
, so
a new dedicated, intermediate interface has been introduced, called ResettableManeuverTriggers
.
If you do use resetters, replace ManeuverTriggers
by ResettableManeuverTriggers
.
If you were not implementing these methods with actual logic, remove them.