Updated dependencies

This commit is contained in:
tidwall 2021-02-07 17:54:56 -07:00
parent d209edbd59
commit 60678020fa
37 changed files with 1219 additions and 1152 deletions

9
go.mod
View File

@ -15,19 +15,18 @@ require (
github.com/peterh/liner v1.2.1
github.com/streadway/amqp v1.0.0
github.com/tidwall/btree v0.3.0
github.com/tidwall/buntdb v1.1.8
github.com/tidwall/cities v0.1.0 // indirect
github.com/tidwall/geoindex v1.4.0
github.com/tidwall/geojson v1.2.3
github.com/tidwall/buntdb v1.2.0
github.com/tidwall/geoindex v1.4.1
github.com/tidwall/geojson v1.2.4
github.com/tidwall/gjson v1.6.8
github.com/tidwall/lotsa v1.0.2 // indirect
github.com/tidwall/match v1.0.3
github.com/tidwall/pretty v1.0.2
github.com/tidwall/rbang v1.2.4
github.com/tidwall/redbench v0.1.0
github.com/tidwall/redcon v1.4.0
github.com/tidwall/resp v0.1.0
github.com/tidwall/rhh v1.1.1
github.com/tidwall/rtree v1.2.6
github.com/tidwall/sjson v1.1.5
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad

20
go.sum
View File

@ -188,14 +188,14 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/tidwall/btree v0.2.2/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
github.com/tidwall/btree v0.3.0 h1:LcwmLI5kv+AaH/xnBgOuKfbu5eLBWVdWTpD2o+qSRdU=
github.com/tidwall/btree v0.3.0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
github.com/tidwall/buntdb v1.1.8 h1:3rHHTlR4uFACKLbC+ws9jH0Ytk1Pd+n/cMEERtbMVKk=
github.com/tidwall/buntdb v1.1.8/go.mod h1:a/Xp8Hsllzxex0WTNdANz2+hOwGaYDHW4xBK9dMp30w=
github.com/tidwall/buntdb v1.2.0 h1:8KOzf5Gg97DoCMSOgcwZjnM0FfROtq0fcZkPW54oGKU=
github.com/tidwall/buntdb v1.2.0/go.mod h1:XLza/dhlwzO6dc5o/KWor4kfZSt3BP8QV+77ZMKfI58=
github.com/tidwall/cities v0.1.0 h1:CVNkmMf7NEC9Bvokf5GoSsArHCKRMTgLuubRTHnH0mE=
github.com/tidwall/cities v0.1.0/go.mod h1:lV/HDp2gCcRcHJWqgt6Di54GiDrTZwh1aG2ZUPNbqa4=
github.com/tidwall/geoindex v1.4.0 h1:6O8bwdRrNDwLzPSb6AhuyBW27PSa24Pa4I70fnoiBGI=
github.com/tidwall/geoindex v1.4.0/go.mod h1:3gTa91BW+eiVIipuR6aU1Y9Sa0q75b1teE/NP2vfsTc=
github.com/tidwall/geojson v1.2.3 h1:z//3h73oC+EkQ1TQr0oLbk7fKtGqAYkhSILtFZi1j8w=
github.com/tidwall/geojson v1.2.3/go.mod h1:tBjfxeALRFLc25LLpjtWzy2nIrNmW1ze1EAhLtd8+QQ=
github.com/tidwall/geoindex v1.4.1 h1:dlhM+2isLqz8ndHn7vOCevD3IPH3XeO/BaFN4rLpimo=
github.com/tidwall/geoindex v1.4.1/go.mod h1:NQJQszWCH4+KlD0wY+mgQ2hK/GdSH+9+ZRknDY8bOHc=
github.com/tidwall/geojson v1.2.4 h1:INKsEJULXKiKSuFQZQ7Vy3v9zchg0VPtcQl2+KeTlvc=
github.com/tidwall/geojson v1.2.4/go.mod h1:ZaA93utbJL8CLGaJ5L/M8gV/YC81lvW3ydzo5fI7yp0=
github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w=
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
@ -209,8 +209,6 @@ github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
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.4 h1:A0kWtX9lKr9YzWkKzmVYZFqLyo0L6tlkpO+kswDWp7E=
github.com/tidwall/rbang v1.2.4/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=
@ -219,8 +217,10 @@ github.com/tidwall/resp v0.1.0 h1:zZ6Hq+2cY4QqhZ4LqrV05T5yLOSPspj+l+DgAoJ25Ak=
github.com/tidwall/resp v0.1.0/go.mod h1:18xEj855iMY2bK6tNF2A4x+nZy5gWO1iO7OOl3jETKw=
github.com/tidwall/rhh v1.1.1 h1:8zDpMKcK1pA1zU+Jyuo1UdzTFvME8pH3Sx/MdYgM5sE=
github.com/tidwall/rhh v1.1.1/go.mod h1:DmqiIRtSnlVEi5CSKqNaX6m3YTa3YNSYrGB4FlfdLUU=
github.com/tidwall/rtree v0.1.0 h1:w/RoiV3WvrKF/EzLaK6CT0MXAV6KveGy3EOyeJACoFA=
github.com/tidwall/rtree v0.1.0/go.mod h1:xsujlpupsrYVnWaok1pqGtxhj9TKHSbMbY8eKPatQ4U=
github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
github.com/tidwall/rtree v1.2.6 h1:Q4FhJZId5k22IYsZ55Bz8nNo4Z9LyyOzWb+WIwc1vdM=
github.com/tidwall/rtree v1.2.6/go.mod h1:fn56Cu3AyoR5U5LLQywyuTOCDF8Lq6GQCjLRSxnPAJI=
github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE=
github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=

View File

@ -9,7 +9,7 @@ import (
"github.com/tidwall/geojson"
"github.com/tidwall/geojson/geo"
"github.com/tidwall/geojson/geometry"
"github.com/tidwall/rbang"
"github.com/tidwall/rtree"
"github.com/tidwall/tile38/internal/deadline"
)
@ -64,7 +64,7 @@ var counter uint64
func New() *Collection {
col := &Collection{
items: btree.New(byID),
index: geoindex.Wrap(&rbang.RTree{}),
index: geoindex.Wrap(&rtree.RTree{}),
values: btree.New(byValue),
fieldMap: make(map[string]int),
fieldArr: make([]string, 0),

View File

@ -10,9 +10,9 @@ import (
"github.com/tidwall/btree"
"github.com/tidwall/geojson"
"github.com/tidwall/geojson/geometry"
"github.com/tidwall/rbang"
"github.com/tidwall/resp"
"github.com/tidwall/rhh"
"github.com/tidwall/rtree"
"github.com/tidwall/tile38/internal/collection"
"github.com/tidwall/tile38/internal/glob"
)
@ -513,8 +513,8 @@ func (server *Server) cmdFlushDB(msg *Message) (res resp.Value, d commandDetails
server.expires = rhh.New(0)
server.hooks = make(map[string]*Hook)
server.hooksOut = make(map[string]*Hook)
server.hookTree = rbang.RTree{}
server.hookCross = rbang.RTree{}
server.hookTree = rtree.RTree{}
server.hookCross = rtree.RTree{}
d.command = "flushdb"
d.updated = true
d.timestamp = time.Now()

View File

@ -27,10 +27,10 @@ import (
"github.com/tidwall/geojson"
"github.com/tidwall/geojson/geometry"
"github.com/tidwall/gjson"
"github.com/tidwall/rbang"
"github.com/tidwall/redcon"
"github.com/tidwall/resp"
"github.com/tidwall/rhh"
"github.com/tidwall/rtree"
"github.com/tidwall/tile38/core"
"github.com/tidwall/tile38/internal/collection"
"github.com/tidwall/tile38/internal/deadline"
@ -117,8 +117,8 @@ type Server struct {
shrinking bool // aof shrinking flag
shrinklog [][]string // aof shrinking log
hooks map[string]*Hook // hook name
hookCross rbang.RTree // hook spatial tree for "cross" geofences
hookTree rbang.RTree // hook spatial tree for all
hookCross rtree.RTree // hook spatial tree for "cross" geofences
hookTree rtree.RTree // hook spatial tree for all
hooksOut map[string]*Hook // hooks with "outside" detection
aofconnM map[net.Conn]bool
luascripts *lScriptMap

View File

@ -19,7 +19,7 @@ import (
"github.com/tidwall/gjson"
"github.com/tidwall/grect"
"github.com/tidwall/match"
"github.com/tidwall/rtree"
"github.com/tidwall/rtred"
)
var (
@ -248,7 +248,7 @@ func (db *DB) Load(rd io.Reader) error {
// b-tree/r-tree context for itself.
type index struct {
btr *btree.BTree // contains the items
rtr *rtree.RTree // contains the items
rtr *rtred.RTree // contains the items
name string // name of the index
pattern string // a required key pattern
less func(a, b string) bool // less comparison function
@ -289,7 +289,7 @@ func (idx *index) clearCopy() *index {
nidx.btr = btree.New(lessCtx(nidx))
}
if nidx.rect != nil {
nidx.rtr = rtree.New(nidx)
nidx.rtr = rtred.New(nidx)
}
return nidx
}
@ -301,7 +301,7 @@ func (idx *index) rebuild() {
idx.btr = btree.New(lessCtx(idx))
}
if idx.rect != nil {
idx.rtr = rtree.New(idx)
idx.rtr = rtred.New(idx)
}
// iterate through all keys and fill the index
btreeAscend(idx.db.keys, func(item interface{}) bool {
@ -1824,7 +1824,7 @@ func (tx *Tx) Nearby(index, bounds string,
return nil
}
// // wrap a rtree specific iterator around the user-defined iterator.
iter := func(item rtree.Item, dist float64) bool {
iter := func(item rtred.Item, dist float64) bool {
dbi := item.(*dbItem)
return iterator(dbi.key, dbi.val, dist)
}
@ -1862,7 +1862,7 @@ func (tx *Tx) Intersects(index, bounds string,
return nil
}
// wrap a rtree specific iterator around the user-defined iterator.
iter := func(item rtree.Item) bool {
iter := func(item rtred.Item) bool {
dbi := item.(*dbItem)
return iterator(dbi.key, dbi.val)
}

View File

@ -4,8 +4,8 @@ go 1.15
require (
github.com/tidwall/btree v0.3.0
github.com/tidwall/gjson v1.6.7
github.com/tidwall/gjson v1.6.8
github.com/tidwall/grect v0.1.0
github.com/tidwall/match v1.0.3
github.com/tidwall/rtree v0.1.0
github.com/tidwall/rtred v0.1.2
)

View File

@ -1,14 +1,15 @@
github.com/tidwall/btree v0.3.0 h1:LcwmLI5kv+AaH/xnBgOuKfbu5eLBWVdWTpD2o+qSRdU=
github.com/tidwall/btree v0.3.0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
github.com/tidwall/gjson v1.6.7 h1:Mb1M9HZCRWEcXQ8ieJo7auYyyiSux6w9XN3AdTpxJrE=
github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w=
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/grect v0.1.0 h1:ICcKWD5uu5A5fmxApGIa0QRvfGnSWKRd07POT08CQSA=
github.com/tidwall/grect v0.1.0/go.mod h1:sa5O42oP6jWfTShL9ka6Sgmg3TgIK649veZe05B7+J8=
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/rtree v0.1.0 h1:w/RoiV3WvrKF/EzLaK6CT0MXAV6KveGy3EOyeJACoFA=
github.com/tidwall/rtree v0.1.0/go.mod h1:xsujlpupsrYVnWaok1pqGtxhj9TKHSbMbY8eKPatQ4U=
github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=

View File

@ -134,60 +134,6 @@ 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
}
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, bool) {
if q.len == 0 {
return qnode{}, false
}
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, true
}
// Scan iterates through all data in tree in no specified order.
func (index *Index) Scan(
iter func(min, max [2]float64, data interface{}) bool,

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

@ -0,0 +1,8 @@
module github.com/tidwall/geoindex
go 1.15
require (
github.com/tidwall/cities v0.1.0
github.com/tidwall/lotsa v1.0.1
)

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

@ -0,0 +1,4 @@
github.com/tidwall/cities v0.1.0 h1:CVNkmMf7NEC9Bvokf5GoSsArHCKRMTgLuubRTHnH0mE=
github.com/tidwall/cities v0.1.0/go.mod h1:lV/HDp2gCcRcHJWqgt6Di54GiDrTZwh1aG2ZUPNbqa4=
github.com/tidwall/lotsa v1.0.1 h1:w4gpDvI7RdkgbMC0q5ndKqG2ffrwCgerUY/gM2TYkH4=
github.com/tidwall/lotsa v1.0.1/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=

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

@ -0,0 +1,53 @@
package geoindex
import "github.com/tidwall/geoindex/child"
// Priority Queue ordered by dist (smallest to largest)
type qnode struct {
dist float64
child child.Child
}
type queue []qnode
func (q *queue) push(node qnode) {
*q = append(*q, node)
nodes := *q
i := len(nodes) - 1
parent := (i - 1) / 2
for ; i != 0 && nodes[parent].dist > nodes[i].dist; parent = (i - 1) / 2 {
nodes[parent], nodes[i] = nodes[i], nodes[parent]
i = parent
}
}
func (q *queue) pop() (qnode, bool) {
nodes := *q
if len(nodes) == 0 {
return qnode{}, false
}
var n qnode
n, nodes[0] = nodes[0], nodes[len(*q)-1]
nodes = nodes[:len(nodes)-1]
*q = nodes
i := 0
for {
smallest := i
left := i*2 + 1
right := i*2 + 2
if left < len(nodes) && nodes[left].dist <= nodes[smallest].dist {
smallest = left
}
if right < len(nodes) && nodes[right].dist <= nodes[smallest].dist {
smallest = right
}
if smallest == i {
break
}
nodes[smallest], nodes[i] = nodes[i], nodes[smallest]
i = smallest
}
return n, true
}

View File

@ -58,7 +58,7 @@ import (
// }
//
var Tests = struct {
TestBenchVarious func(t *testing.T, tr Interface, numPoints int)
TestBenchVarious func(t *testing.T, tr Interface, numPointOrRects int)
TestRandomPoints func(t *testing.T, tr Interface, numPoints int)
TestRandomRects func(t *testing.T, tr Interface, numRects int)
TestCitiesSVG func(t *testing.T, tr Interface)
@ -77,30 +77,85 @@ var Tests = struct {
benchmarkRandomInsert,
}
func benchVarious(t *testing.T, tr Interface, numPoints int) {
N := numPoints
rand.Seed(time.Now().UnixNano())
points := make([][2]float64, N)
for i := 0; i < N; i++ {
points[i][0] = rand.Float64()*360 - 180
points[i][1] = rand.Float64()*180 - 90
type rect struct {
min, max [2]float64
}
// kind = 'r','p','m' for rect,point,mixed
func randRect(kind byte) (r rect) {
r.min[0] = rand.Float64()*360 - 180
r.min[1] = rand.Float64()*180 - 90
r.max = r.min
return randRectOffset(r, kind)
}
func randRectOffset(r rect, kind byte) rect {
rsize := 0.01 // size of rectangle in degrees
pr := r
for {
r.min[0] = (pr.max[0]+pr.min[0])/2 + rand.Float64()*rsize - rsize/2
r.min[1] = (pr.max[1]+pr.min[1])/2 + rand.Float64()*rsize - rsize/2
r.max = r.min
if kind == 'r' || (kind == 'm' && rand.Int()%2 == 0) {
// rect
r.max[0] = r.min[0] + rand.Float64()*rsize
r.max[1] = r.min[1] + rand.Float64()*rsize
} else {
// point
r.max = r.min
}
if r.min[0] < -180 || r.min[1] < -90 ||
r.max[0] > 180 || r.max[1] > 90 {
continue
}
return r
}
pointsReplace := make([][2]float64, N)
}
type mixedTree interface {
IsMixedTree() bool
}
func benchVarious(t *testing.T, tr Interface, numPointOrRects int) {
if v, ok := tr.(mixedTree); ok && v.IsMixedTree() {
println("== points ==")
benchVariousKind(t, tr, numPointOrRects, 'p')
println("== rects ==")
benchVariousKind(t, tr, numPointOrRects, 'r')
println("== mixed (50/50) ==")
benchVariousKind(t, tr, numPointOrRects, 'm')
} else {
benchVariousKind(t, tr, numPointOrRects, 'm')
}
}
func benchVariousKind(t *testing.T, tr Interface, numPointOrRects int,
kind byte,
) {
N := numPointOrRects
rand.Seed(time.Now().UnixNano())
rects := make([]rect, N)
for i := 0; i < N; i++ {
pointsReplace[i][0] = rand.Float64()*360 - 180
pointsReplace[i][1] = rand.Float64()*180 - 90
rects[i] = randRect(kind)
}
rectsReplace := make([]rect, N)
for i := 0; i < N; i++ {
rectsReplace[i] = randRectOffset(rects[i], kind)
}
lotsa.Output = os.Stdout
fmt.Printf("insert: ")
lotsa.Ops(N, 1, func(i, _ int) {
tr.Insert(points[i], points[i], i)
tr.Insert(rects[i].min, rects[i].max, i)
})
fmt.Printf("search: ")
var count int
lotsa.Ops(N, 1, func(i, _ int) {
tr.Search(points[i], points[i],
tr.Search(rects[i].min, rects[i].max,
func(min, max [2]float64, value interface{}) bool {
count++
if value.(int) == i {
count++
return false
}
return true
},
)
@ -111,8 +166,8 @@ func benchVarious(t *testing.T, tr Interface, numPoints int) {
fmt.Printf("replace: ")
lotsa.Ops(N, 1, func(i, _ int) {
tr.Replace(
points[i], points[i], i,
pointsReplace[i], pointsReplace[i], i,
rects[i].min, rects[i].max, i,
rectsReplace[i].min, rectsReplace[i].max, i,
)
})
if tr.Len() != N {
@ -121,7 +176,7 @@ func benchVarious(t *testing.T, tr Interface, numPoints int) {
fmt.Printf("delete: ")
lotsa.Ops(N, 1, func(i, _ int) {
tr.Delete(pointsReplace[i], pointsReplace[i], i)
tr.Delete(rectsReplace[i].min, rectsReplace[i].max, i)
})
if tr.Len() != 0 {
t.Fatalf("expected %d, got %d", 0, tr.Len())
@ -131,9 +186,6 @@ func benchVarious(t *testing.T, tr Interface, numPoints int) {
func testBoxesVarious(t *testing.T, tr Interface, boxes []tBox, label string) {
N := len(boxes)
// N := 10000
// boxes := randPoints(N)
/////////////////////////////////////////
// insert
/////////////////////////////////////////
@ -143,10 +195,6 @@ func testBoxesVarious(t *testing.T, tr Interface, boxes []tBox, label string) {
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))
// ioutil.WriteFile(label+".svg", []byte(rtreetools.SVG(&tr)), 0600)
/////////////////////////////////////////
// scan all items and count one-by-one
@ -269,8 +317,6 @@ func testBoxesVarious(t *testing.T, tr Interface, boxes []tBox, label string) {
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))
/////////////////////////////////////////
// check every point for correctness

View File

@ -1 +0,0 @@
language: go

View File

@ -1,6 +1,6 @@
# `GeoJSON`
# GeoJSON
[![Build Status](https://travis-ci.org/tidwall/geojson.svg?branch=master)](https://travis-ci.org/tidwall/geojson) [![GoDoc](https://godoc.org/github.com/tidwall/geojson?status.svg)](https://godoc.org/github.com/tidwall/geojson)
[![GoDoc](https://godoc.org/github.com/tidwall/geojson?status.svg)](https://godoc.org/github.com/tidwall/geojson)
This package provides GeoJSON utilties for Go. It's designed for [Tile38](https://github.com/tidwall/tile38).

View File

@ -2,13 +2,13 @@ package geojson
import (
"github.com/tidwall/geojson/geometry"
"github.com/tidwall/rbang"
"github.com/tidwall/rtree"
)
type collection struct {
children []Object
extra *extra
tree *rbang.RTree
tree *rtree.RTree
prect geometry.Rect
pempty bool
}
@ -303,7 +303,7 @@ func (g *collection) parseInitRectIndex(opts *ParseOptions) {
count++
}
if count > 0 && opts.IndexChildren != 0 && count >= opts.IndexChildren {
g.tree = new(rbang.RTree)
g.tree = new(rtree.RTree)
for _, child := range g.children {
if child.Empty() {
continue

99
vendor/github.com/tidwall/geojson/geometry/raycast.go generated vendored Normal file
View File

@ -0,0 +1,99 @@
package geometry
import "math"
// RaycastResult holds the results of the Raycast operation
type RaycastResult struct {
In bool // point on the left
On bool // point is directly on top of
}
// Raycast performs the raycast operation
func (seg Segment) Raycast(point Point) RaycastResult {
p, a, b := point, seg.A, seg.B
// make sure that the point is inside the segment bounds
if a.Y < b.Y && (p.Y < a.Y || p.Y > b.Y) {
return RaycastResult{false, false}
} else if a.Y > b.Y && (p.Y < b.Y || p.Y > a.Y) {
return RaycastResult{false, false}
}
// test if point is in on the segment
if a.Y == b.Y {
if a.X == b.X {
if p == a {
return RaycastResult{false, true}
}
return RaycastResult{false, false}
}
if p.Y == b.Y {
// horizontal segment
// check if the point in on the line
if a.X < b.X {
if p.X >= a.X && p.X <= b.X {
return RaycastResult{false, true}
}
} else {
if p.X >= b.X && p.X <= a.X {
return RaycastResult{false, true}
}
}
}
}
if a.X == b.X && p.X == b.X {
// vertical segment
// check if the point in on the line
if a.Y < b.Y {
if p.Y >= a.Y && p.Y <= b.Y {
return RaycastResult{false, true}
}
} else {
if p.Y >= b.Y && p.Y <= a.Y {
return RaycastResult{false, true}
}
}
}
if (p.X-a.X)/(b.X-a.X) == (p.Y-a.Y)/(b.Y-a.Y) {
return RaycastResult{false, true}
}
// do the actual raycast here.
for p.Y == a.Y || p.Y == b.Y {
p.Y = math.Nextafter(p.Y, math.Inf(1))
}
if a.Y < b.Y {
if p.Y < a.Y || p.Y > b.Y {
return RaycastResult{false, false}
}
} else {
if p.Y < b.Y || p.Y > a.Y {
return RaycastResult{false, false}
}
}
if a.X > b.X {
if p.X >= a.X {
return RaycastResult{false, false}
}
if p.X <= b.X {
return RaycastResult{true, false}
}
} else {
if p.X >= b.X {
return RaycastResult{false, false}
}
if p.X <= a.X {
return RaycastResult{true, false}
}
}
if a.Y < b.Y {
if (p.Y-a.Y)/(p.X-a.X) >= (b.Y-a.Y)/(b.X-a.X) {
return RaycastResult{true, false}
}
} else {
if (p.Y-b.Y)/(p.X-b.X) >= (a.Y-b.Y)/(a.X-b.X) {
return RaycastResult{true, false}
}
}
return RaycastResult{false, false}
}

View File

@ -4,10 +4,6 @@
package geometry
import (
"math"
)
// Segment is a two point line
type Segment struct {
A, B Point
@ -53,102 +49,6 @@ func (seg Segment) ContainsPoint(point Point) bool {
// return math.Atan2(seg.B.Y-seg.A.Y, seg.B.X-seg.A.X)
// }
// RaycastResult holds the results of the Raycast operation
type RaycastResult struct {
In bool // point on the left
On bool // point is directly on top of
}
// Raycast performs the raycast operation
func (seg Segment) Raycast(point Point) RaycastResult {
p, a, b := point, seg.A, seg.B
// make sure that the point is inside the segment bounds
if a.Y < b.Y && (p.Y < a.Y || p.Y > b.Y) {
return RaycastResult{false, false}
} else if a.Y > b.Y && (p.Y < b.Y || p.Y > a.Y) {
return RaycastResult{false, false}
}
// test if point is in on the segment
if a.Y == b.Y {
if a.X == b.X {
if p == a {
return RaycastResult{false, true}
}
return RaycastResult{false, false}
}
if p.Y == b.Y {
// horizontal segment
// check if the point in on the line
if a.X < b.X {
if p.X >= a.X && p.X <= b.X {
return RaycastResult{false, true}
}
} else {
if p.X >= b.X && p.X <= a.X {
return RaycastResult{false, true}
}
}
}
}
if a.X == b.X && p.X == b.X {
// vertical segment
// check if the point in on the line
if a.Y < b.Y {
if p.Y >= a.Y && p.Y <= b.Y {
return RaycastResult{false, true}
}
} else {
if p.Y >= b.Y && p.Y <= a.Y {
return RaycastResult{false, true}
}
}
}
if (p.X-a.X)/(b.X-a.X) == (p.Y-a.Y)/(b.Y-a.Y) {
return RaycastResult{false, true}
}
// do the actual raycast here.
for p.Y == a.Y || p.Y == b.Y {
p.Y = math.Nextafter(p.Y, math.Inf(1))
}
if a.Y < b.Y {
if p.Y < a.Y || p.Y > b.Y {
return RaycastResult{false, false}
}
} else {
if p.Y < b.Y || p.Y > a.Y {
return RaycastResult{false, false}
}
}
if a.X > b.X {
if p.X >= a.X {
return RaycastResult{false, false}
}
if p.X <= b.X {
return RaycastResult{true, false}
}
} else {
if p.X >= b.X {
return RaycastResult{false, false}
}
if p.X <= a.X {
return RaycastResult{true, false}
}
}
if a.Y < b.Y {
if (p.Y-a.Y)/(p.X-a.X) >= (b.Y-a.Y)/(b.X-a.X) {
return RaycastResult{true, false}
}
} else {
if (p.Y-b.Y)/(p.X-b.X) >= (a.Y-b.Y)/(a.X-b.X) {
return RaycastResult{true, false}
}
}
return RaycastResult{false, false}
}
// IntersectsSegment detects if segment intersects with other segement
func (seg Segment) IntersectsSegment(other Segment) bool {
a, b, c, d := seg.A, seg.B, other.A, other.B

11
vendor/github.com/tidwall/geojson/go.mod generated vendored Normal file
View File

@ -0,0 +1,11 @@
module github.com/tidwall/geojson
go 1.15
require (
github.com/tidwall/gjson v1.6.8
github.com/tidwall/lotsa v1.0.1
github.com/tidwall/pretty v1.0.2
github.com/tidwall/rtree v1.2.6
github.com/tidwall/sjson v1.1.5
)

15
vendor/github.com/tidwall/geojson/go.sum generated vendored Normal file
View File

@ -0,0 +1,15 @@
github.com/tidwall/cities v0.1.0/go.mod h1:lV/HDp2gCcRcHJWqgt6Di54GiDrTZwh1aG2ZUPNbqa4=
github.com/tidwall/geoindex v1.4.1 h1:dlhM+2isLqz8ndHn7vOCevD3IPH3XeO/BaFN4rLpimo=
github.com/tidwall/geoindex v1.4.1/go.mod h1:NQJQszWCH4+KlD0wY+mgQ2hK/GdSH+9+ZRknDY8bOHc=
github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w=
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/lotsa v1.0.1 h1:w4gpDvI7RdkgbMC0q5ndKqG2ffrwCgerUY/gM2TYkH4=
github.com/tidwall/lotsa v1.0.1/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/rtree v1.2.6 h1:Q4FhJZId5k22IYsZ55Bz8nNo4Z9LyyOzWb+WIwc1vdM=
github.com/tidwall/rtree v1.2.6/go.mod h1:fn56Cu3AyoR5U5LLQywyuTOCDF8Lq6GQCjLRSxnPAJI=
github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE=
github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=

View File

@ -1,78 +0,0 @@
# rbang
[![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) and is optimized for fast rect
inserts and replacements.
<img src="cities.png" width="512" height="256" border="0" alt="Cities">
## Usage
### Installing
To start using rbang, install Go and run `go get`:
```sh
$ go get -u github.com/tidwall/rbang
```
### Basic operations
```go
// create a 2D RTree
var tr rbang.RTree
// insert a point
tr.Insert([2]float64{-112.0078, 33.4373}, [2]float64{-112.0078, 33.4373}, "PHX")
// insert a rect
tr.Insert([2]float64{10, 10}, [2]float64{20, 20}, "rect")
// search
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([2]float64{-112.0078, 33.4373}, [2]float64{-112.0078, 33.4373}, "PHX")
```
## Algorithms
This implementation is a variant of the original paper:
[R-TREES. A DYNAMIC INDEX STRUCTURE FOR SPATIAL SEARCHING](http://www-db.deis.unibo.it/courses/SI-LS/papers/Gut84.pdf)
### Inserting
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 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
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 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` 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` 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.

View File

@ -1,586 +0,0 @@
package rbang
import (
"github.com/tidwall/geoindex/child"
)
const (
maxEntries = 32
minEntries = maxEntries * 20 / 100
)
type rect struct {
min, max [2]float64
data interface{}
}
type node struct {
count int
rects [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.rects[1])
newRoot.rects[0] = tr.root
newRoot.count = 2
tr.root.data = newRoot
tr.root.recalc()
tr.height++
}
tr.count++
}
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++ {
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
}
func (r *rect) recalc() {
n := r.data.(*node)
r.min = n.rects[0].min
r.max = n.rects[0].max
for i := 1; i < n.count; i++ {
r.expand(&n.rects[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.rects[i].min[axis] - left.min[axis]
maxDist := left.max[axis] - leftNode.rects[i].max[axis]
if minDist < maxDist {
// stay left
} else {
if minDist > maxDist {
// move to right
rightNode.rects[rightNode.count] = leftNode.rects[i]
rightNode.count++
} else {
// move to equals, at the end of the left array
equals = append(equals, leftNode.rects[i])
}
leftNode.rects[i] = leftNode.rects[leftNode.count-1]
leftNode.rects[leftNode.count-1].data = nil
leftNode.count--
i--
}
}
for _, b := range equals {
if leftNode.count < rightNode.count {
leftNode.rects[leftNode.count] = b
leftNode.count++
} else {
rightNode.rects[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.rects[n.count] = *item
n.count++
grown = !r.contains(item)
return grown
}
// choose subtree
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 {
child.expand(item)
grown = !r.contains(item)
}
if child.data.(*node).count == maxEntries+1 {
child.splitLargestAxisEdgeSnap(&n.rects[n.count])
n.count++
}
return grown
}
// fit an external item into a rect type
func fit(min, max [2]float64, value interface{}, target *rect) {
target.min = min
target.max = max
target.data = value
}
// 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.rects[i]) {
if !iter(n.rects[i].min, n.rects[i].max,
n.rects[i].data) {
return false
}
}
}
} else if height == 1 {
for i := 0; i < n.count; i++ {
if target.intersects(&n.rects[i]) {
cn := n.rects[i].data.(*node)
for i := 0; i < cn.count; i++ {
if target.intersects(&cn.rects[i]) {
if !iter(cn.rects[i].min, cn.rects[i].max,
cn.rects[i].data) {
return false
}
}
}
}
}
} else {
for i := 0; i < n.count; i++ {
if target.intersects(&n.rects[i]) {
if !n.rects[i].search(target, 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
}
if target.intersects(&tr.root) {
tr.root.search(target, 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.rects[i].min, n.rects[i].max, n.rects[i].data) {
return false
}
}
} else if height == 1 {
for i := 0; i < n.count; i++ {
cn := n.rects[i].data.(*node)
for j := 0; j < cn.count; j++ {
if !iter(cn.rects[j].min, cn.rects[j].max, cn.rects[j].data) {
return false
}
}
}
} else {
for i := 0; i < n.count; i++ {
if !n.rects[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.root.delete(tr, &item, tr.height)
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).rects[0]
tr.height--
tr.root.recalc()
}
}
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(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 < len(rects); i++ {
if rects[i].data == item.data {
// found the target item to delete
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
}
}
} else {
for i := 0; i < len(rects); i++ {
if !rects[i].contains(item) {
continue
}
removed, recalced = rects[i].delete(tr, item, height-1)
if !removed {
continue
}
if rects[i].data.(*node).count < minEntries {
// underflow
if !recalced {
recalced = r.onEdge(&rects[i])
}
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
}
}
return false, false
}
// flatten all leaf rects into a single list
func (r *rect) flatten(all []rect, height int) []rect {
n := r.data.(*node)
if height == 0 {
all = append(all, n.rects[:n.count]...)
} else {
for i := 0; i < n.count; i++ {
all = n.rects[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 rect
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.rects[0].data.(*node); ok {
item = false
}
}
for i := 0; i < n.count; i++ {
children = append(children, child.Child{
Min: n.rects[i].min,
Max: n.rects[i].max,
Data: n.rects[i].data,
Item: item,
})
}
}
return children
}
// 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{},
) {
tr.Delete(oldMin, oldMax, oldData)
tr.Insert(newMin, newMax, newData)
}

View File

@ -1,4 +1,4 @@
Copyright (c) 2018 Josh Baker
Copyright (c) 2016 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
@ -16,4 +16,4 @@ 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.
THE SOFTWARE.

21
vendor/github.com/tidwall/rtred/README.md generated vendored Normal file
View File

@ -0,0 +1,21 @@
RTree implementation for Go
===========================
[![GoDoc](https://godoc.org/github.com/tidwall/rtree?status.svg)](https://godoc.org/github.com/tidwall/rtree)
This package provides an in-memory R-Tree implementation for Go, useful as a spatial data structure.
It has support for 1-20 dimensions, and can store and search multidimensions interchangably in the same tree.
Authors
-------
* 1983 Original algorithm and test code by Antonin Guttman and Michael Stonebraker, UC Berkely
* 1994 ANCI C ported from original test code by Melinda Green
* 1995 Sphere volume fix for degeneracy problem submitted by Paul Brook
* 2004 Templated C++ port by Greg Douglas
* 2016 Go port by Josh Baker
* 2018 Added kNN and merged in some of the RBush logic by Vladimir Agafonkin
License
-------
RTree source code is available under the MIT License.

5
vendor/github.com/tidwall/rtred/go.mod generated vendored Normal file
View File

@ -0,0 +1,5 @@
module github.com/tidwall/rtred
go 1.15
require github.com/tidwall/tinyqueue v0.1.1

2
vendor/github.com/tidwall/rtred/go.sum generated vendored Normal file
View File

@ -0,0 +1,2 @@
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=

278
vendor/github.com/tidwall/rtred/rtree.go generated vendored Normal file
View File

@ -0,0 +1,278 @@
package rtred
import (
"math"
"sync"
"github.com/tidwall/rtred/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
}

View File

@ -1,4 +1,4 @@
Copyright (c) 2016 Josh Baker
Copyright (c) 2021 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
@ -16,4 +16,4 @@ 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.
THE SOFTWARE.

View File

@ -1,21 +1,78 @@
RTree implementation for Go
===========================
# rtree
[![GoDoc](https://godoc.org/github.com/tidwall/rtree?status.svg)](https://godoc.org/github.com/tidwall/rtree)
This package provides an in-memory R-Tree implementation for Go, useful as a spatial data structure.
It has support for 1-20 dimensions, and can store and search multidimensions interchangably in the same tree.
This package provides an in-memory R-Tree implementation for Go. It's designed
for [Tile38](https://github.com/tidwall/tile38) and is optimized for fast rect
inserts and replacements.
Authors
-------
* 1983 Original algorithm and test code by Antonin Guttman and Michael Stonebraker, UC Berkely
* 1994 ANCI C ported from original test code by Melinda Green
* 1995 Sphere volume fix for degeneracy problem submitted by Paul Brook
* 2004 Templated C++ port by Greg Douglas
* 2016 Go port by Josh Baker
* 2018 Added kNN and merged in some of the RBush logic by Vladimir Agafonkin
<img src="cities.png" width="512" height="256" border="0" alt="Cities">
License
-------
RTree source code is available under the MIT License.
## Usage
### Installing
To start using rtree, install Go and run `go get`:
```sh
$ go get -u github.com/tidwall/rtree
```
### Basic operations
```go
// create a 2D RTree
var tr rtree.RTree
// insert a point
tr.Insert([2]float64{-112.0078, 33.4373}, [2]float64{-112.0078, 33.4373}, "PHX")
// insert a rect
tr.Insert([2]float64{10, 10}, [2]float64{20, 20}, "rect")
// search
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([2]float64{-112.0078, 33.4373}, [2]float64{-112.0078, 33.4373}, "PHX")
```
## Algorithms
This implementation is a variant of the original paper:
[R-TREES. A DYNAMIC INDEX STRUCTURE FOR SPATIAL SEARCHING](http://www-db.deis.unibo.it/courses/SI-LS/papers/Gut84.pdf)
### Inserting
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 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
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 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` 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` rect is then one-by-one placed in either `left` or `right`, whichever has less children.
## License
rtree source code is available under the MIT License.

View File

Before

Width:  |  Height:  |  Size: 336 KiB

After

Width:  |  Height:  |  Size: 336 KiB

View File

@ -2,4 +2,4 @@ module github.com/tidwall/rtree
go 1.15
require github.com/tidwall/tinyqueue v0.1.1
require github.com/tidwall/geoindex v1.4.1

View File

@ -1,2 +1,6 @@
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
github.com/tidwall/cities v0.1.0 h1:CVNkmMf7NEC9Bvokf5GoSsArHCKRMTgLuubRTHnH0mE=
github.com/tidwall/cities v0.1.0/go.mod h1:lV/HDp2gCcRcHJWqgt6Di54GiDrTZwh1aG2ZUPNbqa4=
github.com/tidwall/geoindex v1.4.1 h1:dlhM+2isLqz8ndHn7vOCevD3IPH3XeO/BaFN4rLpimo=
github.com/tidwall/geoindex v1.4.1/go.mod h1:NQJQszWCH4+KlD0wY+mgQ2hK/GdSH+9+ZRknDY8bOHc=
github.com/tidwall/lotsa v1.0.1 h1:w4gpDvI7RdkgbMC0q5ndKqG2ffrwCgerUY/gM2TYkH4=
github.com/tidwall/lotsa v1.0.1/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=

View File

@ -1,278 +1,562 @@
// Copyright 2021 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package rtree
import (
"math"
"sync"
import "github.com/tidwall/geoindex/child"
"github.com/tidwall/rtree/base"
const (
maxEntries = 32
minEntries = maxEntries * 20 / 100
)
type Iterator func(item Item) bool
type Item interface {
Rect(ctx interface{}) (min []float64, max []float64)
type rect struct {
min, max [2]float64
data interface{}
}
type node struct {
count int
rects [maxEntries + 1]rect
}
// RTree ...
type RTree struct {
dims int
maxEntries int
ctx interface{}
trs []*base.RTree
used int
height int
root rect
count int
reinsert []rect
}
func New(ctx interface{}) *RTree {
tr := &RTree{
ctx: ctx,
dims: 20,
maxEntries: 13,
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]
}
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 (r *rect) area() float64 {
return (r.max[0] - r.min[0]) * (r.max[1] - r.min[1])
}
func (tr *RTree) Remove(item Item) {
if item == nil {
panic("nil item")
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]
}
min, max := item.Rect(tr.ctx)
if len(min) != len(max) {
return // just return
panic("invalid item rectangle")
if r.min[0] > b.min[0] {
min = r.min[0]
} else {
min = b.min[0]
}
if len(min) < 1 || len(min) > len(tr.trs) {
return // just return
panic("invalid dimension")
if max > min {
area *= max - min
} else {
return 0
}
btr := tr.trs[len(min)-1]
if btr == nil {
return
if r.max[1] < b.max[1] {
max = r.max[1]
} else {
max = b.max[1]
}
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]
if r.min[1] > b.min[1] {
min = r.min[1]
} else {
min = b.min[1]
}
btr.Remove(amin, amax, item)
if btr.IsEmpty() {
tr.trs[len(min)-1] = nil
tr.used--
if max > min {
area *= max - min
} else {
return 0
}
}
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
return area
}
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]
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 {
amin[i] = math.Inf(-1)
amax[i] = math.Inf(+1)
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]
}
}
var ended bool
btr.Search(amin, amax, func(item interface{}) bool {
if !iter(item.(Item)) {
ended = true
return false
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]
}
return true
})
return !ended
} 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
}
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")
}
// 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)
}
if tr.used == 0 {
return
func (tr *RTree) insert(item *rect) {
if tr.root.data == nil {
fit(item.min, item.max, new(node), &tr.root)
}
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
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.rects[1])
newRoot.rects[0] = tr.root
newRoot.count = 2
tr.root.data = newRoot
tr.root.recalc()
tr.height++
}
tr.count++
}
type queueT struct {
done bool
step int
item Item
dist float64
}
const inlineEnlargedArea = true
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
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++ {
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]
}
}
queues[minQueue] = queues[minQueue][1:]
if !iter(minItem, minDist) {
ended = true
break
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]
}
}
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)
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
}
}
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
return j
}
func (r *rect) recalc() {
n := r.data.(*node)
r.min = n.rects[0].min
r.max = n.rects[0].max
for i := 1; i < n.count; i++ {
r.expand(&n.rects[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.rects[i].min[axis] - left.min[axis]
maxDist := left.max[axis] - leftNode.rects[i].max[axis]
if minDist < maxDist {
// stay left
} else {
if minDist > maxDist {
// move to right
rightNode.rects[rightNode.count] = leftNode.rects[i]
rightNode.count++
} else {
// move to equals, at the end of the left array
equals = append(equals, leftNode.rects[i])
}
leftNode.rects[i] = leftNode.rects[leftNode.count-1]
leftNode.rects[leftNode.count-1].data = nil
leftNode.count--
i--
}
}
for _, b := range equals {
if leftNode.count < rightNode.count {
leftNode.rects[leftNode.count] = b
leftNode.count++
} else {
rightNode.rects[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.rects[n.count] = *item
n.count++
grown = !r.contains(item)
return grown
}
// choose subtree
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 {
child.expand(item)
grown = !r.contains(item)
}
if child.data.(*node).count == maxEntries+1 {
child.splitLargestAxisEdgeSnap(&n.rects[n.count])
n.count++
}
return grown
}
// fit an external item into a rect type
func fit(min, max [2]float64, value interface{}, target *rect) {
target.min = min
target.max = max
target.data = value
}
// 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.rects[i]) {
if !iter(n.rects[i].min, n.rects[i].max, n.rects[i].data) {
return false
}
}
}
} else {
for i := 0; i < n.count; i++ {
if target.intersects(&n.rects[i]) {
if !n.rects[i].search(target, 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
}
if target.intersects(&tr.root) {
tr.root.search(target, tr.height, iter)
}
}
// Search ...
func (tr *RTree) Search(
min, max [2]float64,
iter func(min, max [2]float64, value interface{}) bool,
) {
tr.search(rect{min: min, max: max}, 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.rects[i].min, n.rects[i].max, n.rects[i].data) {
return false
}
}
} else {
for i := 0; i < n.count; i++ {
if !n.rects[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.root.delete(tr, &item, tr.height)
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).rects[0]
tr.height--
tr.root.recalc()
}
}
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(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 < len(rects); i++ {
if rects[i].data == item.data {
// found the target item to delete
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
}
}
} else {
for i := 0; i < len(rects); i++ {
if !rects[i].contains(item) {
continue
}
removed, recalced = rects[i].delete(tr, item, height-1)
if !removed {
continue
}
if rects[i].data.(*node).count < minEntries {
// underflow
if !recalced {
recalced = r.onEdge(&rects[i])
}
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
}
}
return false, false
}
// flatten all leaf rects into a single list
func (r *rect) flatten(all []rect, height int) []rect {
n := r.data.(*node)
if height == 0 {
all = append(all, n.rects[:n.count]...)
} else {
for i := 0; i < n.count; i++ {
all = n.rects[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 rect
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.rects[0].data.(*node); ok {
item = false
}
}
for i := 0; i < n.count; i++ {
children = append(children, child.Child{
Min: n.rects[i].min,
Max: n.rects[i].max,
Data: n.rects[i].data,
Item: item,
})
}
}
return children
}
// 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{},
) {
tr.Delete(oldMin, oldMax, oldData)
tr.Insert(newMin, newMax, newData)
}

17
vendor/modules.txt vendored
View File

@ -117,18 +117,17 @@ github.com/streadway/amqp
# github.com/tidwall/btree v0.3.0
## explicit
github.com/tidwall/btree
# github.com/tidwall/buntdb v1.1.8
# github.com/tidwall/buntdb v1.2.0
## explicit
github.com/tidwall/buntdb
# github.com/tidwall/cities v0.1.0
## explicit
github.com/tidwall/cities
# github.com/tidwall/geoindex v1.4.0
# github.com/tidwall/geoindex v1.4.1
## explicit
github.com/tidwall/geoindex
github.com/tidwall/geoindex/algo
github.com/tidwall/geoindex/child
# github.com/tidwall/geojson v1.2.3
# github.com/tidwall/geojson v1.2.4
## explicit
github.com/tidwall/geojson
github.com/tidwall/geojson/geo
@ -146,9 +145,6 @@ github.com/tidwall/match
# github.com/tidwall/pretty v1.0.2
## explicit
github.com/tidwall/pretty
# github.com/tidwall/rbang v1.2.4
## explicit
github.com/tidwall/rbang
# github.com/tidwall/redbench v0.1.0
## explicit
github.com/tidwall/redbench
@ -161,9 +157,12 @@ github.com/tidwall/resp
# github.com/tidwall/rhh v1.1.1
## explicit
github.com/tidwall/rhh
# github.com/tidwall/rtree v0.1.0
# github.com/tidwall/rtred v0.1.2
github.com/tidwall/rtred
github.com/tidwall/rtred/base
# github.com/tidwall/rtree v1.2.6
## explicit
github.com/tidwall/rtree
github.com/tidwall/rtree/base
# github.com/tidwall/sjson v1.1.5
## explicit
github.com/tidwall/sjson