public static <T> Point<T> interpolate(Point<T> p1, Point<T> p2, Instant targetTime) {
        checkNotNull(p1, "Cannot perform interpolation when the first input points is null");
        checkNotNull(p2, "Cannot perform interpolation when the second input points is null");
        checkNotNull(targetTime, "Cannot perform interpolation when the targetTime is null");

        checkArgument(
            p1.time().isBefore(p2.time()) || p1.time().equals(p2.time()),
            "The input points must be in chronological order"
        );

        TimeWindow window = TimeWindow.of(p1.time(), p2.time());

        checkArgument(
            window.contains(targetTime),
            "The targetTime is outside the required time window"
        );

        if (p1.time().equals(targetTime)) {
            return (new PointBuilder<T>(p1)).build();
        } else if (p2.time().equals(targetTime)) {
            return (new PointBuilder<T>(p2)).build();
        } else {

            double fraction = window.toFractionOfRange(targetTime);

            //build an interpolated point
            LatLong interpolatedLatLong = interpolateLatLong(p1.latLong(), p2.latLong(), fraction);

            Double interpolatedCourseInDegrees = interpolateCourse(
                isNull(p1.course()) ? null : p1.course().inDegrees(),
                isNull(p2.course()) ? null : p2.course().inDegrees(),
                fraction
            );

            //correct the interpolated course when one of the input values was null
            if (interpolatedCourseInDegrees == null) {
                interpolatedCourseInDegrees = Spherical.courseInDegrees(p1.latLong(), p2.latLong());
            }

            double interpolatedSpeed = interpolateSpeed(p1, p2, fraction);

            Distance interpolatedAltitude = interpolate(
                p1.altitude(),
                p2.altitude(),
                fraction
            );

            //return a copy of the 1st input point but with corrected trajectory data
            return (new PointBuilder<T>(p1))
                .latLong(interpolatedLatLong)
                .course(Course.ofDegrees(interpolatedCourseInDegrees))
                .speed(Speed.ofKnots(interpolatedSpeed))
                .altitude(interpolatedAltitude)
                .time(targetTime)
                .build();
        }
    }
@Test
    public void testInterpolatePoint2() {
        /*
         * Test the interpolation works properly at the "start" of the timewindow
         */

        Point<String> p1 = (new PointBuilder<String>())
            .time(Instant.EPOCH)
            .altitude(Distance.ofFeet(1000.0))
            .courseInDegrees(120.0)
            .latLong(new LatLong(0.0, 10.0))
            .speedInKnots(200.0)
            .build();

        Point<String> p2 = (new PointBuilder<String>())
            .time(Instant.EPOCH.plusSeconds(8))
            .altitude(Distance.ofFeet(500.0))
            .courseInDegrees(130.0)
            .latLong(new LatLong(5.0, 15.0))
            .speedInKnots(300.0)
            .build();

        Point<String> testPoint = interpolate(p1, p2, Instant.EPOCH);

        double TOLERANCE = 0.0001;

        assertEquals(
            Instant.EPOCH,
            testPoint.time()
        );

        assertEquals(
            1000.0,
            testPoint.altitude().inFeet(),
            TOLERANCE
        );
        assertEquals(
            120.0,
            testPoint.course().inDegrees(),
            TOLERANCE
        );

        assertEquals(LatLong.of(0.0, 10.0), testPoint.latLong());

        assertEquals(
            200.0,
            testPoint.speed().inKnots(),
            TOLERANCE
        );
    }