mirror of https://github.com/tidwall/tile38.git
Replaced boxtree for rbang
This commit is contained in:
parent
93cf63f1bd
commit
639f6e2deb
|
@ -196,14 +196,6 @@
|
||||||
pruneopts = ""
|
pruneopts = ""
|
||||||
revision = "cefed15a0bd808d13947f228770a81b06ebe8e45"
|
revision = "cefed15a0bd808d13947f228770a81b06ebe8e45"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:6e319cc90f8432f0ac3f78e6bb7be410e6939b6405e1ba84ae4e920de15476f3"
|
|
||||||
name = "github.com/tidwall/boxtree"
|
|
||||||
packages = ["d2"]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "a570caa42c5e4c65f50e6f7ca0e7fa1c5084981c"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:b28f2f9253cbb1bf2bcb3c0ab7421d2f88a245386199a6668b0a66eb09ce3e1f"
|
digest = "1:b28f2f9253cbb1bf2bcb3c0ab7421d2f88a245386199a6668b0a66eb09ce3e1f"
|
||||||
|
@ -221,7 +213,18 @@
|
||||||
version = "v1.1.0"
|
version = "v1.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[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"
|
name = "github.com/tidwall/geojson"
|
||||||
packages = [
|
packages = [
|
||||||
".",
|
".",
|
||||||
|
@ -229,8 +232,8 @@
|
||||||
"geometry",
|
"geometry",
|
||||||
]
|
]
|
||||||
pruneopts = ""
|
pruneopts = ""
|
||||||
revision = "ff8d554639ee72ffa650eca1e02f03ab51193c7f"
|
revision = "09ce8fa8548966071daf8df6bfc692cf756ff8cc"
|
||||||
version = "v1.1.6"
|
version = "v1.1.7"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:30e9a79822702670b96d3461aca7da11b8cc6e7954eb4e859e886559ed4802a4"
|
digest = "1:30e9a79822702670b96d3461aca7da11b8cc6e7954eb4e859e886559ed4802a4"
|
||||||
|
@ -272,6 +275,14 @@
|
||||||
pruneopts = ""
|
pruneopts = ""
|
||||||
revision = "65a9db5fad5105a89e17f38adcc9878685be6d78"
|
revision = "65a9db5fad5105a89e17f38adcc9878685be6d78"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:3deeba766e407673583fcef7135199c081c4a236071511b6c4cac412335bcecc"
|
||||||
|
name = "github.com/tidwall/rbang"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "55391bcd9942773f84554000f0c9600345e3ef92"
|
||||||
|
version = "v1.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:e84d0aa788bd55e938ebbaa62782385ca4da00b63c1d6bf23270c031a2ae9a88"
|
digest = "1:e84d0aa788bd55e938ebbaa62782385ca4da00b63c1d6bf23270c031a2ae9a88"
|
||||||
|
@ -460,9 +471,9 @@
|
||||||
"github.com/nats-io/go-nats",
|
"github.com/nats-io/go-nats",
|
||||||
"github.com/peterh/liner",
|
"github.com/peterh/liner",
|
||||||
"github.com/streadway/amqp",
|
"github.com/streadway/amqp",
|
||||||
"github.com/tidwall/boxtree/d2",
|
|
||||||
"github.com/tidwall/btree",
|
"github.com/tidwall/btree",
|
||||||
"github.com/tidwall/buntdb",
|
"github.com/tidwall/buntdb",
|
||||||
|
"github.com/tidwall/geoindex",
|
||||||
"github.com/tidwall/geojson",
|
"github.com/tidwall/geojson",
|
||||||
"github.com/tidwall/geojson/geo",
|
"github.com/tidwall/geojson/geo",
|
||||||
"github.com/tidwall/geojson/geometry",
|
"github.com/tidwall/geojson/geometry",
|
||||||
|
@ -470,6 +481,7 @@
|
||||||
"github.com/tidwall/lotsa",
|
"github.com/tidwall/lotsa",
|
||||||
"github.com/tidwall/match",
|
"github.com/tidwall/match",
|
||||||
"github.com/tidwall/pretty",
|
"github.com/tidwall/pretty",
|
||||||
|
"github.com/tidwall/rbang",
|
||||||
"github.com/tidwall/redbench",
|
"github.com/tidwall/redbench",
|
||||||
"github.com/tidwall/redcon",
|
"github.com/tidwall/redcon",
|
||||||
"github.com/tidwall/resp",
|
"github.com/tidwall/resp",
|
||||||
|
|
|
@ -29,13 +29,9 @@ required = [
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/tidwall/tinybtree"
|
name = "github.com/tidwall/tinybtree"
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/tidwall/boxtree"
|
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/tidwall/geojson"
|
name = "github.com/tidwall/geojson"
|
||||||
version = "1.1.6"
|
version = "1.1.7"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/Shopify/sarama"
|
name = "github.com/Shopify/sarama"
|
||||||
|
|
|
@ -3,11 +3,12 @@ package collection
|
||||||
import (
|
import (
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/tidwall/boxtree/d2"
|
|
||||||
"github.com/tidwall/btree"
|
"github.com/tidwall/btree"
|
||||||
|
"github.com/tidwall/geoindex"
|
||||||
"github.com/tidwall/geojson"
|
"github.com/tidwall/geojson"
|
||||||
"github.com/tidwall/geojson/geo"
|
"github.com/tidwall/geojson/geo"
|
||||||
"github.com/tidwall/geojson/geometry"
|
"github.com/tidwall/geojson/geometry"
|
||||||
|
"github.com/tidwall/rbang"
|
||||||
"github.com/tidwall/tile38/internal/deadline"
|
"github.com/tidwall/tile38/internal/deadline"
|
||||||
"github.com/tidwall/tinybtree"
|
"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.
|
// Collection represents a collection of geojson objects.
|
||||||
type Collection struct {
|
type Collection struct {
|
||||||
items tinybtree.BTree // items sorted by keys
|
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
|
values *btree.BTree // items sorted by value+key
|
||||||
fieldMap map[string]int
|
fieldMap map[string]int
|
||||||
fieldValues map[string][]float64
|
fieldValues map[string][]float64
|
||||||
|
@ -57,7 +58,8 @@ var counter uint64
|
||||||
// New creates an empty collection
|
// New creates an empty collection
|
||||||
func New() *Collection {
|
func New() *Collection {
|
||||||
col := &Collection{
|
col := &Collection{
|
||||||
values: btree.New(16, nil),
|
index: geoindex.Wrap(&rbang.RTree{}),
|
||||||
|
values: btree.New(32, nil),
|
||||||
fieldMap: make(map[string]int),
|
fieldMap: make(map[string]int),
|
||||||
}
|
}
|
||||||
return col
|
return col
|
||||||
|
@ -130,8 +132,8 @@ func (c *Collection) indexDelete(item *itemT) {
|
||||||
if !item.obj.Empty() {
|
if !item.obj.Empty() {
|
||||||
rect := item.obj.Rect()
|
rect := item.obj.Rect()
|
||||||
c.index.Delete(
|
c.index.Delete(
|
||||||
[]float64{rect.Min.X, rect.Min.Y},
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
[]float64{rect.Max.X, rect.Max.Y},
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
item)
|
item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,8 +142,8 @@ func (c *Collection) indexInsert(item *itemT) {
|
||||||
if !item.obj.Empty() {
|
if !item.obj.Empty() {
|
||||||
rect := item.obj.Rect()
|
rect := item.obj.Rect()
|
||||||
c.index.Insert(
|
c.index.Insert(
|
||||||
[]float64{rect.Min.X, rect.Min.Y},
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
[]float64{rect.Max.X, rect.Max.Y},
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
item)
|
item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -497,9 +499,9 @@ func (c *Collection) geoSearch(
|
||||||
) bool {
|
) bool {
|
||||||
alive := true
|
alive := true
|
||||||
c.index.Search(
|
c.index.Search(
|
||||||
[]float64{rect.Min.X, rect.Min.Y},
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
[]float64{rect.Max.X, rect.Max.Y},
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
func(_, _ []float64, itemv interface{}) bool {
|
func(_, _ [2]float64, itemv interface{}) bool {
|
||||||
item := itemv.(*itemT)
|
item := itemv.(*itemT)
|
||||||
alive = iter(item.id, item.obj, c.getFieldValues(item.id))
|
alive = iter(item.id, item.obj, c.getFieldValues(item.id))
|
||||||
return alive
|
return alive
|
||||||
|
@ -687,9 +689,9 @@ func (c *Collection) Nearby(
|
||||||
geo.RectFromCenter(center.Y, center.X, meters)
|
geo.RectFromCenter(center.Y, center.X, meters)
|
||||||
var exists bool
|
var exists bool
|
||||||
c.index.Search(
|
c.index.Search(
|
||||||
[]float64{minLon, minLat},
|
[2]float64{minLon, minLat},
|
||||||
[]float64{maxLon, maxLat},
|
[2]float64{maxLon, maxLat},
|
||||||
func(_, _ []float64, itemv interface{}) bool {
|
func(_, _ [2]float64, itemv interface{}) bool {
|
||||||
exists = true
|
exists = true
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
|
@ -710,9 +712,11 @@ func (c *Collection) Nearby(
|
||||||
cursor.Step(offset)
|
cursor.Step(offset)
|
||||||
}
|
}
|
||||||
c.index.Nearby(
|
c.index.Nearby(
|
||||||
[]float64{center.X, center.Y},
|
geoindex.SimpleBoxAlgo(
|
||||||
[]float64{center.X, center.Y},
|
[2]float64{center.X, center.Y},
|
||||||
func(_, _ []float64, itemv interface{}) bool {
|
[2]float64{center.X, center.Y},
|
||||||
|
),
|
||||||
|
func(_, _ [2]float64, itemv interface{}, _ float64) bool {
|
||||||
count++
|
count++
|
||||||
if count <= offset {
|
if count <= offset {
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -219,9 +219,9 @@ func (server *Server) getQueueCandidates(d *commandDetails) []*Hook {
|
||||||
}
|
}
|
||||||
rect := obj.Rect()
|
rect := obj.Rect()
|
||||||
server.hookTree.Search(
|
server.hookTree.Search(
|
||||||
[]float64{rect.Min.X, rect.Min.Y},
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
[]float64{rect.Max.X, rect.Max.Y},
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
func(_, _ []float64, value interface{}) bool {
|
func(_, _ [2]float64, value interface{}) bool {
|
||||||
hook := value.(*Hook)
|
hook := value.(*Hook)
|
||||||
if hook.Key != d.key {
|
if hook.Key != d.key {
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -8,9 +8,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mmcloughlin/geohash"
|
"github.com/mmcloughlin/geohash"
|
||||||
"github.com/tidwall/boxtree/d2"
|
|
||||||
"github.com/tidwall/geojson"
|
"github.com/tidwall/geojson"
|
||||||
"github.com/tidwall/geojson/geometry"
|
"github.com/tidwall/geojson/geometry"
|
||||||
|
"github.com/tidwall/rbang"
|
||||||
"github.com/tidwall/resp"
|
"github.com/tidwall/resp"
|
||||||
"github.com/tidwall/tile38/internal/collection"
|
"github.com/tidwall/tile38/internal/collection"
|
||||||
"github.com/tidwall/tile38/internal/glob"
|
"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.expires = make(map[string]map[string]time.Time)
|
||||||
server.hooks = make(map[string]*Hook)
|
server.hooks = make(map[string]*Hook)
|
||||||
server.hooksOut = make(map[string]*Hook)
|
server.hooksOut = make(map[string]*Hook)
|
||||||
server.hookTree = d2.BoxTree{}
|
server.hookTree = rbang.RTree{}
|
||||||
d.command = "flushdb"
|
d.command = "flushdb"
|
||||||
d.updated = true
|
d.updated = true
|
||||||
d.timestamp = time.Now()
|
d.timestamp = time.Now()
|
||||||
|
|
|
@ -193,16 +193,16 @@ func (c *Server) cmdSetHook(msg *Message, chanCmd bool) (
|
||||||
if prevHook != nil && prevHook.Fence != nil && prevHook.Fence.obj != nil {
|
if prevHook != nil && prevHook.Fence != nil && prevHook.Fence.obj != nil {
|
||||||
rect := prevHook.Fence.obj.Rect()
|
rect := prevHook.Fence.obj.Rect()
|
||||||
c.hookTree.Delete(
|
c.hookTree.Delete(
|
||||||
[]float64{rect.Min.X, rect.Min.Y},
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
[]float64{rect.Max.X, rect.Max.Y},
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
prevHook)
|
prevHook)
|
||||||
}
|
}
|
||||||
// add hook to spatial index
|
// add hook to spatial index
|
||||||
if hook != nil && hook.Fence != nil && hook.Fence.obj != nil {
|
if hook != nil && hook.Fence != nil && hook.Fence.obj != nil {
|
||||||
rect := hook.Fence.obj.Rect()
|
rect := hook.Fence.obj.Rect()
|
||||||
c.hookTree.Insert(
|
c.hookTree.Insert(
|
||||||
[]float64{rect.Min.X, rect.Min.Y},
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
[]float64{rect.Max.X, rect.Max.Y},
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
hook)
|
hook)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,8 +242,8 @@ func (c *Server) cmdDelHook(msg *Message, chanCmd bool) (
|
||||||
if hook != nil && hook.Fence != nil && hook.Fence.obj != nil {
|
if hook != nil && hook.Fence != nil && hook.Fence.obj != nil {
|
||||||
rect := hook.Fence.obj.Rect()
|
rect := hook.Fence.obj.Rect()
|
||||||
c.hookTree.Delete(
|
c.hookTree.Delete(
|
||||||
[]float64{rect.Min.X, rect.Min.Y},
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
[]float64{rect.Max.X, rect.Max.Y},
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
hook)
|
hook)
|
||||||
}
|
}
|
||||||
d.updated = true
|
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 {
|
if hook != nil && hook.Fence != nil && hook.Fence.obj != nil {
|
||||||
rect := hook.Fence.obj.Rect()
|
rect := hook.Fence.obj.Rect()
|
||||||
c.hookTree.Delete(
|
c.hookTree.Delete(
|
||||||
[]float64{rect.Min.X, rect.Min.Y},
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
[]float64{rect.Max.X, rect.Max.Y},
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
hook)
|
hook)
|
||||||
}
|
}
|
||||||
d.updated = true
|
d.updated = true
|
||||||
|
|
|
@ -22,10 +22,10 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tidwall/boxtree/d2"
|
|
||||||
"github.com/tidwall/buntdb"
|
"github.com/tidwall/buntdb"
|
||||||
"github.com/tidwall/geojson"
|
"github.com/tidwall/geojson"
|
||||||
"github.com/tidwall/geojson/geometry"
|
"github.com/tidwall/geojson/geometry"
|
||||||
|
"github.com/tidwall/rbang"
|
||||||
"github.com/tidwall/redcon"
|
"github.com/tidwall/redcon"
|
||||||
"github.com/tidwall/resp"
|
"github.com/tidwall/resp"
|
||||||
"github.com/tidwall/tile38/core"
|
"github.com/tidwall/tile38/core"
|
||||||
|
@ -116,7 +116,7 @@ type Server struct {
|
||||||
shrinking bool // aof shrinking flag
|
shrinking bool // aof shrinking flag
|
||||||
shrinklog [][]string // aof shrinking log
|
shrinklog [][]string // aof shrinking log
|
||||||
hooks map[string]*Hook // hook name
|
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
|
hooksOut map[string]*Hook // hooks with "outside" detection
|
||||||
aofconnM map[net.Conn]bool
|
aofconnM map[net.Conn]bool
|
||||||
luascripts *lScriptMap
|
luascripts *lScriptMap
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
|
@ -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[:]
|
|
||||||
}
|
|
|
@ -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[:]
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
# `geoindex`
|
||||||
|
|
||||||
|
Simplified interface and extension utilities for a geospatial indexes.
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -1,15 +1,20 @@
|
||||||
package d3
|
package geoindex
|
||||||
|
|
||||||
|
// These tests are ripped off from github.com/tidwall/rbang
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/tidwall/cities"
|
||||||
|
"github.com/tidwall/rbang"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const dims = 2
|
||||||
|
|
||||||
type tBox struct {
|
type tBox struct {
|
||||||
min [dims]float64
|
min [dims]float64
|
||||||
max [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 {
|
sort.Slice(boxes, func(i, j int) bool {
|
||||||
return testBoxDist(boxes[i].min[:], boxes[i].max[:], min, max) <
|
return testBoxDist(boxes[i].min, boxes[i].max, min, max) <
|
||||||
testBoxDist(boxes[j].min[:], boxes[j].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
|
var dist float64
|
||||||
for i := 0; i < len(amin); i++ {
|
for i := 0; i < len(amin); i++ {
|
||||||
var min, max float64
|
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) {
|
func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
N := len(boxes)
|
N := len(boxes)
|
||||||
|
|
||||||
var tr BoxTree
|
tr := Wrap(&rbang.RTree{})
|
||||||
|
|
||||||
// N := 10000
|
// N := 10000
|
||||||
// boxes := randPoints(N)
|
// boxes := randPoints(N)
|
||||||
|
@ -119,10 +124,10 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
// insert
|
// insert
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
for i := 0; i < N; i++ {
|
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 {
|
if tr.Len() != N {
|
||||||
t.Fatalf("expected %d, got %d", N, tr.Count())
|
t.Fatalf("expected %d, got %d", N, tr.Len())
|
||||||
}
|
}
|
||||||
// area := tr.TotalOverlapArea()
|
// area := tr.TotalOverlapArea()
|
||||||
// fmt.Printf("overlap: %.0f, %.1f/item\n", area, area/float64(N))
|
// 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
|
// scan all items and count one-by-one
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
var count int
|
var count int
|
||||||
tr.Scan(func(min, max []float64, value interface{}) bool {
|
tr.Scan(func(min, max [2]float64, value interface{}) bool {
|
||||||
count++
|
count++
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
@ -145,7 +150,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
// check every point for correctness
|
// check every point for correctness
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
var tboxes1 []tBox
|
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))
|
tboxes1 = append(tboxes1, value.(tBox))
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
@ -164,8 +169,8 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
for i := 0; i < N; i++ {
|
for i := 0; i < N; i++ {
|
||||||
var found bool
|
var found bool
|
||||||
tr.Search(boxes[i].min[:], boxes[i].max[:],
|
tr.Search(boxes[i].min, boxes[i].max,
|
||||||
func(min, max []float64, value interface{}) bool {
|
func(min, max [2]float64, value interface{}) bool {
|
||||||
if value == boxes[i] {
|
if value == boxes[i] {
|
||||||
found = true
|
found = true
|
||||||
return false
|
return false
|
||||||
|
@ -177,11 +182,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
centerMin, centerMax := []float64{-18, -9}, []float64{18, 9}
|
centerMin, centerMax := [2]float64{-18, -9}, [2]float64{18, 9}
|
||||||
for j := 2; j < dims; j++ {
|
|
||||||
centerMin = append(centerMin, -10)
|
|
||||||
centerMax = append(centerMax, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
// search for 10% of the items
|
// 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++ {
|
for i := 0; i < N/5; i++ {
|
||||||
var count int
|
var count int
|
||||||
tr.Search(centerMin, centerMax,
|
tr.Search(centerMin, centerMax,
|
||||||
func(min, max []float64, value interface{}) bool {
|
func(min, max [2]float64, value interface{}) bool {
|
||||||
count++
|
count++
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
@ -201,14 +202,14 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
for i := 0; i < N/2; i++ {
|
for i := 0; i < N/2; i++ {
|
||||||
j := i * 2
|
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 all items. should be half of N
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
count = 0
|
count = 0
|
||||||
tr.Scan(func(min, max []float64, value interface{}) bool {
|
tr.Scan(func(min, max [2]float64, value interface{}) bool {
|
||||||
count++
|
count++
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
@ -229,7 +230,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
})
|
})
|
||||||
for i := 0; i < N/2; i++ {
|
for i := 0; i < N/2; i++ {
|
||||||
j := ij[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++ {
|
for i := 0; i < N; i++ {
|
||||||
tr.Insert(nboxes[i].min[:], nboxes[i].max[:], nboxes[i])
|
tr.Insert(nboxes[i].min, nboxes[i].max, nboxes[i])
|
||||||
tr.Delete(boxes[i].min[:], boxes[i].max[:], boxes[i])
|
tr.Delete(boxes[i].min, boxes[i].max, boxes[i])
|
||||||
}
|
}
|
||||||
if tr.Count() != N {
|
if tr.Len() != N {
|
||||||
t.Fatalf("expected %d, got %d", N, tr.Count())
|
t.Fatalf("expected %d, got %d", N, tr.Len())
|
||||||
}
|
}
|
||||||
// area = tr.TotalOverlapArea()
|
// area = tr.TotalOverlapArea()
|
||||||
// fmt.Fprintf(wr, "overlap: %.0f, %.1f/item\n", area, area/float64(N))
|
// 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
|
// check every point for correctness
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
tboxes1 = nil
|
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))
|
tboxes1 = append(tboxes1, value.(tBox))
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
@ -281,7 +282,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
for i := 0; i < N/5; i++ {
|
for i := 0; i < N/5; i++ {
|
||||||
var count int
|
var count int
|
||||||
tr.Search(centerMin, centerMax,
|
tr.Search(centerMin, centerMax,
|
||||||
func(min, max []float64, value interface{}) bool {
|
func(min, max [2]float64, value interface{}) bool {
|
||||||
count++
|
count++
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
@ -289,26 +290,30 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var boxes3 []tBox
|
var boxes3 []tBox
|
||||||
tr.Nearby(centerMin, centerMax,
|
tr.Nearby(
|
||||||
func(min, max []float64, value interface{}) bool {
|
SimpleBoxAlgo(centerMin, centerMax),
|
||||||
|
func(min, max [2]float64, value interface{}, dist float64) bool {
|
||||||
boxes3 = append(boxes3, value.(tBox))
|
boxes3 = append(boxes3, value.(tBox))
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(boxes3) != len(nboxes) {
|
if len(boxes3) != len(nboxes) {
|
||||||
t.Fatalf("expected %d, got %d", len(nboxes), len(boxes3))
|
t.Fatalf("expected %d, got %d", len(nboxes), len(boxes3))
|
||||||
}
|
}
|
||||||
if len(boxes3) != tr.Count() {
|
if len(boxes3) != tr.Len() {
|
||||||
t.Fatalf("expected %d, got %d", tr.Count(), len(boxes3))
|
t.Fatalf("expected %d, got %d", tr.Len(), len(boxes3))
|
||||||
}
|
}
|
||||||
|
|
||||||
var ldist float64
|
var ldist float64
|
||||||
for i, box := range boxes3 {
|
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 {
|
if i > 0 && dist < ldist {
|
||||||
t.Fatalf("out of order")
|
t.Fatalf("out of order")
|
||||||
}
|
}
|
||||||
ldist = dist
|
ldist = dist
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRandomBoxes(t *testing.T) {
|
func TestRandomBoxes(t *testing.T) {
|
||||||
|
@ -319,61 +324,15 @@ func TestRandomPoints(t *testing.T) {
|
||||||
testBoxesVarious(t, randPoints(10000), "points")
|
testBoxesVarious(t, randPoints(10000), "points")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *box) boxstr() string {
|
func TestSVG(t *testing.T) {
|
||||||
var b []byte
|
var tr rbang.RTree
|
||||||
b = append(b, '[', '[')
|
index := Wrap(&tr)
|
||||||
for i := 0; i < len(r.min); i++ {
|
for _, city := range cities.Cities {
|
||||||
if i != 0 {
|
p := [2]float64{city.Longitude, city.Latitude}
|
||||||
b = append(b, ' ')
|
tr.Insert(p, p, &city)
|
||||||
}
|
}
|
||||||
b = strconv.AppendFloat(b, r.min[i], 'f', -1, 64)
|
svg := index.SVG()
|
||||||
}
|
if false {
|
||||||
b = append(b, ']', '[')
|
ioutil.WriteFile("cities.svg", []byte(svg), 0600)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,14 +1,14 @@
|
||||||
package geojson
|
package geojson
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/tidwall/boxtree/d2"
|
|
||||||
"github.com/tidwall/geojson/geometry"
|
"github.com/tidwall/geojson/geometry"
|
||||||
|
"github.com/tidwall/rbang"
|
||||||
)
|
)
|
||||||
|
|
||||||
type collection struct {
|
type collection struct {
|
||||||
children []Object
|
children []Object
|
||||||
extra *extra
|
extra *extra
|
||||||
tree *d2.BoxTree
|
tree *rbang.RTree
|
||||||
prect geometry.Rect
|
prect geometry.Rect
|
||||||
pempty bool
|
pempty bool
|
||||||
}
|
}
|
||||||
|
@ -39,9 +39,9 @@ func (g *collection) Base() []Object {
|
||||||
func (g *collection) Search(rect geometry.Rect, iter func(child Object) bool) {
|
func (g *collection) Search(rect geometry.Rect, iter func(child Object) bool) {
|
||||||
if g.tree != nil {
|
if g.tree != nil {
|
||||||
g.tree.Search(
|
g.tree.Search(
|
||||||
[]float64{rect.Min.X, rect.Min.Y},
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
[]float64{rect.Max.X, rect.Max.Y},
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
func(_, _ []float64, value interface{}) bool {
|
func(_, _ [2]float64, value interface{}) bool {
|
||||||
return iter(value.(Object))
|
return iter(value.(Object))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -303,15 +303,15 @@ func (g *collection) parseInitRectIndex(opts *ParseOptions) {
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
if count > 0 && opts.IndexChildren != 0 && count >= opts.IndexChildren {
|
if count > 0 && opts.IndexChildren != 0 && count >= opts.IndexChildren {
|
||||||
g.tree = new(d2.BoxTree)
|
g.tree = new(rbang.RTree)
|
||||||
for _, child := range g.children {
|
for _, child := range g.children {
|
||||||
if child.Empty() {
|
if child.Empty() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rect := child.Rect()
|
rect := child.Rect()
|
||||||
g.tree.Insert(
|
g.tree.Insert(
|
||||||
[]float64{rect.Min.X, rect.Min.Y},
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
[]float64{rect.Max.X, rect.Max.Y},
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
child,
|
child,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
36
vendor/github.com/tidwall/boxtree/README.md → vendor/github.com/tidwall/rbang/README.md
generated
vendored
36
vendor/github.com/tidwall/boxtree/README.md → vendor/github.com/tidwall/rbang/README.md
generated
vendored
|
@ -1,50 +1,44 @@
|
||||||
# `BoxTree`
|
# `R!tree`
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/tidwall/boxtree?status.svg)](https://godoc.org/github.com/tidwall/boxtree)
|
[![GoDoc](https://godoc.org/github.com/tidwall/rbang?status.svg)](https://godoc.org/github.com/tidwall/rbang)
|
||||||
|
|
||||||
**EXPERIMENTAL**
|
|
||||||
|
|
||||||
This package provides an in-memory R-Tree implementation for Go. It's designed
|
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">
|
<img src="cities.png" width="512" height="256" border="0" alt="Cities">
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Support for 2 and 3 dimensions
|
|
||||||
- Optimized for fast box inserts and replacements.
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Installing
|
### Installing
|
||||||
|
|
||||||
To start using BoxTree, install Go and run `go get`:
|
To start using R!Tree, install Go and run `go get`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ go get -u github.com/tidwall/boxtree
|
$ go get -u github.com/tidwall/rbang
|
||||||
```
|
```
|
||||||
|
|
||||||
### Basic operations
|
### Basic operations
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// create a 2D BoxTree
|
// create a 2D RTree
|
||||||
tr := boxtree.New(2)
|
var tr rbang.RTree
|
||||||
|
|
||||||
// insert a point
|
// 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
|
// insert a box
|
||||||
tr.Insert([]float64{10, 10}, []float64{20, 20}, "rect")
|
tr.Insert([2]float64{10, 10}, [2]float64{20, 20}, "rect")
|
||||||
|
|
||||||
// search
|
// search
|
||||||
tr.Search([]float64{-112.1, 33.4}, []float64{-112.0, 33.5},
|
tr.Search([2]float64{-112.1, 33.4}, [2]float64{-112.0, 33.5},
|
||||||
func(min, max []float64, value interface{}) bool {
|
func(min, max [2]float64, value interface{}) bool {
|
||||||
println(value.(string)) // prints "PHX"
|
println(value.(string)) // prints "PHX"
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// delete
|
// 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
|
## Algorithms
|
||||||
|
@ -90,5 +84,5 @@ I hope to provide more details in the future.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
`BoxTree` source code is available under the MIT License.
|
`rbang` source code is available under the MIT License.
|
||||||
|
|
Before Width: | Height: | Size: 336 KiB After Width: | Height: | Size: 336 KiB |
|
@ -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
|
||||||
|
}
|
|
@ -1,15 +1,75 @@
|
||||||
package d2
|
package rbang
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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 {
|
type tBox struct {
|
||||||
min [dims]float64
|
min [dims]float64
|
||||||
max [dims]float64
|
max [dims]float64
|
||||||
|
@ -20,7 +80,7 @@ var points []tBox
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
seed := time.Now().UnixNano()
|
seed := time.Now().UnixNano()
|
||||||
// seed = 1532132365683340889
|
seed = 1532132365683340889
|
||||||
println("seed:", seed)
|
println("seed:", seed)
|
||||||
rand.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 {
|
sort.Slice(boxes, func(i, j int) bool {
|
||||||
return testBoxDist(boxes[i].min[:], boxes[i].max[:], min, max) <
|
return testBoxDist(boxes[i].min, boxes[i].max, min, max) <
|
||||||
testBoxDist(boxes[j].min[:], boxes[j].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
|
var dist float64
|
||||||
for i := 0; i < len(amin); i++ {
|
for i := 0; i < len(amin); i++ {
|
||||||
var min, max float64
|
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) {
|
func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
N := len(boxes)
|
N := len(boxes)
|
||||||
|
|
||||||
var tr BoxTree
|
var tr RTree
|
||||||
|
|
||||||
// N := 10000
|
// N := 10000
|
||||||
// boxes := randPoints(N)
|
// boxes := randPoints(N)
|
||||||
|
@ -119,10 +179,10 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
// insert
|
// insert
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
for i := 0; i < N; i++ {
|
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 {
|
if tr.Len() != N {
|
||||||
t.Fatalf("expected %d, got %d", N, tr.Count())
|
t.Fatalf("expected %d, got %d", N, tr.Len())
|
||||||
}
|
}
|
||||||
// area := tr.TotalOverlapArea()
|
// area := tr.TotalOverlapArea()
|
||||||
// fmt.Printf("overlap: %.0f, %.1f/item\n", area, area/float64(N))
|
// 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
|
// scan all items and count one-by-one
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
var count int
|
var count int
|
||||||
tr.Scan(func(min, max []float64, value interface{}) bool {
|
tr.Scan(func(min, max [2]float64, value interface{}) bool {
|
||||||
count++
|
count++
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
@ -145,7 +205,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
// check every point for correctness
|
// check every point for correctness
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
var tboxes1 []tBox
|
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))
|
tboxes1 = append(tboxes1, value.(tBox))
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
@ -164,8 +224,8 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
for i := 0; i < N; i++ {
|
for i := 0; i < N; i++ {
|
||||||
var found bool
|
var found bool
|
||||||
tr.Search(boxes[i].min[:], boxes[i].max[:],
|
tr.Search(boxes[i].min, boxes[i].max,
|
||||||
func(min, max []float64, value interface{}) bool {
|
func(min, max [2]float64, value interface{}) bool {
|
||||||
if value == boxes[i] {
|
if value == boxes[i] {
|
||||||
found = true
|
found = true
|
||||||
return false
|
return false
|
||||||
|
@ -177,11 +237,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
centerMin, centerMax := []float64{-18, -9}, []float64{18, 9}
|
centerMin, centerMax := [2]float64{-18, -9}, [2]float64{18, 9}
|
||||||
for j := 2; j < dims; j++ {
|
|
||||||
centerMin = append(centerMin, -10)
|
|
||||||
centerMax = append(centerMax, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
// search for 10% of the items
|
// 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++ {
|
for i := 0; i < N/5; i++ {
|
||||||
var count int
|
var count int
|
||||||
tr.Search(centerMin, centerMax,
|
tr.Search(centerMin, centerMax,
|
||||||
func(min, max []float64, value interface{}) bool {
|
func(min, max [2]float64, value interface{}) bool {
|
||||||
count++
|
count++
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
@ -201,14 +257,14 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
for i := 0; i < N/2; i++ {
|
for i := 0; i < N/2; i++ {
|
||||||
j := i * 2
|
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 all items. should be half of N
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
count = 0
|
count = 0
|
||||||
tr.Scan(func(min, max []float64, value interface{}) bool {
|
tr.Scan(func(min, max [2]float64, value interface{}) bool {
|
||||||
count++
|
count++
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
@ -229,7 +285,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
})
|
})
|
||||||
for i := 0; i < N/2; i++ {
|
for i := 0; i < N/2; i++ {
|
||||||
j := ij[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++ {
|
for i := 0; i < N; i++ {
|
||||||
tr.Insert(nboxes[i].min[:], nboxes[i].max[:], nboxes[i])
|
tr.Insert(nboxes[i].min, nboxes[i].max, nboxes[i])
|
||||||
tr.Delete(boxes[i].min[:], boxes[i].max[:], boxes[i])
|
tr.Delete(boxes[i].min, boxes[i].max, boxes[i])
|
||||||
}
|
}
|
||||||
if tr.Count() != N {
|
if tr.Len() != N {
|
||||||
t.Fatalf("expected %d, got %d", N, tr.Count())
|
t.Fatalf("expected %d, got %d", N, tr.Len())
|
||||||
}
|
}
|
||||||
// area = tr.TotalOverlapArea()
|
// area = tr.TotalOverlapArea()
|
||||||
// fmt.Fprintf(wr, "overlap: %.0f, %.1f/item\n", area, area/float64(N))
|
// 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
|
// check every point for correctness
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
tboxes1 = nil
|
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))
|
tboxes1 = append(tboxes1, value.(tBox))
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
@ -281,7 +337,7 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
for i := 0; i < N/5; i++ {
|
for i := 0; i < N/5; i++ {
|
||||||
var count int
|
var count int
|
||||||
tr.Search(centerMin, centerMax,
|
tr.Search(centerMin, centerMax,
|
||||||
func(min, max []float64, value interface{}) bool {
|
func(min, max [2]float64, value interface{}) bool {
|
||||||
count++
|
count++
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
@ -289,21 +345,24 @@ func testBoxesVarious(t *testing.T, boxes []tBox, label string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var boxes3 []tBox
|
var boxes3 []tBox
|
||||||
tr.Nearby(centerMin, centerMax,
|
geoindex.Wrap(&tr).Nearby(
|
||||||
func(min, max []float64, value interface{}) bool {
|
geoindex.SimpleBoxAlgo(centerMin, centerMax),
|
||||||
|
func(min, max [2]float64, value interface{}, dist float64) bool {
|
||||||
boxes3 = append(boxes3, value.(tBox))
|
boxes3 = append(boxes3, value.(tBox))
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(boxes3) != len(nboxes) {
|
if len(boxes3) != len(nboxes) {
|
||||||
t.Fatalf("expected %d, got %d", len(nboxes), len(boxes3))
|
t.Fatalf("expected %d, got %d", len(nboxes), len(boxes3))
|
||||||
}
|
}
|
||||||
if len(boxes3) != tr.Count() {
|
if len(boxes3) != tr.Len() {
|
||||||
t.Fatalf("expected %d, got %d", tr.Count(), len(boxes3))
|
t.Fatalf("expected %d, got %d", tr.Len(), len(boxes3))
|
||||||
}
|
}
|
||||||
|
|
||||||
var ldist float64
|
var ldist float64
|
||||||
for i, box := range boxes3 {
|
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 {
|
if i > 0 && dist < ldist {
|
||||||
t.Fatalf("out of order")
|
t.Fatalf("out of order")
|
||||||
}
|
}
|
||||||
|
@ -319,7 +378,7 @@ func TestRandomPoints(t *testing.T) {
|
||||||
testBoxesVarious(t, randPoints(10000), "points")
|
testBoxesVarious(t, randPoints(10000), "points")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *box) boxstr() string {
|
func (r *rect) boxstr() string {
|
||||||
var b []byte
|
var b []byte
|
||||||
b = append(b, '[', '[')
|
b = append(b, '[', '[')
|
||||||
for i := 0; i < len(r.min); i++ {
|
for i := 0; i < len(r.min); i++ {
|
||||||
|
@ -339,7 +398,7 @@ func (r *box) boxstr() string {
|
||||||
return string(b)
|
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())
|
fmt.Printf("%s%s", strings.Repeat(" ", indent), r.boxstr())
|
||||||
if height == 0 {
|
if height == 0 {
|
||||||
fmt.Printf("\t'%v'\n", r.data)
|
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 {
|
if tr.root.data == nil {
|
||||||
println("EMPTY TREE")
|
println("EMPTY TREE")
|
||||||
return
|
return
|
||||||
|
@ -362,18 +421,18 @@ func (tr BoxTree) print() {
|
||||||
|
|
||||||
func TestZeroPoints(t *testing.T) {
|
func TestZeroPoints(t *testing.T) {
|
||||||
N := 10000
|
N := 10000
|
||||||
var tr BoxTree
|
var tr RTree
|
||||||
pt := make([]float64, dims)
|
var pt [2]float64
|
||||||
for i := 0; i < N; i++ {
|
for i := 0; i < N; i++ {
|
||||||
tr.Insert(pt, nil, i)
|
tr.Insert(pt, pt, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkRandomInsert(b *testing.B) {
|
func BenchmarkRandomInsert(b *testing.B) {
|
||||||
var tr BoxTree
|
var tr RTree
|
||||||
boxes := randBoxes(b.N)
|
boxes := randBoxes(b.N)
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue