private S2AreaCentroid getAreaCentroid(boolean doCentroid) {
    S2Point centroid = null;
    // Don't crash even if loop is not well-defined.
    if (numVertices() < 3) {
      return new S2AreaCentroid(0D, centroid);
    }

    // The triangle area calculation becomes numerically unstable as the length
    // of any edge approaches 180 degrees. However, a loop may contain vertices
    // that are 180 degrees apart and still be valid, e.g. a loop that defines
    // the northern hemisphere using four points. We handle this case by using
    // triangles centered around an origin that is slightly displaced from the
    // first vertex. The amount of displacement is enough to get plenty of
    // accuracy for antipodal points, but small enough so that we still get
    // accurate areas for very tiny triangles.
    //
    // Of course, if the loop contains a point that is exactly antipodal from
    // our slightly displaced vertex, the area will still be unstable, but we
    // expect this case to be very unlikely (i.e. a polygon with two vertices on
    // opposite sides of the Earth with one of them displaced by about 2mm in
    // exactly the right direction). Note that the approximate point resolution
    // using the E7 or S2CellId representation is only about 1cm.

    S2Point origin = vertex(0);
    int axis = (origin.largestAbsComponent() + 1) % 3;
    double slightlyDisplaced = origin.get(axis) + S2.M_E * 1e-10;
    origin =
        new S2Point((axis == 0) ? slightlyDisplaced : origin.x,
            (axis == 1) ? slightlyDisplaced : origin.y, (axis == 2) ? slightlyDisplaced : origin.z);
    origin = S2Point.normalize(origin);

    double areaSum = 0;
    S2Point centroidSum = new S2Point(0, 0, 0);
    for (int i = 1; i <= numVertices(); ++i) {
      areaSum += S2.signedArea(origin, vertex(i - 1), vertex(i));
      if (doCentroid) {
        // The true centroid is already premultiplied by the triangle area.
        S2Point trueCentroid = S2.trueCentroid(origin, vertex(i - 1), vertex(i));
        centroidSum = S2Point.add(centroidSum, trueCentroid);
      }
    }
    // The calculated area at this point should be between -4*Pi and 4*Pi,
    // although it may be slightly larger or smaller than this due to
    // numerical errors.
    // assert (Math.abs(areaSum) <= 4 * S2.M_PI + 1e-12);

    if (areaSum < 0) {
      // If the area is negative, we have computed the area to the right of the
      // loop. The area to the left is 4*Pi - (-area). Amazingly, the centroid
      // does not need to be changed, since it is the negative of the integral
      // of position over the region to the right of the loop. This is the same
      // as the integral of position over the region to the left of the loop,
      // since the integral of position over the entire sphere is (0, 0, 0).
      areaSum += 4 * S2.M_PI;
    }
    // The loop's sign() does not affect the return result and should be taken
    // into account by the caller.
    if (doCentroid) {
      centroid = centroidSum;
    }
    return new S2AreaCentroid(areaSum, centroid);
  }