/** * Helper method to get area and optionally centroid. */ private S2AreaCentroid GetAreaCentroid(bool doCentroid) { // Don't crash even if loop is not well-defined. if (NumVertices < 3) { return(new S2AreaCentroid(0D)); } // 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. var origin = Vertex(0); var axis = (origin.LargestAbsComponent + 1) % 3; var slightlyDisplaced = origin[axis] + S2.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; var centroidSum = new S2Point(0, 0, 0); for (var 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. var trueCentroid = S2.TrueCentroid(origin, Vertex(i - 1), Vertex(i)); centroidSum = 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.Pi; } // The loop's sign() does not affect the return result and should be taken // into account by the caller. S2Point?centroid = null; if (doCentroid) { centroid = centroidSum; } return(new S2AreaCentroid(areaSum, centroid)); }