Skip to content

Commit fa1e883

Browse files
rsnedalan-strohm
andauthored
Fix Loop contains which contained a missing negation and add test cases. (#156)
Loop's hasCrossingRelation() had the wrong condition on one branch. This has been resolved and test cases added ensuring it was fixed. Fix #77 Fix #78 --------- Co-authored-by: Alan Strohm <astrohm@google.com>
1 parent 8bbaf6e commit fa1e883

File tree

4 files changed

+82
-21
lines changed

4 files changed

+82
-21
lines changed

s2/loop.go

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,11 +1584,11 @@ func (l *loopCrosser) hasCrossing(ai, bi *rangeIterator) bool {
15841584
l.bCells = nil
15851585

15861586
for {
1587-
if n := bi.it.IndexCell().shapes[0].numEdges(); n > 0 {
1587+
if n := bi.clipped().numEdges(); n > 0 {
15881588
totalEdges += n
15891589
if totalEdges >= edgeQueryMinEdges {
15901590
// There are too many edges to test them directly, so use CrossingEdgeQuery.
1591-
if l.cellCrossesAnySubcell(ai.it.IndexCell().shapes[0], ai.cellID()) {
1591+
if l.cellCrossesAnySubcell(ai.clipped(), ai.cellID()) {
15921592
return true
15931593
}
15941594
bi.seekBeyond(ai)
@@ -1604,20 +1604,20 @@ func (l *loopCrosser) hasCrossing(ai, bi *rangeIterator) bool {
16041604

16051605
// Test all the edge crossings directly.
16061606
for _, c := range l.bCells {
1607-
if l.cellCrossesCell(ai.it.IndexCell().shapes[0], c.shapes[0]) {
1607+
if l.cellCrossesCell(ai.clipped(), c.shapes[0]) {
16081608
return true
16091609
}
16101610
}
16111611

16121612
return false
16131613
}
16141614

1615-
// containsCenterMatches reports if the clippedShapes containsCenter boolean corresponds
1616-
// to the crossing target type given. (This is to work around C++ allowing false == 0,
1617-
// true == 1 type implicit conversions and comparisons)
1618-
func containsCenterMatches(a *clippedShape, target crossingTarget) bool {
1619-
return (!a.containsCenter && target == crossingTargetDontCross) ||
1620-
(a.containsCenter && target == crossingTargetCross)
1615+
// containsCenterMatches reports if the clippedShapes containsCenter boolean
1616+
// corresponds to the crossing target type given. (This is to work around C++
1617+
// allowing false == 0, true == 1 type implicit conversions and comparisons)
1618+
func containsCenterMatches(containsCenter bool, target crossingTarget) bool {
1619+
return (!containsCenter && target == crossingTargetDontCross) ||
1620+
(containsCenter && target == crossingTargetCross)
16211621
}
16221622

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

1639-
if containsCenterMatches(aClipped, l.aCrossingTarget) {
1640-
// The crossing target for A is not satisfied, so we skip over these cells of B.
1640+
if !containsCenterMatches(ai.containsCenter(), l.aCrossingTarget) {
1641+
// The crossing target for A is not satisfied, so we skip over
1642+
// these cells of B.
16411643
bi.seekBeyond(ai)
16421644
ai.next()
16431645
return false
@@ -1647,8 +1649,7 @@ func (l *loopCrosser) hasCrossingRelation(ai, bi *rangeIterator) bool {
16471649
// worth iterating through the cells of B to see whether any cell
16481650
// centers also satisfy the crossing target for B.
16491651
for bi.cellID() <= ai.rangeMax {
1650-
bClipped := bi.it.IndexCell().shapes[0]
1651-
if containsCenterMatches(bClipped, l.bCrossingTarget) {
1652+
if containsCenterMatches(bi.containsCenter(), l.bCrossingTarget) {
16521653
return true
16531654
}
16541655
bi.next()
@@ -1701,16 +1702,16 @@ func hasCrossingRelation(a, b *Loop, relation loopRelation) bool {
17011702
return true
17021703
}
17031704
} else {
1704-
// The A and B cells are the same. Since the two cells
1705-
// have the same center point P, check whether P satisfies
1706-
// the crossing targets.
1707-
aClipped := ai.it.IndexCell().shapes[0]
1708-
bClipped := bi.it.IndexCell().shapes[0]
1709-
if containsCenterMatches(aClipped, ab.aCrossingTarget) &&
1710-
containsCenterMatches(bClipped, ab.bCrossingTarget) {
1705+
// The A and B cells are the same. Since the two
1706+
// cells have the same center point P, check
1707+
// whether P satisfies the crossing targets.
1708+
if containsCenterMatches(ai.containsCenter(), ab.aCrossingTarget) &&
1709+
containsCenterMatches(bi.containsCenter(), ab.bCrossingTarget) {
17111710
return true
17121711
}
17131712
// Otherwise test all the edge crossings directly.
1713+
aClipped := ai.clipped()
1714+
bClipped := bi.clipped()
17141715
if aClipped.numEdges() > 0 && bClipped.numEdges() > 0 && ab.cellCrossesCell(aClipped, bClipped) {
17151716
return true
17161717
}

s2/loop_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,29 @@ func TestLoopContainsMatchesCrossingSign(t *testing.T) {
805805
}
806806

807807
func TestLoopRelations(t *testing.T) {
808+
// this loop is used in the regression test cases below.
809+
containingLoop := LoopFromPoints([]Point{
810+
PointFromLatLng(LatLngFromDegrees(-38.0, -135.0)),
811+
PointFromLatLng(LatLngFromDegrees(-38.0, 149.0)),
812+
PointFromLatLng(LatLngFromDegrees(77.0, 149.0)),
813+
PointFromLatLng(LatLngFromDegrees(77.0, -135.0)),
814+
})
815+
816+
innerTile := LoopFromPoints([]Point{
817+
PointFromLatLng(LatLngFromDegrees(37.99616267972809, 13.007812500000002)),
818+
PointFromLatLng(LatLngFromDegrees(37.99616267972809, 13.359375000000002)),
819+
PointFromLatLng(LatLngFromDegrees(38.272819658516866, 13.359375000000002)),
820+
PointFromLatLng(LatLngFromDegrees(38.272819658516866, 13.007812500000002)),
821+
})
822+
823+
// +0.2 lat +0.2 lon from innerTile
824+
extendedTile := LoopFromPoints([]Point{
825+
PointFromLatLng(LatLngFromDegrees(37.99616267972809, 13.007812500000002)),
826+
PointFromLatLng(LatLngFromDegrees(37.99616267972809, 13.559375000000002)),
827+
PointFromLatLng(LatLngFromDegrees(38.472819658516866, 13.559375000000002)),
828+
PointFromLatLng(LatLngFromDegrees(38.472819658516866, 13.007812500000002)),
829+
})
830+
808831
tests := []struct {
809832
a, b *Loop
810833
contains bool // A contains B
@@ -1332,6 +1355,19 @@ func TestLoopRelations(t *testing.T) {
13321355
contains: true,
13331356
sharedEdge: true,
13341357
},
1358+
// Cases below here prevent regressions of bugs which
1359+
// previously appeared in the golang version
1360+
// and which are missing test coverage in the C++ codebase.
1361+
{
1362+
a: containingLoop,
1363+
b: innerTile,
1364+
contains: true,
1365+
},
1366+
{
1367+
a: containingLoop,
1368+
b: extendedTile,
1369+
contains: true,
1370+
},
13351371
}
13361372

13371373
for _, test := range tests {
@@ -1817,3 +1853,17 @@ func BenchmarkLoopContainsPoint(b *testing.B) {
18171853
vertices *= 2
18181854
}
18191855
}
1856+
1857+
// TODO(rsned): Differences from C++
1858+
// TEST_F(S2LoopTestBase, CompressedEncodedLoopDecodesApproxEqual) {
1859+
// TEST_F(S2LoopTestBase, DistanceMethods) {
1860+
// TEST_F(S2LoopTestBase, GetAreaAccuracy) {
1861+
// TEST_F(S2LoopTestBase, GetAreaConsistentWithSign) {
1862+
// TEST_F(S2LoopTestBase, MakeRegularLoop) {
1863+
// TEST(S2Loop, BoundaryNear) {
1864+
// TEST(S2Loop, BoundsForLoopContainment) {
1865+
// TEST(S2LoopDeathTest, IsValidDetectsInvalidLoops) {
1866+
// TEST(S2Loop, DefaultLoopIsInvalid) {
1867+
// TEST(S2Loop, EmptyFullLossyConversions) {
1868+
// TEST(S2Loop, EncodeDecode) {
1869+
// TEST(S2Loop, LoopRelations2) {

s2/shapeindex.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@ func (s *ShapeIndexCell) numEdges() int {
129129
return e
130130
}
131131

132+
// clipped returns the clipped shape at the given index. Shapes are kept sorted in
133+
// increasing order of shape id.
134+
//
135+
// Requires: 0 <= i < len(shapes)
136+
func (s *ShapeIndexCell) clipped(i int) *clippedShape {
137+
return s.shapes[i]
138+
}
139+
132140
// add adds the given clipped shape to this index cell.
133141
func (s *ShapeIndexCell) add(c *clippedShape) {
134142
// C++ uses a set, so it's ordered and unique. We don't currently catch

s2/shapeutil.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ func newRangeIterator(index *ShapeIndex) *rangeIterator {
5252

5353
func (r *rangeIterator) cellID() CellID { return r.it.CellID() }
5454
func (r *rangeIterator) indexCell() *ShapeIndexCell { return r.it.IndexCell() }
55+
func (r *rangeIterator) clipped() *clippedShape { return r.indexCell().clipped(0) }
56+
func (r *rangeIterator) containsCenter() bool { return r.clipped().containsCenter }
5557
func (r *rangeIterator) next() { r.it.Next(); r.refresh() }
5658
func (r *rangeIterator) done() bool { return r.it.Done() }
5759

0 commit comments

Comments
 (0)