AggregatedPVCoordinatesProvider.java

/* Copyright 2002-2024 Joseph Reed
 * 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.
 * Joseph Reed 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.utils;

import java.util.Objects;

import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.frames.Frame;
import org.orekit.time.AbsoluteDate;

/** Aggregate multiple {@link PVCoordinatesProvider} instances together.
 * <p>
 * This can be used to describe an aircraft or surface vehicle.
 * </p>
 * @author Joe Reed
 * @since 11.3
 */
public class AggregatedPVCoordinatesProvider implements PVCoordinatesProvider {

    /** Map of provider instances by transition time. */
    private final TimeSpanMap<PVCoordinatesProvider> pvProvMap;

    /** Earliest date at which {@link #getPVCoordinates(AbsoluteDate, Frame)} will return a valid result. */
    private final AbsoluteDate minDate;

    /** Latest date at which {@link #getPVCoordinates(AbsoluteDate, Frame)} will return a valid result. */
    private final AbsoluteDate maxDate;

    /** Class constructor.
     * <p>
     * Note the provided {@code map} is used directly. Modification of the
     * map after calling this constructor may result in undefined behavior.
     * </p>
     * @param map the map of {@link PVCoordinatesProvider} instances by time.
     */
    public AggregatedPVCoordinatesProvider(final TimeSpanMap<PVCoordinatesProvider> map) {
        this(map, null, null);
    }

    /** Class constructor.
     * <p>
     * Note the provided {@code map} is used directly. Modification of the
     * map after calling this constructor may result in undefined behavior.
     * </p>
     * @param map the map of {@link PVCoordinatesProvider} instances by time.
     * @param minDate the earliest valid date, {@code null} if always valid
     * @param maxDate the latest valid date, {@code null} if always valid
     */
    public AggregatedPVCoordinatesProvider(final TimeSpanMap<PVCoordinatesProvider> map,
                                           final AbsoluteDate minDate, final AbsoluteDate maxDate) {
        this.pvProvMap = Objects.requireNonNull(map, "PVCoordinatesProvider map must be non-null");
        this.minDate = minDate == null ? AbsoluteDate.PAST_INFINITY : minDate;
        this.maxDate = maxDate == null ? AbsoluteDate.FUTURE_INFINITY : maxDate;
    }

    /** Get the first date of the range.
     * @return the first date of the range
     */
    public AbsoluteDate getMinDate() {
        return minDate;
    }

    /** Get the last date of the range.
     * @return the last date of the range
     */
    public AbsoluteDate getMaxDate() {
        return maxDate;
    }

    @Override
    public Vector3D getPosition(final AbsoluteDate date, final Frame frame) {
        if (date.isBefore(minDate) || date.isAfter(maxDate)) {
            throw new OrekitException(OrekitMessages.OUT_OF_RANGE_DATE, date, minDate, maxDate);
        }
        return pvProvMap.get(date).getPosition(date, frame);
    }

    @Override
    public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame) {
        if (date.isBefore(minDate) || date.isAfter(maxDate)) {
            throw new OrekitException(OrekitMessages.OUT_OF_RANGE_DATE, date, minDate, maxDate);
        }
        return pvProvMap.get(date).getPVCoordinates(date, frame);
    }

    /**
     * Builder class for {@link AggregatedPVCoordinatesProvider}.
     */
    public static class Builder {

        /** Time span map holding the incremental values. */
        private final TimeSpanMap<PVCoordinatesProvider> pvProvMap;

        /**
         * Create a builder using the {@link InvalidPVProvider} as the initial provider.
         */
        public Builder() {
            this(new InvalidPVProvider());
        }

        /**
         * Create a builder using the provided initial provider.
         *
         * @param initialProvider the inital provider
         */
        public Builder(final PVCoordinatesProvider initialProvider) {
            pvProvMap = new TimeSpanMap<>(initialProvider);
        }

        /** Add a {@link PVCoordinatesProvider} to the collection.
         * <p>
         * The provided date is the transition time, at which this provider will be used.
         * </p>
         * @param date the transition date
         * @param pvProv the provider
         * @param erasesLater if true, the entry erases all existing transitions that are later than {@code date}
         * @return this builder instance
         * @see TimeSpanMap#addValidAfter(Object, AbsoluteDate, boolean)
         */
        public Builder addPVProviderAfter(final AbsoluteDate date,
                                          final PVCoordinatesProvider pvProv,
                                          final boolean erasesLater) {
            pvProvMap.addValidAfter(pvProv, date, erasesLater);
            return this;
        }

        /** Add a {@link PVCoordinatesProvider} to the collection.
         * <p>
         * The provided date is the final transition time, before which this provider will be used.
         * </p>
         * @param date the transition date
         * @param pvProv the provider
         * @param erasesEarlier if true, the entry erases all existing transitions that are earlier than {@code date}
         * @return this builder instance
         * @see TimeSpanMap#addValidBefore(Object, AbsoluteDate, boolean)
         */
        public Builder addPVProviderBefore(final AbsoluteDate date, final PVCoordinatesProvider pvProv, final boolean erasesEarlier) {
            pvProvMap.addValidBefore(pvProv, date, erasesEarlier);
            return this;
        }

        /** Indicate the date before which the resulting PVCoordinatesProvider is invalid.
         *
         * @param firstValidDate first date at which the resuling provider should be valid
         * @return this instance
         */
        public Builder invalidBefore(final AbsoluteDate firstValidDate) {
            pvProvMap.addValidBefore(new InvalidPVProvider(), firstValidDate, true);
            return this;
        }

        /** Indicate the date after which the resulting PVCoordinatesProvider is invalid.
         *
         * @param lastValidDate last date at which the resuling provider should be valid
         * @return this instance
         */
        public Builder invalidAfter(final AbsoluteDate lastValidDate) {
            pvProvMap.addValidAfter(new InvalidPVProvider(), lastValidDate, true);
            return this;
        }

        /** Build the aggregated PVCoordinatesProvider.
         *
         * @return the new provider instance.
         */
        public AggregatedPVCoordinatesProvider build() {
            AbsoluteDate minDate = null;
            AbsoluteDate maxDate = null;
            // check the first span
            if (pvProvMap.getFirstTransition() != null) {
                if (pvProvMap.getFirstTransition().getBefore() instanceof InvalidPVProvider) {
                    minDate = pvProvMap.getFirstTransition().getDate();
                }
            }
            if (pvProvMap.getLastTransition() != null) {
                if (pvProvMap.getLastTransition().getAfter() instanceof InvalidPVProvider) {
                    maxDate = pvProvMap.getLastTransition().getDate();
                }
            }
            return new AggregatedPVCoordinatesProvider(pvProvMap, minDate, maxDate);
        }
    }

    /** Implementation of {@link PVCoordinatesProvider} that throws an {@link OrekitException} exception.
     *
     */
    public static class InvalidPVProvider implements PVCoordinatesProvider {

        /** Empty constructor.
         * <p>
         * This constructor is not strictly necessary, but it prevents spurious
         * javadoc warnings with JDK 18 and later.
         * </p>
         * @since 12.0
         */
        public InvalidPVProvider() {
            // nothing to do
        }

        @Override
        public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame) {
            throw new OrekitException(OrekitMessages.OUT_OF_RANGE_DATE, date);
        }

    }

}