mirror of https://github.com/tidwall/tile38.git
Updated rtree library
This commit is contained in:
parent
3ed048242e
commit
b37e7395a3
2
go.mod
2
go.mod
|
@ -21,7 +21,7 @@ require (
|
|||
github.com/tidwall/gjson v1.6.8
|
||||
github.com/tidwall/match v1.0.3
|
||||
github.com/tidwall/pretty v1.0.2
|
||||
github.com/tidwall/rbang v1.2.2
|
||||
github.com/tidwall/rbang v1.2.3
|
||||
github.com/tidwall/redbench v0.1.0
|
||||
github.com/tidwall/redcon v1.4.0
|
||||
github.com/tidwall/resp v0.1.0
|
||||
|
|
2
go.sum
2
go.sum
|
@ -141,6 +141,8 @@ github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
|
|||
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/rbang v1.2.2 h1:j5JZDSsybpGzCabqFpabaQNU5MCmIrlThXVUF7LD99I=
|
||||
github.com/tidwall/rbang v1.2.2/go.mod h1:aMGOM1Wj50tooEO/0aO9j+7gyHUs3bUW0t4Q+xiuOjg=
|
||||
github.com/tidwall/rbang v1.2.3 h1:Eg48GtzQEqqwU6kxAna0H0G/m41bm/MQl/EIqU7jfK8=
|
||||
github.com/tidwall/rbang v1.2.3/go.mod h1:aMGOM1Wj50tooEO/0aO9j+7gyHUs3bUW0t4Q+xiuOjg=
|
||||
github.com/tidwall/redbench v0.1.0 h1:UZYUMhwMMObQRq5xU4SA3lmlJRztXzqtushDii+AmPo=
|
||||
github.com/tidwall/redbench v0.1.0/go.mod h1:zxcRGCq/JcqV48YjK9WxBNJL7JSpMzbLXaHvMcnanKQ=
|
||||
github.com/tidwall/redcon v1.4.0 h1:y2PmDD55STRdy4S98qP/Dn+gZG+cPVvIDi9BJV2aOwA=
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# `rbang`
|
||||
# rbang
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/tidwall/rbang?status.svg)](https://godoc.org/github.com/tidwall/rbang)
|
||||
|
||||
|
@ -27,7 +27,7 @@ var tr rbang.RTree
|
|||
// insert a point
|
||||
tr.Insert([2]float64{-112.0078, 33.4373}, [2]float64{-112.0078, 33.4373}, "PHX")
|
||||
|
||||
// insert a box
|
||||
// insert a rect
|
||||
tr.Insert([2]float64{10, 10}, [2]float64{20, 20}, "rect")
|
||||
|
||||
// search
|
||||
|
@ -48,11 +48,11 @@ This implementation is a variant of the original paper:
|
|||
|
||||
### Inserting
|
||||
|
||||
Same as the original algorithm. From the root to the leaf, the boxes which will incur the least enlargment are chosen. Ties go to boxes with the smallest area.
|
||||
Same as the original algorithm. From the root to the leaf, the rects which will incur the least enlargment are chosen. Ties go to rects with the smallest area.
|
||||
|
||||
### Deleting
|
||||
|
||||
Same as the original algorithm. A target box is deleted directly. When the number of children in a box falls below it's minumum entries, it is removed from the tree and it's items are re-inserted.
|
||||
Same as the original algorithm. A target rect is deleted directly. When the number of children in a rect falls below it's minumum entries, it is removed from the tree and it's items are re-inserted.
|
||||
|
||||
### Splitting
|
||||
|
||||
|
@ -60,29 +60,19 @@ This is a custom algorithm.
|
|||
It attempts to minimize intensive operations such as pre-sorting the children and comparing overlaps & area sizes.
|
||||
The desire is to do simple single axis distance calculations each child only once, with a target 50/50 chance that the child might be moved in-memory.
|
||||
|
||||
When a box has reached it's max number of entries it's largest axis is calculated and the box is split into two smaller boxes, named `left` and `right`.
|
||||
Each child boxes is then evaluated to determine which smaller box it should be placed into.
|
||||
When a rect has reached it's max number of entries it's largest axis is calculated and the rect is split into two smaller rects, named `left` and `right`.
|
||||
Each child rects is then evaluated to determine which smaller rect it should be placed into.
|
||||
Two values, `min-dist` and `max-dist`, are calcuated for each child.
|
||||
|
||||
- `min-dist` is the distance from the parent's minumum value of it's largest axis to the child's minumum value of the parent largest axis.
|
||||
- `max-dist` is the distance from the parent's maximum value of it's largest axis to the child's maximum value of the parent largest axis.
|
||||
|
||||
When the `min-dist` is less than `max-dist` then the child is placed into the `left` box.
|
||||
When the `max-dist` is less than `min-dist` then the child is placed into the `right` box.
|
||||
When the `min-dist` is less than `max-dist` then the child is placed into the `left` rect.
|
||||
When the `max-dist` is less than `min-dist` then the child is placed into the `right` rect.
|
||||
When the `min-dist` is equal to `max-dist` then the child is placed into an `equal` bucket until all of the children are evaluated.
|
||||
Each `equal` box is then one-by-one placed in either `left` or `right`, whichever has less children.
|
||||
|
||||
|
||||
## Performance
|
||||
|
||||
In my testing:
|
||||
|
||||
- Insert show similar performance as the quadratic R-tree and ~1.2x - 1.5x faster than R*tree.
|
||||
- Search and Delete is ~1.5x - 2x faster than quadratic and about the same as R*tree.
|
||||
|
||||
I hope to provide more details in the future.
|
||||
Each `equal` rect is then one-by-one placed in either `left` or `right`, whichever has less children.
|
||||
|
||||
## License
|
||||
|
||||
`rbang` source code is available under the MIT License.
|
||||
rbang source code is available under the MIT License.
|
||||
|
||||
|
|
|
@ -140,18 +140,49 @@ func (tr *RTree) insert(item *rect) {
|
|||
tr.count++
|
||||
}
|
||||
|
||||
func (r *rect) chooseLeastEnlargement(b *rect) int {
|
||||
j, jenlargement, jarea := -1, 0.0, 0.0
|
||||
const inlineEnlargedArea = true
|
||||
|
||||
func (r *rect) chooseLeastEnlargement(b *rect) (index int) {
|
||||
n := r.data.(*node)
|
||||
j, jenlargement, jarea := -1, 0.0, 0.0
|
||||
for i := 0; i < n.count; i++ {
|
||||
area := n.rects[i].area()
|
||||
enlargement := n.rects[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
|
||||
var earea float64
|
||||
if inlineEnlargedArea {
|
||||
earea = 1.0
|
||||
if b.max[0] > n.rects[i].max[0] {
|
||||
if b.min[0] < n.rects[i].min[0] {
|
||||
earea *= b.max[0] - b.min[0]
|
||||
} else {
|
||||
earea *= b.max[0] - n.rects[i].min[0]
|
||||
}
|
||||
} else {
|
||||
if b.min[0] < n.rects[i].min[0] {
|
||||
earea *= n.rects[i].max[0] - b.min[0]
|
||||
} else {
|
||||
earea *= n.rects[i].max[0] - n.rects[i].min[0]
|
||||
}
|
||||
}
|
||||
if b.max[1] > n.rects[i].max[1] {
|
||||
if b.min[1] < n.rects[i].min[1] {
|
||||
earea *= b.max[1] - b.min[1]
|
||||
} else {
|
||||
earea *= b.max[1] - n.rects[i].min[1]
|
||||
}
|
||||
} else {
|
||||
if b.min[1] < n.rects[i].min[1] {
|
||||
earea *= n.rects[i].max[1] - b.min[1]
|
||||
} else {
|
||||
earea *= n.rects[i].max[1] - n.rects[i].min[1]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
earea = n.rects[i].enlargedArea(b)
|
||||
}
|
||||
area := n.rects[i].area()
|
||||
enlargement := earea - area
|
||||
if j == -1 || enlargement < jenlargement ||
|
||||
(enlargement == jenlargement && area < jarea) {
|
||||
j, jenlargement, jarea = i, enlargement, area
|
||||
}
|
||||
}
|
||||
return j
|
||||
|
@ -233,8 +264,25 @@ func (r *rect) insert(item *rect, height int) (grown bool) {
|
|||
grown = !r.contains(item)
|
||||
return grown
|
||||
}
|
||||
|
||||
// choose subtree
|
||||
index := r.chooseLeastEnlargement(item)
|
||||
index := -1
|
||||
narea := 0.0
|
||||
// first take a quick look for any nodes that contain the rect
|
||||
for i := 0; i < n.count; i++ {
|
||||
if n.rects[i].contains(item) {
|
||||
area := n.rects[i].area()
|
||||
if index == -1 || area < narea {
|
||||
narea = area
|
||||
index = i
|
||||
}
|
||||
}
|
||||
}
|
||||
// found nothing, now go the slow path
|
||||
if index == -1 {
|
||||
index = r.chooseLeastEnlargement(item)
|
||||
}
|
||||
// insert the item into the child node
|
||||
child := &n.rects[index]
|
||||
grown = child.insert(item, height-1)
|
||||
if grown {
|
||||
|
@ -374,8 +422,7 @@ func (tr *RTree) Delete(min, max [2]float64, data interface{}) {
|
|||
return
|
||||
}
|
||||
var removed, recalced bool
|
||||
removed, recalced, tr.reinsert =
|
||||
tr.root.delete(&item, tr.height, tr.reinsert[:0])
|
||||
removed, recalced = tr.root.delete(tr, &item, tr.height)
|
||||
if !removed {
|
||||
return
|
||||
}
|
||||
|
@ -393,60 +440,62 @@ func (tr *RTree) Delete(min, max [2]float64, data interface{}) {
|
|||
if recalced {
|
||||
tr.root.recalc()
|
||||
}
|
||||
if len(tr.reinsert) > 0 {
|
||||
for i := range tr.reinsert {
|
||||
tr.insert(&tr.reinsert[i])
|
||||
tr.reinsert[i].data = nil
|
||||
}
|
||||
tr.reinsert = tr.reinsert[:0]
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rect) delete(item *rect, height int, reinsert []rect) (
|
||||
removed, recalced bool, reinsertOut []rect,
|
||||
) {
|
||||
func (r *rect) delete(tr *RTree, item *rect, height int,
|
||||
) (removed, recalced bool) {
|
||||
n := r.data.(*node)
|
||||
rects := n.rects[0:n.count]
|
||||
if height == 0 {
|
||||
for i := 0; i < n.count; i++ {
|
||||
if n.rects[i].data == item.data {
|
||||
for i := 0; i < len(rects); i++ {
|
||||
if rects[i].data == item.data {
|
||||
// found the target item to delete
|
||||
recalced = r.onEdge(&n.rects[i])
|
||||
n.rects[i] = n.rects[n.count-1]
|
||||
n.rects[n.count-1].data = nil
|
||||
recalced = r.onEdge(&rects[i])
|
||||
rects[i] = rects[len(rects)-1]
|
||||
rects[len(rects)-1].data = nil
|
||||
n.count--
|
||||
if recalced {
|
||||
r.recalc()
|
||||
}
|
||||
return true, recalced, reinsert
|
||||
return true, recalced
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < n.count; i++ {
|
||||
if !n.rects[i].contains(item) {
|
||||
for i := 0; i < len(rects); i++ {
|
||||
if !rects[i].contains(item) {
|
||||
continue
|
||||
}
|
||||
removed, recalced, reinsert =
|
||||
n.rects[i].delete(item, height-1, reinsert)
|
||||
removed, recalced = rects[i].delete(tr, item, height-1)
|
||||
if !removed {
|
||||
continue
|
||||
}
|
||||
if n.rects[i].data.(*node).count < minEntries {
|
||||
if rects[i].data.(*node).count < minEntries {
|
||||
// underflow
|
||||
if !recalced {
|
||||
recalced = r.onEdge(&n.rects[i])
|
||||
recalced = r.onEdge(&rects[i])
|
||||
}
|
||||
reinsert = n.rects[i].flatten(reinsert, height-1)
|
||||
n.rects[i] = n.rects[n.count-1]
|
||||
n.rects[n.count-1].data = nil
|
||||
tr.reinsert = rects[i].flatten(tr.reinsert, height-1)
|
||||
rects[i] = rects[len(rects)-1]
|
||||
rects[len(rects)-1].data = nil
|
||||
n.count--
|
||||
}
|
||||
if recalced {
|
||||
r.recalc()
|
||||
}
|
||||
return removed, recalced, reinsert
|
||||
return removed, recalced
|
||||
}
|
||||
}
|
||||
return false, false, reinsert
|
||||
return false, false
|
||||
}
|
||||
|
||||
// flatten flattens all leaf rects into a single list
|
||||
// flatten all leaf rects into a single list
|
||||
func (r *rect) flatten(all []rect, height int) []rect {
|
||||
n := r.data.(*node)
|
||||
if height == 0 {
|
||||
|
@ -525,8 +574,9 @@ func (tr *RTree) Children(
|
|||
return children
|
||||
}
|
||||
|
||||
// Replace an item in the structure. This is effectively just a Delete
|
||||
// followed by an Insert.
|
||||
// Replace an item.
|
||||
// This is effectively just a Delete followed by an Insert. Which means the
|
||||
// new item will always be inserted, whether or not the old item was deleted.
|
||||
func (tr *RTree) Replace(
|
||||
oldMin, oldMax [2]float64, oldData interface{},
|
||||
newMin, newMax [2]float64, newData interface{},
|
||||
|
|
|
@ -146,7 +146,7 @@ github.com/tidwall/match
|
|||
# github.com/tidwall/pretty v1.0.2
|
||||
## explicit
|
||||
github.com/tidwall/pretty
|
||||
# github.com/tidwall/rbang v1.2.2
|
||||
# github.com/tidwall/rbang v1.2.3
|
||||
## explicit
|
||||
github.com/tidwall/rbang
|
||||
# github.com/tidwall/redbench v0.1.0
|
||||
|
|
Loading…
Reference in New Issue