diff --git a/Gopkg.lock b/Gopkg.lock index 5b645dbb..4ab4f9fc 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -196,14 +196,6 @@ pruneopts = "" revision = "cefed15a0bd808d13947f228770a81b06ebe8e45" -[[projects]] - branch = "master" - digest = "1:6e319cc90f8432f0ac3f78e6bb7be410e6939b6405e1ba84ae4e920de15476f3" - name = "github.com/tidwall/boxtree" - packages = ["d2"] - pruneopts = "" - revision = "a570caa42c5e4c65f50e6f7ca0e7fa1c5084981c" - [[projects]] branch = "master" digest = "1:b28f2f9253cbb1bf2bcb3c0ab7421d2f88a245386199a6668b0a66eb09ce3e1f" @@ -221,7 +213,18 @@ version = "v1.1.0" [[projects]] - digest = "1:16a87d3d8d3808f3206c4992d1fb50440bb7d5508fbe5bfab7d899b516c7f3b8" + digest = "1:4305de55e110aa13ec68a246b12e512041fe92440e78066fcea93ecf2a68320b" + name = "github.com/tidwall/geoindex" + packages = [ + ".", + "child", + ] + pruneopts = "" + revision = "e56705dcd2788d8eb431e8cb15295bfd0a298976" + version = "v1.0.1" + +[[projects]] + digest = "1:ddb305f09be3613fd1bf9fd8d6d0713f2fd28b5af596437b3d7de2366bbee870" name = "github.com/tidwall/geojson" packages = [ ".", @@ -229,8 +232,8 @@ "geometry", ] pruneopts = "" - revision = "ff8d554639ee72ffa650eca1e02f03ab51193c7f" - version = "v1.1.6" + revision = "09ce8fa8548966071daf8df6bfc692cf756ff8cc" + version = "v1.1.7" [[projects]] digest = "1:30e9a79822702670b96d3461aca7da11b8cc6e7954eb4e859e886559ed4802a4" @@ -272,6 +275,14 @@ pruneopts = "" revision = "65a9db5fad5105a89e17f38adcc9878685be6d78" +[[projects]] + digest = "1:3deeba766e407673583fcef7135199c081c4a236071511b6c4cac412335bcecc" + name = "github.com/tidwall/rbang" + packages = ["."] + pruneopts = "" + revision = "55391bcd9942773f84554000f0c9600345e3ef92" + version = "v1.1.0" + [[projects]] branch = "master" digest = "1:e84d0aa788bd55e938ebbaa62782385ca4da00b63c1d6bf23270c031a2ae9a88" @@ -460,9 +471,9 @@ "github.com/nats-io/go-nats", "github.com/peterh/liner", "github.com/streadway/amqp", - "github.com/tidwall/boxtree/d2", "github.com/tidwall/btree", "github.com/tidwall/buntdb", + "github.com/tidwall/geoindex", "github.com/tidwall/geojson", "github.com/tidwall/geojson/geo", "github.com/tidwall/geojson/geometry", @@ -470,6 +481,7 @@ "github.com/tidwall/lotsa", "github.com/tidwall/match", "github.com/tidwall/pretty", + "github.com/tidwall/rbang", "github.com/tidwall/redbench", "github.com/tidwall/redcon", "github.com/tidwall/resp", diff --git a/Gopkg.toml b/Gopkg.toml index fd771cd0..516f3f55 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -29,13 +29,9 @@ required = [ branch = "master" name = "github.com/tidwall/tinybtree" -[[constraint]] - branch = "master" - name = "github.com/tidwall/boxtree" - [[constraint]] name = "github.com/tidwall/geojson" - version = "1.1.6" + version = "1.1.7" [[constraint]] name = "github.com/Shopify/sarama" diff --git a/internal/collection/collection.go b/internal/collection/collection.go index 8562c3d4..481d6752 100644 --- a/internal/collection/collection.go +++ b/internal/collection/collection.go @@ -3,11 +3,12 @@ package collection import ( "runtime" - "github.com/tidwall/boxtree/d2" "github.com/tidwall/btree" + "github.com/tidwall/geoindex" "github.com/tidwall/geojson" "github.com/tidwall/geojson/geo" "github.com/tidwall/geojson/geometry" + "github.com/tidwall/rbang" "github.com/tidwall/tile38/internal/deadline" "github.com/tidwall/tinybtree" ) @@ -42,7 +43,7 @@ func (item *itemT) Less(other btree.Item, ctx interface{}) bool { // Collection represents a collection of geojson objects. type Collection struct { items tinybtree.BTree // items sorted by keys - index d2.BoxTree // items geospatially indexed + index *geoindex.Index // items geospatially indexed values *btree.BTree // items sorted by value+key fieldMap map[string]int fieldValues map[string][]float64 @@ -57,7 +58,8 @@ var counter uint64 // New creates an empty collection func New() *Collection { col := &Collection{ - values: btree.New(16, nil), + index: geoindex.Wrap(&rbang.RTree{}), + values: btree.New(32, nil), fieldMap: make(map[string]int), } return col @@ -130,8 +132,8 @@ func (c *Collection) indexDelete(item *itemT) { if !item.obj.Empty() { rect := item.obj.Rect() c.index.Delete( - []float64{rect.Min.X, rect.Min.Y}, - []float64{rect.Max.X, rect.Max.Y}, + [2]float64{rect.Min.X, rect.Min.Y}, + [2]float64{rect.Max.X, rect.Max.Y}, item) } } @@ -140,8 +142,8 @@ func (c *Collection) indexInsert(item *itemT) { if !item.obj.Empty() { rect := item.obj.Rect() c.index.Insert( - []float64{rect.Min.X, rect.Min.Y}, - []float64{rect.Max.X, rect.Max.Y}, + [2]float64{rect.Min.X, rect.Min.Y}, + [2]float64{rect.Max.X, rect.Max.Y}, item) } } @@ -497,9 +499,9 @@ func (c *Collection) geoSearch( ) bool { alive := true c.index.Search( - []float64{rect.Min.X, rect.Min.Y}, - []float64{rect.Max.X, rect.Max.Y}, - func(_, _ []float64, itemv interface{}) bool { + [2]float64{rect.Min.X, rect.Min.Y}, + [2]float64{rect.Max.X, rect.Max.Y}, + func(_, _ [2]float64, itemv interface{}) bool { item := itemv.(*itemT) alive = iter(item.id, item.obj, c.getFieldValues(item.id)) return alive @@ -687,9 +689,9 @@ func (c *Collection) Nearby( geo.RectFromCenter(center.Y, center.X, meters) var exists bool c.index.Search( - []float64{minLon, minLat}, - []float64{maxLon, maxLat}, - func(_, _ []float64, itemv interface{}) bool { + [2]float64{minLon, minLat}, + [2]float64{maxLon, maxLat}, + func(_, _ [2]float64, itemv interface{}) bool { exists = true return false }, @@ -710,9 +712,11 @@ func (c *Collection) Nearby( cursor.Step(offset) } c.index.Nearby( - []float64{center.X, center.Y}, - []float64{center.X, center.Y}, - func(_, _ []float64, itemv interface{}) bool { + geoindex.SimpleBoxAlgo( + [2]float64{center.X, center.Y}, + [2]float64{center.X, center.Y}, + ), + func(_, _ [2]float64, itemv interface{}, _ float64) bool { count++ if count <= offset { return true diff --git a/internal/server/aof.go b/internal/server/aof.go index afcb1f08..a82d7de2 100644 --- a/internal/server/aof.go +++ b/internal/server/aof.go @@ -219,9 +219,9 @@ func (server *Server) getQueueCandidates(d *commandDetails) []*Hook { } rect := obj.Rect() server.hookTree.Search( - []float64{rect.Min.X, rect.Min.Y}, - []float64{rect.Max.X, rect.Max.Y}, - func(_, _ []float64, value interface{}) bool { + [2]float64{rect.Min.X, rect.Min.Y}, + [2]float64{rect.Max.X, rect.Max.Y}, + func(_, _ [2]float64, value interface{}) bool { hook := value.(*Hook) if hook.Key != d.key { return true diff --git a/internal/server/crud.go b/internal/server/crud.go index 4ddf4b5a..6c4aeca2 100644 --- a/internal/server/crud.go +++ b/internal/server/crud.go @@ -8,9 +8,9 @@ import ( "time" "github.com/mmcloughlin/geohash" - "github.com/tidwall/boxtree/d2" "github.com/tidwall/geojson" "github.com/tidwall/geojson/geometry" + "github.com/tidwall/rbang" "github.com/tidwall/resp" "github.com/tidwall/tile38/internal/collection" "github.com/tidwall/tile38/internal/glob" @@ -527,7 +527,7 @@ func (server *Server) cmdFlushDB(msg *Message) (res resp.Value, d commandDetails server.expires = make(map[string]map[string]time.Time) server.hooks = make(map[string]*Hook) server.hooksOut = make(map[string]*Hook) - server.hookTree = d2.BoxTree{} + server.hookTree = rbang.RTree{} d.command = "flushdb" d.updated = true d.timestamp = time.Now() diff --git a/internal/server/hooks.go b/internal/server/hooks.go index a84ddaa1..e9a8315b 100644 --- a/internal/server/hooks.go +++ b/internal/server/hooks.go @@ -193,16 +193,16 @@ func (c *Server) cmdSetHook(msg *Message, chanCmd bool) ( if prevHook != nil && prevHook.Fence != nil && prevHook.Fence.obj != nil { rect := prevHook.Fence.obj.Rect() c.hookTree.Delete( - []float64{rect.Min.X, rect.Min.Y}, - []float64{rect.Max.X, rect.Max.Y}, + [2]float64{rect.Min.X, rect.Min.Y}, + [2]float64{rect.Max.X, rect.Max.Y}, prevHook) } // add hook to spatial index if hook != nil && hook.Fence != nil && hook.Fence.obj != nil { rect := hook.Fence.obj.Rect() c.hookTree.Insert( - []float64{rect.Min.X, rect.Min.Y}, - []float64{rect.Max.X, rect.Max.Y}, + [2]float64{rect.Min.X, rect.Min.Y}, + [2]float64{rect.Max.X, rect.Max.Y}, hook) } @@ -242,8 +242,8 @@ func (c *Server) cmdDelHook(msg *Message, chanCmd bool) ( if hook != nil && hook.Fence != nil && hook.Fence.obj != nil { rect := hook.Fence.obj.Rect() c.hookTree.Delete( - []float64{rect.Min.X, rect.Min.Y}, - []float64{rect.Max.X, rect.Max.Y}, + [2]float64{rect.Min.X, rect.Min.Y}, + [2]float64{rect.Max.X, rect.Max.Y}, hook) } d.updated = true @@ -294,8 +294,8 @@ func (c *Server) cmdPDelHook(msg *Message, channel bool) ( if hook != nil && hook.Fence != nil && hook.Fence.obj != nil { rect := hook.Fence.obj.Rect() c.hookTree.Delete( - []float64{rect.Min.X, rect.Min.Y}, - []float64{rect.Max.X, rect.Max.Y}, + [2]float64{rect.Min.X, rect.Min.Y}, + [2]float64{rect.Max.X, rect.Max.Y}, hook) } d.updated = true diff --git a/internal/server/server.go b/internal/server/server.go index 9e163766..b933f1a8 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -22,10 +22,10 @@ import ( "sync/atomic" "time" - "github.com/tidwall/boxtree/d2" "github.com/tidwall/buntdb" "github.com/tidwall/geojson" "github.com/tidwall/geojson/geometry" + "github.com/tidwall/rbang" "github.com/tidwall/redcon" "github.com/tidwall/resp" "github.com/tidwall/tile38/core" @@ -116,7 +116,7 @@ type Server struct { shrinking bool // aof shrinking flag shrinklog [][]string // aof shrinking log hooks map[string]*Hook // hook name - hookTree d2.BoxTree // hook spatial tree containing all + hookTree rbang.RTree // hook spatial tree containing all hooksOut map[string]*Hook // hooks with "outside" detection aofconnM map[net.Conn]bool luascripts *lScriptMap diff --git a/vendor/github.com/tidwall/boxtree/boxtree.go b/vendor/github.com/tidwall/boxtree/boxtree.go deleted file mode 100644 index d218a048..00000000 --- a/vendor/github.com/tidwall/boxtree/boxtree.go +++ /dev/null @@ -1,36 +0,0 @@ -package boxtree - -import ( - "github.com/tidwall/boxtree/d2" - "github.com/tidwall/boxtree/d3" -) - -// BoxTree is an rtree by a different name -type BoxTree interface { - Insert(min, max []float64, value interface{}) - Delete(min, max []float64, value interface{}) - Search(min, max []float64, - iter func(min, max []float64, value interface{}) bool, - ) - TotalOverlapArea() float64 - Traverse(iter func(min, max []float64, height, level int, - value interface{}) int) - Scan(iter func(min, max []float64, value interface{}) bool) - Nearby(min, max []float64, - iter func(min, max []float64, item interface{}) bool, - ) - Bounds() (min, max []float64) - Count() int -} - -// New returns are new BoxTree, only 2 dims are allows -func New(dims int) BoxTree { - switch dims { - default: - panic("invalid dimensions") - case 2: - return new(d2.BoxTree) - case 3: - return new(d3.BoxTree) - } -} diff --git a/vendor/github.com/tidwall/boxtree/boxtree_test.go b/vendor/github.com/tidwall/boxtree/boxtree_test.go deleted file mode 100644 index 8eaa42a5..00000000 --- a/vendor/github.com/tidwall/boxtree/boxtree_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package boxtree - -import ( - "fmt" - "math/rand" - "os" - "testing" - "time" - - "github.com/tidwall/lotsa" -) - -func TestBoxTree(t *testing.T) { - New(2) - New(3) - defer func() { - s := recover().(string) - if s != "invalid dimensions" { - t.Fatalf("expected '%s', got '%s'", "invalid dimensions", s) - } - }() - New(4) - // there are more test in the d2/d3 directories -} -func TestBenchInsert2D(t *testing.T) { - testBenchInsert(t, 100000, 2) -} - -func TestBenchInsert3D(t *testing.T) { - testBenchInsert(t, 100000, 3) -} - -func testBenchInsert(t *testing.T, N, D int) { - rand.Seed(time.Now().UnixNano()) - points := make([]float64, N*D) - for i := 0; i < N; i++ { - for j := 0; j < D; j++ { - points[i*D+j] = rand.Float64()*100 - 50 - } - } - tr := New(D) - lotsa.Output = os.Stdout - fmt.Printf("Insert(%dD): ", D) - lotsa.Ops(N, 1, func(i, _ int) { - tr.Insert(points[i*D+0:i*D+D], nil, i) - }) - fmt.Printf("Search(%dD): ", D) - var count int - lotsa.Ops(N, 1, func(i, _ int) { - tr.Search(points[i*D+0:i*D+D], points[i*D+0:i*D+D], - func(min, max []float64, value interface{}) bool { - count++ - return true - }, - ) - }) - if count != N { - t.Fatalf("expected %d, got %d", N, count) - } - fmt.Printf("Delete(%dD): ", D) - lotsa.Ops(N, 1, func(i, _ int) { - tr.Delete(points[i*D+0:i*D+D], points[i*D+0:i*D+D], i) - }) - if tr.Count() != 0 { - t.Fatalf("expected %d, got %d", N, tr.Count()) - } -} - -type tItem2 struct { - point [2]float64 -} - -func (item *tItem2) Point() (x, y float64) { - return item.point[0], item.point[1] -} -func (item *tItem2) Rect() (minX, minY, maxX, maxY float64) { - return item.point[0], item.point[1], item.point[0], item.point[1] -} - -/////////////////////////////////////////////// -// Old Tile38 Index < July 27, 2018 -/////////////////////////////////////////////// -// func TestBenchInsert2D_Old(t *testing.T) { -// // import "github.com/tidwall/tile38/pkg/index" -// N := 100000 -// D := 2 -// rand.Seed(time.Now().UnixNano()) -// items := make([]*tItem2, N*D) -// for i := 0; i < N; i++ { -// items[i] = new(tItem2) -// for j := 0; j < D; j++ { -// items[i].point[j] = rand.Float64()*100 - 50 -// } -// } - -// tr := index.New() -// lotsa.Output = os.Stdout -// fmt.Printf("Insert(%dD): ", D) -// lotsa.Ops(N, 1, func(i, _ int) { -// tr.Insert(items[i]) -// }) -// fmt.Printf("Search(%dD): ", D) -// var count int -// lotsa.Ops(N, 1, func(i, _ int) { -// tr.Search( -// items[i].point[0], items[i].point[1], -// items[i].point[0], items[i].point[1], -// func(_ interface{}) bool { -// count++ -// return true -// }, -// ) -// }) -// if count != N { -// t.Fatalf("expected %d, got %d", N, count) -// } -// fmt.Printf("Delete(%dD): ", D) -// lotsa.Ops(N, 1, func(i, _ int) { -// tr.Remove(items[i]) -// }) -// if tr.Count() != 0 { -// t.Fatalf("expected %d, got %d", N, tr.Count()) -// } - -// } diff --git a/vendor/github.com/tidwall/boxtree/d2/boxtree.go b/vendor/github.com/tidwall/boxtree/d2/boxtree.go deleted file mode 100644 index 47c63e6a..00000000 --- a/vendor/github.com/tidwall/boxtree/d2/boxtree.go +++ /dev/null @@ -1,707 +0,0 @@ -package d2 - -const dims = 2 - -const ( - maxEntries = 16 - minEntries = maxEntries * 40 / 100 -) - -type box struct { - data interface{} - min, max [dims]float64 -} - -type node struct { - count int - boxes [maxEntries + 1]box -} - -// BoxTree ... -type BoxTree struct { - height int - root box - count int - reinsert []box -} - -func (r *box) expand(b *box) { - for i := 0; i < dims; i++ { - if b.min[i] < r.min[i] { - r.min[i] = b.min[i] - } - if b.max[i] > r.max[i] { - r.max[i] = b.max[i] - } - } -} - -func (r *box) area() float64 { - area := r.max[0] - r.min[0] - for i := 1; i < dims; i++ { - area *= r.max[i] - r.min[i] - } - return area -} - -func (r *box) overlapArea(b *box) float64 { - area := 1.0 - for i := 0; i < dims; i++ { - var max, min float64 - if r.max[i] < b.max[i] { - max = r.max[i] - } else { - max = b.max[i] - } - if r.min[i] > b.min[i] { - min = r.min[i] - } else { - min = b.min[i] - } - if max > min { - area *= max - min - } else { - return 0 - } - } - return area -} - -func (r *box) enlargedArea(b *box) float64 { - area := 1.0 - for i := 0; i < len(r.min); i++ { - if b.max[i] > r.max[i] { - if b.min[i] < r.min[i] { - area *= b.max[i] - b.min[i] - } else { - area *= b.max[i] - r.min[i] - } - } else { - if b.min[i] < r.min[i] { - area *= r.max[i] - b.min[i] - } else { - area *= r.max[i] - r.min[i] - } - } - } - return area -} - -// Insert inserts an item into the RTree -func (tr *BoxTree) Insert(min, max []float64, value interface{}) { - var item box - fit(min, max, value, &item) - tr.insert(&item) -} - -func (tr *BoxTree) insert(item *box) { - if tr.root.data == nil { - fit(item.min[:], item.max[:], new(node), &tr.root) - } - grown := tr.root.insert(item, tr.height) - if grown { - tr.root.expand(item) - } - if tr.root.data.(*node).count == maxEntries+1 { - newRoot := new(node) - tr.root.splitLargestAxisEdgeSnap(&newRoot.boxes[1]) - newRoot.boxes[0] = tr.root - newRoot.count = 2 - tr.root.data = newRoot - tr.root.recalc() - tr.height++ - } - tr.count++ -} - -func (r *box) chooseLeastEnlargement(b *box) int { - j, jenlargement, jarea := -1, 0.0, 0.0 - n := r.data.(*node) - for i := 0; i < n.count; i++ { - var area float64 - if false { - area = n.boxes[i].area() - } else { - // force inline - area = n.boxes[i].max[0] - n.boxes[i].min[0] - for j := 1; j < dims; j++ { - area *= n.boxes[i].max[j] - n.boxes[i].min[j] - } - } - var enlargement float64 - if false { - enlargement = n.boxes[i].enlargedArea(b) - area - } else { - // force inline - enlargedArea := 1.0 - for j := 0; j < len(n.boxes[i].min); j++ { - if b.max[j] > n.boxes[i].max[j] { - if b.min[j] < n.boxes[i].min[j] { - enlargedArea *= b.max[j] - b.min[j] - } else { - enlargedArea *= b.max[j] - n.boxes[i].min[j] - } - } else { - if b.min[j] < n.boxes[i].min[j] { - enlargedArea *= n.boxes[i].max[j] - b.min[j] - } else { - enlargedArea *= n.boxes[i].max[j] - n.boxes[i].min[j] - } - } - } - enlargement = enlargedArea - area - } - - if j == -1 || enlargement < jenlargement { - j, jenlargement, jarea = i, enlargement, area - } else if enlargement == jenlargement { - if area < jarea { - j, jenlargement, jarea = i, enlargement, area - } - } - } - return j -} - -func (r *box) recalc() { - n := r.data.(*node) - r.min = n.boxes[0].min - r.max = n.boxes[0].max - for i := 1; i < n.count; i++ { - r.expand(&n.boxes[i]) - } -} - -// contains return struct when b is fully contained inside of n -func (r *box) contains(b *box) bool { - for i := 0; i < dims; i++ { - if b.min[i] < r.min[i] || b.max[i] > r.max[i] { - return false - } - } - return true -} - -func (r *box) largestAxis() (axis int, size float64) { - j, jsz := 0, 0.0 - for i := 0; i < dims; i++ { - sz := r.max[i] - r.min[i] - if i == 0 || sz > jsz { - j, jsz = i, sz - } - } - return j, jsz -} - -func (r *box) splitLargestAxisEdgeSnap(right *box) { - axis, _ := r.largestAxis() - left := r - leftNode := left.data.(*node) - rightNode := new(node) - right.data = rightNode - - var equals []box - for i := 0; i < leftNode.count; i++ { - minDist := leftNode.boxes[i].min[axis] - left.min[axis] - maxDist := left.max[axis] - leftNode.boxes[i].max[axis] - if minDist < maxDist { - // stay left - } else { - if minDist > maxDist { - // move to right - rightNode.boxes[rightNode.count] = leftNode.boxes[i] - rightNode.count++ - } else { - // move to equals, at the end of the left array - equals = append(equals, leftNode.boxes[i]) - } - leftNode.boxes[i] = leftNode.boxes[leftNode.count-1] - leftNode.boxes[leftNode.count-1].data = nil - leftNode.count-- - i-- - } - } - for _, b := range equals { - if leftNode.count < rightNode.count { - leftNode.boxes[leftNode.count] = b - leftNode.count++ - } else { - rightNode.boxes[rightNode.count] = b - rightNode.count++ - } - } - left.recalc() - right.recalc() -} - -func (r *box) insert(item *box, height int) (grown bool) { - n := r.data.(*node) - if height == 0 { - n.boxes[n.count] = *item - n.count++ - grown = !r.contains(item) - return grown - } - // choose subtree - index := r.chooseLeastEnlargement(item) - child := &n.boxes[index] - grown = child.insert(item, height-1) - if grown { - child.expand(item) - grown = !r.contains(item) - } - if child.data.(*node).count == maxEntries+1 { - child.splitLargestAxisEdgeSnap(&n.boxes[n.count]) - n.count++ - } - return grown -} - -// fit an external item into a box type -func fit(min, max []float64, value interface{}, target *box) { - if max == nil { - max = min - } - if len(min) != len(max) { - panic("min/max dimension mismatch") - } - if len(min) != dims { - panic("invalid number of dimensions") - } - for i := 0; i < dims; i++ { - target.min[i] = min[i] - target.max[i] = max[i] - } - target.data = value -} - -type overlapsResult int - -const ( - not overlapsResult = iota - intersects - contains -) - -// overlaps detects if r insersects or contains b. -// return not, intersects, contains -func (r *box) overlaps(b *box) overlapsResult { - for i := 0; i < dims; i++ { - if b.min[i] > r.max[i] || b.max[i] < r.min[i] { - return not - } - if r.min[i] > b.min[i] || b.max[i] > r.max[i] { - i++ - for ; i < dims; i++ { - if b.min[i] > r.max[i] || b.max[i] < r.min[i] { - return not - } - } - return intersects - } - } - return contains -} - -// contains return struct when b is fully contained inside of n -func (r *box) intersects(b *box) bool { - for i := 0; i < dims; i++ { - if b.min[i] > r.max[i] || b.max[i] < r.min[i] { - return false - } - } - return true -} - -func (r *box) search( - target *box, height int, - iter func(min, max []float64, value interface{}) bool, -) bool { - n := r.data.(*node) - if height == 0 { - for i := 0; i < n.count; i++ { - if target.intersects(&n.boxes[i]) { - if !iter(n.boxes[i].min[:], n.boxes[i].max[:], - n.boxes[i].data) { - return false - } - } - } - } else { - for i := 0; i < n.count; i++ { - switch target.overlaps(&n.boxes[i]) { - case intersects: - if !n.boxes[i].search(target, height-1, iter) { - return false - } - case contains: - if !n.boxes[i].scan(target, height-1, iter) { - return false - } - } - } - } - return true -} - -func (tr *BoxTree) search( - target *box, - iter func(min, max []float64, value interface{}) bool, -) { - if tr.root.data == nil { - return - } - res := target.overlaps(&tr.root) - if res == intersects { - tr.root.search(target, tr.height, iter) - } else if res == contains { - tr.root.scan(target, tr.height, iter) - } -} - -// Search ... -func (tr *BoxTree) Search(min, max []float64, - iter func(min, max []float64, value interface{}) bool, -) { - var target box - fit(min, max, nil, &target) - tr.search(&target, iter) -} - -const ( - // Continue to first child box and/or next sibling. - Continue = iota - // Ignore child boxes but continue to next sibling. - Ignore - // Stop iterating - Stop -) - -// Traverse iterates through all items and container boxes in tree. -func (tr *BoxTree) Traverse( - iter func(min, max []float64, height, level int, value interface{}) int, -) { - if tr.root.data == nil { - return - } - if iter(tr.root.min[:], tr.root.max[:], tr.height+1, 0, nil) == Continue { - tr.root.traverse(tr.height, 1, iter) - } -} - -func (r *box) traverse( - height, level int, - iter func(min, max []float64, height, level int, value interface{}) int, -) int { - n := r.data.(*node) - if height == 0 { - for i := 0; i < n.count; i++ { - action := iter(n.boxes[i].min[:], n.boxes[i].max[:], height, level, - n.boxes[i].data) - if action == Stop { - return Stop - } - } - } else { - for i := 0; i < n.count; i++ { - switch iter(n.boxes[i].min[:], n.boxes[i].max[:], height, level, - n.boxes[i].data) { - case Ignore: - case Continue: - if n.boxes[i].traverse(height-1, level+1, iter) == Stop { - return Stop - } - case Stop: - return Stop - } - } - } - return Continue -} - -func (r *box) scan( - target *box, height int, - iter func(min, max []float64, value interface{}) bool, -) bool { - n := r.data.(*node) - if height == 0 { - for i := 0; i < n.count; i++ { - if !iter(n.boxes[i].min[:], n.boxes[i].max[:], n.boxes[i].data) { - return false - } - } - } else { - for i := 0; i < n.count; i++ { - if !n.boxes[i].scan(target, height-1, iter) { - return false - } - } - } - return true -} - -// Scan iterates through all items in tree. -func (tr *BoxTree) Scan(iter func(min, max []float64, value interface{}) bool) { - if tr.root.data == nil { - return - } - tr.root.scan(nil, tr.height, iter) -} - -// Delete ... -func (tr *BoxTree) Delete(min, max []float64, value interface{}) { - var item box - fit(min, max, value, &item) - if tr.root.data == nil || !tr.root.contains(&item) { - return - } - var removed, recalced bool - removed, recalced, tr.reinsert = - tr.root.delete(&item, tr.height, tr.reinsert[:0]) - if !removed { - return - } - tr.count -= len(tr.reinsert) + 1 - if tr.count == 0 { - tr.root = box{} - recalced = false - } else { - for tr.height > 0 && tr.root.data.(*node).count == 1 { - tr.root = tr.root.data.(*node).boxes[0] - tr.height-- - tr.root.recalc() - } - } - if recalced { - tr.root.recalc() - } - for i := range tr.reinsert { - tr.insert(&tr.reinsert[i]) - tr.reinsert[i].data = nil - } -} - -func (r *box) delete(item *box, height int, reinsert []box) ( - removed, recalced bool, reinsertOut []box, -) { - n := r.data.(*node) - if height == 0 { - for i := 0; i < n.count; i++ { - if n.boxes[i].data == item.data { - // found the target item to delete - recalced = r.onEdge(&n.boxes[i]) - n.boxes[i] = n.boxes[n.count-1] - n.boxes[n.count-1].data = nil - n.count-- - if recalced { - r.recalc() - } - return true, recalced, reinsert - } - } - } else { - for i := 0; i < n.count; i++ { - if !n.boxes[i].contains(item) { - continue - } - removed, recalced, reinsert = - n.boxes[i].delete(item, height-1, reinsert) - if !removed { - continue - } - if n.boxes[i].data.(*node).count < minEntries { - // underflow - if !recalced { - recalced = r.onEdge(&n.boxes[i]) - } - reinsert = n.boxes[i].flatten(reinsert, height-1) - n.boxes[i] = n.boxes[n.count-1] - n.boxes[n.count-1].data = nil - n.count-- - } - if recalced { - r.recalc() - } - return removed, recalced, reinsert - } - } - return false, false, reinsert -} - -// flatten flattens all leaf boxes into a single list -func (r *box) flatten(all []box, height int) []box { - n := r.data.(*node) - if height == 0 { - all = append(all, n.boxes[:n.count]...) - } else { - for i := 0; i < n.count; i++ { - all = n.boxes[i].flatten(all, height-1) - } - } - return all -} - -// onedge returns true when b is on the edge of r -func (r *box) onEdge(b *box) bool { - for i := 0; i < dims; i++ { - if r.min[i] == b.min[i] || r.max[i] == b.max[i] { - return true - } - } - return false -} - -// Count ... -func (tr *BoxTree) Count() int { - return tr.count -} - -func (r *box) totalOverlapArea(height int) float64 { - var area float64 - n := r.data.(*node) - for i := 0; i < n.count; i++ { - for j := i + 1; j < n.count; j++ { - area += n.boxes[i].overlapArea(&n.boxes[j]) - } - - } - if height > 0 { - for i := 0; i < n.count; i++ { - area += n.boxes[i].totalOverlapArea(height - 1) - } - } - return area -} - -// TotalOverlapArea ... -func (tr *BoxTree) TotalOverlapArea() float64 { - if tr.root.data == nil { - return 0 - } - return tr.root.totalOverlapArea(tr.height) -} - -type qnode struct { - dist float64 - box box -} - -type queue struct { - nodes []qnode - len int - size int -} - -func (q *queue) push(dist float64, box box) { - if q.nodes == nil { - q.nodes = make([]qnode, 2) - } else { - q.nodes = append(q.nodes, qnode{}) - } - i := q.len + 1 - j := i / 2 - for i > 1 && q.nodes[j].dist > dist { - q.nodes[i] = q.nodes[j] - i = j - j = j / 2 - } - q.nodes[i].dist = dist - q.nodes[i].box = box - q.len++ -} - -func (q *queue) peek() qnode { - if q.len == 0 { - return qnode{} - } - return q.nodes[1] -} - -func (q *queue) pop() qnode { - if q.len == 0 { - return qnode{} - } - n := q.nodes[1] - q.nodes[1] = q.nodes[q.len] - q.len-- - var j, k int - i := 1 - for i != q.len+1 { - k = q.len + 1 - j = 2 * i - if j <= q.len && q.nodes[j].dist < q.nodes[k].dist { - k = j - } - if j+1 <= q.len && q.nodes[j+1].dist < q.nodes[k].dist { - k = j + 1 - } - q.nodes[i] = q.nodes[k] - i = k - } - return n -} - -// Nearby returns items nearest to farthest. -// The dist param is the "box distance". -func (tr *BoxTree) Nearby(min, max []float64, - iter func(min, max []float64, item interface{}) bool) { - if tr.root.data == nil { - return - } - var bbox box - fit(min, max, nil, &bbox) - box := tr.root - var q queue - for { - n := box.data.(*node) - for i := 0; i < n.count; i++ { - dist := boxDist(&bbox, &n.boxes[i]) - q.push(dist, n.boxes[i]) - } - for q.len > 0 { - if _, ok := q.peek().box.data.(*node); ok { - break - } - item := q.pop() - if !iter(item.box.min[:], item.box.max[:], item.box.data) { - return - } - } - if q.len == 0 { - break - } else { - box = q.pop().box - } - } - return -} - -func boxDist(a, b *box) float64 { - var dist float64 - for i := 0; i < len(a.min); i++ { - var min, max float64 - if a.min[i] > b.min[i] { - min = a.min[i] - } else { - min = b.min[i] - } - if a.max[i] < b.max[i] { - max = a.max[i] - } else { - max = b.max[i] - } - squared := min - max - if squared > 0 { - dist += squared * squared - } - } - return dist -} - -// Bounds returns the minimum bounding box -func (tr *BoxTree) Bounds() (min, max []float64) { - if tr.root.data == nil { - return - } - return tr.root.min[:], tr.root.max[:] -} diff --git a/vendor/github.com/tidwall/boxtree/d3/boxtree.go b/vendor/github.com/tidwall/boxtree/d3/boxtree.go deleted file mode 100644 index 8b0718ed..00000000 --- a/vendor/github.com/tidwall/boxtree/d3/boxtree.go +++ /dev/null @@ -1,707 +0,0 @@ -package d3 - -const dims = 3 - -const ( - maxEntries = 16 - minEntries = maxEntries * 40 / 100 -) - -type box struct { - data interface{} - min, max [dims]float64 -} - -type node struct { - count int - boxes [maxEntries + 1]box -} - -// BoxTree ... -type BoxTree struct { - height int - root box - count int - reinsert []box -} - -func (r *box) expand(b *box) { - for i := 0; i < dims; i++ { - if b.min[i] < r.min[i] { - r.min[i] = b.min[i] - } - if b.max[i] > r.max[i] { - r.max[i] = b.max[i] - } - } -} - -func (r *box) area() float64 { - area := r.max[0] - r.min[0] - for i := 1; i < dims; i++ { - area *= r.max[i] - r.min[i] - } - return area -} - -func (r *box) overlapArea(b *box) float64 { - area := 1.0 - for i := 0; i < dims; i++ { - var max, min float64 - if r.max[i] < b.max[i] { - max = r.max[i] - } else { - max = b.max[i] - } - if r.min[i] > b.min[i] { - min = r.min[i] - } else { - min = b.min[i] - } - if max > min { - area *= max - min - } else { - return 0 - } - } - return area -} - -func (r *box) enlargedArea(b *box) float64 { - area := 1.0 - for i := 0; i < len(r.min); i++ { - if b.max[i] > r.max[i] { - if b.min[i] < r.min[i] { - area *= b.max[i] - b.min[i] - } else { - area *= b.max[i] - r.min[i] - } - } else { - if b.min[i] < r.min[i] { - area *= r.max[i] - b.min[i] - } else { - area *= r.max[i] - r.min[i] - } - } - } - return area -} - -// Insert inserts an item into the RTree -func (tr *BoxTree) Insert(min, max []float64, value interface{}) { - var item box - fit(min, max, value, &item) - tr.insert(&item) -} - -func (tr *BoxTree) insert(item *box) { - if tr.root.data == nil { - fit(item.min[:], item.max[:], new(node), &tr.root) - } - grown := tr.root.insert(item, tr.height) - if grown { - tr.root.expand(item) - } - if tr.root.data.(*node).count == maxEntries+1 { - newRoot := new(node) - tr.root.splitLargestAxisEdgeSnap(&newRoot.boxes[1]) - newRoot.boxes[0] = tr.root - newRoot.count = 2 - tr.root.data = newRoot - tr.root.recalc() - tr.height++ - } - tr.count++ -} - -func (r *box) chooseLeastEnlargement(b *box) int { - j, jenlargement, jarea := -1, 0.0, 0.0 - n := r.data.(*node) - for i := 0; i < n.count; i++ { - var area float64 - if false { - area = n.boxes[i].area() - } else { - // force inline - area = n.boxes[i].max[0] - n.boxes[i].min[0] - for j := 1; j < dims; j++ { - area *= n.boxes[i].max[j] - n.boxes[i].min[j] - } - } - var enlargement float64 - if false { - enlargement = n.boxes[i].enlargedArea(b) - area - } else { - // force inline - enlargedArea := 1.0 - for j := 0; j < len(n.boxes[i].min); j++ { - if b.max[j] > n.boxes[i].max[j] { - if b.min[j] < n.boxes[i].min[j] { - enlargedArea *= b.max[j] - b.min[j] - } else { - enlargedArea *= b.max[j] - n.boxes[i].min[j] - } - } else { - if b.min[j] < n.boxes[i].min[j] { - enlargedArea *= n.boxes[i].max[j] - b.min[j] - } else { - enlargedArea *= n.boxes[i].max[j] - n.boxes[i].min[j] - } - } - } - enlargement = enlargedArea - area - } - - if j == -1 || enlargement < jenlargement { - j, jenlargement, jarea = i, enlargement, area - } else if enlargement == jenlargement { - if area < jarea { - j, jenlargement, jarea = i, enlargement, area - } - } - } - return j -} - -func (r *box) recalc() { - n := r.data.(*node) - r.min = n.boxes[0].min - r.max = n.boxes[0].max - for i := 1; i < n.count; i++ { - r.expand(&n.boxes[i]) - } -} - -// contains return struct when b is fully contained inside of n -func (r *box) contains(b *box) bool { - for i := 0; i < dims; i++ { - if b.min[i] < r.min[i] || b.max[i] > r.max[i] { - return false - } - } - return true -} - -func (r *box) largestAxis() (axis int, size float64) { - j, jsz := 0, 0.0 - for i := 0; i < dims; i++ { - sz := r.max[i] - r.min[i] - if i == 0 || sz > jsz { - j, jsz = i, sz - } - } - return j, jsz -} - -func (r *box) splitLargestAxisEdgeSnap(right *box) { - axis, _ := r.largestAxis() - left := r - leftNode := left.data.(*node) - rightNode := new(node) - right.data = rightNode - - var equals []box - for i := 0; i < leftNode.count; i++ { - minDist := leftNode.boxes[i].min[axis] - left.min[axis] - maxDist := left.max[axis] - leftNode.boxes[i].max[axis] - if minDist < maxDist { - // stay left - } else { - if minDist > maxDist { - // move to right - rightNode.boxes[rightNode.count] = leftNode.boxes[i] - rightNode.count++ - } else { - // move to equals, at the end of the left array - equals = append(equals, leftNode.boxes[i]) - } - leftNode.boxes[i] = leftNode.boxes[leftNode.count-1] - leftNode.boxes[leftNode.count-1].data = nil - leftNode.count-- - i-- - } - } - for _, b := range equals { - if leftNode.count < rightNode.count { - leftNode.boxes[leftNode.count] = b - leftNode.count++ - } else { - rightNode.boxes[rightNode.count] = b - rightNode.count++ - } - } - left.recalc() - right.recalc() -} - -func (r *box) insert(item *box, height int) (grown bool) { - n := r.data.(*node) - if height == 0 { - n.boxes[n.count] = *item - n.count++ - grown = !r.contains(item) - return grown - } - // choose subtree - index := r.chooseLeastEnlargement(item) - child := &n.boxes[index] - grown = child.insert(item, height-1) - if grown { - child.expand(item) - grown = !r.contains(item) - } - if child.data.(*node).count == maxEntries+1 { - child.splitLargestAxisEdgeSnap(&n.boxes[n.count]) - n.count++ - } - return grown -} - -// fit an external item into a box type -func fit(min, max []float64, value interface{}, target *box) { - if max == nil { - max = min - } - if len(min) != len(max) { - panic("min/max dimension mismatch") - } - if len(min) != dims { - panic("invalid number of dimensions") - } - for i := 0; i < dims; i++ { - target.min[i] = min[i] - target.max[i] = max[i] - } - target.data = value -} - -type overlapsResult int - -const ( - not overlapsResult = iota - intersects - contains -) - -// overlaps detects if r insersects or contains b. -// return not, intersects, contains -func (r *box) overlaps(b *box) overlapsResult { - for i := 0; i < dims; i++ { - if b.min[i] > r.max[i] || b.max[i] < r.min[i] { - return not - } - if r.min[i] > b.min[i] || b.max[i] > r.max[i] { - i++ - for ; i < dims; i++ { - if b.min[i] > r.max[i] || b.max[i] < r.min[i] { - return not - } - } - return intersects - } - } - return contains -} - -// contains return struct when b is fully contained inside of n -func (r *box) intersects(b *box) bool { - for i := 0; i < dims; i++ { - if b.min[i] > r.max[i] || b.max[i] < r.min[i] { - return false - } - } - return true -} - -func (r *box) search( - target *box, height int, - iter func(min, max []float64, value interface{}) bool, -) bool { - n := r.data.(*node) - if height == 0 { - for i := 0; i < n.count; i++ { - if target.intersects(&n.boxes[i]) { - if !iter(n.boxes[i].min[:], n.boxes[i].max[:], - n.boxes[i].data) { - return false - } - } - } - } else { - for i := 0; i < n.count; i++ { - switch target.overlaps(&n.boxes[i]) { - case intersects: - if !n.boxes[i].search(target, height-1, iter) { - return false - } - case contains: - if !n.boxes[i].scan(target, height-1, iter) { - return false - } - } - } - } - return true -} - -func (tr *BoxTree) search( - target *box, - iter func(min, max []float64, value interface{}) bool, -) { - if tr.root.data == nil { - return - } - res := target.overlaps(&tr.root) - if res == intersects { - tr.root.search(target, tr.height, iter) - } else if res == contains { - tr.root.scan(target, tr.height, iter) - } -} - -// Search ... -func (tr *BoxTree) Search(min, max []float64, - iter func(min, max []float64, value interface{}) bool, -) { - var target box - fit(min, max, nil, &target) - tr.search(&target, iter) -} - -const ( - // Continue to first child box and/or next sibling. - Continue = iota - // Ignore child boxes but continue to next sibling. - Ignore - // Stop iterating - Stop -) - -// Traverse iterates through all items and container boxes in tree. -func (tr *BoxTree) Traverse( - iter func(min, max []float64, height, level int, value interface{}) int, -) { - if tr.root.data == nil { - return - } - if iter(tr.root.min[:], tr.root.max[:], tr.height+1, 0, nil) == Continue { - tr.root.traverse(tr.height, 1, iter) - } -} - -func (r *box) traverse( - height, level int, - iter func(min, max []float64, height, level int, value interface{}) int, -) int { - n := r.data.(*node) - if height == 0 { - for i := 0; i < n.count; i++ { - action := iter(n.boxes[i].min[:], n.boxes[i].max[:], height, level, - n.boxes[i].data) - if action == Stop { - return Stop - } - } - } else { - for i := 0; i < n.count; i++ { - switch iter(n.boxes[i].min[:], n.boxes[i].max[:], height, level, - n.boxes[i].data) { - case Ignore: - case Continue: - if n.boxes[i].traverse(height-1, level+1, iter) == Stop { - return Stop - } - case Stop: - return Stop - } - } - } - return Continue -} - -func (r *box) scan( - target *box, height int, - iter func(min, max []float64, value interface{}) bool, -) bool { - n := r.data.(*node) - if height == 0 { - for i := 0; i < n.count; i++ { - if !iter(n.boxes[i].min[:], n.boxes[i].max[:], n.boxes[i].data) { - return false - } - } - } else { - for i := 0; i < n.count; i++ { - if !n.boxes[i].scan(target, height-1, iter) { - return false - } - } - } - return true -} - -// Scan iterates through all items in tree. -func (tr *BoxTree) Scan(iter func(min, max []float64, value interface{}) bool) { - if tr.root.data == nil { - return - } - tr.root.scan(nil, tr.height, iter) -} - -// Delete ... -func (tr *BoxTree) Delete(min, max []float64, value interface{}) { - var item box - fit(min, max, value, &item) - if tr.root.data == nil || !tr.root.contains(&item) { - return - } - var removed, recalced bool - removed, recalced, tr.reinsert = - tr.root.delete(&item, tr.height, tr.reinsert[:0]) - if !removed { - return - } - tr.count -= len(tr.reinsert) + 1 - if tr.count == 0 { - tr.root = box{} - recalced = false - } else { - for tr.height > 0 && tr.root.data.(*node).count == 1 { - tr.root = tr.root.data.(*node).boxes[0] - tr.height-- - tr.root.recalc() - } - } - if recalced { - tr.root.recalc() - } - for i := range tr.reinsert { - tr.insert(&tr.reinsert[i]) - tr.reinsert[i].data = nil - } -} - -func (r *box) delete(item *box, height int, reinsert []box) ( - removed, recalced bool, reinsertOut []box, -) { - n := r.data.(*node) - if height == 0 { - for i := 0; i < n.count; i++ { - if n.boxes[i].data == item.data { - // found the target item to delete - recalced = r.onEdge(&n.boxes[i]) - n.boxes[i] = n.boxes[n.count-1] - n.boxes[n.count-1].data = nil - n.count-- - if recalced { - r.recalc() - } - return true, recalced, reinsert - } - } - } else { - for i := 0; i < n.count; i++ { - if !n.boxes[i].contains(item) { - continue - } - removed, recalced, reinsert = - n.boxes[i].delete(item, height-1, reinsert) - if !removed { - continue - } - if n.boxes[i].data.(*node).count < minEntries { - // underflow - if !recalced { - recalced = r.onEdge(&n.boxes[i]) - } - reinsert = n.boxes[i].flatten(reinsert, height-1) - n.boxes[i] = n.boxes[n.count-1] - n.boxes[n.count-1].data = nil - n.count-- - } - if recalced { - r.recalc() - } - return removed, recalced, reinsert - } - } - return false, false, reinsert -} - -// flatten flattens all leaf boxes into a single list -func (r *box) flatten(all []box, height int) []box { - n := r.data.(*node) - if height == 0 { - all = append(all, n.boxes[:n.count]...) - } else { - for i := 0; i < n.count; i++ { - all = n.boxes[i].flatten(all, height-1) - } - } - return all -} - -// onedge returns true when b is on the edge of r -func (r *box) onEdge(b *box) bool { - for i := 0; i < dims; i++ { - if r.min[i] == b.min[i] || r.max[i] == b.max[i] { - return true - } - } - return false -} - -// Count ... -func (tr *BoxTree) Count() int { - return tr.count -} - -func (r *box) totalOverlapArea(height int) float64 { - var area float64 - n := r.data.(*node) - for i := 0; i < n.count; i++ { - for j := i + 1; j < n.count; j++ { - area += n.boxes[i].overlapArea(&n.boxes[j]) - } - - } - if height > 0 { - for i := 0; i < n.count; i++ { - area += n.boxes[i].totalOverlapArea(height - 1) - } - } - return area -} - -// TotalOverlapArea ... -func (tr *BoxTree) TotalOverlapArea() float64 { - if tr.root.data == nil { - return 0 - } - return tr.root.totalOverlapArea(tr.height) -} - -type qnode struct { - dist float64 - box box -} - -type queue struct { - nodes []qnode - len int - size int -} - -func (q *queue) push(dist float64, box box) { - if q.nodes == nil { - q.nodes = make([]qnode, 2) - } else { - q.nodes = append(q.nodes, qnode{}) - } - i := q.len + 1 - j := i / 2 - for i > 1 && q.nodes[j].dist > dist { - q.nodes[i] = q.nodes[j] - i = j - j = j / 2 - } - q.nodes[i].dist = dist - q.nodes[i].box = box - q.len++ -} - -func (q *queue) peek() qnode { - if q.len == 0 { - return qnode{} - } - return q.nodes[1] -} - -func (q *queue) pop() qnode { - if q.len == 0 { - return qnode{} - } - n := q.nodes[1] - q.nodes[1] = q.nodes[q.len] - q.len-- - var j, k int - i := 1 - for i != q.len+1 { - k = q.len + 1 - j = 2 * i - if j <= q.len && q.nodes[j].dist < q.nodes[k].dist { - k = j - } - if j+1 <= q.len && q.nodes[j+1].dist < q.nodes[k].dist { - k = j + 1 - } - q.nodes[i] = q.nodes[k] - i = k - } - return n -} - -// Nearby returns items nearest to farthest. -// The dist param is the "box distance". -func (tr *BoxTree) Nearby(min, max []float64, - iter func(min, max []float64, item interface{}) bool) { - if tr.root.data == nil { - return - } - var bbox box - fit(min, max, nil, &bbox) - box := tr.root - var q queue - for { - n := box.data.(*node) - for i := 0; i < n.count; i++ { - dist := boxDist(&bbox, &n.boxes[i]) - q.push(dist, n.boxes[i]) - } - for q.len > 0 { - if _, ok := q.peek().box.data.(*node); ok { - break - } - item := q.pop() - if !iter(item.box.min[:], item.box.max[:], item.box.data) { - return - } - } - if q.len == 0 { - break - } else { - box = q.pop().box - } - } - return -} - -func boxDist(a, b *box) float64 { - var dist float64 - for i := 0; i < len(a.min); i++ { - var min, max float64 - if a.min[i] > b.min[i] { - min = a.min[i] - } else { - min = b.min[i] - } - if a.max[i] < b.max[i] { - max = a.max[i] - } else { - max = b.max[i] - } - squared := min - max - if squared > 0 { - dist += squared * squared - } - } - return dist -} - -// Bounds returns the minimum bounding box -func (tr *BoxTree) Bounds() (min, max []float64) { - if tr.root.data == nil { - return - } - return tr.root.min[:], tr.root.max[:] -} diff --git a/vendor/github.com/tidwall/boxtree/res/cities/main.go b/vendor/github.com/tidwall/boxtree/res/cities/main.go deleted file mode 100644 index 12fb34e4..00000000 --- a/vendor/github.com/tidwall/boxtree/res/cities/main.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -import ( - "io/ioutil" - - "github.com/tidwall/boxtree" - "github.com/tidwall/boxtree/res/tools" - "github.com/tidwall/cities" -) - -func main() { - tr := boxtree.New(2) - for _, city := range cities.Cities { - tr.Insert([]float64{city.Longitude, city.Latitude}, nil, &city) - } - ioutil.WriteFile("cities.svg", []byte(tools.SVG(tr)), 0600) -} diff --git a/vendor/github.com/tidwall/boxtree/res/gen/main.go b/vendor/github.com/tidwall/boxtree/res/gen/main.go deleted file mode 100644 index 3ba790be..00000000 --- a/vendor/github.com/tidwall/boxtree/res/gen/main.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "io/ioutil" - "log" - "os" - "strings" -) - -func main() { - b, err := ioutil.ReadFile("d2/boxtree.go") - if err != nil { - log.Fatal(err) - } - s := string(b) - s = strings.Replace(s, "package d2", "package d3", 1) - s = strings.Replace(s, "const dims = 2", "const dims = 3", 1) - if err := os.MkdirAll("d3", 0777); err != nil { - log.Fatal(err) - } - if err := ioutil.WriteFile("d3/boxtree.go", []byte(s), 0666); err != nil { - log.Fatal(err) - } - b, err = ioutil.ReadFile("d2/boxtree_test.go") - if err != nil { - log.Fatal(err) - } - b = []byte(strings.Replace(string(b), "package d2", "package d3", 1)) - if err := ioutil.WriteFile("d3/boxtree_test.go", b, 0666); err != nil { - log.Fatal(err) - } - -} diff --git a/vendor/github.com/tidwall/boxtree/res/tools/tools.go b/vendor/github.com/tidwall/boxtree/res/tools/tools.go deleted file mode 100644 index c5437ea7..00000000 --- a/vendor/github.com/tidwall/boxtree/res/tools/tools.go +++ /dev/null @@ -1,112 +0,0 @@ -package tools - -import ( - "fmt" - "strconv" - "strings" -) - -// RTree interface -type RTree interface { - Insert(min, max []float64, value interface{}) - Scan(iter func(min, max []float64, value interface{}) bool) - Search(min, max []float64, iter func(min, max []float64, - value interface{}) bool) - Delete(min, max []float64, value interface{}) - Traverse(iter func(min, max []float64, height, level int, - value interface{}) int) - Count() int - TotalOverlapArea() float64 - Nearby(min, max []float64, iter func(min, max []float64, - item interface{}) bool) -} - -func svg(min, max []float64, height int) string { - var out string - point := true - for i := 0; i < 2; i++ { - if min[i] != max[i] { - point = false - break - } - } - if point { // is point - out += fmt.Sprintf( - "\n", - (min[0])*svgScale, - (min[1])*svgScale, - (max[0]-min[0]+1/svgScale)*svgScale, - (max[1]-min[1]+1/svgScale)*svgScale, - strokes[height%len(strokes)]) - } else { // is rect - out += fmt.Sprintf( - "\n", - (min[0])*svgScale, - (min[1])*svgScale, - (max[0]-min[0]+1/svgScale)*svgScale, - (max[1]-min[1]+1/svgScale)*svgScale, - strokes[height%len(strokes)]) - } - return out -} - -const ( - // Continue to first child rectangle and/or next sibling. - Continue = iota - // Ignore child rectangles but continue to next sibling. - Ignore - // Stop iterating - Stop -) - -const svgScale = 4.0 - -var strokes = [...]string{"black", "#cccc00", "green", "red", "purple"} - -// SVG prints 2D rtree in wgs84 coordinate space -func SVG(tr RTree) string { - var out string - out += fmt.Sprintf("\n", - -190.0*svgScale, -100.0*svgScale, - 380.0*svgScale, 190.0*svgScale) - - out += fmt.Sprintf("\n") - var outb []byte - tr.Traverse(func(min, max []float64, height, level int, _ interface{}) int { - outb = append(outb, svg(min, max, height)...) - return Continue - }) - out += string(outb) - out += fmt.Sprintf("\n") - out += fmt.Sprintf("\n") - return out -} - -// Cities returns big list of cities base on json from -// https://github.com/lutangar/cities.json -func Cities(bigJSON string) [][2]float64 { - var out [][2]float64 - s := bigJSON - for i := 0; ; i++ { - idx := strings.Index(s, `"lat": "`) - if idx == -1 { - break - } - s = s[idx+8:] - idx = strings.IndexByte(s, '"') - lat, _ := strconv.ParseFloat(s[:idx], 64) - idx = strings.Index(s, `"lng": "`) - s = s[idx+8:] - idx = strings.IndexByte(s, '"') - lng, _ := strconv.ParseFloat(s[:idx], 64) - s = s[idx+1:] - out = append(out, [2]float64{lng, lat}) - } - return out -} diff --git a/vendor/github.com/tidwall/boxtree/LICENSE b/vendor/github.com/tidwall/geoindex/LICENSE similarity index 100% rename from vendor/github.com/tidwall/boxtree/LICENSE rename to vendor/github.com/tidwall/geoindex/LICENSE diff --git a/vendor/github.com/tidwall/geoindex/README.md b/vendor/github.com/tidwall/geoindex/README.md new file mode 100644 index 00000000..2b579af4 --- /dev/null +++ b/vendor/github.com/tidwall/geoindex/README.md @@ -0,0 +1,3 @@ +# `geoindex` + +Simplified interface and extension utilities for a geospatial indexes. diff --git a/vendor/github.com/tidwall/geoindex/child/child.go b/vendor/github.com/tidwall/geoindex/child/child.go new file mode 100644 index 00000000..0a640c8d --- /dev/null +++ b/vendor/github.com/tidwall/geoindex/child/child.go @@ -0,0 +1,11 @@ +package child + +// Child represents a child of a 2d geospatial tree. +// The Min and Max fields are the bounds of the Child. +// Data is whatever the child consists of. +// Item is true when the Data is a leaf item, otherwise it's probably a node. +type Child struct { + Min, Max [2]float64 + Data interface{} + Item bool +} diff --git a/vendor/github.com/tidwall/geoindex/geoindex.go b/vendor/github.com/tidwall/geoindex/geoindex.go new file mode 100644 index 00000000..9dfc67bf --- /dev/null +++ b/vendor/github.com/tidwall/geoindex/geoindex.go @@ -0,0 +1,305 @@ +package geoindex + +import ( + "fmt" + + "github.com/tidwall/geoindex/child" +) + +// Interface is a tree-like structure that contains geospatial data +type Interface interface { + // Insert an item into the structure + Insert(min, max [2]float64, data interface{}) + // Delete an item from the structure + Delete(min, max [2]float64, data interface{}) + // Search the structure for items that intersects the rect param + Search( + min, max [2]float64, + iter func(min, max [2]float64, data interface{}) bool, + ) + // Scan iterates through all data in tree in no specified order. + Scan(iter func(min, max [2]float64, data interface{}) bool) + // Len returns the number of items in tree + Len() int + // Bounds returns the minimum bounding box + Bounds() (min, max [2]float64) + // Children returns all children for parent node. If parent node is nil + // then the root nodes should be returned. + // The reuse buffer is an empty length slice that can optionally be used + // to avoid extra allocations. + Children(parent interface{}, reuse []child.Child) (children []child.Child) +} + +// Index is a wrapper around Interface that provides extra features like a +// Nearby (kNN) function. +// This can be created like such: +// var tree = rbang.New() +// var index = index.Index{tree} +// Now you can use `index` just like tree but with the extra features. +type Index struct { + tree Interface +} + +// Wrap a tree-like geospatial interface. +func Wrap(tree Interface) *Index { + return &Index{tree} +} + +// Insert an item into the index +func (index *Index) Insert(min, max [2]float64, data interface{}) { + index.tree.Insert(min, max, data) +} + +// Search the index for items that intersects the rect param +func (index *Index) Search( + min, max [2]float64, + iter func(min, max [2]float64, data interface{}) bool, +) { + index.tree.Search(min, max, iter) +} + +// Delete an item from the index +func (index *Index) Delete(min, max [2]float64, data interface{}) { + index.tree.Delete(min, max, data) +} + +// Children returns all children for parent node. If parent node is nil +// then the root nodes should be returned. +// The reuse buffer is an empty length slice that can optionally be used +// to avoid extra allocations. +func (index *Index) Children(parent interface{}, reuse []child.Child) ( + children []child.Child, +) { + return index.tree.Children(parent, reuse) +} + +// Nearby performs a kNN-type operation on the index. It's expected that the +// caller provides the `dist` function, which is used to calculate the +// distance from a node or item to another object. The other object is unknown +// this operation, but is expected to be known by the caller. The iter will +// return all items from the smallest dist to the largest dist. +func (index *Index) Nearby( + algo func(min, max [2]float64, data interface{}, item bool) (dist float64), + iter func(min, max [2]float64, data interface{}, dist float64) bool, +) { + var q queue + var parent interface{} + var children []child.Child + for { + // gather all children for parent + children = index.tree.Children(parent, children[:0]) + for _, child := range children { + q.push(qnode{ + dist: algo(child.Min, child.Max, child.Data, child.Item), + child: child, + filled: true, + }) + } + for { + node := q.pop() + if !node.filled { + // nothing left in queue + return + } + if node.child.Item { + if !iter(node.child.Min, node.child.Max, + node.child.Data, node.dist) { + return + } + } else { + // gather more children + parent = node.child.Data + break + } + } + } +} + +// Len returns the number of items in tree +func (index *Index) Len() int { + return index.tree.Len() +} + +// Bounds returns the minimum bounding box +func (index *Index) Bounds() (min, max [2]float64) { + return index.tree.Bounds() +} + +// Priority Queue ordered by dist (smallest to largest) + +type qnode struct { + dist float64 + child child.Child + filled bool +} + +type queue struct { + nodes []qnode + len int + size int +} + +func (q *queue) push(node qnode) { + if q.nodes == nil { + q.nodes = make([]qnode, 2) + } else { + q.nodes = append(q.nodes, qnode{}) + } + i := q.len + 1 + j := i / 2 + for i > 1 && q.nodes[j].dist > node.dist { + q.nodes[i] = q.nodes[j] + i = j + j = j / 2 + } + q.nodes[i] = node + q.len++ +} + +func (q *queue) pop() qnode { + if q.len == 0 { + return qnode{} + } + n := q.nodes[1] + q.nodes[1] = q.nodes[q.len] + q.len-- + var j, k int + i := 1 + for i != q.len+1 { + k = q.len + 1 + j = 2 * i + if j <= q.len && q.nodes[j].dist < q.nodes[k].dist { + k = j + } + if j+1 <= q.len && q.nodes[j+1].dist < q.nodes[k].dist { + k = j + 1 + } + q.nodes[i] = q.nodes[k] + i = k + } + return n +} + +// Scan iterates through all data in tree in no specified order. +func (index *Index) Scan( + iter func(min, max [2]float64, data interface{}) bool, +) { + index.tree.Scan(iter) +} + +// SimpleBoxAlgo ... +func SimpleBoxAlgo(targetMin, targetMax [2]float64) ( + dist func(min, max [2]float64, data interface{}, item bool) (dist float64), +) { + return func(min, max [2]float64, data interface{}, item bool) float64 { + return boxDist(targetMin, targetMax, min, max) + } +} + +func boxDist(amin, amax, bmin, bmax [2]float64) float64 { + var dist float64 + var min, max float64 + if amin[0] > bmin[0] { + min = amin[0] + } else { + min = bmin[0] + } + if amax[0] < bmax[0] { + max = amax[0] + } else { + max = bmax[0] + } + squared := min - max + if squared > 0 { + dist += squared * squared + } + if amin[1] > bmin[1] { + min = amin[1] + } else { + min = bmin[1] + } + if amax[1] < bmax[1] { + max = amax[1] + } else { + max = bmax[1] + } + squared = min - max + if squared > 0 { + dist += squared * squared + } + return dist +} + +func (index *Index) svg(child child.Child, height int) []byte { + var out []byte + point := true + for i := 0; i < 2; i++ { + if child.Min[i] != child.Max[i] { + point = false + break + } + } + if point { // is point + out = append(out, fmt.Sprintf( + "\n", + (child.Min[0])*svgScale, + (child.Min[1])*svgScale, + (child.Max[0]-child.Min[0]+1/svgScale)*svgScale, + (child.Max[1]-child.Min[1]+1/svgScale)*svgScale, + strokes[height%len(strokes)])...) + } else { // is rect + out = append(out, fmt.Sprintf( + "\n", + (child.Min[0])*svgScale, + (child.Min[1])*svgScale, + (child.Max[0]-child.Min[0]+1/svgScale)*svgScale, + (child.Max[1]-child.Min[1]+1/svgScale)*svgScale, + strokes[height%len(strokes)])...) + } + if !child.Item { + children := index.tree.Children(child.Data, nil) + for _, child := range children { + out = append(out, index.svg(child, height+1)...) + } + } + return out +} + +const ( + // Continue to first child rectangle and/or next sibling. + Continue = iota + // Ignore child rectangles but continue to next sibling. + Ignore + // Stop iterating + Stop +) + +const svgScale = 4.0 + +var strokes = [...]string{"black", "#cccc00", "green", "red", "purple"} + +// SVG prints 2D rtree in wgs84 coordinate space +func (index *Index) SVG() string { + var out string + out += fmt.Sprintf("\n", + -190.0*svgScale, -100.0*svgScale, + 380.0*svgScale, 190.0*svgScale) + + out += fmt.Sprintf("\n") + + var outb []byte + for _, child := range index.Children(nil, nil) { + outb = append(outb, index.svg(child, 1)...) + } + + out += string(outb) + out += fmt.Sprintf("\n") + out += fmt.Sprintf("\n") + return out +} diff --git a/vendor/github.com/tidwall/boxtree/d3/boxtree_test.go b/vendor/github.com/tidwall/geoindex/geoindex_test.go similarity index 69% rename from vendor/github.com/tidwall/boxtree/d3/boxtree_test.go rename to vendor/github.com/tidwall/geoindex/geoindex_test.go index ddb7bd1e..d67a3b64 100644 --- a/vendor/github.com/tidwall/boxtree/d3/boxtree_test.go +++ b/vendor/github.com/tidwall/geoindex/geoindex_test.go @@ -1,15 +1,20 @@ -package d3 +package geoindex + +// These tests are ripped off from github.com/tidwall/rbang import ( - "fmt" + "io/ioutil" "math/rand" "sort" - "strconv" - "strings" "testing" "time" + + "github.com/tidwall/cities" + "github.com/tidwall/rbang" ) +const dims = 2 + type tBox struct { min [dims]float64 max [dims]float64 @@ -78,14 +83,14 @@ func sortBoxes(boxes []tBox) { }) } -func sortBoxesNearby(boxes []tBox, min, max []float64) { +func sortBoxesNearby(boxes []tBox, min, max [2]float64) { sort.Slice(boxes, func(i, j int) bool { - return testBoxDist(boxes[i].min[:], boxes[i].max[:], min, max) < - testBoxDist(boxes[j].min[:], boxes[j].max[:], min, max) + return testBoxDist(boxes[i].min, boxes[i].max, min, max) < + testBoxDist(boxes[j].min, boxes[j].max, min, max) }) } -func testBoxDist(amin, amax, bmin, bmax []float64) float64 { +func testBoxDist(amin, amax, bmin, bmax [2]float64) float64 { var dist float64 for i := 0; i < len(amin); i++ { var min, max float64 @@ -110,7 +115,7 @@ func testBoxDist(amin, amax, bmin, bmax []float64) float64 { func testBoxesVarious(t *testing.T, boxes []tBox, label string) { N := len(boxes) - var tr BoxTree + tr := Wrap(&rbang.RTree{}) // N := 10000 // boxes := randPoints(N) @@ -119,10 +124,10 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { // insert ///////////////////////////////////////// for i := 0; i < N; i++ { - tr.Insert(boxes[i].min[:], boxes[i].max[:], boxes[i]) + tr.Insert(boxes[i].min, boxes[i].max, boxes[i]) } - if tr.Count() != N { - t.Fatalf("expected %d, got %d", N, tr.Count()) + if tr.Len() != N { + t.Fatalf("expected %d, got %d", N, tr.Len()) } // area := tr.TotalOverlapArea() // fmt.Printf("overlap: %.0f, %.1f/item\n", area, area/float64(N)) @@ -133,7 +138,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { // scan all items and count one-by-one ///////////////////////////////////////// var count int - tr.Scan(func(min, max []float64, value interface{}) bool { + tr.Scan(func(min, max [2]float64, value interface{}) bool { count++ return true }) @@ -145,7 +150,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { // check every point for correctness ///////////////////////////////////////// var tboxes1 []tBox - tr.Scan(func(min, max []float64, value interface{}) bool { + tr.Scan(func(min, max [2]float64, value interface{}) bool { tboxes1 = append(tboxes1, value.(tBox)) return true }) @@ -164,8 +169,8 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { ///////////////////////////////////////// for i := 0; i < N; i++ { var found bool - tr.Search(boxes[i].min[:], boxes[i].max[:], - func(min, max []float64, value interface{}) bool { + tr.Search(boxes[i].min, boxes[i].max, + func(min, max [2]float64, value interface{}) bool { if value == boxes[i] { found = true return false @@ -177,11 +182,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { } } - centerMin, centerMax := []float64{-18, -9}, []float64{18, 9} - for j := 2; j < dims; j++ { - centerMin = append(centerMin, -10) - centerMax = append(centerMax, 10) - } + centerMin, centerMax := [2]float64{-18, -9}, [2]float64{18, 9} ///////////////////////////////////////// // search for 10% of the items @@ -189,7 +190,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { for i := 0; i < N/5; i++ { var count int tr.Search(centerMin, centerMax, - func(min, max []float64, value interface{}) bool { + func(min, max [2]float64, value interface{}) bool { count++ return true }, @@ -201,14 +202,14 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { ///////////////////////////////////////// for i := 0; i < N/2; i++ { j := i * 2 - tr.Delete(boxes[j].min[:], boxes[j].max[:], boxes[j]) + tr.Delete(boxes[j].min, boxes[j].max, boxes[j]) } ///////////////////////////////////////// // count all items. should be half of N ///////////////////////////////////////// count = 0 - tr.Scan(func(min, max []float64, value interface{}) bool { + tr.Scan(func(min, max [2]float64, value interface{}) bool { count++ return true }) @@ -229,7 +230,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { }) for i := 0; i < N/2; i++ { j := ij[i] - tr.Insert(boxes[j].min[:], boxes[j].max[:], boxes[j]) + tr.Insert(boxes[j].min, boxes[j].max, boxes[j]) } ////////////////////////////////////////////////////// @@ -248,11 +249,11 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { } for i := 0; i < N; i++ { - tr.Insert(nboxes[i].min[:], nboxes[i].max[:], nboxes[i]) - tr.Delete(boxes[i].min[:], boxes[i].max[:], boxes[i]) + tr.Insert(nboxes[i].min, nboxes[i].max, nboxes[i]) + tr.Delete(boxes[i].min, boxes[i].max, boxes[i]) } - if tr.Count() != N { - t.Fatalf("expected %d, got %d", N, tr.Count()) + if tr.Len() != N { + t.Fatalf("expected %d, got %d", N, tr.Len()) } // area = tr.TotalOverlapArea() // fmt.Fprintf(wr, "overlap: %.0f, %.1f/item\n", area, area/float64(N)) @@ -261,7 +262,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { // check every point for correctness ///////////////////////////////////////// tboxes1 = nil - tr.Scan(func(min, max []float64, value interface{}) bool { + tr.Scan(func(min, max [2]float64, value interface{}) bool { tboxes1 = append(tboxes1, value.(tBox)) return true }) @@ -281,7 +282,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { for i := 0; i < N/5; i++ { var count int tr.Search(centerMin, centerMax, - func(min, max []float64, value interface{}) bool { + func(min, max [2]float64, value interface{}) bool { count++ return true }, @@ -289,26 +290,30 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { } var boxes3 []tBox - tr.Nearby(centerMin, centerMax, - func(min, max []float64, value interface{}) bool { + tr.Nearby( + SimpleBoxAlgo(centerMin, centerMax), + func(min, max [2]float64, value interface{}, dist float64) bool { boxes3 = append(boxes3, value.(tBox)) return true }, ) + if len(boxes3) != len(nboxes) { t.Fatalf("expected %d, got %d", len(nboxes), len(boxes3)) } - if len(boxes3) != tr.Count() { - t.Fatalf("expected %d, got %d", tr.Count(), len(boxes3)) + if len(boxes3) != tr.Len() { + t.Fatalf("expected %d, got %d", tr.Len(), len(boxes3)) } + var ldist float64 for i, box := range boxes3 { - dist := testBoxDist(box.min[:], box.max[:], centerMin, centerMax) + dist := testBoxDist(box.min, box.max, centerMin, centerMax) if i > 0 && dist < ldist { t.Fatalf("out of order") } ldist = dist } + } func TestRandomBoxes(t *testing.T) { @@ -319,61 +324,15 @@ func TestRandomPoints(t *testing.T) { testBoxesVarious(t, randPoints(10000), "points") } -func (r *box) boxstr() string { - var b []byte - b = append(b, '[', '[') - for i := 0; i < len(r.min); i++ { - if i != 0 { - b = append(b, ' ') - } - b = strconv.AppendFloat(b, r.min[i], 'f', -1, 64) +func TestSVG(t *testing.T) { + var tr rbang.RTree + index := Wrap(&tr) + for _, city := range cities.Cities { + p := [2]float64{city.Longitude, city.Latitude} + tr.Insert(p, p, &city) } - b = append(b, ']', '[') - for i := 0; i < len(r.max); i++ { - if i != 0 { - b = append(b, ' ') - } - b = strconv.AppendFloat(b, r.max[i], 'f', -1, 64) - } - b = append(b, ']', ']') - return string(b) -} - -func (r *box) print(height, indent int) { - fmt.Printf("%s%s", strings.Repeat(" ", indent), r.boxstr()) - if height == 0 { - fmt.Printf("\t'%v'\n", r.data) - } else { - fmt.Printf("\n") - for i := 0; i < r.data.(*node).count; i++ { - r.data.(*node).boxes[i].print(height-1, indent+1) - } - } - -} - -func (tr BoxTree) print() { - if tr.root.data == nil { - println("EMPTY TREE") - return - } - tr.root.print(tr.height+1, 0) -} - -func TestZeroPoints(t *testing.T) { - N := 10000 - var tr BoxTree - pt := make([]float64, dims) - for i := 0; i < N; i++ { - tr.Insert(pt, nil, i) - } -} - -func BenchmarkRandomInsert(b *testing.B) { - var tr BoxTree - boxes := randBoxes(b.N) - b.ResetTimer() - for i := 0; i < b.N; i++ { - tr.Insert(boxes[i].min[:], boxes[i].max[:], i) + svg := index.SVG() + if false { + ioutil.WriteFile("cities.svg", []byte(svg), 0600) } } diff --git a/vendor/github.com/tidwall/geojson/collection.go b/vendor/github.com/tidwall/geojson/collection.go index 0edd0493..f65339d6 100644 --- a/vendor/github.com/tidwall/geojson/collection.go +++ b/vendor/github.com/tidwall/geojson/collection.go @@ -1,14 +1,14 @@ package geojson import ( - "github.com/tidwall/boxtree/d2" "github.com/tidwall/geojson/geometry" + "github.com/tidwall/rbang" ) type collection struct { children []Object extra *extra - tree *d2.BoxTree + tree *rbang.RTree prect geometry.Rect pempty bool } @@ -39,9 +39,9 @@ func (g *collection) Base() []Object { func (g *collection) Search(rect geometry.Rect, iter func(child Object) bool) { if g.tree != nil { g.tree.Search( - []float64{rect.Min.X, rect.Min.Y}, - []float64{rect.Max.X, rect.Max.Y}, - func(_, _ []float64, value interface{}) bool { + [2]float64{rect.Min.X, rect.Min.Y}, + [2]float64{rect.Max.X, rect.Max.Y}, + func(_, _ [2]float64, value interface{}) bool { return iter(value.(Object)) }, ) @@ -303,15 +303,15 @@ func (g *collection) parseInitRectIndex(opts *ParseOptions) { count++ } if count > 0 && opts.IndexChildren != 0 && count >= opts.IndexChildren { - g.tree = new(d2.BoxTree) + g.tree = new(rbang.RTree) for _, child := range g.children { if child.Empty() { continue } rect := child.Rect() g.tree.Insert( - []float64{rect.Min.X, rect.Min.Y}, - []float64{rect.Max.X, rect.Max.Y}, + [2]float64{rect.Min.X, rect.Min.Y}, + [2]float64{rect.Max.X, rect.Max.Y}, child, ) } diff --git a/vendor/github.com/tidwall/rbang/LICENSE b/vendor/github.com/tidwall/rbang/LICENSE new file mode 100644 index 00000000..3a6c0261 --- /dev/null +++ b/vendor/github.com/tidwall/rbang/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 Josh Baker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/tidwall/boxtree/README.md b/vendor/github.com/tidwall/rbang/README.md similarity index 73% rename from vendor/github.com/tidwall/boxtree/README.md rename to vendor/github.com/tidwall/rbang/README.md index e08328d3..58e666e7 100644 --- a/vendor/github.com/tidwall/boxtree/README.md +++ b/vendor/github.com/tidwall/rbang/README.md @@ -1,50 +1,44 @@ -# `BoxTree` +# `R!tree` -[![GoDoc](https://godoc.org/github.com/tidwall/boxtree?status.svg)](https://godoc.org/github.com/tidwall/boxtree) - -**EXPERIMENTAL** +[![GoDoc](https://godoc.org/github.com/tidwall/rbang?status.svg)](https://godoc.org/github.com/tidwall/rbang) This package provides an in-memory R-Tree implementation for Go. It's designed -for [Tile38](https://github.com/tidwall/tile38). +for [Tile38](https://github.com/tidwall/tile38) and is optimized for fast rect +inserts and replacements. -Cities - -## Features - -- Support for 2 and 3 dimensions -- Optimized for fast box inserts and replacements. +Cities ## Usage ### Installing -To start using BoxTree, install Go and run `go get`: +To start using R!Tree, install Go and run `go get`: ```sh -$ go get -u github.com/tidwall/boxtree +$ go get -u github.com/tidwall/rbang ``` ### Basic operations ```go -// create a 2D BoxTree -tr := boxtree.New(2) +// create a 2D RTree +var tr rbang.RTree // insert a point -tr.Insert([]float64{-112.0078, 33.4373}, nil, "PHX") +tr.Insert([2]float64{-112.0078, 33.4373}, [2]float64{-112.0078, 33.4373}, "PHX") // insert a box -tr.Insert([]float64{10, 10}, []float64{20, 20}, "rect") +tr.Insert([2]float64{10, 10}, [2]float64{20, 20}, "rect") // search -tr.Search([]float64{-112.1, 33.4}, []float64{-112.0, 33.5}, - func(min, max []float64, value interface{}) bool { +tr.Search([2]float64{-112.1, 33.4}, [2]float64{-112.0, 33.5}, + func(min, max [2]float64, value interface{}) bool { println(value.(string)) // prints "PHX" }, ) // delete -tr.Delete([]float64{-112.0078, 33.4373}, []float64{-112.0078, 33.4373}, "PHX") +tr.Delete([2]float64{-112.0078, 33.4373}, [2]float64{-112.0078, 33.4373}, "PHX") ``` ## Algorithms @@ -90,5 +84,5 @@ I hope to provide more details in the future. ## License -`BoxTree` source code is available under the MIT License. +`rbang` source code is available under the MIT License. diff --git a/vendor/github.com/tidwall/boxtree/res/cities.png b/vendor/github.com/tidwall/rbang/cities.png similarity index 100% rename from vendor/github.com/tidwall/boxtree/res/cities.png rename to vendor/github.com/tidwall/rbang/cities.png diff --git a/vendor/github.com/tidwall/rbang/rbang.go b/vendor/github.com/tidwall/rbang/rbang.go new file mode 100644 index 00000000..fa5bbc78 --- /dev/null +++ b/vendor/github.com/tidwall/rbang/rbang.go @@ -0,0 +1,540 @@ +package rbang + +import ( + "github.com/tidwall/geoindex/child" +) + +const ( + maxEntries = 32 + minEntries = maxEntries * 40 / 100 +) + +type rect struct { + min, max [2]float64 + data interface{} +} + +type node struct { + count int + boxes [maxEntries + 1]rect +} + +// RTree ... +type RTree struct { + height int + root rect + count int + reinsert []rect +} + +func (r *rect) expand(b *rect) { + if b.min[0] < r.min[0] { + r.min[0] = b.min[0] + } + if b.max[0] > r.max[0] { + r.max[0] = b.max[0] + } + if b.min[1] < r.min[1] { + r.min[1] = b.min[1] + } + if b.max[1] > r.max[1] { + r.max[1] = b.max[1] + } +} + +func (r *rect) area() float64 { + return (r.max[0] - r.min[0]) * (r.max[1] - r.min[1]) +} + +func (r *rect) overlapArea(b *rect) float64 { + area := 1.0 + var max, min float64 + if r.max[0] < b.max[0] { + max = r.max[0] + } else { + max = b.max[0] + } + if r.min[0] > b.min[0] { + min = r.min[0] + } else { + min = b.min[0] + } + if max > min { + area *= max - min + } else { + return 0 + } + if r.max[1] < b.max[1] { + max = r.max[1] + } else { + max = b.max[1] + } + if r.min[1] > b.min[1] { + min = r.min[1] + } else { + min = b.min[1] + } + if max > min { + area *= max - min + } else { + return 0 + } + return area +} + +func (r *rect) enlargedArea(b *rect) float64 { + area := 1.0 + if b.max[0] > r.max[0] { + if b.min[0] < r.min[0] { + area *= b.max[0] - b.min[0] + } else { + area *= b.max[0] - r.min[0] + } + } else { + if b.min[0] < r.min[0] { + area *= r.max[0] - b.min[0] + } else { + area *= r.max[0] - r.min[0] + } + } + if b.max[1] > r.max[1] { + if b.min[1] < r.min[1] { + area *= b.max[1] - b.min[1] + } else { + area *= b.max[1] - r.min[1] + } + } else { + if b.min[1] < r.min[1] { + area *= r.max[1] - b.min[1] + } else { + area *= r.max[1] - r.min[1] + } + } + return area +} + +// Insert inserts an item into the RTree +func (tr *RTree) Insert(min, max [2]float64, value interface{}) { + var item rect + fit(min, max, value, &item) + tr.insert(&item) +} + +func (tr *RTree) insert(item *rect) { + if tr.root.data == nil { + fit(item.min, item.max, new(node), &tr.root) + } + grown := tr.root.insert(item, tr.height) + if grown { + tr.root.expand(item) + } + if tr.root.data.(*node).count == maxEntries+1 { + newRoot := new(node) + tr.root.splitLargestAxisEdgeSnap(&newRoot.boxes[1]) + newRoot.boxes[0] = tr.root + newRoot.count = 2 + tr.root.data = newRoot + tr.root.recalc() + tr.height++ + } + tr.count++ +} + +func (r *rect) chooseLeastEnlargement(b *rect) int { + j, jenlargement, jarea := -1, 0.0, 0.0 + n := r.data.(*node) + for i := 0; i < n.count; i++ { + area := n.boxes[i].area() + enlargement := n.boxes[i].enlargedArea(b) - area + if j == -1 || enlargement < jenlargement { + j, jenlargement, jarea = i, enlargement, area + } else if enlargement == jenlargement { + if area < jarea { + j, jenlargement, jarea = i, enlargement, area + } + } + } + return j +} + +func (r *rect) recalc() { + n := r.data.(*node) + r.min = n.boxes[0].min + r.max = n.boxes[0].max + for i := 1; i < n.count; i++ { + r.expand(&n.boxes[i]) + } +} + +// contains return struct when b is fully contained inside of n +func (r *rect) contains(b *rect) bool { + if b.min[0] < r.min[0] || b.max[0] > r.max[0] { + return false + } + if b.min[1] < r.min[1] || b.max[1] > r.max[1] { + return false + } + return true +} + +func (r *rect) largestAxis() (axis int, size float64) { + if r.max[1]-r.min[1] > r.max[0]-r.min[0] { + return 1, r.max[1] - r.min[1] + } + return 0, r.max[0] - r.min[0] +} + +func (r *rect) splitLargestAxisEdgeSnap(right *rect) { + axis, _ := r.largestAxis() + left := r + leftNode := left.data.(*node) + rightNode := new(node) + right.data = rightNode + + var equals []rect + for i := 0; i < leftNode.count; i++ { + minDist := leftNode.boxes[i].min[axis] - left.min[axis] + maxDist := left.max[axis] - leftNode.boxes[i].max[axis] + if minDist < maxDist { + // stay left + } else { + if minDist > maxDist { + // move to right + rightNode.boxes[rightNode.count] = leftNode.boxes[i] + rightNode.count++ + } else { + // move to equals, at the end of the left array + equals = append(equals, leftNode.boxes[i]) + } + leftNode.boxes[i] = leftNode.boxes[leftNode.count-1] + leftNode.boxes[leftNode.count-1].data = nil + leftNode.count-- + i-- + } + } + for _, b := range equals { + if leftNode.count < rightNode.count { + leftNode.boxes[leftNode.count] = b + leftNode.count++ + } else { + rightNode.boxes[rightNode.count] = b + rightNode.count++ + } + } + left.recalc() + right.recalc() +} + +func (r *rect) insert(item *rect, height int) (grown bool) { + n := r.data.(*node) + if height == 0 { + n.boxes[n.count] = *item + n.count++ + grown = !r.contains(item) + return grown + } + // choose subtree + index := r.chooseLeastEnlargement(item) + child := &n.boxes[index] + grown = child.insert(item, height-1) + if grown { + child.expand(item) + grown = !r.contains(item) + } + if child.data.(*node).count == maxEntries+1 { + child.splitLargestAxisEdgeSnap(&n.boxes[n.count]) + n.count++ + } + return grown +} + +// fit an external item into a box type +func fit(min, max [2]float64, value interface{}, target *rect) { + target.min = min + target.max = max + target.data = value +} + +type overlapsResult int + +const ( + not overlapsResult = iota + intersects + contains +) + +// overlaps detects if r insersects or contains b. +// return not, intersects, contains +func (r *rect) overlaps(b *rect) overlapsResult { + if b.min[0] > r.max[0] || b.max[0] < r.min[0] { + return not + } + if r.min[0] > b.min[0] || b.max[0] > r.max[0] { + if b.min[1] > r.max[1] || b.max[1] < r.min[1] { + return not + } + return intersects + } + if b.min[1] > r.max[1] || b.max[1] < r.min[1] { + return not + } + if r.min[1] > b.min[1] || b.max[1] > r.max[1] { + return intersects + } + return contains +} + +// contains return struct when b is fully contained inside of n +func (r *rect) intersects(b *rect) bool { + if b.min[0] > r.max[0] || b.max[0] < r.min[0] { + return false + } + if b.min[1] > r.max[1] || b.max[1] < r.min[1] { + return false + } + return true +} + +func (r *rect) search( + target *rect, height int, + iter func(min, max [2]float64, value interface{}) bool, +) bool { + n := r.data.(*node) + if height == 0 { + for i := 0; i < n.count; i++ { + if target.intersects(&n.boxes[i]) { + if !iter(n.boxes[i].min, n.boxes[i].max, + n.boxes[i].data) { + return false + } + } + } + } else { + for i := 0; i < n.count; i++ { + switch target.overlaps(&n.boxes[i]) { + case intersects: + if !n.boxes[i].search(target, height-1, iter) { + return false + } + case contains: + if !n.boxes[i].scan(height-1, iter) { + return false + } + } + } + } + return true +} + +func (tr *RTree) search( + target *rect, + iter func(min, max [2]float64, value interface{}) bool, +) { + if tr.root.data == nil { + return + } + res := target.overlaps(&tr.root) + if res == intersects { + tr.root.search(target, tr.height, iter) + } else if res == contains { + tr.root.scan(tr.height, iter) + } +} + +// Search ... +func (tr *RTree) Search( + min, max [2]float64, + iter func(min, max [2]float64, value interface{}) bool, +) { + var target rect + fit(min, max, nil, &target) + tr.search(&target, iter) +} + +func (r *rect) scan( + height int, + iter func(min, max [2]float64, value interface{}) bool, +) bool { + n := r.data.(*node) + if height == 0 { + for i := 0; i < n.count; i++ { + if !iter(n.boxes[i].min, n.boxes[i].max, n.boxes[i].data) { + return false + } + } + } else { + for i := 0; i < n.count; i++ { + if !n.boxes[i].scan(height-1, iter) { + return false + } + } + } + return true +} + +// Scan iterates through all data in tree. +func (tr *RTree) Scan(iter func(min, max [2]float64, data interface{}) bool) { + if tr.root.data == nil { + return + } + tr.root.scan(tr.height, iter) +} + +// Delete data from tree +func (tr *RTree) Delete(min, max [2]float64, data interface{}) { + var item rect + fit(min, max, data, &item) + if tr.root.data == nil || !tr.root.contains(&item) { + return + } + var removed, recalced bool + removed, recalced, tr.reinsert = + tr.root.delete(&item, tr.height, tr.reinsert[:0]) + if !removed { + return + } + tr.count -= len(tr.reinsert) + 1 + if tr.count == 0 { + tr.root = rect{} + recalced = false + } else { + for tr.height > 0 && tr.root.data.(*node).count == 1 { + tr.root = tr.root.data.(*node).boxes[0] + tr.height-- + tr.root.recalc() + } + } + if recalced { + tr.root.recalc() + } + for i := range tr.reinsert { + tr.insert(&tr.reinsert[i]) + tr.reinsert[i].data = nil + } +} + +func (r *rect) delete(item *rect, height int, reinsert []rect) ( + removed, recalced bool, reinsertOut []rect, +) { + n := r.data.(*node) + if height == 0 { + for i := 0; i < n.count; i++ { + if n.boxes[i].data == item.data { + // found the target item to delete + recalced = r.onEdge(&n.boxes[i]) + n.boxes[i] = n.boxes[n.count-1] + n.boxes[n.count-1].data = nil + n.count-- + if recalced { + r.recalc() + } + return true, recalced, reinsert + } + } + } else { + for i := 0; i < n.count; i++ { + if !n.boxes[i].contains(item) { + continue + } + removed, recalced, reinsert = + n.boxes[i].delete(item, height-1, reinsert) + if !removed { + continue + } + if n.boxes[i].data.(*node).count < minEntries { + // underflow + if !recalced { + recalced = r.onEdge(&n.boxes[i]) + } + reinsert = n.boxes[i].flatten(reinsert, height-1) + n.boxes[i] = n.boxes[n.count-1] + n.boxes[n.count-1].data = nil + n.count-- + } + if recalced { + r.recalc() + } + return removed, recalced, reinsert + } + } + return false, false, reinsert +} + +// flatten flattens all leaf boxes into a single list +func (r *rect) flatten(all []rect, height int) []rect { + n := r.data.(*node) + if height == 0 { + all = append(all, n.boxes[:n.count]...) + } else { + for i := 0; i < n.count; i++ { + all = n.boxes[i].flatten(all, height-1) + } + } + return all +} + +// onedge returns true when b is on the edge of r +func (r *rect) onEdge(b *rect) bool { + if r.min[0] == b.min[0] || r.max[0] == b.max[0] { + return true + } + if r.min[1] == b.min[1] || r.max[1] == b.max[1] { + return true + } + return false +} + +// Len returns the number of items in tree +func (tr *RTree) Len() int { + return tr.count +} + +// Bounds returns the minimum bounding box +func (tr *RTree) Bounds() (min, max [2]float64) { + if tr.root.data == nil { + return + } + return tr.root.min, tr.root.max +} + +// Children is a utility function that returns all children for parent node. +// If parent node is nil then the root nodes should be returned. The min, max, +// data, and items slices all must have the same lengths. And, each element +// from all slices must be associated. Returns true for `items` when the the +// item at the leaf level. The reuse buffers are empty length slices that can +// optionally be used to avoid extra allocations. +func (tr *RTree) Children( + parent interface{}, + reuse []child.Child, +) []child.Child { + children := reuse + if parent == nil { + if tr.Len() > 0 { + // fill with the root + children = append(children, child.Child{ + Min: tr.root.min, + Max: tr.root.max, + Data: tr.root.data, + Item: false, + }) + } + } else { + // fill with child items + n := parent.(*node) + item := true + if n.count > 0 { + if _, ok := n.boxes[0].data.(*node); ok { + item = false + } + } + for i := 0; i < n.count; i++ { + children = append(children, child.Child{ + Min: n.boxes[i].min, + Max: n.boxes[i].max, + Data: n.boxes[i].data, + Item: item, + }) + } + } + return children +} diff --git a/vendor/github.com/tidwall/boxtree/d2/boxtree_test.go b/vendor/github.com/tidwall/rbang/rbang_test.go similarity index 69% rename from vendor/github.com/tidwall/boxtree/d2/boxtree_test.go rename to vendor/github.com/tidwall/rbang/rbang_test.go index db9c20a7..46365282 100644 --- a/vendor/github.com/tidwall/boxtree/d2/boxtree_test.go +++ b/vendor/github.com/tidwall/rbang/rbang_test.go @@ -1,15 +1,75 @@ -package d2 +package rbang import ( "fmt" "math/rand" + "os" "sort" "strconv" "strings" "testing" "time" + + "github.com/tidwall/geoindex" + "github.com/tidwall/lotsa" ) +func TestBenchInsert2D(t *testing.T) { + testBenchInsert(t, 100000, 2) +} + +func testBenchInsert(t *testing.T, N, D int) { + rand.Seed(time.Now().UnixNano()) + points := make([]float64, N*D) + for i := 0; i < N; i++ { + for j := 0; j < D; j++ { + points[i*D+j] = rand.Float64()*100 - 50 + } + } + var tr RTree + lotsa.Output = os.Stdout + fmt.Printf("Insert(%dD): ", D) + lotsa.Ops(N, 1, func(i, _ int) { + point := [2]float64{points[i*D+0], points[i*D+1]} + tr.Insert(point, point, i) + }) + fmt.Printf("Search(%dD): ", D) + var count int + lotsa.Ops(N, 1, func(i, _ int) { + point := [2]float64{points[i*D+0], points[i*D+1]} + tr.Search(point, point, + func(min, max [2]float64, value interface{}) bool { + count++ + return true + }, + ) + }) + if count != N { + t.Fatalf("expected %d, got %d", N, count) + } + fmt.Printf("Delete(%dD): ", D) + lotsa.Ops(N, 1, func(i, _ int) { + point := [2]float64{points[i*D+0], points[i*D+1]} + tr.Delete(point, point, i) + }) + if tr.Len() != 0 { + t.Fatalf("expected %d, got %d", N, tr.Len()) + } +} + +type tItem2 struct { + point [2]float64 +} + +func (item *tItem2) Point() (x, y float64) { + return item.point[0], item.point[1] +} +func (item *tItem2) Rect() (minX, minY, maxX, maxY float64) { + return item.point[0], item.point[1], item.point[0], item.point[1] +} + +const dims = 2 + type tBox struct { min [dims]float64 max [dims]float64 @@ -20,7 +80,7 @@ var points []tBox func init() { seed := time.Now().UnixNano() - // seed = 1532132365683340889 + seed = 1532132365683340889 println("seed:", seed) rand.Seed(seed) } @@ -78,14 +138,14 @@ func sortBoxes(boxes []tBox) { }) } -func sortBoxesNearby(boxes []tBox, min, max []float64) { +func sortBoxesNearby(boxes []tBox, min, max [2]float64) { sort.Slice(boxes, func(i, j int) bool { - return testBoxDist(boxes[i].min[:], boxes[i].max[:], min, max) < - testBoxDist(boxes[j].min[:], boxes[j].max[:], min, max) + return testBoxDist(boxes[i].min, boxes[i].max, min, max) < + testBoxDist(boxes[j].min, boxes[j].max, min, max) }) } -func testBoxDist(amin, amax, bmin, bmax []float64) float64 { +func testBoxDist(amin, amax, bmin, bmax [2]float64) float64 { var dist float64 for i := 0; i < len(amin); i++ { var min, max float64 @@ -110,7 +170,7 @@ func testBoxDist(amin, amax, bmin, bmax []float64) float64 { func testBoxesVarious(t *testing.T, boxes []tBox, label string) { N := len(boxes) - var tr BoxTree + var tr RTree // N := 10000 // boxes := randPoints(N) @@ -119,10 +179,10 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { // insert ///////////////////////////////////////// for i := 0; i < N; i++ { - tr.Insert(boxes[i].min[:], boxes[i].max[:], boxes[i]) + tr.Insert(boxes[i].min, boxes[i].max, boxes[i]) } - if tr.Count() != N { - t.Fatalf("expected %d, got %d", N, tr.Count()) + if tr.Len() != N { + t.Fatalf("expected %d, got %d", N, tr.Len()) } // area := tr.TotalOverlapArea() // fmt.Printf("overlap: %.0f, %.1f/item\n", area, area/float64(N)) @@ -133,7 +193,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { // scan all items and count one-by-one ///////////////////////////////////////// var count int - tr.Scan(func(min, max []float64, value interface{}) bool { + tr.Scan(func(min, max [2]float64, value interface{}) bool { count++ return true }) @@ -145,7 +205,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { // check every point for correctness ///////////////////////////////////////// var tboxes1 []tBox - tr.Scan(func(min, max []float64, value interface{}) bool { + tr.Scan(func(min, max [2]float64, value interface{}) bool { tboxes1 = append(tboxes1, value.(tBox)) return true }) @@ -164,8 +224,8 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { ///////////////////////////////////////// for i := 0; i < N; i++ { var found bool - tr.Search(boxes[i].min[:], boxes[i].max[:], - func(min, max []float64, value interface{}) bool { + tr.Search(boxes[i].min, boxes[i].max, + func(min, max [2]float64, value interface{}) bool { if value == boxes[i] { found = true return false @@ -177,11 +237,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { } } - centerMin, centerMax := []float64{-18, -9}, []float64{18, 9} - for j := 2; j < dims; j++ { - centerMin = append(centerMin, -10) - centerMax = append(centerMax, 10) - } + centerMin, centerMax := [2]float64{-18, -9}, [2]float64{18, 9} ///////////////////////////////////////// // search for 10% of the items @@ -189,7 +245,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { for i := 0; i < N/5; i++ { var count int tr.Search(centerMin, centerMax, - func(min, max []float64, value interface{}) bool { + func(min, max [2]float64, value interface{}) bool { count++ return true }, @@ -201,14 +257,14 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { ///////////////////////////////////////// for i := 0; i < N/2; i++ { j := i * 2 - tr.Delete(boxes[j].min[:], boxes[j].max[:], boxes[j]) + tr.Delete(boxes[j].min, boxes[j].max, boxes[j]) } ///////////////////////////////////////// // count all items. should be half of N ///////////////////////////////////////// count = 0 - tr.Scan(func(min, max []float64, value interface{}) bool { + tr.Scan(func(min, max [2]float64, value interface{}) bool { count++ return true }) @@ -229,7 +285,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { }) for i := 0; i < N/2; i++ { j := ij[i] - tr.Insert(boxes[j].min[:], boxes[j].max[:], boxes[j]) + tr.Insert(boxes[j].min, boxes[j].max, boxes[j]) } ////////////////////////////////////////////////////// @@ -248,11 +304,11 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { } for i := 0; i < N; i++ { - tr.Insert(nboxes[i].min[:], nboxes[i].max[:], nboxes[i]) - tr.Delete(boxes[i].min[:], boxes[i].max[:], boxes[i]) + tr.Insert(nboxes[i].min, nboxes[i].max, nboxes[i]) + tr.Delete(boxes[i].min, boxes[i].max, boxes[i]) } - if tr.Count() != N { - t.Fatalf("expected %d, got %d", N, tr.Count()) + if tr.Len() != N { + t.Fatalf("expected %d, got %d", N, tr.Len()) } // area = tr.TotalOverlapArea() // fmt.Fprintf(wr, "overlap: %.0f, %.1f/item\n", area, area/float64(N)) @@ -261,7 +317,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { // check every point for correctness ///////////////////////////////////////// tboxes1 = nil - tr.Scan(func(min, max []float64, value interface{}) bool { + tr.Scan(func(min, max [2]float64, value interface{}) bool { tboxes1 = append(tboxes1, value.(tBox)) return true }) @@ -281,7 +337,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { for i := 0; i < N/5; i++ { var count int tr.Search(centerMin, centerMax, - func(min, max []float64, value interface{}) bool { + func(min, max [2]float64, value interface{}) bool { count++ return true }, @@ -289,21 +345,24 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) { } var boxes3 []tBox - tr.Nearby(centerMin, centerMax, - func(min, max []float64, value interface{}) bool { + geoindex.Wrap(&tr).Nearby( + geoindex.SimpleBoxAlgo(centerMin, centerMax), + func(min, max [2]float64, value interface{}, dist float64) bool { boxes3 = append(boxes3, value.(tBox)) return true }, ) + if len(boxes3) != len(nboxes) { t.Fatalf("expected %d, got %d", len(nboxes), len(boxes3)) } - if len(boxes3) != tr.Count() { - t.Fatalf("expected %d, got %d", tr.Count(), len(boxes3)) + if len(boxes3) != tr.Len() { + t.Fatalf("expected %d, got %d", tr.Len(), len(boxes3)) } + var ldist float64 for i, box := range boxes3 { - dist := testBoxDist(box.min[:], box.max[:], centerMin, centerMax) + dist := testBoxDist(box.min, box.max, centerMin, centerMax) if i > 0 && dist < ldist { t.Fatalf("out of order") } @@ -319,7 +378,7 @@ func TestRandomPoints(t *testing.T) { testBoxesVarious(t, randPoints(10000), "points") } -func (r *box) boxstr() string { +func (r *rect) boxstr() string { var b []byte b = append(b, '[', '[') for i := 0; i < len(r.min); i++ { @@ -339,7 +398,7 @@ func (r *box) boxstr() string { return string(b) } -func (r *box) print(height, indent int) { +func (r *rect) print(height, indent int) { fmt.Printf("%s%s", strings.Repeat(" ", indent), r.boxstr()) if height == 0 { fmt.Printf("\t'%v'\n", r.data) @@ -352,7 +411,7 @@ func (r *box) print(height, indent int) { } -func (tr BoxTree) print() { +func (tr RTree) print() { if tr.root.data == nil { println("EMPTY TREE") return @@ -362,18 +421,18 @@ func (tr BoxTree) print() { func TestZeroPoints(t *testing.T) { N := 10000 - var tr BoxTree - pt := make([]float64, dims) + var tr RTree + var pt [2]float64 for i := 0; i < N; i++ { - tr.Insert(pt, nil, i) + tr.Insert(pt, pt, i) } } func BenchmarkRandomInsert(b *testing.B) { - var tr BoxTree + var tr RTree boxes := randBoxes(b.N) b.ResetTimer() for i := 0; i < b.N; i++ { - tr.Insert(boxes[i].min[:], boxes[i].max[:], i) + tr.Insert(boxes[i].min, boxes[i].max, i) } }