WaypointPVBuilder.java

  1. /* Copyright 2002-2024 Joseph Reed
  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.  * Joseph Reed 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.utils;

  18. import java.util.Map.Entry;
  19. import java.util.TreeMap;

  20. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  21. import org.hipparchus.geometry.spherical.twod.Circle;
  22. import org.hipparchus.geometry.spherical.twod.S2Point;
  23. import org.hipparchus.util.FastMath;
  24. import org.orekit.bodies.GeodeticPoint;
  25. import org.orekit.bodies.LoxodromeArc;
  26. import org.orekit.bodies.OneAxisEllipsoid;
  27. import org.orekit.frames.Frame;
  28. import org.orekit.frames.TopocentricFrame;
  29. import org.orekit.time.AbsoluteDate;

  30. /** Builder class, enabling incremental building of an {@link PVCoordinatesProvider}
  31.  * instance using waypoints defined on an ellipsoid.
  32.  *
  33.  * Given a series of waypoints ({@code (date, point)} tuples),
  34.  * build a {@link PVCoordinatesProvider} representing the path.
  35.  * The static methods provide implementations for the most common path definitions
  36.  * (cartesian, great-circle, loxodrome). If these methods are insufficient,
  37.  * the public constructor provides a way to customize the path definition.
  38.  *
  39.  * This class connects the path segments using the {@link AggregatedPVCoordinatesProvider}.
  40.  * As such, no effort is made to smooth the velocity between segments.
  41.  * While position is unaffected, the velocity may be discontinuous between adjacent time points.
  42.  * Thus, care should be taken when modeling paths with abrupt direction changes
  43.  * (e.g. fast-moving aircraft); understand how the {@link PVCoordinatesProvider}
  44.  * will be used in the particular application.
  45.  *
  46.  * @author Joe Reed
  47.  * @since 11.3
  48.  */
  49. public class WaypointPVBuilder {

  50.     /** Factory used to create intermediate pv providers between waypoints. */
  51.     private final InterpolationFactory factory;

  52.     /** Central body, on which the waypoints are defined. */
  53.     private final OneAxisEllipsoid body;

  54.     /** Set of waypoints, indexed by time. */
  55.     private final TreeMap<AbsoluteDate, GeodeticPoint> waypoints;

  56.     /** Whether the resulting provider should be invalid or constant prior to the first waypoint. */
  57.     private boolean invalidBefore;

  58.     /** Whether the resulting provider should be invalid or constant after to the last waypoint. */
  59.     private boolean invalidAfter;

  60.     /** Create a new instance.
  61.      * @param factory The factory used to create the intermediate coordinate providers between waypoints.
  62.      * @param body The central body, on which the way points are defined.
  63.      */
  64.     public WaypointPVBuilder(final InterpolationFactory factory, final OneAxisEllipsoid body) {
  65.         this.factory       = factory;
  66.         this.body          = body;
  67.         this.waypoints     = new TreeMap<>();
  68.         this.invalidBefore = true;
  69.         this.invalidAfter  = true;
  70.     }

  71.     /** Construct a waypoint builder interpolating points using a linear cartesian interpolation.
  72.      *
  73.      * @param body the reference ellipsoid on which the waypoints are defined.
  74.      * @return the waypoint builder
  75.      */
  76.     public static WaypointPVBuilder cartesianBuilder(final OneAxisEllipsoid body) {
  77.         return new WaypointPVBuilder(CartesianWaypointPVProv::new, body);
  78.     }

  79.     /** Construct a waypoint builder interpolating points using a loxodrome (or Rhumbline).
  80.      *
  81.      * @param body the reference ellipsoid on which the waypoints are defined.
  82.      * @return the waypoint builder
  83.      */
  84.     public static WaypointPVBuilder loxodromeBuilder(final OneAxisEllipsoid body) {
  85.         return new WaypointPVBuilder(LoxodromeWaypointPVProv::new, body);
  86.     }

  87.     /** Construct a waypoint builder interpolating points using a great-circle.
  88.      *
  89.      * The altitude of the intermediate points is linearly interpolated from the bounding waypoints.
  90.      * Extrapolating before the first waypoint or after the last waypoint may result in undefined altitudes.
  91.      *
  92.      * @param body the reference ellipsoid on which the waypoints are defined.
  93.      * @return the waypoint builder
  94.      */
  95.     public static WaypointPVBuilder greatCircleBuilder(final OneAxisEllipsoid body) {
  96.         return new WaypointPVBuilder(GreatCircleWaypointPVProv::new, body);
  97.     }

  98.     /** Add a waypoint.
  99.      *
  100.      * @param point the waypoint location
  101.      * @param date the waypoint time
  102.      * @return this instance
  103.      */
  104.     public WaypointPVBuilder addWaypoint(final GeodeticPoint point, final AbsoluteDate date) {
  105.         waypoints.put(date, point);
  106.         return this;
  107.     }

  108.     /** Indicate the resulting {@link PVCoordinatesProvider} should be invalid before the first waypoint.
  109.      *
  110.      * @return this instance
  111.      */
  112.     public WaypointPVBuilder invalidBefore() {
  113.         invalidBefore = true;
  114.         return this;
  115.     }

  116.     /** Indicate the resulting {@link PVCoordinatesProvider} provide
  117.      * a constant location of the first waypoint prior to the first time.
  118.      *
  119.      * @return this instance
  120.      */
  121.     public WaypointPVBuilder constantBefore() {
  122.         invalidBefore = false;
  123.         return this;
  124.     }

  125.     /** Indicate the resulting {@link PVCoordinatesProvider} should be invalid after the last waypoint.
  126.      *
  127.      * @return this instance
  128.      */
  129.     public WaypointPVBuilder invalidAfter() {
  130.         invalidAfter = true;
  131.         return this;
  132.     }

  133.     /** Indicate the resulting {@link PVCoordinatesProvider} provide
  134.      * a constant location of the last waypoint after to the last time.
  135.      *
  136.      * @return this instance
  137.      */
  138.     public WaypointPVBuilder constantAfter() {
  139.         invalidAfter = false;
  140.         return this;
  141.     }

  142.     /** Build a {@link PVCoordinatesProvider} from the waypoints added to this builder.
  143.      *
  144.      * @return the coordinates provider instance.
  145.      */
  146.     public PVCoordinatesProvider build() {
  147.         final PVCoordinatesProvider initialProvider = createInitial(waypoints.firstKey(),
  148.                                                                     waypoints.firstEntry().getValue());
  149.         final AggregatedPVCoordinatesProvider.Builder builder = new AggregatedPVCoordinatesProvider.Builder(initialProvider);

  150.         Entry<AbsoluteDate, GeodeticPoint> previousEntry = null;
  151.         for (final Entry<AbsoluteDate, GeodeticPoint> entry: waypoints.entrySet()) {
  152.             if (previousEntry != null) {
  153.                 builder.addPVProviderAfter(previousEntry.getKey(),
  154.                                            factory.create(previousEntry.getKey(),
  155.                                                           previousEntry.getValue(),
  156.                                                           entry.getKey(),
  157.                                                           entry.getValue(),
  158.                                                           body),
  159.                                            true);
  160.             }
  161.             previousEntry = entry;
  162.         }
  163.         // add the point so we're valid at the final waypoint
  164.         builder.addPVProviderAfter(previousEntry.getKey(),
  165.                                    new ConstantPVCoordinatesProvider(previousEntry.getValue(), body),
  166.                                    true);
  167.         // add the final propagator after the final waypoint
  168.         builder.addPVProviderAfter(previousEntry.getKey().shiftedBy(Double.MIN_VALUE),
  169.                                    createFinal(previousEntry.getKey(), previousEntry.getValue()),
  170.                                    true);

  171.         return builder.build();
  172.     }

  173.     /** Create the initial provider.
  174.      *
  175.      * This method uses the internal {@code validBefore} flag to
  176.      * either return an invalid PVCoordinatesProvider or a constant one.
  177.      *
  178.      * @param firstDate the date at which the first waypoint is reached
  179.      *                  and this provider will no longer be called
  180.      * @param firstPoint the first waypoint
  181.      * @return the coordinate provider
  182.      */
  183.     protected PVCoordinatesProvider createInitial(final AbsoluteDate firstDate, final GeodeticPoint firstPoint) {
  184.         if (invalidBefore) {
  185.             return new AggregatedPVCoordinatesProvider.InvalidPVProvider();
  186.         }
  187.         else {
  188.             return new ConstantPVCoordinatesProvider(firstPoint, body);
  189.         }
  190.     }

  191.     /** Create the final provider.
  192.      *
  193.      * This method uses the internal {@code validAfter} flag to
  194.      * either return an invalid PVCoordinatesProvider or a constant one.
  195.      *
  196.      * @param lastDate the date at which the last waypoint is reached
  197.      *                 and this provider will be called
  198.      * @param lastPoint the last waypoint
  199.      * @return the coordinate provider
  200.      */
  201.     protected PVCoordinatesProvider createFinal(final AbsoluteDate lastDate, final GeodeticPoint lastPoint) {
  202.         if (invalidAfter) {
  203.             return new AggregatedPVCoordinatesProvider.InvalidPVProvider();
  204.         }
  205.         else {
  206.             return new ConstantPVCoordinatesProvider(lastPoint, body);
  207.         }
  208.     }

  209.     /**
  210.      * Factory interface, creating the {@link PVCoordinatesProvider} instances between the provided waypoints.
  211.      */
  212.     @FunctionalInterface
  213.     public interface InterpolationFactory {

  214.         /** Create a {@link PVCoordinatesProvider} which interpolates between the provided waypoints.
  215.          *
  216.          * @param date1 the first waypoint's date
  217.          * @param point1 the first waypoint's location
  218.          * @param date2 the second waypoint's date
  219.          * @param point2 the second waypoint's location
  220.          * @param body the body on which the waypoints are defined
  221.          * @return a {@link PVCoordinatesProvider} providing the locations at times between the waypoints.
  222.          */
  223.         PVCoordinatesProvider create(AbsoluteDate date1, GeodeticPoint point1,
  224.                                      AbsoluteDate date2, GeodeticPoint point2,
  225.                                      OneAxisEllipsoid body);
  226.     }

  227.     /**
  228.      * Coordinate provider interpolating along the great-circle between two points.
  229.      */
  230.     static class GreatCircleWaypointPVProv implements PVCoordinatesProvider {

  231.         /** Great circle estimation. */
  232.         private final Circle circle;
  233.         /** Duration between the two points (seconds). */
  234.         private final double duration;
  235.         /** Phase along the circle of the first point. */
  236.         private final double phase0;
  237.         /** Phase length from the first point to the second. */
  238.         private final double phaseLength;
  239.         /** Time at which interpolation results in the initial point. */
  240.         private final AbsoluteDate t0;
  241.         /** Body on which the great circle is defined. */
  242.         private final OneAxisEllipsoid body;
  243.         /** Phase of one second. */
  244.         private double oneSecondPhase;
  245.         /** Altitude of the initial point. */
  246.         private double initialAltitude;
  247.         /** Time-derivative of the altitude. */
  248.         private double altitudeSlope;

  249.         /** Class constructor. Aligns to the {@link InterpolationFactory} functional interface.
  250.          *
  251.          * @param date1 the first waypoint's date
  252.          * @param point1 the first waypoint's location
  253.          * @param date2 the second waypoint's date
  254.          * @param point2 the second waypoint's location
  255.          * @param body the body on which the waypoints are defined
  256.          * @see InterpolationFactory
  257.          */
  258.         GreatCircleWaypointPVProv(final AbsoluteDate date1, final GeodeticPoint point1,
  259.                                   final AbsoluteDate date2, final GeodeticPoint point2,
  260.                                   final OneAxisEllipsoid body) {
  261.             this.t0 = date1;
  262.             this.duration = date2.durationFrom(date1);
  263.             this.body = body;
  264.             final S2Point s0 = toSpherical(point1);
  265.             final S2Point s1 = toSpherical(point2);
  266.             circle = new Circle(s0, s1, 1e-9);

  267.             phase0 = circle.getPhase(s0.getVector());
  268.             phaseLength = circle.getPhase(s1.getVector()) - phase0;

  269.             oneSecondPhase = phaseLength / duration;
  270.             altitudeSlope = (point2.getAltitude() - point1.getAltitude()) / duration;
  271.             initialAltitude = point1.getAltitude();
  272.         }

  273.         @Override
  274.         public Vector3D getPosition(final AbsoluteDate date, final Frame frame) {
  275.             final double d = date.durationFrom(t0);
  276.             final double fraction = d / duration;
  277.             final double phase = fraction * phaseLength;

  278.             final S2Point sp = new S2Point(circle.getPointAt(phase0 + phase));
  279.             final GeodeticPoint point = toGeodetic(sp, initialAltitude + d * altitudeSlope);
  280.             final Vector3D p = body.transform(point);

  281.             return body.getBodyFrame().getStaticTransformTo(frame, date).transformPosition(p);

  282.         }

  283.         @Override
  284.         public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame) {
  285.             final double d = date.durationFrom(t0);
  286.             final double fraction = d / duration;
  287.             final double phase = fraction * phaseLength;

  288.             final S2Point sp = new S2Point(circle.getPointAt(phase0 + phase));
  289.             final GeodeticPoint point = toGeodetic(sp, initialAltitude + d * altitudeSlope);
  290.             final Vector3D p = body.transform(point);

  291.             // add 1 second to get another point along the circle, to use for velocity
  292.             final S2Point sp2 = new S2Point(circle.getPointAt(phase0 + phase + oneSecondPhase));
  293.             final GeodeticPoint point2 = toGeodetic(sp2, initialAltitude + (d + 1) * altitudeSlope);
  294.             final Vector3D p2 = body.transform(point2);
  295.             final Vector3D v = p2.subtract(p);

  296.             final TimeStampedPVCoordinates tpv = new TimeStampedPVCoordinates(date, p, v);
  297.             return body.getBodyFrame().getTransformTo(frame, date).transformPVCoordinates(tpv);
  298.         }

  299.         /** Converts the given geodetic point to a point on the 2-sphere.
  300.          * @param point input geodetic point
  301.          * @return a point on the 2-sphere
  302.          */
  303.         static S2Point toSpherical(final GeodeticPoint point) {
  304.             return new S2Point(point.getLongitude(), 0.5 * FastMath.PI - point.getLatitude());
  305.         }

  306.         /** Converts a 2-sphere point to a geodetic point.
  307.          * @param point point on the 2-sphere
  308.          * @param alt point altitude
  309.          * @return a geodetic point
  310.          */
  311.         static GeodeticPoint toGeodetic(final S2Point point, final double alt) {
  312.             return new GeodeticPoint(0.5 * FastMath.PI - point.getPhi(), point.getTheta(), alt);
  313.         }
  314.     }

  315.     /**
  316.      * Coordinate provider interpolating along the loxodrome between two points.
  317.      */
  318.     static class LoxodromeWaypointPVProv implements PVCoordinatesProvider {

  319.         /** Arc along which the interpolation occurs. */
  320.         private final LoxodromeArc arc;
  321.         /** Time at which the interpolation begins (at arc start). */
  322.         private final AbsoluteDate t0;
  323.         /** Total duration to get the length of the arc (seconds). */
  324.         private final double duration;
  325.         /** Velocity along the arc (m/s). */
  326.         private final double velocity;

  327.         /** Class constructor. Aligns to the {@link InterpolationFactory} functional interface.
  328.          *
  329.          * @param date1 the first waypoint's date
  330.          * @param point1 the first waypoint's location
  331.          * @param date2 the second waypoint's date
  332.          * @param point2 the second waypoint's location
  333.          * @param body the body on which the waypoints are defined
  334.          * @see InterpolationFactory
  335.          */
  336.         LoxodromeWaypointPVProv(final AbsoluteDate date1, final GeodeticPoint point1, final AbsoluteDate date2,
  337.                 final GeodeticPoint point2, final OneAxisEllipsoid body) {
  338.             this.arc = new LoxodromeArc(point1, point2, body);
  339.             this.t0 = date1;
  340.             this.duration = date2.durationFrom(date1);
  341.             this.velocity = arc.getDistance() / duration;
  342.         }

  343.         @Override
  344.         public Vector3D getPosition(final AbsoluteDate date, final Frame frame) {
  345.             final double fraction = date.durationFrom(t0) / duration;
  346.             final GeodeticPoint point = arc.calculatePointAlongArc(fraction);
  347.             final Vector3D p = arc.getBody().transform(point);

  348.             return arc.getBody().getBodyFrame().getStaticTransformTo(frame, date).transformPosition(p);
  349.         }

  350.         @Override
  351.         public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame) {
  352.             final double fraction = date.durationFrom(t0) / duration;
  353.             final GeodeticPoint point = arc.calculatePointAlongArc(fraction);
  354.             final Vector3D p = arc.getBody().transform(point);
  355.             final Vector3D vp = arc.getBody().transform(
  356.                     new TopocentricFrame(arc.getBody(), point, "frame")
  357.                         .pointAtDistance(arc.getAzimuth(), 0, velocity));

  358.             final TimeStampedPVCoordinates tpv = new TimeStampedPVCoordinates(date, p, vp.subtract(p));
  359.             return arc.getBody().getBodyFrame().getTransformTo(frame, date).transformPVCoordinates(tpv);
  360.         }
  361.     }

  362.     /**
  363.      * Coordinate provider interpolating along the cartesian (3-space) line between two points.
  364.      */
  365.     static class CartesianWaypointPVProv implements PVCoordinatesProvider {

  366.         /** Date at which the position is valid. */
  367.         private final AbsoluteDate t0;
  368.         /** Initial point. */
  369.         private final Vector3D p0;
  370.         /** Velocity. */
  371.         private final Vector3D vel;
  372.         /** Frame in which the point and velocity are defined. */
  373.         private final Frame sourceFrame;

  374.         /** Class constructor. Aligns to the {@link InterpolationFactory} functional interface.
  375.          *
  376.          * @param date1 the first waypoint's date
  377.          * @param point1 the first waypoint's location
  378.          * @param date2 the second waypoint's date
  379.          * @param point2 the second waypoint's location
  380.          * @param body the body on which the waypoints are defined
  381.          * @see InterpolationFactory
  382.          */
  383.         CartesianWaypointPVProv(final AbsoluteDate date1, final GeodeticPoint point1,
  384.                                 final AbsoluteDate date2, final GeodeticPoint point2,
  385.                                 final OneAxisEllipsoid body) {
  386.             this.t0 = date1;
  387.             this.p0 = body.transform(point1);
  388.             this.vel = body.transform(point2).subtract(p0).scalarMultiply(1. / date2.durationFrom(t0));
  389.             this.sourceFrame = body.getBodyFrame();
  390.         }

  391.         @Override
  392.         public Vector3D getPosition(final AbsoluteDate date, final Frame frame) {
  393.             final double d = date.durationFrom(t0);
  394.             final Vector3D p = p0.add(vel.scalarMultiply(d));
  395.             return sourceFrame.getStaticTransformTo(frame, date).transformPosition(p);
  396.         }

  397.         @Override
  398.         public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame) {
  399.             final double d = date.durationFrom(t0);
  400.             final Vector3D p = p0.add(vel.scalarMultiply(d));
  401.             final TimeStampedPVCoordinates pv = new TimeStampedPVCoordinates(date, p, vel);
  402.             return sourceFrame.getTransformTo(frame, date).transformPVCoordinates(pv);
  403.         }

  404.     }
  405. }