WalkerConstellation.java

/* Copyright 2002-2024 Luc Maisonobe
 * 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.orbits;

import org.hipparchus.geometry.euclidean.threed.Rotation;
import org.hipparchus.geometry.euclidean.threed.RotationConvention;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.util.MathUtils;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.utils.PVCoordinates;

import java.util.ArrayList;
import java.util.List;

/** Builder for orbits of satellites forming a Walker constellation.
 * @author Luc Maisonobe
 * @since 12.1
 */
public class WalkerConstellation {

    /** Total number of satellites. */
    private final int t;

    /** Number of orbital planes. */
    private final int p;

    /** Phasing parameter. */
    private final int f;

    /** Simple constructor.
     * @param t total number of satellites
     * @param p number of orbital planes
     * @param f phasing parameter
     */
    public WalkerConstellation(final int t, final int p, final int f) {
        this.t = t;
        this.p = p;
        this.f = f;
        if (t % p != 0) {
            throw new OrekitException(OrekitMessages.WALKER_INCONSISTENT_PLANES, p, t);
        }
    }

    /** Get the total number of satellites.
     * @return total number of satellites
     */
    public int getT() {
        return t;
    }

    /** Get the number of orbital planes.
     * @return number of orbital planes
     */
    public int getP() {
        return p;
    }

    /** Get the phasing parameter.
     * @return phasing parameter
     */
    public int getF() {
        return f;
    }

    /** Create the regular slots.
     * <p>
     * This method builds the {@link #getT() T} regular satellite, with
     * integer {@link WalkerConstellationSlot#getSatellite() satellite indices}. If
     * additional in-orbit spare satellites must be created, the {@link
     * #buildSlot(WalkerConstellationSlot, int, double) buildSlot} method must be called
     * explicitly.
     * </p>
     * <p>
     * The various orbits are built from the {@code referenceOrbit} using plane
     * rotations and {@link Orbit#shiftedBy(double) shifts}. This implies that
     * if orbit does not include {@link Orbit#hasDerivatives() derivatives}, a
     * simple Keplerian motion is assumed, which is the intended use case.
     * </p>
     * @param <O> type of the orbits
     * @param referenceOrbit orbit of the reference satellite, in
     * {@link WalkerConstellationSlot#getPlane() plane} 0 and
     * at {@link WalkerConstellationSlot#getSatellite()} satellite index} 0
     * @return built orbits as a list of list, organized by planes
     * @see #buildReferenceSlot(Orbit)
     * @see #buildSlot(WalkerConstellationSlot, int, double)
     */
    public <O extends Orbit> List<List<WalkerConstellationSlot<O>>> buildRegularSlots(final O referenceOrbit) {

        // build the reference slot
        final WalkerConstellationSlot<O> referenceSlot = buildReferenceSlot(referenceOrbit);

        final List<List<WalkerConstellationSlot<O>>> all = new ArrayList<>(p);
        for (int plane = 0; plane < p; ++plane) {

            // prepare list for one plane
            final List<WalkerConstellationSlot<O>> planeSlots = new ArrayList<>(t / p);

            // build all slots belonging to this plane
            for (int satellite = 0; satellite < t / p; ++satellite) {
                planeSlots.add(plane == 0 && satellite == 0 ?
                               referenceSlot :
                               buildSlot(referenceSlot, plane, satellite));
            }

            // finished plane
            all.add(planeSlots);

        }

        // return the complete constellation
        return all;

    }

    /** Create the reference slot, which is satellite 0 in plane 0.
     * @param <O> type of the orbits
     * @param referenceOrbit orbit of the reference satellite, in
     * {@link WalkerConstellationSlot#getPlane() plane} 0 and
     * at {@link WalkerConstellationSlot#getSatellite()} satellite index} 0
     * @return build reference slot
     * @see #buildRegularSlots(Orbit)
     * @see #buildSlot(WalkerConstellationSlot, int, double)
     */
    public <O extends Orbit> WalkerConstellationSlot<O>buildReferenceSlot(final O referenceOrbit) {
        return new WalkerConstellationSlot<>(this, 0, 0, referenceOrbit);
    }

    /** Create one offset slot from an already existing slot.
     * @param <O> type of the orbits
     * @param existingSlot existing slot (may be the {@link #buildReferenceSlot(Orbit) reference slot} or not)
     * @param plane plane index of the new slot (may be non-integer for in-orbit spare satellites)
     * @param satellite new slot satellite index in plane (may be non-integer if needed)
     * @return built slot
     * @see #buildRegularSlots(Orbit)
     * @see #buildReferenceSlot(Orbit)
     */
    public <O extends Orbit> WalkerConstellationSlot<O> buildSlot(final WalkerConstellationSlot<O> existingSlot,
                                                                  final int plane, final double satellite) {

        // offsets from existing slot
        final O      refOrbit = existingSlot.getOrbit();
        final int    dp       = plane - existingSlot.getPlane();
        final double ds       = satellite - existingSlot.getSatellite();

        // in plane shift
        final double deltaT = (dp * f + ds * p) * refOrbit.getKeplerianPeriod() / t;
        final Orbit shifted = refOrbit.shiftedBy(deltaT);

        // plane rotation
        final Rotation      r       = new Rotation(Vector3D.PLUS_K,
                                                   MathUtils.TWO_PI * dp / p,
                                                   RotationConvention.VECTOR_OPERATOR);
        final PVCoordinates pv      = shifted.getPVCoordinates();
        final PVCoordinates rotated = new PVCoordinates(r.applyTo(pv.getPosition()),
                                                        r.applyTo(pv.getVelocity()));

        // build orbit
        final CartesianOrbit c = new CartesianOrbit(rotated, refOrbit.getFrame(),
                                                    refOrbit.getDate(), refOrbit.getMu());
        @SuppressWarnings("unchecked")
        final O orbit = (O) refOrbit.getType().convertType(c);

        // build slot
        return new WalkerConstellationSlot<>(this, plane, satellite, orbit);

    }

}