Skip to content

Return early in surfaceIntegral if there are less than 3 vertices. #201

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions s2/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,10 @@ func (l *Loop) ContainsNested(other *Loop) bool {
//
// Any changes to this method may need corresponding changes to surfaceIntegralPoint as well.
func (l *Loop) surfaceIntegralFloat64(f func(a, b, c Point) float64) float64 {
if len(l.vertices) < 3 {
// If the loop has less than 3 vertices, there's no interior.
return 0
}
// We sum f over a collection T of oriented triangles, possibly
// overlapping. Let the sign of a triangle be +1 if it is CCW and -1
// otherwise, and let the sign of a point x be the sum of the signs of the
Expand Down Expand Up @@ -1092,6 +1096,10 @@ func (l *Loop) surfaceIntegralFloat64(f func(a, b, c Point) float64) float64 {
func (l *Loop) surfaceIntegralPoint(f func(a, b, c Point) Point) Point {
const maxLength = math.Pi - 1e-5
var sum r3.Vector
if len(l.vertices) < 3 {
// If the loop has less than 3 vertices, there's no interior.
return Point{sum}
}

origin := l.Vertex(0)
for i := 1; i+1 < len(l.vertices); i++ {
Expand Down
71 changes: 51 additions & 20 deletions s2/loop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1584,28 +1584,59 @@ func TestLoopTurningAngle(t *testing.T) {
}

func TestLoopAreaAndCentroid(t *testing.T) {
var p Point

if got, want := EmptyLoop().Area(), 0.0; got != want {
t.Errorf("EmptyLoop.Area() = %v, want %v", got, want)
}
if got, want := FullLoop().Area(), 4*math.Pi; got != want {
t.Errorf("FullLoop.Area() = %v, want %v", got, want)
}
if got := EmptyLoop().Centroid(); !p.ApproxEqual(got) {
t.Errorf("EmptyLoop.Centroid() = %v, want %v", got, p)
}
if got := FullLoop().Centroid(); !p.ApproxEqual(got) {
t.Errorf("FullLoop.Centroid() = %v, want %v", got, p)
}

if got, want := northHemi.Area(), 2*math.Pi; !float64Eq(got, want) {
t.Errorf("northHemi.Area() = %v, want %v", got, want)
tests := []struct {
name string
loop *Loop
wantArea float64
wantCentroid Point
}{
{
name: "EmptyLoop",
loop: EmptyLoop(),
wantArea: 0.0,
wantCentroid: Point{},
},
{
name: "FullLoop",
loop: FullLoop(),
wantArea: 4 * math.Pi,
wantCentroid: Point{},
},
{
name: "northHemi",
loop: northHemi,
wantArea: 2 * math.Pi,
wantCentroid: Point{}, // Centroid of a hemisphere is (0,0,0)
},
{
name: "eastHemi",
loop: eastHemi,
wantArea: 2 * math.Pi,
wantCentroid: Point{}, // Centroid of a hemisphere is (0,0,0)
},
{
name: "lineTriangle",
loop: lineTriangle,
wantArea: 0,
wantCentroid: Point{},
},
{
name: "twoPoints",
loop: LoopFromPoints([]Point{PointFromLatLng(LatLngFromDegrees(0, 0)), PointFromLatLng(LatLngFromDegrees(0, 1))}),
wantArea: 0,
wantCentroid: Point{},
},
}

eastHemiArea := eastHemi.Area()
if eastHemiArea < 2*math.Pi-1e-12 || eastHemiArea > 2*math.Pi+1e-12 {
t.Errorf("eastHemi.Area() = %v, want between [%v, %v]", eastHemiArea, 2*math.Pi-1e-12, 2*math.Pi+1e-12)
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := test.loop.Area(); !float64Near(got, test.wantArea, epsilon) {
t.Errorf("Area() = %v, want %v", got, test.wantArea)
}
if got := test.loop.Centroid(); !got.ApproxEqual(test.wantCentroid) {
t.Errorf("Centroid() = %v, want %v", got, test.wantCentroid)
}
})
}

// Construct spherical caps of random height, and approximate their boundary
Expand Down