package index import ( "github.com/tidwall/tile38/index/qtree" "github.com/tidwall/tile38/index/rtree" ) // Item represents an index item. type Item interface { qtree.Item rtree.Item } // FlexItem can represent a point or a rectangle type FlexItem struct { MinX, MinY, MaxX, MaxY float64 } // Rect returns the rectangle func (item *FlexItem) Rect() (minX, minY, maxX, maxY float64) { return item.MinX, item.MinY, item.MaxX, item.MaxY } // Point returns the point func (item *FlexItem) Point() (x, y float64) { return item.MinX, item.MinY } // Index is a geospatial index type Index struct { q *qtree.QTree r *rtree.RTree np map[*qtree.Point]Item // normalized points npr map[Item]*qtree.Point // normalized points nr map[*rtree.Rect]Item // normalized points nrr map[Item][]*rtree.Rect // normalized points mulm map[Item]bool // store items that contain multiple rects } // New create a new index func New() *Index { return &Index{ q: qtree.New(-180, -90, 180, 90), r: rtree.New(), mulm: make(map[Item]bool), np: make(map[*qtree.Point]Item), npr: make(map[Item]*qtree.Point), nr: make(map[*rtree.Rect]Item), nrr: make(map[Item][]*rtree.Rect), } } // Insert inserts an item into the index func (ix *Index) Insert(item Item) { minX, minY, maxX, maxY := item.Rect() if minX == maxX && minY == maxY { x, y, normd := normPoint(minY, minX) if normd { nitem := &qtree.Point{X: x, Y: y} ix.np[nitem] = item ix.npr[item] = nitem ix.q.Insert(nitem) } else { ix.q.Insert(item) } } else { mins, maxs, normd := normRect(minY, minX, maxY, maxX) if normd { var nitems []*rtree.Rect for i := range mins { 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} ix.nr[nitem] = item nitems = append(nitems, nitem) ix.r.Insert(nitem) } ix.nrr[item] = nitems if len(mins) > 1 { ix.mulm[item] = true } } else { ix.r.Insert(item) } } return } // Remove removed an item from the index func (ix *Index) Remove(item Item) { minX, minY, maxX, maxY := item.Rect() if minX == maxX && minY == maxY { if nitem, ok := ix.npr[item]; ok { ix.q.Remove(nitem) delete(ix.np, nitem) delete(ix.npr, item) } else { ix.q.Remove(item) } } else { if nitems, ok := ix.nrr[item]; ok { for _, nitem := range nitems { ix.r.Remove(nitem) delete(ix.nr, nitem) } delete(ix.npr, item) } else { ix.r.Remove(item) } } } // Count counts all items in the index. func (ix *Index) Count() int { count := 0 ix.Search(0, -90, -180, 90, 180, func(item Item) bool { count++ return true }) return count } // RemoveAll removes all items from the index. func (ix *Index) RemoveAll() { ix.r.RemoveAll() ix.q.RemoveAll() } func (ix *Index) getQTreeItem(item qtree.Item) Item { switch item := item.(type) { case Item: return item case *qtree.Point: return ix.np[item] } return nil } func (ix *Index) getRTreeItem(item rtree.Item) Item { switch item := item.(type) { case Item: return item case *rtree.Rect: return ix.nr[item] } return nil } // 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) { var idx uint64 var active = true var idm = make(map[Item]bool) mins, maxs, _ := normRect(swLat, swLon, neLat, neLon) // Points if len(mins) == 1 { // There is only one rectangle. // Simply return all quad points in that search rect. if active { ix.q.Search(mins[0][0], mins[0][1], maxs[0][0], maxs[0][1], func(item qtree.Item) bool { if idx >= cursor { iitm := ix.getQTreeItem(item) if iitm != nil { active = iterator(iitm) } } idx++ return active }) } // It's possible that a r rect may span multiple entries. Check mulm map for spanning rects. if active { ix.r.Search(mins[0][0], mins[0][1], maxs[0][0], maxs[0][1], func(item rtree.Item) bool { if idx >= cursor { iitm := ix.getRTreeItem(item) if iitm != nil { if ix.mulm[iitm] { if !idm[iitm] { idm[iitm] = true active = iterator(iitm) } } else { active = iterator(iitm) } } } idx++ return active }) } } else { // There are multiple rectangles. Duplicates might occur. for i := range mins { if active { ix.q.Search(mins[i][0], mins[i][1], maxs[i][0], maxs[i][1], func(item qtree.Item) bool { if idx >= cursor { iitm := ix.getQTreeItem(item) if iitm != nil { if !idm[iitm] { idm[iitm] = true active = iterator(iitm) } } else { active = iterator(iitm) } } idx++ return active }) } } for i := range mins { if active { ix.r.Search(mins[i][0], mins[i][1], maxs[i][0], maxs[i][1], func(item rtree.Item) bool { if idx >= cursor { iitm := ix.getRTreeItem(item) if iitm != nil { if !idm[iitm] { idm[iitm] = true active = iterator(iitm) } } else { active = iterator(iitm) } } idx++ return active }) } } } return idx }