Skip to content

Commit f335e21

Browse files
rsneddsymonds
authored andcommitted
s2: Add RegionCoverer.IsCanonical.
Update the normalize covering method with the changes on C++ side from the past couple of years. Other than finishing more of the tests, this completes RegionCoverer. Signed-off-by: David Symonds <dsymonds@golang.org>
1 parent 673a6f8 commit f335e21

File tree

3 files changed

+263
-29
lines changed

3 files changed

+263
-29
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ Approximately ~40% complete.
128128
* Point
129129
* PointCompression
130130
* Region
131+
* RegionCoverer
131132
* s2edge_clipping
132133
* s2edge_crosser
133134
* s2edge_crossings
@@ -153,7 +154,6 @@ listing of the incomplete methods are documented at the end of each file.
153154
* Loop - Loop is mostly complete now. Missing Project, Distance, Union, etc.
154155
* Polyline - Missing InitTo... methods, NearlyCoversPolyline
155156
* Rect (AKA s2latlngrect in C++) - Missing Centroid, InteriorContains.
156-
* RegionCoverer - canonicalize
157157
* s2_test.go (AKA s2testing and s2textformat in C++) - Missing Fractal test
158158
shape generation. This file is a collection of testing helper methods.
159159
* s2edge_distances - Missing Intersection

s2/regioncoverer.go

Lines changed: 153 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package s2
1616

1717
import (
1818
"container/heap"
19+
"sort"
1920
)
2021

2122
// RegionCoverer allows arbitrary regions to be approximated as unions of cells (CellUnion).
@@ -78,6 +79,16 @@ type RegionCoverer struct {
7879
MaxCells int // the maximum desired number of cells in the approximation.
7980
}
8081

82+
// NewRegionCoverer returns a region coverer with the appropriate defaults.
83+
func NewRegionCoverer() *RegionCoverer {
84+
return &RegionCoverer{
85+
MinLevel: 0,
86+
MaxLevel: maxLevel,
87+
LevelMod: 1,
88+
MaxCells: 8,
89+
}
90+
}
91+
8192
type coverer struct {
8293
minLevel int // the minimum cell level to be used.
8394
maxLevel int // the maximum cell level to be used.
@@ -306,8 +317,20 @@ func (c *coverer) coveringInternal(region Region) {
306317
c.addCandidate(cand)
307318
}
308319
}
320+
309321
c.pq.Reset()
310322
c.region = nil
323+
324+
// Rather than just returning the raw list of cell ids, we construct a cell
325+
// union and then denormalize it. This has the effect of replacing four
326+
// child cells with their parent whenever this does not violate the covering
327+
// parameters specified (min_level, level_mod, etc). This significantly
328+
// reduces the number of cells returned in many cases, and it is cheap
329+
// compared to computing the covering in the first place.
330+
c.result.Normalize()
331+
if c.minLevel > 0 || c.levelMod > 1 {
332+
c.result.Denormalize(c.minLevel, c.levelMod)
333+
}
311334
}
312335

313336
// newCoverer returns an instance of coverer.
@@ -378,8 +401,26 @@ func (rc *RegionCoverer) FastCovering(region Region) CellUnion {
378401
return cu
379402
}
380403

