github.com/coyove/common@v0.0.0-20240403014525-f70e643f9de8/quadtree/quartree.go (about)

     1  // Simple quad-tree
     2  package quadtree
     3  
     4  import (
     5  	"bytes"
     6  	"crypto/rand"
     7  	"encoding/base64"
     8  	"fmt"
     9  	"math"
    10  	"strconv"
    11  	"strings"
    12  )
    13  
    14  var (
    15  	directions = []string{"u", "d", "l", "r", "ul", "ur", "dl", "dr"}
    16  	MaxElems   = 8
    17  )
    18  
    19  type Element struct {
    20  	Point
    21  	Data []byte
    22  }
    23  
    24  type Point struct {
    25  	Ix, Iy uint64
    26  }
    27  
    28  func Pt(x, y float64) Point               { return Point{^math.Float64bits(x), ^math.Float64bits(y)} }
    29  func (p Point) X() float64                { return math.Float64frombits(^p.Ix) }
    30  func (p Point) Y() float64                { return math.Float64frombits(^p.Iy) }
    31  func (p Point) Sub(p2 Point) Point        { return Pt(p.X()-p2.X(), p.Y()-p2.Y()) }
    32  func (p Point) Distance(p2 Point) float64 { p = p.Sub(p2); return math.Sqrt(p.X()*p.X() + p.Y()*p.Y()) }
    33  func (p Point) String() string            { return fmt.Sprintf("(%.2f,%.2f)", p.X(), p.Y()) }
    34  func (p Point) Marshal() string           { return fmt.Sprintf("%016x%016x", p.Ix, p.Iy) }
    35  func (e Element) String() string          { return fmt.Sprintf("<%q-%v>", e.Data, e.Point) }
    36  
    37  type QuadTree struct {
    38  	ID     string
    39  	Parent string
    40  	AABB   [2]Point
    41  	MinBox float64
    42  	O      [4]string         `json:"-"` // stored as "0", "1", "2" and "3"
    43  	Elems  map[Point]Element `json:"-"` // stored as a hashmap: Point -> Element
    44  	mgr    Database
    45  }
    46  
    47  func NewQuadTree(mgr Database, tl, br Point, fill func(t *QuadTree)) (QuadTree, error) {
    48  	var id [12]byte
    49  	rand.Read(id[:])
    50  	t := QuadTree{ID: base64.URLEncoding.EncodeToString(id[:]), AABB: [2]Point{tl, br}, mgr: mgr}
    51  	if fill != nil {
    52  		fill(&t)
    53  	}
    54  	return t, mgr.Store(t)
    55  }
    56  
    57  func (t QuadTree) insideOrth(p Point) (orthantIndex int, topLeft, bottomRight Point, err error) { // returns 0-3
    58  	tl, br := t.AABB[0], t.AABB[1]
    59  	if p.X() > br.X() || p.X() < tl.X() {
    60  		return 0, Point{}, Point{}, fmt.Errorf("x outside")
    61  	} else if p.Y() > tl.Y() || p.Y() < br.Y() {
    62  		return 0, Point{}, Point{}, fmt.Errorf("y outside")
    63  	} else if tl == (Point{}) || br == (Point{}) {
    64  		return 0, Point{}, Point{}, fmt.Errorf("invalid AABB box")
    65  	}
    66  
    67  	center := Pt((tl.X()+br.X())/2, (tl.Y()+br.Y())/2)
    68  	if p.X() >= center.X() && p.Y() > center.Y() {
    69  		return 0, Pt(center.X(), tl.Y()), Pt(br.X(), center.Y()), nil
    70  	} else if p.X() < center.X() && p.Y() >= center.Y() {
    71  		return 1, tl, center, nil
    72  	} else if p.X() <= center.X() && p.Y() < center.Y() {
    73  		return 2, Pt(tl.X(), center.Y()), Pt(center.X(), br.Y()), nil
    74  	}
    75  	return 3, center, br, nil // center itself will be inside 3
    76  }
    77  
    78  func (t QuadTree) Put(p Point, v []byte) error {
    79  	if t.isleaf() {
    80  		if len(t.Elems) < MaxElems {
    81  			// Have spare room
    82  			return t.mgr.StoreElement(t.ID, Element{p, v})
    83  		}
    84  
    85  		if size := t.AABB[0].Sub(t.AABB[1]); math.Abs(size.X())/2 < t.MinBox || math.Abs(size.Y())/2 < t.MinBox {
    86  			// Cannot split anymore
    87  			return t.mgr.StoreElement(t.ID, Element{p, v})
    88  		}
    89  
    90  		// Split node
    91  		for _, e := range t.Elems {
    92  			if err := t.calcPutOrth(e.Point, e.Data); err != nil {
    93  				return err
    94  			}
    95  		}
    96  
    97  		if err := t.mgr.DeleteAllElements(t.ID); err != nil {
    98  			return err
    99  		}
   100  	}
   101  	return t.calcPutOrth(p, v)
   102  }
   103  
   104  func (t QuadTree) calcPutOrth(p Point, v []byte) error {
   105  	if p == (Point{}) {
   106  		return nil
   107  	}
   108  
   109  	i, iul, idr, err := t.insideOrth(p)
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	if t.O[i] == "" {
   115  		tr, err := NewQuadTree(t.mgr, iul, idr, func(nt *QuadTree) {
   116  			nt.MinBox = t.MinBox
   117  			nt.Parent = t.ID
   118  		})
   119  		if err != nil {
   120  			return err
   121  		}
   122  		if err := t.mgr.StoreElement(tr.ID, Element{p, v}); err != nil {
   123  			return err
   124  		}
   125  		existed, err := t.mgr.StoreOrthant(t.ID, i, tr.ID)
   126  		if err != nil {
   127  			return err
   128  		}
   129  		if existed {
   130  			t, err := t.LoadTree(t.ID) // reload
   131  			if err != nil {
   132  				return err
   133  			}
   134  			return t.calcPutOrth(p, v)
   135  		}
   136  		return nil
   137  	}
   138  	t, err = t.LoadTree(t.O[i])
   139  	if err != nil {
   140  		return err
   141  	}
   142  	return t.Put(p, v)
   143  }
   144  
   145  func (t QuadTree) Get(p Point) (Element, error) {
   146  	e, _, err := t.find(nil, p)
   147  	return e, err
   148  }
   149  
   150  func (t QuadTree) Remove(p Point) (Element, error) {
   151  	e, tid, err := t.find(nil, p)
   152  	if err != nil {
   153  		return e, err
   154  	}
   155  	return e, t.mgr.DeleteElement(tid, e)
   156  }
   157  
   158  func (t QuadTree) find(buf *bytes.Buffer, p Point) (Element, string, error) {
   159  	if t.isleaf() {
   160  		if e, ok := t.Elems[p]; ok {
   161  			return e, t.ID, nil
   162  		}
   163  	}
   164  	i, _, _, err := t.insideOrth(p)
   165  	if err != nil {
   166  		return Element{}, "", err
   167  	}
   168  	// fmt.Println(p, t.Pos, i, t.O)
   169  	if t.O[i] == "" {
   170  		return Element{}, t.ID, fmt.Errorf("%v not found", p)
   171  	}
   172  	if buf != nil {
   173  		buf.WriteByte(byte(i))
   174  	}
   175  	t, err = t.LoadTree(t.O[i])
   176  	if err != nil {
   177  		return Element{}, "", err
   178  	}
   179  	return t.find(buf, p)
   180  }
   181  
   182  func (t QuadTree) Iterate(cb func(Element) error) error {
   183  	if t.isleaf() {
   184  		for _, e := range t.Elems {
   185  			if x, y, tl, br := e.Point.X(), e.Point.Y(), t.AABB[0], t.AABB[1]; !(x >= tl.X() && x <= br.X() &&
   186  				y >= br.Y() && y <= tl.Y()) {
   187  				return fmt.Errorf("invalid point outside quadrant: p=%v, aabb=%v-%v", e.Point, tl, br)
   188  			}
   189  			if err := cb(e); err != nil {
   190  				return err
   191  			}
   192  		}
   193  	} else {
   194  		for _, o := range t.O {
   195  			if o != "" {
   196  				ot, err := t.LoadTree(o)
   197  				if err != nil {
   198  					return err
   199  				}
   200  				if err := ot.Iterate(cb); err != nil {
   201  					return err
   202  				}
   203  			}
   204  		}
   205  	}
   206  	return nil
   207  }
   208  
   209  func (t QuadTree) MaxDepth() (depth int, leaves int, err error) {
   210  	d, n := 0, len(t.Elems)
   211  	if !t.isleaf() {
   212  		n = 0
   213  	}
   214  	for _, o := range t.O {
   215  		if o != "" {
   216  			ot, err := t.LoadTree(o)
   217  			if err != nil {
   218  				return 0, 0, err
   219  			}
   220  			od, on, err := ot.MaxDepth()
   221  			if err != nil {
   222  				return 0, 0, err
   223  			}
   224  			if od > d {
   225  				d = od
   226  			}
   227  			n += on
   228  		}
   229  	}
   230  	return d + 1, n, nil
   231  }
   232  
   233  func (t QuadTree) String() string { return t.str(0, "") }
   234  
   235  func (t QuadTree) str(ident int, locode string) string {
   236  	p := bytes.Buffer{}
   237  	prefix := strings.Repeat("  ", ident)
   238  	if t.isleaf() {
   239  		p.WriteString(prefix)
   240  		for _, e := range t.Elems {
   241  			p.WriteString(e.String())
   242  			p.WriteString(" ")
   243  		}
   244  		p.WriteString("\n")
   245  	} else {
   246  		for i, o := range t.O {
   247  			if o != "" {
   248  				p.WriteString(prefix)
   249  				p.WriteString(locode)
   250  				p.WriteString(strconv.Itoa(i))
   251  				p.WriteString(":\n")
   252  
   253  				ot, err := t.LoadTree(o)
   254  				if err != nil {
   255  					p.WriteString(prefix)
   256  					p.WriteString("  error: ")
   257  					p.WriteString(err.Error())
   258  				} else {
   259  					p.WriteString(ot.str(ident+1, locode+strconv.Itoa(i)))
   260  				}
   261  			}
   262  		}
   263  	}
   264  	return p.String()
   265  }
   266  
   267  func (t QuadTree) isleaf() bool { return len(t.O[0])+len(t.O[1])+len(t.O[2])+len(t.O[3]) == 0 }
   268  
   269  func (t QuadTree) LoadTree(id string) (QuadTree, error) {
   270  	lt, err := t.mgr.Load(id)
   271  	lt.mgr = t.mgr
   272  	return lt, err
   273  }
   274  
   275  func (t QuadTree) SetDataSource(mgr Database) QuadTree {
   276  	t.mgr = mgr
   277  	return t
   278  }