Replaced boxtree for rbang

This commit is contained in:
tidwall 2019-09-12 18:42:53 -07:00
parent 93cf63f1bd
commit 639f6e2deb
25 changed files with 1112 additions and 1947 deletions

36
Gopkg.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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)
}
}

View File

@ -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())
// }
// }

View File

@ -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[:]
}

View File

@ -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[:]
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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(
"<rect x=\"%.0f\" y=\"%0.f\" width=\"%0.f\" height=\"%0.f\" "+
"stroke=\"%s\" fill=\"purple\" "+
"fill-opacity=\"0\" stroke-opacity=\"1\" "+
"rx=\"15\" ry=\"15\"/>\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(
"<rect x=\"%.0f\" y=\"%0.f\" width=\"%0.f\" height=\"%0.f\" "+
"stroke=\"%s\" fill=\"purple\" "+
"fill-opacity=\"0\" stroke-opacity=\"1\"/>\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("<svg viewBox=\"%.0f %.0f %.0f %.0f\" "+
"xmlns =\"http://www.w3.org/2000/svg\">\n",
-190.0*svgScale, -100.0*svgScale,
380.0*svgScale, 190.0*svgScale)
out += fmt.Sprintf("<g transform=\"scale(1,-1)\">\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("</g>\n")
out += fmt.Sprintf("</svg>\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
}

3
vendor/github.com/tidwall/geoindex/README.md generated vendored Normal file
View File

@ -0,0 +1,3 @@
# `geoindex`
Simplified interface and extension utilities for a geospatial indexes.

11
vendor/github.com/tidwall/geoindex/child/child.go generated vendored Normal file
View File

@ -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
}

305
vendor/github.com/tidwall/geoindex/geoindex.go generated vendored Normal file
View File

@ -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(
"<rect x=\"%.0f\" y=\"%0.f\" width=\"%0.f\" height=\"%0.f\" "+
"stroke=\"%s\" fill=\"purple\" "+
"fill-opacity=\"0\" stroke-opacity=\"1\" "+
"rx=\"15\" ry=\"15\"/>\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(
"<rect x=\"%.0f\" y=\"%0.f\" width=\"%0.f\" height=\"%0.f\" "+
"stroke=\"%s\" fill=\"purple\" "+
"fill-opacity=\"0\" stroke-opacity=\"1\"/>\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("<svg viewBox=\"%.0f %.0f %.0f %.0f\" "+
"xmlns =\"http://www.w3.org/2000/svg\">\n",
-190.0*svgScale, -100.0*svgScale,
380.0*svgScale, 190.0*svgScale)
out += fmt.Sprintf("<g transform=\"scale(1,-1)\">\n")
var outb []byte
for _, child := range index.Children(nil, nil) {
outb = append(outb, index.svg(child, 1)...)
}
out += string(outb)
out += fmt.Sprintf("</g>\n")
out += fmt.Sprintf("</svg>\n")
return out
}

View File

@ -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)
}
}

View File

@ -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,
)
}

19
vendor/github.com/tidwall/rbang/LICENSE generated vendored Normal file
View File

@ -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.

View File

@ -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.
<img src="/res/cities.png" width="512" height="256" border="0" alt="Cities">
## Features
- Support for 2 and 3 dimensions
- Optimized for fast box inserts and replacements.
<img src="cities.png" width="512" height="256" border="0" alt="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.

View File

Before

Width:  |  Height:  |  Size: 336 KiB

After

Width:  |  Height:  |  Size: 336 KiB

540
vendor/github.com/tidwall/rbang/rbang.go generated vendored Normal file
View File

@ -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
}

View File

@ -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)
}
}