Z optimized, fixed #61

This commit is contained in:
Josh Baker 2016-10-03 11:37:16 -07:00
parent 46072f614f
commit c425d76374
12 changed files with 281 additions and 254 deletions

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
set -e set -e
VERSION="1.4.2" VERSION="1.5.0"
PROTECTED_MODE="no" PROTECTED_MODE="no"
# Hardcode some values to the core package # Hardcode some values to the core package

View File

@ -36,13 +36,13 @@ func (i *itemT) Less(item btree.Item, ctx interface{}) bool {
} }
} }
func (i *itemT) Rect() (minX, minY, maxX, maxY float64) { func (i *itemT) Rect() (minX, minY, minZ, maxX, maxY, maxZ float64) {
bbox := i.object.CalculatedBBox() bbox := i.object.CalculatedBBox()
return bbox.Min.X, bbox.Min.Y, bbox.Max.X, bbox.Max.Y return bbox.Min.X, bbox.Min.Y, bbox.Min.Z, bbox.Max.X, bbox.Max.Y, bbox.Max.Z
} }
func (i *itemT) Point() (x, y float64) { func (i *itemT) Point() (x, y, z float64) {
x, y, _, _ = i.Rect() x, y, z, _, _, _ = i.Rect()
return return
} }
@ -87,7 +87,7 @@ func (c *Collection) TotalWeight() int {
} }
// Bounds returns the bounds of all the items in the collection. // Bounds returns the bounds of all the items in the collection.
func (c *Collection) Bounds() (minX, minY, maxX, maxY float64) { func (c *Collection) Bounds() (minX, minY, minZ, maxX, maxY, maxZ float64) {
return c.index.Bounds() return c.index.Bounds()
} }
@ -333,7 +333,7 @@ func (c *Collection) ScanGreaterOrEqual(id string, cursor uint64, desc bool,
} }
func (c *Collection) geoSearch(cursor uint64, bbox geojson.BBox, iterator func(id string, obj geojson.Object, fields []float64) bool) (ncursor uint64) { func (c *Collection) geoSearch(cursor uint64, bbox geojson.BBox, iterator func(id string, obj geojson.Object, fields []float64) bool) (ncursor uint64) {
return c.index.Search(cursor, bbox.Min.Y, bbox.Min.X, bbox.Max.Y, bbox.Max.X, func(item index.Item) bool { return c.index.Search(cursor, bbox.Min.Y, bbox.Min.X, bbox.Max.Y, bbox.Max.X, bbox.Min.Z, bbox.Max.Z, func(item index.Item) bool {
var iitm *itemT var iitm *itemT
iitm, ok := item.(*itemT) iitm, ok := item.(*itemT)
if !ok { if !ok {
@ -347,12 +347,13 @@ func (c *Collection) geoSearch(cursor uint64, bbox geojson.BBox, iterator func(i
} }
// Nearby returns all object that are nearby a point. // Nearby returns all object that are nearby a point.
func (c *Collection) Nearby(cursor uint64, sparse uint8, lat, lon, meters float64, iterator func(id string, obj geojson.Object, fields []float64) bool) (ncursor uint64) { func (c *Collection) Nearby(cursor uint64, sparse uint8, lat, lon, meters, minZ, maxZ float64, iterator func(id string, obj geojson.Object, fields []float64) bool) (ncursor uint64) {
center := geojson.Position{X: lon, Y: lat, Z: 0} center := geojson.Position{X: lon, Y: lat, Z: 0}
bbox := geojson.BBoxesFromCenter(lat, lon, meters) bbox := geojson.BBoxesFromCenter(lat, lon, meters)
bboxes := bbox.Sparse(sparse) bboxes := bbox.Sparse(sparse)
if sparse > 0 { if sparse > 0 {
for _, bbox := range bboxes { for _, bbox := range bboxes {
bbox.Min.Z, bbox.Max.Z = minZ, maxZ
c.geoSearch(cursor, bbox, func(id string, obj geojson.Object, fields []float64) bool { c.geoSearch(cursor, bbox, func(id string, obj geojson.Object, fields []float64) bool {
if obj.Nearby(center, meters) { if obj.Nearby(center, meters) {
if iterator(id, obj, fields) { if iterator(id, obj, fields) {
@ -364,6 +365,7 @@ func (c *Collection) Nearby(cursor uint64, sparse uint8, lat, lon, meters float6
} }
return 0 return 0
} }
bbox.Min.Z, bbox.Max.Z = minZ, maxZ
return c.geoSearch(cursor, bbox, func(id string, obj geojson.Object, fields []float64) bool { return c.geoSearch(cursor, bbox, func(id string, obj geojson.Object, fields []float64) bool {
if obj.Nearby(center, meters) { if obj.Nearby(center, meters) {
return iterator(id, obj, fields) return iterator(id, obj, fields)
@ -373,12 +375,12 @@ func (c *Collection) Nearby(cursor uint64, sparse uint8, lat, lon, meters float6
} }
// Within returns all object that are fully contained within an object or bounding box. Set obj to nil in order to use the bounding box. // Within returns all object that are fully contained within an object or bounding box. Set obj to nil in order to use the bounding box.
func (c *Collection) Within(cursor uint64, sparse uint8, obj geojson.Object, minLat, minLon, maxLat, maxLon float64, iterator func(id string, obj geojson.Object, fields []float64) bool) (ncursor uint64) { func (c *Collection) Within(cursor uint64, sparse uint8, obj geojson.Object, minLat, minLon, maxLat, maxLon, minZ, maxZ float64, iterator func(id string, obj geojson.Object, fields []float64) bool) (ncursor uint64) {
var bbox geojson.BBox var bbox geojson.BBox
if obj != nil { if obj != nil {
bbox = obj.CalculatedBBox() bbox = obj.CalculatedBBox()
} else { } else {
bbox = geojson.BBox{Min: geojson.Position{X: minLon, Y: minLat, Z: 0}, Max: geojson.Position{X: maxLon, Y: maxLat, Z: 0}} bbox = geojson.BBox{Min: geojson.Position{X: minLon, Y: minLat, Z: minZ}, Max: geojson.Position{X: maxLon, Y: maxLat, Z: maxZ}}
} }
bboxes := bbox.Sparse(sparse) bboxes := bbox.Sparse(sparse)
if sparse > 0 { if sparse > 0 {
@ -421,12 +423,12 @@ func (c *Collection) Within(cursor uint64, sparse uint8, obj geojson.Object, min
} }
// Intersects returns all object that are intersect an object or bounding box. Set obj to nil in order to use the bounding box. // Intersects returns all object that are intersect an object or bounding box. Set obj to nil in order to use the bounding box.
func (c *Collection) Intersects(cursor uint64, sparse uint8, obj geojson.Object, minLat, minLon, maxLat, maxLon float64, iterator func(id string, obj geojson.Object, fields []float64) bool) (ncursor uint64) { func (c *Collection) Intersects(cursor uint64, sparse uint8, obj geojson.Object, minLat, minLon, maxLat, maxLon, maxZ, minZ float64, iterator func(id string, obj geojson.Object, fields []float64) bool) (ncursor uint64) {
var bbox geojson.BBox var bbox geojson.BBox
if obj != nil { if obj != nil {
bbox = obj.CalculatedBBox() bbox = obj.CalculatedBBox()
} else { } else {
bbox = geojson.BBox{Min: geojson.Position{X: minLon, Y: minLat, Z: 0}, Max: geojson.Position{X: maxLon, Y: maxLat, Z: 0}} bbox = geojson.BBox{Min: geojson.Position{X: minLon, Y: minLat, Z: minZ}, Max: geojson.Position{X: maxLon, Y: maxLat, Z: maxZ}}
} }
var bboxes []geojson.BBox var bboxes []geojson.BBox
if sparse > 0 { if sparse > 0 {
@ -436,8 +438,8 @@ func (c *Collection) Intersects(cursor uint64, sparse uint8, obj geojson.Object,
for y := bbox.Min.Y; y < bbox.Max.Y; y += ypart { for y := bbox.Min.Y; y < bbox.Max.Y; y += ypart {
for x := bbox.Min.X; x < bbox.Max.X; x += xpart { for x := bbox.Min.X; x < bbox.Max.X; x += xpart {
bboxes = append(bboxes, geojson.BBox{ bboxes = append(bboxes, geojson.BBox{
Min: geojson.Position{X: x, Y: y, Z: 0}, Min: geojson.Position{X: x, Y: y, Z: minZ},
Max: geojson.Position{X: x + xpart, Y: y + ypart, Z: 0}, Max: geojson.Position{X: x + xpart, Y: y + ypart, Z: maxZ},
}) })
} }
} }

View File

@ -73,19 +73,23 @@ func (c *Controller) cmdBounds(msg *server.Message) (string, error) {
if msg.OutputType == server.JSON { if msg.OutputType == server.JSON {
buf.WriteString(`{"ok":true`) buf.WriteString(`{"ok":true`)
} }
bbox := geojson.New2DBBox(col.Bounds()) minX, minY, minZ, maxX, maxY, maxZ := col.Bounds()
bbox := geojson.New2DBBox(minX, minY, maxX, maxY)
if msg.OutputType == server.JSON { if msg.OutputType == server.JSON {
buf.WriteString(`,"bounds":`) buf.WriteString(`,"bounds":`)
buf.WriteString(bbox.ExternalJSON()) buf.WriteString(bbox.ExternalJSON())
} else { } else {
vals = append(vals, resp.ArrayValue([]resp.Value{ vals = append(vals, resp.ArrayValue([]resp.Value{
resp.ArrayValue([]resp.Value{ resp.ArrayValue([]resp.Value{
resp.FloatValue(bbox.Min.Y), resp.FloatValue(minX),
resp.FloatValue(bbox.Min.X), resp.FloatValue(minY),
resp.FloatValue(minZ),
}), }),
resp.ArrayValue([]resp.Value{ resp.ArrayValue([]resp.Value{
resp.FloatValue(bbox.Max.Y), resp.FloatValue(maxX),
resp.FloatValue(bbox.Max.X), resp.FloatValue(maxY),
resp.FloatValue(maxZ),
}), }),
})) }))
} }

View File

@ -1,6 +1,7 @@
package controller package controller
import ( import (
"math"
"strconv" "strconv"
"strings" "strings"
@ -196,7 +197,7 @@ func fenceMatchRoam(c *Controller, fence *liveFenceSwitches, tkey, tid string, o
return return
} }
p := obj.CalculatedPoint() p := obj.CalculatedPoint()
col.Nearby(0, 0, p.Y, p.X, fence.roam.meters, col.Nearby(0, 0, p.Y, p.X, fence.roam.meters, math.Inf(-1), math.Inf(+1),
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields []float64) bool {
var match bool var match bool
if id == tid { if id == tid {

View File

@ -264,6 +264,8 @@ func (c *Controller) cmdNearby(msg *server.Message) (res string, err error) {
if s.fence { if s.fence {
return "", s return "", s
} }
minZ, maxZ := zMinMaxFromWheres(s.wheres)
sw, err := c.newScanWriter(wr, msg, s.key, s.output, s.precision, s.glob, false, s.limit, s.wheres, s.nofields) sw, err := c.newScanWriter(wr, msg, s.key, s.output, s.precision, s.glob, false, s.limit, s.wheres, s.nofields)
if err != nil { if err != nil {
return "", err return "", err
@ -273,7 +275,7 @@ func (c *Controller) cmdNearby(msg *server.Message) (res string, err error) {
} }
sw.writeHead() sw.writeHead()
if sw.col != nil { if sw.col != nil {
s.cursor = sw.col.Nearby(s.cursor, s.sparse, s.lat, s.lon, s.meters, func(id string, o geojson.Object, fields []float64) bool { s.cursor = sw.col.Nearby(s.cursor, s.sparse, s.lat, s.lon, s.meters, minZ, maxZ, func(id string, o geojson.Object, fields []float64) bool {
return sw.writeObject(id, o, fields, false) return sw.writeObject(id, o, fields, false)
}) })
} }
@ -316,14 +318,15 @@ func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res
wr.WriteString(`{"ok":true`) wr.WriteString(`{"ok":true`)
} }
sw.writeHead() sw.writeHead()
minZ, maxZ := zMinMaxFromWheres(s.wheres)
if cmd == "within" { if cmd == "within" {
s.cursor = sw.col.Within(s.cursor, s.sparse, s.o, s.minLat, s.minLon, s.maxLat, s.maxLon, s.cursor = sw.col.Within(s.cursor, s.sparse, s.o, s.minLat, s.minLon, s.maxLat, s.maxLon, minZ, maxZ,
func(id string, o geojson.Object, fields []float64) bool { func(id string, o geojson.Object, fields []float64) bool {
return sw.writeObject(id, o, fields, false) return sw.writeObject(id, o, fields, false)
}, },
) )
} else if cmd == "intersects" { } else if cmd == "intersects" {
s.cursor = sw.col.Intersects(s.cursor, s.sparse, s.o, s.minLat, s.minLon, s.maxLat, s.maxLon, s.cursor = sw.col.Intersects(s.cursor, s.sparse, s.o, s.minLat, s.minLon, s.maxLat, s.maxLon, minZ, maxZ,
func(id string, o geojson.Object, fields []float64) bool { func(id string, o geojson.Object, fields []float64) bool {
return sw.writeObject(id, o, fields, false) return sw.writeObject(id, o, fields, false)
}, },

View File

@ -117,6 +117,19 @@ func (where whereT) match(value float64) bool {
return true return true
} }
func zMinMaxFromWheres(wheres []whereT) (minZ, maxZ float64) {
for _, w := range wheres {
if w.field == "z" {
minZ = w.min
maxZ = w.max
return
}
}
minZ = math.Inf(-1)
maxZ = math.Inf(+1)
return
}
type searchScanBaseTokens struct { type searchScanBaseTokens struct {
key string key string
cursor uint64 cursor uint64

View File

@ -163,8 +163,8 @@ func (b BBox) Sparse(amount byte) []BBox {
for y := b.Min.Y; y < b.Max.Y; y += ysize { for y := b.Min.Y; y < b.Max.Y; y += ysize {
for x := b.Min.X; x < b.Max.X; x += xsize { for x := b.Min.X; x < b.Max.X; x += xsize {
bboxes = append(bboxes, BBox{ bboxes = append(bboxes, BBox{
Min: Position{X: x, Y: y, Z: 0}, Min: Position{X: x, Y: y, Z: b.Min.Z},
Max: Position{X: x + xsize, Y: y + ysize, Z: 0}, Max: Position{X: x + xsize, Y: y + ysize, Z: b.Max.Z},
}) })
} }
} }

View File

@ -1,26 +1,30 @@
package index package index
import "github.com/tidwall/tile38/index/rtree" import (
"math"
"github.com/tidwall/tile38/index/rtree"
)
// Item represents an index item. // Item represents an index item.
type Item interface { type Item interface {
Point() (x, y float64) Point() (x, y, z float64)
Rect() (minX, minY, maxX, maxY float64) Rect() (minX, minY, minZ, maxX, maxY, maxZ float64)
} }
// FlexItem can represent a point or a rectangle // FlexItem can represent a point or a rectangle
type FlexItem struct { type FlexItem struct {
MinX, MinY, MaxX, MaxY float64 MinX, MinY, MinZ, MaxX, MaxY, MaxZ float64
} }
// Rect returns the rectangle // Rect returns the rectangle
func (item *FlexItem) Rect() (minX, minY, maxX, maxY float64) { func (item *FlexItem) Rect() (minX, minY, minZ, maxX, maxY, maxZ float64) {
return item.MinX, item.MinY, item.MaxX, item.MaxY return item.MinX, item.MinY, item.MinZ, item.MaxX, item.MaxY, item.MaxZ
} }
// Point returns the point // Point returns the point
func (item *FlexItem) Point() (x, y float64) { func (item *FlexItem) Point() (x, y, z float64) {
return item.MinX, item.MinY return item.MinX, item.MinY, item.MinZ
} }
// Index is a geospatial index // Index is a geospatial index
@ -43,11 +47,11 @@ func New() *Index {
// Insert inserts an item into the index // Insert inserts an item into the index
func (ix *Index) Insert(item Item) { func (ix *Index) Insert(item Item) {
minX, minY, maxX, maxY := item.Rect() minX, minY, minZ, maxX, maxY, maxZ := item.Rect()
if minX == maxX && minY == maxY { if minX == maxX && minY == maxY {
x, y, normd := normPoint(minY, minX) x, y, normd := normPoint(minY, minX)
if normd { if normd {
nitem := &rtree.Rect{MinX: x, MinY: y, MaxX: x, MaxY: y} nitem := &rtree.Rect{MinX: x, MinY: y, MinZ: minZ, MaxX: x, MaxY: y, MaxZ: maxZ}
ix.nr[nitem] = item ix.nr[nitem] = item
ix.nrr[item] = []*rtree.Rect{nitem} ix.nrr[item] = []*rtree.Rect{nitem}
ix.r.Insert(nitem) ix.r.Insert(nitem)
@ -60,7 +64,7 @@ func (ix *Index) Insert(item Item) {
var nitems []*rtree.Rect var nitems []*rtree.Rect
for i := range mins { for i := range mins {
minX, minY, maxX, maxY := mins[i][0], mins[i][1], maxs[i][0], maxs[i][1] minX, minY, maxX, maxY := mins[i][0], mins[i][1], maxs[i][0], maxs[i][1]
nitem := &rtree.Rect{MinX: minX, MinY: minY, MaxX: maxX, MaxY: maxY} nitem := &rtree.Rect{MinX: minX, MinY: minY, MinZ: minZ, MaxX: maxX, MaxY: maxY, MaxZ: maxZ}
ix.nr[nitem] = item ix.nr[nitem] = item
nitems = append(nitems, nitem) nitems = append(nitems, nitem)
ix.r.Insert(nitem) ix.r.Insert(nitem)
@ -92,7 +96,7 @@ func (ix *Index) Remove(item Item) {
// Count counts all items in the index. // Count counts all items in the index.
func (ix *Index) Count() int { func (ix *Index) Count() int {
count := 0 count := 0
ix.Search(0, -90, -180, 90, 180, func(item Item) bool { ix.Search(0, -90, -180, 90, 180, math.Inf(-1), math.Inf(+1), func(item Item) bool {
count++ count++
return true return true
}) })
@ -100,7 +104,7 @@ func (ix *Index) Count() int {
} }
// Bounds returns the minimum bounding rectangle of all items in the index. // Bounds returns the minimum bounding rectangle of all items in the index.
func (ix *Index) Bounds() (MinX, MinY, MaxX, MaxY float64) { func (ix *Index) Bounds() (MinX, MinY, MinZ, MaxX, MaxY, MaxZ float64) {
return ix.r.Bounds() return ix.r.Bounds()
} }
@ -120,7 +124,7 @@ func (ix *Index) getRTreeItem(item rtree.Item) Item {
} }
// Search returns all items that intersect the bounding box. // Search returns all items that intersect the bounding box.
func (ix *Index) Search(cursor uint64, swLat, swLon, neLat, neLon float64, iterator func(item Item) bool) (ncursor uint64) { func (ix *Index) Search(cursor uint64, swLat, swLon, neLat, neLon, minZ, maxZ float64, iterator func(item Item) bool) (ncursor uint64) {
var idx uint64 var idx uint64
var active = true var active = true
var idm = make(map[Item]bool) var idm = make(map[Item]bool)
@ -130,7 +134,7 @@ func (ix *Index) Search(cursor uint64, swLat, swLon, neLat, neLon float64, itera
// There is only one rectangle. // There is only one rectangle.
// It's possible that a r rect may span multiple entries. Check mulm map for spanning rects. // It's possible that a r rect may span multiple entries. Check mulm map for spanning rects.
if active { if active {
ix.r.Search(mins[0][0], mins[0][1], maxs[0][0], maxs[0][1], func(item rtree.Item) bool { ix.r.Search(mins[0][0], mins[0][1], minZ, maxs[0][0], maxs[0][1], maxZ, func(item rtree.Item) bool {
if idx >= cursor { if idx >= cursor {
iitm := ix.getRTreeItem(item) iitm := ix.getRTreeItem(item)
if iitm != nil { if iitm != nil {
@ -152,7 +156,7 @@ func (ix *Index) Search(cursor uint64, swLat, swLon, neLat, neLon float64, itera
// There are multiple rectangles. Duplicates might occur. // There are multiple rectangles. Duplicates might occur.
for i := range mins { for i := range mins {
if active { if active {
ix.r.Search(mins[i][0], mins[i][1], maxs[i][0], maxs[i][1], func(item rtree.Item) bool { ix.r.Search(mins[i][0], mins[i][1], minZ, maxs[i][0], maxs[i][1], maxZ, func(item rtree.Item) bool {
if idx >= cursor { if idx >= cursor {
iitm := ix.getRTreeItem(item) iitm := ix.getRTreeItem(item)
if iitm != nil { if iitm != nil {

View File

@ -60,7 +60,7 @@ func TestRandomInserts(t *testing.T) {
} }
count = 0 count = 0
items := make([]Item, 0, l) items := make([]Item, 0, l)
tr.Search(0, -90, -180, 90, 180, func(item Item) bool { tr.Search(0, -90, -180, 90, 180, 0, 0, func(item Item) bool {
count++ count++
items = append(items, item) items = append(items, item)
return true return true
@ -70,7 +70,7 @@ func TestRandomInserts(t *testing.T) {
} }
start = time.Now() start = time.Now()
count1 := 0 count1 := 0
tr.Search(0, 33, -115, 34, -114, func(item Item) bool { tr.Search(0, 33, -115, 34, -114, 0, 0, func(item Item) bool {
count1++ count1++
return true return true
}) })
@ -79,7 +79,7 @@ func TestRandomInserts(t *testing.T) {
start = time.Now() start = time.Now()
count2 := 0 count2 := 0
tr.Search(0, 33-180, -115-360, 34-180, -114-360, func(item Item) bool { tr.Search(0, 33-180, -115-360, 34-180, -114-360, 0, 0, func(item Item) bool {
count2++ count2++
return true return true
}) })
@ -87,7 +87,7 @@ func TestRandomInserts(t *testing.T) {
start = time.Now() start = time.Now()
count3 := 0 count3 := 0
tr.Search(0, -10, 170, 20, 200, func(item Item) bool { tr.Search(0, -10, 170, 20, 200, 0, 0, func(item Item) bool {
count3++ count3++
return true return true
}) })
@ -99,16 +99,16 @@ func TestRandomInserts(t *testing.T) {
fmt.Printf("Searched %d items in %s.\n", count2, searchdur2.String()) fmt.Printf("Searched %d items in %s.\n", count2, searchdur2.String())
fmt.Printf("Searched %d items in %s.\n", count3, searchdur3.String()) fmt.Printf("Searched %d items in %s.\n", count3, searchdur3.String())
tr.Search(0, -10, 170, 20, 200, func(item Item) bool { tr.Search(0, -10, 170, 20, 200, 0, 0, func(item Item) bool {
lat1, lon1, lat2, lon2 := item.Rect() lat1, lon1, _, lat2, lon2, _ := item.Rect()
if lat1 == lat2 && lon1 == lon2 { if lat1 == lat2 && lon1 == lon2 {
return false return false
} }
return true return true
}) })
tr.Search(0, -10, 170, 20, 200, func(item Item) bool { tr.Search(0, -10, 170, 20, 200, 0, 0, func(item Item) bool {
lat1, lon1, lat2, lon2 := item.Rect() lat1, lon1, _, lat2, lon2, _ := item.Rect()
if lat1 != lat2 || lon1 != lon2 { if lat1 != lat2 || lon1 != lon2 {
return false return false
} }
@ -173,7 +173,7 @@ func TestInsertVarious(t *testing.T) {
t.Fatalf("count = %d, expect 1", count) t.Fatalf("count = %d, expect 1", count)
} }
found := false found := false
tr.Search(0, -90, -180, 90, 180, func(item2 Item) bool { tr.Search(0, -90, -180, 90, 180, 0, 0, func(item2 Item) bool {
if item2 == item { if item2 == item {
found = true found = true
} }

View File

@ -2,46 +2,46 @@ package rtree
// Item is an rtree item // Item is an rtree item
type Item interface { type Item interface {
Rect() (minX, minY, maxX, maxY float64) Rect() (minX, minY, minZ, maxX, maxY, maxZ float64)
} }
// Rect is a rectangle // Rect is a rectangle
type Rect struct { type Rect struct {
MinX, MinY, MaxX, MaxY float64 MinX, MinY, MinZ, MaxX, MaxY, MaxZ float64
} }
// Rect returns the rectangle // Rect returns the rectangle
func (item *Rect) Rect() (minX, minY, maxX, maxY float64) { func (item *Rect) Rect() (minX, minY, minZ, maxX, maxY, maxZ float64) {
return item.MinX, item.MinY, item.MaxX, item.MaxY return item.MinX, item.MinY, item.MinZ, item.MaxX, item.MaxY, item.MaxZ
} }
// RTree is an implementation of an rtree // RTree is an implementation of an rtree
type RTree struct { type RTree struct {
tr *d2RTree tr *d3RTree
} }
// New creates a new RTree // New creates a new RTree
func New() *RTree { func New() *RTree {
return &RTree{ return &RTree{
tr: d2New(), tr: d3New(),
} }
} }
// Insert inserts item into rtree // Insert inserts item into rtree
func (tr *RTree) Insert(item Item) { func (tr *RTree) Insert(item Item) {
minX, minY, maxX, maxY := item.Rect() minX, minY, minZ, maxX, maxY, maxZ := item.Rect()
tr.tr.Insert([2]float64{minX, minY}, [2]float64{maxX, maxY}, item) tr.tr.Insert([3]float64{minX, minY, minZ}, [3]float64{maxX, maxY, maxZ}, item)
} }
// Remove removes item from rtree // Remove removes item from rtree
func (tr *RTree) Remove(item Item) { func (tr *RTree) Remove(item Item) {
minX, minY, maxX, maxY := item.Rect() minX, minY, minZ, maxX, maxY, maxZ := item.Rect()
tr.tr.Remove([2]float64{minX, minY}, [2]float64{maxX, maxY}, item) tr.tr.Remove([3]float64{minX, minY, minZ}, [3]float64{maxX, maxY, maxZ}, item)
} }
// Search finds all items in bounding box. // Search finds all items in bounding box.
func (tr *RTree) Search(minX, minY, maxX, maxY float64, iterator func(item Item) bool) { func (tr *RTree) Search(minX, minY, minZ, maxX, maxY, maxZ float64, iterator func(item Item) bool) {
tr.tr.Search([2]float64{minX, minY}, [2]float64{maxX, maxY}, func(data interface{}) bool { tr.tr.Search([3]float64{minX, minY, minZ}, [3]float64{maxX, maxY, maxZ}, func(data interface{}) bool {
return iterator(data.(Item)) return iterator(data.(Item))
}) })
} }
@ -56,17 +56,17 @@ func (tr *RTree) RemoveAll() {
tr.tr.RemoveAll() tr.tr.RemoveAll()
} }
func (tr *RTree) Bounds() (minX, minY, maxX, maxY float64) { func (tr *RTree) Bounds() (minX, minY, minZ, maxX, maxY, maxZ float64) {
var rect d2rectT var rect d3rectT
if tr.tr.root != nil { if tr.tr.root != nil {
if tr.tr.root.count > 0 { if tr.tr.root.count > 0 {
rect = tr.tr.root.branch[0].rect rect = tr.tr.root.branch[0].rect
for i := 1; i < tr.tr.root.count; i++ { for i := 1; i < tr.tr.root.count; i++ {
rect2 := tr.tr.root.branch[i].rect rect2 := tr.tr.root.branch[i].rect
rect = d2combineRect(&rect, &rect2) rect = d3combineRect(&rect, &rect2)
} }
} }
} }
minX, minY, maxX, maxY = rect.min[0], rect.min[1], rect.max[0], rect.max[1] minX, minY, minZ, maxX, maxY, maxZ = rect.min[0], rect.min[1], rect.min[2], rect.max[0], rect.max[1], rect.max[2]
return return
} }

View File

@ -38,10 +38,10 @@ func wp(min, max []float64) *Rect {
MaxY: max[1], MaxY: max[1],
} }
} }
func wpp(x, y float64) *Rect { func wpp(x, y, z float64) *Rect {
return &Rect{ return &Rect{
x, y, x, y, z,
x, y, x, y, z,
} }
} }
func TestA(t *testing.T) { func TestA(t *testing.T) {
@ -51,7 +51,7 @@ func TestA(t *testing.T) {
tr.Insert(item1) tr.Insert(item1)
tr.Insert(item2) tr.Insert(item2)
var itemA Item var itemA Item
tr.Search(21, 20, 25, 25, func(item Item) bool { tr.Search(21, 20, 0, 25, 25, 0, func(item Item) bool {
itemA = item itemA = item
return true return true
}) })
@ -77,14 +77,14 @@ func TestMemory(t *testing.T) {
} }
func TestBounds(t *testing.T) { func TestBounds(t *testing.T) {
tr := New() tr := New()
tr.Insert(wpp(10, 10)) tr.Insert(wpp(10, 10, 0))
tr.Insert(wpp(10, 20)) tr.Insert(wpp(10, 20, 0))
tr.Insert(wpp(10, 30)) tr.Insert(wpp(10, 30, 0))
tr.Insert(wpp(20, 10)) tr.Insert(wpp(20, 10, 0))
tr.Insert(wpp(30, 10)) tr.Insert(wpp(30, 10, 0))
minX, minY, maxX, maxY := tr.Bounds() minX, minY, minZ, maxX, maxY, maxZ := tr.Bounds()
if minX != 10 || minY != 10 || maxX != 30 || maxY != 30 { if minX != 10 || minY != 10 || minZ != 0 || maxX != 30 || maxY != 30 || maxZ != 0 {
t.Fatalf("expected 10,10 30,30, got %v,%v %v,%v\n", minX, minY, maxX, maxY) t.Fatalf("expected 10,10,0 30,30,0, got %v,%v %v,%v\n", minX, minY, minZ, maxX, maxY, maxZ)
} }
} }
func BenchmarkInsert(b *testing.B) { func BenchmarkInsert(b *testing.B) {

View File

@ -2,13 +2,13 @@ package rtree
import "math" import "math"
func d2fmin(a, b float64) float64 { func d3fmin(a, b float64) float64 {
if a < b { if a < b {
return a return a
} }
return b return b
} }
func d2fmax(a, b float64) float64 { func d3fmax(a, b float64) float64 {
if a > b { if a > b {
return a return a
} }
@ -16,13 +16,13 @@ func d2fmax(a, b float64) float64 {
} }
const ( const (
d2numDims = 2 d3numDims = 3
d2maxNodes = 8 d3maxNodes = 8
d2minNodes = d2maxNodes / 2 d3minNodes = d3maxNodes / 2
d2useSphericalVolume = true // Better split classification, may be slower on some systems d3useSphericalVolume = true // Better split classification, may be slower on some systems
) )
var d2unitSphereVolume = []float64{ var d3unitSphereVolume = []float64{
0.000000, 2.000000, 3.141593, // Dimension 0,1,2 0.000000, 2.000000, 3.141593, // Dimension 0,1,2
4.188790, 4.934802, 5.263789, // Dimension 3,4,5 4.188790, 4.934802, 5.263789, // Dimension 3,4,5
5.167713, 4.724766, 4.058712, // Dimension 6,7,8 5.167713, 4.724766, 4.058712, // Dimension 6,7,8
@ -30,69 +30,69 @@ var d2unitSphereVolume = []float64{
1.335263, 0.910629, 0.599265, // Dimension 12,13,14 1.335263, 0.910629, 0.599265, // Dimension 12,13,14
0.381443, 0.235331, 0.140981, // Dimension 15,16,17 0.381443, 0.235331, 0.140981, // Dimension 15,16,17
0.082146, 0.046622, 0.025807, // Dimension 18,19,20 0.082146, 0.046622, 0.025807, // Dimension 18,19,20
}[d2numDims] }[d3numDims]
type d2RTree struct { type d3RTree struct {
root *d2nodeT ///< Root of tree root *d3nodeT ///< Root of tree
} }
/// Minimal bounding rectangle (n-dimensional) /// Minimal bounding rectangle (n-dimensional)
type d2rectT struct { type d3rectT struct {
min [d2numDims]float64 ///< Min dimensions of bounding box min [d3numDims]float64 ///< Min dimensions of bounding box
max [d2numDims]float64 ///< Max dimensions of bounding box max [d3numDims]float64 ///< Max dimensions of bounding box
} }
/// May be data or may be another subtree /// May be data or may be another subtree
/// The parents level determines this. /// The parents level determines this.
/// If the parents level is 0, then this is data /// If the parents level is 0, then this is data
type d2branchT struct { type d3branchT struct {
rect d2rectT ///< Bounds rect d3rectT ///< Bounds
child *d2nodeT ///< Child node child *d3nodeT ///< Child node
data interface{} ///< Data Id or Ptr data interface{} ///< Data Id or Ptr
} }
/// d2nodeT for each branch level /// d3nodeT for each branch level
type d2nodeT struct { type d3nodeT struct {
count int ///< Count count int ///< Count
level int ///< Leaf is zero, others positive level int ///< Leaf is zero, others positive
branch [d2maxNodes]d2branchT ///< Branch branch [d3maxNodes]d3branchT ///< Branch
} }
func (node *d2nodeT) isInternalNode() bool { func (node *d3nodeT) isInternalNode() bool {
return (node.level > 0) // Not a leaf, but a internal node return (node.level > 0) // Not a leaf, but a internal node
} }
func (node *d2nodeT) isLeaf() bool { func (node *d3nodeT) isLeaf() bool {
return (node.level == 0) // A leaf, contains data return (node.level == 0) // A leaf, contains data
} }
/// A link list of nodes for reinsertion after a delete operation /// A link list of nodes for reinsertion after a delete operation
type d2listNodeT struct { type d3listNodeT struct {
next *d2listNodeT ///< Next in list next *d3listNodeT ///< Next in list
node *d2nodeT ///< Node node *d3nodeT ///< Node
} }
const d2notTaken = -1 // indicates that position const d3notTaken = -1 // indicates that position
/// Variables for finding a split partition /// Variables for finding a split partition
type d2partitionVarsT struct { type d3partitionVarsT struct {
partition [d2maxNodes + 1]int partition [d3maxNodes + 1]int
total int total int
minFill int minFill int
count [2]int count [2]int
cover [2]d2rectT cover [2]d3rectT
area [2]float64 area [2]float64
branchBuf [d2maxNodes + 1]d2branchT branchBuf [d3maxNodes + 1]d3branchT
branchCount int branchCount int
coverSplit d2rectT coverSplit d3rectT
coverSplitArea float64 coverSplitArea float64
} }
func d2New() *d2RTree { func d3New() *d3RTree {
// We only support machine word size simple data type eg. integer index or object pointer. // We only support machine word size simple data type eg. integer index or object pointer.
// Since we are storing as union with non data branch // Since we are storing as union with non data branch
return &d2RTree{ return &d3RTree{
root: &d2nodeT{}, root: &d3nodeT{},
} }
} }
@ -100,63 +100,63 @@ func d2New() *d2RTree {
/// \param a_min Min of bounding rect /// \param a_min Min of bounding rect
/// \param a_max Max of bounding rect /// \param a_max Max of bounding rect
/// \param a_dataId Positive Id of data. Maybe zero, but negative numbers not allowed. /// \param a_dataId Positive Id of data. Maybe zero, but negative numbers not allowed.
func (tr *d2RTree) Insert(min, max [d2numDims]float64, dataId interface{}) { func (tr *d3RTree) Insert(min, max [d3numDims]float64, dataId interface{}) {
var branch d2branchT var branch d3branchT
branch.data = dataId branch.data = dataId
for axis := 0; axis < d2numDims; axis++ { for axis := 0; axis < d3numDims; axis++ {
branch.rect.min[axis] = min[axis] branch.rect.min[axis] = min[axis]
branch.rect.max[axis] = max[axis] branch.rect.max[axis] = max[axis]
} }
d2insertRect(&branch, &tr.root, 0) d3insertRect(&branch, &tr.root, 0)
} }
/// Remove entry /// Remove entry
/// \param a_min Min of bounding rect /// \param a_min Min of bounding rect
/// \param a_max Max of bounding rect /// \param a_max Max of bounding rect
/// \param a_dataId Positive Id of data. Maybe zero, but negative numbers not allowed. /// \param a_dataId Positive Id of data. Maybe zero, but negative numbers not allowed.
func (tr *d2RTree) Remove(min, max [d2numDims]float64, dataId interface{}) { func (tr *d3RTree) Remove(min, max [d3numDims]float64, dataId interface{}) {
var rect d2rectT var rect d3rectT
for axis := 0; axis < d2numDims; axis++ { for axis := 0; axis < d3numDims; axis++ {
rect.min[axis] = min[axis] rect.min[axis] = min[axis]
rect.max[axis] = max[axis] rect.max[axis] = max[axis]
} }
d2removeRect(&rect, dataId, &tr.root) d3removeRect(&rect, dataId, &tr.root)
} }
/// Find all within d2search rectangle /// Find all within d3search rectangle
/// \param a_min Min of d2search bounding rect /// \param a_min Min of d3search bounding rect
/// \param a_max Max of d2search bounding rect /// \param a_max Max of d3search bounding rect
/// \param a_searchResult d2search result array. Caller should set grow size. Function will reset, not append to array. /// \param a_searchResult d3search result array. Caller should set grow size. Function will reset, not append to array.
/// \param a_resultCallback Callback function to return result. Callback should return 'true' to continue searching /// \param a_resultCallback Callback function to return result. Callback should return 'true' to continue searching
/// \param a_context User context to pass as parameter to a_resultCallback /// \param a_context User context to pass as parameter to a_resultCallback
/// \return Returns the number of entries found /// \return Returns the number of entries found
func (tr *d2RTree) Search(min, max [d2numDims]float64, resultCallback func(data interface{}) bool) int { func (tr *d3RTree) Search(min, max [d3numDims]float64, resultCallback func(data interface{}) bool) int {
var rect d2rectT var rect d3rectT
for axis := 0; axis < d2numDims; axis++ { for axis := 0; axis < d3numDims; axis++ {
rect.min[axis] = min[axis] rect.min[axis] = min[axis]
rect.max[axis] = max[axis] rect.max[axis] = max[axis]
} }
foundCount, _ := d2search(tr.root, rect, 0, resultCallback) foundCount, _ := d3search(tr.root, rect, 0, resultCallback)
return foundCount return foundCount
} }
/// Count the data elements in this container. This is slow as no internal counter is maintained. /// Count the data elements in this container. This is slow as no internal counter is maintained.
func (tr *d2RTree) Count() int { func (tr *d3RTree) Count() int {
var count int var count int
d2countRec(tr.root, &count) d3countRec(tr.root, &count)
return count return count
} }
/// Remove all entries from tree /// Remove all entries from tree
func (tr *d2RTree) RemoveAll() { func (tr *d3RTree) RemoveAll() {
// Delete all existing nodes // Delete all existing nodes
tr.root = &d2nodeT{} tr.root = &d3nodeT{}
} }
func d2countRec(node *d2nodeT, count *int) { func d3countRec(node *d3nodeT, count *int) {
if node.isInternalNode() { // not a leaf node if node.isInternalNode() { // not a leaf node
for index := 0; index < node.count; index++ { for index := 0; index < node.count; index++ {
d2countRec(node.branch[index].child, count) d3countRec(node.branch[index].child, count)
} }
} else { // A leaf node } else { // A leaf node
*count += node.count *count += node.count
@ -170,40 +170,40 @@ func d2countRec(node *d2nodeT, count *int) {
// new_node to point to the new node. Old node updated to become one of two. // new_node to point to the new node. Old node updated to become one of two.
// The level argument specifies the number of steps up from the leaf // The level argument specifies the number of steps up from the leaf
// level to insert; e.g. a data rectangle goes in at level = 0. // level to insert; e.g. a data rectangle goes in at level = 0.
func d2insertRectRec(branch *d2branchT, node *d2nodeT, newNode **d2nodeT, level int) bool { func d3insertRectRec(branch *d3branchT, node *d3nodeT, newNode **d3nodeT, level int) bool {
// recurse until we reach the correct level for the new record. data records // recurse until we reach the correct level for the new record. data records
// will always be called with a_level == 0 (leaf) // will always be called with a_level == 0 (leaf)
if node.level > level { if node.level > level {
// Still above level for insertion, go down tree recursively // Still above level for insertion, go down tree recursively
var otherNode *d2nodeT var otherNode *d3nodeT
//var newBranch d2branchT //var newBranch d3branchT
// find the optimal branch for this record // find the optimal branch for this record
index := d2pickBranch(&branch.rect, node) index := d3pickBranch(&branch.rect, node)
// recursively insert this record into the picked branch // recursively insert this record into the picked branch
childWasSplit := d2insertRectRec(branch, node.branch[index].child, &otherNode, level) childWasSplit := d3insertRectRec(branch, node.branch[index].child, &otherNode, level)
if !childWasSplit { if !childWasSplit {
// Child was not split. Merge the bounding box of the new record with the // Child was not split. Merge the bounding box of the new record with the
// existing bounding box // existing bounding box
node.branch[index].rect = d2combineRect(&branch.rect, &(node.branch[index].rect)) node.branch[index].rect = d3combineRect(&branch.rect, &(node.branch[index].rect))
return false return false
} else { } else {
// Child was split. The old branches are now re-partitioned to two nodes // Child was split. The old branches are now re-partitioned to two nodes
// so we have to re-calculate the bounding boxes of each node // so we have to re-calculate the bounding boxes of each node
node.branch[index].rect = d2nodeCover(node.branch[index].child) node.branch[index].rect = d3nodeCover(node.branch[index].child)
var newBranch d2branchT var newBranch d3branchT
newBranch.child = otherNode newBranch.child = otherNode
newBranch.rect = d2nodeCover(otherNode) newBranch.rect = d3nodeCover(otherNode)
// The old node is already a child of a_node. Now add the newly-created // The old node is already a child of a_node. Now add the newly-created
// node to a_node as well. a_node might be split because of that. // node to a_node as well. a_node might be split because of that.
return d2addBranch(&newBranch, node, newNode) return d3addBranch(&newBranch, node, newNode)
} }
} else if node.level == level { } else if node.level == level {
// We have reached level for insertion. Add rect, split if necessary // We have reached level for insertion. Add rect, split if necessary
return d2addBranch(branch, node, newNode) return d3addBranch(branch, node, newNode)
} else { } else {
// Should never occur // Should never occur
return false return false
@ -211,32 +211,32 @@ func d2insertRectRec(branch *d2branchT, node *d2nodeT, newNode **d2nodeT, level
} }
// Insert a data rectangle into an index structure. // Insert a data rectangle into an index structure.
// d2insertRect provides for splitting the root; // d3insertRect provides for splitting the root;
// returns 1 if root was split, 0 if it was not. // returns 1 if root was split, 0 if it was not.
// The level argument specifies the number of steps up from the leaf // The level argument specifies the number of steps up from the leaf
// level to insert; e.g. a data rectangle goes in at level = 0. // level to insert; e.g. a data rectangle goes in at level = 0.
// InsertRect2 does the recursion. // InsertRect2 does the recursion.
// //
func d2insertRect(branch *d2branchT, root **d2nodeT, level int) bool { func d3insertRect(branch *d3branchT, root **d3nodeT, level int) bool {
var newNode *d2nodeT var newNode *d3nodeT
if d2insertRectRec(branch, *root, &newNode, level) { // Root split if d3insertRectRec(branch, *root, &newNode, level) { // Root split
// Grow tree taller and new root // Grow tree taller and new root
newRoot := &d2nodeT{} newRoot := &d3nodeT{}
newRoot.level = (*root).level + 1 newRoot.level = (*root).level + 1
var newBranch d2branchT var newBranch d3branchT
// add old root node as a child of the new root // add old root node as a child of the new root
newBranch.rect = d2nodeCover(*root) newBranch.rect = d3nodeCover(*root)
newBranch.child = *root newBranch.child = *root
d2addBranch(&newBranch, newRoot, nil) d3addBranch(&newBranch, newRoot, nil)
// add the split node as a child of the new root // add the split node as a child of the new root
newBranch.rect = d2nodeCover(newNode) newBranch.rect = d3nodeCover(newNode)
newBranch.child = newNode newBranch.child = newNode
d2addBranch(&newBranch, newRoot, nil) d3addBranch(&newBranch, newRoot, nil)
// set the new root as the root node // set the new root as the root node
*root = newRoot *root = newRoot
@ -247,10 +247,10 @@ func d2insertRect(branch *d2branchT, root **d2nodeT, level int) bool {
} }
// Find the smallest rectangle that includes all rectangles in branches of a node. // Find the smallest rectangle that includes all rectangles in branches of a node.
func d2nodeCover(node *d2nodeT) d2rectT { func d3nodeCover(node *d3nodeT) d3rectT {
rect := node.branch[0].rect rect := node.branch[0].rect
for index := 1; index < node.count; index++ { for index := 1; index < node.count; index++ {
rect = d2combineRect(&rect, &(node.branch[index].rect)) rect = d3combineRect(&rect, &(node.branch[index].rect))
} }
return rect return rect
} }
@ -259,20 +259,20 @@ func d2nodeCover(node *d2nodeT) d2rectT {
// Returns 0 if node not split. Old node updated. // Returns 0 if node not split. Old node updated.
// Returns 1 if node split, sets *new_node to address of new node. // Returns 1 if node split, sets *new_node to address of new node.
// Old node updated, becomes one of two. // Old node updated, becomes one of two.
func d2addBranch(branch *d2branchT, node *d2nodeT, newNode **d2nodeT) bool { func d3addBranch(branch *d3branchT, node *d3nodeT, newNode **d3nodeT) bool {
if node.count < d2maxNodes { // Split won't be necessary if node.count < d3maxNodes { // Split won't be necessary
node.branch[node.count] = *branch node.branch[node.count] = *branch
node.count++ node.count++
return false return false
} else { } else {
d2splitNode(node, branch, newNode) d3splitNode(node, branch, newNode)
return true return true
} }
} }
// Disconnect a dependent node. // Disconnect a dependent node.
// Caller must return (or stop using iteration index) after this as count has changed // Caller must return (or stop using iteration index) after this as count has changed
func d2disconnectBranch(node *d2nodeT, index int) { func d3disconnectBranch(node *d3nodeT, index int) {
// Remove element by swapping with the last element to prevent gaps in array // Remove element by swapping with the last element to prevent gaps in array
node.branch[index] = node.branch[node.count-1] node.branch[index] = node.branch[node.count-1]
node.branch[node.count-1].data = nil node.branch[node.count-1].data = nil
@ -285,20 +285,20 @@ func d2disconnectBranch(node *d2nodeT, index int) {
// least total area for the covering rectangles in the current node. // least total area for the covering rectangles in the current node.
// In case of a tie, pick the one which was smaller before, to get // In case of a tie, pick the one which was smaller before, to get
// the best resolution when searching. // the best resolution when searching.
func d2pickBranch(rect *d2rectT, node *d2nodeT) int { func d3pickBranch(rect *d3rectT, node *d3nodeT) int {
var firstTime bool = true var firstTime bool = true
var increase float64 var increase float64
var bestIncr float64 = -1 var bestIncr float64 = -1
var area float64 var area float64
var bestArea float64 var bestArea float64
var best int var best int
var tempRect d2rectT var tempRect d3rectT
for index := 0; index < node.count; index++ { for index := 0; index < node.count; index++ {
curRect := &node.branch[index].rect curRect := &node.branch[index].rect
area = d2calcRectVolume(curRect) area = d3calcRectVolume(curRect)
tempRect = d2combineRect(rect, curRect) tempRect = d3combineRect(rect, curRect)
increase = d2calcRectVolume(&tempRect) - area increase = d3calcRectVolume(&tempRect) - area
if (increase < bestIncr) || firstTime { if (increase < bestIncr) || firstTime {
best = index best = index
bestArea = area bestArea = area
@ -314,12 +314,12 @@ func d2pickBranch(rect *d2rectT, node *d2nodeT) int {
} }
// Combine two rectangles into larger one containing both // Combine two rectangles into larger one containing both
func d2combineRect(rectA, rectB *d2rectT) d2rectT { func d3combineRect(rectA, rectB *d3rectT) d3rectT {
var newRect d2rectT var newRect d3rectT
for index := 0; index < d2numDims; index++ { for index := 0; index < d3numDims; index++ {
newRect.min[index] = d2fmin(rectA.min[index], rectB.min[index]) newRect.min[index] = d3fmin(rectA.min[index], rectB.min[index])
newRect.max[index] = d2fmax(rectA.max[index], rectB.max[index]) newRect.max[index] = d3fmax(rectA.max[index], rectB.max[index])
} }
return newRect return newRect
@ -329,41 +329,41 @@ func d2combineRect(rectA, rectB *d2rectT) d2rectT {
// Divides the nodes branches and the extra one between two nodes. // Divides the nodes branches and the extra one between two nodes.
// Old node is one of the new ones, and one really new one is created. // Old node is one of the new ones, and one really new one is created.
// Tries more than one method for choosing a partition, uses best result. // Tries more than one method for choosing a partition, uses best result.
func d2splitNode(node *d2nodeT, branch *d2branchT, newNode **d2nodeT) { func d3splitNode(node *d3nodeT, branch *d3branchT, newNode **d3nodeT) {
// Could just use local here, but member or external is faster since it is reused // Could just use local here, but member or external is faster since it is reused
var localVars d2partitionVarsT var localVars d3partitionVarsT
parVars := &localVars parVars := &localVars
// Load all the branches into a buffer, initialize old node // Load all the branches into a buffer, initialize old node
d2getBranches(node, branch, parVars) d3getBranches(node, branch, parVars)
// Find partition // Find partition
d2choosePartition(parVars, d2minNodes) d3choosePartition(parVars, d3minNodes)
// Create a new node to hold (about) half of the branches // Create a new node to hold (about) half of the branches
*newNode = &d2nodeT{} *newNode = &d3nodeT{}
(*newNode).level = node.level (*newNode).level = node.level
// Put branches from buffer into 2 nodes according to the chosen partition // Put branches from buffer into 2 nodes according to the chosen partition
node.count = 0 node.count = 0
d2loadNodes(node, *newNode, parVars) d3loadNodes(node, *newNode, parVars)
} }
// Calculate the n-dimensional volume of a rectangle // Calculate the n-dimensional volume of a rectangle
func d2rectVolume(rect *d2rectT) float64 { func d3rectVolume(rect *d3rectT) float64 {
var volume float64 = 1 var volume float64 = 1
for index := 0; index < d2numDims; index++ { for index := 0; index < d3numDims; index++ {
volume *= rect.max[index] - rect.min[index] volume *= rect.max[index] - rect.min[index]
} }
return volume return volume
} }
// The exact volume of the bounding sphere for the given d2rectT // The exact volume of the bounding sphere for the given d3rectT
func d2rectSphericalVolume(rect *d2rectT) float64 { func d3rectSphericalVolume(rect *d3rectT) float64 {
var sumOfSquares float64 = 0 var sumOfSquares float64 = 0
var radius float64 var radius float64
for index := 0; index < d2numDims; index++ { for index := 0; index < d3numDims; index++ {
halfExtent := (rect.max[index] - rect.min[index]) * 0.5 halfExtent := (rect.max[index] - rect.min[index]) * 0.5
sumOfSquares += halfExtent * halfExtent sumOfSquares += halfExtent * halfExtent
} }
@ -371,43 +371,43 @@ func d2rectSphericalVolume(rect *d2rectT) float64 {
radius = math.Sqrt(sumOfSquares) radius = math.Sqrt(sumOfSquares)
// Pow maybe slow, so test for common dims just use x*x, x*x*x. // Pow maybe slow, so test for common dims just use x*x, x*x*x.
if d2numDims == 5 { if d3numDims == 5 {
return (radius * radius * radius * radius * radius * d2unitSphereVolume) return (radius * radius * radius * radius * radius * d3unitSphereVolume)
} else if d2numDims == 4 { } else if d3numDims == 4 {
return (radius * radius * radius * radius * d2unitSphereVolume) return (radius * radius * radius * radius * d3unitSphereVolume)
} else if d2numDims == 3 { } else if d3numDims == 3 {
return (radius * radius * radius * d2unitSphereVolume) return (radius * radius * radius * d3unitSphereVolume)
} else if d2numDims == 2 { } else if d3numDims == 2 {
return (radius * radius * d2unitSphereVolume) return (radius * radius * d3unitSphereVolume)
} else { } else {
return (math.Pow(radius, d2numDims) * d2unitSphereVolume) return (math.Pow(radius, d3numDims) * d3unitSphereVolume)
} }
} }
// Use one of the methods to calculate retangle volume // Use one of the methods to calculate retangle volume
func d2calcRectVolume(rect *d2rectT) float64 { func d3calcRectVolume(rect *d3rectT) float64 {
if d2useSphericalVolume { if d3useSphericalVolume {
return d2rectSphericalVolume(rect) // Slower but helps certain merge cases return d3rectSphericalVolume(rect) // Slower but helps certain merge cases
} else { // RTREE_USE_SPHERICAL_VOLUME } else { // RTREE_USE_SPHERICAL_VOLUME
return d2rectVolume(rect) // Faster but can cause poor merges return d3rectVolume(rect) // Faster but can cause poor merges
} // RTREE_USE_SPHERICAL_VOLUME } // RTREE_USE_SPHERICAL_VOLUME
} }
// Load branch buffer with branches from full node plus the extra branch. // Load branch buffer with branches from full node plus the extra branch.
func d2getBranches(node *d2nodeT, branch *d2branchT, parVars *d2partitionVarsT) { func d3getBranches(node *d3nodeT, branch *d3branchT, parVars *d3partitionVarsT) {
// Load the branch buffer // Load the branch buffer
for index := 0; index < d2maxNodes; index++ { for index := 0; index < d3maxNodes; index++ {
parVars.branchBuf[index] = node.branch[index] parVars.branchBuf[index] = node.branch[index]
} }
parVars.branchBuf[d2maxNodes] = *branch parVars.branchBuf[d3maxNodes] = *branch
parVars.branchCount = d2maxNodes + 1 parVars.branchCount = d3maxNodes + 1
// Calculate rect containing all in the set // Calculate rect containing all in the set
parVars.coverSplit = parVars.branchBuf[0].rect parVars.coverSplit = parVars.branchBuf[0].rect
for index := 1; index < d2maxNodes+1; index++ { for index := 1; index < d3maxNodes+1; index++ {
parVars.coverSplit = d2combineRect(&parVars.coverSplit, &parVars.branchBuf[index].rect) parVars.coverSplit = d3combineRect(&parVars.coverSplit, &parVars.branchBuf[index].rect)
} }
parVars.coverSplitArea = d2calcRectVolume(&parVars.coverSplit) parVars.coverSplitArea = d3calcRectVolume(&parVars.coverSplit)
} }
// Method #0 for choosing a partition: // Method #0 for choosing a partition:
@ -421,24 +421,24 @@ func d2getBranches(node *d2nodeT, branch *d2branchT, parVars *d2partitionVarsT)
// If one group gets too full (more would force other group to violate min // If one group gets too full (more would force other group to violate min
// fill requirement) then other group gets the rest. // fill requirement) then other group gets the rest.
// These last are the ones that can go in either group most easily. // These last are the ones that can go in either group most easily.
func d2choosePartition(parVars *d2partitionVarsT, minFill int) { func d3choosePartition(parVars *d3partitionVarsT, minFill int) {
var biggestDiff float64 var biggestDiff float64
var group, chosen, betterGroup int var group, chosen, betterGroup int
d2initParVars(parVars, parVars.branchCount, minFill) d3initParVars(parVars, parVars.branchCount, minFill)
d2pickSeeds(parVars) d3pickSeeds(parVars)
for ((parVars.count[0] + parVars.count[1]) < parVars.total) && for ((parVars.count[0] + parVars.count[1]) < parVars.total) &&
(parVars.count[0] < (parVars.total - parVars.minFill)) && (parVars.count[0] < (parVars.total - parVars.minFill)) &&
(parVars.count[1] < (parVars.total - parVars.minFill)) { (parVars.count[1] < (parVars.total - parVars.minFill)) {
biggestDiff = -1 biggestDiff = -1
for index := 0; index < parVars.total; index++ { for index := 0; index < parVars.total; index++ {
if d2notTaken == parVars.partition[index] { if d3notTaken == parVars.partition[index] {
curRect := &parVars.branchBuf[index].rect curRect := &parVars.branchBuf[index].rect
rect0 := d2combineRect(curRect, &parVars.cover[0]) rect0 := d3combineRect(curRect, &parVars.cover[0])
rect1 := d2combineRect(curRect, &parVars.cover[1]) rect1 := d3combineRect(curRect, &parVars.cover[1])
growth0 := d2calcRectVolume(&rect0) - parVars.area[0] growth0 := d3calcRectVolume(&rect0) - parVars.area[0]
growth1 := d2calcRectVolume(&rect1) - parVars.area[1] growth1 := d3calcRectVolume(&rect1) - parVars.area[1]
diff := growth1 - growth0 diff := growth1 - growth0
if diff >= 0 { if diff >= 0 {
group = 0 group = 0
@ -457,7 +457,7 @@ func d2choosePartition(parVars *d2partitionVarsT, minFill int) {
} }
} }
} }
d2classify(chosen, betterGroup, parVars) d3classify(chosen, betterGroup, parVars)
} }
// If one group too full, put remaining rects in the other // If one group too full, put remaining rects in the other
@ -468,26 +468,26 @@ func d2choosePartition(parVars *d2partitionVarsT, minFill int) {
group = 0 group = 0
} }
for index := 0; index < parVars.total; index++ { for index := 0; index < parVars.total; index++ {
if d2notTaken == parVars.partition[index] { if d3notTaken == parVars.partition[index] {
d2classify(index, group, parVars) d3classify(index, group, parVars)
} }
} }
} }
} }
// Copy branches from the buffer into two nodes according to the partition. // Copy branches from the buffer into two nodes according to the partition.
func d2loadNodes(nodeA, nodeB *d2nodeT, parVars *d2partitionVarsT) { func d3loadNodes(nodeA, nodeB *d3nodeT, parVars *d3partitionVarsT) {
for index := 0; index < parVars.total; index++ { for index := 0; index < parVars.total; index++ {
targetNodeIndex := parVars.partition[index] targetNodeIndex := parVars.partition[index]
targetNodes := []*d2nodeT{nodeA, nodeB} targetNodes := []*d3nodeT{nodeA, nodeB}
// It is assured that d2addBranch here will not cause a node split. // It is assured that d3addBranch here will not cause a node split.
d2addBranch(&parVars.branchBuf[index], targetNodes[targetNodeIndex], nil) d3addBranch(&parVars.branchBuf[index], targetNodes[targetNodeIndex], nil)
} }
} }
// Initialize a d2partitionVarsT structure. // Initialize a d3partitionVarsT structure.
func d2initParVars(parVars *d2partitionVarsT, maxRects, minFill int) { func d3initParVars(parVars *d3partitionVarsT, maxRects, minFill int) {
parVars.count[0] = 0 parVars.count[0] = 0
parVars.count[1] = 0 parVars.count[1] = 0
parVars.area[0] = 0 parVars.area[0] = 0
@ -495,24 +495,24 @@ func d2initParVars(parVars *d2partitionVarsT, maxRects, minFill int) {
parVars.total = maxRects parVars.total = maxRects
parVars.minFill = minFill parVars.minFill = minFill
for index := 0; index < maxRects; index++ { for index := 0; index < maxRects; index++ {
parVars.partition[index] = d2notTaken parVars.partition[index] = d3notTaken
} }
} }
func d2pickSeeds(parVars *d2partitionVarsT) { func d3pickSeeds(parVars *d3partitionVarsT) {
var seed0, seed1 int var seed0, seed1 int
var worst, waste float64 var worst, waste float64
var area [d2maxNodes + 1]float64 var area [d3maxNodes + 1]float64
for index := 0; index < parVars.total; index++ { for index := 0; index < parVars.total; index++ {
area[index] = d2calcRectVolume(&parVars.branchBuf[index].rect) area[index] = d3calcRectVolume(&parVars.branchBuf[index].rect)
} }
worst = -parVars.coverSplitArea - 1 worst = -parVars.coverSplitArea - 1
for indexA := 0; indexA < parVars.total-1; indexA++ { for indexA := 0; indexA < parVars.total-1; indexA++ {
for indexB := indexA + 1; indexB < parVars.total; indexB++ { for indexB := indexA + 1; indexB < parVars.total; indexB++ {
oneRect := d2combineRect(&parVars.branchBuf[indexA].rect, &parVars.branchBuf[indexB].rect) oneRect := d3combineRect(&parVars.branchBuf[indexA].rect, &parVars.branchBuf[indexB].rect)
waste = d2calcRectVolume(&oneRect) - area[indexA] - area[indexB] waste = d3calcRectVolume(&oneRect) - area[indexA] - area[indexB]
if waste > worst { if waste > worst {
worst = waste worst = waste
seed0 = indexA seed0 = indexA
@ -521,35 +521,35 @@ func d2pickSeeds(parVars *d2partitionVarsT) {
} }
} }
d2classify(seed0, 0, parVars) d3classify(seed0, 0, parVars)
d2classify(seed1, 1, parVars) d3classify(seed1, 1, parVars)
} }
// Put a branch in one of the groups. // Put a branch in one of the groups.
func d2classify(index, group int, parVars *d2partitionVarsT) { func d3classify(index, group int, parVars *d3partitionVarsT) {
parVars.partition[index] = group parVars.partition[index] = group
// Calculate combined rect // Calculate combined rect
if parVars.count[group] == 0 { if parVars.count[group] == 0 {
parVars.cover[group] = parVars.branchBuf[index].rect parVars.cover[group] = parVars.branchBuf[index].rect
} else { } else {
parVars.cover[group] = d2combineRect(&parVars.branchBuf[index].rect, &parVars.cover[group]) parVars.cover[group] = d3combineRect(&parVars.branchBuf[index].rect, &parVars.cover[group])
} }
// Calculate volume of combined rect // Calculate volume of combined rect
parVars.area[group] = d2calcRectVolume(&parVars.cover[group]) parVars.area[group] = d3calcRectVolume(&parVars.cover[group])
parVars.count[group]++ parVars.count[group]++
} }
// Delete a data rectangle from an index structure. // Delete a data rectangle from an index structure.
// Pass in a pointer to a d2rectT, the tid of the record, ptr to ptr to root node. // Pass in a pointer to a d3rectT, the tid of the record, ptr to ptr to root node.
// Returns 1 if record not found, 0 if success. // Returns 1 if record not found, 0 if success.
// d2removeRect provides for eliminating the root. // d3removeRect provides for eliminating the root.
func d2removeRect(rect *d2rectT, id interface{}, root **d2nodeT) bool { func d3removeRect(rect *d3rectT, id interface{}, root **d3nodeT) bool {
var reInsertList *d2listNodeT var reInsertList *d3listNodeT
if !d2removeRectRec(rect, id, *root, &reInsertList) { if !d3removeRectRec(rect, id, *root, &reInsertList) {
// Found and deleted a data item // Found and deleted a data item
// Reinsert any branches from eliminated nodes // Reinsert any branches from eliminated nodes
for reInsertList != nil { for reInsertList != nil {
@ -557,7 +557,7 @@ func d2removeRect(rect *d2rectT, id interface{}, root **d2nodeT) bool {
for index := 0; index < tempNode.count; index++ { for index := 0; index < tempNode.count; index++ {
// TODO go over this code. should I use (tempNode->m_level - 1)? // TODO go over this code. should I use (tempNode->m_level - 1)?
d2insertRect(&tempNode.branch[index], root, tempNode.level) d3insertRect(&tempNode.branch[index], root, tempNode.level)
} }
reInsertList = reInsertList.next reInsertList = reInsertList.next
} }
@ -575,21 +575,21 @@ func d2removeRect(rect *d2rectT, id interface{}, root **d2nodeT) bool {
} }
// Delete a rectangle from non-root part of an index structure. // Delete a rectangle from non-root part of an index structure.
// Called by d2removeRect. Descends tree recursively, // Called by d3removeRect. Descends tree recursively,
// merges branches on the way back up. // merges branches on the way back up.
// Returns 1 if record not found, 0 if success. // Returns 1 if record not found, 0 if success.
func d2removeRectRec(rect *d2rectT, id interface{}, node *d2nodeT, listNode **d2listNodeT) bool { func d3removeRectRec(rect *d3rectT, id interface{}, node *d3nodeT, listNode **d3listNodeT) bool {
if node.isInternalNode() { // not a leaf node if node.isInternalNode() { // not a leaf node
for index := 0; index < node.count; index++ { for index := 0; index < node.count; index++ {
if d2overlap(*rect, node.branch[index].rect) { if d3overlap(*rect, node.branch[index].rect) {
if !d2removeRectRec(rect, id, node.branch[index].child, listNode) { if !d3removeRectRec(rect, id, node.branch[index].child, listNode) {
if node.branch[index].child.count >= d2minNodes { if node.branch[index].child.count >= d3minNodes {
// child removed, just resize parent rect // child removed, just resize parent rect
node.branch[index].rect = d2nodeCover(node.branch[index].child) node.branch[index].rect = d3nodeCover(node.branch[index].child)
} else { } else {
// child removed, not enough entries in node, eliminate node // child removed, not enough entries in node, eliminate node
d2reInsert(node.branch[index].child, listNode) d3reInsert(node.branch[index].child, listNode)
d2disconnectBranch(node, index) // Must return after this call as count has changed d3disconnectBranch(node, index) // Must return after this call as count has changed
} }
return false return false
} }
@ -599,7 +599,7 @@ func d2removeRectRec(rect *d2rectT, id interface{}, node *d2nodeT, listNode **d2
} else { // A leaf node } else { // A leaf node
for index := 0; index < node.count; index++ { for index := 0; index < node.count; index++ {
if node.branch[index].data == id { if node.branch[index].data == id {
d2disconnectBranch(node, index) // Must return after this call as count has changed d3disconnectBranch(node, index) // Must return after this call as count has changed
return false return false
} }
} }
@ -607,9 +607,9 @@ func d2removeRectRec(rect *d2rectT, id interface{}, node *d2nodeT, listNode **d2
} }
} }
// Decide whether two rectangles d2overlap. // Decide whether two rectangles d3overlap.
func d2overlap(rectA, rectB d2rectT) bool { func d3overlap(rectA, rectB d3rectT) bool {
for index := 0; index < d2numDims; index++ { for index := 0; index < d3numDims; index++ {
if rectA.min[index] > rectB.max[index] || if rectA.min[index] > rectB.max[index] ||
rectB.min[index] > rectA.max[index] { rectB.min[index] > rectA.max[index] {
return false return false
@ -620,21 +620,21 @@ func d2overlap(rectA, rectB d2rectT) bool {
// Add a node to the reinsertion list. All its branches will later // Add a node to the reinsertion list. All its branches will later
// be reinserted into the index structure. // be reinserted into the index structure.
func d2reInsert(node *d2nodeT, listNode **d2listNodeT) { func d3reInsert(node *d3nodeT, listNode **d3listNodeT) {
newListNode := &d2listNodeT{} newListNode := &d3listNodeT{}
newListNode.node = node newListNode.node = node
newListNode.next = *listNode newListNode.next = *listNode
*listNode = newListNode *listNode = newListNode
} }
// d2search in an index tree or subtree for all data retangles that d2overlap the argument rectangle. // d3search in an index tree or subtree for all data retangles that d3overlap the argument rectangle.
func d2search(node *d2nodeT, rect d2rectT, foundCount int, resultCallback func(data interface{}) bool) (int, bool) { func d3search(node *d3nodeT, rect d3rectT, foundCount int, resultCallback func(data interface{}) bool) (int, bool) {
if node.isInternalNode() { if node.isInternalNode() {
// This is an internal node in the tree // This is an internal node in the tree
for index := 0; index < node.count; index++ { for index := 0; index < node.count; index++ {
if d2overlap(rect, node.branch[index].rect) { if d3overlap(rect, node.branch[index].rect) {
var ok bool var ok bool
foundCount, ok = d2search(node.branch[index].child, rect, foundCount, resultCallback) foundCount, ok = d3search(node.branch[index].child, rect, foundCount, resultCallback)
if !ok { if !ok {
// The callback indicated to stop searching // The callback indicated to stop searching
return foundCount, false return foundCount, false
@ -644,7 +644,7 @@ func d2search(node *d2nodeT, rect d2rectT, foundCount int, resultCallback func(d
} else { } else {
// This is a leaf node // This is a leaf node
for index := 0; index < node.count; index++ { for index := 0; index < node.count; index++ {
if d2overlap(rect, node.branch[index].rect) { if d3overlap(rect, node.branch[index].rect) {
id := node.branch[index].data id := node.branch[index].data
foundCount++ foundCount++
if !resultCallback(id) { if !resultCallback(id) {