ntree/ntree.go

222 lines
6.3 KiB
Go
Raw Normal View History

2015-03-25 16:53:31 +00:00
// NTrees are an N-dimensional subdividing spacial representation.
// Common dimension-specific types are the 2-dimensional (quadtree) and
// 3-dimensional (octree) variants. This library supports an arbitrary number of
// dimensions, implemented in the same manner as those specific cases.
package ntree
import (
"errors"
"strconv"
"sync"
"github.com/cznic/mathutil"
)
// Maximum number of dimensions handled by this lib. Currently restricted by
// number of usable bits in Go's int type.
const MaxN = 63
// NTree is a bounding box in N-dimensional space, along with optional data
// and children.
type NTree struct {
// The bounding n-dimensional box for this ntree. It should always
// be true that origin[i] += bounds[i] contains p.coords[i].
center, bounds []float64
// Optional piece of data to associate with this node. Location may be
// imprecise on leaf nodes.
p *Point
// Slice for child storage, should be 2^n if initialized.
children []*NTree
// Used to coordinate write operations, concurrency.
mutex sync.RWMutex
// keep track of child point counts under each node, useful for
// histograms, density predictions, etc.
count uint64
}
// New creates an ntree root node, using N dimensional slices for
// the center coordinates and relative bounds of the tree space.
// Bounds slice values must be positive, as they define
// a range of center[i] +- bounds[i] for each dimension.
//
// Returns an error if center and bounds don't have the same cardinality,
// or a bounds dimension is <= 0.
func New(center, bounds []float64) (nt *NTree, err error) {
if len(center) > MaxN {
return nil, errors.New("64 bit ints limit this library to <= 63 dimensions")
}
if len(center) != len(bounds) {
return nil, errors.New("center and bounds have mismatched lengths")
}
if len(center) == 0 {
return nil, errors.New("Can't have 0-dimensional ntree")
}
for i := range bounds {
if bounds[i] <= 0 {
return nil, errors.New("Dimension " + strconv.FormatInt(int64(i), 10) +
" has bounding size <= 0.")
}
}
nt = new(NTree)
nt.center = center
nt.bounds = bounds
return nt, nil
}
// N returns the number of dimensions (N) for this NTree.
func (nt *NTree) N() int {
return len(nt.center)
}
// Center returns the center coordinates for this NTree node.
func (nt *NTree) Center() []float64 {
nt.mutex.RLock()
defer nt.mutex.RUnlock()
return nt.center
}
// Bounds returns the positive bounding dimensions from center for this NTree
// node. This node covers the entire space of Center() +- Bounds().
func (nt *NTree) Bounds() []float64 {
nt.mutex.RLock()
defer nt.mutex.RUnlock()
return nt.bounds
}
// BoundPoints returns the min and max points for this NTree node.
// This is a shortcut instead of doing the Center() +- Bounds() math manually.
func (nt *NTree) BoundPoints() (min, max []float64) {
nt.mutex.RLock()
defer nt.mutex.RUnlock()
min = make([]float64, len(nt.center))
max = make([]float64, len(nt.center))
for i := range nt.center {
min[i] = nt.center[i] - nt.bounds[i]
max[i] = nt.center[i] + nt.bounds[i]
}
return min, max
}
// Point returns the optional data chunk associated with this NTree nodes.
// This will return null if there's no Point on this node, which should happen
// on any non-leaf node.
func (nt *NTree) Point() *Point {
nt.mutex.RLock()
defer nt.mutex.RUnlock()
return nt.p
}
// Count returns this node's estimate of how many Points lie within it.
func (nt *NTree) Count() uint64 {
nt.mutex.RLock()
defer nt.mutex.RUnlock()
return nt.count
}
// Contains checks if point p is within the bounds of the ntree.
// Returns an error if len(p) != nt.N().
func (nt *NTree) Contains(p *Point) (bool, error) {
nt.mutex.RLock()
defer nt.mutex.RUnlock()
if p == nil {
return false, errors.New("Point is nil")
}
if len(p.Coords) != nt.N() {
return false, errors.New("Point is " + strconv.FormatUint(uint64(len(p.Coords)), 10) +
" dimensional, NTree is " + strconv.FormatUint(uint64(nt.N()), 10))
}
// fmt.Println(p)
for i := range p.Coords {
if (nt.center[i]-nt.bounds[i]) > p.Coords[i] || (nt.center[i]+nt.bounds[i]) < p.Coords[i] {
return false, nil
}
}
return true, nil
}
// Bitwise operations on array indices are used to keep track of what subset of
// space each child occupies, as described here:
// http://www.brandonpelfrey.com/blog/coding-a-simple-octree/
func hasBit(n int, pos uint) bool {
val := n & (1 << pos)
return (val > 0)
}
func setBit(n int, pos uint) int {
n |= (1 << pos)
return n
}
// Add inserts a new Point into the NTree. Returns an error on any failure,
// or nil.
func (nt *NTree) Add(p *Point) error {
in, err := nt.Contains(p)
if err != nil {
return err
}
if !in {
return errors.New("Point doesn't fall within bounds of NTree.")
}
nt.mutex.Lock()
if nt.p == nil && nt.children == nil {
defer nt.mutex.Unlock()
// simplest case, add to current node
nt.p = p
nt.count++
return nil
}
if nt.children != nil {
defer nt.mutex.Unlock()
// recurse into children by generating child bounding bitmask
var target int
for j := range nt.center {
if p.Coords[j] > nt.center[j] {
target = setBit(target, uint(j))
}
}
err = nt.children[target].Add(p)
if err == nil {
nt.count++
}
return err
}
if nt.p != nil && nt.children == nil {
// create children, re-add current node's Point data, then add Point p.
size := mathutil.ModPowUint64(2, uint64(nt.N()), mathutil.MaxInt)
nt.children = make([]*NTree, size)
// create new child nodes with correct bounds
for i := range nt.children {
// determine child dimensions
center := make([]float64, nt.N())
bounds := make([]float64, nt.N())
for j := range center {
// use bitmask of child index to determine dimension range for child.
// positive bit means positive range, otherwise negative range.
if hasBit(i, uint(j)) {
bounds[j] = nt.bounds[j] / 2.0
center[j] = nt.center[j] + bounds[j]
} else {
bounds[j] = nt.bounds[j] / 2.0
center[j] = nt.center[j] - bounds[j]
}
}
if nt.children[i], err = New(center, bounds); err != nil {
return err
}
}
// remove current Point data and re-add so it cascades into child nodes.
// need to bounce the mutex for this, prossible race condition?
curP := nt.p
nt.p = nil
nt.count--
nt.mutex.Unlock()
if err = nt.Add(curP); err != nil {
return err
}
// now add new Point
return nt.Add(p)
}
return nil
}