mirror of https://github.com/tidwall/tile38.git
Updated dependencies
This commit is contained in:
parent
d209edbd59
commit
60678020fa
9
go.mod
9
go.mod
|
@ -15,19 +15,18 @@ require (
|
||||||
github.com/peterh/liner v1.2.1
|
github.com/peterh/liner v1.2.1
|
||||||
github.com/streadway/amqp v1.0.0
|
github.com/streadway/amqp v1.0.0
|
||||||
github.com/tidwall/btree v0.3.0
|
github.com/tidwall/btree v0.3.0
|
||||||
github.com/tidwall/buntdb v1.1.8
|
github.com/tidwall/buntdb v1.2.0
|
||||||
github.com/tidwall/cities v0.1.0 // indirect
|
github.com/tidwall/geoindex v1.4.1
|
||||||
github.com/tidwall/geoindex v1.4.0
|
github.com/tidwall/geojson v1.2.4
|
||||||
github.com/tidwall/geojson v1.2.3
|
|
||||||
github.com/tidwall/gjson v1.6.8
|
github.com/tidwall/gjson v1.6.8
|
||||||
github.com/tidwall/lotsa v1.0.2 // indirect
|
github.com/tidwall/lotsa v1.0.2 // indirect
|
||||||
github.com/tidwall/match v1.0.3
|
github.com/tidwall/match v1.0.3
|
||||||
github.com/tidwall/pretty v1.0.2
|
github.com/tidwall/pretty v1.0.2
|
||||||
github.com/tidwall/rbang v1.2.4
|
|
||||||
github.com/tidwall/redbench v0.1.0
|
github.com/tidwall/redbench v0.1.0
|
||||||
github.com/tidwall/redcon v1.4.0
|
github.com/tidwall/redcon v1.4.0
|
||||||
github.com/tidwall/resp v0.1.0
|
github.com/tidwall/resp v0.1.0
|
||||||
github.com/tidwall/rhh v1.1.1
|
github.com/tidwall/rhh v1.1.1
|
||||||
|
github.com/tidwall/rtree v1.2.6
|
||||||
github.com/tidwall/sjson v1.1.5
|
github.com/tidwall/sjson v1.1.5
|
||||||
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da
|
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da
|
||||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||||
|
|
20
go.sum
20
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.2.2/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
||||||
github.com/tidwall/btree v0.3.0 h1:LcwmLI5kv+AaH/xnBgOuKfbu5eLBWVdWTpD2o+qSRdU=
|
github.com/tidwall/btree v0.3.0 h1:LcwmLI5kv+AaH/xnBgOuKfbu5eLBWVdWTpD2o+qSRdU=
|
||||||
github.com/tidwall/btree v0.3.0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
github.com/tidwall/btree v0.3.0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
||||||
github.com/tidwall/buntdb v1.1.8 h1:3rHHTlR4uFACKLbC+ws9jH0Ytk1Pd+n/cMEERtbMVKk=
|
github.com/tidwall/buntdb v1.2.0 h1:8KOzf5Gg97DoCMSOgcwZjnM0FfROtq0fcZkPW54oGKU=
|
||||||
github.com/tidwall/buntdb v1.1.8/go.mod h1:a/Xp8Hsllzxex0WTNdANz2+hOwGaYDHW4xBK9dMp30w=
|
github.com/tidwall/buntdb v1.2.0/go.mod h1:XLza/dhlwzO6dc5o/KWor4kfZSt3BP8QV+77ZMKfI58=
|
||||||
github.com/tidwall/cities v0.1.0 h1:CVNkmMf7NEC9Bvokf5GoSsArHCKRMTgLuubRTHnH0mE=
|
github.com/tidwall/cities v0.1.0 h1:CVNkmMf7NEC9Bvokf5GoSsArHCKRMTgLuubRTHnH0mE=
|
||||||
github.com/tidwall/cities v0.1.0/go.mod h1:lV/HDp2gCcRcHJWqgt6Di54GiDrTZwh1aG2ZUPNbqa4=
|
github.com/tidwall/cities v0.1.0/go.mod h1:lV/HDp2gCcRcHJWqgt6Di54GiDrTZwh1aG2ZUPNbqa4=
|
||||||
github.com/tidwall/geoindex v1.4.0 h1:6O8bwdRrNDwLzPSb6AhuyBW27PSa24Pa4I70fnoiBGI=
|
github.com/tidwall/geoindex v1.4.1 h1:dlhM+2isLqz8ndHn7vOCevD3IPH3XeO/BaFN4rLpimo=
|
||||||
github.com/tidwall/geoindex v1.4.0/go.mod h1:3gTa91BW+eiVIipuR6aU1Y9Sa0q75b1teE/NP2vfsTc=
|
github.com/tidwall/geoindex v1.4.1/go.mod h1:NQJQszWCH4+KlD0wY+mgQ2hK/GdSH+9+ZRknDY8bOHc=
|
||||||
github.com/tidwall/geojson v1.2.3 h1:z//3h73oC+EkQ1TQr0oLbk7fKtGqAYkhSILtFZi1j8w=
|
github.com/tidwall/geojson v1.2.4 h1:INKsEJULXKiKSuFQZQ7Vy3v9zchg0VPtcQl2+KeTlvc=
|
||||||
github.com/tidwall/geojson v1.2.3/go.mod h1:tBjfxeALRFLc25LLpjtWzy2nIrNmW1ze1EAhLtd8+QQ=
|
github.com/tidwall/geojson v1.2.4/go.mod h1:ZaA93utbJL8CLGaJ5L/M8gV/YC81lvW3ydzo5fI7yp0=
|
||||||
github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
|
github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
|
||||||
github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w=
|
github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w=
|
||||||
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
|
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
|
||||||
|
@ -209,8 +209,6 @@ github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
|
||||||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
|
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
|
||||||
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
github.com/tidwall/rbang v1.2.4 h1:A0kWtX9lKr9YzWkKzmVYZFqLyo0L6tlkpO+kswDWp7E=
|
|
||||||
github.com/tidwall/rbang v1.2.4/go.mod h1:aMGOM1Wj50tooEO/0aO9j+7gyHUs3bUW0t4Q+xiuOjg=
|
|
||||||
github.com/tidwall/redbench v0.1.0 h1:UZYUMhwMMObQRq5xU4SA3lmlJRztXzqtushDii+AmPo=
|
github.com/tidwall/redbench v0.1.0 h1:UZYUMhwMMObQRq5xU4SA3lmlJRztXzqtushDii+AmPo=
|
||||||
github.com/tidwall/redbench v0.1.0/go.mod h1:zxcRGCq/JcqV48YjK9WxBNJL7JSpMzbLXaHvMcnanKQ=
|
github.com/tidwall/redbench v0.1.0/go.mod h1:zxcRGCq/JcqV48YjK9WxBNJL7JSpMzbLXaHvMcnanKQ=
|
||||||
github.com/tidwall/redcon v1.4.0 h1:y2PmDD55STRdy4S98qP/Dn+gZG+cPVvIDi9BJV2aOwA=
|
github.com/tidwall/redcon v1.4.0 h1:y2PmDD55STRdy4S98qP/Dn+gZG+cPVvIDi9BJV2aOwA=
|
||||||
|
@ -219,8 +217,10 @@ github.com/tidwall/resp v0.1.0 h1:zZ6Hq+2cY4QqhZ4LqrV05T5yLOSPspj+l+DgAoJ25Ak=
|
||||||
github.com/tidwall/resp v0.1.0/go.mod h1:18xEj855iMY2bK6tNF2A4x+nZy5gWO1iO7OOl3jETKw=
|
github.com/tidwall/resp v0.1.0/go.mod h1:18xEj855iMY2bK6tNF2A4x+nZy5gWO1iO7OOl3jETKw=
|
||||||
github.com/tidwall/rhh v1.1.1 h1:8zDpMKcK1pA1zU+Jyuo1UdzTFvME8pH3Sx/MdYgM5sE=
|
github.com/tidwall/rhh v1.1.1 h1:8zDpMKcK1pA1zU+Jyuo1UdzTFvME8pH3Sx/MdYgM5sE=
|
||||||
github.com/tidwall/rhh v1.1.1/go.mod h1:DmqiIRtSnlVEi5CSKqNaX6m3YTa3YNSYrGB4FlfdLUU=
|
github.com/tidwall/rhh v1.1.1/go.mod h1:DmqiIRtSnlVEi5CSKqNaX6m3YTa3YNSYrGB4FlfdLUU=
|
||||||
github.com/tidwall/rtree v0.1.0 h1:w/RoiV3WvrKF/EzLaK6CT0MXAV6KveGy3EOyeJACoFA=
|
github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
|
||||||
github.com/tidwall/rtree v0.1.0/go.mod h1:xsujlpupsrYVnWaok1pqGtxhj9TKHSbMbY8eKPatQ4U=
|
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
|
||||||
|
github.com/tidwall/rtree v1.2.6 h1:Q4FhJZId5k22IYsZ55Bz8nNo4Z9LyyOzWb+WIwc1vdM=
|
||||||
|
github.com/tidwall/rtree v1.2.6/go.mod h1:fn56Cu3AyoR5U5LLQywyuTOCDF8Lq6GQCjLRSxnPAJI=
|
||||||
github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE=
|
github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE=
|
||||||
github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
|
github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
|
||||||
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
|
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/tidwall/geojson"
|
"github.com/tidwall/geojson"
|
||||||
"github.com/tidwall/geojson/geo"
|
"github.com/tidwall/geojson/geo"
|
||||||
"github.com/tidwall/geojson/geometry"
|
"github.com/tidwall/geojson/geometry"
|
||||||
"github.com/tidwall/rbang"
|
"github.com/tidwall/rtree"
|
||||||
"github.com/tidwall/tile38/internal/deadline"
|
"github.com/tidwall/tile38/internal/deadline"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ var counter uint64
|
||||||
func New() *Collection {
|
func New() *Collection {
|
||||||
col := &Collection{
|
col := &Collection{
|
||||||
items: btree.New(byID),
|
items: btree.New(byID),
|
||||||
index: geoindex.Wrap(&rbang.RTree{}),
|
index: geoindex.Wrap(&rtree.RTree{}),
|
||||||
values: btree.New(byValue),
|
values: btree.New(byValue),
|
||||||
fieldMap: make(map[string]int),
|
fieldMap: make(map[string]int),
|
||||||
fieldArr: make([]string, 0),
|
fieldArr: make([]string, 0),
|
||||||
|
|
|
@ -10,9 +10,9 @@ import (
|
||||||
"github.com/tidwall/btree"
|
"github.com/tidwall/btree"
|
||||||
"github.com/tidwall/geojson"
|
"github.com/tidwall/geojson"
|
||||||
"github.com/tidwall/geojson/geometry"
|
"github.com/tidwall/geojson/geometry"
|
||||||
"github.com/tidwall/rbang"
|
|
||||||
"github.com/tidwall/resp"
|
"github.com/tidwall/resp"
|
||||||
"github.com/tidwall/rhh"
|
"github.com/tidwall/rhh"
|
||||||
|
"github.com/tidwall/rtree"
|
||||||
"github.com/tidwall/tile38/internal/collection"
|
"github.com/tidwall/tile38/internal/collection"
|
||||||
"github.com/tidwall/tile38/internal/glob"
|
"github.com/tidwall/tile38/internal/glob"
|
||||||
)
|
)
|
||||||
|
@ -513,8 +513,8 @@ func (server *Server) cmdFlushDB(msg *Message) (res resp.Value, d commandDetails
|
||||||
server.expires = rhh.New(0)
|
server.expires = rhh.New(0)
|
||||||
server.hooks = make(map[string]*Hook)
|
server.hooks = make(map[string]*Hook)
|
||||||
server.hooksOut = make(map[string]*Hook)
|
server.hooksOut = make(map[string]*Hook)
|
||||||
server.hookTree = rbang.RTree{}
|
server.hookTree = rtree.RTree{}
|
||||||
server.hookCross = rbang.RTree{}
|
server.hookCross = rtree.RTree{}
|
||||||
d.command = "flushdb"
|
d.command = "flushdb"
|
||||||
d.updated = true
|
d.updated = true
|
||||||
d.timestamp = time.Now()
|
d.timestamp = time.Now()
|
||||||
|
|
|
@ -27,10 +27,10 @@ import (
|
||||||
"github.com/tidwall/geojson"
|
"github.com/tidwall/geojson"
|
||||||
"github.com/tidwall/geojson/geometry"
|
"github.com/tidwall/geojson/geometry"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
"github.com/tidwall/rbang"
|
|
||||||
"github.com/tidwall/redcon"
|
"github.com/tidwall/redcon"
|
||||||
"github.com/tidwall/resp"
|
"github.com/tidwall/resp"
|
||||||
"github.com/tidwall/rhh"
|
"github.com/tidwall/rhh"
|
||||||
|
"github.com/tidwall/rtree"
|
||||||
"github.com/tidwall/tile38/core"
|
"github.com/tidwall/tile38/core"
|
||||||
"github.com/tidwall/tile38/internal/collection"
|
"github.com/tidwall/tile38/internal/collection"
|
||||||
"github.com/tidwall/tile38/internal/deadline"
|
"github.com/tidwall/tile38/internal/deadline"
|
||||||
|
@ -117,8 +117,8 @@ type Server struct {
|
||||||
shrinking bool // aof shrinking flag
|
shrinking bool // aof shrinking flag
|
||||||
shrinklog [][]string // aof shrinking log
|
shrinklog [][]string // aof shrinking log
|
||||||
hooks map[string]*Hook // hook name
|
hooks map[string]*Hook // hook name
|
||||||
hookCross rbang.RTree // hook spatial tree for "cross" geofences
|
hookCross rtree.RTree // hook spatial tree for "cross" geofences
|
||||||
hookTree rbang.RTree // hook spatial tree for all
|
hookTree rtree.RTree // hook spatial tree for all
|
||||||
hooksOut map[string]*Hook // hooks with "outside" detection
|
hooksOut map[string]*Hook // hooks with "outside" detection
|
||||||
aofconnM map[net.Conn]bool
|
aofconnM map[net.Conn]bool
|
||||||
luascripts *lScriptMap
|
luascripts *lScriptMap
|
||||||
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
"github.com/tidwall/grect"
|
"github.com/tidwall/grect"
|
||||||
"github.com/tidwall/match"
|
"github.com/tidwall/match"
|
||||||
"github.com/tidwall/rtree"
|
"github.com/tidwall/rtred"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -248,7 +248,7 @@ func (db *DB) Load(rd io.Reader) error {
|
||||||
// b-tree/r-tree context for itself.
|
// b-tree/r-tree context for itself.
|
||||||
type index struct {
|
type index struct {
|
||||||
btr *btree.BTree // contains the items
|
btr *btree.BTree // contains the items
|
||||||
rtr *rtree.RTree // contains the items
|
rtr *rtred.RTree // contains the items
|
||||||
name string // name of the index
|
name string // name of the index
|
||||||
pattern string // a required key pattern
|
pattern string // a required key pattern
|
||||||
less func(a, b string) bool // less comparison function
|
less func(a, b string) bool // less comparison function
|
||||||
|
@ -289,7 +289,7 @@ func (idx *index) clearCopy() *index {
|
||||||
nidx.btr = btree.New(lessCtx(nidx))
|
nidx.btr = btree.New(lessCtx(nidx))
|
||||||
}
|
}
|
||||||
if nidx.rect != nil {
|
if nidx.rect != nil {
|
||||||
nidx.rtr = rtree.New(nidx)
|
nidx.rtr = rtred.New(nidx)
|
||||||
}
|
}
|
||||||
return nidx
|
return nidx
|
||||||
}
|
}
|
||||||
|
@ -301,7 +301,7 @@ func (idx *index) rebuild() {
|
||||||
idx.btr = btree.New(lessCtx(idx))
|
idx.btr = btree.New(lessCtx(idx))
|
||||||
}
|
}
|
||||||
if idx.rect != nil {
|
if idx.rect != nil {
|
||||||
idx.rtr = rtree.New(idx)
|
idx.rtr = rtred.New(idx)
|
||||||
}
|
}
|
||||||
// iterate through all keys and fill the index
|
// iterate through all keys and fill the index
|
||||||
btreeAscend(idx.db.keys, func(item interface{}) bool {
|
btreeAscend(idx.db.keys, func(item interface{}) bool {
|
||||||
|
@ -1824,7 +1824,7 @@ func (tx *Tx) Nearby(index, bounds string,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// // wrap a rtree specific iterator around the user-defined iterator.
|
// // wrap a rtree specific iterator around the user-defined iterator.
|
||||||
iter := func(item rtree.Item, dist float64) bool {
|
iter := func(item rtred.Item, dist float64) bool {
|
||||||
dbi := item.(*dbItem)
|
dbi := item.(*dbItem)
|
||||||
return iterator(dbi.key, dbi.val, dist)
|
return iterator(dbi.key, dbi.val, dist)
|
||||||
}
|
}
|
||||||
|
@ -1862,7 +1862,7 @@ func (tx *Tx) Intersects(index, bounds string,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// wrap a rtree specific iterator around the user-defined iterator.
|
// wrap a rtree specific iterator around the user-defined iterator.
|
||||||
iter := func(item rtree.Item) bool {
|
iter := func(item rtred.Item) bool {
|
||||||
dbi := item.(*dbItem)
|
dbi := item.(*dbItem)
|
||||||
return iterator(dbi.key, dbi.val)
|
return iterator(dbi.key, dbi.val)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ go 1.15
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/tidwall/btree v0.3.0
|
github.com/tidwall/btree v0.3.0
|
||||||
github.com/tidwall/gjson v1.6.7
|
github.com/tidwall/gjson v1.6.8
|
||||||
github.com/tidwall/grect v0.1.0
|
github.com/tidwall/grect v0.1.0
|
||||||
github.com/tidwall/match v1.0.3
|
github.com/tidwall/match v1.0.3
|
||||||
github.com/tidwall/rtree v0.1.0
|
github.com/tidwall/rtred v0.1.2
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
github.com/tidwall/btree v0.3.0 h1:LcwmLI5kv+AaH/xnBgOuKfbu5eLBWVdWTpD2o+qSRdU=
|
github.com/tidwall/btree v0.3.0 h1:LcwmLI5kv+AaH/xnBgOuKfbu5eLBWVdWTpD2o+qSRdU=
|
||||||
github.com/tidwall/btree v0.3.0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
github.com/tidwall/btree v0.3.0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
||||||
github.com/tidwall/gjson v1.6.7 h1:Mb1M9HZCRWEcXQ8ieJo7auYyyiSux6w9XN3AdTpxJrE=
|
|
||||||
github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
|
github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
|
||||||
|
github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w=
|
||||||
|
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
|
||||||
github.com/tidwall/grect v0.1.0 h1:ICcKWD5uu5A5fmxApGIa0QRvfGnSWKRd07POT08CQSA=
|
github.com/tidwall/grect v0.1.0 h1:ICcKWD5uu5A5fmxApGIa0QRvfGnSWKRd07POT08CQSA=
|
||||||
github.com/tidwall/grect v0.1.0/go.mod h1:sa5O42oP6jWfTShL9ka6Sgmg3TgIK649veZe05B7+J8=
|
github.com/tidwall/grect v0.1.0/go.mod h1:sa5O42oP6jWfTShL9ka6Sgmg3TgIK649veZe05B7+J8=
|
||||||
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
|
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
|
||||||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
|
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
|
||||||
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
github.com/tidwall/rtree v0.1.0 h1:w/RoiV3WvrKF/EzLaK6CT0MXAV6KveGy3EOyeJACoFA=
|
github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
|
||||||
github.com/tidwall/rtree v0.1.0/go.mod h1:xsujlpupsrYVnWaok1pqGtxhj9TKHSbMbY8eKPatQ4U=
|
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
|
||||||
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
|
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
|
||||||
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
|
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
|
||||||
|
|
|
@ -134,60 +134,6 @@ func (index *Index) Bounds() (min, max [2]float64) {
|
||||||
return index.tree.Bounds()
|
return index.tree.Bounds()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Priority Queue ordered by dist (smallest to largest)
|
|
||||||
|
|
||||||
type qnode struct {
|
|
||||||
dist float64
|
|
||||||
child child.Child
|
|
||||||
}
|
|
||||||
|
|
||||||
type queue struct {
|
|
||||||
nodes []qnode
|
|
||||||
len int
|
|
||||||
size int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *queue) push(node qnode) {
|
|
||||||
if q.nodes == nil {
|
|
||||||
q.nodes = make([]qnode, 2)
|
|
||||||
} else {
|
|
||||||
q.nodes = append(q.nodes, qnode{})
|
|
||||||
}
|
|
||||||
i := q.len + 1
|
|
||||||
j := i / 2
|
|
||||||
for i > 1 && q.nodes[j].dist > node.dist {
|
|
||||||
q.nodes[i] = q.nodes[j]
|
|
||||||
i = j
|
|
||||||
j = j / 2
|
|
||||||
}
|
|
||||||
q.nodes[i] = node
|
|
||||||
q.len++
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *queue) pop() (qnode, bool) {
|
|
||||||
if q.len == 0 {
|
|
||||||
return qnode{}, false
|
|
||||||
}
|
|
||||||
n := q.nodes[1]
|
|
||||||
q.nodes[1] = q.nodes[q.len]
|
|
||||||
q.len--
|
|
||||||
var j, k int
|
|
||||||
i := 1
|
|
||||||
for i != q.len+1 {
|
|
||||||
k = q.len + 1
|
|
||||||
j = 2 * i
|
|
||||||
if j <= q.len && q.nodes[j].dist < q.nodes[k].dist {
|
|
||||||
k = j
|
|
||||||
}
|
|
||||||
if j+1 <= q.len && q.nodes[j+1].dist < q.nodes[k].dist {
|
|
||||||
k = j + 1
|
|
||||||
}
|
|
||||||
q.nodes[i] = q.nodes[k]
|
|
||||||
i = k
|
|
||||||
}
|
|
||||||
return n, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan iterates through all data in tree in no specified order.
|
// Scan iterates through all data in tree in no specified order.
|
||||||
func (index *Index) Scan(
|
func (index *Index) Scan(
|
||||||
iter func(min, max [2]float64, data interface{}) bool,
|
iter func(min, max [2]float64, data interface{}) bool,
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
|
@ -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=
|
|
@ -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
|
||||||
|
}
|
|
@ -58,7 +58,7 @@ import (
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
var Tests = struct {
|
var Tests = struct {
|
||||||
TestBenchVarious func(t *testing.T, tr Interface, numPoints int)
|
TestBenchVarious func(t *testing.T, tr Interface, numPointOrRects int)
|
||||||
TestRandomPoints func(t *testing.T, tr Interface, numPoints int)
|
TestRandomPoints func(t *testing.T, tr Interface, numPoints int)
|
||||||
TestRandomRects func(t *testing.T, tr Interface, numRects int)
|
TestRandomRects func(t *testing.T, tr Interface, numRects int)
|
||||||
TestCitiesSVG func(t *testing.T, tr Interface)
|
TestCitiesSVG func(t *testing.T, tr Interface)
|
||||||
|
@ -77,30 +77,85 @@ var Tests = struct {
|
||||||
benchmarkRandomInsert,
|
benchmarkRandomInsert,
|
||||||
}
|
}
|
||||||
|
|
||||||
func benchVarious(t *testing.T, tr Interface, numPoints int) {
|
type rect struct {
|
||||||
N := numPoints
|
min, max [2]float64
|
||||||
rand.Seed(time.Now().UnixNano())
|
}
|
||||||
points := make([][2]float64, N)
|
|
||||||
for i := 0; i < N; i++ {
|
// kind = 'r','p','m' for rect,point,mixed
|
||||||
points[i][0] = rand.Float64()*360 - 180
|
func randRect(kind byte) (r rect) {
|
||||||
points[i][1] = rand.Float64()*180 - 90
|
r.min[0] = rand.Float64()*360 - 180
|
||||||
|
r.min[1] = rand.Float64()*180 - 90
|
||||||
|
r.max = r.min
|
||||||
|
return randRectOffset(r, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
func randRectOffset(r rect, kind byte) rect {
|
||||||
|
rsize := 0.01 // size of rectangle in degrees
|
||||||
|
pr := r
|
||||||
|
for {
|
||||||
|
r.min[0] = (pr.max[0]+pr.min[0])/2 + rand.Float64()*rsize - rsize/2
|
||||||
|
r.min[1] = (pr.max[1]+pr.min[1])/2 + rand.Float64()*rsize - rsize/2
|
||||||
|
r.max = r.min
|
||||||
|
if kind == 'r' || (kind == 'm' && rand.Int()%2 == 0) {
|
||||||
|
// rect
|
||||||
|
r.max[0] = r.min[0] + rand.Float64()*rsize
|
||||||
|
r.max[1] = r.min[1] + rand.Float64()*rsize
|
||||||
|
} else {
|
||||||
|
// point
|
||||||
|
r.max = r.min
|
||||||
}
|
}
|
||||||
pointsReplace := make([][2]float64, N)
|
if r.min[0] < -180 || r.min[1] < -90 ||
|
||||||
|
r.max[0] > 180 || r.max[1] > 90 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mixedTree interface {
|
||||||
|
IsMixedTree() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchVarious(t *testing.T, tr Interface, numPointOrRects int) {
|
||||||
|
if v, ok := tr.(mixedTree); ok && v.IsMixedTree() {
|
||||||
|
println("== points ==")
|
||||||
|
benchVariousKind(t, tr, numPointOrRects, 'p')
|
||||||
|
println("== rects ==")
|
||||||
|
benchVariousKind(t, tr, numPointOrRects, 'r')
|
||||||
|
println("== mixed (50/50) ==")
|
||||||
|
benchVariousKind(t, tr, numPointOrRects, 'm')
|
||||||
|
} else {
|
||||||
|
benchVariousKind(t, tr, numPointOrRects, 'm')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchVariousKind(t *testing.T, tr Interface, numPointOrRects int,
|
||||||
|
kind byte,
|
||||||
|
) {
|
||||||
|
N := numPointOrRects
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
rects := make([]rect, N)
|
||||||
for i := 0; i < N; i++ {
|
for i := 0; i < N; i++ {
|
||||||
pointsReplace[i][0] = rand.Float64()*360 - 180
|
rects[i] = randRect(kind)
|
||||||
pointsReplace[i][1] = rand.Float64()*180 - 90
|
}
|
||||||
|
rectsReplace := make([]rect, N)
|
||||||
|
for i := 0; i < N; i++ {
|
||||||
|
rectsReplace[i] = randRectOffset(rects[i], kind)
|
||||||
}
|
}
|
||||||
lotsa.Output = os.Stdout
|
lotsa.Output = os.Stdout
|
||||||
fmt.Printf("insert: ")
|
fmt.Printf("insert: ")
|
||||||
lotsa.Ops(N, 1, func(i, _ int) {
|
lotsa.Ops(N, 1, func(i, _ int) {
|
||||||
tr.Insert(points[i], points[i], i)
|
tr.Insert(rects[i].min, rects[i].max, i)
|
||||||
})
|
})
|
||||||
fmt.Printf("search: ")
|
fmt.Printf("search: ")
|
||||||
var count int
|
var count int
|
||||||
lotsa.Ops(N, 1, func(i, _ int) {
|
lotsa.Ops(N, 1, func(i, _ int) {
|
||||||
tr.Search(points[i], points[i],
|
tr.Search(rects[i].min, rects[i].max,
|
||||||
func(min, max [2]float64, value interface{}) bool {
|
func(min, max [2]float64, value interface{}) bool {
|
||||||
|
if value.(int) == i {
|
||||||
count++
|
count++
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -111,8 +166,8 @@ func benchVarious(t *testing.T, tr Interface, numPoints int) {
|
||||||
fmt.Printf("replace: ")
|
fmt.Printf("replace: ")
|
||||||
lotsa.Ops(N, 1, func(i, _ int) {
|
lotsa.Ops(N, 1, func(i, _ int) {
|
||||||
tr.Replace(
|
tr.Replace(
|
||||||
points[i], points[i], i,
|
rects[i].min, rects[i].max, i,
|
||||||
pointsReplace[i], pointsReplace[i], i,
|
rectsReplace[i].min, rectsReplace[i].max, i,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
if tr.Len() != N {
|
if tr.Len() != N {
|
||||||
|
@ -121,7 +176,7 @@ func benchVarious(t *testing.T, tr Interface, numPoints int) {
|
||||||
|
|
||||||
fmt.Printf("delete: ")
|
fmt.Printf("delete: ")
|
||||||
lotsa.Ops(N, 1, func(i, _ int) {
|
lotsa.Ops(N, 1, func(i, _ int) {
|
||||||
tr.Delete(pointsReplace[i], pointsReplace[i], i)
|
tr.Delete(rectsReplace[i].min, rectsReplace[i].max, i)
|
||||||
})
|
})
|
||||||
if tr.Len() != 0 {
|
if tr.Len() != 0 {
|
||||||
t.Fatalf("expected %d, got %d", 0, tr.Len())
|
t.Fatalf("expected %d, got %d", 0, tr.Len())
|
||||||
|
@ -131,9 +186,6 @@ func benchVarious(t *testing.T, tr Interface, numPoints int) {
|
||||||
func testBoxesVarious(t *testing.T, tr Interface, boxes []tBox, label string) {
|
func testBoxesVarious(t *testing.T, tr Interface, boxes []tBox, label string) {
|
||||||
N := len(boxes)
|
N := len(boxes)
|
||||||
|
|
||||||
// N := 10000
|
|
||||||
// boxes := randPoints(N)
|
|
||||||
|
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
// insert
|
// insert
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
|
@ -143,10 +195,6 @@ func testBoxesVarious(t *testing.T, tr Interface, boxes []tBox, label string) {
|
||||||
if tr.Len() != N {
|
if tr.Len() != N {
|
||||||
t.Fatalf("expected %d, got %d", N, tr.Len())
|
t.Fatalf("expected %d, got %d", N, tr.Len())
|
||||||
}
|
}
|
||||||
// area := tr.TotalOverlapArea()
|
|
||||||
// fmt.Printf("overlap: %.0f, %.1f/item\n", area, area/float64(N))
|
|
||||||
|
|
||||||
// ioutil.WriteFile(label+".svg", []byte(rtreetools.SVG(&tr)), 0600)
|
|
||||||
|
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
// scan all items and count one-by-one
|
// scan all items and count one-by-one
|
||||||
|
@ -269,8 +317,6 @@ func testBoxesVarious(t *testing.T, tr Interface, boxes []tBox, label string) {
|
||||||
if tr.Len() != N {
|
if tr.Len() != N {
|
||||||
t.Fatalf("expected %d, got %d", N, tr.Len())
|
t.Fatalf("expected %d, got %d", N, tr.Len())
|
||||||
}
|
}
|
||||||
// area = tr.TotalOverlapArea()
|
|
||||||
// fmt.Fprintf(wr, "overlap: %.0f, %.1f/item\n", area, area/float64(N))
|
|
||||||
|
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
// check every point for correctness
|
// check every point for correctness
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
language: go
|
|
|
@ -1,6 +1,6 @@
|
||||||
# `GeoJSON`
|
# GeoJSON
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/tidwall/geojson.svg?branch=master)](https://travis-ci.org/tidwall/geojson) [![GoDoc](https://godoc.org/github.com/tidwall/geojson?status.svg)](https://godoc.org/github.com/tidwall/geojson)
|
[![GoDoc](https://godoc.org/github.com/tidwall/geojson?status.svg)](https://godoc.org/github.com/tidwall/geojson)
|
||||||
|
|
||||||
This package provides GeoJSON utilties for Go. It's designed for [Tile38](https://github.com/tidwall/tile38).
|
This package provides GeoJSON utilties for Go. It's designed for [Tile38](https://github.com/tidwall/tile38).
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,13 @@ package geojson
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/tidwall/geojson/geometry"
|
"github.com/tidwall/geojson/geometry"
|
||||||
"github.com/tidwall/rbang"
|
"github.com/tidwall/rtree"
|
||||||
)
|
)
|
||||||
|
|
||||||
type collection struct {
|
type collection struct {
|
||||||
children []Object
|
children []Object
|
||||||
extra *extra
|
extra *extra
|
||||||
tree *rbang.RTree
|
tree *rtree.RTree
|
||||||
prect geometry.Rect
|
prect geometry.Rect
|
||||||
pempty bool
|
pempty bool
|
||||||
}
|
}
|
||||||
|
@ -303,7 +303,7 @@ func (g *collection) parseInitRectIndex(opts *ParseOptions) {
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
if count > 0 && opts.IndexChildren != 0 && count >= opts.IndexChildren {
|
if count > 0 && opts.IndexChildren != 0 && count >= opts.IndexChildren {
|
||||||
g.tree = new(rbang.RTree)
|
g.tree = new(rtree.RTree)
|
||||||
for _, child := range g.children {
|
for _, child := range g.children {
|
||||||
if child.Empty() {
|
if child.Empty() {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -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}
|
||||||
|
}
|
|
@ -4,10 +4,6 @@
|
||||||
|
|
||||||
package geometry
|
package geometry
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Segment is a two point line
|
// Segment is a two point line
|
||||||
type Segment struct {
|
type Segment struct {
|
||||||
A, B Point
|
A, B Point
|
||||||
|
@ -53,102 +49,6 @@ func (seg Segment) ContainsPoint(point Point) bool {
|
||||||
// return math.Atan2(seg.B.Y-seg.A.Y, seg.B.X-seg.A.X)
|
// return math.Atan2(seg.B.Y-seg.A.Y, seg.B.X-seg.A.X)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// RaycastResult holds the results of the Raycast operation
|
|
||||||
type RaycastResult struct {
|
|
||||||
In bool // point on the left
|
|
||||||
On bool // point is directly on top of
|
|
||||||
}
|
|
||||||
|
|
||||||
// Raycast performs the raycast operation
|
|
||||||
func (seg Segment) Raycast(point Point) RaycastResult {
|
|
||||||
|
|
||||||
p, a, b := point, seg.A, seg.B
|
|
||||||
// make sure that the point is inside the segment bounds
|
|
||||||
if a.Y < b.Y && (p.Y < a.Y || p.Y > b.Y) {
|
|
||||||
return RaycastResult{false, false}
|
|
||||||
} else if a.Y > b.Y && (p.Y < b.Y || p.Y > a.Y) {
|
|
||||||
return RaycastResult{false, false}
|
|
||||||
}
|
|
||||||
|
|
||||||
// test if point is in on the segment
|
|
||||||
if a.Y == b.Y {
|
|
||||||
if a.X == b.X {
|
|
||||||
if p == a {
|
|
||||||
return RaycastResult{false, true}
|
|
||||||
}
|
|
||||||
return RaycastResult{false, false}
|
|
||||||
}
|
|
||||||
if p.Y == b.Y {
|
|
||||||
// horizontal segment
|
|
||||||
// check if the point in on the line
|
|
||||||
if a.X < b.X {
|
|
||||||
if p.X >= a.X && p.X <= b.X {
|
|
||||||
return RaycastResult{false, true}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if p.X >= b.X && p.X <= a.X {
|
|
||||||
return RaycastResult{false, true}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if a.X == b.X && p.X == b.X {
|
|
||||||
// vertical segment
|
|
||||||
// check if the point in on the line
|
|
||||||
if a.Y < b.Y {
|
|
||||||
if p.Y >= a.Y && p.Y <= b.Y {
|
|
||||||
return RaycastResult{false, true}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if p.Y >= b.Y && p.Y <= a.Y {
|
|
||||||
return RaycastResult{false, true}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (p.X-a.X)/(b.X-a.X) == (p.Y-a.Y)/(b.Y-a.Y) {
|
|
||||||
return RaycastResult{false, true}
|
|
||||||
}
|
|
||||||
|
|
||||||
// do the actual raycast here.
|
|
||||||
for p.Y == a.Y || p.Y == b.Y {
|
|
||||||
p.Y = math.Nextafter(p.Y, math.Inf(1))
|
|
||||||
}
|
|
||||||
if a.Y < b.Y {
|
|
||||||
if p.Y < a.Y || p.Y > b.Y {
|
|
||||||
return RaycastResult{false, false}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if p.Y < b.Y || p.Y > a.Y {
|
|
||||||
return RaycastResult{false, false}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if a.X > b.X {
|
|
||||||
if p.X >= a.X {
|
|
||||||
return RaycastResult{false, false}
|
|
||||||
}
|
|
||||||
if p.X <= b.X {
|
|
||||||
return RaycastResult{true, false}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if p.X >= b.X {
|
|
||||||
return RaycastResult{false, false}
|
|
||||||
}
|
|
||||||
if p.X <= a.X {
|
|
||||||
return RaycastResult{true, false}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if a.Y < b.Y {
|
|
||||||
if (p.Y-a.Y)/(p.X-a.X) >= (b.Y-a.Y)/(b.X-a.X) {
|
|
||||||
return RaycastResult{true, false}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (p.Y-b.Y)/(p.X-b.X) >= (a.Y-b.Y)/(a.X-b.X) {
|
|
||||||
return RaycastResult{true, false}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return RaycastResult{false, false}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntersectsSegment detects if segment intersects with other segement
|
// IntersectsSegment detects if segment intersects with other segement
|
||||||
func (seg Segment) IntersectsSegment(other Segment) bool {
|
func (seg Segment) IntersectsSegment(other Segment) bool {
|
||||||
a, b, c, d := seg.A, seg.B, other.A, other.B
|
a, b, c, d := seg.A, seg.B, other.A, other.B
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
|
@ -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=
|
|
@ -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.
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
2
vendor/github.com/tidwall/rbang/LICENSE → vendor/github.com/tidwall/rtred/LICENSE
generated
vendored
2
vendor/github.com/tidwall/rbang/LICENSE → vendor/github.com/tidwall/rtred/LICENSE
generated
vendored
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2018 Josh Baker
|
Copyright (c) 2016 Josh Baker
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
|
@ -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.
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
module github.com/tidwall/rtred
|
||||||
|
|
||||||
|
go 1.15
|
||||||
|
|
||||||
|
require github.com/tidwall/tinyqueue v0.1.1
|
|
@ -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=
|
|
@ -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
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2016 Josh Baker
|
Copyright (c) 2021 Josh Baker
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
@ -1,21 +1,78 @@
|
||||||
RTree implementation for Go
|
# rtree
|
||||||
===========================
|
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/tidwall/rtree?status.svg)](https://godoc.org/github.com/tidwall/rtree)
|
[![GoDoc](https://godoc.org/github.com/tidwall/rtree?status.svg)](https://godoc.org/github.com/tidwall/rtree)
|
||||||
|
|
||||||
This package provides an in-memory R-Tree implementation for Go, useful as a spatial data structure.
|
This package provides an in-memory R-Tree implementation for Go. It's designed
|
||||||
It has support for 1-20 dimensions, and can store and search multidimensions interchangably in the same tree.
|
for [Tile38](https://github.com/tidwall/tile38) and is optimized for fast rect
|
||||||
|
inserts and replacements.
|
||||||
|
|
||||||
Authors
|
<img src="cities.png" width="512" height="256" border="0" alt="Cities">
|
||||||
-------
|
|
||||||
* 1983 Original algorithm and test code by Antonin Guttman and Michael Stonebraker, UC Berkely
|
|
||||||
* 1994 ANCI C ported from original test code by Melinda Green
|
|
||||||
* 1995 Sphere volume fix for degeneracy problem submitted by Paul Brook
|
|
||||||
* 2004 Templated C++ port by Greg Douglas
|
|
||||||
* 2016 Go port by Josh Baker
|
|
||||||
* 2018 Added kNN and merged in some of the RBush logic by Vladimir Agafonkin
|
|
||||||
|
|
||||||
License
|
## Usage
|
||||||
-------
|
|
||||||
RTree source code is available under the MIT License.
|
### Installing
|
||||||
|
|
||||||
|
To start using rtree, install Go and run `go get`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ go get -u github.com/tidwall/rtree
|
||||||
|
```
|
||||||
|
|
||||||
|
### Basic operations
|
||||||
|
|
||||||
|
```go
|
||||||
|
// create a 2D RTree
|
||||||
|
var tr rtree.RTree
|
||||||
|
|
||||||
|
// insert a point
|
||||||
|
tr.Insert([2]float64{-112.0078, 33.4373}, [2]float64{-112.0078, 33.4373}, "PHX")
|
||||||
|
|
||||||
|
// insert a rect
|
||||||
|
tr.Insert([2]float64{10, 10}, [2]float64{20, 20}, "rect")
|
||||||
|
|
||||||
|
// search
|
||||||
|
tr.Search([2]float64{-112.1, 33.4}, [2]float64{-112.0, 33.5},
|
||||||
|
func(min, max [2]float64, value interface{}) bool {
|
||||||
|
println(value.(string)) // prints "PHX"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// delete
|
||||||
|
tr.Delete([2]float64{-112.0078, 33.4373}, [2]float64{-112.0078, 33.4373}, "PHX")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Algorithms
|
||||||
|
|
||||||
|
This implementation is a variant of the original paper:
|
||||||
|
[R-TREES. A DYNAMIC INDEX STRUCTURE FOR SPATIAL SEARCHING](http://www-db.deis.unibo.it/courses/SI-LS/papers/Gut84.pdf)
|
||||||
|
|
||||||
|
### Inserting
|
||||||
|
|
||||||
|
Same as the original algorithm. From the root to the leaf, the rects which will incur the least enlargment are chosen. Ties go to rects with the smallest area.
|
||||||
|
|
||||||
|
### Deleting
|
||||||
|
|
||||||
|
Same as the original algorithm. A target rect is deleted directly. When the number of children in a rect falls below it's minumum entries, it is removed from the tree and it's items are re-inserted.
|
||||||
|
|
||||||
|
### Splitting
|
||||||
|
|
||||||
|
This is a custom algorithm.
|
||||||
|
It attempts to minimize intensive operations such as pre-sorting the children and comparing overlaps & area sizes.
|
||||||
|
The desire is to do simple single axis distance calculations each child only once, with a target 50/50 chance that the child might be moved in-memory.
|
||||||
|
|
||||||
|
When a rect has reached it's max number of entries it's largest axis is calculated and the rect is split into two smaller rects, named `left` and `right`.
|
||||||
|
Each child rects is then evaluated to determine which smaller rect it should be placed into.
|
||||||
|
Two values, `min-dist` and `max-dist`, are calcuated for each child.
|
||||||
|
|
||||||
|
- `min-dist` is the distance from the parent's minumum value of it's largest axis to the child's minumum value of the parent largest axis.
|
||||||
|
- `max-dist` is the distance from the parent's maximum value of it's largest axis to the child's maximum value of the parent largest axis.
|
||||||
|
|
||||||
|
When the `min-dist` is less than `max-dist` then the child is placed into the `left` rect.
|
||||||
|
When the `max-dist` is less than `min-dist` then the child is placed into the `right` rect.
|
||||||
|
When the `min-dist` is equal to `max-dist` then the child is placed into an `equal` bucket until all of the children are evaluated.
|
||||||
|
Each `equal` rect is then one-by-one placed in either `left` or `right`, whichever has less children.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
rtree source code is available under the MIT License.
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 336 KiB After Width: | Height: | Size: 336 KiB |
|
@ -2,4 +2,4 @@ module github.com/tidwall/rtree
|
||||||
|
|
||||||
go 1.15
|
go 1.15
|
||||||
|
|
||||||
require github.com/tidwall/tinyqueue v0.1.1
|
require github.com/tidwall/geoindex v1.4.1
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
|
github.com/tidwall/cities v0.1.0 h1:CVNkmMf7NEC9Bvokf5GoSsArHCKRMTgLuubRTHnH0mE=
|
||||||
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
|
github.com/tidwall/cities v0.1.0/go.mod h1:lV/HDp2gCcRcHJWqgt6Di54GiDrTZwh1aG2ZUPNbqa4=
|
||||||
|
github.com/tidwall/geoindex v1.4.1 h1:dlhM+2isLqz8ndHn7vOCevD3IPH3XeO/BaFN4rLpimo=
|
||||||
|
github.com/tidwall/geoindex v1.4.1/go.mod h1:NQJQszWCH4+KlD0wY+mgQ2hK/GdSH+9+ZRknDY8bOHc=
|
||||||
|
github.com/tidwall/lotsa v1.0.1 h1:w4gpDvI7RdkgbMC0q5ndKqG2ffrwCgerUY/gM2TYkH4=
|
||||||
|
github.com/tidwall/lotsa v1.0.1/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=
|
||||||
|
|
|
@ -1,278 +1,562 @@
|
||||||
|
// Copyright 2021 Joshua J Baker. All rights reserved.
|
||||||
|
// Use of this source code is governed by an MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package rtree
|
package rtree
|
||||||
|
|
||||||
import (
|
import "github.com/tidwall/geoindex/child"
|
||||||
"math"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/tidwall/rtree/base"
|
const (
|
||||||
|
maxEntries = 32
|
||||||
|
minEntries = maxEntries * 20 / 100
|
||||||
)
|
)
|
||||||
|
|
||||||
type Iterator func(item Item) bool
|
type rect struct {
|
||||||
type Item interface {
|
min, max [2]float64
|
||||||
Rect(ctx interface{}) (min []float64, max []float64)
|
data interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type node struct {
|
||||||
|
count int
|
||||||
|
rects [maxEntries + 1]rect
|
||||||
|
}
|
||||||
|
|
||||||
|
// RTree ...
|
||||||
type RTree struct {
|
type RTree struct {
|
||||||
dims int
|
height int
|
||||||
maxEntries int
|
root rect
|
||||||
ctx interface{}
|
count int
|
||||||
trs []*base.RTree
|
reinsert []rect
|
||||||
used int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx interface{}) *RTree {
|
func (r *rect) expand(b *rect) {
|
||||||
tr := &RTree{
|
if b.min[0] < r.min[0] {
|
||||||
ctx: ctx,
|
r.min[0] = b.min[0]
|
||||||
dims: 20,
|
}
|
||||||
maxEntries: 13,
|
if b.max[0] > r.max[0] {
|
||||||
|
r.max[0] = b.max[0]
|
||||||
|
}
|
||||||
|
if b.min[1] < r.min[1] {
|
||||||
|
r.min[1] = b.min[1]
|
||||||
|
}
|
||||||
|
if b.max[1] > r.max[1] {
|
||||||
|
r.max[1] = b.max[1]
|
||||||
}
|
}
|
||||||
tr.trs = make([]*base.RTree, 20)
|
|
||||||
return tr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tr *RTree) Insert(item Item) {
|
func (r *rect) area() float64 {
|
||||||
if item == nil {
|
return (r.max[0] - r.min[0]) * (r.max[1] - r.min[1])
|
||||||
panic("nil item")
|
|
||||||
}
|
|
||||||
min, max := item.Rect(tr.ctx)
|
|
||||||
if len(min) != len(max) {
|
|
||||||
return // just return
|
|
||||||
panic("invalid item rectangle")
|
|
||||||
}
|
|
||||||
if len(min) < 1 || len(min) > len(tr.trs) {
|
|
||||||
return // just return
|
|
||||||
panic("invalid dimension")
|
|
||||||
}
|
|
||||||
btr := tr.trs[len(min)-1]
|
|
||||||
if btr == nil {
|
|
||||||
btr = base.New(len(min), tr.maxEntries)
|
|
||||||
tr.trs[len(min)-1] = btr
|
|
||||||
tr.used++
|
|
||||||
}
|
|
||||||
amin := make([]float64, len(min))
|
|
||||||
amax := make([]float64, len(max))
|
|
||||||
for i := 0; i < len(min); i++ {
|
|
||||||
amin[i], amax[i] = min[i], max[i]
|
|
||||||
}
|
|
||||||
btr.Insert(amin, amax, item)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tr *RTree) Remove(item Item) {
|
func (r *rect) overlapArea(b *rect) float64 {
|
||||||
if item == nil {
|
area := 1.0
|
||||||
panic("nil item")
|
var max, min float64
|
||||||
}
|
if r.max[0] < b.max[0] {
|
||||||
min, max := item.Rect(tr.ctx)
|
max = r.max[0]
|
||||||
if len(min) != len(max) {
|
|
||||||
return // just return
|
|
||||||
panic("invalid item rectangle")
|
|
||||||
}
|
|
||||||
if len(min) < 1 || len(min) > len(tr.trs) {
|
|
||||||
return // just return
|
|
||||||
panic("invalid dimension")
|
|
||||||
}
|
|
||||||
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 {
|
} else {
|
||||||
amin[i] = math.Inf(-1)
|
max = b.max[0]
|
||||||
amax[i] = math.Inf(+1)
|
|
||||||
}
|
}
|
||||||
|
if r.min[0] > b.min[0] {
|
||||||
|
min = r.min[0]
|
||||||
|
} else {
|
||||||
|
min = b.min[0]
|
||||||
}
|
}
|
||||||
var ended bool
|
if max > min {
|
||||||
btr.Search(amin, amax, func(item interface{}) bool {
|
area *= max - min
|
||||||
if !iter(item.(Item)) {
|
} else {
|
||||||
ended = true
|
return 0
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
return true
|
if r.max[1] < b.max[1] {
|
||||||
})
|
max = r.max[1]
|
||||||
return !ended
|
} 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 (tr *RTree) KNN(bounds Item, center bool, iter func(item Item, dist float64) bool) {
|
func (r *rect) enlargedArea(b *rect) float64 {
|
||||||
if bounds == nil {
|
area := 1.0
|
||||||
panic("nil bounds being used for search")
|
if b.max[0] > r.max[0] {
|
||||||
|
if b.min[0] < r.min[0] {
|
||||||
|
area *= b.max[0] - b.min[0]
|
||||||
|
} else {
|
||||||
|
area *= b.max[0] - r.min[0]
|
||||||
}
|
}
|
||||||
min, max := bounds.Rect(tr.ctx)
|
} else {
|
||||||
if len(min) != len(max) {
|
if b.min[0] < r.min[0] {
|
||||||
return // just return
|
area *= r.max[0] - b.min[0]
|
||||||
panic("invalid item rectangle")
|
} else {
|
||||||
|
area *= r.max[0] - r.min[0]
|
||||||
}
|
}
|
||||||
if len(min) < 1 || len(min) > len(tr.trs) {
|
|
||||||
return // just return
|
|
||||||
panic("invalid dimension")
|
|
||||||
}
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
if tr.used == 0 {
|
// Insert inserts an item into the RTree
|
||||||
return
|
func (tr *RTree) Insert(min, max [2]float64, value interface{}) {
|
||||||
}
|
var item rect
|
||||||
if tr.used == 1 {
|
fit(min, max, value, &item)
|
||||||
for i, btr := range tr.trs {
|
tr.insert(&item)
|
||||||
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 {
|
func (tr *RTree) insert(item *rect) {
|
||||||
done bool
|
if tr.root.data == nil {
|
||||||
step int
|
fit(item.min, item.max, new(node), &tr.root)
|
||||||
item Item
|
|
||||||
dist float64
|
|
||||||
}
|
}
|
||||||
|
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++
|
||||||
|
}
|
||||||
|
|
||||||
var mu sync.Mutex
|
const inlineEnlargedArea = true
|
||||||
var ended bool
|
|
||||||
queues := make(map[int][]queueT)
|
func (r *rect) chooseLeastEnlargement(b *rect) (index int) {
|
||||||
cond := sync.NewCond(&mu)
|
n := r.data.(*node)
|
||||||
for i, btr := range tr.trs {
|
j, jenlargement, jarea := -1, 0.0, 0.0
|
||||||
if btr != nil {
|
for i := 0; i < n.count; i++ {
|
||||||
dims := i + 1
|
var earea float64
|
||||||
mu.Lock()
|
if inlineEnlargedArea {
|
||||||
queues[dims] = []queueT{}
|
earea = 1.0
|
||||||
cond.Signal()
|
if b.max[0] > n.rects[i].max[0] {
|
||||||
mu.Unlock()
|
if b.min[0] < n.rects[i].min[0] {
|
||||||
go func(dims int, btr *base.RTree) {
|
earea *= b.max[0] - b.min[0]
|
||||||
knn(btr, min, max, center, dims, func(item interface{}, dist float64) bool {
|
} else {
|
||||||
mu.Lock()
|
earea *= b.max[0] - n.rects[i].min[0]
|
||||||
if ended {
|
}
|
||||||
mu.Unlock()
|
} 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 false
|
||||||
}
|
}
|
||||||
queues[dims] = append(queues[dims], queueT{item: item.(Item), dist: dist})
|
|
||||||
cond.Signal()
|
|
||||||
mu.Unlock()
|
|
||||||
return true
|
return true
|
||||||
})
|
}
|
||||||
mu.Lock()
|
|
||||||
queues[dims] = append(queues[dims], queueT{done: true})
|
func (r *rect) largestAxis() (axis int, size float64) {
|
||||||
cond.Signal()
|
if r.max[1]-r.min[1] > r.max[0]-r.min[0] {
|
||||||
mu.Unlock()
|
return 1, r.max[1] - r.min[1]
|
||||||
}(dims, btr)
|
}
|
||||||
|
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--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mu.Lock()
|
for _, b := range equals {
|
||||||
for {
|
if leftNode.count < rightNode.count {
|
||||||
ready := true
|
leftNode.rects[leftNode.count] = b
|
||||||
for i := range queues {
|
leftNode.count++
|
||||||
if len(queues[i]) == 0 {
|
} else {
|
||||||
ready = false
|
rightNode.rects[rightNode.count] = b
|
||||||
break
|
rightNode.count++
|
||||||
}
|
|
||||||
if queues[i][0].done {
|
|
||||||
delete(queues, i)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(queues) == 0 {
|
left.recalc()
|
||||||
break
|
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
|
||||||
}
|
}
|
||||||
if ready {
|
|
||||||
var j int
|
// choose subtree
|
||||||
var minDist float64
|
index := -1
|
||||||
var minItem Item
|
narea := 0.0
|
||||||
var minQueue int
|
// first take a quick look for any nodes that contain the rect
|
||||||
for i := range queues {
|
for i := 0; i < n.count; i++ {
|
||||||
if j == 0 || queues[i][0].dist < minDist {
|
if n.rects[i].contains(item) {
|
||||||
minDist = queues[i][0].dist
|
area := n.rects[i].area()
|
||||||
minItem = queues[i][0].item
|
if index == -1 || area < narea {
|
||||||
minQueue = i
|
narea = area
|
||||||
|
index = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
queues[minQueue] = queues[minQueue][1:]
|
|
||||||
if !iter(minItem, minDist) {
|
|
||||||
ended = true
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
// 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
|
continue
|
||||||
}
|
}
|
||||||
cond.Wait()
|
removed, recalced = rects[i].delete(tr, item, height-1)
|
||||||
|
if !removed {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
mu.Unlock()
|
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
|
||||||
}
|
}
|
||||||
func knn(btr *base.RTree, min, max []float64, center bool, dims int, iter func(item interface{}, dist float64) bool) bool {
|
|
||||||
amin := make([]float64, dims)
|
// flatten all leaf rects into a single list
|
||||||
amax := make([]float64, dims)
|
func (r *rect) flatten(all []rect, height int) []rect {
|
||||||
for i := 0; i < dims; i++ {
|
n := r.data.(*node)
|
||||||
if i < len(min) {
|
if height == 0 {
|
||||||
amin[i] = min[i]
|
all = append(all, n.rects[:n.count]...)
|
||||||
amax[i] = max[i]
|
|
||||||
} else {
|
} else {
|
||||||
amin[i] = math.Inf(-1)
|
for i := 0; i < n.count; i++ {
|
||||||
amax[i] = math.Inf(+1)
|
all = n.rects[i].flatten(all, height-1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var ended bool
|
return all
|
||||||
btr.KNN(amin, amax, center, func(item interface{}, dist float64) bool {
|
}
|
||||||
if !iter(item.(Item), dist) {
|
|
||||||
ended = true
|
// onedge returns true when b is on the edge of r
|
||||||
return false
|
func (r *rect) onEdge(b *rect) bool {
|
||||||
}
|
if r.min[0] == b.min[0] || r.max[0] == b.max[0] {
|
||||||
return true
|
return true
|
||||||
})
|
}
|
||||||
return !ended
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,18 +117,17 @@ github.com/streadway/amqp
|
||||||
# github.com/tidwall/btree v0.3.0
|
# github.com/tidwall/btree v0.3.0
|
||||||
## explicit
|
## explicit
|
||||||
github.com/tidwall/btree
|
github.com/tidwall/btree
|
||||||
# github.com/tidwall/buntdb v1.1.8
|
# github.com/tidwall/buntdb v1.2.0
|
||||||
## explicit
|
## explicit
|
||||||
github.com/tidwall/buntdb
|
github.com/tidwall/buntdb
|
||||||
# github.com/tidwall/cities v0.1.0
|
# github.com/tidwall/cities v0.1.0
|
||||||
## explicit
|
|
||||||
github.com/tidwall/cities
|
github.com/tidwall/cities
|
||||||
# github.com/tidwall/geoindex v1.4.0
|
# github.com/tidwall/geoindex v1.4.1
|
||||||
## explicit
|
## explicit
|
||||||
github.com/tidwall/geoindex
|
github.com/tidwall/geoindex
|
||||||
github.com/tidwall/geoindex/algo
|
github.com/tidwall/geoindex/algo
|
||||||
github.com/tidwall/geoindex/child
|
github.com/tidwall/geoindex/child
|
||||||
# github.com/tidwall/geojson v1.2.3
|
# github.com/tidwall/geojson v1.2.4
|
||||||
## explicit
|
## explicit
|
||||||
github.com/tidwall/geojson
|
github.com/tidwall/geojson
|
||||||
github.com/tidwall/geojson/geo
|
github.com/tidwall/geojson/geo
|
||||||
|
@ -146,9 +145,6 @@ github.com/tidwall/match
|
||||||
# github.com/tidwall/pretty v1.0.2
|
# github.com/tidwall/pretty v1.0.2
|
||||||
## explicit
|
## explicit
|
||||||
github.com/tidwall/pretty
|
github.com/tidwall/pretty
|
||||||
# github.com/tidwall/rbang v1.2.4
|
|
||||||
## explicit
|
|
||||||
github.com/tidwall/rbang
|
|
||||||
# github.com/tidwall/redbench v0.1.0
|
# github.com/tidwall/redbench v0.1.0
|
||||||
## explicit
|
## explicit
|
||||||
github.com/tidwall/redbench
|
github.com/tidwall/redbench
|
||||||
|
@ -161,9 +157,12 @@ github.com/tidwall/resp
|
||||||
# github.com/tidwall/rhh v1.1.1
|
# github.com/tidwall/rhh v1.1.1
|
||||||
## explicit
|
## explicit
|
||||||
github.com/tidwall/rhh
|
github.com/tidwall/rhh
|
||||||
# github.com/tidwall/rtree v0.1.0
|
# github.com/tidwall/rtred v0.1.2
|
||||||
|
github.com/tidwall/rtred
|
||||||
|
github.com/tidwall/rtred/base
|
||||||
|
# github.com/tidwall/rtree v1.2.6
|
||||||
|
## explicit
|
||||||
github.com/tidwall/rtree
|
github.com/tidwall/rtree
|
||||||
github.com/tidwall/rtree/base
|
|
||||||
# github.com/tidwall/sjson v1.1.5
|
# github.com/tidwall/sjson v1.1.5
|
||||||
## explicit
|
## explicit
|
||||||
github.com/tidwall/sjson
|
github.com/tidwall/sjson
|
||||||
|
|
Loading…
Reference in New Issue