Golang program to implement a quadtree for spatial indexing


Spatial indexing is a crucial technique for efficiently organising and querying spatial data. Quadtree is a popular data structure used for spatial indexing, dividing a two-dimensional space into smaller regions. In this article, we will explore two different examples to implement a Quadtree in Golang. The examples demonstrated below are going to perform operations like initialization, insertion, display and visualisation of data of a quad tree.

Explanation

A quadtree as a tree data structure ensures that each node can have up to four children, a property commonly needed to partition a 2D space into smaller regions, allowing efficient indexing and querying of spatial data. It makes quadtree a perfect fit for scenarios like geographical information systems (GIS), collision detection in gaming, and more.

Syntax

func NewQuadTree(b Boundary, c int) *QuadTree

The syntax represents a method called NewQuadTree which initializes and returns a new instance of the QuadTree structure, taking a Boundary representing a rectangular area and an integer representing capacity as input parameters.

func (qt *QuadTree) subdivide()

The syntax represents a method called subdivide which splits the current QuadTree calculates the center of the current boundary, to node into four child nodes each representing a quadrant of the boundary.

Algorithm

  • Start by defining the Quadtree Node Structure.

  • Implement Node Insertion Logic.

  • Define Splitting Conditions.

  • Implement Querying Logic.

  • Create a Sample Scenario and Query the Quadtree.

Example 1

In this example, we implement a quadtree in go, using Point and Boundary structs to represent 2D points and rectangular areas. NewQuadTree function initializes a QuadTree, Insert method adds points and subdivides nodes when capacity is reached.. In the main function, a QuadTree is created with a boundary and capacity.

package main
import (
	"fmt"
	"strings"
)
type Point struct{ x, y float64 }
type Boundary struct{ xMin, yMin, xMax, yMax float64 }
type QuadTree struct {
	boundary Boundary
	capacity int
	points   []Point
}
func NewQuadTree(b Boundary, c int) *QuadTree {
	return &QuadTree{boundary: b, capacity: c}
}
func (qt *QuadTree) Insert(p Point) {
	if qt.boundary.containsPoint(p) && len(qt.points) < qt.capacity {
		qt.points = append(qt.points, p)
	}
}
func (b *Boundary) containsPoint(p Point) bool {
	return p.x >= b.xMin && p.x <= b.xMax && p.y >= b.yMin && p.y <= b.yMax
}
func main() {
	qt := NewQuadTree(Boundary{0, 0, 100, 100}, 4)
	points := []Point{{25, 75}, {60, 40}, {10, 20}, {80, 90}, {45, 60}, {70, 30}, {15, 85}, {90, 10}, {30, 30}, {70, 70}}
	for _, p := range points {
		qt.Insert(p)
	}
	fmt.Println("Quadtree after insertion:")
	displayQuadTree(qt, 0)
}
func displayQuadTree(qt *QuadTree, d int) {
	fmt.Printf("\n%sBoundary: (%.2f, %.2f, %.2f, %.2f), Points: %d", getIndent(d), qt.boundary.xMin, qt.boundary.yMin, qt.boundary.xMax, qt.boundary.yMax, len(qt.points))
}
func getIndent(d int) string {
	return strings.Repeat("  ", d)
}

Output

Quadtree after insertion:

Boundary: (0.00, 0.00, 100.00, 100.00), Points: 4

Example 2

In this example, to implement a quadtree in go, we have defined a QuadTree struct having a boundary, capacity, points, and child nodes, and which supports insertion of points and querying points within a given boundary. Insert method adds points to the QuadTree and subdivides nodes if capacity is exceeded, while the QueryRange function retrieves points within a specified boundary. The Boundary struct defines a rectangular area, and its methods check point containment and intersection with other boundaries. The main function initializes and demonstrates the QuadTree structure using the displayQuadTree function and finally queries points within a range.

