Commit a98cefad authored by Pascal Parraud's avatar Pascal Parraud
Browse files

Merge branch 'develop' into bistatic-tdoa

parents 9858c8f8 5234ebf7
......@@ -21,6 +21,12 @@
</properties>
<body>
<release version="11.2" date="TBD" description="TBD">
<action dev="pascal" type="fix" issue="908">
Fixed unmanaged comment in OMM.
</action>
<action dev="pascal" type="fix" issue="906">
Fixed unmanaged units in OMM.
</action>
<action dev="bryan" type="add" issue="900">
Added init method in {Field}AdditionalStateProvider.
</action>
......@@ -34,6 +40,33 @@
Added static method to create a BodyFacade from a CenterName.
</action>
</release>
<release version="11.1.1" date="2022-03-17"
description="Version 11.1.1 is a patch release of Orekit.
It fixes issues related to the parsing of SP3 and Rinex files. It also takes
additional derivatives into account in {Field}SpacecraftState.shiftedBy method.
Finally it includes some improvements in the class documentation">
<action dev="lars" type="add" issue="896">
Added Git configuration instructions in contributing guide.
</action>
<action dev="lars" type="fix" issue="897">
Corrected wrong path in release guide.
</action>
<action dev="bryan" type="fix" issue="894">
Fixed dead link in contributing guidelines.
</action>
<action dev="bryan" type="fix" issue="698">
Added missing BDS-3 signal for Rinex 3.04.
</action>
<action dev="bryan" type="fixed" issue="892">
Removed check of not supported keys in RinexLoader.
</action>
<action dev="lirw1984" type="update" issue="895">
Enhanced parsing of SP3 files.
</action>
<action dev="luc" type="add" issue="902">
Take additional derivatives into account in {Field}SpacecraftState.shiftedBy.
</action>
</release>
<release version="11.1" date="2022-02-14"
description="Version 11.1 is a minor release of Orekit.
It includes both new features and bug fixes. New features introduced
......
......@@ -16,7 +16,9 @@
*/
package org.orekit.files.ccsds.definitions;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.bodies.CelestialBody;
import org.orekit.data.DataContext;
/** Facade in front of several center bodies in CCSDS messages.
* @author Luc Maisonobe
......@@ -55,10 +57,14 @@ public class BodyFacade {
/**
* Create a body facade from an input center name.
*
* <p>This method uses the {@link DataContext#getDefault() default data context}.
*
* @param centerName input center name
* @return a body facade corresponding to the input center name
* @since 11.2
*/
@DefaultDataContext
public static BodyFacade create(final CenterName centerName) {
return new BodyFacade(centerName.name(), centerName.getCelestialBody());
}
......
......@@ -79,6 +79,9 @@ public class Units {
/** Hertz per second unit. */
public static final Unit HZ_PER_S = Unit.parse("Hz/s");
/** Earth radii reciprocal unit. */
public static final Unit ONE_PER_ER = Unit.parse("1/ER");
/** Private constructor for a utility class.
*/
private Units() {
......
......@@ -19,7 +19,7 @@ package org.orekit.files.ccsds.ndm.odm.omm;
import org.orekit.files.ccsds.definitions.Units;
import org.orekit.files.ccsds.utils.ContextBinding;
import org.orekit.files.ccsds.utils.lexical.ParseToken;
import org.orekit.utils.units.Unit;
import org.orekit.files.ccsds.utils.lexical.TokenType;
/** Keys for {@link OmmTle TLE} entries.
......@@ -28,6 +28,10 @@ import org.orekit.utils.units.Unit;
*/
public enum OmmTleKey {
/** Comment entry. */
COMMENT((token, context, container) ->
token.getType() == TokenType.ENTRY ? container.addComment(token.getContentAsNormalizedString()) : true),
/** Ephemeris Type, only required if MEAN_ELEMENT_THEORY = SGP/SGP4. */
EPHEMERIS_TYPE((token, context, container) -> token.processAsInteger(container::setEphemerisType)),
......@@ -44,7 +48,7 @@ public enum OmmTleKey {
REV_AT_EPOCH((token, context, container) -> token.processAsInteger(container::setRevAtEpoch)),
/** SGP/SGP4 drag-like coefficient. */
BSTAR((token, context, container) -> token.processAsDouble(Unit.ONE, context.getParsedUnitsBehavior(),
BSTAR((token, context, container) -> token.processAsDouble(Units.ONE_PER_ER, context.getParsedUnitsBehavior(),
container::setBStar)),
/** First time derivative of mean motion. */
......
......@@ -19,6 +19,8 @@ package org.orekit.files.sp3;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Scanner;
......@@ -28,10 +30,12 @@ import java.util.stream.Stream;
import org.hipparchus.exception.LocalizedCoreFormats;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.util.FastMath;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.data.DataContext;
import org.orekit.data.DataSource;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitIllegalArgumentException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.general.EphemerisFileParser;
import org.orekit.files.sp3.SP3.SP3Coordinate;
......@@ -39,7 +43,9 @@ import org.orekit.files.sp3.SP3.SP3FileType;
import org.orekit.frames.Frame;
import org.orekit.gnss.TimeSystem;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.DateComponents;
import org.orekit.time.DateTimeComponents;
import org.orekit.time.TimeComponents;
import org.orekit.time.TimeScale;
import org.orekit.time.TimeScales;
import org.orekit.utils.CartesianDerivativesFilter;
......@@ -524,7 +530,7 @@ public class SP3Parser implements EphemerisFileParser<SP3> {
},
/** Parser for comments. */
HEADER_COMMENTS("^/\\*.*") {
HEADER_COMMENTS("^[%]?/\\*.*|") {
/** {@inheritDoc} */
@Override
......@@ -551,14 +557,95 @@ public class SP3Parser implements EphemerisFileParser<SP3> {
final int day = Integer.parseInt(line.substring(11, 13).trim());
final int hour = Integer.parseInt(line.substring(14, 16).trim());
final int minute = Integer.parseInt(line.substring(17, 19).trim());
final double second = Double.parseDouble(line.substring(20, 31).trim());
pi.latestEpoch = new AbsoluteDate(year, month, day,
hour, minute, second,
pi.timeScale);
final double second = Double.parseDouble(line.substring(20).trim());
// some SP3 files have weird epochs as in the following two examples, where
// the middle dates are wrong
//
// * 2016 7 6 16 58 0.00000000
// PL51 11872.234459 3316.551981 101.400098 999999.999999
// VL51 8054.606014 -27076.640110 -53372.762255 999999.999999
// * 2016 7 6 16 60 0.00000000
// PL51 11948.228978 2986.113872 -538.901114 999999.999999
// VL51 4605.419303 -27972.588048 -53316.820671 999999.999999
// * 2016 7 6 17 2 0.00000000
// PL51 11982.652569 2645.786926 -1177.549463 999999.999999
// VL51 1128.248622 -28724.293303 -53097.358387 999999.999999
//
// * 2016 7 6 23 58 0.00000000
// PL51 3215.382310 -7958.586164 8812.395707
// VL51 -18058.659942 -45834.335707 -34496.540437
// * 2016 7 7 24 0 0.00000000
// PL51 2989.229334 -8494.421415 8385.068555
// VL51 -19617.027447 -43444.824985 -36706.159070
// * 2016 7 7 0 2 0.00000000
// PL51 2744.983592 -9000.639164 7931.904779
// VL51 -21072.925764 -40899.633288 -38801.567078
//
// In the first case, the date should really be 2016 7 6 17 0 0.00000000,
// i.e as the minutes field overflows, the hours field should be incremented
// In the second case, the date should really be 2016 7 7 0 0 0.00000000,
// i.e. as the hours field overflows, the day field should be kept as is
// we cannot be sure how carry was managed when these bogus files were written
// so we try different options, incrementing or not previous field, and selecting
// the closest one to expected date
DateComponents dc = new DateComponents(year, month, day);
final List<AbsoluteDate> candidates = new ArrayList<>();
int h = hour;
int m = minute;
double s = second;
if (s >= 60.0) {
s -= 60;
addCandidate(candidates, dc, h, m, s, pi.timeScale);
m++;
}
if (m > 59) {
m = 0;
addCandidate(candidates, dc, h, m, s, pi.timeScale);
h++;
}
if (h > 23) {
h = 0;
addCandidate(candidates, dc, h, m, s, pi.timeScale);
dc = new DateComponents(dc, 1);
}
addCandidate(candidates, dc, h, m, s, pi.timeScale);
final AbsoluteDate expected = pi.latestEpoch == null ?
pi.file.getEpoch() :
pi.latestEpoch.shiftedBy(pi.file.getEpochInterval());
pi.latestEpoch = null;
for (final AbsoluteDate candidate : candidates) {
if (FastMath.abs(candidate.durationFrom(expected)) < 0.01 * pi.file.getEpochInterval()) {
pi.latestEpoch = candidate;
}
}
if (pi.latestEpoch == null) {
// no date recognized, just parse again the initial fields
// in order to generate again an exception
pi.latestEpoch = new AbsoluteDate(year, month, day, hour, minute, second, pi.timeScale);
}
pi.nbEpochs++;
}
/** Add an epoch candidate to a list.
* @param candidates list of candidates
* @param dc date components
* @param hour hour number from 0 to 23
* @param minute minute number from 0 to 59
* @param second second number from 0.0 to 60.0 (excluded)
* @param timeScale time scale
* @since 11.1.1
*/
private void addCandidate(final List<AbsoluteDate> candidates, final DateComponents dc,
final int hour, final int minute, final double second,
final TimeScale timeScale) {
try {
candidates.add(new AbsoluteDate(dc, new TimeComponents(hour, minute, second), timeScale));
} catch (OrekitIllegalArgumentException oiae) {
// ignored
}
}
/** {@inheritDoc} */
@Override
public Stream<LineParser> allowedNext() {
......@@ -586,7 +673,7 @@ public class SP3Parser implements EphemerisFileParser<SP3> {
pi.latestPosition = new Vector3D(x * 1000, y * 1000, z * 1000);
// clock (microsec)
pi.latestClock = line.length() <= 46 ?
pi.latestClock = line.trim().length() <= 46 ?
DEFAULT_CLOCK_VALUE :
Double.parseDouble(line.substring(46, 60).trim()) * 1e-6;
......@@ -670,7 +757,7 @@ public class SP3Parser implements EphemerisFileParser<SP3> {
final Vector3D velocity = new Vector3D(xv / 10d, yv / 10d, zv / 10d);
// clock rate in file is 1e-4 us / s
final double clockRateChange = line.length() <= 46 ?
final double clockRateChange = line.trim().length() <= 46 ?
DEFAULT_CLOCK_VALUE :
Double.parseDouble(line.substring(46, 60).trim()) * 1e-4;
......
......@@ -63,7 +63,7 @@ import org.orekit.utils.ParameterDriver;
* The radiative model of the satellite, and its ability to diffuse, reflect or absorb radiation is handled
* by a {@link RadiationSensitive radiation sensitive model}.
* </p> <p>
* <b>Caution:</b> The spacecraft state must be defined in an Earth centered frame.
* <b>Caution:</b> This model is only suitable for Earth. Using it with another central body is prone to error..
* </p>
*
* @author Thomas Paulet
......
......@@ -87,6 +87,19 @@ public enum Frequency {
/** Beidou B3 (1268.52 MHz). */
B03(SatelliteSystem.BEIDOU, "B3", 124),
/** Beidou B1 (1575.42 MHz).
* FIXME the name must be updated in 12.0.
* It has been set to B04 as a workaround to handle the incompatibility between Rinex 3.02 and Rinex 3.04 for C2X
* In 3.02 the frequency of C2X is equal to 1561.098 MHz whereas in 3.04 it is equal to 1575.42 MHz
*/
B04(SatelliteSystem.BEIDOU, "B1", 154),
/** Beidou B2a (1176.45 MHz). */
B05(SatelliteSystem.BEIDOU, "B2a", 115),
/** Beidou B2 (B2a + B2b) (1191.795MHz). */
B08(SatelliteSystem.BEIDOU, "B2 (B2a+B2b)", 116.5),
/** QZSS L1 (1575.42 MHz). */
J01(SatelliteSystem.QZSS, "L1", 154),
......
......@@ -270,7 +270,6 @@ public class RinexObservationLoader {
int nbSatObs = -1;
int nbLinesSat = -1;
double rcvrClkOffset = 0;
boolean inRunBy = false;
boolean inMarkerName = false;
boolean inObserver = false;
boolean inRecType = false;
......@@ -280,8 +279,6 @@ public class RinexObservationLoader {
boolean inTypesObs = false;
boolean inFirstObs = false;
boolean inPhaseShift = false;
boolean inGlonassSlot = false;
boolean inGlonassCOD = false;
RinexObservationHeader rinexHeader = null;
int scaleFactor = 1;
int nbObsScaleFactor = 0;
......@@ -327,7 +324,7 @@ public class RinexObservationLoader {
// nothing to do
break;
case PGM_RUN_BY_DATE :
inRunBy = true;
// nothing to do
break;
case MARKER_NAME :
markerName = parseString(0, 60);
......@@ -482,7 +479,7 @@ public class RinexObservationLoader {
break;
case END_OF_HEADER :
//We make sure that we have read all the mandatory fields inside the header of the Rinex
if (!inRinexVersion || !inRunBy || !inMarkerName ||
if (!inRinexVersion || !inMarkerName ||
!inObserver || !inRecType || !inAntType ||
formatVersion < 2.20 && !inAproxPos ||
formatVersion < 2.20 && !inAntDelta ||
......@@ -678,7 +675,7 @@ public class RinexObservationLoader {
// nothing to do
break;
case PGM_RUN_BY_DATE :
inRunBy = true;
// nothing to do
break;
case MARKER_NAME :
markerName = parseString(0, 60);
......@@ -936,20 +933,17 @@ public class RinexObservationLoader {
break;
}
case GLONASS_SLOT_FRQ_NB :
//Not defined yet
inGlonassSlot = true;
// Not defined yet
break;
case GLONASS_COD_PHS_BIS :
//Not defined yet
inGlonassCOD = true;
// Not defined yet
break;
case END_OF_HEADER :
//We make sure that we have read all the mandatory fields inside the header of the Rinex
if (!inRinexVersion || !inRunBy || !inMarkerName ||
if (!inRinexVersion || !inMarkerName ||
!inObserver || !inRecType || !inAntType ||
!inAntDelta || !inTypesObs || !inFirstObs ||
formatVersion >= 3.01 && !inPhaseShift ||
formatVersion >= 3.03 && (!inGlonassSlot || !inGlonassCOD)) {
formatVersion >= 3.01 && !inPhaseShift) {
throw new OrekitException(OrekitMessages.INCOMPLETE_HEADER, name);
}
......
......@@ -24,7 +24,7 @@ package org.orekit.gnss;
*/
public enum SignalCode {
/** Galileo A PRS / IRNSS A SPS / GLONASS L1OCd and L2CSI codes. */
/** Galileo A PRS / IRNSS A SPS / GLONASS L1OCd and L2CSI / Beidou B1A and B3A codes. */
A,
/** Galileo B I/NAV and B C/NAV / IRNSS B RS / GLONASS L1OCp and LO2Cp codes. */
......@@ -33,7 +33,7 @@ public enum SignalCode {
/** GPS C/A / GLONASS C/A / Galileo C / SBAS C/A / QZSS C/A / IRNSS C RS(P) codes. */
C,
/** GPS L1(C/A) + (P2-P1) / QZSS L5D codes. */
/** GPS L1(C/A) + (P2-P1) / QZSS L5D / Beidou Data codes. */
D,
/** QZSS L6E and L6 (D+E) codes. */
......@@ -48,7 +48,10 @@ public enum SignalCode {
/** GPS M code. */
M,
/** GPS P (AS off) / GLONASS P / QZSS L5P codes. */
/** Beidou B1 Codeless. */
N,
/** GPS P (AS off) / GLONASS P / QZSS L5P / Beidou Pilot codes. */
P,
/** GPS Q / GLONASS Q / Galileo Q / SBAS Q / QZSS Q / Beidou Q codes. */
......@@ -60,13 +63,13 @@ public enum SignalCode {
/** GPS Z - tracking and similar (AS off) / code. */
W,
/** GPS L1C (D+P), L2C (M+L) and I+Q / GLONASS I+Q, L1OCd+L1OCp and L2CSI+LO2Cp / Galileo B+C and I+Q / SBAS I+Q / QZSS L1C (D+P), L2C (M+L), I+Q and S+L / Beidou I+Q / IRNSS B+C codes. */
/** GPS L1C (D+P), L2C (M+L) and I+Q / GLONASS I+Q, L1OCd+L1OCp and L2CSI+LO2Cp / Galileo B+C and I+Q / SBAS I+Q / QZSS L1C (D+P), L2C (M+L), I+Q and S+L / Beidou B1 (I+Q), B2b I+Q, B2a Data+Pilot, B3 (I+Q) / IRNSS B+C codes. */
X,
/** GPS Y code. */
Y,
/** Galileo A+B+C / QZSS L1-SAIF, L5(D+P) and L6(D+E) codes. */
/** Galileo A+B+C / QZSS L1-SAIF, L5(D+P) and L6(D+E) / Beidou B2b Data+Pilot codes. */
Z,
/** Codeless. */
......
......@@ -699,10 +699,10 @@ public class FieldSpacecraftState <T extends CalculusFieldElement<T>>
public FieldSpacecraftState<T> shiftedBy(final double dt) {
if (absPva == null) {
return new FieldSpacecraftState<>(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
mass, additional, additionalDot);
mass, shiftAdditional(dt), additionalDot);
} else {
return new FieldSpacecraftState<>(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
mass, additional, additionalDot);
mass, shiftAdditional(dt), additionalDot);
}
}
......@@ -740,13 +740,63 @@ public class FieldSpacecraftState <T extends CalculusFieldElement<T>>
public FieldSpacecraftState<T> shiftedBy(final T dt) {
if (absPva == null) {
return new FieldSpacecraftState<>(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
mass, additional, additionalDot);
mass, shiftAdditional(dt), additionalDot);
} else {
return new FieldSpacecraftState<>(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
mass, additional, additionalDot);
mass, shiftAdditional(dt), additionalDot);
}
}
/** Shift additional states.
* @param dt time shift in seconds
* @return shifted additional states
* @since 11.1.1
*/
private FieldArrayDictionary<T> shiftAdditional(final double dt) {
// fast handling when there are no derivatives at all
if (additionalDot.size() == 0) {
return additional;
}
// there are derivatives, we need to take them into account in the additional state
final FieldArrayDictionary<T> shifted = new FieldArrayDictionary<T>(additional);
for (final FieldArrayDictionary<T>.Entry dotEntry : additionalDot.getData()) {
final FieldArrayDictionary<T>.Entry entry = shifted.getEntry(dotEntry.getKey());
if (entry != null) {
entry.scaledIncrement(dt, dotEntry);
}
}
return shifted;
}
/** Shift additional states.
* @param dt time shift in seconds
* @return shifted additional states
* @since 11.1.1
*/
private FieldArrayDictionary<T> shiftAdditional(final T dt) {
// fast handling when there are no derivatives at all
if (additionalDot.size() == 0) {
return additional;
}
// there are derivatives, we need to take them into account in the additional state
final FieldArrayDictionary<T> shifted = new FieldArrayDictionary<T>(additional);
for (final FieldArrayDictionary<T>.Entry dotEntry : additionalDot.getData()) {
final FieldArrayDictionary<T>.Entry entry = shifted.getEntry(dotEntry.getKey());
if (entry != null) {
entry.scaledIncrement(dt, dotEntry);
}
}
return shifted;
}
/** {@inheritDoc}
* <p>
* The additional states that are interpolated are the ones already present
......
......@@ -627,13 +627,38 @@ public class SpacecraftState
public SpacecraftState shiftedBy(final double dt) {
if (absPva == null) {
return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
mass, additional, additionalDot);
mass, shiftAdditional(dt), additionalDot);
} else {
return new SpacecraftState(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
mass, additional, additionalDot);
mass, shiftAdditional(dt), additionalDot);
}
}
/** Shift additional states.
* @param dt time shift in seconds
* @return shifted additional states
* @since 11.1.1
*/
private DoubleArrayDictionary shiftAdditional(final double dt) {
// fast handling when there are no derivatives at all
if (additionalDot.size() == 0) {
return additional;
}
// there are derivatives, we need to take them into account in the additional state
final DoubleArrayDictionary shifted = new DoubleArrayDictionary(additional);
for (final DoubleArrayDictionary.Entry dotEntry : additionalDot.getData()) {
final DoubleArrayDictionary.Entry entry = shifted.getEntry(dotEntry.getKey());
if (entry != null) {
entry.scaledIncrement(dt, dotEntry);
}
}
return shifted;
}
/** {@inheritDoc}
* <p>
* The additional states that are interpolated are the ones already present
......
......@@ -39,7 +39,7 @@ import org.orekit.time.TimeScale;
*/
public class MagneticFieldDetector extends AbstractDetector<MagneticFieldDetector> {
/** Fixed threshold value of Magnetic field to be crossed. */
/** Fixed threshold value of Magnetic field to be crossed, in nano Teslas. */
private final double limit;
/** Fixed altitude of computed magnetic field value. */
......@@ -68,7 +68,7 @@ public class MagneticFieldDetector extends AbstractDetector<MagneticFieldDetecto
*
* <p>This method uses the {@link DataContext#getDefault() default data context}.
*
* @param limit the threshold value of magnetic field at see level
* @param limit the threshold value of magnetic field at see level, in nano Teslas
* @param type the magnetic field model
* @param body the body
* @exception OrekitIllegalArgumentException if orbit type is {@link OrbitType#CARTESIAN}
......@@ -87,7 +87,7 @@ public class MagneticFieldDetector extends AbstractDetector<MagneticFieldDetecto
*
* <p>This method uses the {@link DataContext#getDefault() default data context}.
*
* @param limit the threshold value of magnetic field at see level
* @param limit the threshold value of magnetic field at see level, in nano Teslas
* @param type the magnetic field model
* @param body the body
* @param seaLevel true if the magnetic field intensity is computed at the sea level, false if it is computed at satellite altitude
......@@ -106,7 +106,7 @@ public class MagneticFieldDetector extends AbstractDetector<MagneticFieldDetecto
*
* @param maxCheck maximal checking interval (s)
* @param threshold convergence threshold (s)
* @param limit the threshold value of magnetic field at see level
* @param limit the threshold value of magnetic field at see level, in nano Teslas
* @param type the magnetic field model
* @param body the body
* @param seaLevel true if the magnetic field intensity is computed at the sea level, false if it is computed at satellite altitude
......@@ -126,7 +126,7 @@ public class MagneticFieldDetector extends AbstractDetector<MagneticFieldDetecto
*
* @param maxCheck maximal checking interval (s)
* @param threshold convergence threshold (s)
* @param limit the threshold value of magnetic field at see level
* @param limit the threshold value of magnetic field at see level, in nano Teslas
* @param type the magnetic field model
* @param body the body
* @param seaLevel true if the magnetic field intensity is computed at the sea
......@@ -157,7 +157,7 @@ public class MagneticFieldDetector extends AbstractDetector<MagneticFieldDetecto
* @param threshold convergence threshold (s)
* @param maxIter maximum number of iterations in the event time search
* @param handler event handler to call at event occurrences
* @param limit the threshold value of magnetic field at see level
* @param limit the threshold value of magnetic field at see level, in nano Teslas
* @param type the magnetic field model
* @param body the body
* @param seaLevel true if the magnetic field intensity is computed at the sea level, false if it is computed at satellite altitude
......
......@@ -275,6 +275,23 @@ public class DoubleArrayDictionary implements Serializable {
}
}