LOSBuilder.java

/* Copyright 2013-2022 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.rugged.los;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

import org.hipparchus.analysis.differentiation.Derivative;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.orekit.rugged.utils.DerivativeGenerator;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.ParameterDriver;
import org.orekit.utils.ParameterObserver;

/** Builder for lines-of-sight list.
 * <p>
 * This class implements the <em>builder pattern</em> to create {@link TimeDependentLOS} instances.
 * It does so by using a <em>fluent API</em> in order to clarify reading and allow
 * later extensions with new configuration parameters.
 * </p>
 * <p>
 * This builder aims at creating lines-of-sight directions which are
 * the result of several transforms applied to an initial list of raw
 * directions. It therefore allows to take into account the optical
 * path due to mirrors and the alignments of sensors frames with respect
 * to a spacecraft.
 * </p>
 * @see TimeDependentLOS
 * @see <a href="https://en.wikipedia.org/wiki/Builder_pattern">Builder pattern (wikipedia)</a>
 * @see <a href="https://en.wikipedia.org/wiki/Fluent_interface">Fluent interface (wikipedia)</a>
 * @author Luc Maisonobe
 */
public class LOSBuilder {

    /** Raw fixed line-of-sights. */
    private final List<Vector3D> rawLOS;

    /** Transforms to be applied. */
    private final List<LOSTransform> transforms;

    /** Flag for time-independent only transforms. */
    private boolean timeIndependent;

    /** Create builder.
     * @param rawLOS raw fixed lines-of-sight
     */
    public LOSBuilder(final List<Vector3D> rawLOS) {
        this.rawLOS          = rawLOS;
        this.transforms      = new ArrayList<>();
        this.timeIndependent = true;
    }

    /** Add a transform to be applied after the already registered transforms.
     * @param transform transform to be applied to the lines-of-sight
     * @return the builder instance
     */
    public LOSBuilder addTransform(final TimeIndependentLOSTransform transform) {
        transforms.add(new TransformAdapter(transform));
        return this;
    }

    /** Add a transform to be applied after the already registered transforms.
     * @param transform transform to be applied to the lines-of-sight
     * @return the builder instance
     */
    public LOSBuilder addTransform(final LOSTransform transform) {
        transforms.add(transform);
        timeIndependent = false;
        return this;
    }

    /** Build a lines-of-sight provider.
     * @return lines-of-sight provider
     */
    public TimeDependentLOS build() {

        if (timeIndependent) {
            // fast implementation for time-independent lines-of-sight
            return new FixedLOS(rawLOS, transforms);
        } else {
            // regular implementation, for time-dependent lines-of-sight
            return new TransformsSequenceLOS(rawLOS, transforms);
        }

    }

    /** Adapter from time-independent transform to time-dependent transform. */
    private static class TransformAdapter implements LOSTransform {

        /** Underlying transform. */
        private final TimeIndependentLOSTransform transform;

        /** Simple constructor.
         * @param transform underlying time-independent transform
         */
        TransformAdapter(final TimeIndependentLOSTransform transform) {
            this.transform = transform;
        }

        /** {@inheritDoc} */
        @Override
        public Vector3D transformLOS(final int i, final Vector3D los, final AbsoluteDate date) {
            return transform.transformLOS(i, los);
        }

        /** {@inheritDoc} */
        @Override
        public <T extends Derivative<T>> FieldVector3D<T> transformLOS(final int i, final FieldVector3D<T> los,
                                                                       final AbsoluteDate date, final DerivativeGenerator<T> generator) {
            return transform.transformLOS(i, los, generator);
        }

        /** {@inheritDoc} */
        @Override
        public Stream<ParameterDriver> getParametersDrivers() {
            return transform.getParametersDrivers();
        }

    }

    /** Implement time-independent LOS by recomputing directions by applying all transforms each time. */
    private static class TransformsSequenceLOS implements TimeDependentLOS {

        /** Raw direction. */
        private final Vector3D[] raw;

        /** Transforms to be applied. */
        private final List<LOSTransform> transforms;

        /** Simple constructor.
         * @param raw raw directions
         * @param transforms transforms to apply
         */
        TransformsSequenceLOS(final List<Vector3D> raw, final List<LOSTransform> transforms) {

            // copy the lists, to ensure immutability of the built object,
            // in case addTransform is called again after build
            // or the raw LOS list is changed by caller
            this.raw = new Vector3D[raw.size()];
            for (int i = 0; i < raw.size(); ++i) {
                this.raw[i] = raw.get(i);
            }

            this.transforms = new ArrayList<>(transforms);

        }

        /** {@inheritDoc} */
        public int getNbPixels() {
            return raw.length;
        }

        /** {@inheritDoc} */
        @Override
        public Vector3D getLOS(final int index, final AbsoluteDate date) {
            Vector3D los = raw[index];
            for (final LOSTransform transform : transforms) {
                los = transform.transformLOS(index, los, date);
            }
            return los.normalize();
        }

        /** {@inheritDoc} */
        @Override
        public <T extends Derivative<T>> FieldVector3D<T> getLOSDerivatives(final int index, final AbsoluteDate date,
                                                                            final DerivativeGenerator<T> generator) {

            // the raw line of sights are considered to be constant
            FieldVector3D<T> los = new FieldVector3D<>(generator.constant(raw[index].getX()),
                                                       generator.constant(raw[index].getY()),
                                                       generator.constant(raw[index].getZ()));

            // apply the transforms, which depend on parameters and hence may introduce non-zero derivatives
            for (final LOSTransform transform : transforms) {
                los = transform.transformLOS(index, los, date, generator);
            }

            return los.normalize();

        }

        @Override
        public Stream<ParameterDriver> getParametersDrivers() {
            Stream<ParameterDriver> drivers = Stream.<ParameterDriver>empty();
            for (final LOSTransform transform : transforms) {
                drivers = Stream.concat(drivers, transform.getParametersDrivers());
            }
            return drivers;
        }

    }

    /** Implement time-independent LOS by computing directions only when parameters are changed. */
    private static class FixedLOS extends TransformsSequenceLOS {

        /** transformed direction for los. */
        private final Vector3D[] transformed;

        /** Simple constructor.
         * @param raw raw directions
         * @param transforms transforms to apply (must be time-independent!)
         */
        FixedLOS(final List<Vector3D> raw, final List<LOSTransform> transforms) {

            super(raw, transforms);
            transformed = new Vector3D[raw.size()];

            // we will reset the transforms to null when parameters are changed
            final ParameterObserver resettingObserver = new ParameterObserver() {
                /** {@inheritDoc} */
                @Override
                public void valueChanged(final double previousValue, final ParameterDriver driver) {
                    Arrays.fill(transformed, null);
                }
            };
            getParametersDrivers().forEach(driver -> {
                driver.addObserver(resettingObserver);
            });
        }

        /** {@inheritDoc} */
        @Override
        public Vector3D getLOS(final int index, final AbsoluteDate date) {
            if (transformed[index] == null) {
                // recompute the transformed los direction only if needed
                transformed[index] = super.getLOS(index, date);
            }
            return transformed[index];
        }

    }

}