Skip to content

Fix Loop contains which contained a missing negation and add test cases. #156

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

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a87035b
Fix some small issues that were flagged by upcoming lint checks.
rsned Apr 2, 2025
ed507ae
replace the swap edges, newEdges which left a dangling value with _
rsned Apr 2, 2025
39caea7
Add todo on this //nolint.
rsned Apr 2, 2025
69704c9
Add linter/formatter/govet github action.
rsned Apr 2, 2025
12554af
Merge branch 'golang:master' into master
rsned Apr 3, 2025
55902bd
Fix OSSF detected go.mod toolchain version issue.
rsned Apr 3, 2025
4f7483f
update to use git hashes instead of @v5 style.
rsned Apr 4, 2025
e141dbf
Use dependabot compatible version comments
alan-strohm Apr 4, 2025
c597829
Add comments on disabled checks. Add a few more entries.
rsned Apr 8, 2025
5836f6f
Merge branch 'golang:master' into master
rsned Apr 8, 2025
7c7e52c
Merge remote-tracking branch 'refs/remotes/origin/master'
rsned Apr 8, 2025
6df2625
Update some comments and add in reference to related issue number for…
rsned Apr 8, 2025
0f45e9d
Remove govet from enabled. Drop exclusions. Clarify some comments.
rsned Apr 8, 2025
6937d18
Fix some typos
alan-strohm Apr 9, 2025
aeac25e
Set gofmt formatter to keep the default -s (simplify option)
rsned Apr 10, 2025
6570bb0
Re-disable simplify until existing files are fixed.
rsned Apr 10, 2025
46b2465
Update permissions to read-all
rsned Apr 10, 2025
c8d0c06
Remove simply: false from format checks.
rsned Apr 10, 2025
a09abef
Merge branch 'golang:master' into master
rsned Apr 10, 2025
cc51d8b
read-all should be on permissions, not the specific sub element.
rsned Apr 10, 2025
61272a9
Merge remote-tracking branch 'refs/remotes/origin/master'
rsned Apr 10, 2025
f996d29
Merge branch 'golang:master' into master
rsned Apr 12, 2025
63e5133
drop comment on pull requests.
rsned Apr 14, 2025
9a1faa3
Merge remote-tracking branch 'refs/remotes/origin/master'
rsned Apr 14, 2025
05836a2
Merge branch 'golang:master' into master
rsned Apr 14, 2025
86e764d
Merge branch 'golang:master' into master
rsned Apr 17, 2025
a1c1dd6
Fixes for loop crossing reversed condition.
rsned Apr 17, 2025
e2d2c5f
Add test cases that tripped prior behavior of loop contains.
rsned Apr 17, 2025
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
37 changes: 19 additions & 18 deletions s2/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -1612,12 +1612,12 @@ func (l *loopCrosser) hasCrossing(ai, bi *rangeIterator) bool {
return false
}

