// For shapes of dimension 2, returns the area of the shape on the unit // sphere. The result is between 0 and 4*Pi steradians. Otherwise returns // zero. This method has good relative accuracy for both very large and very // small regions. // // All edges are modeled as spherical geodesics. The result can be converted // to an area on the Earth's surface (with a worst-case error of 0.900% near // the poles) using the functions in s2earth.h. public static double GetArea(S2Shape shape) { if (shape.Dimension() != 2) { return(0.0); } // Since S2Shape uses the convention that the interior of the shape is to // the left of all edges, in theory we could compute the area of the polygon // by simply adding up all the loop areas modulo 4*Pi. The problem with // this approach is that polygons holes typically have areas near 4*Pi, // which can create large cancellation errors when computing the area of // small polygons with holes. For example, a shell with an area of 4 square // meters (1e-13 steradians) surrounding a hole with an area of 3 square // meters (7.5e-14 sterians) would lose almost all of its accuracy if the // area of the hole was computed as 12.566370614359098. // // So instead we use S2.GetSignedArea() to ensure that all loops have areas // in the range [-2*Pi, 2*Pi]. // // TODO(ericv): Rarely, this function returns the area of the complementary // region (4*Pi - area). This can only happen when the true area is very // close to zero or 4*Pi and the polygon has multiple loops. To make this // function completely robust requires checking whether the signed area sum is // ambiguous, and if so, determining the loop nesting structure. This allows // the sum to be evaluated in a way that is guaranteed to have the correct // sign. double area = 0; double max_error = 0; int num_chains = shape.NumChains(); for (int chain_id = 0; chain_id < num_chains; ++chain_id) { GetChainVertices(shape, chain_id, out var vertices); area += S2.GetSignedArea(vertices.ToList()); #if DEBUG max_error += S2.GetCurvatureMaxError(new(vertices)); #endif } // Note that S2.GetSignedArea() guarantees that the full loop (containing // all points on the sphere) has a very small negative area. System.Diagnostics.Debug.Assert(Math.Abs(area) <= S2.M_4_PI + max_error); if (area < 0.0) { area += S2.M_4_PI; } return(area); }
public void Test_GetSignedArea_Underflow() { var loop = ParsePointsOrDie("0:0, 0:1e-88, 1e-88:1e-88, 1e-88:0"); Assert.True(S2.GetSignedArea(loop) > 0); }