1   /* Copyright 2022-2025 Luc Maisonobe
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.orbits;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  
22  import org.hipparchus.geometry.euclidean.threed.Rotation;
23  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
24  import org.hipparchus.geometry.euclidean.threed.Vector3D;
25  import org.hipparchus.util.FastMath;
26  import org.hipparchus.util.MathUtils;
27  import org.orekit.errors.OrekitException;
28  import org.orekit.errors.OrekitMessages;
29  import org.orekit.utils.PVCoordinates;
30  
31  /** Builder for orbits of satellites forming a Walker constellation.
32   * <p>
33   * It  manages the 2 patterns:
34   * <ul>
35   * <li>Delta, with ascending nodes distributed over 360°</li>
36   * <li>Star, with ascending nodes distributed over 180°</li>
37   * </ul>
38   * @author Luc Maisonobe
39   * @since 12.1
40   */
41  public class WalkerConstellation {
42  
43      /** Total number of satellites. */
44      private final int t;
45  
46      /** Number of orbital planes. */
47      private final int p;
48  
49      /** Phasing parameter. */
50      private final int f;
51  
52      /** Constellation pattern. */
53      private final Pattern pattern;
54  
55      /** Default constructor for Walker Delta constellation.
56       * @param t total number of satellites
57       * @param p number of orbital planes
58       * @param f phasing parameter
59       */
60      public WalkerConstellation(final int t, final int p, final int f) {
61          this(t, p, f, Pattern.DELTA);
62      }
63  
64      /** Complete constructor with the choice of the pattern.
65       * @param t       total number of satellites
66       * @param p       number of orbital planes
67       * @param f       phasing parameter
68       * @param pattern constellation pattern
69       */
70      public WalkerConstellation(final int t, final int p, final int f, final Pattern pattern) {
71          this.t       = t;
72          this.p       = p;
73          this.f       = f;
74          this.pattern = pattern;
75          if (t % p != 0) {
76              throw new OrekitException(OrekitMessages.WALKER_INCONSISTENT_PLANES, p, t);
77          }
78      }
79  
80      /** Get the total number of satellites.
81       * @return total number of satellites
82       */
83      public int getT() {
84          return t;
85      }
86  
87      /** Get the number of orbital planes.
88       * @return number of orbital planes
89       */
90      public int getP() {
91          return p;
92      }
93  
94      /** Get the phasing parameter.
95       * @return phasing parameter
96       */
97      public int getF() {
98          return f;
99      }
100 
101     /** Get the constellation pattern.
102      * @return constellation pattern
103      */
104     public Pattern getPattern() {
105         return pattern;
106     }
107 
108     /** Create the regular slots.
109      * <p>
110      * This method builds the {@link #getT() T} regular satellite, with
111      * integer {@link WalkerConstellationSlot#getSatellite() satellite indices}. If
112      * additional in-orbit spare satellites must be created, the {@link
113      * #buildSlot(WalkerConstellationSlot, int, double) buildSlot} method must be called
114      * explicitly.
115      * </p>
116      * <p>
117      * The various orbits are built from the {@code referenceOrbit} using plane
118      * rotations and {@link Orbit#shiftedBy(double) shifts}. This implies that
119      * if orbit does not include non-Keplerian derivatives, a
120      * simple Keplerian motion is assumed, which is the intended use case.
121      * </p>
122      * @param <O> type of the orbits
123      * @param referenceOrbit orbit of the reference satellite, in
124      * {@link WalkerConstellationSlot#getPlane() plane} 0 and
125      * at {@link WalkerConstellationSlot#getSatellite()} satellite index} 0
126      * @return built orbits as a list of list, organized by planes
127      * @see #buildReferenceSlot(Orbit)
128      * @see #buildSlot(WalkerConstellationSlot, int, double)
129      */
130     public <O extends Orbit> List<List<WalkerConstellationSlot<O>>> buildRegularSlots(final O referenceOrbit) {
131 
132         // build the reference slot
133         final WalkerConstellationSlot<O> referenceSlot = buildReferenceSlot(referenceOrbit);
134 
135         final List<List<WalkerConstellationSlot<O>>> all = new ArrayList<>(p);
136         for (int plane = 0; plane < p; ++plane) {
137 
138             // prepare list for one plane
139             final List<WalkerConstellationSlot<O>> planeSlots = new ArrayList<>(t / p);
140 
141             // build all slots belonging to this plane
142             for (int satellite = 0; satellite < t / p; ++satellite) {
143                 planeSlots.add(plane == 0 && satellite == 0 ?
144                                referenceSlot :
145                                buildSlot(referenceSlot, plane, satellite));
146             }
147 
148             // finished plane
149             all.add(planeSlots);
150 
151         }
152 
153         // return the complete constellation
154         return all;
155 
156     }
157 
158     /** Create the reference slot, which is satellite 0 in plane 0.
159      * @param <O> type of the orbits
160      * @param referenceOrbit orbit of the reference satellite, in
161      * {@link WalkerConstellationSlot#getPlane() plane} 0 and
162      * at {@link WalkerConstellationSlot#getSatellite()} satellite index} 0
163      * @return build reference slot
164      * @see #buildRegularSlots(Orbit)
165      * @see #buildSlot(WalkerConstellationSlot, int, double)
166      */
167     public <O extends Orbit> WalkerConstellationSlot<O>buildReferenceSlot(final O referenceOrbit) {
168         return new WalkerConstellationSlot<>(this, 0, 0, referenceOrbit);
169     }
170 
171     /** Create one offset slot from an already existing slot.
172      * @param <O> type of the orbits
173      * @param existingSlot existing slot (may be the {@link #buildReferenceSlot(Orbit) reference slot} or not)
174      * @param plane plane index of the new slot (may be non-integer for in-orbit spare satellites)
175      * @param satellite new slot satellite index in plane (may be non-integer if needed)
176      * @return built slot
177      * @see #buildRegularSlots(Orbit)
178      * @see #buildReferenceSlot(Orbit)
179      */
180     public <O extends Orbit> WalkerConstellationSlot<O> buildSlot(final WalkerConstellationSlot<O> existingSlot,
181                                                                   final int plane, final double satellite) {
182 
183         // offsets from existing slot
184         final O      refOrbit = existingSlot.getOrbit();
185         final int    dp       = plane - existingSlot.getPlane();
186         final double ds       = satellite - existingSlot.getSatellite();
187 
188         // in plane shift
189         final double deltaT = (dp * f + ds * p) * refOrbit.getKeplerianPeriod() / t;
190         final Orbit shifted = refOrbit.shiftedBy(deltaT);
191 
192         // plane rotation
193         final Rotation      r       = new Rotation(Vector3D.PLUS_K,
194                                                    pattern.getRaanDistribution() * dp / p,
195                                                    RotationConvention.VECTOR_OPERATOR);
196         final PVCoordinates pv      = shifted.getPVCoordinates();
197         final PVCoordinates rotated = new PVCoordinates(r.applyTo(pv.getPosition()),
198                                                         r.applyTo(pv.getVelocity()));
199 
200         // build orbit
201         final CartesianOrbit c = new CartesianOrbit(rotated, refOrbit.getFrame(),
202                                                     refOrbit.getDate(), refOrbit.getMu());
203         @SuppressWarnings("unchecked")
204         final O orbit = (O) refOrbit.getType().convertType(c);
205 
206         // build slot
207         return new WalkerConstellationSlot<>(this, plane, satellite, orbit);
208 
209     }
210 
211     /**
212      * Enumerate for Walker constellation design patterns.
213      */
214     public enum Pattern {
215 
216         /** Delta pattern: ascending nodes distributed over 360°. */
217         DELTA {
218 
219             /** {@inheritDoc} */
220             @Override
221             public double getRaanDistribution() {
222                 return MathUtils.TWO_PI;
223             }
224         },
225 
226         /** Star pattern: ascending nodes distributed over 180°. */
227         STAR {
228 
229             /** {@inheritDoc} */
230             @Override
231             public double getRaanDistribution() {
232                 return FastMath.PI;
233             }
234         };
235 
236         /** Get the RAAN distribution for the pattern.
237          * @return the RAAN distribution for the pattern
238          */
239         public abstract double getRaanDistribution();
240     }
241 }