mirror of https://github.com/tidwall/rtred.git
279 lines
5.3 KiB
Go
279 lines
5.3 KiB
Go
package rtreed
|
|
|
|
import (
|
|
"math"
|
|
"sync"
|
|
|
|
"github.com/tidwall/rtreed/base"
|
|
)
|
|
|
|
type Iterator func(item Item) bool
|
|
type Item interface {
|
|
Rect(ctx interface{}) (min []float64, max []float64)
|
|
}
|
|
|
|
type RTree struct {
|
|
dims int
|
|
maxEntries int
|
|
ctx interface{}
|
|
trs []*base.RTree
|
|
used int
|
|
}
|
|
|
|
func New(ctx interface{}) *RTree {
|
|
tr := &RTree{
|
|
ctx: ctx,
|
|
dims: 20,
|
|
maxEntries: 13,
|
|
}
|
|
tr.trs = make([]*base.RTree, 20)
|
|
return tr
|
|
}
|
|
|
|
func (tr *RTree) Insert(item Item) {
|
|
if item == nil {
|
|
panic("nil item")
|
|
}
|
|
min, max := item.Rect(tr.ctx)
|
|
if len(min) != len(max) {
|
|
return // just return
|
|
panic("invalid item rectangle")
|
|
}
|
|
if len(min) < 1 || len(min) > len(tr.trs) {
|
|
return // just return
|
|
panic("invalid dimension")
|
|
}
|
|
btr := tr.trs[len(min)-1]
|
|
if btr == nil {
|
|
btr = base.New(len(min), tr.maxEntries)
|
|
tr.trs[len(min)-1] = btr
|
|
tr.used++
|
|
}
|
|
amin := make([]float64, len(min))
|
|
amax := make([]float64, len(max))
|
|
for i := 0; i < len(min); i++ {
|
|
amin[i], amax[i] = min[i], max[i]
|
|
}
|
|
btr.Insert(amin, amax, item)
|
|
}
|
|
|
|
func (tr *RTree) Remove(item Item) {
|
|
if item == nil {
|
|
panic("nil item")
|
|
}
|
|
min, max := item.Rect(tr.ctx)
|
|
if len(min) != len(max) {
|
|
return // just return
|
|
panic("invalid item rectangle")
|
|
}
|
|
if len(min) < 1 || len(min) > len(tr.trs) {
|
|
return // just return
|
|
panic("invalid dimension")
|
|
}
|
|
btr := tr.trs[len(min)-1]
|
|
if btr == nil {
|
|
return
|
|
}
|
|
amin := make([]float64, len(min))
|
|
amax := make([]float64, len(max))
|
|
for i := 0; i < len(min); i++ {
|
|
amin[i], amax[i] = min[i], max[i]
|
|
}
|
|
btr.Remove(amin, amax, item)
|
|
if btr.IsEmpty() {
|
|
tr.trs[len(min)-1] = nil
|
|
tr.used--
|
|
}
|
|
}
|
|
func (tr *RTree) Reset() {
|
|
for i := 0; i < len(tr.trs); i++ {
|
|
tr.trs[i] = nil
|
|
}
|
|
tr.used = 0
|
|
}
|
|
func (tr *RTree) Count() int {
|
|
var count int
|
|
for _, btr := range tr.trs {
|
|
if btr != nil {
|
|
count += btr.Count()
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
func (tr *RTree) Search(bounds Item, iter Iterator) {
|
|
if bounds == nil {
|
|
panic("nil bounds being used for search")
|
|
}
|
|
min, max := bounds.Rect(tr.ctx)
|
|
if len(min) != len(max) {
|
|
return // just return
|
|
panic("invalid item rectangle")
|
|
}
|
|
if len(min) < 1 || len(min) > len(tr.trs) {
|
|
return // just return
|
|
panic("invalid dimension")
|
|
}
|
|
used := tr.used
|
|
for i, btr := range tr.trs {
|
|
if used == 0 {
|
|
break
|
|
}
|
|
if btr != nil {
|
|
if !search(btr, min, max, i+1, iter) {
|
|
return
|
|
}
|
|
used--
|
|
}
|
|
}
|
|
}
|
|
func search(btr *base.RTree, min, max []float64, dims int, iter Iterator) bool {
|
|
amin := make([]float64, dims)
|
|
amax := make([]float64, dims)
|
|
for i := 0; i < dims; i++ {
|
|
if i < len(min) {
|
|
amin[i] = min[i]
|
|
amax[i] = max[i]
|
|
} else {
|
|
amin[i] = math.Inf(-1)
|
|
amax[i] = math.Inf(+1)
|
|
}
|
|
}
|
|
var ended bool
|
|
btr.Search(amin, amax, func(item interface{}) bool {
|
|
if !iter(item.(Item)) {
|
|
ended = true
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
return !ended
|
|
}
|
|
|
|
func (tr *RTree) KNN(bounds Item, center bool, iter func(item Item, dist float64) bool) {
|
|
if bounds == nil {
|
|
panic("nil bounds being used for search")
|
|
}
|
|
min, max := bounds.Rect(tr.ctx)
|
|
if len(min) != len(max) {
|
|
return // just return
|
|
panic("invalid item rectangle")
|
|
}
|
|
if len(min) < 1 || len(min) > len(tr.trs) {
|
|
return // just return
|
|
panic("invalid dimension")
|
|
}
|
|
|
|
if tr.used == 0 {
|
|
return
|
|
}
|
|
if tr.used == 1 {
|
|
for i, btr := range tr.trs {
|
|
if btr != nil {
|
|
knn(btr, min, max, center, i+1, func(item interface{}, dist float64) bool {
|
|
return iter(item.(Item), dist)
|
|
})
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
type queueT struct {
|
|
done bool
|
|
step int
|
|
item Item
|
|
dist float64
|
|
}
|
|
|
|
var mu sync.Mutex
|
|
var ended bool
|
|
queues := make(map[int][]queueT)
|
|
cond := sync.NewCond(&mu)
|
|
for i, btr := range tr.trs {
|
|
if btr != nil {
|
|
dims := i + 1
|
|
mu.Lock()
|
|
queues[dims] = []queueT{}
|
|
cond.Signal()
|
|
mu.Unlock()
|
|
go func(dims int, btr *base.RTree) {
|
|
knn(btr, min, max, center, dims, func(item interface{}, dist float64) bool {
|
|
mu.Lock()
|
|
if ended {
|
|
mu.Unlock()
|
|
return false
|
|
}
|
|
queues[dims] = append(queues[dims], queueT{item: item.(Item), dist: dist})
|
|
cond.Signal()
|
|
mu.Unlock()
|
|
return true
|
|
})
|
|
mu.Lock()
|
|
queues[dims] = append(queues[dims], queueT{done: true})
|
|
cond.Signal()
|
|
mu.Unlock()
|
|
}(dims, btr)
|
|
}
|
|
}
|
|
mu.Lock()
|
|
for {
|
|
ready := true
|
|
for i := range queues {
|
|
if len(queues[i]) == 0 {
|
|
ready = false
|
|
break
|
|
}
|
|
if queues[i][0].done {
|
|
delete(queues, i)
|
|
}
|
|
}
|
|
if len(queues) == 0 {
|
|
break
|
|
}
|
|
if ready {
|
|
var j int
|
|
var minDist float64
|
|
var minItem Item
|
|
var minQueue int
|
|
for i := range queues {
|
|
if j == 0 || queues[i][0].dist < minDist {
|
|
minDist = queues[i][0].dist
|
|
minItem = queues[i][0].item
|
|
minQueue = i
|
|
}
|
|
}
|
|
queues[minQueue] = queues[minQueue][1:]
|
|
if !iter(minItem, minDist) {
|
|
ended = true
|
|
break
|
|
}
|
|
continue
|
|
}
|
|
cond.Wait()
|
|
}
|
|
mu.Unlock()
|
|
}
|
|
func knn(btr *base.RTree, min, max []float64, center bool, dims int, iter func(item interface{}, dist float64) bool) bool {
|
|
amin := make([]float64, dims)
|
|
amax := make([]float64, dims)
|
|
for i := 0; i < dims; i++ {
|
|
if i < len(min) {
|
|
amin[i] = min[i]
|
|
amax[i] = max[i]
|
|
} else {
|
|
amin[i] = math.Inf(-1)
|
|
amax[i] = math.Inf(+1)
|
|
}
|
|
}
|
|
var ended bool
|
|
btr.KNN(amin, amax, center, func(item interface{}, dist float64) bool {
|
|
if !iter(item.(Item), dist) {
|
|
ended = true
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
return !ended
|
|
}
|