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 }