From 60678020fa38d0d1cc5182d88ce24a3b4141b025 Mon Sep 17 00:00:00 2001 From: tidwall Date: Sun, 7 Feb 2021 17:54:56 -0700 Subject: [PATCH] Updated dependencies --- go.mod | 9 +- go.sum | 20 +- internal/collection/collection.go | 4 +- internal/server/crud.go | 6 +- internal/server/server.go | 6 +- vendor/github.com/tidwall/buntdb/buntdb.go | 12 +- vendor/github.com/tidwall/buntdb/go.mod | 4 +- vendor/github.com/tidwall/buntdb/go.sum | 7 +- .../github.com/tidwall/geoindex/geoindex.go | 54 -- vendor/github.com/tidwall/geoindex/go.mod | 8 + vendor/github.com/tidwall/geoindex/go.sum | 4 + vendor/github.com/tidwall/geoindex/queue.go | 53 ++ vendor/github.com/tidwall/geoindex/tests.go | 98 ++- vendor/github.com/tidwall/geojson/.travis.yml | 1 - vendor/github.com/tidwall/geojson/README.md | 4 +- .../github.com/tidwall/geojson/collection.go | 6 +- .../tidwall/geojson/geometry/raycast.go | 99 +++ .../tidwall/geojson/geometry/segment.go | 100 --- vendor/github.com/tidwall/geojson/go.mod | 11 + vendor/github.com/tidwall/geojson/go.sum | 15 + vendor/github.com/tidwall/rbang/README.md | 78 -- vendor/github.com/tidwall/rbang/rbang.go | 586 -------------- .../tidwall/{rbang => rtred}/LICENSE | 4 +- vendor/github.com/tidwall/rtred/README.md | 21 + .../tidwall/{rtree => rtred}/base/knn.go | 0 .../tidwall/{rtree => rtred}/base/load.go | 0 .../tidwall/{rtree => rtred}/base/rtree.go | 0 vendor/github.com/tidwall/rtred/go.mod | 5 + vendor/github.com/tidwall/rtred/go.sum | 2 + vendor/github.com/tidwall/rtred/rtree.go | 278 +++++++ vendor/github.com/tidwall/rtree/LICENSE | 4 +- vendor/github.com/tidwall/rtree/README.md | 87 +- .../tidwall/{rbang => rtree}/cities.png | Bin vendor/github.com/tidwall/rtree/go.mod | 2 +- vendor/github.com/tidwall/rtree/go.sum | 8 +- vendor/github.com/tidwall/rtree/rtree.go | 758 ++++++++++++------ vendor/modules.txt | 17 +- 37 files changed, 1219 insertions(+), 1152 deletions(-) create mode 100644 vendor/github.com/tidwall/geoindex/go.mod create mode 100644 vendor/github.com/tidwall/geoindex/go.sum create mode 100644 vendor/github.com/tidwall/geoindex/queue.go delete mode 100644 vendor/github.com/tidwall/geojson/.travis.yml create mode 100644 vendor/github.com/tidwall/geojson/geometry/raycast.go create mode 100644 vendor/github.com/tidwall/geojson/go.mod create mode 100644 vendor/github.com/tidwall/geojson/go.sum delete mode 100644 vendor/github.com/tidwall/rbang/README.md delete mode 100644 vendor/github.com/tidwall/rbang/rbang.go rename vendor/github.com/tidwall/{rbang => rtred}/LICENSE (95%) create mode 100644 vendor/github.com/tidwall/rtred/README.md rename vendor/github.com/tidwall/{rtree => rtred}/base/knn.go (100%) rename vendor/github.com/tidwall/{rtree => rtred}/base/load.go (100%) rename vendor/github.com/tidwall/{rtree => rtred}/base/rtree.go (100%) create mode 100644 vendor/github.com/tidwall/rtred/go.mod create mode 100644 vendor/github.com/tidwall/rtred/go.sum create mode 100644 vendor/github.com/tidwall/rtred/rtree.go rename vendor/github.com/tidwall/{rbang => rtree}/cities.png (100%) diff --git a/go.mod b/go.mod index 49cdf230..d4b5ceb7 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index ad045d5e..96cbeac4 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/collection/collection.go b/internal/collection/collection.go index 32f54aed..f5b55683 100644 --- a/internal/collection/collection.go +++ b/internal/collection/collection.go @@ -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), diff --git a/internal/server/crud.go b/internal/server/crud.go index c9997095..e7b076d5 100644 --- a/internal/server/crud.go +++ b/internal/server/crud.go @@ -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() diff --git a/internal/server/server.go b/internal/server/server.go index 9e8348fc..e6b28a22 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -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 diff --git a/vendor/github.com/tidwall/buntdb/buntdb.go b/vendor/github.com/tidwall/buntdb/buntdb.go index 37ac20f2..c47c3a0e 100644 --- a/vendor/github.com/tidwall/buntdb/buntdb.go +++ b/vendor/github.com/tidwall/buntdb/buntdb.go @@ -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) } diff --git a/vendor/github.com/tidwall/buntdb/go.mod b/vendor/github.com/tidwall/buntdb/go.mod index 316d2f93..cc39e696 100644 --- a/vendor/github.com/tidwall/buntdb/go.mod +++ b/vendor/github.com/tidwall/buntdb/go.mod @@ -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 ) diff --git a/vendor/github.com/tidwall/buntdb/go.sum b/vendor/github.com/tidwall/buntdb/go.sum index 129bdca5..d00c00d0 100644 --- a/vendor/github.com/tidwall/buntdb/go.sum +++ b/vendor/github.com/tidwall/buntdb/go.sum @@ -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= diff --git a/vendor/github.com/tidwall/geoindex/geoindex.go b/vendor/github.com/tidwall/geoindex/geoindex.go index 067e5e12..e811a9d6 100644 --- a/vendor/github.com/tidwall/geoindex/geoindex.go +++ b/vendor/github.com/tidwall/geoindex/geoindex.go @@ -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, diff --git a/vendor/github.com/tidwall/geoindex/go.mod b/vendor/github.com/tidwall/geoindex/go.mod new file mode 100644 index 00000000..16629d7b --- /dev/null +++ b/vendor/github.com/tidwall/geoindex/go.mod @@ -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 +) diff --git a/vendor/github.com/tidwall/geoindex/go.sum b/vendor/github.com/tidwall/geoindex/go.sum new file mode 100644 index 00000000..0b7284c6 --- /dev/null +++ b/vendor/github.com/tidwall/geoindex/go.sum @@ -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= diff --git a/vendor/github.com/tidwall/geoindex/queue.go b/vendor/github.com/tidwall/geoindex/queue.go new file mode 100644 index 00000000..11c3eaa4 --- /dev/null +++ b/vendor/github.com/tidwall/geoindex/queue.go @@ -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 +} diff --git a/vendor/github.com/tidwall/geoindex/tests.go b/vendor/github.com/tidwall/geoindex/tests.go index bbc5a496..782586db 100644 --- a/vendor/github.com/tidwall/geoindex/tests.go +++ b/vendor/github.com/tidwall/geoindex/tests.go @@ -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 diff --git a/vendor/github.com/tidwall/geojson/.travis.yml b/vendor/github.com/tidwall/geojson/.travis.yml deleted file mode 100644 index 4f2ee4d9..00000000 --- a/vendor/github.com/tidwall/geojson/.travis.yml +++ /dev/null @@ -1 +0,0 @@ -language: go diff --git a/vendor/github.com/tidwall/geojson/README.md b/vendor/github.com/tidwall/geojson/README.md index 352f1c7d..8deeb86d 100644 --- a/vendor/github.com/tidwall/geojson/README.md +++ b/vendor/github.com/tidwall/geojson/README.md @@ -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). diff --git a/vendor/github.com/tidwall/geojson/collection.go b/vendor/github.com/tidwall/geojson/collection.go index f65339d6..d8b2bc80 100644 --- a/vendor/github.com/tidwall/geojson/collection.go +++ b/vendor/github.com/tidwall/geojson/collection.go @@ -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 diff --git a/vendor/github.com/tidwall/geojson/geometry/raycast.go b/vendor/github.com/tidwall/geojson/geometry/raycast.go new file mode 100644 index 00000000..ce29493d --- /dev/null +++ b/vendor/github.com/tidwall/geojson/geometry/raycast.go @@ -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} +} diff --git a/vendor/github.com/tidwall/geojson/geometry/segment.go b/vendor/github.com/tidwall/geojson/geometry/segment.go index 1153e7a3..d8684107 100644 --- a/vendor/github.com/tidwall/geojson/geometry/segment.go +++ b/vendor/github.com/tidwall/geojson/geometry/segment.go @@ -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 diff --git a/vendor/github.com/tidwall/geojson/go.mod b/vendor/github.com/tidwall/geojson/go.mod new file mode 100644 index 00000000..0c5cd67a --- /dev/null +++ b/vendor/github.com/tidwall/geojson/go.mod @@ -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 +) diff --git a/vendor/github.com/tidwall/geojson/go.sum b/vendor/github.com/tidwall/geojson/go.sum new file mode 100644 index 00000000..ddd169ce --- /dev/null +++ b/vendor/github.com/tidwall/geojson/go.sum @@ -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= diff --git a/vendor/github.com/tidwall/rbang/README.md b/vendor/github.com/tidwall/rbang/README.md deleted file mode 100644 index 4ff26e4c..00000000 --- a/vendor/github.com/tidwall/rbang/README.md +++ /dev/null @@ -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. - -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. - diff --git a/vendor/github.com/tidwall/rbang/rbang.go b/vendor/github.com/tidwall/rbang/rbang.go deleted file mode 100644 index a3c34e7d..00000000 --- a/vendor/github.com/tidwall/rbang/rbang.go +++ /dev/null @@ -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) -} diff --git a/vendor/github.com/tidwall/rbang/LICENSE b/vendor/github.com/tidwall/rtred/LICENSE similarity index 95% rename from vendor/github.com/tidwall/rbang/LICENSE rename to vendor/github.com/tidwall/rtred/LICENSE index 3a6c0261..1a6cb670 100644 --- a/vendor/github.com/tidwall/rbang/LICENSE +++ b/vendor/github.com/tidwall/rtred/LICENSE @@ -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. \ No newline at end of file +THE SOFTWARE. diff --git a/vendor/github.com/tidwall/rtred/README.md b/vendor/github.com/tidwall/rtred/README.md new file mode 100644 index 00000000..361d0412 --- /dev/null +++ b/vendor/github.com/tidwall/rtred/README.md @@ -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. + diff --git a/vendor/github.com/tidwall/rtree/base/knn.go b/vendor/github.com/tidwall/rtred/base/knn.go similarity index 100% rename from vendor/github.com/tidwall/rtree/base/knn.go rename to vendor/github.com/tidwall/rtred/base/knn.go diff --git a/vendor/github.com/tidwall/rtree/base/load.go b/vendor/github.com/tidwall/rtred/base/load.go similarity index 100% rename from vendor/github.com/tidwall/rtree/base/load.go rename to vendor/github.com/tidwall/rtred/base/load.go diff --git a/vendor/github.com/tidwall/rtree/base/rtree.go b/vendor/github.com/tidwall/rtred/base/rtree.go similarity index 100% rename from vendor/github.com/tidwall/rtree/base/rtree.go rename to vendor/github.com/tidwall/rtred/base/rtree.go diff --git a/vendor/github.com/tidwall/rtred/go.mod b/vendor/github.com/tidwall/rtred/go.mod new file mode 100644 index 00000000..9fa4967c --- /dev/null +++ b/vendor/github.com/tidwall/rtred/go.mod @@ -0,0 +1,5 @@ +module github.com/tidwall/rtred + +go 1.15 + +require github.com/tidwall/tinyqueue v0.1.1 diff --git a/vendor/github.com/tidwall/rtred/go.sum b/vendor/github.com/tidwall/rtred/go.sum new file mode 100644 index 00000000..0fbfb4da --- /dev/null +++ b/vendor/github.com/tidwall/rtred/go.sum @@ -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= diff --git a/vendor/github.com/tidwall/rtred/rtree.go b/vendor/github.com/tidwall/rtred/rtree.go new file mode 100644 index 00000000..89522cac --- /dev/null +++ b/vendor/github.com/tidwall/rtred/rtree.go @@ -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 +} diff --git a/vendor/github.com/tidwall/rtree/LICENSE b/vendor/github.com/tidwall/rtree/LICENSE index 1a6cb670..2c52f1dc 100644 --- a/vendor/github.com/tidwall/rtree/LICENSE +++ b/vendor/github.com/tidwall/rtree/LICENSE @@ -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. \ No newline at end of file diff --git a/vendor/github.com/tidwall/rtree/README.md b/vendor/github.com/tidwall/rtree/README.md index 361d0412..92b6e895 100644 --- a/vendor/github.com/tidwall/rtree/README.md +++ b/vendor/github.com/tidwall/rtree/README.md @@ -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 +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. diff --git a/vendor/github.com/tidwall/rbang/cities.png b/vendor/github.com/tidwall/rtree/cities.png similarity index 100% rename from vendor/github.com/tidwall/rbang/cities.png rename to vendor/github.com/tidwall/rtree/cities.png diff --git a/vendor/github.com/tidwall/rtree/go.mod b/vendor/github.com/tidwall/rtree/go.mod index 3b8434a6..feef5de8 100644 --- a/vendor/github.com/tidwall/rtree/go.mod +++ b/vendor/github.com/tidwall/rtree/go.mod @@ -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 diff --git a/vendor/github.com/tidwall/rtree/go.sum b/vendor/github.com/tidwall/rtree/go.sum index 0fbfb4da..906e5d35 100644 --- a/vendor/github.com/tidwall/rtree/go.sum +++ b/vendor/github.com/tidwall/rtree/go.sum @@ -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= diff --git a/vendor/github.com/tidwall/rtree/rtree.go b/vendor/github.com/tidwall/rtree/rtree.go index bbbb1ffe..9d458c79 100644 --- a/vendor/github.com/tidwall/rtree/rtree.go +++ b/vendor/github.com/tidwall/rtree/rtree.go @@ -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) } diff --git a/vendor/modules.txt b/vendor/modules.txt index ad3aa48b..016fc0b3 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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