package main
import "fmt"
type Point struct{ x, y float64 }
type Boundary struct{ xMin, yMin, xMax, yMax float64 }
type QuadTree struct {
    boundary  Boundary
	capacity  int
	points	[]Point
	divided   bool
	nw, ne, sw, se *QuadTree
}
func NewQuadTree(b Boundary, c int) *QuadTree { return &QuadTree{boundary: b, capacity: c} }
func (qt *QuadTree) Insert(p Point) {
	if !qt.boundary.containsPoint(p) { return }
	if len(qt.points) < qt.capacity { qt.points = append(qt.points, p); return }
	if !qt.divided { qt.subdivide() }
	qt.nw.Insert(p); qt.ne.Insert(p); qt.sw.Insert(p); qt.se.Insert(p)
}
func (qt *QuadTree) QueryRange(rb Boundary) []Point {
	var foundPoints []Point
	if !qt.boundary.intersectsBoundary(rb) { return foundPoints }
	for _, p := range qt.points { if rb.containsPoint(p) { foundPoints = append(foundPoints, p) } }
	if qt.divided {
    	foundPoints = append(foundPoints, qt.nw.QueryRange(rb)...)
        foundPoints = append(foundPoints, qt.ne.QueryRange(rb)...)
    	foundPoints = append(foundPoints, qt.sw.QueryRange(rb)...)
    	foundPoints = append(foundPoints, qt.se.QueryRange(rb)...)
    }
    return foundPoints
}
func (b *Boundary) containsPoint(p Point) bool {
	return p.x >= b.xMin && p.x <= b.xMax && p.y >= b.yMin && p.y <= b.yMax
}
func (b *Boundary) intersectsBoundary(rb Boundary) bool {
	return !(rb.xMax < b.xMin || rb.xMin > b.xMax || rb.yMax < b.yMin || rb.yMin > b.yMax)
}
func (qt *QuadTree) subdivide() {
	cx, cy := (qt.boundary.xMin+qt.boundary.xMax)/2, (qt.boundary.yMin+qt.boundary.yMax)/2
	qt.nw, qt.ne, qt.sw, qt.se = NewQuadTree(Boundary{qt.boundary.xMin, qt.boundary.yMin, cx, cy}, qt.capacity), NewQuadTree(Boundary{cx, qt.boundary.yMin, qt.boundary.xMax, cy}, qt.capacity), NewQuadTree(Boundary{qt.boundary.xMin, cy, cx, qt.boundary.yMax}, qt.capacity), NewQuadTree(Boundary{cx, cy, qt.boundary.xMax, qt.boundary.yMax}, qt.capacity)
	qt.divided = true
}
func main() {
	qt := NewQuadTree(Boundary{0, 0, 100, 100}, 4)
	points := []Point{{25, 75}, {60, 40}, {10, 20}, {80, 90}, {45, 60}, {70, 30}}
	fmt.Println("Inserting points:")
	for _, p := range points { qt.Insert(p); fmt.Printf("(%v, %v) ", p.x, p.y) }
	fmt.Println("\nQuadtree structure:")
	displayQuadTree(qt, 0)
	queryRange := Boundary{20, 70, 30, 80}
	foundPoints := qt.QueryRange(queryRange)
	fmt.Println("\nQuery Range:", queryRange)
	fmt.Println("Points within query range:", foundPoints)
}
func displayQuadTree(qt *QuadTree, d int) {
	fmt.Printf("\n%sBoundary: (%.2f, %.2f, %.2f, %.2f), Points: %d", getIndent(d), qt.boundary.xMin, qt.boundary.yMin, qt.boundary.xMax, qt.boundary.yMax, len(qt.points))
	if qt.divided { displayQuadTree(qt.nw, d+1); displayQuadTree(qt.ne, d+1); displayQuadTree(qt.sw, d+1); displayQuadTree(qt.se, d+1) }
}
func getIndent(d int) string { i := ""; for j := 0; j < d; j++ { i += "  " }; return i }

Output

Inserting points:
(25, 75) (60, 40) (10, 20) (80, 90) (45, 60) (70, 30)
Quadtree structure:
 
Boundary: (0.00, 0.00, 100.00, 100.00), Points: 4
 Boundary: (0.00, 0.00, 50.00, 50.00), Points: 0
Boundary: (50.00, 0.00, 100.00, 50.00), Points: 1
Boundary: (0.00, 50.00, 50.00, 100.00), Points: 1
Boundary: (50.00, 50.00, 100.00, 100.00), Points: 0
Query Range: {20 70 30 80}
Points within query range: [{25 75}]

Real Life Implementation

  • Physics Simulations: Collision detection is a critical component of physics simulations and video games, and the usage of quadtrees has shown to be an efficient method. Objects are placed among the nodes of a tree structure in a simulation, allowing for the efficient detection of collisions by selecting inspecting the nodes that are relevant to the current scenario. This solution successfully reduces the number of pairwise collision tests while increasing simulation performance.

  • Fractal Generation and Terrain Modeling: Quadtrees are often used in the creation of fractal landscapes and terrain models. Quadtree-based algorithms may produce detailed and realistic landscape representations by repeatedly dividing it down into smaller portions.

Conclusion

A QuadTree when implemented for spatial indexing provides an efficient solution for managing and querying spatial data. In this article, we explored two different methods to implement a Quadtree in Golang. The first approach focused on inserting points and querying using text-based output, highlighting the internal structure of the QuadTree. The second approach enhanced this by visualizing the structure through indentation, offering a clearer depiction of subdivision and data distribution.

Updated on: 18-Oct-2023

91 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements