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 }