private double[] derivativesErrors(
      PVCoordinatesProvider provider, AbsoluteDate date, Frame frame, OneAxisEllipsoid model)
      throws OrekitException {
    List<TimeStampedPVCoordinates> pvList = new ArrayList<TimeStampedPVCoordinates>();
    List<TimeStampedPVCoordinates> groundPVList = new ArrayList<TimeStampedPVCoordinates>();
    for (double dt = -0.25; dt <= 0.25; dt += 0.125) {
      TimeStampedPVCoordinates shiftedPV = provider.getPVCoordinates(date.shiftedBy(dt), frame);
      Vector3D p = model.projectToGround(shiftedPV.getPosition(), shiftedPV.getDate(), frame);
      pvList.add(shiftedPV);
      groundPVList.add(
          new TimeStampedPVCoordinates(shiftedPV.getDate(), p, Vector3D.ZERO, Vector3D.ZERO));
    }
    TimeStampedPVCoordinates computed =
        model.projectToGround(
            TimeStampedPVCoordinates.interpolate(date, CartesianDerivativesFilter.USE_P, pvList),
            frame);
    TimeStampedPVCoordinates reference =
        TimeStampedPVCoordinates.interpolate(date, CartesianDerivativesFilter.USE_P, groundPVList);

    TimeStampedPVCoordinates pv0 = provider.getPVCoordinates(date, frame);
    Vector3D p0 = pv0.getPosition();
    Vector3D v0 = pv0.getVelocity();
    Vector3D a0 = pv0.getAcceleration();

    return new double[] {
      Vector3D.distance(computed.getPosition(), reference.getPosition()) / p0.getNorm(),
      Vector3D.distance(computed.getVelocity(), reference.getVelocity()) / v0.getNorm(),
      Vector3D.distance(computed.getAcceleration(), reference.getAcceleration()) / a0.getNorm(),
    };
  }
  /** {@inheritDoc} */
  protected TimeStampedPVCoordinates getTargetPV(
      final PVCoordinatesProvider pvProv, final AbsoluteDate date, final Frame frame)
      throws OrekitException {

    // transform from specified reference frame to spacecraft frame
    final Transform refToSc =
        new Transform(
            date,
            new Transform(date, pvProv.getPVCoordinates(date, frame).negate()),
            new Transform(date, attitudeLaw.getAttitude(pvProv, date, frame).getOrientation()));

    // transform from specified reference frame to body frame
    final Transform refToBody = frame.getTransformTo(shape.getBodyFrame(), date);

    // sample intersection points in current date neighborhood
    final Transform scToBody = new Transform(date, refToSc.getInverse(), refToBody);
    final double h = 0.1;
    final List<TimeStampedPVCoordinates> sample = new ArrayList<TimeStampedPVCoordinates>();
    sample.add(losIntersectionWithBody(scToBody.shiftedBy(-h)));
    sample.add(losIntersectionWithBody(scToBody));
    sample.add(losIntersectionWithBody(scToBody.shiftedBy(+h)));

    // use interpolation to compute properly the time-derivatives
    final TimeStampedPVCoordinates targetBody =
        TimeStampedPVCoordinates.interpolate(date, CartesianDerivativesFilter.USE_P, sample);

    // convert back to caller specified frame
    return refToBody.getInverse().transformPVCoordinates(targetBody);
  }
  @Test
  public void testGroundProjectionTaylor() throws OrekitException {
    Frame itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
    Frame eme2000 = FramesFactory.getEME2000();
    OneAxisEllipsoid model =
        new OneAxisEllipsoid(
            Constants.WGS84_EARTH_EQUATORIAL_RADIUS, Constants.WGS84_EARTH_FLATTENING, itrf);

    TimeStampedPVCoordinates initPV =
        new TimeStampedPVCoordinates(
            AbsoluteDate.J2000_EPOCH.shiftedBy(584.),
            new Vector3D(3220103., 69623., 6449822.),
            new Vector3D(6414.7, -2006., -3180.),
            Vector3D.ZERO);
    Orbit orbit = new EquinoctialOrbit(initPV, eme2000, Constants.EIGEN5C_EARTH_MU);

    TimeStampedPVCoordinates pv0 = orbit.getPVCoordinates(orbit.getDate(), model.getBodyFrame());
    PVCoordinatesProvider groundTaylor =
        model.projectToGround(pv0, model.getBodyFrame()).toTaylorProvider(model.getBodyFrame());

    TimeStampedPVCoordinates g0 =
        groundTaylor.getPVCoordinates(orbit.getDate(), model.getBodyFrame());
    Vector3D zenith = pv0.getPosition().subtract(g0.getPosition()).normalize();
    Vector3D acrossTrack = Vector3D.crossProduct(zenith, g0.getVelocity()).normalize();
    Vector3D alongTrack = Vector3D.crossProduct(acrossTrack, zenith).normalize();
    for (double dt = -1; dt < 1; dt += 0.01) {
      AbsoluteDate date = orbit.getDate().shiftedBy(dt);
      Vector3D taylorP = groundTaylor.getPVCoordinates(date, model.getBodyFrame()).getPosition();
      Vector3D refP =
          model.projectToGround(
              orbit.getPVCoordinates(date, model.getBodyFrame()).getPosition(),
              date,
              model.getBodyFrame());
      Vector3D delta = taylorP.subtract(refP);
      Assert.assertEquals(0.0, Vector3D.dotProduct(delta, alongTrack), 0.0015);
      Assert.assertEquals(0.0, Vector3D.dotProduct(delta, acrossTrack), 0.0007);
      Assert.assertEquals(0.0, Vector3D.dotProduct(delta, zenith), 0.00002);
    }
  }
  /** {@inheritDoc} */
  public FieldVector3D<DerivativeStructure> accelerationDerivatives(
      final SpacecraftState s, final String paramName) throws OrekitException {

    complainIfNotSupported(paramName);
    final AbsoluteDate date = s.getDate();
    final Frame frame = s.getFrame();
    final Vector3D position = s.getPVCoordinates().getPosition();
    final Vector3D sunSatVector =
        position.subtract(sun.getPVCoordinates(date, frame).getPosition());
    final double r2 = sunSatVector.getNormSq();

    // compute flux
    final double rawP = kRef * getLightningRatio(position, frame, date) / r2;
    final Vector3D flux = new Vector3D(rawP / FastMath.sqrt(r2), sunSatVector);

    return spacecraft.radiationPressureAcceleration(
        date, frame, position, s.getAttitude().getRotation(), s.getMass(), flux, paramName);
  }
  /** {@inheritDoc} */
  public void addContribution(final SpacecraftState s, final TimeDerivativesEquations adder)
      throws OrekitException {

    final AbsoluteDate date = s.getDate();
    final Frame frame = s.getFrame();
    final Vector3D position = s.getPVCoordinates().getPosition();
    final Vector3D sunSatVector =
        position.subtract(sun.getPVCoordinates(date, frame).getPosition());
    final double r2 = sunSatVector.getNormSq();

    // compute flux
    final double rawP = kRef * getLightningRatio(position, frame, date) / r2;
    final Vector3D flux = new Vector3D(rawP / FastMath.sqrt(r2), sunSatVector);

    final Vector3D acceleration =
        spacecraft.radiationPressureAcceleration(
            date, frame, position, s.getAttitude().getRotation(), s.getMass(), flux);

    // provide the perturbing acceleration to the derivatives adder
    adder.addAcceleration(acceleration, s.getFrame());
  }
  /**
   * Get the useful angles for eclipse computation.
   *
   * @param position the satellite's position in the selected frame.
   * @param frame in which is defined the position
   * @param date the date
   * @return the 3 angles {(satCentral, satSun), Central body apparent radius, Sun apparent radius}
   * @exception OrekitException if the trajectory is inside the Earth
   */
  private double[] getEclipseAngles(
      final Vector3D position, final Frame frame, final AbsoluteDate date) throws OrekitException {
    final double[] angle = new double[3];

    final Vector3D satSunVector =
        sun.getPVCoordinates(date, frame).getPosition().subtract(position);

    // Sat-Sun / Sat-CentralBody angle
    angle[0] = Vector3D.angle(satSunVector, position.negate());

    // Central body apparent radius
    final double r = position.getNorm();
    if (r <= equatorialRadius) {
      throw new OrekitException(OrekitMessages.TRAJECTORY_INSIDE_BRILLOUIN_SPHERE, r);
    }
    angle[1] = FastMath.asin(equatorialRadius / r);

    // Sun apparent radius
    angle[2] = FastMath.asin(Constants.SUN_RADIUS / satSunVector.getNorm());

    return angle;
  }
  /** {@inheritDoc} */
  public FieldVector3D<DerivativeStructure> accelerationDerivatives(
      final AbsoluteDate date,
      final Frame frame,
      final FieldVector3D<DerivativeStructure> position,
      final FieldVector3D<DerivativeStructure> velocity,
      final FieldRotation<DerivativeStructure> rotation,
      final DerivativeStructure mass)
      throws OrekitException {

    final FieldVector3D<DerivativeStructure> sunSatVector =
        position.subtract(sun.getPVCoordinates(date, frame).getPosition());
    final DerivativeStructure r2 = sunSatVector.getNormSq();

    // compute flux
    final double ratio = getLightningRatio(position.toVector3D(), frame, date);
    final DerivativeStructure rawP = r2.reciprocal().multiply(kRef * ratio);
    final FieldVector3D<DerivativeStructure> flux =
        new FieldVector3D<DerivativeStructure>(rawP.divide(r2.sqrt()), sunSatVector);

    // compute acceleration with all its partial derivatives
    return spacecraft.radiationPressureAcceleration(date, frame, position, rotation, mass, flux);
  }