1 /* Copyright 2002-2026 CS GROUP
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.frames;
18
19 import org.hipparchus.Field;
20 import org.hipparchus.CalculusFieldElement;
21 import org.hipparchus.analysis.UnivariateFunction;
22 import org.hipparchus.analysis.solvers.BracketingNthOrderBrentSolver;
23 import org.hipparchus.analysis.solvers.UnivariateSolver;
24 import org.hipparchus.exception.MathRuntimeException;
25 import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
26 import org.hipparchus.geometry.euclidean.threed.Rotation;
27 import org.hipparchus.geometry.euclidean.threed.Vector3D;
28 import org.hipparchus.util.FastMath;
29 import org.hipparchus.util.FieldSinCos;
30 import org.hipparchus.util.MathUtils;
31 import org.hipparchus.util.SinCos;
32 import org.orekit.bodies.BodyShape;
33 import org.orekit.bodies.FieldGeodeticPoint;
34 import org.orekit.bodies.GeodeticPoint;
35 import org.orekit.errors.OrekitException;
36 import org.orekit.time.AbsoluteDate;
37 import org.orekit.time.FieldAbsoluteDate;
38 import org.orekit.utils.Constants;
39 import org.orekit.utils.ExtendedPositionProvider;
40 import org.orekit.utils.FieldPVCoordinates;
41 import org.orekit.utils.FieldTrackingCoordinates;
42 import org.orekit.utils.PVCoordinates;
43 import org.orekit.utils.TimeStampedFieldPVCoordinates;
44 import org.orekit.utils.TimeStampedPVCoordinates;
45 import org.orekit.utils.TrackingCoordinates;
46
47
48 /** Topocentric frame.
49 * <p>Frame associated to a position near the surface of a body shape.</p>
50 * <p>
51 * The origin of the frame is at the defining {@link GeodeticPoint geodetic point}
52 * location, and the right-handed canonical trihedra is:
53 * </p>
54 * <ul>
55 * <li>X axis in the local horizontal plane (normal to zenith direction) and
56 * following the local parallel towards East</li>
57 * <li>Y axis in the horizontal plane (normal to zenith direction) and
58 * following the local meridian towards North</li>
59 * <li>Z axis towards Zenith direction</li>
60 * </ul>
61 * @author Véronique Pommier-Maurussane
62 * @see org.orekit.utils.GeodeticExtendedPositionProvider
63 */
64 public class TopocentricFrame extends Frame implements ExtendedPositionProvider {
65
66 /** Body shape on which the local point is defined. */
67 private final BodyShape parentShape;
68
69 /** Geodetic point where the topocentric frame is defined. */
70 private final GeodeticPoint point;
71
72 /** Cartesian point where the topocentric frame is defined.
73 * @since 12.0
74 */
75 private final Vector3D cartesianPoint;
76
77 /** Simple constructor.
78 * @param parentShape body shape on which the local point is defined
79 * @param point local surface point where topocentric frame is defined
80 * @param name the string representation
81 */
82 public TopocentricFrame(final BodyShape parentShape, final GeodeticPoint point,
83 final String name) {
84
85 super(parentShape.getBodyFrame(),
86 new Transform(AbsoluteDate.ARBITRARY_EPOCH,
87 new Transform(AbsoluteDate.ARBITRARY_EPOCH,
88 parentShape.transform(point).negate()),
89 new Transform(AbsoluteDate.ARBITRARY_EPOCH,
90 new Rotation(point.getEast(), point.getZenith(),
91 Vector3D.PLUS_I, Vector3D.PLUS_K),
92 Vector3D.ZERO)),
93 name, false);
94 this.parentShape = parentShape;
95 this.point = point;
96 this.cartesianPoint = getTransformProvider().
97 getStaticTransform(AbsoluteDate.ARBITRARY_EPOCH).
98 getInverse().
99 transformPosition(Vector3D.ZERO);
100 }
101
102 /** Get the body shape on which the local point is defined.
103 * @return body shape on which the local point is defined
104 */
105 public BodyShape getParentShape() {
106 return parentShape;
107 }
108
109 /** Get the surface point defining the origin of the frame.
110 * @return surface point defining the origin of the frame
111 */
112 public GeodeticPoint getPoint() {
113 return point;
114 }
115
116 /** Get the surface point defining the origin of the frame.
117 * @return surface point defining the origin of the frame in body frame
118 * @since 12.0
119 */
120 public Vector3D getCartesianPoint() {
121 return cartesianPoint;
122 }
123
124 /** Get the surface point defining the origin of the frame.
125 * @param <T> type of the elements
126 * @param field of the elements
127 * @return surface point defining the origin of the frame
128 * @since 9.3
129 */
130 public <T extends CalculusFieldElement<T>> FieldGeodeticPoint<T> getPoint(final Field<T> field) {
131 return new FieldGeodeticPoint<>(field, point);
132 }
133
134 /** Get the zenith direction of topocentric frame, expressed in parent shape frame.
135 * <p>The zenith direction is defined as the normal to local horizontal plane.</p>
136 * @return unit vector in the zenith direction
137 * @see #getNadir()
138 */
139 public Vector3D getZenith() {
140 return point.getZenith();
141 }
142
143 /** Get the nadir direction of topocentric frame, expressed in parent shape frame.
144 * <p>The nadir direction is the opposite of zenith direction.</p>
145 * @return unit vector in the nadir direction
146 * @see #getZenith()
147 */
148 public Vector3D getNadir() {
149 return point.getNadir();
150 }
151
152 /** Get the north direction of topocentric frame, expressed in parent shape frame.
153 * <p>The north direction is defined in the horizontal plane
154 * (normal to zenith direction) and following the local meridian.</p>
155 * @return unit vector in the north direction
156 * @see #getSouth()
157 */
158 public Vector3D getNorth() {
159 return point.getNorth();
160 }
161
162 /** Get the south direction of topocentric frame, expressed in parent shape frame.
163 * <p>The south direction is the opposite of north direction.</p>
164 * @return unit vector in the south direction
165 * @see #getNorth()
166 */
167 public Vector3D getSouth() {
168 return point.getSouth();
169 }
170
171 /** Get the east direction of topocentric frame, expressed in parent shape frame.
172 * <p>The east direction is defined in the horizontal plane
173 * in order to complete direct triangle (east, north, zenith).</p>
174 * @return unit vector in the east direction
175 * @see #getWest()
176 */
177 public Vector3D getEast() {
178 return point.getEast();
179 }
180
181 /** Get the west direction of topocentric frame, expressed in parent shape frame.
182 * <p>The west direction is the opposite of east direction.</p>
183 * @return unit vector in the west direction
184 * @see #getEast()
185 */
186 public Vector3D getWest() {
187 return point.getWest();
188 }
189
190 /** Get the tracking coordinates of a point with regards to the local point.
191 * @param extPoint point for which elevation shall be computed
192 * @param frame frame in which the point is defined
193 * @param date computation date
194 * @return tracking coordinates of the point
195 * @since 12.0
196 */
197 public TrackingCoordinates getTrackingCoordinates(final Vector3D extPoint, final Frame frame,
198 final AbsoluteDate date) {
199
200 // transform given point from given frame to topocentric frame
201 final Vector3D extPointTopo = transformPoint(extPoint, frame, date);
202
203 final double azimuth = computeAzimuthFromTopoPoint(extPointTopo);
204
205 return new TrackingCoordinates(azimuth, extPointTopo.getDelta(), extPointTopo.getNorm());
206
207 }
208
209 /** Get the tracking coordinates of a point with regards to the local point.
210 * @param <T> type of the field elements
211 * @param extPoint point for which elevation shall be computed
212 * @param frame frame in which the point is defined
213 * @param date computation date
214 * @return tracking coordinates of the point
215 * @since 12.0
216 */
217 public <T extends CalculusFieldElement<T>> FieldTrackingCoordinates<T> getTrackingCoordinates(final FieldVector3D<T> extPoint,
218 final Frame frame,
219 final FieldAbsoluteDate<T> date) {
220
221 // Transform given point from given frame to topocentric frame
222 final FieldVector3D<T> extPointTopo = transformPoint(extPoint, frame, date);
223
224 final T azimuth = computeAzimuthFromTopoPoint(extPointTopo);
225
226 return new FieldTrackingCoordinates<>(azimuth, extPointTopo.getDelta(), extPointTopo.getNorm());
227
228 }
229
230 /** Get the elevation of a point with regards to the local point.
231 * <p>The elevation is the angle between the local horizontal and
232 * the direction from local point to given point.</p>
233 * @param extPoint point for which elevation shall be computed
234 * @param frame frame in which the point is defined
235 * @param date computation date
236 * @return elevation of the point
237 */
238 public double getElevation(final Vector3D extPoint, final Frame frame,
239 final AbsoluteDate date) {
240
241 // Transform given point from given frame to topocentric frame
242 final Vector3D extPointTopo = transformPoint(extPoint, frame, date);
243
244 return extPointTopo.getDelta();
245 }
246
247 /** Get the elevation of a point with regards to the local point.
248 * <p>The elevation is the angle between the local horizontal and
249 * the direction from local point to given point.</p>
250 * @param <T> type of the elements
251 * @param extPoint point for which elevation shall be computed
252 * @param frame frame in which the point is defined
253 * @param date computation date
254 * @return elevation of the point
255 * @since 9.3
256 */
257 public <T extends CalculusFieldElement<T>> T getElevation(final FieldVector3D<T> extPoint, final Frame frame,
258 final FieldAbsoluteDate<T> date) {
259
260 // Transform given point from given frame to topocentric frame
261 final FieldVector3D<T> extPointTopo = transformPoint(extPoint, frame, date);
262
263 return extPointTopo.getDelta();
264 }
265
266 /** Get the azimuth of a point with regards to the topocentric frame center point.
267 * <p>The azimuth is the angle between the North direction at local point and
268 * the projection in local horizontal plane of the direction from local point
269 * to given point. Azimuth angles are counted clockwise, i.e positive towards the East.</p>
270 * @param extPoint point for which elevation shall be computed
271 * @param frame frame in which the point is defined
272 * @param date computation date
273 * @return azimuth of the point
274 */
275 public double getAzimuth(final Vector3D extPoint, final Frame frame,
276 final AbsoluteDate date) {
277
278 // Transform given point from given frame to topocentric frame
279 final Vector3D extPointTopo = transformPoint(extPoint, frame, date);
280
281 return computeAzimuthFromTopoPoint(extPointTopo);
282
283 }
284
285 /** Get the azimuth of a point with regards to the topocentric frame center point.
286 * <p>The azimuth is the angle between the North direction at local point and
287 * the projection in local horizontal plane of the direction from local point
288 * to given point. Azimuth angles are counted clockwise, i.e positive towards the East.</p>
289 * @param <T> type of the elements
290 * @param extPoint point for which elevation shall be computed
291 * @param frame frame in which the point is defined
292 * @param date computation date
293 * @return azimuth of the point
294 * @since 9.3
295 */
296 public <T extends CalculusFieldElement<T>> T getAzimuth(final FieldVector3D<T> extPoint, final Frame frame,
297 final FieldAbsoluteDate<T> date) {
298
299 // Transform given point from given frame to topocentric frame
300 final FieldVector3D<T> extPointTopo = transformPoint(extPoint, frame, date);
301
302 return computeAzimuthFromTopoPoint(extPointTopo);
303
304 }
305
306 /** Get the range of a point with regards to the topocentric frame center point.
307 * @param extPoint point for which range shall be computed
308 * @param frame frame in which the point is defined
309 * @param date computation date
310 * @return range (distance) of the point
311 */
312 public double getRange(final Vector3D extPoint, final Frame frame,
313 final AbsoluteDate date) {
314
315 // Transform given point from given frame to topocentric frame
316 final Vector3D extPointTopo = transformPoint(extPoint, frame, date);
317
318 return extPointTopo.getNorm();
319
320 }
321
322 /** Get the range of a point with regards to the topocentric frame center point.
323 * @param <T> type of the elements
324 * @param extPoint point for which range shall be computed
325 * @param frame frame in which the point is defined
326 * @param date computation date
327 * @return range (distance) of the point
328 * @since 9.3
329 */
330 public <T extends CalculusFieldElement<T>> T getRange(final FieldVector3D<T> extPoint, final Frame frame,
331 final FieldAbsoluteDate<T> date) {
332
333 // Transform given point from given frame to topocentric frame
334 final FieldVector3D<T> extPointTopo = transformPoint(extPoint, frame, date);
335
336 return extPointTopo.getNorm();
337
338 }
339
340 /** Get the range rate of a point with regards to the topocentric frame center point.
341 * @param extPV point/velocity for which range rate shall be computed
342 * @param frame frame in which the point is defined
343 * @param date computation date
344 * @return range rate of the point (positive if point departs from frame)
345 */
346 public double getRangeRate(final PVCoordinates extPV, final Frame frame,
347 final AbsoluteDate date) {
348
349 // Transform given point from given frame to topocentric frame
350 final KinematicTransform t = frame.getKinematicTransformTo(this, date);
351 final PVCoordinates extPVTopo = t.transformOnlyPV(extPV);
352
353 // Compute range rate (doppler) : relative rate along the line of sight
354 return Vector3D.dotProduct(extPVTopo.getPosition(), extPVTopo.getVelocity()) /
355 extPVTopo.getPosition().getNorm();
356
357 }
358
359 /** Get the range rate of a point with regards to the topocentric frame center point.
360 * @param <T> type of the elements
361 * @param extPV point/velocity for which range rate shall be computed
362 * @param frame frame in which the point is defined
363 * @param date computation date
364 * @return range rate of the point (positive if point departs from frame)
365 * @since 9.3
366 */
367 public <T extends CalculusFieldElement<T>> T getRangeRate(final FieldPVCoordinates<T> extPV, final Frame frame,
368 final FieldAbsoluteDate<T> date) {
369
370 // Transform given point from given frame to topocentric frame
371 final FieldKinematicTransform<T> t = frame.getKinematicTransformTo(this, date);
372 final FieldPVCoordinates<T> extPVTopo = t.transformOnlyPV(extPV);
373
374 // Compute range rate (doppler) : relative rate along the line of sight
375 return FieldVector3D.dotProduct(extPVTopo.getPosition(), extPVTopo.getVelocity()).divide(
376 extPVTopo.getPosition().getNorm());
377
378 }
379
380 /**
381 * Compute the limit visibility point for a satellite in a given direction.
382 * <p>
383 * This method can be used to compute visibility circles around ground stations
384 * for example, using a simple loop on azimuth, with either a fixed elevation
385 * or an elevation that depends on azimuth to take ground masks into account.
386 * </p>
387 * @param radius satellite distance to Earth center
388 * @param azimuth pointing azimuth from station
389 * @param elevation pointing elevation from station
390 * @return limit visibility point for the satellite
391 */
392 public GeodeticPoint computeLimitVisibilityPoint(final double radius,
393 final double azimuth, final double elevation) {
394 try {
395 // convergence threshold on point position: 1mm
396 final double deltaP = 0.001;
397 final UnivariateSolver solver =
398 new BracketingNthOrderBrentSolver(deltaP / Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
399 deltaP, deltaP, 5);
400
401 // find the distance such that a point in the specified direction and at the solved-for
402 // distance is exactly at the specified radius
403 final double distance = solver.solve(1000, new UnivariateFunction() {
404 /** {@inheritDoc} */
405 public double value(final double x) {
406 final GeodeticPoint gp = pointAtDistance(azimuth, elevation, x);
407 return parentShape.transform(gp).getNorm() - radius;
408 }
409 }, 0, 2 * radius);
410
411 // return the limit point
412 return pointAtDistance(azimuth, elevation, distance);
413
414 } catch (MathRuntimeException mrte) {
415 throw new OrekitException(mrte);
416 }
417 }
418
419 /** Compute the point observed from the station at some specified distance.
420 * @param azimuth pointing azimuth from station
421 * @param elevation pointing elevation from station
422 * @param distance distance to station
423 * @return observed point
424 */
425 public GeodeticPoint pointAtDistance(final double azimuth, final double elevation,
426 final double distance) {
427 final SinCos scAz = FastMath.sinCos(azimuth);
428 final SinCos scEl = FastMath.sinCos(elevation);
429 final Vector3D observed = new Vector3D(distance * scEl.cos() * scAz.sin(),
430 distance * scEl.cos() * scAz.cos(),
431 distance * scEl.sin());
432 return parentShape.transform(observed, this, AbsoluteDate.ARBITRARY_EPOCH);
433 }
434
435 /** {@inheritDoc} */
436 @Override
437 public Vector3D getPosition(final AbsoluteDate date, final Frame frame) {
438 return getStaticTransformTo(frame, date).transformPosition(Vector3D.ZERO);
439 }
440
441 /** {@inheritDoc} */
442 @Override
443 public <T extends CalculusFieldElement<T>> FieldVector3D<T> getPosition(final FieldAbsoluteDate<T> date,
444 final Frame frame) {
445 return getStaticTransformTo(frame, date).transformPosition(Vector3D.ZERO);
446 }
447
448 /** {@inheritDoc} */
449 @Override
450 public Vector3D getVelocity(final AbsoluteDate date, final Frame frame) {
451 return getKinematicTransformTo(frame, date).transformOnlyPV(PVCoordinates.ZERO).getVelocity();
452 }
453
454 /** {@inheritDoc} */
455 @Override
456 public <T extends CalculusFieldElement<T>> FieldVector3D<T> getVelocity(final FieldAbsoluteDate<T> date, final Frame frame) {
457 return getKinematicTransformTo(frame, date).transformOnlyPV(FieldPVCoordinates.getZero(date.getField())).getVelocity();
458 }
459
460 /** {@inheritDoc} */
461 @Override
462 public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame) {
463 return getTransformTo(frame, date).transformPVCoordinates(new TimeStampedPVCoordinates(date, PVCoordinates.ZERO));
464 }
465
466 /** {@inheritDoc} */
467 @Override
468 public <T extends CalculusFieldElement<T>> TimeStampedFieldPVCoordinates<T> getPVCoordinates(final FieldAbsoluteDate<T> date,
469 final Frame frame) {
470 return getTransformTo(frame, date).transformPVCoordinates(new TimeStampedFieldPVCoordinates<>(date, FieldPVCoordinates.getZero(date.getField())));
471 }
472
473 /** Get the topocentric position from {@link TrackingCoordinates}.
474 * @param coords The coordinates that are to be converted.
475 * @return The topocentric coordinates.
476 * @since 12.1
477 */
478 public static Vector3D getTopocentricPosition(final TrackingCoordinates coords) {
479 return getTopocentricPosition(coords.getAzimuth(), coords.getElevation(), coords.getRange());
480 }
481
482 /** Get the topocentric position from {@link FieldTrackingCoordinates}.
483 * @param coords The coordinates that are to be converted.
484 * @param <T> Type of the field coordinates.
485 * @return The topocentric coordinates.
486 * @since 12.1
487 */
488 public static <T extends CalculusFieldElement<T>> FieldVector3D<T> getTopocentricPosition(final FieldTrackingCoordinates<T> coords) {
489 return getTopocentricPosition(coords.getAzimuth(), coords.getElevation(), coords.getRange());
490 }
491
492 /**
493 * Gets the topocentric position from a set of az/el/ra coordinates.
494 * @param azimuth the angle of rotation around the vertical axis, going East.
495 * @param elevation the elevation angle from the local horizon.
496 * @param range the distance from the goedetic position.
497 * @return the topocentric position.
498 * @since 12.1
499 */
500 private static Vector3D getTopocentricPosition(final double azimuth, final double elevation, final double range) {
501 final SinCos sinCosAz = FastMath.sinCos(azimuth);
502 final SinCos sinCosEL = FastMath.sinCos(elevation);
503 return new Vector3D(range * sinCosEL.cos() * sinCosAz.sin(), range * sinCosEL.cos() * sinCosAz.cos(), range * sinCosEL.sin());
504 }
505
506 /**
507 * Gets the topocentric position from a set of az/el/ra coordinates.
508 * @param azimuth the angle of rotation around the vertical axis, going East.
509 * @param elevation the elevation angle from the local horizon.
510 * @param range the distance from the geodetic position.
511 * @return the topocentric position.
512 * @param <T> the type of the az/el/ra coordinates.
513 * @since 12.1
514 */
515 private static <T extends CalculusFieldElement<T>> FieldVector3D<T> getTopocentricPosition(final T azimuth, final T elevation, final T range) {
516 final FieldSinCos<T> sinCosAz = FastMath.sinCos(azimuth);
517 final FieldSinCos<T> sinCosEl = FastMath.sinCos(elevation);
518 return new FieldVector3D<>(
519 range.multiply(sinCosEl.cos()).multiply(sinCosAz.sin()),
520 range.multiply(sinCosEl.cos()).multiply(sinCosAz.cos()),
521 range.multiply(sinCosEl.sin())
522 );
523 }
524
525 /** Transform point in topocentric frame.
526 * @param extPoint point
527 * @param date current date
528 * @param frame the frame where to define the position
529 * @return transformed point in topocentric frame
530 */
531 private Vector3D transformPoint(final Vector3D extPoint, final Frame frame, final AbsoluteDate date) {
532 final StaticTransform t = frame.getStaticTransformTo(this, date);
533 return t.transformPosition(extPoint);
534 }
535
536 /** Transform point in topocentric frame.
537 * @param <T> type of the field elements
538 * @param extPoint point
539 * @param date current date
540 * @param frame the frame where to define the position
541 * @return transformed point in topocentric frame
542 */
543 private <T extends CalculusFieldElement<T>> FieldVector3D<T> transformPoint(final FieldVector3D<T> extPoint,
544 final Frame frame,
545 final FieldAbsoluteDate<T> date) {
546 final FieldStaticTransform<T> t = frame.getStaticTransformTo(this, date);
547 return t.transformPosition(extPoint);
548 }
549
550 /** Compute azimuth from topocentric point.
551 * @param extPointTopo topocentric point
552 * @return azimuth
553 */
554 private double computeAzimuthFromTopoPoint(final Vector3D extPointTopo) {
555 final double azimuth = FastMath.atan2(extPointTopo.getX(), extPointTopo.getY());
556 if (azimuth < 0.0) {
557 return azimuth + MathUtils.TWO_PI;
558 } else {
559 return azimuth;
560 }
561 }
562
563 /** Compute azimuth from topocentric point.
564 * @param <T> type of the field elements
565 * @param extPointTopo topocentric point
566 * @return azimuth
567 */
568 private <T extends CalculusFieldElement<T>> T computeAzimuthFromTopoPoint(final FieldVector3D<T> extPointTopo) {
569 final T azimuth = FastMath.atan2(extPointTopo.getX(), extPointTopo.getY());
570 if (azimuth.getReal() < 0.0) {
571 return azimuth.add(MathUtils.TWO_PI);
572 } else {
573 return azimuth;
574 }
575 }
576
577 }