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