// containsCenterMatches reports if the clippedShapes containsCenter boolean corresponds
// to the crossing target type given. (This is to work around C++ allowing false == 0,
// true == 1 type implicit conversions and comparisons)
func containsCenterMatches(a *clippedShape, target crossingTarget) bool {
return (!a.containsCenter && target == crossingTargetDontCross) ||
(a.containsCenter && target == crossingTargetCross)
// containsCenterMatches reports if the clippedShapes containsCenter boolean
// corresponds to the crossing target type given. (This is to work around C++
// allowing false == 0, true == 1 type implicit conversions and comparisons)
func containsCenterMatches(a bool, target crossingTarget) bool {
Copy link
Collaborator

@alan-strohm alan-strohm Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not make a type *rangeIterator? That way you can ensure that the boolean means what you intend it to mean. Alternatively, consider renaming a to containsCenter.

return (!a && target == crossingTargetDontCross) ||
(a && target == crossingTargetCross)
}

// hasCrossingRelation reports whether given two iterators positioned such that
Expand All @@ -1626,7 +1626,8 @@ func containsCenterMatches(a *clippedShape, target crossingTarget) bool {
// is an edge crossing, a wedge crossing, or a point P that matches both relations
// crossing targets. This function advances both iterators past ai.cellID.
func (l *loopCrosser) hasCrossingRelation(ai, bi *rangeIterator) bool {
aClipped := ai.it.IndexCell().shapes[0]
// ABSL_DCHECK(ai->id().contains(bi->id()));
aClipped := ai.clipped()
if aClipped.numEdges() != 0 {
// The current cell of A has at least one edge, so check for crossings.
if l.hasCrossing(ai, bi) {
Expand All @@ -1636,8 +1637,9 @@ func (l *loopCrosser) hasCrossingRelation(ai, bi *rangeIterator) bool {
return false
}

if containsCenterMatches(aClipped, l.aCrossingTarget) {
// The crossing target for A is not satisfied, so we skip over these cells of B.
if !containsCenterMatches(ai.containsCenter(), l.aCrossingTarget) {
// The crossing target for A is not satisfied, so we skip over
// these cells of B.
bi.seekBeyond(ai)
ai.next()
return false
Expand All @@ -1647,8 +1649,7 @@ func (l *loopCrosser) hasCrossingRelation(ai, bi *rangeIterator) bool {
// worth iterating through the cells of B to see whether any cell
// centers also satisfy the crossing target for B.
for bi.cellID() <= ai.rangeMax {
bClipped := bi.it.IndexCell().shapes[0]
if containsCenterMatches(bClipped, l.bCrossingTarget) {
if containsCenterMatches(bi.containsCenter(), l.bCrossingTarget) {
return true
}
bi.next()
Expand Down Expand Up @@ -1701,16 +1702,16 @@ func hasCrossingRelation(a, b *Loop, relation loopRelation) bool {
return true
}
} else {
// The A and B cells are the same. Since the two cells
// have the same center point P, check whether P satisfies
// the crossing targets.
aClipped := ai.it.IndexCell().shapes[0]
bClipped := bi.it.IndexCell().shapes[0]
if containsCenterMatches(aClipped, ab.aCrossingTarget) &&
containsCenterMatches(bClipped, ab.bCrossingTarget) {
// The A and B cells are the same. Since the two
// cells have the same center point P, check
// whether P satisfies the crossing targets.
if containsCenterMatches(ai.containsCenter(), ab.aCrossingTarget) &&
containsCenterMatches(bi.containsCenter(), ab.bCrossingTarget) {
return true
}
// Otherwise test all the edge crossings directly.
aClipped := ai.it.IndexCell().shapes[0]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use your new ai/bi.clipped() function?

bClipped := bi.it.IndexCell().shapes[0]
if aClipped.numEdges() > 0 && bClipped.numEdges() > 0 && ab.cellCrossesCell(aClipped, bClipped) {
return true
}
Expand Down
51 changes: 51 additions & 0 deletions s2/loop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,31 @@ func TestLoopContainsMatchesCrossingSign(t *testing.T) {
}

func TestLoopRelations(t *testing.T) {

// Test cases from https://github.com/golang/geo/issues/77 and 78
// relating to loop relations contains giving wrong result some cases.
containingLoop := LoopFromPoints([]Point{
PointFromLatLng(LatLngFromDegrees(-38.0, -135.0)),
PointFromLatLng(LatLngFromDegrees(-38.0, 149.0)),
PointFromLatLng(LatLngFromDegrees(77.0, 149.0)),
PointFromLatLng(LatLngFromDegrees(77.0, -135.0)),
})

innerTile := LoopFromPoints([]Point{
PointFromLatLng(LatLngFromDegrees(37.99616267972809, 13.007812500000002)),
PointFromLatLng(LatLngFromDegrees(37.99616267972809, 13.359375000000002)),
PointFromLatLng(LatLngFromDegrees(38.272819658516866, 13.359375000000002)),
PointFromLatLng(LatLngFromDegrees(38.272819658516866, 13.007812500000002)),
})

// +0.2 lat +0.2 lon from innerTile
extendedTile := LoopFromPoints([]Point{
PointFromLatLng(LatLngFromDegrees(37.99616267972809, 13.007812500000002)),
PointFromLatLng(LatLngFromDegrees(37.99616267972809, 13.559375000000002)),
PointFromLatLng(LatLngFromDegrees(38.472819658516866, 13.559375000000002)),
PointFromLatLng(LatLngFromDegrees(38.472819658516866, 13.007812500000002)),
})

tests := []struct {
a, b *Loop
contains bool // A contains B
Expand Down Expand Up @@ -1332,6 +1357,18 @@ func TestLoopRelations(t *testing.T) {
contains: true,
sharedEdge: true,
},
// https://github.com/golang/geo/issues/77 and 78
// These cases failed prior to this fix.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this comment ("this fix" means the pull request, not the file). I'd suggest a different comment. Something like:
// Cases below here prevent regressions of bugs which previously appeared in the golang version
// and which are missing test coverage in the C++ codebase.

{
a: containingLoop,
b: innerTile,
contains: true,
},
{
a: containingLoop,
b: extendedTile,
contains: true,
},
}

for _, test := range tests {
Expand Down Expand Up @@ -1817,3 +1854,17 @@ func BenchmarkLoopContainsPoint(b *testing.B) {
vertices *= 2
}
}

// TODO(rsned): Differences from C++
// TEST_F(S2LoopTestBase, CompressedEncodedLoopDecodesApproxEqual) {
// TEST_F(S2LoopTestBase, DistanceMethods) {
// TEST_F(S2LoopTestBase, GetAreaAccuracy) {
// TEST_F(S2LoopTestBase, GetAreaConsistentWithSign) {
// TEST_F(S2LoopTestBase, MakeRegularLoop) {
// TEST(S2Loop, BoundaryNear) {
// TEST(S2Loop, BoundsForLoopContainment) {
// TEST(S2LoopDeathTest, IsValidDetectsInvalidLoops) {
// TEST(S2Loop, DefaultLoopIsInvalid) {
// TEST(S2Loop, EmptyFullLossyConversions) {
// TEST(S2Loop, EncodeDecode) {
// TEST(S2Loop, LoopRelations2) {
8 changes: 8 additions & 0 deletions s2/shapeindex.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ func (s *ShapeIndexCell) numEdges() int {
return e
}

// clipped returns the clipped shape at the given index. Shapes are kept sorted in
// increasing order of shape id.
//
// Requires: 0 <= i < len(shapes)
func (s *ShapeIndexCell) clipped(i int) *clippedShape {
return s.shapes[i]
}

// add adds the given clipped shape to this index cell.
func (s *ShapeIndexCell) add(c *clippedShape) {
// C++ uses a set, so it's ordered and unique. We don't currently catch
Expand Down
2 changes: 2 additions & 0 deletions s2/shapeutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ func newRangeIterator(index *ShapeIndex) *rangeIterator {

func (r *rangeIterator) cellID() CellID { return r.it.CellID() }
func (r *rangeIterator) indexCell() *ShapeIndexCell { return r.it.IndexCell() }
func (r *rangeIterator) clipped() *clippedShape { return r.indexCell().clipped(0) }
func (r *rangeIterator) containsCenter() bool { return r.clipped().containsCenter }
func (r *rangeIterator) next() { r.it.Next(); r.refresh() }
func (r *rangeIterator) done() bool { return r.it.Done() }

Expand Down