diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 914b115729deb984eac87dc43954b7100fc15c89..31e298b4c04ba3853a93ca818ddcdfe328d3d967 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -21,6 +21,9 @@
+
+ Added aggregator for bounded attitude providers.
+
Added Knocke's Earth rediffused radiation pressure force model.
diff --git a/src/main/java/org/orekit/attitudes/AggregateBoundedAttitudeProvider.java b/src/main/java/org/orekit/attitudes/AggregateBoundedAttitudeProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..b386ccd622b486382b6d55cfc665bb132e2e79cc
--- /dev/null
+++ b/src/main/java/org/orekit/attitudes/AggregateBoundedAttitudeProvider.java
@@ -0,0 +1,129 @@
+/* Copyright 2002-2020 CS GROUP
+ * Licensed to CS GROUP (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.attitudes;
+
+import java.util.Collection;
+import java.util.Map.Entry;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+import org.hipparchus.RealFieldElement;
+import org.orekit.errors.OrekitException;
+import org.orekit.errors.OrekitMessages;
+import org.orekit.frames.Frame;
+import org.orekit.time.AbsoluteDate;
+import org.orekit.time.FieldAbsoluteDate;
+import org.orekit.utils.FieldPVCoordinatesProvider;
+import org.orekit.utils.PVCoordinatesProvider;
+
+/**
+ * A {@link BoundedAttitudeProvider} that covers a larger time span from several constituent
+ * attitude providers that cover shorter time spans.
+ *
+ * @author Bryan Cazabonne
+ * @since 10.3
+ */
+public class AggregateBoundedAttitudeProvider implements BoundedAttitudeProvider {
+
+ /** Constituent attitude provider. */
+ private final NavigableMap providers;
+
+ /**
+ * Constructor.
+ * @param providers attitude providers that provide the backing data for this instance.
+ * There must be at least one attitude provider in the collection.
+ * If there are gaps between the {@link BoundedAttitudeProvider#getMaxDate()}
+ * of one attitude provider and the {@link BoundedAttitudeProvider#getMinDate()}
+ * of the next attitude provider an exception may be thrown by any method of
+ * this class at any time. If there are overlaps between the the {@link
+ * BoundedAttitudeProvider#getMaxDate()} of one attitude provider and the {@link
+ * BoundedAttitudeProvider#getMinDate()} of the next attitude provider then the
+ * attitude provider with the latest {@link BoundedAttitudeProvider#getMinDate()}
+ * is used.
+ */
+ public AggregateBoundedAttitudeProvider(final Collection extends BoundedAttitudeProvider> providers) {
+
+ // Check if the collection is empty
+ if (providers.isEmpty()) {
+ throw new OrekitException(OrekitMessages.NOT_ENOUGH_ATTITUDE_PROVIDERS);
+ }
+
+ // Initialize map
+ this.providers = new TreeMap<>();
+
+ // Loop on providers
+ for (final BoundedAttitudeProvider provider : providers) {
+ // Fill collection
+ this.providers.put(provider.getMinDate(), provider);
+ }
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Attitude getAttitude(final PVCoordinatesProvider pvProv, final AbsoluteDate date,
+ final Frame frame) {
+
+ // Get the attitude provider for the given date
+ final BoundedAttitudeProvider provider = getAttitudeProvider(date);
+
+ // Build attitude
+ return provider.getAttitude(pvProv, date, frame);
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public > FieldAttitude getAttitude(final FieldPVCoordinatesProvider pvProv,
+ final FieldAbsoluteDate date, final Frame frame) {
+
+ // Get the attitude provider for the given date
+ final BoundedAttitudeProvider provider = getAttitudeProvider(date.toAbsoluteDate());
+
+ // Build attitude
+ return provider.getAttitude(pvProv, date, frame);
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public AbsoluteDate getMinDate() {
+ return providers.firstEntry().getValue().getMinDate();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public AbsoluteDate getMaxDate() {
+ return providers.lastEntry().getValue().getMaxDate();
+ }
+
+ /**
+ * Get the attitude provider to use for the given date.
+ * @param date of query
+ * @return attitude provider to use on date.
+ */
+ private BoundedAttitudeProvider getAttitudeProvider(final AbsoluteDate date) {
+ final Entry attitudeEntry = providers.floorEntry(date);
+ if (attitudeEntry != null) {
+ return attitudeEntry.getValue();
+ } else {
+ // Let the first attitude provider throw the exception
+ return providers.firstEntry().getValue();
+ }
+ }
+
+}
diff --git a/src/main/java/org/orekit/attitudes/BoundedAttitudeProvider.java b/src/main/java/org/orekit/attitudes/BoundedAttitudeProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe637190ef55098e81b2c6d9e4721c3527668b57
--- /dev/null
+++ b/src/main/java/org/orekit/attitudes/BoundedAttitudeProvider.java
@@ -0,0 +1,42 @@
+/* Copyright 2002-2020 CS GROUP
+ * Licensed to CS GROUP (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.attitudes;
+
+import org.orekit.time.AbsoluteDate;
+
+/** This interface is intended for attitude ephemerides valid only during a time range.
+*
+* This interface provides a mean to retrieve an attitude at
+* any time within a given range. It should be implemented by attitude readers
+* based on external data files.
+*
+* @author Bryan Cazabonne
+* @since 10.3
+*/
+public interface BoundedAttitudeProvider extends AttitudeProvider {
+
+ /** Get the first date of the range.
+ * @return the first date of the range
+ */
+ AbsoluteDate getMinDate();
+
+ /** Get the last date of the range.
+ * @return the last date of the range
+ */
+ AbsoluteDate getMaxDate();
+
+}
diff --git a/src/main/java/org/orekit/errors/OrekitMessages.java b/src/main/java/org/orekit/errors/OrekitMessages.java
index 2dc8edd31162f6c84e13fddf18f1d644a7f0c9f4..e52a5581b075d5855e4b1e09fc5dba97c7f9d808 100644
--- a/src/main/java/org/orekit/errors/OrekitMessages.java
+++ b/src/main/java/org/orekit/errors/OrekitMessages.java
@@ -220,6 +220,8 @@ public enum OrekitMessages implements Localizable {
NOT_ENOUGH_GNSS_FOR_DOP("only {0} GNSS orbits are provided while {1} are needed to compute the DOP"),
NOT_ENOUGH_PROPAGATORS(
"Creating an aggregate propagator requires at least one constituent propagator, but none were provided."),
+ NOT_ENOUGH_ATTITUDE_PROVIDERS(
+ "Creating an aggregate attitude provider requires at least one constituent attitude provider, but none were provided."),
NULL_ARGUMENT("argument {0} cannot be null"), VALUE_NOT_FOUND("value {0} not found in {1}"),
EPHEMERIS_FILE_NO_MULTI_SUPPORT("Ephemeris file format does not support multiple space objects"),
KLOBUCHAR_ALPHA_BETA_NOT_LOADED("Klobuchar coefficients α or β could not be loaded from {0}"),
diff --git a/src/main/java/org/orekit/files/ccsds/AEMFile.java b/src/main/java/org/orekit/files/ccsds/AEMFile.java
index 071920a6c156fe31986f75b755089e49fa794e95..2a33b1b4d8b2ebaebc702e511d22ce3d5d649f1c 100644
--- a/src/main/java/org/orekit/files/ccsds/AEMFile.java
+++ b/src/main/java/org/orekit/files/ccsds/AEMFile.java
@@ -24,8 +24,6 @@ import java.util.Map;
import java.util.Map.Entry;
import org.hipparchus.geometry.euclidean.threed.RotationOrder;
-import org.orekit.attitudes.AttitudeProvider;
-import org.orekit.attitudes.TabulatedProvider;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.general.AttitudeEphemerisFile;
@@ -313,10 +311,8 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
}
- /**
- * Get the reference frame from which attitude is defined.
- * @return the reference frame from which attitude is defined
- */
+ /** {@inheritDoc} */
+ @Override
public Frame getReferenceFrame() {
return refFrame;
}
@@ -560,13 +556,6 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
this.rotationOrder = order;
}
- /** {@inheritDoc} */
- @Override
- public AttitudeProvider getAttitudeProvider() {
- return new TabulatedProvider(getReferenceFrame(), getAngularCoordinates(),
- getInterpolationSamples(), getAvailableDerivatives());
- }
-
}
diff --git a/src/main/java/org/orekit/files/general/AttitudeEphemerisFile.java b/src/main/java/org/orekit/files/general/AttitudeEphemerisFile.java
index 11142ce20ad8a4b990b91018505d0c2ffcabf87d..11f19ef1f11e42726497a9294c8e748d2d28c4e7 100644
--- a/src/main/java/org/orekit/files/general/AttitudeEphemerisFile.java
+++ b/src/main/java/org/orekit/files/general/AttitudeEphemerisFile.java
@@ -16,11 +16,14 @@
*/
package org.orekit.files.general;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.hipparchus.geometry.euclidean.threed.RotationOrder;
-import org.orekit.attitudes.AttitudeProvider;
+import org.orekit.attitudes.AggregateBoundedAttitudeProvider;
+import org.orekit.attitudes.BoundedAttitudeProvider;
+import org.orekit.frames.Frame;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeScale;
import org.orekit.utils.AngularDerivativesFilter;
@@ -96,6 +99,20 @@ public interface AttitudeEphemerisFile {
*/
AbsoluteDate getStop();
+ /**
+ * Get the attitude provider corresponding to this ephemeris, combining data from all {@link
+ * #getSegments() segments}.
+ *
+ * @return an attitude provider for all the data in this attitude ephemeris file.
+ */
+ default BoundedAttitudeProvider getAttitudeProvider() {
+ final List providers = new ArrayList<>();
+ for (final AttitudeEphemerisSegment attitudeSegment : this.getSegments()) {
+ providers.add(attitudeSegment.getAttitudeProvider());
+ }
+ return new AggregateBoundedAttitudeProvider(providers);
+ }
+
}
/**
@@ -141,6 +158,13 @@ public interface AttitudeEphemerisFile {
*/
String getRefFrameBString();
+ /**
+ * Get the reference frame from which attitude is defined.
+ *
+ * @return the reference frame from which attitude is defined
+ */
+ Frame getReferenceFrame();
+
/**
* Get the rotation direction of the attitude.
*
@@ -225,7 +249,9 @@ public interface AttitudeEphemerisFile {
*
* @return the attitude provider for this attitude ephemeris segment.
*/
- AttitudeProvider getAttitudeProvider();
+ default BoundedAttitudeProvider getAttitudeProvider() {
+ return new EphemerisSegmentAttitudeProvider(this);
+ }
}
diff --git a/src/main/java/org/orekit/files/general/EphemerisSegmentAttitudeProvider.java b/src/main/java/org/orekit/files/general/EphemerisSegmentAttitudeProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f45c7ce63e7bb25a2eb3785b894a7b8c9b582b2
--- /dev/null
+++ b/src/main/java/org/orekit/files/general/EphemerisSegmentAttitudeProvider.java
@@ -0,0 +1,108 @@
+/* Copyright 2002-2020 CS GROUP
+ * Licensed to CS GROUP (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.files.general;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.hipparchus.RealFieldElement;
+import org.orekit.attitudes.Attitude;
+import org.orekit.attitudes.AttitudeProvider;
+import org.orekit.attitudes.BoundedAttitudeProvider;
+import org.orekit.attitudes.FieldAttitude;
+import org.orekit.files.general.AttitudeEphemerisFile.AttitudeEphemerisSegment;
+import org.orekit.frames.Frame;
+import org.orekit.time.AbsoluteDate;
+import org.orekit.time.FieldAbsoluteDate;
+import org.orekit.utils.FieldPVCoordinatesProvider;
+import org.orekit.utils.ImmutableTimeStampedCache;
+import org.orekit.utils.PVCoordinatesProvider;
+import org.orekit.utils.TimeStampedAngularCoordinates;
+import org.orekit.utils.TimeStampedFieldAngularCoordinates;
+
+/**
+ * An {@link AttitudeProvider} based on an {@link AttitudeEphemerisSegment}.
+ * @author Bryan Cazabonne
+ * @since 10.3
+ */
+public class EphemerisSegmentAttitudeProvider implements BoundedAttitudeProvider {
+
+ /** Cached attitude table. */
+ private final transient ImmutableTimeStampedCache table;
+
+ /** Tabular data from which this attitude provider is built. */
+ private final AttitudeEphemerisSegment segment;
+
+ /**
+ * Constructor.
+ * @param segment segment containing the attitude data for this provider.
+ */
+ public EphemerisSegmentAttitudeProvider(final AttitudeEphemerisSegment segment) {
+ this.segment = segment;
+ this.table = new ImmutableTimeStampedCache(segment.getInterpolationSamples(),
+ segment.getAngularCoordinates());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Attitude getAttitude(final PVCoordinatesProvider pvProv, final AbsoluteDate date,
+ final Frame frame) {
+
+ // Get attitudes sample on which interpolation will be performed
+ final List attitudeSample = table.getNeighbors(date).collect(Collectors.toList());
+
+ // Interpolate attitude data
+ final TimeStampedAngularCoordinates interpolatedAttitude =
+ TimeStampedAngularCoordinates.interpolate(date, segment.getAvailableDerivatives(), attitudeSample);
+
+ // Build the interpolated attitude
+ return new Attitude(segment.getReferenceFrame(), interpolatedAttitude);
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public > FieldAttitude getAttitude(final FieldPVCoordinatesProvider pvProv,
+ final FieldAbsoluteDate date, final Frame frame) {
+
+ // Get attitudes sample on which interpolation will be performed
+ final List> attitudeSample = table.getNeighbors(date.toAbsoluteDate()).
+ map(ac -> new TimeStampedFieldAngularCoordinates<>(date.getField(), ac)).
+ collect(Collectors.toList());
+
+ // Interpolate attitude data
+ final TimeStampedFieldAngularCoordinates interpolatedAttitude =
+ TimeStampedFieldAngularCoordinates.interpolate(date, segment.getAvailableDerivatives(), attitudeSample);
+
+ // Build the interpolated attitude
+ return new FieldAttitude<>(segment.getReferenceFrame(), interpolatedAttitude);
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public AbsoluteDate getMinDate() {
+ return segment.getStart();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public AbsoluteDate getMaxDate() {
+ return segment.getStop();
+ }
+
+}
diff --git a/src/main/java/org/orekit/files/general/OrekitAttitudeEphemerisFile.java b/src/main/java/org/orekit/files/general/OrekitAttitudeEphemerisFile.java
index e574c7e11c2ea072301623c576b3fab0433ec448..0ff96aeb718363118f0f13103210dba88467503a 100644
--- a/src/main/java/org/orekit/files/general/OrekitAttitudeEphemerisFile.java
+++ b/src/main/java/org/orekit/files/general/OrekitAttitudeEphemerisFile.java
@@ -24,8 +24,6 @@ import java.util.concurrent.ConcurrentHashMap;
import org.hipparchus.geometry.euclidean.threed.RotationOrder;
import org.orekit.annotation.DefaultDataContext;
-import org.orekit.attitudes.AttitudeProvider;
-import org.orekit.attitudes.TabulatedProvider;
import org.orekit.bodies.CelestialBody;
import org.orekit.data.DataContext;
import org.orekit.errors.OrekitIllegalArgumentException;
@@ -518,6 +516,12 @@ public class OrekitAttitudeEphemerisFile implements AttitudeEphemerisFile {
return refFrameBString;
}
+ /** {@inheritDoc} */
+ @Override
+ public Frame getReferenceFrame() {
+ return referenceFrame;
+ }
+
/** {@inheritDoc} */
@Override
public String getAttitudeDirection() {
@@ -584,13 +588,6 @@ public class OrekitAttitudeEphemerisFile implements AttitudeEphemerisFile {
return angularDerivativesFilter;
}
- /** {@inheritDoc} */
- @Override
- public AttitudeProvider getAttitudeProvider() {
- return new TabulatedProvider(referenceFrame, getAngularCoordinates(),
- getInterpolationSamples(), getAvailableDerivatives());
- }
-
}
}
diff --git a/src/main/resources/assets/org/orekit/localization/OrekitMessages_da.utf8 b/src/main/resources/assets/org/orekit/localization/OrekitMessages_da.utf8
index 6e3d5a67c4e23617066e0817a873fc2f532c0ffa..c5d8b07b6b6483c80ba78b61357f581bb6785ee9 100644
--- a/src/main/resources/assets/org/orekit/localization/OrekitMessages_da.utf8
+++ b/src/main/resources/assets/org/orekit/localization/OrekitMessages_da.utf8
@@ -454,6 +454,9 @@ CCSDS_TIME_SYSTEM_NOT_IMPLEMENTED = brugen af tidssystemet {0} i CCSDS filer kr
# Creating an aggregate propagator requires at least one constituent propagator, but none were provided.
NOT_ENOUGH_PROPAGATORS = Oprettelsen af en aggregeret propagator kræver mindst en propagator-bestanddel, men ingen blev angivet.
+# Creating an aggregate attitude provider requires at least one constituent attitude provider, but none were provided.
+NOT_ENOUGH_ATTITUDE_PROVIDERS =
+
# argument {0} cannot be null
NULL_ARGUMENT = argument {0} kan ikke være null
diff --git a/src/main/resources/assets/org/orekit/localization/OrekitMessages_de.utf8 b/src/main/resources/assets/org/orekit/localization/OrekitMessages_de.utf8
index 1e0a91aef342b928c74f6864dfd46e5f0cb162a0..2290555fb358727024b675e4102ca28769e0535e 100644
--- a/src/main/resources/assets/org/orekit/localization/OrekitMessages_de.utf8
+++ b/src/main/resources/assets/org/orekit/localization/OrekitMessages_de.utf8
@@ -454,6 +454,9 @@ CCSDS_TIME_SYSTEM_NOT_IMPLEMENTED = Die Benutzung des Zeitsystems {0} in CCSDS D
# Creating an aggregate propagator requires at least one constituent propagator, but none were provided.
NOT_ENOUGH_PROPAGATORS = Das erstellen von AggregatePropagator benötigt zumindest einen Bestandteil des Propagator. Es wurden keine zur Verfügung gestellt
+# Creating an aggregate attitude provider requires at least one constituent attitude provider, but none were provided.
+NOT_ENOUGH_ATTITUDE_PROVIDERS =
+
# argument {0} cannot be null
NULL_ARGUMENT = Das Argument {0} kann nicht "null" sein
diff --git a/src/main/resources/assets/org/orekit/localization/OrekitMessages_el.utf8 b/src/main/resources/assets/org/orekit/localization/OrekitMessages_el.utf8
index 4cae60c0690872f9d06837beb588ae0699359364..2fae9b061756b234491fbe2278a2b6450d8e0247 100644
--- a/src/main/resources/assets/org/orekit/localization/OrekitMessages_el.utf8
+++ b/src/main/resources/assets/org/orekit/localization/OrekitMessages_el.utf8
@@ -454,6 +454,9 @@ CCSDS_TIME_SYSTEM_NOT_IMPLEMENTED =
# Creating an aggregate propagator requires at least one constituent propagator, but none were provided.
NOT_ENOUGH_PROPAGATORS =
+# Creating an aggregate attitude provider requires at least one constituent attitude provider, but none were provided.
+NOT_ENOUGH_ATTITUDE_PROVIDERS =
+
# argument {0} cannot be null
NULL_ARGUMENT =
diff --git a/src/main/resources/assets/org/orekit/localization/OrekitMessages_en.utf8 b/src/main/resources/assets/org/orekit/localization/OrekitMessages_en.utf8
index a198855c7ecad145b4813f16d38013499b0c9b1b..bb68314b76b10b6f2f514a02b49987414f07ab5a 100644
--- a/src/main/resources/assets/org/orekit/localization/OrekitMessages_en.utf8
+++ b/src/main/resources/assets/org/orekit/localization/OrekitMessages_en.utf8
@@ -454,6 +454,9 @@ CCSDS_TIME_SYSTEM_NOT_IMPLEMENTED = use of time system {0} in CCSDS files requir
# Creating an aggregate propagator requires at least one constituent propagator, but none were provided.
NOT_ENOUGH_PROPAGATORS = Creating an aggregate propagator requires at least one constituent propagator, but none were provided.
+# Creating an aggregate attitude provider requires at least one constituent attitude provider, but none were provided.
+NOT_ENOUGH_ATTITUDE_PROVIDERS = Creating an aggregate attitude provider requires at least one constituent attitude provider, but none were provided.
+
# argument {0} cannot be null
NULL_ARGUMENT = argument {0} cannot be null
diff --git a/src/main/resources/assets/org/orekit/localization/OrekitMessages_es.utf8 b/src/main/resources/assets/org/orekit/localization/OrekitMessages_es.utf8
index 460ec1b6131b58845c70433329da2fb1065e59f3..06965d346d29dfe459777cae892c9db444b7a470 100644
--- a/src/main/resources/assets/org/orekit/localization/OrekitMessages_es.utf8
+++ b/src/main/resources/assets/org/orekit/localization/OrekitMessages_es.utf8
@@ -454,6 +454,9 @@ CCSDS_TIME_SYSTEM_NOT_IMPLEMENTED = para utilizar el sistema temporal {0} en los
# Creating an aggregate propagator requires at least one constituent propagator, but none were provided.
NOT_ENOUGH_PROPAGATORS = para crear un propagador combinado se necesita al menos un propagador, pero no se ha especificado ninguno
+# Creating an aggregate attitude provider requires at least one constituent attitude provider, but none were provided.
+NOT_ENOUGH_ATTITUDE_PROVIDERS =
+
# argument {0} cannot be null
NULL_ARGUMENT = la entrada {0} no puede ser nula
diff --git a/src/main/resources/assets/org/orekit/localization/OrekitMessages_fr.utf8 b/src/main/resources/assets/org/orekit/localization/OrekitMessages_fr.utf8
index 6f15c187415459a19c7b315074283dda8c848c08..fdf9747cf5c30893c46f438bba215ce7d10bf081 100644
--- a/src/main/resources/assets/org/orekit/localization/OrekitMessages_fr.utf8
+++ b/src/main/resources/assets/org/orekit/localization/OrekitMessages_fr.utf8
@@ -454,6 +454,9 @@ CCSDS_TIME_SYSTEM_NOT_IMPLEMENTED = l''utilisation du système temporel {0} néc
# Creating an aggregate propagator requires at least one constituent propagator, but none were provided.
NOT_ENOUGH_PROPAGATORS = la création d''un propagateur combiné nécessite au moins un propagateur, mais aucun n''a été fourni
+# Creating an aggregate attitude provider requires at least one constituent attitude provider, but none were provided.
+NOT_ENOUGH_ATTITUDE_PROVIDERS = la création d''un fournisseur d''attitude combiné nécessite au moins un fournisseur d''attitude, mais aucun n''a été fourni.
+
# argument {0} cannot be null
NULL_ARGUMENT = l''argument {0} ne devrait pas être nul
diff --git a/src/main/resources/assets/org/orekit/localization/OrekitMessages_gl.utf8 b/src/main/resources/assets/org/orekit/localization/OrekitMessages_gl.utf8
index 214c4b53556c03070d13d13c9495d4dc47aa203e..49982b52534f17a5597eaeffab8840002ec9ec0e 100644
--- a/src/main/resources/assets/org/orekit/localization/OrekitMessages_gl.utf8
+++ b/src/main/resources/assets/org/orekit/localization/OrekitMessages_gl.utf8
@@ -454,6 +454,9 @@ CCSDS_TIME_SYSTEM_NOT_IMPLEMENTED =
# Creating an aggregate propagator requires at least one constituent propagator, but none were provided.
NOT_ENOUGH_PROPAGATORS =
+# Creating an aggregate attitude provider requires at least one constituent attitude provider, but none were provided.
+NOT_ENOUGH_ATTITUDE_PROVIDERS =
+
# argument {0} cannot be null
NULL_ARGUMENT =
diff --git a/src/main/resources/assets/org/orekit/localization/OrekitMessages_it.utf8 b/src/main/resources/assets/org/orekit/localization/OrekitMessages_it.utf8
index 68066662ad4ea7e753511a37930976071b495a7f..1da0b851efbe1b336b377bd432dcd2d184e52511 100644
--- a/src/main/resources/assets/org/orekit/localization/OrekitMessages_it.utf8
+++ b/src/main/resources/assets/org/orekit/localization/OrekitMessages_it.utf8
@@ -455,6 +455,9 @@ CCSDS_TIME_SYSTEM_NOT_IMPLEMENTED = l''utilizzo del sistema temporale {0} nei fi
# Creating an aggregate propagator requires at least one constituent propagator, but none were provided.
NOT_ENOUGH_PROPAGATORS = la creazione di un propagatore combinato richiede almeno un propagatore, ma non ne è stato fornito alcuno
+# Creating an aggregate attitude provider requires at least one constituent attitude provider, but none were provided.
+NOT_ENOUGH_ATTITUDE_PROVIDERS =
+
# argument {0} cannot be null
NULL_ARGUMENT = l''argomento {0} non può essere nullo
diff --git a/src/main/resources/assets/org/orekit/localization/OrekitMessages_no.utf8 b/src/main/resources/assets/org/orekit/localization/OrekitMessages_no.utf8
index 4bc7529490c1e675ab1b24bdeeb06f6b3364b319..a3cc0b1c2fa3dfe5f0593099f3856173b7e1301f 100644
--- a/src/main/resources/assets/org/orekit/localization/OrekitMessages_no.utf8
+++ b/src/main/resources/assets/org/orekit/localization/OrekitMessages_no.utf8
@@ -454,6 +454,9 @@ CCSDS_TIME_SYSTEM_NOT_IMPLEMENTED = Bruk av tidssystemet {0} i CCSDS-filer kreve
# Creating an aggregate propagator requires at least one constituent propagator, but none were provided.
NOT_ENOUGH_PROPAGATORS = En aggregat-propagatør krever minst en bestanddel propagatør, men ingen var spesifisert.
+# Creating an aggregate attitude provider requires at least one constituent attitude provider, but none were provided.
+NOT_ENOUGH_ATTITUDE_PROVIDERS =
+
# argument {0} cannot be null
NULL_ARGUMENT = argumentet {0} kan ikke være null
diff --git a/src/main/resources/assets/org/orekit/localization/OrekitMessages_ro.utf8 b/src/main/resources/assets/org/orekit/localization/OrekitMessages_ro.utf8
index 02bf22308d32baa0610b406b88531b16988b79df..7bd95f02366e15d64379e4000f75829ea05818e5 100644
--- a/src/main/resources/assets/org/orekit/localization/OrekitMessages_ro.utf8
+++ b/src/main/resources/assets/org/orekit/localization/OrekitMessages_ro.utf8
@@ -454,6 +454,9 @@ CCSDS_TIME_SYSTEM_NOT_IMPLEMENTED = utilizarea sistemului de timp {0} în fișie
# Creating an aggregate propagator requires at least one constituent propagator, but none were provided.
NOT_ENOUGH_PROPAGATORS = Crearea unui propagator combinator necesită cel puțin un propagator, dar nici unul nu a fost definit.
+# Creating an aggregate attitude provider requires at least one constituent attitude provider, but none were provided.
+NOT_ENOUGH_ATTITUDE_PROVIDERS =
+
# argument {0} cannot be null
NULL_ARGUMENT = argumentul {0} nu poate fi nul
diff --git a/src/test/java/org/orekit/attitudes/AggregateBoundedAttitudeProviderTest.java b/src/test/java/org/orekit/attitudes/AggregateBoundedAttitudeProviderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e1ba5d509267d155b47306ad21c17374ab9cdf90
--- /dev/null
+++ b/src/test/java/org/orekit/attitudes/AggregateBoundedAttitudeProviderTest.java
@@ -0,0 +1,183 @@
+/* Copyright 2002-2020 CS GROUP
+ * Licensed to CS GROUP (CS) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.attitudes;
+
+import java.io.InputStream;
+import java.util.Collections;
+
+import org.hipparchus.Field;
+import org.hipparchus.RealFieldElement;
+import org.hipparchus.geometry.euclidean.threed.FieldRotation;
+import org.hipparchus.geometry.euclidean.threed.Rotation;
+import org.hipparchus.util.Decimal64Field;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.orekit.Utils;
+import org.orekit.bodies.CelestialBodyFactory;
+import org.orekit.errors.OrekitException;
+import org.orekit.errors.OrekitMessages;
+import org.orekit.files.ccsds.AEMFile;
+import org.orekit.files.ccsds.AEMFile.AemSatelliteEphemeris;
+import org.orekit.files.ccsds.AEMParser;
+import org.orekit.time.AbsoluteDate;
+import org.orekit.time.FieldAbsoluteDate;
+import org.orekit.time.TimeScalesFactory;
+import org.orekit.utils.IERSConventions;
+
+public class AggregateBoundedAttitudeProviderTest {
+
+ @Before
+ public void setUp() {
+ Utils.setDataRoot("regular-data:ccsds");
+ }
+
+ @Test
+ public void testEmptyList() {
+ try {
+ new AggregateBoundedAttitudeProvider(Collections.emptyList());
+ } catch (OrekitException oe) {
+ Assert.assertEquals(OrekitMessages.NOT_ENOUGH_ATTITUDE_PROVIDERS, oe.getSpecifier());
+ }
+ }
+
+ @Test
+ public void testAEM() {
+
+ final String ex = "/ccsds/AEMExample10.txt";
+ final InputStream inEntry = getClass().getResourceAsStream(ex);
+ final AEMParser parser = new AEMParser().withMu(CelestialBodyFactory.getEarth().getGM()).
+ withConventions(IERSConventions.IERS_2010).
+ withSimpleEOP(true);
+ final AEMFile file = parser.parse(inEntry, "AEMExample10.txt");
+
+ final AemSatelliteEphemeris ephemeris = file.getSatellites().get("1996-062A");
+ final BoundedAttitudeProvider provider = ephemeris.getAttitudeProvider();
+
+ // Verify dates
+ Assert.assertEquals(0.0, provider.getMinDate().durationFrom(ephemeris.getStart()), 1.0e-10);
+ Assert.assertEquals(0.0, provider.getMaxDate().durationFrom(ephemeris.getStop()), 1.0e-10);
+ Assert.assertEquals(0.0, provider.getMinDate().durationFrom(ephemeris.getSegments().get(0).getStart()), 1.0e-10);
+ Assert.assertEquals(0.0, provider.getMaxDate().durationFrom(ephemeris.getSegments().get(1).getStop()), 1.0e-10);
+
+ // Verify computation with data in first segment
+ Attitude attitude = provider.getAttitude(null, new AbsoluteDate("1996-11-28T22:08:04.555", TimeScalesFactory.getUTC()), null);
+ Rotation rotation = attitude.getRotation();
+ Assert.assertEquals(0.45652, rotation.getQ0(), 0.00001);
+ Assert.assertEquals(-0.84532, rotation.getQ1(), 0.00001);
+ Assert.assertEquals(0.26974, rotation.getQ2(), 0.00001);
+ Assert.assertEquals(-0.06532, rotation.getQ3(), 0.00001);
+
+ }
+
+ @Test
+ public void testFieldAEM() {
+ doTestFieldAEM(Decimal64Field.getInstance());
+ }
+
+ private > void doTestFieldAEM(final Field field) {
+
+ final String ex = "/ccsds/AEMExample10.txt";
+ final InputStream inEntry = getClass().getResourceAsStream(ex);
+ final AEMParser parser = new AEMParser().withMu(CelestialBodyFactory.getEarth().getGM()).
+ withConventions(IERSConventions.IERS_2010).
+ withSimpleEOP(true);
+ final AEMFile file = parser.parse(inEntry, "AEMExample10.txt");
+
+ final AemSatelliteEphemeris ephemeris = file.getSatellites().get("1996-062A");
+ final BoundedAttitudeProvider provider = ephemeris.getAttitudeProvider();
+
+ // Verify dates
+ Assert.assertEquals(0.0, provider.getMinDate().durationFrom(ephemeris.getStart()), 1.0e-10);
+ Assert.assertEquals(0.0, provider.getMaxDate().durationFrom(ephemeris.getStop()), 1.0e-10);
+ Assert.assertEquals(0.0, provider.getMinDate().durationFrom(ephemeris.getSegments().get(0).getStart()), 1.0e-10);
+ Assert.assertEquals(0.0, provider.getMaxDate().durationFrom(ephemeris.getSegments().get(1).getStop()), 1.0e-10);
+
+ // Verify computation with data in first segment
+ FieldAttitude attitude = provider.getAttitude(null, new FieldAbsoluteDate<>(new AbsoluteDate("1996-11-28T22:08:04.555", TimeScalesFactory.getUTC()), field.getZero()), null);
+ FieldRotation rotation = attitude.getRotation();
+ Assert.assertEquals(0.45652, rotation.getQ0().getReal(), 0.00001);
+ Assert.assertEquals(-0.84532, rotation.getQ1().getReal(), 0.00001);
+ Assert.assertEquals(0.26974, rotation.getQ2().getReal(), 0.00001);
+ Assert.assertEquals(-0.06532, rotation.getQ3().getReal(), 0.00001);
+
+ }
+
+ @Test
+ public void testOutsideBounds() throws Exception {
+
+ final String ex = "/ccsds/AEMExample10.txt";
+ final InputStream inEntry = getClass().getResourceAsStream(ex);
+ final AEMParser parser = new AEMParser().withMu(CelestialBodyFactory.getEarth().getGM()).
+ withConventions(IERSConventions.IERS_2010).
+ withSimpleEOP(true);
+ final AEMFile file = parser.parse(inEntry, "AEMExample10.txt");
+
+ final AemSatelliteEphemeris ephemeris = file.getSatellites().get("1996-062A");
+ final BoundedAttitudeProvider provider = ephemeris.getAttitudeProvider();
+
+ // before bound of first attitude provider
+ try {
+ provider.getAttitude(null, provider.getMinDate().shiftedBy(-60.0), null);
+ } catch (OrekitException oe) {
+ Assert.assertEquals(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_BEFORE, oe.getSpecifier());
+ }
+
+ // after bound of last attitude provider
+ try {
+ provider.getAttitude(null, provider.getMaxDate().shiftedBy(60.0), null);
+ } catch (OrekitException oe) {
+ Assert.assertEquals(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_AFTER, oe.getSpecifier());
+ }
+
+ }
+
+ @Test
+ public void testFieldOutsideBounds() throws Exception {
+ doTestFieldOutsideBounds(Decimal64Field.getInstance());
+ }
+
+ private > void doTestFieldOutsideBounds(final Field field) throws Exception {
+
+ final String ex = "/ccsds/AEMExample10.txt";
+ final InputStream inEntry = getClass().getResourceAsStream(ex);
+ final AEMParser parser = new AEMParser().withMu(CelestialBodyFactory.getEarth().getGM()).
+ withConventions(IERSConventions.IERS_2010).
+ withSimpleEOP(true);
+ final AEMFile file = parser.parse(inEntry, "AEMExample10.txt");
+
+ final AemSatelliteEphemeris ephemeris = file.getSatellites().get("1996-062A");
+ final BoundedAttitudeProvider provider = ephemeris.getAttitudeProvider();
+
+ // before bound of first attitude provider
+ try {
+ provider.getAttitude(null, new FieldAbsoluteDate<>(provider.getMinDate(), field.getZero().subtract(60.0)), null);
+ } catch (OrekitException oe) {
+ Assert.assertEquals(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_BEFORE, oe.getSpecifier());
+ }
+
+ // after bound of last attitude provider
+ try {
+ provider.getAttitude(null, new FieldAbsoluteDate<>(provider.getMinDate(), field.getZero().add(60.0)), null);
+ } catch (OrekitException oe) {
+ Assert.assertEquals(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_AFTER, oe.getSpecifier());
+ }
+
+ }
+
+
+}
diff --git a/src/test/java/org/orekit/errors/OrekitMessagesTest.java b/src/test/java/org/orekit/errors/OrekitMessagesTest.java
index 7a67f50f35c2db452e2ef2b75fb43edb0dc6a2be..824db0bb19764966c9bb520482e6b653609c07cf 100644
--- a/src/test/java/org/orekit/errors/OrekitMessagesTest.java
+++ b/src/test/java/org/orekit/errors/OrekitMessagesTest.java
@@ -30,7 +30,7 @@ public class OrekitMessagesTest {
@Test
public void testMessageNumber() {
- Assert.assertEquals(213, OrekitMessages.values().length);
+ Assert.assertEquals(214, OrekitMessages.values().length);
}
@Test
diff --git a/src/test/java/org/orekit/files/ccsds/AEMParserTest.java b/src/test/java/org/orekit/files/ccsds/AEMParserTest.java
index bbbee4188964b7d9a57c78a757dab7b4a57b5dff..07134f6e47ba796f04cdd828c50698c0a0dd47ff 100644
--- a/src/test/java/org/orekit/files/ccsds/AEMParserTest.java
+++ b/src/test/java/org/orekit/files/ccsds/AEMParserTest.java
@@ -31,7 +31,7 @@ import org.junit.Before;
import org.junit.Test;
import org.orekit.Utils;
import org.orekit.attitudes.Attitude;
-import org.orekit.attitudes.AttitudeProvider;
+import org.orekit.attitudes.BoundedAttitudeProvider;
import org.orekit.bodies.CelestialBodyFactory;
import org.orekit.data.DataContext;
import org.orekit.errors.OrekitException;
@@ -474,13 +474,16 @@ public class AEMParserTest {
final AEMFile file = parser.parse(inEntry, "AEMExample8.txt");
Assert.assertEquals(FramesFactory.getEME2000(), file.getAttitudeBlocks().get(0).getReferenceFrame());
- final AttitudeProvider provider = file.getAttitudeBlocks().get(0).getAttitudeProvider();
+ final BoundedAttitudeProvider provider = file.getAttitudeBlocks().get(0).getAttitudeProvider();
Attitude attitude = provider.getAttitude(null, new AbsoluteDate("1996-11-28T22:08:03.555", TimeScalesFactory.getUTC()), null);
Rotation rotation = attitude.getRotation();
Assert.assertEquals(0.42319, rotation.getQ1(), 0.0001);
Assert.assertEquals(-0.45697, rotation.getQ2(), 0.0001);
Assert.assertEquals(0.23784, rotation.getQ3(), 0.0001);
Assert.assertEquals(0.74533, rotation.getQ0(), 0.0001);
+ Assert.assertEquals(0.0, provider.getMinDate().durationFrom(file.getAttitudeBlocks().get(0).getStart()), 0.0001);
+ Assert.assertEquals(0.0, provider.getMaxDate().durationFrom(file.getAttitudeBlocks().get(0).getStop()), 0.0001);
+
}
@Test
@@ -494,13 +497,16 @@ public class AEMParserTest {
final AEMFile file = parser.parse(inEntry, "AEMExample9.txt");
Assert.assertEquals(FramesFactory.getITRF(ITRFVersion.ITRF_93, IERSConventions.IERS_2010, true), file.getAttitudeBlocks().get(0).getReferenceFrame());
- final AttitudeProvider provider = file.getAttitudeBlocks().get(0).getAttitudeProvider();
+ final BoundedAttitudeProvider provider = file.getAttitudeBlocks().get(0).getAttitudeProvider();
Attitude attitude = provider.getAttitude(null, new AbsoluteDate("1996-11-28T22:08:03.555", TimeScalesFactory.getUTC()), null);
Rotation rotation = attitude.getRotation();
Assert.assertEquals(0.42319, rotation.getQ1(), 0.0001);
Assert.assertEquals(-0.45697, rotation.getQ2(), 0.0001);
Assert.assertEquals(0.23784, rotation.getQ3(), 0.0001);
Assert.assertEquals(0.74533, rotation.getQ0(), 0.0001);
+ Assert.assertEquals(0.0, provider.getMinDate().durationFrom(file.getAttitudeBlocks().get(0).getStart()), 0.0001);
+ Assert.assertEquals(0.0, provider.getMaxDate().durationFrom(file.getAttitudeBlocks().get(0).getStop()), 0.0001);
+
}
}
diff --git a/src/test/resources/ccsds/AEMExample10.txt b/src/test/resources/ccsds/AEMExample10.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8340f0327727d65ceea47bbbf590d81c747bec14
--- /dev/null
+++ b/src/test/resources/ccsds/AEMExample10.txt
@@ -0,0 +1,55 @@
+CCSDS_AEM_VERS = 1.0
+CREATION_DATE = 2002-11-04T17:22:31
+ORIGINATOR = NASA/JPL
+
+META_START
+COMMENT This file was produced by M.R. Somebody, MSOO NAV/JPL, 2002 OCT 04.
+COMMENT It is to be used for attitude reconstruction only. The relative accuracy of these
+COMMENT attitudes is 0.1 degrees per axis.
+OBJECT_NAME = MARS GLOBAL SURVEYOR
+OBJECT_ID = 1996-062A
+CENTER_NAME = MARS BARYCENTER
+REF_FRAME_A = EME2000
+REF_FRAME_B = SC_BODY_1
+ATTITUDE_DIR = A2B
+TIME_SYSTEM = UTC
+START_TIME = 1996-11-28T21:29:07.255
+USEABLE_START_TIME = 1996-11-28T22:08:02.555
+USEABLE_STOP_TIME = 1996-11-30T01:18:02.555
+STOP_TIME = 1996-11-30T01:28:02.555
+ATTITUDE_TYPE = QUATERNION
+QUATERNION_TYPE = LAST
+INTERPOLATION_METHOD = LINEAR
+INTERPOLATION_DEGREE = 1
+META_STOP
+
+DATA_START
+1996-11-28T21:29:07.255 0.56748 0.03146 0.45689 0.68427
+1996-11-28T22:08:03.555 0.42319 -0.45697 0.23784 0.74533
+1996-11-28T22:08:04.555 -0.84532 0.26974 -0.06532 0.45652
+1996-11-30T01:28:02.555 0.74563 -0.45375 0.36875 0.31964
+DATA_STOP
+
+META_START
+COMMENT This block begins after trajectory correction maneuver TCM-3.
+OBJECT_NAME = MARS GLOBAL SURVEYOR
+OBJECT_ID = 1996-062A
+CENTER_NAME = MARS BARYCENTER
+REF_FRAME_A = EME2000
+REF_FRAME_B = SC_BODY_1
+ATTITUDE_DIR = A2B
+TIME_SYSTEM = UTC
+START_TIME = 1996-12-18T12:05:00.555
+USEABLE_START_TIME = 1996-12-18T12:10:00.555
+USEABLE_STOP_TIME = 1996-12-28T21:23:00.555
+STOP_TIME = 1996-12-28T21:28:00.555
+ATTITUDE_TYPE = QUATERNION
+QUATERNION_TYPE = LAST
+META_STOP
+
+DATA_START
+1996-12-18T12:05:00.555 -0.64585 0.018542 -0.23854 0.72501
+1996-12-18T12:10:05.555 0.87451 -0.43475 0.13458 -0.16767
+1996-12-18T12:10:10.555 0.03125 -0.65874 0.23458 -0.71418
+1996-12-28T21:28:00.555 -0.25485 0.58745 -0.36845 0.67394
+DATA_STOP
\ No newline at end of file