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/peterh/liner v1.2.1
github.com/streadway/amqp v1.0.0 github.com/streadway/amqp v1.0.0
github.com/tidwall/btree v0.3.0 github.com/tidwall/btree v0.3.0
github.com/tidwall/buntdb v1.1.8 github.com/tidwall/buntdb v1.2.0
github.com/tidwall/cities v0.1.0 // indirect github.com/tidwall/geoindex v1.4.1
github.com/tidwall/geoindex v1.4.0 github.com/tidwall/geojson v1.2.4
github.com/tidwall/geojson v1.2.3
github.com/tidwall/gjson v1.6.8 github.com/tidwall/gjson v1.6.8
github.com/tidwall/lotsa v1.0.2 // indirect github.com/tidwall/lotsa v1.0.2 // indirect
github.com/tidwall/match v1.0.3 github.com/tidwall/match v1.0.3
github.com/tidwall/pretty v1.0.2 github.com/tidwall/pretty v1.0.2
github.com/tidwall/rbang v1.2.4
github.com/tidwall/redbench v0.1.0 github.com/tidwall/redbench v0.1.0
github.com/tidwall/redcon v1.4.0 github.com/tidwall/redcon v1.4.0
github.com/tidwall/resp v0.1.0 github.com/tidwall/resp v0.1.0
github.com/tidwall/rhh v1.1.1 github.com/tidwall/rhh v1.1.1
github.com/tidwall/rtree v1.2.6
github.com/tidwall/sjson v1.1.5 github.com/tidwall/sjson v1.1.5
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad 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.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 h1:LcwmLI5kv+AaH/xnBgOuKfbu5eLBWVdWTpD2o+qSRdU=
github.com/tidwall/btree v0.3.0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= 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.2.0 h1:8KOzf5Gg97DoCMSOgcwZjnM0FfROtq0fcZkPW54oGKU=
github.com/tidwall/buntdb v1.1.8/go.mod h1:a/Xp8Hsllzxex0WTNdANz2+hOwGaYDHW4xBK9dMp30w= 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 h1:CVNkmMf7NEC9Bvokf5GoSsArHCKRMTgLuubRTHnH0mE=
github.com/tidwall/cities v0.1.0/go.mod h1:lV/HDp2gCcRcHJWqgt6Di54GiDrTZwh1aG2ZUPNbqa4= 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.1 h1:dlhM+2isLqz8ndHn7vOCevD3IPH3XeO/BaFN4rLpimo=
github.com/tidwall/geoindex v1.4.0/go.mod h1:3gTa91BW+eiVIipuR6aU1Y9Sa0q75b1teE/NP2vfsTc= github.com/tidwall/geoindex v1.4.1/go.mod h1:NQJQszWCH4+KlD0wY+mgQ2hK/GdSH+9+ZRknDY8bOHc=
github.com/tidwall/geojson v1.2.3 h1:z//3h73oC+EkQ1TQr0oLbk7fKtGqAYkhSILtFZi1j8w= github.com/tidwall/geojson v1.2.4 h1:INKsEJULXKiKSuFQZQ7Vy3v9zchg0VPtcQl2+KeTlvc=
github.com/tidwall/geojson v1.2.3/go.mod h1:tBjfxeALRFLc25LLpjtWzy2nIrNmW1ze1EAhLtd8+QQ= 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.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w= 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/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/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 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 h1:UZYUMhwMMObQRq5xU4SA3lmlJRztXzqtushDii+AmPo=
github.com/tidwall/redbench v0.1.0/go.mod h1:zxcRGCq/JcqV48YjK9WxBNJL7JSpMzbLXaHvMcnanKQ= github.com/tidwall/redbench v0.1.0/go.mod h1:zxcRGCq/JcqV48YjK9WxBNJL7JSpMzbLXaHvMcnanKQ=
github.com/tidwall/redcon v1.4.0 h1:y2PmDD55STRdy4S98qP/Dn+gZG+cPVvIDi9BJV2aOwA= 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/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 h1:8zDpMKcK1pA1zU+Jyuo1UdzTFvME8pH3Sx/MdYgM5sE=
github.com/tidwall/rhh v1.1.1/go.mod h1:DmqiIRtSnlVEi5CSKqNaX6m3YTa3YNSYrGB4FlfdLUU= 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/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
github.com/tidwall/rtree v0.1.0/go.mod h1:xsujlpupsrYVnWaok1pqGtxhj9TKHSbMbY8eKPatQ4U= 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 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE=
github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE= github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE= 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"
"github.com/tidwall/geojson/geo" "github.com/tidwall/geojson/geo"
"github.com/tidwall/geojson/geometry" "github.com/tidwall/geojson/geometry"
"github.com/tidwall/rbang" "github.com/tidwall/rtree"
"github.com/tidwall/tile38/internal/deadline" "github.com/tidwall/tile38/internal/deadline"
) )
@ -64,7 +64,7 @@ var counter uint64
func New() *Collection { func New() *Collection {
col := &Collection{ col := &Collection{
items: btree.New(byID), items: btree.New(byID),
index: geoindex.Wrap(&rbang.RTree{}), index: geoindex.Wrap(&rtree.RTree{}),
values: btree.New(byValue), values: btree.New(byValue),
fieldMap: make(map[string]int), fieldMap: make(map[string]int),
fieldArr: make([]string, 0), fieldArr: make([]string, 0),

View File

@ -10,9 +10,9 @@ import (
"github.com/tidwall/btree" "github.com/tidwall/btree"
"github.com/tidwall/geojson" "github.com/tidwall/geojson"
"github.com/tidwall/geojson/geometry" "github.com/tidwall/geojson/geometry"
"github.com/tidwall/rbang"
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/rhh" "github.com/tidwall/rhh"
"github.com/tidwall/rtree"
"github.com/tidwall/tile38/internal/collection" "github.com/tidwall/tile38/internal/collection"
"github.com/tidwall/tile38/internal/glob" "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.expires = rhh.New(0)
server.hooks = make(map[string]*Hook) server.hooks = make(map[string]*Hook)
server.hooksOut = make(map[string]*Hook) server.hooksOut = make(map[string]*Hook)
server.hookTree = rbang.RTree{} server.hookTree = rtree.RTree{}
server.hookCross = rbang.RTree{} server.hookCross = rtree.RTree{}
d.command = "flushdb" d.command = "flushdb"
d.updated = true d.updated = true
d.timestamp = time.Now() d.timestamp = time.Now()

View File

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

View File

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

View File

@ -4,8 +4,8 @@ go 1.15
require ( require (
github.com/tidwall/btree v0.3.0 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/grect v0.1.0
github.com/tidwall/match v1.0.3 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 h1:LcwmLI5kv+AaH/xnBgOuKfbu5eLBWVdWTpD2o+qSRdU=
github.com/tidwall/btree v0.3.0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= 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.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 h1:ICcKWD5uu5A5fmxApGIa0QRvfGnSWKRd07POT08CQSA=
github.com/tidwall/grect v0.1.0/go.mod h1:sa5O42oP6jWfTShL9ka6Sgmg3TgIK649veZe05B7+J8= 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 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 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 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 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/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
github.com/tidwall/rtree v0.1.0/go.mod h1:xsujlpupsrYVnWaok1pqGtxhj9TKHSbMbY8eKPatQ4U= 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 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw= 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() 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. // Scan iterates through all data in tree in no specified order.
func (index *Index) Scan( func (index *Index) Scan(
iter func(min, max [2]float64, data interface{}) bool, 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 { 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) TestRandomPoints func(t *testing.T, tr Interface, numPoints int)
TestRandomRects func(t *testing.T, tr Interface, numRects int) TestRandomRects func(t *testing.T, tr Interface, numRects int)
TestCitiesSVG func(t *testing.T, tr Interface) TestCitiesSVG func(t *testing.T, tr Interface)
@ -77,30 +77,85 @@ var Tests = struct {
benchmarkRandomInsert, benchmarkRandomInsert,
} }
func benchVarious(t *testing.T, tr Interface, numPoints int) { type rect struct {
N := numPoints min, max [2]float64
rand.Seed(time.Now().UnixNano()) }
points := make([][2]float64, N)
for i := 0; i < N; i++ { // kind = 'r','p','m' for rect,point,mixed
points[i][0] = rand.Float64()*360 - 180 func randRect(kind byte) (r rect) {
points[i][1] = rand.Float64()*180 - 90 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++ { for i := 0; i < N; i++ {
pointsReplace[i][0] = rand.Float64()*360 - 180 rects[i] = randRect(kind)
pointsReplace[i][1] = rand.Float64()*180 - 90 }
rectsReplace := make([]rect, N)
for i := 0; i < N; i++ {
rectsReplace[i] = randRectOffset(rects[i], kind)
} }
lotsa.Output = os.Stdout lotsa.Output = os.Stdout
fmt.Printf("insert: ") fmt.Printf("insert: ")
lotsa.Ops(N, 1, func(i, _ int) { 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: ") fmt.Printf("search: ")
var count int var count int
lotsa.Ops(N, 1, func(i, _ 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 { func(min, max [2]float64, value interface{}) bool {
count++ if value.(int) == i {
count++
return false
}
return true return true
}, },
) )
@ -111,8 +166,8 @@ func benchVarious(t *testing.T, tr Interface, numPoints int) {
fmt.Printf("replace: ") fmt.Printf("replace: ")
lotsa.Ops(N, 1, func(i, _ int) { lotsa.Ops(N, 1, func(i, _ int) {
tr.Replace( tr.Replace(
points[i], points[i], i, rects[i].min, rects[i].max, i,
pointsReplace[i], pointsReplace[i], i, rectsReplace[i].min, rectsReplace[i].max, i,
) )
}) })
if tr.Len() != N { if tr.Len() != N {
@ -121,7 +176,7 @@ func benchVarious(t *testing.T, tr Interface, numPoints int) {
fmt.Printf("delete: ") fmt.Printf("delete: ")
lotsa.Ops(N, 1, func(i, _ int) { 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 { if tr.Len() != 0 {
t.Fatalf("expected %d, got %d", 0, tr.Len()) 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) { func testBoxesVarious(t *testing.T, tr Interface, boxes []tBox, label string) {
N := len(boxes) N := len(boxes)
// N := 10000
// boxes := randPoints(N)
///////////////////////////////////////// /////////////////////////////////////////
// insert // insert
///////////////////////////////////////// /////////////////////////////////////////
@ -143,10 +195,6 @@ func testBoxesVarious(t *testing.T, tr Interface, boxes []tBox, label string) {
if tr.Len() != N { if tr.Len() != N {
t.Fatalf("expected %d, got %d", N, tr.Len()) 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 // 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 { if tr.Len() != N {
t.Fatalf("expected %d, got %d", N, tr.Len()) 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 // 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). 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 ( import (
"github.com/tidwall/geojson/geometry" "github.com/tidwall/geojson/geometry"
"github.com/tidwall/rbang" "github.com/tidwall/rtree"
) )
type collection struct { type collection struct {
children []Object children []Object
extra *extra extra *extra
tree *rbang.RTree tree *rtree.RTree
prect geometry.Rect prect geometry.Rect
pempty bool pempty bool
} }
@ -303,7 +303,7 @@ func (g *collection) parseInitRectIndex(opts *ParseOptions) {
count++ count++
} }
if count > 0 && opts.IndexChildren != 0 && count >= opts.IndexChildren { if count > 0 && opts.IndexChildren != 0 && count >= opts.IndexChildren {
g.tree = new(rbang.RTree) g.tree = new(rtree.RTree)
for _, child := range g.children { for _, child := range g.children {
if child.Empty() { if child.Empty() {
continue 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 package geometry
import (
"math"
)
// Segment is a two point line // Segment is a two point line
type Segment struct { type Segment struct {
A, B Point 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) // 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 // IntersectsSegment detects if segment intersects with other segement
func (seg Segment) IntersectsSegment(other Segment) bool { func (seg Segment) IntersectsSegment(other Segment) bool {
a, b, c, d := seg.A, seg.B, other.A, other.B 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 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) [![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. This package provides an in-memory R-Tree implementation for Go. It's designed
It has support for 1-20 dimensions, and can store and search multidimensions interchangably in the same tree. for [Tile38](https://github.com/tidwall/tile38) and is optimized for fast rect
inserts and replacements.
Authors <img src="cities.png" width="512" height="256" border="0" alt="Cities">
-------
* 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 ## Usage
-------
RTree source code is available under the MIT License. ### 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 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/cities v0.1.0 h1:CVNkmMf7NEC9Bvokf5GoSsArHCKRMTgLuubRTHnH0mE=
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw= 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 package rtree
import ( import "github.com/tidwall/geoindex/child"
"math"
"sync"
"github.com/tidwall/rtree/base" const (
maxEntries = 32
minEntries = maxEntries * 20 / 100
) )
type Iterator func(item Item) bool type rect struct {
type Item interface { min, max [2]float64
Rect(ctx interface{}) (min []float64, max []float64) data interface{}
} }
type node struct {
count int
rects [maxEntries + 1]rect
}
// RTree ...
type RTree struct { type RTree struct {
dims int height int
maxEntries int root rect
ctx interface{} count int
trs []*base.RTree reinsert []rect
used int
} }
func New(ctx interface{}) *RTree { func (r *rect) expand(b *rect) {
tr := &RTree{ if b.min[0] < r.min[0] {
ctx: ctx, r.min[0] = b.min[0]
dims: 20, }
maxEntries: 13, 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) { func (r *rect) area() float64 {
if item == nil { return (r.max[0] - r.min[0]) * (r.max[1] - r.min[1])
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) { func (r *rect) overlapArea(b *rect) float64 {
if item == nil { area := 1.0
panic("nil item") 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 r.min[0] > b.min[0] {
if len(min) != len(max) { min = r.min[0]
return // just return } else {
panic("invalid item rectangle") min = b.min[0]
} }
if len(min) < 1 || len(min) > len(tr.trs) { if max > min {
return // just return area *= max - min
panic("invalid dimension") } else {
return 0
} }
btr := tr.trs[len(min)-1] if r.max[1] < b.max[1] {
if btr == nil { max = r.max[1]
return } else {
max = b.max[1]
} }
amin := make([]float64, len(min)) if r.min[1] > b.min[1] {
amax := make([]float64, len(max)) min = r.min[1]
for i := 0; i < len(min); i++ { } else {
amin[i], amax[i] = min[i], max[i] min = b.min[1]
} }
btr.Remove(amin, amax, item) if max > min {
if btr.IsEmpty() { area *= max - min
tr.trs[len(min)-1] = nil } else {
tr.used-- return 0
} }
} return area
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) { func (r *rect) enlargedArea(b *rect) float64 {
if bounds == nil { area := 1.0
panic("nil bounds being used for search") if b.max[0] > r.max[0] {
} if b.min[0] < r.min[0] {
min, max := bounds.Rect(tr.ctx) area *= b.max[0] - b.min[0]
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 { } else {
amin[i] = math.Inf(-1) area *= b.max[0] - r.min[0]
amax[i] = math.Inf(+1) }
} 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 if b.max[1] > r.max[1] {
btr.Search(amin, amax, func(item interface{}) bool { if b.min[1] < r.min[1] {
if !iter(item.(Item)) { area *= b.max[1] - b.min[1]
ended = true } else {
return false area *= b.max[1] - r.min[1]
} }
return true } else {
}) if b.min[1] < r.min[1] {
return !ended 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) { // Insert inserts an item into the RTree
if bounds == nil { func (tr *RTree) Insert(min, max [2]float64, value interface{}) {
panic("nil bounds being used for search") var item rect
} fit(min, max, value, &item)
min, max := bounds.Rect(tr.ctx) tr.insert(&item)
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 { func (tr *RTree) insert(item *rect) {
return if tr.root.data == nil {
fit(item.min, item.max, new(node), &tr.root)
} }
if tr.used == 1 { grown := tr.root.insert(item, tr.height)
for i, btr := range tr.trs { if grown {
if btr != nil { tr.root.expand(item)
knn(btr, min, max, center, i+1, func(item interface{}, dist float64) bool {
return iter(item.(Item), dist)
})
break
}
}
return
} }
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 { const inlineEnlargedArea = true
done bool
step int
item Item
dist float64
}
var mu sync.Mutex func (r *rect) chooseLeastEnlargement(b *rect) (index int) {
var ended bool n := r.data.(*node)
queues := make(map[int][]queueT) j, jenlargement, jarea := -1, 0.0, 0.0
cond := sync.NewCond(&mu) for i := 0; i < n.count; i++ {
for i, btr := range tr.trs { var earea float64
if btr != nil { if inlineEnlargedArea {
dims := i + 1 earea = 1.0
mu.Lock() if b.max[0] > n.rects[i].max[0] {
queues[dims] = []queueT{} if b.min[0] < n.rects[i].min[0] {
cond.Signal() earea *= b.max[0] - b.min[0]
mu.Unlock() } else {
go func(dims int, btr *base.RTree) { earea *= b.max[0] - n.rects[i].min[0]
knn(btr, min, max, center, dims, func(item interface{}, dist float64) bool { }
mu.Lock() } else {
if ended { if b.min[0] < n.rects[i].min[0] {
mu.Unlock() earea *= n.rects[i].max[0] - b.min[0]
return false } else {
} earea *= n.rects[i].max[0] - n.rects[i].min[0]
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 b.max[1] > n.rects[i].max[1] {
if !iter(minItem, minDist) { if b.min[1] < n.rects[i].min[1] {
ended = true earea *= b.max[1] - b.min[1]
break } 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 { } else {
amin[i] = math.Inf(-1) earea = n.rects[i].enlargedArea(b)
amax[i] = math.Inf(+1) }
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 return j
btr.KNN(amin, amax, center, func(item interface{}, dist float64) bool { }
if !iter(item.(Item), dist) {
ended = true func (r *rect) recalc() {
return false n := r.data.(*node)
} r.min = n.rects[0].min
return true r.max = n.rects[0].max
}) for i := 1; i < n.count; i++ {
return !ended 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 # github.com/tidwall/btree v0.3.0
## explicit ## explicit
github.com/tidwall/btree github.com/tidwall/btree
# github.com/tidwall/buntdb v1.1.8 # github.com/tidwall/buntdb v1.2.0
## explicit ## explicit
github.com/tidwall/buntdb github.com/tidwall/buntdb
# github.com/tidwall/cities v0.1.0 # github.com/tidwall/cities v0.1.0
## explicit
github.com/tidwall/cities github.com/tidwall/cities
# github.com/tidwall/geoindex v1.4.0 # github.com/tidwall/geoindex v1.4.1
## explicit ## explicit
github.com/tidwall/geoindex github.com/tidwall/geoindex
github.com/tidwall/geoindex/algo github.com/tidwall/geoindex/algo
github.com/tidwall/geoindex/child github.com/tidwall/geoindex/child
# github.com/tidwall/geojson v1.2.3 # github.com/tidwall/geojson v1.2.4
## explicit ## explicit
github.com/tidwall/geojson github.com/tidwall/geojson
github.com/tidwall/geojson/geo github.com/tidwall/geojson/geo
@ -146,9 +145,6 @@ github.com/tidwall/match
# github.com/tidwall/pretty v1.0.2 # github.com/tidwall/pretty v1.0.2
## explicit ## explicit
github.com/tidwall/pretty github.com/tidwall/pretty
# github.com/tidwall/rbang v1.2.4
## explicit
github.com/tidwall/rbang
# github.com/tidwall/redbench v0.1.0 # github.com/tidwall/redbench v0.1.0
## explicit ## explicit
github.com/tidwall/redbench github.com/tidwall/redbench
@ -161,9 +157,12 @@ github.com/tidwall/resp
# github.com/tidwall/rhh v1.1.1 # github.com/tidwall/rhh v1.1.1
## explicit ## explicit
github.com/tidwall/rhh 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
github.com/tidwall/rtree/base
# github.com/tidwall/sjson v1.1.5 # github.com/tidwall/sjson v1.1.5
## explicit ## explicit
github.com/tidwall/sjson github.com/tidwall/sjson