diff --git a/go.mod b/go.mod
index 58c07631..f40f8c65 100644
--- a/go.mod
+++ b/go.mod
@@ -14,18 +14,17 @@ 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/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 f42efd49..c93b13b7 100644
--- a/go.sum
+++ b/go.sum
@@ -119,14 +119,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=
@@ -139,8 +139,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=
@@ -149,8 +147,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.
-
-<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.
-
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
+<img src="cities.png" width="512" height="256" border="0" alt="Cities">
 
-License
--------
-RTree source code is available under the MIT License.
+## Usage
+
+### Installing
+
+To start using rtree, install Go and run `go get`:
+
+```sh
+$ go get -u github.com/tidwall/rtree
+```
+
+### Basic operations
+
+```go
+// create a 2D RTree
+var tr rtree.RTree
+
+// insert a point
+tr.Insert([2]float64{-112.0078, 33.4373}, [2]float64{-112.0078, 33.4373}, "PHX")
+
+// insert a rect
+tr.Insert([2]float64{10, 10}, [2]float64{20, 20}, "rect")
+
+// search 
+tr.Search([2]float64{-112.1, 33.4}, [2]float64{-112.0, 33.5}, 
+ 	func(min, max [2]float64, value interface{}) bool {
+		println(value.(string)) // prints "PHX"
+	},
+)
+
+// delete 
+tr.Delete([2]float64{-112.0078, 33.4373}, [2]float64{-112.0078, 33.4373}, "PHX")
+```
+
+## Algorithms
+
+This implementation is a variant of the original paper:  
+[R-TREES. A DYNAMIC INDEX STRUCTURE FOR SPATIAL SEARCHING](http://www-db.deis.unibo.it/courses/SI-LS/papers/Gut84.pdf)
+
+### Inserting
+
+Same as the original algorithm. From the root to the leaf, the rects which will incur the least enlargment are chosen. Ties go to rects with the smallest area.
+
+### Deleting
+
+Same as the original algorithm. A target rect is deleted directly. When the number of children in a rect falls below it's minumum entries, it is removed from the tree and it's items are re-inserted.
+
+### Splitting
+
+This is a custom algorithm.
+It attempts to minimize intensive operations such as pre-sorting the children and comparing overlaps & area sizes.
+The desire is to do simple single axis distance calculations each child only once, with a target 50/50 chance that the child might be moved in-memory.
+
+When a rect has reached it's max number of entries it's largest axis is calculated and the rect is split into two smaller rects, named `left` and `right`.
+Each child rects is then evaluated to determine which smaller rect it should be placed into.
+Two values, `min-dist` and `max-dist`, are calcuated for each child. 
+
+- `min-dist` is the distance from the parent's minumum value of it's largest axis to the child's minumum value of the parent largest axis.
+- `max-dist` is the distance from the parent's maximum value of it's largest axis to the child's maximum value of the parent largest axis.
+
+When the `min-dist` is less than `max-dist` then the child is placed into the `left` rect. 
+When the `max-dist` is less than `min-dist` then the child is placed into the `right` rect. 
+When the `min-dist` is equal to `max-dist` then the child is placed into an `equal` bucket until all of the children are evaluated.
+Each `equal` rect is then one-by-one placed in either `left` or `right`, whichever has less children.
+
+## License
+
+rtree source code is available under the MIT License.
 
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