1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.gnss.attitude;
18
19 import org.hipparchus.CalculusFieldElement;
20 import org.hipparchus.Field;
21 import org.hipparchus.analysis.UnivariateFunction;
22 import org.hipparchus.analysis.differentiation.FieldUnivariateDerivative2;
23 import org.hipparchus.analysis.solvers.BracketingNthOrderBrentSolver;
24 import org.hipparchus.analysis.solvers.UnivariateSolverUtils;
25 import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
26 import org.hipparchus.geometry.euclidean.threed.Vector3D;
27 import org.hipparchus.util.FastMath;
28 import org.hipparchus.util.FieldSinCos;
29 import org.hipparchus.util.SinCos;
30 import org.orekit.frames.FieldTransform;
31 import org.orekit.frames.Frame;
32 import org.orekit.frames.LOFType;
33 import org.orekit.time.AbsoluteDate;
34 import org.orekit.time.FieldAbsoluteDate;
35 import org.orekit.time.FieldTimeStamped;
36 import org.orekit.utils.ExtendedPositionProvider;
37 import org.orekit.utils.FieldPVCoordinates;
38 import org.orekit.utils.FieldPVCoordinatesProvider;
39 import org.orekit.utils.PVCoordinates;
40 import org.orekit.utils.TimeStampedFieldAngularCoordinates;
41 import org.orekit.utils.TimeStampedFieldPVCoordinates;
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 class GNSSFieldAttitudeContext<T extends CalculusFieldElement<T>> implements FieldTimeStamped<T> {
58
59
60 private static final PVCoordinates PLUS_Y_PV = new PVCoordinates(Vector3D.PLUS_J);
61
62
63 private static final PVCoordinates MINUS_Z_PV = new PVCoordinates(Vector3D.MINUS_K);
64
65
66 private static final double BETA_SIGN_CHANGE_PROTECTION = FastMath.toRadians(0.07);
67
68
69 private final FieldPVCoordinates<T> plusY;
70
71
72 private final FieldPVCoordinates<T> minusZ;
73
74
75 private final AbsoluteDate dateDouble;
76
77
78 private final FieldAbsoluteDate<T> date;
79
80
81 private final ExtendedPositionProvider sun;
82
83
84 private final FieldPVCoordinatesProvider<T> pvProv;
85
86
87
88
89 private final TimeStampedFieldPVCoordinates<T> svPV;
90
91
92 private final Frame inertialFrame;
93
94
95 private final T svbCos;
96
97
98 private final boolean morning;
99
100
101 private final FieldUnivariateDerivative2<T> delta;
102
103
104
105
106 private final FieldUnivariateDerivative2<T> beta;
107
108
109 private final T muRate;
110
111
112 private FieldTurnSpan<T> turnSpan;
113
114
115
116
117
118
119
120
121
122 GNSSFieldAttitudeContext(final FieldAbsoluteDate<T> date,
123 final ExtendedPositionProvider sun, final FieldPVCoordinatesProvider<T> pvProv,
124 final Frame inertialFrame, final FieldTurnSpan<T> turnSpan) {
125
126 final Field<T> field = date.getField();
127 plusY = new FieldPVCoordinates<>(field, PLUS_Y_PV);
128 minusZ = new FieldPVCoordinates<>(field, MINUS_Z_PV);
129
130 this.dateDouble = date.toAbsoluteDate();
131 this.date = date;
132 this.sun = sun;
133 this.pvProv = pvProv;
134 this.inertialFrame = inertialFrame;
135 final TimeStampedFieldPVCoordinates<T> sunPV = sun.getPVCoordinates(date, inertialFrame);
136 this.svPV = pvProv.getPVCoordinates(date, inertialFrame);
137 this.morning = Vector3D.dotProduct(svPV.getVelocity().toVector3D(), sunPV.getPosition().toVector3D()) >= 0.0;
138 this.muRate = svPV.getAngularVelocity().getNorm();
139 this.turnSpan = turnSpan;
140
141 final FieldPVCoordinates<FieldUnivariateDerivative2<T>> sunPVD2 = sunPV.toUnivariateDerivative2PV();
142 final FieldPVCoordinates<FieldUnivariateDerivative2<T>> svPVD2 = svPV.toUnivariateDerivative2PV();
143 final FieldUnivariateDerivative2<T> svbCosD2 = FieldVector3D.dotProduct(sunPVD2.getPosition(), svPVD2.getPosition()).
144 divide(sunPVD2.getPosition().getNorm().multiply(svPVD2.getPosition().getNorm()));
145 svbCos = svbCosD2.getValue();
146
147 beta = FieldVector3D.angle(sunPVD2.getPosition(), svPVD2.getMomentum()).negate().add(0.5 * FastMath.PI);
148
149 final FieldUnivariateDerivative2<T> absDelta;
150 if (svbCos.getReal() <= 0) {
151
152 absDelta = FastMath.acos(svbCosD2.negate().divide(FastMath.cos(beta)));
153 } else {
154
155 absDelta = FastMath.acos(svbCosD2.divide(FastMath.cos(beta)));
156 }
157 delta = absDelta.copySign(absDelta.getPartialDerivative(1).negate());
158
159 }
160
161
162
163
164
165 public TimeStampedFieldAngularCoordinates<T> nominalYaw(final FieldAbsoluteDate<T> d) {
166 final TimeStampedFieldPVCoordinates<T> pv = pvProv.getPVCoordinates(d, inertialFrame);
167 return new TimeStampedFieldAngularCoordinates<>(d,
168 pv.normalize(),
169 sun.getPVCoordinates(d, inertialFrame).crossProduct(pv).normalize(),
170 minusZ,
171 plusY,
172 1.0e-9);
173 }
174
175
176
177
178
179 public T beta(final FieldAbsoluteDate<T> d) {
180 final TimeStampedFieldPVCoordinates<T> pv = pvProv.getPVCoordinates(d, inertialFrame);
181 return FieldVector3D.angle(sun.getPosition(d, inertialFrame), pv.getMomentum()).
182 negate().
183 add(svPV.getPosition().getX().getPi().multiply(0.5));
184 }
185
186
187
188
189 public FieldUnivariateDerivative2<T> betaD2() {
190 return beta;
191 }
192
193
194 @Override
195 public FieldAbsoluteDate<T> getDate() {
196 return date;
197 }
198
199
200
201
202 public FieldTurnSpan<T> getTurnSpan() {
203 return turnSpan;
204 }
205
206
207
208
209 public T getSVBcos() {
210 return svbCos;
211 }
212
213
214
215
216
217
218
219
220
221
222 public T getSecuredBeta() {
223 return FastMath.abs(beta.getValue().getReal()) < BETA_SIGN_CHANGE_PROTECTION ?
224 beta(turnSpan.getTurnStartDate()) :
225 beta.getValue();
226 }
227
228
229
230
231
232
233 public boolean linearModelStillActive(final T linearPhi, final T phiDot) {
234 final AbsoluteDate absDate = date.toAbsoluteDate();
235 final double dt0 = turnSpan.getTurnEndDate().durationFrom(date).getReal();
236 final UnivariateFunction yawReached = dt -> {
237 final AbsoluteDate t = absDate.shiftedBy(dt);
238 final Vector3D pSun = sun.getPosition(t, inertialFrame);
239 final PVCoordinates pv = pvProv.getPVCoordinates(date.shiftedBy(dt), inertialFrame).toPVCoordinates();
240 final Vector3D pSat = pv.getPosition();
241 final Vector3D targetX = Vector3D.crossProduct(pSat, Vector3D.crossProduct(pSun, pSat)).normalize();
242
243 final double phi = linearPhi.getReal() + dt * phiDot.getReal();
244 final SinCos sc = FastMath.sinCos(phi);
245 final Vector3D pU = pv.getPosition().normalize();
246 final Vector3D mU = pv.getMomentum().normalize();
247 final Vector3D omega = new Vector3D(-phiDot.getReal(), pU);
248 final Vector3D currentX = new Vector3D(-sc.sin(), mU, -sc.cos(), Vector3D.crossProduct(pU, mU));
249 final Vector3D currentXDot = Vector3D.crossProduct(omega, currentX);
250
251 return Vector3D.dotProduct(targetX, currentXDot);
252 };
253 final double fullTurn = 2 * FastMath.PI / FastMath.abs(phiDot.getReal());
254 final double dtMin = FastMath.min(turnSpan.getTurnStartDate().durationFrom(date).getReal(), dt0 - 60.0);
255 final double dtMax = FastMath.max(dtMin + fullTurn, dt0 + 60.0);
256 double[] bracket = UnivariateSolverUtils.bracket(yawReached, dt0,
257 dtMin, dtMax, fullTurn / 100, 1.0, 100);
258 if (yawReached.value(bracket[0]) <= 0.0) {
259
260 bracket = UnivariateSolverUtils.bracket(yawReached, 0.5 * (bracket[0] + bracket[1] + fullTurn),
261 bracket[1], bracket[1] + fullTurn, fullTurn / 100, 1.0, 100);
262 }
263 final double dt = new BracketingNthOrderBrentSolver(1.0e-3, 5).
264 solve(100, yawReached, bracket[0], bracket[1]);
265 turnSpan.updateEnd(date.shiftedBy(dt), absDate);
266
267 return dt > 0.0;
268
269 }
270
271
272
273
274
275
276 public boolean setUpTurnRegion(final double cosNight, final double cosNoon) {
277 if (svbCos.getReal() < cosNight || svbCos.getReal() > cosNoon) {
278
279 return true;
280 } else {
281
282
283 return inTurnTimeRange();
284 }
285 }
286
287
288
289
290 public FieldUnivariateDerivative2<T> getDeltaDS() {
291 return delta;
292 }
293
294
295
296
297 public T getOrbitAngleSinceMidnight() {
298 final T absAngle = inOrbitPlaneAbsoluteAngle(FastMath.acos(svbCos).negate().add(svbCos.getPi()));
299 return morning ? absAngle : absAngle.negate();
300 }
301
302
303
304
305 public boolean inSunSide() {
306 return svbCos.getReal() > 0;
307 }
308
309
310
311
312
313
314
315 public T getYawStart(final T sunBeta) {
316 final T halfSpan = turnSpan.getTurnDuration().multiply(muRate).multiply(0.5);
317 return computePhi(sunBeta, FastMath.copySign(halfSpan, svbCos));
318 }
319
320
321
322
323
324
325
326 public T getYawEnd(final T sunBeta) {
327 final T halfSpan = turnSpan.getTurnDuration().multiply(muRate).multiply(0.5);
328 return computePhi(sunBeta, FastMath.copySign(halfSpan, svbCos.negate()));
329 }
330
331
332
333
334
335
336
337 public T yawRate(final T sunBeta) {
338 return getYawEnd(sunBeta).subtract(getYawStart(sunBeta)).divide(turnSpan.getTurnDuration());
339 }
340
341
342
343
344 public T getMuRate() {
345 return muRate;
346 }
347
348
349
350
351
352
353
354
355
356
357
358 public T inOrbitPlaneAbsoluteAngle(final T angle) {
359 return FastMath.acos(FastMath.cos(angle).divide(FastMath.cos(beta(getDate()))));
360 }
361
362
363
364
365
366
367
368
369
370 public T computePhi(final T sunBeta, final T inOrbitPlaneAngle) {
371 return FastMath.atan2(FastMath.tan(sunBeta).negate(), FastMath.sin(inOrbitPlaneAngle));
372 }
373
374
375
376
377
378 public void setHalfSpan(final T halfSpan, final double endMargin) {
379 final FieldAbsoluteDate<T> start = date.shiftedBy(delta.getValue().subtract(halfSpan).divide(muRate));
380 final FieldAbsoluteDate<T> end = date.shiftedBy(delta.getValue().add(halfSpan).divide(muRate));
381 final AbsoluteDate estimationDate = getDate().toAbsoluteDate();
382 if (turnSpan == null) {
383 turnSpan = new FieldTurnSpan<>(start, end, estimationDate, endMargin);
384 } else {
385 turnSpan.updateStart(start, estimationDate);
386 turnSpan.updateEnd(end, estimationDate);
387 }
388 }
389
390
391
392
393 public boolean inTurnTimeRange() {
394 return turnSpan != null && turnSpan.inTurnTimeRange(dateDouble);
395 }
396
397
398
399
400 public T timeSinceTurnStart() {
401 return getDate().durationFrom(turnSpan.getTurnStartDate());
402 }
403
404
405
406
407
408
409 public TimeStampedFieldAngularCoordinates<T> turnCorrectedAttitude(final T yaw, final T yawDot) {
410 return turnCorrectedAttitude(new FieldUnivariateDerivative2<>(yaw, yawDot, yaw.getField().getZero()));
411 }
412
413
414
415
416
417 public TimeStampedFieldAngularCoordinates<T> turnCorrectedAttitude(final FieldUnivariateDerivative2<T> yaw) {
418
419
420 final FieldVector3D<T> p = svPV.getPosition();
421 final FieldVector3D<T> v = svPV.getVelocity();
422 final FieldVector3D<T> a = svPV.getAcceleration();
423 final T r2 = p.getNorm2Sq();
424 final T r = FastMath.sqrt(r2);
425 final FieldVector3D<T> keplerianJerk = new FieldVector3D<>(FieldVector3D.dotProduct(p, v).multiply(-3).divide(r2), a,
426 a.getNorm().negate().divide(r), v);
427 final FieldPVCoordinates<T> velocity = new FieldPVCoordinates<>(v, a, keplerianJerk);
428 final FieldPVCoordinates<T> momentum = svPV.crossProduct(velocity);
429
430 final FieldSinCos<FieldUnivariateDerivative2<T>> sc = FastMath.sinCos(yaw);
431 final FieldUnivariateDerivative2<T> c = sc.cos().negate();
432 final FieldUnivariateDerivative2<T> s = sc.sin().negate();
433 final T z = yaw.getValueField().getZero();
434 final FieldVector3D<T> m0 = new FieldVector3D<>(s.getValue(), c.getValue(), z);
435 final FieldVector3D<T> m1 = new FieldVector3D<>(s.getPartialDerivative(1), c.getPartialDerivative(1), z);
436 final FieldVector3D<T> m2 = new FieldVector3D<>(s.getPartialDerivative(2), c.getPartialDerivative(2), z);
437 return new TimeStampedFieldAngularCoordinates<>(date,
438 svPV.normalize(), momentum.normalize(),
439 minusZ, new FieldPVCoordinates<>(m0, m1, m2),
440 1.0e-9);
441
442 }
443
444
445
446
447 public TimeStampedFieldAngularCoordinates<T> orbitNormalYaw() {
448 final FieldTransform<T> t = LOFType.LVLH_CCSDS.transformFromInertial(date, pvProv.getPVCoordinates(date, inertialFrame));
449 return new TimeStampedFieldAngularCoordinates<>(date,
450 t.getRotation(),
451 t.getRotationRate(),
452 t.getRotationAcceleration());
453 }
454
455 }