arcs = new ConvexRegionBoundaryBuilder<>(GreatArc.class).build(bounds);
return arcs.isEmpty() ?
full() :
new ConvexArea2S(arcs);
}
/** Compute the weighted centroid vector for the hemisphere formed by the given arc.
* @param arc arc defining the hemisphere
* @return the weighted centroid vector for the hemisphere
* @see #getWeightedCentroidVector()
*/
private static Vector3D computeHemisphereWeightedCentroidVector(final GreatArc arc) {
return arc.getCircle().getPole().withNorm(HALF_SIZE);
}
/** Compute the weighted centroid vector for the lune formed by the given arcs.
* @param a first arc for the lune
* @param b second arc for the lune
* @return the weighted centroid vector for the lune
* @see #getWeightedCentroidVector()
*/
private static Vector3D computeLuneWeightedCentroidVector(final GreatArc a, final GreatArc b) {
final Point2S aMid = a.getCentroid();
final Point2S bMid = b.getCentroid();
// compute the centroid vector as the exact center of the lune to avoid inaccurate
// results with very small regions
final Vector3D.Unit centroid = aMid.slerp(bMid, 0.5).getVector();
// compute the weight using the reverse of the algorithm from computeArcPoleWeightedCentroidVector()
final double weight =
(a.getSize() * centroid.dot(a.getCircle().getPole())) +
(b.getSize() * centroid.dot(b.getCircle().getPole()));
return centroid.withNorm(weight);
}
/** Compute the weighted centroid vector for the triangle or polygon formed by the given arcs
* by adding together the arc pole vectors multiplied by their respective arc lengths. This
* algorithm is described in the paper
* *The Centroid and Inertia Tensor for a Spherical Triangle* by John E Brock.
*
* Note: This algorithm works well in general but is susceptible to floating point errors
* on very small areas. In these cases, the computed centroid may not be in the expected location
* and may even be outside of the area. The {@link #computeTriangleFanWeightedCentroidVector(List)}
* method can produce more accurate results in these cases.

* @param arcs boundary arcs for the area
* @return the weighted centroid vector for the area
* @see #computeTriangleFanWeightedCentroidVector(List)
*/
private static Vector3D computeArcPoleWeightedCentroidVector(final List arcs) {
final Vector3D.Sum centroid = Vector3D.Sum.create();
for (final GreatArc arc : arcs) {
centroid.addScaled(arc.getSize(), arc.getCircle().getPole());
}
return centroid.get();
}
/** Compute the weighted centroid vector for the triangle or polygon formed by the given arcs
* using a triangle fan approach. This method is specifically designed for use with areas of very small size,
* where use of the standard algorithm from {@link ##computeArcPoleWeightedCentroidVector(List))} can produce
* inaccurate results. The algorithm proceeds as follows:
*
* - The polygon is divided into spherical triangles using a triangle fan.
* - For each triangle, the vectors of the 3 spherical points are added together to approximate the direction
* of the spherical centroid. This ensures that the computed centroid lies within the area.
* - The length of the weighted centroid vector is determined by computing the sum of the contributions that
* each arc in the triangle would make to the centroid using the algorithm from
* {@link ##computeArcPoleWeightedCentroidVector(List)}. This essentially performs part of that algorithm in
* reverse: given a centroid direction, compute the contribution that each arc makes.
* - The sum of the weighted centroid vectors for each triangle is computed and returned.
*

* @param arcs boundary arcs for the area; must contain at least 3 arcs
* @return the weighted centroid vector for the area
* @see ##computeArcPoleWeightedCentroidVector(List)
*/
private static Vector3D computeTriangleFanWeightedCentroidVector(final List arcs) {
final Iterator arcIt = arcs.iterator();
final Point2S p0 = arcIt.next().getStartPoint();
final Vector3D.Unit v0 = p0.getVector();
final Vector3D.Sum areaCentroid = Vector3D.Sum.create();
GreatArc arc;
Point2S p1;
Point2S p2;
Vector3D.Unit v1;
Vector3D.Unit v2;
Vector3D.Unit triangleCentroid;
double triangleCentroidLen;
while (arcIt.hasNext()) {
arc = arcIt.next();
if (!arc.contains(p0)) {
p1 = arc.getStartPoint();
p2 = arc.getEndPoint();
v1 = p1.getVector();
v2 = p2.getVector();
triangleCentroid = Vector3D.Sum.create()
.add(v0)
.add(v1)
.add(v2)
.get().normalize();
triangleCentroidLen =
computeArcCentroidContribution(v0, v1, triangleCentroid) +
computeArcCentroidContribution(v1, v2, triangleCentroid) +
computeArcCentroidContribution(v2, v0, triangleCentroid);
areaCentroid.addScaled(triangleCentroidLen, triangleCentroid);
}
}
return areaCentroid.get();
}
/** Compute the contribution made by a single arc to a weighted centroid vector.
* @param a first point in the arc
* @param b second point in the arc
* @param triangleCentroid the centroid vector for the area
* @return the contribution made by the arc {@code ab} to the length of the weighted centroid vector
*/
private static double computeArcCentroidContribution(final Vector3D.Unit a, final Vector3D.Unit b,
final Vector3D.Unit triangleCentroid) {
final double arcLength = a.angle(b);
final Vector3D.Unit planeNormal = a.cross(b).normalize();
return arcLength * triangleCentroid.dot(planeNormal);
}
}