From f69153efb007e250d6322a00fc8935c2fc1d8306 Mon Sep 17 00:00:00 2001 From: Josh Baker Date: Fri, 19 Aug 2016 07:47:39 -0700 Subject: [PATCH] removed quadtree --- index/index.go | 100 ++++---------------- index/index_test.go | 7 +- index/qtree/qtree.go | 189 -------------------------------------- index/qtree/qtree_test.go | 131 -------------------------- 4 files changed, 22 insertions(+), 405 deletions(-) delete mode 100644 index/qtree/qtree.go delete mode 100644 index/qtree/qtree_test.go diff --git a/index/index.go b/index/index.go index 5c2c479d..8db768dc 100644 --- a/index/index.go +++ b/index/index.go @@ -1,14 +1,11 @@ package index -import ( - "github.com/tidwall/tile38/index/qtree" - "github.com/tidwall/tile38/index/rtree" -) +import "github.com/tidwall/tile38/index/rtree" // Item represents an index item. type Item interface { - qtree.Item - rtree.Item + Point() (x, y float64) + Rect() (minX, minY, maxX, maxY float64) } // FlexItem can represent a point or a rectangle @@ -28,25 +25,17 @@ func (item *FlexItem) Point() (x, y float64) { // Index is a geospatial index type Index struct { - q *qtree.QTree - r *rtree.RTree - - np map[*qtree.Point]Item // normalized points - npr map[Item]*qtree.Point // normalized points - nr map[*rtree.Rect]Item // normalized points - nrr map[Item][]*rtree.Rect // normalized points - - mulm map[Item]bool // store items that contain multiple rects + r *rtree.RTree + nr map[*rtree.Rect]Item // normalized points + nrr map[Item][]*rtree.Rect // normalized points + mulm map[Item]bool // store items that contain multiple rects } // New create a new index func New() *Index { return &Index{ - q: qtree.New(-180, -90, 180, 90), r: rtree.New(), mulm: make(map[Item]bool), - np: make(map[*qtree.Point]Item), - npr: make(map[Item]*qtree.Point), nr: make(map[*rtree.Rect]Item), nrr: make(map[Item][]*rtree.Rect), } @@ -58,12 +47,12 @@ func (ix *Index) Insert(item Item) { if minX == maxX && minY == maxY { x, y, normd := normPoint(minY, minX) if normd { - nitem := &qtree.Point{X: x, Y: y} - ix.np[nitem] = item - ix.npr[item] = nitem - ix.q.Insert(nitem) + nitem := &rtree.Rect{MinX: x, MinY: y, MaxX: x, MaxY: y} + ix.nr[nitem] = item + ix.nrr[item] = []*rtree.Rect{nitem} + ix.r.Insert(nitem) } else { - ix.q.Insert(item) + ix.r.Insert(item) } } else { mins, maxs, normd := normRect(minY, minX, maxY, maxX) @@ -89,25 +78,14 @@ func (ix *Index) Insert(item Item) { // Remove removed an item from the index func (ix *Index) Remove(item Item) { - minX, minY, maxX, maxY := item.Rect() - if minX == maxX && minY == maxY { - if nitem, ok := ix.npr[item]; ok { - ix.q.Remove(nitem) - delete(ix.np, nitem) - delete(ix.npr, item) - } else { - ix.q.Remove(item) + if nitems, ok := ix.nrr[item]; ok { + for _, nitem := range nitems { + ix.r.Remove(nitem) + delete(ix.nr, nitem) } + delete(ix.nrr, item) } else { - if nitems, ok := ix.nrr[item]; ok { - for _, nitem := range nitems { - ix.r.Remove(nitem) - delete(ix.nr, nitem) - } - delete(ix.npr, item) - } else { - ix.r.Remove(item) - } + ix.r.Remove(item) } } @@ -124,17 +102,6 @@ func (ix *Index) Count() int { // RemoveAll removes all items from the index. func (ix *Index) RemoveAll() { ix.r.RemoveAll() - ix.q.RemoveAll() -} - -func (ix *Index) getQTreeItem(item qtree.Item) Item { - switch item := item.(type) { - case Item: - return item - case *qtree.Point: - return ix.np[item] - } - return nil } func (ix *Index) getRTreeItem(item rtree.Item) Item { @@ -156,19 +123,6 @@ func (ix *Index) Search(cursor uint64, swLat, swLon, neLat, neLon float64, itera // Points if len(mins) == 1 { // There is only one rectangle. - // Simply return all quad points in that search rect. - if active { - ix.q.Search(mins[0][0], mins[0][1], maxs[0][0], maxs[0][1], func(item qtree.Item) bool { - if idx >= cursor { - iitm := ix.getQTreeItem(item) - if iitm != nil { - active = iterator(iitm) - } - } - idx++ - return active - }) - } // It's possible that a r rect may span multiple entries. Check mulm map for spanning rects. if active { ix.r.Search(mins[0][0], mins[0][1], maxs[0][0], maxs[0][1], func(item rtree.Item) bool { @@ -191,24 +145,6 @@ func (ix *Index) Search(cursor uint64, swLat, swLon, neLat, neLon float64, itera } } else { // There are multiple rectangles. Duplicates might occur. - for i := range mins { - if active { - ix.q.Search(mins[i][0], mins[i][1], maxs[i][0], maxs[i][1], func(item qtree.Item) bool { - if idx >= cursor { - iitm := ix.getQTreeItem(item) - if iitm != nil { - if !idm[iitm] { - idm[iitm] = true - active = iterator(iitm) - } - } - } - idx++ - return active - - }) - } - } for i := range mins { if active { ix.r.Search(mins[i][0], mins[i][1], maxs[i][0], maxs[i][1], func(item rtree.Item) bool { diff --git a/index/index_test.go b/index/index_test.go index da1d1eb7..80453956 100644 --- a/index/index_test.go +++ b/index/index_test.go @@ -126,9 +126,10 @@ func TestRandomInserts(t *testing.T) { } tr.RemoveAll() - if tr.getQTreeItem(nil) != nil { - t.Fatal("getQTreeItem(nil) should return nil") - } + /* if tr.getQTreeItem(nil) != nil { + t.Fatal("getQTreeItem(nil) should return nil") + } + */ if tr.getRTreeItem(nil) != nil { t.Fatal("getRTreeItem(nil) should return nil") } diff --git a/index/qtree/qtree.go b/index/qtree/qtree.go deleted file mode 100644 index 39f78ff1..00000000 --- a/index/qtree/qtree.go +++ /dev/null @@ -1,189 +0,0 @@ -package qtree - -// Item is a qtree item -type Item interface { - Point() (x, y float64) -} - -// Point is point -type Point struct { - X, Y float64 -} - -// Point returns the point -func (item *Point) Point() (x, y float64) { - return item.X, item.Y -} - -const maxPoints = 16 -const appendGrowth = false // Set 'true' for faster inserts, Set 'false' for smaller memory size - -type nodeT struct { - count int - points []Item - nodes [4]*nodeT -} - -// QTree is an implementation of a quad tree -type QTree struct { - root *nodeT - minX, minY float64 - maxX, maxY float64 -} - -// New creates a new QTree -func New(minX, minY, maxX, maxY float64) *QTree { - return &QTree{&nodeT{}, minX, minY, maxX, maxY} -} - -func (tr *QTree) clip(item Item) (float64, float64) { - x, y := item.Point() - if x < tr.minX { - x = tr.minX - } else if x > tr.maxX { - x = tr.maxX - } - if y < tr.minY { - y = tr.minY - } else if y > tr.maxY { - y = tr.maxY - } - return x, y -} - -func split(minX, minY, maxX, maxY, cx, cy float64) (quad int, nMinX, nMinY, nMaxX, nMaxY float64) { - if cx < (maxX-minX)/2+minX { - if cy < (maxY-minY)/2+minY { - return 2, minX, minY, (maxX-minX)/2 + minX, (maxY-minY)/2 + minY - } - return 0, minX, (maxY-minY)/2 + minY, (maxX-minX)/2 + minX, maxY - } - if cy < (maxY-minY)/2+minY { - return 3, (maxX-minX)/2 + minX, minY, maxX, (maxY-minY)/2 + minY - } - return 1, (maxX-minX)/2 + minX, (maxY-minY)/2 + minY, maxX, maxY -} - -// Insert inserts an item into the tree -func (tr *QTree) Insert(item Item) { - cx, cy := tr.clip(item) - insert(tr.root, tr.minX, tr.minY, tr.maxX, tr.maxY, cx, cy, item) -} - -// Remove removes an item from the tree -func (tr *QTree) Remove(item Item) { - cx, cy := tr.clip(item) - remove(tr.root, tr.minX, tr.minY, tr.maxX, tr.maxY, cx, cy, item) -} - -// Search finds all items contained in a bounding box -func (tr *QTree) Search(minX, minY, maxX, maxY float64, iterator func(item Item) bool) { - search(tr.root, tr.minX, tr.minY, tr.maxX, tr.maxY, minX, minY, maxX, maxY, true, iterator) -} - -// Count counts all of the items in the tree -func (tr *QTree) Count() int { - return count(tr.root, 0) -} - -// RemoveAll removes all items from the tree -func (tr *QTree) RemoveAll() { - tr.root = &nodeT{} -} - -func insert(node *nodeT, nMinX, nMinY, nMaxX, nMaxY, cx, cy float64, item Item) { - if node.count < maxPoints { - if len(node.points) == node.count { - if appendGrowth { - node.points = append(node.points, item) - } else { - npoints := make([]Item, node.count+1) - copy(npoints, node.points) - node.points = npoints - node.points[node.count] = item - } - } else { - node.points[node.count] = item - } - node.count++ - } else { - var quad int - quad, nMinX, nMinY, nMaxX, nMaxY = split(nMinX, nMinY, nMaxX, nMaxY, cx, cy) - if node.nodes[quad] == nil { - node.nodes[quad] = &nodeT{} - } - insert(node.nodes[quad], nMinX, nMinY, nMaxX, nMaxY, cx, cy, item) - } -} - -func remove(node *nodeT, nMinX, nMinY, nMaxX, nMaxY, cx, cy float64, item Item) { - for i := 0; i < node.count; i++ { - if node.points[i] == item { - node.points[i] = node.points[node.count-1] - node.count-- - return - } - } - var quad int - quad, nMinX, nMinY, nMaxX, nMaxY = split(nMinX, nMinY, nMaxX, nMaxY, cx, cy) - if node.nodes[quad] != nil { - remove(node.nodes[quad], nMinX, nMinY, nMaxX, nMaxY, cx, cy, item) - } -} - -func count(node *nodeT, counter int) int { - counter += node.count - for i := 0; i < 4; i++ { - if node.nodes[i] != nil { - counter = count(node.nodes[i], counter) - } - } - return counter -} - -func doesOverlap(nMinX, nMinY, nMaxX, nMaxY float64, minX, minY, maxX, maxY float64) bool { - if nMinX > maxX || minX > nMaxX { - return false - } - if nMinY > maxY || minY > nMaxY { - return false - } - return true -} - -func search(node *nodeT, nMinX, nMinY, nMaxX, nMaxY float64, minX, minY, maxX, maxY float64, overlap bool, iterator func(item Item) bool) bool { - if overlap { - overlap = doesOverlap(nMinX, nMinY, nMaxX, nMaxY, minX, minY, maxX, maxY) - } - if !overlap { - return true - } - for i := 0; i < node.count; i++ { - item := node.points[i] - x, y := item.Point() - if x >= minX && x <= maxX && y >= minY && y <= maxY { - if !iterator(item) { - return false - } - } - } - var qMinX, qMaxX, qMinY, qMaxY float64 - for i := 0; i < 4; i++ { - if node.nodes[i] != nil { - switch i { - case 0: - qMinX, qMinY, qMaxX, qMaxY = nMinX, (nMaxY-nMinY)/2+nMinY, (nMaxX-nMinX)/2+nMinX, nMaxY - case 1: - qMinX, qMinY, qMaxX, qMaxY = (nMaxX-nMinX)/2+nMinX, (nMaxY-nMinY)/2+nMinY, nMaxX, nMaxY - case 2: - qMinX, qMinY, qMaxX, qMaxY = nMinX, nMinY, (nMaxX-nMinX)/2+nMinX, (nMaxY-nMinY)/2+nMinY - case 3: - qMinX, qMinY, qMaxX, qMaxY = (nMaxX-nMinX)/2+nMinX, nMinY, nMaxX, (nMaxY-nMinY)/2+nMinY - } - if !search(node.nodes[i], qMinX, qMinY, qMaxX, qMaxY, minX, minY, maxX, maxY, overlap, iterator) { - return false - } - } - } - return true -} diff --git a/index/qtree/qtree_test.go b/index/qtree/qtree_test.go deleted file mode 100644 index 7e4656f0..00000000 --- a/index/qtree/qtree_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package qtree - -import ( - "math/rand" - "runtime" - "testing" -) - -func randf(min, max float64) float64 { - return rand.Float64()*(max-min) + min -} -func randXY() (x float64, y float64) { - return randf(0, 100), randf(0, 100) -} -func randPoint() (lat float64, lon float64) { - return randf(-90, 90), randf(-180, 180) -} - -func wp(x, y float64) *Point { - return &Point{x, y} -} - -func TestClip(t *testing.T) { - tr := New(-180, -90, 180, 90) - if x, y := tr.clip(wp(-900, 100)); x != -180 || y != 90 { - t.Fatalf("x,y == %f,%f, expect %f,%f", x, y, -180.0, 90.0) - } - if x, y := tr.clip(wp(900, -100)); x != 180 || y != -90 { - t.Fatalf("x,y == %f,%f, expect %f,%f", x, y, 180.0, -90.0) - } - if x, y := tr.clip(wp(100, 100)); x != 100 || y != 90 { - t.Fatalf("x,y == %f,%f, expect %f,%f", x, y, 100.0, 90.0) - } - if x, y := tr.clip(wp(50, 50)); x != 50 || y != 50 { - t.Fatalf("x,y == %f,%f, expect %f,%f", x, y, 50.0, 50.0) - } - if x, y := tr.clip(wp(-50, -50)); x != -50 || y != -50 { - t.Fatalf("x,y == %f,%f, expect %f,%f", x, y, -50.0, -50.0) - } -} - -func TestSimpleSplit(t *testing.T) { - quad, nMinX, nMinY, nMaxX, nMaxY := split(0, 0, 100, 100, 0, 100) - if quad != 0 || nMinX != 0 || nMinY != 50 || nMaxX != 50 || nMaxY != 100 { - t.Fatalf("failed 0: %d, %f, %f, %f, %f\n", quad, nMinX, nMinY, nMaxX, nMaxY) - } - quad, nMinX, nMinY, nMaxX, nMaxY = split(0, 0, 100, 100, 100, 100) - if quad != 1 || nMinX != 50 || nMinY != 50 || nMaxX != 100 || nMaxY != 100 { - t.Fatalf("failed 1: %d, %f, %f, %f, %f\n", quad, nMinX, nMinY, nMaxX, nMaxY) - } - quad, nMinX, nMinY, nMaxX, nMaxY = split(0, 0, 100, 100, 0, 0) - if quad != 2 || nMinX != 0 || nMinY != 0 || nMaxX != 50 || nMaxY != 50 { - t.Fatalf("failed 2: %d, %f, %f, %f, %f\n", quad, nMinX, nMinY, nMaxX, nMaxY) - } - quad, nMinX, nMinY, nMaxX, nMaxY = split(0, 0, 100, 100, 100, 0) - if quad != 3 || nMinX != 50 || nMinY != 0 || nMaxX != 100 || nMaxY != 50 { - t.Fatalf("failed 3: %d, %f, %f, %f, %f\n", quad, nMinX, nMinY, nMaxX, nMaxY) - } -} - -func TestGeoSplit(t *testing.T) { - quad, nMinX, nMinY, nMaxX, nMaxY := split(-180, -90, 180, 90, -180, 90) - if quad != 0 || nMinX != -180 || nMinY != 0 || nMaxX != 0 || nMaxY != 90 { - t.Fatalf("failed 0: %d, %f, %f, %f, %f\n", quad, nMinX, nMinY, nMaxX, nMaxY) - } - quad, nMinX, nMinY, nMaxX, nMaxY = split(-180, -90, 180, 90, 180, 90) - if quad != 1 || nMinX != 0 || nMinY != 0 || nMaxX != 180 || nMaxY != 90 { - t.Fatalf("failed 1: %d, %f, %f, %f, %f\n", quad, nMinX, nMinY, nMaxX, nMaxY) - } - quad, nMinX, nMinY, nMaxX, nMaxY = split(-180, -90, 180, 90, -180, -90) - if quad != 2 || nMinX != -180 || nMinY != -90 || nMaxX != 0 || nMaxY != 0 { - t.Fatalf("failed 2: %d, %f, %f, %f, %f\n", quad, nMinX, nMinY, nMaxX, nMaxY) - } - quad, nMinX, nMinY, nMaxX, nMaxY = split(-180, -90, 180, 90, 180, -90) - if quad != 3 || nMinX != 0 || nMinY != -90 || nMaxX != 180 || nMaxY != 0 { - t.Fatalf("failed 3: %d, %f, %f, %f, %f\n", quad, nMinX, nMinY, nMaxX, nMaxY) - } -} - -func TestGeoInsert(t *testing.T) { - tr := New(-180, -90, 180, 90) - l := 50000 - for i := 0; i < l; i++ { - swLat, swLon := randPoint() - tr.Insert(wp(swLon, swLat)) - } - count := 0 - tr.Search(-180, -90, 180, 90, func(item Item) bool { - count++ - return true - }) - if count != l { - t.Fatalf("count == %d, expect %d", count, l) - } -} - -func TestMemory(t *testing.T) { - rand.Seed(0) - tr := New(0, 0, 100, 100) - for i := 0; i < 500000; i++ { - x, y := randXY() - tr.Insert(wp(x, y)) - } - runtime.GC() - var m runtime.MemStats - runtime.ReadMemStats(&m) - println(int(m.HeapAlloc)/tr.Count(), "bytes/point") -} - -func BenchmarkInsert(b *testing.B) { - rand.Seed(0) - tr := New(0, 0, 100, 100) - for i := 0; i < b.N; i++ { - x, y := randXY() - tr.Insert(wp(x, y)) - } - count := 0 - tr.Search(0, 0, 100, 100, func(item Item) bool { - count++ - return true - }) - if count != b.N { - b.Fatalf("count == %d, expect %d", count, b.N) - } - - // tr.Search(0, 0, 100, 100, func(id int) bool { - // count++ - // return true - // }) - //println(tr.Count()) -}