381-
// normalizeCovering normalizes the "covering" so that it conforms to the current covering
382-
// parameters (MaxCells, minLevel, maxLevel, and levelMod).
404+
// IsCanonical reports whether the given CellUnion represents a valid covering
405+
// that conforms to the current covering parameters. In particular:
406+
//
407+
// - All CellIDs must be valid.
408+
//
409+
// - CellIDs must be sorted and non-overlapping.
410+
//
411+
// - CellID levels must satisfy MinLevel, MaxLevel, and LevelMod.
412+
//
413+
// - If the covering has more than MaxCells, there must be no two cells with
414+
// a common ancestor at MinLevel or higher.
415+
//
416+
// - There must be no sequence of cells that could be replaced by an
417+
// ancestor (i.e. with LevelMod == 1, the 4 child cells of a parent).
418+
func (rc *RegionCoverer) IsCanonical(covering CellUnion) bool {
419+
return rc.newCoverer().isCanonical(covering)
420+
}
421+
422+
// normalizeCovering normalizes the "covering" so that it conforms to the
423+
// current covering parameters (maxCells, minLevel, maxLevel, and levelMod).
383424
// This method makes no attempt to be optimal. In particular, if
384425
// minLevel > 0 or levelMod > 1 then it may return more than the
385426
// desired number of cells even when this isn't necessary.
@@ -400,6 +441,25 @@ func (c *coverer) normalizeCovering(covering *CellUnion) {
400441
// Sort the cells and simplify them.
401442
covering.Normalize()
402443

444+
// Make sure that the covering satisfies minLevel and levelMod,
445+
// possibly at the expense of satisfying MaxCells.
446+
if c.minLevel > 0 || c.levelMod > 1 {
447+
covering.Denormalize(c.minLevel, c.levelMod)
448+
}
449+
450+
// If there are too many cells and the covering is very large, use the
451+
// RegionCoverer to compute a new covering. (This avoids possible O(n^2)
452+
// behavior of the simpler algorithm below.)
453+
excess := len(*covering) - c.maxCells
454+
if excess <= 0 || c.isCanonical(*covering) {
455+
return
456+
}
457+
if excess*len(*covering) > 10000 {
458+
rc := NewRegionCoverer()
459+
(*covering) = rc.Covering(covering)
460+
return
461+
}
462+
403463
// If there are still too many cells, then repeatedly replace two adjacent
404464
// cells in CellID order by their lowest common ancestor.
405465
for len(*covering) > c.maxCells {
@@ -420,14 +480,99 @@ func (c *coverer) normalizeCovering(covering *CellUnion) {
420480
if bestLevel < c.minLevel {
421481
break
422482
}
423-
(*covering)[bestIndex] = (*covering)[bestIndex].Parent(bestLevel)
424-
covering.Normalize()
483+
484+
// Replace all cells contained by the new ancestor cell.
485+
id := (*covering)[bestIndex].Parent(bestLevel)
486+
(*covering) = c.replaceCellsWithAncestor(*covering, id)
487+
488+
// Now repeatedly check whether all children of the parent cell are
489+
// present, in which case we can replace those cells with their parent.
490+
for bestLevel > c.minLevel {
491+
bestLevel -= c.levelMod
492+
id = id.Parent(bestLevel)
493+
if !c.containsAllChildren(*covering, id) {
494+
break
495+
}
496+
(*covering) = c.replaceCellsWithAncestor(*covering, id)
497+
}
425498
}
426-
// Make sure that the covering satisfies minLevel and levelMod,
427-
// possibly at the expense of satisfying MaxCells.
428-
if c.minLevel > 0 || c.levelMod > 1 {
429-
covering.Denormalize(c.minLevel, c.levelMod)
499+
}
500+
501+
// isCanonical reports whether the covering is canonical.
502+
func (c *coverer) isCanonical(covering CellUnion) bool {
503+
trueMax := c.maxLevel
504+
if c.levelMod != 1 {
505+
trueMax = c.maxLevel - (c.maxLevel-c.minLevel)%c.levelMod
506+
}
507+
tooManyCells := len(covering) > c.maxCells
508+
sameParentCount := 1
509+
510+
prevID := CellID(0)
511+
for _, id := range covering {
512+
if !id.IsValid() {
513+
return false
514+
}
515+
516+
// Check that the CellID level is acceptable.
517+
level := id.Level()
518+
if level < c.minLevel || level > trueMax {
519+
return false
520+
}
521+
if c.levelMod > 1 && (level-c.minLevel)%c.levelMod != 0 {
522+
return false
523+
}
524+
525+
if prevID != 0 {
526+
// Check that cells are sorted and non-overlapping.
527+
if prevID.RangeMax() >= id.RangeMin() {
528+
return false
529+
}
530+
531+
lev, ok := id.CommonAncestorLevel(prevID)
532+
// If there are too many cells, check that no pair of adjacent cells
533+
// could be replaced by an ancestor.
534+
if tooManyCells && (ok && lev >= c.minLevel) {
535+
return false
536+
}
537+
538+
// Check that there are no sequences of (4 ** level_mod) cells that all
539+
// have the same parent (considering only multiples of "level_mod").
540+
pLevel := level - c.levelMod
541+
if pLevel < c.minLevel || level != prevID.Level() ||
542+
id.Parent(pLevel) != prevID.Parent(pLevel) {
543+
sameParentCount = 1
544+
} else {
545+
sameParentCount++
546+
if sameParentCount == 1<<uint(2*c.levelMod) {
547+
return false
548+
}
549+
}
550+
}
551+
prevID = id
552+
}
553+
554+
return true
555+
}
556+
557+
func (c *coverer) containsAllChildren(covering []CellID, id CellID) bool {
558+
pos := sort.Search(len(covering), func(i int) bool { return (covering)[i] >= id.RangeMin() })
559+
level := id.Level() + c.levelMod
560+
for child := id.ChildBeginAtLevel(level); child != id.ChildEndAtLevel(level); child = child.Next() {
561+
if pos == len(covering) || covering[pos] != child {
562+
return false
563+
}
564+
pos++
430565
}
566+
return true
567+
}
568+
569+
// replaceCellsWithAncestor replaces all descendants of the given id in covering
570+
// with id. This requires the covering contains at least one descendant of id.
571+
func (c *coverer) replaceCellsWithAncestor(covering []CellID, id CellID) []CellID {
572+
begin := sort.Search(len(covering), func(i int) bool { return covering[i] > id.RangeMin() })
573+
end := sort.Search(len(covering), func(i int) bool { return covering[i] > id.RangeMax() })
574+
575+
return append(append(covering[:begin], id), covering[end:]...)
431576
}
432577

433578
// SimpleRegionCovering returns a set of cells at the given level that cover
@@ -468,10 +613,3 @@ func FloodFillRegionCovering(region Region, start CellID) []CellID {
468613

469614
return output
470615
}
471-
472-
// TODO(roberts): The differences from the C++ version
473-
// finish up FastCovering to match C++
474-
// IsCanonical
475-
// CanonicalizeCovering
476-
// containsAllChildren
477-
// replaceCellsWithAncestor

s2/regioncoverer_test.go

Lines changed: 109 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,12 @@ func TestCovererRandomCells(t *testing.T) {
2727

2828
// Test random cell ids at all levels.
2929
for i := 0; i < 10000; i++ {
30-
id := CellID(randomUint64())
31-
for !id.IsValid() {
32-
id = CellID(randomUint64())
33-
}
30+
id := randomCellID()
3431
covering := rc.Covering(Region(CellFromCellID(id)))
3532
if len(covering) != 1 {
3633
t.Errorf("Iteration %d, cell ID token %s, got covering size = %d, want covering size = 1", i, id.ToToken(), len(covering))
34+
// if the covering isn't len 1, the next check will panic
35+
break
3736
}
3837
if (covering)[0] != id {
3938
t.Errorf("Iteration %d, cell ID token %s, got covering = %v, want covering = %v", i, id.ToToken(), covering, id)
@@ -115,7 +114,7 @@ func checkCoveringTight(t *testing.T, r Region, cover CellUnion, checkTight bool
115114
}
116115

117116
func TestCovererRandomCaps(t *testing.T) {
118-
rc := &RegionCoverer{}
117+
rc := &RegionCoverer{MinLevel: 0, MaxLevel: 30, LevelMod: 1, MaxCells: 1}
119118
for i := 0; i < 1000; i++ {
120119
rc.MinLevel = int(rand.Int31n(maxLevel + 1))
121120
rc.MaxLevel = int(rand.Int31n(maxLevel + 1))
@@ -194,6 +193,111 @@ func TestRegionCovererSimpleRegionCovering(t *testing.T) {
194193
}
195194
}
196195

196+
func TestRegionCovererIsCanonical(t *testing.T) {
197+
tests := []struct {
198+
cells []string
199+
cov *RegionCoverer
200+
want bool
201+
}{
202+
// InvalidCellID
203+
{cells: []string{"1/"}, cov: NewRegionCoverer(), want: true},
204+
{cells: []string{"invalid"}, cov: NewRegionCoverer(), want: false},
205+
// Unsorted
206+
{cells: []string{"1/1", "1/3"}, cov: NewRegionCoverer(), want: true},
207+
{cells: []string{"1/3", "1/1"}, cov: NewRegionCoverer(), want: false},
208+
209+
// Overlapping
210+
{cells: []string{"1/2", "1/33"}, cov: NewRegionCoverer(), want: true},
211+
{cells: []string{"1/3", "1/33"}, cov: NewRegionCoverer(), want: false},
212+
213+
// MinLevel
214+
{
215+
cells: []string{"1/31"},
216+
cov: &RegionCoverer{MinLevel: 2, MaxLevel: 30, LevelMod: 1, MaxCells: 8},
217+
want: true,
218+
},
219+
{
220+
cells: []string{"1/3"},
221+
cov: &RegionCoverer{MinLevel: 2, MaxLevel: 30, LevelMod: 1, MaxCells: 8},
222+
want: false,
223+
},
224+
225+
// MaxLevel
226+
{
227+
cells: []string{"1/31"},
228+
cov: &RegionCoverer{MinLevel: 0, MaxLevel: 2, LevelMod: 1, MaxCells: 8},
229+
want: true,
230+
},
231+
{
232+
cells: []string{"1/312"},
233+
cov: &RegionCoverer{MinLevel: 0, MaxLevel: 2, LevelMod: 1, MaxCells: 8},
234+
want: false,
235+
},
236+
237+
// LevelMod
238+
{
239+
cells: []string{"1/31"},
240+
cov: &RegionCoverer{MinLevel: 0, MaxLevel: 30, LevelMod: 2, MaxCells: 8},
241+
want: true,
242+
},
243+
{
244+
cells: []string{"1/312"},
245+
cov: &RegionCoverer{MinLevel: 0, MaxLevel: 30, LevelMod: 2, MaxCells: 8},
246+
want: false,
247+
},
248+
249+
// MaxCells
250+
{cells: []string{"1/1", "1/3"}, cov: &RegionCoverer{MinLevel: 0, MaxLevel: 30, LevelMod: 1, MaxCells: 2}, want: true},
251+
{cells: []string{"1/1", "1/3", "2/"}, cov: &RegionCoverer{MinLevel: 0, MaxLevel: 30, LevelMod: 1, MaxCells: 2}, want: false},
252+
{cells: []string{"1/123", "2/1", "3/0122"}, cov: &RegionCoverer{MinLevel: 0, MaxLevel: 30, LevelMod: 1, MaxCells: 2}, want: true},
253+
254+
// Normalized
255+
// Test that no sequence of cells could be replaced by an ancestor.
256+
{
257+
cells: []string{"1/01", "1/02", "1/03", "1/10", "1/11"},
258+
cov: NewRegionCoverer(),
259+
want: true,
260+
},
261+
{
262+
cells: []string{"1/00", "1/01", "1/02", "1/03", "1/10"},
263+
cov: NewRegionCoverer(),
264+
want: false,
265+
},
266+
267+
{
268+
cells: []string{"0/22", "1/01", "1/02", "1/03", "1/10"},
269+
cov: NewRegionCoverer(),
270+
want: true,
271+
},
272+
{
273+
cells: []string{"0/22", "1/00", "1/01", "1/02", "1/03"},
274+
cov: NewRegionCoverer(),
275+
want: false,
276+
},
277+
278+
{
279+
cells: []string{"1/1101", "1/1102", "1/1103", "1/1110", "1/1111", "1/1112",
280+
"1/1113", "1/1120", "1/1121", "1/1122", "1/1123", "1/1130",
281+
"1/1131", "1/1132", "1/1133", "1/1200"},
282+
cov: &RegionCoverer{MinLevel: 0, MaxLevel: 30, LevelMod: 2, MaxCells: 20},
283+
want: true,
284+
},
285+
{
286+
cells: []string{"1/1100", "1/1101", "1/1102", "1/1103", "1/1110", "1/1111",
287+
"1/1112", "1/1113", "1/1120", "1/1121", "1/1122", "1/1123",
288+
"1/1130", "1/1131", "1/1132", "1/1133"},
289+
cov: &RegionCoverer{MinLevel: 0, MaxLevel: 30, LevelMod: 2, MaxCells: 20},
290+
want: false,
291+
},
292+
}
293+
for _, test := range tests {
294+
cu := makeCellUnion(test.cells...)
295+
if got := test.cov.IsCanonical(cu); got != test.want {
296+
t.Errorf("IsCanonical(%v) = %t, want %t", cu, got, test.want)
297+
}
298+
}
299+
}
300+
197301
const numCoveringBMRegions = 1000
198302

199303
func BenchmarkRegionCovererCoveringCap(b *testing.B) {
@@ -278,14 +382,6 @@ func benchmarkRegionCovererCovering(b *testing.B, genLabel func(n int) string, g
278382
// TODO(roberts): Differences from C++
279383
// func TestRegionCovererAccuracy(t *testing.T) {
280384
// func TestRegionCovererFastCoveringHugeFixedLevelCovering(t *testing.T) {
281-
// func TestRegionCovererIsCanonicalInvalidS2CellId(t *testing.T) {
282-
// func TestRegionCovererIsCanonicalUnsorted(t *testing.T) {
283-
// func TestRegionCovererIsCanonicalOverlapping(t *testing.T) {
284-
// func TestRegionCovererIsCanonicalMinLevel(t *testing.T) {
285-
// func TestRegionCovererIsCanonicalMaxLevel(t *testing.T) {
286-
// func TestRegionCovererIsCanonicalLevelMod(t *testing.T) {
287-
// func TestRegionCovererIsCanonicalMaxCells(t *testing.T) {
288-
// func TestRegionCovererIsCanonicalNormalized(t *testing.T) {
289385
// func TestRegionCovererCanonicalizeCoveringUnsortedDuplicateCells(t *testing.T) {
290386
// func TestRegionCovererCanonicalizeCoveringMaxLevelExceeded(t *testing.T) {
291387
// func TestRegionCovererCanonicalizeCoveringWrongLevelMod(t *testing.T) {

0 commit comments

Comments
 (0)